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

com.adobe.xfa.RichTextNode 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;

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); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy