com.adobe.xfa.Model Maven / Gradle / Ivy
Show all versions of aem-sdk-api Show documentation
/*
* ADOBE CONFIDENTIAL
*
* Copyright 2005 Adobe Systems Incorporated All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains the property of
* Adobe Systems Incorporated and its suppliers, if any. The intellectual and
* technical concepts contained herein are proprietary to Adobe Systems
* Incorporated and its suppliers and may be covered by U.S. and Foreign
* Patents, patents in process, and are protected by trade secret or copyright
* law. Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained from
* Adobe Systems Incorporated.
*/
package com.adobe.xfa;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import org.xml.sax.Attributes;
import com.adobe.xfa.content.Content;
import com.adobe.xfa.ut.Assertions;
import com.adobe.xfa.ut.BooleanHolder;
import com.adobe.xfa.ut.ExFull;
import com.adobe.xfa.ut.FindBugsSuppress;
import com.adobe.xfa.ut.LcLocale;
import com.adobe.xfa.ut.MsgFormatPos;
import com.adobe.xfa.ut.Numeric;
import com.adobe.xfa.ut.ResId;
import com.adobe.xfa.ut.ResourceLoader;
import com.adobe.xfa.ut.StringUtils;
import com.adobe.xfa.ut.SymbolTable;
//Adobe Patent or Adobe Patent Pending Invention Included Within this File
/**
* An abstract class from which to derive all other models.
*/
public abstract class Model extends Element implements Element.DualDomNode {
/**
* A marker interface that indicates that a Model's contents are dual-DOM,
* as opposed to the model Element itself, which is always dual-DOM.
* @exclude from published api.
*/
public interface DualDomModel {
}
/**
* @exclude from published api.
*/
public static abstract class Publisher {
/**
* See {@link Model#publish(Publisher)} for a description of this method.
*/
public abstract String updateExternalRef(Node oNode, int eAttribute,
String sExternalRefValue);
public abstract int getTargetVersion();
public abstract int getTargetAvailability();
}
/**
* Helper function for isCompatibleNS
* @param aNS - namespace of node being checked
* @param aModelNS - namespace of model being checked against.
* @exclude from published api.
*/
public static boolean checkforCompatibleNS(String aNS, String aModelNS) {
if (aNS == null)
return false; // atom doesn't exist
if (aNS.length() == 0)
return false;
int nNSLen = aModelNS.length();
if (aNS.length() >= nNSLen) {
if (aNS.startsWith(aModelNS))
return true;
}
return false;
}
/**
* For use by derived classes only.
* @exclude from published api.
*/
@FindBugsSuppress(code="ES")
protected static Model getNamedModel(AppModel oAppModel, String aModelName) {
Node child = oAppModel.getFirstXFAChild();
while (child != null) {
if (child.getClassName() == aModelName)
return (Model) child;
child = child.getNextXFASibling();
}
return null;
}
/**
* @exclude from published api.
*/
public ScriptTable getScriptTable() {
return ModelScript.getScriptTable();
}
/**
* @param startModel
* @param aShortCutName.
*
* @exclude from published api.
*/
static Node lookupShortCut(Model startModel, String sShortCutName) {
if (startModel.shortCutName().equals(sShortCutName))
return startModel.getAliasNode();
// Hunt for children which are models.
for (Node child = startModel.getFirstXFAChild(); child != null; child = child.getNextXFASibling()) {
if (child instanceof Model) {
Node retNode = lookupShortCut((Model) child, sShortCutName);
if (retNode != null)
return retNode;
}
}
return null;
}
static void resolveList(List extends ProtoableNode> oPendingProtos, boolean bResolveExternalOnly) {
int nPos = 0;
int nLen = oPendingProtos.size();
while(nLen > 0 && nPos != nLen) {
ProtoableNode oObject = oPendingProtos.get(nPos);
boolean bRemoved = oObject.performResolveProtos(bResolveExternalOnly);
// Note: if someone removes a node from oPendingProtos it will be after the current node
if (!bRemoved)
nPos++;
// make sure the length is correct because a node maybe removed from oPendingProtos
// by performResolveProtos
nLen = oPendingProtos.size();
}
}
private static final boolean ACROBAT_PLUGIN = ResourceLoader.loadProperty("ACROBAT_PLUGIN").equalsIgnoreCase("true");
private boolean mbLoading; // true if in loadchildren
/**
* Normalize the namespaces of the nodes under this model
*
* @exclude from published api.
*/
protected boolean mbNormalizedNameSpaces;
private boolean mbReady; // true if ready() has been called
/**
* @exclude from published api.
*/
protected boolean mbValidateTextOnLoad = true;
private boolean mbWillDirtyDoc;
private final List mErrorContextList = new ArrayList();
/**
* List of errors encountered during load
*/
private final List mErrorList = new ArrayList();
private Generator mGenerator;
private IDValueMap mIDValueMap;
private String mName;
private int mnCurrentVersion;
private Element mAliasNode; // The node referred to by the alias (eg. $data)
private ProcessingInstruction moOriginalVersion;
/**
* For performance save the protos.
*/
private final List moProtoList = new ArrayList();
private Node moSOMContext; // set by setContext & used in resolveNode
/**
* Similar to moUseList, but for USEHREF nodes.
*/
private final List moUseHRefList = new ArrayList();
/**
* For performance save the use (reference) nodes
*/
private final List moUseList = new ArrayList();
private AppModel mAppModel;
private String msCachedLocale; // see getCachedLocale
private final Schema mSchema;
private final String mShortCutName; // eg. $data
/**
* used to perform SOM parsing
*/
private SOMParser mSOMParser;
/**
* This is used to make serialization behaviour consistent with C++.
* In some cases, whether the AppModel node is transient is specific
* to a particular model (i.e., the DOM that model is associated with).
*/
private boolean mbAppModelIsTransient;
/**
* Used to optimize the interning of attribute values.
*/
private SymbolTable mSymbolTable;
private boolean mbAllowUpdates;
private Element mXmlPeer; // Our XML tree peer.
/**
* @exclude from published api.
*/
protected int mnOriginalVersion = 0;
/**
* @exclude from published api.
*/
@FindBugsSuppress(code="ES")
protected Model(Element parent, Node prevSibling, String uri, String qName,
String name, String aShortCutName, int classTag, String className,
Schema schema) {
super(parent, prevSibling, uri, name, qName, null, classTag, className);
// AppModel always creates with a null parent
if (getClass() == AppModel.class)
assert parent == null;
if (parent != null) {
// If a Model has a parent, it must be an AppModel
if (parent.getClass() != AppModel.class)
throw new IllegalArgumentException("parent is not an AppModel.");
}
mShortCutName = aShortCutName;
mSchema = schema;
// setClassAtom not called since this class doesn't exist on its own
setModel(this);
if (name.length() > 0)
setName(name);
//
// Move up the hierarchy until we reach the top level model.
//
Element currentParent = parent;
if (currentParent != null) {
parent = parent.getXFAParent();
while (parent != null) {
currentParent = parent;
parent = parent.getXFAParent();
}
assert(currentParent instanceof AppModel);
if (currentParent instanceof AppModel)
setAppModel((AppModel)currentParent);
}
// default the version on the model to the head
setCurrentVersion(getHeadVersion());
// JavaPort: In C++ this is done in the loadChildren method
if (uri != "" && uri != null) {
int nVersion = getVersion(uri);
setCurrentVersion(nVersion);
}
}
/** @exclude */
protected void addAttributes(Element pParent, Attribute[] oAttributes, Generator generator) {
// do nothing
}
/**
* @exclude from published api.
*/
public final void addErrorList(ExFull error,
int /* LogMessage.LogMessageSeverity */ eSeverity,
Element context) {
mErrorList.add(error);
// Keep track of all the nodes where errors occurred.
// That way if we want, we can try and correct some errors
// in a post-load fixup operation.
mErrorContextList.add(context);
LogMessage oMsg = new LogMessage();
oMsg.insertMessage(error, eSeverity, getCachedLocale());
getLogMessenger().sendMessage(oMsg);
}
/**
* @exclude from public api.
*/
public final void removeLastError() {
if (mErrorList.size() > 0)
mErrorList.remove(mErrorList.size() - 1);
}
/**
* @exclude from published api.
*/
protected void removeOriginalVersionNode() {
if (moOriginalVersion != null) {
// remove the node from the tree
moOriginalVersion.remove();
}
}
void addProto(ProtoableNode poProto) {
moProtoList.add(poProto);
}
/**
* add a new new node to the useNodeHrefList
* @exclude from published api.
*/
public void addUseHRefNode(Element poUseHRefNode) {
//
// Watson 1799140: discard non-protoable elements from list.
//
if (poUseHRefNode instanceof ProtoableNode)
moUseHRefList.add((ProtoableNode)poUseHRefNode);
}
/**
* add a new new node to the useNodeList
* @exclude from published api.
*/
public void addUseNode(Element poUse) {
// Javaport: new for Java. Ensure entries are unique!
for (int i = 0; i < moUseList.size(); i++)
if (poUse == moUseList.get(i))
return;
//
// Watson 1799140: discard non-protoable elements from list.
//
if (poUse instanceof ProtoableNode)
moUseList.add((ProtoableNode)poUse);
}
/**
* This method is called by derived classes to output the filename and line
* number when an error occurs during the load of an XML File.
*
* @exclude from published api.
*/
protected final void addXMLLoadErrorContext(int lineNumber,
String fileName, ExFull oEx) {
if (!StringUtils.isEmpty(fileName) && lineNumber != 0) {
MsgFormatPos oMessage = new MsgFormatPos(
ResId.XMLFileLineNumberLoadException);
oMessage.format(fileName);
oMessage.format(Integer.toString(lineNumber));
ExFull err2 = new ExFull(oMessage);
oEx.insert(err2, true);
}
else if (!StringUtils.isEmpty(fileName)) {
MsgFormatPos oMessage = new MsgFormatPos(ResId.XMLFileLoadException);
oMessage.format(fileName);
ExFull err2 = new ExFull(oMessage);
oEx.insert(err2, true);
}
else if (lineNumber > 0) {
MsgFormatPos oMessage = new MsgFormatPos(
ResId.XMLLineNumberLoadException);
oMessage.format(Integer.toString(lineNumber));
ExFull err2 = new ExFull(oMessage);
oEx.insert(err2, true);
}
}
/**
* This method is called by derived classes to output the filename and
* line number when an error occurs during the load of an XML File.
* @exclude from published api.
*/
public void addXMLLoadErrorContext(Node oSrc, ExFull oEx) {
Node oNode = oSrc;
// If possible give more context by providing the filename and
// the line number of the offending operation.
String sFileName = oNode.getOwnerDocument().getParseFileName();
if (sFileName == null)
sFileName = "";
// Could be a #text node
if (oNode instanceof Chars) {
oNode = oNode.getXMLParent();
}
String sLineNumber = "";
if (oNode instanceof Element) {
int nLineNumber = ((Element) oNode).getLineNumber();
sLineNumber = Integer.toString(nLineNumber);
}
if (sFileName.length() > 0 && sLineNumber.length() > 0) {
MsgFormatPos oMessage = new MsgFormatPos(
ResId.XMLFileLineNumberLoadException);
oMessage.format(sFileName);
oMessage.format(sLineNumber);
ExFull pErr2 = new ExFull(oMessage);
oEx.insert(pErr2, true);
} else if (sFileName.length() > 0) {
MsgFormatPos oMessage = new MsgFormatPos(ResId.XMLFileLoadException);
oMessage.format(sFileName);
ExFull pErr2 = new ExFull(oMessage.resId(), oMessage.toString());
oEx.insert(pErr2, true);
} else if (sLineNumber.length() > 0) {
MsgFormatPos oMessage = new MsgFormatPos(
ResId.XMLLineNumberLoadException);
oMessage.format(sLineNumber);
ExFull pErr2 = new ExFull(oMessage.resId(), oMessage.toString());
oEx.insert(pErr2, true);
}
}
/*
* Does this model notify protoable nodes of any changes from their source nodes?
* this should be TRUE only for models that are allowed to change at runtime.
* In Designer the template model should turn this on
* In Acrobat and XMLFormAgent/PresentationAgent the form model should have this on.
* @return true
if this model can be modified at runtime, otherwise false
.
* @exclude from published api.
*/
boolean allowUpdates() {
return mbAllowUpdates;
}
/**
* Sets whether this model can be updated at runtime.
* This should only be true
for Designer.
* @param bAllowUpdates - true
if this model can be modified at runtime, otherwise false
.
* @exclude from published api.
*/
public void allowUpdates(boolean bAllowUpdates) {
mbAllowUpdates = bAllowUpdates;
}
/**
* Clears the model's current list of errors.
*/
public void clearErrorList() {
mErrorList.clear();
mErrorContextList.clear();
}
/**
* @see Element#clone(Element)
*
* @exclude from published api.
*/
public Element clone(Element parent, boolean bDeep) {
// Models are not to be cloned
MsgFormatPos oMessage = new MsgFormatPos(ResId.InvalidMethodException, getClassAtom());
oMessage.format("clone");
throw new ExFull(oMessage);
}
/**
* Runs through all the installed factories, calling createDOM on each, and
* appending those nodes to parent.
* @param parent
* root of the XML DOM
*
* @exclude from published api.
*/
protected void createDOM(Element parent) {
List factoryList = parent.getAppModel().factories();
int count = factoryList.size();
for (int i = 0; i < count; i++) {
ModelFactory factory = factoryList.get(i);
factory.createDOM(parent);
}
}
/**
* Creates an element with the given parent, previous sibling,
* namespace uri and qualified name.
* @param parent the element's parent, if any.
* @param prevSibling the element's previous sibling, if any.
* @param uri the element's namespace URI. This string must be interned.
* @param qName the element's qualified name. This string must be interned.
* @return a new element conformant to our schema.
*/
public Element createElement(Element parent, Node prevSibling, String uri, String qName) {
return createElement(parent, prevSibling, uri, qName, qName, null, 0, null);
}
/**
* Creates an element with the given parent, sibling, namespace uri,
* local name and SAX attributes.
* @param parent the element's parent, if any.
* @param prevSibling the element's previous sibling, if any.
* @param uri the element's namespace. This string must be interned.
* @param localName the element's name. This string must be interned.
* @param qName the element's qualified name. This string must be interned.
* @param attributes the element's (SAX) attribute definitions.
* @return a new element conformant to our schema.
*
* @exclude from published api.
*/
public Element createElement(Element parent, Node prevSibling, String uri,
String localName, String qName, Attributes attributes,
int lineNumber, String fileName) {
super.setLineNumber(lineNumber);
Element retVal = null;
int eTag = XFA.INVALID_ELEMENT;
//
// Ensure we use the proper model schema.
//
Schema oSchema = getSchema();
Model model = null;
if (parent != null)
model = parent.getModel();
if (parent instanceof Model) {
model = (Model)parent;
oSchema = model.getSchema();
}
else if (parent instanceof Element) {
model = parent.getModel();
oSchema = model.getSchema();
}
try {
if (parent != null &&
(parent.getElementClass() == XFA.INVALID_ELEMENT ||
parent.getElementClass() == XFA.PACKETTAG)) {
// We hit a schema error, or an exceptional case,
// so create a plain element node that's not
// part of the XFA Schema
retVal = new Element(parent, prevSibling, uri, localName,
qName, attributes, XFA.INVALID_ELEMENT, localName);
return retVal;
}
eTag = oSchema.getElementTag(uri, localName);
if (eTag < 0) {
//
// If invalid then give the schema one last opportunity
// to determine if the tag, in the context of its parent,
// is potentially valid. This is used by configuration in the
// places where we allow arbitrary syntax in the config file.
//
eTag = oSchema.getElementTag(parent);
}
if (eTag < 0)
throw new ExFull(ResId.InvalidNodeTypeException, localName);
boolean bValid = true;
if (parent != null && ! parent.isValidChild(eTag, 0, true, false)) {
bValid = false;
int eRemappedTag = remapTag(eTag);
if (eTag != eRemappedTag && parent.isValidChild(eRemappedTag, 0, true, false)) {
eTag = eRemappedTag;
bValid = true;
}
}
retVal = oSchema.getInstance(eTag, this, parent, prevSibling, true);
// Validate the parent/child relationship.
if ( ! bValid) {
MsgFormatPos msg
= new MsgFormatPos(ResId.InvalidChildAppendException,
parent.getClassName());
msg.format(localName);
ExFull err = new ExFull(msg);
// See if we can further qualify the error.
// Was it a case of "wrong number of occurrences"?
// If the schema says the relationship is ok, then it must have
// been an occurrence problem.
if (null != parent.getChildReln(retVal.getClassTag())) {
ExFull err2 = new ExFull(
ResId.OccurrenceViolationException, localName);
err.insert(err2, true);
}
addXMLLoadErrorContext(lineNumber, fileName, err);
// Mark this instance with an invalid class tag so that we'll
// know
// to exclude it from normal XFA Node processing.
retVal.setClass(retVal.getClassName(), XFA.INVALID_ELEMENT);
// TODO figure out what the error severity is
addErrorList(err, LogMessage.MSG_WARNING, retVal);
}
} catch (ExFull e) {
boolean bReportError = true;
//ignore any element belonging to an unrecognized namespace
if (uri != null && uri.length() > 0 && model != null) {
if (!model.isCompatibleNS(uri)) {
//
// Watson #1354332
//
// Make sure elements in the template packet that are not in the XFA template namespace
// are not pretty printed. Outputting elements from unrecognized namespaces without pretty
// printing could be generalized to all models in XFAModelImpl::loadChildren, using a check with
// isCompatibleNS, but it would mean a performance hit for every model and every element,
// as XFAModelImpl::checkforCompatibleNS calls strncmp
if (retVal != null)
retVal.inhibitPrettyPrint (true);
bReportError = false;
}
}
// We hit a schema error, so create a plain element node that's not
// part of the XFA Schema
//Bug#2940586: In case of schema error, DONOT pass in the attributes since they'll anyway be
//created while setting DOM properties (scroll down a bit).
retVal = new Element(parent, prevSibling, uri, localName, qName,
null, XFA.INVALID_ELEMENT, localName);
// TODO figure out what the error severity is
if (bReportError) {
addXMLLoadErrorContext(lineNumber, fileName, e);
addErrorList(e, LogMessage.MSG_WARNING, retVal);
}
}
retVal.setLineNumber(lineNumber);
//
// Re-classify element if the context demands it.
//
retVal.setClass(parent, eTag);
retVal.setDOMProperties(uri, localName, qName, attributes);
return retVal;
}
/**
* Create an element with the given element tag and name.
* @param eTag the element's tag.
* @param name the element's name, if known. This string must be interned.
* @return a new element conformant to the XFA schema.
*/
final public Element createElement(int eTag, String name) {
String className = XFA.getAtom(eTag);
Element e = createElement(null, null, null, className, className, null, 0, null);
if (name != null && name.length()> 0)
e.setName(name);
return e;
}
/**
* Creates an element with the given class, name and parent.
* @param className the element's class name. This string must be interned.
* @param name the element's name, if known. This string must be interned.
* @param parent the element's parent.
* @return a new element conformant to the XFA schema.
*/
final public Element createElement(String className, String name, Element parent) {
// Javaport: This method exists to provide consistency with the C++:
// XFAModelImpl::createNode(size_t, XFANodeImpl*, jfAtom, jfAtom, jfBool)
// That method ignores the uri parameter, resulting in a node that
// inherits its Model's namespace.
// This gets called from ProtoableNode.createProto
Element e = createElement(parent, null, parent.getModel().getNS(), className, className, null, 0, null);
if (name != null && name.length()> 0)
e.setName(name);
return e;
}
/**
* Creates an element with the given name.
* @param name the element's name. This string must be interned.
* @return a new element conformant to our schema.
*
* @exclude from published api.
*/
public Element createElement(String name) {
return createElement(null, null, null, name, name, null, 0, null);
}
/**
* Create an element with the given tag, parent, name and uri.
* @param eTag the element's tag.
* @param parent the element's parent.
* @param aName the element's name.
* @param aNS the element's namespace.
* @param bDoVersionCheck check the element's version.
* @return a new element.
* @exclude from published api.
*/
public abstract Node createNode(
int eTag,
Element parent,
String aName /* = "" */,
String aNS /* = "" */,
boolean bDoVersionCheck /* = true */);
/**
* @exclude from published api.
*/
final public TextNode createTextNode(Element parent, Node prevSibling,
char[] ch, int start, int length) {
return new TextNode(parent, prevSibling, ch, start, length);
}
/**
* Creates a text node with the given text.
* @param parent the node's parent, if any.
* @param prevSibling the node's previous sibling, if any.
* @param text the node's text.
* @return a new node conformant to our schema.
*/
final public TextNode createTextNode(Element parent, Node prevSibling,
String text) {
return new TextNode(parent, prevSibling, text);
}
/**
* Returns the node that is represented by the alias for this model.
*
* The returned model is normally the model itself, but not always.
* For example, while $template refers to the template model, by
* default, $data refers to the first child of the data model.
*
* @return an Node that corresponds to the alias for this model.
* @exclude from public api.
*/
public final Element getAliasNode() {
if (mAliasNode == null)
return this;
return mAliasNode;
}
/**
*
* @exclude from published api.
*/
public AppModel getAppModel() {
return mAppModel;
}
/**
* @exclude from public api.
*/
public boolean getAppModelIsTransient() {
return mbAppModelIsTransient;
}
/**
* @exclude from published api.
*/
public abstract String getBaseNS();
/**
* @exclude from published api.
*/
public String getCachedLocale() {
if (StringUtils.isEmpty(msCachedLocale)) {
LcLocale oLocale = new LcLocale(LcLocale.getLocale());
if (! oLocale.isValid())
oLocale = new LcLocale(LcLocale.DEFAULT_LOCALE);
msCachedLocale = oLocale.getIsoName();
}
return msCachedLocale;
}
/**
* Retrieves the current node, which is the starting node for calls to
* resolveNode()
and resolveNodes()
* @return The current node.
*
* @exclude from published api.
*/
public final Node getContext() {
if (moSOMContext == null)
return this;
return moSOMContext;
}
/**
* Gets the current version of this model.
* @return the version number (times 10).
*/
public int getCurrentVersion() {
if (mnCurrentVersion == 0)
return getHeadVersion();
if (mnCurrentVersion > getHeadVersion())
return getHeadVersion();
return mnCurrentVersion;
}
/**
* @exclude from published api.
*/
public Obj getDeltas(Node oNode) {
return new XFAList();
}
/**
* Returns this model's document.
* @return the document node.
*/
public final Document getDocument() {
return getOwnerDocument();
}
/**
* @exclude from published api.
*/
public Obj getDelta(Element node, String sSOM) {
return new Delta(node, null, sSOM);
}
/**
* Gets all the context nodes that correspond to entries in the error list.
*
* @return A list of Element objects where the load discovered a problem.
*/
public List getErrorContextList() {
return mErrorContextList;
}
/**
* Gets all the errors that have been generated by this model
* since the last method call to clear the error list.
* Note that these are not fatal errors.
* They are typically syntax problems discovered when loading the
* collateral. Some applications may choose to sift through the list
* and stop processing if they recognize specific problems. However
* most applications should simply dump the messages into the log file
* and continue processing. i.e. treat this as a list of warnings.
* @return the current list of {@link ExFull} error objects.
*/
public List getErrorList() {
return mErrorList;
}
/**
* the EventManager manages xfe:script scripts and their
* associated events (ie. events that cause the scripts to execute)
* @exclude from public api.
*/
public EventManager getEventManager() {
return getAppModel().getEventManager();
}
/**
* @exclude from published api.
*/
public Generator getGenerator() {
return mGenerator;
}
/**
* @exclude from published api.
*/
public abstract String getHeadNS();
/**
* @exclude from published api.
*/
public int getHeadVersion() {
return Schema.XFAVERSION_HEAD;
}
/**
* @exclude from published api.
*/
public IDValueMap getIDValueMap() {
return mIDValueMap;
}
/**
* Gets the boolean value of a particular legacy setting.
* @param nLegacyFlag the specific legacy setting to check
* @return true
if the legacy setting is set, or is the default the original version.
* @exclude from published api.
*/
public boolean getLegacySetting(AppModel.LegacyMask nLegacyFlag) {
// overridden by AppModel and TemplateModel
// no one should be calling this version
assert(false);
return false;
}
/**
* Retrieves the log messenger associated with this model.
* @return The log messenger.
*
* @exclude from published api.
*/
public LogMessenger getLogMessenger() {
return getAppModel().getLogMessenger();
}
/**
* @see Element#getName()
*
* @exclude from published api.
*/
final public String getName() {
if (mName != null)
return mName;
return super.getName();
}
/**
* @exclude from published api.
*/
public boolean getNeedsNSNormalize() {
return mbNormalizedNameSpaces;
}
/**
* Look up an XFA ID. Since XFA IDs are only guaranteed to be unique within the scope of
* their model (to prevent conflicts with customer-controlled data), the lookup is done
* by namespace.
*
* Note: jfDomDocument::getElementByXFAId handles the conversion of versioned model namespace
* strings to unversioned ones.
* @param aID
* The node id
* @return The node if found, null if not found.
*
* @exclude from published api.
*/
public Element getNode(String aID) {
String aModelNamespace = getNSInternal();
Element oElement = getOwnerDocument().getElementByXFAId(aModelNamespace, aID);
if (oElement == null)
return null;
else
return oElement.getXfaPeer();
}
/**
* @see Element#getNS()
*
* @exclude from published api.
*/
public String getNS() {
int nVersion = getCurrentVersion();
// if we have a version set use it.
if (nVersion > 0)
return getNS(nVersion);
else if (getAppModel().getSourceBelow() == EnumAttr.SOURCEBELOW_UPDATE)
return getHeadNS();
else
return super.getNS();
}
/**
* @exclude from published api.
*/
protected String getNS(int nVersion) {
String sNS = "";
if (nVersion > 0) {
StringBuilder sNSBuf = new StringBuilder(getBaseNS());
sNSBuf.append(nVersion);
sNSBuf.insert(sNSBuf.length() - 1, '.');
sNSBuf.append('/');
sNS = sNSBuf.toString().intern();
// JavaPort: Original version creates does many object allocations
// double dVersion = ((double)nVersion)/10.0;
// String sNSBuf = getBaseNS() + Numeric.doubleToString(dVersion, 1, false) + '/';
//sNS = sNSBuf.intern();
}
return sNS;
}
/**
* @exclude from published api.
*/
public String getOriginalVersion(boolean bDefault) {
if (moOriginalVersion != null) {
// moOriginalVersion is not connected to the tree, delete it
if (moOriginalVersion.getXMLParent() == null) {
moOriginalVersion = null;
}
else
return moOriginalVersion.getData();
}
if (moOriginalVersion == null && bDefault) {
//This part is being handled in TemplateModel class.
//Instead of current NameSpace it should return empty like it does on C++ side.
return "";
//return getNS();
}
return "";
}
/**
* Get the processing instruction that holds our version number.
* @param bCreate - if true, create the PI if it doesn't exist
* @param sValue - the value to use for the PI if we create it
* @return the version node PI
* @exclude from published api.
*/
public ProcessingInstruction getOriginalVersionNode(boolean bCreate, String sValue /* "" */) {
if ((moOriginalVersion == null || moOriginalVersion.getXMLParent() == null) && bCreate) {
moOriginalVersion = new ProcessingInstruction(null, null, STRS.ORIGINALXFAVERSION, sValue);
//watson 1217329 do not append the PI if we are in the plugin, if we do we will
//break document signatures.
if (!ACROBAT_PLUGIN)
appendChild(moOriginalVersion, false);
}
return moOriginalVersion;
}
/**
* Keep track of proto Nodes and 'use' tags for quick lookup later our
* @return our list of protos
*
* @exclude from published api.
*/
public List getProtoList() {
return moProtoList;
}
/**
* Return the schema definition for this model.
* @return a class derived from Schema
*
* @exclude from published api.
*/
final public Schema getSchema() {
return mSchema;
}
/**
* Get the ScriptHandler associated with the specified language.
* @param sLanguageName the language name (should not include any "application/x-" prefix).
* @return The ScriptHandler registered for the specified language, or null if no
* handler has been registered for that language.
*
* @exclude from published api.
*/
ScriptHandler getScriptHandler(String sLanguageName) {
return getAppModel().getScriptHandler(sLanguageName);
}
/**
* @exclude from published api.
*/
public int getSourceBelow() {
return getAppModel().getSourceBelow();
}
/**
* @exclude from published api.
*/
@FindBugsSuppress(code="ES")
public int getVersion(String sNS) {
int nRet = 0;
if (!StringUtils.isEmpty(sNS)) {
// search for the template namespace version
String sBaseNS = getBaseNS();
int nStart = sNS.indexOf(sBaseNS);
if (nStart >= 0) {
nStart = nStart + sBaseNS.length();
int nLast = sNS.lastIndexOf('/');
if (nLast > nStart) {
String sNum = sNS.substring(nStart,nLast);
double dValue = Double.parseDouble(sNum);
nRet = (int) (dValue * 10);
}
}
}
return nRet;
}
/**
* @exclude from public api.
*/
public void setXmlPeer(Node peer) {
mXmlPeer = (Element)peer;
}
/**
* @exclude from public api.
*/
public Node getXmlPeer() {
return mXmlPeer;
}
/**
* Initialize the symbol table that is used to optimize the
* interning of Attribute values.
*
* The {@link Model#intern(String)} method will work correctly
* whether or not there is a SymbolTable initialized, but can
* significantly speed up loading of a document from file, so it
* will typically only be enabled during loading.
*
* @exclude from published api.
*/
final void initializeSymbolTable() {
mSymbolTable = new SymbolTable();
}
/**
* Save memory used by the SymbolTable once it is no longer needed
* (i.e., no longer loading a document).
*
* @exclude from published api.
*/
final void disposeSymbolTable() {
mSymbolTable = null;
}
/**
* Load a node from one DOM into another.
* In the C++ implementation, this would load a node from the XML DOM into the XFA DOM.
* In this implementation, this method exists to handle the case of a form packet used
* to restore state that has been parsed into a generic packet, but now needs to be loaded
* into an XFA FormModel DOM.
* @param parent the XFA Form parent of the node that is to be loaded
* @param node the generic packet node that is to be loaded
* @param genTag
* @return the loaded node
* @exclude from published api.
*/
protected Node doLoadNode(Element parent, Node node, Generator genTag) {
Model model = parent.getModel();
if (node instanceof Chars) {
return model.createTextNode(parent, null, ((Chars)node).getData());
}
assert node instanceof Element;
Element element = (Element)node;
Element newNode;
try {
int eTag = XFA.getElementTag(element.getLocalName());
if (eTag == -1)
throw new ExFull(new MsgFormatPos(ResId.InvalidNodeTypeException, element.getLocalName()));
newNode = getSchema().getInstance(eTag, this, parent, null, true);
newNode.setDOMProperties(element.getNS(), element.getLocalName(), element.getXMLName(), null);
}
catch (ExFull exception) {
// This method is called by derived classes to output the
// filename and line number when an error occurs during the
// load of an XML File.
addXMLLoadErrorContext(node, exception);
addErrorList(exception, LogMessage.MSG_WARNING, null);
return null;
}
doLoadAttributes(element, newNode);
// iterate though all the children and load them
Node nextChild;
for (Node child = node.getFirstXMLChild(); child != null; child = nextChild) {
nextChild = preLoadNode(newNode, child, genTag);
// preLoad could remove the child
if (child.getXFAParent() == null)
break;
if (child instanceof Element || child instanceof Chars) {
doLoadNode(newNode, child, genTag);
}
}
if (newNode instanceof Content && newNode.getFirstXFAChild() == null)
new TextNode(newNode, null, "");
return newNode;
}
/**
* Load the attributes from one node to another.
* This method was extracted from {@link #doLoadNode(Element, Node, Generator)}
* @param fromElement the element that attributes should be copied from
* @param toElement the elemen that attributes should be copied to
* @exclude from published api.
*/
protected void doLoadAttributes(Element fromElement, Element toElement) {
final int numAttrs = fromElement.getNumAttrs();
for (int i = 0; i < numAttrs; i++) {
Attribute attr = fromElement.getAttr(i);
String aName = attr.getLocalName();
// JavaPort: This is different in Java since in C++, any xsi:nil attribute will have
// already been "copied" to the loaded node because it was peered with an XML DOM element.
// Ignore XSI nil attr; allow the node to control the use of the attribute.
// (Watson 1321484).
if (attr.isXSINilAttr()) {
toElement.updateAttribute(attr);
continue;
}
// load Attribute checks for eval attributes
// check for proto-related attributes
boolean bHandled = saveProtoInformation(toElement, aName, i == 0);
// Nothing we recognize yet? Check if it fits our schema.
if (!bHandled) {
int eTag = XFA.getAttributeTag(aName);
if (eTag == -1 || !toElement.isValidAttr(eTag, true, null)) {
if (!attr.isNameSpaceAttr()) { //ignore the namespace attr
MsgFormatPos message = new MsgFormatPos(ResId.InvalidAttributeLoadException);
message.format(aName);
message.format(fromElement.getName());
message.format(fromElement.getLineNumber());
addErrorList(new ExFull(message), LogMessage.MSG_WARNING, null);
}
}
else {
// Copy the Attribute - we don't need to duplicate since Attributes are immutable
toElement.setAttribute(attr, eTag);
}
}
}
}
/**
* Determine if a specified namespace string is compatible with the
* namespace of this model. Essentially this determines if the two
* namespaces are equivalent (though the strings that represent them may not
* be identical).
* @param aNS
* The namespace to compare.
*
* @exclude from published api.
*/
public boolean isCompatibleNS(String aNS) {
return checkforCompatibleNS(aNS,getBaseNS());
}
/**
* @exclude from published api.
*/
public final boolean isContainer() {
return true;
}
/**
* @exclude from published api.
*/
public final boolean isLoading() {
return mbLoading;
}
/**
* @exclude from published api.
*/
protected final void isLoading(boolean bIsLoading) {
mbLoading = bIsLoading;
}
/**
* @exclude from published api.
*/
public boolean isVersionCompatible(int nVersion, int nTargetVersion) {
return nVersion <= nTargetVersion;
}
/**
* @exclude from published api.
*/
public void loadNode(Element parent, Node node, Generator genTag) {
//
// Search for a model factory which can initialize itself
// on this node.
//
if (parent instanceof AppModel) {
AppModel appModel = (AppModel)parent;
List factoryList = appModel.factories();
int nFactoryCount = factoryList.size();
for (int i = 0; i < nFactoryCount; i++) {
ModelFactory factory = (ModelFactory) factoryList.get(i);
if (factory.isRootNode((AppModel)node, null, node.getName())) {
//
// Strip any unneccesary white space before processing
// the node. We do not strip white space if any children
// that are text or cdata section node's have
// non-whitespace values.
//
boolean stripWhiteSpace = true;
Node oNode = node.getFirstXMLChild();
while (oNode != null) {
if (oNode instanceof TextNode) {
if (! ((TextNode) oNode).isXMLSpace()) {
stripWhiteSpace = false;
break;
}
}
else if (oNode instanceof Chars) {
if (! ((Chars) oNode).isXMLSpace()) {
stripWhiteSpace = false;
break;
}
}
oNode = oNode.getNextXMLSibling();
}
// watson bug 2324810, if we remove the whitespace node while incremental loading it can
// cause problems for XML parser which may be holding references to these whitespace nodes.
if (stripWhiteSpace && node instanceof Element && !node.getOwnerDocument().isIncrementalLoad()) {
((Element) node).removeWhiteSpace();
}
Node oParentNode = parent;
//
// first check to see if a model already exists.
// If so, call add, not loadChildren
//
Model oNewModel = factory.findModel(oParentNode);
if (oNewModel == null) {
oNewModel = factory.newModel(appModel, node,
null, node.getName(), "", null);
// JavaPort: This is incomplete.
// oNewModel.domDocument(node.getOwnerDocument());
// oNewModel.scanForOriginalVersionNode(node);
// load all our children
// oNewModel.loadChildren(node, genTag);
}
else {
// load all our children
// JavaPort: This is incomplete.
// oNewModel.add(node, genTag);
}
oNewModel.postLoad();
return;
}
}
//
// Everything below that's not a model is a packet.
// We no longer create generic XFANodes when we load.
//
//getAppModel().loadPacket(node);
return;
}
}
/**
* loadSpecialAttribute should be called by the loadNode method. It scans for
* special attributes. Currently it only looks for event-related attributes
* (for automatic execution of script). The bCheckOnly flag can be set to TRUE
* to cause this routine to do nothing except return whether or not the
* attribute would be handled.
* @return true
if the attribute is handled
* @exclude
*/
@FindBugsSuppress(code="ES")
public boolean loadSpecialAttribute(Attribute attr,
String aLocalName,
Element element,
boolean bCheckOnly) {
// Ignore XSI nil attr; allow the node to control the use of the attribute.
if (attr.isXSINilAttr())
return true;
// check namespace
String ns = attr.getNS();
if (ns == null || !attr.getNS().startsWith(STRS.XFAEVENTSNS))
return false;
if (aLocalName == XFA.SCRIPT) {
if (bCheckOnly)
return true;
EventManager tm = getEventManager();
String sEventName = element.getEvent().trim();
if (!StringUtils.isEmpty(sEventName)) {
// parse sEvent (individual events are separated by blanks).
// just look at the first event... we use to be able to wait for
// multiple events to happen but this functionality has been dropped
int index = sEventName.indexOf(' ');
if (index != -1)
sEventName = sEventName.substring(0, index);
assert !StringUtils.isEmpty(sEventName);
if (StringUtils.isEmpty(sEventName))
return true;
String sEventContext = "$";
index = sEventName.indexOf(':');
if (index != -1) {
sEventContext = sEventName.substring(0, index);
sEventName = sEventName.substring(index + 1);
}
int nEventID = tm.getEventID(sEventName);
ScriptDispatcher sd = new ScriptDispatcher(element,
sEventContext,
nEventID,
tm,
element.getEventScript(null),
element.getEventContentType(null));
tm.registerEvents(sd);
}
return true;
}
else if (aLocalName == XFA.EVENT) {
return true;
}
else if (aLocalName == XFA.CONTENTTYPE) {
return true;
}
return false;
}
/**
* loadSpecialNode should be called by the loadNode method. It checks
* for special nodes. Currently it only looks for event-related nodes
* (for automatic execution of script).
* @return true
if the node is handled
* @exclude from published api.
*/
public boolean loadSpecialNode(Element parent, Node node, boolean bCheckOnly) {
if (!(node instanceof Element))
return false;
Element element = (Element)node;
String aNodeLocalName = element.getLocalName();
if (aNodeLocalName != XFA.SCRIPT)
return false;
// Having efficiently checked that the node name is not "script",
// do a more costly check of the namespace.
String ns = element.getNS();
if (ns == null || !element.getNS().startsWith(STRS.XFAEVENTSNS))
return false;
if (bCheckOnly)
return true;
if (element.getNumAttrs() != 0) {
EventManager tm = getEventManager();
final int len = element.getNumAttrs();
for (int i = 0; i < len; i++) {
Attribute attr = element.getAttr(i);
if (attr.getLocalName() != XFA.EVENT)
continue;
// check namespace
if (!attr.getNS().startsWith(STRS.XFAEVENTSNS))
continue;
String sEventName = attr.getAttrValue().trim();
// parse sEvent (individual events are separated by blanks).
// just look at the first event... we used to be able to wait for
// multiple events to happen but this functionality has been dropped
int index = sEventName.indexOf(' ');
if (index != -1)
sEventName = sEventName.substring(0, index);
assert !StringUtils.isEmpty(sEventName);
if (StringUtils.isEmpty(sEventName))
continue;
String sEventContext = "$";
index = sEventName.indexOf(':');
if (index != -1) {
sEventContext = sEventName.substring(0, index);
sEventName = sEventName.substring(index + 1);
}
int nEventID = tm.getEventID(sEventName);
ScriptDispatcher sd = new ScriptDispatcher(parent,
sEventContext,
nEventID,
tm,
parent.getEventScript(element),
parent.getEventContentType(element));
tm.registerEvents(sd);
break;
}
}
return true;
}
/**
* Load and append the XML document to pParent
* @param parent the parent node to the xml content being loaded
* @param is XML document
* @param bIgnoreAggregatingTag - TRUE if the root node of the loaded
* XML should be ignored. In this case, the children of the root node
* will be appended to pParent. FALSE if the root node
* itself will be appended to pParent.
* @param eReplaceContent ReplaceContent.AllContent if all XFA and non-XFA DOM content
* under e
is to be replaced with the result of the load. ReplaceContent.XFAContent
* if XFA content only is to be cleared before adding the result of the load.
* ReplaceContent.None
if the result of the load is to be appended to the content under e
* without clearing any existing content.
*
* @exclude from public api.
*/
@FindBugsSuppress(code="ES")
protected void loadXMLImpl(Element parent, InputStream is, boolean bIgnoreAggregatingTag, ReplaceContent eReplaceContent) {
if (parent instanceof DualDomNode &&
!(parent instanceof Model || parent instanceof Packet)) {
assert false;
// JAVAPORT_DATA: in order to support loadXML correctly, we load into an existing
// model in-place. This is in contrast to the C++ where the XML is parsed into
// an XML DOM, then loaded into the XFA DOM. Any DualDomModel implementations
// must override this method.
MsgFormatPos msg = new MsgFormatPos(ResId.UnsupportedOperationException);
msg.format("loadXML");
msg.format("");
throw new ExFull(msg);
}
if (eReplaceContent == ReplaceContent.AllContent) {
Node child = parent.getFirstXMLChild();
while (child != null) {
Node nextChild = child.getNextXMLSibling();
child.remove();
child = nextChild;
}
}
else if (eReplaceContent == ReplaceContent.XFAContent) {
Node child = parent.getFirstXFAChild();
while (child != null) {
Node nextChild = child.getNextXFASibling();
child.remove();
child = nextChild;
}
}
Document doc = getDocument();
doc.setContext(this, parent, bIgnoreAggregatingTag);
doc.load(is, null, false);
// 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(DESCENDENT_VALUE_CHANGED, "", parent);
}
/**
* @exclude from published api.
*/
public boolean loadRootAttributes() {
return false;
}
/**
* @exclude from published api.
*/
public void modelCleanup(Node node) {
int nodeTag = node.getClassTag();
if (node instanceof ProtoableNode) {
ProtoableNode proto = (ProtoableNode)node;
// Don't reduce fragment references - we may have a default override to a non-default fragment property.
// Cleaning up defaults would potentially lose local overrides.
if (proto.hasExternalProto())
return;
// Watson 1592035: Ditto for proto references, for the same reason as above.
if (proto.hasProto())
return;
}
// Don't attempt to reduce rich text nodes.
// They may have non-standard attributes.
if (XFA.RICHTEXTNODETAG == nodeTag
|| XFA.XMLMULTISELECTNODETAG == nodeTag
|| XFA.INVALID_ELEMENT == nodeTag)
return;
if (node instanceof TextNode)
nodeCleanup(node, false, false);
// If there are comments or processing instructions, don't clean up.
if (!(node instanceof Element))
return;
Element pNode = (Element) node;
int nChildren = pNode.getXFAChildCount();
for (int i = nChildren; i > 0; i--) {
Node pChild = pNode.getXFAChild(i - 1);
// process the grandchildren first. When that's done, this
// node may end up empty as well.
modelCleanup(pChild);
}
boolean bHasNonDefaultAttributes = false;
try {
for (int j = pNode.getNumAttrs(); j > 0; j--) {
String aName = pNode.getAttrName(j - 1);
String sVal = pNode.getAttrVal(j - 1);
String aNS = pNode.getAttrNS(j-1);
if (aNS == "") {
aNS = getNS();
}
//Certain attributes we do not want to cleanup
int eTag = getSchema().getAttributeTag(aNS, aName);
if (eTag != -1 && doAttributeCleanup(pNode, eTag, sVal)) {
pNode.removeAttr(j - 1);
} else {
bHasNonDefaultAttributes = true;
}
}
} catch (ExFull e) {
// If we encounter attributes that don't conform to the schema,
// there will
// be an exception thrown. We don't want to stop processing in this
// case...
}
// if (pNode.getLocalName() == XFA.EXTRAS ||
// pNode.getLocalName() == XFA.DESC ||
// pNode.getLocalName() == XFA.AREA ) {
// nodeCleanup(pNode, bHasNonDefaultAttributes, false);
// return;
// }
Element pParent = pNode.getXFAParent();
int parentTag = pParent.getClassTag();
if (pNode.getFirstXFAChild() == null && !bHasNonDefaultAttributes) {
ChildReln pSchema = pParent.getChildReln(nodeTag);
if (pSchema.getOccurrence() == ChildReln.zeroOrOne) {
// Call the virtual method to see if the model can do any
// schema-specific cleanup
nodeCleanup(pNode, bHasNonDefaultAttributes, false);
return;
}
if (pSchema.getOccurrence() == ChildReln.zeroOrMore) {
if ((parentTag == XFA.BORDERTAG) && (nodeTag == XFA.CORNERTAG)) {
nodeCleanup(pNode, bHasNonDefaultAttributes, false);
return;
}
}
if (pSchema.getOccurrence() == ChildReln.oneOfChild) {
// watson 1081879: the UI oneOfChild tells us what the object
// is...
// it should never be stripped out and should never be transient
// (if transient - won't get saved - and value get stipped out
// we have no way telling what kind of object it is)
if ((parentTag == XFA.UITAG) &&
(pNode.isDefault(false)) &&
(nodeTag != XFA.DEFAULTUITAG)) {
pNode.makeNonDefault(false);
return;
}
if ((parentTag != XFA.UITAG) &&
// Don't process #text data...
(pNode instanceof Element) &&
(pParent.defaultElement() == nodeTag)) {
nodeCleanup(pNode, bHasNonDefaultAttributes, false);
return;
}
}
return;
}
nodeCleanup(pNode, bHasNonDefaultAttributes, pNode.getFirstXFAChild() != null);
}
/**
* @exclude from published api.
*/
public void nodeCleanup(Node pNode, boolean bHasAttrs,
boolean bHasChildren) {
// To be implemented by derived models.
}
/**
* Return TRUE if the given attribute can be removed, aka cleaned up from a given node.
* Return FALSE otherwise.
* @exclude from published api.
*/
public boolean doAttributeCleanup(Node node, int eAttributeTag, String sAttrValue) {
// If there's a colon character inside, ignore the attribute.
// it's likely something like xmlns:xfa or xfa:APIVersion or something else we want
// to ignore.
// We also want to ignore activity because 'click' is now the default (need a default
// value for enum validation on load) and it look strange to strip it from
Element pNode = (Element) node;
if (eAttributeTag != XFA.XMLNSTAG &&
eAttributeTag != XFA.ACTIVITYTAG &&
eAttributeTag != XFA.COMMITONTAG &&
eAttributeTag != XFA.HIGHLIGHTTAG &&
null != pNode &&
pNode.isValidAttr(eAttributeTag, false, null) && // Check for valid schema. defaultAttribute() will throw if it's not valid.
pNode.newAttribute(eAttributeTag, sAttrValue).toString().equals(pNode.defaultAttribute(eAttributeTag).toString())) {
return true; //ok to cleanup this attribute
}
return false; //do not cleanup this attribute
}
/**
* @exclude from published api.
*/
public void normalizeNameSpaces() {
String aNS = getNS();
normalizeNameSpaces(aNS);
}
/**
* Helper function for normalizeNameSpaces().
* @param poNode the node to normalize.
* @param aURI the new namespace.
*
* @exclude from published api.
*/
public void normalizeNameSpaces(Element poNode, String aURI) {
String aNS = poNode.getNS();
if (aNS == "" || isCompatibleNS(aNS)) {
// this call to createElementNS goes right to the Impl class and is thus not protected by XFAPerms
// we must check perms manually in this case
// make sure that namespace prefix on elements used within packet are removed
poNode.setNameSpaceURI(aURI, false, false, true);
}
if (poNode.getLocalName() != XFA.EXDATA) {
Node poChild = poNode.getFirstXMLChild();
while (poChild != null) {
if (poChild instanceof Element) {
normalizeNameSpaces((Element)poChild, aURI);
}
poChild = poChild.getNextXMLSibling();
}
}
}
/**
* Walks through the XFA DOM and
* normalizes the namespaces of all the nodes.
*
* @param nTargetVersion
* the version of the schema desired. One of
* {@link Schema#XFAVERSION_10 XFAVERSION_10}, ...
* {@link Schema#XFAVERSION_HEAD XFAVERSION_HEAD}.
* @param oResult
* a list of {@link NodeValidationInfo NodeValidationInfo} objects:
* invalid children, attributes and attribute values based upon the
* target version. If this model is not a valid child of its
* parent, this model will be the first entry of oResult.
* If oResult is not null, this method will ensure all child nodes
* attributes and attribute values are valid for given the target
* version.
* @return true if successful, else false.
*
*/
public boolean normalizeNameSpaces(int nTargetVersion, List oResult) {
boolean bRet = false;
// if the target version is >= the current don't need to call
// validateSchema
if (nTargetVersion >= getCurrentVersion())
bRet = true;
// else validate the schema
else if (oResult != null)
bRet = validateSchema(nTargetVersion, Schema.XFAAVAILABILITY_ALL, true, oResult);
else
bRet = true; // manual
if (bRet) {
// If the target version is greater than the head version for
// a particular model, then set the target version to the head.
if (nTargetVersion > getHeadVersion())
nTargetVersion = getHeadVersion();
setCurrentVersion(nTargetVersion);
normalizeNameSpaces();
}
return bRet;
}
/**
* @exclude from published api.
*/
protected void normalizeNameSpaces(String aNewNS) {
String sNewNS = aNewNS;
// update the version number
int nVersion = getVersion(sNewNS);
setCurrentVersion(nVersion);
// watson bug 1470736 : We used to have the rest of this method wrapped
// in a #ifndef ACROBAT_PLUGIN (see watson 1229534). This is no longer
// needed as we now maintain the current version of the document (we don't
// always update it to the head version like in the past).
String sOldNS = getOriginalVersion(true);
String aNS = getNS();
if (aNS == "" || isCompatibleNS(aNS)) {
// make sure that namespace prefix on elements used within packet are removed
setNameSpaceURI(aNewNS, false, false, true);
}
for (Node child = getFirstXMLChild(); child != null; child = child.getNextXMLSibling()) {
if (child instanceof Element) {
normalizeNameSpaces((Element) child, aNewNS);
}
}
if (moOriginalVersion == null && !StringUtils.isEmpty(sOldNS) && !sOldNS.equals(sNewNS)) {
// when designer changes the target version of a new document the call normalize namespaces
// however they don't want the originalXFAVersion PI created.
// if we don't do this all new documents have the incorrect version.
if (getAppModel().updateOriginalVersion())
getOriginalVersionNode(true, sOldNS);
}
mnOriginalVersion = 0;
}
/**
* postLoad is called after we've stopped receiving DOM events to load the
* DOM
*
* @exclude from published api.
*/
abstract protected void postLoad();
/**
* Preprocess the node. The node may be removed.
* @return the next XML sibling to be processed, or null
if
* all siblings have been processed.
*
* @exclude from published api.
*/
public Node preLoadNode(
Element parent,
Node node,
Generator genTag) {
Node nextSibling = node.getNextXMLSibling();
// search for the originalXFAVersion X.x > PI this corresponds to the
// original xfa namespace used.
if (node instanceof ProcessingInstruction) {
// todo cs move into the xtg namespance
if (node.getName() == STRS.ORIGINALXFAVERSION) {
//Bug#2940586:
//Due to difference in parsing mechanism of C++ and Java, it may be possible that moOriginalVersion
//has already been created and appended as a child of Template Model.
//Now that actual version is found, remove the previously added PI and update originalXFAVersionPI
{
if (moOriginalVersion != null && moOriginalVersion.getXFAParent() == this){
this.removeChild(moOriginalVersion);
}
}
moOriginalVersion = (ProcessingInstruction) node;
}
}
return nextSibling;
}
/**
* Prepares this Model
to be saved. Any model maintenance that
* may have been deferred is performed to ensure that the serialized form of
* the Model
is correct. This method is called automatically
* before a Model
is serialized to a stream, so it does not
* normally need to be called directly. The exception is when a
* Document
is saved using
* {@link Document#saveAs(java.io.OutputStream, Node, DOMSaveOptions)} or
* {@link Document#saveXML(java.io.OutputStream, DOMSaveOptions)}.
* @exclude from published api.
*/
public void preSave(boolean bSaveXMLScript /* = false */) {
// watson bug 1757392, don't dirty the document when saving
final boolean previousWillDirty = getWillDirty();
setWillDirty(false);
try {
if (mbNormalizedNameSpaces || isDirty())
normalizeNameSpaces();
}
finally {
setWillDirty(previousWillDirty);
}
}
/**
* Publish the model to an Application Storage facility. This involves
* updating all external references (such as image hrefs) such that they
* point to local collateral. The actual details of this are left up to the
* implementer of the class derived from Model.Publisher specified in the
* oPublisher parameter.
*
* What publish() itself does is recursively traverse the tree structure of
* the model. When an external reference is encountered (for example, the
* href attribute of an image node), the updateExternalRef() method is
* called. The node, attribute identifier and original value of the external
* reference are passed to updateExternalRef(). The derived class should
* copy the collateral to the application storage area (if desired), and
* should update the value of sExternalRefValue to indicate the new value
* for the external reference (probably some sort of relative path). If an
* external reference stored as an element value (for example, a uri node)
* is encountered, updateExternalRef is called with node and the eAttribute
* arg is set to XFA.TEXTNODETAG.
*
* Two or more instances of an identical external reference result in only
* one call to updateExternalRef(), because returned values are cached and
* reused.
*
* If called on the AppModel, this method recursively calls publish
* on all contained models.
*
* @param publisher
* an instance of a class derived from TemplateModel.Publisher.
*
* @exclude from published api.
*/
public boolean publish(Publisher publisher) {
int nVersion = publisher.getTargetVersion();
int nAvailability = publisher.getTargetAvailability();
return validateSchema(nVersion, nAvailability, false, null);
}
/**
* Indicates that the model is ready. This causes the "ready"
* event to be issued so that script waiting on that event will
* execute. When applied to an AppModel
, this
* call recursively calls ready()
on each child
* model (as a convenience). Some applications may choose to call
* the ready()
on each model individually. Calling
* ready()
a second time on a given model has no
* effect.
* @return true
if ready scripts fire
* @exclude from published api.
*/
public boolean ready(boolean bForced /* = false */) {
boolean bRet = false;
if (!bForced && mbReady)
return bRet;
EventManager tm = getEventManager();
int nId = tm.getEventID("ready");
bRet |= tm.eventOccurred(nId, getAliasNode());
mbReady = true;
return bRet;
}
/**
* @see Node#remove()
* @exclude from published api.
*/
public void remove() {
setAppModel(null);
super.remove();
}
/**
* removes poRefNode from both the UseNodeList and UseHRefNodeList
* @exclude from published api.
*/
void removeIDReferenceNode(Element poRefNode) {
moUseList.remove(poRefNode);
moUseHRefList.remove(poRefNode);
}
/**
* remove references to a given node.
* @exclude from published api.
*/
public void removeReferences(Node node) {
if (node == null)
return;
// bump the ref count to ensure poNodeImpl doesn't go away on us.
// JavaPort: not needed: jfObjWrap oWrapper(poNodeImpl);
// depth first traversal
// go through backwards in case nodes are removed
// Javaport: We move forward through the children,
// but be careful to pre-fetch the next child.
Node child = node.getFirstXFAChild();
while (child != null) {
Node nextChild = child.getNextXFASibling();
removeReferences(child);
child = nextChild;
}
// All descendants of a model have a model pointer to that model
// (and no other model), so for performance don't traverse the
// children if the model isn't "this". Call verifyModel to
// confirm this in debug mode.
// Javaport: only Elements have a reference to their model.
if (node instanceof Element) {
Element element = (Element)node;
if (element.getModel() != this) {
if (Assertions.isEnabled) verifyModel(node, this);
return;
}
element.setModel(null);
}
EventManager.EventTable eventTable = node.getEventTable(false);
EventManager.resetEventTable(eventTable);
// Remove all peers of this node
node.clearPeers();
}
// This helper function verifies the assumption that no descendants of
// node have a model pointer of model.
private static void verifyModel(Node node, Model model) {
for (Node child = node.getFirstXFAChild(); child != null; child = child.getNextXFASibling()) {
assert child.getModel() != model;
verifyModel(child, model);
}
}
/**
* Remap a given tag to a new value. This gives a model a chance to remap a tag
* to a new value after it's determined that the original wasn't valid in the current
* context. The default implementation returns the input tag, which is a no-op.
*
* @param eTag the original input tag.
*
* @return the new remapped tag.
*
* @exclude from published api.
*/
public int remapTag(int eTag) {
return eTag;
}
/**
* @see Node#resolveNodes(String, boolean, boolean, boolean)
*/
public NodeList resolveNodes(String somNodes,
boolean bPeek /* = false */,
boolean bLastInstance /* = false */,
boolean bNoProperties /* = false */,
DependencyTracker oDependencyTracker /* = null */,
BooleanHolder isAssociation /* = null */) {
// Note that in these routines involving current context, moSOMContext is
// never set to this to avoid a cyclic dependency.
if (mSOMParser == null)
mSOMParser = new SOMParser(null);
ArrayNodeList oResult = new ArrayNodeList();
// If a resolveNodes is done on a clone, for instance, then the
// model's refCount will be zero. When creating oModel (below)
// the refCount will be incremented to one and when oModel goes
// out of scope the model will be destroyed prematurely.
// assert(poThis->refCount() != 0);
mSOMParser.setOptions(bPeek, bLastInstance, bNoProperties);
mSOMParser.setDependencyTracker(oDependencyTracker);
if (moSOMContext == null || moSOMContext.getModel() == null)
mSOMParser.resolve(this, somNodes, oResult, isAssociation);
else
mSOMParser.resolve(moSOMContext, somNodes, oResult, isAssociation);
return oResult;
}
/**
* @exclude from published api.
*/
public void resolveProtos(boolean bForceExternalProtoResolve/*false*/) {
// Two passes -- first for external references (usehref), and one for internal (use).
if (bForceExternalProtoResolve ||
getAppModel().getResolveAllExternalProtos()) {
resolveList(moUseHRefList, true);
}
// only processes the use list if the document isn't a fragment
if (!getAppModel().getIsFragmentDoc()) {
resolveList(moUseList, false);
}
}
/**
* @exclude from published api.
*/
@FindBugsSuppress(code="ES")
protected boolean saveProtoInformation(Element poXfaNode,
String aLocalName, boolean bCheckParent) {
// Check for any proto definitions (parent tag )
//
if (bCheckParent && poXfaNode.getXFAParent() != null
&& poXfaNode.getXFAParent() instanceof Proto) {
addProto((ProtoableNode)poXfaNode);
}
// Check for node with at least one attribute being use= or usehref=
if (aLocalName == XFA.USE) {
addUseNode(poXfaNode);
return true;
}
else if (aLocalName == XFA.USEHREF) {
addUseHRefNode(poXfaNode);
return true;
}
return false;
}
/**
* @throws IOException
* @exclude
*/
public void serialize(OutputStream outStream, DOMSaveOptions options, int level, Node prevSibling) throws IOException, IOException {
// Ensure that we serialize from the XML DOM side.
getXmlPeer().serialize(outStream, options, level, prevSibling);
}
/**
* Specify the node that is represented by the alias for this model.
* @param aliasNode
* The node that will represent this model.
*
* @exclude from published api.
*/
final public void setAliasNode(Element aliasNode) {
// don't store a copy to self to avoid circular dependencies
if (aliasNode == this)
mAliasNode = null;
else
mAliasNode = aliasNode;
}
/**
* @param pNewModel
*
* @exclude from published api.
*/
public void setAppModel(AppModel pNewModel) {
mAppModel = pNewModel;
}
/**
* @exclude from public api.
*/
public void setAppModelIsTransient(boolean bAppModelIsTransient) {
mbAppModelIsTransient = bAppModelIsTransient;
}
/**
* Specifies the current node, which is the starting node for calls to
* resolveNode and resolveNodes.
* @param node
* the new current node.
* resolveNode
and resolveNodes
* differ from Node.resolveNode()
and
* Node.resolveNodes()
in that the current node is
* the node set via setContext()
, instead of "this"
* node.
*
* @exclude from published api.
*/
public void setContext(Node node) {
if (node == this)
moSOMContext = null;
else
moSOMContext = node;
}
/**
* @exclude from published api.
*/
public void setCurrentVersion(int nVersion) {
mnCurrentVersion = nVersion;
}
/**
* @see Element#setDOMProperties(String, String, String, Attributes)
* @exclude from published api.
*/
public final void setDOMProperties(String uri, String localName,
String qName, Attributes attributes) {
super.setDOMProperties(uri, localName, qName, attributes);
// JavaPort: In C++ this is done in the loadChildren method
int nVersion = getVersion(uri);
if (nVersion > getHeadVersion()) {
String sHostName = "";
HostPseudoModel oHost = (HostPseudoModel)getAppModel().lookupPseudoModel(STRS.DOLLARHOST);
if (oHost != null)
sHostName = oHost.getName();
// Fix for Watson 1706661.
// Make sure the host name has a value, otherwise the message doesn't help.
if (StringUtils.isEmpty(sHostName))
sHostName = Numeric.doubleToString(getHeadVersion() / 10.0, 1, false);
MsgFormatPos oMessage = new MsgFormatPos(ResId.InvalidModelVersionException, localName);
oMessage.format(sHostName);
if (getAppModel().getSourceAbove() == EnumAttr.SOURCEABOVE_WARN) {
// log an warning
addErrorList(new ExFull(oMessage), LogMessage.MSG_WARNING, this);
}
else if (getAppModel().getSourceAbove() == EnumAttr.SOURCEABOVE_ERROR) {
// throw an error and stop processing.
throw new ExFull(oMessage);
}
}
// set the version
setCurrentVersion(nVersion);
}
/**
* @exclude from published api.
*/
void setGenerator(Generator gen) {
mGenerator = gen;
}
/**
* @exclude from published api.
*/
public void setIDValueMap(IDValueMap idValueMap) {
mIDValueMap = idValueMap;
}
/**
* Specifies the log messenger that this model should use.
* @exclude from published api.
*/
void setLogMessenger(LogMessenger oMessenger) {
getAppModel().setLogMessenger(oMessenger);
}
/**
* Set the name for this model
* @param name
* the model name
*
* @exclude from published api.
*/
final public void setName(String name) {
mName = name;
}
/**
* @exclude from published api.
*/
public void setNeedsNSNormalize(boolean bNormalize) {
mbNormalizedNameSpaces = bNormalize;
}
/**
* @exclude from published api.
*/
public String shortCutName() {
return mShortCutName;
}
/**
* append "_copyi" where i increments from 1 until the ID value is unique
* @param psID the starting id value
* @return the unique id
* @exclude from published api.
*/
protected String uniquifyID (String psID) {
String sTest = psID;
int iCopyIndex = 1;
while (getNode(sTest) != null) {
String sCopyIndex = Integer.toString(iCopyIndex++);
sTest = psID + "_copy" + sCopyIndex;
}
return sTest;
}
/**
* @see Node#validateUsage(int, int, boolean)
* @exclude from published api.
*/
public boolean validateUsage(int nXFAVersion, int nAvailability, boolean bUpdateVersion) {
return validateUsage(nXFAVersion, nAvailability, false, bUpdateVersion);
}
/**
* @see Node#validateUsageFailedIsFatal(int, int)
* @exclude from published api.
*/
public boolean validateUsageFailedIsFatal(int nXFAVersion, int nAvailability) {
return validateUsage(nXFAVersion, nAvailability, true, false);
}
/**
* Determine if a version is allowed or if a disallowed version is a fatal error.
*
* This method previously combined the two tests in a single call, and used a Scalar
* parameter as a way of passing an output parameter by reference. Since this method
* is called so frequently, creating the Scalar instances was a performance issue.
* In this implementation, the isFatalTest parameter selects which test to perform.
* @param nVersion the version to be tested.
* @param isFatalTest indicates whether to perform the fatal disallowance test.
* @param bUpdateVersion allow the current version to be updated if necessary.
* @return if isFatalTest is false, returns true if nVersion is allowed;
* if isFatalTest is true, returns true if disallowing nVersion is a fatal error.
*/
private boolean validateUsage(int nVersion, int nAvailability, boolean bFatalTest, boolean bUpdateVersion) {
AppModel poAppModel = getAppModel();
// first check if an output version is set
int eOutputBelow = poAppModel.getOutputBelow();
int nTargetOutputVer = poAppModel.getVersionRestriction();
boolean bRet = bFatalTest ? false : true;
// check if source below has been set
if (!isVersionCompatible(nVersion, getCurrentVersion()) &&
getSourceBelow() == EnumAttr.SOURCEBELOW_MAINTAIN) {
if (bFatalTest) {
bRet = !mbLoading; // fatal error if we are not loading
}
else {
bRet = false;
}
}
else if (getHeadVersion() < nVersion) {
// last option check what should be done if we exceed the head version
if (bFatalTest) {
if (poAppModel.getSourceAbove() == EnumAttr.SOURCEABOVE_ERROR)
bRet = true;
}
else {
bRet = false;
}
}
else if (nTargetOutputVer != 0 &&
!isVersionCompatible(nVersion, nTargetOutputVer) &&
eOutputBelow != EnumAttr.OUTPUTBELOW_UPDATE) {
if (bFatalTest) {
if (eOutputBelow == EnumAttr.OUTPUTBELOW_ERROR)
bRet = true;
}
else {
bRet = false;
}
}
else if (bUpdateVersion &&
poAppModel.getSourceBelow() == EnumAttr.SOURCEBELOW_UPDATE &&
nVersion <= getHeadVersion() ) {
// bump up the version if needed
if (mnCurrentVersion < nVersion)
//setCurrentVersion(nVersion);
mnCurrentVersion = nVersion;
}
// todo check availability when the appModel stores this info
// size_t nTargetAvail = poAppModel->getTargetAvailability()
// bRet &= nTargetAvail & nAvailability
return bRet;
}
/**
* @exclude from published api.
*/
public void willDirtyDoc(boolean bWillDirty) {
mbWillDirtyDoc = bWillDirty;
}
/**
* @exclude from published api.
*/
public boolean willDirtyDoc() {
return mbWillDirtyDoc;
}
/**
* Intern a string.
*
* The implementation is optimized to use a cache of previously interned symbols to avoid
* the overhead of calling intern on symbols that have already been seen. This is intended
* to optimize the case when we need to optimize the value of many strings where we expect
* the values to be frequently repeated (e.g., Attribute values).
*
* @param value the string to be interned.
* @return the interned string value.
*
* @exclude from published api.
*/
final String intern(String value) {
return mSymbolTable != null ? mSymbolTable.internSymbol(value) : value.intern();
}
/**
* @exclude from published api.
*/
public int getOriginalXFAVersion() {
if (mnOriginalVersion == 0) {
// watson bug 1521181, don't cache the default value because this could change at runtime
// without updating moLegacyInfo.nXFAVersion, this only happens in designer
String sPIValue = getOriginalVersion(false);
// If there isn't an original XFA version PI in the template and behaviorOverride is empty, then get
// the current version of the template.
int nVersion;
if (StringUtils.isEmpty(sPIValue)) {
sPIValue = getOriginalVersion(true);
nVersion = getVersion(sPIValue);
mnOriginalVersion = nVersion;
return nVersion;
}
else {
// read in the settings
// IF YOU CHANGE A DEFAULT OR ADD A NEW VALUE HERE
// MAKE SURE YOU UPDATE THE XFA_LEGACY_XX_DEFAULT setting in XFATemplateModel.h
// Get the defaults depending on the XFA version.
nVersion = getVersion(sPIValue);
mnOriginalVersion = nVersion;
}
}
return mnOriginalVersion;
}
/**
* @exclude from published api.
*/
public void setOriginalXFAVersion(int nXFAVersion) {
// if 0 restore the defaults and remove the PI
if (nXFAVersion == 0) {
mnOriginalVersion = 0;
removeOriginalVersionNode();
}
else if (nXFAVersion <= getCurrentVersion()) {
// call getOriginalXFAVersion first to ensure that mnLegacyInfo are updated
// this will ensure updateOriginalXFAVersionPI does the right thing
getOriginalXFAVersion();
// now update the version number
mnOriginalVersion = nXFAVersion;
// update the PI
updateOriginalXFAVersionPI();
}
}
/**
* @exclude from published api.
*/
protected void updateOriginalXFAVersionPI() {
String sPIValue = getNS(mnOriginalVersion);
getVersion(sPIValue);
ProcessingInstruction oPI = getOriginalVersionNode(false, "");
// update the pi
if (oPI == null)
getOriginalVersionNode(true, sPIValue); // create
else
oPI.setData(sPIValue);
}
/**
* search for the originalXFAVersion X.x > PI. This corresponds to the
* original xfa namespace used. Only checks immediate children of root node.
*
* @exclude from published api.
*/
// JavaPort TODO: The code calling this function is currently incomplete
// (Model.loadNode()).
/*
private void scanForOriginalVersionNode(Node oRootNode) {
assert(moOriginalVersion == null);
Node oNode = oRootNode.getFirstXMLChild();
while (oNode != null) {
if (oNode instanceof ProcessingInstruction) {
if (oNode.getName() == STRS.ORIGINALXFAVERSION) {
moOriginalVersion = (ProcessingInstruction)oNode;
break;
}
}
oNode = oNode.getNextXMLSibling();
}
}
*/
}