blob: aba57a894cdd7e8657c7eb9c65864a664ac37e85 [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
29my $test_tempdir = tempdir();
30my $offset_file = "$test_tempdir/test_offset.conllu";
31{
32 open(my $ofh, '>:encoding(UTF-8)', $offset_file)
33 or die "Cannot write test file: $!";
34 print $ofh $offset_data;
35 close($ofh);
36}
37
38my $zipcontent_off = '';
39script_runs(
40 [ 'script/conllu2korapxml', '-f', 'ud', $offset_file ],
41 { stdout => \$zipcontent_off },
42 "conllu2korapxml computes offsets from # text"
43);
44
45my $zipfile_off = "$test_tempdir/test_offset.zip";
46if ($zipcontent_off) {
47 open(my $zfh, '>:raw', $zipfile_off) or die "Cannot write zip: $!";
48 print $zfh $zipcontent_off;
49 close($zfh);
50
51 my $morpho_xml = `$UNZIP -p $zipfile_off 'TEST/TEST/TEST_OFF_001/ud/morpho.xml' 2>/dev/null`;
52
53 # Sentence 1: "Geras rytas." (12 chars, starts at 0)
54 # Geras: 0-5, rytas: 6-11, .: 11-12
55 like($morpho_xml,
56 qr/id="s1_n1" from="0" to="5"/,
57 "Computed offset: token 'Geras' at 0..5");
58 like($morpho_xml,
59 qr/id="s1_n2" from="6" to="11"/,
60 "Computed offset: token 'rytas' at 6..11");
61 like($morpho_xml,
62 qr/id="s1_n3" from="11" to="12"/,
63 "Computed offset: token '.' at 11..12 (SpaceAfter=No)");
64
65 # Sentence 2: "Kaip sekasi?" (12 chars, starts at 13 = 12 + 1 space)
66 # Kaip: 13-17, sekasi: 18-24, ?: 24-25
67 like($morpho_xml,
68 qr/id="s2_n1" from="13" to="17"/,
69 "Computed offset: token 'Kaip' at 13..17 (sentence 2)");
70 like($morpho_xml,
71 qr/id="s2_n2" from="18" to="24"/,
72 "Computed offset: token 'sekasi' at 18..24");
73 like($morpho_xml,
74 qr/id="s2_n3" from="24" to="25"/,
75 "Computed offset: token '?' at 24..25 (SpaceAfter=No)");
76
77 # Verify dependency XML has correct offsets (head references)
78 my $dep_xml = `$UNZIP -p $zipfile_off 'TEST/TEST/TEST_OFF_001/ud/dependency.xml' 2>/dev/null`;
79
80 # Token 1 "Geras" (0..5) -> head token 2 "rytas" (6..11)
81 like($dep_xml,
82 qr/id="s1_n1" from="0" to="5".*?<span from="6" to="11"/s,
83 "Dependency: 'Geras' head points to 'rytas' offsets");
84
85 # Token 2 "rytas" (6..11) -> head 0 = sentence span (0..12)
86 like($dep_xml,
87 qr/id="s1_n2" from="6" to="11".*?<span from="0" to="12"/s,
88 "Dependency: 'rytas' head points to sentence span");
89}
90else {
91 fail("Computed offset: token 'Geras' at 0..5");
92 fail("Computed offset: token 'rytas' at 6..11");
93 fail("Computed offset: token '.' at 11..12 (SpaceAfter=No)");
94 fail("Computed offset: token 'Kaip' at 13..17 (sentence 2)");
95 fail("Computed offset: token 'sekasi' at 18..24");
96 fail("Computed offset: token '?' at 24..25 (SpaceAfter=No)");
97 fail("Dependency: 'Geras' head points to 'rytas' offsets");
98 fail("Dependency: 'rytas' head points to sentence span");
99}
100
101# No SpaceAfter at all - every token has normal space separation
102my $no_spaceafter_data = <<'CONLLU';
103# filename = TEST/TEST/TEST_NSA_001/base/tokens.xml
104# text_id = TEST_TEST.TEST_NSA_001
105# text = One two three
1061 One one NUM num. _ 0 root _ _
1072 two two NUM num. _ 1 flat _ _
1083 three three NUM num. _ 1 flat _ _
109
110CONLLU
111
112my $nsa_file = "$test_tempdir/test_no_spaceafter.conllu";
113{
114 open(my $nfh, '>:encoding(UTF-8)', $nsa_file)
115 or die "Cannot write test file: $!";
116 print $nfh $no_spaceafter_data;
117 close($nfh);
118}
119
120my $zipcontent_nsa = '';
121script_runs(
122 [ 'script/conllu2korapxml', '-f', 'ud', $nsa_file ],
123 { stdout => \$zipcontent_nsa },
124 "conllu2korapxml handles tokens with no SpaceAfter"
125);
126
127my $zipfile_nsa = "$test_tempdir/test_no_spaceafter.zip";
128if ($zipcontent_nsa) {
129 open(my $zfh, '>:raw', $zipfile_nsa) or die "Cannot write zip: $!";
130 print $zfh $zipcontent_nsa;
131 close($zfh);
132
133 my $morpho_nsa = `$UNZIP -p $zipfile_nsa 'TEST/TEST/TEST_NSA_001/ud/morpho.xml' 2>/dev/null`;
134
135 # "One two three" = 13 chars
136 # One: 0-3, two: 4-7, three: 8-13
137 like($morpho_nsa,
138 qr/id="s1_n1" from="0" to="3"/,
139 "No SpaceAfter: token 'One' at 0..3");
140 like($morpho_nsa,
141 qr/id="s1_n2" from="4" to="7"/,
142 "No SpaceAfter: token 'two' at 4..7");
143 like($morpho_nsa,
144 qr/id="s1_n3" from="8" to="13"/,
145 "No SpaceAfter: token 'three' at 8..13");
146}
147else {
148 fail("No SpaceAfter: token 'One' at 0..3");
149 fail("No SpaceAfter: token 'two' at 4..7");
150 fail("No SpaceAfter: token 'three' at 8..13");
151}
152
153# Adjacent tokens - no space between consecutive tokens (SpaceAfter=No)
154my $adjacent_data = <<'CONLLU';
155# filename = TEST/TEST/TEST_ADJ_001/base/tokens.xml
156# text_id = TEST_TEST.TEST_ADJ_001
157# text = foo(bar)baz end
1581 foo foo NOUN n. _ 0 root _ SpaceAfter=No
1592 ( ( PUNCT skyr. _ 3 punct _ SpaceAfter=No
1603 bar bar NOUN n. _ 1 appos _ SpaceAfter=No
1614 ) ) PUNCT skyr. _ 3 punct _ SpaceAfter=No
1625 baz baz NOUN n. _ 1 conj _ _
1636 end end NOUN n. _ 1 conj _ _
164
165CONLLU
166
167my $adj_file = "$test_tempdir/test_adjacent.conllu";
168{
169 open(my $afh, '>:encoding(UTF-8)', $adj_file)
170 or die "Cannot write test file: $!";
171 print $afh $adjacent_data;
172 close($afh);
173}
174
175my $zipcontent_adj = '';
176script_runs(
177 [ 'script/conllu2korapxml', '-f', 'ud', $adj_file ],
178 { stdout => \$zipcontent_adj },
179 "conllu2korapxml handles adjacent tokens without spaces"
180);
181
182my $zipfile_adj = "$test_tempdir/test_adjacent.zip";
183if ($zipcontent_adj) {
184 open(my $zfh, '>:raw', $zipfile_adj) or die "Cannot write zip: $!";
185 print $zfh $zipcontent_adj;
186 close($zfh);
187
188 my $morpho_adj = `$UNZIP -p $zipfile_adj 'TEST/TEST/TEST_ADJ_001/ud/morpho.xml' 2>/dev/null`;
189
190 # "foo(bar)baz end" = 15 chars
191 # foo: 0-3, (: 3-4, bar: 4-7, ): 7-8, baz: 8-11, end: 12-15
192 like($morpho_adj,
193 qr/id="s1_n1" from="0" to="3"/,
194 "Adjacent: token 'foo' at 0..3");
195 like($morpho_adj,
196 qr/id="s1_n2" from="3" to="4"/,
197 "Adjacent: token '(' at 3..4 (no space before)");
198 like($morpho_adj,
199 qr/id="s1_n3" from="4" to="7"/,
200 "Adjacent: token 'bar' at 4..7 (no space before)");
201 like($morpho_adj,
202 qr/id="s1_n4" from="7" to="8"/,
203 "Adjacent: token ')' at 7..8 (no space before)");
204 like($morpho_adj,
205 qr/id="s1_n5" from="8" to="11"/,
206 "Adjacent: token 'baz' at 8..11 (no space before)");
207 like($morpho_adj,
208 qr/id="s1_n6" from="12" to="15"/,
209 "Adjacent: token 'end' at 12..15 (space before)");
210}
211else {
212 fail("Adjacent: token 'foo' at 0..3");
213 fail("Adjacent: token '(' at 3..4 (no space before)");
214 fail("Adjacent: token 'bar' at 4..7 (no space before)");
215 fail("Adjacent: token ')' at 7..8 (no space before)");
216 fail("Adjacent: token 'baz' at 8..11 (no space before)");
217 fail("Adjacent: token 'end' at 12..15 (space before)");
218}
219
220# Repeated token form - same word appears multiple times in sentence
221my $repeat_data = <<'CONLLU';
222# filename = TEST/TEST/TEST_REP_001/base/tokens.xml
223# text_id = TEST_TEST.TEST_REP_001
224# text = the cat and the dog
2251 the the DET det. _ 2 det _ _
2262 cat cat NOUN n. _ 0 root _ _
2273 and and CCONJ cc. _ 5 cc _ _
2284 the the DET det. _ 5 det _ _
2295 dog dog NOUN n. _ 2 conj _ _
230
231CONLLU
232
233my $rep_file = "$test_tempdir/test_repeat.conllu";
234{
235 open(my $rfh, '>:encoding(UTF-8)', $rep_file)
236 or die "Cannot write test file: $!";
237 print $rfh $repeat_data;
238 close($rfh);
239}
240
241my $zipcontent_rep = '';
242script_runs(
243 [ 'script/conllu2korapxml', '-f', 'ud', $rep_file ],
244 { stdout => \$zipcontent_rep },
245 "conllu2korapxml handles repeated token forms"
246);
247
248my $zipfile_rep = "$test_tempdir/test_repeat.zip";
249if ($zipcontent_rep) {
250 open(my $zfh, '>:raw', $zipfile_rep) or die "Cannot write zip: $!";
251 print $zfh $zipcontent_rep;
252 close($zfh);
253
254 my $morpho_rep = `$UNZIP -p $zipfile_rep 'TEST/TEST/TEST_REP_001/ud/morpho.xml' 2>/dev/null`;
255
256 # "the cat and the dog" = 19 chars
257 # the: 0-3, cat: 4-7, and: 8-11, the: 12-15, dog: 16-19
258 like($morpho_rep,
259 qr/id="s1_n1" from="0" to="3"/,
260 "Repeated: first 'the' at 0..3");
261 like($morpho_rep,
262 qr/id="s1_n2" from="4" to="7"/,
263 "Repeated: 'cat' at 4..7");
264 like($morpho_rep,
265 qr/id="s1_n4" from="12" to="15"/,
266 "Repeated: second 'the' at 12..15 (not matching first)");
267 like($morpho_rep,
268 qr/id="s1_n5" from="16" to="19"/,
269 "Repeated: 'dog' at 16..19");
270}
271else {
272 fail("Repeated: first 'the' at 0..3");
273 fail("Repeated: 'cat' at 4..7");
274 fail("Repeated: second 'the' at 12..15 (not matching first)");
275 fail("Repeated: 'dog' at 16..19");
276}
277
278# Single-token sentence - minimal sentence with only one word
279my $single_data = <<'CONLLU';
280# filename = TEST/TEST/TEST_SNG_001/base/tokens.xml
281# text_id = TEST_TEST.TEST_SNG_001
282# text = Hello
2831 Hello hello INTJ intj. _ 0 root _ _
284
285# text = World
2861 World world NOUN n. _ 0 root _ _
287
288CONLLU
289
290my $sng_file = "$test_tempdir/test_single.conllu";
291{
292 open(my $sfh, '>:encoding(UTF-8)', $sng_file)
293 or die "Cannot write test file: $!";
294 print $sfh $single_data;
295 close($sfh);
296}
297
298my $zipcontent_sng = '';
299script_runs(
300 [ 'script/conllu2korapxml', '-f', 'ud', $sng_file ],
301 { stdout => \$zipcontent_sng },
302 "conllu2korapxml handles single-token sentences"
303);
304
305my $zipfile_sng = "$test_tempdir/test_single.zip";
306if ($zipcontent_sng) {
307 open(my $zfh, '>:raw', $zipfile_sng) or die "Cannot write zip: $!";
308 print $zfh $zipcontent_sng;
309 close($zfh);
310
311 my $morpho_sng = `$UNZIP -p $zipfile_sng 'TEST/TEST/TEST_SNG_001/ud/morpho.xml' 2>/dev/null`;
312
313 # Sentence 1: "Hello" (5 chars, starts at 0)
314 # Sentence 2: "World" (5 chars, starts at 6 = 5 + 1 space)
315 like($morpho_sng,
316 qr/id="s1_n1" from="0" to="5"/,
317 "Single-token: 'Hello' at 0..5");
318 like($morpho_sng,
319 qr/id="s2_n1" from="6" to="11"/,
320 "Single-token: 'World' at 6..11 (after space separator)");
321}
322else {
323 fail("Single-token: 'Hello' at 0..5");
324 fail("Single-token: 'World' at 6..11 (after space separator)");
325}
326
327# Explicit offsets take precedence over # text when both are present
328my $explicit_wins_data = <<'CONLLU';
329# filename = TEST/TEST/TEST_EXP_001/base/tokens.xml
330# text_id = TEST_TEST.TEST_EXP_001
331# text = Geras rytas.
332# start_offsets = 0 100 200 300
333# end_offsets = 999 199 299 399
3341 Geras geras ADJ bdv. Case=Nom 2 amod _ _
3352 rytas rytas NOUN dkt. Case=Nom 0 root _ _
3363 . . PUNCT skyr. _ 2 punct _ _
337
338CONLLU
339
340my $exp_file = "$test_tempdir/test_explicit.conllu";
341{
342 open(my $efh, '>:encoding(UTF-8)', $exp_file)
343 or die "Cannot write test file: $!";
344 print $efh $explicit_wins_data;
345 close($efh);
346}
347
348my $zipcontent_exp = '';
349script_runs(
350 [ 'script/conllu2korapxml', '-f', 'ud', $exp_file ],
351 { stdout => \$zipcontent_exp },
352 "conllu2korapxml uses explicit offsets when both # text and offsets present"
353);
354
355my $zipfile_exp = "$test_tempdir/test_explicit.zip";
356if ($zipcontent_exp) {
357 open(my $zfh, '>:raw', $zipfile_exp) or die "Cannot write zip: $!";
358 print $zfh $zipcontent_exp;
359 close($zfh);
360
361 my $morpho_exp = `$UNZIP -p $zipfile_exp 'TEST/TEST/TEST_EXP_001/ud/morpho.xml' 2>/dev/null`;
362 like($morpho_exp,
363 qr/id="s1_n1" from="100" to="199"/,
364 "Explicit offsets win: token uses from=100 (not computed 0)");
365}
366else {
367 fail("Explicit offsets win: token uses from=100 (not computed 0)");
368}
369
Akron8dabdc12026-06-02 16:36:07 +0200370# -------------------------------------------------------------------
371# Inline test data: minimal UD CoNLL-U with explicit offsets.
372# Uses "# newdoc id" (UD style) instead of "# filename" / "# text_id"
373# (KorAP style). Explicit offsets are provided so this test does not
374# depend on automatic offset computation.
375# -------------------------------------------------------------------
376
377my $ud_conllu_data = <<'CONLLU';
378# newdoc id = TEST_LIT_001
379# start_offsets = 0 0 6 11
380# end_offsets = 12 5 11 12
3811 Geras geras ADJ bdv. Case=Nom 2 amod _ _
3822 rytas rytas NOUN dkt. Case=Nom 0 root _ _
3833 . . PUNCT skyr. _ 2 punct _ _
384
385CONLLU
386
387my $conllu_file = "$test_tempdir/test_ud.conllu";
388{
389 open(my $fh, '>:encoding(UTF-8)', $conllu_file)
390 or die "Cannot write test file: $!";
391 print $fh $ud_conllu_data;
392 close($fh);
393}
394
395my $zipcontent = '';
396script_runs(
397 [ 'script/conllu2korapxml', '-f', 'ud',
398 '--text-sigle', 'TEST/TEST/{ID}', $conllu_file ],
399 { stdout => \$zipcontent },
400 "conllu2korapxml accepts --text-sigle with UD CoNLL-U input"
401);
402
403my $zipfile = "$test_tempdir/test_newdoc.zip";
404if ($zipcontent) {
405 open(my $zfh, '>:raw', $zipfile) or die "Cannot write zip: $!";
406 print $zfh $zipcontent;
407 close($zfh);
408
409 my $ziplist = `$UNZIP -l $zipfile 2>/dev/null`;
410 like($ziplist,
411 qr@TEST/TEST/TEST_LIT_001/ud/morpho\.xml@,
412 "Zip contains morpho.xml at path derived from newdoc id");
413 like($ziplist,
414 qr@TEST/TEST/TEST_LIT_001/ud/dependency\.xml@,
415 "Zip contains dependency.xml at path derived from newdoc id");
416
417 my $zipdata = `$UNZIP -c $zipfile 2>/dev/null`;
418 like($zipdata,
419 qr/docid="TEST_TEST\.TEST_LIT_001"/,
420 "docid correctly derived from text-sigle template and newdoc id");
421}
422else {
423 fail("Zip contains morpho.xml at path derived from newdoc id");
424 fail("Zip contains dependency.xml at path derived from newdoc id");
425 fail("docid correctly derived from text-sigle template and newdoc id");
426}
427
428my $zipcontent_lc = '';
429script_runs(
430 [ 'script/conllu2korapxml', '-f', 'ud',
431 '--text-sigle', 'TEST/TEST/{id}', $conllu_file ],
432 { stdout => \$zipcontent_lc },
433 "text-sigle template accepts {id} (lowercase)"
434);
435
436if ($zipcontent_lc) {
437 my $zipfile_lc = "$test_tempdir/test_lc.zip";
438 open(my $zfh, '>:raw', $zipfile_lc) or die "Cannot write zip: $!";
439 print $zfh $zipcontent_lc;
440 close($zfh);
441
442 my $zipdata_lc = `$UNZIP -c $zipfile_lc 2>/dev/null`;
443 like($zipdata_lc,
444 qr/docid="TEST_TEST\.TEST_LIT_001"/,
445 "docid correct with lowercase {id} template");
446}
447else {
448 fail("docid correct with lowercase {id} template");
449}
450
451my $zipcontent_mc = '';
452script_runs(
453 [ 'script/conllu2korapxml', '-f', 'ud',
454 '--text-sigle', 'TEST/TEST/{Id}', $conllu_file ],
455 { stdout => \$zipcontent_mc },
456 "text-sigle template accepts {Id} (mixed case)"
457);
458
459if ($zipcontent_mc) {
460 my $zipfile_mc = "$test_tempdir/test_mc.zip";
461 open(my $zfh, '>:raw', $zipfile_mc) or die "Cannot write zip: $!";
462 print $zfh $zipcontent_mc;
463 close($zfh);
464
465 my $zipdata_mc = `$UNZIP -c $zipfile_mc 2>/dev/null`;
466 like($zipdata_mc,
467 qr/docid="TEST_TEST\.TEST_LIT_001"/,
468 "docid correct with mixed-case {Id} template");
469}
470else {
471 fail("docid correct with mixed-case {Id} template");
472}
473
474# Template with only 2 parts (missing corpus level)
475my $err_2parts = `$^X script/conllu2korapxml -f ud --text-sigle 'TEST/{ID}' $conllu_file 2>&1`;
476isnt($? >> 8, 0, "Rejects template with only 2 parts (non-zero exit)");
477like($err_2parts, qr/ERROR/, "Error message for 2-part template");
478
479# Template with 4 parts (too many levels)
480my $err_4parts = `$^X script/conllu2korapxml -f ud --text-sigle 'A/B/C/{ID}' $conllu_file 2>&1`;
481isnt($? >> 8, 0, "Rejects template with 4 parts (non-zero exit)");
482like($err_4parts, qr/ERROR/, "Error message for 4-part template");
483
484# Template with empty middle part
485my $err_empty = `$^X script/conllu2korapxml -f ud --text-sigle 'TEST//{ID}' $conllu_file 2>&1`;
486isnt($? >> 8, 0, "Rejects template with empty part (non-zero exit)");
487like($err_empty, qr/ERROR/, "Error message for empty-part template");
488
489# Template with only 1 part (no slashes)
490my $err_1part = `$^X script/conllu2korapxml -f ud --text-sigle '{ID}' $conllu_file 2>&1`;
491isnt($? >> 8, 0, "Rejects template with only 1 part (non-zero exit)");
492like($err_1part, qr/ERROR/, "Error message for 1-part template");
493
494my $bad_id_data = <<'CONLLU';
495# newdoc id = BAD/SLASH_ID
496# start_offsets = 0 0
497# end_offsets = 4 4
4981 Test test NOUN NN _ 0 root _ _
499
500CONLLU
501
502my $bad_id_file = "$test_tempdir/bad_id.conllu";
503{
504 open(my $bfh, '>:encoding(UTF-8)', $bad_id_file)
505 or die "Cannot write test file: $!";
506 print $bfh $bad_id_data;
507 close($bfh);
508}
509
510my $err_slash = `$^X script/conllu2korapxml -f ud --text-sigle 'A/B/{ID}' $bad_id_file 2>&1`;
511isnt($? >> 8, 0,
512 "Rejects newdoc id with slash (expanded sigle has wrong part count)");
513like($err_slash, qr/ERROR/,
514 "Error message for newdoc id containing slash");
Akron982dd1e2026-06-03 11:52:48 +0200515
516my $base_text_data = <<'CONLLU';
517# filename = TEST/TEST/TEST_BTX_001/base/tokens.xml
518# text_id = TEST_TEST.TEST_BTX_001
519# text = Geras rytas.
520# start_offsets = 0 0 6 11
521# end_offsets = 12 5 11 12
5221 Geras geras ADJ bdv. Case=Nom 2 amod _ _
5232 rytas rytas NOUN dkt. Case=Nom 0 root _ _
5243 . . PUNCT skyr. _ 2 punct _ SpaceAfter=No
525
526# text = Kaip sekasi?
527# start_offsets = 12 13 18 24
528# end_offsets = 25 17 24 25
5291 Kaip kaip ADV prv. _ 2 advmod _ _
5302 sekasi sektis VERB vksm. _ 0 root _ SpaceAfter=No
5313 ? ? PUNCT skyr. _ 2 punct _ _
532
533CONLLU
534
535my $btx_file = "$test_tempdir/test_base_text.conllu";
536{
537 open(my $bfh, '>:encoding(UTF-8)', $btx_file)
538 or die "Cannot write test file: $!";
539 print $bfh $base_text_data;
540 close($bfh);
541}
542
543# Run with --base-text to generate data.xml
544my $zipcontent_btx = '';
545script_runs(
546 [ 'script/conllu2korapxml', '-f', 'ud', '--base-text', $btx_file ],
547 { stdout => \$zipcontent_btx },
548 "conllu2korapxml accepts --base-text option"
549);
550
551my $zipfile_btx = "$test_tempdir/test_base_text.zip";
552if ($zipcontent_btx) {
553 open(my $zfh, '>:raw', $zipfile_btx) or die "Cannot write zip: $!";
554 print $zfh $zipcontent_btx;
555 close($zfh);
556
557 my $ziplist = `$UNZIP -l $zipfile_btx 2>/dev/null`;
558 like($ziplist,
559 qr@TEST/TEST/TEST_BTX_001/data\.xml@,
560 "Zip contains data.xml at correct path");
561
562 my $data_xml = `$UNZIP -p $zipfile_btx 'TEST/TEST/TEST_BTX_001/data.xml' 2>/dev/null`;
563
564 like($data_xml,
565 qr/docid="TEST_TEST\.TEST_BTX_001"/,
566 "data.xml has correct docid attribute");
567
568 like($data_xml,
569 qr/<raw_text\b/,
570 "data.xml contains <raw_text> element");
571
572 like($data_xml,
573 qr{<text>Geras rytas\. Kaip sekasi\?</text>},
574 "data.xml text is sentences joined by single space");
575
576 like($data_xml,
577 qr/xmlns="http:\/\/ids-mannheim\.de\/ns\/KorAP"/,
578 "data.xml has correct namespace");
579
580 like($data_xml,
581 qr/<\?xml-model href="text\.rng"/,
582 "data.xml has correct processing instruction");
583}
584else {
585 fail("Zip contains data.xml at correct path");
586 fail("data.xml has correct docid attribute");
587 fail("data.xml contains <raw_text> element");
588 fail("data.xml text is sentences joined by single space");
589 fail("data.xml has correct namespace");
590 fail("data.xml has correct processing instruction");
591}
592
593# Run WITHOUT --base-text to verify data.xml is NOT generated
594my $zipcontent_no_btx = '';
595script_runs(
596 [ 'script/conllu2korapxml', '-f', 'ud', $btx_file ],
597 { stdout => \$zipcontent_no_btx },
598 "conllu2korapxml runs without --base-text"
599);
600
601my $zipfile_no_btx = "$test_tempdir/test_no_base_text.zip";
602if ($zipcontent_no_btx) {
603 open(my $zfh, '>:raw', $zipfile_no_btx) or die "Cannot write zip: $!";
604 print $zfh $zipcontent_no_btx;
605 close($zfh);
606
607 my $ziplist_no = `$UNZIP -l $zipfile_no_btx 2>/dev/null`;
608 unlike($ziplist_no,
609 qr/data\.xml/,
610 "Zip does NOT contain data.xml when --base-text is omitted");
611}
612else {
613 fail("Zip does NOT contain data.xml when --base-text is omitted");
614}
615
616# Test XML escaping: text containing &, <, > characters
617my $escape_data = <<'CONLLU';
618# filename = TEST/TEST/TEST_ESC_001/base/tokens.xml
619# text_id = TEST_TEST.TEST_ESC_001
620# text = A & B < C > D
621# start_offsets = 0 0 2 4 6 8 10 12
622# end_offsets = 13 1 3 5 7 9 11 13
6231 A a NOUN n. _ 0 root _ _
6242 & & PUNCT p. _ 1 punct _ _
6253 B b NOUN n. _ 1 conj _ _
6264 < < PUNCT p. _ 3 punct _ _
6275 C c NOUN n. _ 1 conj _ _
6286 > > PUNCT p. _ 5 punct _ _
6297 D d NOUN n. _ 1 conj _ _
630
631CONLLU
632
633my $esc_file = "$test_tempdir/test_escape.conllu";
634{
635 open(my $efh, '>:encoding(UTF-8)', $esc_file)
636 or die "Cannot write test file: $!";
637 print $efh $escape_data;
638 close($efh);
639}
640
641my $zipcontent_esc = '';
642script_runs(
643 [ 'script/conllu2korapxml', '-f', 'ud', '--base-text', $esc_file ],
644 { stdout => \$zipcontent_esc },
645 "conllu2korapxml handles XML special chars in text"
646);
647
648my $zipfile_esc = "$test_tempdir/test_escape.zip";
649if ($zipcontent_esc) {
650 open(my $zfh, '>:raw', $zipfile_esc) or die "Cannot write zip: $!";
651 print $zfh $zipcontent_esc;
652 close($zfh);
653
654 my $data_esc = `$UNZIP -p $zipfile_esc 'TEST/TEST/TEST_ESC_001/data.xml' 2>/dev/null`;
655 like($data_esc,
656 qr{<text>A &amp; B &lt; C &gt; D</text>},
657 "data.xml escapes &, <, > in full text string");
658}
659else {
660 fail("data.xml escapes &, <, > in full text string");
661}
662
663# Test single-sentence document (no joining needed)
664my $single_sent_data = <<'CONLLU';
665# filename = TEST/TEST/TEST_SST_001/base/tokens.xml
666# text_id = TEST_TEST.TEST_SST_001
667# text = Hello world
668# start_offsets = 0 0 6
669# end_offsets = 11 5 11
6701 Hello hello INTJ intj. _ 0 root _ _
6712 world world NOUN n. _ 1 flat _ _
672
673CONLLU
674
675my $sst_file = "$test_tempdir/test_single_sent.conllu";
676{
677 open(my $sfh, '>:encoding(UTF-8)', $sst_file)
678 or die "Cannot write test file: $!";
679 print $sfh $single_sent_data;
680 close($sfh);
681}
682
683my $zipcontent_sst = '';
684script_runs(
685 [ 'script/conllu2korapxml', '-f', 'ud', '--base-text', $sst_file ],
686 { stdout => \$zipcontent_sst },
687 "conllu2korapxml generates data.xml for single-sentence document"
688);
689
690my $zipfile_sst = "$test_tempdir/test_single_sent.zip";
691if ($zipcontent_sst) {
692 open(my $zfh, '>:raw', $zipfile_sst) or die "Cannot write zip: $!";
693 print $zfh $zipcontent_sst;
694 close($zfh);
695
696 my $data_sst = `$UNZIP -p $zipfile_sst 'TEST/TEST/TEST_SST_001/data.xml' 2>/dev/null`;
697 like($data_sst,
698 qr{<text>Hello world</text>},
699 "data.xml single sentence: text matches exactly");
700}
701else {
702 fail("data.xml single sentence: text matches exactly");
703}
704
Akroncdabed82026-06-03 15:35:39 +0200705# Test: --base-tokens produces base/tokens.xml
706my $zipcontent_tok = '';
707script_runs(
708 [ 'script/conllu2korapxml', '-f', 'ud', '--base-tokens', $btx_file ],
709 { stdout => \$zipcontent_tok },
710 "conllu2korapxml runs with --base-tokens"
711);
712
713my $zipfile_tok = "$test_tempdir/test_tokens.zip";
714if ($zipcontent_tok) {
715 open(my $zfh, '>:raw', $zipfile_tok) or die "Cannot write zip: $!";
716 print $zfh $zipcontent_tok;
717 close($zfh);
718
719 my $ziplist = `$UNZIP -l $zipfile_tok 2>/dev/null`;
720 like($ziplist,
721 qr@TEST/TEST/TEST_BTX_001/base/tokens\.xml@,
722 "Zip contains base/tokens.xml at correct path");
723
724 my $tokens_xml = `$UNZIP -p $zipfile_tok 'TEST/TEST/TEST_BTX_001/base/tokens.xml' 2>/dev/null`;
725
726 like($tokens_xml,
727 qr/docid="TEST_TEST\.TEST_BTX_001"/,
728 "tokens.xml has correct docid attribute");
729
730 like($tokens_xml,
731 qr/xmlns="http:\/\/ids-mannheim\.de\/ns\/KorAP"/,
732 "tokens.xml has correct namespace");
733
734 like($tokens_xml,
735 qr/<\?xml-model href="span\.rng"/,
736 "tokens.xml has correct processing instruction");
737
738 like($tokens_xml,
739 qr/version="KorAP-0\.4"/,
740 "tokens.xml has correct version");
741
742 # Token spans with sequential IDs and correct offsets
743 # Sentence 1: Geras(0..5) rytas(6..11) .(11..12)
744 like($tokens_xml,
745 qr/<span id="t_0" from="0" to="5"\/>/,
746 "Token t_0: Geras from=0 to=5");
747
748 like($tokens_xml,
749 qr/<span id="t_1" from="6" to="11"\/>/,
750 "Token t_1: rytas from=6 to=11");
751
752 like($tokens_xml,
753 qr/<span id="t_2" from="11" to="12"\/>/,
754 "Token t_2: period from=11 to=12");
755
756 # Sentence 2: Kaip(13..17) sekasi(18..24) ?(24..25)
757 like($tokens_xml,
758 qr/<span id="t_3" from="13" to="17"\/>/,
759 "Token t_3: Kaip from=13 to=17");
760
761 like($tokens_xml,
762 qr/<span id="t_4" from="18" to="24"\/>/,
763 "Token t_4: sekasi from=18 to=24");
764
765 like($tokens_xml,
766 qr/<span id="t_5" from="24" to="25"\/>/,
767 "Token t_5: question mark from=24 to=25");
768
769 # Self-closing span elements (no child fs element)
770 unlike($tokens_xml,
771 qr/<span id="t_0"[^\/]*>.*?<\/span>/s,
772 "Token spans are self-closing (no child elements)");
773}
774else {
775 fail("Zip contains base/tokens.xml at correct path");
776 fail("tokens.xml has correct docid attribute");
777 fail("tokens.xml has correct namespace");
778 fail("tokens.xml has correct processing instruction");
779 fail("tokens.xml has correct version");
780 fail("Token t_0: Geras from=0 to=5");
781 fail("Token t_1: rytas from=6 to=11");
782 fail("Token t_2: period from=11 to=12");
783 fail("Token t_3: Kaip from=13 to=17");
784 fail("Token t_4: sekasi from=18 to=24");
785 fail("Token t_5: question mark from=24 to=25");
786 fail("Token spans are self-closing (no child elements)");
787}
788
789# Test: without --base-tokens, no base/tokens.xml is produced
790my $zipcontent_notok = '';
791script_runs(
792 [ 'script/conllu2korapxml', '-f', 'ud', $btx_file ],
793 { stdout => \$zipcontent_notok },
794 "conllu2korapxml runs without --base-tokens"
795);
796
797my $zipfile_notok = "$test_tempdir/test_notokens.zip";
798if ($zipcontent_notok) {
799 open(my $zfh, '>:raw', $zipfile_notok) or die "Cannot write zip: $!";
800 print $zfh $zipcontent_notok;
801 close($zfh);
802
803 my $ziplist_notok = `$UNZIP -l $zipfile_notok 2>/dev/null`;
804 unlike($ziplist_notok,
805 qr/base\/tokens\.xml/,
806 "Zip does NOT contain base/tokens.xml when --base-tokens omitted");
807}
808else {
809 fail("Zip does NOT contain base/tokens.xml when --base-tokens omitted");
810}
811
Akronf3efc9e2026-06-03 10:46:37 +0200812done_testing;