Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.icefaces.impl.util.DOMUtils Maven / Gradle / Ivy
/*
* Copyright 2004-2014 ICEsoft Technologies Canada Corp.
*
* 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 org.icefaces.impl.util;
import org.w3c.dom.Attr;
import org.w3c.dom.CDATASection;
import org.w3c.dom.Comment;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Entity;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import javax.faces.component.UIComponent;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
public class DOMUtils {
private static Logger log = Logger.getLogger("org.icefaces.util.DOMUtil");
private static HashSet TAGS_THAT_CAN_CLOSE_SHORT = new HashSet(
Arrays.asList("img", "input", "br", "hr", "meta", "base", "link", "frame", "col", "area"));
private static HashSet TAGS_THAT_ALLOW_NEWLINE = new HashSet(
Arrays.asList("img", "input", "td"));
private static Pattern CDATA_END = Pattern.compile("]]>");
//TODO: look at replacing with a lighter, more targetted DOM implementation
private static DocumentBuilder DOCUMENT_BUILDER;
private static boolean isDOMChecking = true;
public static String DIFF_SUPPRESS = "data-ice-diffsuppress";
public static String DIFF_INSDEL = "data-ice-insdel";
public static String DIFF_TRUE = "true";
public static class EditOperation {
public String id = null;
//will be Element once pruning is integrated
public Node element = null;
public Map attributes = null;
public String toString() {
return this.getClass().getName() + ":" + id + ":" + element;
}
}
public static class InsertOperation extends EditOperation {
public InsertOperation(String id, Node element) {
this.id = id;
this.element = element;
}
}
public static class DeleteOperation extends EditOperation {
public DeleteOperation(String id) {
this.id = id;
this.element = null;
}
}
public static class ReplaceOperation extends EditOperation {
public ReplaceOperation(String id, Node element) {
this.id = id;
this.element = element;
}
public ReplaceOperation(Node element) {
this(null, element);
}
}
public static class AttributeOperation extends EditOperation {
public AttributeOperation(String id, Node element,
Map attributes) {
this.id = id;
this.element = element;
this.attributes = attributes;
}
}
public static class CursorList {
public int cursor;
public List list;
public CursorList() {
list = new ArrayList();
cursor = 0;
}
boolean add(EditOperation op) {
assert (null != getNodeId(op.element));
if (list.size() == cursor) {
cursor++;
return list.add(op);
}
list.set(cursor++, op);
return true;
}
boolean addAll(List ops) {
if (list.size() == cursor) {
cursor += ops.size();
return list.addAll(ops);
}
//we are in the middle so iterate and add
for (EditOperation op : ops) {
add(op);
}
return true;
}
public List asList() {
return list.subList(0, cursor);
}
}
public static class DiffConfig {
public int maxDiffs = Integer.MAX_VALUE;
public boolean isInsDel = false;
public boolean isDebug = false;
public boolean isDebugAB = false;
public boolean isAtt = false;
private static String MAXDIFFS = "maxDiffs";
private static String INSDEL = "insDel";
private static String DEBUG = "debug";
private static String DEBUGAB = "debugAB";
private static String ATT = "att";
private static Pattern SPACE_PATTERN = Pattern.compile(" ");
public DiffConfig(String config) {
try {
HashMap params = new HashMap();
String[] paramPairs = SPACE_PATTERN.split(config);
for (String pair: paramPairs) {
int center = pair.indexOf("=");
if (-1 != center) {
String key = pair.substring(0,center);
String value = pair.substring(center + 1);
params.put(key, value);
} else {
//singleton values act as flags
params.put(pair, "true");
}
}
String maxDiffsParam = (String) params.get(MAXDIFFS);
if (null != maxDiffsParam) {
maxDiffs = Integer.parseInt(maxDiffsParam);
}
String insDelParam = (String) params.get(INSDEL);
if (null != insDelParam) {
isInsDel = true;
}
String debugParam = (String) params.get(DEBUG);
if (null != debugParam) {
isDebug = true;
}
String debugABParam = (String) params.get(DEBUGAB);
if (null != debugABParam) {
isDebugAB = true;
}
String attParam = (String) params.get(ATT);
if (null != attParam) {
isAtt = true;
}
} catch (Exception e) {
log.log(Level.SEVERE, "Malformed DiffConfig " + config, e);
}
}
public String toString() {
String values =
MAXDIFFS + ": " + String.valueOf(maxDiffs) + " " +
DEBUG + ": " + String.valueOf(isDebug) + " " +
DEBUGAB + ": " + String.valueOf(isDebugAB) + " " +
ATT + ": " + String.valueOf(isAtt) + " " +
INSDEL + ": " + String.valueOf(isInsDel);
return values;
}
}
static {
try {
DOCUMENT_BUILDER = DocumentBuilderFactory.newInstance().newDocumentBuilder();
} catch (ParserConfigurationException e) {
log.log(Level.SEVERE, "unable to acquire a DocumentBuilder", e);
}
}
public static Document getNewDocument() {
Document doc = DOCUMENT_BUILDER.newDocument();
applyDocumentSettings(doc);
return doc;
}
private static void applyDocumentSettings(Document doc) {
if (!isDOMChecking) {
Method setErrorCheckingMethod = null;
try {
setErrorCheckingMethod = doc.getClass().getMethod("setErrorChecking",
new Class[]{boolean.class});
setErrorCheckingMethod.invoke(doc, new Object[]{Boolean.FALSE});
} catch (Exception e) {
if (log.isLoggable(Level.FINE)) {
log.log(Level.FINE, "DOM error checking not disabled ", e);
}
}
}
}
public static String DocumentTypetoString(String publicID, String systemID,
String root) {
return "";
}
public static String nodeToString(Node node) throws IOException {
StringWriter writer = new StringWriter();
try {
printNode(node, writer);
} finally {
writer.flush();
return writer.toString();
}
}
public static String childrenToString(Node node) throws IOException {
StringWriter writer = new StringWriter();
try {
printChildNodes(node, writer);
} finally {
writer.flush();
return writer.toString();
}
}
public static void printChildNodes(Node node, Writer writer) throws IOException {
NodeList children = node.getChildNodes();
int l = children.getLength();
for (int i = 0; i < l; i++) {
printNode(children.item(i), writer);
}
}
public static void printNodeCDATA(Node node, Writer writer) throws IOException {
printNode(node, writer, 0, true, false, true);
}
public static void printNode(Node node, Writer writer) throws IOException {
printNode(node, writer, 0, true, false, false);
}
private static void printNode(
Node node, Writer writer,
int depth, boolean allowAddingWhitespace,
boolean addTrailingNewline, boolean isInCdata) throws IOException {
switch (node.getNodeType()) {
case Node.DOCUMENT_NODE:
//writer.write("\n");
// recurse on each child
NodeList nodes = node.getChildNodes();
if (nodes != null) {
for (int i = 0; i < nodes.getLength(); i++) {
printNode(nodes.item(i), writer, depth + 1,
allowAddingWhitespace, false, isInCdata);
}
}
break;
case Node.ELEMENT_NODE:
String name = node.getNodeName();
//#2393 removed limited test for
writer.write("<");
writer.write(name);
NamedNodeMap attributes = node.getAttributes();
for (int i = 0; i < attributes.getLength(); i++) {
Node current = attributes.item(i);
writer.write(" ");
writer.write(current.getNodeName());
writer.write("=\"");
writer.write(escapeAttribute(current.getNodeValue()));
writer.write("\"");
}
// #2393 allow short closing of certain tags
if (!node.hasChildNodes() && xmlShortClosingAllowed(node)) {
writer.write(" />");
break;
}
writer.write(">");
// recurse on each child
NodeList children = node.getChildNodes();
if (children != null) {
int childrenLength = children.getLength();
for (int i = 0; i < childrenLength; i++) {
boolean childAddTrailingNewline = false;
if (allowAddingWhitespace) {
if ((i + 1) < childrenLength) {
Node nextChild = children.item(i + 1);
// We don't add the newline if the next tag is a TD,
// because when rendering our tabbedPane, if there's
// any whitespace between the adjacent TDs, then
// Internet Explorer will add vertical spacing
// Also same for some other tags to avoid extra space (JIRA ICE-1351)
childAddTrailingNewline =
!isWhitespaceText(nextChild) && isNewlineAllowedTag(nextChild);
}
}
printNode(children.item(i), writer, depth + 1,
allowAddingWhitespace,
childAddTrailingNewline, isInCdata);
}
}
writer.write("");
//ICE-5625: Adding a newline causes issues with some Mojarra tests but can also
// affect general layout and value display.
// if (allowAddingWhitespace && addTrailingNewline) {
// writer.write("\n");
// }
break;
case Node.TEXT_NODE:
if (!isInCdata) {
writer.write(node.getNodeValue());
} else {
String value = node.getNodeValue();
String escaped = CDATA_END.matcher(value)
.replaceAll("]]>]]>");
break;
}
}
private static boolean isWhitespaceText(Node node) {
if (node.getNodeType() == Node.TEXT_NODE) {
String val = node.getNodeValue();
// Treat an empty string or null value like whitespace
if (val == null){
return true;
}
for (int i = val.length() - 1; i >= 0; i--) {
if (!Character.isWhitespace(val.charAt(i))) {
return false;
}
}
return true;
}
return false;
}
private static boolean isNewlineAllowedTag(Node node) {
short nodeType = node.getNodeType();
String nodeName = node.getNodeName().toLowerCase();
return !(nodeType == Node.ELEMENT_NODE &&
TAGS_THAT_ALLOW_NEWLINE.contains(nodeName));
}
/**
* Check if short closing form is allowed. Short closing is of the form
*
*
* @param node Node
* @return true if allowed
*/
private static boolean xmlShortClosingAllowed(Node node) {
short nodeType = node.getNodeType();
String nodeName = node.getNodeName().toLowerCase();
return (nodeType == Node.ELEMENT_NODE &&
TAGS_THAT_CAN_CLOSE_SHORT.contains(nodeName));
}
/* Return the first child of the given nodeName under the given node.
* @param node node to search under
* @param name nodeName to search for
*/
public static Node getChildByNodeName(Node node, String name) {
NodeList children = node.getChildNodes();
Node child;
int l = children.getLength();
for (int i = 0; i < l; i++) {
child = children.item(i);
if (child.getNodeName().equalsIgnoreCase(name)) {
return child;
}
}
return null;
}
/**
* Determine the set of top-level nodes in newDOM that are different from
* the corresponding nodes in oldDOM. If the DOMs are identical, this
* method will return an empty array. This method should not be called if one
* of the DOMs is null.
*
* @param config optional diff configuration
* @param oldDOM original dom Document
* @param newDOM changed dom Document
* @return array of top-level nodes in newDOM that differ from oldDOM, an
* empty array if no nodes are different
*/
public static List domDiff(DiffConfig config,
Document oldDOM, Document newDOM) {
return nodeDiff(config, oldDOM.getDocumentElement(), newDOM.getDocumentElement());
}
/**
* Variant of domDiff with default (null) DiffConfig
*/
public static List domDiff(Document oldDOM, Document newDOM) {
return domDiff(null, oldDOM, newDOM);
}
/**
* Determine the set of top-level nodes in newNode subtree that are different from
* the corresponding nodes in oldNode. If the subtrees are identical, this
* method will return an empty array. This method should not be called if one
* of the subtrees is null.
*
* @param config optional diff configuration
* @param oldNode original DOM subtree
* @param newNode changed DOM subtree
* @return array of top-level nodes in newNode subtree that differ from
* oldNode subtree, an empty array if no nodes are different
*/
public static List nodeDiff(DiffConfig config,
Node oldNode, Node newNode) {
CursorList nodeDiffs = new CursorList();
try {
if (isDebug(config)) {
log.log(Level.INFO, "nodeDiff debug " + config);
}
if (isDebugAB(config)) {
dumpDebugAB(oldNode, newNode);
}
boolean success;
success = compareNodes(config, nodeDiffs, oldNode, newNode);
if (!success) {
log.severe("Diff propagated to root but no ID set " + newNode);
if (isDebugAB(config)) {
dumpDebugAB(oldNode, newNode);
}
}
assert checkPrunes(nodeDiffs.asList());
} catch (Throwable t) {
//assert will not normally require a special try/catch
//but Throwable handling above this is not sufficient
log.log(Level.SEVERE, "Pruning failure", t);
}
return nodeDiffs.asList();
}
/**
* Variant of nodeDiff with default (null) DiffConfig
*/
public static List nodeDiff(Node oldNode, Node newNode) {
return nodeDiff(null, oldNode, newNode);
}
/**
* Nodes are equivalent if they have the same names, attributes, and
* children
*
* @param nodeDiffs
* @param oldNode
* @param newNode
* @return true if diff was handled fully
*/
private static boolean compareNodes(DiffConfig config, CursorList nodeDiffs,
Node oldNode, Node newNode) {
int startCursor = nodeDiffs.cursor;
//h:messages renderer does not care about this constrain
// if (!oldNode.getNodeName().equals(newNode.getNodeName())) {
// debugNameDifference(config, newNode, oldNode, newNode);
// return false;
// }
if (!compareIDs(oldNode, newNode)) {
debugIdDifference(config, newNode, oldNode, newNode, "A");
return false;
}
if (isSuppressed(newNode)) {
return true;
}
if (isAtt(config)) {
String id = getNodeId(newNode);
AttributeOperation op = detectAttributes(id, oldNode, newNode);
if (null != op) {
if (null == id) {
//attributes differ, but no id to apply change
debugAttributesDifference(config, newNode, oldNode, newNode, "A");
return false;
}
if (op.attributes.containsKey("value")) {
//value update will require a special case in jsf.js
nodeDiffs.add(new ReplaceOperation(newNode));
debugAttributeValueDifference(config, newNode, oldNode, newNode, "A replace");
return true;
}
nodeDiffs.add(op);
debugAttributesDifference(config, newNode, oldNode, newNode, "B attribute");
}
} else {
if (!compareAttributes(oldNode, newNode)) {
String id = getNodeId(newNode);
if (null == id) {
debugAttributesDifference(config, newNode, oldNode, newNode, "C");
return false;
}
nodeDiffs.add(new ReplaceOperation(newNode));
debugAttributesDifference(config, newNode, oldNode, newNode, "D replace");
return true;
}
}
if (!compareStrings(oldNode.getNodeValue(),
newNode.getNodeValue())) {
String id = getNodeId(newNode);
if (null == id) {
debugTextValueDifference(config, newNode, oldNode, newNode, "A");
return false;
}
nodeDiffs.add(new ReplaceOperation(newNode));
debugTextValueDifference(config, newNode, oldNode, newNode, "B replace");
return true;
}
//if insert/delete is not enabled perform simple DOM diff below
if (!isInsDel(config, newNode)) {
NodeList oldChildNodes = oldNode.getChildNodes();
NodeList newChildNodes = newNode.getChildNodes();
int oldChildLength = oldChildNodes.getLength();
int newChildLength = newChildNodes.getLength();
if (oldChildLength != newChildLength) {
String id = getNodeId(newNode);
if (null == id) {
debugChildCountDifference(config, newNode, oldNode, newNode, oldChildLength, newChildLength, "A");
return false;
}
nodeDiffs.add(new ReplaceOperation(newNode));
debugChildCountDifference(config, newNode, oldNode, newNode, oldChildLength, newChildLength, "B replace");
return true;
}
for (int i = 0; i < newChildLength; i++) {
if (!compareNodes(config, nodeDiffs, oldChildNodes.item(i),
newChildNodes.item(i))) {
String id = getNodeId(newNode);
if (null != id) {
//subtree was unable to process the diff
nodeDiffs.cursor = startCursor;
nodeDiffs.add(new ReplaceOperation(newNode));
diffDebug(config, "replace: diff from below ", newNode);
return true;
}
return false;
}
}
if (null != config) {
int numDiffs = nodeDiffs.cursor - startCursor;
if (numDiffs > config.maxDiffs) {
String id = getNodeId(newNode);
if (null != id) {
if (log.isLoggable(Level.FINE)) {
log.fine("DOM diff coalescing " +
(nodeDiffs.cursor - startCursor) +
" diffs for " + id);
}
//subtree generated a diff that was too large
nodeDiffs.cursor = startCursor;
nodeDiffs.add(new ReplaceOperation(newNode));
diffDebug(config, "replace: exceeded maxDiffs ", newNode);
return true;
}
}
}
return true;
}
//searching for insert/delete is enabled
if (!findChildOps(config, nodeDiffs, oldNode, newNode)) {
String id = getNodeId(newNode);
if (null != id) {
//subtree was unable to process the diff
nodeDiffs.cursor = startCursor;
nodeDiffs.add(new ReplaceOperation(newNode));
diffDebug(config, "replace: diff below ", newNode);
return true;
}
return false;
}
return true;
}
private static String PAD = "not_an_id_of_any_element";
/**
* Nodes are equivalent if they have the same names, attributes, and
* children
*
* @param nodeDiffs
* @param oldNode
* @param newNode
* @return true if diff was handled fully
*/
private static boolean findChildOps(DiffConfig config, CursorList nodeDiffs,
Node oldNode, Node newNode) {
NodeList oldChildNodes = oldNode.getChildNodes();
NodeList newChildNodes = newNode.getChildNodes();
int oldChildCount = oldChildNodes.getLength();
int newChildCount = newChildNodes.getLength();
if ((0 == oldChildCount) && (0 == newChildCount) ) {
//the nodes themselves have already been compared
//so if they both have no children, they match
return true;
}
if ((0 == oldChildCount) || (0 == newChildCount) ) {
//the node is either newly populated or cleared
//simple replace is the most efficient
if (null == getNodeId(newNode)) {
debugChildCountDifference(config, newNode, oldNode, newNode, oldChildCount, newChildCount, "C");
return false;
}
nodeDiffs.add(new ReplaceOperation(newNode));
debugChildCountDifference(config, newNode, oldNode, newNode, oldChildCount, newChildCount, "D replace: cleared");
return true;
}
List oldList = getListOfIds(oldNode.getChildNodes());
List newList = getListOfIds(newNode.getChildNodes());
List ops = new ArrayList();
List oldDirectCompare = new ArrayList();
List newDirectCompare = new ArrayList();
boolean keepRunning = true;
int oldListLen = oldList.size();
int newListLen = newList.size();
int oldIndex = 0;
int newIndex = 0;
while (keepRunning) {
//mainly operate on IDs to detect insert and delete
String currentOld = paddedGet(oldList, oldIndex);
String currentNew = paddedGet(newList, newIndex);
if (!currentOld.equals(currentNew)) {
boolean newInOld = oldList.contains(currentNew);
boolean oldInNew = newList.contains(currentOld);
EditOperation operation = null;
String insertAnchor = null;
if (newInOld && oldInNew) {
//swap operation is not supported by jsf bridge
keepRunning = false;
//cancel all operations and replace oldNode with newNode
if (null == getNodeId(newNode)) {
debugNodeDifference(config, newNode, "Node swapped with other", "A");
return false;
}
nodeDiffs.add(new ReplaceOperation(newNode));
debugNodeDifference(config, newNode, "Node swapped with other", "B replace");
ops = null;
break;
}
if (newInOld && !oldInNew) {
operation = new DeleteOperation(currentOld);
debugNodeDifference(config, oldNode, "Node deleted " + currentOld, "A");
oldIndex++;
}
if (!newInOld && oldInNew) {
if (newIndex > 0) {
insertAnchor = paddedGet(newList, newIndex - 1);
} else {
//TODO Implement InsertBefore
//this case is properly handled by an insert "before"
if (null == getNodeId(newNode)) {
debugNodeDifference(config, newNode, "Node inserted before other", "A");
return false;
}
nodeDiffs.add(new ReplaceOperation(newNode));
debugNodeDifference(config, newNode, "Node inserted before other", "B replace");
ops = null;
break;
}
if (insertAnchor.startsWith("?")) {
//anchor is not valid so we must replace parent
if (null == getNodeId(newNode)) {
debugNodeDifference(config, newNode, "Invalid state", "A");
return false;
}
nodeDiffs.add(new ReplaceOperation(newNode));
debugNodeDifference(config, newNode, "No insert ID", "B replace");
ops = null;
break;
} else {
operation = new InsertOperation(insertAnchor,
newChildNodes.item(newIndex) );
debugNodeDifference(config, newNode, "Inserted node", "A");
diffDebug(config, "insert: !new old ",
newChildNodes.item(newIndex));
}
newIndex++;
}
if (!newInOld && !oldInNew) {
if (PAD == currentNew) {
operation = new DeleteOperation(currentOld);
diffDebug(config, "delete: ins/del " + currentOld, null);
} else if (PAD == currentOld) {
if (newIndex > 0) {
insertAnchor = paddedGet(newList, newIndex - 1);
} else {
//a new child added to an empty parent
//can be handled by a parent replace
//this should be covered by the length test on entry
if (null == getNodeId(newNode)) {
debugNodeDifference(config, newNode, "Child added to previously empty parent", "A");
return false;
}
nodeDiffs.add(new ReplaceOperation(newNode));
debugNodeDifference(config, newNode, "Child added to previously empty parent", "B replace");
ops = null;
break;
}
if (insertAnchor.startsWith("?")) {
//anchor is not valid so we must replace parent
if (null == getNodeId(newNode)) {
debugNodeDifference(config, newNode, "Invalid state", "C");
return false;
}
nodeDiffs.add(new ReplaceOperation(newNode));
debugNodeDifference(config, newNode, "No insert ID", "D replace");
ops = null;
break;
} else {
operation = new InsertOperation(insertAnchor,
newChildNodes.item(newIndex) );
debugNodeDifference(config, newNode, "Inserted node", "B");
diffDebug(config, "insert2 ",
newChildNodes.item(newIndex));
}
} else {
//two completely different IDs at this location
//cancel and let parent handle
if (null == getNodeId(newNode)) {
debugIdDifference(config, newNode, oldNode, newNode, "B");
return false;
}
nodeDiffs.add(new ReplaceOperation(newNode));
debugIdDifference(config, newNode, oldNode, newNode, "C replace");
ops = null;
break;
}
oldIndex++;
newIndex++;
}
ops.add(operation);
} else {
//keep going on match
oldDirectCompare.add(oldChildNodes.item(oldIndex));
newDirectCompare.add(newChildNodes.item(newIndex));
oldIndex++;
newIndex++;
}
if ((oldIndex >= oldListLen) && (newIndex >= newListLen) ) {
keepRunning = false;
}
}
//examine the subtrees below the identified insert/delete locations
if (null != ops) {
nodeDiffs.addAll(ops);
int newChildLength = newDirectCompare.size();
boolean handledBelow = true;
for (int i = 0; i < newChildLength; i++) {
if (!compareNodes(config, nodeDiffs, oldDirectCompare.get(i),
newDirectCompare.get(i))) {
handledBelow = false;
}
}
return handledBelow;
}
return true;
}
private static String paddedGet(List theList, int theIndex) {
if (theIndex >= theList.size()) {
return PAD;
}
return theList.get(theIndex);
}
private static boolean paddedContains(List theList, String theValue) {
if (PAD == theValue) {
return true;
}
return theList.contains(theValue);
}
private static List getListOfNodes(NodeList nodeList) {
int length = nodeList.getLength();
List listOfNode = new ArrayList(length);
for (int i = 0; i < length; i++) {
listOfNode.add(nodeList.item(i));
}
return listOfNode;
}
private static List getListOfIds(NodeList nodeList) {
int length = nodeList.getLength();
List listOfIds = new ArrayList(length);
for (int i = 0; i < length; i++) {
Node node = nodeList.item(i);
String id = "?" + i;
if (node instanceof Element) {
String tempId = ((Element) node).getAttribute("id");
if ((null != tempId) && (!"".equals(tempId))) {
id = tempId;
}
}
listOfIds.add(id);
}
return listOfIds;
}
private static boolean compareStrings(String oldString, String newString) {
if ((null == oldString) && (null == newString)) {
return true;
}
try {
return (oldString.equals(newString));
} catch (NullPointerException e) {
}
return false;
}
/**
* @param oldNode
* @param newNode
* @return true if Nodes have the same IDs
*/
public static boolean compareIDs(Node oldNode, Node newNode) {
if (!(oldNode instanceof Element) &&
!(newNode instanceof Element)) {
//both do not have an ID
return true;
}
try {
return ((Element) oldNode).getAttribute("id").equals(
((Element) newNode).getAttribute("id"));
} catch (Exception e) {
}
return false;
}
/**
* @param oldNode
* @param newNode
* @return true if Nodes have the same attributes
*/
public static boolean compareAttributes(Node oldNode, Node newNode) {
boolean oldHasAttributes = oldNode.hasAttributes();
boolean newHasAttributes = newNode.hasAttributes();
if (!oldHasAttributes && !newHasAttributes) {
return true;
}
if (oldHasAttributes != newHasAttributes) {
return false;
}
NamedNodeMap oldMap = oldNode.getAttributes();
NamedNodeMap newMap = newNode.getAttributes();
int oldLength = oldMap.getLength();
int newLength = newMap.getLength();
if (oldLength != newLength) {
return false;
}
Node newAttribute = null;
Node oldAttribute = null;
for (int i = 0; i < newLength; i++) {
newAttribute = newMap.item(i);
oldAttribute = oldMap.getNamedItem(newAttribute.getNodeName());
if (null == oldAttribute) {
return false;
}
if (!(String.valueOf(oldAttribute.getNodeValue()).equals(
String.valueOf(newAttribute.getNodeValue())))) {
return false;
}
}
return true;
}
/** The attribute operation sets attributes on the target node, so
* deleted attributes are simply cleared.
* @param oldNode
* @param newNode
* @return op if Nodes have different attributes
*/
public static AttributeOperation detectAttributes(String id,
Node oldNode, Node newNode) {
boolean oldHasAttributes = oldNode.hasAttributes();
boolean newHasAttributes = newNode.hasAttributes();
if (!oldHasAttributes && !newHasAttributes) {
return null;
}
Map output = new HashMap();
NamedNodeMap oldMap = oldNode.getAttributes();
NamedNodeMap newMap = newNode.getAttributes();
int oldLength = oldMap.getLength();
int newLength = newMap.getLength();
Node newAttribute = null;
Node oldAttribute = null;
for (int i = 0; i < newLength; i++) {
newAttribute = newMap.item(i);
String newName = newAttribute.getNodeName();
String newValue = newAttribute.getNodeValue();
oldAttribute = oldMap.getNamedItem(newName);
if (null == oldAttribute) {
output.put(newName, newValue);
} else {
if (!newValue.equals(oldAttribute.getNodeValue())) {
output.put(newName, newValue);
}
}
}
for (int i = 0; i < oldLength; i++) {
oldAttribute = oldMap.item(i);
String oldName = oldAttribute.getNodeName();
newAttribute = newMap.getNamedItem(oldName);
if (null == newAttribute) {
//clear values of old attributes no longer present
output.put(oldName, "");
}
}
if (output.size() > 0) {
return new AttributeOperation(id, newNode, output);
}
return null;
}
public static boolean isDOMDiffFeature(String feature, Node newNode) {
if (!newNode.hasAttributes()) {
return false;
}
NamedNodeMap newMap = newNode.getAttributes();
Node featureMarker = newMap.getNamedItem(feature);
if (null != featureMarker) {
String featureValue = featureMarker.getNodeValue();
if (DIFF_TRUE.equals(featureValue)) {
if (log.isLoggable(Level.FINE)) {
log.fine("DOM diff " + feature + " on " + newNode);
}
return true;
}
}
return false;
}
public static boolean isSuppressed(Node newNode) {
return isDOMDiffFeature(DIFF_SUPPRESS, newNode);
}
public static boolean isInsDel(DiffConfig config, Node newNode) {
if ((null != config) && config.isInsDel) {
return true;
}
return isDOMDiffFeature(DIFF_INSDEL, newNode);
}
public static boolean isAtt(DiffConfig config) {
if ((null != config) && config.isAtt) {
return true;
}
return false;
}
static void diffDebug(DiffConfig config, String message, Node node) {
if (!isDebug(config)) {
return;
}
String nodePath = "";
Node parent = node;
while (null != parent) {
nodePath = toDebugString(parent) + " " + nodePath;
parent = parent.getParentNode();
}
log.info(message + nodePath);
}
static boolean isDebug(DiffConfig config) {
return ((null != config) && config.isDebug);
}
static boolean isDebugAB(DiffConfig config) {
return ((null != config) && config.isDebugAB);
}
static void dumpDebugAB(Node oldNode, Node newNode) {
String oldDebug = "null document";
String newDebug = "null document";
if (null != oldNode) {
oldDebug = toDebugStringDeep(oldNode);
}
if (null != newNode) {
newDebug = toDebugStringDeep(newNode);
}
log.log(Level.INFO, "nodeDiff--------------debugA\n");
log.log(Level.INFO, "nodeDiff oldNode \n" + oldDebug);
log.log(Level.INFO, "nodeDiff--------------debugB\n");
log.log(Level.INFO, "nodeDiff newNode \n" + newDebug);
log.log(Level.INFO, "nodeDiff--------------------\n");
}
public static String getNodeId(Node node) {
if (node instanceof Element) {
String id = ((Element) node).getAttribute("id");
if ((null != id) && (!"".equals(id))) {
return id;
}
}
return null;
}
private static boolean pruneCheckWarned = false;
private static boolean checkPrunes(List nodeDiffs) {
if (!pruneCheckWarned) {
log.severe("nodeDiff assertion checking active, disable to improve performance");
pruneCheckWarned = true;
}
List justNodes = new ArrayList();
for (EditOperation op : nodeDiffs) {
if (op instanceof ReplaceOperation) {
Node startNode = op.element;
Node ascendNode = ascendToNodeWithID(op.element);
if (!startNode.equals(ascendNode)) {
log.warning("ID missing " + startNode + " " + ascendNode);
return false;
}
op.element = ascendNode;
justNodes.add(op.element);
}
}
Node[] prunedDiff = null;
try {
prunedDiff = pruneAncestors(justNodes);
} catch (Exception e) {
e.printStackTrace();
}
if (null == prunedDiff) {
if (0 == nodeDiffs.size()) {
return true;
}
}
if (nodeDiffs.size() == prunedDiff.length) {
return true;
}
log.warning("pruning occured " + nodeDiffs.size() + " " + prunedDiff.length);
return false;
}
public static Element ascendToNodeWithID(final Node start) {
Node node = start;
while (null != node) {
if (node instanceof Element) {
String id = ((Element) node).getAttribute("id");
if ((null != id) && (!"".equals(id))) {
return (Element) node;
}
}
node = node.getParentNode();
}
//stop DOM diffing at the root
return start.getOwnerDocument().getDocumentElement();
}
/**
* Escaping is required unless the escape attribute is present and is
* "false"
*
* @param uiComponent
* @return
*/
public static boolean escapeIsRequired(UIComponent uiComponent) {
Object escapeAttribute = uiComponent.getAttributes().get("escape");
if (escapeAttribute != null) {
if (escapeAttribute instanceof String) {
return Boolean.valueOf((String) escapeAttribute).booleanValue();
} else if (escapeAttribute instanceof Boolean) {
return ((Boolean) escapeAttribute).booleanValue();
}
}
return true; //default
}
/**
* Escape Java String and discard illegal characters as suitable for a
* double quoted "" XML attribute value
*
* @param text
* @return escaped XML attribute value
*/
public static String escapeAttribute(String text) {
if (null == text) {
return "";
}
char[] chars = text.toCharArray();
StringBuilder buffer = new StringBuilder(chars.length);
for (int index = 0; index < chars.length; index++) {
char ch = chars[index];
if (ch <= 31) {
if (ch == '\t' || ch == '\n' || ch == '\r') {
buffer.append(ch);
}
//skip any other control character
} else if (ch == '<') {
buffer.append("<");
} else if (ch == '>') {
buffer.append(">");
} else if (ch == '&') {
buffer.append("&");
} else if (ch == '"') {
buffer.append(""");
} else {
buffer.append(ch);
}
}
return buffer.toString();
}
public static String escapeAnsi(String text) {
if (null == text) {
return "";
}
char[] chars = text.toCharArray();
StringBuffer buffer = new StringBuffer(chars.length);
for (int index = 0; index < chars.length; index++) {
char ch = chars[index];
//see: http://www.w3schools.com/tags/ref_ascii.asp
if (ch <= 31) {
if (ch == '\t' || ch == '\n' || ch == '\r') {
buffer.append(ch);
}
//skip any other control character
} else if (ch == 127) {
//skip 'delete' character
} else if (ch == '>') {
buffer.append(">");
} else if (ch == '<') {
buffer.append("<");
} else if (ch == '&') {
buffer.append("&");
} else if (ch == '\'') {
buffer.append("'");
} else if (ch == '"') {
buffer.append(""");
} else if (ch >= 0xA0 && ch <= 0xff) {
buffer.append("").append(Integer.toString(ch)).append(";");
} else if (ch == 0x20AC) {//special case for euro symbol
buffer.append("€");
} else {
buffer.append(ch);
}
}
return buffer.toString();
}
/**
* @param character
* @return
*/
private static String escapeAnsi(char character) {
int indexOfEscapedCharacter = character - 0xA0;
return ansiCharacters[indexOfEscapedCharacter];
}
/**
* from http://www.w3.org/TR/REC-html40/sgml/entities.html
* Portions Copyright International Organization for Standardization 1986
* Permission to copy in any form is granted for use with
* conforming SGML systems and applications as defined in
* ISO 8879, provided this notice is included in all copies.
*/
private static String[] ansiCharacters = new String[]{
"nbsp"
/* " " -- no-break space = non-breaking space, U+00A0 ISOnum -->*/,
"iexcl" /* "¡" -- inverted exclamation mark, U+00A1 ISOnum */,
"cent" /* "¢" -- cent sign, U+00A2 ISOnum */,
"pound" /* "£" -- pound sign, U+00A3 ISOnum */,
"curren" /* "¤" -- currency sign, U+00A4 ISOnum */,
"yen" /* "¥" -- yen sign = yuan sign, U+00A5 ISOnum */,
"brvbar"
/* "¦" -- broken bar = broken vertical bar, U+00A6 ISOnum */,
"sect" /* "§" -- section sign, U+00A7 ISOnum */,
"uml"
/* "¨" -- diaeresis = spacing diaeresis, U+00A8 ISOdia */,
"copy" /* "©" -- copyright sign, U+00A9 ISOnum */,
"ordf"
/* "ª" -- feminine ordinal indicator, U+00AA ISOnum */,
"laquo"
/* "«" -- left-pointing double angle quotation mark = left pointing guillemet, U+00AB ISOnum */,
"not" /* "¬" -- not sign, U+00AC ISOnum */,
"shy"
/* "" -- soft hyphen = discretionary hyphen, U+00AD ISOnum */,
"reg"
/* "®" -- registered sign = registered trade mark sign, U+00AE ISOnum */,
"macr"
/* "¯" -- macron = spacing macron = overline = APL overbar, U+00AF ISOdia */,
"deg" /* "°" -- degree sign, U+00B0 ISOnum */,
"plusmn"
/* "±" -- plus-minus sign = plus-or-minus sign, U+00B1 ISOnum */,
"sup2"
/* "²" -- superscript two = superscript digit two = squared, U+00B2 ISOnum */,
"sup3"
/* "³" -- superscript three = superscript digit three = cubed, U+00B3 ISOnum */,
"acute"
/* "´" -- acute accent = spacing acute, U+00B4 ISOdia */,
"micro" /* "µ" -- micro sign, U+00B5 ISOnum */,
"para"
/* "¶" -- pilcrow sign = paragraph sign, U+00B6 ISOnum */,
"middot"
/* "·" -- middle dot = Georgian comma = Greek middle dot, U+00B7 ISOnum */,
"cedil" /* "¸" -- cedilla = spacing cedilla, U+00B8 ISOdia */,
"sup1"
/* "¹" -- superscript one = superscript digit one, U+00B9 ISOnum */,
"ordm"
/* "º" -- masculine ordinal indicator, U+00BA ISOnum */,
"raquo"
/* "»" -- right-pointing double angle quotation mark = right pointing guillemet, U+00BB ISOnum */,
"frac14"
/* "¼" -- vulgar fraction one quarter = fraction one quarter, U+00BC ISOnum --> */,
"frac12"
/* "½" -- vulgar fraction one half = fraction one half, U+00BD ISOnum */,
"frac34"
/* "¾" -- vulgar fraction three quarters = fraction three quarters, U+00BE ISOnum */,
"iquest"
/* "¿" -- inverted question mark = turned question mark, U+00BF ISOnum */,
"Agrave"
/* "À" -- latin capital letter A with grave = latin capital letter A grave, U+00C0 ISOlat1 */,
"Aacute"
/* "Á" -- latin capital letter A with acute, U+00C1 ISOlat1 */,
"Acirc"
/* "Â" -- latin capital letter A with circumflex, U+00C2 ISOlat1 */,
"Atilde"
/* "Ã" -- latin capital letter A with tilde, U+00C3 ISOlat1 */,
"Auml"
/* "Ä" -- latin capital letter A with diaeresis, U+00C4 ISOlat1 */,
"Aring"
/* "Å" -- latin capital letter A with ring above = latin capital letter A ring, U+00C5 ISOlat1 --> */,
"AElig"
/* "Æ" -- latin capital letter AE = latin capital ligature AE, U+00C6 ISOlat1 --> */,
"Ccedil"
/* "Ç" -- latin capital letter C with cedilla, U+00C7 ISOlat1 */,
"Egrave"
/* "È" -- latin capital letter E with grave, U+00C8 ISOlat1 */,
"Eacute"
/* "É" -- latin capital letter E with acute, U+00C9 ISOlat1 */,
"Ecirc"
/* "Ê" -- latin capital letter E with circumflex, U+00CA ISOlat1 */,
"Euml"
/* "Ë" -- latin capital letter E with diaeresis, U+00CB ISOlat1 */,
"Igrave"
/* "Ì" -- latin capital letter I with grave, U+00CC ISOlat1 */,
"Iacute"
/* "Í" -- latin capital letter I with acute, U+00CD ISOlat1 */,
"Icirc"
/* "Î" -- latin capital letter I with circumflex, U+00CE ISOlat1 */,
"Iuml"
/* "Ï" -- latin capital letter I with diaeresis, U+00CF ISOlat1 */,
"ETH" /* "Ð" -- latin capital letter ETH, U+00D0 ISOlat1 */,
"Ntilde"
/* "Ñ" -- latin capital letter N with tilde, U+00D1 ISOlat1 */,
"Ograve"
/* "Ò" -- latin capital letter O with grave, U+00D2 ISOlat1 */,
"Oacute"
/* "Ó" -- latin capital letter O with acute, U+00D3 ISOlat1 */,
"Ocirc"
/* "Ô" -- latin capital letter O with circumflex, U+00D4 ISOlat1 */,
"Otilde"
/* "Õ" -- latin capital letter O with tilde, U+00D5 ISOlat1 */,
"Ouml"
/* "Ö" -- latin capital letter O with diaeresis, U+00D6 ISOlat1 */,
"times" /* "×" -- multiplication sign, U+00D7 ISOnum */,
"Oslash"
/* "Ø" -- latin capital letter O with stroke = latin capital letter O slash, U+00D8 ISOlat1 */,
"Ugrave"
/* "Ù" -- latin capital letter U with grave, U+00D9 ISOlat1 */,
"Uacute"
/* "Ú" -- latin capital letter U with acute, U+00DA ISOlat1 */,
"Ucirc"
/* "Û" -- latin capital letter U with circumflex, U+00DB ISOlat1 */,
"Uuml"
/* "Ü" -- latin capital letter U with diaeresis, U+00DC ISOlat1 */,
"Yacute"
/* "Ý" -- latin capital letter Y with acute, U+00DD ISOlat1 */,
"THORN"
/* "Þ" -- latin capital letter THORN, U+00DE ISOlat1 */,
"szlig"
/* "ß" -- latin small letter sharp s = ess-zed, U+00DF ISOlat1 */,
"agrave"
/* "à" -- latin small letter a with grave = latin small letter a grave, U+00E0 ISOlat1 */,
"aacute"
/* "á" -- latin small letter a with acute, U+00E1 ISOlat1 */,
"acirc"
/* "â" -- latin small letter a with circumflex, U+00E2 ISOlat1 */,
"atilde"
/* "ã" -- latin small letter a with tilde, U+00E3 ISOlat1 */,
"auml"
/* "ä" -- latin small letter a with diaeresis, U+00E4 ISOlat1 */,
"aring"
/* "å" -- latin small letter a with ring above = latin small letter a ring, U+00E5 ISOlat1 */,
"aelig"
/* "æ" -- latin small letter ae = latin small ligature ae, U+00E6 ISOlat1 */,
"ccedil"
/* "ç" -- latin small letter c with cedilla, U+00E7 ISOlat1 */,
"egrave"
/* "è" -- latin small letter e with grave, U+00E8 ISOlat1 */,
"eacute"
/* "é" -- latin small letter e with acute, U+00E9 ISOlat1 */,
"ecirc"
/* "ê" -- latin small letter e with circumflex, U+00EA ISOlat1 */,
"euml"
/* "ë" -- latin small letter e with diaeresis, U+00EB ISOlat1 */,
"igrave"
/* "ì" -- latin small letter i with grave, U+00EC ISOlat1 */,
"iacute"
/* "í" -- latin small letter i with acute, U+00ED ISOlat1 */,
"icirc"
/* "î" -- latin small letter i with circumflex, U+00EE ISOlat1 */,
"iuml"
/* "ï" -- latin small letter i with diaeresis, U+00EF ISOlat1 */,
"eth" /* "ð" -- latin small letter eth, U+00F0 ISOlat1 */,
"ntilde"
/* "ñ" -- latin small letter n with tilde, U+00F1 ISOlat1 */,
"ograve"
/* "ò" -- latin small letter o with grave, U+00F2 ISOlat1 */,
"oacute"
/* "ó" -- latin small letter o with acute, U+00F3 ISOlat1 */,
"ocirc"
/* "ô" -- latin small letter o with circumflex, U+00F4 ISOlat1 */,
"otilde"
/* "õ" -- latin small letter o with tilde, U+00F5 ISOlat1 */,
"ouml"
/* "ö" -- latin small letter o with diaeresis, U+00F6 ISOlat1 */,
"divide" /* "÷" -- division sign, U+00F7 ISOnum */,
"oslash"
/* "ø" -- latin small letter o with stroke, = latin small letter o slash, U+00F8 ISOlat1 */,
"ugrave"
/* "ù" -- latin small letter u with grave, U+00F9 ISOlat1 */,
"uacute"
/* "ú" -- latin small letter u with acute, U+00FA ISOlat1 */,
"ucirc"
/* "û" -- latin small letter u with circumflex, U+00FB ISOlat1 */,
"uuml"
/* "ü" -- latin small letter u with diaeresis, U+00FC ISOlat1 */,
"yacute"
/* "ý" -- latin small letter y with acute, U+00FD ISOlat1 */,
"thorn" /* "þ" -- latin small letter thorn,U+00FE ISOlat1 */,
"yuml"
/* "ÿ" -- latin small letter y with diaeresis, U+00FF ISOlat1 */,
};
private static Node[] pruneAncestors(List nodeList) {
Node[] changed = (Node[]) nodeList.toArray(new Node[0]);
HashMap depthMaps = new HashMap();
for (int i = 0; i < changed.length; i++) {
Element changeRoot =
DOMUtils.ascendToNodeWithID(changed[i]);
changed[i] = changeRoot;
Integer depth = new Integer(getDepth(changeRoot));
HashSet peers = (HashSet) depthMaps.get(depth);
if (null == peers) {
peers = new HashSet();
depthMaps.put(depth, peers);
}
//place the node in a collection of all nodes
//at its same depth in the DOM
peers.add(changeRoot);
}
Iterator allDepths = depthMaps.keySet().iterator();
while (allDepths.hasNext()) {
Integer baseDepth = (Integer) allDepths.next();
Iterator checkDepths = depthMaps.keySet().iterator();
while (checkDepths.hasNext()) {
Integer checkDepth = (Integer) checkDepths.next();
if (baseDepth.intValue() < checkDepth.intValue()) {
pruneAncestors(baseDepth, (HashSet) depthMaps.get(baseDepth),
checkDepth, (HashSet) depthMaps.get(checkDepth));
}
}
}
//Merge all remaining elements at different depths
//Collection is a Set so duplicates will be discarded
HashSet topElements = new HashSet();
Iterator allDepthMaps = depthMaps.values().iterator();
while (allDepthMaps.hasNext()) {
topElements.addAll((HashSet) allDepthMaps.next());
}
Element[] elements = null;
if (!topElements.isEmpty()) {
boolean reload = false;
int j = 0;
elements = new Element[topElements.size()];
HashSet dupCheck = new HashSet();
//copy the succsessful changed elements and check for change
//to head or body
for (int i = 0; i < changed.length; i++) {
Element element = (Element) changed[i];
String tag = element.getTagName();
//send reload command if 'html', 'body', or 'head' elements need to be updated (see: ICE-3063)
//TODO: pass the reload flag back out of this function
reload = reload || "html".equalsIgnoreCase(tag) || "head".equalsIgnoreCase(tag);
if (topElements.contains(element)) {
if (!dupCheck.contains(element)) {
dupCheck.add(element);
elements[j++] = element;
}
}
}
}
return elements;
}
//prune the children by looking for ancestors in the parents collection
private static void pruneAncestors(Integer parentDepth, Collection parents,
Integer childDepth, Collection children) {
Iterator parentList = parents.iterator();
while (parentList.hasNext()) {
Node parent = (Node) parentList.next();
Iterator childList = children.iterator();
while (childList.hasNext()) {
Node child = (Node) childList.next();
if (isAncestor(parentDepth, parent, childDepth, child)) {
childList.remove();
}
}
}
}
private static int getDepth(Node node) {
int depth = 0;
Node parent = node;
while ((parent = parent.getParentNode()) != null) {
depth++;
}
return depth;
}
private static boolean isAncestor(Integer parentDepth, Node parent,
Integer childDepth, Node child) {
if (!parent.hasChildNodes()) {
return false;
}
Node testParent = child;
int testDepth = childDepth.intValue();
int stopDepth = parentDepth.intValue();
while (((testParent = testParent.getParentNode()) != null) &&
(testDepth > stopDepth)) {
testDepth--;
if (testParent.equals(parent)) {
return true;
}
}
return false;
}
public static String toDebugStringDeep(Node node) {
return toDebugStringDeep(node, "");
}
static String toDebugStringDeep(Node node, String indent) {
String result = toDebugString(node) + "\n";
indent = indent + " ";
NodeList nodes = node.getChildNodes();
if (nodes != null) {
for (int i = 0; i < nodes.getLength(); i++) {
result += indent + toDebugStringDeep(nodes.item(i), indent);
}
}
return result;
}
public static String toDebugString(Node node) {
short type = node.getNodeType();
switch (type) {
case Node.ATTRIBUTE_NODE: {
Attr attr = (Attr) node;
return "attribute[name: " + attr.getName() + "; value: " + attr.getValue() + "]";
}
case Node.ELEMENT_NODE: {
Element element = (Element) node;
StringBuffer buffer = new StringBuffer();
buffer.append("element[tag: ");
buffer.append(element.getTagName());
buffer.append("; attributes: ");
NamedNodeMap attributes = element.getAttributes();
for (int i = 0; i < attributes.getLength(); i++) {
Attr attr = (Attr) attributes.item(i);
buffer.append(attr.getName());
buffer.append("=");
buffer.append(attr.getValue());
buffer.append(' ');
}
buffer.append(']');
return buffer.toString();
}
case Node.CDATA_SECTION_NODE: {
CDATASection cdataSection = (CDATASection) node;
return "cdata[" + cdataSection.getData() + "]";
}
case Node.TEXT_NODE: {
Text text = (Text) node;
return "text[" + text.getData() + "]";
}
case Node.COMMENT_NODE: {
Comment comment = (Comment) node;
return "comment[" + comment.getData() + "]";
}
case Node.ENTITY_NODE: {
Entity entity = (Entity) node;
return "entity[public: " + entity.getPublicId() + "; system: " + entity.getSystemId() + "]";
}
default: {
return node.getNodeName();
}
}
}
public static void debugIdDifference(DiffConfig config,
Node differenceOrigin, Node oldNode, Node newNode, String variant) {
if (!isDebug(config)) {
return;
}
String differenceReason = "Id changed from '" + getNodeId(oldNode) +
"' to '" + getNodeId(newNode) + "'. Examine attributes:\nOld: " +
describeAttributes(oldNode) + "New: " + describeAttributes(newNode);
log.info(getDifference(differenceOrigin, variant, differenceReason));
}
public static void debugNameDifference(DiffConfig config,
Node differenceOrigin, Node oldNode, Node newNode) {
if (!isDebug(config)) {
return;
}
String differenceReason = "Name changed from '" + oldNode.getNodeName() +
"' to '" + newNode.getNodeName() + "'. Examine attributes:\nOld: " +
describeAttributes(oldNode) + "New: " + describeAttributes(newNode);
log.info(getDifference(differenceOrigin, null, differenceReason));
}
public static void debugAttributesDifference(DiffConfig config,
Node differenceOrigin, Node oldNode, Node newNode, String variant) {
if (!isDebug(config)) {
return;
}
String differenceReason = "Attributes changed\nOld: " +
describeAttributes(oldNode) + "New: " + describeAttributes(newNode);
log.info(getDifference(differenceOrigin, variant, differenceReason));
}
public static void debugAttributeValueDifference(DiffConfig config,
Node differenceOrigin, Node oldNode, Node newNode, String variant) {
if (!isDebug(config)) {
return;
}
String differenceReason = "Attribute 'value' changed\nOld: " +
describeAttributes(oldNode) + "New: " + describeAttributes(newNode);
log.info(getDifference(differenceOrigin, variant, differenceReason));
}
public static void debugTextValueDifference(DiffConfig config,
Node differenceOrigin, Node oldNode, Node newNode, String variant) {
if (!isDebug(config)) {
return;
}
String differenceReason = "Text value changed from '" +
oldNode.getNodeValue() + "' to '" + newNode.getNodeValue() + "'";
log.info(getDifference(differenceOrigin, variant, differenceReason));
}
public static void debugChildCountDifference(DiffConfig config,
Node differenceOrigin, Node oldNode, Node newNode,
int oldChildLength, int newChildLength, String variant) {
if (!isDebug(config)) {
return;
}
String differenceReason = "Number of children changed from " +
oldChildLength + " to " + newChildLength + "\nOld: " +
describeChildren(oldNode) + "New: " + describeChildren(newNode);
log.info(getDifference(differenceOrigin, variant, differenceReason));
}
public static void debugNodeDifference(DiffConfig config, Node differenceOrigin, String variant, String differenceReason) {
if (!isDebug(config)) {
return;
}
log.info(getDifference(differenceOrigin, variant, differenceReason));
}
private static String getDifference(Node differenceOrigin, String variant, String differenceReason) {
if (differenceOrigin == null) {
return null;
}
int differenceReasonLength = (differenceReason == null) ? 0 : differenceReason.length();
StringBuilder sb = new StringBuilder(256 + differenceReasonLength);
Node n = differenceOrigin;
while (true) {
describeNodePrepended(n, sb);
Node p = n.getParentNode();
if (p == null) {
break;
}
describeNodeIndexInParentPrepended(p, n, sb);
n = p;
}
if (differenceReason != null) {
sb.append(" :: ");
if (variant != null) {
sb.append("(").append(variant).append(") ");
}
sb.append(differenceReason);
}
return sb.toString();
}
private static void describeNodePrepended(Node n, StringBuilder sb) {
sb.insert(0, ">");
String id = getNodeId(n);
if (id != null) {
sb.insert(0, "\"");
sb.insert(0, id);
sb.insert(0, " id=\"");
}
sb.insert(0, n.getNodeName());
sb.insert(0, "<");
}
private static void describeNodeIndexInParentPrepended(Node p, Node n, StringBuilder sb) {
NodeList nl = p.getChildNodes();
for (int i = nl.getLength()-1; i >= 0; i--) {
Node c = nl.item(i);
if (c == n) {
sb.insert(0, "]");
sb.insert(0, i);
sb.insert(0, "[");
return;
}
}
}
private static String describeChildren(Node n) {
StringBuilder sb = new StringBuilder(256);
NodeList nl = n.getChildNodes();
sb.append("Children: ").append(nl.getLength()).append('\n');
for (int i = 0; i < nl.getLength(); i++) {
sb.append(" [").append(i).append("] ");
Node c = nl.item(i);
short type = c.getNodeType();
switch (type) {
case Node.CDATA_SECTION_NODE: {
CDATASection cdataSection = (CDATASection) c;
sb.append("cdata[").append(cdataSection.getData()).append("]\n");
break;
}
case Node.TEXT_NODE: {
Text text = (Text) c;
String str = text.getData();
sb.append("text");
if (str == null || str.length() == 0) {
sb.append(":EMPTY\n");
} else if (str.trim().length() == 0) {
sb.append(":WHITESPACE\n");
} else {
sb.append("[").append(str).append("]\n");
}
break;
}
case Node.COMMENT_NODE: {
Comment comment = (Comment) c;
sb.append("comment[").append(comment.getData()).append("]\n");
break;
}
case Node.ENTITY_NODE: {
Entity entity = (Entity) c;
sb.append("entity[public: ").append(entity.getPublicId()).append("; system: ").append(entity.getSystemId()).append("]\n");
break;
}
default:
case Node.ELEMENT_NODE: {
sb.append("<").append(c.getNodeName());
String id = getNodeId(c);
if (id != null) {
sb.append(" id=\"").append(id).append("\"");
}
sb.append(">\n");
if (id == null) {
describeAttributes(c, sb, true);
}
}
}
}
return sb.toString();
}
private static String describeAttributes(Node n) {
StringBuilder sb = new StringBuilder(256);
describeAttributes(n, sb, false);
return sb.toString();
}
private static void describeAttributes(Node n, StringBuilder sb, boolean doubleIndent) {
NamedNodeMap nnm = n.getAttributes();
int numAttribs = (nnm == null) ? 0 : nnm.getLength();
if (doubleIndent) {
sb.append(" ");
}
sb.append("Attributes: ").append(numAttribs).append('\n');
for (int i = 0; i < numAttribs; i++) {
Node c = nnm.item(i);
if (doubleIndent) {
sb.append(" ");
}
sb.append(" [").append(i).append("] ");
sb.append(c.getNodeName());
sb.append("=\"").append(c.getNodeValue()).append("\"\n");
}
}
}