File Coverage

blib/lib/Image/ExifTool/XMPStruct.pl
Criterion Covered Total %
statement 397 536 74.0
branch 237 418 56.7
condition 63 120 52.5
subroutine 12 15 80.0
pod 0 11 0.0
total 709 1100 64.4


line stmt bran cond sub pod time code
1             #------------------------------------------------------------------------------
2             # File: XMPStruct.pl
3             #
4             # Description: XMP structure support
5             #
6             # Revisions: 01/01/2011 - P. Harvey Created
7             #------------------------------------------------------------------------------
8              
9             package Image::ExifTool::XMP;
10              
11 15     15   119 use strict;
  15         37  
  15         827  
12 15     15   106 use vars qw(%specialStruct %stdXlatNS);
  15         35  
  15         1172  
13              
14 15     15   104 use Image::ExifTool qw(:Utils);
  15         37  
  15         3047  
15 15     15   1488 use Image::ExifTool::XMP;
  15         50  
  15         148677  
16              
17             sub SerializeStruct($$;$);
18             sub InflateStruct($$;$);
19             sub DumpStruct($;$);
20             sub CheckStruct($$$);
21             sub AddNewStruct($$$$$$);
22             sub ConvertStruct($$$$;$);
23             sub EscapeJSON($;$);
24              
25             # lookups for JSON characters that we escape specially
26             my %jsonChar = ( '"'=>'"', '\\'=>'\\', "\b"=>'b', "\f"=>'f', "\n"=>'n', "\r"=>'r', "\t"=>'t' );
27             my %jsonEsc = ( '"'=>'"', '\\'=>'\\', 'b'=>"\b", 'f'=>"\f", 'n'=>"\n", 'r'=>"\r", 't'=>"\t" );
28              
29             #------------------------------------------------------------------------------
30             # Serialize a structure (or other object) into a simple string
31             # Inputs: 0) ExifTool ref, 1) HASH ref, ARRAY ref, or SCALAR, 2) closing bracket (or undef)
32             # Returns: serialized structure string (in format specified by StructFormat option)
33             # eg) "{field=text with {braces|}|, and a comma, field2=val2,field3={field4=[a,b]}}"
34             sub SerializeStruct($$;$)
35             {
36 0     0 0 0 my ($et, $obj, $ket) = @_;
37 0         0 my ($key, $val, @vals, $rtnVal);
38 0         0 my $sfmt = $et->Options('StructFormat');
39              
40 0 0       0 if (ref $obj eq 'HASH') {
    0          
    0          
    0          
41             # support hashes with ordered keys
42 0         0 foreach $key (Image::ExifTool::OrderedKeys($obj)) {
43 0 0       0 my $hdr = $sfmt ? EscapeJSON($key) . ':' : $key . '=';
44 0         0 push @vals, $hdr . SerializeStruct($et, $$obj{$key}, '}');
45             }
46 0         0 $rtnVal = '{' . join(',', @vals) . '}';
47             } elsif (ref $obj eq 'ARRAY') {
48 0         0 foreach $val (@$obj) {
49 0         0 push @vals, SerializeStruct($et, $val, ']');
50             }
51 0         0 $rtnVal = '[' . join(',', @vals) . ']';
52             } elsif (defined $obj) {
53 0 0       0 $obj = $$obj if ref $obj eq 'SCALAR';
54             # escape necessary characters in string (closing bracket plus "," and "|")
55 0 0       0 if ($sfmt) {
56 0         0 $rtnVal = EscapeJSON($obj, $sfmt eq 'JSONQ');
57             } else {
58 0 0       0 my $pat = $ket ? "\\$ket|,|\\|" : ',|\\|';
59 0         0 ($rtnVal = $obj) =~ s/($pat)/|$1/g;
60             # also must escape opening bracket or whitespace at start of string
61 0         0 $rtnVal =~ s/^([\s\[\{])/|$1/;
62             }
63             } elsif ($sfmt) {
64 0         0 $rtnVal = 'null';
65             } else {
66 0         0 $rtnVal = ''; # allow undefined list items
67             }
68 0         0 return $rtnVal;
69             }
70              
71             #------------------------------------------------------------------------------
72             # Inflate structure (or other object) from a serialized string
73             # Inputs: 0) ExifTool ref, 1) reference to object in string form
74             # (serialized using the '|' escape, or JSON)
75             # 2) extra delimiter for scalar values delimiters
76             # Returns: 0) object as a SCALAR, HASH ref, or ARRAY ref (or undef on error),
77             # 1) warning string (or undef)
78             # Notes: modifies input string to remove parsed objects
79             sub InflateStruct($$;$)
80             {
81 109     109 0 291 my ($et, $obj, $delim) = @_;
82 109         216 my ($val, $warn, $part);
83 109         509 my $sfmt = $et->Options('StructFormat');
84              
85 109 100       722 if ($$obj =~ s/^\s*\{//) {
    100          
86 13         30 my %struct;
87 13         20 for (;;) {
88 20 50       137 last unless $sfmt ? $$obj =~ s/^\s*"(.*?)"\s*://s :
    100          
89             $$obj =~ s/^\s*([-\w:.]+#?)\s*=//s;
90 19         78 my $tag = $1;
91 19         57 my ($v, $w) = InflateStruct($et, $obj, '}');
92 19 50 33     51 $warn = $w if $w and not $warn;
93 19 50       40 return(undef, $warn) unless defined $v;
94 19         50 $struct{$tag} = $v;
95             # eat comma separator, or all done if there wasn't one
96 19 100       69 last unless $$obj =~ s/^\s*,//s;
97             }
98             # eat closing brace and warn if we didn't find one
99 13 50 33     61 unless ($$obj =~ s/^\s*\}//s or $warn) {
100 0 0       0 if (length $$obj) {
101 0         0 ($part = $$obj) =~ s/^\s*//s;
102 0         0 $part =~ s/[\x0d\x0a].*//s;
103 0 0       0 $part = substr($part,0,27) . '...' if length($part) > 30;
104 0         0 $warn = "Invalid structure field at '${part}'";
105             } else {
106 0         0 $warn = 'Missing closing brace for structure';
107             }
108             }
109 13         31 $val = \%struct;
110             } elsif ($$obj =~ s/^\s*\[//) {
111 5         12 my @list;
112 5         10 for (;;) {
113 9         31 my ($v, $w) = InflateStruct($et, $obj, ']');
114 9 50 33     28 $warn = $w if $w and not $warn;
115 9 50       24 return(undef, $warn) unless defined $v;
116 9         20 push @list, $v;
117 9 100       40 last unless $$obj =~ s/^\s*,//s;
118             }
119             # eat closing bracket and warn if we didn't find one
120 5 50 33     32 $$obj =~ s/^\s*\]//s or $warn or $warn = 'Missing closing bracket for list';
121 5         11 $val = \@list;
122             } else {
123 91         391 $$obj =~ s/^\s+//s; # remove leading whitespace
124 91 50       262 if ($sfmt) {
125 0 0       0 if ($$obj =~ s/^"//) {
    0          
    0          
126 0         0 $val = '';
127 0         0 while ($$obj =~ s/(.*?)"//) {
128 0         0 $val .= $1;
129 0 0 0     0 last unless $val =~ /([\\]+)$/ and length($1) & 0x01;
130 0         0 substr($val, -1, 1) = '"'; # (was an escaped quote)
131             }
132 0 0       0 if ($val =~ s/^base64://) {
133 0         0 $val = DecodeBase64($val);
134             } else {
135             # un-escape characters in JSON string
136 0 0       0 $val =~ s/\\(.)/$jsonEsc{$1}||'\\'.$1/egs;
  0         0  
137             }
138             } elsif ($$obj =~ s/^(true|false)\b//) {
139 0         0 $val = '"' . ucfirst($1) . '"';
140             } elsif ($$obj =~ s/^([+-]?(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?)//) {
141 0         0 $val = $1;
142             } else {
143 0 0       0 $warn or $warn = 'Unknown JSON object';
144 0         0 $val = '""';
145             }
146             } else {
147             # read scalar up to specified delimiter (or "," if not defined)
148 91 100       394 $delim = $delim ? "\\$delim|,|\\||\$" : ',|\\||$';
149 91         185 $val = '';
150 91         183 for (;;) {
151 91 50       1977 $$obj =~ s/^(.*?)($delim)//s or last;
152 91         387 $val .= $1;
153 91 100       407 last unless $2;
154 27 50       141 $2 eq '|' or $$obj = $2 . $$obj, last;
155 0 0       0 $$obj =~ s/^(.)//s and $val .= $1; # add escaped character
156             }
157             }
158             }
159 109         489 return($val, $warn);
160             }
161              
162             #------------------------------------------------------------------------------
163             # Escape string for JSON
164             # Inputs: 0) string, 1) flag to force numbers to be quoted too
165             # Returns: Escaped string (quoted if necessary)
166             sub EscapeJSON($;$)
167             {
168 0     0 0 0 my ($str, $quote) = @_;
169 0 0       0 unless ($quote) {
170 0 0       0 return 'null' unless defined $str;
171             # JSON boolean (true or false)
172 0 0       0 return lc($str) if $str =~ /^(true|false)$/i;
173             # JSON number (see json.org for numerical format)
174             # return $str if $str =~ /^-?(\d|[1-9]\d+)(\.\d+)?(e[-+]?\d+)?$/i;
175             # (these big numbers caused problems for some JSON parsers, so be more conservative)
176 0 0       0 return $str if $str =~ /^-?(\d|[1-9]\d{1,14})(\.\d{1,16})?(e[-+]?\d{1,3})?$/i;
177             }
178 0 0       0 return '""' unless defined $str;
179             # encode JSON string in base64 if necessary
180 0 0       0 return '"base64:' . EncodeBase64($str, 1) . '"' if Image::ExifTool::IsUTF8(\$str) < 0;
181             # escape special characters
182 0         0 $str =~ s/(["\t\n\r\\])/\\$jsonChar{$1}/sg;
183 0         0 $str =~ tr/\0//d; # remove all nulls
184             # escape other control characters with \u
185 0         0 $str =~ s/([\0-\x1f])/sprintf("\\u%.4X",ord $1)/sge;
  0         0  
186 0         0 return '"' . $str . '"'; # return the quoted string
187             }
188              
189             #------------------------------------------------------------------------------
190             # Get XMP language code from tag name string
191             # Inputs: 0) tag name string
192             # Returns: 0) separated tag name, 1) language code (in standard case), or '' if
193             # language code was 'x-default', or undef if the tag had no language code
194             sub GetLangCode($)
195             {
196 8     8 0 14 my $tag = shift;
197 8 50       52 if ($tag =~ /^(\w+)[-_]([a-z]{2,3}|[xi])([-_][a-z\d]{2,8}([-_][a-z\d]{1,8})*)?$/i) {
198             # normalize case of language codes
199 8         37 my ($tg, $langCode) = ($1, lc($2));
200 8 50       28 $langCode .= (length($3) == 3 ? uc($3) : lc($3)) if $3;
    100          
201 8         40 $langCode =~ tr/_/-/; # RFC 3066 specifies '-' as a separator
202 8 50       27 $langCode = '' if lc($langCode) eq 'x-default';
203 8         37 return($tg, $langCode);
204             } else {
205 0         0 return($tag, undef);
206             }
207             }
208              
209             #------------------------------------------------------------------------------
210             # Debugging routine to dump a structure, list or scalar
211             # Inputs: 0) scalar, ARRAY ref or HASH ref, 1) indent (or undef)
212             sub DumpStruct($;$)
213             {
214 0     0 0 0 local $_;
215 0         0 my ($obj, $indent) = @_;
216              
217 0 0       0 $indent or $indent = '';
218 0 0       0 if (ref $obj eq 'HASH') {
    0          
219 0         0 print "{\n";
220 0         0 foreach (Image::ExifTool::OrderedKeys($obj)) {
221 0         0 print "$indent $_ = ";
222 0         0 DumpStruct($$obj{$_}, "$indent ");
223             }
224 0         0 print $indent, "},\n";
225             } elsif (ref $obj eq 'ARRAY') {
226 0         0 print "[\n";
227 0         0 foreach (@$obj) {
228 0         0 print "$indent ";
229 0         0 DumpStruct($_, "$indent ");
230             }
231 0         0 print $indent, "],\n",
232             } else {
233 0         0 print "\"$obj\",\n";
234             }
235             }
236              
237             #------------------------------------------------------------------------------
238             # Recursively validate structure fields (tags)
239             # Inputs: 0) ExifTool ref, 1) Structure ref, 2) structure table definition ref
240             # Returns: 0) validated structure ref, 1) error string, or undef on success
241             # Notes:
242             # - fixes field names in structure and applies inverse conversions to values
243             # - copies structure to avoid interdependencies with calling code on referenced values
244             # - handles lang-alt tags, and '#' on field names
245             # - resets UTF-8 flag of SCALAR values
246             # - un-escapes for XML or HTML as per Escape option setting
247             sub CheckStruct($$$)
248             {
249 51     51 0 96 my ($et, $struct, $strTable) = @_;
250              
251 51   66     193 my $strName = $$strTable{STRUCT_NAME} || ('XMP ' . RegisterNamespace($strTable));
252 51 0       134 ref $struct eq 'HASH' or return wantarray ? (undef, "Expecting $strName structure") : undef;
    50          
253              
254 51         77 my ($key, $err, $warn, %copy, $rtnVal, $val);
255             # copy the ordered keys if they exist
256 51 50       141 $copy{_ordered_keys_} = [ ] if $$struct{_ordered_keys_};
257             Key:
258 51         170 foreach $key (Image::ExifTool::OrderedKeys($struct)) {
259 85         121 my $tag = $key;
260             # allow trailing '#' to disable print conversion on a per-field basis
261 85         123 my ($type, $fieldInfo);
262 85 100       285 $type = 'ValueConv' if $tag =~ s/#$//;
263 85 50       258 $fieldInfo = $$strTable{$tag} unless $specialStruct{$tag};
264             # fix case of field name if necessary
265 85 100       222 unless ($fieldInfo) {
266             # (sort in reverse to get lower case (not special) tags first)
267 44         1194 my ($fix) = reverse sort grep /^$tag$/i, keys %$strTable;
268 44 100 66     291 $fieldInfo = $$strTable{$tag = $fix} if $fix and not $specialStruct{$fix};
269             }
270 85         254 until (ref $fieldInfo eq 'HASH') {
271             # generate wildcard fields on the fly (eg. mwg-rs:Extensions)
272 15 100       39 unless ($$strTable{NAMESPACE}) {
273 10         13 my ($grp, $tg, $langCode);
274 10 100       55 ($grp, $tg) = $tag =~ /^(.+):(.+)/ ? (lc $1, $2) : ('', $tag);
275 10 50       20 undef $grp if $grp eq 'XMP'; # (a group of 'XMP' is implied)
276 10         49 require Image::ExifTool::TagLookup;
277 10         36 my @matches = Image::ExifTool::TagLookup::FindTagInfo($tg);
278             # also look for lang-alt tags
279 10 100       27 unless (@matches) {
280 3         13 ($tg, $langCode) = GetLangCode($tg);
281 3 50       14 @matches = Image::ExifTool::TagLookup::FindTagInfo($tg) if defined $langCode;
282             }
283 10         17 my ($tagInfo, $priority, $ti, $g1);
284             # find best matching tag
285 10         17 foreach $ti (@matches) {
286 34         69 my @grps = $et->GetGroup($ti);
287 34 100       64 next unless $grps[0] eq 'XMP';
288 10 50 66     38 next if $grp and $grp ne lc $grps[1];
289             # must be lang-alt tag if we are writing an alternate language
290 10 50 33     51 next if defined $langCode and not ($$ti{Writable} and $$ti{Writable} eq 'lang-alt');
      66        
291 10   50     37 my $pri = $$ti{Priority} || 1;
292 10 50       26 $pri -= 10 if $$ti{Avoid};
293 10 50 33     31 next if defined $priority and $priority >= $pri;
294 10         14 $priority = $pri;
295 10         13 $tagInfo = $ti;
296 10         18 $g1 = $grps[1];
297             }
298 10 50       24 $tagInfo or $warn = "'${tag}' is not a writable XMP tag", next Key;
299 10         39 GetPropertyPath($tagInfo); # make sure property path is generated for this tag
300 10         21 $tag = $$tagInfo{Name};
301 10 100       30 $tag = "$g1:$tag" if $grp;
302 10 100       19 $tag .= "-$langCode" if $langCode;
303 10         22 $fieldInfo = $$strTable{$tag};
304             # create new structure field if necessary
305             $fieldInfo or $fieldInfo = $$strTable{$tag} = {
306             %$tagInfo, # (also copies the necessary TagID and PropertyPath)
307             Namespace => $$tagInfo{Namespace} || $$tagInfo{Table}{NAMESPACE},
308 10 50 33     142 LangCode => $langCode,
309             };
310             # delete stuff we don't need (shouldn't cause harm, but better safe than sorry)
311             # - need to keep StructType and Table in case we need to call AddStructType later
312 10         22 delete $$fieldInfo{Description};
313 10         15 delete $$fieldInfo{Groups};
314 10         26 last; # write this dynamically-generated field
315             }
316             # generate lang-alt fields on the fly (eg. Iptc4xmpExt:AOTitle)
317 5         14 my ($tg, $langCode) = GetLangCode($tag);
318 5 50       15 if (defined $langCode) {
319 5 50       18 $fieldInfo = $$strTable{$tg} unless $specialStruct{$tg};
320 5 100       12 unless ($fieldInfo) {
321 1         42 my ($fix) = reverse sort grep /^$tg$/i, keys %$strTable;
322 1 50 33     13 $fieldInfo = $$strTable{$tg = $fix} if $fix and not $specialStruct{$fix};
323             }
324 5 50 33     41 if (ref $fieldInfo eq 'HASH' and $$fieldInfo{Writable} and
      33        
325             $$fieldInfo{Writable} eq 'lang-alt')
326             {
327 5         9 my $srcInfo = $fieldInfo;
328 5 50       14 $tag = $tg . '-' . $langCode if $langCode;
329 5         10 $fieldInfo = $$strTable{$tag};
330             # create new structure field if necessary
331 5 50       33 $fieldInfo or $fieldInfo = $$strTable{$tag} = {
332             %$srcInfo,
333             TagID => $tg,
334             LangCode => $langCode,
335             };
336 5         12 last; # write this lang-alt field
337             }
338             }
339 0         0 $warn = "'${tag}' is not a field of $strName";
340 0         0 next Key;
341             }
342 85 100       358 if (ref $$struct{$key} eq 'HASH') {
    100          
    50          
343 10 50       28 $$fieldInfo{Struct} or $warn = "$tag is not a structure in $strName", next Key;
344             # recursively check this structure
345 10         44 ($val, $err) = CheckStruct($et, $$struct{$key}, $$fieldInfo{Struct});
346 10 50       27 $err and $warn = $err, next Key;
347 10         30 $copy{$tag} = $val;
348             } elsif (ref $$struct{$key} eq 'ARRAY') {
349 13 50       36 $$fieldInfo{List} or $warn = "$tag is not a list in $strName", next Key;
350             # check all items in the list
351 13         47 my ($item, @copy);
352 13         19 my $i = 0;
353 13         15 foreach $item (@{$$struct{$key}}) {
  13         36  
354 21 100       47 if (not ref $item) {
    50          
355 13 50       28 $item = '' unless defined $item; # use empty string for missing items
356 13 100       35 if ($$fieldInfo{Struct}) {
357             # (allow empty structures)
358 2 50       6 $item =~ /^\s*$/ or $warn = "$tag items are not valid structures", next Key;
359 2         5 $copy[$i] = { }; # create hash for empty structure
360             } else {
361 11         50 $et->Sanitize(\$item);
362 11         54 ($copy[$i],$err) = $et->ConvInv($item,$fieldInfo,$tag,$strName,$type,'');
363 11 50       31 $copy[$i] = '' unless defined $copy[$i]; # avoid undefined item
364 11 50       29 $err and $warn = $err, next Key;
365 11         40 $err = CheckXMP($et, $fieldInfo, \$copy[$i]);
366 11 50       32 $err and $warn = "$err in $strName $tag", next Key;
367             }
368             } elsif (ref $item eq 'HASH') {
369 8 50       16 $$fieldInfo{Struct} or $warn = "$tag is not a structure in $strName", next Key;
370 8         20 ($copy[$i], $err) = CheckStruct($et, $item, $$fieldInfo{Struct});
371 8 50       17 $err and $warn = $err, next Key;
372             } else {
373 0         0 $warn = "Invalid value for $tag in $strName";
374 0         0 next Key;
375             }
376 21         41 ++$i;
377             }
378 13         41 $copy{$tag} = \@copy;
379             } elsif ($$fieldInfo{Struct}) {
380 0         0 $warn = "Improperly formed structure in $strName $tag";
381 0         0 next;
382             } else {
383 62         292 $et->Sanitize(\$$struct{$key});
384 62         272 ($val,$err) = $et->ConvInv($$struct{$key},$fieldInfo,$tag,$strName,$type,'');
385 62 50       139 $err and $warn = $err, next Key;
386 62 50       122 next Key unless defined $val; # check for undefined
387 62         244 $err = CheckXMP($et, $fieldInfo, \$val);
388 62 50       141 $err and $warn = "$err in $strName $tag", next Key;
389             # turn this into a list if necessary
390 62 100       269 $copy{$tag} = $$fieldInfo{List} ? [ $val ] : $val;
391             }
392 85 50       240 push @{$copy{_ordered_keys_}}, $tag if $copy{_ordered_keys_}; # save ordered keys
  0         0  
393             }
394 51 50 66     171 if (%copy or not $warn) {
395 51         83 $rtnVal = \%copy;
396 51         80 undef $err;
397 51 50       128 $$et{CHECK_WARN} = $warn if $warn;
398             } else {
399 0         0 $err = $warn;
400             }
401 51 50       233 return wantarray ? ($rtnVal, $err) : $rtnVal;
402             }
403              
404             #------------------------------------------------------------------------------
405             # Delete matching structures from existing linearized XMP
406             # Inputs: 0) ExifTool ref, 1) capture hash ref, 2) structure path ref,
407             # 3) new value hash ref, 4) reference to change counter
408             # Returns: 0) delete flag, 1) list index of deleted structure if adding to list
409             # 2) flag set if structure existed
410             # Notes: updates path to new base path for structure to be added
411             sub DeleteStruct($$$$$)
412             {
413 217     217 0 662 my ($et, $capture, $pathPt, $nvHash, $changed) = @_;
414 217         632 my ($deleted, $added, $existed, $p, $pp, $val, $delPath);
415 217         0 my (@structPaths, @matchingPaths, @delPaths);
416              
417             # find all existing elements belonging to this structure
418 217         1338 ($pp = $$pathPt) =~ s/ \d+/ \\d\+/g;
419 217         18842 @structPaths = sort grep(/^$pp(\/|$)/, keys %$capture);
420 217 100       1605 $existed = 1 if @structPaths;
421             # delete only structures with matching fields if necessary
422 217 100       950 if ($$nvHash{DelValue}) {
    100          
423 4 50       10 if (@{$$nvHash{DelValue}}) {
  4         15  
424 4         9 my $strTable = $$nvHash{TagInfo}{Struct};
425             # all fields must match corresponding elements in the same
426             # root structure for it to be deleted
427 4         5 foreach $val (@{$$nvHash{DelValue}}) {
  4         12  
428 4 50       17 next unless ref $val eq 'HASH';
429 0         0 my (%cap, $p2, %match);
430 0 0       0 next unless AddNewStruct(undef, undef, \%cap, $$pathPt, $val, $strTable);
431 0         0 foreach $p (keys %cap) {
432 0 0       0 if ($p =~ / /) {
433 0         0 ($p2 = $p) =~ s/ \d+/ \\d\+/g;
434 0         0 @matchingPaths = sort grep(/^$p2$/, @structPaths);
435             } else {
436 0         0 push @matchingPaths, $p;
437             }
438 0         0 foreach $p2 (@matchingPaths) {
439 0 0       0 $p2 =~ /^($pp)/ or next;
440             # language attribute must also match if it exists
441 0         0 my $attr = $cap{$p}[1];
442 0 0       0 if ($$attr{'xml:lang'}) {
443 0         0 my $a2 = $$capture{$p2}[1];
444 0 0 0     0 next unless $$a2{'xml:lang'} and $$a2{'xml:lang'} eq $$attr{'xml:lang'};
445             }
446 0 0 0     0 if ($$capture{$p2} and $$capture{$p2}[0] eq $cap{$p}[0]) {
447             # ($1 contains root path for this structure)
448 0   0     0 $match{$1} = ($match{$1} || 0) + 1;
449             }
450             }
451             }
452 0         0 my $num = scalar(keys %cap);
453 0         0 foreach $p (keys %match) {
454             # do nothing unless all fields matched the same structure
455 0 0       0 next unless $match{$p} == $num;
456             # delete all elements of this structure
457 0         0 foreach $p2 (@structPaths) {
458 0 0       0 push @delPaths, $p2 if $p2 =~ /^$p/;
459             }
460             # remember path of first deleted structure
461 0 0 0     0 $delPath = $p if not $delPath or $delPath gt $p;
462             }
463             }
464             } # (else don't delete anything)
465             } elsif (@structPaths) {
466 2         5 @delPaths = @structPaths; # delete all
467 2         24 $structPaths[0] =~ /^($pp)/;
468 2         11 $delPath = $1;
469             }
470 217 100       841 if (@delPaths) {
    100          
471 2         14 my $verbose = $et->Options('Verbose');
472 2 50       10 @delPaths = sort @delPaths if $verbose > 1;
473 2         6 foreach $p (@delPaths) {
474 6 50       12 if ($verbose > 1) {
475 0         0 my $p2 = $p;
476 0 0       0 $p2 =~ s/^(\w+)/$stdXlatNS{$1} || $1/e;
  0         0  
477 0         0 $et->VerboseValue("- XMP-$p2", $$capture{$p}[0]);
478             }
479 6         16 delete $$capture{$p};
480 6         8 $deleted = 1;
481 6         10 ++$$changed;
482             }
483 2 50       8 $delPath or warn("Internal error 1 in DeleteStruct\n"), return(undef,undef,$existed);
484 2         5 $$pathPt = $delPath; # return path of first element deleted
485             } elsif ($$nvHash{TagInfo}{List}) {
486             # NOTE: we don't yet properly handle lang-alt elements!!!!
487 160 100       346 if (@structPaths) {
488 1 50       16 $structPaths[-1] =~ /^($pp)/ or warn("Internal error 2 in DeleteStruct\n"), return(undef,undef,$existed);
489 1         3 my $path = $1;
490             # delete any improperly formatted xmp
491 1 50       6 if ($$capture{$path}) {
492 0         0 my $cap = $$capture{$path};
493             # an error unless this was an empty structure
494 0 0 0     0 $et->Error("Improperly structured XMP ($path)",1) if ref $cap ne 'ARRAY' or $$cap[0];
495 0         0 delete $$capture{$path};
496             }
497             # (match last index to put in same lang-alt list for Bag of lang-alt items)
498 1 50       7 $path =~ m/.* (\d+)/g or warn("Internal error 3 in DeleteStruct\n"), return(undef,undef,$existed);
499 1         3 $added = $1;
500             # add after last item in list
501 1         3 my $len = length $added;
502 1         2 my $pos = pos($path) - $len;
503 1         3 my $nxt = substr($added, 1) + 1;
504 1         7 substr($path, $pos, $len) = length($nxt) . $nxt;
505 1         3 $$pathPt = $path;
506             } else {
507 159         299 $added = '10';
508             }
509             }
510 217         1040 return($deleted, $added, $existed);
511             }
512              
513             #------------------------------------------------------------------------------
514             # Add new element to XMP capture hash
515             # Inputs: 0) ExifTool ref, 1) TagInfo ref, 2) capture hash ref,
516             # 3) resource path, 4) value ref, 5) hash ref for last used index numbers
517             sub AddNewTag($$$$$$)
518             {
519 71     71 0 173 my ($et, $tagInfo, $capture, $path, $valPtr, $langIdx) = @_;
520 71         279 my $val = EscapeXML($$valPtr);
521 71         140 my %attrs;
522             # support writing RDF "resource" values
523 71 100       203 if ($$tagInfo{Resource}) {
524 2         8 $attrs{'rdf:resource'} = $val;
525 2         4 $val = '';
526             }
527 71 100 100     306 if ($$tagInfo{Writable} and $$tagInfo{Writable} eq 'lang-alt') {
528             # write the lang-alt tag
529 20         40 my $langCode = $$tagInfo{LangCode};
530             # add indexed lang-alt list properties
531 20   100     81 my $i = $$langIdx{$path} || 0;
532 20         71 $$langIdx{$path} = $i + 1; # save next list index
533 20 100       51 if ($i) {
534 8         19 my $idx = length($i) . $i;
535 8         67 $path =~ s/(.*) \d+/$1 $idx/; # set list index
536             }
537 20   100     79 $attrs{'xml:lang'} = $langCode || 'x-default';
538             }
539 71         282 $$capture{$path} = [ $val, \%attrs ];
540             # print verbose message
541 71 50 33     376 if ($et and $et->Options('Verbose') > 1) {
542 0         0 my $p = $path;
543 0 0       0 $p =~ s/^(\w+)/$stdXlatNS{$1} || $1/e;
  0         0  
544 0         0 $et->VerboseValue("+ XMP-$p", $val);
545             }
546             }
547              
548             #------------------------------------------------------------------------------
549             # Add new structure to capture hash for writing
550             # Inputs: 0) ExifTool object ref (or undef for no warnings),
551             # 1) tagInfo ref (or undef if no ExifTool), 2) capture hash ref,
552             # 3) base path, 4) struct ref, 5) struct hash ref
553             # Returns: number of tags changed
554             # Notes: Escapes values for XML
555             sub AddNewStruct($$$$$$)
556             {
557 50     50 0 150 my ($et, $tagInfo, $capture, $basePath, $struct, $strTable) = @_;
558 50 50       243 my $verbose = $et ? $et->Options('Verbose') : 0;
559 50         118 my ($tag, %langIdx);
560              
561 50   100     200 my $ns = $$strTable{NAMESPACE} || '';
562 50         121 my $changed = 0;
563              
564             # add dummy field to allow empty structures (name starts with '~' so it will come
565             # after all valid structure fields, which is necessary when serializing the XMP later)
566 50 100       151 %$struct or $$struct{'~dummy~'} = '';
567              
568 50         176 foreach $tag (Image::ExifTool::OrderedKeys($struct)) {
569 85         214 my $fieldInfo = $$strTable{$tag};
570 85 100       211 unless ($fieldInfo) {
571 3 50       8 next unless $tag eq '~dummy~'; # check for dummy field
572 3         5 $fieldInfo = { }; # create dummy field info for dummy structure
573             }
574 85         195 my $val = $$struct{$tag};
575 85         235 my $propPath = $$fieldInfo{PropertyPath};
576 85 100       246 unless ($propPath) {
577 37   66     225 $propPath = ($$fieldInfo{Namespace} || $ns) . ':' . ($$fieldInfo{TagID} || $tag);
      66        
578 37 100       99 if ($$fieldInfo{List}) {
579 7         23 $propPath .= "/rdf:$$fieldInfo{List}/rdf:li 10";
580             }
581 37 100 100     172 if ($$fieldInfo{Writable} and $$fieldInfo{Writable} eq 'lang-alt') {
582 7         16 $propPath .= "/rdf:Alt/rdf:li 10";
583             }
584 37         95 $$fieldInfo{PropertyPath} = $propPath; # save for next time
585             }
586 85         273 my $path = $basePath . '/' . ConformPathToNamespace($et, $propPath);
587 85         156 my $addedTag;
588 85 100       350 if (ref $val eq 'HASH') {
    100          
589 10 50       38 my $subStruct = $$fieldInfo{Struct} or next;
590 10         66 $changed += AddNewStruct($et, $tagInfo, $capture, $path, $val, $subStruct);
591             } elsif (ref $val eq 'ARRAY') {
592 15 50       67 next unless $$fieldInfo{List};
593 15         29 my $i = 0;
594 15         28 my ($item, $p);
595 15         90 my $level = scalar(() = ($propPath =~ / \d+/g));
596             # loop through all list items (note: can't yet write multi-dimensional lists)
597 15         32 foreach $item (@{$val}) {
  15         39  
598 23 100       51 if ($i) {
599             # update first index in field property (may be list of lang-alt lists)
600 8         59 $p = ConformPathToNamespace($et, $propPath);
601 8         24 my $idx = length($i) . $i;
602 8         57 $p =~ s/ \d+/ $idx/;
603 8         21 $p = "$basePath/$p";
604             } else {
605 15         29 $p = $path;
606             }
607 23 100 33     96 if (ref $item eq 'HASH') {
    100 66        
608 10 50       75 my $subStruct = $$fieldInfo{Struct} or next;
609 10 50       43 AddNewStruct($et, $tagInfo, $capture, $p, $item, $subStruct) or next;
610             # don't write empty items in upper-level list
611             } elsif (length $item or (defined $item and $level == 1)) {
612 11         38 AddNewTag($et, $fieldInfo, $capture, $p, \$item, \%langIdx);
613 11         24 $addedTag = 1;
614             }
615 23         39 ++$changed;
616 23         95 ++$i;
617             }
618             } else {
619 60         219 AddNewTag($et, $fieldInfo, $capture, $path, \$val, \%langIdx);
620 60         175 $addedTag = 1;
621 60         109 ++$changed;
622             }
623             # this is tricky, but we must add the rdf:type for contained structures
624             # in the case that a whole hierarchy was added at once by writing a
625             # flattened tag inside a variable-namespace structure
626 85 50 100     529 if ($addedTag and $$fieldInfo{StructType} and $$fieldInfo{Table}) {
      66        
627 1         5 AddStructType($et, $$fieldInfo{Table}, $capture, $propPath, $basePath);
628             }
629             }
630             # add 'rdf:type' property if necessary
631 50 100 66     196 if ($$strTable{TYPE} and $changed) {
632 3         15 my $path = $basePath . '/' . ConformPathToNamespace($et, "rdf:type");
633 3 50       15 unless ($$capture{$path}) {
634 3         21 $$capture{$path} = [ '', { 'rdf:resource' => $$strTable{TYPE} } ];
635 3 50       12 if ($verbose > 1) {
636 0         0 my $p = $path;
637 0 0       0 $p =~ s/^(\w+)/$stdXlatNS{$1} || $1/e;
  0         0  
638 0         0 $et->VerboseValue("+ XMP-$p", $$strTable{TYPE});
639             }
640             }
641             }
642 50         210 return $changed;
643             }
644              
645             #------------------------------------------------------------------------------
646             # Convert structure field values for printing
647             # Inputs: 0) ExifTool ref, 1) tagInfo ref for structure tag, 2) value,
648             # 3) conversion type: PrintConv, ValueConv or Raw (Both not allowed)
649             # 4) tagID of parent structure (needed only if there was no flattened tag)
650             # Notes: Makes a copy of the hash so any applied escapes won't affect raw values
651             sub ConvertStruct($$$$;$)
652             {
653 204     204 0 488 my ($et, $tagInfo, $value, $type, $parentID) = @_;
654 204 100       667 if (ref $value eq 'HASH') {
    100          
655 108         167 my (%struct, $key);
656 108         237 my $table = $$tagInfo{Table};
657 108 100       275 $parentID = $$tagInfo{TagID} unless $parentID;
658 108 100       307 $struct{_ordered_keys_} = [ ] if $$value{_ordered_keys_};
659 108         351 foreach $key (Image::ExifTool::OrderedKeys($value)) {
660 212         467 my $tagID = $parentID . ucfirst($key);
661 212         501 my $flatInfo = $$table{$tagID};
662 212 100       441 unless ($flatInfo) {
663             # handle variable-namespace structures
664 16 100       85 if ($key =~ /^XMP-(.*?:)(.*)/) {
665 13         62 $tagID = $1 . $parentID . ucfirst($2);
666 13         33 $flatInfo = $$table{$tagID};
667             }
668 16 100       54 $flatInfo or $flatInfo = $tagInfo;
669             }
670 212         440 my $v = $$value{$key};
671 212 100       403 if (ref $v) {
672 48         120 $v = ConvertStruct($et, $flatInfo, $v, $type, $tagID);
673             } else {
674 164         435 $v = $et->GetValue($flatInfo, $type, $v);
675             }
676 212 50       439 if (defined $v) {
677 212         535 $struct{$key} = $v; # save the converted value
678             # maintain ordered keys if necessary
679 212 100       577 push @{$struct{_ordered_keys_}}, $key if $struct{_ordered_keys_};
  4         8  
680             }
681             }
682 108         385 return \%struct;
683             } elsif (ref $value eq 'ARRAY') {
684 66 50       232 if (defined $$et{OPTIONS}{ListItem}) {
685 0         0 my $li = $$et{OPTIONS}{ListItem};
686 0 0       0 return undef unless defined $$value[$li];
687 0         0 undef $$et{OPTIONS}{ListItem}; # only do top-level list
688 0         0 my $val = ConvertStruct($et, $tagInfo, $$value[$li], $type, $parentID);
689 0         0 $$et{OPTIONS}{ListItem} = $li;
690 0         0 return $val;
691             } else {
692 66         109 my (@list, $val);
693 66         135 foreach $val (@$value) {
694 98         273 my $v = ConvertStruct($et, $tagInfo, $val, $type, $parentID);
695 98 50       294 push @list, $v if defined $v;
696             }
697 66         206 return \@list;
698             }
699             } else {
700 30         92 return $et->GetValue($tagInfo, $type, $value);
701             }
702             }
703              
704             #------------------------------------------------------------------------------
705             # Restore XMP structures in extracted information
706             # Inputs: 0) ExifTool object ref, 1) flag to keep original flattened tags
707             # Notes: also restores lists (including multi-dimensional)
708             sub RestoreStruct($;$)
709             {
710 28     28 0 78 local $_;
711 28         98 my ($et, $keepFlat) = @_;
712 28         79 my ($key, %structs, %var, %lists, $si, %listKeys, @siList);
713 28         98 my $valueHash = $$et{VALUE};
714 28         95 my $fileOrder = $$et{FILE_ORDER};
715 28         82 my $tagExtra = $$et{TAG_EXTRA};
716 28         66 foreach $key (keys %{$$et{TAG_INFO}}) {
  28         905  
717 2326 100       6107 my $structProps = $$tagExtra{$key}{Struct} or next;
718 329         583 delete $$tagExtra{$key}{Struct}; # (don't re-use)
719 329         710 my $tagInfo = $$et{TAG_INFO}{$key}; # tagInfo for flattened tag
720 329         709 my $table = $$tagInfo{Table};
721 329         603 my $prop = shift @$structProps;
722 329         688 my $tag = $$prop[0];
723             # get reference to structure tag (or normal list tag if not a structure)
724 329 100       852 my $strInfo = @$structProps ? $$table{$tag} : $tagInfo;
725 329 100       741 if ($strInfo) {
726 326 50       871 ref $strInfo eq 'HASH' or next; # (just to be safe)
727 326 50 66     1119 if (@$structProps and not $$strInfo{Struct}) {
728             # this could happen for invalid XMP containing mixed lists
729             # (or for something like this -- what should we do here?:
730             # test)
731 0 0       0 $et->Warn("$$strInfo{Name} is not a structure!") unless $$et{NO_STRUCT_WARN};
732 0         0 next;
733             }
734             } else {
735             # create new entry in tag table for this structure
736 3   50     12 my $g1 = $$table{GROUPS}{0} || 'XMP';
737 3         4 my $name = $tag;
738             # tag keys will have a group 1 prefix when coming from import of XML from -X option
739 3 50       14 if ($tag =~ /(.+):(.+)/) {
740 3         6 my $ns;
741 3         7 ($ns, $name) = ($1, $2);
742 3         5 $ns =~ s/^XMP-//; # remove leading "XMP-" if it exists because we add it later
743 3 50       10 $ns = $stdXlatNS{$ns} if $stdXlatNS{$ns};
744 3         6 $g1 .= "-$ns";
745             }
746             $strInfo = {
747 3         13 Name => ucfirst $name,
748             Groups => { 1 => $g1 },
749             Struct => 'Unknown',
750             };
751             # add Struct entry if this is a structure
752 3 50       6 if (@$structProps) {
    0          
753             # this is a structure
754 3 50       10 $$strInfo{Struct} = { STRUCT_NAME => 'XMP Unknown' } if @$structProps;
755             } elsif ($$tagInfo{LangCode}) {
756             # this is lang-alt list
757 0         0 $tag = $tag . '-' . $$tagInfo{LangCode};
758 0         0 $$strInfo{LangCode} = $$tagInfo{LangCode};
759             }
760 3         8 AddTagToTable($table, $tag, $strInfo);
761             }
762             # use strInfo ref for base key to avoid collisions
763 329         532 $tag = $strInfo;
764 329         553 my $struct = \%structs;
765 329         723 my $oldStruct = $structs{$strInfo};
766             # (fyi: 'lang-alt' Writable type will be valid even if tag is not pre-defined)
767 329   100     1100 my $writable = $$tagInfo{Writable} || '';
768             # walk through the stored structure property information
769             # to rebuild this structure
770 329         564 my ($err, $i);
771 329         493 for (;;) {
772 579         1037 my $index = $$prop[1];
773 579 100 100     1745 if ($index and not @$structProps) {
774             # ignore this list if it is a simple lang-alt tag
775 216 100       548 if ($writable eq 'lang-alt') {
776 90         189 pop @$prop; # remove lang-alt index
777 90 100       289 undef $index if @$prop < 2;
778             }
779             # add language code if necessary
780 216 100 100     709 if ($$tagInfo{LangCode} and not ref $tag) {
781 24         57 $tag = $tag . '-' . $$tagInfo{LangCode};
782             }
783             }
784 579         1038 my $nextStruct = $$struct{$tag};
785 579 100       1043 if (defined $index) {
786             # the field is a list
787 276         516 $index = substr $index, 1; # remove digit count
788 276 100       546 if ($nextStruct) {
789 160 50       396 ref $nextStruct eq 'ARRAY' or $err = 2, last;
790 160         272 $struct = $nextStruct;
791             } else {
792 116         419 $struct = $$struct{$tag} = [ ];
793             }
794 276         521 $nextStruct = $$struct[$index];
795             # descend into multi-dimensional lists
796 276         706 for ($i=2; $$prop[$i]; ++$i) {
797 0 0       0 if ($nextStruct) {
798 0 0       0 ref $nextStruct eq 'ARRAY' or last;
799 0         0 $struct = $nextStruct;
800             } else {
801 0         0 $lists{$struct} = $struct;
802 0         0 $struct = $$struct[$index] = [ ];
803             }
804 0         0 $nextStruct = $$struct[$index];
805 0         0 $index = substr $$prop[$i], 1;
806             }
807 276 100       717 if (ref $nextStruct eq 'HASH') {
    100          
808 61         103 $struct = $nextStruct; # continue building sub-structure
809             } elsif (@$structProps) {
810 66         176 $lists{$struct} = $struct;
811 66         162 $struct = $$struct[$index] = { };
812             } else {
813 149         366 $lists{$struct} = $struct;
814 149         443 $$struct[$index] = $$valueHash{$key};
815 149         304 last;
816             }
817             } else {
818 303 100       682 if ($nextStruct) {
    100          
819 93 50       251 ref $nextStruct eq 'HASH' or $err = 3, last;
820 93         150 $struct = $nextStruct;
821             } elsif (@$structProps) {
822 30         121 $struct = $$struct{$tag} = { };
823             } else {
824 180         584 $$struct{$tag} = $$valueHash{$key};
825 180         394 last;
826             }
827             }
828 250 50       656 $prop = shift @$structProps or last;
829 250         518 $tag = $$prop[0];
830 250 100       641 if ($tag =~ /(.+):(.+)/) {
831             # tag in variable-namespace tables will have a leading
832             # XMP namespace on the tag name. In this case, add
833             # the corresponding group1 name to the tag ID.
834 17         77 my ($ns, $name) = ($1, $2);
835 17 100       64 $ns = $stdXlatNS{$ns} if $stdXlatNS{$ns};
836 17         88 $tag = "XMP-$ns:" . ucfirst $name;
837             } else {
838 233         471 $tag = ucfirst $tag;
839             }
840             }
841 329 50       945 if ($err) {
    100          
842             # this may happen if we have a structural error in the XMP
843             # (like an improperly contained list for example)
844 0 0       0 unless ($$et{NO_STRUCT_WARN}) {
845 0   0     0 my $ns = $$tagInfo{Namespace} || $$tagInfo{Table}{NAMESPACE} || '';
846 0         0 $et->Warn("Error $err placing $ns:$$tagInfo{TagID} in structure or list", 1);
847             }
848 0 0       0 delete $structs{$strInfo} unless $oldStruct;
849             } elsif ($tagInfo eq $strInfo) {
850             # just a regular list tag (or an empty structure)
851 178 100       408 if ($oldStruct) {
852             # keep tag with lowest numbered key (well, not exactly, since
853             # "Tag (10)" is lt "Tag (2)", but at least "Tag" is lt
854             # everything else, and this is really what we care about)
855 75         194 my $k = $listKeys{$oldStruct};
856 75 50       191 if ($k) { # ($k will be undef for an empty structure)
857 75 100       208 if ($k lt $key) {
858             # keep lowest file order
859 51 100       182 $$fileOrder{$k} = $$fileOrder{$key} if $$fileOrder{$k} > $$fileOrder{$key};
860 51         311 $et->DeleteTag($key);
861 51         216 next;
862             }
863 24 100       94 $$fileOrder{$key} = $$fileOrder{$k} if $$fileOrder{$key} > $$fileOrder{$k};
864 24         112 $et->DeleteTag($k); # remove tag with greater copy number
865             }
866             }
867             # replace existing value with new list
868 127         303 $$valueHash{$key} = $structs{$strInfo};
869 127         606 $listKeys{$structs{$strInfo}} = $key; # save key for this list tag
870             } else {
871             # save strInfo ref and file order
872 151 100       402 if ($var{$strInfo}) {
873             # set file order to just before the first associated flattened tag
874 104 100       403 if ($var{$strInfo}[1] > $$fileOrder{$key}) {
875 28         78 $var{$strInfo}[1] = $$fileOrder{$key} - 0.5;
876             }
877             } else {
878 47         208 $var{$strInfo} = [ $strInfo, $$fileOrder{$key} - 0.5 ];
879             }
880             # preserve original flattened tags if requested
881 151 100       350 if ($keepFlat) {
882 81         175 my $extra = $$tagExtra{$key};
883             # restore list behaviour of this flattened tag
884 81 100       390 if ($$extra{NoList}) {
    100          
885 6         17 $$valueHash{$key} = $$extra{NoList};
886 6         21 delete $$extra{NoList};
887             } elsif ($$extra{NoListDel}) {
888             # delete this tag since its value was included another list
889 8         36 $et->DeleteTag($key);
890             }
891             } else {
892 70         208 $et->DeleteTag($key); # delete the flattened tag
893             }
894             }
895             }
896             # fill in undefined items in lists. In theory, undefined list items should
897             # be fine, but in practice the calling code may not check for this (and
898             # historically this wasn't necessary, so do this for backward compatibility)
899 28         421 foreach $si (keys %lists) {
900 116   100     178 defined $_ or $_ = '' foreach @{$lists{$si}};
  116         452  
901             }
902             # make a list of all new structures we generated
903 28   66     321 $var{$_} and push @siList, $_ foreach keys %structs;
904             # save new structures in the same order they were read from file
905 28         187 foreach $si (sort { $var{$a}[1] <=> $var{$b}[1] } @siList) {
  42         108  
906             # test to see if a tag for this structure has already been generated
907             # (this could happen only if one of the structures in a list was empty)
908 47         151 $key = $var{$si}[0]{Name};
909 47         82 my $found;
910 47 50       137 if ($$valueHash{$key}) {
911 0         0 my @keys = grep /^$key( \(\d+\))?$/, keys %$valueHash;
912 0         0 foreach $key (@keys) {
913 0 0       0 next unless $$valueHash{$key} eq $structs{$si};
914 0         0 $found = 1;
915 0         0 last;
916             }
917             }
918 47 50       117 unless ($found) {
919             # otherwise, generate a new tag for this structure
920 47         187 $key = $et->FoundTag($var{$si}[0], '');
921 47         148 $$valueHash{$key} = $structs{$si};
922             }
923 47         373 $$fileOrder{$key} = $var{$si}[1];
924             }
925             }
926              
927              
928             1; #end
929              
930             __END__