All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.adobe.xfa.Document Maven / Gradle / Ivy

There is a newer version: 2024.11.18598.20241113T125352Z-241000
Show newest version
/*
 * ADOBE CONFIDENTIAL
 *
 * Copyright 2005 Adobe Systems Incorporated All Rights Reserved.
 *
 * NOTICE: All information contained herein is, and remains the property of
 * Adobe Systems Incorporated and its suppliers, if any. The intellectual and
 * technical concepts contained herein are proprietary to Adobe Systems
 * Incorporated and its suppliers and may be covered by U.S. and Foreign
 * Patents, patents in process, and are protected by trade secret or copyright
 * law. Dissemination of this information or reproduction of this material
 * is strictly forbidden unless prior written permission is obtained from
 * Adobe Systems Incorporated.
 */
package com.adobe.xfa;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;

import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

import com.adobe.xfa.Model.DualDomModel;
import com.adobe.xfa.protocol.ProtocolUtils;
import com.adobe.xfa.ut.ExFull;
import com.adobe.xfa.ut.FindBugsSuppress;
import com.adobe.xfa.ut.IntegerHolder;
import com.adobe.xfa.ut.Key;
import com.adobe.xfa.ut.MsgFormatPos;
import com.adobe.xfa.ut.ResId;


/**
 * A container class to hold the XML document node of the DOM.
 * 

* Conceptually, a DOM document node is the root of the document tree, * and provides the primary access to the document's data. *

Since elements, text nodes, comments, processing instructions, etc. * cannot exist outside the context of a document, the * Document interface also contains the factory methods needed * to create these objects. Each Node objects created have an * ownerDocument attribute which associates them with the * Document in which they were created. */ public class Document extends Element { private static class QName { final String maNameSpaceURI; final String maLocalName; QName(String aNameSpaceURI, String aLocalName) { maNameSpaceURI = aNameSpaceURI; maLocalName = aLocalName; } } private static class QNameCache { private final static List mCache = new ArrayList(); public final QName getQName(String aNameSpaceURI, String aLocalName) { QName qName = getExistingQName(aNameSpaceURI, aLocalName); // That QName doesn't exist yet, so create it if (qName == null) { qName = new QName(aNameSpaceURI, aLocalName); mCache.add(qName); } return qName; } @FindBugsSuppress(code="ES") public final QName getExistingQName(String aNameSpaceURI, String aLocalName) { for (int i = 0; i < mCache.size(); i++) { QName qName = mCache.get(i); if (aNameSpaceURI == qName.maNameSpaceURI && aLocalName == qName.maLocalName) return qName; } return null; } } private static class KeySpec { final List mNodeAddresses = new ArrayList(); final Element mNamespaceContextNode; KeySpec(Element namespaceContextNode) { mNamespaceContextNode = namespaceContextNode; } } private final AppModel mAppModel; private SaveNameSpaceChecker mChecker; private URL mParseFile; private String msParseFileName; // Define some starting context in case we're adding to an existing dom private Model mStartingModel; private Element mStartingParent; private boolean mbIgnoreAggregating; private boolean mbWillDirty = true; private final Document mRealDocument; // A default document represents the case in C++ where a Document hasn't // even been created yet. In XFA4J, Documents are created earlier, so we // need to know the equivalent of when they would be instantiated in C++. private boolean mbIsDefaultDocument; private boolean mbAutoUniquifyIDs = true; // Automatically uniquify IDs on import (default TRUE) private boolean mbUniquifyIDsOnParse; // Automatically uniquify IDs on XML parse (default FALSE) // // Support for XML-style IDs and XFA-style IDs // // XML-style ID attributes are declared on an element basis. Each element can have at most a single // ID attribute defined. XML also includes global IDs which can be on any element. Examples include // xml:id and wsu:Id. // // XFA-style ID attributes are declared on a namespace basis. Each namespace can have at most a // single ID attribute defined. // JavaPort: QName construction is frequent enough to be expensive in Java, so use // a cache of unique ones (we expect very few). Using this cache also means that // we "atomize" QName instances, so QNames can be used as the key in an IdentityHashMap. private final QNameCache mQNameCache = new QNameCache(); // This first set of maps allow one to lookup what the relevant ID attribute names and/or primary // key node address lists are for a given context (whether namespace, element, or whatever): private final IdentityHashMap mElementToIdAttrNameMap = new IdentityHashMap(); // Map of ElementName -> IdAttrName private final IdentityHashMap mNameSpaceToIdAttrNameMap = new IdentityHashMap(); // Map of NameSpaceURI -> IdAttrName private final List mGlobalXMLIdAttrList = new ArrayList(); // List of global XML ID attr QNames private final IdentityHashMap mElementToPKeySpecMap = new IdentityHashMap(); // Map of ElementName -> PKey node address list // These are the actual ID -> element lookup tables: //typedef jfSortedArray IdIndex; // Map of IdValue -> Element private final Map mXMLIdIndex = new HashMap(); // IdIndex for XML IDs private final IdentityHashMap> mXFAIdIndexes = new IdentityHashMap>();// Map of namespace -> IdIndex for XFA IDs // And the lookup tables for primary keys: //typedef jfSortedArray KeyIndex; // Map of KeyValue -> Element private final Map mPKeyIndex = new HashMap(); // KeyIndex for Primary Keys /** * Cache for looking up simple namespace prefixes given a namespace URI. * The C++ implementation uses a static map, but an instance variable is used here * so that the implementation is thread safe. */ private final IdentityHashMap mSimpleNameSpaceMap = new IdentityHashMap(); /** * XFA always encodes Documents as UTF-8 when serializing. * @exclude from published api. */ public static final String Encoding = STRS.UTF8STR; /** * @exclude from published api. * These byte sequences used to be Strings, but since we always use them for writing * to XML files, it's less expensive to store the byte versions rather than * convert each time we write them. No need to worry about different encodings * since the strings are all safely inside 7bit ASCII. */ public static final byte[] MarkupAttrMiddle = { (byte) 0x3D, (byte) 0x22 // "=\"" }; /** * @exclude from published api. */ public static final byte[] MarkupAttrMiddleQuote = { (byte) 0x3D, (byte) 0x27 // "='" }; static final byte[] MarkupColon = { (byte) 0x3A // ":" }; /** * @exclude from published api. */ public static final byte[] MarkupDQuoteString = { (byte) 0x22 // "\"" }; /** * @exclude from published api. */ public static final byte[] MarkupQuoteString = { (byte) 0x27 // "'" }; /** * @exclude from published api. */ public static final byte[] MarkupCDATAStart = { (byte) 0x3C, (byte) 0x21, (byte) 0x5B, (byte) 0x43, (byte) 0x44, (byte) 0x41, (byte) 0x54, (byte) 0x41, (byte) 0x5B // "" }; static final byte[] MarkupCommentStart = { (byte) 0x3C, (byte) 0x21, (byte) 0x2D, (byte) 0x2D // "" }; /** * @exclude from published api. */ public static final byte[] MarkupCloseTag = { (byte) 0x3C, (byte) 0x2F // "" }; /** * @exclude from published api. */ public static final byte[] MarkupEndParen2 = { (byte) 0x5D, (byte) 0x3E // "]>" }; /** * @exclude from published api. */ public static final byte[] MarkupEndTag = { (byte) 0x3E // ">" }; static final byte[] MarkupEndTag2 = { (byte) 0x2F, (byte) 0x3E // "/>" }; /** * @exclude from published api. */ public static final byte[] MarkupEntity = { (byte) 0x3C, (byte) 0x21, (byte) 0x45, (byte) 0x4E, (byte) 0x54, (byte) 0x49, (byte) 0x54, (byte) 0x59 // "" }; static final byte[] MarkupPrefix = { (byte) 0x3C, (byte) 0x3F, (byte) 0x78, (byte) 0x6D, (byte) 0x6C, (byte) 0x20, (byte) 0x76, (byte) 0x65, (byte) 0x72, (byte) 0x73, (byte) 0x69, (byte) 0x6F, (byte) 0x6E, (byte) 0x3D, (byte) 0x22, (byte) 0x31, (byte) 0x2E, (byte) 0x30, (byte) 0x22, (byte) 0x20, (byte) 0x65, (byte) 0x6E, (byte) 0x63, (byte) 0x6F, (byte) 0x64, (byte) 0x69, (byte) 0x6E, (byte) 0x67, (byte) 0x3D, (byte) 0x22 // "AppModel * @param aliasExistingAppModelDocument if true, the new document * is an alias for the document that contains the app model; if false * a document is created that is separate from any existing document. * @exclude from published api. */ private Document(AppModel appModel, boolean aliasExistingAppModelDocument) { super(null, null, "", ""); if (appModel == null) throw new NullPointerException("appModel"); mAppModel = appModel; // Only set the AppModel's Document to this if this is the first time // that a document has been constructed to contain this AppModel. // This idiom is something of a porting mistake, since it doesn't make // clear which comes first: the Document or the AppModel. This code is // careful to support these extra documents for compatibility with // existing XFA4J. In particular, see uses of setWillDirty/getWillDirty // that delegate to mRealDocument. if (mAppModel.getDocument() == null) { mRealDocument = this; mAppModel.setDocument(this); } else { if (aliasExistingAppModelDocument) { mRealDocument = mAppModel.getDocument(); } else { mRealDocument = this; } } setDocument(this); setModel(mAppModel); // All Documents are default until loaded or AppModel.newDOM mbIsDefaultDocument = true; declareGlobalXMLId(STRS.XMLNSURI, STRS.LOWERCASEID); } /** * Instantiates a document node with the given app model. * @param appModel an AppModel */ public Document(AppModel appModel) { this(appModel, true); } /** * Creates a new Document that is separate from any default Document * associated with the app model. * @param appModel an AppModel * @return a new Document * @exclude from published api. */ public static Document createDocument(AppModel appModel) { return new Document(appModel, false); } /** * @exclude from published api. */ public void appendChild(Node child) { if (child instanceof Element && getDocumentElement() != null) { throw new ExFull(ResId.HierarchyRequestException); } super.appendChild(child); } /** @exclude */ public final boolean autoUniquifyIDs() { return mbAutoUniquifyIDs; } /** @exclude */ public final void autoUniquifyIDs(boolean bAutoUniquifyIDs) { mbAutoUniquifyIDs = bAutoUniquifyIDs; } /** @exclude */ public final boolean uniquifyIDsOnParse() { return mbUniquifyIDsOnParse; } /** @exclude */ public final void uniquifyIDsOnParse(boolean bUniquifyIDsOnParse) { mbUniquifyIDsOnParse = bUniquifyIDsOnParse; } /** * Clears all elements from the ID look up table. * @exclude from published api. */ public final void clearIdMap() { mXMLIdIndex.clear(); for (Map index : mXFAIdIndexes.values()) index.clear(); mPKeyIndex.clear(); } private String stripVersionFromNameSpace(String aNameSpaceURI) { String aSimpleNameSpace = mSimpleNameSpaceMap.get(aNameSpaceURI); if (aSimpleNameSpace == null) { String sNameSpaceURI = aNameSpaceURI == null ? "" : aNameSpaceURI; int len = sNameSpaceURI.length(); if (sNameSpaceURI.length() >= 4 && sNameSpaceURI.charAt(len - 1) == '/' && Character.isDigit(sNameSpaceURI.charAt(len - 2)) && sNameSpaceURI.charAt(len - 3) == '.' && Character.isDigit(sNameSpaceURI.charAt(len - 4))) { aSimpleNameSpace = sNameSpaceURI.substring(0, len - 4).intern(); } else { // include even identity maps as they keep us from having to do // string manipulations aSimpleNameSpace = aNameSpaceURI; } mSimpleNameSpaceMap.put(aNameSpaceURI, aSimpleNameSpace); } return aSimpleNameSpace; } // ============================================================================================ // XFA & XML ID HANDLING. See: https://zerowing.corp.adobe.com/display/xtg/NewIDArchitecture // /** * @exclude from published api. */ public final Element getElementByXMLId(String idValue) { return mXMLIdIndex.get(idValue); } /** * @exclude from published api. */ @FindBugsSuppress(code="ES") public final Element getElementByXFAId(String aNameSpaceURI, String aIdValue) { if (aNameSpaceURI == "") { // For whatever reason, the caller didn't know which namespace to look in. // Search them all. for (Map index : mXFAIdIndexes.values()) { Element element = index.get(aIdValue); if (element != null) return element; } return null; } else { String aSimpleNameSpaceURI = stripVersionFromNameSpace(aNameSpaceURI); Map index = mXFAIdIndexes.get(aSimpleNameSpaceURI); return index != null ? index.get(aIdValue) : null; } } /** * Returns an element specified by its primary key. * * @param key The primary key value to search on. Often constructed with the use of * Element.constructKey. * @exclude from published api. */ @FindBugsSuppress(code="ES") public final Element getElementByPKey(Key key) { return mPKeyIndex.get(key); } // Helper to determine if a given node is currently in the document // (as opposed to in a detached subtree). private static boolean referencedByHostDocument(Element element) { while (true) { Element parent = Element.getXMLParent(element); if (parent == null) break; else element = parent; } return element instanceof Document || element.getIsDataWindowRoot(); } private static void indexHelper(Map index, Attribute attr, Element element) { assert index != null; Element existing = index.get(attr.getAttrValue()); if (existing == null) index.put(attr.getAttrValue (), element); else if (existing == element) { // already indexed; nothing to do } else { // Collision! Uniquify the ID value String sOriginalValue = attr.getAttrValue(); int i = 1; while (true) { String sTest = sOriginalValue + "_copy" + i; if (!index.containsKey(sTest)) { // Skip ID re-indexing Attribute newValue = attr.newAttribute(attr.getNS(), attr.getLocalName(), attr.getQName(), sTest, false); element.updateAttributeInternal(newValue); index.put(sTest, element); break; } else i++; } } } /** @exclude from published api */ @FindBugsSuppress(code="ES") public final void declareXMLId(String aElementNameSpaceURI, String aElementName, String aIdAttributeName) { QName elementQName = mQNameCache.getQName(aElementNameSpaceURI, aElementName); String aExisting = mElementToIdAttrNameMap.get(elementQName); if (aExisting == null) mElementToIdAttrNameMap.put(elementQName, aIdAttributeName); else assert aExisting == aIdAttributeName; // Not allowed to redefine an ID decl! } /** @exclude from published api */ @FindBugsSuppress(code="ES") public final void declareGlobalXMLId (String aIdAttrNameSpaceURI, String aIdAttrName) { for (int i = 0; i < mGlobalXMLIdAttrList.size(); i++) { QName test = mGlobalXMLIdAttrList.get(i); if (test.maNameSpaceURI == aIdAttrNameSpaceURI && test.maLocalName == aIdAttrName) return; } // still here? must not have found an existing decl QName newQName = mQNameCache.getQName(aIdAttrNameSpaceURI, aIdAttrName); mGlobalXMLIdAttrList.add(newQName); } /** @exclude from published api */ @FindBugsSuppress(code="ES") public final void declareXFAId(String aNameSpaceURI, String aIdAttributeName) { String aSimpleNameSpaceURI = stripVersionFromNameSpace(aNameSpaceURI); String aExisting = mNameSpaceToIdAttrNameMap.get (aSimpleNameSpaceURI); if (aExisting == null) { mNameSpaceToIdAttrNameMap.put(aSimpleNameSpaceURI, aIdAttributeName); // Add a new IdIndex for IDs in this namespace: assert mXFAIdIndexes.get(aSimpleNameSpaceURI) == null; mXFAIdIndexes.put(aSimpleNameSpaceURI, new HashMap()); } else assert aExisting == aIdAttributeName; // Not allowed to redefine an ID decl! } /** * Defines a primary key on an element. * * @param aElementNameSpaceURI The element's namespace URI * @param aElementName The element's local name * @param aPKeyNodeAddressList A comma separated list of XPaths which identify the nodes * to be used as primary key for this element * @exclude from published api */ public final void declarePKey(String aElementNameSpaceURI, String aElementName, String aPKeyNodeAddressList, Element namespaceContextNode) { QName elementQName = mQNameCache.getQName(aElementNameSpaceURI, aElementName); KeySpec keySpec = mElementToPKeySpecMap.get(elementQName); if (keySpec == null) { // store the context node for resolving namespace prefixes found // in the node addresses: keySpec = new KeySpec(namespaceContextNode); // Parse and atomize the node address list at declaration time: StringTokenizer tokenizer = new StringTokenizer(aPKeyNodeAddressList, ","); while (tokenizer.hasMoreTokens()) { keySpec.mNodeAddresses.add(tokenizer.nextToken().trim()); } mElementToPKeySpecMap.put (elementQName, keySpec); } } // Architecture document: https://zerowing.corp.adobe.com/display/xtg/NewIDArchitecture @FindBugsSuppress(code="ES") public final boolean isId(String aElementNameSpaceURI, String aElementName, String aAttrNameSpaceURI, String aAttrLocalName) { // // XFA IDs are declared by model. Query the ID attribute name by model namespace, // and then see if the attribute name matches: String aSimpleNameSpaceURI = stripVersionFromNameSpace(aElementNameSpaceURI); if (mNameSpaceToIdAttrNameMap.get(aSimpleNameSpaceURI) == aAttrLocalName) return true; if (mElementToIdAttrNameMap.size() > 0) { // // XML IDs are declared on a particular element. Query the ID attribute name by // element QName, and then see if the attribute name matches: QName elementQName = mQNameCache.getExistingQName(aElementNameSpaceURI, aElementName); if (elementQName != null && mElementToIdAttrNameMap.get (elementQName) == aAttrLocalName) return true; // // DTD fragments don't handle namespaces, so XML IDs might be declared on a simple // local name as well. Re-run the query from above without a namespace URI: elementQName = mQNameCache.getExistingQName("", aElementName); if (elementQName != null && mElementToIdAttrNameMap.get(elementQName) == aAttrLocalName) return true; } // // Global XML IDs are declared by attribute QName, and are valid on any element. So // just go through the list of them (it shouldn't contain much more than xml:id and // perhaps wsu:id) and see if any of them match: for (int i = 0; i < mGlobalXMLIdAttrList.size(); i++) { QName qName = mGlobalXMLIdAttrList.get(i); if (qName.maNameSpaceURI == aAttrNameSpaceURI && qName.maLocalName == aAttrLocalName) return true; } // Must not be an ID return false; } /** * @exclude from published api. */ public final Node clone(Element parent) { // Documents are not not to be cloned MsgFormatPos oMessage = new MsgFormatPos(ResId.InvalidMethodException, getClassAtom()); oMessage.format("clone"); throw new ExFull(oMessage); } /** * @exclude from published api. */ public final Node cloneNode(boolean bDeep) { Document newDoc = new Document(mAppModel, false); if (bDeep) { for (Node child = getFirstXMLChild(); child != null; child = child.getNextXMLSibling()) { newDoc.appendChild(newDoc.importNode(child, true, null, null)); } } return newDoc; } /** * @exclude from published api. */ public final Element createElementNS(String sNameSpaceURI, String sQualifiedName, Element parent /* =null */) { Node.doQualifyNodeName(sQualifiedName, false); Element newElement = new Element(parent, null, sNameSpaceURI, sQualifiedName); newElement.setDocument(this); if (parent != null) parent.appendChild(newElement, true); // // Check for a valid namespace. // If not valid, delete the node. // checkValidNameSpace(newElement, sQualifiedName); return newElement; } /* * Determine the encoding of the given input stream of an XML document. * In reality, this function only looks for the special case of invalid charset * names in the XML Decl that XFA provides support for. For other cases, * this returns null and the XML parser is left to deal with detecting * the encoding of the stream. * @param is an input stream. * @return the name of the encoding or null if none found. */ private static String findEncoding(InputStream is) { assert is.markSupported() || is instanceof MarkableInputStream; try { // // Read as little of the source as necessary to avoid triggering // character conversion issues. 64 bytes is enough to decode any // UTF-8 encoding XMLDecl we are ever likely to see. final byte[] buf = new byte[DEFAULT_XMLDECL_BUFFER_SIZE]; is.mark(buf.length); final int nBuf = is.read(buf, 0, buf.length); is.reset(); int offset = 0; if (nBuf < 3) return null; if (buf[0] == (byte)0xEF && buf[1] == (byte)0xBB && buf[2] == (byte)0xBF) { // UTF-8 BOM offset = 3; } else if (buf[0] == '<' && buf[1] == '?') { // "' for this XMLDecl. // This is a safe place to break the input (e.g., no splitting surrogate pairs) // XMLDecl ::= '' int end = -1; for (int i = offset; i < nBuf; i++) { if (buf[i] == '>') { end = i; break; } } if (end == -1) return null; final String sXMLDecl = new String(buf, offset, end, "UTF-8"); // Extract the charset name from // EncodingDecl ::= S 'encoding' Eq ('"' EncName '"' | "'" EncName "'" ) // Eq ::= S? '=' S? offset = sXMLDecl.indexOf("encoding"); if (offset == -1) return null; else offset += "encoding".length(); // skip whitespace for ( ; offset < sXMLDecl.length() && isXMLSpace(sXMLDecl.charAt(offset)); offset++) ; if (offset == sXMLDecl.length()) return null; // accept = if (sXMLDecl.charAt(offset) != '=') return null; else offset++; // skip whitespace for ( ; offset < sXMLDecl.length() && isXMLSpace(sXMLDecl.charAt(offset)); offset++) ; if (offset == sXMLDecl.length()) return null; // accept ' or " char quote = sXMLDecl.charAt(offset); if (quote != '\'' && quote != '"') return null; else offset++; end = sXMLDecl.indexOf(quote, offset); String sCharset = sXMLDecl.substring(offset, end); if (sCharset.equalsIgnoreCase("SHIFT-JIS")) return "SHIFT_JIS"; } catch (IOException e) { throw new ExFull(e); } return null; } private static boolean isXMLSpace(char c) { return c == 0x20 || c == 0x9 || c == 0xD || c == 0xA; } /** * @exclude from published api. */ public Node getFirstXMLChild() { return mRealDocument.mFirstXMLChild; } /** * Gets this document's app model. * @return the app model. */ public AppModel getAppModel() { return mAppModel; } /** * Gets this document's first element. * @return the first element. * * @exclude from published api. */ public final Element getDocumentElement() { for (Node child = getFirstXMLChild(); child != null; child = child.getNextXMLSibling()) { if (child instanceof Element) return (Element)child; } return null; } /** * @exclude from published api. */ public Generator getGenerator() { // TODO JavaPort implement! return null; } /** * Gets this document's name. * @return the document name which is the constant value "#document". */ public String getName() { return STRS.DOCUMENTNAME; } /** * Gets an absolute URL that represents the source of the document, * or null if the source is unknown. * @exclude from published api. */ public URL getParseFile() { return mRealDocument.mParseFile; } /** * Gets the parse file name for reporting purposes. * For backwards compatibility, URL prefix is removed * @exclude from published api. */ public String getParseFileName() { return mRealDocument.msParseFileName; } /** * Indicates whether changes should cause the namespace dirty flag to * be set on changes within this document. * * @return true if changes should cause the namespace dirty flag to be set. * @exclude from published api. */ public boolean getWillDirty() { return mRealDocument.mbWillDirty; } /** * @exclude from published api. */ public final SaveNameSpaceChecker getSaveChecker() { return mRealDocument.mChecker; } /** @exclude */ public final boolean isDefaultDocument() { return mRealDocument.mbIsDefaultDocument; } /** @exclude */ public final void isDefaultDocument(boolean bIsDefault) { mRealDocument.mbIsDefaultDocument = bIsDefault; } /** * Imports a copy of a Node from another document into this document. * The source node, which may belong to a different document, is * copied, with the copy belonging to this document. The copy * is not placed into the document (the parent node is null). * * @param source the node (subtree) to be copied * @param bDeep if true, recursively import the subtree under the specified * node; if false, import only the node itself (and its attributes, if * if is an Element * @return the newly created copy of the source node, belonging to this document */ public final Node importNode(Node source, boolean bDeep) { return importNode(source, bDeep, null, null); } /** * @exclude from published api. */ @FindBugsSuppress(code="REC") private final Node importNode(Node source, boolean bDeep, Element parent /* = null */, Node previousSibling) { if (source == null) return null; // Since we are concerned with Document nodes here, jump over to the XML DOM if we // encounter a DualDomNode. if (source instanceof DualDomNode) source = ((DualDomNode)source).getXmlPeer(); if (source instanceof Document) { // Document can't be child of Document throw new ExFull(ResId.UNSUPPORTED_OPERATION, "Document#importNode - document child"); } else if (source instanceof Element) { Element elementSource = (Element)source; Element newElement = new Element( parent, previousSibling, elementSource.getNS(), elementSource.getLocalName(), elementSource.getXMLName(), null, XFA.INVALID_ELEMENT, ""); newElement.setDocument(this); newElement.setModel(mAppModel); final int n = elementSource.getNumAttrs(); for (int i = 0; i < n; i++) { Attribute attr = elementSource.getAttr(i); newElement.setAttribute(attr.getNS(), attr.getQName(), attr.getLocalName(), attr.getAttrValue(), false); } // Jump back to the XFA tree if this is a ModelPeer, but not a DualDomModel if (source instanceof ModelPeer && !(source instanceof DualDomModel)) source = ((ModelPeer)source).getXfaPeer(); // replicate and attach children, if necessary if (bDeep) { Node prevSibling = null; for (Node child = source.getFirstXMLChild(); child != null; child = child.getNextXMLSibling()) { prevSibling = importNode(child, true, newElement, prevSibling); } } return newElement; } // TODO: Implement SVGTextData, or remove it else if (source instanceof TextNode) { return new TextNode(parent, previousSibling, ((TextNode)source).getText()); } else if (source instanceof Chars) { return new Chars(parent, previousSibling, ((Chars)source).getText()); } else if (source instanceof ProcessingInstruction) { ProcessingInstruction pi = (ProcessingInstruction)source; return new ProcessingInstruction(parent, previousSibling, pi.getName(), pi.getData()); } else if (source instanceof Comment) { return new Comment(parent, previousSibling, ((Comment)source).getData()); } else { throw new ExFull(ResId.UNSUPPORTED_OPERATION, "Document.importNode - unknown type"); } } /** * Determines if this document has changed since being last loaded. * * @return the document state. */ public final boolean hasChanged() { return mRealDocument.isDirty(); } // Re-index a node based on all its ID attributes // Architecture document: https://zerowing.corp.adobe.com/display/xtg/NewIDArchitecture final void indexNode (Element element, boolean bInDocumentCheckCompleted /* = false */) { if (element instanceof DualDomNode) { Node node = ((DualDomNode)element).getXmlPeer(); if (node instanceof TextNode) return; element = (Element)node; } if (! bInDocumentCheckCompleted) { // Check to see if the source node is in the document. If not, then we'll wait // and index it when it gets added: if (! referencedByHostDocument(element)) return; } Attribute attr; // // XFA IDs // attr = null; // // Query the XFA ID attribute name for this model (based on its versionless namespace): String aElementNameSpaceURI = stripVersionFromNameSpace(element.getNS()); String aXFAIdAttrName = mNameSpaceToIdAttrNameMap.get(aElementNameSpaceURI); // // See if said attribute is defined, and if so, index it: if (aXFAIdAttrName != null) { int index = element.findAttr("", aXFAIdAttrName); if (index != -1) attr = element.getAttr(index); } if (attr != null) indexHelper(mXFAIdIndexes.get(aElementNameSpaceURI), attr, element); // // XML IDs // attr = null; // // Query the XML ID attribute name for this element: String aXMLIdAttrName = null; QName elementQName = mQNameCache.getExistingQName(aElementNameSpaceURI, element.getLocalName()); if (elementQName != null) aXMLIdAttrName = mElementToIdAttrNameMap.get(elementQName); // // If we didn't get an answer, try again with no namespace (as DTD fragments // declare ID attributes on elements with no namespace support): if (aXMLIdAttrName == null) { QName elementLocalName = mQNameCache.getExistingQName("", element.getLocalName()); if (elementLocalName != null) aXMLIdAttrName = mElementToIdAttrNameMap.get(elementLocalName); } // See if said attribute is defined, and if so, index it: if (aXMLIdAttrName != null) { int index = element.findAttr("", aXMLIdAttrName); if (index != -1) attr = element.getAttr(index); } if (attr != null) indexHelper (mXMLIdIndex, attr, element); // // GLOBAL XML IDs (xml:id, wsu:Id, etc.) // // These are in a list and are valid on any element in any namespace, so just // go through the list: for (int i = 0; i < mGlobalXMLIdAttrList.size (); i++) { QName xmlIdAttrQName = mGlobalXMLIdAttrList.get(i); int index = element.findAttr(xmlIdAttrQName.maNameSpaceURI, xmlIdAttrQName.maLocalName); if (index != -1) attr = element.getAttr(index); if (attr != null) indexHelper (mXMLIdIndex, attr, element); } // // PRIMARY KEYS // // See if there is a primary key declared for this element: KeySpec primaryKeySpec = mElementToPKeySpecMap.get(elementQName); if (primaryKeySpec != null) { Key primaryKey = element.constructKey(primaryKeySpec.mNodeAddresses, primaryKeySpec.mNamespaceContextNode); if (primaryKey.numValues() > 0) { Element existing = mPKeyIndex.get(primaryKey); if (existing == null) mPKeyIndex.put(primaryKey, element); else assert existing == element; } } } /** @exclude from published api */ public final void indexSubtree(Element source, boolean bInDocumentCheckCompleted /* = false */) { if (! bInDocumentCheckCompleted) { // Check to see if the source node is in the document. If not, then we'll wait // and index it when it gets added: if (! referencedByHostDocument(source)) return; } indexNode(source, true); // Recurse through our children. Note that we don't use a wrapper // with poChild, because that would cause a reference to this document to // be added (causing a circular reference). The upshot is that poChild // must let this document know if/when it is deleted, so that we can remove // it from our cache. for (Node child = source.getFirstXMLChild (); child != null; child = child.getNextXMLSibling()) { if (child instanceof Element) indexSubtree((Element)child, true); } } // Architecture document: https://zerowing.corp.adobe.com/display/xtg/NewIDArchitecture final void deindexNode(Element element, boolean bInDocumentCheckCompleted /* = false */) { if (element instanceof DualDomNode) { Node node = ((DualDomNode)element).getXmlPeer(); if (node instanceof TextNode) return; element = (Element)node; } if (! bInDocumentCheckCompleted) { // Check to see if the source node is in the document. If not, then it won't // have been indexed and there's nothing to do to deindex it: if (! referencedByHostDocument(element)) return; } // if (documentInDestructor ()) // Performance optimization // return; Attribute attr; // // XFA IDs // attr = null; // // Query the XFA ID attribute name for this model (based on its versionless namespace): String aElementNameSpaceURI = stripVersionFromNameSpace(element.getNSInternal()); String aXFAIdAttrName = mNameSpaceToIdAttrNameMap.get(aElementNameSpaceURI); // // See if said attribute is defined, and if so, deindex it: if (aXFAIdAttrName != null) { int index = element.findAttr("", aXFAIdAttrName); if (index != -1) attr = element.getAttr(index); } if (attr != null) mXFAIdIndexes.get(aElementNameSpaceURI).remove(attr.getAttrValue()); // // XML IDs // attr = null; // // Query the XML ID attribute name for this element: String aXMLIdAttrName = null; QName elementQName = mQNameCache.getExistingQName(aElementNameSpaceURI, element.getLocalName ()); if (elementQName != null) aXMLIdAttrName = mElementToIdAttrNameMap.get(elementQName); // // If we didn't get an answer, try again with no namespace (as DTD fragments // declare ID attributes on elements with no namespace support): if (aXMLIdAttrName == null) { QName elementLocalName = mQNameCache.getExistingQName("", element.getLocalName()); if (elementLocalName != null) aXMLIdAttrName = mElementToIdAttrNameMap.get (elementLocalName); } // See if said attribute is defined, and if so, deindex it: if (aXMLIdAttrName != null) { int index = element.findAttr("", aXMLIdAttrName); if (index != -1) attr = element.getAttr(index); } if (attr != null) mXMLIdIndex.remove(attr.getAttrValue()); // // GLOBAL XML IDs (xml:id, wsu:Id, etc.) // // These are in a list and are valid on any element in any namespace, so just // go through the list: for (int i = 0; i < mGlobalXMLIdAttrList.size (); i++) { QName xmlIdAttrQName = mGlobalXMLIdAttrList.get(i); int index = element.findAttr(xmlIdAttrQName.maNameSpaceURI, xmlIdAttrQName.maLocalName); if (index != -1) attr = element.getAttr(index); if (attr != null) mXMLIdIndex.remove(attr.getAttrValue ()); } // // PRIMARY KEYS // // See if there is a primary key declared for this element: KeySpec primaryKeySpec = mElementToPKeySpecMap.get(elementQName); if (primaryKeySpec != null) { // If so, remove it from the index: Key primaryKey = element.constructKey(primaryKeySpec.mNodeAddresses, primaryKeySpec.mNamespaceContextNode); if (primaryKey.numValues() > 0) mPKeyIndex.remove(primaryKey); } } /** * @exclude from published api. */ public final void deindexSubtree(Element source, boolean bInDocumentCheckCompleted /* = false */) { if (! bInDocumentCheckCompleted) { // Check to see if the source node is in the document. If not, then it won't // have been indexed and there's nothing to do to deindex it: if (! referencedByHostDocument (source)) return; } // if (documentInDestructor ()) // Performance optimization // return; deindexNode (source, true); // Recurse through our children. Note that we don't use a wrapper // with poChild, because that would cause a reference to this document to // be added (causing a circular reference). The upshot is that poChild // must let this document know if/when it is deleted, so that we can remove // it from our cache. for (Node child = source.getFirstXMLChild (); child != null; child = child.getNextXMLSibling()) { if (child instanceof Element) deindexSubtree((Element)child, true); } } /** * Indicates whether or not an ID value is currently in use in the document. It is * not ID-type-specific; it will check all XFA IDs, XML IDs, etc. * * @param idValue the ID in question * @return true if the ID value is currently used in the document * @exclude from published api. */ public final boolean idValueInUse(String idValue) { if (mXMLIdIndex.get(idValue) != null) return true; for (Map index : mXFAIdIndexes.values()) { if (index != null && index.containsKey(idValue)) return true; } return false; } /** * @exclude from published api. */ public final boolean isIncrementalLoad () { // Javaport: TODO // return (mLazyLoader != null); return false; } /** * Loads a document from an input stream. *

* This overload should only be used in the case where the source is not * known, or is not relevant (e.g., loading from an in-memory stream). * * @param is an input stream. * @param encoding the input stream's encoding if known. * If null, the encoding is detected automatically. * @param parseExternalDTD parse any external DTD when true. * @throws ExFull exceptions upon parse errors. */ public final void load(InputStream is, String encoding, boolean parseExternalDTD) { load(is, null, encoding, parseExternalDTD); } /** * Loads a document from a file. * The lead bytes of the file are read to determine its encoding. * * @param file an input file. * @throws ExFull exceptions upon parse errors. */ public final void load(File file) { try { InputStream is = null; try { is = new FileInputStream(file); BufferedInputStream in = new BufferedInputStream(is); load(in, file.toString(), null, false); } finally { if (is != null) is.close(); } } catch (IOException e) { throw new ExFull(e); } } /** * Loads a document from an input stream. * * @param is an input stream. * @param source the source location, formatted as a file or URL. * @param encoding the input stream's encoding if known. * If null, the encoding is detected automatically. * @param parseExternalDTD parse any external DTD when true. * @throws ExFull exceptions upon parse errors. */ public final void load(InputStream is, String source, String encoding, boolean parseExternalDTD) { mRealDocument.setParseFileName(source); setWillDirty(false); try { SaxHandler handler = new SaxHandler(mRealDocument); if (mStartingModel != null) handler.setContext(mRealDocument.mStartingModel, mRealDocument.mStartingParent, mRealDocument.mbIgnoreAggregating); if (encoding == null) { is = makeInputStreamSupportMark(is); encoding = findEncoding(is); } launchSAX(handler, is, encoding, parseExternalDTD); } finally { setWillDirty(true); } isDefaultDocument(false); } /** * This method loads an XML file into a unattached branch of the current * document it is up to the class user to attach this branch to the current * document * @exclude from published api. */ public final Element loadIntoDocument(InputStream is) { setWillDirty(false); try { Element root = createElementNS("", STRS.DUMMYELEMENT, null); SaxHandler handler = new SaxHandler(mRealDocument); handler.setContext(null, root, false); is = makeInputStreamSupportMark(is); String encoding = findEncoding(is); launchSAX (handler, is, encoding, false); return root; } finally { setWillDirty (true); } } /** * Retrieves an element from the DOM, using lazy loading. * * When using lazy loading, no XSL processing is available. * @param depth optional parameter which returns the depth of the * returned element in the DOM tree being constructed. * * @exclude from published api. */ public final Element loadToNextElement(IntegerHolder depth /* = null */) { // Javaport: TODO return null; } /** * Launches the SAX parser: *

    *
  • instantiate a SAXParser, an XMLReader and InputSource, *
  • configure the reader and input source, *
  • identify the reader's callback handler, and, *
  • then launch the parser. *
* @param h a SAX handler. * @param is an InputStream. * @param encoding the input stream's encoding, or null if the * encoding should be determined automatically by the SAX parser. * @param parseExtrnlDTD parse any external DTD when true. * @throws ExFull exceptions upon parse errors. */ private void launchSAX(SaxHandler h, InputStream is, String encoding, boolean parseExtrnlDTD) { try { SAXParser p = javax.xml.parsers.SAXParserFactory.newInstance().newSAXParser(); XMLReader r = p.getXMLReader(); r.setFeature("http://xml.org/sax/features/namespaces", true); r.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", parseExtrnlDTD); if (! parseExtrnlDTD) r.setFeature("http://xml.org/sax/features/validation", false); r.setContentHandler(h); r.setErrorHandler(h); r.setProperty("http://xml.org/sax/properties/lexical-handler", h); // JavaPort: uncomment the following to exclude external general entities. // r.setFeature("http://xml.org/sax/features/external-general-entities", false); InputSource in = new InputSource(is); if (encoding != null) in.setEncoding(encoding); r.parse(in); } catch (SAXException s) { MsgFormatPos msg = new MsgFormatPos(ResId.EXPAT_ERROR); // Not really EXPAT, but a parser error msg.format(s.getMessage()); // TODO: Should probably use the SAXParser's locator to fill in more fields of the message. throw new ExFull(msg); } catch (ParserConfigurationException p) { throw new ExFull(p); } catch (IOException e) { throw new ExFull(e); } } /** * @exclude from published api. */ public final void savePreamble(OutputStream os, DOMSaveOptions options) { try { if (! options.getExcludePreamble()) { // // put out the preamble. // os.write(MarkupPrefix); os.write(Encoding.getBytes(Encoding)); os.write(MarkupDQuoteString); os.write(MarkupPIEnd); if (options.getDisplayFormat() != DOMSaveOptions.RAW_OUTPUT || options.getFormatOutside()) { os.write(MarkupReturn); } options.setExcludePreamble(true); } } catch (IOException e) { throw new ExFull(e); } } /** * @exclude from published api. */ public void preSave() { // TODO Auto-generated method stub throw new ExFull(ResId.UNSUPPORTED_OPERATION, "Document#preSave"); } /** * Serializes this document (and all its children) to an output stream. *

* This method does not perform any maintenance (e.g., namespace * normalization) on any models contained within the document before * saving. * * @param os an output stream. * @param options the XML save options * @exclude */ @Deprecated public final void saveXML(OutputStream os, DOMSaveOptions options) { boolean bClearChecker = false; if (mRealDocument.mChecker == null) { mRealDocument.mChecker = new SaveNameSpaceChecker(this); bClearChecker = true; } try { mRealDocument.saveXMLInternal(os, options); } catch (IOException ex) { throw new ExFull(ex); } finally { if (bClearChecker) { mRealDocument.mChecker = null; } } } private void saveXMLInternal(OutputStream os, DOMSaveOptions options) throws IOException { Node prevChild = null; for (Node child = getFirstXMLChild(); child != null; child = child.getNextXMLSibling()) { child.serialize (os, options, 0, prevChild); prevChild = child; } } /** * Serializes the given starting node (and all its children) * to an output stream. *

* This method does not perform any maintenance (e.g., namespace * normalization) on any models contained within the document before * saving. * * @param os an output stream. * @param startNode the starting node. * @param options the XML save options */ public final void saveAs(OutputStream os, Node startNode, DOMSaveOptions options) { assert startNode == null || startNode.getXMLParent() == null || startNode.getOwnerDocument().mRealDocument == mRealDocument; options = prepareSaveOptions(os, startNode, options); try { // JavaPort: no incrementalSave yet. // if (moIncrementalSave == null) { assert(mRealDocument.mChecker == null); mRealDocument.mChecker = new SaveNameSpaceChecker((startNode != null) ? startNode : this); // } if (startNode != null && startNode != this) { // Need to supply a previous sibling of the XML DOM node. Node xmlDomStartNode = startNode instanceof DualDomNode ? ((DualDomNode)startNode).getXmlPeer() : startNode; Node previousSibling = xmlDomStartNode.getPreviousXMLSibling(); startNode.serialize(os, options, 0, previousSibling); } else { mRealDocument.saveXMLInternal(os, options); } } catch (IOException ex) { throw new ExFull(ex); } finally { mRealDocument.mChecker = null; } } private DOMSaveOptions prepareSaveOptions(OutputStream os, Node startNode, DOMSaveOptions options) { // add UTF-8 output optimization, if possible DOMSaveOptions tweakedOptions = options == null ? new DOMSaveOptions() : new DOMSaveOptions(options); savePreamble(os, tweakedOptions); // write out the internal DTD, if needed for external entities if (tweakedOptions.getIncludeDTD()) { Node node; if (startNode != null && startNode != this) node = startNode; else node = getFirstXMLChild(); boolean bEmitCR = (tweakedOptions.getDisplayFormat() != DOMSaveOptions.RAW_OUTPUT); saveDTD(os, node, bEmitCR); } return tweakedOptions; } // all this method does is generate entity definitions // for use with external entities private void saveDTD(OutputStream os, Node oNode, boolean bEmitCr) { throw new ExFull(ResId.UNSUPPORTED_OPERATION, "Document#saveDTD"); // // put out an internal DTD definition for external entities which // // may be in use // if (bEmitCr) // os.write(MarkupReturn.getBytes(Encoding)); // os.write(MarkupDocType.getBytes(Encoding)); // os.write(MarkupSpace.getBytes(Encoding)); // os.write(((Element) oNode).getLocalName(Encoding).getBytes()); // os.write(MarkupSpace.getBytes(Encoding)); // os.write(MarkupStartParen.getBytes(Encoding)); // if (bEmitCr) // os.write(MarkupReturn.getBytes(Encoding)); // else // os.write(MarkupSpace.getBytes(Encoding)); // // // // if there were multiple entity references, loop through them here // // // os.write(MarkupEntity.getBytes(Encoding)); // os.write(MarkupSpace.getBytes(Encoding)); // os.write(msExternalEntityName.getBytes(Encoding)); // os.write(MarkupSpace.getBytes(Encoding)); // os.write(MarkupSystem.getBytes(Encoding)); // os.write(MarkupSpace.getBytes(Encoding)); // os.write(MarkupDQuoteString.getBytes(Encoding)); // os.write(mpoIncrementSaveStream->OpenFileName(Encoding).getBytes()); // os.write(MarkupEndEntity.getBytes(Encoding)); // if (bEmitCr) // os.write(MarkupReturn.getBytes(Encoding)); // else // os.write(MarkupSpace.getBytes(Encoding)); // os.write(MarkupEndParen2.getBytes(Encoding)); // if (bEmitCr) // os.write(MarkupReturn.getBytes(Encoding)); } /** * @exclude from published api. * @deprecated */ public void serialize(OutputStream outFile, DOMSaveOptions options, int level, Node prevSibling) throws IOException { boolean bClearChecker = false; if (mRealDocument.mChecker == null) { mRealDocument.mChecker = new SaveNameSpaceChecker(this); bClearChecker = true; } try { options = prepareSaveOptions(outFile, this, options); mRealDocument.saveXMLInternal(outFile, options); } finally { if (bClearChecker) { mRealDocument.mChecker = null; } } } /** * Sets this document's context for loading * so that we can import XML into an existing DOM. * @param model the model we're loading this data into. * @param parent the parent element to load the XML under. * @param bIgnoreAggregating indicates whether we're to load * the aggregating element or only its children. * * @exclude from published api. */ public final void setContext(Model model, Element parent, boolean bIgnoreAggregating) { mRealDocument.mStartingModel = model; mRealDocument.mStartingParent = parent; mRealDocument.mbIgnoreAggregating = bIgnoreAggregating; } /** * @exclude from published api. */ public final void setParseFileName(String source) { // JavaPort: The C++ mixes File and URL, so attempt to distinguish here URL url = null; if (source != null) { try { File file = new File(source); if (file.exists()) { url = file.toURI().toURL(); } } catch(IOException ignored) { } if (url == null) { try { url = new URL(source); } catch (MalformedURLException ignored) { } } if (url == null) { throw new IllegalArgumentException("source"); } } mRealDocument.msParseFileName = source; mRealDocument.mParseFile = url; } /** * Sets the flag that indicates whether changes within this document * should cause the namespace dirty check flag to be set. * @param bWillDirty true if changes within this document should cause the * namespace dirty check flag to be set. * @exclude from published api. */ public void setWillDirty(boolean bWillDirty) { mRealDocument.mbWillDirty = bWillDirty; } /** * Make an InputStream support mark/reset. * @param is an InputStream * @return an InputStream that supports mark/reset */ private static InputStream makeInputStreamSupportMark(InputStream is) { return is.markSupported() ? is : new MarkableInputStream(is); } private final static int DEFAULT_XMLDECL_BUFFER_SIZE = 64; /** * An InputStream that wraps an InputStream (that doesn't support mark/reset), * and adds the capability to use mark/reset on the first DEFAULT_XMLDECL_BUFFER_SIZE * bytes. *

* This class is a lightweight replacement for adding the subset of support * for mark/reset that we need without using something like java.util.BufferedInputStream. * */ private final static class MarkableInputStream extends InputStream { private final InputStream mInputStream; private byte[] mBuffer; private int mOffset; private int mLength; public MarkableInputStream(InputStream is) { mInputStream = is; } public int read() throws IOException { preLoadBuffer(); if (mBuffer != null) { if (mOffset == mLength) { disposeBuffer(); } else { return mBuffer[mOffset++] & 0xFF; } } return mInputStream.read(); } public int read(byte[] b, int off, int len) throws IOException { preLoadBuffer(); if (mBuffer != null) { if (mOffset == mLength) { disposeBuffer(); } else { int bytesRead = Math.min(mLength - mOffset, len); System.arraycopy(mBuffer, mOffset, b, off, bytesRead); mOffset += bytesRead; return bytesRead; } } return mInputStream.read(b, off, len); } private void preLoadBuffer() throws IOException { if (mOffset == 0 && mBuffer == null) { mBuffer = new byte[DEFAULT_XMLDECL_BUFFER_SIZE]; int nRead = ProtocolUtils.read(mInputStream, mBuffer); mLength = (nRead < 0) ? 0 : nRead; } } private void disposeBuffer() { mBuffer = null; mOffset = mLength = -1; } public long skip(long n) throws IOException { // This implementation is irrelevant since Xerces never calls it if (n <= 0) return 0; preLoadBuffer(); if (mBuffer != null) { int bytesLeft = mLength - mOffset; if (n >= bytesLeft) { disposeBuffer(); return bytesLeft + mInputStream.skip(n - bytesLeft); } else { mOffset += n; return n; } } return mInputStream.skip(n); } public int available() throws IOException { // This implementation is irrelevant since Xerces never calls it preLoadBuffer(); if (mBuffer != null) { int bytesLeft = mLength - mOffset; return bytesLeft == 0 ? mInputStream.available() : bytesLeft; } return mInputStream.available(); } public void mark(int howMuch) { assert mOffset == 0; assert howMuch == DEFAULT_XMLDECL_BUFFER_SIZE; } public void reset() { assert mBuffer != null; mOffset = 0; } public boolean markSupported() { // This class doesn't really support full mark/reset semantics. return false; } public void close() throws IOException { mInputStream.close(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy