001/* 002 * This file is part of McIDAS-V 003 * 004 * Copyright 2007-2024 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 https://www.gnu.org/licenses/. 027 */ 028package edu.wisc.ssec.mcidasv.util; 029 030import static java.util.Objects.requireNonNull; 031import static javax.xml.xpath.XPathConstants.NODESET; 032 033import java.io.File; 034import java.io.IOException; 035import java.io.InputStream; 036import java.util.ArrayList; 037import java.util.Iterator; 038import java.util.List; 039import java.util.Map; 040import java.util.NoSuchElementException; 041import java.util.concurrent.ConcurrentHashMap; 042 043import javax.xml.parsers.DocumentBuilder; 044import javax.xml.parsers.DocumentBuilderFactory; 045import javax.xml.xpath.XPathExpression; 046import javax.xml.xpath.XPathExpressionException; 047import javax.xml.xpath.XPathFactory; 048 049import org.slf4j.Logger; 050import org.slf4j.LoggerFactory; 051import org.w3c.dom.Document; 052import org.w3c.dom.Element; 053import org.w3c.dom.Node; 054import org.w3c.dom.NodeList; 055 056import ucar.unidata.idv.IntegratedDataViewer; 057import ucar.unidata.idv.IdvResourceManager.IdvResource; 058import ucar.unidata.util.ResourceCollection.Resource; 059import ucar.unidata.xml.XmlResourceCollection; 060 061/** 062 * Documentation is still forthcoming, but remember that <b>no methods accept 063 * {@code null} parameters!</b> 064 */ 065public final class XPathUtils { 066 067 /** Logging object. */ 068 private static final Logger logger = 069 LoggerFactory.getLogger(XPathUtils.class); 070 071 /** 072 * Maps (and caches) the XPath {@link String} to its compiled 073 * {@link XPathExpression}. 074 */ 075 private static final Map<String, XPathExpression> pathMap = 076 new ConcurrentHashMap<>(); 077 078 /** 079 * Thou shalt not create an instantiation of this class! 080 */ 081 private XPathUtils() {} 082 083 public static XPathExpression expr(String xPath) { 084 requireNonNull(xPath, "Cannot compile a null string"); 085 086 XPathExpression expr = pathMap.get(xPath); 087 if (expr == null) { 088 try { 089 expr = XPathFactory.newInstance().newXPath().compile(xPath); 090 pathMap.put(xPath, expr); 091 } catch (XPathExpressionException e) { 092 throw new RuntimeException("Error compiling xpath", e); 093 } 094 } 095 return expr; 096 } 097 098 public static List<Node> eval(final XmlResourceCollection collection, 099 final String xPath) 100 { 101 requireNonNull(collection, "Cannot search a null resource collection"); 102 requireNonNull(xPath, "Cannot search using a null XPath query"); 103 104 try { 105 List<Node> nodeList = new ArrayList<>(); 106 XPathExpression expression = expr(xPath); 107 108 // Resources are the only things added to the list returned by 109 // getResources(). 110 @SuppressWarnings("unchecked") 111 List<Resource> files = collection.getResources(); 112 113 for (int i = 0; i < files.size(); i++) { 114 if (!collection.isValid(i)) { 115 continue; 116 } 117 String f = files.get(i).toString(); 118 try (InputStream in = XPathUtils.class.getResourceAsStream(f)) { 119 NodeList tmpList = (NodeList)expression.evaluate(loadXml(in), NODESET); 120 for (int j = 0; j < tmpList.getLength(); j++) { 121 nodeList.add(tmpList.item(j)); 122 } 123 } catch (IOException e) { 124 logger.warn("Problem reading from file", e); 125 } 126 127 128 } 129 return nodeList; 130 } catch (XPathExpressionException e) { 131 throw new RuntimeException("Error evaluating xpath", e); 132 } 133 } 134 135 public static NodeList eval(final String xmlFile, final String xPath) { 136 requireNonNull(xmlFile, "Null path to a XML file"); 137 requireNonNull(xPath, "Cannot search using a null XPath query"); 138 139 try { 140 return (NodeList)expr(xPath).evaluate(loadXml(xmlFile), NODESET); 141 } catch (XPathExpressionException e) { 142 throw new RuntimeException("Error evaluation xpath", e); 143 } 144 } 145 146 public static NodeList eval(final Node root, final String xPath) { 147 requireNonNull(root, "Cannot search a null root node"); 148 requireNonNull(xPath, "Cannot search using a null XPath query"); 149 150 try { 151 return (NodeList)expr(xPath).evaluate(root, NODESET); 152 } catch (XPathExpressionException e) { 153 throw new RuntimeException("Error evaluation xpath", e); 154 } 155 } 156 157 public static List<Node> nodes(final IntegratedDataViewer idv, 158 final IdvResource collectionId, 159 final String xPath) 160 { 161 requireNonNull(idv); 162 requireNonNull(collectionId); 163 requireNonNull(xPath); 164 165 XmlResourceCollection collection = 166 idv.getResourceManager().getXmlResources(collectionId); 167 return nodes(collection, xPath); 168 } 169 170 public static List<Node> nodes(final XmlResourceCollection collection, 171 final String xPath) 172 { 173 requireNonNull(collection); 174 requireNonNull(xPath); 175 return eval(collection, xPath); 176 } 177 178 public static NodeListIterator nodes(final String xmlFile, 179 final String xPath) 180 { 181 requireNonNull(xmlFile); 182 requireNonNull(xPath); 183 return new NodeListIterator(eval(xmlFile, xPath)); 184 } 185 186 public static NodeListIterator nodes(final Node root, final String xPath) { 187 requireNonNull(root); 188 requireNonNull(xPath); 189 return new NodeListIterator(eval(root, xPath)); 190 } 191 192 public static NodeListIterator nodes(final Node root) { 193 requireNonNull(root); 194 return nodes(root, "//*"); 195 } 196 197 public static List<Element> elements(final IntegratedDataViewer idv, 198 final IdvResource collectionId, 199 final String xPath) 200 { 201 requireNonNull(idv); 202 requireNonNull(collectionId); 203 requireNonNull(xPath); 204 205 XmlResourceCollection collection = 206 idv.getResourceManager().getXmlResources(collectionId); 207 return elements(collection, xPath); 208 } 209 210 public static List<Element> elements(final XmlResourceCollection collection, 211 final String xPath) 212 { 213 requireNonNull(collection); 214 requireNonNull(xPath); 215 List<Element> elements = new ArrayList<>(); 216 for (Node n : eval(collection, xPath)) { 217 elements.add((Element)n); 218 } 219 return elements; 220 } 221 222 public static ElementListIterator elements(final String xmlFile, 223 final String xPath) 224 { 225 requireNonNull(xmlFile); 226 requireNonNull(xPath); 227 return new ElementListIterator(eval(xmlFile, xPath)); 228 } 229 230 public static ElementListIterator elements(final Node root) { 231 requireNonNull(root); 232 return elements(root, "//*"); 233 } 234 235 public static ElementListIterator elements(final Node root, 236 final String xPath) 237 { 238 requireNonNull(root); 239 requireNonNull(xPath); 240 return new ElementListIterator(eval(root, xPath)); 241 } 242 243 public static Document loadXml(final String xmlFile) { 244 requireNonNull(xmlFile); 245 246 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 247 factory.setNamespaceAware(false); 248 try { 249 DocumentBuilder builder = factory.newDocumentBuilder(); 250 return builder.parse(xmlFile); 251 } catch (Exception e) { 252 throw new RuntimeException("Error loading XML file: "+e.getMessage(), e); 253 } 254 } 255 256 public static Document loadXml(final File xmlFile) { 257 requireNonNull(xmlFile); 258 259 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 260 factory.setNamespaceAware(false); 261 try { 262 DocumentBuilder builder = factory.newDocumentBuilder(); 263 return builder.parse(xmlFile); 264 } catch (Exception e) { 265 throw new RuntimeException("Error loading XML file: "+e.getMessage(), e); 266 } 267 } 268 269 public static Document loadXml(final InputStream in) { 270 requireNonNull(in); 271 272 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 273 factory.setNamespaceAware(false); 274 try { 275 DocumentBuilder builder = factory.newDocumentBuilder(); 276 return builder.parse(in); 277 } catch (Exception e) { 278 throw new RuntimeException("Error loading XML from input stream: "+e.getMessage(), e); 279 } 280 } 281 282 public static class NodeListIterator implements Iterable<Node>, 283 Iterator<Node> 284 { 285 private final NodeList nodeList; 286 private int index = 0; 287 288 public NodeListIterator(final NodeList nodeList) { 289 requireNonNull(nodeList); 290 this.nodeList = nodeList; 291 } 292 293 public Iterator<Node> iterator() { 294 return this; 295 } 296 297 @Override public boolean hasNext() { 298 return index < nodeList.getLength(); 299 } 300 301 @Override public Node next() { 302 Node result = nodeList.item(index++); 303 if (result != null) { 304 return result; 305 } else { 306 throw new NoSuchElementException("No more nodes to iterate through!"); 307 } 308 } 309 310 @Override public void remove() { 311 throw new UnsupportedOperationException("not implemented"); 312 } 313 } 314 315 public static class ElementListIterator implements Iterable<Element>, 316 Iterator<Element> 317 { 318 private final NodeList nodeList; 319 private int index = 0; 320 321 public ElementListIterator(final NodeList nodeList) { 322 requireNonNull(nodeList); 323 this.nodeList = nodeList; 324 } 325 326 public Iterator<Element> iterator() { 327 return this; 328 } 329 330 @Override public boolean hasNext() { 331 return index < nodeList.getLength(); 332 } 333 334 @Override public Element next() { 335 Element result = (Element)nodeList.item(index++); 336 if (result != null) { 337 return result; 338 } else { 339 throw new NoSuchElementException("No more elements to iterate through!"); 340 } 341 } 342 343 @Override public void remove() { 344 throw new UnsupportedOperationException("not implemented"); 345 } 346 } 347}