public static boolean hasAllDescendantsMatching(Node node, Predicate<Node> 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; } }
/** * Sets the name of matching elements. * <p> * Uses a Depth First traversal for recursion.<br> * Change is applied on parent before its children. * * @param parent The initial node. <em>MUST NOT</em> be null. * @param predicate The predicate of matching elements. <em>MUST NOT</em> be null. * @param converter The name converter. <em>MUST NOT</em> be null. * @param evaluator The evaluator. <em>MUST NOT</em> 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(parent, element.getName())); } } for (final Parent child : parent.getChildren(Parent.class)) { if (evaluator.evaluate(child) == Evaluation.CONTINUE) { setNameOfMatchingElements(child, predicate, converter, evaluator); } } }
/** * Sorts attributes of elements using a user defined sorter. * <p> * Uses a Depth First traversal for recursion.<br> * Sort is applied on parent before its children. * * @param parent The initial node. <em>MUST NOT</em> be null. * @param comparator The attribute name comparator. <em>MUST NOT</em> be null. * @param evaluator The evaluator. <em>MUST NOT</em> be null. */ public static void sortAttributes(Parent parent, Comparator<Attribute> comparator, Evaluator<? super Parent> evaluator) { if (parent instanceof Element) { final List<Attribute> 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); } } }
DataUtil.removeMatchingChildren(document, Element.matching(margs.removed), DataUtil.RECURSE); DataUtil.removeEmptyAttributes(document, DataUtil.RECURSE); DataUtil.removeMatchingAttributes(document, AttributePredicate.fromPaths(margs.removed), DataUtil.RECURSE); DataUtil.removePureElements(document, DataUtil.RECURSE); DataUtil.setValueOfMatchingAttributes(document, AttributePredicate.fromPaths(margs.set.keySet()), AttributeValueConverter.fromPathMap(margs.set), DataUtil.setNameOfMatchingAttributes(document, AttributePredicate.ANY_ATTRIBUTE, AttributeNameConverter.fromPathMap(margs.renamed), DataUtil.RECURSE); DataUtil.setNameOfMatchingElements(document, Predicates.alwaysTrue(), ElementNameConverter.fromPathMap(margs.renamed), DataUtil.setContentOfMatchingElements(document, ElementPredicate.fromPaths(margs.set.keySet()), TextContentConverter.fromPathMap(margs.set), DataUtil.sortAttributes(document,
/** * Sorts children nodes using a user-defined comparator. * <p> * Uses a Depth First traversal for recursion.<br> * Sort is applied on parent before its children. * * @param parent The initial node. <em>MUST NOT</em> be null. * @param predicate The predicate of nodes whose children must be sorted. <em>MUST NOT</em> be null. * @param comparator The children comparator. * @param evaluator The evaluator. <em>MUST NOT</em> 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); } } } }
@Test public void testSetValueOfMatchingAttributes() throws IOException { final String source = XML + EOL + "<root x='a'>" + EOL + "<child y='a'/>" + EOL + "</root>"; final String expected = XML + EOL + "<root x='A'>" + EOL + "<child y='A'/>" + EOL + "</root>"; final Document doc = XmlDataReader.load(source, StandardCharsets.UTF_8); DataUtil.setValueOfMatchingAttributes(doc, AttributePredicate.ANY_ATTRIBUTE, AttributeValueConverter.fromValueFunction(String::toUpperCase), DataUtil.RECURSE); final String target = XmlDataWriter.toString(doc, INDENT, XmlWriter.Feature.USE_SINGLE_QUOTE); assertEquals(expected, target); }
setNameOfMatchingAttributes(child, predicate, converter, evaluator);
/** * Sets the content of matching texts. * <p> * Uses a Depth First traversal for recursion. * * @param parent The initial node. <em>MUST NOT</em> be null. * @param predicate The predicate of matching texts. <em>MUST NOT</em> be null. * @param converter The content converter. * @param evaluator The evaluator. <em>MUST NOT</em> 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); } } }
@Test public void testRemovePureElements() throws IOException { final String source = XML + EOL + "<root x='a'>" + EOL + "<child>" + EOL + "<child/>" + EOL + "<child/>" + EOL + "</child>" + EOL + "</root>"; final String expected = XML + EOL + "<root x='a'/>"; final Document doc = XmlDataReader.load(source, StandardCharsets.UTF_8); DataUtil.removePureElements(doc, DataUtil.RECURSE); final String target = XmlDataWriter.toString(doc, INDENT, XmlWriter.Feature.USE_SINGLE_QUOTE); assertEquals(expected, target); }
@Test public void testRemoveEmptyAttributes() throws IOException { final String source = XML + EOL + "<root x='' y='b'>" + EOL + "<child x='' y=''/>" + EOL + "</root>"; final String expected = XML + EOL + "<root y='b'>" + EOL + "<child/>" + EOL + "</root>"; final Document doc = XmlDataReader.load(source, StandardCharsets.UTF_8); DataUtil.removeEmptyAttributes(doc, DataUtil.RECURSE); final String target = XmlDataWriter.toString(doc, INDENT, XmlWriter.Feature.USE_SINGLE_QUOTE); assertEquals(expected, target); }
final boolean ok = "root".equals(element.getName()) || p.test(element) || DataUtil.hasAncestorMatching(parent, p); return ok; });
@Test public void testSortChildrenElementsByName() throws IOException { final String source = XML + EOL + "<root x='a'>" + EOL + "<child2 x='a'/>" + EOL + "<child1 x='a'/>" + EOL + "</root>"; final String expected = XML + EOL + "<root x='a'>" + EOL + "<child1 x='a'/>" + EOL + "<child2 x='a'/>" + EOL + "</root>"; final Document doc = XmlDataReader.load(source, StandardCharsets.UTF_8); DataUtil.sortChildren(doc, Predicates.alwaysTrue(), Node.ELEMENT_NAME_COMPARATOR, DataUtil.RECURSE); final String target = XmlDataWriter.toString(doc, INDENT, XmlWriter.Feature.USE_SINGLE_QUOTE); assertEquals(expected, target); }
/** * Sets value of matching attributes. * <p> * Uses a Depth First traversal for recursion.<br> * Change is applied on parent before its children. * * @param parent The initial node. <em>MUST NOT</em> be null. * @param predicate The predicate of matching attributes. <em>MUST NOT</em> be null. * @param converter The attribute value converter. <em>MUST NOT</em> be null. * @param evaluator The evaluator. <em>MUST NOT</em> 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); } } }
@Test public void testSetNameOfMatchingAttributes() throws IOException { final String source = XML + EOL + "<root x='a'>" + EOL + "<child y='a'/>" + EOL + "</root>"; final String expected = XML + EOL + "<root X='a'>" + EOL + "<child Y='a'/>" + EOL + "</root>"; final Document doc = XmlDataReader.load(source, StandardCharsets.UTF_8); DataUtil.setNameOfMatchingAttributes(doc, AttributePredicate.ANY_ATTRIBUTE, AttributeNameConverter.fromNameFunction(String::toUpperCase), DataUtil.RECURSE); final String target = XmlDataWriter.toString(doc, INDENT, XmlWriter.Feature.USE_SINGLE_QUOTE); assertEquals(expected, target); }
@Test public void testSetContentOfMatchingTexts() throws IOException { final String source = XML + EOL + "<root x='a'>" + EOL + "<child y='a'>aaa</child>" + EOL + "</root>"; final String expected = XML + EOL + "<root x='a'>" + EOL + "<child y='a'>AAA</child>" + EOL + "</root>"; final Document doc = XmlDataReader.load(source, StandardCharsets.UTF_8); DataUtil.setContentOfMatchingTexts(doc, TextPredicate.ANY_TEXT, TextContentConverter.fromContentFunction(String::toUpperCase), DataUtil.RECURSE); final String target = XmlDataWriter.toString(doc, INDENT, XmlWriter.Feature.USE_SINGLE_QUOTE); assertEquals(expected, target); } }
@Test public void testSortChildrenElementsByNameAndAttributes() throws IOException { final String source = XML + EOL + "<root x='a'>" + EOL + "<child x='b'/>" + EOL + "<child x='a'/>" + EOL + "</root>"; final String expected = XML + EOL + "<root x='a'>" + EOL + "<child x='a'/>" + EOL + "<child x='b'/>" + EOL + "</root>"; final Document doc = XmlDataReader.load(source, StandardCharsets.UTF_8); DataUtil.sortChildren(doc, Predicates.alwaysTrue(), Node.ELEMENT_NAME_AND_ATTRIBUTES_COMPARATOR, DataUtil.RECURSE); final String target = XmlDataWriter.toString(doc, INDENT, XmlWriter.Feature.USE_SINGLE_QUOTE); assertEquals(expected, target); }
@Test public void testSortAttributes() throws IOException { final String source = XML + EOL + "<root y='b' x='a'>" + EOL + "<child y='b' x='a'/>" + EOL + "</root>"; final String expected = XML + EOL + "<root x='a' y='b'>" + EOL + "<child x='a' y='b'/>" + EOL + "</root>"; final Document doc = XmlDataReader.load(source, StandardCharsets.UTF_8); DataUtil.sortAttributes(doc, Attribute.NAME_COMPARATOR, DataUtil.RECURSE); final String target = XmlDataWriter.toString(doc, INDENT, XmlWriter.Feature.USE_SINGLE_QUOTE); assertEquals(expected, target); }
@Test public void testAttributeNameConversion() throws IOException { final XmlDataReader reader = new XmlDataReader(); reader.setEnabled(XmlDataReader.Feature.LOAD_SPACES, false); reader.setAttributeNameConverter((element, name) -> name.toUpperCase()); final URL url = getClass().getClassLoader().getResource("test-reader.xml"); final Document doc = reader.read(Files.toFile(url).getPath()); assertTrue(DataUtil.hasAllDescendantsMatching(doc, node -> { if (node instanceof Element) { final Element e = (Element) node; for (final Attribute a : e.getAttributes()) { if (!a.getName().toUpperCase().equals(a.getName())) { return false; } } return true; } else { return true; } }, true)); LOGGER.debug("Upper case attribute names"); XmlDataWriter.print(doc, OUT, " ", false); }
@Test public void testSetNameOfMatchingElements() throws IOException { final String source = XML + EOL + "<root x='a'>" + EOL + "<child x='a'/>" + EOL + "</root>"; final String expected = XML + EOL + "<ROOT x='a'>" + EOL + "<CHILD x='a'/>" + EOL + "</ROOT>"; final Document doc = XmlDataReader.load(source, StandardCharsets.UTF_8); DataUtil.setNameOfMatchingElements(doc, Predicates.alwaysTrue(), ElementNameConverter.fromNameFunction(String::toUpperCase), DataUtil.RECURSE); final String target = XmlDataWriter.toString(doc, INDENT, XmlWriter.Feature.USE_SINGLE_QUOTE); assertEquals(expected, target); }