diff --git a/openide.util/apichanges.xml b/openide.util/apichanges.xml
--- a/openide.util/apichanges.xml
+++ b/openide.util/apichanges.xml
@@ -1457,6 +1457,22 @@
+
+
+ Refactored XML methods from various modules to be added
+ to XMLUtil. Six new methods added.
+
+
+
+
+
+
+ *
+ *
+ * The implementation is 'insert before', and since the order of existing elements
+ * is not validated, it is important to place the new child before
+ * the existing item instead of after the existing item.
+ *
+ * @param parent parent to which the child will be appended
+ * @param el element to be added
+ * @param order order of the elements which must be followed
+ * @throws IllegalArgumentException if the order cannot be followed, either
+ * a missing existing child or missing new child entry from the desiredAppendOrder
+ *
+ * @since 8.3
+ */
+ public static void appendChildElement(Element parent, Element el, String[] order) throws IllegalArgumentException {
+ List l = Arrays.asList(order);
+ int index = l.indexOf(el.getLocalName());
+
+ // ensure the new new element is contained in the 'order'
+ if (index == -1) {
+ throw new IllegalArgumentException("new child element '"+ el.getLocalName() + "' not specified in order " + l); // NOI18N
+ }
+
+ List elements = findSubElements(parent);
+ Element insertBefore = null;
+
+ for (Element e : elements) {
+ int index2 = l.indexOf(e.getLocalName());
+ // ensure that all existing elements are in 'order'
+ if (index2 == -1) {
+ throw new IllegalArgumentException("Existing child element '" + e.getLocalName() + "' not specified in order " + l); // NOI18N
+ }
+ if (index2 > index) {
+ insertBefore = e;
+ break;
+ }
+ }
+
+ parent.insertBefore(el, insertBefore);
+ }
+
+ /**
+ * Find all direct child elements of an element.
+ * Children which are all-whitespace text nodes or comments are ignored; others cause
+ * an exception to be thrown.
+ * @param parent a parent element in a DOM tree
+ * @return a list of direct child elements (may be empty)
+ * @throws IllegalArgumentException if there are non-element children besides whitespace
+ *
+ * @since 8.3
+ */
+ public static List findSubElements(Element parent) throws IllegalArgumentException {
+ NodeList l = parent.getChildNodes();
+ List elements = new ArrayList(l.getLength());
+ for (int i = 0; i < l.getLength(); i++) {
+ Node n = l.item(i);
+ if (n.getNodeType() == Node.ELEMENT_NODE) {
+ elements.add((Element) n);
+ } else if (n.getNodeType() == Node.TEXT_NODE) {
+ String text = ((Text) n).getNodeValue();
+ if (text.trim().length() > 0) {
+ throw new IllegalArgumentException("non-ws text encountered in " + parent + ": " + text); // NOI18N
+ }
+ } else if (n.getNodeType() == Node.COMMENT_NODE) {
+ // OK, ignore
+ } else {
+ throw new IllegalArgumentException("unexpected non-element child of " + parent + ": " + n); // NOI18N
+ }
+ }
+ return elements;
+ }
+
+ /**
+ * Search for an XML element in the direct children of parent only.
+ *
+ * This compares localName (uses nodeName is localName is null) to name,
+ * and checks the tags namespace with the provided namespace.
+ * A null namespace will match any namespace.
+ *
+ *
This is differs from the DOM version by:
+ *
not searching recursively
+ *
returns a single result
+ *
+ *
+ * @param parent a parent element
+ * @param name the intended local name
+ * @param namespace the intended namespace (or null)
+ * @return the one child element with that name, or null if none
+ * @throws IllegalArgumentException if there is multiple elements of the same name
+ *
+ * @since 8.3
+ */
+ public static Element findElement(Element parent, String name, String namespace) throws IllegalArgumentException {
+ Element result = null;
+ NodeList l = parent.getChildNodes();
+ int nodeCount = l.getLength();
+ for (int i = 0; i < nodeCount; i++) {
+ if (l.item(i).getNodeType() == Node.ELEMENT_NODE) {
+ Node node = l.item(i);
+ String localName = node.getLocalName();
+ localName = localName == null ? node.getNodeName() : localName;
+
+ if (name.equals(localName)
+ && (namespace == null || namespace.equals(node.getNamespaceURI()))) {
+ if (result == null) {
+ result = (Element)node;
+ } else {
+ throw new IllegalArgumentException("more than one element with same name found");
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Extract nested text from a node.
+ * Currently does not handle coalescing text nodes, CDATA sections, etc.
+ * @param parent a parent element
+ * @return the nested text, or null if none was found
+ *
+ * @since 8.3
+ */
+ public static String findText(Node parent) {
+ NodeList l = parent.getChildNodes();
+ for (int i = 0; i < l.getLength(); i++) {
+ if (l.item(i).getNodeType() == Node.TEXT_NODE) {
+ Text text = (Text) l.item(i);
+ return text.getNodeValue();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Convert an XML fragment from one namespace to another.
+ *
+ * @param from
+ * @param namespace
+ * @return
+ *
+ * @since 8.3
+ */
+ public static Element translateXML(Element from, String namespace) {
+ Element to = from.getOwnerDocument().createElementNS(namespace, from.getLocalName());
+ NodeList nl = from.getChildNodes();
+ int length = nl.getLength();
+ for (int i = 0; i < length; i++) {
+ Node node = nl.item(i);
+ Node newNode;
+ if (node.getNodeType() == Node.ELEMENT_NODE) {
+ newNode = translateXML((Element) node, namespace);
+ } else {
+ newNode = node.cloneNode(true);
+ }
+ to.appendChild(newNode);
+ }
+ NamedNodeMap m = from.getAttributes();
+ for (int i = 0; i < m.getLength(); i++) {
+ Node attr = m.item(i);
+ to.setAttribute(attr.getNodeName(), attr.getNodeValue());
+ }
+ return to;
+ }
+
+ /**
+ * Copy elements from one document to another attaching at the specified element
+ * and translating the namespace.
+ *
+ * @param from copy the children of this element (exclusive)
+ * @param to where to attach the copied elements
+ * @param newNamespace destination namespace
+ *
+ * @since 8.3
+ */
+ public static void copyDocument(Element from, Element to, String newNamespace) {
+ Document doc = to.getOwnerDocument();
+ NodeList nl = from.getChildNodes();
+ int length = nl.getLength();
+ for (int i = 0; i < length; i++) {
+ Node node = nl.item(i);
+ Node newNode = null;
+ if (Node.ELEMENT_NODE == node.getNodeType()) {
+ Element oldElement = (Element) node;
+ newNode = doc.createElementNS(newNamespace, oldElement.getTagName());
+ NamedNodeMap m = oldElement.getAttributes();
+ Element newElement = (Element) newNode;
+ for (int index = 0; index < m.getLength(); index++) {
+ Node attr = m.item(index);
+ newElement.setAttribute(attr.getNodeName(), attr.getNodeValue());
+ }
+ copyDocument(oldElement, newElement, newNamespace);
+ } else {
+ newNode = node.cloneNode(true);
+ newNode = to.getOwnerDocument().importNode(newNode, true);
+ }
+ if (newNode != null) {
+ to.appendChild(newNode);
+ }
+ }
+ }
}
diff --git a/openide.util/test/unit/src/org/openide/xml/XMLUtilTest.java b/openide.util/test/unit/src/org/openide/xml/XMLUtilTest.java
--- a/openide.util/test/unit/src/org/openide/xml/XMLUtilTest.java
+++ b/openide.util/test/unit/src/org/openide/xml/XMLUtilTest.java
@@ -49,6 +49,7 @@
import java.io.StringReader;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
+import java.util.List;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.stream.StreamSource;
@@ -497,4 +498,210 @@
assertGC("can collect resolver", resolverRef);
}
+ public void testAppendChildElement() throws Exception {
+ Document doc = XMLUtil.parse(new InputSource(new StringReader("")), false, true, null, null);
+ Element parent = doc.createElementNS("unittest", "parent");
+ Element newElement1 = doc.createElementNS("unittest", "new_element1");
+ Element newElement2 = doc.createElementNS("unittest", "new_element2");
+ Element newElement3 = doc.createElementNS("unittest", "new_element3");
+
+ // append to the default root node (no match in order)
+ XMLUtil.appendChildElement(parent, newElement1, new String[] { "new_element2", "new_element1" });
+ NodeList children = parent.getChildNodes();
+ assertEquals(1, children.getLength());
+
+ // append after the child we just appended
+ XMLUtil.appendChildElement(parent, newElement2, new String[] { "new_element2", "new_element1" });
+
+ children = parent.getChildNodes();
+ assertEquals(2, children.getLength());
+ Node firstChild = parent.getChildNodes().item(0);
+ Node secondChild = parent.getChildNodes().item(1);
+ assertEquals("new_element2", firstChild.getNodeName());
+ assertEquals("new_element1", secondChild.getNodeName());
+
+ // failures
+
+ try {
+ // new child is not in the order list
+ XMLUtil.appendChildElement(parent, newElement3, new String[] { "new_element2", "new_element1"});
+ fail("Expecting IAE");
+ } catch (IllegalArgumentException e) {
+ assertEquals("new child element 'new_element3' not specified in order [new_element2, new_element1]", e.getMessage());
+ }
+ try {
+ // existing child not in the order list
+ XMLUtil.appendChildElement(parent, newElement3, new String[] { "new_element3"});
+ fail("Expecting IAE");
+ } catch (IllegalArgumentException e) {
+ assertEquals("Existing child element 'new_element2' not specified in order [new_element3]", e.getMessage());
+ }
+ }
+
+ public void testFindSubElements() throws Exception {
+ Document doc = XMLUtil.parse(new InputSource(new StringReader("")),
+ false, true, null, null);
+
+ Element parent = doc.getDocumentElement();
+ assertEquals(5, parent.getChildNodes().getLength());
+ List subElements = XMLUtil.findSubElements(parent);
+ assertEquals(3, subElements.size());
+ Element firstChild = subElements.get(0);
+ Element secondChild = subElements.get(1);
+ Element thirdChild = subElements.get(2);
+
+ assertEquals("child1", firstChild.getNodeName());
+ assertEquals("child2", secondChild.getNodeName());
+ assertEquals("child3", thirdChild.getNodeName());
+
+ Document failureDoc = XMLUtil.parse(new InputSource(new StringReader("Non whitespace")),
+ false, true, null, null);
+ Element failedParent = failureDoc.getDocumentElement();
+ try {
+ XMLUtil.findSubElements(failedParent);
+ fail("expected IAE");
+ } catch (IllegalArgumentException e) { }
+ }
+
+ public void testFindElement() throws Exception {
+ String xmlDoc = " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ "";
+
+ Document doc = XMLUtil.parse(new InputSource(new StringReader(xmlDoc)), false, true, null, null);
+ Element parent = doc.getDocumentElement();
+
+ Element doesNotExist1 = XMLUtil.findElement(parent, "doesNotExist", "h");
+ assertNull(doesNotExist1);
+
+ Element doesNotExist2 = XMLUtil.findElement(parent, "doesNotExist", null);
+ assertNull(doesNotExist2);
+
+ Element noTable2 = XMLUtil.findElement(parent, "table", "h");
+ assertNull(noTable2);
+
+ Element noTable3 = XMLUtil.findElement(parent, "table", "f");
+ assertNull(noTable3);
+
+ Element table1 = XMLUtil.findElement(parent, "table", "http://www.w3.org/TR/html4/");
+ assertNotNull(table1);
+
+ Element table2 = XMLUtil.findElement(parent, "table", "http://www.w3schools.com/furniture");
+ assertNotNull(table2);
+
+ Element form1 = XMLUtil.findElement(parent, "form", "http://www.w3.org/TR/html4/");
+ assertNotNull(form1);
+
+ Element form2 = XMLUtil.findElement(parent, "form", null);
+ assertNotNull(form2);
+
+ assertEquals(form1, form2);
+
+ try {
+ XMLUtil.findElement(parent, "dup", null);
+ fail("Expected IAE");
+ } catch (IllegalArgumentException e) { }
+
+ try {
+ XMLUtil.findElement(parent, "table", null);
+ fail("Expected IAE");
+ } catch (IllegalArgumentException e) { }
+ }
+
+ public void testFindText() throws Exception {
+ Document doc = XMLUtil.parse(new InputSource(new StringReader("Text To Find")),
+ false, true, null, null);
+
+ Element parent = doc.getDocumentElement();
+ String foundText = XMLUtil.findText(parent);
+
+ assertEquals("Text To Find", foundText);
+
+ String notFoundText = XMLUtil.findText(parent.getFirstChild());
+
+ assertNull(notFoundText);
+ }
+
+ public void testTranslateXML() throws Exception {
+ // don't add any whitespace to the first 3 nodes
+ String xmlDoc = "