Index: trunk/src/com/drew/metadata/iptc/IptcDescriptor.java
===================================================================
--- trunk/src/com/drew/metadata/iptc/IptcDescriptor.java	(revision 4231)
+++ trunk/src/com/drew/metadata/iptc/IptcDescriptor.java	(revision 6127)
@@ -1,36 +1,242 @@
 /*
- * This is public domain software - that is, you can do whatever you want
- * with it, and include it software that is licensed under the GNU or the
- * BSD license, or whatever other licence you choose, including proprietary
- * closed source licenses.  I do ask that you leave this header in tact.
- *
- * If you make modifications to this code that you think would benefit the
- * wider community, please send me a copy and I'll post it on my site.
- *
- * If you make use of this code, I'd appreciate hearing about it.
- *   drew@drewnoakes.com
- * Latest version of this software kept at
- *   http://drewnoakes.com/
- *
- * Created by dnoakes on 21-Nov-2002 17:58:19 using IntelliJ IDEA.
+ * Copyright 2002-2012 Drew Noakes
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ *
+ * More information about this project is available at:
+ *
+ *    http://drewnoakes.com/code/exif/
+ *    http://code.google.com/p/metadata-extractor/
  */
 package com.drew.metadata.iptc;
 
-import com.drew.metadata.Directory;
+import com.drew.lang.StringUtil;
+import com.drew.lang.annotations.NotNull;
+import com.drew.lang.annotations.Nullable;
 import com.drew.metadata.TagDescriptor;
 
 /**
- *
+ * Provides human-readable string representations of tag values stored in a <code>IptcDirectory</code>.
+ * <p/>
+ * As the IPTC directory already stores values as strings, this class simply returns the tag's value.
+ *
+ * @author Drew Noakes http://drewnoakes.com
  */
