| 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 |  | 98 | use strict; | 
|  | 15 |  |  |  |  | 35 |  | 
|  | 15 |  |  |  |  | 2728 |  | 
| 12 | 15 |  |  | 15 |  | 90 | use vars qw(%specialStruct %stdXlatNS); | 
|  | 15 |  |  |  |  | 31 |  | 
|  | 15 |  |  |  |  | 703 |  | 
| 13 |  |  |  |  |  |  |  | 
| 14 | 15 |  |  | 15 |  | 83 | use Image::ExifTool qw(:Utils); | 
|  | 15 |  |  |  |  | 28 |  | 
|  | 15 |  |  |  |  | 1765 |  | 
| 15 | 15 |  |  | 15 |  | 997 | use Image::ExifTool::XMP; | 
|  | 15 |  |  |  |  | 47 |  | 
|  | 15 |  |  |  |  | 75728 |  | 
| 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 | 228 | my ($obj, $delim) = @_; | 
| 69 | 101 |  |  |  |  | 189 | my ($val, $warn, $part); | 
| 70 |  |  |  |  |  |  |  | 
| 71 | 101 | 100 |  |  |  | 496 | if ($$obj =~ s/^\s*\{//) { | 
|  |  | 100 |  |  |  |  |  | 
| 72 | 13 |  |  |  |  | 27 | my %struct; | 
| 73 | 13 |  |  |  |  | 82 | while ($$obj =~ s/^\s*([-\w:]+#?)\s*=//s) { | 
| 74 | 19 |  |  |  |  | 47 | my $tag = $1; | 
| 75 | 19 |  |  |  |  | 57 | my ($v, $w) = InflateStruct($obj, '}'); | 
| 76 | 19 | 50 | 33 |  |  | 48 | $warn = $w if $w and not $warn; | 
| 77 | 19 | 50 |  |  |  | 42 | 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 |  |  |  | 85 | last unless $$obj =~ s/^\s*,//s; | 
| 81 |  |  |  |  |  |  | } | 
| 82 |  |  |  |  |  |  | # eat closing brace and warn if we didn't find one | 
| 83 | 13 | 50 | 33 |  |  | 62 | 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 |  |  |  |  | 28 | $val = \%struct; | 
| 94 |  |  |  |  |  |  | } elsif ($$obj =~ s/^\s*\[//) { | 
| 95 | 5 |  |  |  |  | 8 | my @list; | 
| 96 | 5 |  |  |  |  | 9 | for (;;) { | 
| 97 | 9 |  |  |  |  | 21 | my ($v, $w) = InflateStruct($obj, ']'); | 
| 98 | 9 | 50 | 33 |  |  | 408 | $warn = $w if $w and not $warn; | 
| 99 | 9 | 50 |  |  |  | 20 | return(undef, $warn) unless defined $v; | 
| 100 | 9 |  |  |  |  | 17 | push @list, $v; | 
| 101 | 9 | 100 |  |  |  | 39 | last unless $$obj =~ s/^\s*,//s; | 
| 102 |  |  |  |  |  |  | } | 
| 103 |  |  |  |  |  |  | # eat closing bracket and warn if we didn't find one | 
| 104 | 5 | 50 | 33 |  |  | 31 | $$obj =~ s/^\s*\]//s or $warn or $warn = 'Missing closing bracket for list'; | 
| 105 | 5 |  |  |  |  | 14 | $val = \@list; | 
| 106 |  |  |  |  |  |  | } else { | 
| 107 | 83 |  |  |  |  | 258 | $$obj =~ s/^\s+//s; # remove leading whitespace | 
| 108 |  |  |  |  |  |  | # read scalar up to specified delimiter (or "," if not defined) | 
| 109 | 83 |  |  |  |  | 174 | $val = ''; | 
| 110 | 83 | 100 |  |  |  | 229 | $delim = $delim ? "\\$delim|,|\\||\$" : ',|\\||$'; | 
| 111 | 83 |  |  |  |  | 129 | for (;;) { | 
| 112 | 83 | 50 |  |  |  | 1196 | $$obj =~ s/^(.*?)($delim)//s or last; | 
| 113 | 83 |  |  |  |  | 306 | $val .= $1; | 
| 114 | 83 | 100 |  |  |  | 277 | last unless $2; | 
| 115 | 26 | 50 |  |  |  | 111 | $2 eq '|' or $$obj = $2 . $$obj, last; | 
| 116 | 0 | 0 |  |  |  | 0 | $$obj =~ s/^(.)//s and $val .= $1;  # add escaped character | 
| 117 |  |  |  |  |  |  | } | 
| 118 |  |  |  |  |  |  | } | 
| 119 | 101 |  |  |  |  | 323 | 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 | 14 | my $tag = shift; | 
| 130 | 8 | 50 |  |  |  | 38 | 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 |  |  |  |  | 28 | my ($tg, $langCode) = ($1, lc($2)); | 
| 133 | 8 | 50 |  |  |  | 27 | $langCode .= (length($3) == 3 ? uc($3) : lc($3)) if $3; | 
|  |  | 100 |  |  |  |  |  | 
| 134 | 8 |  |  |  |  | 13 | $langCode =~ tr/_/-/;   # RFC 3066 specifies '-' as a separator | 
| 135 | 8 | 50 |  |  |  | 17 | $langCode = '' if lc($langCode) eq 'x-default'; | 
| 136 | 8 |  |  |  |  | 24 | 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 | 110 | my ($et, $struct, $strTable) = @_; | 
| 183 |  |  |  |  |  |  |  | 
| 184 | 51 |  | 66 |  |  | 175 | my $strName = $$strTable{STRUCT_NAME} || ('XMP ' . RegisterNamespace($strTable)); | 
| 185 | 51 | 0 |  |  |  | 136 | ref $struct eq 'HASH' or return wantarray ? (undef, "Expecting $strName structure") : undef; | 
|  |  | 50 |  |  |  |  |  | 
| 186 |  |  |  |  |  |  |  | 
| 187 | 51 |  |  |  |  | 129 | my ($key, $err, $warn, %copy, $rtnVal, $val); | 
| 188 |  |  |  |  |  |  | Key: | 
| 189 | 51 |  |  |  |  | 169 | foreach $key (keys %$struct) { | 
| 190 | 85 |  |  |  |  | 134 | my $tag = $key; | 
| 191 |  |  |  |  |  |  | # allow trailing '#' to disable print conversion on a per-field basis | 
| 192 | 85 |  |  |  |  | 117 | my ($type, $fieldInfo); | 
| 193 | 85 | 100 |  |  |  | 209 | $type = 'ValueConv' if $tag =~ s/#$//; | 
| 194 | 85 | 50 |  |  |  | 220 | $fieldInfo = $$strTable{$tag} unless $specialStruct{$tag}; | 
| 195 |  |  |  |  |  |  | # fix case of field name if necessary | 
| 196 | 85 | 100 |  |  |  | 160 | unless ($fieldInfo) { | 
| 197 |  |  |  |  |  |  | # (sort in reverse to get lower case (not special) tags first) | 
| 198 | 44 |  |  |  |  | 755 | my ($fix) = reverse sort grep /^$tag$/i, keys %$strTable; | 
| 199 | 44 | 100 | 66 |  |  | 266 | $fieldInfo = $$strTable{$tag = $fix} if $fix and not $specialStruct{$fix}; | 
| 200 |  |  |  |  |  |  | } | 
| 201 | 85 |  |  |  |  | 217 | until (ref $fieldInfo eq 'HASH') { | 
| 202 |  |  |  |  |  |  | # generate wildcard fields on the fly (eg. mwg-rs:Extensions) | 
| 203 | 15 | 100 |  |  |  | 39 | unless ($$strTable{NAMESPACE}) { | 
| 204 | 10 |  |  |  |  | 17 | my ($grp, $tg, $langCode); | 
| 205 | 10 | 100 |  |  |  | 48 | ($grp, $tg) = $tag =~ /^(.+):(.+)/ ? (lc $1, $2) : ('', $tag); | 
| 206 | 10 | 50 |  |  |  | 25 | undef $grp if $grp eq 'XMP'; # (a group of 'XMP' is implied) | 
| 207 | 10 |  |  |  |  | 43 | require Image::ExifTool::TagLookup; | 
| 208 | 10 |  |  |  |  | 32 | my @matches = Image::ExifTool::TagLookup::FindTagInfo($tg); | 
| 209 |  |  |  |  |  |  | # also look for lang-alt tags | 
| 210 | 10 | 100 |  |  |  | 35 | unless (@matches) { | 
| 211 | 3 |  |  |  |  | 10 | ($tg, $langCode) = GetLangCode($tg); | 
| 212 | 3 | 50 |  |  |  | 14 | @matches = Image::ExifTool::TagLookup::FindTagInfo($tg) if defined $langCode; | 
| 213 |  |  |  |  |  |  | } | 
| 214 | 10 |  |  |  |  | 20 | my ($tagInfo, $priority, $ti, $g1); | 
| 215 |  |  |  |  |  |  | # find best matching tag | 
| 216 | 10 |  |  |  |  | 51 | foreach $ti (@matches) { | 
| 217 | 28 |  |  |  |  | 62 | my @grps = $et->GetGroup($ti); | 
| 218 | 28 | 100 |  |  |  | 63 | next unless $grps[0] eq 'XMP'; | 
| 219 | 10 | 50 | 66 |  |  | 34 | 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 |  |  | 35 | next if defined $langCode and not ($$ti{Writable} and $$ti{Writable} eq 'lang-alt'); | 
|  |  |  | 66 |  |  |  |  | 
| 222 | 10 |  | 50 |  |  | 28 | my $pri = $$ti{Priority} || 1; | 
| 223 | 10 | 50 |  |  |  | 23 | $pri -= 10 if $$ti{Avoid}; | 
| 224 | 10 | 50 | 33 |  |  | 21 | next if defined $priority and $priority >= $pri; | 
| 225 | 10 |  |  |  |  | 14 | $priority = $pri; | 
| 226 | 10 |  |  |  |  | 12 | $tagInfo = $ti; | 
| 227 | 10 |  |  |  |  | 20 | $g1 = $grps[1]; | 
| 228 |  |  |  |  |  |  | } | 
| 229 | 10 | 50 |  |  |  | 21 | $tagInfo or $warn =  "'${tag}' is not a writable XMP tag", next Key; | 
| 230 | 10 |  |  |  |  | 29 | GetPropertyPath($tagInfo);  # make sure property path is generated for this tag | 
| 231 | 10 |  |  |  |  | 22 | $tag = $$tagInfo{Name}; | 
| 232 | 10 | 100 |  |  |  | 27 | $tag = "$g1:$tag" if $grp; | 
| 233 | 10 | 100 |  |  |  | 20 | $tag .= "-$langCode" if $langCode; | 
| 234 | 10 |  |  |  |  | 17 | $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 |  |  | 107 | 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 |  |  |  |  | 25 | delete $$fieldInfo{Description}; | 
| 244 | 10 |  |  |  |  | 18 | delete $$fieldInfo{Groups}; | 
| 245 | 10 |  |  |  |  | 20 | last; # write this dynamically-generated field | 
| 246 |  |  |  |  |  |  | } | 
| 247 |  |  |  |  |  |  | # generate lang-alt fields on the fly (eg. Iptc4xmpExt:AOTitle) | 
| 248 | 5 |  |  |  |  | 13 | my ($tg, $langCode) = GetLangCode($tag); | 
| 249 | 5 | 50 |  |  |  | 12 | if (defined $langCode) { | 
| 250 | 5 | 50 |  |  |  | 15 | $fieldInfo = $$strTable{$tg} unless $specialStruct{$tg}; | 
| 251 | 5 | 100 |  |  |  | 10 | unless ($fieldInfo) { | 
| 252 | 1 |  |  |  |  | 33 | my ($fix) = reverse sort grep /^$tg$/i, keys %$strTable; | 
| 253 | 1 | 50 | 33 |  |  | 11 | $fieldInfo = $$strTable{$tg = $fix} if $fix and not $specialStruct{$fix}; | 
| 254 |  |  |  |  |  |  | } | 
| 255 | 5 | 50 | 33 |  |  | 33 | if (ref $fieldInfo eq 'HASH' and $$fieldInfo{Writable} and | 
|  |  |  | 33 |  |  |  |  | 
| 256 |  |  |  |  |  |  | $$fieldInfo{Writable} eq 'lang-alt') | 
| 257 |  |  |  |  |  |  | { | 
| 258 | 5 |  |  |  |  | 7 | my $srcInfo = $fieldInfo; | 
| 259 | 5 | 50 |  |  |  | 14 | $tag = $tg . '-' . $langCode if $langCode; | 
| 260 | 5 |  |  |  |  | 7 | $fieldInfo = $$strTable{$tag}; | 
| 261 |  |  |  |  |  |  | # create new structure field if necessary | 
| 262 | 5 | 50 |  |  |  | 29 | $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 |  |  |  | 300 | if (ref $$struct{$key} eq 'HASH') { | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
| 274 | 10 | 50 |  |  |  | 22 | $$fieldInfo{Struct} or $warn = "$tag is not a structure in $strName", next Key; | 
| 275 |  |  |  |  |  |  | # recursively check this structure | 
| 276 | 10 |  |  |  |  | 45 | ($val, $err) = CheckStruct($et, $$struct{$key}, $$fieldInfo{Struct}); | 
| 277 | 10 | 50 |  |  |  | 42 | $err and $warn = $err, next Key; | 
| 278 | 10 |  |  |  |  | 24 | $copy{$tag} = $val; | 
| 279 |  |  |  |  |  |  | } elsif (ref $$struct{$key} eq 'ARRAY') { | 
| 280 | 13 | 50 |  |  |  | 42 | $$fieldInfo{List} or $warn = "$tag is not a list in $strName", next Key; | 
| 281 |  |  |  |  |  |  | # check all items in the list | 
| 282 | 13 |  |  |  |  | 20 | my ($item, @copy); | 
| 283 | 13 |  |  |  |  | 18 | my $i = 0; | 
| 284 | 13 |  |  |  |  | 17 | foreach $item (@{$$struct{$key}}) { | 
|  | 13 |  |  |  |  | 27 |  | 
| 285 | 21 | 100 |  |  |  | 47 | if (not ref $item) { | 
|  |  | 50 |  |  |  |  |  | 
| 286 | 13 | 50 |  |  |  | 25 | $item = '' unless defined $item; # use empty string for missing items | 
| 287 | 13 | 100 |  |  |  | 26 | if ($$fieldInfo{Struct}) { | 
| 288 |  |  |  |  |  |  | # (allow empty structures) | 
| 289 | 2 | 50 |  |  |  | 9 | $item =~ /^\s*$/ or $warn = "$tag items are not valid structures", next Key; | 
| 290 | 2 |  |  |  |  | 4 | $copy[$i] = { }; # create hash for empty structure | 
| 291 |  |  |  |  |  |  | } else { | 
| 292 | 11 |  |  |  |  | 35 | $et->Sanitize(\$item); | 
| 293 | 11 |  |  |  |  | 29 | ($copy[$i],$err) = $et->ConvInv($item,$fieldInfo,$tag,$strName,$type,''); | 
| 294 | 11 | 50 |  |  |  | 30 | $copy[$i] = '' unless defined $copy[$i];    # avoid undefined item | 
| 295 | 11 | 50 |  |  |  | 19 | $err and $warn = $err, next Key; | 
| 296 | 11 |  |  |  |  | 27 | $err = CheckXMP($et, $fieldInfo, \$copy[$i]); | 
| 297 | 11 | 50 |  |  |  | 26 | $err and $warn = "$err in $strName $tag", next Key; | 
| 298 |  |  |  |  |  |  | } | 
| 299 |  |  |  |  |  |  | } elsif (ref $item eq 'HASH') { | 
| 300 | 8 | 50 |  |  |  | 15 | $$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 |  |  |  | 20 | $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 |  |  |  |  | 39 | ++$i; | 
| 308 |  |  |  |  |  |  | } | 
| 309 | 13 |  |  |  |  | 41 | $copy{$tag} = \@copy; | 
| 310 |  |  |  |  |  |  | } elsif ($$fieldInfo{Struct}) { | 
| 311 | 0 |  |  |  |  | 0 | $warn = "Improperly formed structure in $strName $tag"; | 
| 312 |  |  |  |  |  |  | } else { | 
| 313 | 62 |  |  |  |  | 244 | $et->Sanitize(\$$struct{$key}); | 
| 314 | 62 |  |  |  |  | 292 | ($val,$err) = $et->ConvInv($$struct{$key},$fieldInfo,$tag,$strName,$type,''); | 
| 315 | 62 | 50 |  |  |  | 136 | $err and $warn = $err, next Key; | 
| 316 | 62 | 50 |  |  |  | 127 | next Key unless defined $val;   # check for undefined | 
| 317 | 62 |  |  |  |  | 192 | $err = CheckXMP($et, $fieldInfo, \$val); | 
| 318 | 62 | 50 |  |  |  | 153 | $err and $warn = "$err in $strName $tag", next Key; | 
| 319 |  |  |  |  |  |  | # turn this into a list if necessary | 
| 320 | 62 | 100 |  |  |  | 209 | $copy{$tag} = $$fieldInfo{List} ? [ $val ] : $val; | 
| 321 |  |  |  |  |  |  | } | 
| 322 |  |  |  |  |  |  | } | 
| 323 | 51 | 50 | 66 |  |  | 158 | if (%copy or not $warn) { | 
| 324 | 51 |  |  |  |  | 71 | $rtnVal = \%copy; | 
| 325 | 51 |  |  |  |  | 69 | undef $err; | 
| 326 | 51 | 50 |  |  |  | 100 | $$et{CHECK_WARN} = $warn if $warn; | 
| 327 |  |  |  |  |  |  | } else { | 
| 328 | 0 |  |  |  |  | 0 | $err = $warn; | 
| 329 |  |  |  |  |  |  | } | 
| 330 | 51 | 50 |  |  |  | 237 | 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 | 194 |  |  | 194 | 0 | 450 | my ($et, $capture, $pathPt, $nvHash, $changed) = @_; | 
| 343 | 194 |  |  |  |  | 612 | my ($deleted, $added, $existed, $p, $pp, $val, $delPath); | 
| 344 | 194 |  |  |  |  | 0 | my (@structPaths, @matchingPaths, @delPaths); | 
| 345 |  |  |  |  |  |  |  | 
| 346 |  |  |  |  |  |  | # find all existing elements belonging to this structure | 
| 347 | 194 |  |  |  |  | 1016 | ($pp = $$pathPt) =~ s/ \d+/ \\d\+/g; | 
| 348 | 194 |  |  |  |  | 10297 | @structPaths = sort grep(/^$pp(\/|$)/, keys %$capture); | 
| 349 | 194 | 100 |  |  |  | 984 | $existed = 1 if @structPaths; | 
| 350 |  |  |  |  |  |  | # delete only structures with matching fields if necessary | 
| 351 | 194 | 100 |  |  |  | 869 | if ($$nvHash{DelValue}) { | 
|  |  | 100 |  |  |  |  |  | 
| 352 | 4 | 50 |  |  |  | 10 | if (@{$$nvHash{DelValue}}) { | 
|  | 4 |  |  |  |  | 14 |  | 
| 353 | 4 |  |  |  |  | 11 | 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 |  |  |  |  | 6 | foreach $val (@{$$nvHash{DelValue}}) { | 
|  | 4 |  |  |  |  | 12 |  | 
| 357 | 4 | 50 |  |  |  | 19 | 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 |  |  |  |  | 7 | @delPaths = @structPaths;   # delete all | 
| 396 | 2 |  |  |  |  | 20 | $structPaths[0] =~ /^($pp)/; | 
| 397 | 2 |  |  |  |  | 9 | $delPath = $1; | 
| 398 |  |  |  |  |  |  | } | 
| 399 | 194 | 100 |  |  |  | 878 | if (@delPaths) { | 
|  |  | 100 |  |  |  |  |  | 
| 400 | 2 |  |  |  |  | 14 | my $verbose = $et->Options('Verbose'); | 
| 401 | 2 | 50 |  |  |  | 9 | @delPaths = sort @delPaths if $verbose > 1; | 
| 402 | 2 |  |  |  |  | 6 | foreach $p (@delPaths) { | 
| 403 | 6 | 50 |  |  |  | 15 | 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 |  |  |  |  | 21 | delete $$capture{$p}; | 
| 409 | 6 |  |  |  |  | 8 | $deleted = 1; | 
| 410 | 6 |  |  |  |  | 11 | ++$$changed; | 
| 411 |  |  |  |  |  |  | } | 
| 412 | 2 | 50 |  |  |  | 8 | $delPath or warn("Internal error 1 in DeleteStruct\n"), return(undef,undef,$existed); | 
| 413 | 2 |  |  |  |  | 6 | $$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 | 143 | 100 |  |  |  | 363 | if (@structPaths) { | 
| 417 | 1 | 50 |  |  |  | 16 | $structPaths[-1] =~ /^($pp)/ or warn("Internal error 2 in DeleteStruct\n"), return(undef,undef,$existed); | 
| 418 | 1 |  |  |  |  | 5 | 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 |  |  |  | 7 | $path =~ m/.* (\d+)/g or warn("Internal error 3 in DeleteStruct\n"), return(undef,undef,$existed); | 
| 428 | 1 |  |  |  |  | 4 | $added = $1; | 
| 429 |  |  |  |  |  |  | # add after last item in list | 
| 430 | 1 |  |  |  |  | 3 | my $len = length $added; | 
| 431 | 1 |  |  |  |  | 2 | 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 |  |  |  |  | 3 | $$pathPt = $path; | 
| 435 |  |  |  |  |  |  | } else { | 
| 436 | 142 |  |  |  |  | 304 | $added = '10'; | 
| 437 |  |  |  |  |  |  | } | 
| 438 |  |  |  |  |  |  | } | 
| 439 | 194 |  |  |  |  | 773 | 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 | 148 | my ($et, $tagInfo, $capture, $path, $valPtr, $langIdx) = @_; | 
| 449 | 71 |  |  |  |  | 166 | my $val = EscapeXML($$valPtr); | 
| 450 | 71 |  |  |  |  | 99 | my %attrs; | 
| 451 |  |  |  |  |  |  | # support writing RDF "resource" values | 
| 452 | 71 | 100 |  |  |  | 161 | if ($$tagInfo{Resource}) { | 
| 453 | 2 |  |  |  |  | 11 | $attrs{'rdf:resource'} = $val; | 
| 454 | 2 |  |  |  |  | 5 | $val = ''; | 
| 455 |  |  |  |  |  |  | } | 
| 456 | 71 | 100 | 100 |  |  | 226 | if ($$tagInfo{Writable} and $$tagInfo{Writable} eq 'lang-alt') { | 
| 457 |  |  |  |  |  |  | # write the lang-alt tag | 
| 458 | 20 |  |  |  |  | 33 | my $langCode = $$tagInfo{LangCode}; | 
| 459 |  |  |  |  |  |  | # add indexed lang-alt list properties | 
| 460 | 20 |  | 100 |  |  | 50 | my $i = $$langIdx{$path} || 0; | 
| 461 | 20 |  |  |  |  | 87 | $$langIdx{$path} = $i + 1; # save next list index | 
| 462 | 20 | 100 |  |  |  | 37 | if ($i) { | 
| 463 | 8 |  |  |  |  | 16 | my $idx = length($i) . $i; | 
| 464 | 8 |  |  |  |  | 45 | $path =~ s/(.*) \d+/$1 $idx/;   # set list index | 
| 465 |  |  |  |  |  |  | } | 
| 466 | 20 |  | 100 |  |  | 67 | $attrs{'xml:lang'} = $langCode || 'x-default'; | 
| 467 |  |  |  |  |  |  | } | 
| 468 | 71 |  |  |  |  | 235 | $$capture{$path} = [ $val, \%attrs ]; | 
| 469 |  |  |  |  |  |  | # print verbose message | 
| 470 | 71 | 50 | 33 |  |  | 301 | 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 | 147 | my ($et, $tagInfo, $capture, $basePath, $struct, $strTable) = @_; | 
| 487 | 50 | 50 |  |  |  | 221 | my $verbose = $et ? $et->Options('Verbose') : 0; | 
| 488 | 50 |  |  |  |  | 98 | my ($tag, %langIdx); | 
| 489 |  |  |  |  |  |  |  | 
| 490 | 50 |  | 100 |  |  | 142 | my $ns = $$strTable{NAMESPACE} || ''; | 
| 491 | 50 |  |  |  |  | 85 | 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 |  |  |  | 111 | %$struct or $$struct{'~dummy~'} = ''; | 
| 496 |  |  |  |  |  |  |  | 
| 497 | 50 |  |  |  |  | 179 | foreach $tag (sort keys %$struct) { | 
| 498 | 85 |  |  |  |  | 144 | my $fieldInfo = $$strTable{$tag}; | 
| 499 | 85 | 100 |  |  |  | 159 | unless ($fieldInfo) { | 
| 500 | 3 | 50 |  |  |  | 8 | next unless $tag eq '~dummy~'; # check for dummy field | 
| 501 | 3 |  |  |  |  | 7 | $fieldInfo = { }; # create dummy field info for dummy structure | 
| 502 |  |  |  |  |  |  | } | 
| 503 | 85 |  |  |  |  | 147 | my $val = $$struct{$tag}; | 
| 504 | 85 |  |  |  |  | 143 | my $propPath = $$fieldInfo{PropertyPath}; | 
| 505 | 85 | 100 |  |  |  | 159 | unless ($propPath) { | 
| 506 | 37 |  | 66 |  |  | 212 | $propPath = ($$fieldInfo{Namespace} || $ns) . ':' . ($$fieldInfo{TagID} || $tag); | 
|  |  |  | 66 |  |  |  |  | 
| 507 | 37 | 100 |  |  |  | 91 | if ($$fieldInfo{List}) { | 
| 508 | 7 |  |  |  |  | 22 | $propPath .= "/rdf:$$fieldInfo{List}/rdf:li 10"; | 
| 509 |  |  |  |  |  |  | } | 
| 510 | 37 | 100 | 100 |  |  | 115 | if ($$fieldInfo{Writable} and $$fieldInfo{Writable} eq 'lang-alt') { | 
| 511 | 7 |  |  |  |  | 12 | $propPath .= "/rdf:Alt/rdf:li 10"; | 
| 512 |  |  |  |  |  |  | } | 
| 513 | 37 |  |  |  |  | 75 | $$fieldInfo{PropertyPath} = $propPath;  # save for next time | 
| 514 |  |  |  |  |  |  | } | 
| 515 | 85 |  |  |  |  | 224 | my $path = $basePath . '/' . ConformPathToNamespace($et, $propPath); | 
| 516 | 85 |  |  |  |  | 123 | my $addedTag; | 
| 517 | 85 | 100 |  |  |  | 228 | if (ref $val eq 'HASH') { | 
|  |  | 100 |  |  |  |  |  | 
| 518 | 10 | 50 |  |  |  | 25 | my $subStruct = $$fieldInfo{Struct} or next; | 
| 519 | 10 |  |  |  |  | 58 | $changed += AddNewStruct($et, $tagInfo, $capture, $path, $val, $subStruct); | 
| 520 |  |  |  |  |  |  | } elsif (ref $val eq 'ARRAY') { | 
| 521 | 15 | 50 |  |  |  | 33 | next unless $$fieldInfo{List}; | 
| 522 | 15 |  |  |  |  | 21 | my $i = 0; | 
| 523 | 15 |  |  |  |  | 19 | my ($item, $p); | 
| 524 | 15 |  |  |  |  | 57 | 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 |  |  |  |  | 23 |  | 
| 527 | 23 | 100 |  |  |  | 51 | if ($i) { | 
| 528 |  |  |  |  |  |  | # update first index in field property (may be list of lang-alt lists) | 
| 529 | 8 |  |  |  |  | 19 | $p = ConformPathToNamespace($et, $propPath); | 
| 530 | 8 |  |  |  |  | 23 | my $idx = length($i) . $i; | 
| 531 | 8 |  |  |  |  | 43 | $p =~ s/ \d+/ $idx/; | 
| 532 | 8 |  |  |  |  | 22 | $p = "$basePath/$p"; | 
| 533 |  |  |  |  |  |  | } else { | 
| 534 | 15 |  |  |  |  | 20 | $p = $path; | 
| 535 |  |  |  |  |  |  | } | 
| 536 | 23 | 100 | 33 |  |  | 74 | if (ref $item eq 'HASH') { | 
|  |  | 100 | 66 |  |  |  |  | 
| 537 | 10 | 50 |  |  |  | 23 | my $subStruct = $$fieldInfo{Struct} or next; | 
| 538 | 10 | 50 |  |  |  | 23 | 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 |  |  |  |  | 25 | AddNewTag($et, $fieldInfo, $capture, $p, \$item, \%langIdx); | 
| 542 | 11 |  |  |  |  | 17 | $addedTag = 1; | 
| 543 |  |  |  |  |  |  | } | 
| 544 | 23 |  |  |  |  | 27 | ++$changed; | 
| 545 | 23 |  |  |  |  | 47 | ++$i; | 
| 546 |  |  |  |  |  |  | } | 
| 547 |  |  |  |  |  |  | } else { | 
| 548 | 60 |  |  |  |  | 174 | AddNewTag($et, $fieldInfo, $capture, $path, \$val, \%langIdx); | 
| 549 | 60 |  |  |  |  | 79 | $addedTag = 1; | 
| 550 | 60 |  |  |  |  | 90 | ++$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 |  |  | 287 | if ($addedTag and $$fieldInfo{StructType} and $$fieldInfo{Table}) { | 
|  |  |  | 66 |  |  |  |  | 
| 556 | 1 |  |  |  |  | 6 | AddStructType($et, $$fieldInfo{Table}, $capture, $propPath, $basePath); | 
| 557 |  |  |  |  |  |  | } | 
| 558 |  |  |  |  |  |  | } | 
| 559 |  |  |  |  |  |  | # add 'rdf:type' property if necessary | 
| 560 | 50 | 100 | 66 |  |  | 151 | if ($$strTable{TYPE} and $changed) { | 
| 561 | 3 |  |  |  |  | 15 | my $path = $basePath . '/' . ConformPathToNamespace($et, "rdf:type"); | 
| 562 | 3 | 50 |  |  |  | 12 | unless ($$capture{$path}) { | 
| 563 | 3 |  |  |  |  | 17 | $$capture{$path} = [ '', { 'rdf:resource' => $$strTable{TYPE} } ]; | 
| 564 | 3 | 50 |  |  |  | 9 | 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 |  |  |  |  | 158 | 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 | 398 | my ($et, $tagInfo, $value, $type, $parentID) = @_; | 
| 583 | 204 | 100 |  |  |  | 424 | if (ref $value eq 'HASH') { | 
|  |  | 100 |  |  |  |  |  | 
| 584 | 108 |  |  |  |  | 137 | my (%struct, $key); | 
| 585 | 108 |  |  |  |  | 184 | my $table = $$tagInfo{Table}; | 
| 586 | 108 | 100 |  |  |  | 240 | $parentID = $$tagInfo{TagID} unless $parentID; | 
| 587 | 108 |  |  |  |  | 296 | foreach $key (keys %$value) { | 
| 588 | 212 |  |  |  |  | 436 | my $tagID = $parentID . ucfirst($key); | 
| 589 | 212 |  |  |  |  | 360 | my $flatInfo = $$table{$tagID}; | 
| 590 | 212 | 100 |  |  |  | 377 | unless ($flatInfo) { | 
| 591 |  |  |  |  |  |  | # handle variable-namespace structures | 
| 592 | 16 | 100 |  |  |  | 74 | if ($key =~ /^XMP-(.*?:)(.*)/) { | 
| 593 | 13 |  |  |  |  | 40 | $tagID = $1 . $parentID . ucfirst($2); | 
| 594 | 13 |  |  |  |  | 21 | $flatInfo = $$table{$tagID}; | 
| 595 |  |  |  |  |  |  | } | 
| 596 | 16 | 100 |  |  |  | 30 | $flatInfo or $flatInfo = $tagInfo; | 
| 597 |  |  |  |  |  |  | } | 
| 598 | 212 |  |  |  |  | 329 | my $v = $$value{$key}; | 
| 599 | 212 | 100 |  |  |  | 350 | if (ref $v) { | 
| 600 | 48 |  |  |  |  | 95 | $v = ConvertStruct($et, $flatInfo, $v, $type, $tagID); | 
| 601 |  |  |  |  |  |  | } else { | 
| 602 | 164 |  |  |  |  | 410 | $v = $et->GetValue($flatInfo, $type, $v); | 
| 603 |  |  |  |  |  |  | } | 
| 604 | 212 | 50 |  |  |  | 607 | $struct{$key} = $v if defined $v;  # save the converted value | 
| 605 |  |  |  |  |  |  | } | 
| 606 | 108 |  |  |  |  | 311 | return \%struct; | 
| 607 |  |  |  |  |  |  | } elsif (ref $value eq 'ARRAY') { | 
| 608 | 66 | 50 |  |  |  | 155 | 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 |  |  |  |  | 105 | my (@list, $val); | 
| 617 | 66 |  |  |  |  | 118 | foreach $val (@$value) { | 
| 618 | 98 |  |  |  |  | 215 | my $v = ConvertStruct($et, $tagInfo, $val, $type, $parentID); | 
| 619 | 98 | 50 |  |  |  | 255 | push @list, $v if defined $v; | 
| 620 |  |  |  |  |  |  | } | 
| 621 | 66 |  |  |  |  | 171 | return \@list; | 
| 622 |  |  |  |  |  |  | } | 
| 623 |  |  |  |  |  |  | } else { | 
| 624 | 30 |  |  |  |  | 84 | 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 |  |  |  |  | 83 | my ($et, $keepFlat) = @_; | 
| 636 | 28 |  |  |  |  | 63 | my ($key, %structs, %var, %lists, $si, %listKeys, @siList); | 
| 637 | 28 |  |  |  |  | 98 | my $valueHash = $$et{VALUE}; | 
| 638 | 28 |  |  |  |  | 56 | my $fileOrder = $$et{FILE_ORDER}; | 
| 639 | 28 |  |  |  |  | 64 | my $tagExtra = $$et{TAG_EXTRA}; | 
| 640 | 28 |  |  |  |  | 53 | foreach $key (keys %{$$et{TAG_INFO}}) { | 
|  | 28 |  |  |  |  | 591 |  | 
| 641 | 2326 | 100 |  |  |  | 3663 | $$tagExtra{$key} or next; | 
| 642 | 1337 | 100 |  |  |  | 2310 | my $structProps = $$tagExtra{$key}{Struct} or next; | 
| 643 | 329 |  |  |  |  | 471 | delete $$tagExtra{$key}{Struct};    # (don't re-use) | 
| 644 | 329 |  |  |  |  | 508 | my $tagInfo = $$et{TAG_INFO}{$key}; # tagInfo for flattened tag | 
| 645 | 329 |  |  |  |  | 479 | my $table = $$tagInfo{Table}; | 
| 646 | 329 |  |  |  |  | 504 | my $prop = shift @$structProps; | 
| 647 | 329 |  |  |  |  | 487 | my $tag = $$prop[0]; | 
| 648 |  |  |  |  |  |  | # get reference to structure tag (or normal list tag if not a structure) | 
| 649 | 329 | 100 |  |  |  | 632 | my $strInfo = @$structProps ? $$table{$tag} : $tagInfo; | 
| 650 | 329 | 100 |  |  |  | 510 | if ($strInfo) { | 
| 651 | 326 | 50 |  |  |  | 590 | ref $strInfo eq 'HASH' or next; # (just to be safe) | 
| 652 | 326 | 50 | 66 |  |  | 824 | 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 |  |  | 10 | my $g1 = $$table{GROUPS}{0} || 'XMP'; | 
| 662 | 3 |  |  |  |  | 5 | my $name = $tag; | 
| 663 |  |  |  |  |  |  | # tag keys will have a group 1 prefix when coming from import of XML from -X option | 
| 664 | 3 | 50 |  |  |  | 15 | if ($tag =~ /(.+):(.+)/) { | 
| 665 | 3 |  |  |  |  | 5 | my $ns; | 
| 666 | 3 |  |  |  |  | 9 | ($ns, $name) = ($1, $2); | 
| 667 | 3 |  |  |  |  | 7 | $ns =~ s/^XMP-//; # remove leading "XMP-" if it exists because we add it later | 
| 668 | 3 | 50 |  |  |  | 7 | $ns = $stdXlatNS{$ns} if $stdXlatNS{$ns}; | 
| 669 | 3 |  |  |  |  | 7 | $g1 .= "-$ns"; | 
| 670 |  |  |  |  |  |  | } | 
| 671 |  |  |  |  |  |  | $strInfo = { | 
| 672 | 3 |  |  |  |  | 12 | Name => ucfirst $name, | 
| 673 |  |  |  |  |  |  | Groups => { 1 => $g1 }, | 
| 674 |  |  |  |  |  |  | Struct => 'Unknown', | 
| 675 |  |  |  |  |  |  | }; | 
| 676 |  |  |  |  |  |  | # add Struct entry if this is a structure | 
| 677 | 3 | 50 |  |  |  | 7 | if (@$structProps) { | 
|  |  | 0 |  |  |  |  |  | 
| 678 |  |  |  |  |  |  | # this is a structure | 
| 679 | 3 | 50 |  |  |  | 9 | $$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 |  |  |  |  | 8 | AddTagToTable($table, $tag, $strInfo); | 
| 686 |  |  |  |  |  |  | } | 
| 687 |  |  |  |  |  |  | # use strInfo ref for base key to avoid collisions | 
| 688 | 329 |  |  |  |  | 400 | $tag = $strInfo; | 
| 689 | 329 |  |  |  |  | 419 | my $struct = \%structs; | 
| 690 | 329 |  |  |  |  | 533 | my $oldStruct = $structs{$strInfo}; | 
| 691 |  |  |  |  |  |  | # (fyi: 'lang-alt' Writable type will be valid even if tag is not pre-defined) | 
| 692 | 329 |  | 100 |  |  | 768 | my $writable = $$tagInfo{Writable} || ''; | 
| 693 |  |  |  |  |  |  | # walk through the stored structure property information | 
| 694 |  |  |  |  |  |  | # to rebuild this structure | 
| 695 | 329 |  |  |  |  | 414 | my ($err, $i); | 
| 696 | 329 |  |  |  |  | 366 | for (;;) { | 
| 697 | 579 |  |  |  |  | 768 | my $index = $$prop[1]; | 
| 698 | 579 | 100 | 100 |  |  | 1279 | if ($index and not @$structProps) { | 
| 699 |  |  |  |  |  |  | # ignore this list if it is a simple lang-alt tag | 
| 700 | 216 | 100 |  |  |  | 405 | if ($writable eq 'lang-alt') { | 
| 701 | 90 |  |  |  |  | 126 | pop @$prop; # remove lang-alt index | 
| 702 | 90 | 100 |  |  |  | 212 | undef $index if @$prop < 2; | 
| 703 |  |  |  |  |  |  | } | 
| 704 |  |  |  |  |  |  | # add language code if necessary | 
| 705 | 216 | 100 | 100 |  |  | 459 | if ($$tagInfo{LangCode} and not ref $tag) { | 
| 706 | 24 |  |  |  |  | 53 | $tag = $tag . '-' . $$tagInfo{LangCode}; | 
| 707 |  |  |  |  |  |  | } | 
| 708 |  |  |  |  |  |  | } | 
| 709 | 579 |  |  |  |  | 845 | my $nextStruct = $$struct{$tag}; | 
| 710 | 579 | 100 |  |  |  | 822 | if (defined $index) { | 
| 711 |  |  |  |  |  |  | # the field is a list | 
| 712 | 276 |  |  |  |  | 404 | $index = substr $index, 1;  # remove digit count | 
| 713 | 276 | 100 |  |  |  | 415 | if ($nextStruct) { | 
| 714 | 160 | 50 |  |  |  | 316 | ref $nextStruct eq 'ARRAY' or $err = 2, last; | 
| 715 | 160 |  |  |  |  | 195 | $struct = $nextStruct; | 
| 716 |  |  |  |  |  |  | } else { | 
| 717 | 116 |  |  |  |  | 268 | $struct = $$struct{$tag} = [ ]; | 
| 718 |  |  |  |  |  |  | } | 
| 719 | 276 |  |  |  |  | 416 | $nextStruct = $$struct[$index]; | 
| 720 |  |  |  |  |  |  | # descend into multi-dimensional lists | 
| 721 | 276 |  |  |  |  | 519 | 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 |  |  |  | 564 | if (ref $nextStruct eq 'HASH') { | 
|  |  | 100 |  |  |  |  |  | 
| 733 | 61 |  |  |  |  | 75 | $struct = $nextStruct;  # continue building sub-structure | 
| 734 |  |  |  |  |  |  | } elsif (@$structProps) { | 
| 735 | 66 |  |  |  |  | 153 | $lists{$struct} = $struct; | 
| 736 | 66 |  |  |  |  | 135 | $struct = $$struct[$index] = { }; | 
| 737 |  |  |  |  |  |  | } else { | 
| 738 | 149 |  |  |  |  | 284 | $lists{$struct} = $struct; | 
| 739 | 149 |  |  |  |  | 286 | $$struct[$index] = $$valueHash{$key}; | 
| 740 | 149 |  |  |  |  | 213 | last; | 
| 741 |  |  |  |  |  |  | } | 
| 742 |  |  |  |  |  |  | } else { | 
| 743 | 303 | 100 |  |  |  | 504 | if ($nextStruct) { | 
|  |  | 100 |  |  |  |  |  | 
| 744 | 93 | 50 |  |  |  | 185 | ref $nextStruct eq 'HASH' or $err = 3, last; | 
| 745 | 93 |  |  |  |  | 107 | $struct = $nextStruct; | 
| 746 |  |  |  |  |  |  | } elsif (@$structProps) { | 
| 747 | 30 |  |  |  |  | 72 | $struct = $$struct{$tag} = { }; | 
| 748 |  |  |  |  |  |  | } else { | 
| 749 | 180 |  |  |  |  | 427 | $$struct{$tag} = $$valueHash{$key}; | 
| 750 | 180 |  |  |  |  | 259 | last; | 
| 751 |  |  |  |  |  |  | } | 
| 752 |  |  |  |  |  |  | } | 
| 753 | 250 | 50 |  |  |  | 491 | $prop = shift @$structProps or last; | 
| 754 | 250 |  |  |  |  | 348 | $tag = $$prop[0]; | 
| 755 | 250 | 100 |  |  |  | 474 | 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 |  |  |  |  | 53 | my ($ns, $name) = ($1, $2); | 
| 760 | 17 | 100 |  |  |  | 42 | $ns = $stdXlatNS{$ns} if $stdXlatNS{$ns}; | 
| 761 | 17 |  |  |  |  | 47 | $tag = "XMP-$ns:" . ucfirst $name; | 
| 762 |  |  |  |  |  |  | } else { | 
| 763 | 233 |  |  |  |  | 385 | $tag = ucfirst $tag; | 
| 764 |  |  |  |  |  |  | } | 
| 765 |  |  |  |  |  |  | } | 
| 766 | 329 | 50 |  |  |  | 684 | 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 |  |  |  | 289 | 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 |  |  |  |  | 141 | my $k = $listKeys{$oldStruct}; | 
| 781 | 75 | 50 |  |  |  | 130 | if ($k) {   # ($k will be undef for an empty structure) | 
| 782 | 75 | 100 |  |  |  | 156 | if ($k lt $key) { | 
| 783 |  |  |  |  |  |  | # keep lowest file order | 
| 784 | 52 | 100 |  |  |  | 123 | $$fileOrder{$k} = $$fileOrder{$key} if $$fileOrder{$k} > $$fileOrder{$key}; | 
| 785 | 52 |  |  |  |  | 162 | $et->DeleteTag($key); | 
| 786 | 52 |  |  |  |  | 137 | next; | 
| 787 |  |  |  |  |  |  | } | 
| 788 | 23 | 100 |  |  |  | 67 | $$fileOrder{$key} = $$fileOrder{$k} if $$fileOrder{$key} > $$fileOrder{$k}; | 
| 789 | 23 |  |  |  |  | 80 | $et->DeleteTag($k);   # remove tag with greater copy number | 
| 790 |  |  |  |  |  |  | } | 
| 791 |  |  |  |  |  |  | } | 
| 792 |  |  |  |  |  |  | # replace existing value with new list | 
| 793 | 126 |  |  |  |  | 221 | $$valueHash{$key} = $structs{$strInfo}; | 
| 794 | 126 |  |  |  |  | 412 | $listKeys{$structs{$strInfo}} = $key;   # save key for this list tag | 
| 795 |  |  |  |  |  |  | } else { | 
| 796 |  |  |  |  |  |  | # save strInfo ref and file order | 
| 797 | 151 | 100 |  |  |  | 293 | if ($var{$strInfo}) { | 
| 798 |  |  |  |  |  |  | # set file order to just before the first associated flattened tag | 
| 799 | 104 | 100 |  |  |  | 292 | if ($var{$strInfo}[1] > $$fileOrder{$key}) { | 
| 800 | 38 |  |  |  |  | 92 | $var{$strInfo}[1] = $$fileOrder{$key} - 0.5; | 
| 801 |  |  |  |  |  |  | } | 
| 802 |  |  |  |  |  |  | } else { | 
| 803 | 47 |  |  |  |  | 167 | $var{$strInfo} = [ $strInfo, $$fileOrder{$key} - 0.5 ]; | 
| 804 |  |  |  |  |  |  | } | 
| 805 |  |  |  |  |  |  | # preserve original flattened tags if requested | 
| 806 | 151 | 100 |  |  |  | 314 | if ($keepFlat) { | 
| 807 | 81 | 50 |  |  |  | 155 | my $extra = $$tagExtra{$key} or next; | 
| 808 |  |  |  |  |  |  | # restore list behaviour of this flattened tag | 
| 809 | 81 | 100 |  |  |  | 258 | if ($$extra{NoList}) { | 
|  |  | 100 |  |  |  |  |  | 
| 810 | 6 |  |  |  |  | 18 | $$valueHash{$key} = $$extra{NoList}; | 
| 811 | 6 |  |  |  |  | 16 | delete $$extra{NoList}; | 
| 812 |  |  |  |  |  |  | } elsif ($$extra{NoListDel}) { | 
| 813 |  |  |  |  |  |  | # delete this tag since its value was included another list | 
| 814 | 8 |  |  |  |  | 30 | $et->DeleteTag($key); | 
| 815 |  |  |  |  |  |  | } | 
| 816 |  |  |  |  |  |  | } else { | 
| 817 | 70 |  |  |  |  | 165 | $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 |  |  |  |  | 215 | foreach $si (keys %lists) { | 
| 825 | 116 |  | 100 |  |  | 154 | defined $_ or $_ = '' foreach @{$lists{$si}}; | 
|  | 116 |  |  |  |  | 347 |  | 
| 826 |  |  |  |  |  |  | } | 
| 827 |  |  |  |  |  |  | # make a list of all new structures we generated | 
| 828 | 28 |  | 66 |  |  | 248 | $var{$_} and push @siList, $_ foreach keys %structs; | 
| 829 |  |  |  |  |  |  | # save new structures in the same order they were read from file | 
| 830 | 28 |  |  |  |  | 130 | foreach $si (sort { $var{$a}[1] <=> $var{$b}[1] } @siList) { | 
|  | 39 |  |  |  |  | 73 |  | 
| 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 |  |  |  |  | 114 | $key = $var{$si}[0]{Name}; | 
| 834 | 47 |  |  |  |  | 65 | my $found; | 
| 835 | 47 | 50 |  |  |  | 106 | 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 |  |  |  | 99 | unless ($found) { | 
| 844 |  |  |  |  |  |  | # otherwise, generate a new tag for this structure | 
| 845 | 47 |  |  |  |  | 142 | $key = $et->FoundTag($var{$si}[0], ''); | 
| 846 | 47 |  |  |  |  | 114 | $$valueHash{$key} = $structs{$si}; | 
| 847 |  |  |  |  |  |  | } | 
| 848 | 47 |  |  |  |  | 231 | $$fileOrder{$key} = $var{$si}[1]; | 
| 849 |  |  |  |  |  |  | } | 
| 850 |  |  |  |  |  |  | } | 
| 851 |  |  |  |  |  |  |  | 
| 852 |  |  |  |  |  |  |  | 
| 853 |  |  |  |  |  |  | 1;  #end | 
| 854 |  |  |  |  |  |  |  | 
| 855 |  |  |  |  |  |  | __END__ |