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

com.adobe.xfa.data.DataModel Maven / Gradle / Ivy

The 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.data;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.StringTokenizer;

import org.xml.sax.Attributes;

import com.adobe.xfa.ArrayNodeList;
import com.adobe.xfa.Attribute;
import com.adobe.xfa.AppModel;
import com.adobe.xfa.Chars;
import com.adobe.xfa.Comment;
import com.adobe.xfa.DOMSaveOptions;
import com.adobe.xfa.Document;
import com.adobe.xfa.Element;
import com.adobe.xfa.EnumAttr;
import com.adobe.xfa.EnumType;
import com.adobe.xfa.Generator;
import com.adobe.xfa.LogMessage;
import com.adobe.xfa.Model;
import com.adobe.xfa.ModelFactory;
import com.adobe.xfa.ModelPeer;
import com.adobe.xfa.Node;
import com.adobe.xfa.NodeList;
import com.adobe.xfa.Obj;
import com.adobe.xfa.ProcessingInstruction;
import com.adobe.xfa.PseudoModel;
import com.adobe.xfa.STRS;
import com.adobe.xfa.Schema;
import com.adobe.xfa.ScriptTable;
import com.adobe.xfa.TextNode;
import com.adobe.xfa.XFA;
import com.adobe.xfa.ut.BooleanHolder;
import com.adobe.xfa.ut.ExFull;
import com.adobe.xfa.ut.FindBugsSuppress;
import com.adobe.xfa.ut.Key;
import com.adobe.xfa.ut.MsgFormat;
import com.adobe.xfa.ut.ObjectHolder;
import com.adobe.xfa.ut.Peer;
import com.adobe.xfa.ut.ResId;
import com.adobe.xfa.ut.ResourceLoader;
import com.adobe.xfa.ut.StringHolder;
import com.adobe.xfa.ut.IntegerHolder;
import com.adobe.xfa.ut.Assertions;
import com.adobe.xfa.ut.MsgFormatPos;
import com.adobe.xfa.ut.StringUtils;

/**
 * A class to model the collection of all XFA nodes that make up form data.
 */
public final class DataModel extends Model implements Model.DualDomModel {

