001/*
002 *                    BioJava development code
003 *
004 * This code may be freely distributed and modified under the
005 * terms of the GNU Lesser General Public Licence.  This should
006 * be distributed with the code.  If you do not have a copy,
007 * see:
008 *
009 *      http://www.gnu.org/copyleft/lesser.html
010 *
011 * Copyright for this code is held jointly by the individual
012 * authors.  These should be listed in @author doc comments.
013 *
014 * For more information on the BioJava project and its aims,
015 * or to join the biojava-l mailing list, visit the home page
016 * at:
017 *
018 *      http://www.biojava.org/
019 *
020 */
021package org.biojava.nbio.core.util;
022
023import org.w3c.dom.Document;
024import org.w3c.dom.Element;
025import org.w3c.dom.Node;
026import org.w3c.dom.NodeList;
027import org.xml.sax.SAXException;
028
029import javax.xml.parsers.DocumentBuilder;
030import javax.xml.parsers.DocumentBuilderFactory;
031import javax.xml.parsers.ParserConfigurationException;
032import javax.xml.transform.Transformer;
033import javax.xml.transform.TransformerException;
034import javax.xml.transform.TransformerFactory;
035import javax.xml.transform.dom.DOMSource;
036import javax.xml.transform.stream.StreamResult;
037import javax.xml.xpath.XPath;
038import javax.xml.xpath.XPathConstants;
039import javax.xml.xpath.XPathExpressionException;
040import javax.xml.xpath.XPathFactory;
041import java.io.*;
042import java.util.ArrayList;
043
044import static org.biojava.nbio.core.sequence.io.util.IOUtils.close;
045import static org.biojava.nbio.core.sequence.io.util.IOUtils.openFile;
046
047/**
048 * Helper methods to simplify boilerplate XML parsing code for  {@code}org.w3c.dom{@code} XML objects
049 * @author Scooter
050 */
051public class XMLHelper {
052
053        /**
054         * Creates a new element called {@code}elementName{@code} and adds it to {@code}parentElement{@code}
055         * @param parentElement
056         * @param elementName
057         * @return the new child element
058         */
059        public static Element addChildElement(Element parentElement, String elementName) {
060                Element childElement = parentElement.getOwnerDocument().createElement(elementName);
061                parentElement.appendChild(childElement);
062                return childElement;
063        }
064
065        /**
066         * Create a new, empty {@code}org.w3c.dom.Document{@code}
067         * @return a new {@code}org.w3c.dom.Document{@code}
068         * @throws ParserConfigurationException
069         */
070        public static Document getNewDocument() throws ParserConfigurationException  {
071
072                //Create instance of DocumentBuilderFactory
073                DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
074                //Get the DocumentBuilder
075                DocumentBuilder docBuilder = factory.newDocumentBuilder();
076                //Create blank DOM Document
077                Document doc = docBuilder.newDocument();
078                return doc;
079        }
080
081        /**
082         * Given a path to an XML file, parses into an {@code}org.w3c.dom.Document{@code} 
083         * @param fileName path to a readable XML file
084         * @return
085         * @throws SAXException
086         * @throws IOException
087         * @throws ParserConfigurationException
088         */
089        public static Document loadXML(String fileName) throws SAXException, IOException, ParserConfigurationException  {
090                InputStream is = openFile(new File(fileName));
091                Document doc = inputStreamToDocument(new BufferedInputStream(is));
092                close(is);
093                return doc;
094        }
095
096        /**
097         * Creates an {@code}org.w3c.dom.Document{@code} from the content of the {@code}inputStream{@code}
098         * @param inputStream
099         * @return a {@code}Document{@code}
100         * @throws SAXException
101         * @throws IOException
102         * @throws ParserConfigurationException
103         */
104        public static Document inputStreamToDocument(InputStream inputStream) throws SAXException, IOException, ParserConfigurationException  {
105                DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
106
107                DocumentBuilder db = dbf.newDocumentBuilder();
108
109                Document doc = db.parse(inputStream);
110                doc.getDocumentElement().normalize();
111
112                return doc;
113        }
114
115        /**
116         * Given an {@code}org.w3c.dom.Document{@code}, writes it to the given {@code}outputStream{@code}
117         * @param document
118         * @param outputStream
119         * @throws TransformerException
120         */
121        public static void outputToStream(Document document, OutputStream outputStream) throws TransformerException {
122                // Use a Transformer for output
123                TransformerFactory tFactory = TransformerFactory.newInstance();
124                Transformer transformer = tFactory.newTransformer();
125                //    transformer.setOutputProperty(OutputKeys.INDENT, "yes");
126
127                DOMSource source = new DOMSource(document);
128                StreamResult result = new StreamResult(outputStream);
129                transformer.transform(source, result);
130        }
131
132        //static XPath xpath = XPathFactory.newInstance().newXPath();
133
134        /**
135         * Given an element, searches upwards through ancestor Elements till the first Element
136         * matching the requests {@code}parentName{@code} is found.
137         * @param element The starting element
138         * @param parentName The tag name of the requested Element.
139         * @return The found element, or {@code}null{@code} if no matching element is found,
140         */
141        public static Element selectParentElement(Element element, String parentName) {
142                
143            Node parentNode =  element.getParentNode();
144                if (parentNode == null) {
145                        return null;
146                }
147                // check that parent is actually an element, else return null
148                // this is to prevent ClassCastExceptions if element's parent is not an Element.
149                Element parentElement = null;
150                if (Node.ELEMENT_NODE == parentNode.getNodeType()){
151                        parentElement = (Element)parentNode;
152                } else {
153                        return null;
154                }
155                if (parentElement.getTagName().equals(parentName)) {
156                        return parentElement;
157                }
158                return selectParentElement(parentElement, parentName);
159        }
160
161        /**
162         * If {@code}xpathExpression{@code} is a plain string with no '/' characterr, this is 
163         * interpreted as a child element name to search for. 
164         * <b/>
165         * If {@code}xpathExpression{@code} is an XPath expression, this is evaluated and is assumed
166         * to identify a single element.
167         * @param element
168         * @param xpathExpression
169         * @return A single element or null if no match or the 1st match if matches more than 1
170         * @throws XPathExpressionException
171         */
172        public static Element selectSingleElement(Element element, String xpathExpression) throws XPathExpressionException {
173                if (element == null) {
174                        return null;
175                }
176                if (xpathExpression.indexOf("/") == -1) {
177                        NodeList nodeList = element.getChildNodes();
178                        for (int i = 0; i < nodeList.getLength(); i++) {
179                                Node node = nodeList.item(i);
180                                if (node.getNodeType() == Node.ELEMENT_NODE && node.getNodeName().equals(xpathExpression)) {
181                                        return (Element) node;
182                                }
183                        }
184                        //  NodeList nodes = element.getElementsByTagName(xpathExpression);
185                        //  if (nodes.getLength() > 0) {
186                        //      return (Element) nodes.item(0);
187                        //  } else {
188                        return null;
189                        //  }
190                } else {
191                        XPath xpath = XPathFactory.newInstance().newXPath();
192                        Element node = (Element) xpath.evaluate(xpathExpression, element, XPathConstants.NODE);
193                        return node;
194                }
195        }
196
197        /**
198         * Gets a list of elements matching {@code}xpathExpression{@code}. If xpathExpression lacks
199         * a '/' character, only immediate children o {@code}element{@code} are searched over.
200         * <br/>
201         * If {@code}xpathExpression{@code} contains an '/' character, a full XPath search is made
202         * @param element
203         * @param xpathExpression
204         * @return A possibly empty but non-null {@code}ArrayList{@code}
205         * @throws XPathExpressionException
206         */
207        public static ArrayList<Element> selectElements(Element element, String xpathExpression) throws XPathExpressionException {
208                ArrayList<Element> resultVector = new ArrayList<Element>();
209                if (element == null) {
210                        return resultVector;
211                }
212                if (xpathExpression.indexOf("/") == -1) {
213                        NodeList nodeList = element.getChildNodes();
214                        for (int i = 0; i < nodeList.getLength(); i++) {
215                                Node node = nodeList.item(i);
216                                if (node.getNodeType() == Node.ELEMENT_NODE && node.getNodeName().equals(xpathExpression)) {
217                                        resultVector.add((Element) node);
218                                }
219                        }
220                } else {
221                        XPath xpath = XPathFactory.newInstance().newXPath();
222                        NodeList nodes = (NodeList) xpath.evaluate(xpathExpression, element, XPathConstants.NODESET);
223
224
225                        for (int i = 0; i < nodes.getLength(); i++) {
226                                Node node = nodes.item(i);
227                                resultVector.add((Element) node);
228                        }
229                }
230                return resultVector;
231        }
232}