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

cdc.io.data.util.DataUtils Maven / Gradle / Ivy

The newest version!
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 predicate,
                                                           Function 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 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 predicate,
                                                           Consumer 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 visitor) {
        traverseDepthFirstInt(root,
                              cls,
                              n -> true,
                              n -> {
                                  visitor.accept(n);
                                  return Evaluation.CONTINUE;
                              });
    }

    private static  Evaluation traverseDepthFirstInt(Node node,
                                                                     Class cls,
                                                                     Predicate predicate,
                                                                     Function 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 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 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 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 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 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 predicate, String name, Evaluator 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 predicate, ElementNameConverter converter, Evaluator 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 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 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 predicate, Evaluator 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 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 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 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 predicate, Comparator comparator, Evaluator 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; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy