3 * Copyright © 2014 František Kučera (frantovo.cz)
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 package cz.frantovo.alt2xml.out.xpath;
20 import cz.frantovo.alt2xml.out.AbstractDOMAction;
21 import cz.frantovo.alt2xml.out.ActionContext;
22 import cz.frantovo.alt2xml.out.OutputActionException;
23 import java.io.PrintWriter;
24 import java.io.StringWriter;
25 import java.util.Arrays;
26 import java.util.HashMap;
27 import java.util.List;
29 import java.util.logging.Level;
30 import java.util.logging.Logger;
31 import javax.xml.transform.OutputKeys;
32 import javax.xml.transform.Transformer;
33 import javax.xml.transform.TransformerConfigurationException;
34 import javax.xml.transform.TransformerException;
35 import javax.xml.transform.TransformerFactory;
36 import javax.xml.transform.TransformerFactoryConfigurationError;
37 import javax.xml.transform.dom.DOMResult;
38 import javax.xml.transform.dom.DOMSource;
39 import javax.xml.transform.stream.StreamResult;
40 import javax.xml.xpath.XPath;
41 import javax.xml.xpath.XPathConstants;
42 import javax.xml.xpath.XPathExpression;
43 import javax.xml.xpath.XPathExpressionException;
44 import javax.xml.xpath.XPathFactory;
45 import javax.xml.xpath.XPathVariableResolver;
46 import org.w3c.dom.Attr;
47 import org.w3c.dom.Element;
48 import org.w3c.dom.Node;
49 import org.w3c.dom.NodeList;
53 * @author Ing. František Kučera (frantovo.cz)
55 public class XPathAction extends AbstractDOMAction {
57 public static final String PARAMETER_TYPED_PARAMETERS = "typed-parameters";
58 public static final String PARAMETER_LINE_BREAK = "line-break";
59 public static final String PARAMETER_ENVIRONMENT_VARIABLES = "environment-variables";
60 public static final String PARAMETER_NODE_SET = "node-set";
61 private static final Logger log = Logger.getLogger(XPathAction.class.getName());
62 private final boolean typedParameters;
63 private final boolean lineBreak;
64 private final boolean environmentVariables;
65 private final boolean nodeSet;
66 private final TransformerFactory transformerFactory;
67 private final Transformer copyTransformer;
68 private final XPathFactory xpathFactory;
69 private final XPath xpath;
70 private final String expressionString;
72 public XPathAction(ActionContext actionContext) throws OutputActionException {
74 xpathFactory = XPathFactory.newInstance();
75 xpath = xpathFactory.newXPath();
78 transformerFactory = TransformerFactory.newInstance();
79 copyTransformer = transformerFactory.newTransformer();
80 copyTransformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
81 } catch (TransformerConfigurationException | TransformerFactoryConfigurationError e) {
82 throw new OutputActionException("Unable to initialize XSLT copying transformer", e);
85 final List<String> actionData = getActionContext().getActionData();
87 if (actionData.size() < 1) {
88 throw new OutputActionException("Please specify the XPath expression as action data");
91 typedParameters = Boolean.parseBoolean(actionContext.getActionProperties().getProperty(PARAMETER_TYPED_PARAMETERS));
92 lineBreak = Boolean.parseBoolean(actionContext.getActionProperties().getProperty(PARAMETER_LINE_BREAK, Boolean.TRUE.toString()));
93 environmentVariables = Boolean.parseBoolean(actionContext.getActionProperties().getProperty(PARAMETER_ENVIRONMENT_VARIABLES, Boolean.FALSE.toString()));
94 nodeSet = Boolean.parseBoolean(actionContext.getActionProperties().getProperty(PARAMETER_NODE_SET, Boolean.FALSE.toString()));
96 Map<String, Object> xpathParameters = new HashMap<>();
98 for (int i = 1; i < actionData.size(); i++) {
99 String parameterName = actionData.get(i++);
100 String parameterType = typedParameters ? actionData.get(i++) : PARAMETER_TYPE.STRING.name();
101 Object parameterValue = parseParameterValue(parameterType, actionData.get(i));
102 xpathParameters.put(parameterName, parameterValue);
105 XPathVariableResolver cliVariableResolver = new PropertiesVariableResolver(xpathParameters);
106 XPathVariableResolver variableResolver = environmentVariables ? new CompoundVariableResolver(cliVariableResolver, new EnvironmentVariableResolver()) : cliVariableResolver;
108 xpath.setXPathVariableResolver(variableResolver);
110 expressionString = actionData.get(0);
114 private enum PARAMETER_TYPE {
116 // TODO: wait for Java 8 widespread and rewrite with lambdas :-)
119 public Object parse(String value) {
125 public Object parse(String value) {
126 return Boolean.valueOf(value);
131 public Object parse(String value) {
132 return Integer.parseInt(value);
137 public Object parse(String value) {
138 return Long.parseLong(value);
143 public Object parse(String value) {
144 return Double.parseDouble(value);
148 public abstract Object parse(String value);
152 private static Object parseParameterValue(String type, String value) throws OutputActionException {
155 PARAMETER_TYPE parameterType = PARAMETER_TYPE.valueOf(type.toUpperCase());
158 return parameterType.parse(value);
159 } catch (IllegalArgumentException e) {
160 throw new OutputActionException("Unable to parse value: „" + value + "“ as " + parameterType.name());
162 } catch (IllegalArgumentException e) {
163 throw new OutputActionException(
164 "Invalid XPath parameter type: „" + type
165 + "“. Possible values are: " + Arrays.toString(PARAMETER_TYPE.values())
166 + " and are case insensitive.",
171 private String nodeToString(Node node) throws OutputActionException {
173 StringWriter w = new StringWriter();
174 copyTransformer.transform(new DOMSource(node), new StreamResult(w));
176 } catch (TransformerException e) {
177 throw new OutputActionException("Unable to convert node to string", e);
182 public void run(DOMResult domResult) throws OutputActionException {
183 XPathExpression xpathExpression = null;
185 Node document = domResult.getNode();
187 xpath.setNamespaceContext(new DocumentNamespaceContext(document));
190 xpathExpression = xpath.compile(expressionString);
191 } catch (XPathExpressionException e) {
192 throw new OutputActionException("Unable to compile XPath: " + expressionString, e);
195 try (PrintWriter out = new PrintWriter(getActionContext().getOutputStream())) {
198 NodeList result = (NodeList) xpathExpression.evaluate(document, XPathConstants.NODESET);
199 for (int i = 0; i < result.getLength(); i++) {
200 Node node = result.item(i);
202 log.log(Level.FINE, "Node type: {0}", node.getClass());
204 final String stringValue;
206 if (node instanceof Attr) {
207 Attr attribute = (Attr) node;
208 stringValue = attribute.getValue();
209 } else if (node instanceof Element) {
210 stringValue = nodeToString(node);
212 * TODO: print/log node separator
215 stringValue = String.valueOf(node);
218 out.print(stringValue);
220 * TODO: support also null-byte-separated values
227 log.log(Level.FINE, "String value, no node-set");
228 out.print(xpathExpression.evaluate(document));
235 } catch (XPathExpressionException e) {
236 throw new OutputActionException("Unable to evaluate XPath: " + xpathExpression, e);