📄 xmpmetaparser.java
字号:
// =================================================================================================// ADOBE SYSTEMS INCORPORATED// Copyright 2006-2007 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.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.io.Reader;import java.io.StringReader;import java.io.UnsupportedEncodingException;import javax.xml.parsers.DocumentBuilder;import javax.xml.parsers.DocumentBuilderFactory;import javax.xml.parsers.ParserConfigurationException;import org.w3c.dom.Document;import org.w3c.dom.Node;import org.w3c.dom.NodeList;import org.xml.sax.InputSource;import org.xml.sax.SAXException;import com.adobe.xmp.XMPConst;import com.adobe.xmp.XMPError;import com.adobe.xmp.XMPException;import com.adobe.xmp.XMPMeta;import com.adobe.xmp.options.ParseOptions;/** * This class replaces the <code>ExpatAdapter.cpp</code> and does the * XML-parsing and fixes the prefix. After the parsing several normalisations * are applied to the XMPTree. * * @since 01.02.2006 */public class XMPMetaParser{ /** */ private static final Object XMP_RDF = new Object(); /** the DOM Parser Factory, options are set */ private static DocumentBuilderFactory factory = createDocumentBuilderFactory(); /** * Hidden constructor, initialises the SAX parser handler. */ private XMPMetaParser() { // EMPTY } /** * Parses the input source into an XMP metadata object, including * de-aliasing and normalisation. * * @param input the input can be an <code>InputStream</code>, a <code>String</code> or * a byte buffer containing the XMP packet. * @param options the parse options * @return Returns the resulting XMP metadata object * @throws XMPException Thrown if parsing or normalisation fails. */ public static XMPMeta parse(Object input, ParseOptions options) throws XMPException { ParameterAsserts.assertNotNull(input); options = options != null ? options : new ParseOptions(); Document document = parseXml(input, options); boolean xmpmetaRequired = options.getRequireXMPMeta(); Object[] result = findRootNode(document, xmpmetaRequired); if (result != null && result[1] == XMP_RDF) { XMPMetaImpl xmp = ParseRDF.parse((Node) result[0]); return XMPNormalizer.process(xmp, options); } else { // no appropriate root node found, return empty metadata object return new XMPMetaImpl(); } } /** * Parses the raw XML metadata packet considering the parsing options. * Latin-1/ISO-8859-1 can be accepted when the input is a byte stream * (some old toolkits versions such packets). The stream is * then wrapped in another stream that converts Latin-1 to UTF-8. * <p> * If control characters shall be fixed, a reader is used that fixes the chars to spaces * (if the input is a byte stream is has to be read as character stream). * <p> * Both options reduce the performance of the parser. * * @param input the input can be an <code>InputStream</code>, a <code>String</code> or * a byte buffer containing the XMP packet. * @param options the parsing options * @return Returns the parsed XML document or an exception. * @throws XMPException Thrown if the parsing fails for different reasons */ private static Document parseXml(Object input, ParseOptions options) throws XMPException { if (input instanceof InputStream) { return parseXmlFromInputStream((InputStream) input, options); } else if (input instanceof byte[]) { return parseXmlFromBytebuffer(new ByteBuffer((byte[]) input), options); } else { return parseXmlFromString((String) input, options); } } /** * Parses XML from an {@link InputStream}, * fixing the encoding (Latin-1 to UTF-8) and illegal control character optionally. * * @param stream an <code>InputStream</code> * @param options the parsing options * @return Returns an XML DOM-Document. * @throws XMPException Thrown when the parsing fails. */ private static Document parseXmlFromInputStream(InputStream stream, ParseOptions options) throws XMPException { if (!options.getAcceptLatin1() && !options.getFixControlChars()) { return parseInputSource(new InputSource(stream)); } else { // load stream into bytebuffer try { ByteBuffer buffer = new ByteBuffer(stream); return parseXmlFromBytebuffer(buffer, options); } catch (IOException e) { throw new XMPException("Error reading the XML-file", XMPError.BADSTREAM, e); } } } /** * Parses XML from a byte buffer, * fixing the encoding (Latin-1 to UTF-8) and illegal control character optionally. * * @param buffer a byte buffer containing the XMP packet * @param options the parsing options * @return Returns an XML DOM-Document. * @throws XMPException Thrown when the parsing fails. */ private static Document parseXmlFromBytebuffer(ByteBuffer buffer, ParseOptions options) throws XMPException { InputSource source = new InputSource(buffer.getByteStream()); try { return parseInputSource(source); } catch (XMPException e) { if (e.getErrorCode() == XMPError.BADXML) { if (options.getAcceptLatin1()) { buffer = Latin1Converter.convert(buffer); } if (options.getFixControlChars()) { try { String encoding = buffer.getEncoding(); Reader fixReader = new FixASCIIControlsReader( new InputStreamReader( buffer.getByteStream(), encoding)); return parseInputSource(new InputSource(fixReader)); } catch (UnsupportedEncodingException e1) { // can normally not happen as the encoding is provided by a util function throw new XMPException("Unsupported Encoding", XMPError.INTERNALFAILURE, e); } } source = new InputSource(buffer.getByteStream()); return parseInputSource(source); } else { throw e; } } } /** * Parses XML from a {@link String}, * fixing the illegal control character optionally. * * @param input a <code>String</code> containing the XMP packet * @param options the parsing options * @return Returns an XML DOM-Document. * @throws XMPException Thrown when the parsing fails. */ private static Document parseXmlFromString(String input, ParseOptions options) throws XMPException { InputSource source = new InputSource(new StringReader(input)); try { return parseInputSource(source); } catch (XMPException e) { if (e.getErrorCode() == XMPError.BADXML && options.getFixControlChars()) { source = new InputSource(new FixASCIIControlsReader(new StringReader(input))); return parseInputSource(source); } else { throw e; } } } /** * Runs the XML-Parser. * @param source an <code>InputSource</code> * @return Returns an XML DOM-Document. * @throws XMPException Wraps parsing and I/O-exceptions into an XMPException. */ private static Document parseInputSource(InputSource source) throws XMPException { try { DocumentBuilder builder = factory.newDocumentBuilder(); builder.setErrorHandler(null); return builder.parse(source); } catch (SAXException e) { throw new XMPException("XML parsing failure", XMPError.BADXML, e); } catch (ParserConfigurationException e) { throw new XMPException("XML Parser not correctly configured", XMPError.UNKNOWN, e); } catch (IOException e) { throw new XMPException("Error reading the XML-file", XMPError.BADSTREAM, e); } } /** * Find the XML node that is the root of the XMP data tree. Generally this * will be an outer node, but it could be anywhere if a general XML document * is parsed (e.g. SVG). The XML parser counted all rdf:RDF and * pxmp:XMP_Packet nodes, and kept a pointer to the last one. If there is * more than one possible root use PickBestRoot to choose among them. * <p> * If there is a root node, try to extract the version of the previous XMP * toolkit. * <p> * Pick the first x:xmpmeta among multiple root candidates. If there aren't * any, pick the first bare rdf:RDF if that is allowed. The returned root is * the rdf:RDF child if an x:xmpmeta element was chosen. The search is * breadth first, so a higher level candiate is chosen over a lower level * one that was textually earlier in the serialized XML. * * @param root the root of the xml document * @param xmpmetaRequired flag if the xmpmeta-tag is still required, might be set * initially to <code>true</code>, if the parse option "REQUIRE_XMP_META" is set * @return Returns the rdf:RDF-node or <code>null</code>. */ private static Object[] findRootNode(Node root, boolean xmpmetaRequired) { // Look among this parent's content for x:xapmeta or x:xmpmeta. // The recursion for x:xmpmeta is broader than the strictly defined choice, // but gives us smaller code. NodeList children = root.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { root = children.item(i); if (Node.TEXT_NODE != root.getNodeType() && Node.PROCESSING_INSTRUCTION_NODE != root.getNodeType()) { String rootNS = root.getNamespaceURI(); String rootLocal = root.getLocalName(); if ( ("xmpmeta".equals(rootLocal) || "xapmeta".equals(rootLocal)) && XMPConst.NS_X.equals(rootNS) ) { // by not passing the RequireXMPMeta-option, the rdf-Node will be valid return findRootNode(root, false); } else if (!xmpmetaRequired && "RDF".equals(rootLocal) && XMPConst.NS_RDF.equals(rootNS)) { return new Object[] {root, XMP_RDF}; } else { // continue searching Object[] result = findRootNode(root, xmpmetaRequired); if (result != null) { return result; } else { continue; } } } } // no appropriate node has been found return null; // is extracted here in the C++ Toolkit } /** * @return Creates, configures and returnes the document builder factory for * the Metadata Parser. */ private static DocumentBuilderFactory createDocumentBuilderFactory() { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); factory.setIgnoringComments(true); factory.setExpandEntityReferences(true); return factory; }}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -