File Coverage

blib/lib/Renard/API/MuPDF/mutool/ObjectParser.pm
Criterion Covered Total %
statement 51 94 55.3
branch 9 38 23.6
condition 0 3 0.0
subroutine 12 14 85.7
pod 5 5 100.0
total 77 154 50.6


line stmt bran cond sub pod time code
1 2     2   358568 use Renard::Incunabula::Common::Setup;
  2         12  
  2         19  
2             package Renard::API::MuPDF::mutool::ObjectParser;
3             # ABSTRACT: Parser for the output of C<mutool show>
4             $Renard::API::MuPDF::mutool::ObjectParser::VERSION = '0.006';
5 2     2   17395 use Moo;
  2         7193  
  2         17  
6 2     2   3921 use Renard::Incunabula::Common::Types qw(Str Bool File InstanceOf);
  2         192545  
  2         26  
7 5     2   7829 use Renard::API::MuPDF::mutool::DateObject;
  5         14  
  5         98  
8 5     2   1112 use Regexp::Common;
  5         5648  
  5         53  
9 5     2   340434 use Encode qw(decode encode_utf8);
  2         21463  
  2         171  
10 2     2   17 use utf8;
  2         7  
  2         17  
11              
12             use constant {
13 2         587 TypeNull => 0,
14             TypeString => 1,
15             TypeStringASCII => 2,
16             TypeStringUTF16BE => 3,
17             TypeNumber => 4,
18             TypeBoolean => 5,
19             TypeReference => 6,
20             TypeDictionary => 7,
21             TypeDate => 8,
22             TypeArray => 9,
23 2     2   86 };
  2         4  
