/******************************************************************************* * Copyright (c) 2014, 2015 IBH SYSTEMS GmbH. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBH SYSTEMS GmbH - initial API and implementation *******************************************************************************/ package org.eclipse.packagedrone.repo; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.io.StringWriter; import java.io.Writer; import java.util.Iterator; import java.util.function.Consumer; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.OutputKeys; import javax.xml.transform.Result; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpression; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import org.eclipse.packagedrone.repo.internal.Activator; import org.eclipse.packagedrone.utils.xml.XmlToolsFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; /** * A helper class when working with XML documents * <p> * This class is not thread-safe and methods may throw Exceptions when accessing * XML parsers from multiple thread concurrently. * </p> */ public class XmlHelper { public static final class NodeListIterator implements Iterator<Node> { private final NodeList list; private int index; public NodeListIterator ( final NodeList list ) { this.list = list; } @Override public Node next () { return this.list.item ( this.index++ ); } @Override public boolean hasNext () { return this.index < this.list.getLength (); } } /** * Iterate over the direct child elements of an element */ public static final class ElementIterator implements Iterator<Element> { private final Element element; private int index; private final String name; public ElementIterator ( final Element element ) { this ( element, null ); } public ElementIterator ( final Element element, final String name ) { this.element = element; this.name = name; } private Element peek () { Node node; while ( ( node = this.element.getChildNodes ().item ( this.index ) ) != null ) { if ( ! ( node instanceof Element ) ) { this.index++; continue; } final Element ele = (Element)node; if ( this.name != null && !ele.getNodeName ().equals ( this.name ) ) { this.index++; continue; } return ele; } // out of nodes return null; } @Override public Element next () { final Element ele = peek (); if ( ele != null ) { this.index++; } return ele; } @Override public boolean hasNext () { // we could cache the result for quicker checking return peek () != null; } } private final TransformerFactory transformerFactory; private final XPathFactory xpathFactory; private final DocumentBuilderFactory dbf; private final DocumentBuilderFactory dbfNs; private final XmlToolsFactory tools; public XmlHelper () { this.tools = Activator.getXmlToolsFactory (); this.dbf = this.tools.newDocumentBuilderFactory (); this.dbfNs = this.tools.newDocumentBuilderFactory (); this.dbfNs.setNamespaceAware ( true ); this.transformerFactory = this.tools.newTransformerFactory (); this.xpathFactory = this.tools.newXPathFactory (); } /** * @deprecated Instead the class {@link * org.eclipse.packagedrone.utils.xml.XmlToolsFactory} should be * used. */ @Deprecated public static XPathFactory createXPathFactory () { return Activator.getXmlToolsFactory ().newXPathFactory (); } public Document create () { try { return this.dbf.newDocumentBuilder ().newDocument (); } catch ( final ParserConfigurationException e ) { throw new RuntimeException ( e ); } } public Document createNs () { try { return this.dbfNs.newDocumentBuilder ().newDocument (); } catch ( final ParserConfigurationException e ) { throw new RuntimeException ( e ); } } public DocumentBuilder getBuilder () throws Exception { return this.dbf.newDocumentBuilder (); } public Document parse ( final InputStream stream ) throws Exception { return this.dbf.newDocumentBuilder ().parse ( stream ); } public Document parseNs ( final InputStream stream ) throws Exception { return this.dbfNs.newDocumentBuilder ().parse ( stream ); } public String toString ( final Node doc ) { try { final StringWriter sw = new StringWriter (); write ( doc, sw ); sw.close (); return sw.toString (); } catch ( final Exception e ) { throw new RuntimeException ( e ); } } public void write ( final Node doc, final OutputStream stream ) throws Exception { write ( doc, new StreamResult ( stream ) ); } public void write ( final Node doc, final Writer writer ) throws Exception { write ( doc, new StreamResult ( writer ) ); } public void write ( final Node doc, final Result result ) throws TransformerException { final Transformer transformer = this.transformerFactory.newTransformer (); final DOMSource source = new DOMSource ( doc ); transformer.setOutputProperty ( OutputKeys.INDENT, "yes" ); transformer.setOutputProperty ( OutputKeys.ENCODING, "UTF-8" ); transformer.setOutputProperty ( "{http://xml.apache.org/xslt}indent-amount", "2" ); transformer.transform ( source, result ); } public static void write ( final TransformerFactory transformerFactory, final Node node, final Result result ) throws TransformerException { write ( transformerFactory, node, result, null ); } public static void write ( final TransformerFactory transformerFactory, final Node node, final Result result, final Consumer<Transformer> transformerCustomizer ) throws TransformerException { final Transformer transformer = transformerFactory.newTransformer (); final DOMSource source = new DOMSource ( node ); transformer.setOutputProperty ( OutputKeys.INDENT, "yes" ); transformer.setOutputProperty ( OutputKeys.ENCODING, "UTF-8" ); transformer.setOutputProperty ( "{http://xml.apache.org/xslt}indent-amount", "2" ); if ( transformerCustomizer != null ) { transformerCustomizer.accept ( transformer ); } transformer.transform ( source, result ); } public byte[] toData ( final Node doc ) throws Exception { final ByteArrayOutputStream out = new ByteArrayOutputStream (); write ( doc, out ); out.close (); return out.toByteArray (); } public String getElementValue ( final Node element, final String path ) throws Exception { return getElementValue ( path ( element, path ) ); } public static String getElementValue ( final Node element, final XPathExpression expression ) throws Exception { return getElementValue ( executePath ( element, expression ) ); } public static String getElementValue ( final NodeList list ) { for ( final Node n : iter ( list ) ) { return text ( n ); } return null; } private static String text ( final Node node ) { return node.getTextContent (); } public static Iterable<Element> iterElement ( final Element element, final String name ) { return new Iterable<Element> () { @Override public Iterator<Element> iterator () { return new ElementIterator ( element, name ); } }; } public static Iterable<Node> iter ( final NodeList list ) { return new Iterable<Node> () { @Override public Iterator<Node> iterator () { return new NodeListIterator ( list ); } }; } public NodeList path ( final Node node, final String path ) throws XPathExpressionException { final XPath xpath = this.xpathFactory.newXPath (); final XPathExpression expression = xpath.compile ( path ); return executePath ( node, expression ); } public static NodeList executePath ( final Node node, final XPathExpression expression ) throws XPathExpressionException { return (NodeList)expression.evaluate ( node, XPathConstants.NODESET ); } /** * Create a new element and add it as the last child * * @param parent * the parent of the new element * @param name * the name of the element * @return the new element */ public static Element addElement ( final Element parent, final String name ) { final Element ele = parent.getOwnerDocument ().createElement ( name ); parent.appendChild ( ele ); return ele; } public static Element addElement ( final Element parent, final String name, final Object value ) { final Element ele = addElement ( parent, name ); if ( value != null ) { ele.setTextContent ( value.toString () ); } return ele; } public static Element addOptionalElement ( final Element parent, final String name, final Object value ) { if ( value == null ) { return null; } final Element ele = addElement ( parent, name ); ele.setTextContent ( value.toString () ); return ele; } /** * Create a new element and add it as the first child * * @param parent * the parent to which to add the element * @param name * the name of the element * @return the new element */ public static Element addElementFirst ( final Element parent, final String name ) { final Element ele = parent.getOwnerDocument ().createElement ( name ); parent.insertBefore ( ele, null ); return ele; } public static void fixSize ( final Element element ) { final int len = element.getChildNodes ().getLength (); if ( len > 0 ) { element.setAttribute ( "size", "" + len ); } else { element.getParentNode ().removeChild ( element ); } } /** * Get the text value of the first element with the matching name * <p> * Assuming you have an XML file: * * <pre> * <parent> *   <foo>bar</foo> *   <hello>world</hello> * </parent> * </pre> * * Calling {@link #getText(Element, String)} with "parent" as element and * "hello" as name, it would return "world". * </p> * * @param ele * the element to check * @param name * the name of the child element * @return the text value of the element */ public static String getText ( final Element ele, final String name ) { for ( final Node child : iter ( ele.getChildNodes () ) ) { if ( ! ( child instanceof Element ) ) { continue; } final Element childEle = (Element)child; if ( !childEle.getNodeName ().equals ( name ) ) { continue; } return childEle.getTextContent (); } return null; } }