-public class IptcDescriptor extends TagDescriptor
+public class IptcDescriptor extends TagDescriptor<IptcDirectory>
 {
-    public IptcDescriptor(Directory directory)
+    public IptcDescriptor(@NotNull IptcDirectory directory)
     {
         super(directory);
     }
 
+    @Nullable
     public String getDescription(int tagType)
     {
-        return _directory.getString(tagType);
+        switch (tagType) {
+            case IptcDirectory.TAG_FILE_FORMAT:
+                return getFileFormatDescription();
+            case IptcDirectory.TAG_KEYWORDS:
+                return getKeywordsDescription();
+            default:
+                return super.getDescription(tagType);
+        }
+    }
+
+    @Nullable
+    public String getFileFormatDescription()
+    {
+        Integer value = _directory.getInteger(IptcDirectory.TAG_FILE_FORMAT);
+        if (value == null)
+            return null;
+        switch (value) {
+            case 0: return "No ObjectData";
+            case 1: return "IPTC-NAA Digital Newsphoto Parameter Record";
+            case 2: return "IPTC7901 Recommended Message Format";
+            case 3: return "Tagged Image File Format (Adobe/Aldus Image data)";
+            case 4: return "Illustrator (Adobe Graphics data)";
+            case 5: return "AppleSingle (Apple Computer Inc)";
+            case 6: return "NAA 89-3 (ANPA 1312)";
+            case 7: return "MacBinary II";
+            case 8: return "IPTC Unstructured Character Oriented File Format (UCOFF)";
+            case 9: return "United Press International ANPA 1312 variant";
+            case 10: return "United Press International Down-Load Message";
+            case 11: return "JPEG File Interchange (JFIF)";
+            case 12: return "Photo-CD Image-Pac (Eastman Kodak)";
+            case 13: return "Bit Mapped Graphics File [.BMP] (Microsoft)";
+            case 14: return "Digital Audio File [.WAV] (Microsoft & Creative Labs)";
+            case 15: return "Audio plus Moving Video [.AVI] (Microsoft)";
+            case 16: return "PC DOS/Windows Executable Files [.COM][.EXE]";
+            case 17: return "Compressed Binary File [.ZIP] (PKWare Inc)";
+            case 18: return "Audio Interchange File Format AIFF (Apple Computer Inc)";
+            case 19: return "RIFF Wave (Microsoft Corporation)";
+            case 20: return "Freehand (Macromedia/Aldus)";
+            case 21: return "Hypertext Markup Language [.HTML] (The Internet Society)";
+            case 22: return "MPEG 2 Audio Layer 2 (Musicom), ISO/IEC";
+            case 23: return "MPEG 2 Audio Layer 3, ISO/IEC";
+            case 24: return "Portable Document File [.PDF] Adobe";
+            case 25: return "News Industry Text Format (NITF)";
+            case 26: return "Tape Archive [.TAR]";
+            case 27: return "Tidningarnas Telegrambyra NITF version (TTNITF DTD)";
+            case 28: return "Ritzaus Bureau NITF version (RBNITF DTD)";
+            case 29: return "Corel Draw [.CDR]";
+        }
+        return String.format("Unknown (%d)", value);
+    }
+
+    @Nullable
+    public String getByLineDescription()
+    {
+        return _directory.getString(IptcDirectory.TAG_BY_LINE);
+    }
+
+    @Nullable
+    public String getByLineTitleDescription()
+    {
+        return _directory.getString(IptcDirectory.TAG_BY_LINE_TITLE);
+    }
+
+    @Nullable
+    public String getCaptionDescription()
+    {
+        return _directory.getString(IptcDirectory.TAG_CAPTION);
+    }
+
+    @Nullable
+    public String getCategoryDescription()
+    {
+        return _directory.getString(IptcDirectory.TAG_CATEGORY);
+    }
+
+    @Nullable
+    public String getCityDescription()
+    {
+        return _directory.getString(IptcDirectory.TAG_CITY);
+    }
+
+    @Nullable
+    public String getCopyrightNoticeDescription()
+    {
+        return _directory.getString(IptcDirectory.TAG_COPYRIGHT_NOTICE);
+    }
+
+    @Nullable
+    public String getCountryOrPrimaryLocationDescription()
+    {
+        return _directory.getString(IptcDirectory.TAG_COUNTRY_OR_PRIMARY_LOCATION_NAME);
+    }
+
+    @Nullable
+    public String getCreditDescription()
+    {
+        return _directory.getString(IptcDirectory.TAG_CREDIT);
+    }
+
+    @Nullable
+    public String getDateCreatedDescription()
+    {
+        return _directory.getString(IptcDirectory.TAG_DATE_CREATED);
+    }
+
+    @Nullable
+    public String getHeadlineDescription()
+    {
+        return _directory.getString(IptcDirectory.TAG_HEADLINE);
+    }
+
+    @Nullable
+    public String getKeywordsDescription()
+    {
+        final String[] keywords = _directory.getStringArray(IptcDirectory.TAG_KEYWORDS);
+        if (keywords==null)
+            return null;
+        return StringUtil.join(keywords, ";");
+    }
+
+    @Nullable
+    public String getObjectNameDescription()
+    {
+        return _directory.getString(IptcDirectory.TAG_OBJECT_NAME);
+    }
+
+    @Nullable
+    public String getOriginalTransmissionReferenceDescription()
+    {
+        return _directory.getString(IptcDirectory.TAG_ORIGINAL_TRANSMISSION_REFERENCE);
+    }
+
+    @Nullable
+    public String getOriginatingProgramDescription()
+    {
+        return _directory.getString(IptcDirectory.TAG_ORIGINATING_PROGRAM);
+    }
+
+    @Nullable
+    public String getProvinceOrStateDescription()
+    {
+        return _directory.getString(IptcDirectory.TAG_PROVINCE_OR_STATE);
+    }
+
+    @Nullable
+    public String getRecordVersionDescription()
+    {
+        return _directory.getString(IptcDirectory.TAG_APPLICATION_RECORD_VERSION);
+    }
+
+    @Nullable
+    public String getReleaseDateDescription()
+    {
+        return _directory.getString(IptcDirectory.TAG_RELEASE_DATE);
+    }
+
+    @Nullable
+    public String getReleaseTimeDescription()
+    {
+        return _directory.getString(IptcDirectory.TAG_RELEASE_TIME);
+    }
+
+    @Nullable
+    public String getSourceDescription()
+    {
+        return _directory.getString(IptcDirectory.TAG_SOURCE);
+    }
+
+    @Nullable
+    public String getSpecialInstructionsDescription()
+    {
+        return _directory.getString(IptcDirectory.TAG_SPECIAL_INSTRUCTIONS);
+    }
+
+    @Nullable
+    public String getSupplementalCategoriesDescription()
+    {
+        return _directory.getString(IptcDirectory.TAG_SUPPLEMENTAL_CATEGORIES);
+    }
+
+    @Nullable
+    public String getTimeCreatedDescription()
+    {
+        return _directory.getString(IptcDirectory.TAG_TIME_CREATED);
+    }
+
+    @Nullable
+    public String getUrgencyDescription()
+    {
+        return _directory.getString(IptcDirectory.TAG_URGENCY);
+    }
+
+    @Nullable
+    public String getWriterDescription()
+    {
+        return _directory.getString(IptcDirectory.TAG_CAPTION_WRITER);
     }
 }
Index: trunk/src/com/drew/metadata/iptc/IptcDirectory.java
===================================================================
--- trunk/src/com/drew/metadata/iptc/IptcDirectory.java	(revision 4231)
+++ trunk/src/com/drew/metadata/iptc/IptcDirectory.java	(revision 6127)
@@ -1,82 +1,207 @@
 /*
- * This is public domain software - that is, you can do whatever you want
- * with it, and include it software that is licensed under the GNU or the
- * BSD license, or whatever other licence you choose, including proprietary
- * closed source licenses.  I do ask that you leave this header in tact.
- *
- * If you make modifications to this code that you think would benefit the
- * wider community, please send me a copy and I'll post it on my site.
- *
- * If you make use of this code, I'd appreciate hearing about it.
- *   drew@drewnoakes.com
- * Latest version of this software kept at
- *   http://drewnoakes.com/
- *
- * Created by dnoakes on 26-Nov-2002 01:26:39 using IntelliJ IDEA.
+ * Copyright 2002-2012 Drew Noakes
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ *
+ * More information about this project is available at:
+ *
+ *    http://drewnoakes.com/code/exif/
+ *    http://code.google.com/p/metadata-extractor/
  */
 package com.drew.metadata.iptc;
 
+import com.drew.lang.annotations.NotNull;
+import com.drew.lang.annotations.Nullable;
 import com.drew.metadata.Directory;
 
