/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* $Id: XMPHandler.java 1681108 2015-05-22 13:26:12Z ssteiner $ */

package org.apache.xmlgraphics.xmp;

import java.net.URI;
import java.net.URISyntaxException;

import java.util.Stack;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
import org.xml.sax.helpers.DefaultHandler;

import org.apache.xmlgraphics.util.QName;

Passive XMP parser implemented as a SAX DefaultHandler. After the XML document has been parsed the Metadata object can be retrieved.
/** * Passive XMP parser implemented as a SAX DefaultHandler. After the XML document has been parsed * the Metadata object can be retrieved. */
public class XMPHandler extends DefaultHandler { private Metadata meta; private StringBuffer content = new StringBuffer(); private Stack attributesStack = new Stack(); private Stack nestingInfoStack = new Stack(); private Stack contextStack = new Stack();
Returns:the parsed metadata, available after the parsing.
/** @return the parsed metadata, available after the parsing. */
public Metadata getMetadata() { return this.meta; } private boolean hasComplexContent() { Object obj = this.contextStack.peek(); return !(obj instanceof QName); } private PropertyAccess getCurrentProperties() { Object obj = this.contextStack.peek(); if (obj instanceof PropertyAccess) { return (PropertyAccess)obj; } else { return null; } } private QName getCurrentPropName() { Object obj = this.contextStack.peek(); if (obj instanceof QName) { return (QName)obj; } else { return null; } } private QName popCurrentPropName() throws SAXException { Object obj = this.contextStack.pop(); this.nestingInfoStack.pop(); if (obj instanceof QName) { return (QName)obj; } else { throw new SAXException("Invalid XMP structure. Property name expected"); } } // private XMPComplexValue getCurrentComplexValue() { // Object obj = this.contextStack.peek(); // if (obj instanceof XMPComplexValue) { // return (XMPComplexValue)obj; // } else { // return null; // } // } private XMPStructure getCurrentStructure() { Object obj = this.contextStack.peek(); if (obj instanceof XMPStructure) { return (XMPStructure)obj; } else { return null; } } private XMPArray getCurrentArray(boolean required) throws SAXException { Object obj = this.contextStack.peek(); if (obj instanceof XMPArray) { return (XMPArray)obj; } else { if (required) { throw new SAXException("Invalid XMP structure. Not in array"); } else { return null; } } } // --- Overrides ---
{@inheritDoc}
/** {@inheritDoc} */
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { super.startElement(uri, localName, qName, attributes); content.setLength(0); //Reset text buffer (see characters()) attributesStack.push(new AttributesImpl(attributes)); if (XMPConstants.XMP_NAMESPACE.equals(uri)) { if (!"xmpmeta".equals(localName)) { throw new SAXException("Expected x:xmpmeta element, not " + qName); } if (this.meta != null) { throw new SAXException("Invalid XMP document. Root already received earlier."); } this.meta = new Metadata(); this.contextStack.push(this.meta); this.nestingInfoStack.push("metadata"); } else if (XMPConstants.RDF_NAMESPACE.equals(uri)) { if ("RDF".equals(localName)) { if (this.meta == null) { this.meta = new Metadata(); this.contextStack.push(this.meta); this.nestingInfoStack.push("metadata"); } } else if ("Description".equals(localName)) { String about = attributes.getValue(XMPConstants.RDF_NAMESPACE, "about"); for (int i = 0, c = attributes.getLength(); i < c; i++) { String ns = attributes.getURI(i); if (XMPConstants.RDF_NAMESPACE.equals(ns)) { //ignore } else if (XMPConstants.XMLNS_NAMESPACE.equals(ns)) { //ignore } else if ("".equals(ns)) { //ignore } else { String qn = attributes.getQName(i); String v = attributes.getValue(i); XMPProperty prop = new XMPProperty(new QName(ns, qn), v); getCurrentProperties().setProperty(prop); } } if (this.contextStack.peek().equals(this.meta)) { //rdf:RDF is the parent } else { if (about != null) { throw new SAXException( "Nested rdf:Description elements may not have an about property"); } startStructure(); } } else if ("Seq".equals(localName)) { XMPArray array = new XMPArray(XMPArrayType.SEQ); this.contextStack.push(array); this.nestingInfoStack.push("Seq"); } else if ("Bag".equals(localName)) { XMPArray array = new XMPArray(XMPArrayType.BAG); this.contextStack.push(array); this.nestingInfoStack.push("Bag"); } else if ("Alt".equals(localName)) { XMPArray array = new XMPArray(XMPArrayType.ALT); this.contextStack.push(array); this.nestingInfoStack.push("Alt"); } else if ("li".equals(localName)) { //nop, handle in endElement() } else if ("value".equals(localName)) { QName name = new QName(uri, qName); this.contextStack.push(name); this.nestingInfoStack.push("prop:" + name); } else { throw new SAXException("Unexpected element in the RDF namespace: " + localName); } } else { if (getCurrentPropName() != null) { //Structure (shorthand form) startStructure(); } QName name = new QName(uri, qName); this.contextStack.push(name); this.nestingInfoStack.push("prop:" + name); } } private void startStructure() { //a structured property is the parent XMPStructure struct = new XMPStructure(); this.contextStack.push(struct); this.nestingInfoStack.push("struct"); }
{@inheritDoc}
/** {@inheritDoc} */
public void endElement(String uri, String localName, String qName) throws SAXException { Attributes atts = (Attributes)attributesStack.pop(); if (XMPConstants.XMP_NAMESPACE.equals(uri)) { //nop } else if (XMPConstants.RDF_NAMESPACE.equals(uri) && !"value".equals(localName)) { if ("li".equals(localName)) { XMPStructure struct = getCurrentStructure(); if (struct != null) { //Pop the structure this.contextStack.pop(); this.nestingInfoStack.pop(); getCurrentArray(true).add(struct); } else { String s = content.toString().trim(); if (s.length() > 0) { String lang = atts.getValue(XMPConstants.XML_NS, "lang"); if (lang != null) { getCurrentArray(true).add(s, lang); } else { getCurrentArray(true).add(s); } } else { String res = atts.getValue(XMPConstants.RDF_NAMESPACE, "resource"); if (res != null) { try { URI resource = new URI(res); getCurrentArray(true).add(resource); } catch (URISyntaxException e) { throw new SAXException("rdf:resource value is not a well-formed URI", e); } } } } } else if ("Description".equals(localName)) { /* if (isInStructure()) { //Description is indicating a structure //this.currentProperties = (PropertyAccess)propertiesStack.pop(); this.nestingInfoStack.pop(); }*/ } else { //nop, don't pop stack so the parent element has access } } else { XMPProperty prop; QName name; if (hasComplexContent()) { //Pop content of property Object obj = this.contextStack.pop(); this.nestingInfoStack.pop(); name = popCurrentPropName(); if (obj instanceof XMPComplexValue) { XMPComplexValue complexValue = (XMPComplexValue)obj; prop = new XMPProperty(name, complexValue); } else { throw new UnsupportedOperationException("NYI"); } } else { name = popCurrentPropName(); String s = content.toString().trim(); prop = new XMPProperty(name, s); String lang = atts.getValue(XMPConstants.XML_NS, "lang"); String res = atts.getValue(XMPConstants.RDF_NAMESPACE, "resource"); if (lang != null) { prop.setXMLLang(lang); } if (res != null) { try { URI resource = new URI(res); prop.setValue(resource); } catch (URISyntaxException e) { throw new SAXException("rdf:resource value is not a well-formed URI", e); } } } if (prop.getName() == null) { throw new IllegalStateException("No content in XMP property"); } assert getCurrentProperties() != null : "no current property"; getCurrentProperties().setProperty(prop); } content.setLength(0); //Reset text buffer (see characters()) super.endElement(uri, localName, qName); } /* private boolean isInStructure() { return !propertiesStack.isEmpty(); } */
{@inheritDoc}
/** {@inheritDoc} */
public void characters(char[] ch, int start, int length) throws SAXException { content.append(ch, start, length); } }