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