Support non-word-tokens (fixes #5)

Change-Id: I6867745afd7c0fb865722bcd62a0724aaa9a6ccb
diff --git a/Changes b/Changes
index d6c7931..ce01343 100644
--- a/Changes
+++ b/Changes
@@ -1,3 +1,6 @@
+0.36 2019-01-22
+        - Support for non-word tokens (fixes #5).
+
 0.35 2018-09-24
         - Lift minimum version of Perl to 5.16 as for
           "fc"-feature.
diff --git a/Readme.pod b/Readme.pod
index 9b80513..3b3ef8d 100644
--- a/Readme.pod
+++ b/Readme.pod
@@ -178,6 +178,13 @@
 This is I<deprecated>.
 
 
+=item B<--non-word-tokens|-nwt>
+
+Tokenize non-word tokens like word tokens (defined as matching
+C</[\d\w]/>). Useful to treat punctuations as tokens.
+
+ Defaults to unset.
+
 =item B<--jobs|-j>
 
 Define the number of concurrent jobs in seperated forks
@@ -343,6 +350,9 @@
   Glemm
     #Morpho
 
+  HNC
+    #Morpho
+
   LWC
     #Dependency
 
@@ -389,7 +399,7 @@
 
 =head1 COPYRIGHT AND LICENSE
 
-Copyright (C) 2015-2018, L<IDS Mannheim|http://www.ids-mannheim.de/>
+Copyright (C) 2015-2019, L<IDS Mannheim|http://www.ids-mannheim.de/>
 
 Author: L<Nils Diewald|http://nils-diewald.de/>
 
diff --git a/lib/KorAP/XML/Batch/File.pm b/lib/KorAP/XML/Batch/File.pm
index dbb6ded..583bc8f 100644
--- a/lib/KorAP/XML/Batch/File.pm
+++ b/lib/KorAP/XML/Batch/File.pm
@@ -12,16 +12,17 @@
   my %param = @_;
 
   bless {
-    cache     => $param{cache}     // undef,
-    meta_type => $param{meta_type} || 'I5',
-    overwrite => $param{overwrite},
-    foundry   => $param{foundry}   || 'Base',
-    layer     => $param{layer}     || 'Tokens',
-    anno      => $param{anno}      || [[]],
-    log       => $param{log}       || Mojo::Log->new(level => 'fatal'),
-    primary   => $param{primary},
-    pretty    => $param{pretty},
-    gzip      => $param{gzip}      // 0
+    cache           => $param{cache}     // undef,
+    meta_type       => $param{meta_type} || 'I5',
+    overwrite       => $param{overwrite},
+    foundry         => $param{foundry}   || 'Base',
+    layer           => $param{layer}     || 'Tokens',
+    anno            => $param{anno}      || [[]],
+    log             => $param{log}       || Mojo::Log->new(level => 'fatal'),
+    primary         => $param{primary},
+    non_word_tokens => $param{non_word_tokens},
+    pretty          => $param{pretty},
+    gzip            => $param{gzip}      // 0
   }, $class;
 };
 
@@ -54,7 +55,8 @@
     doc => $doc,
     foundry => $self->{foundry},
     layer => $self->{layer},
-    name => 'tokens'
+    name => 'tokens',
+    non_word_tokens => $self->{non_word_tokens}
   );
 
   # Unable to process base tokenization
diff --git a/lib/KorAP/XML/Krill.pm b/lib/KorAP/XML/Krill.pm
index 95e8204..221c5f4 100644
--- a/lib/KorAP/XML/Krill.pm
+++ b/lib/KorAP/XML/Krill.pm
@@ -16,7 +16,7 @@
 use Data::Dumper;
 use File::Spec::Functions qw/catdir catfile catpath splitdir splitpath rel2abs/;
 
-our $VERSION = '0.35';
+our $VERSION = '0.36';
 
 has 'path';
 has [qw/text_sigle doc_sigle corpus_sigle/];
diff --git a/lib/KorAP/XML/Tokenizer.pm b/lib/KorAP/XML/Tokenizer.pm
index fe8ee04..dec20cf 100644
--- a/lib/KorAP/XML/Tokenizer.pm
+++ b/lib/KorAP/XML/Tokenizer.pm
@@ -34,6 +34,7 @@
 
 has [qw/path foundry doc stream should have name/];
 has layer => 'Tokens';