+import java.util.Arrays;
 import java.util.HashMap;
+import java.util.List;
 
 /**
- *
+ * Describes tags used by the International Press Telecommunications Council (IPTC) metadata format.
+ *
+ * @author Drew Noakes http://drewnoakes.com
  */
 public class IptcDirectory extends Directory
 {
-    public static final int TAG_RECORD_VERSION = 0x0200;
-    public static final int TAG_CAPTION = 0x0278;
-    public static final int TAG_WRITER = 0x027a;
-    public static final int TAG_HEADLINE = 0x0269;
-    public static final int TAG_SPECIAL_INSTRUCTIONS = 0x0228;
-    public static final int TAG_BY_LINE = 0x0250;
-    public static final int TAG_BY_LINE_TITLE = 0x0255;
-    public static final int TAG_CREDIT = 0x026e;
-    public static final int TAG_SOURCE = 0x0273;
-    public static final int TAG_OBJECT_NAME = 0x0205;
-    public static final int TAG_DATE_CREATED = 0x0237;
-    public static final int TAG_CITY = 0x025a;
-    public static final int TAG_PROVINCE_OR_STATE = 0x025f;
-    public static final int TAG_COUNTRY_OR_PRIMARY_LOCATION = 0x0265;
-    public static final int TAG_ORIGINAL_TRANSMISSION_REFERENCE = 0x0267;
-    public static final int TAG_CATEGORY = 0x020f;
-    public static final int TAG_SUPPLEMENTAL_CATEGORIES = 0x0214;
-    public static final int TAG_URGENCY = 0x0200 | 10;
-    public static final int TAG_KEYWORDS = 0x0200 | 25;
-    public static final int TAG_COPYRIGHT_NOTICE = 0x0274;
-    public static final int TAG_RELEASE_DATE = 0x0200 | 30;
-    public static final int TAG_RELEASE_TIME = 0x0200 | 35;
-    public static final int TAG_TIME_CREATED = 0x0200 | 60;
-    public static final int TAG_ORIGINATING_PROGRAM = 0x0200 | 65;
-
-    protected static final HashMap tagNameMap = new HashMap();
+    // IPTC EnvelopeRecord Tags
+    public static final int TAG_ENVELOPE_RECORD_VERSION          = 0x0100; // 0 + 0x0100
+    public static final int TAG_DESTINATION                      = 0x0105; // 5
+    public static final int TAG_FILE_FORMAT                      = 0x0114; // 20
+    public static final int TAG_FILE_VERSION                     = 0x0116; // 22
+    public static final int TAG_SERVICE_ID                       = 0x011E; // 30
+    public static final int TAG_ENVELOPE_NUMBER                  = 0x0128; // 40
+    public static final int TAG_PRODUCT_ID                       = 0x0132; // 50
+    public static final int TAG_ENVELOPE_PRIORITY                = 0x013C; // 60
+    public static final int TAG_DATE_SENT                        = 0x0146; // 70
+    public static final int TAG_TIME_SENT                        = 0x0150; // 80
+    public static final int TAG_CODED_CHARACTER_SET              = 0x015A; // 90
+    public static final int TAG_UNIQUE_OBJECT_NAME               = 0x0164; // 100
+    public static final int TAG_ARM_IDENTIFIER                   = 0x0178; // 120
+    public static final int TAG_ARM_VERSION                      = 0x017a; // 122
+
+    // IPTC ApplicationRecord Tags
+    public static final int TAG_APPLICATION_RECORD_VERSION       = 0x0200; // 0 + 0x0200
+    public static final int TAG_OBJECT_TYPE_REFERENCE            = 0x0203; // 3
+    public static final int TAG_OBJECT_ATTRIBUTE_REFERENCE       = 0x0204; // 4
+    public static final int TAG_OBJECT_NAME                      = 0x0205; // 5
+    public static final int TAG_EDIT_STATUS                      = 0x0207; // 7
+    public static final int TAG_EDITORIAL_UPDATE                 = 0x0208; // 8
+    public static final int TAG_URGENCY                          = 0X020A; // 10
+    public static final int TAG_SUBJECT_REFERENCE                = 0X020C; // 12
+    public static final int TAG_CATEGORY                         = 0x020F; // 15
+    public static final int TAG_SUPPLEMENTAL_CATEGORIES          = 0x0214; // 20
+    public static final int TAG_FIXTURE_ID                       = 0x0216; // 22
+    public static final int TAG_KEYWORDS                         = 0x0219; // 25
+    public static final int TAG_CONTENT_LOCATION_CODE            = 0x021A; // 26
+    public static final int TAG_CONTENT_LOCATION_NAME            = 0x021B; // 27
+    public static final int TAG_RELEASE_DATE                     = 0X021E; // 30
+    public static final int TAG_RELEASE_TIME                     = 0x0223; // 35
+    public static final int TAG_EXPIRATION_DATE                  = 0x0225; // 37
+    public static final int TAG_EXPIRATION_TIME                  = 0x0226; // 38
+    public static final int TAG_SPECIAL_INSTRUCTIONS             = 0x0228; // 40
+    public static final int TAG_ACTION_ADVISED                   = 0x022A; // 42
+    public static final int TAG_REFERENCE_SERVICE                = 0x022D; // 45
+    public static final int TAG_REFERENCE_DATE                   = 0x022F; // 47
+    public static final int TAG_REFERENCE_NUMBER                 = 0x0232; // 50
+    public static final int TAG_DATE_CREATED                     = 0x0237; // 55
+    public static final int TAG_TIME_CREATED                     = 0X023C; // 60
+    public static final int TAG_DIGITAL_DATE_CREATED             = 0x023E; // 62
+    public static final int TAG_DIGITAL_TIME_CREATED             = 0x023F; // 63
+    public static final int TAG_ORIGINATING_PROGRAM              = 0x0241; // 65
+    public static final int TAG_PROGRAM_VERSION                  = 0x0246; // 70
+    public static final int TAG_OBJECT_CYCLE                     = 0x024B; // 75
+    public static final int TAG_BY_LINE                          = 0x0250; // 80
+    public static final int TAG_BY_LINE_TITLE                    = 0x0255; // 85
+    public static final int TAG_CITY                             = 0x025A; // 90
+    public static final int TAG_SUB_LOCATION                     = 0x025C; // 92
+    public static final int TAG_PROVINCE_OR_STATE                = 0x025F; // 95
+    public static final int TAG_COUNTRY_OR_PRIMARY_LOCATION_CODE = 0x0264; // 100
+    public static final int TAG_COUNTRY_OR_PRIMARY_LOCATION_NAME = 0x0265; // 101
+    public static final int TAG_ORIGINAL_TRANSMISSION_REFERENCE  = 0x0267; // 103
+    public static final int TAG_HEADLINE                         = 0x0269; // 105
+    public static final int TAG_CREDIT                           = 0x026E; // 110
+    public static final int TAG_SOURCE                           = 0x0273; // 115
+    public static final int TAG_COPYRIGHT_NOTICE                 = 0x0274; // 116
+    public static final int TAG_CONTACT                          = 0x0276; // 118
+    public static final int TAG_CAPTION                          = 0x0278; // 120
+    public static final int TAG_LOCAL_CAPTION                    = 0x0279; // 121
+    public static final int TAG_CAPTION_WRITER                   = 0x027A; // 122
+    public static final int TAG_RASTERIZED_CAPTION               = 0x027D; // 125
+    public static final int TAG_IMAGE_TYPE                       = 0x0282; // 130
+    public static final int TAG_IMAGE_ORIENTATION                = 0x0283; // 131
+    public static final int TAG_LANGUAGE_IDENTIFIER              = 0x0287; // 135
+    public static final int TAG_AUDIO_TYPE                       = 0x0296; // 150
+    public static final int TAG_AUDIO_SAMPLING_RATE              = 0x0297; // 151
+    public static final int TAG_AUDIO_SAMPLING_RESOLUTION        = 0x0298; // 152
+    public static final int TAG_AUDIO_DURATION                   = 0x0299; // 153
+    public static final int TAG_AUDIO_OUTCUE                     = 0x029A; // 154
+
+    public static final int TAG_JOB_ID                           = 0x02B8; // 184
+    public static final int TAG_MASTER_DOCUMENT_ID               = 0x02B9; // 185
+    public static final int TAG_SHORT_DOCUMENT_ID                = 0x02BA; // 186
+    public static final int TAG_UNIQUE_DOCUMENT_ID               = 0x02BB; // 187
+    public static final int TAG_OWNER_ID                         = 0x02BC; // 188
+
+    public static final int TAG_OBJECT_PREVIEW_FILE_FORMAT       = 0x02C8; // 200
+    public static final int TAG_OBJECT_PREVIEW_FILE_FORMAT_VERSION  = 0x02C9; // 201
+    public static final int TAG_OBJECT_PREVIEW_DATA              = 0x02CA; // 202
+
+    @NotNull
+    protected static final HashMap<Integer, String> _tagNameMap = new HashMap<Integer, String>();
 
     static
     {
-        tagNameMap.put(new Integer(TAG_RECORD_VERSION), "Directory Version");
-        tagNameMap.put(new Integer(TAG_CAPTION), "Caption/Abstract");
-        tagNameMap.put(new Integer(TAG_WRITER), "Writer/Editor");
-        tagNameMap.put(new Integer(TAG_HEADLINE), "Headline");
-        tagNameMap.put(new Integer(TAG_SPECIAL_INSTRUCTIONS), "Special Instructions");
-        tagNameMap.put(new Integer(TAG_BY_LINE), "By-line");
-        tagNameMap.put(new Integer(TAG_BY_LINE_TITLE), "By-line Title");
-        tagNameMap.put(new Integer(TAG_CREDIT), "Credit");
-        tagNameMap.put(new Integer(TAG_SOURCE), "Source");
-        tagNameMap.put(new Integer(TAG_OBJECT_NAME), "Object Name");
-        tagNameMap.put(new Integer(TAG_DATE_CREATED), "Date Created");
-        tagNameMap.put(new Integer(TAG_CITY), "City");
-        tagNameMap.put(new Integer(TAG_PROVINCE_OR_STATE), "Province/State");
-        tagNameMap.put(new Integer(TAG_COUNTRY_OR_PRIMARY_LOCATION), "Country/Primary Location");
-        tagNameMap.put(new Integer(TAG_ORIGINAL_TRANSMISSION_REFERENCE), "Original Transmission Reference");
-        tagNameMap.put(new Integer(TAG_CATEGORY), "Category");
-        tagNameMap.put(new Integer(TAG_SUPPLEMENTAL_CATEGORIES), "Supplemental Category(s)");
-        tagNameMap.put(new Integer(TAG_URGENCY), "Urgency");
-        tagNameMap.put(new Integer(TAG_KEYWORDS), "Keywords");
-        tagNameMap.put(new Integer(TAG_COPYRIGHT_NOTICE), "Copyright Notice");
-        tagNameMap.put(new Integer(TAG_RELEASE_DATE), "Release Date");
-        tagNameMap.put(new Integer(TAG_RELEASE_TIME), "Release Time");
-        tagNameMap.put(new Integer(TAG_TIME_CREATED), "Time Created");
-        tagNameMap.put(new Integer(TAG_ORIGINATING_PROGRAM), "Originating Program");
+        _tagNameMap.put(TAG_ENVELOPE_RECORD_VERSION, "Enveloped Record Version");
+        _tagNameMap.put(TAG_DESTINATION, "Destination");
+        _tagNameMap.put(TAG_FILE_FORMAT, "File Format");
+        _tagNameMap.put(TAG_FILE_VERSION, "File Version");
+        _tagNameMap.put(TAG_SERVICE_ID, "Service Identifier");
+        _tagNameMap.put(TAG_ENVELOPE_NUMBER, "Envelope Number");
+        _tagNameMap.put(TAG_PRODUCT_ID, "Product Identifier");
+        _tagNameMap.put(TAG_ENVELOPE_PRIORITY, "Envelope Priority");
+        _tagNameMap.put(TAG_DATE_SENT, "Date Sent");
+        _tagNameMap.put(TAG_TIME_SENT, "Time Sent");
+        _tagNameMap.put(TAG_CODED_CHARACTER_SET, "Coded Character Set");
+        _tagNameMap.put(TAG_UNIQUE_OBJECT_NAME, "Unique Object Name");
+        _tagNameMap.put(TAG_ARM_IDENTIFIER, "ARM Identifier");
+        _tagNameMap.put(TAG_ARM_VERSION, "ARM Version");
+
+        _tagNameMap.put(TAG_APPLICATION_RECORD_VERSION, "Application Record Version");
+        _tagNameMap.put(TAG_OBJECT_TYPE_REFERENCE, "Object Type Reference");
+        _tagNameMap.put(TAG_OBJECT_ATTRIBUTE_REFERENCE, "Object Attribute Reference");
+        _tagNameMap.put(TAG_OBJECT_NAME, "Object Name");
+        _tagNameMap.put(TAG_EDIT_STATUS, "Edit Status");
+        _tagNameMap.put(TAG_EDITORIAL_UPDATE, "Editorial Update");
+        _tagNameMap.put(TAG_URGENCY, "Urgency");
+        _tagNameMap.put(TAG_SUBJECT_REFERENCE, "Subject Reference");
+        _tagNameMap.put(TAG_CATEGORY, "Category");
+        _tagNameMap.put(TAG_SUPPLEMENTAL_CATEGORIES, "Supplemental Category(s)");
+        _tagNameMap.put(TAG_FIXTURE_ID, "Fixture Identifier");
+        _tagNameMap.put(TAG_KEYWORDS, "Keywords");
+        _tagNameMap.put(TAG_CONTENT_LOCATION_CODE, "Content Location Code");
+        _tagNameMap.put(TAG_CONTENT_LOCATION_NAME, "Content Location Name");
+        _tagNameMap.put(TAG_RELEASE_DATE, "Release Date");
+        _tagNameMap.put(TAG_RELEASE_TIME, "Release Time");
+        _tagNameMap.put(TAG_EXPIRATION_DATE, "Expiration Date");
+        _tagNameMap.put(TAG_EXPIRATION_TIME, "Expiration Time");
+        _tagNameMap.put(TAG_SPECIAL_INSTRUCTIONS, "Special Instructions");
+        _tagNameMap.put(TAG_ACTION_ADVISED, "Action Advised");
+        _tagNameMap.put(TAG_REFERENCE_SERVICE, "Reference Service");
+        _tagNameMap.put(TAG_REFERENCE_DATE, "Reference Date");
+        _tagNameMap.put(TAG_REFERENCE_NUMBER, "Reference Number");
+        _tagNameMap.put(TAG_DATE_CREATED, "Date Created");
+        _tagNameMap.put(TAG_TIME_CREATED, "Time Created");
+        _tagNameMap.put(TAG_DIGITAL_DATE_CREATED, "Digital Date Created");
+        _tagNameMap.put(TAG_DIGITAL_TIME_CREATED, "Digital Time Created");
+        _tagNameMap.put(TAG_ORIGINATING_PROGRAM, "Originating Program");
+        _tagNameMap.put(TAG_PROGRAM_VERSION, "Program Version");
+        _tagNameMap.put(TAG_OBJECT_CYCLE, "Object Cycle");
+        _tagNameMap.put(TAG_BY_LINE, "By-line");
+        _tagNameMap.put(TAG_BY_LINE_TITLE, "By-line Title");
+        _tagNameMap.put(TAG_CITY, "City");
+        _tagNameMap.put(TAG_SUB_LOCATION, "Sub-location");
+        _tagNameMap.put(TAG_PROVINCE_OR_STATE, "Province/State");
+        _tagNameMap.put(TAG_COUNTRY_OR_PRIMARY_LOCATION_CODE, "Country/Primary Location Code");
+        _tagNameMap.put(TAG_COUNTRY_OR_PRIMARY_LOCATION_NAME, "Country/Primary Location Name");
+        _tagNameMap.put(TAG_ORIGINAL_TRANSMISSION_REFERENCE, "Original Transmission Reference");
+        _tagNameMap.put(TAG_HEADLINE, "Headline");
+        _tagNameMap.put(TAG_CREDIT, "Credit");
+        _tagNameMap.put(TAG_SOURCE, "Source");
+        _tagNameMap.put(TAG_COPYRIGHT_NOTICE, "Copyright Notice");
+        _tagNameMap.put(TAG_CONTACT, "Contact");
+        _tagNameMap.put(TAG_CAPTION, "Caption/Abstract");
+        _tagNameMap.put(TAG_LOCAL_CAPTION, "Local Caption");
+        _tagNameMap.put(TAG_CAPTION_WRITER, "Caption Writer/Editor");
+        _tagNameMap.put(TAG_RASTERIZED_CAPTION, "Rasterized Caption");
+        _tagNameMap.put(TAG_IMAGE_TYPE, "Image Type");
+        _tagNameMap.put(TAG_IMAGE_ORIENTATION, "Image Orientation");
+        _tagNameMap.put(TAG_LANGUAGE_IDENTIFIER, "Language Identifier");
+        _tagNameMap.put(TAG_AUDIO_TYPE, "Audio Type");
+        _tagNameMap.put(TAG_AUDIO_SAMPLING_RATE, "Audio Sampling Rate");
+        _tagNameMap.put(TAG_AUDIO_SAMPLING_RESOLUTION, "Audio Sampling Resolution");
+        _tagNameMap.put(TAG_AUDIO_DURATION, "Audio Duration");
+        _tagNameMap.put(TAG_AUDIO_OUTCUE, "Audio Outcue");
+
+        _tagNameMap.put(TAG_JOB_ID, "Job Identifier");
+        _tagNameMap.put(TAG_MASTER_DOCUMENT_ID, "Master Document Identifier");
+        _tagNameMap.put(TAG_SHORT_DOCUMENT_ID, "Short Document Identifier");
+        _tagNameMap.put(TAG_UNIQUE_DOCUMENT_ID, "Unique Document Identifier");
+        _tagNameMap.put(TAG_OWNER_ID, "Owner Identifier");
+
+        _tagNameMap.put(TAG_OBJECT_PREVIEW_FILE_FORMAT, "Object Data Preview File Format");
+        _tagNameMap.put(TAG_OBJECT_PREVIEW_FILE_FORMAT_VERSION, "Object Data Preview File Format Version");
+        _tagNameMap.put(TAG_OBJECT_PREVIEW_DATA, "Object Data Preview Data");
     }
 
@@ -86,4 +211,5 @@
     }
 
