blob: e2845d9a1552508219ac1d7e3c8ee44cd948db17 [file] [log] [blame]
Akron9cb13942020-02-14 07:39:54 +01001#!/usr/bin/env perl
Peter Hardersd892a582020-02-12 15:45:22 +01002use strict;
3use warnings;
Peter Harders6f526a32020-06-29 21:44:41 +02004
Akron3378dfd2020-08-01 15:01:36 +02005use Log::Any '$log';
6use Log::Any::Adapter;
Peter Harders6f526a32020-06-29 21:44:41 +02007use Pod::Usage;
Marc Kupietz98198562026-06-16 16:04:38 +02008use Getopt::Long qw(GetOptions :config no_auto_abbrev);
Marc Kupietzfc3a0ee2024-07-05 16:58:16 +02009use KorAP::XML::TEI qw(increase_auto_textsigle);
Peter Harders6f526a32020-06-29 21:44:41 +020010
11use File::Basename qw(dirname);
Peter Hardersd892a582020-02-12 15:45:22 +010012
Marc Kupietz8ab68322026-03-18 18:04:14 +010013use Encode qw(decode encode);
Peter Hardersd892a582020-02-12 15:45:22 +010014
Akron4f67cd42020-07-02 12:27:58 +020015use FindBin;
Marc Kupietz98198562026-06-16 16:04:38 +020016
Akron4f67cd42020-07-02 12:27:58 +020017BEGIN {
18 unshift @INC, "$FindBin::Bin/../lib";
Marc Kupietz98198562026-06-16 16:04:38 +020019}
Akron4f67cd42020-07-02 12:27:58 +020020
Marc Kupietz8a954e52021-02-16 22:03:07 +010021use KorAP::XML::TEI qw!remove_xml_comments replace_entities!;
Akron8b511f92020-07-09 17:28:08 +020022use KorAP::XML::TEI::Tokenizer::External;
Akrond9627472020-07-09 16:53:09 +020023use KorAP::XML::TEI::Tokenizer::Conservative;
24use KorAP::XML::TEI::Tokenizer::Aggressive;
Akron85717512020-07-08 11:19:19 +020025use KorAP::XML::TEI::Zipper;
Akronf57ed812020-07-27 10:37:52 +020026use KorAP::XML::TEI::Header;
Akroneb12e232021-02-25 13:49:50 +010027use KorAP::XML::TEI::Inline;
Peter Hardersd892a582020-02-12 15:45:22 +010028
Marc Kupietz98198562026-06-16 16:04:38 +020029our $VERSION = '2.7.4';
Peter Harders6f526a32020-06-29 21:44:41 +020030
Akrond949e182020-02-14 12:23:57 +010031our $VERSION_MSG = "\ntei2korapxml - v$VERSION\n";
32
Akron33db4ec2021-02-24 12:52:21 +010033use constant {
Marc Kupietz98198562026-06-16 16:04:38 +020034
Akron33db4ec2021-02-24 12:52:21 +010035 # Set to 1 for minimal more debug output (no need to be parametrized)
Akroneb12e232021-02-25 13:49:50 +010036 DEBUG => $ENV{KORAPXMLTEI_DEBUG} // 0
Akron33db4ec2021-02-24 12:52:21 +010037};
Peter Hardersd892a582020-02-12 15:45:22 +010038
Marc Kupietz98198562026-06-16 16:04:38 +020039if ( $ENV{KORAPXMLTEI_INLINE} ) {
Akron692d17d2021-03-05 13:21:03 +010040 warn 'KORAPXMLTEI_INLINE is deprecated in favor of --skip-inline-token-annotations';
Marc Kupietz98198562026-06-16 16:04:38 +020041}
Akron692d17d2021-03-05 13:21:03 +010042
Akrone2819a12021-10-12 15:52:55 +020043# Inline tokens won't be stored in the structure file
44my $inline_tokens_exclusive = 0;
45
Akron6b1f26b2024-09-19 11:35:32 +020046# Inline dependencies won't be stored in the tokens file
47my $inline_deps_exclusive = 0;
48
Peter Harders6f526a32020-06-29 21:44:41 +020049# Parse options from the command line
Peter Hardersd892a582020-02-12 15:45:22 +010050GetOptions(
Marc Kupietz98198562026-06-16 16:04:38 +020051 'auto-textsigle|A=s' => \( my $auto_textsigle = '' ),
52 'root|r=s' => \( my $root_dir = '.' ),
53 'input|i=s' => \( my $input_fname = '' ),
54 'output|o=s' => \( my $output_fname = '' ),
55 'tokenizer-call|tc=s' => \( my $tokenizer_call ),
56 'tokenizer-korap|tk' => \( my $tokenizer_korap ),
57 'tokenizer-internal|ti' => \( my $tokenizer_intern ),
58 'no-tokenizer' => \( my $no_tokenizer ),
59 'use-tokenizer-sentence-splits|s' => \( my $use_tokenizer_sentence_splits ),
60 'inline-tokens=s' => \( my $inline_tokens = 'tokens#morpho' ),
61 'inline-structures=s' => \( my $inline_structures = 'struct#structure' ),
62 'inline-dependencies=s' => \( my $inline_dependencies ),
63 'skip-inline-tokens' => \( my $skip_inline_tokens = 0 ),
64 'skip-inline-token-annotations!' => \( my $skip_inline_token_annotations = ( $ENV{KORAPXMLTEI_INLINE} ? 0 : 1 ) ),
65 'skip-inline-tags=s' => \( my $skip_inline_tags_str = '' ),
66 'base-foundry=s' => \( my $base_dir = 'base' ),
67 'data-file=s' => \( my $data_file = 'data' ),
68 'header-file=s' => \( my $header_file = 'header' ),
69 'tokens-file=s' => \( my $tokens_file = 'tokens' ),
70 'xmlid-to-textsigle|x=s' => \( my $xmlid_to_textsigle = '' ),
71 'log|l=s' => \( my $log_level = 'notice' ),
72 'required-version|rv=s' => \( my $required_version ),
73 'progress|p' => \( my $progress ),
74 '' => \( my $stdio ),
75 'help|h' => sub {
Akrond949e182020-02-14 12:23:57 +010076 pod2usage(
Marc Kupietz98198562026-06-16 16:04:38 +020077 -verbose => 99,
Akrond949e182020-02-14 12:23:57 +010078 -sections => 'NAME|DESCRIPTION|SYNOPSIS|ARGUMENTS|OPTIONS',
Marc Kupietz98198562026-06-16 16:04:38 +020079 -msg => $VERSION_MSG,
80 -output => '-'
81 );
Akrond949e182020-02-14 12:23:57 +010082 },
83 'version|v' => sub {
84 pod2usage(
85 -verbose => 0,
Marc Kupietz98198562026-06-16 16:04:38 +020086 -msg => $VERSION_MSG,
87 -output => '-'
Akrond3e1d282021-02-24 14:51:27 +010088 );
Akrond949e182020-02-14 12:23:57 +010089 }
Peter Hardersd892a582020-02-12 15:45:22 +010090);
91
Akronb87c58d2021-02-23 17:23:30 +010092# Establish logger
Marc Kupietz98198562026-06-16 16:04:38 +020093binmode( STDERR, ':encoding(UTF-8)' );
94Log::Any::Adapter->set( 'Stderr', log_level => $log_level );
Akronb3649472020-09-29 08:24:46 +020095$log->notice('Debugging is activated') if DEBUG;
96
Akron2520a342022-03-29 18:18:05 +020097if ($required_version) {
Marc Kupietz2475c952024-01-09 10:40:04 +010098 $required_version =~ /^\s*(\d+\.\d+\.\d+(-TRIAL)?)\s*$/;
Marc Kupietz98198562026-06-16 16:04:38 +020099 if ( !$1 || $1 ne $VERSION ) {
Akron2520a342022-03-29 18:18:05 +0200100 $log->error("Required version $required_version mismatches version $VERSION");
101 exit(1);
Marc Kupietz98198562026-06-16 16:04:38 +0200102 }
103}
104
105my ( $what, $with );
106my $xmlid_to_textsigle_cb;
107if ( $xmlid_to_textsigle ne '' ) {
108 ( $what, $with ) = split( '@', $xmlid_to_textsigle );
109 $what = qr!$what!;
110 $xmlid_to_textsigle_cb = sub {
111 my $id = shift;
112 my $sigle = $id;
113 if ($what) {
114 local $_ = $id;
115 eval "s|$what|$with|";
116 $sigle = $_;
117 }
118 $sigle =~ s/\./-/g;
119 return $sigle;
Akron2520a342022-03-29 18:18:05 +0200120 };
Marc Kupietz98198562026-06-16 16:04:38 +0200121}
Marc Kupietza671ae52022-12-22 16:28:14 +0100122
Marc Kupietz3c16cb92026-03-05 18:29:59 +0100123my $progress_fh;
Marc Kupietz2115ecc2025-12-10 11:37:03 +0100124if ($progress) {
125 eval {
126 require Time::Progress;
Marc Kupietz3c16cb92026-03-05 18:29:59 +0100127 my $tty = $^O eq 'MSWin32' ? 'CON' : '/dev/tty';
Marc Kupietz98198562026-06-16 16:04:38 +0200128 open( $progress_fh, '>', $tty )
Marc Kupietz3c16cb92026-03-05 18:29:59 +0100129 or die "Cannot open $tty";
130 $progress_fh->autoflush(1);
Marc Kupietz2115ecc2025-12-10 11:37:03 +0100131 1;
132 } or do {
Marc Kupietz98198562026-06-16 16:04:38 +0200133 $log->warn( 'Progress bar disabled: ' . ( $@ =~ s/ at .*//sr ) );
Marc Kupietz3c16cb92026-03-05 18:29:59 +0100134 $progress = 0;
Marc Kupietz2115ecc2025-12-10 11:37:03 +0100135 }
Marc Kupietz98198562026-06-16 16:04:38 +0200136}
Marc Kupietz2115ecc2025-12-10 11:37:03 +0100137
Akron0529e512021-02-22 09:55:35 +0100138# tag (without attributes), which contains the primary text
139my $_TEXT_BODY = 'text';
Marc Kupietz98198562026-06-16 16:04:38 +0200140
Akron0c41ab32020-09-29 07:33:33 +0200141# optional
Akron09e0b2c2020-07-28 15:57:01 +0200142
Akron54c3ff12021-02-25 11:33:37 +0100143# Remember to skip certain inline tags
144my %skip_inline_tags = ();
145if ($skip_inline_tags_str) {
Marc Kupietz98198562026-06-16 16:04:38 +0200146 foreach ( split /\s*,\s*/, $skip_inline_tags_str ) {
Akron54c3ff12021-02-25 11:33:37 +0100147 $skip_inline_tags{$_} = 1;
Marc Kupietz98198562026-06-16 16:04:38 +0200148 }
149}
Akron54c3ff12021-02-25 11:33:37 +0100150
Akrond3e1d282021-02-24 14:51:27 +0100151# External tokenization
Akron0c41ab32020-09-29 07:33:33 +0200152my $ext_tok;
153if ($tokenizer_call) {
154 $ext_tok = KorAP::XML::TEI::Tokenizer::External->new($tokenizer_call);
Akron11484782021-11-03 20:12:14 +0100155 $ext_tok->sentence_splits(1) if $use_tokenizer_sentence_splits;
Akron0c41ab32020-09-29 07:33:33 +0200156}
Marc Kupietz1e882fb2020-09-09 00:05:46 +0200157
Akronb93fabb2023-01-13 12:05:44 +0100158# KorAP tokenization
Akron0c41ab32020-09-29 07:33:33 +0200159elsif ($tokenizer_korap) {
Akronbd4281e2022-03-28 08:31:40 +0200160 eval {
161 require KorAP::XML::TEI::Tokenizer::KorAP;
162 1;
163 };
Akron2520a342022-03-29 18:18:05 +0200164
165 my $korap_tok_ver = $KorAP::XML::TEI::Tokenizer::KorAP::VERSION;
Marc Kupietz98198562026-06-16 16:04:38 +0200166 if ( $korap_tok_ver ne $VERSION ) {
Akron2520a342022-03-29 18:18:05 +0200167 $log->error("KorAP-Tokenizer version ($korap_tok_ver) differs from the expected version ($VERSION)");
168 exit(1);
Marc Kupietz98198562026-06-16 16:04:38 +0200169 }
Akron2520a342022-03-29 18:18:05 +0200170
Marc Kupietz985da0c2021-02-15 19:29:50 +0100171 $ext_tok = KorAP::XML::TEI::Tokenizer::KorAP->new($use_tokenizer_sentence_splits);
Akronb93fabb2023-01-13 12:05:44 +0100172}
173
174# No internal tokenizer chosen
Marc Kupietz98198562026-06-16 16:04:38 +0200175elsif ( !$tokenizer_intern && !$no_tokenizer ) {
Akronb93fabb2023-01-13 12:05:44 +0100176 $log->error("No tokenizer chosen. If only internal tokens should be used, pass the --no-tokenizer flag");
177 exit(1);
Marc Kupietz98198562026-06-16 16:04:38 +0200178}
Peter Harders6f526a32020-06-29 21:44:41 +0200179
Marc Kupietz98198562026-06-16 16:04:38 +0200180if ( !$no_tokenizer && $use_tokenizer_sentence_splits ) {
Akron11484782021-11-03 20:12:14 +0100181 $skip_inline_tags{s} = 1;
Marc Kupietz98198562026-06-16 16:04:38 +0200182}
Akron0c41ab32020-09-29 07:33:33 +0200183
Akrond3e1d282021-02-24 14:51:27 +0100184# Internal tokenization
Akronb87c58d2021-02-23 17:23:30 +0100185my $aggr_tok = KorAP::XML::TEI::Tokenizer::Aggressive->new;
186my $cons_tok = KorAP::XML::TEI::Tokenizer::Conservative->new;
Akrond3e1d282021-02-24 14:51:27 +0100187
Akrondd0be8f2021-02-18 19:29:41 +0100188# Name of the directory and the file containing all inline structure informations
Akrond53913c2021-02-24 09:50:13 +0100189# except for $_TOKENS_TAG information
Marc Kupietz98198562026-06-16 16:04:38 +0200190my ( $_structure_dir, $_structure_file ) = split '#', $inline_structures . '#structure';
Akrondd0be8f2021-02-18 19:29:41 +0100191
Akron1a5271a2021-02-18 13:18:15 +0100192# Name of the directory and the file containing all inline token informations
193# i.e. tokens of the $_TOKENS_TAG, if $_TOKENS_PROC is set
Marc Kupietz98198562026-06-16 16:04:38 +0200194my ( $_tokens_dir, $_tokens_file ) = split '#', $inline_tokens . '#morpho';
Akron1a5271a2021-02-18 13:18:15 +0100195
Marc Kupietz98198562026-06-16 16:04:38 +0200196if ( index( $_tokens_dir, '!' ) == 0 ) {
197 $_tokens_dir = substr( $_tokens_dir, 1 );
Akrone2819a12021-10-12 15:52:55 +0200198 $inline_tokens_exclusive = 1;
Marc Kupietz98198562026-06-16 16:04:38 +0200199}
Akrone2819a12021-10-12 15:52:55 +0200200
Marc Kupietz98198562026-06-16 16:04:38 +0200201my ( $_dep_dir, $_dep_file );
Akron6b1f26b2024-09-19 11:35:32 +0200202if ($inline_dependencies) {
Marc Kupietz98198562026-06-16 16:04:38 +0200203 ( $_dep_dir, $_dep_file ) = split '#', $inline_dependencies . '#dependency';
Akron6b1f26b2024-09-19 11:35:32 +0200204 $inline_dependencies = 1;
205
Marc Kupietz98198562026-06-16 16:04:38 +0200206 if ( $_dep_dir && index( $_dep_dir, '!' ) == 0 ) {
207 $_dep_dir = substr( $_dep_dir, 1 );
Akron6b1f26b2024-09-19 11:35:32 +0200208 $inline_deps_exclusive = 1;
Marc Kupietz98198562026-06-16 16:04:38 +0200209 }
210}
Akron6b1f26b2024-09-19 11:35:32 +0200211
Akronb87c58d2021-02-23 17:23:30 +0100212# Initialize zipper
Marc Kupietz98198562026-06-16 16:04:38 +0200213my $zipper = KorAP::XML::TEI::Zipper->new( $root_dir, $output_fname );
Akron09e0b2c2020-07-28 15:57:01 +0200214
Akronbc899192021-02-24 12:14:47 +0100215# text directory (below $root_dir)
216my $dir = '';
Akron09e0b2c2020-07-28 15:57:01 +0200217
Akronbc899192021-02-24 12:14:47 +0100218# Escaped version of text id
219my $text_id_esc;
Peter Harders6f526a32020-06-29 21:44:41 +0200220
Akrond53913c2021-02-24 09:50:13 +0100221# Default encoding of the text
222my $input_enc = 'UTF-8';
223
Akrond53913c2021-02-24 09:50:13 +0100224# text line (needed for whitespace handling)
225my $text_line = 0;
226
Akrond53913c2021-02-24 09:50:13 +0100227# Input file handle (default: stdin)
Akrona2cb2812021-10-30 10:29:08 +0200228my $input_fh;
Peter Hardersd892a582020-02-12 15:45:22 +0100229
Akrona2cb2812021-10-30 10:29:08 +0200230# Single dash was set
231if ($stdio) {
232 $input_fh = *STDIN;
233}
Marc Kupietz98198562026-06-16 16:04:38 +0200234
Akrona2cb2812021-10-30 10:29:08 +0200235# Input flag was passed
Marc Kupietz98198562026-06-16 16:04:38 +0200236elsif ( @ARGV || $input_fname ne '' ) {
237 unless ( $input_fname ne '' ) {
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200238 $input_fname = shift @ARGV;
Marc Kupietz98198562026-06-16 16:04:38 +0200239 }
240 unless ( open( $input_fh, '<', $input_fname ) ) {
Akron347be812020-09-29 07:52:52 +0200241 die $log->fatal("File '$input_fname' could not be opened.");
Marc Kupietz98198562026-06-16 16:04:38 +0200242 }
Akrona2cb2812021-10-30 10:29:08 +0200243}
244
245# No input to process
246else {
247 pod2usage(
Marc Kupietz98198562026-06-16 16:04:38 +0200248 -verbose => 99,
Akrona2cb2812021-10-30 10:29:08 +0200249 -sections => 'NAME|SYNOPSIS',
Marc Kupietz98198562026-06-16 16:04:38 +0200250 -msg => $VERSION_MSG,
251 -output => '-'
Akrona2cb2812021-10-30 10:29:08 +0200252 );
253 exit;
Marc Kupietz98198562026-06-16 16:04:38 +0200254}
Peter Harders6f526a32020-06-29 21:44:41 +0200255
Akronf8088e62021-02-18 16:18:59 +0100256# Prevents segfaulting (see notes on segfault prevention)
Akron347be812020-09-29 07:52:52 +0200257binmode $input_fh;
Peter Harders6f526a32020-06-29 21:44:41 +0200258
Akroneb12e232021-02-25 13:49:50 +0100259# Create inline parser object
Marc Kupietz98198562026-06-16 16:04:38 +0200260my $inline = KorAP::XML::TEI::Inline->new( $skip_inline_tokens, \%skip_inline_tags, $inline_tokens_exclusive,
261 $inline_dependencies );
Akroneb12e232021-02-25 13:49:50 +0100262
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200263do {
Marc Kupietz2115ecc2025-12-10 11:37:03 +0100264 my $p;
Marc Kupietz98198562026-06-16 16:04:38 +0200265 if ( $progress && $input_fname ne '' ) {
Marc Kupietz2115ecc2025-12-10 11:37:03 +0100266 my $file_size = -s $input_fname;
267 if ($file_size) {
Marc Kupietz98198562026-06-16 16:04:38 +0200268 $p = Time::Progress->new( min => 0, max => $file_size );
269 $log->notice("Reading input document $input_fname (Size: $file_size bytes)");
Marc Kupietz2115ecc2025-12-10 11:37:03 +0100270 }
Marc Kupietz98198562026-06-16 16:04:38 +0200271 } elsif ( $input_fname ne '' ) {
272 $log->notice("Reading input document $input_fname");
273 }
Marc Kupietz2115ecc2025-12-10 11:37:03 +0100274
275 my $i = 0;
Marc Kupietz98198562026-06-16 16:04:38 +0200276MAIN:
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200277 while (<$input_fh>) {
Akroneb12e232021-02-25 13:49:50 +0100278
Marc Kupietz98198562026-06-16 16:04:38 +0200279 if ( $p && ( $i++ % 500 == 0 ) ) {
280 print $progress_fh $p->report( "\r%20b %p ETA: %E", tell($input_fh) );
281 }
Marc Kupietz2115ecc2025-12-10 11:37:03 +0100282
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200283 # remove HTML (multi-line) comments (<!--...-->)
Marc Kupietz98198562026-06-16 16:04:38 +0200284 $_ = remove_xml_comments( $input_fh, $_ );
Akron347be812020-09-29 07:52:52 +0200285
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200286 # Set input encoding
Marc Kupietz98198562026-06-16 16:04:38 +0200287 if ( index( $_, '<?xml' ) == 0 && $_ =~ /\sencoding=(['"])([^\1]+?)\1/ ) {
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200288 $input_enc = $2;
289 next;
Marc Kupietz98198562026-06-16 16:04:38 +0200290 }
Peter Harders6f526a32020-06-29 21:44:41 +0200291
Marc Kupietz98198562026-06-16 16:04:38 +0200292 $_ = decode( $input_enc, $_ );
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200293 $_ = replace_entities($_);
Peter Harders90157342020-07-01 21:05:14 +0200294
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200295 # Start of text body
Marc Kupietz98198562026-06-16 16:04:38 +0200296 if ( index( $_, $_TEXT_BODY ) >= 0 && m#^(.*)<${_TEXT_BODY}(?: [^>]*)?>(.*)$# ) {
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200297 my $suffix = $2;
Peter Harders90157342020-07-01 21:05:14 +0200298
Marc Kupietz98198562026-06-16 16:04:38 +0200299 if ( $1 !~ /^\s*$/ || $suffix !~ /^\s*$/ ) {
300 die $log->fatal( "input line number $.: "
301 . "line with opening text-body tag '${_TEXT_BODY}' "
302 . "contains additional information ... => Aborting (line=$_)" );
303 }
Peter Harders6f526a32020-06-29 21:44:41 +0200304
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200305 # Text body data extracted from input document ($input_fh),
306 # further processed by XML::LibXML::Reader
307 my $text_buffer = '';
Peter Harders6f526a32020-06-29 21:44:41 +0200308
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200309 # Iterate over all lines in the text body
310 while (<$input_fh>) {
Peter Harders6f526a32020-06-29 21:44:41 +0200311
Marc Kupietz98198562026-06-16 16:04:38 +0200312 $_ = remove_xml_comments( $input_fh, $_ );
313 $_ = decode( $input_enc, $_ );
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200314 $_ = replace_entities($_);
Peter Harders6f526a32020-06-29 21:44:41 +0200315
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200316 # End of text body
Marc Kupietz98198562026-06-16 16:04:38 +0200317 if ( ( my $pos = index( $_, "</$_TEXT_BODY>" ) ) >= 0 ) {
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200318
319 # write data.xml, structure.xml and evtl. morpho.xml and/or tokenization files
320
Marc Kupietz98198562026-06-16 16:04:38 +0200321 my $before = substr( $_, 0, $pos );
322 my $after = substr( $_, length("</$_TEXT_BODY>") + $pos );
Marc Kupietzff061ef2026-03-05 09:59:35 +0100323 my $before_check = $before;
Marc Kupietz98198562026-06-16 16:04:38 +0200324 $before_check =~ s/<[^>]+>//g; # strip XML tags like </body>
325 if ( ( $before_check . $after ) !~ /^\s*$/ ) {
326 die $log->fatal( "input line number $.: "
327 . "line with closing text-body tag '${_TEXT_BODY}'"
328 . " contains additional information ... => Aborting (line=$_)" );
329 }
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200330
Marc Kupietzff061ef2026-03-05 09:59:35 +0100331 # Add any remaining content before </text> (e.g. </body>) to the buffer
332 $before =~ s/^\s+//;
333 $before =~ s/\s+$//;
334 $text_buffer .= $before if $before ne '';
335
Marc Kupietz98198562026-06-16 16:04:38 +0200336 if ( $dir eq '' ) {
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200337 $log->warn(
Marc Kupietz98198562026-06-16 16:04:38 +0200338 "Maybe empty textSigle => skipping this text ...\n" . 'data=' . substr( $inline->data->data, 0, 200 ) );
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200339 next MAIN;
Marc Kupietz98198562026-06-16 16:04:38 +0200340 }
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200341
342 # Parse inline structure
Marc Kupietz98198562026-06-16 16:04:38 +0200343 $inline->parse( $text_id_esc, \$text_buffer );
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200344
345 if (DEBUG) {
346 $log->debug("Writing (utf8-formatted) xml file $dir/${data_file}.xml");
Marc Kupietz98198562026-06-16 16:04:38 +0200347 }
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200348
349 my $data = $inline->data;
350
351 # Write data.xml
Marc Kupietz98198562026-06-16 16:04:38 +0200352 $data->to_zip( $zipper->new_stream("$dir/${data_file}.xml"), $text_id_esc );
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200353
354 # Tokenize with external tokenizer
355 if ($ext_tok) {
356
Marc Kupietz98198562026-06-16 16:04:38 +0200357 my $tokens_output = eval { $ext_tok->tokenize( $data->data )->to_string($text_id_esc); };
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200358
Marc Kupietz98198562026-06-16 16:04:38 +0200359 if ( my $err = $@ ) {
Marc Kupietz8ab68322026-03-18 18:04:14 +0100360 $err =~ s/\s+$//;
361 $log->error("Skipping external tokenization for '$text_id_esc': $err");
362 $ext_tok->reset;
Marc Kupietz98198562026-06-16 16:04:38 +0200363 } elsif ( defined $tokens_output ) {
364 $zipper->new_stream("$dir/$base_dir/${tokens_file}.xml")->print( encode( 'UTF-8', $tokens_output ) );
Marc Kupietz8ab68322026-03-18 18:04:14 +0100365
366 if ($use_tokenizer_sentence_splits) {
Marc Kupietz98198562026-06-16 16:04:38 +0200367 $ext_tok->sentencize_from_previous_input( $inline->structures );
368 }
369 }
370 }
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200371
372 # Tokenize with internal tokenizer
373 if ($tokenizer_intern) {
374
375 # Tokenize and output
Marc Kupietz98198562026-06-16 16:04:38 +0200376 $cons_tok->tokenize( $data->data )
377 ->to_zip( $zipper->new_stream( "$dir/$base_dir/" . $cons_tok->name . '.xml' ), $text_id_esc )
378 ->reset;
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200379
Marc Kupietz98198562026-06-16 16:04:38 +0200380 $aggr_tok->tokenize( $data->data )
381 ->to_zip( $zipper->new_stream( "$dir/$base_dir/" . $aggr_tok->name . '.xml' ), $text_id_esc )
382 ->reset;
383 }
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200384
385 # ~ write structures ~
Marc Kupietz98198562026-06-16 16:04:38 +0200386 unless ( $inline->structures->empty ) {
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200387 $inline->structures->to_zip(
388 $zipper->new_stream("$dir/$_structure_dir/${_structure_file}.xml"),
389 $text_id_esc,
Marc Kupietz98198562026-06-16 16:04:38 +0200390 2 # = structure serialization
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200391 );
Marc Kupietz98198562026-06-16 16:04:38 +0200392 }
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200393
394 # ~ write tokens ~
Marc Kupietz98198562026-06-16 16:04:38 +0200395 unless ( $skip_inline_tokens || $inline->tokens->empty ) {
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200396 $inline->tokens->to_zip(
397 $zipper->new_stream("$dir/$_tokens_dir/${_tokens_file}.xml"),
398 $text_id_esc,
Marc Kupietz98198562026-06-16 16:04:38 +0200399
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200400 # Either 0 = tokens without inline or
401 # 1 = tokens with inline
402 # !$skip_inline_token_annotations
Marc Kupietz98198562026-06-16 16:04:38 +0200403 ( $skip_inline_token_annotations ? 0 : ( $inline_deps_exclusive ? 4 : 1 ) )
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200404 );
Marc Kupietz98198562026-06-16 16:04:38 +0200405 }
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200406
407 # ~ write dependencies ~
Marc Kupietz98198562026-06-16 16:04:38 +0200408 unless ( $inline->dependencies->empty ) {
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200409 $inline->dependencies->to_zip(
410 $zipper->new_stream("$dir/$_dep_dir/${_dep_file}.xml"),
411 $text_id_esc,
Marc Kupietz98198562026-06-16 16:04:38 +0200412 3 # = dependency serialization
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200413 );
Marc Kupietz98198562026-06-16 16:04:38 +0200414 }
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200415
416 # reinit.
417 $dir = '';
418
Akrondafaa7a2021-02-19 15:17:58 +0100419 next MAIN;
Marc Kupietz98198562026-06-16 16:04:38 +0200420 }
Akrondafaa7a2021-02-19 15:17:58 +0100421
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200422 # ~ whitespace handling ~
Akrondafaa7a2021-02-19 15:17:58 +0100423
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200424 # Fix whitespaces (see notes on whitespace fixing)
Akroneb12e232021-02-25 13:49:50 +0100425
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200426 # TODO:
427 # Maybe it's best, to keep the stripping of whitespace and
428 # to just remove the if-clause and to insert a blank by default
429 # (with possibly an option on how newlines in primary text should
430 # be handled (stripped or replaced by a whitespace)).
Akrondafaa7a2021-02-19 15:17:58 +0100431
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200432 # Remove consecutive whitespace at beginning and end (mostly one newline)
433 s/^\s+//;
434 s/\s+$//;
Akrondafaa7a2021-02-19 15:17:58 +0100435
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200436 # NOTE:
437 # this is only relevant, if a text consists of more than one line
Akrond53ab4b2021-02-24 09:56:12 +0100438
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200439 # TODO:
440 # find a better solution, or create a warning, if a text has more
441 # than one line ($text_line > 1)
Peter Harders6f526a32020-06-29 21:44:41 +0200442
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200443 # TODO:
444 # do testing with 2 different corpora
445 # (one with only one-line texts, the other with several lines per text)
Peter Harders6f526a32020-06-29 21:44:41 +0200446
Marc Kupietza84fcb52026-03-05 17:22:43 +0100447 # Check if the buffer currently ends inside an open XML tag
448 # (last '<' is after last '>'), meaning this line is a continuation of
449 # a multi-line element (e.g. attributes split across lines like <ref>).
450 # A space must be prepended to avoid "attributes construct error" in the
451 # XML parser when two attribute tokens are concatenated without separator.
Marc Kupietz98198562026-06-16 16:04:38 +0200452 my $in_open_tag =
453 ( $text_buffer ne '' && rindex( $text_buffer, '<' ) > rindex( $text_buffer, '>' ) );
Marc Kupietza84fcb52026-03-05 17:22:43 +0100454
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200455 # line contains at least one non-tag character
Marc Kupietz98198562026-06-16 16:04:38 +0200456 if ( m/^[^<]*$/ || m/(?:<[^>]+>[^<])|(?:[^<]<[^>]+>)/ ) {
Akron598d1a72020-08-02 17:33:31 +0200457
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200458 # Increment counter for text lines
459 $text_line++;
Akrona10ad592020-08-03 11:20:23 +0200460
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200461 # insert blank before 1st character
Marc Kupietza84fcb52026-03-05 17:22:43 +0100462 # (for 2nd line and consecutive lines, or when continuing an open tag)
463 $_ = ' ' . $_ if $text_line > 1 || $in_open_tag;
464 }
465
466 # Line is purely within an open tag (attribute continuation):
467 # prepend a space so attributes are properly separated.
468 elsif ($in_open_tag) {
469 $_ = ' ' . $_;
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200470 }
Akrondafaa7a2021-02-19 15:17:58 +0100471
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200472 # add line to buffer
473 $text_buffer .= $_;
Marc Kupietz98198562026-06-16 16:04:38 +0200474 }
475 } elsif (m#^(.*)\<TEI\s+[^>]*?xml:id=(["'])(.+?)\2#) {
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200476 my $leadin = $1;
Marc Kupietz98198562026-06-16 16:04:38 +0200477 my $id = $3;
478 my $sigle;
Akrondafaa7a2021-02-19 15:17:58 +0100479
Marc Kupietz98198562026-06-16 16:04:38 +0200480 if ($xmlid_to_textsigle_cb) {
481 $sigle = $xmlid_to_textsigle_cb->($id);
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200482 $log->debug("Converted text id `$id' to sigle `$sigle'");
Marc Kupietz98198562026-06-16 16:04:38 +0200483 } else {
484 $sigle = $id;
485 $sigle =~ s/\./-/g;
486 }
Akron6b1f26b2024-09-19 11:35:32 +0200487
Marc Kupietz98198562026-06-16 16:04:38 +0200488 my @parts = split( /[\/_]/, $sigle );
489 if ( @parts != 3 ) {
490 die $log->fatal( "input line number $.: "
491 . "ids must have exactly three parts split by '/', but `$id` only has "
492 . scalar(@parts) . " "
493 . "=> Aborting (line=$_)" );
494 }
Akron598d1a72020-08-02 17:33:31 +0200495
Marc Kupietz98198562026-06-16 16:04:38 +0200496 $dir = join( "/", @parts );
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200497 $text_id_esc = "$parts[0]/$parts[1].$parts[2]";
498 $log->notice("$0: text_id=$text_id_esc");
Peter Harders6f526a32020-06-29 21:44:41 +0200499
Marc Kupietz98198562026-06-16 16:04:38 +0200500 if ( $leadin !~ /^\s*$/ ) {
501 die $log->fatal( "input line number $.: "
502 . 'line with opening header tag is not in expected format ... '
503 . "=> Aborting (line=$_)" );
504 }
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200505 }
Marc Kupietza671ae52022-12-22 16:28:14 +0100506
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200507 # Start of header section
508 elsif (m#^(.*)(\<(?:ids|tei)Header.*)$#) {
509 my $content = "$2\n";
Marc Kupietza671ae52022-12-22 16:28:14 +0100510
Marc Kupietz98198562026-06-16 16:04:38 +0200511 if ( $1 !~ /^\s*$/ ) {
512 die $log->fatal( "input line number $.: "
513 . 'line with opening header tag is not in expected format ... '
514 . "=> Aborting (line=$_)" );
515 }
Marc Kupietza671ae52022-12-22 16:28:14 +0100516
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200517 # Parse header
Marc Kupietz98198562026-06-16 16:04:38 +0200518 my $header =
519 KorAP::XML::TEI::Header->new( $content, $input_enc, $text_id_esc // $auto_textsigle, $xmlid_to_textsigle_cb )
520 ->parse($input_fh);
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200521 if ($auto_textsigle) {
522 $auto_textsigle = increase_auto_textsigle($auto_textsigle);
523 $log->debug("Auto-incremented text sigle to $auto_textsigle");
Marc Kupietz98198562026-06-16 16:04:38 +0200524 }
Akronf57ed812020-07-27 10:37:52 +0200525
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200526 # Header was parseable
527 if ($header) {
Akron347be812020-09-29 07:52:52 +0200528
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200529 # Write header to zip
530 my $file = $header->dir . '/' . $header_file . '.xml';
Akron347be812020-09-29 07:52:52 +0200531
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200532 $log->debug("Writing file $file") if DEBUG;
Akron347be812020-09-29 07:52:52 +0200533
Marc Kupietz98198562026-06-16 16:04:38 +0200534 $header->to_zip( $zipper->new_stream($file) );
Akron347be812020-09-29 07:52:52 +0200535
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200536 # Header is for text level
Marc Kupietz98198562026-06-16 16:04:38 +0200537 if ( $header->type eq 'text' ) {
Akron347be812020-09-29 07:52:52 +0200538
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200539 # Remember dir and sigles
Marc Kupietz98198562026-06-16 16:04:38 +0200540 $dir = $header->dir;
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200541 $text_id_esc = $header->id_esc;
Akron347be812020-09-29 07:52:52 +0200542
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200543 # log output for seeing progression
544 $log->notice("$0: text_id=$text_id_esc");
Akron347be812020-09-29 07:52:52 +0200545
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200546 # Reset counter for text lines
547 # (needed for whitespace handling)
548 $text_line = 0;
Marc Kupietz98198562026-06-16 16:04:38 +0200549 }
550 }
551 }
552 }
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200553 $text_id_esc = $auto_textsigle if ($auto_textsigle);
Marc Kupietz2115ecc2025-12-10 11:37:03 +0100554
555 if ($p) {
Marc Kupietz98198562026-06-16 16:04:38 +0200556 print $progress_fh $p->report( "\r%20b %p ETA: %E\n", tell($input_fh) );
557 }
558} while ( ( $input_fname = shift(@ARGV) ) && open( $input_fh, '<', $input_fname ) );
Akron347be812020-09-29 07:52:52 +0200559$zipper->close;
Peter Harders6f526a32020-06-29 21:44:41 +0200560
Akron9df4a242021-02-19 15:31:16 +0100561$ext_tok->close if $ext_tok;
Peter Hardersd892a582020-02-12 15:45:22 +0100562
Akrond53913c2021-02-24 09:50:13 +0100563close $input_fh;
564
Akrond949e182020-02-14 12:23:57 +0100565__END__
566
567=pod
568
569=encoding utf8
570
571=head1 NAME
572
573tei2korapxml - Conversion of TEI P5 based formats to KorAP-XML
574
575=head1 SYNOPSIS
576
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200577 cat corpus.i5.xml | tei2korapxml -tk - > corpus.korapxml.zip
578 tei2korapxml -tk corpus.i5.xml > corpus.korapxml.zip
Akrond949e182020-02-14 12:23:57 +0100579
580=head1 DESCRIPTION
581
Akronee434b12020-07-08 12:53:01 +0200582C<tei2korapxml> is a script to convert TEI P5 and
Akrond72baca2021-07-23 13:25:32 +0200583L<I5|https://www.ids-mannheim.de/digspra/kl/projekte/korpora/textmodell>
Akronee434b12020-07-08 12:53:01 +0200584based documents to the
585L<KorAP-XML format|https://github.com/KorAP/KorAP-XML-Krill#about-korap-xml>.
Peter Harders6f526a32020-06-29 21:44:41 +0200586
Akrond949e182020-02-14 12:23:57 +0100587This program is usually called from inside another script.
588
Akronee434b12020-07-08 12:53:01 +0200589=head1 FORMATS
590
591=head2 Input restrictions
592
593=over 2
594
595=item
596
Akronee434b12020-07-08 12:53:01 +0200597TEI P5 formatted input with certain restrictions:
598
599=over 4
600
601=item
602
Akrone48bec42023-01-05 12:18:45 +0100603B<mandatory>: text-header with integrated textsigle
604(or convertable identifier), text-body
Akronee434b12020-07-08 12:53:01 +0200605
606=item
607
608B<optional>: corp-header with integrated corpsigle,
609doc-header with integrated docsigle
610
611=back
612
613=item
614
Akron0c41ab32020-09-29 07:33:33 +0200615All tokens inside the primary text may not be
Akronee434b12020-07-08 12:53:01 +0200616newline seperated, because newlines are removed
Akron0c41ab32020-09-29 07:33:33 +0200617(see L<KorAP::XML::TEI::Data>) and a conversion of newlines
Akronee434b12020-07-08 12:53:01 +0200618into blanks between 2 tokens could lead to additional blanks,
619where there should be none (e.g.: punctuation characters like C<,> or
620C<.> should not be seperated from their predecessor token).
Akron8a0c4bf2021-03-16 16:51:21 +0100621(see also code section C<~ whitespace handling ~> in C<script/tei2korapxml>).
Akronee434b12020-07-08 12:53:01 +0200622
Akron940ca6f2021-10-11 12:38:39 +0200623=item
624
625Header types, like C<E<lt>idsHeader [...] type="document" [...] E<gt>>
626need to be defined in the same line as the header tag.
627
Akronee434b12020-07-08 12:53:01 +0200628=back
629
630=head2 Notes on the output
631
632=over 2
633
634=item
635
636zip file output (default on C<stdout>) with utf8 encoded entries
637(which together form the KorAP-XML format)
638
639=back
640
Akrond949e182020-02-14 12:23:57 +0100641=head1 INSTALLATION
642
Akrond26319b2023-01-12 15:34:41 +0100643C<tei2korapxml> requires C<libxml2-dev> bindings and L<File::ShareDir::Install> to be installed.
Marc Kupietze83a4e92021-03-16 20:51:26 +0100644When these requirements are met, the preferred way to install the script is
Akrond949e182020-02-14 12:23:57 +0100645to use L<cpanm|App::cpanminus>.
646
647 $ cpanm https://github.com/KorAP/KorAP-XML-TEI.git
648
649In case everything went well, the C<tei2korapxml> tool will
650be available on your command line immediately.
Peter Harders6f526a32020-06-29 21:44:41 +0200651
Akrond949e182020-02-14 12:23:57 +0100652Minimum requirement for L<KorAP::XML::TEI> is Perl 5.16.
653
654=head1 OPTIONS
655
656=over 2
657
Akrona2cb2812021-10-30 10:29:08 +0200658=item B<--input|-i>
659
660The input file to process. If no specific input is defined and a single
661dash C<-> is passed as an argument, data is read from C<STDIN>.
662
Marc Kupietz5b3f1d82024-07-05 17:50:55 +0200663Instead of using C<-i> input files can also be defined as trailing arguments
664to the command:
665
666 tei2korapxml -tk corpus1.i5.xml corpus2.i5.xml
667
Akron132bdeb2024-06-06 14:28:56 +0200668=item B<--output|-o>
669
670The output zip file to be created. If no specific output is defined,
671data is written to C<STDOUT>.
Akrona2cb2812021-10-30 10:29:08 +0200672
Akron4e603a52020-07-27 14:23:49 +0200673=item B<--root|-r>
Akrond949e182020-02-14 12:23:57 +0100674
Akron4e603a52020-07-27 14:23:49 +0200675The root directory for output. Defaults to C<.>.
Akrond949e182020-02-14 12:23:57 +0100676
677=item B<--help|-h>
678
679Print help information.
680
681=item B<--version|-v>
682
683Print version information.
684
Akrone48bec42023-01-05 12:18:45 +0100685=item B<--tokenizer-korap|-tk>
Akron2520a342022-03-29 18:18:05 +0200686
Akrone48bec42023-01-05 12:18:45 +0100687Use the standard KorAP/DeReKo tokenizer.
688
689=item B<--tokenizer-internal|-ti>
690
691Tokenize the data using two embedded tokenizers,
692that will take an I<aggressive> and a I<conservative>
693approach.
Akron2520a342022-03-29 18:18:05 +0200694
Akron4e603a52020-07-27 14:23:49 +0200695=item B<--tokenizer-call|-tc>
696
697Call an external tokenizer process, that will tokenize
Akron11484782021-11-03 20:12:14 +0100698from STDIN and outputs the offsets of all tokens.
699
700Texts are separated using C<\x04\n>. The external process
701should add a new line per text.
702
703If the L</--use-tokenizer-sentence-splits> option is activated,
704sentences are marked by offset as well in new lines.
705
706To use L<Datok|https://github.com/KorAP/Datok> including sentence
707splitting, call C<tei2korap> as follows:
708
709 $ cat corpus.i5.xml | tei2korapxml -s \
710 $ -tc 'datok tokenize \
711 $ -t ./tokenizer.matok \
712 $ -p --newline-after-eot --no-sentences \
713 $ --no-tokens --sentence-positions -' - \
714 $ > corpus.korapxml.zip
Akron4e603a52020-07-27 14:23:49 +0200715
Akronb93fabb2023-01-13 12:05:44 +0100716=item B<--no-tokenizer>
717
718Boolean flag indicating that no tokenizer should be used.
719This is meant to ensure that by default a final token layer always
720exists.
721If a separate tokenizer is chosen, this flag is ignored.
722
Akron75d63142021-02-23 18:40:56 +0100723=item B<--skip-inline-tokens>
724
725Boolean flag indicating that inline tokens should not
726be processed. Defaults to false (meaning inline tokens will be processed).
727
Akron692d17d2021-03-05 13:21:03 +0100728=item B<--skip-inline-token-annotations>
729
730Boolean flag indicating that inline token annotations should not
731be processed. Defaults to true (meaning inline token annotations
Akron6b1f26b2024-09-19 11:35:32 +0200732won't be processed). Can be negated with
733C<--no-skip-inline-token-annotations>.
Akron692d17d2021-03-05 13:21:03 +0100734
Akronca70a1d2021-02-25 16:21:31 +0100735=item B<--skip-inline-tags> <tags>
Akron54c3ff12021-02-25 11:33:37 +0100736
737Expects a comma-separated list of tags to be ignored when the structure
738is parsed. Content of these tags however will be processed.
739
Marc Kupietzfc3a0ee2024-07-05 16:58:16 +0200740=item B<--auto-textsigle> <textsigle>
741
742Expects a text sigle thats serves as fallback if no text sigles
743are given in the input data.
744The auto text sigle will be incremented for each text processed.
745
746Example:
747
748 tei2korapxml --auto-textsigle 'ICC/GER.00001' -s -tk - \
749 < data.i5.xml > korapxml.zip
750
Marc Kupietza671ae52022-12-22 16:28:14 +0100751=item B<--xmlid-to-textsigle> <from-regex>@<to-c/to-d/to-t>
752
Akrone48bec42023-01-05 12:18:45 +0100753Expects a regular replacement expression (separated by B<@> between the
Marc Kupietza671ae52022-12-22 16:28:14 +0100754search and the replacement) to convert text id attributes to text sigles
755with three parts (separated by B</>).
756
757Example:
758
759 tei2korapxml \
760 --xmlid-to-textsigle 'ICC.German\.([^.]+\.[^.]+)\.(.+)@ICCGER/$1/$2' \
761 -tk - < t/data/icc_german_sample.p5.xml
762
Akrone48bec42023-01-05 12:18:45 +0100763Converts text id C<ICC.German.DeReKo.WPD17.G11.00238> to
764sigle C<ICCGER/DeReKo.WPD17/G11.00238>.
Marc Kupietza671ae52022-12-22 16:28:14 +0100765
Akron1a5271a2021-02-18 13:18:15 +0100766=item B<--inline-tokens> <foundry>#[<file>]
767
768Define the foundry and file (without extension)
769to store inline token information in.
Akron8a0c4bf2021-03-16 16:51:21 +0100770Unless C<--skip-inline-token-annotations> is set,
771this will contain annotations as well.
Akron1a5271a2021-02-18 13:18:15 +0100772Defaults to C<tokens> and C<morpho>.
773
Akrone2819a12021-10-12 15:52:55 +0200774The inline token data will also be stored in the
775inline structures file (see I<--inline-structures>),
776unless the inline token foundry is prepended
777by an B<!> exclamation mark, indicating that inline
778tokens are stored exclusively in the inline tokens
779file.
780
781Example:
782
Akron6b1f26b2024-09-19 11:35:32 +0200783 tei2korapxml --no-tokenizer --inline-tokens \
784 '!gingko#morpho' < data.i5.xml > korapxml.zip
785
786=item B<--inline-dependencies> <foundry>#[<file>]
787
788Define the foundry and file (without extension)
789to store inline dependency information in.
790Defaults to the layer of C<dependency> and
791will be ignored if not set (which means, dependency
792attributes will be stored in the inline tokens file,
793if not skipped).
794
795The dependency data will also be stored in the
796inline token file (see I<--inline-tokens>),
797unless the inline dependencies foundry is prepended
798by an B<!> exclamation mark, indicating that inline
799dependency data is stored exclusively in the inline
800dependencies file.
801
802Example:
803
804 tei2korapxml --no-tokenizer --inline-dependencies \
805 'gingko#dependency' < data.i5.xml > korapxml.zip
806
Akrone2819a12021-10-12 15:52:55 +0200807
Akrondd0be8f2021-02-18 19:29:41 +0100808=item B<--inline-structures> <foundry>#[<file>]
809
810Define the foundry and file (without extension)
811to store inline structure information in.
812Defaults to C<struct> and C<structures>.
813
Akron26a71522021-02-19 10:27:37 +0100814=item B<--base-foundry> <foundry>
815
816Define the base foundry to store newly generated
817token information in.
818Defaults to C<base>.
819
820=item B<--data-file> <file>
821
822Define the file (without extension)
823to store primary data information in.
824Defaults to C<data>.
825
826=item B<--header-file> <file>
827
828Define the file name (without extension)
829to store header information on
830the corpus, document, and text level in.
831Defaults to C<header>.
832
Marc Kupietz985da0c2021-02-15 19:29:50 +0100833=item B<--use-tokenizer-sentence-splits|-s>
834
835Replace existing with, or add new, sentence boundary information
Akron11484782021-11-03 20:12:14 +0100836provided by the tokenizer.
837Currently KorAP-tokenizer and certain external tokenizers support
838these boundaries.
Marc Kupietz985da0c2021-02-15 19:29:50 +0100839
Akron91705d72021-02-19 10:59:45 +0100840=item B<--tokens-file> <file>
841
842Define the file (without extension)
843to store generated token information in
844(either from the KorAP tokenizer or an externally called tokenizer).
845Defaults to C<tokens>.
846
Akron3378dfd2020-08-01 15:01:36 +0200847=item B<--log|-l>
848
849Loglevel for I<Log::Any>. Defaults to C<notice>.
850
Akrond949e182020-02-14 12:23:57 +0100851=back
852
Akronb3649472020-09-29 08:24:46 +0200853=head1 ENVIRONMENT VARIABLES
854
855=over 2
856
857=item B<KORAPXMLTEI_DEBUG>
858
859Activate minimal debugging.
860Defaults to C<false>.
861
Marc Kupietzd254f5c2025-04-16 10:37:08 +0200862=item B<KORAPXMLTEI_TOKENIZER_HEAP_SIZE>
863
864Set the heap size for the tokenizer process.
865Defaults to C<512m>.
866
Akronb3649472020-09-29 08:24:46 +0200867=back
868
Akrond949e182020-02-14 12:23:57 +0100869=head1 COPYRIGHT AND LICENSE
870
Marc Kupietzb6fd6bc2025-04-16 12:47:26 +0200871Copyright (C) 2021-2025, L<IDS Mannheim|https://www.ids-mannheim.de/>
Akrond949e182020-02-14 12:23:57 +0100872
873Author: Peter Harders
874
Akronaabd0952020-09-29 07:35:08 +0200875Contributors: Nils Diewald, Marc Kupietz, Carsten Schnober
Akrond949e182020-02-14 12:23:57 +0100876
877L<KorAP::XML::TEI> is developed as part of the L<KorAP|https://korap.ids-mannheim.de/>
878Corpus Analysis Platform at the
Akrond72baca2021-07-23 13:25:32 +0200879L<Leibniz Institute for the German Language (IDS)|https://www.ids-mannheim.de/>,
Akrond949e182020-02-14 12:23:57 +0100880member of the
881L<Leibniz-Gemeinschaft|http://www.leibniz-gemeinschaft.de/>.
882
883This program is free software published under the
Marc Kupietze955ecc2021-02-17 17:42:01 +0100884L<BSD-2 License|https://opensource.org/licenses/BSD-2-Clause>.
Akrond949e182020-02-14 12:23:57 +0100885
886=cut
Akronf8088e62021-02-18 16:18:59 +0100887
888# NOTES
889
Akronf8088e62021-02-18 16:18:59 +0100890## Notes on segfault prevention
891
Akron91577922021-02-19 10:32:54 +0100892binmode on the input handler prevents segfaulting of 'XML::LibXML::Reader' inside the main loop
Akronf8088e62021-02-18 16:18:59 +0100893(see notes on 'PerlIO layers' in 'man XML::LibXML'),
894removing 'use open qw(:std :utf8)' would fix this problem too, but using binmode on input is more granular
895see in perluniintro: You can switch encodings on an already opened stream by using "binmode()
896see in perlfunc: If LAYER is omitted or specified as ":raw" the filehandle is made suitable for passing binary data.