File Coverage

blib/lib/App/Greple/msdoc.pm
Criterion Covered Total %
statement 35 74 47.3
branch 0 30 0.0
condition 0 9 0.0
subroutine 12 15 80.0
pod 0 3 0.0
total 47 131 35.8


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             msdoc - Greple module for access MS office docx/pptx/xlsx documents
4              
5             =head1 VERSION
6              
7             Version 1.06
8              
9             =head1 SYNOPSIS
10              
11             greple -Mmsdoc pattern example.docx
12              
13             =head1 DESCRIPTION
14              
15             This module makes it possible to search string in Microsoft
16             docx/pptx/xlsx file.
17              
18             Microsoft document consists of multiple files archived in zip format.
19             String information is stored in "word/document.xml",
20             "ppt/slides/*.xml" or "xl/sharedStrings.xml". This module extracts
21             these data and replaces the search target.
22              
23             By default, text part from XML data is extracted. This process is
24             done by very simple method and may include redundant information.
25              
26             Strings are simply connected into paragraph for I<.docx> and I<.pptx>
27             document. For I<.xlsx> document, single space is inserted between
28             them. Use B<--separator> option to change this behavior.
29              
30             After every paragraph, single newline is inserted for I<.pptx> and
31             I<.xlsx> file, and double newlines for I<.docx> file. Use
32             B<--space> option to change.
33              
34             =head1 OPTIONS
35              
36             =over 7
37              
38             =item B<--dump>
39              
40             Simply print all converted data. Additional pattern can be specified,
41             and they will be highlighted inside whole text.
42              
43             $ greple -Mmsdoc --dump -e foo -e bar buz.docx
44              
45             =item B<--space>=I
46              
47             Specify number of newlines inserted after every paragraph. Any
48             non-negative integer is allowed including zero.
49              
50             =item B<--separator>=I
51              
52             Specify the separator string placed between each component strings.
53              
54             =item B<--indent>
55              
56             Extract indented XML document, not a plain text.
57              
58             =item B<--indent-mark>=I
59              
60             Set indentation string. Default is C<| >.
61              
62             =back
63              
64             =head1 INSTALL
65              
66             cpanm App::Greple::msdoc
67              
68             =head1 SEE ALSO
69              
70             L
71              
72             L
73              
74             =head1 AUTHOR
75              
76             Kazumasa Utashiro
77              
78             =head1 LICENSE
79              
80             Copyright 2018-2022 Kazumasa Utashiro.
81              
82             This library is free software; you can redistribute it and/or modify
83             it under the same terms as Perl itself.
84              
85             =cut
86              
87             package App::Greple::msdoc;
88              
89             our $VERSION = '1.06';
90              
91 1     1   571 use strict;
  1         2  
  1         21  
92 1     1   4 use warnings;
  1         2  
  1         17  
93 1     1   7 use v5.14;
  1         3  
94 1     1   5 use Carp;
  1         1  
  1         42  
95 1     1   437 use utf8;
  1         10  
  1         4  
96 1     1   429 use Encode;
  1         7348  
  1         51  
97              
98 1     1   5 use Exporter 'import';
  1         2  
  1         43  
99             our @EXPORT = ();
100             our %EXPORT_TAGS = ();
101             our @EXPORT_OK = ();
102              
103 1     1   338 use App::Greple::Common;
  1         226  
  1         79  
104 1     1   421 use Data::Dumper;
  1         4582  
  1         246  
105              
106             our $indent_mark = "| ";
107             our $opt_space = undef;
108             our $opt_separator = undef;
109             our $opt_type;
110             our $default_format = 'text';
111              
112             sub separate_xml {
113 0 0   0 0   s{ (?<=>) ([^<]*) }{ $1 ? "\n$1\n" : "\n" }gex;
  0            
114             }
115              
116             sub indent_xml {
117 0     0 0   my %arg = @_;
118 0 0         my $file = delete $arg{&FILELABEL} or die;
119              
120 0           my %nonewline = do {
121 0           map { $_ => 1 }
122 0           map { @{$_->[1]} }
  0            
123 0           grep { $file =~ $_->[0] } (
  0            
124             [ qr/\.doc[xm]$/, [ qw(w:t w:delText w:instrText wp:posOffset) ] ],
125             [ qr/\.ppt[xm]$/, [ qw(a:t) ] ],
126             [ qr/\.xls[xm]$/, [ qw(t v f formula1) ] ],
127             );
128             };
129              
130 0           my $level = 0;
131              
132 0           s{
133             (?
134             (?
135             < (?[\w:]+) [^>]* />
136             )
137             |
138             (?
139             < (?[\w:]+) [^>]* (?
140             )
141             |
142             (?
143             < / (?[\w:]+) >
144             )
145             )
146             }{
147 1 0 0 1   366 if (not $+{single} and $nonewline{$+{tag}}) {
  1         286  
  1         216  
  0            
148             join("", $+{open} ? $indent_mark x $level : "",
149             $+{mark},
150 0 0         $+{close} ? "\n" : "");
    0          
151             }
152             else {
153 0 0         $+{close} and $level--;
154 0 0         ($indent_mark x ($+{open} ? $level++ : $level)) . $+{mark} . "\n";
155             }
156             }gex;
157             }
158              
159 1     1   539 use Archive::Zip;
  1         53982  
  1         38  
160 1     1   362 use App::optex::textconv::msdoc qw(to_text get_list);
  1         27625  
  1         8  
161              
162             my %formatter = (
163             'indent-xml' => \&indent_xml,
164             'separate-xml' => \&separate_xml,
165             );
166              
167             sub extract_content {
168 0     0 0   my %arg = @_;
169 0 0         my $file = $arg{&FILELABEL} or die;
170 0 0         my $type = ($file =~ /\.((?:doc|xls|ppt)[xm])$/)[0] or die;
171 0   0       my $pid = open(STDIN, '-|') // croak "process fork failed: $!";
172 0           binmode STDIN, ':encoding(utf8)';
173 0 0         if ($pid) {
174 0           return $pid;
175             }
176 0   0       my $format = $arg{format} // $default_format;
177 0 0         if ($format eq 'text') {
    0          
178 0           print decode 'utf8', to_text($file);
179 0           exit;
180             } elsif ($format =~ /xml$/) {
181 0           my $zip = Archive::Zip->new($file);
182 0           my @xml;
183 0           for my $entry (get_list($zip, $type)) {
184 0 0         my $member = $zip->memberNamed($entry) or next;
185 0 0         my $xml = $member->contents or next;
186 0           push @xml, $xml;
187             }
188 0           my $xml = decode 'utf8', join "\n", @xml;
189 0 0         if (my $sub = $formatter{$format}) {
190 0           $sub->(&FILELABEL => $file) for $xml;
191             }
192 0           print $xml;
193 0           exit;
194             }
195 0           die;
196             }
197              
198             1;
199              
200             __DATA__