001 /*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2013
005 * Space Science and Engineering Center (SSEC)
006 * University of Wisconsin - Madison
007 * 1225 W. Dayton Street, Madison, WI 53706, USA
008 * https://www.ssec.wisc.edu/mcidas
009 *
010 * All Rights Reserved
011 *
012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
013 * some McIDAS-V source code is based on IDV and VisAD source code.
014 *
015 * McIDAS-V is free software; you can redistribute it and/or modify
016 * it under the terms of the GNU Lesser Public License as published by
017 * the Free Software Foundation; either version 3 of the License, or
018 * (at your option) any later version.
019 *
020 * McIDAS-V is distributed in the hope that it will be useful,
021 * but WITHOUT ANY WARRANTY; without even the implied warranty of
022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
023 * GNU Lesser Public License for more details.
024 *
025 * You should have received a copy of the GNU Lesser Public License
026 * along with this program. If not, see http://www.gnu.org/licenses.
027 */
028 package edu.wisc.ssec.mcidasv.util;
029
030 import java.io.File;
031 import java.io.InputStream;
032 import java.util.ArrayList;
033 import java.util.Iterator;
034 import java.util.List;
035 import java.util.Map;
036 import java.util.concurrent.ConcurrentHashMap;
037
038 import javax.xml.parsers.DocumentBuilder;
039 import javax.xml.parsers.DocumentBuilderFactory;
040 import javax.xml.xpath.XPathConstants;
041 import javax.xml.xpath.XPathExpression;
042 import javax.xml.xpath.XPathExpressionException;
043 import javax.xml.xpath.XPathFactory;
044
045 import org.w3c.dom.Document;
046 import org.w3c.dom.Element;
047 import org.w3c.dom.Node;
048 import org.w3c.dom.NodeList;
049
050 import ucar.unidata.idv.IntegratedDataViewer;
051 import ucar.unidata.idv.IdvResourceManager.IdvResource;
052 import ucar.unidata.idv.IdvResourceManager.XmlIdvResource;
053 import ucar.unidata.util.ResourceCollection.Resource;
054 import ucar.unidata.xml.XmlResourceCollection;
055
056 import edu.wisc.ssec.mcidasv.util.Contract;
057
058 /**
059 * Documentation is still forthcoming, but remember that <b>no methods accept
060 * {@code null} parameters!</b>
061 */
062 public final class XPathUtils {
063
064 /** Maps (and caches) the XPath {@link String} to its compiled {@link XPathExpression}. */
065 private static final Map<String, XPathExpression> pathMap = new ConcurrentHashMap<String, XPathExpression>();
066
067 /**
068 * Thou shalt not create an instantiation of this class!
069 */
070 private XPathUtils() {}
071
072 public static XPathExpression expr(String xPath) {
073 Contract.notNull(xPath, "Cannot compile a null string");
074
075 XPathExpression expr = pathMap.get(xPath);
076 if (expr == null) {
077 try {
078 expr = XPathFactory.newInstance().newXPath().compile(xPath);
079 pathMap.put(xPath, expr);
080 } catch (XPathExpressionException e) {
081 throw new RuntimeException("Error compiling xpath", e);
082 }
083 }
084 return expr;
085 }
086
087 public static List<Node> eval(final XmlResourceCollection collection, final String xPath) {
088 Contract.notNull(collection, "Cannot search a null resource collection");
089 Contract.notNull(xPath, "Cannot search using a null XPath query");
090
091 try {
092 List<Node> nodeList = new ArrayList<Node>();
093 XPathExpression expression = expr(xPath);
094
095 // Resources are the only things added to the list returned by
096 // getResources().
097 @SuppressWarnings("unchecked")
098 List<Resource> files = collection.getResources();
099
100 for (int i = 0; i < files.size(); i++) {
101 if (!collection.isValid(i))
102 continue;
103
104 InputStream in = XPathUtils.class.getResourceAsStream(files.get(i).toString());
105 if (in == null)
106 continue;
107
108 NodeList tmpList = (NodeList)expression.evaluate(loadXml(in), XPathConstants.NODESET);
109 for (int j = 0; j < tmpList.getLength(); j++) {
110 nodeList.add(tmpList.item(j));
111 }
112 }
113 return nodeList;
114 } catch (XPathExpressionException e) {
115 throw new RuntimeException("Error evaluating xpath", e);
116 }
117 }
118
119 public static NodeList eval(final String xmlFile, final String xPath) {
120 Contract.notNull(xmlFile, "Null path to a XML file");
121 Contract.notNull(xPath, "Cannot search using a null XPath query");
122
123 try {
124 return (NodeList)expr(xPath).evaluate(loadXml(xmlFile), XPathConstants.NODESET);
125 } catch (XPathExpressionException e) {
126 throw new RuntimeException("Error evaluation xpath", e);
127 }
128 }
129
130 public static NodeList eval(final Node root, final String xPath) {
131 Contract.notNull(root, "Cannot search a null root node");
132 Contract.notNull(xPath, "Cannot search using a null XPath query");
133
134 try {
135 return (NodeList)expr(xPath).evaluate(root, XPathConstants.NODESET);
136 } catch (XPathExpressionException e) {
137 throw new RuntimeException("Error evaluation xpath", e);
138 }
139 }
140
141 public static List<Node> nodes(final IntegratedDataViewer idv, final IdvResource collectionId, final String xPath) {
142 Contract.notNull(idv);
143 Contract.notNull(collectionId);
144 Contract.notNull(xPath);
145
146 XmlResourceCollection collection = idv.getResourceManager().getXmlResources(collectionId);
147 return nodes(collection, xPath);
148 }
149
150 public static List<Node> nodes(final XmlResourceCollection collection, final String xPath) {
151 Contract.notNull(collection);
152 Contract.notNull(xPath);
153 return eval(collection, xPath);
154 }
155
156 public static NodeListIterator nodes(final String xmlFile, final String xPath) {
157 Contract.notNull(xmlFile);
158 Contract.notNull(xPath);
159 return new NodeListIterator(eval(xmlFile, xPath));
160 }
161
162 public static NodeListIterator nodes(final Node root, final String xPath) {
163 Contract.notNull(root);
164 Contract.notNull(xPath);
165 return new NodeListIterator(eval(root, xPath));
166 }
167
168 public static NodeListIterator nodes(final Node root) {
169 Contract.notNull(root);
170 return nodes(root, "//*");
171 }
172
173 public static List<Element> elements(final IntegratedDataViewer idv, final IdvResource collectionId, final String xPath) {
174 Contract.notNull(idv);
175 Contract.notNull(collectionId);
176 Contract.notNull(xPath);
177
178 XmlResourceCollection collection = idv.getResourceManager().getXmlResources(collectionId);
179 return elements(collection, xPath);
180 }
181
182 public static List<Element> elements(final XmlResourceCollection collection, final String xPath) {
183 Contract.notNull(collection);
184 Contract.notNull(xPath);
185 List<Element> elements = new ArrayList<Element>();
186 for (Node n : eval(collection, xPath))
187 elements.add((Element)n);
188 return elements;
189 }
190
191 public static ElementListIterator elements(final String xmlFile, final String xPath) {
192 Contract.notNull(xmlFile);
193 Contract.notNull(xPath);
194 return new ElementListIterator(eval(xmlFile, xPath));
195 }
196
197 public static ElementListIterator elements(final Node root) {
198 Contract.notNull(root);
199 return elements(root, "//*");
200 }
201
202 public static ElementListIterator elements(final Node root, final String xPath) {
203 Contract.notNull(root);
204 Contract.notNull(xPath);
205 return new ElementListIterator(eval(root, xPath));
206 }
207
208 public static Document loadXml(final String xmlFile) {
209 Contract.notNull(xmlFile);
210
211 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
212 factory.setNamespaceAware(false);
213 try {
214 DocumentBuilder builder = factory.newDocumentBuilder();
215 return builder.parse(xmlFile);
216 } catch (Exception e) {
217 throw new RuntimeException("Error loading XML file: "+e.getMessage(), e);
218 }
219 }
220
221 public static Document loadXml(final File xmlFile) {
222 Contract.notNull(xmlFile);
223
224 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
225 factory.setNamespaceAware(false);
226 try {
227 DocumentBuilder builder = factory.newDocumentBuilder();
228 return builder.parse(xmlFile);
229 } catch (Exception e) {
230 throw new RuntimeException("Error loading XML file: "+e.getMessage(), e);
231 }
232 }
233
234 public static Document loadXml(final InputStream in) {
235 Contract.notNull(in);
236
237 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
238 factory.setNamespaceAware(false);
239 try {
240 DocumentBuilder builder = factory.newDocumentBuilder();
241 return builder.parse(in);
242 } catch (Exception e) {
243 throw new RuntimeException("Error loading XML from input stream: "+e.getMessage(), e);
244 }
245 }
246
247 public static class NodeListIterator implements Iterable<Node>, Iterator<Node> {
248 private final NodeList nodeList;
249 private int index = 0;
250
251 public NodeListIterator(final NodeList nodeList) {
252 Contract.notNull(nodeList);
253 this.nodeList = nodeList;
254 }
255
256 public Iterator<Node> iterator() {
257 return this;
258 }
259
260 public boolean hasNext() {
261 return (index < nodeList.getLength());
262 }
263
264 public Node next() {
265 return nodeList.item(index++);
266 }
267
268 public void remove() {
269 throw new UnsupportedOperationException("not implemented");
270 }
271 }
272
273 public static class ElementListIterator implements Iterable<Element>, Iterator<Element> {
274 private final NodeList nodeList;
275 private int index = 0;
276
277 public ElementListIterator(final NodeList nodeList) {
278 Contract.notNull(nodeList);
279 this.nodeList = nodeList;
280 }
281
282 public Iterator<Element> iterator() {
283 return this;
284 }
285
286 public boolean hasNext() {
287 return (index < nodeList.getLength());
288 }
289
290 public Element next() {
291 return (Element)nodeList.item(index++);
292 }
293
294 public void remove() {
295 throw new UnsupportedOperationException("not implemented");
296 }
297 }
298 }