1 /* 2 * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko 3 * SPDX-License-Identifier: MIT 4 */ 5 package com.jcabi.xml; 6 7 import java.util.Collection; 8 import java.util.List; 9 import javax.xml.namespace.NamespaceContext; 10 import org.w3c.dom.Node; 11 import org.w3c.dom.ls.LSResourceResolver; 12 import org.xml.sax.SAXParseException; 13 14 /** 15 * XML document. 16 * 17 * <p>Set of convenient XML manipulations: 18 * 19 * <pre> XML xml = new XMLDocument(content); 20 * for (XML employee : xml.nodes("//Employee")) { 21 * String name = employee.xpath("name/text()").get(0); 22 * // ... 23 * }</pre> 24 * 25 * <p>You can always get DOM node out of this abstraction using {@link #inner()} 26 * or {@link #deepCopy()} methods. 27 * 28 * <p>{@code toString()} must produce a full XML. 29 * 30 * <p>Implementation of this interface must be immutable and thread-safe. 31 * 32 * <p> In most cases, you can use the {@link XMLDocument} implementation. It 33 * implements all required features and will be sufficient for most practical tasks. 34 * The only problem with that implementation is that it uses javax.xml classes under 35 * the hood. The issue with the default java implementation is that it only supports 36 * XPath 1.0. If you require XPath 2.0 support and beyond, you can use the Saxon 37 * implementation of {@link XML} - {@link SaxonDocument}. It is based on the Saxon 38 * library and supports XPath 2.0 and higher. 39 * You can read more about Java XPath versioning problems in the following threads: 40 * <ul> 41 * <li><a href="https://stackoverflow.com/questions/6624149/xpath-2-0-for-java-possible">xpath 2.0 for java possible</a></li> 42 * <li><a href="https://stackoverflow.com/questions/5802895/does-jdk-6-support-all-features-of-xpath-2-0/5803028#5803028">does JDK 6 support all features of XPath 2.0?</a></li> 43 * </ul> 44 * 45 * @see XMLDocument 46 * @since 0.1 47 * @checkstyle AbbreviationAsWordInNameCheck (5 lines) 48 */ 49 public interface XML { 50 51 /** 52 * Find and return text elements or attributes matched by XPath address. 53 * 54 * <p>The XPath query should point to text elements or attributes in the 55 * XML document. If any nodes of different types (elements, comments, etc.) 56 * are found in result node list - 57 * a {@link RuntimeException} will be thrown. 58 * 59 * <p>Alternatively, the XPath query can be a function or expression that 60 * returns a single value instead of pointing to a set of nodes. In this 61 * case, the result will be a List containing a single String, the content 62 * of which is the result of the evaluation. If the expression result is not 63 * a String, it will be converted to a String representation and returned as 64 * such. For example, a document containing three <a> elements, 65 * the input query "count(//a)", will return a singleton List with a single 66 * string value "3". 67 * 68 * <p>This is a convenient method, which is used (according to our 69 * experience) in 95% of all cases. Usually you don't need to get anything 70 * else but a text value of some node or an attribute. And in most cases 71 * you are interested to get just the first value 72 * (use {@code xpath(..).get(0)}). But when/if you need to get more than 73 * just a plain text - use {@link #nodes(String)}. 74 * 75 * <p>The {@link List} returned will throw {@link IndexOutOfBoundsException} 76 * if you try to access a node which wasn't found by this XPath query. 77 * 78 * <p>An {@link IllegalArgumentException} is thrown if the parameter 79 * passed is not a valid XPath expression. 80 * 81 * @param query The XPath query 82 * @return The list of string values (texts) or single function result 83 */ 84 List<String> xpath(String query); 85 86 /** 87 * Retrieve DOM nodes from the XML response. 88 * 89 * <p>The {@link List} returned will throw {@link IndexOutOfBoundsException} 90 * if you try to access a node which wasn't found by this XPath query. 91 * 92 * <p>An {@link IllegalArgumentException} is thrown if the parameter 93 * passed is not a valid XPath expression. 94 * 95 * @param query The XPath query 96 * @return Collection of DOM nodes 97 */ 98 List<XML> nodes(String query); 99 100 /** 101 * Register additional namespace prefix for XPath. 102 * 103 * <p>For example: 104 * 105 * <pre> 106 * String name = new XMLDocument("...") 107 * .registerNs("ns1", "http://example.com") 108 * .registerNs("foo", "http://example.com/foo") 109 * .xpath("/ns1:root/foo:name/text()") 110 * .get(0); 111 * </pre> 112 * 113 * <p>A number of standard namespaces are registered by default in 114 * instances of XML. Their 115 * full list is in {@link XMLDocument#XMLDocument(String)}. 116 * 117 * <p>If a namespace prefix is already registered an 118 * {@link IllegalArgumentException} will be thrown. 119 * 120 * @param prefix The XPath prefix to register 121 * @param uri Namespace URI 122 * @return A new XML document, with this additional namespace registered 123 */ 124 XML registerNs(String prefix, Object uri); 125 126 /** 127 * Append this namespace context to the existing one. 128 * 129 * <p>The existing context (inside this object) and the new one provided 130 * will be merged together. The existing context will have higher 131 * priority. 132 * 133 * @param context The context to append 134 * @return A new XML document, with a merged context on board 135 */ 136 XML merge(NamespaceContext context); 137 138 /** 139 * Retrieve DOM node, represented by this wrapper. 140 * This method works exactly the same as {@link #deepCopy()}. 141 * @return Deep copy of the inner DOM node. 142 * @deprecated Use {@link #inner()} or {@link #deepCopy()} instead. 143 */ 144 @Deprecated 145 Node node(); 146 147 /** 148 * Retrieve DOM node, represented by this wrapper. 149 * Pay attention that this method returns inner node, not a deep copy. 150 * It means that any changes to the returned node will affect the original XML. 151 * @return Inner node. 152 */ 153 Node inner(); 154 155 /** 156 * Retrieve a deep copy of the DOM node, represented by this wrapper. 157 * Might be expensive in terms of performance. 158 * @return Deep copy of the node. 159 */ 160 Node deepCopy(); 161 162 /** 163 * Validate this XML against the XSD schema inside it. 164 * 165 * <p>If you don't have your own resolver, try using 166 * {@link ClasspathResolver}.</p> 167 * 168 * @param resolver XSD schema resolver 169 * @return List of errors found 170 * @since 0.31.0 171 */ 172 Collection<SAXParseException> validate(LSResourceResolver resolver); 173 174 /** 175 * Validate this XML against the provided XSD schema. 176 * @param xsd The Schema 177 * @return List of errors found 178 * @since 0.31.0 179 */ 180 Collection<SAXParseException> validate(XML xsd); 181 }