com.adobe.internal.xmp.impl.ParseRDF 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.List;
import java.util.ArrayList;
import java.util.Iterator;
import org.w3c.dom.Attr;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import com.adobe.internal.xmp.XMPConst;
import com.adobe.internal.xmp.XMPError;
import com.adobe.internal.xmp.XMPException;
import com.adobe.internal.xmp.XMPMetaFactory;
import com.adobe.internal.xmp.XMPSchemaRegistry;
import com.adobe.internal.xmp.options.ParseOptions;
import com.adobe.internal.xmp.options.PropertyOptions;
/**
* Parser for "normal" XML serialisation of RDF.
*
* @author Stefan Makswit
* @version $Revision$
* @since 14.07.2006
*/
public class ParseRDF implements XMPError, XMPConst
{
/** */
public static final int RDFTERM_OTHER = 0;
/** Start of coreSyntaxTerms. */
public static final int RDFTERM_RDF = 1;
/** */
public static final int RDFTERM_ID = 2;
/** */
public static final int RDFTERM_ABOUT = 3;
/** */
public static final int RDFTERM_PARSE_TYPE = 4;
/** */
public static final int RDFTERM_RESOURCE = 5;
/** */
public static final int RDFTERM_NODE_ID = 6;
/** End of coreSyntaxTerms */
public static final int RDFTERM_DATATYPE = 7;
/** Start of additions for syntax Terms. */
public static final int RDFTERM_DESCRIPTION = 8;
/** End of of additions for syntaxTerms. */
public static final int RDFTERM_LI = 9;
/** Start of oldTerms. */
public static final int RDFTERM_ABOUT_EACH = 10;
/** */
public static final int RDFTERM_ABOUT_EACH_PREFIX = 11;
/** End of oldTerms. */
public static final int RDFTERM_BAG_ID = 12;
/** */
public static final int RDFTERM_FIRST_CORE = RDFTERM_RDF;
/** */
public static final int RDFTERM_LAST_CORE = RDFTERM_DATATYPE;
/** ! Yes, the syntax terms include the core terms. */
public static final int RDFTERM_FIRST_SYNTAX = RDFTERM_FIRST_CORE;
/** */
public static final int RDFTERM_LAST_SYNTAX = RDFTERM_LI;
/** */
public static final int RDFTERM_FIRST_OLD = RDFTERM_ABOUT_EACH;
/** */
public static final int RDFTERM_LAST_OLD = RDFTERM_BAG_ID;
/** this prefix is used for default namespaces */
public static final String DEFAULT_PREFIX = "_dflt";
/**
* The main parsing method. The XML tree is walked through from the root node and and XMP tree
* is created. This is a raw parse, the normalisation of the XMP tree happens outside.
*
* @param xmlRoot the XML root node
* @param options ParseOptions to indicate the parse options provided by the client
* @return Returns an XMP metadata object (not normalized)
* @throws XMPException Occurs if the parsing fails for any reason.
*/
static XMPMetaImpl parse(Node xmlRoot, ParseOptions options) throws XMPException
{
XMPMetaImpl xmp = new XMPMetaImpl();
rdf_RDF(xmp, xmlRoot, options);
return xmp;
}
/**
* Each of these parsing methods is responsible for recognizing an RDF
* syntax production and adding the appropriate structure to the XMP tree.
* They simply return for success, failures will throw an exception.
*
* @param xmp the xmp metadata object that is generated
* @param rdfRdfNode the top-level xml node
* @param options ParseOptions to indicate the parse options provided by the client
* @throws XMPException thown on parsing errors
*/
static void rdf_RDF(XMPMetaImpl xmp, Node rdfRdfNode, ParseOptions options) throws XMPException
{
if (rdfRdfNode.hasAttributes())
{
rdf_NodeElementList (xmp, xmp.getRoot(), rdfRdfNode, options);
}
else
{
throw new XMPException("Invalid attributes of rdf:RDF element", BADRDF);
}
}
/**
* 7.2.10 nodeElementList
* ws* ( nodeElement ws* )*
*
* Note: this method is only called from the rdf:RDF-node (top level)
* @param xmp the xmp metadata object that is generated
* @param xmpParent the parent xmp node
* @param rdfRdfNode the top-level xml node
* @param options ParseOptions to indicate the parse options provided by the client
* @throws XMPException thown on parsing errors
*/
private static void rdf_NodeElementList(XMPMetaImpl xmp, XMPNode xmpParent, Node rdfRdfNode, ParseOptions options)
throws XMPException
{
for (int i = 0; i < rdfRdfNode.getChildNodes().getLength(); i++)
{
Node child = rdfRdfNode.getChildNodes().item(i);
// filter whitespaces (and all text nodes)
if (!isWhitespaceNode(child))
{
rdf_NodeElement (xmp, xmpParent, child, true, options);
}
}
}
/**
* 7.2.5 nodeElementURIs
* anyURI - ( coreSyntaxTerms | rdf:li | oldTerms )
*
* 7.2.11 nodeElement
* start-element ( URI == nodeElementURIs,
* attributes == set ( ( idAttr | nodeIdAttr | aboutAttr )?, propertyAttr* ) )
* propertyEltList
* end-element()
*
* A node element URI is rdf:Description or anything else that is not an RDF
* term.
*
* @param xmp the xmp metadata object that is generated
* @param xmpParent the parent xmp node
* @param xmlNode the currently processed XML node
* @param isTopLevel Flag if the node is a top-level node
* @param options ParseOptions to indicate the parse options provided by the client
* @throws XMPException thown on parsing errors
*/
private static void rdf_NodeElement(XMPMetaImpl xmp, XMPNode xmpParent, Node xmlNode,
boolean isTopLevel, ParseOptions options) throws XMPException
{
int nodeTerm = getRDFTermKind (xmlNode);
if (nodeTerm != RDFTERM_DESCRIPTION && nodeTerm != RDFTERM_OTHER)
{
throw new XMPException("Node element must be rdf:Description or typed node",
BADRDF);
}
else if (isTopLevel && nodeTerm == RDFTERM_OTHER)
{
throw new XMPException("Top level typed node not allowed", BADXMP);
}
else
{
rdf_NodeElementAttrs (xmp, xmpParent, xmlNode, isTopLevel, options);
rdf_PropertyElementList (xmp, xmpParent, xmlNode, isTopLevel, options);
}
}
/**
*
* 7.2.7 propertyAttributeURIs
* anyURI - ( coreSyntaxTerms | rdf:Description | rdf:li | oldTerms )
*
* 7.2.11 nodeElement
* start-element ( URI == nodeElementURIs,
* attributes == set ( ( idAttr | nodeIdAttr | aboutAttr )?, propertyAttr* ) )
* propertyEltList
* end-element()
*
* Process the attribute list for an RDF node element. A property attribute URI is
* anything other than an RDF term. The rdf:ID and rdf:nodeID attributes are simply ignored,
* as are rdf:about attributes on inner nodes.
*
* @param xmp the xmp metadata object that is generated
* @param xmpParent the parent xmp node
* @param xmlNode the currently processed XML node
* @param isTopLevel Flag if the node is a top-level node
* @param options ParseOptions to indicate the parse options provided by the client
* @throws XMPException thown on parsing errors
*/
private static void rdf_NodeElementAttrs(XMPMetaImpl xmp, XMPNode xmpParent, Node xmlNode,
boolean isTopLevel, ParseOptions options) throws XMPException
{
// Used to detect attributes that are mutually exclusive.
int exclusiveAttrs = 0;
for (int i = 0; i < xmlNode.getAttributes().getLength(); i++)
{
Node attribute = xmlNode.getAttributes().item(i);
// quick hack, ns declarations do not appear in C++
// ignore "ID" without namespace
if ("xmlns".equals(attribute.getPrefix()) ||
(attribute.getPrefix() == null && "xmlns".equals(attribute.getNodeName())))
{
continue;
}
int attrTerm = getRDFTermKind(attribute);
switch (attrTerm)
{
case RDFTERM_ID:
case RDFTERM_NODE_ID:
case RDFTERM_ABOUT:
if (exclusiveAttrs > 0)
{
throw new XMPException("Mutally exclusive about, ID, nodeID attributes",
BADRDF);
}
exclusiveAttrs++;
if (isTopLevel && (attrTerm == RDFTERM_ABOUT))
{
// This is the rdf:about attribute on a top level node. Set
// the XMP tree name if
// it doesn't have a name yet. Make sure this name matches
// the XMP tree name.
if (xmpParent.getName() != null && xmpParent.getName().length() > 0)
{
if (!xmpParent.getName().equals(attribute.getNodeValue()))
{
throw new XMPException("Mismatched top level rdf:about values",
BADXMP);
}
}
else
{
xmpParent.setName(attribute.getNodeValue());
}
}
break;
case RDFTERM_OTHER:
addChildNode(xmp, xmpParent, attribute, attribute.getNodeValue(), isTopLevel);
break;
default:
throw new XMPException("Invalid nodeElement attribute", BADRDF);
}
}
}
/**
* 7.2.13 propertyEltList
* ws* ( propertyElt ws* )*
*
* @param xmp the xmp metadata object that is generated
* @param xmpParent the parent xmp node
* @param xmlParent the currently processed XML node
* @param isTopLevel Flag if the node is a top-level node
* @param options ParseOptions to indicate the parse options provided by the client
* @throws XMPException thown on parsing errors
*/
private static void rdf_PropertyElementList(XMPMetaImpl xmp, XMPNode xmpParent, Node xmlParent,
boolean isTopLevel, ParseOptions options) throws XMPException
{
for (int i = 0; i < xmlParent.getChildNodes().getLength(); i++)
{
Node currChild = xmlParent.getChildNodes().item(i);
if (isWhitespaceNode(currChild))
{
continue;
}
else if (currChild.getNodeType() != Node.ELEMENT_NODE)
{
throw new XMPException("Expected property element node not found", BADRDF);
}
else if ((xmpParent.getOptions().isArrayLimited() && (i > xmpParent.getOptions().getArrayElementsLimit())))
{
break;
}
else
{
rdf_PropertyElement(xmp, xmpParent, currChild, isTopLevel, options);
}
}
}
/**
* 7.2.14 propertyElt
*
* resourcePropertyElt | literalPropertyElt | parseTypeLiteralPropertyElt |
* parseTypeResourcePropertyElt | parseTypeCollectionPropertyElt |
* parseTypeOtherPropertyElt | emptyPropertyElt
*
* 7.2.15 resourcePropertyElt
* start-element ( URI == propertyElementURIs, attributes == set ( idAttr? ) )
* ws* nodeElement ws*
* end-element()
*
* 7.2.16 literalPropertyElt
* start-element (
* URI == propertyElementURIs, attributes == set ( idAttr?, datatypeAttr?) )
* text()
* end-element()
*
* 7.2.17 parseTypeLiteralPropertyElt
* start-element (
* URI == propertyElementURIs, attributes == set ( idAttr?, parseLiteral ) )
* literal
* end-element()
*
* 7.2.18 parseTypeResourcePropertyElt
* start-element (
* URI == propertyElementURIs, attributes == set ( idAttr?, parseResource ) )
* propertyEltList
* end-element()
*
* 7.2.19 parseTypeCollectionPropertyElt
* start-element (
* URI == propertyElementURIs, attributes == set ( idAttr?, parseCollection ) )
* nodeElementList
* end-element()
*
* 7.2.20 parseTypeOtherPropertyElt
* start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseOther ) )
* propertyEltList
* end-element()
*
* 7.2.21 emptyPropertyElt
* start-element ( URI == propertyElementURIs,
* attributes == set ( idAttr?, ( resourceAttr | nodeIdAttr )?, propertyAttr* ) )
* end-element()
*
* The various property element forms are not distinguished by the XML element name,
* but by their attributes for the most part. The exceptions are resourcePropertyElt and
* literalPropertyElt. They are distinguished by their XML element content.
*
* NOTE: The RDF syntax does not explicitly include the xml:lang attribute although it can
* appear in many of these. We have to allow for it in the attibute counts below.
*
* @param xmp the xmp metadata object that is generated
* @param xmpParent the parent xmp node
* @param xmlNode the currently processed XML node
* @param isTopLevel Flag if the node is a top-level node
* @param options ParseOptions to indicate the parse options provided by the client
* @throws XMPException thown on parsing errors
*/
private static void rdf_PropertyElement(XMPMetaImpl xmp, XMPNode xmpParent, Node xmlNode,
boolean isTopLevel, ParseOptions options) throws XMPException
{
int nodeTerm = getRDFTermKind (xmlNode);
if (!isPropertyElementName(nodeTerm))
{
throw new XMPException("Invalid property element name", BADRDF);
}
// remove the namespace-definitions from the list
NamedNodeMap attributes = xmlNode.getAttributes();
List nsAttrs = null;
for (int i = 0; i < attributes.getLength(); i++)
{
Node attribute = attributes.item(i);
if ("xmlns".equals(attribute.getPrefix()) ||
(attribute.getPrefix() == null && "xmlns".equals(attribute.getNodeName())))
{
if (nsAttrs == null)
{
nsAttrs = new ArrayList();
}
nsAttrs.add(attribute.getNodeName());
}
}
if (nsAttrs != null)
{
for (Iterator it = nsAttrs.iterator(); it.hasNext();)
{
String ns = (String) it.next();
attributes.removeNamedItem(ns);
}
}
if (attributes.getLength() > 3)
{
// Only an emptyPropertyElt can have more than 3 attributes.
rdf_EmptyPropertyElement(xmp, xmpParent, xmlNode, isTopLevel);
}
else
{
// Look through the attributes for one that isn't rdf:ID or xml:lang,
// it will usually tell what we should be dealing with.
// The called routines must verify their specific syntax!
for (int i = 0; i < attributes.getLength(); i++)
{
Node attribute = attributes.item(i);
String attrLocal = attribute.getLocalName();
String attrNS = attribute.getNamespaceURI();
String attrValue = attribute.getNodeValue();
if (!(XML_LANG.equals(attribute.getNodeName()) &&
!("ID".equals(attrLocal) && NS_RDF.equals(attrNS))))
{
if ("datatype".equals(attrLocal) && NS_RDF.equals(attrNS))
{
rdf_LiteralPropertyElement (xmp, xmpParent, xmlNode, isTopLevel);
}
else if (!("parseType".equals(attrLocal) && NS_RDF.equals(attrNS)))
{
rdf_EmptyPropertyElement (xmp, xmpParent, xmlNode, isTopLevel);
}
else if ("Literal".equals(attrValue))
{
rdf_ParseTypeLiteralPropertyElement();
}
else if ("Resource".equals(attrValue))
{
rdf_ParseTypeResourcePropertyElement(xmp, xmpParent, xmlNode, isTopLevel, options);
}
else if ("Collection".equals(attrValue))
{
rdf_ParseTypeCollectionPropertyElement();
}
else
{
rdf_ParseTypeOtherPropertyElement();
}
return;
}
}
// Only rdf:ID and xml:lang, could be a resourcePropertyElt, a literalPropertyElt,
// or an emptyPropertyElt. Look at the child XML nodes to decide which.
if (xmlNode.hasChildNodes())
{
for (int i = 0; i < xmlNode.getChildNodes().getLength(); i++)
{
Node currChild = xmlNode.getChildNodes().item(i);
if (currChild.getNodeType() != Node.TEXT_NODE)
{
rdf_ResourcePropertyElement (xmp, xmpParent, xmlNode, isTopLevel, options);
return;
}
}
rdf_LiteralPropertyElement (xmp, xmpParent, xmlNode, isTopLevel);
}
else
{
rdf_EmptyPropertyElement (xmp, xmpParent, xmlNode, isTopLevel);
}
}
}
/**
* 7.2.15 resourcePropertyElt
* start-element ( URI == propertyElementURIs, attributes == set ( idAttr? ) )
* ws* nodeElement ws*
* end-element()
*
* This handles structs using an rdf:Description node,
* arrays using rdf:Bag/Seq/Alt, and typedNodes. It also catches and cleans up qualified
* properties written with rdf:Description and rdf:value.
*
* @param xmp the xmp metadata object that is generated
* @param xmpParent the parent xmp node
* @param xmlNode the currently processed XML node
* @param isTopLevel Flag if the node is a top-level node
* @param options ParseOptions to indicate the parse options provided by the client
* @throws XMPException thown on parsing errors
*/
private static void rdf_ResourcePropertyElement(XMPMetaImpl xmp, XMPNode xmpParent,
Node xmlNode, boolean isTopLevel, ParseOptions options) throws XMPException
{
if (isTopLevel && "iX:changes".equals(xmlNode.getNodeName()))
{
// Strip old "punchcard" chaff which has on the prefix "iX:".
return;
}
XMPNode newCompound = addChildNode(xmp, xmpParent, xmlNode, "", isTopLevel);
// walk through the attributes
for (int i = 0; i < xmlNode.getAttributes().getLength(); i++)
{
Node attribute = xmlNode.getAttributes().item(i);
if ("xmlns".equals(attribute.getPrefix()) ||
(attribute.getPrefix() == null && "xmlns".equals(attribute.getNodeName())))
{
continue;
}
String attrLocal = attribute.getLocalName();
String attrNS = attribute.getNamespaceURI();
if (XML_LANG.equals(attribute.getNodeName()))
{
addQualifierNode (newCompound, XML_LANG, attribute.getNodeValue());
}
else if ("ID".equals(attrLocal) && NS_RDF.equals(attrNS))
{
continue; // Ignore all rdf:ID attributes.
}
else
{
throw new XMPException(
"Invalid attribute for resource property element", BADRDF);
}
}
// walk through the children
Node currChild = null;
boolean found = false;
int i;
for (i = 0; i < xmlNode.getChildNodes().getLength(); i++)
{
currChild = xmlNode.getChildNodes().item(i);
if (!isWhitespaceNode(currChild))
{
if (currChild.getNodeType() == Node.ELEMENT_NODE && !found)
{
boolean isRDF = NS_RDF.equals(currChild.getNamespaceURI());
String childLocal = currChild.getLocalName();
if (isRDF && "Bag".equals(childLocal))
{
newCompound.getOptions().setArray(true);
}
else if (isRDF && "Seq".equals(childLocal))
{
newCompound.getOptions().setArray(true).setArrayOrdered(true);
}
else if (isRDF && "Alt".equals(childLocal))
{
newCompound.getOptions().setArray(true).setArrayOrdered(true)
.setArrayAlternate(true);
}
else
{
newCompound.getOptions().setStruct(true);
if (!isRDF && !"Description".equals(childLocal))
{
String typeName = currChild.getNamespaceURI();
if (typeName == null)
{
throw new XMPException(
"All XML elements must be in a namespace", BADXMP);
}
typeName += ':' + childLocal;
addQualifierNode (newCompound, "rdf:type", typeName);
}
}
Integer listLimit;
//listLimit will hold the limit value for the array is the array name is found in the mXMPNodesToLimit map, otherwise will be null
if(newCompound.getOptions().isArray() && ((listLimit = options.getXMPNodesToLimit().get(newCompound.getName())) != null))
{
newCompound.getOptions().setArrayElementLimit(listLimit);
}
rdf_NodeElement (xmp, newCompound, currChild, false, options);
if (newCompound.getHasValueChild())
{
fixupQualifiedNode (newCompound);
}
else if (newCompound.getOptions().isArrayAlternate())
{
XMPNodeUtils.detectAltText(newCompound);
}
found = true;
}
else if (found)
{
// found second child element
throw new XMPException(
"Invalid child of resource property element", BADRDF);
}
else
{
throw new XMPException(
"Children of resource property element must be XML elements", BADRDF);
}
}
}
if (!found)
{
// didn't found any child elements
throw new XMPException("Missing child of resource property element", BADRDF);
}
}
/**
* 7.2.16 literalPropertyElt
* start-element ( URI == propertyElementURIs,
* attributes == set ( idAttr?, datatypeAttr?) )
* text()
* end-element()
*
* Add a leaf node with the text value and qualifiers for the attributes.
* @param xmp the xmp metadata object that is generated
* @param xmpParent the parent xmp node
* @param xmlNode the currently processed XML node
* @param isTopLevel Flag if the node is a top-level node
* @throws XMPException thown on parsing errors
*/
private static void rdf_LiteralPropertyElement(XMPMetaImpl xmp, XMPNode xmpParent,
Node xmlNode, boolean isTopLevel) throws XMPException
{
XMPNode newChild = addChildNode (xmp, xmpParent, xmlNode, null, isTopLevel);
for (int i = 0; i < xmlNode.getAttributes().getLength(); i++)
{
Node attribute = xmlNode.getAttributes().item(i);
if ("xmlns".equals(attribute.getPrefix()) ||
(attribute.getPrefix() == null && "xmlns".equals(attribute.getNodeName())))
{
continue;
}
String attrNS = attribute.getNamespaceURI();
String attrLocal = attribute.getLocalName();
if (XML_LANG.equals(attribute.getNodeName()))
{
addQualifierNode(newChild, XML_LANG, attribute.getNodeValue());
}
else if (NS_RDF.equals(attrNS) &&
("ID".equals(attrLocal) || "datatype".equals(attrLocal)))
{
continue; // Ignore all rdf:ID and rdf:datatype attributes.
}
else
{
throw new XMPException(
"Invalid attribute for literal property element", BADRDF);
}
}
// FfF: Can there be more than one text node in a row?
String textValue = "";
for (int i = 0; i < xmlNode.getChildNodes().getLength(); i++)
{
Node child = xmlNode.getChildNodes().item(i);
if (child.getNodeType() == Node.TEXT_NODE)
{
textValue += child.getNodeValue();
}
else
{
throw new XMPException("Invalid child of literal property element", BADRDF);
}
}
newChild.setValue(textValue);
}
/**
* 7.2.17 parseTypeLiteralPropertyElt
* start-element ( URI == propertyElementURIs,
* attributes == set ( idAttr?, parseLiteral ) )
* literal
* end-element()
*
* @throws XMPException thown on parsing errors
*/
private static void rdf_ParseTypeLiteralPropertyElement() throws XMPException
{
throw new XMPException("ParseTypeLiteral property element not allowed", BADXMP);
}
/**
* 7.2.18 parseTypeResourcePropertyElt
* start-element ( URI == propertyElementURIs,
* attributes == set ( idAttr?, parseResource ) )
* propertyEltList
* end-element()
*
* Add a new struct node with a qualifier for the possible rdf:ID attribute.
* Then process the XML child nodes to get the struct fields.
*
* @param xmp the xmp metadata object that is generated
* @param xmpParent the parent xmp node
* @param xmlNode the currently processed XML node
* @param isTopLevel Flag if the node is a top-level node
* @param options ParseOptions to indicate the parse options provided by the client
* @throws XMPException thown on parsing errors
*/
private static void rdf_ParseTypeResourcePropertyElement(XMPMetaImpl xmp, XMPNode xmpParent,
Node xmlNode, boolean isTopLevel, ParseOptions options) throws XMPException
{
XMPNode newStruct = addChildNode (xmp, xmpParent, xmlNode, "", isTopLevel);
newStruct.getOptions().setStruct(true);
for (int i = 0; i < xmlNode.getAttributes().getLength(); i++)
{
Node attribute = xmlNode.getAttributes().item(i);
if ("xmlns".equals(attribute.getPrefix()) ||
(attribute.getPrefix() == null && "xmlns".equals(attribute.getNodeName())))
{
continue;
}
String attrLocal = attribute.getLocalName();
String attrNS = attribute.getNamespaceURI();
if (XML_LANG.equals(attribute.getNodeName()))
{
addQualifierNode (newStruct, XML_LANG, attribute.getNodeValue());
}
else if (NS_RDF.equals(attrNS) &&
("ID".equals(attrLocal) || "parseType".equals(attrLocal)))
{
continue; // The caller ensured the value is "Resource".
// Ignore all rdf:ID attributes.
}
else
{
throw new XMPException("Invalid attribute for ParseTypeResource property element",
BADRDF);
}
}
rdf_PropertyElementList (xmp, newStruct, xmlNode, false, options);
if (newStruct.getHasValueChild())
{
fixupQualifiedNode (newStruct);
}
// *** Need to look for arrays using rdf:Description and rdf:type.
}
/**
* 7.2.19 parseTypeCollectionPropertyElt
* start-element ( URI == propertyElementURIs,
* attributes == set ( idAttr?, parseCollection ) )
* nodeElementList
* end-element()
*
* @throws XMPException thown on parsing errors
*/
private static void rdf_ParseTypeCollectionPropertyElement() throws XMPException
{
throw new XMPException("ParseTypeCollection property element not allowed", BADXMP);
}
/**
* 7.2.20 parseTypeOtherPropertyElt
* start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseOther ) )
* propertyEltList
* end-element()
*
* @throws XMPException thown on parsing errors
*/
private static void rdf_ParseTypeOtherPropertyElement() throws XMPException
{
throw new XMPException("ParseTypeOther property element not allowed", BADXMP);
}
/**
* 7.2.21 emptyPropertyElt
* start-element ( URI == propertyElementURIs,
* attributes == set (
* idAttr?, ( resourceAttr | nodeIdAttr )?, propertyAttr* ) )
* end-element()
*
*
*
*
*
*
* An emptyPropertyElt is an element with no contained content, just a possibly empty set of
* attributes. An emptyPropertyElt can represent three special cases of simple XMP properties: a
* simple property with an empty value (ns:Prop1), a simple property whose value is a URI
* (ns:Prop2), or a simple property with simple qualifiers (ns:Prop3).
* An emptyPropertyElt can also represent an XMP struct whose fields are all simple and
* unqualified (ns:Prop4).
*
* It is an error to use both rdf:value and rdf:resource - that can lead to invalid RDF in the
* verbose form written using a literalPropertyElt.
*
* The XMP mapping for an emptyPropertyElt is a bit different from generic RDF, partly for
* design reasons and partly for historical reasons. The XMP mapping rules are:
*
* - If there is an rdf:value attribute then this is a simple property
* with a text value.
* All other attributes are qualifiers.
*
- If there is an rdf:resource attribute then this is a simple property
* with a URI value.
* All other attributes are qualifiers.
*
- If there are no attributes other than xml:lang, rdf:ID, or rdf:nodeID
* then this is a simple
* property with an empty value.
*
- Otherwise this is a struct, the attributes other than xml:lang, rdf:ID,
* or rdf:nodeID are fields.
*
*
* @param xmp the xmp metadata object that is generated
* @param xmpParent the parent xmp node
* @param xmlNode the currently processed XML node
* @param isTopLevel Flag if the node is a top-level node
* @throws XMPException thown on parsing errors
*/
private static void rdf_EmptyPropertyElement(XMPMetaImpl xmp, XMPNode xmpParent, Node xmlNode,
boolean isTopLevel) throws XMPException
{
boolean hasPropertyAttrs = false;
boolean hasResourceAttr = false;
boolean hasNodeIDAttr = false;
boolean hasValueAttr = false;
Node valueNode = null; // ! Can come from rdf:value or rdf:resource.
if (xmlNode.hasChildNodes())
{
throw new XMPException(
"Nested content not allowed with rdf:resource or property attributes",
BADRDF);
}
// First figure out what XMP this maps to and remember the XML node for a simple value.
for (int i = 0; i < xmlNode.getAttributes().getLength(); i++)
{
Node attribute = xmlNode.getAttributes().item(i);
if ("xmlns".equals(attribute.getPrefix()) ||
(attribute.getPrefix() == null && "xmlns".equals(attribute.getNodeName())))
{
continue;
}
int attrTerm = getRDFTermKind (attribute);
switch (attrTerm)
{
case RDFTERM_ID :
// Nothing to do.
break;
case RDFTERM_RESOURCE :
if (hasNodeIDAttr)
{
throw new XMPException(
"Empty property element can't have both rdf:resource and rdf:nodeID",
BADRDF);
}
else if (hasValueAttr)
{
throw new XMPException(
"Empty property element can't have both rdf:value and rdf:resource",
BADXMP);
}
hasResourceAttr = true;
if (!hasValueAttr)
{
valueNode = attribute;
}
break;
case RDFTERM_NODE_ID:
if (hasResourceAttr)
{
throw new XMPException(
"Empty property element can't have both rdf:resource and rdf:nodeID",
BADRDF);
}
hasNodeIDAttr = true;
break;
case RDFTERM_OTHER:
if ("value".equals(attribute.getLocalName())
&& NS_RDF.equals(attribute.getNamespaceURI()))
{
if (hasResourceAttr)
{
throw new XMPException(
"Empty property element can't have both rdf:value and rdf:resource",
BADXMP);
}
hasValueAttr = true;
valueNode = attribute;
}
else if (!XML_LANG.equals(attribute.getNodeName()))
{
hasPropertyAttrs = true;
}
break;
default:
throw new XMPException("Unrecognized attribute of empty property element",
BADRDF);
}
}
// Create the right kind of child node and visit the attributes again
// to add the fields or qualifiers.
// ! Because of implementation vagaries,
// the xmpParent is the tree root for top level properties.
// ! The schema is found, created if necessary, by addChildNode.
XMPNode childNode = addChildNode(xmp, xmpParent, xmlNode, "", isTopLevel);
boolean childIsStruct = false;
if (hasValueAttr || hasResourceAttr)
{
childNode.setValue(valueNode != null ? valueNode.getNodeValue() : "");
if (!hasValueAttr)
{
// ! Might have both rdf:value and rdf:resource.
childNode.getOptions().setURI(true);
}
}
else if (hasPropertyAttrs)
{
childNode.getOptions().setStruct(true);
childIsStruct = true;
}
for (int i = 0; i < xmlNode.getAttributes().getLength(); i++)
{
Node attribute = xmlNode.getAttributes().item(i);
if (attribute == valueNode ||
"xmlns".equals(attribute.getPrefix()) ||
(attribute.getPrefix() == null && "xmlns".equals(attribute.getNodeName())))
{
continue; // Skip the rdf:value or rdf:resource attribute holding the value.
}
int attrTerm = getRDFTermKind (attribute);
switch (attrTerm)
{
case RDFTERM_ID :
case RDFTERM_NODE_ID :
break; // Ignore all rdf:ID and rdf:nodeID attributes.
case RDFTERM_RESOURCE :
addQualifierNode(childNode, "rdf:resource", attribute.getNodeValue());
break;
case RDFTERM_OTHER :
if (!childIsStruct)
{
addQualifierNode(
childNode, attribute.getNodeName(), attribute.getNodeValue());
}
else if (XML_LANG.equals(attribute.getNodeName()))
{
addQualifierNode (childNode, XML_LANG, attribute.getNodeValue());
}
else
{
addChildNode (xmp, childNode, attribute, attribute.getNodeValue(), false);
}
break;
default :
throw new XMPException("Unrecognized attribute of empty property element",
BADRDF);
}
}
}
/**
* Adds a child node.
*
* @param xmp the xmp metadata object that is generated
* @param xmpParent the parent xmp node
* @param xmlNode the currently processed XML node
* @param value Node value
* @param isTopLevel Flag if the node is a top-level node
* @return Returns the newly created child node.
* @throws XMPException thown on parsing errors
*/
private static XMPNode addChildNode(XMPMetaImpl xmp, XMPNode xmpParent, Node xmlNode,
String value, boolean isTopLevel) throws XMPException
{
XMPSchemaRegistry registry = XMPMetaFactory.getSchemaRegistry();
String namespace = xmlNode.getNamespaceURI();
String childName;
if (namespace != null)
{
if (NS_DC_DEPRECATED.equals(namespace))
{
// Fix a legacy DC namespace
// FfF: remove it after CS3
namespace = NS_DC;
}
String prefix = registry.getNamespacePrefix(namespace);
if (prefix == null)
{
prefix = xmlNode.getPrefix() != null ? xmlNode.getPrefix() : DEFAULT_PREFIX;
prefix = registry.registerNamespace(namespace, prefix);
}
childName = prefix + xmlNode.getLocalName();
}
else
{
throw new XMPException(
"XML namespace required for all elements and attributes", BADRDF);
}
// create schema node if not already there
PropertyOptions childOptions = new PropertyOptions();
boolean isAlias = false;
if (isTopLevel)
{
// Lookup the schema node, adjust the XMP parent pointer.
// Incoming parent must be the tree root.
XMPNode schemaNode = XMPNodeUtils.findSchemaNode(xmp.getRoot(), namespace,
DEFAULT_PREFIX, true);
schemaNode.setImplicit(false); // Clear the implicit node bit.
// *** Should use "opt &= ~flag" (no conditional),
// need runtime check for proper 32 bit code.
xmpParent = schemaNode;
// If this is an alias set the alias flag in the node
// and the hasAliases flag in the tree.
if (registry.findAlias(childName) != null)
{
isAlias = true;
xmp.getRoot().setHasAliases(true);
schemaNode.setHasAliases(true);
}
}
// Make sure that this is not a duplicate of a named node.
boolean isArrayItem = isNumberedArrayItemName(childName);
boolean isValueNode = "rdf:value".equals(childName);
// Create XMP node and so some checks
XMPNode newChild = new XMPNode(
childName, value, childOptions);
newChild.setAlias(isAlias);
// Add the new child to the XMP parent node, a value node first.
if (!isValueNode)
{
xmpParent.addChild(newChild);
}
else
{
xmpParent.addChild(1, newChild);
}
if (isValueNode)
{
if (isTopLevel || !xmpParent.getOptions().isStruct())
{
throw new XMPException("Misplaced rdf:value element", BADRDF);
}
xmpParent.setHasValueChild(true);
}
boolean isParentArray = xmpParent.getOptions().isArray();
if (isParentArray && isArrayItem)
{
newChild.setName(ARRAY_ITEM_NAME);
}
else if (!isParentArray && isArrayItem)
{
throw new XMPException("Misplaced rdf:li element", BADRDF);
}
else if (isParentArray && !isArrayItem)
{
throw new XMPException("Arrays cannot have arbitrary child names", BADRDF);
}
return newChild;
}
/**
* Adds a qualifier node.
*
* @param xmpParent the parent xmp node
* @param name the name of the qualifier which has to be
* QName including the default prefix
* @param value the value of the qualifier
* @return Returns the newly created child node.
* @throws XMPException thown on parsing errors
*/
private static XMPNode addQualifierNode(XMPNode xmpParent, String name, String value)
throws XMPException
{
boolean isLang = XML_LANG.equals(name);
XMPNode newQual = null;
// normalize value of language qualifiers
newQual = new XMPNode(name, isLang ? Utils.normalizeLangValue(value) : value, null);
xmpParent.addQualifier(newQual);
return newQual;
}
/**
* The parent is an RDF pseudo-struct containing an rdf:value field. Fix the
* XMP data model. The rdf:value node must be the first child, the other
* children are qualifiers. The form, value, and children of the rdf:value
* node are the real ones. The rdf:value node's qualifiers must be added to
* the others.
*
* @param xmpParent the parent xmp node
* @throws XMPException thown on parsing errors
*/
private static void fixupQualifiedNode(XMPNode xmpParent) throws XMPException
{
assert xmpParent.getOptions().isStruct() && xmpParent.hasChildren();
XMPNode valueNode = xmpParent.getChild(1);
assert "rdf:value".equals(valueNode.getName());
// Move the qualifiers on the value node to the parent.
// Make sure an xml:lang qualifier stays at the front.
// Check for duplicate names between the value node's qualifiers and the parent's children.
// The parent's children are about to become qualifiers. Check here, between the groups.
// Intra-group duplicates are caught by XMPNode#addChild(...).
// FfF: integrate this into the loop below
if (valueNode.getOptions().getHasLanguage())
{
if (xmpParent.getOptions().getHasLanguage())
{
throw new XMPException("Redundant xml:lang for rdf:value element",
BADXMP);
}
XMPNode langQual = valueNode.getQualifier(1);
valueNode.removeQualifier(langQual);
xmpParent.addQualifier(langQual);
}
// Start the remaining copy after the xml:lang qualifier.
for (int i = 1; i <= valueNode.getQualifierLength(); i++)
{
XMPNode qualifier = valueNode.getQualifier(i);
xmpParent.addQualifier(qualifier);
}
// Change the parent's other children into qualifiers.
// This loop starts at 1, child 0 is the rdf:value node.
for (int i = 2; i <= xmpParent.getChildrenLength(); i++)
{
XMPNode qualifier = xmpParent.getChild(i);
xmpParent.addQualifier(qualifier);
}
// Move the options and value last, other checks need the parent's original options.
// Move the value node's children to be the parent's children.
assert xmpParent.getOptions().isStruct() || xmpParent.getHasValueChild();
xmpParent.setHasValueChild(false);
xmpParent.getOptions().setStruct(false);
xmpParent.getOptions().mergeWith(valueNode.getOptions());
xmpParent.setValue(valueNode.getValue());
xmpParent.removeChildren();
for (Iterator it = valueNode.iterateChildren(); it.hasNext();)
{
XMPNode child = (XMPNode) it.next();
xmpParent.addChild(child);
}
}
/**
* Checks if the node is a white space.
* @param node an XML-node
* @return Returns whether the node is a whitespace node,
* i.e. a text node that contains only whitespaces.
*/
private static boolean isWhitespaceNode(Node node)
{
if (node.getNodeType() != Node.TEXT_NODE)
{
return false;
}
String value = node.getNodeValue();
for (int i = 0; i < value.length(); i++)
{
if (!Character.isWhitespace(value.charAt(i)))
{
return false;
}
}
return true;
}
/**
* 7.2.6 propertyElementURIs
* anyURI - ( coreSyntaxTerms | rdf:Description | oldTerms )
*
* @param term the term id
* @return Return true if the term is a property element name.
*/
private static boolean isPropertyElementName(int term)
{
if (term == RDFTERM_DESCRIPTION || isOldTerm(term))
{
return false;
}
else
{
return (!isCoreSyntaxTerm(term));
}
}
/**
* 7.2.4 oldTerms
* rdf:aboutEach | rdf:aboutEachPrefix | rdf:bagID
*
* @param term the term id
* @return Returns true if the term is an old term.
*/
private static boolean isOldTerm(int term)
{
return RDFTERM_FIRST_OLD <= term && term <= RDFTERM_LAST_OLD;
}
/**
* 7.2.2 coreSyntaxTerms
* rdf:RDF | rdf:ID | rdf:about | rdf:parseType | rdf:resource | rdf:nodeID |
* rdf:datatype
*
* @param term the term id
* @return Return true if the term is a core syntax term
*/
private static boolean isCoreSyntaxTerm(int term)
{
return RDFTERM_FIRST_CORE <= term && term <= RDFTERM_LAST_CORE;
}
/**
* Determines the ID for a certain RDF Term.
* Arranged to hopefully minimize the parse time for large XMP.
*
* @param node an XML node
* @return Returns the term ID.
*/
private static int getRDFTermKind(Node node)
{
String localName = node.getLocalName();
String namespace = node.getNamespaceURI();
if (
namespace == null &&
("about".equals(localName) || "ID".equals(localName)) &&
(node instanceof Attr) &&
NS_RDF.equals(((Attr) node).getOwnerElement().getNamespaceURI())
)
{
namespace = NS_RDF;
}
if (NS_RDF.equals(namespace))
{
if ("li".equals(localName))
{
return RDFTERM_LI;
}
else if ("parseType".equals(localName))
{
return RDFTERM_PARSE_TYPE;
}
else if ("Description".equals(localName))
{
return RDFTERM_DESCRIPTION;
}
else if ("about".equals(localName))
{
return RDFTERM_ABOUT;
}
else if ("resource".equals(localName))
{
return RDFTERM_RESOURCE;
}
else if ("RDF".equals(localName))
{
return RDFTERM_RDF;
}
else if ("ID".equals(localName))
{
return RDFTERM_ID;
}
else if ("nodeID".equals(localName))
{
return RDFTERM_NODE_ID;
}
else if ("datatype".equals(localName))
{
return RDFTERM_DATATYPE;
}
else if ("aboutEach".equals(localName))
{
return RDFTERM_ABOUT_EACH;
}
else if ("aboutEachPrefix".equals(localName))
{
return RDFTERM_ABOUT_EACH_PREFIX;
}
else if ("bagID".equals(localName))
{
return RDFTERM_BAG_ID;
}
}
return RDFTERM_OTHER;
}
/**
* Check if the child name
*
* @param nodeName
* @return Returns
*/
private static boolean isNumberedArrayItemName(String nodeName)
{
boolean result = "rdf:li".equals(nodeName);
if (nodeName.startsWith("rdf:_"))
{
result = true;
for (int i = 5; i < nodeName.length(); i++)
{
result = result && nodeName.charAt(i) >= '0' && nodeName.charAt(i) <= '9';
}
}
return result;
}
}