Package com.adobe.xmp.impl

Source Code of com.adobe.xmp.impl.ParseRDF

// =================================================================================================
// 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 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.xmp.XMPConst;
import com.adobe.xmp.XMPError;
import com.adobe.xmp.XMPException;
import com.adobe.xmp.XMPMetaFactory;
import com.adobe.xmp.XMPSchemaRegistry;
import com.adobe.xmp.options.PropertyOptions;


/**
* Parser for "normal" XML serialisation of RDF. 
*
* @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
   * @return Returns an XMP metadata object (not normalized)
   * @throws XMPException Occurs if the parsing fails for any reason.
   */
  static XMPMetaImpl parse(Node xmlRoot) throws XMPException
  {
    XMPMetaImpl xmp = new XMPMetaImpl();
    rdf_RDF(xmp, xmlRoot);
    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
   * @throws XMPException thown on parsing errors
   */
  static void rdf_RDF(XMPMetaImpl xmp, Node rdfRdfNode) throws XMPException
  {
    if (rdfRdfNode.hasAttributes())
    {
      rdf_NodeElementList (xmp, xmp.getRoot(), rdfRdfNode);
   
    else
   
      throw new XMPException("Invalid attributes of rdf:RDF element", BADRDF);
    }
  }
 
 
  /**
   * 7.2.10 nodeElementList<br>
   * 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
   * @throws XMPException thown on parsing errors
   */
  private static void rdf_NodeElementList(XMPMetaImpl xmp, XMPNode xmpParent, Node rdfRdfNode)
    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);
     
    }
  }


  /**
    * 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
   * @throws XMPException thown on parsing errors
   */
  private static void rdf_NodeElement(XMPMetaImpl xmp, XMPNode xmpParent, Node xmlNode,
      boolean isTopLevel) 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);
      rdf_PropertyElementList (xmp, xmpParent, xmlNode, isTopLevel);
    }
   
  }


  /**
   *
   * 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
   * @throws XMPException thown on parsing errors
   */
  private static void rdf_NodeElementAttrs(XMPMetaImpl xmp, XMPNode xmpParent, Node xmlNode,
      boolean isTopLevel) 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
   * @throws XMPException thown on parsing errors
   */
  private static void rdf_PropertyElementList(XMPMetaImpl xmp, XMPNode xmpParent, Node xmlParent,
      boolean isTopLevel) 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
     
        rdf_PropertyElement(xmp, xmpParent, currChild, isTopLevel);
     
    }
  }


  /**
   * 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
   * @throws XMPException thown on parsing errors
   */
  private static void rdf_PropertyElement(XMPMetaImpl xmp, XMPNode xmpParent, Node xmlNode,
      boolean isTopLevel) 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);
          }
          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);
            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
   * @throws XMPException thown on parsing errors
   */
  private static void rdf_ResourcePropertyElement(XMPMetaImpl xmp, XMPNode xmpParent,
      Node xmlNode, boolean isTopLevel) 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);
            }
          }
 
          rdf_NodeElement (xmp, newCompound, currChild, false);
         
          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);
      }
    }
    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
   * @throws XMPException thown on parsing errors
   */
  private static void rdf_ParseTypeResourcePropertyElement(XMPMetaImpl xmp, XMPNode xmpParent,
      Node xmlNode, boolean isTopLevel) 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);

    if (newStruct.getHasValueChild())
    {
      fixupQualifiedNode (newStruct);
    }
  }

 
  /**
   * 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()
   *
   *  <ns:Prop1/>  <!-- a simple property with an empty value -->
   <ns:Prop2 rdf:resource="http: *www.adobe.com/"/> <!-- a URI value -->
   <ns:Prop3 rdf:value="..." ns:Qual="..."/> <!-- a simple qualified property -->
   <ns:Prop4 ns:Field1="..." ns:Field2="..."/> <!-- a struct with simple fields -->
   *
   * 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:
   * <ol>
   *    <li> If there is an rdf:value attribute then this is a simple property
   *         with a text value.
   *    All other attributes are qualifiers.
   *    <li> If there is an rdf:resource attribute then this is a simple property
   *      with a URI value.
   *    All other attributes are qualifiers.
   *    <li> If there are no attributes other than xml:lang, rdf:ID, or rdf:nodeID
   *        then this is a simple
   *    property with an empty value.
   *    <li> Otherwise this is a struct, the attributes other than xml:lang, rdf:ID,
   *        or rdf:nodeID are fields.
   * </ol>
   *
   * @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
        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.
      // 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  = "rdf:li".equals(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);
    }
   
    if (isArrayItem)
    {
      if (!xmpParent.getOptions().isArray())
      {
        throw new XMPException("Misplaced rdf:li element", BADRDF);
     
      newChild.setName(ARRAY_ITEM_NAME);
    }
   
    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 <b>default prefix</b>
   * @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(...).
    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<br>
   * 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<br>
   * 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;
  }
}
TOP

Related Classes of com.adobe.xmp.impl.ParseRDF

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.