blob: 43bc67f01b03d342fbf694d5d3b64739f8737231 [file] [log] [blame]
Akronf3efc9e2026-06-03 10:46:37 +02001use strict;
2use warnings;
3use Test::More;
4use Test::Script;
5use Test::TempDir::Tiny;
6
7my $UNZIP = `sh -c 'command -v unzip'`;
8chomp $UNZIP;
9
10if ($UNZIP eq '') {
11 plan skip_all => 'No unzip executable found in PATH.';
12}
13
14my $offset_data = <<'CONLLU';
15# filename = TEST/TEST/TEST_OFF_001/base/tokens.xml
16# text_id = TEST_TEST.TEST_OFF_001
17# text = Geras rytas.
181 Geras geras ADJ bdv. Case=Nom 2 amod _ _
192 rytas rytas NOUN dkt. Case=Nom 0 root _ _
203 . . PUNCT skyr. _ 2 punct _ SpaceAfter=No
21
22# text = Kaip sekasi?
231 Kaip kaip ADV prv. _ 2 advmod _ _
242 sekasi sektis VERB vksm. _ 0 root _ SpaceAfter=No
253 ? ? PUNCT skyr. _ 2 punct _ _
26
27CONLLU
28
Akrondb3d0cb2026-06-03 15:53:57 +020029my $misc_data = <<'CONLLU';
30# filename = TEST/TEST/TEST_MISC_001/base/tokens.xml
31# text_id = TEST_TEST.TEST_MISC_001
32# start_offsets = 0 0 6 11
33# end_offsets = 12 5 11 12
341 Geras geras ADJ bdv. Case=Nom 2 amod _ SpaceAfter=No|Tag=NNN
352 rytas rytas NOUN dkt. Case=Nom 0 root _ Tag=LLL
363 . . PUNCT skyr. _ 2 punct _ SpaceAfter=No
37
38# start_offsets = 12 13 18 24
39# end_offsets = 25 17 24 25
401 Kaip kaip ADV prv. _ 2 advmod _ SpacesAfter=\n|Tag=MMM
412 sekasi sektis VERB vksm. _ 0 root _ _
423 ? ? PUNCT skyr. _ 2 punct _ _
43
44CONLLU
45
Akronf3efc9e2026-06-03 10:46:37 +020046my $test_tempdir = tempdir();
47my $offset_file = "$test_tempdir/test_offset.conllu";
48{
49 open(my $ofh, '>:encoding(UTF-8)', $offset_file)
50 or die "Cannot write test file: $!";
51 print $ofh $offset_data;
52 close($ofh);
53}
54
55my $zipcontent_off = '';
56script_runs(
57 [ 'script/conllu2korapxml', '-f', 'ud', $offset_file ],
58 { stdout => \$zipcontent_off },
59 "conllu2korapxml computes offsets from # text"
60);
61
62my $zipfile_off = "$test_tempdir/test_offset.zip";
63if ($zipcontent_off) {
64 open(my $zfh, '>:raw', $zipfile_off) or die "Cannot write zip: $!";
65 print $zfh $zipcontent_off;
66 close($zfh);
67
68 my $morpho_xml = `$UNZIP -p $zipfile_off 'TEST/TEST/TEST_OFF_001/ud/morpho.xml' 2>/dev/null`;
69
70 # Sentence 1: "Geras rytas." (12 chars, starts at 0)
71 # Geras: 0-5, rytas: 6-11, .: 11-12
72 like($morpho_xml,
73 qr/id="s1_n1" from="0" to="5"/,
74 "Computed offset: token 'Geras' at 0..5");
75 like($morpho_xml,
76 qr/id="s1_n2" from="6" to="11"/,
77 "Computed offset: token 'rytas' at 6..11");
78 like($morpho_xml,
79 qr/id="s1_n3" from="11" to="12"/,
80 "Computed offset: token '.' at 11..12 (SpaceAfter=No)");
81
82 # Sentence 2: "Kaip sekasi?" (12 chars, starts at 13 = 12 + 1 space)
83 # Kaip: 13-17, sekasi: 18-24, ?: 24-25
84 like($morpho_xml,
85 qr/id="s2_n1" from="13" to="17"/,
86 "Computed offset: token 'Kaip' at 13..17 (sentence 2)");
87 like($morpho_xml,
88 qr/id="s2_n2" from="18" to="24"/,
89 "Computed offset: token 'sekasi' at 18..24");
90 like($morpho_xml,
91 qr/id="s2_n3" from="24" to="25"/,
92 "Computed offset: token '?' at 24..25 (SpaceAfter=No)");
93
94 # Verify dependency XML has correct offsets (head references)
95 my $dep_xml = `$UNZIP -p $zipfile_off 'TEST/TEST/TEST_OFF_001/ud/dependency.xml' 2>/dev/null`;
96
97 # Token 1 "Geras" (0..5) -> head token 2 "rytas" (6..11)
98 like($dep_xml,
99 qr/id="s1_n1" from="0" to="5".*?<span from="6" to="11"/s,
100 "Dependency: 'Geras' head points to 'rytas' offsets");
101
102 # Token 2 "rytas" (6..11) -> head 0 = sentence span (0..12)
103 like($dep_xml,
104 qr/id="s1_n2" from="6" to="11".*?<span from="0" to="12"/s,
105 "Dependency: 'rytas' head points to sentence span");
106}
107else {
108 fail("Computed offset: token 'Geras' at 0..5");
109 fail("Computed offset: token 'rytas' at 6..11");
110 fail("Computed offset: token '.' at 11..12 (SpaceAfter=No)");
111 fail("Computed offset: token 'Kaip' at 13..17 (sentence 2)");
112 fail("Computed offset: token 'sekasi' at 18..24");
113 fail("Computed offset: token '?' at 24..25 (SpaceAfter=No)");
114 fail("Dependency: 'Geras' head points to 'rytas' offsets");
115 fail("Dependency: 'rytas' head points to sentence span");
116}
117
118# No SpaceAfter at all - every token has normal space separation
119my $no_spaceafter_data = <<'CONLLU';
120# filename = TEST/TEST/TEST_NSA_001/base/tokens.xml
121# text_id = TEST_TEST.TEST_NSA_001
122# text = One two three
1231 One one NUM num. _ 0 root _ _
1242 two two NUM num. _ 1 flat _ _
1253 three three NUM num. _ 1 flat _ _
126
127CONLLU
128
129my $nsa_file = "$test_tempdir/test_no_spaceafter.conllu";
130{
131 open(my $nfh, '>:encoding(UTF-8)', $nsa_file)
132 or die "Cannot write test file: $!";
133 print $nfh $no_spaceafter_data;
134 close($nfh);
135}
136
137my $zipcontent_nsa = '';
138script_runs(
139 [ 'script/conllu2korapxml', '-f', 'ud', $nsa_file ],
140 { stdout => \$zipcontent_nsa },
141 "conllu2korapxml handles tokens with no SpaceAfter"
142);
143
144my $zipfile_nsa = "$test_tempdir/test_no_spaceafter.zip";
145if ($zipcontent_nsa) {
146 open(my $zfh, '>:raw', $zipfile_nsa) or die "Cannot write zip: $!";
147 print $zfh $zipcontent_nsa;
148 close($zfh);
149
150 my $morpho_nsa = `$UNZIP -p $zipfile_nsa 'TEST/TEST/TEST_NSA_001/ud/morpho.xml' 2>/dev/null`;
151
152 # "One two three" = 13 chars
153 # One: 0-3, two: 4-7, three: 8-13
154 like($morpho_nsa,
155 qr/id="s1_n1" from="0" to="3"/,
156 "No SpaceAfter: token 'One' at 0..3");
157 like($morpho_nsa,
158 qr/id="s1_n2" from="4" to="7"/,
159 "No SpaceAfter: token 'two' at 4..7");
160 like($morpho_nsa,
161 qr/id="s1_n3" from="8" to="13"/,
162 "No SpaceAfter: token 'three' at 8..13");
163}
164else {
165 fail("No SpaceAfter: token 'One' at 0..3");
166 fail("No SpaceAfter: token 'two' at 4..7");
167 fail("No SpaceAfter: token 'three' at 8..13");
168}
169
170# Adjacent tokens - no space between consecutive tokens (SpaceAfter=No)
171my $adjacent_data = <<'CONLLU';
172# filename = TEST/TEST/TEST_ADJ_001/base/tokens.xml
173# text_id = TEST_TEST.TEST_ADJ_001
174# text = foo(bar)baz end
1751 foo foo NOUN n. _ 0 root _ SpaceAfter=No
1762 ( ( PUNCT skyr. _ 3 punct _ SpaceAfter=No
1773 bar bar NOUN n. _ 1 appos _ SpaceAfter=No
1784 ) ) PUNCT skyr. _ 3 punct _ SpaceAfter=No
1795 baz baz NOUN n. _ 1 conj _ _
1806 end end NOUN n. _ 1 conj _ _
181
182CONLLU
183
184my $adj_file = "$test_tempdir/test_adjacent.conllu";
185{
186 open(my $afh, '>:encoding(UTF-8)', $adj_file)
187 or die "Cannot write test file: $!";
188 print $afh $adjacent_data;
189 close($afh);
190}
191
192my $zipcontent_adj = '';
193script_runs(
194 [ 'script/conllu2korapxml', '-f', 'ud', $adj_file ],
195 { stdout => \$zipcontent_adj },
196 "conllu2korapxml handles adjacent tokens without spaces"
197);
198
199my $zipfile_adj = "$test_tempdir/test_adjacent.zip";
200if ($zipcontent_adj) {
201 open(my $zfh, '>:raw', $zipfile_adj) or die "Cannot write zip: $!";
202 print $zfh $zipcontent_adj;
203 close($zfh);
204
205 my $morpho_adj = `$UNZIP -p $zipfile_adj 'TEST/TEST/TEST_ADJ_001/ud/morpho.xml' 2>/dev/null`;
206
207 # "foo(bar)baz end" = 15 chars
208 # foo: 0-3, (: 3-4, bar: 4-7, ): 7-8, baz: 8-11, end: 12-15
209 like($morpho_adj,
210 qr/id="s1_n1" from="0" to="3"/,
211 "Adjacent: token 'foo' at 0..3");
212 like($morpho_adj,
213 qr/id="s1_n2" from="3" to="4"/,
214 "Adjacent: token '(' at 3..4 (no space before)");
215 like($morpho_adj,
216 qr/id="s1_n3" from="4" to="7"/,
217 "Adjacent: token 'bar' at 4..7 (no space before)");
218 like($morpho_adj,
219 qr/id="s1_n4" from="7" to="8"/,
220 "Adjacent: token ')' at 7..8 (no space before)");
221 like($morpho_adj,
222 qr/id="s1_n5" from="8" to="11"/,
223 "Adjacent: token 'baz' at 8..11 (no space before)");
224 like($morpho_adj,
225 qr/id="s1_n6" from="12" to="15"/,
226 "Adjacent: token 'end' at 12..15 (space before)");
227}
228else {
229 fail("Adjacent: token 'foo' at 0..3");
230 fail("Adjacent: token '(' at 3..4 (no space before)");
231 fail("Adjacent: token 'bar' at 4..7 (no space before)");
232 fail("Adjacent: token ')' at 7..8 (no space before)");
233 fail("Adjacent: token 'baz' at 8..11 (no space before)");
234 fail("Adjacent: token 'end' at 12..15 (space before)");
235}
236
237# Repeated token form - same word appears multiple times in sentence
238my $repeat_data = <<'CONLLU';
239# filename = TEST/TEST/TEST_REP_001/base/tokens.xml
240# text_id = TEST_TEST.TEST_REP_001
241# text = the cat and the dog
2421 the the DET det. _ 2 det _ _
2432 cat cat NOUN n. _ 0 root _ _
2443 and and CCONJ cc. _ 5 cc _ _
2454 the the DET det. _ 5 det _ _
2465 dog dog NOUN n. _ 2 conj _ _
247
248CONLLU
249
250my $rep_file = "$test_tempdir/test_repeat.conllu";
251{
252 open(my $rfh, '>:encoding(UTF-8)', $rep_file)
253 or die "Cannot write test file: $!";
254 print $rfh $repeat_data;
255 close($rfh);
256}
257
258my $zipcontent_rep = '';
259script_runs(
260 [ 'script/conllu2korapxml', '-f', 'ud', $rep_file ],
261 { stdout => \$zipcontent_rep },
262 "conllu2korapxml handles repeated token forms"
263);
264
265my $zipfile_rep = "$test_tempdir/test_repeat.zip";
266if ($zipcontent_rep) {
267 open(my $zfh, '>:raw', $zipfile_rep) or die "Cannot write zip: $!";
268 print $zfh $zipcontent_rep;
269 close($zfh);
270
271 my $morpho_rep = `$UNZIP -p $zipfile_rep 'TEST/TEST/TEST_REP_001/ud/morpho.xml' 2>/dev/null`;
272
273 # "the cat and the dog" = 19 chars
274 # the: 0-3, cat: 4-7, and: 8-11, the: 12-15, dog: 16-19
275 like($morpho_rep,
276 qr/id="s1_n1" from="0" to="3"/,
277 "Repeated: first 'the' at 0..3");
278 like($morpho_rep,
279 qr/id="s1_n2" from="4" to="7"/,
280 "Repeated: 'cat' at 4..7");
281 like($morpho_rep,
282 qr/id="s1_n4" from="12" to="15"/,
283 "Repeated: second 'the' at 12..15 (not matching first)");
284 like($morpho_rep,
285 qr/id="s1_n5" from="16" to="19"/,
286 "Repeated: 'dog' at 16..19");
287}
288else {
289 fail("Repeated: first 'the' at 0..3");
290 fail("Repeated: 'cat' at 4..7");
291 fail("Repeated: second 'the' at 12..15 (not matching first)");
292 fail("Repeated: 'dog' at 16..19");
293}
294
295# Single-token sentence - minimal sentence with only one word
296my $single_data = <<'CONLLU';
297# filename = TEST/TEST/TEST_SNG_001/base/tokens.xml
298# text_id = TEST_TEST.TEST_SNG_001
299# text = Hello
3001 Hello hello INTJ intj. _ 0 root _ _
301
302# text = World
3031 World world NOUN n. _ 0 root _ _
304
305CONLLU
306
307my $sng_file = "$test_tempdir/test_single.conllu";
308{
309 open(my $sfh, '>:encoding(UTF-8)', $sng_file)
310 or die "Cannot write test file: $!";
311 print $sfh $single_data;
312 close($sfh);
313}
314
315my $zipcontent_sng = '';
316script_runs(
317 [ 'script/conllu2korapxml', '-f', 'ud', $sng_file ],
318 { stdout => \$zipcontent_sng },
319 "conllu2korapxml handles single-token sentences"
320);
321
322my $zipfile_sng = "$test_tempdir/test_single.zip";
323if ($zipcontent_sng) {
324 open(my $zfh, '>:raw', $zipfile_sng) or die "Cannot write zip: $!";
325 print $zfh $zipcontent_sng;
326 close($zfh);
327
328 my $morpho_sng = `$UNZIP -p $zipfile_sng 'TEST/TEST/TEST_SNG_001/ud/morpho.xml' 2>/dev/null`;
329
330 # Sentence 1: "Hello" (5 chars, starts at 0)
331 # Sentence 2: "World" (5 chars, starts at 6 = 5 + 1 space)
332 like($morpho_sng,
333 qr/id="s1_n1" from="0" to="5"/,
334 "Single-token: 'Hello' at 0..5");
335 like($morpho_sng,
336 qr/id="s2_n1" from="6" to="11"/,
337 "Single-token: 'World' at 6..11 (after space separator)");
338}
339else {
340 fail("Single-token: 'Hello' at 0..5");
341 fail("Single-token: 'World' at 6..11 (after space separator)");
342}
343
344# Explicit offsets take precedence over # text when both are present
345my $explicit_wins_data = <<'CONLLU';
346# filename = TEST/TEST/TEST_EXP_001/base/tokens.xml
347# text_id = TEST_TEST.TEST_EXP_001
348# text = Geras rytas.
349# start_offsets = 0 100 200 300
350# end_offsets = 999 199 299 399
3511 Geras geras ADJ bdv. Case=Nom 2 amod _ _
3522 rytas rytas NOUN dkt. Case=Nom 0 root _ _
3533 . . PUNCT skyr. _ 2 punct _ _
354
355CONLLU
356
357my $exp_file = "$test_tempdir/test_explicit.conllu";
358{
359 open(my $efh, '>:encoding(UTF-8)', $exp_file)
360 or die "Cannot write test file: $!";
361 print $efh $explicit_wins_data;
362 close($efh);
363}
364
365my $zipcontent_exp = '';
366script_runs(
367 [ 'script/conllu2korapxml', '-f', 'ud', $exp_file ],
368 { stdout => \$zipcontent_exp },
369 "conllu2korapxml uses explicit offsets when both # text and offsets present"
370);
371
372my $zipfile_exp = "$test_tempdir/test_explicit.zip";
373if ($zipcontent_exp) {
374 open(my $zfh, '>:raw', $zipfile_exp) or die "Cannot write zip: $!";
375 print $zfh $zipcontent_exp;
376 close($zfh);
377
378 my $morpho_exp = `$UNZIP -p $zipfile_exp 'TEST/TEST/TEST_EXP_001/ud/morpho.xml' 2>/dev/null`;
379 like($morpho_exp,
380 qr/id="s1_n1" from="100" to="199"/,
381 "Explicit offsets win: token uses from=100 (not computed 0)");
382}
383else {
384 fail("Explicit offsets win: token uses from=100 (not computed 0)");
385}
386
Akron8dabdc12026-06-02 16:36:07 +0200387# -------------------------------------------------------------------
388# Inline test data: minimal UD CoNLL-U with explicit offsets.
389# Uses "# newdoc id" (UD style) instead of "# filename" / "# text_id"
390# (KorAP style). Explicit offsets are provided so this test does not
391# depend on automatic offset computation.
392# -------------------------------------------------------------------
393
394my $ud_conllu_data = <<'CONLLU';
395# newdoc id = TEST_LIT_001
396# start_offsets = 0 0 6 11
397# end_offsets = 12 5 11 12
3981 Geras geras ADJ bdv. Case=Nom 2 amod _ _
3992 rytas rytas NOUN dkt. Case=Nom 0 root _ _
4003 . . PUNCT skyr. _ 2 punct _ _
401
402CONLLU
403
404my $conllu_file = "$test_tempdir/test_ud.conllu";
405{
406 open(my $fh, '>:encoding(UTF-8)', $conllu_file)
407 or die "Cannot write test file: $!";
408 print $fh $ud_conllu_data;
409 close($fh);
410}
411
412my $zipcontent = '';
413script_runs(
414 [ 'script/conllu2korapxml', '-f', 'ud',
415 '--text-sigle', 'TEST/TEST/{ID}', $conllu_file ],
416 { stdout => \$zipcontent },
417 "conllu2korapxml accepts --text-sigle with UD CoNLL-U input"
418);
419
420my $zipfile = "$test_tempdir/test_newdoc.zip";
421if ($zipcontent) {
422 open(my $zfh, '>:raw', $zipfile) or die "Cannot write zip: $!";
423 print $zfh $zipcontent;
424 close($zfh);
425
426 my $ziplist = `$UNZIP -l $zipfile 2>/dev/null`;
427 like($ziplist,
428 qr@TEST/TEST/TEST_LIT_001/ud/morpho\.xml@,
429 "Zip contains morpho.xml at path derived from newdoc id");
430 like($ziplist,
431 qr@TEST/TEST/TEST_LIT_001/ud/dependency\.xml@,
432 "Zip contains dependency.xml at path derived from newdoc id");
433
434 my $zipdata = `$UNZIP -c $zipfile 2>/dev/null`;
435 like($zipdata,
436 qr/docid="TEST_TEST\.TEST_LIT_001"/,
437 "docid correctly derived from text-sigle template and newdoc id");
438}
439else {
440 fail("Zip contains morpho.xml at path derived from newdoc id");
441 fail("Zip contains dependency.xml at path derived from newdoc id");
442 fail("docid correctly derived from text-sigle template and newdoc id");
443}
444
445my $zipcontent_lc = '';
446script_runs(
447 [ 'script/conllu2korapxml', '-f', 'ud',
448 '--text-sigle', 'TEST/TEST/{id}', $conllu_file ],
449 { stdout => \$zipcontent_lc },
450 "text-sigle template accepts {id} (lowercase)"
451);
452
453if ($zipcontent_lc) {
454 my $zipfile_lc = "$test_tempdir/test_lc.zip";
455 open(my $zfh, '>:raw', $zipfile_lc) or die "Cannot write zip: $!";
456 print $zfh $zipcontent_lc;
457 close($zfh);
458
459 my $zipdata_lc = `$UNZIP -c $zipfile_lc 2>/dev/null`;
460 like($zipdata_lc,
461 qr/docid="TEST_TEST\.TEST_LIT_001"/,
462 "docid correct with lowercase {id} template");
463}
464else {
465 fail("docid correct with lowercase {id} template");
466}
467
468my $zipcontent_mc = '';
469script_runs(
470 [ 'script/conllu2korapxml', '-f', 'ud',
471 '--text-sigle', 'TEST/TEST/{Id}', $conllu_file ],
472 { stdout => \$zipcontent_mc },
473 "text-sigle template accepts {Id} (mixed case)"
474);
475
476if ($zipcontent_mc) {
477 my $zipfile_mc = "$test_tempdir/test_mc.zip";
478 open(my $zfh, '>:raw', $zipfile_mc) or die "Cannot write zip: $!";
479 print $zfh $zipcontent_mc;
480 close($zfh);
481
482 my $zipdata_mc = `$UNZIP -c $zipfile_mc 2>/dev/null`;
483 like($zipdata_mc,
484 qr/docid="TEST_TEST\.TEST_LIT_001"/,
485 "docid correct with mixed-case {Id} template");
486}
487else {
488 fail("docid correct with mixed-case {Id} template");
489}
490
491# Template with only 2 parts (missing corpus level)
492my $err_2parts = `$^X script/conllu2korapxml -f ud --text-sigle 'TEST/{ID}' $conllu_file 2>&1`;
493isnt($? >> 8, 0, "Rejects template with only 2 parts (non-zero exit)");
494like($err_2parts, qr/ERROR/, "Error message for 2-part template");
495
496# Template with 4 parts (too many levels)
497my $err_4parts = `$^X script/conllu2korapxml -f ud --text-sigle 'A/B/C/{ID}' $conllu_file 2>&1`;
498isnt($? >> 8, 0, "Rejects template with 4 parts (non-zero exit)");
499like($err_4parts, qr/ERROR/, "Error message for 4-part template");
500
501# Template with empty middle part
502my $err_empty = `$^X script/conllu2korapxml -f ud --text-sigle 'TEST//{ID}' $conllu_file 2>&1`;
503isnt($? >> 8, 0, "Rejects template with empty part (non-zero exit)");
504like($err_empty, qr/ERROR/, "Error message for empty-part template");
505
506# Template with only 1 part (no slashes)
507my $err_1part = `$^X script/conllu2korapxml -f ud --text-sigle '{ID}' $conllu_file 2>&1`;
508isnt($? >> 8, 0, "Rejects template with only 1 part (non-zero exit)");
509like($err_1part, qr/ERROR/, "Error message for 1-part template");
510
511my $bad_id_data = <<'CONLLU';
512# newdoc id = BAD/SLASH_ID
513# start_offsets = 0 0
514# end_offsets = 4 4
5151 Test test NOUN NN _ 0 root _ _
516
517CONLLU
518
519my $bad_id_file = "$test_tempdir/bad_id.conllu";
520{
521 open(my $bfh, '>:encoding(UTF-8)', $bad_id_file)
522 or die "Cannot write test file: $!";
523 print $bfh $bad_id_data;
524 close($bfh);
525}
526
527my $err_slash = `$^X script/conllu2korapxml -f ud --text-sigle 'A/B/{ID}' $bad_id_file 2>&1`;
528isnt($? >> 8, 0,
529 "Rejects newdoc id with slash (expanded sigle has wrong part count)");
530like($err_slash, qr/ERROR/,
531 "Error message for newdoc id containing slash");
Akron982dd1e2026-06-03 11:52:48 +0200532
533my $base_text_data = <<'CONLLU';
534# filename = TEST/TEST/TEST_BTX_001/base/tokens.xml
535# text_id = TEST_TEST.TEST_BTX_001
536# text = Geras rytas.
537# start_offsets = 0 0 6 11
538# end_offsets = 12 5 11 12
5391 Geras geras ADJ bdv. Case=Nom 2 amod _ _
5402 rytas rytas NOUN dkt. Case=Nom 0 root _ _
5413 . . PUNCT skyr. _ 2 punct _ SpaceAfter=No
542
543# text = Kaip sekasi?
544# start_offsets = 12 13 18 24
545# end_offsets = 25 17 24 25
5461 Kaip kaip ADV prv. _ 2 advmod _ _
5472 sekasi sektis VERB vksm. _ 0 root _ SpaceAfter=No
5483 ? ? PUNCT skyr. _ 2 punct _ _
549
550CONLLU
551
552my $btx_file = "$test_tempdir/test_base_text.conllu";
553{
554 open(my $bfh, '>:encoding(UTF-8)', $btx_file)
555 or die "Cannot write test file: $!";
556 print $bfh $base_text_data;
557 close($bfh);
558}
559
560# Run with --base-text to generate data.xml
561my $zipcontent_btx = '';
562script_runs(
563 [ 'script/conllu2korapxml', '-f', 'ud', '--base-text', $btx_file ],
564 { stdout => \$zipcontent_btx },
565 "conllu2korapxml accepts --base-text option"
566);
567
568my $zipfile_btx = "$test_tempdir/test_base_text.zip";
569if ($zipcontent_btx) {
570 open(my $zfh, '>:raw', $zipfile_btx) or die "Cannot write zip: $!";
571 print $zfh $zipcontent_btx;
572 close($zfh);
573
574 my $ziplist = `$UNZIP -l $zipfile_btx 2>/dev/null`;
575 like($ziplist,
576 qr@TEST/TEST/TEST_BTX_001/data\.xml@,
577 "Zip contains data.xml at correct path");
578
579 my $data_xml = `$UNZIP -p $zipfile_btx 'TEST/TEST/TEST_BTX_001/data.xml' 2>/dev/null`;
580
581 like($data_xml,
582 qr/docid="TEST_TEST\.TEST_BTX_001"/,
583 "data.xml has correct docid attribute");
584
585 like($data_xml,
586 qr/<raw_text\b/,
587 "data.xml contains <raw_text> element");
588
589 like($data_xml,
590 qr{<text>Geras rytas\. Kaip sekasi\?</text>},
591 "data.xml text is sentences joined by single space");
592
593 like($data_xml,
594 qr/xmlns="http:\/\/ids-mannheim\.de\/ns\/KorAP"/,
595 "data.xml has correct namespace");
596
597 like($data_xml,
598 qr/<\?xml-model href="text\.rng"/,
599 "data.xml has correct processing instruction");
600}
601else {
602 fail("Zip contains data.xml at correct path");
603 fail("data.xml has correct docid attribute");
604 fail("data.xml contains <raw_text> element");
605 fail("data.xml text is sentences joined by single space");
606 fail("data.xml has correct namespace");
607 fail("data.xml has correct processing instruction");
608}
609
610# Run WITHOUT --base-text to verify data.xml is NOT generated
611my $zipcontent_no_btx = '';
612script_runs(
613 [ 'script/conllu2korapxml', '-f', 'ud', $btx_file ],
614 { stdout => \$zipcontent_no_btx },
615 "conllu2korapxml runs without --base-text"
616);
617
618my $zipfile_no_btx = "$test_tempdir/test_no_base_text.zip";
619if ($zipcontent_no_btx) {
620 open(my $zfh, '>:raw', $zipfile_no_btx) or die "Cannot write zip: $!";
621 print $zfh $zipcontent_no_btx;
622 close($zfh);
623
624 my $ziplist_no = `$UNZIP -l $zipfile_no_btx 2>/dev/null`;
625 unlike($ziplist_no,
626 qr/data\.xml/,
627 "Zip does NOT contain data.xml when --base-text is omitted");
628}
629else {
630 fail("Zip does NOT contain data.xml when --base-text is omitted");
631}
632
633# Test XML escaping: text containing &, <, > characters
634my $escape_data = <<'CONLLU';
635# filename = TEST/TEST/TEST_ESC_001/base/tokens.xml
636# text_id = TEST_TEST.TEST_ESC_001
637# text = A & B < C > D
638# start_offsets = 0 0 2 4 6 8 10 12
639# end_offsets = 13 1 3 5 7 9 11 13
6401 A a NOUN n. _ 0 root _ _
6412 & & PUNCT p. _ 1 punct _ _
6423 B b NOUN n. _ 1 conj _ _
6434 < < PUNCT p. _ 3 punct _ _
6445 C c NOUN n. _ 1 conj _ _
6456 > > PUNCT p. _ 5 punct _ _
6467 D d NOUN n. _ 1 conj _ _
647
648CONLLU
649
650my $esc_file = "$test_tempdir/test_escape.conllu";
651{
652 open(my $efh, '>:encoding(UTF-8)', $esc_file)
653 or die "Cannot write test file: $!";
654 print $efh $escape_data;
655 close($efh);
656}
657
658my $zipcontent_esc = '';
659script_runs(
660 [ 'script/conllu2korapxml', '-f', 'ud', '--base-text', $esc_file ],
661 { stdout => \$zipcontent_esc },
662 "conllu2korapxml handles XML special chars in text"
663);
664
665my $zipfile_esc = "$test_tempdir/test_escape.zip";
666if ($zipcontent_esc) {
667 open(my $zfh, '>:raw', $zipfile_esc) or die "Cannot write zip: $!";
668 print $zfh $zipcontent_esc;
669 close($zfh);
670
671 my $data_esc = `$UNZIP -p $zipfile_esc 'TEST/TEST/TEST_ESC_001/data.xml' 2>/dev/null`;
672 like($data_esc,
673 qr{<text>A &amp; B &lt; C &gt; D</text>},
674 "data.xml escapes &, <, > in full text string");
675}
676else {
677 fail("data.xml escapes &, <, > in full text string");
678}
679
680# Test single-sentence document (no joining needed)
681my $single_sent_data = <<'CONLLU';
682# filename = TEST/TEST/TEST_SST_001/base/tokens.xml
683# text_id = TEST_TEST.TEST_SST_001
684# text = Hello world
685# start_offsets = 0 0 6
686# end_offsets = 11 5 11
6871 Hello hello INTJ intj. _ 0 root _ _
6882 world world NOUN n. _ 1 flat _ _
689
690CONLLU
691
692my $sst_file = "$test_tempdir/test_single_sent.conllu";
693{
694 open(my $sfh, '>:encoding(UTF-8)', $sst_file)
695 or die "Cannot write test file: $!";
696 print $sfh $single_sent_data;
697 close($sfh);
698}
699
700my $zipcontent_sst = '';
701script_runs(
702 [ 'script/conllu2korapxml', '-f', 'ud', '--base-text', $sst_file ],
703 { stdout => \$zipcontent_sst },
704 "conllu2korapxml generates data.xml for single-sentence document"
705);
706
707my $zipfile_sst = "$test_tempdir/test_single_sent.zip";
708if ($zipcontent_sst) {
709 open(my $zfh, '>:raw', $zipfile_sst) or die "Cannot write zip: $!";
710 print $zfh $zipcontent_sst;
711 close($zfh);
712
713 my $data_sst = `$UNZIP -p $zipfile_sst 'TEST/TEST/TEST_SST_001/data.xml' 2>/dev/null`;
714 like($data_sst,
715 qr{<text>Hello world</text>},
716 "data.xml single sentence: text matches exactly");
717}
718else {
719 fail("data.xml single sentence: text matches exactly");
720}
721
Akroncdabed82026-06-03 15:35:39 +0200722# Test: --base-tokens produces base/tokens.xml
723my $zipcontent_tok = '';
724script_runs(
725 [ 'script/conllu2korapxml', '-f', 'ud', '--base-tokens', $btx_file ],
726 { stdout => \$zipcontent_tok },
727 "conllu2korapxml runs with --base-tokens"
728);
729
730my $zipfile_tok = "$test_tempdir/test_tokens.zip";
731if ($zipcontent_tok) {
732 open(my $zfh, '>:raw', $zipfile_tok) or die "Cannot write zip: $!";
733 print $zfh $zipcontent_tok;
734 close($zfh);
735
736 my $ziplist = `$UNZIP -l $zipfile_tok 2>/dev/null`;
737 like($ziplist,
738 qr@TEST/TEST/TEST_BTX_001/base/tokens\.xml@,
739 "Zip contains base/tokens.xml at correct path");
740
741 my $tokens_xml = `$UNZIP -p $zipfile_tok 'TEST/TEST/TEST_BTX_001/base/tokens.xml' 2>/dev/null`;
742
743 like($tokens_xml,
744 qr/docid="TEST_TEST\.TEST_BTX_001"/,
745 "tokens.xml has correct docid attribute");
746
747 like($tokens_xml,
748 qr/xmlns="http:\/\/ids-mannheim\.de\/ns\/KorAP"/,
749 "tokens.xml has correct namespace");
750
751 like($tokens_xml,
752 qr/<\?xml-model href="span\.rng"/,
753 "tokens.xml has correct processing instruction");
754
755 like($tokens_xml,
756 qr/version="KorAP-0\.4"/,
757 "tokens.xml has correct version");
758
759 # Token spans with sequential IDs and correct offsets
760 # Sentence 1: Geras(0..5) rytas(6..11) .(11..12)
761 like($tokens_xml,
762 qr/<span id="t_0" from="0" to="5"\/>/,
763 "Token t_0: Geras from=0 to=5");
764
765 like($tokens_xml,
766 qr/<span id="t_1" from="6" to="11"\/>/,
767 "Token t_1: rytas from=6 to=11");
768
769 like($tokens_xml,
770 qr/<span id="t_2" from="11" to="12"\/>/,
771 "Token t_2: period from=11 to=12");
772
773 # Sentence 2: Kaip(13..17) sekasi(18..24) ?(24..25)
774 like($tokens_xml,
775 qr/<span id="t_3" from="13" to="17"\/>/,
776 "Token t_3: Kaip from=13 to=17");
777
778 like($tokens_xml,
779 qr/<span id="t_4" from="18" to="24"\/>/,
780 "Token t_4: sekasi from=18 to=24");
781
782 like($tokens_xml,
783 qr/<span id="t_5" from="24" to="25"\/>/,
784 "Token t_5: question mark from=24 to=25");
785
786 # Self-closing span elements (no child fs element)
787 unlike($tokens_xml,
788 qr/<span id="t_0"[^\/]*>.*?<\/span>/s,
789 "Token spans are self-closing (no child elements)");
790}
791else {
792 fail("Zip contains base/tokens.xml at correct path");
793 fail("tokens.xml has correct docid attribute");
794 fail("tokens.xml has correct namespace");
795 fail("tokens.xml has correct processing instruction");
796 fail("tokens.xml has correct version");
797 fail("Token t_0: Geras from=0 to=5");
798 fail("Token t_1: rytas from=6 to=11");
799 fail("Token t_2: period from=11 to=12");
800 fail("Token t_3: Kaip from=13 to=17");
801 fail("Token t_4: sekasi from=18 to=24");
802 fail("Token t_5: question mark from=24 to=25");
803 fail("Token spans are self-closing (no child elements)");
804}
805
806# Test: without --base-tokens, no base/tokens.xml is produced
807my $zipcontent_notok = '';
808script_runs(
809 [ 'script/conllu2korapxml', '-f', 'ud', $btx_file ],
810 { stdout => \$zipcontent_notok },
811 "conllu2korapxml runs without --base-tokens"
812);
813
814my $zipfile_notok = "$test_tempdir/test_notokens.zip";
815if ($zipcontent_notok) {
816 open(my $zfh, '>:raw', $zipfile_notok) or die "Cannot write zip: $!";
817 print $zfh $zipcontent_notok;
818 close($zfh);
819
820 my $ziplist_notok = `$UNZIP -l $zipfile_notok 2>/dev/null`;
821 unlike($ziplist_notok,
822 qr/base\/tokens\.xml/,
823 "Zip does NOT contain base/tokens.xml when --base-tokens omitted");
824}
825else {
826 fail("Zip does NOT contain base/tokens.xml when --base-tokens omitted");
827}
828
Akrondb3d0cb2026-06-03 15:53:57 +0200829my $misc_file = "$test_tempdir/test_misc.conllu";
830{
831 open(my $mfh, '>:encoding(UTF-8)', $misc_file)
832 or die "Cannot write test file: $!";
833 print $mfh $misc_data;
834 close($mfh);
835}
836
837my $zipcontent_misc = '';
838script_runs(
839 [ 'script/conllu2korapxml', '-f', 'ud', $misc_file ],
840 { stdout => \$zipcontent_misc },
841 "conllu2korapxml runs with MISC column data"
842);
843
844my $zipfile_misc = "$test_tempdir/test_misc.zip";
845if ($zipcontent_misc) {
846 open(my $zfh, '>:raw', $zipfile_misc) or die "Cannot write zip: $!";
847 print $zfh $zipcontent_misc;
848 close($zfh);
849
850 my $morpho_xml = `$UNZIP -p $zipfile_misc 'TEST/TEST/TEST_MISC_001/ud/morpho.xml' 2>/dev/null`;
851
852 # SpaceAfter=No|Tag=NNN -> only Tag=NNN kept
853 like($morpho_xml,
854 qr/<f name="misc">Tag=NNN<\/f>/,
855 "MISC: SpaceAfter=No filtered, Tag=NNN kept");
856
857 unlike($morpho_xml,
858 qr/<f name="misc">SpaceAfter=No\|Tag=NNN<\/f>/,
859 "MISC: raw SpaceAfter=No|Tag not stored verbatim");
860
861 # Pure Tag=LLL (no SpaceAfter) -> preserved unchanged
862 like($morpho_xml,
863 qr/<f name="misc">Tag=LLL<\/f>/,
864 "MISC: pure non-SpaceAfter value preserved unchanged");
865
866 # SpaceAfter=No alone -> no misc element at all
867 my ($s1_n3_block) = $morpho_xml =~ /(id="s1_n3".*?<\/span>)/s;
868 ok(defined $s1_n3_block, "Found span block for s1_n3");
869 unlike($s1_n3_block // '',
870 qr/<f name="misc">/,
871 "MISC: SpaceAfter=No alone produces no misc element");
872 unlike($s1_n3_block // '',
873 qr/<f name="certainty">/,
874 "MISC: SpaceAfter=No alone produces no certainty element");
875
876 # SpacesAfter=\n|Tag=MMM -> only Tag=MMM kept
877 like($morpho_xml,
878 qr/<f name="misc">Tag=MMM<\/f>/,
879 "MISC: SpacesAfter filtered, Tag=MMM kept");
880
881 unlike($morpho_xml,
882 qr/SpacesAfter/,
883 "MISC: no SpacesAfter value appears in output");
884
885 unlike($morpho_xml,
886 qr/SpaceAfter/,
887 "MISC: no SpaceAfter value appears in output at all");
888}
889else {
890 fail("MISC: SpaceAfter=No filtered, Tag=NNN kept");
891 fail("MISC: raw SpaceAfter=No|Tag not stored verbatim");
892 fail("MISC: pure non-SpaceAfter value preserved unchanged");
893 fail("Found span block for s1_n3");
894 fail("MISC: SpaceAfter=No alone produces no misc element");
895 fail("MISC: SpaceAfter=No alone produces no certainty element");
896 fail("MISC: SpacesAfter filtered, Tag=MMM kept");
897 fail("MISC: no SpacesAfter value appears in output");
898 fail("MISC: no SpaceAfter value appears in output at all");
899}
900
Akronf3efc9e2026-06-03 10:46:37 +0200901done_testing;