/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.apache.jackrabbit.core.util; import org.w3c.dom.Attr; import org.w3c.dom.CharacterData; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import java.io.IOException; import java.io.InputStream; import java.util.Properties; /** * Document walker class. This class provides an intuitive * interface for traversing a parsed DOM document. */ public final class DOMWalker { /** Static factory for creating stream to DOM transformers. */ private static final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); /** The DOM document being traversed by this walker. */ private final Document document; /** The current element. */ private Element current; /** * Creates a walker for traversing a DOM document read from the given * input stream. The root element of the document is set as the current * element. * * @param xml XML input stream * @throws IOException if a document cannot be read from the stream */ public DOMWalker(InputStream xml) throws IOException { try { DocumentBuilder builder = factory.newDocumentBuilder(); document = builder.parse(xml); current = document.getDocumentElement(); } catch (IOException e) { throw e; } catch (Exception e) { throw (IOException) new IOException(e.getMessage()).initCause(e); } } /** * Returns the namespace mappings defined in the current element. * The returned property set contains the prefix to namespace * mappings specified by the <code>xmlns</code> attributes of the * current element. * * @return prefix to namespace mappings of the current element */ public Properties getNamespaces() { Properties namespaces = new Properties(); NamedNodeMap attributes = current.getAttributes(); for (int i = 0; i < attributes.getLength(); i++) { Attr attribute = (Attr) attributes.item(i); if (attribute.getName().startsWith("xmlns:")) { namespaces.setProperty( attribute.getName().substring(6), attribute.getValue()); } } return namespaces; } /** * Returns the name of the current element. * * @return element name */ public String getName() { return current.getNodeName(); } /** * Returns the value of the named attribute of the current element. * * @param name attribute name * @return attribute value, or <code>null</code> if not found */ public String getAttribute(String name) { Attr attribute = current.getAttributeNode(name); if (attribute != null) { return attribute.getValue(); } else { return null; } } /** * Returns the text content of the current element. * * @return text content */ public String getContent() { StringBuilder content = new StringBuilder(); NodeList nodes = current.getChildNodes(); for (int i = 0; i < nodes.getLength(); i++) { Node node = nodes.item(i); if (node.getNodeType() == Node.TEXT_NODE) { content.append(((CharacterData) node).getData()); } } return content.toString(); } /** * Enters the named child element. If the named child element is * found, then it is made the current element and <code>true</code> * is returned. Otherwise the current element is not changed and * <code>false</code> is returned. * <p> * The standard call sequence for this method is show below. * <pre> * DOMWalker walker = ...; * if (walker.enterElement("...")) { * ...; * walker.leaveElement(); * } * </pre> * * @param name child element name * @return <code>true</code> if the element was entered, * <code>false</code> otherwise */ public boolean enterElement(String name) { NodeList children = current.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { Node child = children.item(i); if (child.getNodeType() == Node.ELEMENT_NODE && name.equals(child.getNodeName())) { current = (Element) child; return true; } } return false; } /** * Leaves the current element. The parent element is set as the new * current element. * * @see #enterElement(String) */ public void leaveElement() { current = (Element) current.getParentNode(); } /** * Iterates through the named child elements over multiple calls. * This method makes it possible to use the following code to * walk through all the child elements with the given name. * <pre> * DOMWalker walker = ...; * while (walker.iterateElements("...")) { * ...; * } * </pre> * <p> * <strong>WARNING:</strong> This method should only be used when * <code>walker.getName()</code> does not equal <code>name</code> when * the while loop is started. Otherwise the walker will not be positioned * at the same node when the while loop ends. * * @param name name of the iterated elements * @return <code>true</code> if another iterated element was entered, or * <code>false</code> if no more iterated elements were found * and the original element is restored as the current element */ public boolean iterateElements(String name) { Node next; if (name.equals(current.getNodeName())) { next = current.getNextSibling(); } else { next = current.getFirstChild(); } while (next != null) { if (next.getNodeType() == Node.ELEMENT_NODE && name.equals(next.getNodeName())) { current = (Element) next; return true; } else { next = next.getNextSibling(); } } if (name.equals(current.getNodeName())) { Node parent = current.getParentNode(); if (parent instanceof Element) { current = (Element) parent; } } return false; } }