24              
25             has filename => (
26             is => 'ro',
27             isa => File,
28             coerce => 1,
29             required => 1,
30             );
31              
32             has string => (
33             is => 'ro',
34             isa => Str,
35             required => 1,
36             );
37              
38             has is_toplevel => (
39             is => 'ro',
40             isa => Bool,
41             default => sub { 1 },
42             );
43              
44 2     2 1 15278 method BUILD(@) {
  2         4  
45 2         8 $self->_parse;
46             };
47              
48 2     2   2 method _parse() {
  2         5  
49 2         8 my $text = $self->string;
50 2         6 chomp($text);
51 2         7 my @lines = split "\n", $text;
52              
53 2 50       6 return unless @lines;
54              
55 2         5 my $id;
56 2 50       7 $id = shift @lines if $self->is_toplevel;
57              
58 2 50       6 if( $lines[0] eq '<<' ) {
59 0         0 my $data = {};
60 0         0 my $line;
61 0         0 while( ">>" ne ($line = shift @lines)) {
62 0 0       0 next unless $line =~ m|^ \s* / (?<Key>\w+) \s+ (?<Value>.*) $|x;
63             $data->{$+{Key}} = Renard::API::MuPDF::mutool::ObjectParser->new(
64             filename => $self->filename,
65             string => $+{Value},
66 0         0 is_toplevel => 0,
67             );
68             }
69              
70 0         0 $self->data( $data );
71 0         0 $self->type( $self->TypeDictionary );
72             } else {
73 2         4 my $scalar = $lines[0];
74 2 50       21 if( $scalar =~ m|^(?<Null>null)$| ) {
    50          
    50          
    50          
    0          
    0          
    0          
    0          
75 0         0 $self->data(undef);
76 0         0 $self->type($self->TypeNull);
77             } elsif( $scalar =~ m|^(?<Id>\d+) 0 R$| ) {
78 0         0 $self->data($+{Id});
79 0         0 $self->type($self->TypeReference);
80             } elsif( $scalar =~ m|^(?<Number>\d+)$| ) {
81 0         0 $self->data($+{Number});
82 0         0 $self->type($self->TypeNumber);
83             } elsif( $scalar =~ m{^(?<Boolean>/True|/False)$} ) {
84 2         19 $self->data($+{Boolean} eq '/True');
85 2         34 $self->type($self->TypeBoolean);
86             } elsif( $scalar =~ /^\((?<String>.*)\)/ ) {
87 0         0 my $string = $+{String};
88 0 0       0 if( $string =~ /^D:/ ) {
89 0         0 $self->data(
90             Renard::API::MuPDF::mutool::DateObject->new(
91             string => $string
92             )
93             );
94 0         0 $self->type($self->TypeDate);
95             } else {
96 0         0 $self->data($self->unescape_ascii_string($string));
97 0         0 $self->type($self->TypeStringASCII);
98             }
99             } elsif( $scalar =~ /^<(?<String>\s*FE\s*FF[^>]*)>/ ) {
100 0         0 $self->data( $self->decode_hex_utf16be( $+{String} ) );
101 0         0 $self->type($self->TypeStringUTF16BE);
102             } elsif( $scalar =~ /^\[ (?<Elements>$RE{list}{-pat => $RE{num}{real}}{-sep=>' '}) \]$/ ) {
103 0         0 $self->data([ split ' ', $+{Elements} ]);
104 0         0 $self->type($self->TypeArray);
105             } elsif( $scalar =~ /^\[/ ) {
106 0         0 $self->data('NOT PARSED');
107 0         0 $self->type($self->TypeArray);
108             } else {
109 0         0 die "unknown PDF type: $scalar"; # uncoverable statement
110             }
111             }
112             }
113              
114 3 50   3 1 4100 classmethod unescape_ascii_string((Str) $pdf_string ) {
  3         7  
  3         12  
  2         9875  
115 2         7 my $new_string = $pdf_string;
116             # TABLE 3.2 Escape sequences in literal strings (pg. 54)
117 2         8 my %map = (
118             'n' => "\n", # Line feed (LF)
119             'r' => "\r", # Carriage return (CR)
120             't' => "\t", # Horizontal tab (HT)
121             'b' => "\b", # Backspace (BS)
122             'f' => "\f", # Form feed (FF)
123             '(' => '(', # Left parenthesis
124             ')' => ')', # Right parenthesis
125             '\\' => '\\', # Backslash
126             );
127              
128 2         18 my $escape_re = qr/
129             (?<Char> \\ [nrtbf()\\] )
130             |
131             (?<Octal> \\ \d{1,3}) # \ddd Character code ddd (octal)
132             /x;
133 2         13 $new_string =~ s/$escape_re/
134             exists $+{Char}
135             ? $map{ substr($+{Char}, 1) }
136 2 50       23 : chr(oct(substr($+{Octal}, 1)))
137             /eg;
138              
139 2         2775 $new_string;
140             }
141              
142 2 0   2 1 9 classmethod decode_hex_utf16be( (Str) $pdf_string ) {
  0            
  0            
  0            
143 0 0         if( $pdf_string =~ /^FE\s*FF/ ) {
144             # it is a UTF-16BE string
145 0           my $string = decode('UTF-16',
146             pack(
147             'H*',
148             # remove strings
149             $pdf_string =~ s/\s+//gr
150             )
151             );
152              
153             # This is a text string, so we can enable the UTF8 flag.
154 0           utf8::upgrade($string);
155              
156 0           return $string;
157             } else {
158             # Possibly PDFDocEncoded string type?
159 0           die "Not a UTF-16BE hex string";
160             }
161             }
162              
163             has data => (
164             is => 'rw',
165             );
166              
167             has type => (
168             is => 'rw',
169             );
170              
171 0 0   0 1   method resolve_key( (Str) $key ) {
  0            
  0            
  0            
172             return unless $self->type == $self->TypeDictionary
173 0 0 0       && exists $self->data->{$key};
174              
175 0           my $value = $self->data->{$key};
176              
177 0           while( $value->type == $self->TypeReference ) {
178 0           $value = $self->new_from_reference( $value );
179             }
180              
181 0           return $value;
182             }
183              
184       0 1   method new_from_reference( (InstanceOf['Renard::API::MuPDF::mutool::ObjectParser']) $ref_obj ) {
185             return unless $ref_obj->type == $self->TypeReference;
186             my $ref_id = $ref_obj->data;
187             $self->new(
188             filename => $self->filename,
189             string => Renard::API::MuPDF::mutool::get_mutool_get_object_raw($self->filename, $ref_id),
190             );
191             }
192              
193              
194             1;
195              
196             __END__
197              
198             =pod
199              
200             =encoding UTF-8
201              
202             =head1 NAME
203              
204             Renard::API::MuPDF::mutool::ObjectParser - Parser for the output of C<mutool show>
205              
206             =head1 VERSION
207              
208             version 0.006
209              
210             =head1 EXTENDS
211              
212             =over 4
213              
214             =item * L<Moo::Object>
215              
216             =back
217              
218             =head1 ATTRIBUTES
219              
220             =head2 filename
221              
222             A required C<File> attribute that represents the location of the PDF file.
223              
224             =head2 string
225              
226             A required C<Str> attribute that represents the raw string output from
227             C<mutool show>.
228              
229             =head2 is_toplevel
230              
231             An optional C<Bool> attribute that tells whether the data is top-level or not.
232             This influences the parsing by removing the header from the C<mutool show>
233             output.
234              
235             Default: C<true>
236              
237             =head2 data
238              
239             A C<Str> containing the parsed data.
240              
241             =head2 type
242              
243             Contains the type parsed in the C<data> attribute. See L</Types> for more
244             information.
245              
246             =head1 CLASS METHODS
247              
248             =head2 unescape_ascii_string
249              
250             classmethod unescape_ascii_string((Str) $pdf_string )
251              
252             A class method that unescapes the escape sequences in a PDF string.
253              
254             Returns a C<Str>.
255              
256             =head2 decode_hex_utf16be
257              
258             classmethod decode_hex_utf16be( (Str) $pdf_string )
259              
260             A class method that decodes data stored in angle brackets.
261              
262             Currently only implements Unicode character encoding for what is called a
263             I<UTF-16BE encoded string with a leading byte order marker> using
264             B<ASCIIHexDecode>:
265              
266             =over 4
267              
268             =item *
269              
270             first two bytes must be the Unicode byte order marker (C<U+FEFF>),
271              
272             =item *
273              
274             one byte per each pair of hex characters (C<< /[0-9A-F]{2}/ >>))
275              
276             =item *
277              
278             whitespace is ignored
279              
280             =back
281              
282             See the following parts of PDF Reference 1.7:
283              
284             =over 4
285              
286             =item *
287              
288             Section 3.3.1 ASCIIHexDecode Filter (pg. 69) and
289              
290             =item *
291              
292             Section 3.8.1 Text String Type (pg. 158)
293              
294             =back
295              
296             Returns a C<Str>.
297              
298             =head1 METHODS
299              
300             =head2 BUILD
301              
302             Initialises the object by parsing the input data.
303              
304             =head2 resolve_key
305              
306             method resolve_key( (Str) $key )
307              
308             A method that follows the reference IDs contained in the data for the until a
309             non-reference type is found.
310              
311             Returns a C<Renard::API::MuPDF::mutool::ObjectParser> instance.
312              
313             =head2 new_from_reference
314              
315             method new_from_reference( (InstanceOf['Renard::API::MuPDF::mutool::ObjectParser']) $ref_obj )
316              
317             Returns an instance of C<Renard::API::MuPDF::mutool::ObjectParser> that
318             follows the reference ID contained inside C<$ref_obj>.
319              
320             =head1 Types
321              
322             TypeNull
323             TypeString
324             TypeStringASCII
325             TypeStringUTF16BE
326             TypeNumber
327             TypeBoolean
328             TypeReference
329             TypeDictionary
330             TypeDate
331             TypeArray
332              
333             The listed types are an enum for the kind of datatypes stored in the C<type>
334             attribute.
335              
336             =begin comment
337              
338             =method _parse
339              
340             A private method that parses the data in the C<string> attribute.
341              
342              
343             =end comment
344              
345             =head1 AUTHOR
346              
347             Project Renard
348              
349             =head1 COPYRIGHT AND LICENSE
350              
351             This software is copyright (c) 2017 by Project Renard.
352              
353             This is free software; you can redistribute it and/or modify it under
354             the same terms as the Perl 5 programming language system itself.
355              
356             =cut