File Coverage

blib/lib/App/Greple/msdoc.pm
Criterion Covered Total %
statement 43 71 60.5
branch 5 30 16.6
condition 2 9 22.2
subroutine 12 14 85.7
pod 0 3 0.0
total 62 127 48.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.07
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-fold>
59              
60             Indent and fold long lines.
61             This option requires L command installed.
62              
63             =item B<--indent-mark>=I
64              
65             Set indentation string. Default is C<| >.
66              
67             =back
68              
69             =head1 INSTALL
70              
71             =head2 CPANMINUS
72              
73             cpanm App::Greple::msdoc
74              
75             =head1 SEE ALSO
76              
77             L,
78             L
79              
80             L,
81             L
82              
83             L,
84             L
85              
86             L,
87              
88             L
89             (in Japanese)
90              
91             =head1 AUTHOR
92              
93             Kazumasa Utashiro
94              
95             =head1 LICENSE
96              
97             Copyright 2018-2024 Kazumasa Utashiro.
98              
99             This library is free software; you can redistribute it and/or modify
100             it under the same terms as Perl itself.
101              
102             =cut
103              
104             package App::Greple::msdoc;
105              
106             our $VERSION = '1.07';
107              
108 6     6   262868 use strict;
  6         18  
  6         292  
109 6     6   44 use warnings;
  6         14  
  6         397  
110 6     6   212 use v5.14;
  6         21  
111 6     6   43 use Carp;
  6         75  
  6         561  
112 6     6   570 use utf8;
  6         266  
  6         139  
113 6     6   256 use Encode;
  6         10  
  6         797  
114              
115 6     6   37 use Exporter 'import';
  6         12  
  6         728  
116             our @EXPORT = ();
117             our %EXPORT_TAGS = ();
118             our @EXPORT_OK = ();
119              
120 6     6   593 use App::Greple::Common qw(&FILELABEL);
  6         554  
  6         911  
121 6     6   553 use Data::Dumper;
  6         7696  
  6         4993  
122              
123             our $indent_mark = "| ";
124             our $opt_space = undef;
125             our $opt_separator = undef;
126             our $opt_type;
127             our $default_format = 'text';
128              
129             sub separate_xml {
130 0 0   0 0 0 s{ (?<=>) ([^<]*) }{ $1 ? "\n$1\n" : "\n" }gex;
  0         0  
131             }
132              
133             sub indent_xml {
134 0     0 0 0 my %arg = @_;
135 0 0       0 my $file = delete $arg{&FILELABEL} or die;
136              
137 0         0 my %nonewline = do {
138 0         0 map { $_ => 1 }
139 0         0 map { @{$_->[1]} }
  0         0  
140 0         0 grep { $file =~ $_->[0] } (
  0         0  
141             [ qr/\.doc[xm]$/, [ qw(w:t w:delText w:instrText wp:posOffset) ] ],
142             [ qr/\.ppt[xm]$/, [ qw(a:t) ] ],
143             [ qr/\.xls[xm]$/, [ qw(t v f formula1) ] ],
144             );
145             };
146              
147 0         0 my $level = 0;
148              
149 0         0 s{
150             (?
151             (?
152             < (?[\w:]+) [^>]* />
153             )
154             |
155             (?
156             < (?[\w:]+) [^>]* (?
157             )
158             |
159             (?
160             < / (?[\w:]+) >
161             )
162             )
163             }{
164 0 0 0     0 if (not $+{single} and $nonewline{$+{tag}}) {
165             join("", $+{open} ? $indent_mark x $level : "",
166             $+{mark},
167 0 0       0 $+{close} ? "\n" : "");
    0          
168             }
169             else {
170 0 0       0 $+{close} and $level--;
171 0 0       0 ($indent_mark x ($+{open} ? $level++ : $level)) . $+{mark} . "\n";
172             }
173             }gex;
174             }
175              
176 6     6   4714 use Archive::Zip;
  6         413661  
  6         350  
177 6     6   2852 use App::optex::textconv::msdoc qw(to_text get_list);
  6         32219  
  6         34  
178              
179             my %formatter = (
180             'indent-xml' => \&indent_xml,
181             'separate-xml' => \&separate_xml,
182             );
183              
184             sub extract_content {
185 4     4 0 1496 my %arg = @_;
186 4 50       26 my $file = $arg{&FILELABEL} or die;
187 4 50       40 my $type = ($file =~ /\.((?:doc|xls|ppt)[xm])$/)[0] or die;
188 4   33     13474 my $pid = open(STDIN, '-|') // croak "process fork failed: $!";
189 4         844 binmode STDIN, ':encoding(utf8)';
190 4 100       1382 if ($pid) {
191 2         435 return $pid;
192             }
193 2   33     316 my $format = $arg{format} // $default_format;
194 2 50       46 if ($format eq 'text') {
    0          
195 2         426 print decode 'utf8', to_text($file);
196 2         201241 exit;
197             } elsif ($format =~ /xml$/) {
198 0           my $zip = Archive::Zip->new($file);
199 0           my @xml;
200 0           for my $entry (get_list($zip, $type)) {
201 0 0         my $member = $zip->memberNamed($entry) or next;
202 0 0         my $xml = $member->contents or next;
203 0           push @xml, $xml;
204             }
205 0           my $xml = decode 'utf8', join "\n", @xml;
206 0 0         if (my $sub = $formatter{$format}) {
207 0           $sub->(&FILELABEL => $file) for $xml;
208             }
209 0           print $xml;
210 0           exit;
211             }
212 0           die;
213             }
214              
215             1;
216              
217             __DATA__