+has non_word_tokens => 0;
 
 has log => sub {
   if (Log::Log4perl->initialized()) {
@@ -131,7 +132,7 @@
     $should++;
 
     # Ignore non-word and non-number tokens (sorry!)
-    if ($token !~ /[\w\d]/) {
+    if (!$self->non_word_tokens && $token !~ /[\w\d]/) {
 
       # TODO: Recognize punctuations!
       #	if ($mtt) {
diff --git a/script/korapxml2krill b/script/korapxml2krill
index 772e430..f1ba9d4 100644
--- a/script/korapxml2krill
+++ b/script/korapxml2krill
@@ -129,9 +129,12 @@
 #
 # 2018/07/19
 # - Preliminary support for HNC.
+#
+# 2019/01/22
+# - Support for non-word tokens.
 # ----------------------------------------------------------
 
-our $LAST_CHANGE = '2018/07/19';
+our $LAST_CHANGE = '2019/01/22';
 our $LOCAL = $FindBin::Bin;
 our $VERSION_MSG = <<"VERSION";
 Version $KorAP::XML::Krill::VERSION - diewald\@ids-mannheim.de - $LAST_CHANGE
@@ -175,6 +178,7 @@
   'pretty|y'    => \(my $pretty),
   'jobs|j=i'    => \(my $jobs),
   'to-tar'      => \(my $to_tar),
+  'non-word-tokens|nwt' => \(my $non_word_tokens),
   'sequential-extraction|se' => \(my $sequential_extraction),
   'cache-size|cs=s'  => \(my $cache_size),
   'cache-delete|cd!' => \(my $cache_delete),
@@ -233,6 +237,11 @@
     $token_base = $config{token};
   };
 
+  # temporary-extract
+  if (!defined($non_word_tokens) && defined $config{'non-word-tokens'}) {
+    $non_word_tokens = $config{'non-word-tokens'};
+  };
+
   # Cache file
   if (!defined($cache_file) && defined $config{cache}) {
     $cache_file = $config{cache};
@@ -322,6 +331,7 @@
 $base_sentences      //= '';
 $base_paragraphs     //= '';
 $base_pagebreaks     //= '';
+$non_word_tokens     //= 0;
 
 $base_sentences  = lc $base_sentences;
 $base_paragraphs = lc $base_paragraphs;
@@ -548,7 +558,8 @@
   log       => $log,
   primary   => $primary,
   pretty    => $pretty,
-  anno      => \@filtered_anno
+  anno      => \@filtered_anno,
+  non_word_tokens => $non_word_tokens
 );
 
 # Get file name based on path information
@@ -1221,6 +1232,13 @@
 This is I<deprecated>.
 
 
+=item B<--non-word-tokens|-nwt>
+
+Tokenize non-word tokens like word tokens (defined as matching
+C</[\d\w]/>). Useful to treat punctuations as tokens.
+
+ Defaults to unset.
+
 =item B<--jobs|-j>
 
 Define the number of concurrent jobs in seperated forks
@@ -1435,7 +1453,7 @@
 
 =head1 COPYRIGHT AND LICENSE
 
-Copyright (C) 2015-2018, L<IDS Mannheim|http://www.ids-mannheim.de/>
+Copyright (C) 2015-2019, L<IDS Mannheim|http://www.ids-mannheim.de/>
 
 Author: L<Nils Diewald|http://nils-diewald.de/>
 
diff --git a/t/script/non_word_tokens.t b/t/script/non_word_tokens.t
new file mode 100644
index 0000000..0eb9e14
--- /dev/null
+++ b/t/script/non_word_tokens.t
@@ -0,0 +1,95 @@
+#/usr/bin/env perl
+use strict;
+use warnings;
+use File::Basename 'dirname';
+use File::Spec::Functions qw/catdir catfile/;
+use File::Temp qw/ :POSIX /;
+use Mojo::File;
+use Mojo::JSON qw/decode_json/;
+use IO::Uncompress::Gunzip;
+use Test::More;
+use Test::Output;
+use Data::Dumper;
+use utf8;
+
+my $f = dirname(__FILE__);
+my $script = catfile($f, '..', '..', 'script', 'korapxml2krill');
+
+my $input = catdir($f, '..', 'corpus', 'WPD', '00001');
+ok(-d $input, 'Input directory found');
+
+my $output = tmpnam();
+my $cache = tmpnam();
+
+ok(!-f $output, 'Output does not exist');
+
+my $call = join(
+  ' ',
+  'perl', $script,
+  '--input' => $input,
+  '--output' => $output,
+  '--cache' => $cache,
+  '-t' => 'OpenNLP#tokens',
+  '-l' => 'INFO'
+);
+
+# Test without compression
+stderr_like(
+  sub {
+    system($call);
+  },
+  qr!The code took!,
+  $call
+);
+
+ok(-f $output, 'Output does exist');
+ok((my $file = Mojo::File->new($output)->slurp), 'Slurp data');
+ok((my $json = decode_json $file), 'decode json');
+is($json->{textSigle}, 'WPD/AAA/00001', 'text sigle');
+is($json->{title}, 'A', 'Title');
+is($json->{data}->{tokenSource}, 'opennlp#tokens', 'Title');
+is($json->{data}->{foundries}, 'base base/paragraphs base/sentences connexor connexor/morpho connexor/phrase connexor/sentences connexor/syntax corenlp corenlp/sentences dereko dereko/structure mate mate/dependency mate/morpho opennlp opennlp/morpho opennlp/sentences treetagger treetagger/morpho treetagger/sentences xip xip/constituency xip/morpho xip/sentences', 'Foundries');
+my $stream = $json->{data}->{stream};
+my $token = $stream->[12];
+is($token->[17], 's:Vokal', 'Token');
+$token = $stream->[13];
+is($token->[23], 's:Der', 'Token');
+
+
+$call = join(
+  ' ',
+  'perl', $script,
+  '--input' => $input,
+  '--output' => $output,
+  '--cache' => $cache,
+  '-t' => 'OpenNLP#tokens',
+  '-l' => 'INFO',
+  '-w' => '',
+  '-nwt' => ''
+);
+
+# Test without compression
+stderr_like(
+  sub {
+    system($call);
+  },
+  qr!The code took!,
+  $call
+);
+
+ok(-f $output, 'Output does exist');
+ok(($file = Mojo::File->new($output)->slurp), 'Slurp data');
+ok(($json = decode_json $file), 'decode json');
+$stream = $json->{data}->{stream};
+$token = $stream->[12];
+is($token->[17], 's:Vokal', 'Token');
+$token = $stream->[13];
+is($token->[7], 's:.', 'Token');
+is($token->[11], 'xip/p:PUNCT', 'Token');
+$token = $stream->[14];
+is($token->[23], 's:Der', 'Token');
+
+
+done_testing;
+
+__END__
diff --git a/t/tokenization.t b/t/tokenization.t
new file mode 100644
index 0000000..4c4ddaa
--- /dev/null
+++ b/t/tokenization.t
@@ -0,0 +1,65 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use utf8;
+use Test::More;
+use JSON::XS;
+
+use File::Basename 'dirname';
+use File::Spec::Functions 'catdir';
+
+sub _t2h {
+  my $string = shift;
+  $string =~ s/^\[\(\d+?-\d+?\)(.+?)\]$/$1/;
+  my %hash = ();
+  foreach (split(qr!\|!, $string)) {
+    $hash{$_} = 1;
+  };
+  return \%hash;
+};
+
+use_ok('KorAP::XML::Krill');
+
+my $path = catdir(dirname(__FILE__), 'corpus/WPD/00001');
+ok(my $doc = KorAP::XML::Krill->new( path => $path ), 'Load Korap::Document');
+like($doc->path, qr!\Q$path\E/$!, 'Path');
+ok($doc->parse, 'Parse document');
+is($doc->text_sigle, 'WPD/AAA/00001', 'ID');
+
+
+# Get tokens
+use_ok('KorAP::XML::Tokenizer');
+
+# Get tokenization
+ok(my $tokens = KorAP::XML::Tokenizer->new(
+  path => $doc->path,
+  doc => $doc,
+  foundry => 'OpenNLP',
+  layer => 'Tokens',
+  name => 'tokens'
+), 'New Tokenizer');
+ok($tokens->parse, 'Parse');
+
+like($tokens->stream->pos(12)->to_string, qr/s:Vokal/);
+like($tokens->stream->pos(13)->to_string, qr/s:Der/);
+
+
+# Get tokenization with non word tokens
+ok($tokens = KorAP::XML::Tokenizer->new(
+  path => $doc->path,
+  doc => $doc,
+  foundry => 'OpenNLP',
+  layer => 'Tokens',
+  name => 'tokens',
+  non_word_tokens => 1
+), 'New Tokenizer');
+ok($tokens->parse, 'Parse');
+
+like($tokens->stream->pos(12)->to_string, qr/s:Vokal/);
+like($tokens->stream->pos(13)->to_string, qr/s:\./);
+like($tokens->stream->pos(14)->to_string, qr/s:Der/);
+
+
+done_testing;
+
+__END__