io.restassured.path.xml.XmlPath Maven / Gradle / Ivy
/*
* Copyright 2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.restassured.path.xml;
import groovy.lang.GroovyRuntimeException;
import groovy.util.XmlSlurper;
import groovy.util.slurpersupport.GPathResult;
import groovy.xml.XmlUtil;
import io.restassured.internal.common.assertion.AssertParameter;
import io.restassured.internal.common.path.ObjectConverter;
import io.restassured.internal.path.xml.*;
import io.restassured.internal.path.xml.mapping.XmlObjectDeserializer;
import io.restassured.path.xml.config.XmlParserType;
import io.restassured.path.xml.config.XmlPathConfig;
import io.restassured.path.xml.element.Node;
import io.restassured.path.xml.element.NodeChildren;
import io.restassured.path.xml.exception.XmlPathException;
import io.restassured.path.xml.mapper.factory.JAXBObjectMapperFactory;
import org.apache.commons.lang3.Validate;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;
import java.io.File;
import java.io.InputStream;
import java.io.Reader;
import java.net.URI;
import java.util.*;
import java.util.Map.Entry;
import static io.restassured.path.xml.XmlPath.CompatibilityMode.XML;
/**
* XmlPath is an alternative to using XPath for easily getting values from an XML document. It follows the Groovy GPath syntax
* described here.
Let's say we have an XML defined as;
*
* <shopping>
* <category type="groceries">
* <item>
* <name>Chocolate</name>
* <price>10</price>
* </item>
* <item>
* <name>Coffee</name>
* <price>20</price>
* </item>
* </category>
* <category type="supplies">
* <item>
* <name>Paper</name>
* <price>5</price>
* </item>
* <item quantity="4">
* <name>Pens</name>
* <price>15</price>
* </item>
* </category>
* <category type="present">
* <item when="Aug 10">
* <name>Kathryn's Birthday</name>
* <price>200</price>
* </item>
* </category>
* </shopping>
*
*
* Get the name of the first category item:
*
* String name = with(XML).get("shopping.category.item[0].name");
*
*
* To get the number of category items:
*
* int items = with(XML).get("shopping.category.item.size()");
*
*
* Get a specific category:
*
* Node category = with(XML).get("shopping.category[0]");
*
*
* To get the number of categories with type attribute equal to 'groceries':
*
* int items = with(XML).get("shopping.category.findAll { it.@type == 'groceries' }.size()");
*
*
* Get all items with price greater than or equal to 10 and less than or equal to 20:
*
* List<Node> itemsBetweenTenAndTwenty = with(XML).get("shopping.category.item.findAll { item -> def price = item.price.toFloat(); price >= 10 && price <= 20 }");
*
*
* Get the chocolate price:
*
* int priceOfChocolate = with(XML).getInt("**.find { it.name == 'Chocolate' }.price"
*
*
* You can also parse HTML by setting compatibility mode to HTML:
*
* XmlPath xmlPath = new XmlPath(CompatibilityMode.HTML,<some html>);
*
* The XmlPath implementation of rest-assured uses a Groovy shell to evaluate expressions so be careful when injecting
* user input into the expression. For example avoid doing this:
*
* String type = System.console().readLine();
* List<Map> books = with(Object).get("shopping.category.findAll { it.@type == '"+type+"' }");
*
* Instead use the {@link #param(java.lang.String, java.lang.Object)} method like this:
*
* String type = System.console().readLine();
* List<Map> books = with(Object).param("type", type).get("shopping.category.findAll { it.@type == type}");
*
*/
public class XmlPath {
public static XmlPathConfig config = null;
private final CompatibilityMode mode;
private LazyXmlParser lazyXmlParser;
private XmlPathConfig xmlPathConfig = null;
/**
* Parameters for groovy console (not initialized here to save memory for queries that don't use params)
*/
private Map params;
private String rootPath = "";
/**
* Instantiate a new XmlPath instance.
*
* @param text The text containing the XML document
*/
public XmlPath(String text) {
this(XML, text);
}
/**
* Instantiate a new XmlPath instance.
*
* @param stream The stream containing the XML document
*/
public XmlPath(InputStream stream) {
this(XML, stream);
}
/**
* Instantiate a new XmlPath instance.
*
* @param source The source containing the XML document
*/
public XmlPath(InputSource source) {
this(XML, source);
}
/**
* Instantiate a new XmlPath instance.
*
* @param file The file containing the XML document
*/
public XmlPath(File file) {
this(XML, file);
}
/**
* Instantiate a new XmlPath instance.
*
* @param reader The reader containing the XML document
*/
public XmlPath(Reader reader) {
this(XML, reader);
}
/**
* Instantiate a new XmlPath instance.
*
* @param uri The URI containing the XML document
*/
public XmlPath(URI uri) {
this(XML, uri);
}
/**
* Instantiate a new XmlPath instance.
*
* @param mode The compatibility mode
* @param text The text containing the XML document
*/
public XmlPath(CompatibilityMode mode, String text) {
Validate.notNull(mode, "Compatibility mode cannot be null");
this.mode = mode;
lazyXmlParser = parseText(text);
}
/**
* Instantiate a new XmlPath instance.
*
* @param mode The compatibility mode
* @param stream The stream containing the XML document
*/
public XmlPath(CompatibilityMode mode, InputStream stream) {
Validate.notNull(mode, "Compatibility mode cannot be null");
this.mode = mode;
lazyXmlParser = parseInputStream(stream);
}
/**
* Instantiate a new XmlPath instance.
*
* @param mode The compatibility mode
* @param source The source containing the XML document
*/
public XmlPath(CompatibilityMode mode, InputSource source) {
Validate.notNull(mode, "Compatibility mode cannot be null");
this.mode = mode;
lazyXmlParser = parseInputSource(source);
}
/**
* Instantiate a new XmlPath instance.
*
* @param mode The compatibility mode
* @param file The file containing the XML document
*/
public XmlPath(CompatibilityMode mode, File file) {
Validate.notNull(mode, "Compatibility mode cannot be null");
this.mode = mode;
lazyXmlParser = parseFile(file);
}
/**
* Instantiate a new XmlPath instance.
*
* @param mode The compatibility mode
* @param reader The reader containing the XML document
*/
public XmlPath(CompatibilityMode mode, Reader reader) {
Validate.notNull(mode, "Compatibility mode cannot be null");
this.mode = mode;
lazyXmlParser = parseReader(reader);
}
/**
* Instantiate a new XmlPath instance.
*
* @param mode The compatibility mode
* @param uri The URI containing the XML document
*/
public XmlPath(CompatibilityMode mode, URI uri) {
Validate.notNull(mode, "Compatibility mode cannot be null");
this.mode = mode;
lazyXmlParser = parseURI(uri);
}
/**
* Configure XmlPath to use a specific JAXB object mapper factory
*
* @param factory The JAXB object mapper factory instance
* @return a new XmlPath instance
*/
public XmlPath using(JAXBObjectMapperFactory factory) {
return new XmlPath(this, getXmlPathConfig().jaxbObjectMapperFactory(factory));
}
/**
* Configure XmlPath to with a specific XmlPathConfig.
*
* @param config The XmlPath config
* @return a new XmlPath instance
*/
public XmlPath using(XmlPathConfig config) {
return new XmlPath(this, config);
}
private XmlPath(XmlPath xmlPath, XmlPathConfig config) {
this.xmlPathConfig = config;
this.mode = xmlPath.mode;
this.lazyXmlParser = xmlPath.lazyXmlParser.changeCompatibilityMode(mode).changeConfig(config);
if (xmlPath.params != null) {
this.params = new HashMap(xmlPath.params);
}
}
/**
* Get the entire XML graph as an Object
* this url.
*
* @return The XML Node. A {@link java.lang.ClassCastException} will be thrown if the object
* cannot be casted to the expected type.
*/
public Node get() {
return (Node) get("$");
}
/**
* Get the result of an XML path expression. For syntax details please refer to
* this url.
*
* @param path The XML path.
* @param The type of the return value.
* @return The object matching the XML path. A {@link java.lang.ClassCastException} will be thrown if the object
* cannot be casted to the expected type.
*/
public T get(String path) {
AssertParameter.notNull(path, "path");
return getFromPath(path, true);
}
/**
* Get the result of an XML path expression as a list. For syntax details please refer to
* this url.
*
* @param path The XML path.
* @param The list type
* @return The object matching the XML path. A {@link java.lang.ClassCastException} will be thrown if the object
* cannot be casted to the expected type.
*/
public List getList(String path) {
return getAsList(path);
}
/**
* Get the result of an XML path expression as a list. For syntax details please refer to
* this url.
*
* @param path The XML path.
* @param genericType The generic list type
* @param The type
* @return The object matching the XML path. A {@link java.lang.ClassCastException} will be thrown if the object
* cannot be casted to the expected type.
*/
public List getList(String path, Class genericType) {
return getAsList(path, genericType);
}
/**
* Get the result of an XML path expression as a map. For syntax details please refer to
* this url.
*
* @param path The XML path.
* @param The type of the expected key
* @param The type of the expected value
* @return The object matching the XML path. A {@link java.lang.ClassCastException} will be thrown if the object
* cannot be casted to the expected type.
*/
public Map getMap(String path) {
return get(path);
}
/**
* Get the result of an XML path expression as a map. For syntax details please refer to
* this url.
*
* @param path The XML path.
* @param keyType The type of the expected key
* @param valueType The type of the expected value
* @param The type of the expected key
* @param The type of the expected value
* @return The object matching the XML path. A {@link java.lang.ClassCastException} will be thrown if the object
* cannot be casted to the expected type.
*/
public Map getMap(String path, Class keyType, Class valueType) {
final Map originalMap = get(path);
final Map newMap = new HashMap();
for (Entry entry : originalMap.entrySet()) {
final K key = entry.getKey() == null ? null : convertObjectTo(entry.getKey(), keyType);
final V value = entry.getValue() == null ? null : convertObjectTo(entry.getValue(), valueType);
newMap.put(key, value);
}
return Collections.unmodifiableMap(newMap);
}
/**
* Get an XML document as a Java Object.
*
* @param objectType The type of the java object.
* @param The type of the java object
* @return A Java object representation of the XML document
*/
public T getObject(String path, Class objectType) {
Object object = getFromPath(path, false);
return getObjectAsType(object, objectType);
}
private T getObjectAsType(Object object, Class objectType) {
if (object == null) {
return null;
} else if (object instanceof GPathResult) {
try {
object = XmlUtil.serialize((GPathResult) object);
} catch (GroovyRuntimeException e) {
throw new IllegalArgumentException("Failed to convert XML to Java Object. If you're trying convert to a list then use the getList method instead.", e);
}
} else if (object instanceof groovy.util.slurpersupport.Node) {
object = GroovyNodeSerializer.toXML((groovy.util.slurpersupport.Node) object);
}
XmlPathConfig cfg = new XmlPathConfig(getXmlPathConfig());
if (cfg.hasCustomJaxbObjectMapperFactory()) {
cfg = cfg.defaultParserType(XmlParserType.JAXB);
}
if (!(object instanceof String)) {
throw new IllegalStateException("Internal error: XML object was not an instance of String, please report to the REST Assured mailing-list.");
}
//noinspection RedundantCast
return (T) XmlObjectDeserializer.deserialize((String) object, objectType, cfg);
}
private T getFromPath(String path, boolean convertToJavaObject) {
final GPathResult input = lazyXmlParser.invoke();
final XMLAssertion xmlAssertion = new XMLAssertion();
if (params != null) {
xmlAssertion.setParams(params);
}
final String root = rootPath.equals("") ? rootPath : rootPath.endsWith(".") ? rootPath : rootPath + ".";
xmlAssertion.setKey(root + path);
//noinspection unchecked
return (T) xmlAssertion.getResult(input, convertToJavaObject, true);
}
/**
* Get the result of an XML path expression as an int. For syntax details please refer to
* this url.
*
* @param path The XML path.
* @return The object matching the XML path. A {@link java.lang.ClassCastException} will be thrown if the object
* cannot be casted to the expected type.
*/
public int getInt(String path) {
final Object object = get(path);
return convertObjectTo(object, Integer.class);
}
/**
* Get the result of an XML path expression as a boolean. For syntax details please refer to
* this url.
*
* @param path The XML path.
* @return The object matching the XML path. A {@link java.lang.ClassCastException} will be thrown if the object
* cannot be casted to the expected type.
*/
public boolean getBoolean(String path) {
Object object = get(path);
return convertObjectTo(object, Boolean.class);
}
/**
* Get the result of an XML path expression as a {@link Node}. For syntax details please refer to
* this url.
*
* @param path The XML path.
* @return The object matching the XML path. A {@link java.lang.ClassCastException} will be thrown if the object
* cannot be casted to the expected type.
*/
public Node getNode(String path) {
return convertObjectTo(get(path), Node.class);
}
/**
* Get the result of an XML path expression as a {@link NodeChildren}. For syntax details please refer to
* this url.
*
* @param path The XML path.
* @return The object matching the XML path. A {@link java.lang.ClassCastException} will be thrown if the object
* cannot be casted to the expected type.
*/
public NodeChildren getNodeChildren(String path) {
return convertObjectTo(get(path), NodeChildren.class);
}
/**
* Get the result of an XML path expression as a char. For syntax details please refer to
* this url.
*
* @param path The XML path.
* @return The object matching the XML path. A {@link java.lang.ClassCastException} will be thrown if the object
* cannot be casted to the expected type.
*/
public char getChar(String path) {
Object object = get(path);
return convertObjectTo(object, Character.class);
}
/**
* Get the result of an XML path expression as a byte. For syntax details please refer to
* this url.
*
* @param path The XML path.
* @return The object matching the XML path. A {@link java.lang.ClassCastException} will be thrown if the object
* cannot be casted to the expected type.
*/
public byte getByte(String path) {
Object object = get(path);
return convertObjectTo(object, Byte.class);
}
/**
* Get the result of an XML path expression as a short. For syntax details please refer to
* this url.
*
* @param path The XML path.
* @return The object matching the XML path. A {@link java.lang.ClassCastException} will be thrown if the object
* cannot be casted to the expected type.
*/
public short getShort(String path) {
Object object = get(path);
return convertObjectTo(object, Short.class);
}
/**
* Get the result of an XML path expression as a float. For syntax details please refer to
* this url.
*
* @param path The XML path.
* @return The object matching the XML path. A {@link java.lang.ClassCastException} will be thrown if the object
* cannot be casted to the expected type.
*/
public float getFloat(String path) {
Object object = get(path);
return convertObjectTo(object, Float.class);
}
/**
* Get the result of an XML path expression as a double. For syntax details please refer to
* this url.
*
* @param path The XML path.
* @return The object matching the XML path. A {@link java.lang.ClassCastException} will be thrown if the object
* cannot be casted to the expected type.
*/
public double getDouble(String path) {
Object object = get(path);
return convertObjectTo(object, Double.class);
}
/**
* Get the result of an XML path expression as a long. For syntax details please refer to
* this url.
*
* @param path The XML path.
* @return The object matching the XML path. A {@link java.lang.ClassCastException} will be thrown if the object
* cannot be casted to the expected type.
*/
public long getLong(String path) {
Object object = get(path);
return convertObjectTo(object, Long.class);
}
/**
* Get the result of an XML path expression as a string. For syntax details please refer to
* this url.
*
* @param path The XML path.
* @return The object matching the XML path. A {@link java.lang.ClassCastException} will be thrown if the object
* cannot be casted to the expected type.
*/
public String getString(String path) {
Object object = get(path);
return convertObjectTo(object, String.class);
}
/**
* Get the result of an XML path expression as a UUID. For syntax details please refer to
* this url.
*
* @param path The XML path.
* @return The object matching the XML path. A {@link java.lang.ClassCastException} will be thrown if the object
* cannot be casted to the expected type.
*/
public UUID getUUID(String path) {
Object object = get(path);
return convertObjectTo(object, UUID.class);
}
/**
* Add a parameter for the expression. Example:
*
* String type = System.console().readLine();
* List<Map> books = with(Object).param("type", type).get("shopping.category.findAll { it.@type == type}");
*
*
* @param key The name of the parameter. Just use this name in your expression as a variable
* @param value The value of the parameter
* @return New XmlPath instance with the parameter set
*/
public XmlPath param(String key, Object value) {
XmlPath newP = new XmlPath(this, getXmlPathConfig());
if (newP.params == null) {
newP.params = new HashMap();
}
newP.params.put(key, value);
return newP;
}
/**
* Peeks into the XML/HTML that XmlPath will parse by printing it to the console. You can
* continue working with XmlPath afterwards. This is mainly for debug purposes. If you want to return a prettified version of the content
* see {@link #prettify()}. If you want to return a prettified version of the content and also print it to the console use {@link #prettyPrint()}.
*
*
* Note that the content is not guaranteed to be looking exactly like the it does at the source. This is because once you peek
* the content has been downloaded and transformed into another data structure (used by XmlPath) and the XML is rendered
* from this data structure.
*
*
* @return The same XmlPath instance
*/
public XmlPath peek() {
final GPathResult result = lazyXmlParser.invoke();
final String render = XmlRenderer.render(result);
System.out.println(render);
return this;
}
/**
* Peeks into the XML/HTML that XmlPath will parse by printing it to the console in a prettified manner. You can
* continue working with XmlPath afterwards. This is mainly for debug purposes. If you want to return a prettified version of the content
* see {@link #prettify()}. If you want to return a prettified version of the content and also print it to the console use {@link #prettyPrint()}.
*
*
* Note that the content is not guaranteed to be looking exactly like the it does at the source. This is because once you peek
* the content has been downloaded and transformed into another data structure (used by XmlPath) and the XML is rendered
* from this data structure.
*
*
* @return The same XmlPath instance
*/
public XmlPath prettyPeek() {
final GPathResult result = lazyXmlParser.invoke();
final String prettify = XmlPrettifier.prettify(result);
System.out.println(prettify);
return this;
}
/**
* Get the XML as a prettified string.
*
*
* Note that the content is not guaranteed to be looking exactly like the it does at the source. This is because once you peek
* the content has been downloaded and transformed into another data structure (used by XmlPath) and the XML is rendered
* from this data structure.
*
*
* @return The XML as a prettified String.
*/
public String prettify() {
return XmlPrettifier.prettify(lazyXmlParser.invoke());
}
/**
* Get and print the XML as a prettified string.
*
*
* Note that the content is not guaranteed to be looking exactly like the it does at the source. This is because once you peek
* the content has been downloaded and transformed into another data structure (used by XmlPath) and the XML is rendered
* from this data structure.
*
*
* @return The XML as a prettified String.
*/
public String prettyPrint() {
final String pretty = prettify();
System.out.println(pretty);
return pretty;
}
/**
* Instantiate a new XmlPath instance.
*
* @param text The text containing the XML document
*/
public static XmlPath given(String text) {
return new XmlPath(text);
}
/**
* Instantiate a new XmlPath instance.
*
* @param stream The stream containing the XML document
*/
public static XmlPath given(InputStream stream) {
return new XmlPath(stream);
}
/**
* Instantiate a new XmlPath instance.
*
* @param source The source containing the XML document
*/
public static XmlPath given(InputSource source) {
return new XmlPath(source);
}
/**
* Instantiate a new XmlPath instance.
*
* @param file The file containing the XML document
*/
public static XmlPath given(File file) {
return new XmlPath(file);
}
/**
* Instantiate a new XmlPath instance.
*
* @param reader The reader containing the XML document
*/
public static XmlPath given(Reader reader) {
return new XmlPath(reader);
}
/**
* Instantiate a new XmlPath instance.
*
* @param uri The URI containing the XML document
*/
public static XmlPath given(URI uri) {
return new XmlPath(uri);
}
public static XmlPath with(InputStream stream) {
return new XmlPath(stream);
}
/**
* Instantiate a new XmlPath instance.
*
* @param text The text containing the XML document
*/
public static XmlPath with(String text) {
return new XmlPath(text);
}
/**
* Instantiate a new XmlPath instance.
*
* @param source The source containing the XML document
*/
public static XmlPath with(InputSource source) {
return new XmlPath(source);
}
/**
* Instantiate a new XmlPath instance.
*
* @param file The file containing the XML document
*/
public static XmlPath with(File file) {
return new XmlPath(file);
}
/**
* Instantiate a new XmlPath instance.
*
* @param reader The reader containing the XML document
*/
public static XmlPath with(Reader reader) {
return new XmlPath(reader);
}
/**
* Instantiate a new XmlPath instance.
*
* @param uri The URI containing the XML document
*/
public static XmlPath with(URI uri) {
return new XmlPath(uri);
}
/**
* Instantiate a new XmlPath instance.
*
* @param stream The stream containing the XML document
*/
public static XmlPath from(InputStream stream) {
return new XmlPath(stream);
}
/**
* Instantiate a new XmlPath instance.
*
* @param text The text containing the XML document
*/
public static XmlPath from(String text) {
return new XmlPath(text);
}
/**
* Instantiate a new XmlPath instance.
*
* @param source The source containing the XML document
*/
public static XmlPath from(InputSource source) {
return new XmlPath(source);
}
/**
* Instantiate a new XmlPath instance.
*
* @param file The file containing the XML document
*/
public static XmlPath from(File file) {
return new XmlPath(file);
}
/**
* Instantiate a new XmlPath instance.
*
* @param reader The reader containing the XML document
*/
public static XmlPath from(Reader reader) {
return new XmlPath(reader);
}
/**
* Instantiate a new XmlPath instance.
*
* @param uri The URI containing the XML document
*/
public static XmlPath from(URI uri) {
return new XmlPath(uri);
}
private LazyXmlParser parseText(final String text) {
return new LazyXmlParser(getXmlPathConfig(), mode) {
protected GPathResult method(XmlSlurper slurper) throws Exception {
return slurper.parseText(text);
}
};
}
/**
* Set the root path of the document so that you don't need to write the entire path. E.g.
*
* final XmlPath xmlPath = new XmlPath(XML).setRoot("shopping.category.item");
* assertThat(xmlPath.getInt("size()"), equalTo(5));
* assertThat(xmlPath.getList("children().list()", String.class), hasItem("Pens"));
*
*
* @param rootPath The root path to use.
* @deprecated Use {@link #setRootPath(String)} instead
*/
@Deprecated
public XmlPath setRoot(String rootPath) {
return setRootPath(rootPath);
}
/**
* Set the root path of the document so that you don't need to write the entire path. E.g.
*
* final XmlPath xmlPath = new XmlPath(XML).setRootPath("shopping.category.item");
* assertThat(xmlPath.getInt("size()"), equalTo(5));
* assertThat(xmlPath.getList("children().list()", String.class), hasItem("Pens"));
*
*
* @param rootPath The root path to use.
*/
public XmlPath setRootPath(String rootPath) {
AssertParameter.notNull(rootPath, "Root path");
this.rootPath = rootPath;
return this;
}
private List getAsList(String path) {
return getAsList(path, null);
}
private List getAsList(String path, final Class> explicitType) {
Object returnObject = get(path);
if (returnObject instanceof NodeChildren) {
final NodeChildren nodeChildren = (NodeChildren) returnObject;
returnObject = convertElementsListTo(nodeChildren.list(), explicitType);
} else if (!(returnObject instanceof List)) {
final List asList = new ArrayList();
if (returnObject != null) {
final T e;
if (explicitType == null) {
e = (T) returnObject.toString();
} else {
e = (T) convertObjectTo(returnObject, explicitType);
}
asList.add(e);
}
returnObject = asList;
} else if (explicitType != null) {
final List> returnObjectAsList = (List>) returnObject;
final List convertedList = new ArrayList();
for (Object o : returnObjectAsList) {
convertedList.add((T) convertObjectTo(o, explicitType));
}
returnObject = convertedList;
}
return returnObject == null ? null : Collections.unmodifiableList((List) returnObject);
}
private List