+    @NotNull
     public String getName()
     {
@@ -91,7 +217,20 @@
     }
 
-    protected HashMap getTagNameMap()
-    {
-        return tagNameMap;
+    @NotNull
+    protected HashMap<Integer, String> getTagNameMap()
+    {
+        return _tagNameMap;
+    }
+
+    /**
+     * Returns any keywords contained in the IPTC data.  This value may be <code>null</code>.
+     */
+    @Nullable
+    public List<String> getKeywords()
+    {
+        final String[] array = getStringArray(IptcDirectory.TAG_KEYWORDS);
+        if (array==null)
+            return null;
+        return Arrays.asList(array);
     }
 }
Index: trunk/src/com/drew/metadata/iptc/IptcProcessingException.java
===================================================================
--- trunk/src/com/drew/metadata/iptc/IptcProcessingException.java	(revision 4231)
+++ 	(revision )
@@ -1,51 +1,0 @@
-/*
- * ExifProcessingException.java
- *
- * This class is public domain software - that is, you can do whatever you want
- * with it, and include it software that is licensed under the GNU or the
- * BSD license, or whatever other licence you choose, including proprietary
- * closed source licenses.  I do ask that you leave this header in tact.
- *
- * If you make modifications to this code that you think would benefit the
- * wider community, please send me a copy and I'll post it on my site.
- *
- * If you make use of this code, I'd appreciate hearing about it.
- *   drew@drewnoakes.com
- * Latest version of this software kept at
- *   http://drewnoakes.com/
- *
- * Created on 29 April 2002, 00:33
- */
-
-package com.drew.metadata.iptc;
-
-import com.drew.metadata.MetadataException;
-
-/**
- * The exception type raised during reading of Iptc data in the instance of
- * unexpected data conditions.
- * @author  Drew Noakes http://drewnoakes.com
- */
-public class IptcProcessingException extends MetadataException
-{
-    /**
-     * Constructs an instance of <code>ExifProcessingException</code> with the
-     * specified detail message.
-     * @param message the detail message
-     */
-    public IptcProcessingException(String message)
-    {
-        super(message);
-    }
-
-    /**
-     * Constructs an instance of <code>IptcProcessingException</code> with the
-     * specified detail message and inner exception.
-     * @param message the detail message
-     * @param cause an inner exception
-     */
-    public IptcProcessingException(String message, Throwable cause)
-    {
-        super(message, cause);
-    }
-}
Index: trunk/src/com/drew/metadata/iptc/IptcReader.java
===================================================================
--- trunk/src/com/drew/metadata/iptc/IptcReader.java	(revision 4231)
+++ trunk/src/com/drew/metadata/iptc/IptcReader.java	(revision 6127)
@@ -1,36 +1,41 @@
 /*
- * This is public domain software - that is, you can do whatever you want
- * with it, and include it software that is licensed under the GNU or the
- * BSD license, or whatever other licence you choose, including proprietary
- * closed source licenses.  I do ask that you leave this header in tact.
+ * Copyright 2002-2012 Drew Noakes
  *
- * If you make modifications to this code that you think would benefit the
- * wider community, please send me a copy and I'll post it on my site.
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
  *
- * If you make use of this code, I'd appreciate hearing about it.
- *   drew@drewnoakes.com
- * Latest version of this software kept at
- *   http://drewnoakes.com/
+ *        http://www.apache.org/licenses/LICENSE-2.0
  *
- * Created by dnoakes on 12-Nov-2002 19:00:03 using IntelliJ IDEA.
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ *
+ * More information about this project is available at:
+ *
+ *    http://drewnoakes.com/code/exif/
+ *    http://code.google.com/p/metadata-extractor/
  */
 package com.drew.metadata.iptc;
 
