com.adobe.internal.xmp.impl.Utils Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of xmpcore Show documentation
Show all versions of xmpcore Show documentation
The XMP Library for Java is based on the C++ XMPCore library
and the API is similar.
// =================================================================================================
// 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.internal.xmp.impl;
import java.util.HashSet;
import java.util.Set;
import com.adobe.internal.xmp.XMPConst;
/**
* Utility functions for the XMPToolkit implementation.
*
* @author Stefan Makswit
* @version $Revision$
* @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:
*
* - The primary subtag is lower case, the suggested practice of ISO 639.
*
- All 2 letter secondary subtags are upper case, the suggested
* practice of ISO 3166.
*
- All other subtags are lower case.
*
*
* @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:
*
* - [qualName="value"] - An element in an array of structs, chosen by a
* field value.
*
- [?qualName="value"] - An element in an array, chosen by a qualifier
* value.
*
* 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. Note: 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() };
}
static Set EXTERNAL_XMPDM_PROPS = new HashSet(){{
add("xmpDM:album");
add( "xmpDM:altTapeName");
add("xmpDM:altTimecode");
add("xmpDM:artist");
add("xmpDM:cameraAngle");
add("xmpDM:cameraLabel");
add("xmpDM:cameraModel");
add("xmpDM:cameraMove");
add("xmpDM:client");
add("xmpDM:comment");
add("xmpDM:composer");
add("xmpDM:director");
add( "xmpDM:directorPhotography");
add("xmpDM:engineer");
add( "xmpDM:genre");
add( "xmpDM:good");
add("xmpDM:instrument");
add( "xmpDM:logComment");
add("xmpDM:projectName");
add("xmpDM:releaseDate");
add("xmpDM:scene");
add("xmpDM:shotDate");
add("xmpDM:shotDay");
add("xmpDM:shotLocation");
add("xmpDM:shotName");
add("xmpDM:shotNumber");
add("xmpDM:shotSize");
add("xmpDM:speakerPlacement");
add("xmpDM:takeNumber");
add("xmpDM:tapeName");
add("xmpDM:trackNumber");
add("xmpDM:videoAlphaMode");
add("xmpDM:videoAlphaPremultipleColor");
}};
/**
*
* @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) || "photoshop:TextLayers".equals(prop))
{
isInternal = true;
}
}
else if(NS_DM.equals(schema)){
isInternal = ! EXTERNAL_XMPDM_PROPS.contains(prop);
}
else if (NS_CAMERARAW.equals(schema))
{
isInternal = true;
}
else if(NS_SCRIPT.equals(schema)){
isInternal = true;
if(("xmpScript:action".equals(prop)) || ("xmpScript:character".equals(prop))
||("xmpScript:dialog".equals(prop))||("xmpScript:sceneSetting".equals(prop))
||("xmpScript:sceneTimeOfDay".equals(prop)))
isInternal = false;
}
else if(NS_BWF.equals(schema)){
if("bext:version".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:
*
* - Length of the UUID is 32
* - The Delimiter count is 4 and all the 4 delimiter are on their right
* position (8,13,18,23)
*
*
* FfF: in Java 1.5 replace by java.util.UUID
*
* @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
* ":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] | [#xD8-#xF6]
* are accepted, above all characters (which is not entirely
* correct according to the XML Spec.
*
* @param name an XML Name
* @return Return true
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.
* Note: The attribute is always limited by quotes,
* thats why '
is never serialized.
* Note: 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("");
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.
* All characters according to the XML Spec 1.1 are accepted:
* http://www.w3.org/TR/xml11/#NT-NameStartChar
*
* @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]) ||
(ch >= 0x100 && ch <= 0x2FF) ||
(ch >= 0x370 && ch <= 0x37D) ||
(ch >= 0x37F && ch <= 0x1FFF) ||
(ch >= 0x200C && ch <= 0x200D) ||
(ch >= 0x2070 && ch <= 0x218F) ||
(ch >= 0x2C00 && ch <= 0x2FEF) ||
(ch >= 0x3001 && ch <= 0xD7FF) ||
(ch >= 0xF900 && ch <= 0xFDCF) ||
(ch >= 0xFDF0 && ch <= 0xFFFD) ||
(ch >= 0x10000 && ch <= 0xEFFFF);
}
/**
* Simple check if a character is a valid XML name char
* (every char except the first one), according to the XML Spec 1.1:
* http://www.w3.org/TR/xml11/#NT-NameChar
*
* @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]) ||
isNameStartChar(ch) ||
(ch >= 0x300 && ch <= 0x36F) ||
(ch >= 0x203F && ch <= 0x2040);
}
/**
* Initializes the char tables for the chars 0x00-0xFF for later use,
* according to the XML 1.1 specification
* http://www.w3.org/TR/xml11
*/
private static void initCharTables()
{
xmlNameChars = new boolean[0x0100];
xmlNameStartChars = new boolean[0x0100];
for (char ch = 0; ch < xmlNameChars.length; ch++)
{
xmlNameStartChars[ch] =
ch == ':' ||
('A' <= ch && ch <= 'Z') ||
ch == '_' ||
('a' <= ch && ch <= 'z') ||
(0xC0 <= ch && ch <= 0xD6) ||
(0xD8 <= ch && ch <= 0xF6) ||
(0xF8 <= ch && ch <= 0xFF);
xmlNameChars[ch] =
xmlNameStartChars[ch] ||
ch == '-' ||
ch == '.' ||
('0' <= ch && ch <= '9') ||
ch == 0xB7;
}
}
}