Skip to content

Commit 46b3684

Browse files
VSB-TUO/Submission configuration (#1042)
* Added flyway script to fix database sequences * Created submission-processes and submission-maps * Validated submission-forms * Copied cfgs from v5 and templates * Created scripts for generating evyuka_forms and for fetching vocabularies. Also generated evyuka_forms. * Created python script for copying vocabularies and generated fresh vocabularies. * Generated forms using python script * Updated README - use python scripts * Added v7 forms * Removed not existing forms * Added default qualifier tag * temp current item-submission.xml * Just problem with importing forms * Updated fast dspace api package build * Created evyuka-types.xml for evyuka schema * Upload optional configuration fix * Load content of the form-definitions from the external form definition file * Do not create two types of the DCInputsReader, because the values (value pairs) configuration is not loaded correctly. * Updated external form definitions and they are imported to the submission-forms.xml - NOW IT IS WORKING. * Another improvement in the cfg * Manually copied AUD vp and added it into README * Added missing imports * The xml file cannot be empty * The CLARIN versioning is used instead of vanilla one * Fixed checkstyle issues * Fixed testing submissino-forms
1 parent 7b56d1d commit 46b3684

103 files changed

Lines changed: 77011 additions & 5145 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

convert_forms.py

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Script to convert v5 DSpace form definitions to v7 format
4+
"""
5+
import os
6+
import re
7+
import xml.etree.ElementTree as ET
8+
from pathlib import Path
9+
10+
def is_v5_form_file(file_path):
11+
"""Check if a file contains v5 form definitions (has <page number=> structure)"""
12+
try:
13+
with open(file_path, 'r', encoding='utf-8') as f:
14+
content = f.read()
15+
return '<page number=' in content and '<form name=' in content
16+
except:
17+
return False
18+
19+
def is_v7_form_file(file_path):
20+
"""Check if a file contains v7 form definitions (has <form-definitions> structure)"""
21+
try:
22+
with open(file_path, 'r', encoding='utf-8') as f:
23+
content = f.read()
24+
return '<form-definitions>' in content or ('<form name=' in content and '<row>' in content)
25+
except:
26+
return False
27+
28+
def extract_form_code_from_filename(filename):
29+
"""Extract form code from filename patterns like evyuka_form_HGF.xml"""
30+
# Match patterns like evyuka_form_XXX.xml, form_XXX.xml, etc.
31+
patterns = [
32+
r'evyuka_form_([^.]+)\.xml',
33+
r'form_([^.]+)\.xml',
34+
r'([^_]+)_form\.xml',
35+
r'([^.]+)\.xml'
36+
]
37+
38+
for pattern in patterns:
39+
match = re.search(pattern, filename)
40+
if match:
41+
return match.group(1)
42+
43+
# Fallback: use filename without extension
44+
return Path(filename).stem
45+
46+
def extract_form_name_from_content(content):
47+
"""Extract the original form name from v5 content"""
48+
form_match = re.search(r'<form name="([^"]+)">', content)
49+
if form_match:
50+
return form_match.group(1)
51+
return None
52+
53+
def convert_form_to_v7(input_file, output_file, form_code=None):
54+
"""Convert a v5 form file to v7 format"""
55+
56+
# Read the original v5 form
57+
with open(input_file, 'r', encoding='utf-8') as f:
58+
content = f.read()
59+
60+
# Extract form name from content
61+
original_form_name = extract_form_name_from_content(content)
62+
if not original_form_name:
63+
print(f"Could not find form name in {input_file}")
64+
return False
65+
66+
# If no form_code provided, try to extract from filename or use original form name
67+
if not form_code:
68+
form_code = extract_form_code_from_filename(input_file.name)
69+
70+
# Extract all fields from all pages
71+
page_pattern = r'<page number="(\d+)">(.*?)</page>'
72+
pages = re.findall(page_pattern, content, re.DOTALL)
73+
74+
if not pages:
75+
print(f"No pages found in {input_file}")
76+
return False
77+
78+
# Start building the v7 form
79+
v7_content = '''<?xml version="1.0"?>
80+
<!DOCTYPE form-definitions SYSTEM "submission-forms.dtd">
81+
82+
<form-definitions>
83+
'''
84+
85+
# Process each page and distribute fields across 3 forms
86+
all_fields = []
87+
for page_num, page_content in pages:
88+
field_pattern = r'<field>(.*?)</field>'
89+
fields = re.findall(field_pattern, page_content, re.DOTALL)
90+
all_fields.extend(fields)
91+
92+
if not all_fields:
93+
print(f"No fields found in {input_file}")
94+
return False
95+
96+
# Distribute fields across 3 pages (or the number of original pages if less than 3)
97+
num_target_pages = max(3, len(pages))
98+
fields_per_page = len(all_fields) // num_target_pages
99+
remainder = len(all_fields) % num_target_pages
100+
101+
page_field_counts = [fields_per_page] * num_target_pages
102+
for i in range(remainder):
103+
page_field_counts[i] += 1
104+
105+
# Generate form names based on the original form name
106+
page_names = []
107+
if 'e-vyuka' in original_form_name:
108+
# Handle evyuka forms specially
109+
base_name = original_form_name.replace('e-vyuka-', '').replace('e-vyuka', form_code)
110+
page_names = [f"e-vyuka-{form_code}page{word}" for word in ['one', 'two', 'three']]
111+
else:
112+
# Generic form naming
113+
page_names = [f"{original_form_name}page{word}" for word in ['one', 'two', 'three']]
114+
115+
# Ensure we have enough page names
116+
while len(page_names) < num_target_pages:
117+
page_names.append(f"{original_form_name}page{len(page_names)+1}")
118+
119+
field_index = 0
120+
for page_num in range(num_target_pages):
121+
if page_num < len(page_names):
122+
page_name = page_names[page_num]
123+
else:
124+
page_name = f"{original_form_name}page{page_num+1}"
125+
126+
v7_content += f' <form name="{page_name}">\n'
127+
128+
# Add fields for this page
129+
fields_added = 0
130+
target_fields = page_field_counts[page_num] if page_num < len(page_field_counts) else 0
131+
132+
while fields_added < target_fields and field_index < len(all_fields):
133+
field_content = all_fields[field_index].strip()
134+
# Convert field to row format
135+
v7_content += ' <row>\n'
136+
v7_content += ' <field>\n'
137+
138+
# Clean up the field content and add proper indentation
139+
field_lines = field_content.split('\n')
140+
for line in field_lines:
141+
cleaned_line = line.strip()
142+
if cleaned_line:
143+
v7_content += ' ' + cleaned_line + '\n'
144+
145+
v7_content += ' </field>\n'
146+
v7_content += ' </row>\n\n'
147+
field_index += 1
148+
fields_added += 1
149+
150+
v7_content += ' </form>\n\n'
151+
152+
v7_content += '</form-definitions>'
153+
154+
# Write the converted form
155+
with open(output_file, 'w', encoding='utf-8') as f:
156+
f.write(v7_content)
157+
158+
print(f"Converted {input_file} to v7 format -> {output_file}")
159+
return True
160+
161+
def find_all_form_files(directory):
162+
"""Find all XML files that contain DSpace form definitions"""
163+
form_files = []
164+
directory = Path(directory)
165+
166+
for xml_file in directory.glob('*.xml'):
167+
if is_v5_form_file(xml_file):
168+
form_files.append(xml_file)
169+
elif is_v7_form_file(xml_file):
170+
print(f"Skipping {xml_file.name} - already in v7 format")
171+
172+
return form_files
173+
174+
def main():
175+
"""Main function to convert all form files"""
176+
vsb_dir = Path('C:/dspace-be/dspace/config/vsb')
177+
178+
if not vsb_dir.exists():
179+
print(f"Directory {vsb_dir} does not exist")
180+
return
181+
182+
# Find all v5 form definition files
183+
v5_form_files = find_all_form_files(vsb_dir)
184+
185+
if not v5_form_files:
186+
print("No v5 form definition files found for conversion")
187+
return
188+
189+
print(f"Found {len(v5_form_files)} v5 form files to convert:")
190+
for file in v5_form_files:
191+
print(f" - {file.name}")
192+
193+
# Convert each file
194+
converted_count = 0
195+
for input_file in v5_form_files:
196+
form_code = extract_form_code_from_filename(input_file.name)
197+
if convert_form_to_v7(input_file, input_file, form_code):
198+
converted_count += 1
199+
200+
print(f"\nConversion complete: {converted_count}/{len(v5_form_files)} files converted successfully")
201+
202+
if __name__ == "__main__":
203+
main()

dspace-api/src/main/java/org/dspace/app/util/DCInputsReader.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,11 @@ private void processDefinition(Node e)
351351
if (rows.size() < 1) {
352352
throw new DCInputsReaderException("Form " + formName + " has no rows");
353353
}
354+
} else if (nd.getNodeName().equals("form-definitions")) {
355+
// Handle nested form-definitions elements (from XML entity expansion)
356+
// Recursively process the nested form-definitions
357+
processDefinition(nd);
358+
numForms++; // Count this as having found forms to avoid the "No form definition found" error
354359
}
355360
}
356361
if (numForms == 0) {

dspace-api/src/main/java/org/dspace/content/authority/DCInputAuthority.java

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -96,20 +96,33 @@ private static synchronized void initPluginNames() {
9696
if (pluginNames == null) {
9797
try {
9898
dcis = new HashMap<Locale, DCInputsReader>();
99+
100+
// Add default locale to locales if not already present
101+
Locale defaultLocale = I18nUtil.getDefaultLocale();
102+
Set<Locale> localeSet = new HashSet<>(Arrays.asList(locales));
103+
if (!localeSet.contains(defaultLocale)) {
104+
localeSet.add(defaultLocale);
105+
locales = localeSet.toArray(new Locale[0]);
106+
}
107+
99108
for (Locale locale : locales) {
100-
dcis.put(locale, new DCInputsReader(I18nUtil.getInputFormsFileName(locale)));
109+
String inputFormsFileName = I18nUtil.getInputFormsFileName(locale);
110+
if (inputFormsFileName != null) {
111+
dcis.put(locale, new DCInputsReader(inputFormsFileName));
112+
} else {
113+
// Fallback to default submission-forms.xml for this locale
114+
dcis.put(locale, new DCInputsReader());
115+
}
101116
}
102-
for (Locale l : locales) {
103-
Iterator pi = dcis.get(l).getPairsNameIterator();
117+
118+
// Collect all unique pair names from all locales
119+
for (Locale l : dcis.keySet()) {
120+
DCInputsReader dci = dcis.get(l);
121+
Iterator pi = dci.getPairsNameIterator();
104122
while (pi.hasNext()) {
105123
names.add((String) pi.next());
106124
}
107125
}
108-
DCInputsReader dcirDefault = new DCInputsReader();
109-
Iterator pi = dcirDefault.getPairsNameIterator();
110-
while (pi.hasNext()) {
111-
names.add((String) pi.next());
112-
}
113126
} catch (DCInputsReaderException e) {
114127
log.error("Failed reading DCInputs initialization: ", e);
115128
}
@@ -124,10 +137,13 @@ private void init() {
124137
values = new HashMap<String, String[]>();
125138
labels = new HashMap<String, String[]>();
126139
String pname = this.getPluginInstanceName();
140+
boolean foundAnyPairs = false;
141+
127142
for (Locale l : dcis.keySet()) {
128143
DCInputsReader dci = dcis.get(l);
129144
List<String> pairs = dci.getPairs(pname);
130145
if (pairs != null) {
146+
foundAnyPairs = true;
131147
String[] valuesLocale = new String[pairs.size() / 2];
132148
String[]labelsLocale = new String[pairs.size() / 2];
133149
for (int i = 0; i < pairs.size(); i += 2) {
@@ -137,11 +153,15 @@ private void init() {
137153
values.put(l.getLanguage(), valuesLocale);
138154
labels.put(l.getLanguage(), labelsLocale);
139155
log.debug("Found pairs for name=" + pname + ",locale=" + l);
140-
} else {
141-
log.error("Failed to find any pairs for name=" + pname, new IllegalStateException());
142156
}
143157
}
144158

159+
if (!foundAnyPairs) {
160+
log.error("Failed to find any pairs for name=" + pname + " in any locale", new IllegalStateException());
161+
// Initialize empty arrays to prevent NPE
162+
values.put(I18nUtil.getDefaultLocale().getLanguage(), new String[0]);
163+
labels.put(I18nUtil.getDefaultLocale().getLanguage(), new String[0]);
164+
}
145165
}
146166
}
147167

dspace-api/src/test/data/dspaceFolder/config/submission-forms_it.xml

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@
7777
<!-- <language value-pairs-name="common_iso_languages">true</language> -->
7878
</field>
7979
</row>
80-
80+
8181

8282
<row>
8383
<field>
@@ -164,6 +164,94 @@
164164
<stored-value>other</stored-value>
165165
</pair>
166166
</value-pairs>
167+
168+
<value-pairs value-pairs-name="common_types" dc-term="type">
169+
<pair>
170+
<displayed-value>Animazione</displayed-value>
171+
<stored-value>Animation</stored-value>
172+
</pair>
173+
<pair>
174+
<displayed-value>Articolo</displayed-value>
175+
<stored-value>Article</stored-value>
176+
</pair>
177+
<pair>
178+
<displayed-value>Libro</displayed-value>
179+
<stored-value>Book</stored-value>
180+
</pair>
181+
<pair>
182+
<displayed-value>Capitolo di libro</displayed-value>
183+
<stored-value>Book chapter</stored-value>
184+
</pair>
185+
<pair>
186+
<displayed-value>Dataset</displayed-value>
187+
<stored-value>Dataset</stored-value>
188+
</pair>
189+
<pair>
190+
<displayed-value>Oggetto di apprendimento</displayed-value>
191+
<stored-value>Learning Object</stored-value>
192+
</pair>
193+
<pair>
194+
<displayed-value>Immagine</displayed-value>
195+
<stored-value>Image</stored-value>
196+
</pair>
197+
<pair>
198+
<displayed-value>Immagine 3D</displayed-value>
199+
<stored-value>Image, 3-D</stored-value>
200+
</pair>
201+
<pair>
202+
<displayed-value>Mappa</displayed-value>
203+
<stored-value>Map</stored-value>
204+
</pair>
205+
<pair>
206+
<displayed-value>Partitura musicale</displayed-value>
207+
<stored-value>Musical Score</stored-value>
208+
</pair>
209+
<pair>
210+
<displayed-value>Piano o progetto</displayed-value>
211+
<stored-value>Plan or blueprint</stored-value>
212+
</pair>
213+
<pair>
214+
<displayed-value>Preprint</displayed-value>
215+
<stored-value>Preprint</stored-value>
216+
</pair>
217+
<pair>
218+
<displayed-value>Presentazione</displayed-value>
219+
<stored-value>Presentation</stored-value>
220+
</pair>
221+
<pair>
222+
<displayed-value>Registrazione acustica</displayed-value>
223+
<stored-value>Recording, acoustical</stored-value>
224+
</pair>
225+
<pair>
226+
<displayed-value>Registrazione musicale</displayed-value>
227+
<stored-value>Recording, musical</stored-value>
228+
</pair>
229+
<pair>
230+
<displayed-value>Registrazione orale</displayed-value>
231+
<stored-value>Recording, oral</stored-value>
232+
</pair>
233+
<pair>
234+
<displayed-value>Software</displayed-value>
235+
<stored-value>Software</stored-value>
236+
</pair>
237+
<pair>
238+
<displayed-value>Tesi</displayed-value>
239+
<stored-value>Thesis</stored-value>
240+
</pair>
241+
<pair>
242+
<displayed-value>Rapporto tecnico</displayed-value>
243+
<stored-value>Technical Report</stored-value>
244+
</pair>
245+
<pair>
246+
<displayed-value>Altro</displayed-value>
247+
<stored-value>Other</stored-value>
248+
</pair>
249+
</value-pairs>
250+
167251
</form-value-pairs>
168252

253+
<form-complex-definitions>
254+
<definition name="test"></definition>
255+
</form-complex-definitions>
256+
169257
</input-forms>

0 commit comments

Comments
 (0)