cdc.io.data.util.DataUtils Maven / Gradle / Ivy
package cdc.io.data.util;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;
import cdc.io.data.Attribute;
import cdc.io.data.Child;
import cdc.io.data.Element;
import cdc.io.data.Node;
import cdc.io.data.Parent;
import cdc.io.data.Text;
import cdc.io.data.paths.SPath;
import cdc.util.function.Evaluation;
import cdc.util.function.Evaluator;
/**
* Utilities for modification of data trees.
*
* @author Damien Carbonne
*
*/
public final class DataUtils {
public static final Evaluator RECURSE = Evaluator.continueTraversal();
private DataUtils() {
}
/**
* Traverses all nodes of the tree rooted on a root node, till traversal is interrupted.
*
* @param The node type.
* @param root The root node.
* @param cls The class of visited nodes.
* @param predicate The predicate that visited nodes must match.
* @param visitor The visitor that is invoked on accepted nodes.
* If it returns {@link Evaluation#PRUNE}, traversal is interrupted.
*/
public static void traverseDepthFirst(Node root,
Class cls,
Predicate super N> predicate,
Function super N, Evaluation> visitor) {
traverseDepthFirstInt(root,
cls,
predicate,
visitor);
}
/**
* Traverses all nodes of the tree rooted on a root node, till traversal is interrupted.
*
* @param The node type.
* @param root The root node.
* @param cls The class of visited nodes.
* @param visitor The visitor that is invoked on accepted nodes.
* If it returns {@link Evaluation#PRUNE}, traversal is interrupted.
*/
public static void traverseDepthFirst(Node root,
Class cls,
Function super N, Evaluation> visitor) {
traverseDepthFirstInt(root,
cls,
n -> true,
visitor);
}
/**
* Traverses all nodes of the tree rooted on a root node.
*
* @param The node type.
* @param root The root node.
* @param cls The class of visited nodes.
* @param predicate The predicate that visited nodes must match.
* @param visitor The visitor that is invoked on accepted nodes.
*/
public static void traverseDepthFirst(Node root,
Class cls,
Predicate super N> predicate,
Consumer super N> visitor) {
traverseDepthFirstInt(root,
cls,
predicate,
n -> {
visitor.accept(n);
return Evaluation.CONTINUE;
});
}
/**
* Traverses all nodes of the tree rooted on a root node.
*
* @param The node type.
* @param root The root node.
* @param cls The class of visited nodes.
* @param visitor The visitor that is invoked on accepted nodes.
*/
public static void traverseDepthFirst(Node root,
Class cls,
Consumer super N> visitor) {
traverseDepthFirstInt(root,
cls,
n -> true,
n -> {
visitor.accept(n);
return Evaluation.CONTINUE;
});
}
private static Evaluation traverseDepthFirstInt(Node node,
Class cls,
Predicate super N> predicate,
Function super N, Evaluation> visitor) {
if (cls.isInstance(node)) {
final N n = cls.cast(node);
if (predicate.test(n)) {
final Evaluation evaluation = visitor.apply(n);
if (evaluation.isPrune()) {
return Evaluation.PRUNE;
}
}
}
if (node instanceof Parent) {
for (final Node child : ((Parent) node).getChildren()) {
final Evaluation evaluation = traverseDepthFirstInt(child, cls, predicate, visitor);
if (evaluation.isPrune()) {
return Evaluation.PRUNE;
}
}
}
return Evaluation.CONTINUE;
}
/**
* Sets names of matching attributes.
*
* Uses a Depth First traversal for recursion.
* Change is applied on parent before its children.
*
* @param parent The initial node. MUST NOT be null.
* @param predicate The predicate of matching attributes. MUST NOT be null.
* @param converter The attribute name converter. MUST NOT be null.
* @param evaluator The evaluator. MUST NOT be null.
*/
public static void setNameOfMatchingAttributes(Parent parent,
AttributePredicate predicate,
AttributeNameConverter converter,
Evaluator super Parent> evaluator) {
if (parent instanceof Element) {
final Element element = (Element) parent;
// Attributes in the same order as initially
final Attribute[] atts = new Attribute[element.getAttributesCount()];
boolean changed = false;
// Attributes to rename
for (int index = 0; index < element.getAttributesCount(); index++) {
final Attribute att = element.getAttributeAt(index);
if (predicate.accepts(element, att.getName(), att.getValue())) {
final Attribute newAtt = new Attribute(converter.convertAttributeName(element, att.getName()),
att.getValue());
atts[index] = newAtt;
changed = true;
} else {
atts[index] = att;
}
}
if (changed) {
element.removeAttributes();
for (final Attribute att : atts) {
element.addAttribute(att);
}
}
}
for (final Parent child : parent.getChildren(Parent.class)) {
if (evaluator.evaluate(child) == Evaluation.CONTINUE) {
setNameOfMatchingAttributes(child, predicate, converter, evaluator);
}
}
}
/**
* Sets value of matching attributes.
*
* Uses a Depth First traversal for recursion.
* Change is applied on parent before its children.
*
* @param parent The initial node. MUST NOT be null.
* @param predicate The predicate of matching attributes. MUST NOT be null.
* @param converter The attribute value converter. MUST NOT be null.
* @param evaluator The evaluator. MUST NOT be null.
*/
public static void setValueOfMatchingAttributes(Parent parent,
AttributePredicate predicate,
AttributeValueConverter converter,
Evaluator super Parent> evaluator) {
if (parent instanceof Element) {
final Element element = (Element) parent;
for (final Attribute att : element.getAttributes()) {
if (predicate.accepts(element, att.getName(), att.getValue())) {
final String newValue = converter.convertAttributeValue(element, att.getName(), att.getValue());
att.setValue(newValue);
}
}
}
for (final Parent child : parent.getChildren(Parent.class)) {
if (evaluator.evaluate(child) == Evaluation.CONTINUE) {
setValueOfMatchingAttributes(child, predicate, converter, evaluator);
}
}
}
/**
* Removes matching attributes.
*
* Uses a Depth First traversal for recursion.
* Removal is applied on parent before its children.
*
* @param parent The initial node. MUST NOT be null.
* @param predicate The predicate of matching attributes. MUST NOT be null.
* @param evaluator The evaluator. MUST NOT be null.
*/
public static void removeMatchingAttributes(Parent parent,
AttributePredicate predicate,
Evaluator super Parent> evaluator) {
if (parent instanceof Element) {
final Element element = (Element) parent;
final Iterator atts = element.getAttributes().iterator();
while (atts.hasNext()) {
final Attribute att = atts.next();
if (predicate.accepts(element, att.getName(), att.getValue())) {
atts.remove();
}
}
}
for (final Parent child : parent.getChildren(Parent.class)) {
if (evaluator.evaluate(child) == Evaluation.CONTINUE) {
removeMatchingAttributes(child, predicate, evaluator);
}
}
}
/**
* Recursively removes empty attributes.
*
* Uses a Depth First traversal for recursion.
* Removal is applied on parent before its children.
*
* @param parent The initial node. MUST NOT be null.
* @param evaluator The evaluator. MUST NOT be null.
*/
public static void removeEmptyAttributes(Parent parent,
Evaluator super Parent> evaluator) {
removeMatchingAttributes(parent,
AttributePredicate.IS_EMPTY_ATTRIBUTE,
evaluator);
}
/**
* Sorts attributes of elements using a user defined sorter.
*
* Uses a Depth First traversal for recursion.
* Sort is applied on parent before its children.
*
* @param parent The initial node. MUST NOT be null.
* @param comparator The attribute name comparator. MUST NOT be null.
* @param evaluator The evaluator. MUST NOT be null.
*/
public static void sortAttributes(Parent parent,
Comparator comparator,
Evaluator super Parent> evaluator) {
if (parent instanceof Element) {
final List atts = ((Element) parent).getAttributes();
if (atts.size() > 1) {
Collections.sort(atts, comparator);
}
}
for (final Parent child : parent.getChildren(Parent.class)) {
if (evaluator.evaluate(child) == Evaluation.CONTINUE) {
sortAttributes(child, comparator, evaluator);
}
}
}
/**
* Moves the attribute that has a given name to first position, if it exists.
*
* Uses a Depth First traversal for recursion.
* Move is applied on parent before its children.
*
* @param parent The initial node. MUST NOT be null.
* @param predicate The predicate of matching elements. MUST NOT be null.
* @param name The attribute name.
* @param evaluator The evaluator. MUST NOT be null.
*/
public static void moveAttributeFirst(Parent parent,
Predicate super Element> predicate,
String name,
Evaluator super Parent> evaluator) {
if (parent instanceof Element) {
final Element element = (Element) parent;
if (predicate.test(element)
&& element.getAttributesCount() > 1
&& !element.getAttributeAt(0).getName().equals(name)) {
final Attribute attribute = element.removeAttribute(name);
if (attribute != null) {
element.getAttributes().add(0, attribute);
}
}
}
for (final Parent child : parent.getChildren(Parent.class)) {
if (evaluator.evaluate(child) == Evaluation.CONTINUE) {
moveAttributeFirst(child, predicate, name, evaluator);
}
}
}
/**
* Sets the name of matching elements.
*
* Uses a Depth First traversal for recursion.
* Change is applied on parent before its children.
*
* @param parent The initial node. MUST NOT be null.
* @param predicate The predicate of matching elements. MUST NOT be null.
* @param converter The name converter. MUST NOT be null.
* @param evaluator The evaluator. MUST NOT be null.
*/
public static void setNameOfMatchingElements(Parent parent,
Predicate super Element> predicate,
ElementNameConverter converter,
Evaluator super Parent> evaluator) {
if (parent instanceof Element) {
final Element element = (Element) parent;
if (predicate.test(element)) {
element.setName(converter.convertElementName(element.getParent(), element.getName()));
}
}
for (final Parent child : parent.getChildren(Parent.class)) {
if (evaluator.evaluate(child) == Evaluation.CONTINUE) {
setNameOfMatchingElements(child, predicate, converter, evaluator);
}
}
}
/**
* Sets the content of matching texts.
*
* Uses a Depth First traversal for recursion.
*
* @param parent The initial node. MUST NOT be null.
* @param predicate The predicate of matching texts. MUST NOT be null.
* @param converter The content converter.
* @param evaluator The evaluator. MUST NOT be null.
*/
public static void setContentOfMatchingTexts(Parent parent,
TextPredicate predicate,
TextContentConverter converter,
Evaluator super Parent> evaluator) {
for (final Child child : parent.getChildren()) {
if (child instanceof Text) {
final Text text = (Text) child;
if (predicate.accepts(parent, text)) {
final String content = converter.convertTextContent(parent, text.getContent());
text.setContent(content);
}
} else if (child instanceof Parent
&& evaluator.evaluate((Parent) child) == Evaluation.CONTINUE) {
setContentOfMatchingTexts((Parent) child, predicate, converter, evaluator);
}
}
}
public static void setContentOfMatchingElements(Parent parent,
ElementPredicate predicate,
TextContentConverter converter,
Evaluator super Parent> evaluator) {
if (parent instanceof Element) {
final Element element = (Element) parent;
if (predicate.accepts(element.getParent(), element)) {
final int count = element.getChildrenCount();
if (count == 0) {
// Add a text child
final String content = converter.convertTextContent(element, null);
if (content != null) {
element.addText(content);
}
} else if (count == 1 && element.hasOnlyText()) {
// Modify text child if possible
final Text text = element.getChild(Text.class);
final String content = converter.convertTextContent(element, text.getContent());
text.setContent(content);
}
}
}
for (final Child child : parent.getChildren()) {
if (child instanceof Parent
&& evaluator.evaluate((Parent) child) == Evaluation.CONTINUE) {
setContentOfMatchingElements((Parent) child, predicate, converter, evaluator);
}
}
}
/**
* Removes matching children.
*
* Uses a Depth First traversal for recursion.
* Removal is applied on children before parent.
*
* @param parent The initial node. MUST NOT be null.
* @param predicate The predicate of children nodes to remove. MUST NOT be null.
* @param evaluator The evaluator. MUST NOT be null.
*/
public static void removeMatchingChildren(Parent parent,
Predicate super Child> predicate,
Evaluator super Parent> evaluator) {
// First, recursive processing
for (final Parent child : parent.getChildren(Parent.class)) {
if (evaluator.evaluate(child) == Evaluation.CONTINUE) {
removeMatchingChildren(child, predicate, evaluator);
}
}
// Then local removal process
parent.removeChildren(predicate);
}
/**
* Removes comments nodes.
*
* Uses a Depth First traversal for recursion.
* Removal is applied on children before parent.
*
* @param parent The initial node. MUST NOT be null.
* @param evaluator The evaluator. MUST NOT be null.
*/
public static void removeComments(Parent parent,
Evaluator super Parent> evaluator) {
removeMatchingChildren(parent, Node.IS_COMMENT, evaluator);
}
/**
* Removes text that can be ignored.
*
* Uses a Depth First traversal for recursion.
* Removal is applied on children before parent.
*
* @param parent The initial node. MUST NOT be null.
* @param evaluator The evaluator. MUST NOT be null.
*/
public static void removeIgnorableText(Parent parent,
Evaluator super Parent> evaluator) {
removeMatchingChildren(parent, Node.IS_IGNORABLE_TEXT, evaluator);
}
/**
* Removes pure elements (they have no children and no attributes).
*
* Uses a Depth First traversal for recursion.
* Removal is applied on children before parent.
*
* @param parent The initial node. MUST NOT be null.
* @param evaluator The evaluator. MUST NOT be null.
*/
public static void removePureElements(Parent parent,
Evaluator super Parent> evaluator) {
removeMatchingChildren(parent, Node.IS_PURE_ELEMENT, evaluator);
}
/**
* Sorts children nodes using a user-defined comparator.
*
* Uses a Depth First traversal for recursion.
* Sort is applied on parent before its children.
*
* @param parent The initial node. MUST NOT be null.
* @param predicate The predicate of nodes whose children must be sorted. MUST NOT be null.
* @param comparator The children comparator.
* @param evaluator The evaluator. MUST NOT be null.
*/
public static void sortChildren(Parent parent,
Predicate super Parent> predicate,
Comparator super Child> comparator,
Evaluator super Parent> evaluator) {
if (predicate.test(parent) && parent.getChildren() != null) {
parent.sortChildren(comparator);
for (final Parent child : parent.getChildren(Parent.class)) {
if (evaluator.evaluate(child) == Evaluation.CONTINUE) {
sortChildren(child, predicate, comparator, evaluator);
}
}
}
}
public static boolean hasAncestorMatching(Node node,
Predicate predicate) {
Node index = node;
while (index != null) {
if (predicate.test(index)) {
return true;
}
if (index instanceof Child) {
index = ((Child) index).getParent();
} else {
index = null;
}
}
return false;
}
public static boolean hasDescendantMatching(Node node,
Predicate predicate,
boolean strict) {
if (!strict && predicate.test(node)) {
return true;
}
if (node instanceof Parent) {
final Parent parent = (Parent) node;
for (final Child child : parent.getChildren()) {
if (hasDescendantMatching(child, predicate, false)) {
return true;
}
}
}
return false;
}
public static boolean hasAllDescendantsMatching(Node node,
Predicate predicate,
boolean strict) {
if (!strict && !predicate.test(node)) {
return false;
}
if (node instanceof Parent) {
final Parent parent = (Parent) node;
for (final Child child : parent.getChildren()) {
if (!hasAllDescendantsMatching(child, predicate, false)) {
return false;
}
}
}
return true;
}
public static String applyOnAttribute(Map> map,
Element parent,
String attributeName,
String value) {
for (final Map.Entry> entry : map.entrySet()) {
if (entry.getKey().matchesAttribute(parent, attributeName)) {
return entry.getValue().apply(value);
}
}
return value;
}
public static String applyOnElement(Map> map,
Parent parent,
String elementName,
String value) {
for (final Map.Entry> entry : map.entrySet()) {
if (entry.getKey().matchesElement(parent, elementName)) {
return entry.getValue().apply(value);
}
}
return value;
}
public static String applyOnElement(Map> map,
Element element,
String value) {
for (final Map.Entry> entry : map.entrySet()) {
if (entry.getKey().matchesElement(element)) {
return entry.getValue().apply(value);
}
}
return value;
}
}