You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
666 lines
22 KiB
666 lines
22 KiB
/*
|
|
* Copyright (c) 2007, 2018, Oracle and/or its affiliates. All rights reserved.
|
|
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
|
|
*/
|
|
/*
|
|
* Copyright 1999-2002,2004 The Apache Software Foundation.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package com.sun.org.apache.xerces.internal.dom;
|
|
|
|
import org.w3c.dom.CharacterData;
|
|
import org.w3c.dom.DOMException;
|
|
import org.w3c.dom.Node;
|
|
import org.w3c.dom.Text;
|
|
|
|
/**
|
|
* Text nodes hold the non-markup, non-Entity content of
|
|
* an Element or Attribute.
|
|
* <P>
|
|
* When a document is first made available to the DOM, there is only
|
|
* one Text object for each block of adjacent plain-text. Users (ie,
|
|
* applications) may create multiple adjacent Texts during editing --
|
|
* see {@link org.w3c.dom.Element#normalize} for discussion.
|
|
* <P>
|
|
* Note that CDATASection is a subclass of Text. This is conceptually
|
|
* valid, since they're really just two different ways of quoting
|
|
* characters when they're written out as part of an XML stream.
|
|
*
|
|
* @xerces.internal
|
|
*
|
|
* @since PR-DOM-Level-1-19980818.
|
|
*/
|
|
public class TextImpl
|
|
extends CharacterDataImpl
|
|
implements CharacterData, Text {
|
|
|
|
//
|
|
// Private Data members
|
|
//
|
|
|
|
|
|
//
|
|
// Constants
|
|
//
|
|
|
|
/** Serialization version. */
|
|
static final long serialVersionUID = -5294980852957403469L;
|
|
|
|
//
|
|
// Constructors
|
|
//
|
|
|
|
/** Default constructor */
|
|
public TextImpl(){}
|
|
|
|
/** Factory constructor. */
|
|
public TextImpl(CoreDocumentImpl ownerDoc, String data) {
|
|
super(ownerDoc, data);
|
|
}
|
|
|
|
/**
|
|
* NON-DOM: resets node and sets specified values for the current node
|
|
*
|
|
* @param ownerDoc
|
|
* @param data
|
|
*/
|
|
public void setValues(CoreDocumentImpl ownerDoc, String data){
|
|
|
|
flags=0;
|
|
nextSibling = null;
|
|
previousSibling=null;
|
|
setOwnerDocument(ownerDoc);
|
|
super.data = data;
|
|
}
|
|
//
|
|
// Node methods
|
|
//
|
|
|
|
/**
|
|
* A short integer indicating what type of node this is. The named
|
|
* constants for this value are defined in the org.w3c.dom.Node interface.
|
|
*/
|
|
public short getNodeType() {
|
|
return Node.TEXT_NODE;
|
|
}
|
|
|
|
/** Returns the node name. */
|
|
public String getNodeName() {
|
|
return "#text";
|
|
}
|
|
|
|
/**
|
|
* NON-DOM: Set whether this Text is ignorable whitespace.
|
|
*/
|
|
public void setIgnorableWhitespace(boolean ignore) {
|
|
|
|
if (needsSyncData()) {
|
|
synchronizeData();
|
|
}
|
|
isIgnorableWhitespace(ignore);
|
|
|
|
} // setIgnorableWhitespace(boolean)
|
|
|
|
|
|
/**
|
|
* DOM L3 Core CR - Experimental
|
|
*
|
|
* Returns whether this text node contains
|
|
* element content whitespace</a>, often abusively called "ignorable whitespace".
|
|
* The text node is determined to contain whitespace in element content
|
|
* during the load of the document or if validation occurs while using
|
|
* <code>Document.normalizeDocument()</code>.
|
|
* @since DOM Level 3
|
|
*/
|
|
public boolean isElementContentWhitespace() {
|
|
// REVISIT: is this implemenation correct?
|
|
if (needsSyncData()) {
|
|
synchronizeData();
|
|
}
|
|
return internalIsIgnorableWhitespace();
|
|
}
|
|
|
|
|
|
/**
|
|
* DOM Level 3 WD - Experimental.
|
|
* Returns all text of <code>Text</code> nodes logically-adjacent text
|
|
* nodes to this node, concatenated in document order.
|
|
* @since DOM Level 3
|
|
*/
|
|
public String getWholeText(){
|
|
|
|
if (needsSyncData()) {
|
|
synchronizeData();
|
|
}
|
|
|
|
if (fBufferStr == null){
|
|
fBufferStr = new StringBuffer();
|
|
}
|
|
else {
|
|
fBufferStr.setLength(0);
|
|
}
|
|
if (data != null && data.length() != 0) {
|
|
fBufferStr.append(data);
|
|
}
|
|
|
|
//concatenate text of logically adjacent text nodes to the left of this node in the tree
|
|
getWholeTextBackward(this.getPreviousSibling(), fBufferStr, this.getParentNode());
|
|
String temp = fBufferStr.toString();
|
|
|
|
//clear buffer
|
|
fBufferStr.setLength(0);
|
|
|
|
//concatenate text of logically adjacent text nodes to the right of this node in the tree
|
|
getWholeTextForward(this.getNextSibling(), fBufferStr, this.getParentNode());
|
|
|
|
return temp + fBufferStr.toString();
|
|
|
|
}
|
|
|
|
/**
|
|
* internal method taking a StringBuffer in parameter and inserts the
|
|
* text content at the start of the buffer
|
|
*
|
|
* @param buf
|
|
*/
|
|
protected void insertTextContent(StringBuffer buf) throws DOMException {
|
|
String content = getNodeValue();
|
|
if (content != null) {
|
|
buf.insert(0, content);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Concatenates the text of all logically-adjacent text nodes to the
|
|
* right of this node
|
|
* @param node
|
|
* @param buffer
|
|
* @param parent
|
|
* @return true - if execution was stopped because the type of node
|
|
* other than EntityRef, Text, CDATA is encountered, otherwise
|
|
* return false
|
|
*/
|
|
private boolean getWholeTextForward(Node node, StringBuffer buffer, Node parent){
|
|
// boolean to indicate whether node is a child of an entity reference
|
|
boolean inEntRef = false;
|
|
|
|
if (parent!=null) {
|
|
inEntRef = parent.getNodeType()==Node.ENTITY_REFERENCE_NODE;
|
|
}
|
|
|
|
while (node != null) {
|
|
short type = node.getNodeType();
|
|
if (type == Node.ENTITY_REFERENCE_NODE) {
|
|
if (getWholeTextForward(node.getFirstChild(), buffer, node)){
|
|
return true;
|
|
}
|
|
}
|
|
else if (type == Node.TEXT_NODE ||
|
|
type == Node.CDATA_SECTION_NODE) {
|
|
((NodeImpl)node).getTextContent(buffer);
|
|
}
|
|
else {
|
|
return true;
|
|
}
|
|
|
|
node = node.getNextSibling();
|
|
}
|
|
|
|
// if the parent node is an entity reference node, must
|
|
// check nodes to the right of the parent entity reference node for logically adjacent
|
|
// text nodes
|
|
if (inEntRef) {
|
|
getWholeTextForward(parent.getNextSibling(), buffer, parent.getParentNode());
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Concatenates the text of all logically-adjacent text nodes to the left of
|
|
* the node
|
|
* @param node
|
|
* @param buffer
|
|
* @param parent
|
|
* @return true - if execution was stopped because the type of node
|
|
* other than EntityRef, Text, CDATA is encountered, otherwise
|
|
* return false
|
|
*/
|
|
private boolean getWholeTextBackward(Node node, StringBuffer buffer, Node parent){
|
|
|
|
// boolean to indicate whether node is a child of an entity reference
|
|
boolean inEntRef = false;
|
|
if (parent!=null) {
|
|
inEntRef = parent.getNodeType()==Node.ENTITY_REFERENCE_NODE;
|
|
}
|
|
|
|
while (node != null) {
|
|
short type = node.getNodeType();
|
|
if (type == Node.ENTITY_REFERENCE_NODE) {
|
|
if (getWholeTextBackward(node.getLastChild(), buffer, node)){
|
|
return true;
|
|
}
|
|
}
|
|
else if (type == Node.TEXT_NODE ||
|
|
type == Node.CDATA_SECTION_NODE) {
|
|
((TextImpl)node).insertTextContent(buffer);
|
|
}
|
|
else {
|
|
return true;
|
|
}
|
|
|
|
node = node.getPreviousSibling();
|
|
}
|
|
|
|
// if the parent node is an entity reference node, must
|
|
// check nodes to the left of the parent entity reference node for logically adjacent
|
|
// text nodes
|
|
if (inEntRef) {
|
|
getWholeTextBackward(parent.getPreviousSibling(), buffer, parent.getParentNode());
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Replaces the text of the current node and all logically-adjacent text
|
|
* nodes with the specified text. All logically-adjacent text nodes are
|
|
* removed including the current node unless it was the recipient of the
|
|
* replacement text.
|
|
*
|
|
* @param content
|
|
* The content of the replacing Text node.
|
|
* @return text - The Text node created with the specified content.
|
|
* @since DOM Level 3
|
|
*/
|
|
public Text replaceWholeText(String content) throws DOMException {
|
|
|
|
if (needsSyncData()) {
|
|
synchronizeData();
|
|
}
|
|
|
|
//if the content is null
|
|
Node parent = this.getParentNode();
|
|
if (content == null || content.length() == 0) {
|
|
// remove current node
|
|
if (parent != null) { // check if node in the tree
|
|
parent.removeChild(this);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// make sure we can make the replacement
|
|
if (ownerDocument().errorChecking) {
|
|
if (!canModifyPrev(this)) {
|
|
throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR,
|
|
DOMMessageFormatter.formatMessage(
|
|
DOMMessageFormatter.DOM_DOMAIN,
|
|
"NO_MODIFICATION_ALLOWED_ERR", null));
|
|
}
|
|
|
|
// make sure we can make the replacement
|
|
if (!canModifyNext(this)) {
|
|
throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR,
|
|
DOMMessageFormatter.formatMessage(
|
|
DOMMessageFormatter.DOM_DOMAIN,
|
|
"NO_MODIFICATION_ALLOWED_ERR", null));
|
|
}
|
|
}
|
|
|
|
//replace the text node
|
|
Text currentNode = null;
|
|
if (isReadOnly()) {
|
|
Text newNode = this.ownerDocument().createTextNode(content);
|
|
if (parent != null) { // check if node in the tree
|
|
parent.insertBefore(newNode, this);
|
|
parent.removeChild(this);
|
|
currentNode = newNode;
|
|
} else {
|
|
return newNode;
|
|
}
|
|
} else {
|
|
this.setData(content);
|
|
currentNode = this;
|
|
}
|
|
|
|
//check logically-adjacent text nodes
|
|
Node prev = currentNode.getPreviousSibling();
|
|
while (prev != null) {
|
|
//If the logically-adjacent next node can be removed
|
|
//remove it. A logically adjacent node can be removed if
|
|
//it is a Text or CDATASection node or an EntityReference with
|
|
//Text and CDATA only children.
|
|
if ((prev.getNodeType() == Node.TEXT_NODE)
|
|
|| (prev.getNodeType() == Node.CDATA_SECTION_NODE)
|
|
|| (prev.getNodeType() == Node.ENTITY_REFERENCE_NODE && hasTextOnlyChildren(prev))) {
|
|
parent.removeChild(prev);
|
|
prev = currentNode;
|
|
} else {
|
|
break;
|
|
}
|
|
prev = prev.getPreviousSibling();
|
|
}
|
|
|
|
//check logically-adjacent text nodes
|
|
Node next = currentNode.getNextSibling();
|
|
while (next != null) {
|
|
//If the logically-adjacent next node can be removed
|
|
//remove it. A logically adjacent node can be removed if
|
|
//it is a Text or CDATASection node or an EntityReference with
|
|
//Text and CDATA only children.
|
|
if ((next.getNodeType() == Node.TEXT_NODE)
|
|
|| (next.getNodeType() == Node.CDATA_SECTION_NODE)
|
|
|| (next.getNodeType() == Node.ENTITY_REFERENCE_NODE && hasTextOnlyChildren(next))) {
|
|
parent.removeChild(next);
|
|
next = currentNode;
|
|
} else {
|
|
break;
|
|
}
|
|
next = next.getNextSibling();
|
|
}
|
|
|
|
return currentNode;
|
|
}
|
|
|
|
/**
|
|
* If any EntityReference to be removed has descendants that are not
|
|
* EntityReference, Text, or CDATASection nodes, the replaceWholeText method
|
|
* must fail before performing any modification of the document, raising a
|
|
* DOMException with the code NO_MODIFICATION_ALLOWED_ERR. Traverse previous
|
|
* siblings of the node to be replaced. If a previous sibling is an
|
|
* EntityReference node, get it's last child. If the last child was a Text
|
|
* or CDATASection node and its previous siblings are neither a replaceable
|
|
* EntityReference or Text or CDATASection nodes, return false. IF the last
|
|
* child was neither Text nor CDATASection nor a replaceable EntityReference
|
|
* Node, then return true. If the last child was a Text or CDATASection node
|
|
* any its previous sibling was not or was an EntityReference that did not
|
|
* contain only Text or CDATASection nodes, return false. Check this
|
|
* recursively for EntityReference nodes.
|
|
*
|
|
* @param node
|
|
* @return true - can replace text false - can't replace exception must be
|
|
* raised
|
|
*/
|
|
private boolean canModifyPrev(Node node) {
|
|
boolean textLastChild = false;
|
|
|
|
Node prev = node.getPreviousSibling();
|
|
|
|
while (prev != null) {
|
|
|
|
short type = prev.getNodeType();
|
|
|
|
if (type == Node.ENTITY_REFERENCE_NODE) {
|
|
//If the previous sibling was entityreference
|
|
//check if its content is replaceable
|
|
Node lastChild = prev.getLastChild();
|
|
|
|
//if the entity reference has no children
|
|
//return false
|
|
if (lastChild == null) {
|
|
return false;
|
|
}
|
|
|
|
//The replacement text of the entity reference should
|
|
//be either only text,cadatsections or replaceable entity
|
|
//reference nodes or the last child should be neither of these
|
|
while (lastChild != null) {
|
|
short lType = lastChild.getNodeType();
|
|
|
|
if (lType == Node.TEXT_NODE
|
|
|| lType == Node.CDATA_SECTION_NODE) {
|
|
textLastChild = true;
|
|
} else if (lType == Node.ENTITY_REFERENCE_NODE) {
|
|
if (!canModifyPrev(lastChild)) {
|
|
return false;
|
|
} else {
|
|
//If the EntityReference child contains
|
|
//only text, or non-text or ends with a
|
|
//non-text node.
|
|
textLastChild = true;
|
|
}
|
|
} else {
|
|
//If the last child was replaceable and others are not
|
|
//Text or CDataSection or replaceable EntityRef nodes
|
|
//return false.
|
|
if (textLastChild) {
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
lastChild = lastChild.getPreviousSibling();
|
|
}
|
|
} else if (type == Node.TEXT_NODE
|
|
|| type == Node.CDATA_SECTION_NODE) {
|
|
//If the previous sibling was text or cdatasection move to next
|
|
} else {
|
|
//If the previous sibling was anything but text or
|
|
//cdatasection or an entity reference, stop search and
|
|
//return true
|
|
return true;
|
|
}
|
|
|
|
prev = prev.getPreviousSibling();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* If any EntityReference to be removed has descendants that are not
|
|
* EntityReference, Text, or CDATASection nodes, the replaceWholeText method
|
|
* must fail before performing any modification of the document, raising a
|
|
* DOMException with the code NO_MODIFICATION_ALLOWED_ERR. Traverse previous
|
|
* siblings of the node to be replaced. If a previous sibling is an
|
|
* EntityReference node, get it's last child. If the first child was a Text
|
|
* or CDATASection node and its next siblings are neither a replaceable
|
|
* EntityReference or Text or CDATASection nodes, return false. IF the first
|
|
* child was neither Text nor CDATASection nor a replaceable EntityReference
|
|
* Node, then return true. If the first child was a Text or CDATASection
|
|
* node any its next sibling was not or was an EntityReference that did not
|
|
* contain only Text or CDATASection nodes, return false. Check this
|
|
* recursively for EntityReference nodes.
|
|
*
|
|
* @param node
|
|
* @return true - can replace text false - can't replace exception must be
|
|
* raised
|
|
*/
|
|
private boolean canModifyNext(Node node) {
|
|
boolean textFirstChild = false;
|
|
|
|
Node next = node.getNextSibling();
|
|
while (next != null) {
|
|
|
|
short type = next.getNodeType();
|
|
|
|
if (type == Node.ENTITY_REFERENCE_NODE) {
|
|
//If the previous sibling was entityreference
|
|
//check if its content is replaceable
|
|
Node firstChild = next.getFirstChild();
|
|
|
|
//if the entity reference has no children
|
|
//return false
|
|
if (firstChild == null) {
|
|
return false;
|
|
}
|
|
|
|
//The replacement text of the entity reference should
|
|
//be either only text,cadatsections or replaceable entity
|
|
//reference nodes or the last child should be neither of these
|
|
while (firstChild != null) {
|
|
short lType = firstChild.getNodeType();
|
|
|
|
if (lType == Node.TEXT_NODE
|
|
|| lType == Node.CDATA_SECTION_NODE) {
|
|
textFirstChild = true;
|
|
} else if (lType == Node.ENTITY_REFERENCE_NODE) {
|
|
if (!canModifyNext(firstChild)) {
|
|
return false;
|
|
} else {
|
|
//If the EntityReference child contains
|
|
//only text, or non-text or ends with a
|
|
//non-text node.
|
|
textFirstChild = true;
|
|
}
|
|
} else {
|
|
//If the first child was replaceable text and next
|
|
//children are not, then return false
|
|
if (textFirstChild) {
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
firstChild = firstChild.getNextSibling();
|
|
}
|
|
} else if (type == Node.TEXT_NODE
|
|
|| type == Node.CDATA_SECTION_NODE) {
|
|
//If the previous sibling was text or cdatasection move to next
|
|
} else {
|
|
//If the next sibling was anything but text or
|
|
//cdatasection or an entity reference, stop search and
|
|
//return true
|
|
return true;
|
|
}
|
|
|
|
next = next.getNextSibling();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Check if an EntityReference node has Text Only child nodes
|
|
*
|
|
* @param node
|
|
* @return true - Contains text only children
|
|
*/
|
|
private boolean hasTextOnlyChildren(Node node) {
|
|
|
|
Node child = node;
|
|
|
|
if (child == null) {
|
|
return false;
|
|
}
|
|
|
|
child = child.getFirstChild();
|
|
while (child != null) {
|
|
int type = child.getNodeType();
|
|
|
|
if (type == Node.ENTITY_REFERENCE_NODE) {
|
|
return hasTextOnlyChildren(child);
|
|
}
|
|
else if (type != Node.TEXT_NODE
|
|
&& type != Node.CDATA_SECTION_NODE
|
|
&& type != Node.ENTITY_REFERENCE_NODE) {
|
|
return false;
|
|
}
|
|
child = child.getNextSibling();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* NON-DOM: Returns whether this Text is ignorable whitespace.
|
|
*/
|
|
public boolean isIgnorableWhitespace() {
|
|
|
|
if (needsSyncData()) {
|
|
synchronizeData();
|
|
}
|
|
return internalIsIgnorableWhitespace();
|
|
|
|
} // isIgnorableWhitespace():boolean
|
|
|
|
|
|
//
|
|
// Text methods
|
|
//
|
|
|
|
/**
|
|
* Break a text node into two sibling nodes. (Note that if the current node
|
|
* has no parent, they won't wind up as "siblings" -- they'll both be
|
|
* orphans.)
|
|
*
|
|
* @param offset
|
|
* The offset at which to split. If offset is at the end of the
|
|
* available data, the second node will be empty.
|
|
*
|
|
* @return A reference to the new node (containing data after the offset
|
|
* point). The original node will contain data up to that point.
|
|
*
|
|
* @throws DOMException(INDEX_SIZE_ERR)
|
|
* if offset is <0 or >length.
|
|
*
|
|
* @throws DOMException(NO_MODIFICATION_ALLOWED_ERR)
|
|
* if node is read-only.
|
|
*/
|
|
public Text splitText(int offset)
|
|
throws DOMException {
|
|
|
|
if (isReadOnly()) {
|
|
throw new DOMException(
|
|
DOMException.NO_MODIFICATION_ALLOWED_ERR,
|
|
DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "NO_MODIFICATION_ALLOWED_ERR", null));
|
|
}
|
|
|
|
if (needsSyncData()) {
|
|
synchronizeData();
|
|
}
|
|
if (offset < 0 || offset > data.length() ) {
|
|
throw new DOMException(DOMException.INDEX_SIZE_ERR,
|
|
DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INDEX_SIZE_ERR", null));
|
|
}
|
|
|
|
// split text into two separate nodes
|
|
Text newText =
|
|
getOwnerDocument().createTextNode(data.substring(offset));
|
|
setNodeValue(data.substring(0, offset));
|
|
|
|
// insert new text node
|
|
Node parentNode = getParentNode();
|
|
if (parentNode != null) {
|
|
parentNode.insertBefore(newText, nextSibling);
|
|
}
|
|
|
|
return newText;
|
|
|
|
} // splitText(int):Text
|
|
|
|
|
|
/**
|
|
* NON-DOM (used by DOMParser): Reset data for the node.
|
|
*/
|
|
public void replaceData (String value){
|
|
data = value;
|
|
}
|
|
|
|
|
|
/**
|
|
* NON-DOM (used by DOMParser: Sets data to empty string.
|
|
* Returns the value the data was set to.
|
|
*/
|
|
public String removeData (){
|
|
String olddata=data;
|
|
data = "";
|
|
return olddata;
|
|
}
|
|
|
|
} // class TextImpl
|