| 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__ |