-import com.drew.imaging.jpeg.JpegProcessingException;
-import com.drew.imaging.jpeg.JpegSegmentReader;
+import com.drew.lang.BufferBoundsException;
+import com.drew.lang.BufferReader;
+import com.drew.lang.annotations.NotNull;
 import com.drew.metadata.Directory;
 import com.drew.metadata.Metadata;
-import com.drew.metadata.MetadataException;
 import com.drew.metadata.MetadataReader;
 
-import java.io.File;
-import java.io.InputStream;
 import java.util.Date;
 
 /**
+ * Decodes IPTC binary data, populating a <code>Metadata</code> object with tag values in an <code>IptcDirectory</code>.
  *
+ * @author Drew Noakes http://drewnoakes.com
  */
 public class IptcReader implements MetadataReader
 {
+    // TODO consider breaking the IPTC section up into multiple directories and providing segregation of each IPTC directory
 /*
     public static final int DIRECTORY_IPTC = 2;
@@ -46,70 +51,43 @@
     public static final int POST_DATA_RECORD = 9;
 */
-    /**
-     * The Iptc data segment.
-     */
-    private final byte[] _data;
 
-    /**
-     * Creates a new IptcReader for the specified Jpeg jpegFile.
-     */
-    public IptcReader(File jpegFile) throws JpegProcessingException
+    /** Performs the IPTC data extraction, adding found values to the specified instance of <code>Metadata</code>. */
+    public void extract(@NotNull final BufferReader reader, @NotNull final Metadata metadata)
     {
-        this(new JpegSegmentReader(jpegFile).readSegment(JpegSegmentReader.SEGMENT_APPD));
-    }
+        IptcDirectory directory = metadata.getOrCreateDirectory(IptcDirectory.class);
 
-    /** Creates an IptcReader for a JPEG stream.
-     *
-     * @param is JPEG stream. Stream will be closed.
-     */
-    public IptcReader(InputStream is) throws JpegProcessingException
-    {
-        this(new JpegSegmentReader(is).readSegment(JpegSegmentReader.SEGMENT_APPD));
-    }
+        int offset = 0;
 
-    public IptcReader(byte[] data)
-    {
-        _data = data;
-    }
-
-    /**
-     * Performs the Exif data extraction, returning a new instance of <code>Metadata</code>.
-     */
-    public Metadata extract()
-    {
-        return extract(new Metadata());
-    }
-
-    /**
-     * Performs the Exif data extraction, adding found values to the specified
-     * instance of <code>Metadata</code>.
-     */
-    public Metadata extract(Metadata metadata)
-    {
-        if (_data == null) {
-            return metadata;
+/*
+        // find start-of-segment marker (potentially need to skip some ASCII photoshop header info)
+        try {
+            while (offset < data.length - 1 && reader.getUInt16(offset) != 0x1c01 && reader.getUInt16(offset) != 0x1c02)
+                offset++;
+        } catch (BufferBoundsException e) {
+            directory.addError("Couldn't find start of IPTC data (invalid segment)");
+            return;
         }
-
-        Directory directory = metadata.getDirectory(IptcDirectory.class);
-
-        // find start of data
-        int offset = 0;
-        try {
-            while (offset < _data.length - 1 && get32Bits(offset) != 0x1c02) {
-                offset++;
-            }
-        } catch (MetadataException e) {
-            directory.addError("Couldn't find start of Iptc data (invalid segment)");
-            return metadata;
-        }
+*/
 
         // for each tag
-        while (offset < _data.length) {
+        while (offset < reader.getLength()) {
+
             // identifies start of a tag
-            if (_data[offset] != 0x1c) {
+            short startByte;
+            try {
+                startByte = reader.getUInt8(offset);
+            } catch (BufferBoundsException e) {
+                directory.addError("Unable to read starting byte of IPTC tag");
                 break;
             }
+
+            if (startByte != 0x1c) {
+                directory.addError("Invalid start to IPTC tag");
+                break;
+            }
+
             // we need at least five bytes left to read a tag
-            if ((offset + 5) >= _data.length) {
+            if (offset + 5 >= reader.getLength()) {
+                directory.addError("Too few bytes remain for a valid IPTC tag");
                 break;
             }
@@ -121,54 +99,42 @@
             int tagByteCount;
             try {
-                directoryType = _data[offset++];
-                tagType = _data[offset++];
-                tagByteCount = get32Bits(offset);
-            } catch (MetadataException e) {
-                directory.addError("Iptc data segment ended mid-way through tag descriptor");
-                return metadata;
+                directoryType = reader.getUInt8(offset++);
+                tagType = reader.getUInt8(offset++);
+                tagByteCount = reader.getUInt16(offset);
+                offset += 2;
+            } catch (BufferBoundsException e) {
+                directory.addError("IPTC data segment ended mid-way through tag descriptor");
+                return;
             }
-            offset += 2;
-            if ((offset + tagByteCount) > _data.length) {
-                directory.addError("data for tag extends beyond end of iptc segment");
+
+            if (offset + tagByteCount > reader.getLength()) {
+                directory.addError("Data for tag extends beyond end of IPTC segment");
                 break;
             }
 
-            processTag(directory, directoryType, tagType, offset, tagByteCount);
+            try {
+                processTag(reader, directory, directoryType, tagType, offset, tagByteCount);
+            } catch (BufferBoundsException e) {
+                directory.addError("Error processing IPTC tag");
+                break;
+            }
+
             offset += tagByteCount;
         }
-
-        return metadata;
     }
 
-    /**
-     * Returns an int calculated from two bytes of data at the specified offset (MSB, LSB).
-     * @param offset position within the data buffer to read first byte
-     * @return the 32 bit int value, between 0x0000 and 0xFFFF
-     */
-    private int get32Bits(int offset) throws MetadataException
-    {
-        if (offset >= _data.length) {
-            throw new MetadataException("Attempt to read bytes from outside Iptc data buffer");
-        }
-        return ((_data[offset] & 255) << 8) | (_data[offset + 1] & 255);
-    }
-
-    /**
-     * This method serves as marsheller of objects for dataset. It converts from IPTC
-     * octets to relevant java object.
-     */
-    private void processTag(Directory directory, int directoryType, int tagType, int offset, int tagByteCount)
+    private void processTag(@NotNull BufferReader reader, @NotNull Directory directory, int directoryType, int tagType, int offset, int tagByteCount) throws BufferBoundsException
     {
         int tagIdentifier = tagType | (directoryType << 8);
 
         switch (tagIdentifier) {
-            case IptcDirectory.TAG_RECORD_VERSION:
+            case IptcDirectory.TAG_APPLICATION_RECORD_VERSION:
                 // short
-                short shortValue = (short)((_data[offset] << 8) | _data[offset + 1]);
+                int shortValue = reader.getUInt16(offset);
                 directory.setInt(tagIdentifier, shortValue);
                 return;
             case IptcDirectory.TAG_URGENCY:
                 // byte
-                directory.setInt(tagIdentifier, _data[offset]);
+                directory.setInt(tagIdentifier, reader.getUInt8(offset));
                 return;
             case IptcDirectory.TAG_RELEASE_DATE:
@@ -176,10 +142,10 @@
                 // Date object
                 if (tagByteCount >= 8) {
-                    String dateStr = new String(_data, offset, tagByteCount);
+                    String dateStr = reader.getString(offset, tagByteCount);
                     try {
                         int year = Integer.parseInt(dateStr.substring(0, 4));
                         int month = Integer.parseInt(dateStr.substring(4, 6)) - 1;
                         int day = Integer.parseInt(dateStr.substring(6, 8));
-                        Date date = (new java.util.GregorianCalendar(year, month, day)).getTime();
+                        Date date = new java.util.GregorianCalendar(year, month, day).getTime();
                         directory.setDate(tagIdentifier, date);
                         return;
@@ -194,26 +160,22 @@
                 // fall through
         }
-        // If no special handling by now, treat it as a string
+
+        // If we haven't returned yet, treat it as a string
         String str;
         if (tagByteCount < 1) {
             str = "";
         } else {
-            str = new String(_data, offset, tagByteCount);
+            str = reader.getString(offset, tagByteCount, System.getProperty("file.encoding")); // "ISO-8859-1"
         }
+
         if (directory.containsTag(tagIdentifier)) {
-            String[] oldStrings;
+            // this fancy string[] business avoids using an ArrayList for performance reasons
+            String[] oldStrings = directory.getStringArray(tagIdentifier);
             String[] newStrings;
-            try {
-                oldStrings = directory.getStringArray(tagIdentifier);
-            } catch (MetadataException e) {
-                oldStrings = null;
-            }
             if (oldStrings == null) {
                 newStrings = new String[1];
             } else {
                 newStrings = new String[oldStrings.length + 1];
-                for (int i = 0; i < oldStrings.length; i++) {
-                    newStrings[i] = oldStrings[i];
-                }
+                System.arraycopy(oldStrings, 0, newStrings, 0, oldStrings.length);
             }
             newStrings[newStrings.length - 1] = str;
