com.adobe.xfa.RichTextNode 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.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import com.adobe.xfa.ut.ExFull;
import com.adobe.xfa.ut.ResId;
import com.adobe.xfa.ut.UnitSpan;
import com.adobe.xfa.service.canonicalize.Canonicalize;
/**
* A node to contain rich text content
*
* @exclude from published api -- Mike Tardif, May 2006.
*/
public final class RichTextNode extends Element {
class FetchVisitAttr extends VisitAttr {
private final List moIDValues; // Note: reference, not storage.
FetchVisitAttr(List oIDValues) {
moIDValues = oIDValues;
}
void visitAttribute(Element element, Attribute oAttr) {
String sIDValue = oAttr.getAttrValue();
// Only add ID-based references, not SOM-based.
if (sIDValue.length() > 0 && sIDValue.charAt(0) == '#') {
sIDValue = sIDValue.substring(1); // Eat '#'
moIDValues.add(sIDValue);
}
}
};
/**
* Define and use a VisitAttr-derived class whose visit method updates ID values.
*/
class UpdateVisitAttr extends VisitAttr {
private final List moNewIDValues; // Note: reference, not storage.
private final List moOrigIDValues; // Note: reference, not storage.
UpdateVisitAttr(List oOrigIDValues, List oNewIDValues) {
moOrigIDValues = oOrigIDValues;
moNewIDValues = oNewIDValues;
}
void visitAttribute(Element element, Attribute oAttr) {
String sIDValue = oAttr.getAttrValue();
// Watson 2402033: don't alter SOM-based references. Only modify ID-based references.
if ((sIDValue.length() > 0 && sIDValue.charAt(0) == '#'))
sIDValue = sIDValue.substring(1); // Eat '#'
for (int i = 0; i < moOrigIDValues.size(); i++) {
String sOrigIDValue = moOrigIDValues.get(i);
String sNewIDValue = moNewIDValues.get(i);
if (sIDValue.equals(sOrigIDValue)) {
// Restore the "#" that we stripped off.
sNewIDValue = "#" + sNewIDValue;
element.setAttribute(oAttr.getNS(), oAttr.getQName(), oAttr
.getLocalName(), sNewIDValue, false);
break;
}
}
}
};
/**
* VisitAttr is a base class used in traversing an XML DOM tree. The derived
* classes FetchVisitAttr and UpdateVisitAttr gather and update DOM
* attribute values, respectively.
*/
private abstract class VisitAttr {
/**
* Define and use a VisitAttr-derived class whose visit method fetches
* ID values. JavaPort: Need to add an Element parameter since in Java
* attributes are immutable and don't hold a pointer back to their
* parent.
*/
abstract void visitAttribute(Element element, Attribute oAttr);
}
/**
* Traverse the XML DOM tree, calling the virtual visitAttribute method on
* each ID-related attribute found.
*
* @param oDomNode
* @param oVisitAttr
*/
static void updateIDAttrs(Element oDomNode, String sPrefix, List oldReferenceList) {
String sName = (oDomNode instanceof Element)
? ((Element) oDomNode).getLocalName() : oDomNode.getName();
if (sName == "span" && oDomNode.getNumAttrs() > 0) {
int nSize = oDomNode.getNumAttrs();
for (int nIndex = 0; nIndex < nSize; nIndex++) {
Attribute oAttr = oDomNode.getAttr(nIndex);
if (oAttr.getLocalName().equals("embed")) {
String sIDReference = oAttr.getAttrValue();
// Watson 2402033: don't alter SOM-based references. Only modify ID-based references.
if (! (sIDReference.length() >= 1 && sIDReference.charAt(0) == '#'))
continue;
sIDReference = sIDReference.substring(1); // Eat '#'
if (oldReferenceList != null)
oldReferenceList.add(sIDReference);
String sNewReference = "#" + sPrefix + ":" + sIDReference;
oDomNode.updateAttributeInternal(
oAttr.newAttribute(oAttr.getNS(), oAttr.getLocalName(), oAttr.getQName(), sNewReference, false));
}
}
}
Node oChild = oDomNode.getFirstXMLChild();
while (oChild != null) {
if (oChild instanceof Element)
updateIDAttrs((Element) oChild, sPrefix, oldReferenceList);
oChild = oChild.getNextXMLSibling();
}
}
public RichTextNode(Element pParent, Node prevSibling) {
super(pParent, prevSibling, XFA.RICHTEXTNODE, null, XFA.RICHTEXTNODE,
null, XFA.RICHTEXTNODETAG, XFA.RICHTEXTNODE);
inhibitPrettyPrint(true);
}
private static boolean addValidationInfo(List validationInfo, RichTextNode node, String aRichText, int nVersionIntro) {
if (validationInfo != null) {
NodeValidationInfo info = new NodeValidationInfo(aRichText, nVersionIntro, Schema.XFAAVAILABILITY_ALL, node);
validationInfo.add(info);
return true;
}
return false; // nothing to add to
}
public Element createProto(Element parent, boolean bFull) {
RichTextNode clonedNode = (RichTextNode)clone(parent, true);
// If resolving external protos into a doc which has rich text version bumping enabled (by Designer), we need
// to validate in case the external proto has features for which the host doc version is too low.
AppModel parentAppModel = parent.getAppModel();
if (parentAppModel != null && parentAppModel != getAppModel() && parentAppModel.bumpVersionOnRichTextLoad()) {
parent.validateUsage(clonedNode.getVersionRequired(), 0, true);
}
return clonedNode;
}
/**
* Update a list of ID-based string values in the rich text.
*/
public void updateIDValuesImpl(String sPrefix, List oldReferenceList) {
updateIDAttrs(this, sPrefix, oldReferenceList);
}
/**
* @see Element#getDeltas(Element, XFAList)
* @exclude
*/
public void getDeltas(Element delta, XFAList list) {
// Adobe patent application tracking # B252, entitled METHOD AND SYSTEM TO PERSIST STATE, inventors: Roberto Perelman, Chris Solc, Anatole Matveief, Jeff Young, John Brinkman
// Adobe patent application tracking # B322, entitled METHOD AND SYSTEM TO MAINTAIN THE INTEGRITY OF A CERTIFIED DOCUMENT WHILE PERSISTING STATE IN A DYNAMIC FORM, inventors: Roberto Perelman, Chris Solc, Anatole Matveief, Jeff Young, John Brinkman
if (isSameClass(delta) && list != null) {
Element parent = getXFAParent();
Element deltaParent = delta.getXFAParent();
Delta newDelta = new Delta(parent, deltaParent, this, delta, "");
list.append(newDelta);
}
}
/**
* @see Element#getFirstXFAChild()
*/
public Node getFirstXFAChild() {
// In the C++ XFA world, the rich text node does not expose
// any of its children.
return null;
}
/**
* @exclude from published api.
*/
public ScriptTable getScriptTable() {
return RichTextNodeScript.getScriptTable();
}
/**
* Get the pcData for this node.
*
* @param bAsFragment
* if true, it returns a String containing an html fragment,
* false return only the text content.
* @return the pcData as a string.
*/
public String getValue(
boolean bAsFragment /* = false */,
boolean bSuppressPreamble /* = false */,
boolean bLegacyWhitespaceProcessing /* = false */) {
String sReturn = getValuesFromDom(this, bAsFragment, bSuppressPreamble, bLegacyWhitespaceProcessing);
return sReturn;
}
/**
*
* @param node
* @param bAsFragment
* @param bSuppressPreamble
* @param bLegacyWhitespaceProcessing
* @return gotten values.
* @exclude
*/
public String getValuesFromDom(Node node, boolean bAsFragment,
boolean bSuppressPreamble, boolean bLegacyWhitespaceProcessing) {
StringBuilder htmlTextValue = new StringBuilder();
if (bAsFragment) {
// Use UTF-8 converter to avoid losing asian characters
ByteArrayOutputStream oFragmentStream = new ByteArrayOutputStream();
DOMSaveOptions oOptions = new DOMSaveOptions();
// XFAPlugin Vantive bug#595482 Convert CR and extended characters to entity
// references. Acrobat prefers these to raw utf8 byte data.
oOptions.setDisplayFormat(DOMSaveOptions.RAW_OUTPUT);
oOptions.setSaveTransient(true);
oOptions.setEntityChars("\r");
oOptions.setRangeMin('\u007F');
oOptions.setRangeMax('\u00FF');
oOptions.setExcludePreamble((bSuppressPreamble));
node.getOwnerDocument().saveAs(oFragmentStream, node, oOptions);
try {
htmlTextValue.append(oFragmentStream.toString("UTF-8"));
} catch (UnsupportedEncodingException e) {
// Not possible - UTF-8 is always supported
}
} else {
if (node instanceof TextNode) {
String sNodeValue = ((TextNode)node).getValue();
if (!bLegacyWhitespaceProcessing) {
//
// Implement a poor-man's version of XHTML's whitespace processing rules
//
boolean bSuppressLeading, bSuppressTrailing;
if (node.getXMLParent().getLocalName() == "span") {
// Be more protective of spaces in s
bSuppressLeading = bSuppressTrailing = false;
}
else {
bSuppressLeading = node.getPreviousXMLSibling() == null;
bSuppressTrailing = node.getNextXMLSibling() == null;
}
int leadingWhitespace = 0;
for (int i = 0; i < sNodeValue.length(); i++) {
if (Character.isWhitespace(sNodeValue.charAt(i)))
leadingWhitespace++;
else
break;
}
// Collapse pure whitespace (of whatever size)
if (leadingWhitespace == sNodeValue.length()) {
if (bSuppressLeading || bSuppressTrailing)
sNodeValue = ""; // after starttag or before endtag ignore completely
else
sNodeValue = " "; // between elements collapse to single space
}
else {
// Collapse multiple starting spaces
int nOriginalLength = sNodeValue.length();
sNodeValue = sNodeValue.substring(leadingWhitespace);
if (!bSuppressLeading && (sNodeValue.length() < nOriginalLength))
sNodeValue = " " + sNodeValue; // collapse to single space if not after starttag
// Collapse multiple ending spaces
nOriginalLength = sNodeValue.length();
sNodeValue = sNodeValue.replaceAll("\\s+$","");
if (!bSuppressTrailing && sNodeValue.length() < nOriginalLength)
sNodeValue = sNodeValue + " "; // collapse to single space if not before endtag
}
}
htmlTextValue.append(sNodeValue);
}
Node child = node.getFirstXMLChild();
boolean bPara = false;
while (child != null) {
String sName = child instanceof Element ? ((Element) child).getLocalName() : null;
if (sName == "p") {
if (!bPara)
bPara = true;
else
htmlTextValue.append('\n');
}
else if (sName == "br")
htmlTextValue.append('\n');
boolean bXFASpecialSpan = false;
if (sName == "span") {
final Element elementChild = (Element)child;
final int nSize = elementChild.getNumAttrs();
for (int nIndex = 0; nIndex < nSize; nIndex++) {
final Attribute attr = elementChild.getAttr(nIndex);
if (attr.getLocalName() == "style") {
if (attr.getAttrValue().startsWith("xfa-tab-count:")) {
final String sNum = attr.getAttrValue().substring(14);
final int nNum = Integer.parseInt(sNum);
for (int k = 0; k < nNum; k++)
htmlTextValue.append('\t');
bXFASpecialSpan = true;
break;
}
else if (attr.getAttrValue().startsWith("xfa-spacerun:yes") && !bLegacyWhitespaceProcessing) {
String sSpanContent = getValuesFromDom(child, bAsFragment, bSuppressPreamble, bLegacyWhitespaceProcessing);
// JavaPort: This seems pointless - sSpanContent is never used!
// Convert non-breaking spaces to spaces
sSpanContent = sSpanContent.replace('\u00A0', '\u0020');
htmlTextValue.append(' ');
bXFASpecialSpan = true;
break;
}
}
}
}
if (!bXFASpecialSpan)
htmlTextValue.append(getValuesFromDom(child, bAsFragment, bSuppressPreamble, bLegacyWhitespaceProcessing));
child = child.getNextXMLSibling();
}
}
return htmlTextValue.toString();
}
/**
* Indicate that this node should not be indexed by id attribute.
*
* In the C++ implementation, rich text nodes only exist in the XML DOM,
* so they are not included in the index by id attribute.
*
* @exclude from published api.
*/
protected boolean isIndexable() {
return false;
}
/**
* @see Element#isValidChild(int, int, boolean, boolean)
*/
public boolean isValidChild(int eTag, int nError, boolean bBeforeInsert,
boolean bOccurrenceErrorOnly/* =false */) {
// JavaPort
// We really don't want to be validating the children of a rich text
// node
// so... accept anything :-)
return true;
}
/**
* Set the pcdata for this node.
*
* @param sData -
* a string containing the new pcdata.
*/
public void setValue(String sData) {
// TODO JavaPort: The C++ version doesn't look like it could possibly
// work...
throw new ExFull(ResId.UNSUPPORTED_OPERATION, "RichTextNode#setValue");
// getDomPeer().setNodeValue(sData);
//
// // After a "set" operation we won't be a default property anymore,
// and
// // we'll no longer delegate to any protos.
// isTransient(false);
}
/**
* Cast this node to a string value.
*
* @return the string representing the pcdata.
*/
public String toString() {
return getValue(false, false, false);
}
//
// Validation of xhtml content to determine minimum XFA version required.
// This is fairly simple now - hyperlinks and leaders for 2.8 - but a better
// approach long term would probably be to get this information from AXTE.
// Note that version updating based on rich text validation is only enabled
// normally by Designer see XFAAppModel::bumpVersionOnRichTextLoad() which is
// off by default.
//
boolean validateRichText(Node node,
int nTargetVersion,
List validationInfo) {
boolean bRet = true;
if (nTargetVersion >= Schema.XFAVERSION_HEAD)
return bRet;
if (!(node instanceof Element))
return bRet;
final Element element = (Element)node;
final String aName = element.getLocalName();
final int numAttrs = element.getNumAttrs();
if ((aName == "span" || aName == "p") && numAttrs != 0) {
// features requiring XFA 2.8
for (int nIndex = 0; nIndex < numAttrs; nIndex++) {
Attribute attr = element.getAttr(nIndex);
if (attr.getLocalName() == "style") {
String sStyleValue = attr.getAttrValue();
// tab stops 2.8
if (nTargetVersion < Schema.XFAVERSION_28 && sStyleValue.contains(STRS.RichText_XFATabStops)) {
bRet = false;
if (!addValidationInfo(validationInfo, this, STRS.RichText_XFATabStops, Schema.XFAVERSION_28))
return false; // stop if we don't have a list to populate
}
// horizontal font scaling 2.8
if (nTargetVersion < Schema.XFAVERSION_28 && sStyleValue.contains(STRS.RichText_XFAHorizontalScale)) {
bRet = false;
if (!addValidationInfo(validationInfo, this, STRS.RichText_XFAHorizontalScale, Schema.XFAVERSION_28))
return false; // stop if we don't have a list to populate
}
// vertical font scaling 2.8
if (nTargetVersion < Schema.XFAVERSION_28 && sStyleValue.contains(STRS.RichText_XFAVerticalScale)) {
bRet = false;
if (!addValidationInfo(validationInfo, this, STRS.RichText_XFAVerticalScale, Schema.XFAVERSION_28))
return false; // stop if we don't have a list to populate
}
// kerning 2.8
if (nTargetVersion < Schema.XFAVERSION_28 && sStyleValue.contains(STRS.RichText_KerningMode)) {
bRet = false;
if (!addValidationInfo(validationInfo, this, STRS.RichText_KerningMode, Schema.XFAVERSION_28))
return false; // stop if we don't have a list to populate
}
// letter spacing 2.8
// note: the default letter spacing of 0 will always be generated for
// - so don't warn on the default value.
if (nTargetVersion < Schema.XFAVERSION_28 && sStyleValue.contains(STRS.RichText_LetterSpacing)) {
int nFoundAt = sStyleValue.indexOf(STRS.RichText_LetterSpacing);
String sLetterSpacingValue = sStyleValue.substring(nFoundAt + STRS.RichText_LetterSpacing.length());
nFoundAt = sLetterSpacingValue.indexOf(';');
if (nFoundAt != -1)
sLetterSpacingValue = sLetterSpacingValue.substring(0, nFoundAt);
UnitSpan value = new UnitSpan(sLetterSpacingValue);
if (!value.equals(UnitSpan.ZERO)) {
bRet = false;
if (!addValidationInfo(validationInfo, this, STRS.RichText_LetterSpacing, Schema.XFAVERSION_28))
return false; // stop if we don't have a list to populate
}
}
}
}
}
else if (nTargetVersion < Schema.XFAVERSION_28 && aName == "a" && element.getNumAttrs() != 0) {
// hyperlinks require XFA 2.8
for (int nIndex = 0; nIndex < numAttrs; nIndex++) {
final Attribute attr = element.getAttr(nIndex);
if (attr.getLocalName() == XFA.HREF) {
bRet = false;
if (!addValidationInfo(validationInfo, this, XFA.HREF, Schema.XFAVERSION_28))
return false; // stop if we don't have a list to populate
}
}
}
else if (nTargetVersion < Schema.XFAVERSION_33)
{
if(aName.equals("ol"))
{
// List require XFA 3.3
bRet = false;
if (!addValidationInfo(validationInfo, this, XFA.OL, Schema.XFAVERSION_33))
return false; // stop if we don't have a list to populate
}
else if(aName.equals("ul"))
{
// List require XFA 3.3
bRet = false;
if (!addValidationInfo(validationInfo, this, XFA.UL, Schema.XFAVERSION_33))
return false; // stop if we don't have a list to populate
}
else if(aName.equals("li"))
{
// List require XFA 3.3
bRet = false;
if (!addValidationInfo(validationInfo, this, XFA.LI, Schema.XFAVERSION_33))
return false; // stop if we don't have a list to populate
}
}
for (Node child = node.getFirstXMLChild(); child != null; child = child.getNextXMLSibling()) {
if (!validateRichText(child, nTargetVersion, validationInfo)) {
bRet = false;
if (validationInfo == null)
return false; // stop if we don't have a list to populate
}
}
return bRet;
}
//
// Override normal schema validation to also allow validation of the XHTML content
public boolean validateSchema(int nTargetVersion,
int nTargetAvailability,
boolean bRecursive,
List pValidationInfo) {
boolean bRet = super.validateSchema(nTargetVersion, nTargetAvailability, bRecursive, pValidationInfo);
if (!validateRichText(this, nTargetVersion, pValidationInfo))
bRet = false;
return bRet;
}
/** @exclude from published api. */
public int getVersionRequired() {
int nVersion = Schema.XFAVERSION_21;
List infos = new ArrayList();
if (!validateRichText(this, nVersion, infos)) {
for (int i = 0; i < infos.size(); i++) {
NodeValidationInfo info = infos.get(i);
if (info.nVersionIntro > nVersion)
nVersion = info.nVersionIntro;
}
}
return nVersion;
}
/**
* Override of Element.compareVersions.
*
* @exclude from published api.
*/
protected boolean compareVersions(Node oRollback, Node oContainer, Node.ChangeLogger oChangeLogger, Object oUserData) {
boolean bMatches = super.compareVersions(oRollback, oContainer, oChangeLogger, oUserData);
if (bMatches == false && oChangeLogger == null)
return false; // performance optimization
//
// XHTML needs canonicalizing before comparing
//
String sCanonicalSource = "";
String sCanonicalRollback = "";
Canonicalize c = new Canonicalize(this, false, true);
byte [] pBuffer = c.canonicalize(Canonicalize.CANONICALWITHOUT, null);
try {
sCanonicalSource = new String(pBuffer, "UTF-8");
}
catch (UnsupportedEncodingException ex) {
// not possible - UTF-8 is always supported
}
Canonicalize cRollBack = new Canonicalize(oRollback, false, true);
pBuffer = cRollBack.canonicalize(Canonicalize.CANONICALWITHOUT, null);
try {
sCanonicalRollback = new String(pBuffer, "UTF-8");
}
catch (UnsupportedEncodingException ex) {
// not possible - UTF-8 is always supported
}
if (!sCanonicalSource.equals( sCanonicalRollback )) {
bMatches = false;
if (oChangeLogger != null)
logValueChangeHelper(oContainer, sCanonicalSource, oChangeLogger, oUserData);
}
return bMatches;
}
/**
* Serializes this element (and all its children) to an output stream.
* @param outFile an output stream.
* @param options the XML save options
*/
public void saveXML(OutputStream outFile, DOMSaveOptions options, boolean bSaveXMLScript /* = false */) {
if (options != null){
DOMSaveOptions oNewOptions = new DOMSaveOptions(options);
oNewOptions.setSaveTransient(true);
super.saveXML(outFile, oNewOptions, bSaveXMLScript);
}else{
super.saveXML(outFile, options, bSaveXMLScript);
}
}
}