	/**
	 * Enumeration XFAXMLFormat:
	 * 
    *
  1. FMT_XML_DATA = 3rd party XML, *
  2. FMT_XPF_DATA = XPF format. *
* Note! The numbers assigned to these constants can't be changed! The * scripting interface uses the numerical value to specify formats. * * @exclude from published api. */ public static final int FMT_XML_DATA = 0; /** * @exclude from published api. */ public static final int FMT_XPF_DATA = 1; private static final DataSchema gDataSchema = new DataSchema(); private static final boolean ACROBAT_PLUGIN = ResourceLoader.loadProperty("ACROBAT_PLUGIN").equalsIgnoreCase("true"); /** * @exclude from published api. */ public static String dataDescriptionNS() { return STRS.DATADESCRIPTIONURI; } /** * Transfer node and all its children (recursively) to parentNode * */ static private void flattenNode(Element parentNode, Node node) { // Don't move text nodes if (node instanceof TextNode) return; // Search for an element node in the children of node boolean bHasChildElements = false; Node child = node.getFirstXMLChild(); while (child != null) { if (child instanceof Element) { bHasChildElements = true; break; } child = child.getNextXMLSibling(); } if (bHasChildElements) { // append children of node to parentNode child = node.getFirstXMLChild(); while (child != null) { Node nextChild = child.getNextXMLSibling(); flattenNode(parentNode, child); child = nextChild; } node.getXFAParent().removeChild(node); } else { parentNode.appendChild(node, true); } } /** * Get the data description for this node. * * @param dataNode * Node to find a data description for * @return the data description (null if there isn't one) */ static DataNode getDataDesc(Node dataNode) { if (dataNode instanceof DataNode) return ((DataNode) dataNode).getDataDescription(); return null; } static String getDataDescriptionName(Element domDataDesc) { String sDDName = ""; if (domDataDesc != null) { int attr = domDataDesc.findAttr(STRS.DATADESCRIPTIONURI, XFA.NAME); if (attr != -1) sDDName = domDataDesc.getAttrVal(attr); } return sDDName; } /** * Gets the data model held within an XFA DOM hierarchy. * * @param app * the application model. * @param bCreateIfNotFound * when true, create a data model if needed. * @param bAppend * when true, append any underling xml to the xfa root * element when loading, and when false, create orphan * xfa:datasets and xfa:data nodes. This behaviour * occurs only works when bCreateIfNotFound is set to true. * @return * the data model, or null if none found. */ public static DataModel getDataModel(AppModel app, boolean bCreateIfNotFound /* = false */, boolean bAppend /* = false */) { DataModel data = (DataModel) Model.getNamedModel(app, XFA.DATAMODEL); if (bCreateIfNotFound && data == null) { // Check if we already have an XFADataModelFactory in // our app model - if we do, use it to create the dataModel. final List factoryList = app.factories(); final int nFactoryCount = factoryList.size(); DataModelFactory dataModelFactory = null; for (int i = 0; i < nFactoryCount; i++) { final ModelFactory item = factoryList.get(i); if (item instanceof DataModelFactory) { dataModelFactory = (DataModelFactory)item; break; } } if (dataModelFactory == null) dataModelFactory = new DataModelFactory(); Document doc = bAppend ? app.getDocument() : Document.createDocument(app); data = (DataModel) dataModelFactory.createDOM(bAppend ? (Element)app.getXmlPeer() : doc); data.setDocument(doc); // JAVAPORT_DATA: in the C++, we would call XFADataModelImpl::loadChildren, but // here we attempt to create the DataModel completely initialized. The DataWindow // does need to be initialized. data.mDataWindow.initialize(data, (Element)data.getXmlPeer(), data.mnRecordLevel, data.maRecordName, data.mnDataWindowRecordsAfter, data.mnDataWindowRecordsAfter); app.notifyPeers(Peer.CHILD_ADDED, XFA.DATAMODEL, data); } return data; } static int getMaxOccurFromDataDescription(Node node, boolean bUseGroup /* = false */) { int nRet = 1; if (node instanceof Element) { Element element = (Element)node; int i = element.findAttr(STRS.DATADESCRIPTIONURI, "maxOccur"); if (i != -1) { String sMaxOccur = element.getAttrVal(i); try { nRet = Integer.parseInt(sMaxOccur); } catch (NumberFormatException ex) { } } } if (bUseGroup && nRet > 0) { // this is the product of the max on this node and all parent group elements. // no need to check the parent if the current value is less than 1 Element group = node.getXFAParent(); if (isDataDescriptionGroup (group)) { nRet *= getMaxOccurFromDataDescription (group, true); } } if (nRet < 0) { return -1; } return nRet; } static int getMinOccurFromDataDescription(Node node, boolean bUseGroup) { int nRet = 1; if (node instanceof Element) { Element element = (Element)node; int i = element.findAttr(STRS.DATADESCRIPTIONURI, "minOccur"); if (i != -1) { String sMinOccur = element.getAttrVal(i); try { nRet = Integer.parseInt(sMinOccur); } catch (NumberFormatException ex) { } } } // ensure we have a valid value. if (nRet < 0) { return -1; } // this is the product of the min on this node and all parent group elements // no need to check the parent if the current value is less than 1 if (bUseGroup && nRet > 0) { // this is the product of the max on this node and all parent group elements. // no need to check the parent if the current value is less than 1 Element group = node.getXFAParent(); if (isDataDescriptionGroup (group)) { nRet *= getMinOccurFromDataDescription (group, true); } } return nRet; } static Schema getModelSchema() { return gDataSchema; } /** * @exclude from published api. */ public ScriptTable getScriptTable() { return DataModelScript.getScriptTable(); } // helper functions static boolean isDataDescriptionGroup(Element e) { if (isDataDescriptionNS(e.getNS()) && e.getLocalName() == XFA.GROUP) return true; return false; } @FindBugsSuppress(code="ES") static boolean isDataDescriptionNode(Node dataDescNode) { if (dataDescNode instanceof Element) { Element node = (Element) dataDescNode; if (node.getName() == XFA.DATADESCRIPTION && node.getNS() == DataModel.dataDescriptionNS()) { return true; } } return false; } @FindBugsSuppress(code="ES") static boolean isDataDescriptionNS(String aNS) { return aNS == STRS.DATADESCRIPTIONURI; } private String maRecordName; // null if not set private boolean mbAttributesAreValues; private boolean mbDataWindowRecordsSpecified; private boolean mbHasMapping; private boolean mbProcessRecords; private final DataWindow mDataWindow; private int meFormat = FMT_XML_DATA; private boolean mbDisableSchemaValidation; private final Map mExcludeNSList; @FindBugsSuppress(pattern="UWF_UNWRITTEN_FIELD") private IncrementalLoader mIncrLoadHandler; private int mnDataWindowRecordsAfter; private int mnDataWindowRecordsBefore; private int mnRecordLevel; // 1 if not set /** * @exclude from public api. */ public interface SourceSetLink { public boolean isSource(Node contextNode, String connection); public List getColumnData(Node contextNode, String connection, String colName); public Object /* SourceSetLink */ clone(); // TODO: If this interface is actually used, it probably needs an equivalent to the dtor (e.g., close()) // to close the data connection. } private SourceSetLink mSourceSetLink; /** * A SOM expression indicating an alternate starting node (in an XML file). * Default starting node is the root node */ private String mStartNodeSOMString; private DataTransformations mTransformations; // RecordPseudoModel implements the $record pseudo-model. This pseudo-model // is unusual in that properties and functions are not inherited from its parent. // Instead, all requests are simply redirected to the current record. If the data // window is not valid, then the result will have the default script model // defined by PseudoModel. private static class RecordPseudoModel extends PseudoModel { private final DataWindow mDataWindow; public RecordPseudoModel(DataWindow dataWindow) { mDataWindow = dataWindow; setClass("recordPseudoModel"); } public String getClassAtom() { return super.getClassName(); } public ScriptTable getScriptTable() { if (!mDataWindow.isDefined()) return super.getScriptTable(); return mDataWindow.record(0).getScriptTable(); } public Obj getAliasObject() { if (!mDataWindow.isDefined()) return super.getAliasObject(); return mDataWindow.record(0); } public Obj getScriptThis(){ if (!mDataWindow.isDefined()) return super.getScriptThis(); return mDataWindow.record(0); } } private static class RecordRangeFilter extends DataWindowFilter { private final SortedMap mRanges = new TreeMap(); RecordRangeFilter(DataWindow dataWindow, String sRanges) { super(dataWindow); setRanges(sRanges); } private void setRanges(String sRanges) { int offset = 0; final char tokenSeparator = ','; final char rangeSeparator = '-'; while (offset < sRanges.length() && Character.isWhitespace(sRanges.charAt(offset))) offset++; while (offset < sRanges.length()) { String token; int index = sRanges.indexOf(tokenSeparator, offset); if (index == -1) { token = sRanges.substring(offset); offset = sRanges.length(); } else { token = sRanges.substring(offset, index); offset = index + 1; } int nStart = 0; int nEnd = 0; index = token.indexOf(rangeSeparator); if (index != -1) { if (index == 0) { // can't have just - as a range MsgFormatPos msg = new MsgFormatPos(ResId.MalformedOptionException); msg.format(XFA.RANGE); throw new ExFull(msg); } String sStart = token.substring(0, index); try { nStart = Integer.parseInt(sStart); } catch (NumberFormatException ex) { } if (index == (token.length() - 1)) { // last character in the token is a '-' means to the last record nEnd = -1; } else { String sEnd = token.substring(index + 1); try { nEnd = Integer.parseInt(sEnd); } catch (NumberFormatException ex) { } } mRanges.put(nStart, nEnd); } else { try { nStart = Integer.parseInt(token); } catch (NumberFormatException ex) { } mRanges.put(nStart, nStart); } while (offset < sRanges.length() && Character.isWhitespace(sRanges.charAt(offset))) offset++; } } boolean filterRecord(DataNode dataGroup, int nAbsRecordIndex) { if (mRanges.isEmpty()) return true; for (Map.Entry entry : mRanges.entrySet()) { if (nAbsRecordIndex >= entry.getKey()) { if (entry.getValue() == -1) return true; if (nAbsRecordIndex <= entry.getValue()) return true; } else // start of range is too big break; // since list is sorted we can stop searching. } return false; } } /** * Default Constructor. * * @exclude from published api. */ public DataModel(Element parent, Node prevSibling) { super(parent, prevSibling, STRS.XFADATANS_CURRENT, STRS.XFADATASETS, STRS.DATASETS, STRS.DOLLARDATA, XFA.DATAMODELTAG, XFA.DATAMODEL, getModelSchema()); mbAttributesAreValues = true; mnRecordLevel = 1; //maRecordName = null; mDataWindow = new DataWindow(); // If we are adding in a new DataModel, we will temporarily allow // the creation of a duplicate DataModel which will get merged into // the existing DataModel during post load processing. // In that case, don't attempt to add duplicate PseudoModels. if (!isDuplicateDataModel()) { getAppModel().addPseudoModel("$dataWindow", mDataWindow); getAppModel().addPseudoModel("$record", new RecordPseudoModel(mDataWindow)); } mExcludeNSList = new HashMap(); super.mbValidateTextOnLoad = false; } private boolean isDuplicateDataModel() { for (Node child = getAppModel().getFirstXFAChild(); child != null; child = child.getNextXFASibling()) if (child != this && child instanceof DataModel) return true; return false; } /** * The AttributeWrapper exists so that we can pass an Attribute to a method that expects an Element. * This is by no means elegant or ideal, but it allows us to minimize differences between the C++ code * and Java. In C++, the loading routines take a jfDomNode, which is the parent for both attributes * and elements. In Java, the Attribute class doesn't derive from anything. * @exclude from published api. */ public final static class AttributeWrapper extends Element { final Attribute mAttribute; final Element mParent; private Element mXfaPeer; public AttributeWrapper(Attribute attribute, Element parent) { mAttribute = attribute; mParent = parent; } public String getNS() { return mAttribute.getNS(); } public String getLocalName() { return mAttribute.getLocalName(); } public String getXMLName() { return mAttribute.getQName(); } public String getValue() { return mAttribute.getAttrValue(); } public Element getXMLParent() { return mParent; } public Element clone(Element parent, boolean deep) { return new AttributeWrapper(mAttribute, parent); } public Element getXfaPeer() { return mXfaPeer; } public void setXfaPeer(Element xfaPeer) { mXfaPeer = xfaPeer; } } /** @exclude */ protected void addAttributes(Element pParent, Element node, Generator generator) { if (!attributesAreValues() || node.getNumAttrs() == 0) return; int numAttrs = node.getNumAttrs(); // JavaPort: Code to clone attributes removed since Attributes are immutable in XFA4J // Process sub-nodes. for (int i = 0; i < numAttrs; i++) { Attribute domAttr = node.getAttr(i); String aLocalName = domAttr.getLocalName(); // // Fix for Watson 1082694. Disabled loading of scripts for execution // in data model in all platforms (plug-in and server). // boolean bCheckOnly = true; // Check if it's one of our eval attributes. boolean bSkipAttr = loadSpecialAttribute(domAttr, aLocalName, pParent, bCheckOnly); // Don't load attributes of the form xmlns:alias="value" or // xmlns="value". // Note that any changes made here must be reflected in xfadatavalueimpl::clone(), // which does similar processing. if (domAttr.isNameSpaceAttr()) bSkipAttr = true; // Don't load attributes in our data namespace String aNamespaceURI = domAttr.getNS(); if (( ! bSkipAttr) && (isCompatibleNS(aNamespaceURI))) bSkipAttr = true; // Don't load attributes in the dataDescription namespace if (( ! bSkipAttr) && (isDataDescriptionNS(aNamespaceURI))) bSkipAttr = true; if ( ! bSkipAttr) { BooleanHolder bRecordLoaded = new BooleanHolder(); loadDataNode(pParent, new AttributeWrapper(domAttr, pParent), generator, bRecordLoaded); } } } /** * @exclude from published api. */ protected boolean canCreateChild(boolean bIsLeaf, String aName) { return true; } @FindBugsSuppress(code="ES") boolean canCreateChild(DataNode parent, boolean bIsLeaf, String aName) { // check and see if we have a data description node final DataNode desc = getDataDesc(parent); if (!mbDisableSchemaValidation && desc != null) { // look for the data description child String sNewNode = aName; final Node dataDescChild = getDataDescriptionChild(desc, sNewNode); if (dataDescChild != null) { // check the type of the data description child if ((bIsLeaf && dataDescChild.getClassTag() == XFA.DATAVALUETAG) || (!bIsLeaf && dataDescChild.getClassTag() == XFA.DATAGROUPTAG)) { // check the type of the data description child // check occurrence if it could be valid to create the node. // TODO Acrobat 10. Ensure that we haven't' already created all // the nodes int nMax = getMaxOccurFromDataDescription(dataDescChild, true); if (nMax < 0) { return true; // if less then zero we can have an infinite number of nodes. } int nCount = 0; for (Node child = parent.getFirstXFAChild(); child != null; child = child.getNextXFASibling()) { if (child.getName() == aName && child.isSameClass(dataDescChild)) nCount++; if (nCount == nMax) // max violated return false; } if (nCount < nMax) return true; return false; // max violated } else return false; // not a valid node type } else return false; // not a valid child } else { // default type check // do type check if (parent.getClassTag() == XFA.DATAGROUPTAG) return true; else if (parent.getClassTag() == XFA.DATAVALUETAG) return bIsLeaf; // data values can only have data value children. aka leaf nodes } assert false; // should not get here return false; } boolean attributesAreValues() { return mbAttributesAreValues; } /** @exclude */ public void appendChild(Node newChild, boolean bValidate) { if (getXmlPeer().getXMLParent() == null && getName() == STRS.DATASETS && isCompatibleNS(((ModelPeer)getXmlPeer()).getNS())) connectPeerToDocument(); super.appendChild(newChild, bValidate); } /** * @exclude from published api. */ public Node createChild(boolean bIsLeaf, String aName) { assert aName != null; return getModel().createNode(XFA.DATAGROUPTAG, this, aName, getNS(), true); } /** * @exclude from public api. */ @FindBugsSuppress(code="ES") public Node createNode(int eClassTag, Element parent, String aNodeName, String ns /* = "" */, boolean bDoVersionCheck /* = true */) { assert aNodeName != null; if (Assertions.isEnabled) assert aNodeName == aNodeName.intern(); // If we had a very extensive data model, we might have a list of nodes // available for cloning... but we don't. So we'll do the simple // implementation for now. Element oParentPeer = parent == null ? null : (Element)((DualDomNode)parent).getXmlPeer(); if (eClassTag == XFA.DATAVALUETAG) { if (aNodeName == "") { // create a text node Node text = new TextNode(oParentPeer, null, ""); DataNode dataValue = createDataValueFromPeer(parent, text); return dataValue; } Element peerNode = new Element(oParentPeer, null, ns, aNodeName, aNodeName, null, XFA.INVALID_ELEMENT, ""); peerNode.setModel(this); peerNode.setDocument(getDocument()); TextNode text = new TextNode(peerNode, null, ""); // Add the default #text child DataNode dataValue = createDataValueFromPeer(parent, peerNode); dataValue.singleTextChild(text); processDataDescription(parent); // new DV's are null by default dataValue.setIsNull(true, false); return dataValue; } else if (eClassTag == XFA.DATAGROUPTAG) { Element peerNode = new Element(oParentPeer, null, ns, aNodeName, aNodeName, null, XFA.INVALID_ELEMENT, ""); peerNode.setModel(this); peerNode.setDocument(getDocument()); DataNode dataGroup = createDataGroupFromPeer(parent, peerNode); processDataDescription(parent); return dataGroup; } else if (eClassTag == XFA.DSIGDATATAG) { // For now, throw an exception as before. TODO: support creating this class. MsgFormatPos msgFormatPos = new MsgFormatPos(ResId.InvalidNodeTypeException); msgFormatPos.format(getAtom(eClassTag)); throw new ExFull(msgFormatPos); } // No match on name? throw an exception! MsgFormatPos msgFormatPos = new MsgFormatPos(ResId.InvalidNodeTypeException); msgFormatPos.format(getAtom(eClassTag)); throw new ExFull(msgFormatPos); } /** * @see Model#createElement(Element, Node, String, String, String, * Attributes, int, String) * * @exclude from published api. */ @FindBugsSuppress(code="ES") public Element createElement(Element parent, Node prevSibling, String uri, String localName, String qName, Attributes attributes, int lineNumber, String fileName) { // Create a plain element node that's not part of the XFA Schema. Element retVall = new Element(parent, prevSibling, uri, localName, qName, attributes, XFA.INVALID_ELEMENT, localName); return retVall; } static DataNode changeType(DataNode source, boolean bDataValue) { if (bDataValue) { source.setClass(XFA.DATAVALUE, XFA.DATAVALUETAG); } else { source.setClass(XFA.DATAGROUP, XFA.DATAGROUPTAG); } Node child = source.getFirstXFAChild(); while (child != null) { // // if we are changing to datavalues all children // must also be datavalues // if (bDataValue && child.getClassTag() == XFA.DATAGROUPTAG) { child = changeType((DataNode) child, true); } child = child.getNextXFASibling(); } return source; } /** * Ignore the namespace "http://www.xfa.com/schema/xml-package" or any * namespace beginning with "http://www.xfa.org/schema/xfa-package/" .. or * any of the namespaces in mExcludeNSList. */ @FindBugsSuppress(code="ES") boolean excludeNS(String aNS) { if (aNS == STRS.XFADATANS_CURRENT) return false; if (aNS == null) return false; // atom doesn't exist if (aNS == "") return false; if (aNS == STRS.XMLPACKAGENS) return true; if (aNS == STRS.XFDFTRANSITIONNS) return true; if (aNS == STRS.XMLNSURI) return true; int nPackageNSLen = STRS.XFAPACKAGENS.length(); if (aNS.length() >= nPackageNSLen) { if (aNS.startsWith(STRS.XFAPACKAGENS, nPackageNSLen)) return true; } Boolean b = mExcludeNSList.get(aNS); if (b != null && b.booleanValue()) return true; return false; } /** * Find an attribute via loose namespace checking. Our check simply verifies * that the namespace is compatible with any variation of namespaces for * this model. Using the variations are simply changes in version number. * * @param e * the element to search * @param aNodeName * the local name of the attribute * @return attribute index. -1 if not found. * * @exclude from published api. */ @FindBugsSuppress(code="ES") public Attribute findAttrInNS(Element e, String aNodeName) { int len = e.getNumAttrs(); for (int i = 0; i < len; i++) { Attribute a = e.getAttr(i); if (a.getName() == aNodeName) { if (isCompatibleNS(a.getNS())) return a; } } return null; } /** * Given an array of attributes, find the specified node name which exists * in a namespace that is compatible with this data model's namespace. * (This is a direct port of the C++ method by the same name, unlike the other * variant of findAttrInNS above). * * @param attrs * the array of attributes to search * @param aNodeName * the local name of the attribute * @return the attribute. null if not found. * * @exclude from published api. */ @FindBugsSuppress(code="ES") public Attribute findAttrInNS(Attribute[] attrs, String aNodeName) { // If the atom is null, then we haven't seen the URI. if (aNodeName == null) return null; int len = attrs.length; for (int i = 0; i < len; i++) { Attribute node = attrs[i]; if (node.getLocalName() == aNodeName) { if (isCompatibleNS(node.getNS())) return node; } } return null; } /** * @see Model#getBaseNS() * * @exclude from published api. */ public String getBaseNS() { return STRS.XFADATANS; } /** * @exclude from public api. */ public DataNode getDataDescriptionRoot(String sDataRootName) { DataNode ret = null; Node child = getFirstXFAChild(); while (child != null) { if (isDataDescriptionNode(child)) { // does the dataDescription have the right name String sDDName = getDataDescriptionName((Element) child); if (sDDName.equals(sDataRootName)) return (DataNode) child; if (ret == null && sDDName.length() == 0) ret = (DataNode) child; // default if we don't find any name // - handle old SAP forms } child = child.getNextXFASibling(); } return ret; } private static void dissolveNode(Element parentNode, Node node) { Node nextChild; for (Node child = node.getFirstXMLChild(); child != null; child = nextChild) { nextChild = child.getNextXMLSibling(); if (!(child instanceof TextNode)) { //child.remove(); parentNode.appendChild(child); } } node.remove(); } String getMapping(String aNodeName) { String aNewName = ""; if (mTransformations != null) { aNewName = mTransformations.getMapName(aNodeName); } return aNewName; } /** * @exclude from published api. */ public DataNode createDataRootElement(DataNode dataDescription) { DataNode rootElement = null; if (dataDescription != null) { for (Node dataDesc = dataDescription.getFirstXFAChild(); dataDesc != null; dataDesc = dataDesc.getNextXFASibling()) { if (dataDesc instanceof DataNode) { rootElement = createNodeFromDataDescription((DataNode)dataDesc, null, null, false); clearDataDescriptionInfo(rootElement); break; } } } return rootElement; } private Node populateParent(Element dataParent, ObjectHolder prevDataNode, Node dataDescriptionParent, DataNode dataDescriptionNode, int occIndex, boolean bDefault) { int eModel = getElementModel(dataDescriptionParent); if (eModel == EnumAttr.RELATION_ORDERED) { return populateOrderedParent(dataParent, prevDataNode, dataDescriptionParent, dataDescriptionNode, occIndex, bDefault); } else if (eModel == EnumAttr.RELATION_UNORDERED) { return populateUnorderedParent(dataParent, prevDataNode, dataDescriptionParent, dataDescriptionNode, occIndex, bDefault); } else { // RELATION_CHOICE return populateChoiceParent(dataParent, prevDataNode, dataDescriptionParent, dataDescriptionNode, occIndex, bDefault); } } private int getElementModel(Node node) { if (node instanceof Element) { Element element = (Element)node; int index = element.findAttr(STRS.DATADESCRIPTIONURI, "model"); if (index != -1) { String sModel = element.getAttrVal(index); if (sModel.equals("choice")) return EnumAttr.RELATION_CHOICE; else if (sModel.equals("unordered")) return EnumAttr.RELATION_UNORDERED; } } return EnumAttr.RELATION_ORDERED; } private Node populateOrderedParent(Element dataParent, ObjectHolder prevDataNodeHolder, Node dataDescriptionParent, DataNode dataDescriptionNode, int occIndex, boolean bDefault) { Node newNode = null; Node prevDataNode = prevDataNodeHolder != null ? prevDataNodeHolder.value : null; for (DataNode dataDesc = (DataNode)dataDescriptionParent.getFirstXFAChild(); dataDesc != null; dataDesc = (DataNode)dataDesc.getNextXFASibling()) { // element children only // attributes are handled in ::createDataNodeFromDescription Node peer = dataDesc.getXmlPeer(); if (!(peer instanceof Element) || peer instanceof AttributeWrapper) continue; if (isDataDescriptionNS(dataDesc.getNS())) { if (isDataDescriptionGroup(dataDesc)) { if (prevDataNodeHolder == null) prevDataNodeHolder = new ObjectHolder(); prevDataNodeHolder.value = prevDataNode; Node groupNode = populateParent(dataParent, prevDataNodeHolder, dataDesc, dataDescriptionNode, occIndex, bDefault); prevDataNode = prevDataNodeHolder.value; if (newNode == null && groupNode != null) { newNode = groupNode; } } // any other dd namespaced element is ignored here } else { IntegerHolder nDDCount = new IntegerHolder(); // target node if (dataDescriptionNode == dataDesc) { // return first available node or last node Node existingNode = getNodeFromDataDescription(dataParent, dataDesc, occIndex, nDDCount); if (existingNode != null) { if (occIndex == -1 && getDDPlaceholderFlag(existingNode)) return existingNode; // found open node if (occIndex == (nDDCount.value - 1) && nDDCount.value > 0) return existingNode; // found target node } if (existingNode != null) prevDataNode = existingNode; // get the ref child Node nextDataNode = null; if (prevDataNode == null) { nextDataNode = getFirstElementChild(dataParent); } else { nextDataNode = getNextElementChild(prevDataNode); } if (occIndex == -1 ) occIndex = nDDCount.value; // need one more so take the num of children since we are 0 based // ensure we are not violating the max allowed, // calling code should handle a null result int nMax = getMaxOccurFromDataDescription(dataDesc, true);//bug 2514290 if (occIndex >= nMax && nMax >= 0) { // TODO Acrobat 10. If we have reached the max, before we return null we should check // if another group that can be created return null; } Node createNode = null; // create the the number required to get to the desired index int nNumToCreate = occIndex - nDDCount.value + 1; // add 1 for 0 based index for (int j = 0; j < nNumToCreate; j++) { // last node created here is our target node createNode = createNodeFromDataDescription(dataDesc, dataParent, nextDataNode, bDefault); } // ensure we have the min number int nMin = getMinOccurFromDataDescription(dataDesc, false) - occIndex - 1; // minus 1 for 0 based index for (int j = 0; j < nMin; j++) { // create the min createNodeFromDataDescription(dataDesc, dataParent, nextDataNode, bDefault); } if (existingNode != null) { return createNode; // if we repeating then we've been through the sequence before - don't need to continue } else { newNode = createNode; // otherwise need to complete the sequence } prevDataNode = newNode; } else { // Some other Node // Grab the last node Node existingNode = getNodeFromDataDescription(dataParent, dataDesc, -2, nDDCount); if (existingNode == null) { // grab ref child Node nextDataNode = null; if (prevDataNode == null) { nextDataNode = getFirstElementChild(dataParent); } else { nextDataNode = getNextElementChild(prevDataNode); } // create the min required Node createNode = null; int nMin = getMinOccurFromDataDescription(dataDesc, false); for (int j = 0; j < nMin; j++) { // create the min createNode = createNodeFromDataDescription(dataDesc, dataParent, nextDataNode, bDefault); } if (createNode != null) prevDataNode = createNode; } else { // grab the last index prevDataNode = existingNode; } } } } if (prevDataNodeHolder != null) prevDataNodeHolder.value = prevDataNode; return newNode; } Node populateUnorderedParent(Element dataParent, ObjectHolder prevDataNodeHolder, Node dataDescriptionParent, DataNode dataDescriptionNode, int occIndex, boolean bDefault) { return populateOrderedParent(dataParent, prevDataNodeHolder, dataDescriptionParent, dataDescriptionNode, occIndex, bDefault); } Node populateChoiceParent(Element dataParent, ObjectHolder prevDataNodeHolder, Node dataDescriptionParent, DataNode dataDescriptionNode, int occIndex, boolean bDefault) { // check if we already have a child for our choice // this means - do we already have anything contained within poDataDescriptionParent- since this is // the 'choice' object - data element or group. int nDDCount = 0; Node choiceChild = null; for (Node child = dataParent.getFirstXFAChild(); child != null; child = child.getNextXFASibling()) { if (child instanceof Element) { Node childDDesc = getDataDesc(child); if (childDDesc == null) continue; if (dataDescriptionNode == childDDesc) { // found required node if (occIndex == -1 && getDDPlaceholderFlag(child)) return child; // found open node if (occIndex == nDDCount) return child; // found target node nDDCount++; choiceChild = child; // last child } else if (isDescendantOfParent(childDDesc, dataDescriptionParent)) { choiceChild = child; // continue - need to get last choice (might be group within choice) } } } // Is the choice already satisfied by some other node? // TODO Acrobat 10. This code precludes the generation of // alternating data nodes that live under a 0..n choice group. // Example true
if there were any primaryKey declarations found. */ private boolean registerKeys(DataNode dataDesc) { boolean bPrimaryKeysFound = false; Element domNode = (Element)dataDesc.getXmlPeer(); int index = domNode.findAttr(STRS.DATADESCRIPTIONURI, XFA.PRIMARYKEY); if (index != -1) { Attribute primaryKeyAttr = domNode.getAttr(index); domNode.getOwnerDocument().declarePKey(domNode.getNS(), domNode.getLocalName(), primaryKeyAttr.getAttrValue(), domNode); bPrimaryKeysFound = true; } for (Node childDataDesc = dataDesc.getFirstXFAChild(); childDataDesc != null; childDataDesc = childDataDesc.getNextXFASibling()) { bPrimaryKeysFound |= registerKeys((DataNode)childDataDesc); } return bPrimaryKeysFound; } private DataNode createNodeFromDataDescription(DataNode dataDescriptionNode, Element dataParent, Node refChild /* = null */, boolean bDefault /* = false */) { DataNode newNode = (DataNode)dataDescriptionNode.clone(null, false); Node newDOMNode = newNode.getXmlPeer(); if (newDOMNode instanceof Element && !(newDOMNode instanceof AttributeWrapper)) { Element newDOMElement = (Element)newDOMNode; clearDataDescriptionInfo(newNode); if (newDOMElement.getNumAttrs() != 0) { addAttributes(newNode, newDOMElement, new Generator("", "")); for (Node child = newNode.getFirstXFAChild(); child != null; child = child.getNextXFASibling()) setDDPlaceholderFlag(child, true); } } if (dataParent != null) { if (refChild != null) dataParent.insertChild(newNode, refChild, true); else dataParent.appendChild(newNode, true); } setDDPlaceholderFlag(newNode, true); setDataDesc(dataDescriptionNode, newNode); // all new DV's are null by default newNode.setIsNull(true, false); if (bDefault) newNode.makeDefault(); // now recurse through children to ensure we have a valid XML instance populateParent(newNode, null, dataDescriptionNode, null, 0, bDefault); // JavaPort: Despite what the XFA spec says, the C++ implementation does use any // default value for attribute data values that are present in the data definition. // We need to copy them explicitly here. for (Node child = newNode.getFirstXFAChild(); child != null; child = child.getNextXFASibling()) { if (!(child instanceof DataNode)) continue; DataNode dataNode = (DataNode)child; if (dataNode.getClassTag() != XFA.DATAVALUETAG || !dataNode.isAttribute()) continue; // Find the attribute in the parent that corresponds to this node int index = newNode.findAttr(dataNode.getNS(), dataNode.getLocalName()); if (index != -1) dataNode.setValue(newNode.getAttrVal(index), false); } return newNode; } private void clearDataDescriptionInfo(Element dataNode) { if (dataNode == null) return; for (int j = 0; j < dataNode.getNumAttrs(); j++) { Attribute attr = dataNode.getAttr(j); if (isDataDescriptionNS(attr.getNS()) || attr.isNameSpaceAttr() && isDataDescriptionNS(attr.getAttrValue())) { dataNode.removeAttr(j); j--; } } } /** @exclude */ public void connectPeerToDocument() { super.connectPeerToDocument(); getDataWindow().resetRecordDepth(); } /** * Parse SOM expression in mStartNodeSOMString and update node. Note that we can't * use XFASomParser because it operates on XFANodes, not jfDomNodes. This is * greatly simplified anyway since [*] and relative notation aren't supported -- * the only valid bracket reference is an absolute reference. */ private Element findStartNodeSOM(Element node) { String sep = "."; // separates A.B[2].C etc StringTokenizer tokenizer = new StringTokenizer(mStartNodeSOMString, sep); if (! tokenizer.hasMoreTokens()) throw new ExFull(ResId.SOMEmptyResultException); // shouldn't happen String token = tokenizer.nextToken(); // first section must match name of node (bracket notation doesn't make // any sense at the root node, so do a simple name comparison). IntegerHolder occurrence = new IntegerHolder(); String nodeName = parseSOMString(token, occurrence); if (occurrence.value != 0 || !node.getName().equals(nodeName)) throw new ExFull(ResId.SOMEmptyResultException); while (tokenizer.hasMoreTokens()) { token = tokenizer.nextToken(); nodeName = parseSOMString(token, occurrence); boolean bFound = false; for (Node child = node.getFirstXMLChild(); child != null; child = child.getNextXMLSibling()) { if (child instanceof Element && child.getName().equals(nodeName)) { if (occurrence.value == 0) { // found the desired occurrence node = (Element)child; bFound = true; break; } occurrence.value--; } } if (!bFound) throw new ExFull(ResId.SOMEmptyResultException); } return node; } private static void setDDPlaceholderFlag(Node dataNode, boolean bIsDDPlaceholder) { if (dataNode instanceof DataNode) { ((DataNode)dataNode).setIsDDPlaceholder(bIsDDPlaceholder); } } private static boolean getDDPlaceholderFlag(Node dataNode) { if (dataNode instanceof DataNode) { return ((DataNode)dataNode).getIsDDPlaceholder(); } return false; } private static boolean isDescendantOfParent(Node dataDescNode, Node dataDescParent) { Node poParent = dataDescNode.getXFAParent(); while (poParent != null) { if (poParent == dataDescParent) return true; poParent = poParent.getXFAParent(); } return false; } private static boolean isDataDescriptionGroup(Node node) { if (node instanceof Element) { Element e = (Element)node; return isDataDescriptionNS(e.getNS()) && e.getLocalName() == XFA.GROUP; } return false; } private static Node getDataDescriptionGroup(Node dataDescriptionNode, Node currentDDParent) { // get the highest level containing which is within the parent Node ddParent = dataDescriptionNode.getXFAParent(); Node dataGroup = null; while (ddParent != currentDDParent && isDataDescriptionGroup(ddParent)) { dataGroup = ddParent; ddParent = ddParent.getXFAParent(); } return dataGroup; } private static Element getFirstElementChild(Node dataParent) { for (Node node = dataParent.getFirstXFAChild(); node != null; node = node.getNextXFASibling()) { Node domNode = ((DualDomNode)node).getXmlPeer(); if (domNode instanceof Element && !(domNode instanceof AttributeWrapper)) { return (Element)node; } } return null; } private static Element getNextElementChild(Node prevDataNode) { for (prevDataNode = prevDataNode.getNextXFASibling(); prevDataNode != null; prevDataNode = prevDataNode.getNextXFASibling()) { Node domNode = ((DualDomNode)prevDataNode).getXmlPeer(); if (domNode instanceof Element && !(domNode instanceof AttributeWrapper)) { return (Element)prevDataNode; } } return null; } private Node getNodeFromDataDescription(Node dataParent, Node dataDescriptionNode, int index, IntegerHolder count) { Node ret = null; Node last = null; count.value = 0; for (Node node = dataParent.getFirstXFAChild(); node != null; node = node.getNextXFASibling()) { if (dataDescriptionNode == getDataDesc(node)) { count.value++; if (index == -1 && getDDPlaceholderFlag(node)) return node; // can abort early last = node; // found the node we are looking for, but we must continue to count the nodes if ((index + 1) == count.value) ret = node; } } if (ret != null) return ret; return last; // last index } /** * @exclude from published api. */ public DataNode getDataRoot() { Node child = getFirstXFAChild(); while (child != null) { if (child instanceof DataNode && child.getName() == XFA.DATA) { // Need to verify that this node is in our namespace -- not // just any node called "data" will do. if (isCompatibleNS(((Element) child).getNS())) { return (DataNode) child; } } child = child.getNextXFASibling(); } return null; } /** * @exclude from published api. */ public DataWindow getDataWindow() { return mDataWindow; } /** * @exclude from public api. */ public SourceSetLink getSourceSetLink() { return mSourceSetLink; } /** * @exclude from published api. */ protected int getSaveFormat() { return meFormat; } /** * used to ensure an export data tree has a basic population * since an export data tree doesn't necessarily have any actual * data bindings. * * @exclude from public api. */ public void initFromDataDescription(Element dataNode) { Node dataDescription = getDataDesc(dataNode); if (dataDescription == null) return; for (Node dataDescriptionChild = dataDescription.getFirstXFAChild(); dataDescriptionChild != null; dataDescriptionChild = dataDescriptionChild.getNextXFASibling()) { if (!(dataDescriptionChild instanceof DataNode)) continue; DataNode dataDescriptionChildElement = (DataNode)dataDescriptionChild; if (!dataDescriptionChildElement.isAttribute()) { createNodeFromDataDescription(dataDescriptionChildElement, dataNode, null, false); } } } /** @exclude */ public void insertChild(Node newChild, Node refChild, boolean bValidate) { if (getXmlPeer().getXMLParent() == null && getName() == STRS.DATASETS && isCompatibleNS(((ModelPeer)getXmlPeer()).getNS())) connectPeerToDocument(); super.insertChild(newChild, refChild, bValidate); } /** * @exclude from published api. */ @FindBugsSuppress(code="ES") public boolean isCompatibleNS(String aNS) { if (aNS == null) return false; // atom doesn't exist if (aNS == "") return false; if (aNS == STRS.OLDXFADATANS) return true; int nDataNSLen = STRS.XFADATANS.length(); if (aNS.length() >= nDataNSLen) { if (aNS.startsWith(STRS.XFADATANS)) return true; } if (aNS == STRS.DATADESCRIPTIONURI) return true; return false; } Node loadImage(DataNode node, String sHref) { boolean bIsRichText = false; String sContentType = ""; int contentTypeAttr = node.findAttr(null, XFA.CONTENTTYPE); if (contentTypeAttr != -1) { sContentType = node.getAttrVal(contentTypeAttr); bIsRichText = sContentType.equals(STRS.TEXTHTML); } boolean bLoad = false; try { File oContent = new File(sHref); bLoad = oContent.exists(); } catch (Exception e) { } if (bLoad) { int len = sContentType.length(); //Node imrted = null; // // xml content xml // if (bIsRichText || (len > 4 && (sContentType.endsWith("/xml") || sContentType.endsWith("+xml")))) { throw new ExFull(ResId.UNSUPPORTED_OPERATION, "DataModel#loadImage - xml content"); // Javaport: TODO // jfStreamFile oContentStream; // oContentStream.Open(oContent,jfFileMode::ReadBinary()); // try { // // add xml content // Document doc = Document::load(oContentStream); // Node startNode = doc.getDocumentElement(); // imrted = node.getOwnerDocument().imrtNode(startNode, true); // } catch(ExFull e) { // // failed to load so load it as text // String sData; // oContentStream.Position(jfFilePosition()); // reset stream // oContentStream.ReadFile(sData); // imrted = node.getOwnerDocument().createCDATASection(sData); // } } else if (len > 5 && sContentType.startsWith("text/")) { throw new ExFull(ResId.UNSUPPORTED_OPERATION, "DataModel#loadImage - text content"); // Javaport: TODO // // load the data into a stream and save // jfStreamFile oContentStream; // oContentStream.Open(oContent,jfFileMode::ReadText()); // String sData; // oContentStream.ReadFile(sData); // // create cData section // imrted = node.getOwnerDocument().createCDATASection(sData); } else { throw new ExFull(ResId.UNSUPPORTED_OPERATION, "DataModel#loadImage - other content"); // Javaport: TODO // // TODO INTEGRATE THIS WITH THE IMAGE CACHE // jfStreamFile oContentStream; // oContentStream.Open(oContent,jfFileMode::ReadBinary()); // // // // make sure the content will be base64 encoded while read // // // oContentStream.PushTransformer(new jfBase64StreamTransformer); // String sData; // // read transformed data // oContentStream.ReadFile(sData); // imrted = node.getOwnerDocument().createTextNode(sData); } // // append node and set to transient so it isn't saved out // // Javaport: TODO // if (imrted != null) { // node.appendChild(imrted, true); // imrted.isTransient(true, false); // } } // // return the first child of this node // return node.getFirstXFAChild(); } /** @exclude */ public void loadNode(Element parent, Node node, Generator generator) { BooleanHolder bRecordLoaded = new BooleanHolder(); loadDataNode(parent, node, generator, bRecordLoaded); } /** * @exclude from published api. */ public boolean loadRootAttributes() { return mbAttributesAreValues; } /* * ParseSOMString is a helper routine which examines a single section of a SOM * expression (ie. a part between the '.' characters) & returns the node name * (stripped of any bracket notation) and the 0-based occurrence number. */ private static String parseSOMString(String somSection, IntegerHolder occurrence) { String nodeName = somSection; int nLeftBracketOffset = nodeName.indexOf('['); occurrence.value = 0; //parse brackets if they exist if (nLeftBracketOffset >= 0) { int nRightBracketOffset = nodeName.indexOf(']'); if ( nRightBracketOffset < 0) throw new ExFull(ResId.InvalidSOMException); // ensure ']' is at end of string int nLength = nodeName.length(); if (nRightBracketOffset != nLength - 1) throw new ExFull(ResId.InvalidSOMException); String occurrenceString = nodeName.substring(nLeftBracketOffset + 1, nLength - 1); try { int nOccurrence = Integer.parseInt(occurrenceString); occurrence.value = nOccurrence; } catch (NumberFormatException e) { throw new ExFull(ResId.InvalidSOMException); } nodeName = nodeName.substring(0, nLeftBracketOffset); } return nodeName; } Node loadToNextRecord() { return null; // Javaport: TODO // int nRecordDepth = mIncrLoadHandler.recordDepth(); // for (;;) { // // Keep loading from the XML DOM until the next element at the // // depth of a record is encountered. The element might not // // actually be a record but we have to stop somewhere, and at // // this point it's not possible to tell if something is going // // to become a record or not. This loop ensures that the next // // sibling (if it exists) will be connected to the XML DOM tree. // // The next sibling is required to prime the stack for the next // // iteration. Note that this routine might actually return // // before loading a record (hence the function name isn't great). // // But the only caller, datawindowimpl, loops until it gets // // the record it's looking for. // IntegerHolder nDepth = new IntegerHolder; // Element e = domDocument().loadToNextElement(nDepth); // if (e == null || nDepth.value == nRecordDepth) // break; // } // Element parent = new Element(); // Node node = new Node(); // mIncrLoadHandler.backOut(false); // if (! mIncrLoadHandler.getNext(parent, node)) // return null; // loadNode(parent, node, mIncrLoadHandler.getGenerator()); // // // // Prime the stack for the next iteration by replacing the node at // // the top of the stack with it's sibling. If that's null, then // // pop the next one off the stack and use it's sibling, etc. // // // Element nextParent = new Element(); // Node nextResumeNode = new Node(); // boolean bDone = false; // while (mIncrLoadHandler.getNext(nextParent, nextResumeNode)) { // // // // Don't load siblings of the start node -- but continue looping to // // empty the stack. // // // if (mIncrLoadHandler.isStartNode(nextResumeNode)) // bDone = true; // // // // push the next sibling onto the stack (skipping over text nodes) // // // Node nextNode = nextResumeNode.getNextSibling(); // while (nextNode instanceof Chars) { // nextNode = nextNode.getNextSibling(); // } // if ( ! bDone && nextNode != null) { // mIncrLoadHandler.Push(nextParent, nextNode); // break; // } // } // if (mIncrLoadHandler.stackIsEmpty()) { // if (getDataRoot() == null) { // // // // If an xfa:data node was not parsed out of the file, create an orphan node // // and peer a new data group to it. This will be $data. // // // Document doc = node.getOwnerDocument(); // Node dataNode(doc.createElementNS(DataModel.dataNS(), STRS.XFADATA)); // Node pData = new DataGroup(this, dataNode, this); // // // // Set our alias node ($data) to datasets.data // // // setAliasNode(getDataRoot()); // } // } // return parent; } /** * Override Model.loadXMLImpl to provide DualDomNode-specific implementation. * @exclude */ protected void loadXMLImpl(Element parent, InputStream is, boolean bIgnoreAggregatingTag, ReplaceContent eReplaceContent) { Element domPeer = (Element)((DualDomNode)parent).getXmlPeer(); if (eReplaceContent == ReplaceContent.AllContent || eReplaceContent == ReplaceContent.XFAContent) { // Delete all XFA children while (parent.getFirstXFAChild() != null) parent.getFirstXFAChild().remove(); } if (eReplaceContent == ReplaceContent.AllContent) { while (domPeer.getFirstXMLChild() != null) domPeer.getFirstXMLChild().remove(); } Document doc = domPeer.getOwnerDocument(); Element imported = doc.loadIntoDocument(is); // Start with the first element node. // skip comments, processing instructions etc. Element startNode = imported.getFirstXMLChildElement(); // No element node found... // throw an error ?? if (startNode == null) return; if (! bIgnoreAggregatingTag) { // load root of the XML file and append to this node domPeer.appendChild(startNode); loadNode(parent, startNode, new Generator("", "")); } else { // load all the children of the root of the XML file // and append to this node Node child = startNode.getFirstXMLChild(); while (child != null) { Node nextChild = preLoadNode(parent, child, new Generator("", "")); // preLoad could remove the child if (child.getXMLParent() == null) break; // Save the next node early in case child gets deleted along the way. domPeer.appendChild(child, true); if (child instanceof Element || child instanceof Chars) { loadNode(parent, child, new Generator("", "")); } child = nextChild; } if (loadRootAttributes()) { // watson bug 1266068 addAttributes will now worry about cloning the attributes correctly addAttributes(this, startNode, new Generator("", "")); } } // call virtual function to reset any cached data parent.resetPostLoadXML(); // resolve protos resolveProtos(false); // Watson 1915148: for new documents, ensure we track dependencies for loadXML. AppModel appModel = getAppModel(); if (appModel != null && !appModel.getLegacySetting(AppModel.XFA_LEGACY_V29_SCRIPTING)) // Watson 1928585: Pass pParent, not NULL, so that peers don't crash. parent.notifyPeers(Peer.DESCENDENT_VALUE_CHANGED, "", parent); } void popStack() { if (mIncrLoadHandler != null) { mIncrLoadHandler.mnLoadNodeLevel--; if ((mIncrLoadHandler.mnLoadNodeLevel > 0) && (!mIncrLoadHandler.backOut())) mIncrLoadHandler.pop(); } } /** * @exclude from published api. */ public void preSave(boolean bSaveXMLScript /* = false */) { // Recursively call preSave for all children for (Node child = getFirstXFAChild(); child != null; child = child.getNextXFASibling()) { child.preSave(bSaveXMLScript); } } /** * Loads the DataModel from the XML DOM. * In the C++ implementation, this contains processing similar to XFADataModelImpl::loadChildren * and XFADataModelImpl::add. * @see Model#postLoad() * * @exclude from published api. */ protected void postLoad() { // During parsing, the document would have been parsed into a tree that is // currently the peer to this DataModel. Detach it, so that we can load // that XML in properly and re-peer it as necessary. Element xmlTreeRoot = (Element)getXmlPeer(); ModelPeer modelPeer = new ModelPeer(null, null, STRS.XFADATANS_CURRENT, STRS.DATASETS, STRS.XFADATASETS, null, this); modelPeer.setModel(this); setXmlPeer(modelPeer); Generator gen = null; isLoading(true); if (!StringUtils.isEmpty(mStartNodeSOMString)) { // An option (presumably a processing instruction in a 3rd party // XML file) has overridden the default start node. Find the new // node using mStartNodeSOMString. Currently the only method supported // is a SOM string, via xfasom(som expression). // startNode is defined to be from the root of the document, // so start at the root element of the document. // JavaPort: Can't use getOwnerDocument() in XFA4J at this stage of processing. // Element startNode = xmlTreeRoot.getOwnerDocument().getDocumentElement(); Element startNode = xmlTreeRoot; while (startNode.getXMLParent() != null) startNode = startNode.getXMLParent(); startNode = ((Document)startNode).getDocumentElement(); xmlTreeRoot = findStartNodeSOM(startNode); // Verify that the node doesn't contain any text children. for (Node child = startNode.getFirstXMLChild(); child != null; child = child.getNextXMLSibling()) { if (child instanceof Chars && !((Chars)child).isXMLSpace()) { throw new ExFull(ResId.InvalidDataSourceException); } } } // If this is a duplicate DataModel - add it to the existing model DataModel existingDataModel = DataModel.getDataModel(getAppModel(), false, false); if (existingDataModel != this) { existingDataModel.add(xmlTreeRoot, gen); remove(); return; } boolean bLazyLoading = false;// JAVAPORT_DATA startNode.getOwnerDocument().isIncrementalLoad(); if (! mbDataWindowRecordsSpecified) { // provide default values if (bLazyLoading) { mnDataWindowRecordsBefore = 0; mnDataWindowRecordsAfter = 0; } else { mnDataWindowRecordsBefore = Integer.MAX_VALUE; mnDataWindowRecordsAfter = Integer.MAX_VALUE; } } // Initialize the data window DataWindow dw = getDataWindow(); dw.initialize(this, xmlTreeRoot, mnRecordLevel, maRecordName, mnDataWindowRecordsBefore, mnDataWindowRecordsAfter); // Create the XFA children as children of this DataModel, peering to the XML nodes rooted at mXMLTreeRoot. BooleanHolder bValidRecordLoaded = new BooleanHolder(); loadDataNode(this, xmlTreeRoot, gen, bValidRecordLoaded); // JAVAPORT_DATA generator and bValidRecordLoaded unused today. // let the data window know the loading is done dw.updateAfterLoad(); processDataDescription(getDataRoot()); // XFADataModelImpl::postLoad implementation dw.postLoad(); isLoading(false); } private void add(Element inputNode, Generator generator) { boolean bisCompatibleNS = isCompatibleNS(inputNode.getNS()); if (bisCompatibleNS) { // if xfa:datasets grab its children and load them if (inputNode.getLocalName() == STRS.DATASETS) { for (Node child = inputNode.getFirstXMLChild(); child != null; child = child.getNextXMLSibling()) { if (child instanceof Element) { add((Element)child, generator, true); } } return; } } add(inputNode, generator, false); } private void add(Element inputNode, Generator generator, boolean bDatasetParent) { boolean bisCompatibleNS = isCompatibleNS(inputNode.getNS()); boolean bIsDataDescription = false; boolean bIsData = false; if (bisCompatibleNS) { String aNodeLocalName = inputNode.getLocalName(); if (aNodeLocalName == XFA.DATA) { bIsData = true; } else if (aNodeLocalName == STRS.DATADESCRIPTION) { // If we're adding then be sure that we don't overwrite an existing form // dataDescription with one that might inadvertantly appear e.g. in a data XDP. // add() should never overwrite a dataDescription of the same name - check and // return if we already have a dataDescription loaded for this name. String aDDName = getDataDescriptionName(inputNode); if (getDataDescriptionRoot(aDDName) != null) // gets loaded dataDescs only return; bIsDataDescription = true; } } //isLoading(true); // managed in DataModel.postLoad() Element startNode = inputNode; Document doc = getDocument(); Document inputDoc = inputNode.getOwnerDocument(); if (doc != inputDoc) { // if the inputdoc is incrementally loaded, load the rest of the doc // to ensure all the xml is imported into this doc. // Otherwise the doc will only load until the next record. // TODO don't do an import and have the data window manage // new data records. if (inputDoc.isIncrementalLoad()) { // warn that incremental mode is turned off // TODO remove this resource ID when fixed. ExFull ex = new ExFull(ResId.IncrementalDataLoadException); addErrorList(ex, LogMessage.MSG_WARNING, null); // JAVAPORT_DATA - TODO // Element e = oInputDoc.loadToNextElement(); // while (e != null) // e = oInputDoc.loadToNextElement(); } try { doc.autoUniquifyIDs(true); // Don't auto-uniquify in context of data startNode = (Element)doc.importNode(inputNode, true); } finally { doc.autoUniquifyIDs(true); // Reset default behaviour } // We've left oStartNode detached from its parent jfDOM tree, but // we still want to consider it part of the document. if (startNode != null) startNode.setIsDataWindowRoot(true); } // add xml ids, this needs to be done before createReference // DomSignature::DeclareXMLIds(oDoc); // JAVAPORT_DATA - TODO doc.indexSubtree(startNode, false); // Default behavior now is that data in an external data file will // replace a dataset () already specified in the XDP. // Non-data content (e.g. dataDescriptions) will simply be loaded // at the end of the existing data model. final DataNode firstData = getDataRoot(); // We want to replace the xfa:data node if the current node is xfa:data OR // if the current node does not have a dataset parent. if (firstData != null && (!bDatasetParent || bIsData)) { DataNode data; // Create a new XFA::Data node to replace the existing one if (bIsData) { data = createDataGroupFromPeer(this, startNode); } else { Element dataNode = getDocument().createElementNS(STRS.XFADATANS_CURRENT, STRS.XFADATA, null); dataNode.setModel(this); data = createDataGroupFromPeer(this, dataNode); } // replace old xfa:data insertChild(data, firstData, false); firstData.remove(); // Re-initialize the data window. mDataWindow.uninitialize(); mDataWindow.initialize(this, startNode, mnRecordLevel, maRecordName, mnDataWindowRecordsBefore, mnDataWindowRecordsAfter); // we are the data node so just load the children if (bIsData) { Node nextSibling; for (Node child = startNode.getFirstXMLChild(); child != null; child = nextSibling) { // grab next sibling in case data transforms modify child nextSibling = child.getNextXMLSibling(); if (child instanceof Element) loadNode(data, child, generator); } } // load the node given. else loadNode(data, startNode, generator); // Set our alias node ($data) to datasets.data setAliasNode(getDataRoot()); processDataDescription(getDataRoot()); //isLoading(false); return; } if (bIsData || bIsDataDescription || bDatasetParent) { loadNode(this, startNode, generator); } else { // create a new data set for unknown data with no datasets parent Element dataNode = getDocument().createElementNS(STRS.XFADATANS_CURRENT, STRS.XFADATA, null); DataNode data = createDataGroupFromPeer(this, dataNode); loadNode(data, startNode, generator); } // Set our alias node ($data) to datasets.data setAliasNode(getDataRoot()); // if the node we are loading is a data description or if we are loading the first data entry if (bIsDataDescription || firstData != null) processDataDescription(getDataRoot()); //isLoading(false); } /* * Recursively build tree from the XML peer. generator and bValidRecordLoaded are currently unused (placeholders) */ private void loadDataNode(Element parent, Node node, Generator generator, BooleanHolder bValidRecordLoaded) { // Watson bug 1679357, only update bValidRecordLoaded if we actually load a record. // calling code is responsible for initializing bValidRecordLoaded to false // // Fix for Waton 1082694. Disabled loading of scripts for execution // in data model in all platforms (plug-in and server). // boolean bCheckOnly = true; if (loadSpecialNode(parent, node, bCheckOnly)) return; /*JAVAPORT_DATA StackManager sm(mIncrLoadHandler); if (mIncrLoadHandler != NULL) { // Might have to come back to this node, so store it. If a record is loaded, then the // loading process will be interrupted and this will be left on the stack. If no record // is loaded and recursion finishes "naturally", then this will be popped off the stack // and discarded. mIncrLoadHandler->Push(pParent, node); } END JAVAPORT_DATA*/ DataNode newNode = null; // TODO Post NewPort: We should get the nodes attributes once here, // instead of asking for it a bunch of times as // we do in this method. (see below) SS. // jfDomNamedNodeMap attrs(node.getAttributes()); // // boolean bOverrideDataName = false; boolean bOverride = false; boolean bParentIsDataGroup = parent.getClassTag() == XFA.DATAGROUPTAG; boolean bCreateDataValue = false; boolean bParentIsDataValue = false; boolean bParentIsDataModel = false; boolean bGrandParentIsDataModel = false; boolean bStripWhiteSpace = false; boolean bParentIsDataDescription = false; // grab node info String aNodeNameSpace = node instanceof Element ? ((Element)node).getNS() : ""; String aNodeLocalName = node instanceof Element ? ((Element)node).getLocalName() : ""; if (node instanceof TextNode) aNodeLocalName = "#text"; if (!bParentIsDataGroup) bParentIsDataValue = parent.getClassTag() == XFA.DATAVALUETAG; if (!bParentIsDataValue && !bParentIsDataGroup) { bParentIsDataModel = parent.getClassTag() == XFA.DATAMODELTAG; } if (bParentIsDataGroup) { if (!bParentIsDataModel) { if (parent.getXFAParent() != null) { bGrandParentIsDataModel = parent.getXFAParent().getClassTag() == XFA.DATAMODELTAG; } } } String aMappedName = ""; String aRename = ""; String aNameAttr = ""; boolean bHasNameAttr = false; Attribute nameAttr = null; boolean bHasDataPictureFormat = false; String sPictureFormat = ""; int eOperation = EnumAttr.UNDEFINED; if (mTransformations != null) { eOperation = mTransformations.getOperation(aNodeLocalName, EnumType.PRESENCE); aRename = mTransformations.getMapName(aNodeLocalName); mbHasMapping = (aRename != ""); // Find out if there was a nameAttr specified. aNameAttr = mTransformations.getNameAttr(aNodeLocalName); // If we have a nameAttr, we really want to ask the XFADataValue if // it has an attribute with the name sNameAttr. If it does, then // we will get its value and this is what we will use to rename // the node as. (Only on the XFA side). // Additionally we want to make sure that the nameAttr attribute does // not get promoted to a data value - so remove it (on dom side) // but add it again after XFA nodes have been created or before returning // (use READD_NAMEATTR define below). if (aNameAttr != "") { if (node instanceof Element) { Element element = (Element)node; int index = element.findAttr("", aNameAttr); if (index != -1) { nameAttr = element.getAttr(index); element.removeAttr(index); bHasNameAttr = true; } } } //Ugh. XFADataTransformations class needs to be re-worked. sPictureFormat = mTransformations.getPictureFormat(aNodeLocalName); bHasDataPictureFormat = !StringUtils.isEmpty(sPictureFormat); } boolean bExclude = (eOperation == EnumAttr.NODEPRESENCE_IGNORE) || (eOperation == EnumAttr.NODEPRESENCE_DISSOLVE) || excludeNS(aNodeNameSpace); boolean bFlatten = (eOperation == EnumAttr.NODEPRESENCE_DISSOLVESTRUCTURE); // This is for convenience: Re-add any nameAttr attribute. It was removed so that no data values // were created from it, but it must remain in the dom document // READD_NAMEATTR // if (bHasNameAttr && oNameAttr != null) // updateAttribute(oNameAttr); boolean bIsDatasets = false; // true if node parameter is xfa:datasets if ( !bExclude && (bParentIsDataModel || bGrandParentIsDataModel)) { // We always ignore xfa:DataGroup (this is a record wrapper in XPF // that we don't care about) // If we encounter xfa:datasets as the first non-ignored node, we peer // against it and skip over it to avoid creating a dataGroup called // "datasets". if (aNodeLocalName == STRS.DATAGROUP) bExclude = isCompatibleNS(aNodeNameSpace); else if (aNodeLocalName == STRS.DATASETS) { if (isCompatibleNS(aNodeNameSpace)) { assert node instanceof ModelPeer; setXmlPeer(node); bExclude = true; bIsDatasets = true; } } } if (bExclude) { // this is an excluded namespace (or node), so ignore it and dig through // the children of the node Node child = node.getFirstXMLChild(); Node oLastChild = node.getLastXMLChild(); while (child != null) { Node nextChild = child.getNextXMLSibling(); // if ignoring an element, ignore all its text nodes too if (! (child instanceof TextNode)) { /*JAVAPORT_DATA if (mIncrLoadHandler != NULL && mIncrLoadHandler->BackOut()) { if (bParentIsDataModel) setAliasNode(XFANode((jfObjImpl*)getDataRoot())); if (bHasNameAttr && oNameAttr != null) //READD_NAMEATTR updateAttribute(oNameAttr); return; } */ loadDataNode(parent, child, generator, bValidRecordLoaded); } // transform/dissolve appends nodes so break after processing the last child if (child == oLastChild) break; child = nextChild; } if (eOperation == EnumAttr.NODEPRESENCE_DISSOLVE) { Node peer; if (parent instanceof DataNode) peer = ((DataNode)parent).getXmlPeer(); else { assert parent instanceof DataModel; peer = ((DataModel)parent).getXmlPeer(); } dissolveNode((Element)peer, node); // JAVAPORT_DATA: ugly cast operations - fix types! } if (bIsDatasets && (getDataRoot() == null)) { // If an xfa:data node was not parsed out of the file, create an orphan node // and peer a new data group to it. This will be $data. Document doc = node.getOwnerDocument(); Element dataNode = doc.createElementNS (STRS.XFADATANS_CURRENT, STRS.XFADATA, null); /* DataNode data = */ createDataGroupFromPeer(this, dataNode); // Set our alias node ($data) to datasets.data setAliasNode(getDataRoot()); } /*JAVAPORT_DATA if (mIncrLoadHandler != NULL && processRecords()) { XFADataWindowImpl *poDWImpl = (XFADataWindowImpl*) moDataWindow.getObjPtr(); if (poDWImpl->isRecordDepth(node)) { // We've just finished loading a node at the level of a record. mIncrLoadHandler->BackOut(TRUE); } } */ if (bHasNameAttr && nameAttr != null) //READD_NAMEATTR updateAttribute(nameAttr); return; } // A non-excluded node. if (bParentIsDataModel) { boolean b_Data = false; // nodeLocalName == "Data" boolean b_data = false; // nodeLocalName == "data" boolean b_DataDesc = false; if (aNodeLocalName == XFA.DATA) b_data = true; else if (aNodeLocalName == STRS.DATA) b_Data = true; else if (aNodeLocalName == STRS.DATADESCRIPTION) b_DataDesc = true; // Check to see if we (the dataModel) are still peered // to an orphan node. If that's the case, we haven't // encountered xfa:datasets in the file. if (getXmlPeer().getXMLParent() == null) { // Since bParentIsDataModel is TRUE, we're currently // loading the top-most node in the XML file. If that // node is xfa:data or xfa:Data, then proceed as normal. // Otherwise, the current node is the first node we've // encountered, and it's 3rd party XML -- so create // the xfa:data group. boolean b3rdPartyNode = true; if (b_data || b_Data || b_DataDesc) { if (isCompatibleNS(aNodeNameSpace)) b3rdPartyNode = false; } if (b3rdPartyNode) { Element dataNodePeer = getOwnerDocument().createElementNS (STRS.XFADATANS_CURRENT, STRS.XFADATA, null); dataNodePeer.setModel(this); // JAVAPORT_DATA: all XML peers should have their model set (used in serialization) DataNode data = createDataGroupFromPeer(this, dataNodePeer); loadDataNode(data, node, generator, bValidRecordLoaded); // Set our alias node ($data) to datasets.data. This is set here to handle // the odd case where somebody calls loadXML on an empty "datasets" node. setAliasNode(getDataRoot()); if (bHasNameAttr && nameAttr != null) //READD_NAMEATTR updateAttribute(nameAttr); return; } } if (isCompatibleNS(aNodeNameSpace)) { if (b_Data || b_data || b_DataDesc) { if (b_Data) { aMappedName = XFA.DATA; bOverrideDataName = true; } bCreateDataValue = false; bOverride = true; } } // don't process records until we get to grand children processRecords(false); } else if (bGrandParentIsDataModel) { // check and see if we should process record info. // Only process records if the parent is the first xfa:dataNode String aParentName = parent.getName(); if (aParentName == STRS.DATADESCRIPTION) { bParentIsDataDescription = true; processRecords(false); // the child of needs to load as a dataGroup since it will // bind to the top level subform bCreateDataValue = false; bOverride = true; } else if(aParentName == XFA.DATA) { if (parent.getIndex(true) == 0) processRecords(true); else processRecords(false); } else processRecords(false); } /*JAVAPORT_DATA // load dig sig nodes if (DomSignature::IsSignature(node)) { XFADSigDataImpl *pSig = new XFADSigDataImpl(pParent, node, this); if (bHasNameAttr && oNameAttr != null) //READD_NAMEATTR updateAttribute(oNameAttr); loadDSigData(pSig); return; } end JAVAPORT_DATA*/ Node firstChild = node.getFirstXMLChild(); boolean bIsRichText = false; boolean bIsNull = false; Attribute dataNodeAttr = null; if (node instanceof Element) { Element e = (Element)node; dataNodeAttr = findAttrInNS(e, XFA.DATANODE); String sContentType = ""; Attribute contentTypeAttr = findAttrInNS(e, XFA.CONTENTTYPE); if (contentTypeAttr != null) { sContentType = contentTypeAttr.getAttrValue(); bIsRichText = (sContentType.equals(STRS.TEXTHTML)); } /* JavaPort: This section has been ported, but it is commented out * since it is an obvious security risk. Attribute oHrefAttr = findAttrInNS(e, XFA.HREF); if (oFirstChild == null && // no children oHrefAttr != null) { // has a href attr String sHref = oHrefAttr.getAttrValue(); if (!StringUtils.isEmpty(sHref)) { boolean bLoad = false; File oContent = new File(sHref); //Document doc = node.getOwnerDocument(); // JAVAPORT_DATA: Do we have a comparable security check for XFA4J? // bLoad = oContent.isTrusted(jfFileId::DATAINJECTION,FALSE) && oContent.ExistsLocally(); bLoad = oContent.isFile(); if (bLoad) { Node imported; try { // xml content if (bIsRichText || // richtext or xml (sContentType.endsWith("+xml"))) { try { // add xml content InputStream is = new FileInputStream(oContent); try { // JAVAPORT_DATA - this is something of a departure from C++ // but it is unclear whether we would ever want this code to // actually execute for security reasons. imported = getOwnerDocument().loadIntoDocument(is).getFirstXMLChildElement(); } finally { try { is.close(); } catch (IOException ignored) { } } //AutoUniquifyIDsHelper oAUIHelper(doc); //imported = node.getOwnerDocument().importNode(startNode, TRUE); } catch (ExFull ex) { // failed to load so load it as text byte[] buffer = readBytes(oContent); String sData = new String(buffer, "UTF-8"); // JAVAPORT_DATA - would be a CDATASection in C++ imported = new TextNode(null, null, sData); } } // text else if (sContentType.startsWith("text/")) { // load the data into a stream and save byte[] buffer = readBytes(oContent); String sData = new String(buffer, "UTF-8"); // JAVAPORT_DATA - would be a CDATASection in C++ // create cData section imported = new TextNode(null, null, sData); } else { // TODO INTEGRATE THIS WITH THE IMAGE CACHE byte[] buffer = readBytes(oContent); String sData = Base64.encode(buffer, true); imported = new TextNode(null, null, sData); } } catch (IOException ex) { throw new ExFull(ex); } // append node and set to transient so it isn't saved out if (imported != null) { ((Element)node).appendChild(imported, true); imported.isTransient(true, true); } // get the first child of this node oFirstChild = node.getFirstXMLChild(); } } } */ } // skip over any comment nodes or processing instruction nodes while (firstChild != null) { if (!(firstChild instanceof Comment) && !(firstChild instanceof ProcessingInstruction)) break; firstChild = firstChild.getNextXMLSibling(); } // Strip any unnecessary white space before processing the node if (!bStripWhiteSpace && (bParentIsDataGroup || bParentIsDataModel) && (dataNodeAttr == null || (dataNodeAttr != null && dataNodeAttr.getAttrValue() == XFA.DATAGROUP))) { Node node2 = firstChild; while (node2 != null) { if (node2 instanceof Element) { // JAVAPORT_DATA may need to revisit this, since it diverges from C++. if (loadSpecialNode(null, (Element)node2, true)) { // check only node2 = node2.getNextXMLSibling(); continue; } } if (node2 instanceof Element) { bStripWhiteSpace = true; } else if (node2 instanceof Chars && !((Chars)node2).isXMLSpace()) { bStripWhiteSpace = false; break; } node2 = node2.getNextXMLSibling(); } } if (bStripWhiteSpace) { if (node instanceof Element) ((Element)node).removeWhiteSpace(); firstChild = node.getFirstXMLChild(); } // // Figure out what kind of data node to create, depending // on what kind of Dom node we're starting with. // DataWindow dw = mDataWindow; boolean bIsRecordDepth = false; // only need to know this (now) if lazy loading /*JAVAPORT_DATA if (mIncrLoadHandler != NULL && processRecords()) bIsRecordDepth = poDWImpl->isRecordDepth(node); // need to know */ if (node instanceof Element) { // // Check for an attribute named: "xfa:dataNode" which will give // a clue to our processing // // // If the parent is a dataValue, there's no point in checking for // an xfa:dataNode attribute, since the child can't be anything but // a dataValue. // if (!bParentIsDataValue && dataNodeAttr != null) { if (dataNodeAttr.getAttrValue().equals(XFA.DATAVALUE)) { bCreateDataValue = true; bOverride = true; } else if (dataNodeAttr.getAttrValue().equals(XFA.DATAGROUP)) { bCreateDataValue = false; bOverride = true; } } // If the xfa:contentType node is set to "text/html", ensure that // a dataValue is created. if (!bParentIsDataValue && (bIsRichText)) { bCreateDataValue = true; bOverride = true; } boolean bFoundText = false; boolean bFoundFirstElement = false; // If there's a text node, it's a datavalue. Node node3 = firstChild; while (node3 != null) { if (node3 instanceof TextNode) { bFoundText = true; break; } // Check the namespace of the first child element to see if it's // equal to the xhtml namespace. If so, create a dataValue instead // of a dataGroup. if (node3 instanceof Element) { String aNameSpaceURI = ((Element)node3).getNS(); if (!bFoundFirstElement && aNameSpaceURI == STRS.XHTMLNS) { bCreateDataValue = true; bOverride = true; bIsRichText = true; break; } bFoundFirstElement = true; } node3 = node3.getNextXMLSibling(); } if (!bOverride) { // // Empty nodes will be treated as data values // bCreateDataValue = bFoundText || bParentIsDataValue || firstChild == null; } if (!bOverrideDataName) { // don't overwite data aMappedName = aNodeLocalName; // TODO Post NewPort: // // This could be a potential problem. What if a user specifies // both a rename and a nameAttr? Currently the code is written // so that the nameAttr will win. The whole transformation // architecture, need to be revisited with respect order, // relationships etc. SS // grab mapped name if (mbHasMapping) { aMappedName = aRename; bOverrideDataName = true; } if (bHasNameAttr) { aMappedName = nameAttr.getAttrValue().intern(); bOverrideDataName = true; } } if (bCreateDataValue) { //pNewNode = XFADataValueImpl::NEW(pParent, node, this); newNode = createDataValueFromPeer(parent, node); if (bOverrideDataName) newNode.setPrivateName(aMappedName); if (bIsNull) newNode.setIsNull(true, true); if (bHasDataPictureFormat) newNode.setPictureFormat(sPictureFormat, "", false); } else { if (mIncrLoadHandler == null && processRecords()) // haven't figured this out yet, but we need it now bIsRecordDepth = dw.isRecordDepth(node); boolean bIsRecordGroup = bIsRecordDepth; if (bIsRecordGroup) { // Avoid calling isRecordGroup() because it also checks depth (which we know already). // Just check the name. if (maRecordName != null && maRecordName != aMappedName) { bIsRecordGroup = false; } } newNode = createDataGroupFromPeer(parent, node); // must create here after check to see if it is a valid record if (bOverrideDataName) newNode.setPrivateName(aMappedName); if (bIsRecordGroup) { DataNode dg = newNode; // Watson bug 1679357, only update bValidRecordLoaded if we actually load a record. bValidRecordLoaded.value = true; /* JAVAPORT_DATA if ((mIncrLoadHandler != NULL) && (mIncrLoadHandler->XSLStream() != NULL)) { // Transform individual records via the XSL script jfDomElement origElement(node); jfElementImpl *pElementImpl = (jfElementImpl*) &origElement.getObj(); jfMemoryStreamFile oXSLMem; // supply our own copy of result of XSL tranformation // in case we need to report what it is jfBool bException = FALSE; jfExFull oThrow(DOM_TRANSFORMED_FILE_ERR); jfDomElement newElement; try { newElement = jfDomElement((jfObjImpl*)pElementImpl->cloneNodeViaXSL(*mIncrLoadHandler->XSLStream(), &oXSLMem)); } catch (jfExFull & oEx) { bException = TRUE; oThrow.Insert(oEx,TRUE); } if (mIncrLoadHandler->XSLDebugStream() != NULL) { jfNodeImpl *pNodeImpl; // write opening tag { jfLiteral sStartTag (""); mIncrLoadHandler->XSLDebugStream()->Write(sStartTag); } if (bException) { // parsing failed; insert the contents of the memstream into a CDATA for the debug file jfString sData; oXSLMem.Position(jfFilePosition()); // reset memory stream oXSLMem.ReadFile(sData); jfDomCDATASection cdata(domDocument().createCDATASection(sData)); pNodeImpl = (jfNodeImpl*) &cdata.getObj(); } else { pNodeImpl = (jfNodeImpl*) &newElement.getObj(); } jfDocumentImpl *poDomDocumentImpl = (jfDocumentImpl*) &domDocument().getObj(); jfDomSaveOptions oOptions; oOptions.setDisplayFormat(jfDomSaveOptions::PRETTY_OUTPUT); oOptions.setExcludePreamble(TRUE); poDomDocumentImpl->saveAs(*mIncrLoadHandler->XSLDebugStream(), pNodeImpl, oOptions); // write closing tag { jfLiteral sEndTag (""); mIncrLoadHandler->XSLDebugStream()->Write(sEndTag); } } if (bException) throw oThrow; pNewNode->setDomPeer(newElement); XFAPermsHandler::replaceChild(node.getParentNode(), newElement, origElement); // node (i.e. origElement) is the next node on the incremental-load // stack -- replace it with newElement XFANodeImpl *pNextParent; jfDomNode nextResumeNode; if (mIncrLoadHandler->GetNext(&pNextParent, nextResumeNode)) { assert (nextResumeNode == node); mIncrLoadHandler->Push(pNextParent, newElement); } node = newElement; oFirstChild = node.getFirstChild(); // Reset the attrs and numAttrs variables. attrs = node.getAttributes(); if (attrs.isNull()) numAttrs = 0; else numAttrs = attrs.getLength(); // Finally, verify that the transformed node is still a // valid record. If it's not, the user's out of luck. if ( ! moDataWindow.isRecordGroup(dg)) JfExThrow (RecordInvalidAfterXSL); } END JAVAPORT_DATA*/ if (bFlatten && node instanceof Element) { Node child = firstChild; Node last = node.getLastXMLChild(); while (child != null) { Node next = child.getNextXMLSibling(); flattenNode((Element)node, child); if (child == last) break; child = next; } firstChild = node.getFirstXMLChild(); } mDataWindow.addRecordGroup(dg); } } } else if (node instanceof AttributeWrapper || node instanceof Chars) { // JAVAPORT_DATA was CDATA newNode = createDataValueFromPeer(parent, node); if (mbHasMapping && (node instanceof AttributeWrapper)) { if (aRename != "") newNode.setPrivateName(aRename); } if (mTransformations != null) newNode = mTransformations.transform(newNode); // No need to process any more. Attributes and Text nodes won't // have children to deal with... if (bHasNameAttr && nameAttr != null) //READD_NAMEATTR updateAttribute(nameAttr); return; } if (newNode == null) { if (bHasNameAttr && nameAttr != null) //READD_NAMEATTR updateAttribute(nameAttr); return; // Could be a comment or something else we're not interested in. } // make sure we read all attributes for a dataDescription boolean bSaveAttributesAreValues = attributesAreValues(); if (bParentIsDataDescription) { setAttributesAreValues(true); } if (node instanceof Element) { Element element = (Element)node; if (attributesAreValues()) { addAttributes(newNode, element, generator); } else { // still must scan for eval attributes... final int numAttrs = element.getNumAttrs(); for (int i = 0; i < numAttrs; i++) { Attribute domAttr = element.getAttr(i); String aLocalName = domAttr.getLocalName(); // disable execution of scripts in data in plug-in boolean bCheckOnly2 = ACROBAT_PLUGIN; // Check if it's one of our eval attributes. loadSpecialAttribute(domAttr, aLocalName, newNode, bCheckOnly2); } } } boolean bOnlyChild = firstChild != null; if (bOnlyChild) { // Look for another sibling, but ignore and comment or processing instruction nodes Node nextSibling = firstChild.getNextXMLSibling(); while (nextSibling != null) { if (!(nextSibling instanceof Comment) && !(nextSibling instanceof ProcessingInstruction)) break; nextSibling = nextSibling.getNextXMLSibling(); } bOnlyChild = nextSibling == null; } boolean bSetText = false; // If we're a DOM ELEMENT and have just one child, that child // will be our data value... i.e. don't treat it as a nested // datavalue child. if (bCreateDataValue && bOnlyChild && node instanceof Element) { if (firstChild instanceof TextNode) { newNode.singleTextChild((TextNode)firstChild); bSetText = true; } } if (!bSetText) { if (! bIsRichText && !bIsNull) { // Don't load children of a rich text dataValue Node child = node.getFirstXMLChild(); Node oLastChild = node.getLastXMLChild(); while (child != null) { Node nextSibling = child.getNextXMLSibling(); if ((child.getClass() == Element.class) || child.getClass() == TextNode.class) { loadDataNode(newNode, child, generator, bValidRecordLoaded); /*JAVAPORT_DATA if (mIncrLoadHandler != NULL && mIncrLoadHandler->BackOut()) { if (bParentIsDataModel) setAliasNode(XFANode((jfObjImpl*)getDataRoot())); if (bParentIsDataDescription) setAttributesAreValues(bSaveAttributesAreValues); if (bHasNameAttr && oNameAttr != null) //READD_NAMEATTR updateAttribute(oNameAttr); return; } END JAVAPORT_DATA*/ } // transform/dissolve appends nodes so break after processing the last child if (child == oLastChild) break; child = nextSibling; } } } if (mTransformations != null) newNode = mTransformations.transform(newNode); if (bHasNameAttr && nameAttr != null) //READD_NAMEATTR updateAttribute(nameAttr); if (bParentIsDataModel) { // Set our alias node ($data) to datasets.data. This is set here to handle // the odd case where somebody calls loadXML on an empty "datasets" node. setAliasNode(getDataRoot()); } if (bParentIsDataDescription) { // restore attributesAreValues; setAttributesAreValues(bSaveAttributesAreValues); } /*JAVAPORT_DATA if (bIsRecordDepth && (mIncrLoadHandler != NULL)) { // We've just finished loading a node at the level of a record. mIncrLoadHandler->BackOut(TRUE); } */ } // private byte[] readBytes(File file) throws IOException { // long length = file.length(); // length = Math.min(length, Integer.MAX_VALUE); // byte[] buffer = new byte[(int)length]; // InputStream oContentStream = new FileInputStream(file); // try { // int bytesRead = oContentStream.read(buffer); // assert bytesRead == buffer.length; // return buffer; // } // finally { // try { oContentStream.close(); } // catch (IOException ignored) { } // } // } private DataNode createDataValueFromPeer(Element parentNode, Node peerNode) { if (peerNode instanceof Element) ((Element)peerNode).inhibitPrettyPrint(true); return createDataNodeFromPeer(parentNode, peerNode, XFA.DATAVALUE, XFA.DATAVALUETAG); } private DataNode createDataGroupFromPeer(Element parentNode, Node peerNode) { return createDataNodeFromPeer(parentNode, peerNode, XFA.DATAGROUP, XFA.DATAGROUPTAG); } @FindBugsSuppress(code="ES") private DataNode createDataNodeFromPeer(Element parentNode, Node peerNode, String sClassName, int eClassTag) { DataNode dataNode = new DataNode(parentNode, null, null, null, null, null); dataNode.setClass(sClassName, eClassTag); dataNode.setModel(this); dataNode.setDocument(getDocument()); if (peerNode instanceof Element) ((Element)peerNode).setModel(this); peerNode.setDocument(getDocument()); peerNode.setXfaPeer(dataNode); dataNode.setXmlPeer(peerNode); return dataNode; } private void processRecords(boolean b) { mbProcessRecords = b; } private boolean processRecords() { return mbProcessRecords; } /** * For each data node under poDataParent, find and walk its corresponding data description to * hook up each data nodes to its data description node. * The primary keys within the data description are also registered with the document so that * they will be indexed. * * @param dataDescriptionParent * @param dataParent */ @FindBugsSuppress(code="ES") void processDataDescription(Element dataParent) { if (dataParent == null) return; DataNode dataDescriptionParent = getDataDesc(dataParent); if (dataParent == getDataRoot()) { // watson bug 1616608, ensure we apply the data description to each child // under , not just the first one. for (Node dataNode = dataParent.getFirstXFAChild(); dataNode != null; dataNode = dataNode.getNextXFASibling()) { // is there a data child for which the name matches a dataDescription?? dataDescriptionParent = getDataDescriptionRoot(dataNode.getName()); if (dataDescriptionParent != null) { DataNode dataDesc = (DataNode)dataDescriptionParent.locateChildByName(dataNode.getName(), 0); if (dataDesc != null && setDataDesc(dataDesc, (DataNode)dataNode)) { if (registerKeys(dataDesc)) dataDesc.getOwnerDocument().indexSubtree((DataNode)dataNode, false); connectDataNodesToDataDescription(dataDesc,dataNode); } } } } else if (dataDescriptionParent != null) { if (registerKeys(dataDescriptionParent)) dataDescriptionParent.getOwnerDocument().indexSubtree(dataParent, false); connectDataNodesToDataDescription(dataDescriptionParent, dataParent); } } void pushStack() { if (mIncrLoadHandler != null) mIncrLoadHandler.mnLoadNodeLevel++; } /** * @see Node#remove() */ public void remove() { AppModel appModel = getAppModel(); if (appModel != null && !isDuplicateDataModel()) { appModel.removePseudoModel("$dataWindow"); appModel.removePseudoModel("$record"); } super.remove(); } /** * @exclude from public api. */ public static void removeDDPlaceholderFlags(Node dataNode, boolean bDeep) { if (dataNode == null) return; if (dataNode instanceof DataNode) ((DataNode)dataNode).setIsDDPlaceholder(false); if (bDeep) { for (Node child = dataNode.getFirstXFAChild(); child != null; child = child.getNextXFASibling()) { removeDDPlaceholderFlags(child, true); } } else { // remove the placeholder flag on this node and the parents. Node parent = dataNode.getXFAParent(); if ((parent != null) && getDDPlaceholderFlag (parent)) { removeDDPlaceholderFlags (parent, false); } } } /** * @exclude from published api. */ protected void removeTextNodes() { Node nextSibling; for (Node child = getFirstXMLChild(); child != null; child = nextSibling) { nextSibling = child.getNextXMLSibling(); if (child instanceof TextNode) removeChild(child); } } /** * @exclude from published api. */ public void resetPostLoadXML() { // update data description processDataDescription(getDataRoot()); // Tell the data window that we have loaded xml if (mDataWindow != null) mDataWindow.updateAfterLoad(); } /** * @exclude from published api. */ public Node resolveRef(String sSOM, Element contextNode, boolean bDataValue, boolean bDefault /* = false */ ) { // we're here because we didn't find the node we wanted. // So - need to create it Node newNode = null; boolean bCreate = true; DataNode dataDescription = getDataDesc(contextNode); if (dataDescription != null) { // watson bug 1402895 if we have the schema disabled, make sure we still // create the data node even if it doesn't match the schema bCreate = mbDisableSchemaValidation; if (sSOM.startsWith("$data.")) { DataNode dataNode = (DataNode)resolveNodeCreate("$data", Element.CREATEACTION, false, false, false).object; String sRelSOM = sSOM.substring(6); DataNode dataDescForData = getRootOfDataDescription(dataDescription); if (dataDescForData != null) { newNode = createDataNodeFromDescription(dataDescForData, dataNode, sRelSOM, bDataValue,bDefault); } } else if (sSOM.startsWith("$record.")) { DataNode recordNode = (DataNode)resolveNodeCreate("$record", Element.CREATEACTION, false, false, false).object; String sRelSOM = sSOM.substring(8); // find the data description for the record node i.e. the data root child of DataNode dataDescRoot = getRootOfDataDescription(dataDescription); DataNode dataDescForRecord = (DataNode)dataDescRoot.getFirstXFAChild(); if (dataDescForRecord != null) { newNode = createDataNodeFromDescription(dataDescForRecord, recordNode, sRelSOM, bDataValue,bDefault); } } else if (sSOM.startsWith("!connectionData.")) { String sRelSOM = sSOM.substring(16); DataNode connectionData = (DataNode)resolveNodeCreate("!connectionData", Element.CREATEACTION, false, false, false).object; DataNode dataDescForConnection = getRootOfDataDescription(dataDescription); if (dataDescForConnection != null && connectionData != null) { newNode = createDataNodeFromDescription(dataDescForConnection, connectionData, sRelSOM, bDataValue,bDefault); } } else { newNode = createDataNodeFromDescription(dataDescription, contextNode, sSOM, bDataValue, bDefault); } } // default if the dataDescription didn't exist or couldn't help if (bCreate && newNode == null) { Node oNode = (Node)contextNode.resolveNodeCreate(sSOM, Element.CREATEACTION, bDataValue, bDefault, true).object; newNode = oNode; } return newNode; } Node createDataNodeFromDescription(DataNode dataDescriptionParent, Element dataParent, String sSom, boolean bDataValue, boolean bDefault) { // figure out what already exists and what needs to be created List nodesToCreate = new ArrayList(); ObjectHolder dataDescriptionParentHolder = new ObjectHolder(dataDescriptionParent); ObjectHolder dataParentHolder = new ObjectHolder(dataParent); if (!determineWhatToCreate(sSom, dataParentHolder, dataDescriptionParentHolder, nodesToCreate)) return null; dataDescriptionParent = dataDescriptionParentHolder.value; dataParent = dataParentHolder.value; // Create the node(s) for (int i = 0; i < nodesToCreate.size(); i++) { String sNewNode = (String)nodesToCreate.get(i); int occIndex = 0; int nBraceStart = sNewNode.indexOf('['); if (nBraceStart != -1) { int nBraceEnd = sNewNode.indexOf(']'); if (nBraceEnd != -1) { String sNum = sNewNode.substring(nBraceStart + 1, nBraceEnd); if (sNum.equals("*")) occIndex = -1; // to flag multiple else { try { occIndex = Integer.parseInt(sNum); } catch(NumberFormatException ex) { } } } sNewNode = sNewNode.substring(0, nBraceStart); // clear the [...] from the end } DataNode dataDescriptionNode = getDataDescriptionChild(dataDescriptionParent, sNewNode); if (dataDescriptionNode == null) return null; Element newNode; if (dataDescriptionNode.getXmlPeer() instanceof AttributeWrapper) { // if we just created the parent element then we should have created and loaded the // attribute during the element creation - so recheck in case the attribute was // created since the first check Element attrNode = getDataChild(dataParent, sNewNode); if (attrNode != null) { newNode = attrNode; } else { Node firstElement = getFirstElementChild(dataParent); // easy case - just add the attribute before any elements newNode = createNodeFromDataDescription(dataDescriptionNode, dataParent, firstElement, bDefault); } } else { // TODO Acrobat 10. look to see if we already have an instance of the requested node // if we find one we can simply create another instance if it doesn't violate the max occurrence. // This will save us from the more expensive step of going through populateParent which should only be called // if you need to create the first instance or a new group. newNode = (DataNode)populateParent(dataParent, null, dataDescriptionParent, dataDescriptionNode, occIndex, bDefault); } clearDataDescriptionInfo(newNode); dataParent = newNode; if (dataParent == null) // sanity check - we don't expect this return null; dataDescriptionParent = dataDescriptionNode; } if (dataParent != null && (dataParent.getClassTag() == XFA.DATAVALUETAG) != bDataValue) { // sanity checking - mismatch on value vs. group - just return null return null; } return dataParent; } private boolean determineWhatToCreate(String sSom, ObjectHolder dataParentHolder, ObjectHolder dataDescriptionParentHolder, List nodesToCreate) { // figure out what already exists - keeping in mind that we're here because we need to create at least the // leaf node in the som expr - so make sure we don't pick up some existing, mapped node Element dataParent = dataParentHolder.value; DataNode dataDescriptionParent = dataDescriptionParentHolder.value; List nodeList = new ArrayList(); IntegerHolder nOffset = new IntegerHolder(); while (true) { String sNode = getSOMSection(sSom, nOffset); if (sNode.length() == 0) break; nodeList.add(sNode); } int i; for (i = 0; i < nodeList.size(); i++) { Element tmpNode = dataParent; String sTestNameExists = nodeList.get(i); // boolean bMultiple = sTestNameExists.indexOf('*') != -1; // if (bMultiple) { // // find last child with this name (if any) // poDataParent = getDataChild(poDataParent, sTestNameExists); // TODO jak // } // else { dataParent = getDataChild(dataParent, sTestNameExists); // } if (dataParent == null) { dataParent = tmpNode; break; } if (getDataDesc(dataParent) != null) { dataDescriptionParent = getDataDesc(dataParent); } else { // jak TODO - seems like we shouldn't need the 'else' case - figure out what's missing where dataDescriptionParent = getDataDescriptionChild(dataDescriptionParent, sTestNameExists); } if (dataDescriptionParent == null) { return false; } } if (dataParent != null && i == nodeList.size()) { // found a leaf node - need to back up to last multiple and create new while (true) { String sNodeName = nodeList.get(--i); dataParent = (DataNode)dataParent.getXFAParent(); dataDescriptionParent = (DataNode)dataDescriptionParent.getXFAParent(); if (sNodeName.indexOf('*') != -1) { // got multiple - reset to make new from this point break; } if (i == 0) { // at the top of the list - no multiples - give up return false; } } } for ( ; i < nodeList.size(); i++) { nodesToCreate.add(nodeList.get(i)); } dataParentHolder.value = dataParent; dataDescriptionParentHolder.value = dataDescriptionParent; return true; } // get a child of a data node based on a som reference private Element getDataChild(Element parent, String sNewNodeRef) { if (sNewNodeRef.equals("$")) return parent; StringHolder sNewNode = new StringHolder(sNewNodeRef); int nIndex = getIndexFromString(sNewNode); return (DataNode)parent.locateChildByName(sNewNode.value.intern(), nIndex); } // get the index from the string, also remove [....] from the string private int getIndexFromString(StringHolder sRef) { int nRet = 0; int nBraceStart = sRef.value.indexOf('['); if (nBraceStart != -1) { int nBraceEnd = sRef.value.indexOf(']', nBraceStart); if (nBraceEnd != -1) { String sNum = sRef.value.substring(nBraceStart + 1, nBraceEnd); if (! "*".equals(sNum)) { try { nRet = Integer.parseInt(sNum); } catch (NumberFormatException ex) { } } } sRef.value = sRef.value.substring(0, nBraceStart); } return nRet; } // GetSOMSection is a helper routine which examines a SOM expression and extracts // the next section (i.e. a part between '.' separators). Note this function // handles escaped '.' characters (i.e. "\.") which indicates a '.' character which // is part of the name rather than a separator. The section returned will have // escape characters removed. // Returns true is section found, false otherwise. private static String getSOMSection(String sSom, IntegerHolder nOffset) { if (nOffset.value >= sSom.length()) return ""; StringBuilder sSection = new StringBuilder(); // JavaPort: The C++ code never increments nOffset, which results in an infinite look. Is this ever called? while (nOffset.value < sSom.length()) { char cUni = sSom.charAt(nOffset.value); nOffset.value++; if (cUni == '\\') { if (nOffset.value < sSom.length()) { cUni = sSom.charAt(nOffset.value); nOffset.value++; sSection.append(cUni); } } else if (cUni == '.') { if (sSection.length() > 0) break; } else { sSection.append(cUni); } } return sSection.toString(); } private DataNode getDataDescriptionChild(DataNode parent, String sNewNodeRef) { if (sNewNodeRef.equals("$")) return parent; StringHolder sNewNode = new StringHolder(sNewNodeRef); // TODO jak - send/use the index in case same named siblings have different models // EG int nIndex = getIndexFromString(sNewNode); return getDataDescriptionChild(parent, sNewNode.value, nIndex); } private DataNode getDataDescriptionChild(DataNode parent, String sNewNode, int nIndex) { String aNewNode = sNewNode.intern(); for (DataNode child = (DataNode)parent.getFirstXFAChild(); child != null; child = (DataNode)child.getNextXFASibling()) { if (isDataDescriptionNS(child.getNS())) { if (isDataDescriptionGroup(child)) { // search the children of the group DataNode grandChild = getDataDescriptionChild(child, sNewNode, nIndex); if (grandChild != null) { return grandChild; } } // ignore any other dd namespaced element } else { if (child.getName() == aNewNode) { // Check if the node is a possible valid match. // TODO Acrobat 10, ensure that we have not already consumed all the // valid instance of a node. int nMax = getMaxOccurFromDataDescription(child, true); if (nMax < 0 || nMax >= nIndex) return child; // reduce the required index nIndex -= nMax; } } } return null; } /** * @see Element#serialize(OutputStream, DOMSaveOptions, int, Node) * * @exclude from published api. */ public void serialize(OutputStream os, DOMSaveOptions options, int level, Node prevSibling) throws IOException { // JAVAPORT_DATA - Serialization of the DataModel happens by serializing its // peer - in much the same way that the C++ implementation does. getXmlPeer().serialize(os, options, level, prevSibling); } void setAttributesAreValues(boolean bAttributesAreValues) { mbAttributesAreValues = bAttributesAreValues; } // for data descriptions. static boolean setDataDesc(DataNode desc, DataNode dataNode) { if (desc.getClassTag() != dataNode.getClassTag()) { // change the data to the appropriate type. if (dataNode.getClassTag() == XFA.DATAGROUPTAG) dataNode = changeType(dataNode, true); else if (dataNode.getClassTag() == XFA.DATAVALUETAG) dataNode = changeType(dataNode, false); else { ExFull ex = new ExFull( new MsgFormat(ResId.InvalidNodeTypeException, dataNode.getClassAtom())); Model model = dataNode.getModel(); model.addXMLLoadErrorContext(dataNode, ex); model.addErrorList(ex, LogMessage.MSG_WARNING, dataNode); return false; } } if (dataNode.getClassTag() == XFA.DATAGROUPTAG) { if (dataNode.getDataDescription() == null) { dataNode.setDataDescription(desc); return true; } } else if (dataNode.getClassTag() == XFA.DATAVALUETAG) { if (dataNode.getDataDescription() == null) { dataNode.setDataDescription(desc); return true; } } return false; } void setDataWindowParameters(int nDataWindowRecordsBefore, int nDataWindowRecordsAfter) { mnDataWindowRecordsBefore = nDataWindowRecordsBefore; mnDataWindowRecordsAfter = nDataWindowRecordsAfter; mbDataWindowRecordsSpecified = true; } void setExcludeNSList(String newExcludeNSString) { String whiteSpace = "[\n\t\r ]+"; String[] tokens = newExcludeNSString.split(whiteSpace); for (int i = 0; i < tokens.length; i++) { String token = tokens[i].intern(); mExcludeNSList.put(token, Boolean.TRUE); } } /** * @exclude from published api. */ protected void setRangeOptions(String sRanges) { RecordRangeFilter poFilter = new RecordRangeFilter(mDataWindow, sRanges); mDataWindow.addFilter(poFilter); } /** * @exclude from published api. */ protected void setRecordOptions(int recordLevel, String recordName) { mnRecordLevel = recordLevel; maRecordName = recordName == null ? null : recordName.intern(); } // Access to options for XFADataModelFactoryImpl to set on creation. void setStartNodeSOMString(String newSOMString) { mStartNodeSOMString = newSOMString; } /** * @exclude from public api. */ public void setSourceSetLink(SourceSetLink sourceSetLink) { if (mSourceSetLink != null) { //mSourceSetLink.close(); mSourceSetLink = null; } if (sourceSetLink != null) { mSourceSetLink = (SourceSetLink)sourceSetLink.clone(); } } /** * @exclude from published api. */ public void setSaveFormat(int format) { if (meFormat == format) return; // the data node under datasets... DataNode data = getDataRoot(); // No xfa:data node! Not much we can do in this case if (data == null) { throw new ExFull(ResId.UNSUPPORTED_OPERATION, "DataModel#setSaveFormat - no xfa:data node"); } // JavaPort: Only support FMT_XML_DATA for now. else if (format != FMT_XML_DATA) { throw new ExFull(ResId.UNSUPPORTED_OPERATION, "DataModel#setSaveFormat - XPF format"); } meFormat = format; } void setTransformations(DataTransformations transformations) { mTransformations = transformations; } void singleTextChild(TextNode child) { //mSingleTextChild = child; } /** * Apply any defined transformations for this node * * @param parent * parent of the node being transformed * @param node * the node being transformed */ void transformNode(Element parent, DataNode node) { int eOperation = EnumAttr.UNDEFINED; String aMappedName = ""; String aNodeLocalName = node.getLocalName(); eOperation = mTransformations.getOperation(aNodeLocalName, EnumType.PRESENCE); // String aRename = mTransformations.getMapName(aNodeLocalName); // if (aRename != "") // mbHasMapping = true; // else // mbHasMapping = false; // Find out if there was a nameAttr specified. String aNameAttr = mTransformations.getNameAttr(aNodeLocalName); // If we have a nameAttr, we really want to ask the XFADataValue if // it has an attribute with the name sNameAttr. If it does, then // we will get it's value and this is what we will use to rename // the node as. (Only on the XFA side). // Additionally we want to make sure that the nameAttr attribute // does // not get promoted to a data value - so remove it (on dom side) // but add it again after XFA nodes have been created or before // returning. int attrIndex = -1; if (aNameAttr != "") { attrIndex = node.findAttr(null, aNameAttr); if (attrIndex != -1) { node.setPrivateName(aMappedName); } } // Ugh. XFADataTransformations class needs to be re-worked. String sPictureFormat = mTransformations.getPictureFormat(aNodeLocalName); if (sPictureFormat != "") node.setPictureFormat(sPictureFormat, "", false); boolean bExclude = (eOperation == EnumAttr.NODEPRESENCE_IGNORE) || (eOperation == EnumAttr.NODEPRESENCE_DISSOLVE) || excludeNS(node.getNS()); boolean bFlatten = (eOperation == EnumAttr.NODEPRESENCE_DISSOLVESTRUCTURE); if (bFlatten) { Node next; for (Node child = node.getFirstXFAChild(); child != null; child = next) { next = child.getNextXFASibling(); flattenNode(node, child); } } if (bExclude) { throw new ExFull(ResId.UNSUPPORTED_OPERATION, "DataModel#transformNode"); // // this is an excluded namespace (or node), so ignore it and dig // through // // the children of the node // // NodeList children = node.getChildNodes(); // int numChildren = children.getLength(); // // Node child(node.getFirstChild()); // for (int i = 0; i < numChildren; i++) // a for loop is required: // transform/dissolve appends nodes // { // Node nextChild(child.getNextSibling()); // // // if ignoring an element, ignore all its text nodes too // if (child.getNodeType() != Node::TEXT_NODE && // child.getNodeType() != Node::CDATA_SECTION_NODE) // { // if (mIncrLoadHandler != null && mIncrLoadHandler.BackOut()) // { // if (bParentIsDataModel) // setAliasNode(Node((jfObjImpl*)getDataRoot())); // // if(bHasNameAttr) { // setAttribute(oNameAttrNS, oNameAttrQName, oNameAttr, // oNameAttrValue); // } // popStack(); // Always pop the stack before returning // return; // } // loadNode(pParent, child, generator); // } // child = nextChild; // } // if (eOperation==EnumAttr.NODEPRESENCE_DISSOLVE) // { // dissolveNode(pParent.getDomPeer(), node); // } // // if (bIsDatasets && (getDataRoot() == null)) // { // // If an xfa:data node was not parsed out of the file, create an // orphan node // // and peer a new data group to it. This will be $data. // Document doc(node.getOwnerDocument()); // // Node dataNode(doc.createElementNS (XFADataModel::dataNS(), // XFASTRS::getString(aXFASTRS_XFADATA))); // XFANodeImpl *pData = XFADataGroupImpl::NEW(this, dataNode, this); // // // Set our alias node ($data) to datasets.data // setAliasNode(Node((jfObjImpl*)getDataRoot())); // } // if (mIncrLoadHandler != null && mbProcessRecords) // { // XFADataWindowImpl *poDWImpl = (XFADataWindowImpl*) // moDataWindow.getObjPtr(); // if (poDWImpl.isRecordDepth(node)) // { // // We've just finished loading a node at the level of a record. // mIncrLoadHandler.BackOut(true); // } // } // // if(bHasNameAttr) { // setAttribute(oNameAttrNS, oNameAttrQName, oNameAttr, // oNameAttrValue); // } // popStack(); // Always pop the stack before returning // return; } mTransformations.transform(node); } private boolean validateUsage(int nVersion, int nAvailability, boolean bFatalError, boolean bUpdateVersion) { // watson bug 1705675, the data model only has one version and that is 1.0, // so we must use the target version to do all our checking. AppModel appModel = getAppModel(); // first check if an output version is set int eOutputBelow = appModel.getOutputBelow(); int nTargetOutputVer = appModel.getVersionRestriction(); boolean bRet = bFatalError ? false : true; if (nTargetOutputVer != 0 && !isVersionCompatible(nVersion, nTargetOutputVer) && eOutputBelow != EnumAttr.OUTPUTBELOW_UPDATE) { if (bFatalError) { if (eOutputBelow == EnumAttr.OUTPUTBELOW_ERROR) bRet = true; } else { bRet = false; } } // todo check availability when the appModel stores this info // size_t nTargetAvail = poAppModel->getTargetAvailability() // bRet &= nTargetAvail & nAvailability return bRet; } /** * @see Model#validateUsage(int, int, boolean) * @exclude from published api. */ public boolean validateUsage(int nXFAVersion, int nAvailability, boolean bUpdateVersion) { return validateUsage(nXFAVersion, nAvailability, false, bUpdateVersion); } /** * @see Model#validateUsageFailedIsFatal(int, int) * @exclude from published api. */ public boolean validateUsageFailedIsFatal(int nXFAVersion, int nAvailability) { return validateUsage(nXFAVersion, nAvailability, true, false); } /** * @exclude from published api. */ public void disableSchemaValidation(boolean bMode) { mbDisableSchemaValidation = bMode; } /** * @exclude from published api. */ public String getHeadNS() { return STRS.XFADATANS_CURRENT; //return dataNS(); } /** * @exclude from published api. */ public int getHeadVersion() { return Schema.XFAVERSION_10; } // Portions of this code implement AdobePatentID="1082" final static String patentRef = "AdobePatentID=\"B1082\""; /** * @exclude from published api. */ public static Obj resolveAssociation(Element dataNode, String aAssociationName, BooleanHolder foundNullAssociation /* = NULL */) { if (foundNullAssociation != null) foundNullAssociation.value = false; DataNode dataDesc = getDataDesc(dataNode); if (dataDesc != null) { // // Search the data description for a matching dd:association: // for (DataNode child = (DataNode)dataDesc.getFirstXFAChild(); child != null; child = (DataNode)child.getNextXFASibling()) { Element assoc = (Element)child.getXmlPeer(); if (assoc.getNS() == STRS.DATADESCRIPTIONURI && assoc.getLocalName() == XFA.ASSOCIATION) { int index = assoc.findAttr(STRS.DATADESCRIPTIONURI, XFA.NAME); if (index != -1) { if (assoc.getAttr(index).getAttrValue().equals(aAssociationName)) { List keyNodeAddressList = new ArrayList(); index = assoc.findAttr(STRS.DATADESCRIPTIONURI, XFA.MAPPEDBY); if (index != -1) { String sReflectedAssoc = assoc.getAttrVal(index); // // First construct our own primary key: // index = dataDesc.findAttr(STRS.DATADESCRIPTIONURI, XFA.PRIMARYKEY); StringTokenizer tokenizer = new StringTokenizer(index == -1 ? "" : dataDesc.getAttrVal(index), ","); while (tokenizer.hasMoreTokens()) keyNodeAddressList.add(tokenizer.nextToken().trim()); Key primaryKey = dataNode.constructKey(keyNodeAddressList, dataDesc); // // Now get the recipe for foreign key construction from the key definition in the // reflected association: // Element reflectedAssoc = (Element)findAssociation(getRootOfDataDescription(dataDesc), sReflectedAssoc); index = reflectedAssoc.findAttr(STRS.DATADESCRIPTIONURI, XFA.KEY); tokenizer = new StringTokenizer(index == -1 ? "" : reflectedAssoc.getAttrVal(index), ","); // Parse and atomize the list: keyNodeAddressList.clear(); while (tokenizer.hasMoreTokens()) keyNodeAddressList.add(tokenizer.nextToken().trim()); // // Get the target node type from the association, resolving any namespace prefix it // might have. // StringHolder aTargetNamespacePrefix = new StringHolder(); StringHolder aTargetName = new StringHolder(); index = assoc.findAttr(STRS.DATADESCRIPTIONURI, XFA.TARGET); String sTarget = index == -1 ? "" : assoc.getAttrVal(index); Element.explodeQName(sTarget, aTargetNamespacePrefix, aTargetName); String aTargetNamespaceURI = assoc.resolvePrefix(aTargetNamespacePrefix.value); // // Then search all data nodes of target type for a foreign key which is equal to // our primary key. All nodes which match get returned. // NodeList targetList = new ArrayNodeList(); Element dataRoot = dataNode.getModel().getAliasNode(); getReflectedNodes(dataRoot, aTargetNamespaceURI, aTargetName.value, primaryKey, keyNodeAddressList, reflectedAssoc, targetList); return targetList; } else { // // Get the recpe for the key from the association and then construct it based // on data stored under us: // index = assoc.findAttr(STRS.DATADESCRIPTIONURI, XFA.KEY); StringTokenizer tokenizer = new StringTokenizer(index == -1 ? "" : assoc.getAttrVal(index), ","); while (tokenizer.hasMoreTokens()) keyNodeAddressList.add(tokenizer.nextToken().trim()); Element sourceElem = dataNode; // // Note that the association may have a maxOccur > 1, so we have to allow for the // construction of a list of keys, not just a single key. // List keys = new ArrayList(); sourceElem.constructKeys(keyNodeAddressList, assoc, keys); NodeList targetList = new ArrayNodeList(); for (int j = 0; j < keys.size(); j++) { // // Look up the target nodes based on the keys: // Element targetElem = sourceElem.getOwnerDocument().getElementByPKey(keys.get(j)); if (targetElem != null) targetList.append(targetElem.getXfaPeer()); } if (targetList.length() == 1) return targetList.item(0); else if (targetList.length() > 1) return targetList; } if (foundNullAssociation != null) foundNullAssociation.value = true; return null; } } } } } return null; } /** * Find a data description node's ancestor. * @exclude from published api. */ public static DataNode getRootOfDataDescription(Node dataDesc) { if (dataDesc == null) return null; if (isDataDescriptionNode(dataDesc)) return (DataNode)dataDesc; return getRootOfDataDescription(dataDesc.getXFAParent()); } /** * Recursive search of a data description for a with a particular name. The * first hit is returned. * @exclude from published api. */ @FindBugsSuppress(code="ES") public static Node findAssociation(Node dataDesc, String aAssociationName) { if (dataDesc instanceof Element) { Element dataDescElement = (Element)dataDesc; if (dataDescElement.getNS() == STRS.DATADESCRIPTIONURI && dataDesc.getName() == XFA.ASSOCIATION) { int index = dataDescElement.findAttr(STRS.DATADESCRIPTIONURI, XFA.NAME); if (index != -1 && dataDescElement.getAttrVal(index).equals(aAssociationName)) return dataDesc; } } for (Node child = dataDesc.getFirstXFAChild(); child != null; child = child.getNextXFASibling()) { Node association = findAssociation(child, aAssociationName); if (association != null) return association; } return null; } /** * Recursive search of a data model for nodes of a particular target QName whose foreign key * matches the supplied primary key. All hits are returned. * @exclude from published api. */ @FindBugsSuppress(code="ES") public static void getReflectedNodes(Node dataNode, String aTargetNamespaceURI, String aTargetName, Key targetKey, List oFKeyValueAddressList, Node namespaceContextNode, NodeList results) { if (dataNode instanceof DataNode) { Element domNode = (Element) ((DataNode)dataNode).getXmlPeer(); if (domNode.getNS() == aTargetNamespaceURI && domNode.getName() == aTargetName) { // // Candidate target node; build a list of foreign keys and see if any of them match the primary key: // List foreignKeys = new ArrayList(); domNode.constructKeys(oFKeyValueAddressList, namespaceContextNode, foreignKeys); for (int i = 0; i < foreignKeys.size(); i++) { if (foreignKeys.get(i).equals(targetKey)) { results.append(dataNode); break; } } // // Relational data models should not nest persistent elements // return; } } for (Node child = dataNode.getFirstXFAChild(); child != null; child = child.getNextXFASibling()) { getReflectedNodes(child, aTargetNamespaceURI, aTargetName, targetKey, oFKeyValueAddressList, namespaceContextNode, results); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy