diff options
Diffstat (limited to 'XMPCore/src/com/adobe/xmp/impl/Utils.java')
-rw-r--r-- | XMPCore/src/com/adobe/xmp/impl/Utils.java | 511 |
1 files changed, 511 insertions, 0 deletions
diff --git a/XMPCore/src/com/adobe/xmp/impl/Utils.java b/XMPCore/src/com/adobe/xmp/impl/Utils.java new file mode 100644 index 0000000..7ca3b27 --- /dev/null +++ b/XMPCore/src/com/adobe/xmp/impl/Utils.java @@ -0,0 +1,511 @@ +// ================================================================================================= +// ADOBE SYSTEMS INCORPORATED +// Copyright 2006 Adobe Systems Incorporated +// All Rights Reserved +// +// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms +// of the Adobe license agreement accompanying it. +// ================================================================================================= + +package com.adobe.xmp.impl; + + +import com.adobe.xmp.XMPConst; + + +/** + * Utility functions for the XMPToolkit implementation. + * + * @since 06.06.2006 + */ +public class Utils implements XMPConst +{ + /** segments of a UUID */ + public static final int UUID_SEGMENT_COUNT = 4; + /** length of a UUID */ + public static final int UUID_LENGTH = 32 + UUID_SEGMENT_COUNT; + /** table of XML name start chars (<= 0xFF) */ + private static boolean[] xmlNameStartChars; + /** table of XML name chars (<= 0xFF) */ + private static boolean[] xmlNameChars; + /** init char tables */ + static + { + initCharTables(); + } + + + /** + * Private constructor + */ + private Utils() + { + // EMPTY + } + + + /** + * Normalize an xml:lang value so that comparisons are effectively case + * insensitive as required by RFC 3066 (which superceeds RFC 1766). The + * normalization rules: + * <ul> + * <li> The primary subtag is lower case, the suggested practice of ISO 639. + * <li> All 2 letter secondary subtags are upper case, the suggested + * practice of ISO 3166. + * <li> All other subtags are lower case. + * </ul> + * + * @param value + * raw value + * @return Returns the normalized value. + */ + public static String normalizeLangValue(String value) + { + // don't normalize x-default + if (XMPConst.X_DEFAULT.equals(value)) + { + return value; + } + + int subTag = 1; + StringBuffer buffer = new StringBuffer(); + + for (int i = 0; i < value.length(); i++) + { + switch (value.charAt(i)) + { + case '-': + case '_': + // move to next subtag and convert underscore to hyphen + buffer.append('-'); + subTag++; + break; + case ' ': + // remove spaces + break; + default: + // convert second subtag to uppercase, all other to lowercase + if (subTag != 2) + { + buffer.append(Character.toLowerCase(value.charAt(i))); + } + else + { + buffer.append(Character.toUpperCase(value.charAt(i))); + } + } + + } + return buffer.toString(); + } + + + /** + * Split the name and value parts for field and qualifier selectors: + * <ul> + * <li>[qualName="value"] - An element in an array of structs, chosen by a + * field value. + * <li>[?qualName="value"] - An element in an array, chosen by a qualifier + * value. + * </ul> + * The value portion is a string quoted by ''' or '"'. The value may contain + * any character including a doubled quoting character. The value may be + * empty. <em>Note:</em> It is assumed that the expression is formal + * correct + * + * @param selector + * the selector + * @return Returns an array where the first entry contains the name and the + * second the value. + */ + static String[] splitNameAndValue(String selector) + { + // get the name + int eq = selector.indexOf('='); + int pos = 1; + if (selector.charAt(pos) == '?') + { + pos++; + } + String name = selector.substring(pos, eq); + + // get the value + pos = eq + 1; + char quote = selector.charAt(pos); + pos++; + int end = selector.length() - 2; // quote and ] + StringBuffer value = new StringBuffer(end - eq); + while (pos < end) + { + value.append(selector.charAt(pos)); + pos++; + if (selector.charAt(pos) == quote) + { + // skip one quote in value + pos++; + } + } + return new String[] { name, value.toString() }; + } + + + /** + * + * @param schema + * a schema namespace + * @param prop + * an XMP Property + * @return Returns true if the property is defined as "Internal + * Property", see XMP Specification. + */ + static boolean isInternalProperty(String schema, String prop) + { + boolean isInternal = false; + + if (NS_DC.equals(schema)) + { + if ("dc:format".equals(prop) || "dc:language".equals(prop)) + { + isInternal = true; + } + } + else if (NS_XMP.equals(schema)) + { + if ("xmp:BaseURL".equals(prop) || "xmp:CreatorTool".equals(prop) + || "xmp:Format".equals(prop) || "xmp:Locale".equals(prop) + || "xmp:MetadataDate".equals(prop) || "xmp:ModifyDate".equals(prop)) + { + isInternal = true; + } + } + else if (NS_PDF.equals(schema)) + { + if ("pdf:BaseURL".equals(prop) || "pdf:Creator".equals(prop) + || "pdf:ModDate".equals(prop) || "pdf:PDFVersion".equals(prop) + || "pdf:Producer".equals(prop)) + { + isInternal = true; + } + } + else if (NS_TIFF.equals(schema)) + { + isInternal = true; + if ("tiff:ImageDescription".equals(prop) || "tiff:Artist".equals(prop) + || "tiff:Copyright".equals(prop)) + { + isInternal = false; + } + } + else if (NS_EXIF.equals(schema)) + { + isInternal = true; + if ("exif:UserComment".equals(prop)) + { + isInternal = false; + } + } + else if (NS_EXIF_AUX.equals(schema)) + { + isInternal = true; + } + else if (NS_PHOTOSHOP.equals(schema)) + { + if ("photoshop:ICCProfile".equals(prop)) + { + isInternal = true; + } + } + else if (NS_CAMERARAW.equals(schema)) + { + if ("crs:Version".equals(prop) || "crs:RawFileName".equals(prop) + || "crs:ToneCurveName".equals(prop)) + { + isInternal = true; + } + } + else if (NS_ADOBESTOCKPHOTO.equals(schema)) + { + isInternal = true; + } + else if (NS_XMP_MM.equals(schema)) + { + isInternal = true; + } + else if (TYPE_TEXT.equals(schema)) + { + isInternal = true; + } + else if (TYPE_PAGEDFILE.equals(schema)) + { + isInternal = true; + } + else if (TYPE_GRAPHICS.equals(schema)) + { + isInternal = true; + } + else if (TYPE_IMAGE.equals(schema)) + { + isInternal = true; + } + else if (TYPE_FONT.equals(schema)) + { + isInternal = true; + } + + return isInternal; + } + + + /** + * Check some requirements for an UUID: + * <ul> + * <li>Length of the UUID is 32</li> + * <li>The Delimiter count is 4 and all the 4 delimiter are on their right + * position (8,13,18,23)</li> + * </ul> + * + * + * @param uuid uuid to test + * @return true - this is a well formed UUID, false - UUID has not the expected format + */ + + static boolean checkUUIDFormat(String uuid) + { + boolean result = true; + int delimCnt = 0; + int delimPos = 0; + + if (uuid == null) + { + return false; + } + + for (delimPos = 0; delimPos < uuid.length(); delimPos++) + { + if (uuid.charAt(delimPos) == '-') + { + delimCnt++; + result = result && + (delimPos == 8 || delimPos == 13 || delimPos == 18 || delimPos == 23); + } + } + + return result && UUID_SEGMENT_COUNT == delimCnt && UUID_LENGTH == delimPos; + } + + + /** + * Simple check for valid XMLNames. Within ASCII range<br> + * ":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] | [#xD8-#xF6]<br> + * are accepted, above all characters (which is not entirely + * correct according to the XML Spec. + * + * @param name an XML Name + * @return Return <code>true</code> if the name is correct. + */ + public static boolean isXMLName(String name) + { + if (name.length() > 0 && !isNameStartChar(name.charAt(0))) + { + return false; + } + for (int i = 1; i < name.length(); i++) + { + if (!isNameChar(name.charAt(i))) + { + return false; + } + } + return true; + } + + + /** + * Checks if the value is a legal "unqualified" XML name, as + * defined in the XML Namespaces proposed recommendation. + * These are XML names, except that they must not contain a colon. + * @param name the value to check + * @return Returns true if the name is a valid "unqualified" XML name. + */ + public static boolean isXMLNameNS(String name) + { + if (name.length() > 0 && (!isNameStartChar(name.charAt(0)) || name.charAt(0) == ':')) + { + return false; + } + for (int i = 1; i < name.length(); i++) + { + if (!isNameChar(name.charAt(i)) || name.charAt(i) == ':') + { + return false; + } + } + return true; + } + + + /** + * @param c a char + * @return Returns true if the char is an ASCII control char. + */ + static boolean isControlChar(char c) + { + return (c <= 0x1F || c == 0x7F) && + c != 0x09 && c != 0x0A && c != 0x0D; + } + + + /** + * Serializes the node value in XML encoding. Its used for tag bodies and + * attributes.<br> + * <em>Note:</em> The attribute is always limited by quotes, + * thats why <code>&apos;</code> is never serialized.<br> + * <em>Note:</em> Control chars are written unescaped, but if the user uses others than tab, LF + * and CR the resulting XML will become invalid. + * @param value a string + * @param forAttribute flag if string is attribute value (need to additional escape quotes) + * @param escapeWhitespaces Decides if LF, CR and TAB are escaped. + * @return Returns the value ready for XML output. + */ + public static String escapeXML(String value, boolean forAttribute, boolean escapeWhitespaces) + { + // quick check if character are contained that need special treatment + boolean needsEscaping = false; + for (int i = 0; i < value.length (); i++) + { + char c = value.charAt (i); + if ( + c == '<' || c == '>' || c == '&' || // XML chars + (escapeWhitespaces && (c == '\t' || c == '\n' || c == '\r')) || + (forAttribute && c == '"')) + { + needsEscaping = true; + break; + } + } + + if (!needsEscaping) + { + // fast path + return value; + } + else + { + // slow path with escaping + StringBuffer buffer = new StringBuffer(value.length() * 4 / 3); + for (int i = 0; i < value.length (); i++) + { + char c = value.charAt (i); + if (!(escapeWhitespaces && (c == '\t' || c == '\n' || c == '\r'))) + { + switch (c) + { + // we do what "Canonical XML" expects + // AUDIT: ' not serialized as only outer qoutes are used + case '<': buffer.append("<"); continue; + case '>': buffer.append(">"); continue; + case '&': buffer.append("&"); continue; + case '"': buffer.append(forAttribute ? """ : "\""); continue; + default: buffer.append(c); continue; + } + } + else + { + // write control chars escaped, + // if there are others than tab, LF and CR the xml will become invalid. + buffer.append("&#x"); + buffer.append(Integer.toHexString(c).toUpperCase()); + buffer.append(';'); + } + } + return buffer.toString(); + } + } + + + /** + * Replaces the ASCII control chars with a space. + * + * @param value + * a node value + * @return Returns the cleaned up value + */ + static String removeControlChars(String value) + { + StringBuffer buffer = new StringBuffer(value); + for (int i = 0; i < buffer.length(); i++) + { + if (isControlChar(buffer.charAt(i))) + { + buffer.setCharAt(i, ' '); + } + } + return buffer.toString(); + } + + + /** + * Simple check if a character is a valid XML start name char. + * Within ASCII range<br> + * ":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] | [#xD8-#xF6]<br> + * are accepted, above all characters (which is not entirely + * correct according to the XML Spec) + * + * @param ch a character + * @return Returns true if the character is a valid first char of an XML name. + */ + private static boolean isNameStartChar(char ch) + { + return ch > 0xFF || xmlNameStartChars[ch]; + } + + + /** + * Simple check if a character is a valid XML name char + * (every char except the first one). + * Within ASCII range<br> + * ":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] | [#xD8-#xF6]<br> + * are accepted, above all characters (which is not entirely + * correct according to the XML Spec) + * + * @param ch a character + * @return Returns true if the character is a valid char of an XML name. + */ + private static boolean isNameChar(char ch) + { + return ch > 0xFF || xmlNameChars[ch]; + } + + + /** + * Initializes the char tables for later use. + */ + private static void initCharTables() + { + xmlNameChars = new boolean[0x0100]; + xmlNameStartChars = new boolean[0x0100]; + + for (char ch = 0; ch < xmlNameChars.length; ch++) + { + xmlNameStartChars[ch] = + ('a' <= ch && ch <= 'z') || + ('A' <= ch && ch <= 'Z') || + ch == ':' || + ch == '_' || + (0xC0 <= ch && ch <= 0xD6) || + (0xD8 <= ch && ch <= 0xF6); + + xmlNameChars[ch] = + ('a' <= ch && ch <= 'z') || + ('A' <= ch && ch <= 'Z') || + ('0' <= ch && ch <= '9') || + ch == ':' || + ch == '_' || + ch == '-' || + ch == '.' || + ch == 0xB7 || + (0xC0 <= ch && ch <= 0xD6) || + (0xD8 <= ch && ch <= 0xF6); + } + } +}
\ No newline at end of file |