Ignore:
Timestamp:
2021-02-26T19:39:51+13:00 (3 years ago)
Author:
anupama
Message:

Committing the improvements to EmbeddedMetaPlugin's processing of Keywords vs other metadata fields. Keywords were literally stored as arrays of words rather than phrases in PDFs (at least in Diego's sample PDF), whereas other meta fields like Subjects and Creators stored them as arrays of phrases. To get both to work, Kathy updated EXIF to a newer version, to retrieve the actual EXIF values stored in the PDF. And Kathy and Dr Bainbridge came up with a new option that I added called apply_join_before_split_to_metafields that's a regex which can list the metadata fields to apply the join_before_split to and whcih previously always got applied to all metadata fields. Now it's applied to any *Keywords metafields by default, as that's the metafield we have experience of that behaves differently to the others, as it stores by word instead of phrases. Tested on Diego's sample PDF. Diego has double-checked it to works on his sample PDF too, setting the split char to ; and turning on the join_before_split and leaving apply_join_before_split_to_metafields at its default of .*Keywords. File changes are strings.properties for the tooltip, the plugin introducing the option and working with it and Kathy's EXIF updates affecting cpan/File and cpan/Image.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • main/trunk/greenstone2/perllib/cpan/Image/ExifTool/BuildTagLookup.pm

    r24107 r34921  
    3232use Image::ExifTool::Canon;
    3333use Image::ExifTool::Nikon;
    34 
    35 $VERSION = '2.28';
     34use Image::ExifTool::Sony;
     35use Image::ExifTool::Validate;
     36use Image::ExifTool::MacOS;
     37
     38$VERSION = '3.42';
    3639@ISA = qw(Exporter);
    3740
    38 sub NumbersFirst;
    39 
    40 my $numbersFirst = 1;   # set to -1 to sort numbers last
     41sub NumbersFirst($$);
     42sub SortedTagTablekeys($);
     43
     44my $createDate = 'Feb 15, 2005';
     45
     46# global variables to control sorting order of table entries
     47my $numbersFirst = 1;   # set to -1 to sort numbers last, or 2 to put negative numbers last
     48my $caseInsensitive;    # flag to ignore case when sorting tag names
    4149
    4250# list of all tables in plug-in modules
     
    5159};
    5260
    53 my $homePage = 'http://owl.phy.queensu.ca/~phil/exiftool';
     61my $homePage = 'https://exiftool.org';
     62
     63# tweak the ordering of tables in the documentation
     64my %tweakOrder = (
     65  # this    => comes after this
     66  # -------    -----------------
     67    JPEG    => '-',     # JPEG comes first
     68    IPTC    => 'Exif',  # put IPTC after EXIF,
     69    GPS     => 'XMP',   # etc...
     70    Composite => 'Extra',
     71    GeoTiff => 'GPS',
     72    CanonVRD=> 'CanonCustom',
     73    DJI     => 'Casio',
     74    FLIR    => 'DJI',
     75    FujiFilm => 'FLIR',
     76    Kodak   => 'JVC',
     77    Leaf    => 'Kodak',
     78    Minolta => 'Leaf',
     79    Motorola => 'Minolta',
     80    Nikon   => 'Motorola',
     81    NikonCustom => 'Nikon',
     82    NikonCapture => 'NikonCustom',
     83    Nintendo => 'NikonCapture',
     84    Pentax  => 'Panasonic',
     85    SonyIDC => 'Sony',
     86    Unknown => 'SonyIDC',
     87    DNG     => 'Unknown',
     88    PrintIM => 'ICC_Profile',
     89    Vorbis  => 'Ogg',
     90    ID3     => 'PostScript',
     91    MinoltaRaw => 'KyoceraRaw',
     92    KyoceraRaw => 'CanonRaw',
     93    SigmaRaw => 'PanasonicRaw',
     94    Lytro   => 'SigmaRaw',
     95    PhotoMechanic => 'FotoStation',
     96    Microsoft     => 'PhotoMechanic',
     97   'Microsoft::MP'=> 'Microsoft::MP1',
     98    GIMP    => 'Microsoft',
     99   'Nikon::CameraSettingsD300' => 'Nikon::ShotInfoD300b',
     100   'Pentax::LensData' => 'Pentax::LensInfo2',
     101   'Sony::SRF2' => 'Sony::SRF',
     102    DarwinCore => 'AFCP',
     103   'MWG::Regions' => 'MWG::Composite',
     104   'MWG::Keywords' => 'MWG::Regions',
     105   'MWG::Collections' => 'MWG::Keywords',
     106   'GoPro::fdsc' => 'GoPro::KBAT',
     107);
    54108
    55109# list of all recognized Format strings
    56110# (not a complete list, but this is all we use so far)
    57 # (also, formats like "var_X[Y]" are allowed for any valid X)
     111# (also, formats like "var_X[num]" are allowed for any valid X)
    58112my %formatOK = (
    59113    %Image::ExifTool::Exif::formatNumber,
    60114    0 => 1,
    61115    1 => 1,
     116    2 => 1, # (writable for docs only)
    62117    real    => 1,
    63118    integer => 1,
     
    74129    digits   => 1,
    75130    int16uRev => 1,
     131    int32uRev => 1,
    76132    rational32u => 1,
    77133    rational32s => 1,
     134    pstring     => 1,
    78135    var_string  => 1,
    79136    var_int16u  => 1,
    80137    var_pstr32  => 1,
     138    var_ustr32  => 1,
     139    var_ue7     => 1, # (BPG)
    81140    # Matroska
    82141    signed      => 1,
     
    84143    utf8        => 1,
    85144);
    86 
    87 my $caseInsensitive;    # flag to ignore case when sorting tag names
    88145
    89146# Descriptions for the TagNames documentation
     
    107164    ExifTool => q{
    108165The tables listed below give the names of all tags recognized by ExifTool.
     166They contain a total of $$self{COUNT}{'total tags'} tags, with $$self{COUNT}{'unique tag names'} unique tag names.
    109167},
    110168    ExifTool2 => q{
    111 B<Tag ID>, B<Index> or B<Sequence> is given in the first column of each
     169B<Tag ID>, B<Index#> or B<Sequence> is given in the first column of each
    112170table.  A B<Tag ID> is the computer-readable equivalent of a tag name, and
    113 is the identifier that is actually stored in the file.  An B<Index> refers
    114 to the location of a value when found at a fixed position within a data
    115 block, and B<Sequence> gives the order of values for a serial data stream.
     171is the identifier that is actually stored in the file.  B<Index#> refers to
     172the offset of a value when found at a fixed position within a data block
     173(B<#> is the multiplier for calculating a byte offset: B<1>, B<2>, B<4> or
     174B<8>).  These offsets may have a decimal part which is used only to
     175differentiate tags with values stored at the same position.  (Note that
     176writable tags within binary data blocks are not individually deletable,
     177and the usual alternative is to set them to a value of zero.)  B<Sequence>
     178gives the order of values for a serial data stream.
    116179
    117180A B<Tag Name> is the handle by which the information is accessed in
     
    121184question mark (C<?>) after a tag name indicates that the information is
    122185either not understood, not verified, or not very useful -- these tags are
    123 not extracted by ExifTool unless the Unknown (-u) option is enabled.  Be
     186not extracted by ExifTool unless the L<Unknown|../ExifTool.html#Unknown> (-u) option is enabled.  Be
    124187aware that some tag names are different than the descriptions printed out by
    125188default when extracting information with exiftool.  To see the tag names
     
    127190
    128191The B<Writable> column indicates whether the tag is writable by ExifTool.
    129 Anything but an C<N> in this column means the tag is writable.  A C<Y>
     192Anything but a C<no> in this column means the tag is writable.  A C<yes>
    130193indicates writable information that is either unformatted or written using
    131 the existing format.  Other expressions give details about the information
    132 format, and vary depending on the general type of information.  The format
    133 name may be followed by a number in square brackets to indicate the number
    134 of values written, or the number of characters in a fixed-length string
    135 (including a null terminator which is added if required).
     194the existing format.  Other expressions give details about the format of the
     195stored value, and vary depending on the general type of information.  The
     196format name may be followed by a number in square brackets to indicate the
     197number of values written, or the number of characters in a fixed-length
     198string (including a null terminator which is added if required).
    136199
    137200A plus sign (C<+>) after an entry in the B<Writable> column indicates a
    138 "list" tag which supports multiple values and allows individual values to be
    139 added and deleted.  A slash (C</>) indicates an "avoided" tag that is not
    140 created when writing if another same-named tag may be created instead.  To
    141 write these tags, the group should be specified.  A tilde (C<~>) indicates a
    142 tag this is writable only when the print conversion is disabled (by setting
    143 PrintConv to 0, using the -n option, or suffixing the tag name with a C<#>
    144 character).  An exclamation point (C<!>) indicates a tag that is considered
    145 unsafe to write under normal circumstances.  These "unsafe" tags are not set
    146 when calling SetNewValuesFromFile() or copied with the exiftool
    147 -tagsFromFile option unless specified explicitly, and care should be taken
    148 when editing them manually since they may affect the way an image is
    149 rendered.  An asterisk (C<*>) indicates a "protected" tag which is not
     201I<List> tag which supports multiple values and allows individual values to
     202be added and deleted.  A slash (C</>) indicates a tag that ExifTool will
     203I<Avoid> when writing.  These will be edited but not created if another
     204same-named tag may be created instead.  To create these tags, the group
     205should be specified.  A tilde (C<~>) indicates a tag this is writable only
     206when the print conversion is disabled (by setting L<PrintConv|../ExifTool.html#PrintConv> to 0, using the
     207-n option, or suffixing the tag name with a C<#> character). An exclamation
     208point (C<!>) indicates a tag that is considered I<Unsafe> to write under
     209normal circumstances.  These tags are not written unless specified
     210explicitly (ie. not when wildcards or "all" are used), and care should be
     211taken when editing them manually since they may affect the way an image is
     212rendered.  An asterisk (C<*>) indicates a I<Protected> tag which is not
    150213writable directly, but is written automatically by ExifTool (often when a
    151 corresponding Composite or Extra tag is written).  A colon (C<:>) indicates
    152 a mandatory tag which may be added automatically when writing.
     214corresponding L<Composite|Image::ExifTool::TagNames/Composite Tags> or
     215L<Extra|Image::ExifTool::TagNames/Extra Tags> tag is written). A colon
     216(C<:>) indicates a I<Mandatory> tag which may be added automatically when
     217writing.  Normally MakerNotes tags may not be deleted individually, but a
     218caret (C<^>) indicates a I<Deletable> MakerNotes tag.
    153219
    154220The HTML version of these tables also lists possible B<Values> for
    155221discrete-valued tags, as well as B<Notes> for some tags.  The B<Values> are
    156 listed as the computer-readable and human-readable values on the left and
    157 right hand side of an equals sign (C<=>) respectively.  The human-readable
     222listed with the computer-readable values on the left of the equals sign
     223(C<=>), and the human-readable values on the right.  The human-readable
    158224values are used by default when reading and writing, but the
    159225computer-readable values may be accessed by disabling the value conversion
    160 with the -n option on the command line, by setting the ValueConv option to 0
    161 in the API, or or on a per-tag basis by appending a number symbol (C<#>) to
    162 the tag name.
     226with the -n option on the command line, by setting the L<PrintConv|../ExifTool.html#PrintConv> option to 0
     227in the API, or or on a per-tag basis by adding a hash (C<#>) after the tag
     228name.
    163229
    164230B<Note>: If you are familiar with common meta-information tag names, you may
     
    168234documentation or run C<exiftool -s> on a file containing the information in
    169235question.
     236
     237I<(This documentation is the result of years of research, testing and
     238reverse engineering, and is the most complete metadata tag list available
     239anywhere on the internet.  It is provided not only for ExifTool users, but
     240more importantly as a public service to help augment the collective
     241knowledge, and is often used as a primary source of information in the
     242development of other metadata software.  Please help keep this documentation
     243as accurate and complete as possible, and feed any new discoveries back to
     244ExifTool.  A big thanks to everyone who has helped with this so far!)>
    170245},
    171246    EXIF => q{
    172247EXIF stands for "Exchangeable Image File Format".  This type of information
    173248is formatted according to the TIFF specification, and may be found in JPG,
    174 TIFF, PNG, PGF, MIFF, HDP, PSP and XCF images, as well as many TIFF-based
    175 RAW images, and even some AVI and MOV videos.
     249TIFF, PNG, JP2, PGF, MIFF, HDP, PSP and XCF images, as well as many
     250TIFF-based RAW images, and even some AVI and MOV videos.
    176251
    177252The EXIF meta information is organized into different Image File Directories
     
    180255B<Group> listed below is used unless another group is specified.
    181256
     257Mandatory tags (indicated by a colon after the B<Writable> type) may be
     258added automatically with default values when creating a new IFD, and the IFD
     259is removed automatically when deleting tags if only default-valued mandatory
     260tags remain.
     261
    182262The table below lists all EXIF tags.  Also listed are TIFF, DNG, HDP and
    183263other tags which are not part of the EXIF specification, but may co-exist
    184 with EXIF tags in some images.  Tags which are part of the EXIF 2.3
     264with EXIF tags in some images.  Tags which are part of the EXIF 2.32
    185265specification have an underlined B<Tag Name> in the HTML version of this
    186266documentation.  See
    187 L<http://www.cipa.jp/english/hyoujunka/kikaku/pdf/DC-008-2010_E.pdf> for the
    188 official EXIF 2.3 specification.
     267L<https://web.archive.org/web/20190624045241if_/http://www.cipa.jp:80/std/documents/e/DC-008-Translation-2019-E.pdf>
     268for the official EXIF 2.32 specification.
    189269},
    190270    GPS => q{
     
    201281indicated string lengths include a null terminator which is added
    202282automatically by ExifTool.  Remember that the descriptive values are used
    203 when writing (ie. 'Above Sea Level', not '0') unless the print conversion is
    204 disabled (with '-n' on the command line or the PrintConv option in the API,
     283when writing (eg. 'Above Sea Level', not '0') unless the print conversion is
     284disabled (with '-n' on the command line or the L<PrintConv|../ExifTool.html#PrintConv> option in the API,
    205285or by suffixing the tag name with a C<#> character).
    206286
    207287When adding GPS information to an image, it is important to set all of the
    208288following tags: GPSLatitude, GPSLatitudeRef, GPSLongitude, GPSLongitudeRef,
    209 GPSAltitude and GPSAltitudeRef.  ExifTool will write the required
    210 GPSVersionID tag automatically if new a GPS IFD is added to an image.
     289and GPSAltitude and GPSAltitudeRef if the altitude is known.  ExifTool will
     290write the required GPSVersionID tag automatically if new a GPS IFD is added
     291to an image.
    211292},
    212293    XMP => q{
     
    214295format which is being pushed by Adobe.  Information in this format can be
    215296embedded in many different image file types including JPG, JP2, TIFF, GIF,
    216 EPS, PDF, PSD, IND, PNG, DJVU, SVG, PGF, MIFF, XCF, CRW, DNG and a variety
    217 of proprietary TIFF-based RAW images, as well as MOV, AVI, ASF, WMV, FLV,
    218 SWF and MP4 videos, and WMA and audio formats supporting ID3v2 information.
     297EPS, PDF, PSD, IND, INX, PNG, DJVU, SVG, PGF, MIFF, XCF, CRW, DNG and a
     298variety of proprietary TIFF-based RAW images, as well as MOV, AVI, ASF, WMV,
     299FLV, SWF and MP4 videos, and WMA and audio formats supporting ID3v2
     300information.
    219301
    220302The XMP B<Tag ID>'s aren't listed because in most cases they are identical
     
    229311point number but stored as two C<integer> strings separated by a '/'
    230312character, C<date> is a date/time string entered in the format "YYYY:mm:dd
    231 HH:MM:SS[.ss][+/-HH:MM]", C<boolean> is either "True" or "False",
    232 C<lang-alt> indicates that the tag supports alternate languages (see below),
    233 and C<struct> is an XMP structure.  When reading, structures are extracted
    234 only if the Struct (-struct) option is used.  Otherwise the corresponding
    235 "flattened" tags, indicated by an underline (C<_>) after the B<Writable>
    236 type, are extracted.  When copying information, the Struct option is in
    237 effect by default.  When writing, the Struct option has no effect, and both
    238 structured and flattened tags may be written.  See
    239 L<http://owl.phy.queensu.ca/~phil/exiftool/struct.html> for more details.
     313HH:MM:SS[.ss][+/-HH:MM]", C<boolean> is either "True" or "False" (but "true"
     314and "false" may be written as a ValueConv value for compatibility with
     315non-conforming applications), C<struct> indicates a structured tag, and
     316C<lang-alt> is a tag that supports alternate languages.
     317
     318When reading, C<struct> tags are extracted only if the L<Struct|../ExifTool.html#Struct> (-struct)
     319option is used.  Otherwise the corresponding I<Flattened> tags, indicated by
     320an underline (C<_>) after the B<Writable> type, are extracted.  When
     321copying, by default both structured and flattened tags are available, but
     322the flattened tags are considered "unsafe" so they aren't copied unless
     323specified explicitly.  The L<Struct|../ExifTool.html#Struct> option may be disabled by setting Struct
     324to 0 via the API or with --struct on the command line to copy only flattened
     325tags, or enabled by setting Struct to 1 via the API or with -struct on the
     326command line to copy only as structures.  When writing, the L<Struct|../ExifTool.html#Struct> option
     327has no effect, and both structured and flattened tags may be written.  See
     328L<https://exiftool.org/struct.html> for more details.
    240329
    241330Individual languages for C<lang-alt> tags are accessed by suffixing the tag
    242 name with a '-', followed by an RFC 3066 language code (ie. "XMP:Title-fr",
     331name with a '-', followed by an RFC 3066 language code (eg. "XMP:Title-fr",
    243332or "Rights-en-US").  (See L<http://www.ietf.org/rfc/rfc3066.txt> for the RFC
    2443333066 specification.)  A C<lang-alt> tag with no language code accesses the
    245334"x-default" language, but causes other languages for this tag to be deleted
    246335when writing.  The "x-default" language code may be specified when writing
    247 to preserve other existing languages (ie. "XMP-dc:Description-x-default").
     336to preserve other existing languages (eg. "XMP-dc:Description-x-default").
    248337When reading, "x-default" is not specified.
    249338
    250339The XMP tags are organized according to schema B<Namespace> in the following
    251 tables.  Note that a few of the longer namespace prefixes given below have
    252 been shortened for convenience (since the family 1 group names are derived
    253 from these by adding a leading "XMP-").  In cases where a tag name exists in
    254 more than one namespace, less common namespaces are avoided when writing.
    255 However, any namespace may be written by specifying a family 1 group name
    256 for the tag, ie) XMP-exif:Contrast or XMP-crs:Contrast.  When deciding on
    257 which tags to add to an image, using standard schemas such as
    258 L<dc|/XMP dc Tags>, L<xmp|/XMP xmp Tags> or L<iptc|/XMP iptcCore Tags> is
     340tables.  The ExifTool family 1 group names are derived from the namespace
     341prefixes by adding a leading "XMP-" (eg. "XMP-dc").  A few of the longer
     342prefixes have been shortened (as mentioned in the documentation below) to
     343avoid excessively long ExifTool group names.  The tags of any namespace may
     344be deleted as a group by specifying the family 1 group name (eg.
     345"-XMP-dc:all=" on the command line).  This includes namespaces which are not
     346pre-defined by ExifTool.
     347
     348In cases where a tag name exists in more than one namespace, less common
     349namespaces are avoided when writing.  However, a specific namespace may be
     350written by providing a family 1 group name for the tag (eg. XMP-crs:Contrast
     351or XMP-exif:Contrast).  When deciding on which tags to add to an image,
     352using standard schemas such as L<dc|/XMP dc Tags>, L<xmp|/XMP xmp Tags>,
     353L<iptcCore|/XMP iptcCore Tags> and L<iptcExt|/XMP iptcExt Tags> is
    259354recommended if possible.
    260355
     
    262357names are very similar to tag names, except they are used to identify fields
    263358inside structures instead of stand-alone tags.  See
    264 L<the Field Name section of the Structured Information documentation|http://owl.phy.queensu.ca/~phil/exiftool/struct.html#Fields> for more
     359L<the Field Name section of the Structured Information documentation|https://exiftool.org/struct.html#Fields> for more
    265360details.
    266361
    267362ExifTool will extract XMP information even if it is not listed in these
    268 tables.  For example, the C<pdfx> namespace doesn't have a predefined set of
    269 tag names because it is used to store application-defined PDF information,
    270 but this information is extracted by ExifTool.
    271 
    272 See L<http://www.adobe.com/devnet/xmp/> for the official XMP specification.
     363tables, but other tags are not writable unless added as user-defined tags in
     364the L<ExifTool config file|../config.html>.  For example, the C<pdfx> namespace doesn't have a
     365predefined set of tag names because it is used to store application-defined
     366PDF information, so although this information will be extracted, it is only
     367writable if the corresponding user-defined tags have been created.
     368
     369The tables below list tags from the official XMP specification (with an
     370underlined B<Namespace> in the HTML version of this documentation), as well
     371as extensions from various other sources.  See
     372L<http://www.adobe.com/devnet/xmp/> for the official XMP specification.
    273373},
    274374    IPTC => q{
     
    276376Council (IPTC) and the Newspaper Association of America (NAA) Information
    277377Interchange Model (IIM).  This is an older meta information format, slowly
    278 being phased out in favor of XMP.  (In fact, the newer IPTCCore
    279 specification actually uses XMP format!)  IPTC information may be embedded
    280 in JPG, TIFF, PNG, MIFF, PS, PDF, PSD, XCF and DNG images.
     378being phased out in favor of XMP -- the newer IPTCCore specification uses
     379XMP format.  IPTC information may be found in JPG, TIFF, PNG, MIFF, PS, PDF,
     380PSD, XCF and DNG images.
    281381
    282382IPTC information is separated into different records, each of which has its
    283383own set of tags.  See
    284384L<http://www.iptc.org/std/IIM/4.1/specification/IIMV4.1.pdf> for the
    285 official specification.
     385official IPTC IIM specification.
    286386
    287387This specification dictates a length for ASCII (C<string> or C<digits>) and
     
    289389the B<Writable> format name.  For tags where a range of lengths is allowed,
    290390the minimum and maximum lengths are separated by a comma within the
    291 brackets.  IPTC strings are not null terminated.  When writing, ExifTool
    292 issues a minor warning and truncates the value if it is longer than allowed
    293 by the IPTC specification. Minor errors may be ignored with the
    294 IgnoreMinorErrors (-m) option, allowing longer values to be written, but
    295 beware that values like this may cause problems for some other IPTC readers.
     391brackets.  When writing, ExifTool issues a minor warning and truncates the
     392value if it is longer than allowed by the IPTC specification.  Minor errors
     393may be ignored with the L<IgnoreMinorErrors|../ExifTool.html#IgnoreMinorErrors> (-m) option, allowing longer
     394values to be written, but beware that values like this may cause problems
     395for some other IPTC readers. ExifTool will happily read IPTC values of any
     396length.
    296397
    297398Separate IPTC date and time tags may be written with a combined date/time
     
    306407offset at the specified date/time is used, which may be different due to
    307408changes in daylight savings time).
     409
     410Note that it is not uncommon for IPTC to be found in non-standard locations
     411in JPEG and TIFF-based images.  When reading, the family 1 group name has a
     412number added for non-standard IPTC ("IPTC2", "IPTC3", etc), but when writing
     413only "IPTC" may be specified as the group.  To keep the IPTC consistent,
     414ExifTool updates tags in all existing IPTC locations, but will create a new
     415IPTC group only in the standard location.
     416},
     417    QuickTime => q{
     418The QuickTime format is used for many different types of audio, video and
     419image files (most notably, MOV/MP4 videos and HEIC/CR3 images).  ExifTool
     420extracts standard meta information and a variety of audio, video and image
     421parameters, as well as proprietary information written by many camera
     422models.  Tags with a question mark after their name are not extracted unless
     423the L<Unknown|../ExifTool.html#Unknown> option is set.
     424
     425When writing, ExifTool creates both QuickTime and XMP tags by default, but
     426the group may be specified to write one or the other separately.  If no
     427location is specified, newly created QuickTime tags are added in the
     428L<ItemList|Image::ExifTool::TagNames/QuickTime ItemList Tags> location if
     429possible, otherwise in
     430L<UserData|Image::ExifTool::TagNames/QuickTime UserData Tags>, and
     431finally in L<Keys|Image::ExifTool::TagNames/QuickTime Keys Tags>,
     432but this order may be changed by setting the PREFERRED level of the
     433appropriate table in the config file (see
     434L<example.config|../config.html#PREF> in the full distribution for an
     435example).  Note that some tags with the same name but different ID's may
     436exist in the same location, but the family 7 group names may be used to
     437differentiate these.  ExifTool currently writes only top-level metadata in
     438QuickTime-based files; it extracts other track-specific and timed metadata,
     439but can not yet edit tags in these locations (with the exception of
     440track-level date/time tags).
     441
     442Alternate language tags may be accessed for
     443L<ItemList|Image::ExifTool::TagNames/QuickTime ItemList Tags> and
     444L<Keys|Image::ExifTool::TagNames/QuickTime Keys Tags> tags by adding
     445a 3-character ISO 639-2 language code and an optional ISO 3166-1 alpha 2
     446country code to the tag name (eg. "ItemList:Artist-deu" or
     447"ItemList::Artist-deu-DE").  Most
     448L<UserData|Image::ExifTool::TagNames/QuickTime UserData Tags> tags support a
     449language code, but without a country code.  If no language code is specified
     450when writing, the default language is written and alternate languages for
     451the tag are deleted.  Use the "und" language code to write the default
     452language without deleting alternate languages.  Note that "eng" is treated
     453as a default language when reading, but not when writing.
     454
     455According to the specification, integer-format QuickTime date/time tags
     456should be stored as UTC.  Unfortunately, digital cameras often store local
     457time values instead (presumably because they don't know the time zone).  For
     458this reason, by default ExifTool does not assume a time zone for these
     459values.  However, if the L<QuickTimeUTC|../ExifTool.html#QuickTimeUTC> API option is set, then ExifTool will
     460assume these values are properly stored as UTC, and will convert them to
     461local time when extracting.
     462
     463When writing string-based date/time tags, the system time zone is added if
     464the PrintConv option is enabled and no time zone is specified.  This is
     465because Apple software may display crazy values if the time zone is missing
     466for some tags.
     467
     468See
     469L<https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/>
     470for the official specification.
    308471},
    309472    Photoshop => q{
     
    314477Many Photoshop tags are marked as Unknown (indicated by a question mark
    315478after the tag name) because the information they provide is not very useful
    316 under normal circumstances I<[and because Adobe denied my application for
    317 their file format documentation -- apparently open source software is too
    318 big a concept for them]>.  These unknown tags are not extracted unless the
    319 Unknown (-u) option is used.
     479under normal circumstances.  These unknown tags are not extracted unless the
     480L<Unknown|../ExifTool.html#Unknown> (-u) option is used.  See
     481L<http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/> for the
     482official specification
     483
     484Photoshop path tags (Tag ID's 0x7d0 to 0xbb5) are not defined by default,
     485but a config file included in the full ExifTool distribution
     486(config_files/photoshop_paths.config) contains the tag definitions to allow
     487access to this information.
    320488},
    321489    PrintIM => q{
    322490The format of the PrintIM information is known, however no PrintIM tags have
    323 been decoded.  Use the Unknown (-u) option to extract PrintIM information.
     491been decoded.  Use the L<Unknown|../ExifTool.html#Unknown> (-u) option to extract PrintIM information.
    324492},
    325493    GeoTiff => q{
    326494ExifTool extracts the following tags from GeoTIFF images.  See
    327495L<http://www.remotesensing.org/geotiff/spec/geotiffhome.html> for the
    328 complete GeoTIFF specification.
     496complete GeoTIFF specification.  Also included in the table below are
     497ChartTIFF tags (see L<http://www.charttiff.com/whitepapers.shtml>). GeoTIFF
     498tags are not writable individually, but they may be copied en mass via the
     499block tags GeoTiffDirectory, GeoTiffDoubleParams and GeoTiffAsciiParams.
    329500},
    330501    JFIF => q{
    331502The following information is extracted from the JPEG JFIF header.  See
    332 L<http://www.jpeg.org/public/jfif.pdf> for the JFIF 1.02 specification.
     503L<https://www.w3.org/Graphics/JPEG/jfif3.pdf> for the JFIF 1.02
     504specification.
    333505},
    334506    Kodak => q{
     
    350522    Olympus => q{
    351523Tags 0x0000 through 0x0103 are used by some older Olympus cameras, and are
    352 the same as Konica/Minolta tags.  The Olympus tags are also used for Epson
    353 and Agfa cameras.
     524the same as Konica/Minolta tags.  These tags are also used for some models
     525from other brands such as Acer, BenQ, Epson, Hitachi, HP, Maginon, Minolta,
     526Pentax, Ricoh, Samsung, Sanyo, SeaLife, Sony, Supra and Vivitar.
    354527},
    355528    Panasonic => q{
     
    358531    Pentax => q{
    359532These tags are used in Pentax/Asahi cameras.
    360 },
    361     Sigma => q{
    362 These tags are used in Sigma/Foveon cameras.
    363 },
    364     Sony => q{
    365 The maker notes in images from most recent Sony camera models contain a
    366 wealth of information, but for some models very little has been decoded.
    367 Use the ExifTool Unknown (-u) or Verbose (-v) options to see information
    368 about the unknown tags.  Also see the Minolta tags which are used by some
    369 Sony models.
    370533},
    371534    CanonRaw => q{
     
    374537length of the information is preserved (and the new information is truncated
    375538or padded as required) unless B<Writable> is C<resize>. Currently, only
    376 JpgFromRaw and ThumbnailImage are allowed to change size.
     539JpgFromRaw and ThumbnailImage are allowed to change size.  See
     540L<https://exiftool.org/canon_raw.html> for a description of the Canon CRW
     541format.
    377542
    378543CRW images also support the addition of a CanonVRD trailer, which in turn
     
    380545ExifTool is used to write XMP to a CRW image.
    381546},
     547    NikonCustom => q{
     548Unfortunately, the NikonCustom settings are stored in a binary data block
     549which changes from model to model.  This means that significant effort must
     550be spent in decoding these for each model, usually requiring hundreds of
     551test images from a dedicated Nikon owner.  For this reason, the NikonCustom
     552settings have not been decoded for all models.  The tables below list the
     553custom settings for the currently supported models.
     554},
    382555    Unknown => q{
    383 The following tags are decoded in unsupported maker notes.  Use the Unknown
     556The following tags are decoded in unsupported maker notes.  Use the L<Unknown|../ExifTool.html#Unknown>
    384557(-u) option to display other unknown tags.
    385558},
     
    393566ExifTool supports reading and writing PDF documents up to version 1.7
    394567extension level 3, including support for RC4, AES-128 and AES-256
    395 encryption.  A Password option is provided to allow processing of
     568encryption.  A L<Password|../ExifTool.html#Password> option is provided to allow processing of
    396569password-protected PDF files.
    397570
    398 When writing PDF files, ExifTool uses an incremental update.  This has the
    399 advantages of being fast and reversible.  The original PDF can be easily
    400 recovered by deleting the C<PDF-update> pseudo-group (with
    401 C<-PDF-update:all=> on the command line).  But there are two main
     571ExifTool may be used to write native PDF and XMP metadata to PDF files. It
     572uses an incremental update technique that has the advantages of being both
     573fast and reversible.  If ExifTool was used to modify a PDF file, the
     574original may be recovered by deleting the C<PDF-update> pseudo-group (with
     575C<-PDF-update:all=> on the command line).  However, there are two main
    402576disadvantages to this technique:
    403577
     
    4075812) All metadata edits are reversible.  While this would normally be
    408582considered an advantage, it is a potential security problem because old
    409 information is never actually deleted from the file.
     583information is never actually deleted from the file.  (However, after
     584running ExifTool the old information may be removed permanently using the
     585"qpdf" utility with this command: "qpdf --linearize in.pdf out.pdf".)
    410586},
    411587    DNG => q{
     
    425601},
    426602    Extra => q{
    427 The extra tags represent extra information extracted or generated by
    428 ExifTool that is not directly associated with another tag group.  The three
    429 writable "pseudo" tags (FileName, Directory and FileModifyDate) may be
    430 written without the need to rewrite the file since their values are not
    431 contained within the file data.  These "pseudo" tags belong to the family 1
    432 "System" group.
     603The extra tags provide extra features or extra information extracted or
     604generated by ExifTool that is not directly associated with another tag
     605group.  The B<Group> column lists the family 1 group name when reading.
     606Tags with a "-" in this column are write-only.
     607
     608Tags in the family 1 "System" group are referred to as "pseudo" tags because
     609they don't represent real metadata in the file.  Instead, this information
     610is stored in the directory structure of the filesystem.  The B<Writable>
     611System "pseudo" tags in this table may be written without modifying the file
     612itself.  The TestName tag is used for dry-run testing before writing
     613FileName.
    433614},
    434615    Composite => q{
    435616The values of the composite tags are B<Derived From> the values of other
    436617tags.  These are convenience tags which are calculated after all other
    437 information is extracted.
     618information is extracted.  Only a few of these tags are writable directly,
     619the others are changed by writing the corresponding B<Derived From> tags.
     620User-defined Composite tags, also useful for custom-formatting of tag
     621values, may created via the L<ExifTool configuration file|../config.html>.
    438622},
    439623    Shortcuts => q{
     
    447631for more details.
    448632},
     633    MWG => q{
     634The Metadata Working Group (MWG) recommends techniques to allow certain
     635overlapping EXIF, IPTC and XMP tags to be reconciled when reading, and
     636synchronized when writing.  The MWG Composite tags below are designed to aid
     637in the implementation of these recommendations.  As well, the MWG defines
     638new XMP tags which are listed in the subsequent tables below.  See
     639L<https://web.archive.org/web/20181006115950/http://www.metadataworkinggroup.org/specs/>
     640for the official MWG specification.
     641},
     642    MacOS => q{
     643On MacOS systems, the there are additional MDItem and XAttr Finder tags that
     644may be extracted.  These tags are not extracted by default -- they must be
     645specifically requested or enabled via an API option.  (Except when reading
     646MacOS "._" files directly, see below.)
     647
     648The tables below list some of the tags that may be extracted, but ExifTool
     649will extract all available information even for tags not listed.
     650
     651Tags in these tables are referred to as "pseudo" tags because their
     652information is not stored in the file itself.  As such, B<Writable> tags in
     653these tables may be changed without having to rewrite the file.
     654},
    449655    PodTrailer => q{
    450656~head1 NOTES
     
    455661~head1 AUTHOR
    456662
    457 Copyright 2003-2011, Phil Harvey (phil at owl.phy.queensu.ca)
     663Copyright 2003-2021, Phil Harvey (philharvey66 at gmail.com)
    458664
    459665This library is free software; you can redistribute it and/or modify it
     
    470676# notes for Shortcuts tags
    471677my %shortcutNotes = (
     678    AllDates => q{
     679        contrary to the shortcut name, this represents only the common EXIF
     680        date/time tags.  To access all date/time tags, use Time:All instead
     681    },
    472682    MakerNotes => q{
    473683        useful when copying tags between files to either copy the maker notes as a
    474684        block or prevent it from being copied
    475685    },
     686    ColorSpaceTags => q{
     687        standard tags which carry color space information.  Useful for preserving
     688        color space when deleting all other metadata
     689    },
    476690    CommonIFD0 => q{
    477691        common metadata tags found in IFD0 of TIFF-format images.  Used to simpify
    478         deletion of all metadata from these images.  See FAQ number 7 for details
     692        deletion of all metadata from these images.  See
     693        L<FAQ number 7|../faq.html#Q7> for details
    479694    },
    480695    Unsafe => q{
    481         "unsafe" tags in JPEG images which are normally not copied.  Defined here
    482         as a shortcut to use when rebuilding JPEG EXIF from scratch
     696        I<Unsafe> tags in JPEG images which are normally not copied.  Defined here
     697        as a shortcut to use when rebuilding JPEG EXIF from scratch. See
     698        L<FAQ number 20|../faq.html#Q20> for more information
     699    },
     700    LargeTags => q{
     701        large binary data tags which may be excluded to reduce memory usage if
     702        memory limitations are a problem
     703    },
     704   'ls-l' => q{
     705        mimics columns shown by Unix "ls -l" command.  Includes some tags which are
     706        extracted only if the API L<SystemTags|../ExifTool.html#SystemTags> option
     707        is enabled
    483708    },
    484709);
    485710
    486711
    487 
    488 # EXIF table tag ID's which are part of the EXIF 2.3 specification
    489 # (used only to add underlines in HTML version of EXIF Tag Table)
    490 my %exifSpec = (
    491     0x100 => 1,  0x212 => 1,   0x9204 => 1,  0xa217 => 1,
    492     0x101 => 1,  0x213 => 1,   0x9205 => 1,  0xa300 => 1,
    493     0x102 => 1,  0x214 => 1,   0x9206 => 1,  0xa301 => 1,
    494     0x103 => 1,  0x8298 => 1,  0x9207 => 1,  0xa302 => 1,
    495     0x106 => 1,  0x829a => 1,  0x9208 => 1,  0xa401 => 1,
    496     0x10e => 1,  0x829d => 1,  0x9209 => 1,  0xa402 => 1,
    497     0x10f => 1,  0x8769 => 1,  0x920a => 1,  0xa403 => 1,
    498     0x110 => 1,  0x8822 => 1,  0x9214 => 1,  0xa404 => 1,
    499     0x111 => 1,  0x8824 => 1,  0x927c => 1,  0xa405 => 1,
    500     0x112 => 1,  0x8825 => 1,  0x9286 => 1,  0xa406 => 1,
    501     0x115 => 1,  0x8827 => 1,  0x9290 => 1,  0xa407 => 1,
    502     0x116 => 1,  0x8828 => 1,  0x9291 => 1,  0xa408 => 1,
    503     0x117 => 1,  0x8830 => 1,  0x9292 => 1,  0xa409 => 1,
    504     0x11a => 1,  0x8831 => 1,  0xa000 => 1,  0xa40a => 1,
    505     0x11b => 1,  0x8832 => 1,  0xa001 => 1,  0xa40b => 1,
    506     0x11c => 1,  0x8833 => 1,  0xa002 => 1,  0xa40c => 1,
    507     0x128 => 1,  0x8834 => 1,  0xa003 => 1,  0xa420 => 1,
    508     0x12d => 1,  0x8835 => 1,  0xa004 => 1,  0xa430 => 1,
    509     0x131 => 1,  0x9000 => 1,  0xa005 => 1,  0xa431 => 1,
    510     0x132 => 1,  0x9003 => 1,  0xa20b => 1,  0xa432 => 1,
    511     0x13b => 1,  0x9004 => 1,  0xa20c => 1,  0xa433 => 1,
    512     0x13e => 1,  0x9101 => 1,  0xa20e => 1,  0xa434 => 1,
    513     0x13f => 1,  0x9102 => 1,  0xa20f => 1,  0xa435 => 1,
    514     0x201 => 1,  0x9201 => 1,  0xa210 => 1,
    515     0x202 => 1,  0x9202 => 1,  0xa214 => 1,
    516     0x211 => 1,  0x9203 => 1,  0xa215 => 1,
    517 );
    518712# same thing for RIFF INFO tags found in the EXIF spec
    519713my %riffSpec = (
     
    523717    ICMT => 1,  IDPI => 1,  IMED => 1,  ISFT => 1,
    524718    ICOP => 1,  IENG => 1,  INAM => 1,  ISHP => 1,
     719);
     720# same thing for XMP namespaces
     721my %xmpSpec = (
     722    aux       => 1,   'x'        => 1,
     723    crs       => 1,    xmp       => 1,
     724    dc        => 1,    xmpBJ     => 1,
     725    exif      => 1,    xmpDM     => 1,
     726    pdf       => 1,    xmpMM     => 1,
     727    pdfx      => 1,    xmpNote   => 1,
     728    photoshop => 1,    xmpRights => 1,
     729    tiff      => 1,    xmpTPg    => 1,
    525730);
    526731
     
    534739    my $class = ref($that) || $that || 'Image::ExifTool::BuildTagLookup';
    535740    my $self = bless {}, $class;
    536     my (%subdirs, %isShortcut, %allStructs);
     741    my (%subdirs, %isShortcut);
    537742    my %count = (
    538743        'unique tag names' => 0,
     
    543748#
    544749    my (%tagNameInfo, %id, %longID, %longName, %shortName, %tableNum,
    545         %tagLookup, %tagExists, %tableWritable, %sepTable, %structs,
    546         %compositeModules, %isPlugin, %flattened, %structLookup);
     750        %tagLookup, %tagExists, %noLookup, %tableWritable, %sepTable, %case,
     751        %structs, %compositeModules, %isPlugin, %flattened, %structLookup,
     752        @writePseudo);
    547753    $self->{TAG_NAME_INFO} = \%tagNameInfo;
    548754    $self->{ID_LOOKUP} = \%id;
     
    560766    $self->{COMPOSITE_MODULES} = \%compositeModules;
    561767    $self->{COUNT} = \%count;
     768    $self->{WRITE_PSEUDO} = \@writePseudo;
    562769
    563770    Image::ExifTool::LoadAllTables();
     
    566773    push @tableNames, 'Image::ExifTool::Shortcuts::Main';
    567774    # add plug-in modules last
    568     $Image::ExifTool::documentOnly = 1; # (don't really load them)
    569775    foreach (@pluginTables) {
    570776        push @tableNames, $_;
     
    573779
    574780    my $tableNum = 0;
    575     my $exifTool = new Image::ExifTool;
     781    my $et = new Image::ExifTool;
    576782    my ($tableName, $tag);
    577783    # create lookup for short table names
     
    583789        $short =~ s/^(.+)Tags$/\u$1/ unless $short eq 'Nikon AVITags';
    584790        $short =~ s/^Exif\b/EXIF/;
     791        # change underlines to dashes in XMP-mwg group names
     792        # (we used underlines just because Perl variables can't contain dashes)
     793        $short =~ s/^XMP mwg_/XMP mwg-/;
    585794        $shortName{$tableName} = $short;    # remember short name
    586795        $tableNum{$tableName} = $tableNum++;
     
    622831        $longName{$tableName} = 0;
    623832        # save all tag names
    624         my ($tagID, $binaryTable, $noID, $isIPTC, $isXMP);
     833        my ($tagID, $binaryTable, $noID, $hexID, $isIPTC, $isXMP);
    625834        $isIPTC = 1 if $writeProc and $writeProc eq \&Image::ExifTool::IPTC::WriteIPTC;
    626835        # generate flattened tag names for structure fields if this is an XMP table
    627836        if ($$table{GROUPS} and $$table{GROUPS}{0} eq 'XMP') {
     837            Image::ExifTool::XMP::AddFlattenedTags($table);
    628838            $isXMP = 1;
    629             foreach $tagID (TagTableKeys($table)) {
    630                 my $tagInfo = $$table{$tagID};
    631                 next unless ref $tagInfo eq 'HASH' and $$tagInfo{Struct};
    632                 Image::ExifTool::XMP::AddFlattenedTags($table, $tagID);
    633             }
    634839        }
    635840        $noID = 1 if $isXMP or $short =~ /^(Shortcuts|ASF.*)$/ or $$vars{NO_ID};
     841        $hexID = $$vars{HEX_ID};
    636842        my $processBinaryData = ($$table{PROCESS_PROC} and (
    637843            $$table{PROCESS_PROC} eq \&Image::ExifTool::ProcessBinaryData or
    638             $$table{PROCESS_PROC} eq \&Image::ExifTool::Nikon::ProcessNikonEncrypted));
     844            $$table{PROCESS_PROC} eq \&Image::ExifTool::Nikon::ProcessNikonEncrypted or
     845            $$table{PROCESS_PROC} eq \&Image::ExifTool::Sony::ProcessEnciphered));
    639846        if ($$vars{ID_LABEL} or $processBinaryData) {
     847            my $s = $$table{FORMAT} ? Image::ExifTool::FormatSize($$table{FORMAT}) || 1 : 1;
    640848            $binaryTable = 1;
    641             $id{$tableName} = $$vars{ID_LABEL} || 'Index';
     849            $id{$tableName} = $$vars{ID_LABEL} || "Index$s";
    642850        } elsif ($isIPTC and $$table{PROCESS_PROC}) { #only the main IPTC table has a PROCESS_PROC
    643851            $id{$tableName} = 'Record';
     
    646854        }
    647855        $caseInsensitive = $isXMP;
     856        $numbersFirst = 2;
    648857        $numbersFirst = -1 if $$table{VARS} and $$table{VARS}{ALPHA_FIRST};
    649         my @keys = sort NumbersFirst TagTableKeys($table);
     858        my @keys = SortedTagTableKeys($table);
    650859        $numbersFirst = 1;
    651860        my $defFormat = $table->{FORMAT};
     
    654863
    655864TagID:  foreach $tagID (@keys) {
    656             my ($tagInfo, @tagNames, $subdir, $format, @values);
     865            my ($tagInfo, @tagNames, $subdir, @values);
    657866            my (@infoArray, @require, @writeGroup, @writable);
    658867            if ($shortcut) {
     
    672881                @infoArray = GetTagInfoList($table,$tagID);
    673882            }
    674             $format = $defFormat;
    675883            foreach $tagInfo (@infoArray) {
    676884                my $name = $$tagInfo{Name};
    677                 # validate Name
    678                 warn "Warning: Invalid tag name $short '$name'\n" if $name !~ /^[-\w]+$/;
     885                if ($$tagInfo{WritePseudo}) {
     886                    push @writePseudo, $name;
     887                    warn "Writable pseudo tag $name is not protected!\n" unless $$tagInfo{Protected};
     888                }
     889                unless ($$tagInfo{SubDirectory} or $$tagInfo{Struct}) {
     890                    my $lc = lc $name;
     891                    warn "Different case for $tableName $name $case{$lc}\n" if $case{$lc} and $case{$lc} ne $name;
     892                    $case{$lc} = $name;
     893                }
     894                my $format = $$tagInfo{Format};
     895                # check TagID's to make sure they don't start with 'ID-'
     896                my @grps = $et->GetGroup($tagInfo);
     897                foreach (@grps) {
     898                    warn "Group name starts with 'ID-' for $short $name\n" if /^ID-/i;
     899                }
     900                # validate Name (must not start with a digit or else XML output will not be valid;
     901                # must not start with a dash or exiftool command line may get confused)
     902                if ($name !~ /^[_A-Za-z][-\w]+$/ and
     903                    # single-character subdirectory names are allowed
     904                    (not $$tagInfo{SubDirectory} or $name !~ /^[_A-Za-z]$/))
     905                {
     906                    warn "Warning: Invalid tag name $short '${name}'\n";
     907                }
     908                # validate list type
     909                if ($$tagInfo{List} and $$tagInfo{List} !~ /^(1|Alt|Bag|Seq|array|string)$/) {
     910                    warn "Warning: Unknown List type ($$tagInfo{List}) for $name in $tableName\n";
     911                }
    679912                # accumulate information for consistency check of BinaryData tables
    680913                if ($processBinaryData and $$table{WRITABLE}) {
     914                    # can't currently write tag if Condition accesses $valPt
     915                    if ($$tagInfo{Condition} and not $$tagInfo{SubDirectory} and
     916                        $$tagInfo{Condition} =~ /\$valPt/ and
     917                        ($$tagInfo{Writable} or not defined $$tagInfo{Writable}))
     918                    {
     919                        warn "Warning: Tag not writable due to Condition - $short $name\n";
     920                    }
    681921                    $isOffset{$tagID} = $name if $$tagInfo{IsOffset};
    682922                    $hasSubdir{$tagID} = $name if $$tagInfo{SubDirectory};
    683923                    # require DATAMEMBER for writable var-format tags, Hook and DataMember tags
    684                     if ($$tagInfo{Format} and $$tagInfo{Format} =~ /^var_/) {
     924                    if ($format and $format =~ /^var_/) {
    685925                        $datamember{$tagID} = $name;
    686926                        unless (defined $$tagInfo{Writable} and not $$tagInfo{Writable}) {
    687927                            warn "Warning: Var-format tag is writable - $short $name\n"
     928                        }
     929                        # also need DATAMEMBER for tags used in length of var-sized value
     930                        while ($format =~ /\$val\{(.*?)\}/g) {
     931                            my $id = $1;
     932                            $id = hex($id) if $id =~ /^0x/; # convert from hex if necessary
     933                            $datamember{$id} = $$table{$id}{Name} if ref $$table{$id} eq 'HASH';
     934                            unless ($datamember{$id}) {
     935                                warn "Warning: Unknown ID ($id) used in Format - $short $name\n";
     936                            }
    688937                        }
    689938                    } elsif ($$tagInfo{Hook} or ($$tagInfo{RawConv} and
     
    692941                        $datamember{$tagID} = $name;
    693942                    }
     943                    if ($format and $format =~ /\$val\{/ and
     944                        ($$tagInfo{Writable} or not defined $$tagInfo{Writable}))
     945                    {
     946                        warn "Warning: \$var{} used in Format of writable tag - $short $name\n"
     947                    }
    694948                }
    695949                if ($$tagInfo{Hidden}) {
    696                     warn "Warning: Hidden tag in list - $short $name\n" if @infoArray > 1;
    697                     next TagID;
     950                    if ($tagInfo == $infoArray[0]) {
     951                        next TagID; # hide all tags with this ID if first tag in list is hidden
     952                    } else {
     953                        next;       # only hide this tag
     954                    }
    698955                }
    699956                my $writable;
     
    702959                    # validate Writable
    703960                    unless ($formatOK{$writable} or  ($writable =~ /(.*)\[/ and $formatOK{$1})) {
    704                         warn "Warning: Unknown Writable ($writable) for $short $name\n",
     961                        warn "Warning: Unknown Writable ($writable) - $short $name\n",
    705962                    }
    706963                } elsif (not $$tagInfo{SubDirectory}) {
    707964                    $writable = $$table{WRITABLE};
    708965                }
     966                # in general, we can't write unless we have a WRITE_PROC
     967                if ($writable and not ($$table{WRITE_PROC} or $tableName =~ /Shortcuts/ or $writable eq '2')) {
     968                    undef $writable;
     969                }
    709970                # validate some characteristics of obvious date/time tags
     971                my @g = $et->GetGroup($tagInfo);
     972                if ($$tagInfo{List} and $g[2] eq 'Time' and $writable and not $$tagInfo{Protected} and
     973                    not $$tagInfo{PrintConvInv})
     974                {
     975                    # (this is a problem because shifting Time:All would create a new list entry)
     976                    warn "Writable List-type Time tag $g[1]:$name has no PrintConvInv and is not Protected!\n";
     977                }
    710978                if ($$tagInfo{PrintConv} and $$tagInfo{PrintConv} eq '$self->ConvertDateTime($val)') {
    711                     my @g = $exifTool->GetGroup($tagInfo);
    712979                    warn "$short $name should be in 'Time' group!\n" unless $g[2] eq 'Time';
    713                     if ($writable and not $$tagInfo{Shift} and $g[0] ne 'Composite' and
    714                         $short ne 'PostScript')
     980                    if ($writable and not defined $$tagInfo{Shift} and $short ne 'PostScript') {
     981                        warn "$short $name is not shiftable!\n";
     982                    }
     983                    if ($writable and (not $$tagInfo{PrintConvInv} or
     984                        $$tagInfo{PrintConvInv} !~ /InverseDateTime/))
    715985                    {
    716                         warn "$short $name is not shiftable!\n";
     986                        warn "$short $name missing InverseDateTime PrintConvInv\n";
    717987                    }
    718988                } elsif ($name =~ /DateTime(?!Stamp)/ and (not $$tagInfo{Groups}{2} or
    719989                    $$tagInfo{Groups}{2} ne 'Time') and $short ne 'DICOM') {
    720990                    warn "$short $name should be in 'Time' group!\n";
     991                }
     992                if ($name eq 'DateTimeOriginal' and (not $$tagInfo{Description} or
     993                    $$tagInfo{Description} ne 'Date/Time Original'))
     994                {
     995                    warn "Need Description for $short DateTimeOriginal\n";
    721996                }
    722997                # validate Description (can't contain special characters)
     
    7281003                    warn "$name description contains special characters!\n";
    7291004                }
    730                 # validate SubIFD flag
    731                 my $subdir = $$tagInfo{SubDirectory};
     1005                # generate structure lookup
    7321006                my $struct = $$tagInfo{Struct};
    7331007                my $strTable;
     
    7491023                    $strTable = $structLookup{$struct};
    7501024                    unless ($strTable) {
    751                         warn "Missing XMP $struct structure!\n";
     1025                        $struct = "XMP $struct" unless $struct =~ / /;
     1026                        warn "Missing $struct structure!\n";
    7521027                        undef $struct;
    7531028                    }
    7541029                }
    755                 my $isSub = ($subdir and $$subdir{Start} and $$subdir{Start} eq '$val');
     1030                # validate SubIFD flag
     1031                my $subdir = $$tagInfo{SubDirectory};
     1032                my $isSub = ($subdir and $$subdir{Start} and $$subdir{Start} =~ /\$val\b/);
    7561033                if ($$tagInfo{SubIFD}) {
    7571034                    warn "Warning: Wrong SubDirectory Start for SubIFD tag - $short $name\n" unless $isSub;
     
    7621039                    my $note = $$tagInfo{Notes};
    7631040                    # remove leading/trailing blank lines
    764                     $note =~ s/(^\s+|\s+$)//g;
     1041                    $note =~ s/^\s+//; $note =~ s/\s+$//;
    7651042                    # remove leading/trailing spaces on each line
    7661043                    $note =~ s/(^[ \t]+|[ \t]+$)//mg;
    7671044                    push @values, "($note)";
    768                 } elsif ($isXMP and lc $tagID ne lc $name) {
    769                     # add note about different XMP Tag ID
    770                     if ($$tagInfo{RootTagInfo}) {
    771                         push @values, "($tagID)";
     1045                }
     1046                if ($isXMP and (lc $tagID ne lc $name or $$tagInfo{NotFlat})) {
     1047                    my $note;
     1048                    if ($$tagInfo{NotFlat}) {
     1049                        $note = 'NOT a flattened tag!';
    7721050                    } else {
    773                         push @values,"(called $tagID by the spec)";
     1051                        # add note about different XMP Tag ID
     1052                        $note = $$tagInfo{RootTagInfo} ? $tagID : "called $tagID by the spec";
     1053                    }
     1054                    if ($$tagInfo{Notes}) {
     1055                        $values[-1] =~ s/^\(/($note; /;
     1056                    } else {
     1057                        push @values, "($note)";
    7741058                    }
    7751059                }
    7761060                my $writeGroup;
    777                 $writeGroup = $$tagInfo{WriteGroup};
     1061                if ($short eq 'Extra') {
     1062                    $writeGroup = $$tagInfo{WriteOnly} ? '-' : $g[1];
     1063                } else {
     1064                    $writeGroup = $$tagInfo{WriteGroup};
     1065                }
    7781066                unless ($writeGroup) {
    7791067                    $writeGroup = $$table{WRITE_GROUP} if $writable;
    7801068                    $writeGroup = '-' unless $writeGroup;
    7811069                }
    782                 if (defined $$tagInfo{Format}) {
    783                     $format = $$tagInfo{Format};
     1070                if (defined $format) {
    7841071                    # validate Format
    7851072                    unless ($formatOK{$format} or $short eq 'PICT' or
     
    7881075                        warn "Warning: Unknown Format ($format) for $short $name\n";
    7891076                    }
     1077                } else {
     1078                    $format = $defFormat;
    7901079                }
    7911080                if ($subdir) {
     
    8101099                if ($$tagInfo{Mask}) {
    8111100                    my $val = $$tagInfo{Mask};
    812                     push @values, sprintf('[Mask 0x%.2x]',$val);
    813                     $$tagInfo{PrintHex} = 1 unless defined $$tagInfo{PrintHex};
     1101                    my $bsh = $$tagInfo{BitShift};
     1102                    if ($bsh) {
     1103                        push @values, sprintf('[val >> %d & 0x%x]',$bsh,$val>>$bsh);
     1104                    } else {
     1105                        push @values, sprintf('[val & 0x%x]',$val);
     1106                    }
    8141107                    # verify that all values are within the mask
    8151108                    if (ref $printConv eq 'HASH') {
    816                         # convert mask if necessary
    817                         if ($$tagInfo{ValueConv}) {
    818                             my $v = eval $$tagInfo{ValueConv};
    819                             $val = $v if defined $v;
    820                         }
     1109                        $val >>= $$tagInfo{BitShift};
    8211110                        foreach (keys %$printConv) {
    8221111                            next if $_ !~ /^\d+$/ or ($_ & $val) == $_;
     
    8271116                }
    8281117                if (ref($printConv) =~ /^(HASH|ARRAY)$/) {
    829                     my (@printConvList, @indexList, $index);
     1118                    my (@printConvList, @indexList, $index, $valueConvHash);
    8301119                    if (ref $printConv eq 'ARRAY') {
    8311120                        for ($index=0; $index<@$printConv; ++$index) {
    832                             next if ref $$printConv[$index] ne 'HASH';
    833                             next unless %{$$printConv[$index]};
    834                             push @printConvList, $$printConv[$index];
    835                             push @indexList, $index;
     1121                            if (ref $$printConv[$index] eq 'HASH') {
     1122                                next unless %{$$printConv[$index]};
     1123                                push @printConvList, $$printConv[$index];
     1124                                push @indexList, $index;
     1125                            } elsif ($$printConv[$index] and $$printConv[$index] eq 'REPEAT' and $index) {
     1126                                push @printConvList, $$printConv[$index-1];
     1127                                push @indexList, 'N';
     1128                            } else {
     1129                                next;
     1130                            }
    8361131                            # collapse values with identical PrintConv's
    8371132                            if (@printConvList >= 2 and $printConvList[-1] eq $printConvList[-2]) {
     
    8471142                        $printConv = shift @printConvList;
    8481143                        $index = shift @indexList;
     1144                    } else {
     1145                        $valueConvHash = $$tagInfo{ValueConv} if ref $$tagInfo{ValueConv} eq 'HASH';
    8491146                    }
    8501147                    while (defined $printConv) {
     
    8581155                                my ($i, @i, $rngStart);
    8591156                                for ($i=0; $i<@$idx; ++$i) {
    860                                     if ($i < @$idx - 1 and $$idx[$i+1] == $$idx[$i] + 1) {
    861                                         $rngStart = $i unless defined $rngStart;
     1157                                    if ($i < @$idx - 1 and ($$idx[$i+1] eq 'N' or $$idx[$i+1] == $$idx[$i] + 1)) {
     1158                                        $rngStart = $$idx[$i] unless defined $rngStart;
    8621159                                        next;
    8631160                                    }
    864                                     push @i, defined($rngStart) ? "$rngStart-$i" : $i;
     1161                                    push @i, (defined($rngStart) ? "$rngStart-" : '') . $$idx[$i];
    8651162                                }
    8661163                                ($idx = join ', ', @i) =~ s/(.*),/$1 and/;
     
    8901187                        } else {
    8911188                            $caseInsensitive = 0;
    892                             my @pk = sort NumbersFirst keys %$printConv;
     1189                            my @pk;
     1190                            if ($$tagInfo{PrintSort}) {
     1191                                @pk = sort { NumbersFirst($$printConv{$a},$$printConv{$b}) } keys %$printConv;
     1192                            } else {
     1193                                @pk = sort { NumbersFirst($a,$b) } keys %$printConv;
     1194                            }
    8931195                            my $n = scalar @values;
    894                             my ($bits, $cols, $i);
     1196                            my ($bits, $i, $v);
    8951197                            foreach (@pk) {
    896                                 next if $_ eq '';
     1198                                next if $_ eq '' and $$printConv{$_} eq '';
    8971199                                $_ eq 'BITMASK' and $bits = $$printConv{$_}, next;
    8981200                                $_ eq 'OTHER' and next;
    8991201                                my $index;
    900                                 if (($$tagInfo{PrintHex} or $$printConv{BITMASK}) and /^\d+$/) {
    901                                     $index = sprintf('0x%x',$_);
     1202                                if (($$tagInfo{PrintHex} or $$printConv{BITMASK}) and /^-?\d+$/) {
     1203                                    my $dig = $$tagInfo{PrintHex} || 1;
     1204                                    if ($_ >= 0) {
     1205                                        $index = sprintf('0x%.*x', $dig, $_);
     1206                                    } elsif ($format and $format =~ /int(16|32)/) {
     1207                                        # mask off unused bits of signed integer hex value
     1208                                        my $mask = { 16 => 0xffff, 32 => 0xffffffff }->{$1};
     1209                                        $index = sprintf('0x%.*x', $dig, $_ & $mask);
     1210                                    } else {
     1211                                        $index = $_;
     1212                                    }
    9021213                                } elsif (/^[+-]?(?=\d|\.\d)\d*(\.\d*)?$/ and not $$tagInfo{PrintString}) {
    9031214                                    $index = $_;
     
    9081219                                        $index = qq{"$index"};
    9091220                                    } else {
    910                                         $index = qq{'$index'};
     1221                                        $index = qq{'${index}'};
    9111222                                    }
    9121223                                }
    9131224                                push @values, "$index = " . $$printConv{$_};
     1225                                if ($valueConvHash) {
     1226                                    foreach $v (keys %$valueConvHash) {
     1227                                        next unless $$valueConvHash{$v} eq $_;
     1228                                        $values[-1] = "$v => " . $values[-1];
     1229                                        last;
     1230                                    }
     1231                                }
    9141232                                # validate all PrintConv values
    9151233                                if ($$printConv{$_} =~ /[\0-\x1f\x7f-\xff]/) {
     
    9181236                            }
    9191237                            if ($bits) {
    920                                 my @pk = sort NumbersFirst keys %$bits;
     1238                                my @pk = sort { NumbersFirst($a,$b) } keys %$bits;
    9211239                                foreach (@pk) {
    9221240                                    push @values, "Bit $_ = " . $$bits{$_};
     
    9241242                            }
    9251243                            # organize values into columns if specified
    926                             if (defined($cols = $$tagInfo{PrintConvColumns})) {
     1244                            my $cols = $$tagInfo{PrintConvColumns};
     1245                            if (not $cols and scalar(@values) - $n >= 6) {
     1246                                # do columns if more than 6 short entries
     1247                                my $maxLen = 0;
     1248                                for ($i=$n; $i<@values; ++$i) {
     1249                                    next unless $maxLen < length $values[$i];
     1250                                    $maxLen = length $values[$i];
     1251                                }
     1252                                my $num = scalar(@values) - $n;
     1253                                $cols = int(50 / ($maxLen + 2)); # (50 chars max width)
     1254                                # have 3 rows minimum
     1255                                --$cols while $cols and $num / $cols < 3;
     1256                            }
     1257                            if ($cols) {
    9271258                                my @new = splice @values, $n;
    9281259                                my $v = '[!HTML]<table class=cols><tr>';
    9291260                                my $rows = int((scalar(@new) + $cols - 1) / $cols);
    930                                 for ($n=0; $n<@new; $n+=$rows) {
     1261                                for ($n=0; ;) {
    9311262                                    $v .= "\n  <td>";
    9321263                                    for ($i=0; $i<$rows and $n+$i<@new; ++$i) {
     
    9341265                                        $v .= EscapeHTML($new[$n+$i]);
    9351266                                    }
    936                                     $v .= '</td><td>&nbsp;&nbsp;</td>';
     1267                                    $v .= '</td>';
     1268                                    last if ($n += $rows) >= @new;
     1269                                    $v .= '<td>&nbsp;&nbsp;</td>'; # add spaces between columns
    9371270                                }
    9381271                                push @values, $v . "</tr></table>\n";
     
    9501283                        warn $@;
    9511284                    } else {
    952                         my @pk = sort NumbersFirst keys %$bits;
     1285                        my @pk = sort { NumbersFirst($a,$b) } keys %$bits;
    9531286                        foreach (@pk) {
    9541287                            push @values, "Bit $_ = " . $$bits{$_};
     
    9571290                }
    9581291                if ($subdir and not $$tagInfo{SeparateTable}) {
    959                     # subdirectories are only writable if specified explicitly
    960                     my $tw = $$tagInfo{Writable};
    961                     $writable = '-' . ($tw ? $writable : '');
    962                     $writable .= '!' if $tw and ($$tagInfo{Protected} || 0) & 0x01;
     1292                    if ($writable) {
     1293                        # subdirectories are only writable if specified explicitly
     1294                        my $tw = $$tagInfo{Writable};
     1295                        $writable = 'yes' if $tw and $writable eq '1' or $writable eq '2';
     1296                        $writable = '-' . ($tw ? $writable : '');
     1297                        $writable .= '!' if $tw and ($$tagInfo{Protected} || 0) & 0x01;
     1298                        $writable .= '+' if $$tagInfo{List};
     1299                        if (defined $$tagInfo{Permanent}) {
     1300                            $writable .= '^' unless $$tagInfo{Permanent};
     1301                        } elsif (defined $$table{PERMANENT}) {
     1302                            $writable .= '^' unless $$table{PERMANENT};
     1303                        }
     1304                    } else {
     1305                        $writable = '-';
     1306                    }
    9631307                } else {
    9641308                    # not writable if we can't do the inverse conversions
     
    9781322                    }
    9791323                    if (not $writable) {
    980                         $writable = 'N';
     1324                        $writable = 'no';
    9811325                    } else {
    982                         $writable eq '1' and $writable = $format ? $format : 'Y';
     1326                        $writable = $format ? $format : 'yes' if $writable eq '1' or $writable eq '2';
    9831327                        my $count = $$tagInfo{Count} || 1;
    9841328                        # adjust count to Writable size if different than Format
     
    10081352                    $writable .= '+' if $$tagInfo{List};
    10091353                    $writable .= ':' if $$tagInfo{Mandatory};
     1354                    if (defined $$tagInfo{Permanent}) {
     1355                        $writable .= '^' unless $$tagInfo{Permanent};
     1356                    } elsif (defined $$table{PERMANENT}) {
     1357                        $writable .= '^' unless $$table{PERMANENT};
     1358                    }
    10101359                    # separate tables link like subdirectories (flagged with leading '-')
    10111360                    $writable = "-$writable" if $subdir;
     
    10281377                }
    10291378#
    1030 # add this tag to the tag lookup unless PROCESS_PROC is 0 or shortcut or plug-in tag
     1379# add this tag to the tag lookup unless NO_LOOKUP is set or shortcut or plug-in tag
    10311380#
    10321381                next if $shortcut or $isPlugin;
    1033                 next if defined $$table{PROCESS_PROC} and not $$table{PROCESS_PROC};
    1034                 # count our tags
     1382                # count tags
    10351383                if ($$tagInfo{SubDirectory}) {
     1384                    next if $$vars{NO_LOOKUP};
    10361385                    $subdirs{$lcName} or $subdirs{$lcName} = 0;
    10371386                    ++$subdirs{$lcName};
    10381387                } else {
    10391388                    ++$count{'total tags'};
    1040                     unless ($tagExists{$lcName} and (not $subdirs{$lcName} or $subdirs{$lcName} == $tagExists{$lcName})) {
    1041                         ++$count{'unique tag names'};
    1042                     }
     1389                    unless ($tagExists{$lcName} and
     1390                        (not $subdirs{$lcName} or $subdirs{$lcName} == $tagExists{$lcName}))
     1391                    {
     1392                        ++$count{'unique tag names'} unless $noLookup{$lcName};
     1393                    }
     1394                    # don't add to tag lookup if specified
     1395                    $$vars{NO_LOOKUP} and $noLookup{$lcName} = 1, next;
    10431396                }
    10441397                $tagExists{$lcName} or $tagExists{$lcName} = 0;
     
    10711424                $tableWritable{$tableName} = 1;
    10721425                $tagLookup{$lcName}->{$tableNum} = $tagIDs;
    1073                 if ($short eq 'Composite' and $$tagInfo{Module}) {
    1074                     $compositeModules{$lcName} = $$tagInfo{Module};
    1075                 }
     1426                # keep track of extra modules needed for Composite tags
     1427                $compositeModules{$lcName} = $$tagInfo{Module} if $$tagInfo{Module};
    10761428            }
    10771429#
     
    10791431#
    10801432            my $tagIDstr;
    1081             if ($tagID =~ /^\d+(\.\d+)?$/) {
    1082                 if ($1 or $binaryTable or $isIPTC or ($short =~ /^CanonCustom/ and $tagID < 256)) {
    1083                     if ($tagID < 0x10000) {
    1084                         $tagIDstr = $tagID;
    1085                     } else {
    1086                         $tagIDstr = sprintf('0x%.8x',$tagID);
    1087                     }
     1433            if ($tagID =~ /^(-)?\d+(\.\d+)?$/) {
     1434                if ($1) {
     1435                    $tagIDstr = $tagID;
     1436                } elsif (defined $hexID) {
     1437                    $tagIDstr = $hexID ? sprintf('0x%.4x',$tagID) : $tagID;
     1438                } elsif (not $2 and not $binaryTable and not $isIPTC and
     1439                         not ($short =~ /^CanonCustom/ and $tagID < 256))
     1440                {
     1441                    $tagIDstr = sprintf('0x%.4x',$tagID);
     1442                } elsif ($tagID < 0x10000) {
     1443                    $tagIDstr = $tagID;
    10881444                } else {
    1089                     $tagIDstr = sprintf('0x%.4x',$tagID);
     1445                    $tagIDstr = sprintf('0x%.8x',$tagID);
    10901446                }
    10911447            } elsif ($short eq 'DICOM') {
    10921448                ($tagIDstr = $tagID) =~ s/_/,/;
     1449            } elsif ($tagID =~ /^0x([0-9a-f]+)\.(\d+)$/) {  # DR4 tags like '0x20500.0'
     1450                $tagIDstr = $tagID;
    10931451            } else {
    10941452                # convert non-printable characters to hex escape sequences
     
    10981456                    $tagIDstr = qq{"$tagID"};
    10991457                } else {
    1100                     $tagIDstr = "'$tagID'";
     1458                    $tagIDstr = "'${tagID}'";
    11011459                }
    11021460            }
     
    11221480                    foreach $tagID (@{$$table{$var}}) {
    11231481                        $$hash{$tagID} and delete($$hash{$tagID}), next;
    1124                         warn "Warning: Extra $var for $short tag $tagID\n";
     1482                        warn sprintf("Warning: Extra %s for %s tag %d (0x%.4x)\n",
     1483                                     $var, $short, $tagID, $tagID);
    11251484                    }
    11261485                }
    11271486                foreach $tagID (sort keys %$hash) {
    1128                     warn "Warning: Missing $var for $short $$hash{$tagID}\n";
     1487                    warn sprintf("Warning: Missing %s for %s %s, tag %d (0x%.4x)\n",
     1488                                 $var, $short, $$hash{$tagID}, $tagID, $tagID);
    11291489                }
    11301490            }
     
    11351495    foreach $strName (keys %structs) {
    11361496        my $struct = $structLookup{$strName};
    1137         my $info = $tagNameInfo{"XMP $strName Struct"} = [ ];
     1497        my $fullName = ($strName =~ / / ? '' : 'XMP ') . "$strName Struct";
     1498        my $info = $tagNameInfo{$fullName} = [ ];
    11381499        my $tag;
    11391500        foreach $tag (sort keys %$struct) {
    11401501            my $tagInfo = $$struct{$tag};
    1141             next unless ref $tagInfo eq 'HASH';
     1502            next unless ref $tagInfo eq 'HASH' and $tag ne 'NAMESPACE' and $tag ne 'GROUPS';
     1503            warn "WARNING: $strName Struct containes $tag\n" if $Image::ExifTool::specialTags{$tag};
    11421504            my $writable = $$tagInfo{Writable};
    11431505            my @vals;
     
    11541516            }
    11551517            $writable .= '+' if $$tagInfo{List};
     1518            push @vals, "($$tagInfo{Notes})" if $$tagInfo{Notes};
     1519            # handle PrintConv lookups in Structure elements
     1520            my $printConv = $$tagInfo{PrintConv};
     1521            if (ref $printConv eq 'HASH') {
     1522                foreach (sort keys %$printConv) {
     1523                    next if /^(OTHER|BITMASK)$/;
     1524                    push @vals, "$_ = $$printConv{$_}";
     1525                }
     1526            }
    11561527            push @$info, [
    11571528                $tag,
     
    11691540# Rewrite this file to build the lookup tables
    11701541# Inputs: 0) BuildTagLookup object reference
    1171 #         1) output tag lookup module name (ie. 'lib/Image/ExifTool/TagLookup.pm')
     1542#         1) output tag lookup module name (eg. 'lib/Image/ExifTool/TagLookup.pm')
    11721543# Returns: true on success
    11731544sub WriteTagLookup($$)
     
    12101581    foreach $tableName (@tableNames) {
    12111582        if ($$tableWritable{$tableName}) {
    1212             print OUTFILE "\t'$tableName',\n";
     1583            print OUTFILE "\t'${tableName}',\n";
    12131584            $wrNum{$count} = $num++;
    12141585        }
     
    12231594        next unless $$tagLookup{$tag};
    12241595        my $n = scalar keys %{$$tagLookup{$tag}};
    1225         warn "Warning: $n writable '$tag' tags!\n" if $n > 1;
     1596        warn "Warning: $n writable '${tag}' tags!\n" if $n > 1;
    12261597    }
    12271598    print OUTFILE ");\n\n# lookup for all writable tags\nmy \%tagLookup = (\n";
    12281599    foreach $tag (sort keys %$tagLookup) {
    1229         print OUTFILE "\t'$tag' => { ";
     1600        print OUTFILE "\t'${tag}' => { ";
    12301601        my @tableNums = sort { $a <=> $b } keys %{$$tagLookup{$tag}};
    12311602        my (@entries, $tableNum);
     
    12491620                # reference to root structure ID must come first in lookup
    12501621                # (so we can generate the flattened tags just before we need them)
    1251                 unshift @tagIDs, "\\'$rootID'" if $rootID;
     1622                unshift @tagIDs, "\\'${rootID}'" if $rootID;
    12521623                $entry = '[' . join(',', @tagIDs) . ']';
    12531624            } elsif ($tagID =~ /^\d+$/) {
    12541625                $entry = sprintf('0x%x',$tagID);
    12551626            } else {
    1256                 $entry = "'$tagID'";
     1627                my $quot = "'";
     1628                # escape non-printable characters in tag ID if necessary
     1629                $quot = '"' if $tagID =~ s/[\x00-\x1f,\x7f-\xff]/sprintf('\\x%.2x',ord($&))/ge;
     1630                $entry = "$quot${tagID}$quot";
    12571631            }
    12581632            my $wrNum = $wrNum{$tableNum};
     
    12691643    foreach $tag (sort keys %$tagExists) {
    12701644        next if $$tagLookup{$tag};
    1271         print OUTFILE "\t'$tag' => 1,\n";
     1645        print OUTFILE "\t'${tag}' => 1,\n";
    12721646    }
    12731647#
     
    12781652    print OUTFILE "my \%compositeModules = (\n";
    12791653    foreach (sort keys %$compositeModules) {
    1280         print OUTFILE "\t'$_' => '$$compositeModules{$_}',\n";
     1654        print OUTFILE "\t'${_}' => '$$compositeModules{$_}',\n";
    12811655    }
    12821656    print OUTFILE ");\n\n";
     
    13251699
    13261700#------------------------------------------------------------------------------
    1327 # sort numbers first numerically, then strings alphabetically (case insensitive)
    1328 sub NumbersFirst
     1701# Sort numbers first numerically, then strings alphabetically (case insensitive)
     1702# - two global variables are used to change the sort algorithm:
     1703#   $numbersFirst: -1 = put numbers after other strings
     1704#                   1 = put numbers before other strings
     1705#                   2 = put numbers first, but negative numbers last
     1706#   $caseInsensitive: flag set for case-insensitive sorting
     1707sub NumbersFirst($$)
    13291708{
     1709    my ($a, $b) = @_;
    13301710    my $rtnVal;
    1331     my $bNum = ($b =~ /^-?[0-9]+(\.\d*)?$/);
    1332     if ($a =~ /^-?[0-9]+(\.\d*)?$/) {
    1333         $rtnVal = ($bNum ? $a <=> $b : -$numbersFirst);
    1334     } elsif ($bNum) {
     1711    my ($bNum, $bDec);
     1712    ($bNum, $bDec) = ($1, $3) if $b =~ /^(-?[0-9]+)(\.(\d*))?$/;
     1713    if ($a =~ /^(-?[0-9]+)(\.(\d*))?$/) {
     1714        if (defined $bNum) {
     1715            $bNum += 1e9 if $numbersFirst == 2 and $bNum < 0;
     1716            my $aInt = $1;
     1717            $aInt += 1e9 if $numbersFirst == 2 and $aInt < 0;
     1718            # compare integer part as a number
     1719            $rtnVal = $aInt <=> $bNum;
     1720            unless ($rtnVal) {
     1721                my $aDec = $3 || 0;
     1722                $bDec or $bDec = 0;
     1723                # compare decimal part as an integer too
     1724                # (so that "1.10" comes after "1.9")
     1725                $rtnVal = $aDec <=> $bDec;
     1726            }
     1727        } else {
     1728            $rtnVal = -$numbersFirst;
     1729        }
     1730    } elsif (defined $bNum) {
    13351731        $rtnVal = $numbersFirst;
    13361732    } else {
    13371733        my ($a2, $b2) = ($a, $b);
    13381734        # expand numbers to 3 digits (with restrictions to avoid messing up ascii-hex tags)
    1339         $a2 =~ s/(\d+)/sprintf("%.3d",$1)/eg if $a2 =~ /^(APP)?[.0-9 ]*$/ and length($a2)<16;
    1340         $b2 =~ s/(\d+)/sprintf("%.3d",$1)/eg if $b2 =~ /^(APP)?[.0-9 ]*$/ and length($b2)<16;
     1735        $a2 =~ s/(\d+)/sprintf("%.3d",$1)/eg if $a2 =~ /^(APP|DMC-\w+ )?[.0-9 ]*$/ and length($a2)<16;
     1736        $b2 =~ s/(\d+)/sprintf("%.3d",$1)/eg if $b2 =~ /^(APP|DMC-\w+ )?[.0-9 ]*$/ and length($b2)<16;
    13411737        $caseInsensitive and $rtnVal = (lc($a2) cmp lc($b2));
    13421738        $rtnVal or $rtnVal = ($a2 cmp $b2);
     
    13461742
    13471743#------------------------------------------------------------------------------
    1348 # Convert pod documentation to pod
     1744# Convert our pod-like documentation to pod
    13491745# (funny, I know, but the pod headings must be hidden to prevent confusing
    13501746#  the pod parser)
     
    13571753    $doc =~ s/\n~/\n=/g;
    13581754    $doc =~ s/L<[^>]+?\|(http[^>]+)>/L<$1>/g; # POD doesn't support text for http links
     1755    $doc =~ s/L<([^>]+?)\|[^>]+\.html(#\w+)?>/$1/g;  # remove relative HTML links
    13591756    return $doc;
    13601757}
    13611758
    13621759#------------------------------------------------------------------------------
    1363 # Convert pod documentation to html
     1760# Convert our pod-like documentation to html
    13641761# Inputs: 0) string
    13651762sub Doc2Html($)
     
    13701767    $doc =~ s/C&lt;(.*?)&gt;/<code>$1<\/code>/sg;
    13711768    $doc =~ s/I&lt;(.*?)&gt;/<i>$1<\/i>/sg;
    1372     $doc =~ s{L&lt;([^&]+?)\|\Q$homePage\E/TagNames/(.*?)&gt;}{<a href="$2">$1<\/a>}sg;
     1769    # L<some text|https://exiftool.org/struct.html#Fields> --> <a href="../struct.html#Fields">some text</a>
    13731770    $doc =~ s{L&lt;([^&]+?)\|\Q$homePage\E/(.*?)&gt;}{<a href="../$2">$1<\/a>}sg;
    1374     $doc =~ s{L&lt;\Q$homePage\E/TagNames/(.*?)&gt;}{<a href="$1">$1<\/a>}sg;
     1771    # L<https://exiftool.org/struct.html> --> <a href="https://exiftool.org/struct.html">https://exiftool.org/struct.html</a>
    13751772    $doc =~ s{L&lt;\Q$homePage\E/(.*?)&gt;}{<a href="../$1">$1<\/a>}sg;
     1773    # L<XMP DICOM Tags|Image::ExifTool::TagNames/XMP DICOM Tags> --> <a href="XMP.html#DICOM">XMP DICOM Tags</a>
     1774    # (specify "Image::ExifTool::TagNames" to link to another html file)
     1775    $doc =~ s{L&lt;([^&]+?)\|Image::ExifTool::TagNames/(\w+) ([^/&|]+) Tags&gt;}{<a href="$2.html#$3">$1</a>}sg;
     1776    # L<DICOM Tags|Image::ExifTool::TagNames/DICOM Tags> --> <a href="DICOM.html">DICOM Tags</a>
     1777    $doc =~ s{L&lt;([^&]+?)\|Image::ExifTool::TagNames/(\w+) Tags&gt;}{<a href="$2.html">$1</a>}sg;
     1778    # L<dc|/XMP dc Tags> --> <a href="#dc">dc</a>
     1779    # (a relative POD link turns into a relative HTML link)
    13761780    $doc =~ s{L&lt;([^&]+?)\|/\w+ ([^/&|]+) Tags&gt;}{<a href="#$2">$1</a>}sg;
     1781    # L<sample config file|../config.html> --> <a href="../config.html">sample config file</a>
    13771782    $doc =~ s/L&lt;([^&]+?)\|(.+?)&gt;/<a href="$2">$1<\/a>/sg;
    1378     $doc =~ s/L&lt;(.*?)&gt;/<a href="$1">$1<\/a>/sg;
     1783    # L<http://some.web.site/> --> <a href="http://some.web.site">http://some.web.site</a>
     1784    $doc =~ s/L&lt;(http.*?)&gt;/<a href="$1">$1<\/a>/sg;
    13791785    return $doc;
    13801786}
    13811787
    13821788#------------------------------------------------------------------------------
     1789# Tweak order of tables
     1790# Inputs: 0) table list ref, 1) reference to tweak hash
     1791sub TweakOrder($$)
     1792{
     1793    local $_;
     1794    my ($sortedTables, $tweakOrder) = @_;
     1795    my @tweak = sort keys %$tweakOrder;
     1796    my (%addedMain, @sorted);
     1797    # flag files which have a "Main" table
     1798    foreach (@$sortedTables) {
     1799        $addedMain{$1} = 0 if /^Image::ExifTool::(\w+)::(\w+)/ and $2 eq 'Main';
     1800    }
     1801    # make sure that the main table always comes first in each file
     1802    foreach (@$sortedTables) {
     1803        if (/^Image::ExifTool::(\w+)::(\w+)/) {
     1804            if ($addedMain{$1}) {
     1805                next if $2 eq 'Main';   # don't add again
     1806            } elsif (defined $addedMain{$1}) {
     1807                push @sorted, "Image::ExifTool::${1}::Main" if $2 ne 'Main';
     1808                $addedMain{$1} = 1;
     1809            }
     1810        }
     1811        push @sorted, $_;
     1812    }
     1813    @$sortedTables = @sorted;
     1814    # apply manual tweaks
     1815    while (@tweak) {
     1816        my $table = shift @tweak;
     1817        my $first = $$tweakOrder{$table};
     1818        if ($$tweakOrder{$first}) {
     1819            push @tweak, $table;    # must defer this till later
     1820            next;
     1821        }
     1822        delete $$tweakOrder{$table}; # because the table won't move again
     1823        my @moving = grep /^Image::ExifTool::$table\b/, @$sortedTables;
     1824        my @notMoving = grep !/^Image::ExifTool::$table\b/, @$sortedTables;
     1825        my @after;
     1826        while (@notMoving) {
     1827            last if $notMoving[-1] =~ /^Image::ExifTool::$first\b/;
     1828            unshift @after, pop @notMoving;
     1829        }
     1830        @$sortedTables = (@notMoving, @moving, @after);
     1831    }
     1832}
     1833
     1834#------------------------------------------------------------------------------
     1835# Get a list of sorted tag ID's from a table
     1836# Inputs: 0) tag table ref
     1837# Returns: list of sorted keys
     1838sub SortedTagTableKeys($)
     1839{
     1840    my $table = shift;
     1841    my $vars = $$table{VARS} || { };
     1842    my @keys = TagTableKeys($table);
     1843    if ($$vars{NO_ID}) {
     1844        # sort by tag name if ID not shown
     1845        my ($key, %name);
     1846        foreach $key (@keys) {
     1847            my ($tagInfo) = GetTagInfoList($table, $key);
     1848            $name{$key} = $$tagInfo{Name};
     1849        }
     1850        return sort { $name{$a} cmp $name{$b} or $a cmp $b } @keys;
     1851    } else {
     1852        my $sortProc = $$vars{SORT_PROC} || \&NumbersFirst;
     1853        return sort { &$sortProc($a,$b) } @keys;
     1854    }
     1855}
     1856
     1857#------------------------------------------------------------------------------
    13831858# Get the order that we want to print the tables in the documentation
     1859# Inputs: 0-N) Extra tables to add at end
    13841860# Returns: tables in the order we want
    1385 sub GetTableOrder()
     1861sub GetTableOrder(@)
    13861862{
    13871863    my %gotTable;
     
    14061882        $caseInsensitive = ($$table{GROUPS} and $$table{GROUPS}{0} eq 'XMP');
    14071883        $numbersFirst = -1 if $$table{VARS} and $$table{VARS}{ALPHA_FIRST};
    1408         my @keys = sort NumbersFirst TagTableKeys($table);
     1884        my @keys = SortedTagTableKeys($table);
    14091885        $numbersFirst = 1;
    14101886        foreach (@keys) {
     
    14501926        }
    14511927    }
     1928    # add the extra tables
     1929    push @sortedTables, @_;
    14521930    # tweak the table order
    1453     my %tweakOrder = (
    1454         JPEG    => '-',     # JPEG comes first
    1455         IPTC    => 'Exif',  # put IPTC after EXIF,
    1456         GPS     => 'XMP',   # etc...
    1457         GeoTiff => 'GPS',
    1458         CanonVRD=> 'CanonCustom',
    1459         Kodak   => 'JVC',
    1460        'Kodak::IFD' => 'Kodak::Unknown',
    1461        'Kodak::TextualInfo' => 'Kodak::IFD',
    1462        'Kodak::Processing' => 'Kodak::TextualInfo',
    1463         Leaf    => 'Kodak',
    1464         Minolta => 'Leaf',
    1465         SonyIDC => 'Sony',
    1466         Unknown => 'SonyIDC',
    1467         DNG     => 'Unknown',
    1468         PrintIM => 'ICC_Profile',
    1469         ID3     => 'PostScript',
    1470         MinoltaRaw => 'KyoceraRaw',
    1471         KyoceraRaw => 'CanonRaw',
    1472         SigmaRaw => 'PanasonicRaw',
    1473         Olympus => 'NikonCapture',
    1474         PhotoMechanic => 'FotoStation',
    1475         Microsoft     => 'PhotoMechanic',
    1476        'Microsoft::MP'=> 'Microsoft::MP1',
    1477         GIMP    => 'Microsoft',
    1478        'Nikon::CameraSettingsD300' => 'Nikon::ShotInfoD300b',
    1479        'Pentax::LensData' => 'Pentax::LensInfo2',
    1480        'Sony::SRF2' => 'Sony::SRF',
    1481        'Samsung::INFO' => 'Samsung::Type2', # (necessary because Samsung doesn't have a main table)
    1482        'Samsung::MP4' => 'Samsung::INFO', # (necessary because Samsung doesn't have a main table)
    1483     );
    1484     my @tweak = sort keys %tweakOrder;
    1485     while (@tweak) {
    1486         my $table = shift @tweak;
    1487         my $first = $tweakOrder{$table};
    1488         if ($tweakOrder{$first}) {
    1489             push @tweak, $table;    # must defer this till later
    1490             next;
    1491         }
    1492         delete $tweakOrder{$table}; # because the table won't move again
    1493         my @moving = grep /^Image::ExifTool::$table\b/, @sortedTables;
    1494         my @notMoving = grep !/^Image::ExifTool::$table\b/, @sortedTables;
    1495         my @after;
    1496         while (@notMoving) {
    1497             last if $notMoving[-1] =~ /^Image::ExifTool::$first\b/;
    1498             unshift @after, pop @notMoving;
    1499         }
    1500         @sortedTables = (@notMoving, @moving, @after);
    1501     }
    1502     return @sortedTables
     1931    TweakOrder(\@sortedTables, \%tweakOrder);
     1932
     1933    return @sortedTables;
    15031934}
    15041935
     
    15191950        $htmlFile = "$htmldir/TagNames/$class.html";
    15201951        $head = $category;
    1521         if ($head =~ /^XMP .+ Struct$/) {
     1952        if ($head =~ /^\S+ .+ Struct$/) {
    15221953            pop @names;
    15231954        } else {
     
    15351966    } else {
    15361967        open(HTMLFILE, ">${htmlFile}_tmp") or return 0;
    1537         print HTMLFILE "$docType<html>\n<head>\n<title>$title</title>\n";
     1968        print HTMLFILE "$docType<html>\n";
     1969        print HTMLFILE "<!-- (this file generated automatically by Image::ExifTool::BuildTagLookup) -->\n";
     1970        print HTMLFILE "<head>\n<title>$title</title>\n";
    15381971        print HTMLFILE "<link rel=stylesheet type='text/css' href='style.css' title='Style'>\n";
    15391972        print HTMLFILE "</head>\n<body>\n";
     
    15451978        }
    15461979    }
    1547     $head = "<a name='$url'>$head</a>" if $url;
     1980    $head = "<a name='${url}'>$head</a>" if $url;
    15481981    print HTMLFILE "<h2$top>$head</h2>\n" or return 0;
    15491982    print HTMLFILE '<p>',Doc2Html($docs{$category}),"</p>\n" if $docs{$category};
     
    15802013        print HTMLFILE "<hr>\n";
    15812014        print HTMLFILE "(This document generated automatically by Image::ExifTool::BuildTagLookup)\n";
     2015        print HTMLFILE "<br><i>Created $createDate</i>\n" if $htmlFile eq 'html/TagNames/index.html';
    15822016        print HTMLFILE "<br><i>Last revised $fileDate</i>\n";
    15832017        print HTMLFILE "<p class=lf><a href=";
     
    16282062
    16292063#------------------------------------------------------------------------------
     2064# Get bitmask for POD documentation
     2065# Inputs: mask string from HTML docs
     2066# Returns: mask string for POD, or ''
     2067sub PodMask($)
     2068{
     2069    my $mask = shift;
     2070    return '' unless $mask =~ /^\[val( >> (\d+))? \& (0x[\da-f]+)\]/;
     2071    return sprintf(' & 0x%.2x', hex($3) << ($2 || 0));
     2072}
     2073
     2074#------------------------------------------------------------------------------
    16302075# Write the TagName HTML and POD documentation
    16312076# Inputs: 0) BuildTagLookup object reference
    1632 #         1) output pod file (ie. 'lib/Image/ExifTool/TagNames.pod')
    1633 #         2) output html directory (ie. 'html')
     2077#         1) output pod file (eg. 'lib/Image/ExifTool/TagNames.pod')
     2078#         2) output html directory (eg. 'html')
    16342079# Returns: true on success
    16352080# Notes: My apologies for the patchwork code, but this is only used to generate the docs.
     
    16502095    # open the file and write the header
    16512096    open(PODFILE, ">$podFile") or return 0;
     2097    $docs{ExifTool} = eval qq{"$docs{ExifTool}"};
    16522098    print PODFILE Doc2Pod($docs{PodHeader}, $docs{ExifTool}, $docs{ExifTool2});
    1653     mkdir "$htmldir/TagNames";
     2099    mkdir "$htmldir/TagNames", 0777;
    16542100    OpenHtmlFile($htmldir) or return 0;
    16552101    print HTMLFILE "<blockquote>\n";
     
    16582104    print HTMLFILE "<th colspan=$columns><span class=l>Tag Table Index</span></th></tr>\n";
    16592105    print HTMLFILE "<tr class=b><td width='$percent%'>\n";
    1660     # write the index
    1661     my @tableNames = GetTableOrder();
    1662     # add shortcuts last
    1663     push @tableNames, 'Image::ExifTool::Shortcuts::Main';
    1664     push @tableNames, @pluginTables;
     2106
     2107    # get the full table list, adding shortcuts and plug-in tables to the end
     2108    my @tableNames = GetTableOrder('Image::ExifTool::Shortcuts::Main', @pluginTables);
     2109
    16652110    # get list of headings and add any missing ones
    16662111    my $heading = 'xxx';
     
    16982143        $short = $tableName unless $short;
    16992144        $url = "$short.html";
    1700         print HTMLFILE "<a href='$url'>$short</a>";
     2145        print HTMLFILE "<a href='${url}'>$short</a>";
    17012146        ++$count;
    17022147    }
    17032148    print HTMLFILE "\n</td></tr></table></td></tr></table></blockquote>\n";
    17042149    print HTMLFILE '<p>',Doc2Html($docs{ExifTool2}),"</p>\n";
     2150
    17052151    # write all the tag tables
    17062152    while (@tableNames or @sepTables or @structs) {
     
    17132159            if ($notes) {
    17142160                # remove unnecessary whitespace
    1715                 $notes =~ s/(^\s+|\s+$)//g;
     2161                $notes =~ s/^\s+//; $notes =~ s/\s+$//;
    17162162                $notes =~ s/(^[ \t]+|[ \t]+$)//mg;
    17172163            }
    17182164            my $head = $tableName;
    1719             $head =~ s/.* //;
     2165            $head =~ s/^.* //s;
    17202166            close HTMLFILE;
    17212167            if (OpenHtmlFile($htmldir, $tableName, 1)) {
     
    17272173                my $wid = 0;
    17282174                my @keys;
    1729                 foreach (sort NumbersFirst keys %$printConv) {
     2175                foreach (sort { NumbersFirst($a,$b) } keys %$printConv) {
    17302176                    next if /^(Notes|PrintHex|PrintString|OTHER)$/;
    17312177                    $align = '' if $align and /[^\d]/;
     
    17332179                    $wid = $w if $wid < $w;
    17342180                    push @keys, $_;
     2181                    if ($$printConv{$_} =~ /[\0-\x1f\x7f-\xff]/) {
     2182                        warn "Warning: Special characters in $tableName PrintConv ($$printConv{$_})\n";
     2183                    }
    17352184                }
    17362185                $wid = length($tableName)+7 if $wid < length($tableName)+7;
    17372186                # print in multiple columns if there is room
    1738                 my $cols = int(80 / ($wid + 4));
     2187                my $cols = int(110 / ($wid + 4));
    17392188                $cols = 1 if $cols < 1 or $cols > @keys or @keys < 4;
    17402189                my $rows = int((scalar(@keys) + $cols - 1) / $cols);
     
    17542203                            $prt = '= ' . EscapeHTML($$printConv{$key});
    17552204                            if ($$printConv{PrintHex}) {
     2205                                $index =~ s/(\.\d+)$//; # remove decimal
    17562206                                $index = sprintf('0x%x',$index);
     2207                                $index .= $1 if $1; # add back decimal
    17572208                            } elsif ($$printConv{PrintString} or
    17582209                                $index !~ /^[+-]?(?=\d|\.\d)\d*(\.\d*)?$/)
     
    17862237            $isStruct = $$structLookup{$tableName};
    17872238            $isStruct or warn("Missing structure $tableName\n"), next;
    1788             $short = $tableName = "XMP $tableName Struct";
     2239            $tableName = "XMP $tableName" unless $tableName =~ / /;
     2240            $short = $tableName = "$tableName Struct";
    17892241            my $maxLen = 0;
    17902242            $maxLen < length and $maxLen = length foreach keys %$isStruct;
     
    18002252            }
    18012253        }
    1802         my $isExif = $tableName eq 'Image::ExifTool::Exif::Main' ? 1 : undef;
    1803         my $isRiff = $tableName eq 'Image::ExifTool::RIFF::Info' ? 1 : undef;
     2254        my $isExif = ($tableName eq 'Image::ExifTool::Exif::Main');
     2255        my $isRiff = ($tableName eq 'Image::ExifTool::RIFF::Info');
     2256        my $isXmpMain = ($tableName eq 'Image::ExifTool::XMP::Main');
    18042257        my $info = $$tagNameInfo{$tableName};
    18052258        my $id = $$idLabel{$tableName};
     
    18072260        # widths of the different columns in the POD documentation
    18082261        my ($wID,$wTag,$wReq,$wGrp) = (8,36,24,10);
    1809         my ($composite, $derived, $notes, $prefix);
     2262        my ($composite, $derived, $notes, $longTags, $wasLong, $prefix);
    18102263        if ($short eq 'Shortcuts') {
    18112264            $derived = '<th>Refers To</th>';
     
    18172270            my $table = GetTagTable($tableName);
    18182271            $notes = $$table{NOTES};
     2272            if ($$table{NAMESPACE}) {
     2273                my $ns = $Image::ExifTool::XMP::stdXlatNS{$$table{NAMESPACE}} || $$table{NAMESPACE};
     2274                my $msg = "These tags belong to the ExifTool XMP-$ns family 1 group.";
     2275                if ($notes) {
     2276                    $notes =~ s/\s+$//;
     2277                    $notes .= "\n\n" . $msg;
     2278                } else {
     2279                    $notes = $msg;
     2280                }
     2281            }
     2282            $longTags = $$table{VARS}{LONG_TAGS} if $$table{VARS};
    18192283            if ($$table{GROUPS}{0} eq 'Composite') {
    18202284                $composite = 1;
     
    18282292        if ($notes) {
    18292293            # remove unnecessary whitespace
    1830             $notes =~ s/(^\s+|\s+$)//g;
     2294            $notes =~ s/^\s+//; $notes =~ s/\s+$//;
    18312295            $notes =~ s/(^[ \t]+|[ \t]+$)//mg;
    18322296            if ($notes =~ /leading '(.*?_)' which/) {
     
    18502314            my $longTag = $self->{LONG_NAME}->{$tableName};
    18512315            if ($wTag < $longTag) {
    1852                 warn "Notice: Long tags in $tableName table\n";
    18532316                if ($wID - $longTag + $wTag >= 6) { # don't let ID column get too narrow
    18542317                    $wID -= $longTag - $wTag;
    18552318                    $wTag = $longTag;
    18562319                }
     2320                $wasLong = 1 if $wID <= $self->{LONG_ID}->{$tableName};
    18572321            }
    18582322        } elsif ($composite) {
     
    18632327            $hid = '';
    18642328        }
    1865         if ($short eq 'EXIF') {
     2329        if ($short eq 'EXIF' or $short eq 'Extra') {
    18662330            $derived = '<th>Group</th>';
    18672331            $showGrp = 1;
     2332            $wGrp += 8 if $short eq 'Extra';    # tweak Group column width for "Extra" tags
    18682333            $wTag -= $wGrp + 1;
    18692334        }
     
    18872352        $line .= ' Writable';
    18882353        print PODFILE $line;
    1889         $line =~ s/^(\s*\w.{6}\w) /$1\t/;   # change space to tab after long ID label (ie. "Sequence")
     2354        $line =~ s/^(\s*\w.{6}\w) /$1\t/;   # change space to tab after long ID label (eg. "Sequence")
    18902355        $line =~ s/\S/-/g;
    18912356        $line =~ s/- -/---/g;
     
    19102375            if (not $id) {
    19112376                $idStr = '  ';
    1912             } elsif ($tagIDstr =~ /^\d+(\.\d+)?$/) {
     2377            } elsif ($tagIDstr =~ /^-?\d+(\.\d+)?$/) {
    19132378                $w = $wID - 3;
    19142379                $idStr = sprintf "  %${w}g    ", $tagIDstr;
     2380                my $tooLong = length($idStr) - 6 - $w;
     2381                if ($tooLong) {
     2382                    $tooLong = 3 if $tooLong > 3;
     2383                    $idStr = substr($idStr, 0, -$tooLong);
     2384                }
    19152385                $align = " class=r";
    19162386            } else {
     
    19262396                        # put tag name on next line if ID is too long
    19272397                        $idStr = "  $tagIDstr\n   " . (' ' x $w);
    1928                         warn "Notice: Split $$tagNames[0] line\n";
     2398                        if ($longTags) {
     2399                            --$longTags;
     2400                        } else {
     2401                            warn "Notice: Split $$tagNames[0] line\n";
     2402                        }
    19292403                    }
    19302404                }
     
    19382412            my $wrStr = shift @vals;
    19392413            my $subdir;
    1940             my @masks = grep /^\[Mask 0x[\da-f]+\]/, @$values;
     2414            my @masks = grep /^\[val( >> \d+)? \& 0x[\da-f]+\]/, @$values;
    19412415            my $tag = shift @tags;
    19422416            # if this is a subdirectory or structure, print subdir name (from values) instead of writable
     
    19452419                if (@masks) {
    19462420                    # combine any mask into the format string
    1947                     $wrStr .= " & $1" if $masks[0] =~ /(0x[\da-f]+)/;
     2421                    $wrStr .= PodMask($masks[0]);
    19482422                    shift @masks;
    1949                     @vals = grep !/^\[Mask 0x[\da-f]+\]/, @$values;
     2423                    @vals = grep !/^\[val( >> \d+)? \& 0x[\da-f]+\]/, @$values;
    19502424                } else {
    19512425                    @vals = @$values;
     
    19582432                    $vals[$i] = $$writable[$i] unless defined $vals[$i];
    19592433                    if (@masks) {
    1960                         $vals[$i] .= " & $1" if $masks[0] =~ /(0x[\da-f]+)/;
     2434                        $vals[$i] .= PodMask($masks[0]);
    19612435                        shift @masks;
    19622436                    }
     
    19642438                if ($$sepTable{$vals[0]}) {
    19652439                    $wrStr =~ s/^[-=]//;
    1966                     $wrStr = 'N' unless $wrStr;
     2440                    $wrStr = 'no' unless $wrStr;
    19672441                } elsif ($$structs{$vals[0]}) {
    19682442                    my $flags = $wrStr =~ /([+_]+)$/ ? $1 : '';
     
    19722446                }
    19732447                shift @vals;
    1974             } elsif ($wrStr and $wrStr ne 'N' and @masks) {
     2448            } elsif ($wrStr and $wrStr ne 'no' and @masks) {
    19752449                # fill in missing entries if masks are different
    19762450                my $mask = shift @masks;
     
    19812455                }
    19822456                # add Mask to Writable column in POD doc
    1983                 $wrStr .= " & $1" if $mask =~ /(0x[\da-f]+)/;
    1984             }
    1985             printf PODFILE "%s%-${wTag2}s", $idStr, $tag;
    1986             warn "Warning: Pushed $tag\n" if $id and length($tag) > $wTag2;
    1987             printf PODFILE " %-${wGrp}s", shift(@wGrp) || '-' if $showGrp;
     2457                $wrStr .= PodMask($mask);
     2458            }
     2459            my $pod = sprintf "%s%-${wTag2}s", $idStr, $tag;
     2460            my $tGrp = $wGrp;
     2461            if ($id and length($tag) > $wTag2) {
     2462                my $madeRoom;
     2463                if ($showGrp) {
     2464                    my $wGrp0 = length($wGrp[0] || '-');
     2465                    if (not $composite and $wGrp > $wGrp0) {
     2466                        $tGrp = $wGrp - (length($tag) - $wTag2);
     2467                        if ($tGrp < length $wGrp0) {
     2468                            $tGrp = length $wGrp0;
     2469                        } else {
     2470                            $madeRoom = 1;
     2471                        }
     2472                    }
     2473                }
     2474                warn "Warning: Pushed $tag\n" unless $madeRoom;
     2475            }
     2476            $pod .= sprintf " %-${tGrp}s", shift(@wGrp) || '-' if $showGrp;
    19882477            if ($composite) {
    19892478                @reqs = @$require;
    19902479                $w = $wReq; # Keep writable column in line
    19912480                length($tag) > $wTag2 and $w -= length($tag) - $wTag2;
    1992                 printf PODFILE " %-${w}s", shift(@reqs) || '';
    1993             }
    1994             printf PODFILE " $wrStr\n";
     2481                $pod .= sprintf " %-${w}s", shift(@reqs) || '';
     2482            }
     2483            $pod .= " $wrStr";
     2484            # limit line length to 82 characters (even if it means messing up column alignment)
     2485            if (length $pod > 82 and $pod !~ /\n/) {
     2486                my $remove = length($pod) - 82;
     2487                $pod =~ s/(\w)\s{$remove}/$1/;
     2488            }
     2489            print PODFILE $pod, "\n";
    19952490            my $numTags = scalar @$tagNames;
    19962491            my $n = 0;
     
    20192514                        $line .= " $val";
    20202515                        if (@masks) {
    2021                             $line .= " & $1" if $masks[0] =~ /(0x[\da-f]+)/;
     2516                            $line .= PodMask($masks[0]);
    20222517                            shift @masks;
    20232518                        }
     
    20312526                push @htmlTags, EscapeHTML($_);
    20322527            }
    2033             if (($isExif and $exifSpec{hex $tagIDstr}) or
    2034                 ($isRiff and $tagIDstr=~/(\w+)/ and $riffSpec{$1}))
     2528            if (($isExif and $Image::ExifTool::Validate::exifSpec{hex $tagIDstr}) or
     2529                ($isRiff and $tagIDstr=~/(\w+)/ and $riffSpec{$1}) or
     2530                ($isXmpMain and $tagIDstr=~/([-\w]+)/ and $xmpSpec{$1}))
    20352531            {
    20362532                # underline "unknown" makernote tags only
     
    20452541            }
    20462542            # add tooltip for hex conversion of Tag ID
    2047             if ($tagIDstr =~ /^0x[0-9a-f]+$/i) {
    2048                 $tip = sprintf(" title='$tagIDstr = %u'",hex $tagIDstr);
     2543            if ($tagIDstr =~ /^(0x[0-9a-f]+)(\.\d+)?$/i) {
     2544                $tip = sprintf(" title='$tagIDstr = %u%s'", hex($1), $2||'');
    20492545            } elsif ($tagIDstr =~ /^(\d+)(\.\d*)?$/) {
    20502546                $tip = sprintf(" title='%u = 0x%x'", $1, $1);
     
    20592555                '_' => 'Flattened',
    20602556                '+' => 'List',
    2061                 '/' => 'Avoided',
     2557                '/' => 'Avoid',
    20622558                '~' => 'Writable only with -n',
    20632559                '!' => 'Unsafe',
    20642560                '*' => 'Protected',
    20652561                ':' => 'Mandatory',
     2562                '^' => 'Deletable',
    20662563            );
    20672564            my ($wstr, %hasAttr, @hasAttr);
    20682565            foreach $wstr (@$writable) {
    2069                 next unless $wstr =~ m{([+/~!*:_]+)$};
     2566                next unless $wstr =~ m{([+/~!*:_^]+)$};
    20702567                my @a = split //, $1;
    20712568                foreach (@a) {
     
    20982595                    foreach (@$values) {
    20992596                        if (/^\(/) {
     2597                            $_ = Doc2Html($_);
    21002598                            # set the note font
    21012599                            $smallNote = 1 if $numTags < 2;
     
    21042602                        }
    21052603                        # make text in square brackets small
    2106                         /^\[/ and push(@values, "<span class=s>$_</span>"), next;
     2604                        if (/^\[/) {
     2605                            if (s/^\[!HTML\]//) {
     2606                                push @values, $_;
     2607                            } else {
     2608                                $_ = Doc2Html($_);
     2609                                push @values, "<span class=s>$_</span>";
     2610                            }
     2611                            next;
     2612                        }
    21072613                        /=/ and push(@values, $_), next;
    21082614                        my @names = split;
     
    21122618                            $suffix = ' Values';
    21132619                        }
    2114                         # currently all structures are in XMP documentation
    2115                         if ($$structs{$_} and $short =~ /^XMP/) {
    2116                             unshift @names, 'XMP';
     2620                        # handle structures specially
     2621                        if ($$structs{$_}) {
     2622                            # assume XMP module for this struct unless otherwise specified
     2623                            unshift @names, 'XMP' unless / /;
    21172624                            push @structs, $_;  # list this later
     2625                            # hack to put Area Struct in with XMP tags,
     2626                            # even though it is only used by the MWG module
     2627                            push @structs, 'Area' if $_ eq 'Dimensions';
    21182628                            $suffix = ' Struct';
    21192629                        }
    21202630                        $url = (shift @names) . '.html';
    21212631                        @names and $url .= '#' . join '_', @names;
    2122                         push @values, "--&gt; <a href='$url'>$_$suffix</a>";
     2632                        push @values, "--&gt; <a href='${url}'>$_$suffix</a>";
    21232633                    }
    21242634                    # put small note last
     
    21512661            print HTMLFILE "</td></tr>\n";
    21522662        }
     2663        warn "$longTags unaccounted-for long tags in $tableName\n" if $longTags;
     2664        if ($wasLong and not defined $longTags) {
     2665            warn "Notice: Long tags in $tableName table\n";
     2666        }
    21532667        unless ($infoCount) {
    2154             printf PODFILE "  [no tags known]\n";
     2668            print PODFILE "  [no tags known]\n";
    21552669            my $cols = 3;
    21562670            ++$cols if $hid;
     
    21822696Image::ExifTool::TagNames.pod, as well as HTML tag name documentation.  It
    21832697is used before each new ExifTool release to update the lookup tables and
    2184 documentation, but it is not used otherwise.
     2698documentation, but it is not used otherwise.  It also performs some
     2699validation and consistency checks on the tag tables.
    21852700
    21862701=head1 SYNOPSIS
     
    21902705  $builder = new Image::ExifTool::BuildTagLookup;
    21912706
     2707  # update Image::ExifTool::TagLookup
    21922708  $ok = $builder->WriteTagLookup('lib/Image/ExifTool/TagLookup.pm');
    21932709
     2710  # update the tag name documentation
    21942711  $ok = $builder->WriteTagNames('lib/Image/ExifTool/TagNames.pod','html');
     2712
     2713  # print some statistics
     2714  my $count = $$builder{COUNT};
     2715  foreach (sort keys %$count) {
     2716      printf "%5d %s\n", $$count{$_}, $_;
     2717  }
    21952718
    21962719=head1 MEMBER VARIABLES
     
    22102733WriteTagNames().
    22112734
     2735=item WRITE_PSEUDO
     2736
     2737Returned list of writable pseudo tags.
     2738
    22122739=back
    22132740
    22142741=head1 AUTHOR
    22152742
    2216 Copyright 2003-2011, Phil Harvey (phil at owl.phy.queensu.ca)
     2743Copyright 2003-2021, Phil Harvey (philharvey66 at gmail.com)
    22172744
    22182745This library is free software; you can redistribute it and/or modify it
Note: See TracChangeset for help on using the changeset viewer.