franta-hg@43: /**
franta-hg@43: * Alt2XML
franta-hg@43: * Copyright © 2014 František Kučera (frantovo.cz)
franta-hg@43: *
franta-hg@43: * This program is free software: you can redistribute it and/or modify
franta-hg@43: * it under the terms of the GNU General Public License as published by
franta-hg@111: * the Free Software Foundation, version 3 of the License.
franta-hg@43: *
franta-hg@43: * This program is distributed in the hope that it will be useful,
franta-hg@43: * but WITHOUT ANY WARRANTY; without even the implied warranty of
franta-hg@43: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
franta-hg@43: * GNU General Public License for more details.
franta-hg@43: *
franta-hg@43: * You should have received a copy of the GNU General Public License
franta-hg@43: * along with this program. If not, see .
franta-hg@43: */
franta-hg@49: package cz.frantovo.alt2xml.out.xpath;
franta-hg@43:
franta-hg@63: import cz.frantovo.alt2xml.out.AbstractDOMAction;
franta-hg@43: import cz.frantovo.alt2xml.out.ActionContext;
franta-hg@49: import cz.frantovo.alt2xml.out.OutputActionException;
franta-hg@63: import java.io.PrintWriter;
franta-hg@105: import java.io.StringWriter;
franta-hg@72: import java.util.Arrays;
franta-hg@69: import java.util.HashMap;
franta-hg@69: import java.util.List;
franta-hg@69: import java.util.Map;
franta-hg@105: import java.util.logging.Level;
franta-hg@105: import java.util.logging.Logger;
franta-hg@105: import javax.xml.transform.OutputKeys;
franta-hg@105: import javax.xml.transform.Transformer;
franta-hg@105: import javax.xml.transform.TransformerConfigurationException;
franta-hg@105: import javax.xml.transform.TransformerException;
franta-hg@105: import javax.xml.transform.TransformerFactory;
franta-hg@105: import javax.xml.transform.TransformerFactoryConfigurationError;
franta-hg@63: import javax.xml.transform.dom.DOMResult;
franta-hg@105: import javax.xml.transform.dom.DOMSource;
franta-hg@105: import javax.xml.transform.stream.StreamResult;
franta-hg@63: import javax.xml.xpath.XPath;
franta-hg@105: import javax.xml.xpath.XPathConstants;
franta-hg@63: import javax.xml.xpath.XPathExpression;
franta-hg@63: import javax.xml.xpath.XPathExpressionException;
franta-hg@63: import javax.xml.xpath.XPathFactory;
franta-hg@97: import javax.xml.xpath.XPathVariableResolver;
franta-hg@105: import org.w3c.dom.Attr;
franta-hg@105: import org.w3c.dom.Element;
franta-hg@101: import org.w3c.dom.Node;
franta-hg@105: import org.w3c.dom.NodeList;
franta-hg@43:
franta-hg@43: /**
franta-hg@43: *
franta-hg@43: * @author Ing. František Kučera (frantovo.cz)
franta-hg@43: */
franta-hg@63: public class XPathAction extends AbstractDOMAction {
franta-hg@43:
franta-hg@69: public static final String PARAMETER_TYPED_PARAMETERS = "typed-parameters";
franta-hg@79: public static final String PARAMETER_LINE_BREAK = "line-break";
franta-hg@97: public static final String PARAMETER_ENVIRONMENT_VARIABLES = "environment-variables";
franta-hg@105: public static final String PARAMETER_NODE_SET = "node-set";
franta-hg@105: private static final Logger log = Logger.getLogger(XPathAction.class.getName());
franta-hg@79: private final boolean typedParameters;
franta-hg@79: private final boolean lineBreak;
franta-hg@97: private final boolean environmentVariables;
franta-hg@105: private final boolean nodeSet;
franta-hg@105: private final TransformerFactory transformerFactory;
franta-hg@105: private final Transformer copyTransformer;
franta-hg@66: private final XPathFactory xpathFactory;
franta-hg@66: private final XPath xpath;
franta-hg@68: private final String expressionString;
franta-hg@66:
franta-hg@68: public XPathAction(ActionContext actionContext) throws OutputActionException {
franta-hg@43: super(actionContext);
franta-hg@66: xpathFactory = XPathFactory.newInstance();
franta-hg@66: xpath = xpathFactory.newXPath();
franta-hg@43:
franta-hg@105: try {
franta-hg@105: transformerFactory = TransformerFactory.newInstance();
franta-hg@105: copyTransformer = transformerFactory.newTransformer();
franta-hg@105: copyTransformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
franta-hg@105: } catch (TransformerConfigurationException | TransformerFactoryConfigurationError e) {
franta-hg@105: throw new OutputActionException("Unable to initialize XSLT copying transformer", e);
franta-hg@105: }
franta-hg@105:
franta-hg@69: final List actionData = getActionContext().getActionData();
franta-hg@63:
franta-hg@69: if (actionData.size() < 1) {
franta-hg@69: throw new OutputActionException("Please specify the XPath expression as action data");
franta-hg@69: } else {
franta-hg@69:
franta-hg@79: typedParameters = Boolean.parseBoolean(actionContext.getActionProperties().getProperty(PARAMETER_TYPED_PARAMETERS));
franta-hg@79: lineBreak = Boolean.parseBoolean(actionContext.getActionProperties().getProperty(PARAMETER_LINE_BREAK, Boolean.TRUE.toString()));
franta-hg@97: environmentVariables = Boolean.parseBoolean(actionContext.getActionProperties().getProperty(PARAMETER_ENVIRONMENT_VARIABLES, Boolean.FALSE.toString()));
franta-hg@105: nodeSet = Boolean.parseBoolean(actionContext.getActionProperties().getProperty(PARAMETER_NODE_SET, Boolean.FALSE.toString()));
franta-hg@69:
franta-hg@69: Map xpathParameters = new HashMap<>();
franta-hg@69:
franta-hg@69: for (int i = 1; i < actionData.size(); i++) {
franta-hg@69: String parameterName = actionData.get(i++);
franta-hg@72: String parameterType = typedParameters ? actionData.get(i++) : PARAMETER_TYPE.STRING.name();
franta-hg@71: Object parameterValue = parseParameterValue(parameterType, actionData.get(i));
franta-hg@69: xpathParameters.put(parameterName, parameterValue);
franta-hg@69: }
franta-hg@69:
franta-hg@97: XPathVariableResolver cliVariableResolver = new PropertiesVariableResolver(xpathParameters);
franta-hg@97: XPathVariableResolver variableResolver = environmentVariables ? new CompoundVariableResolver(cliVariableResolver, new EnvironmentVariableResolver()) : cliVariableResolver;
franta-hg@97:
franta-hg@97: xpath.setXPathVariableResolver(variableResolver);
franta-hg@69:
franta-hg@69: expressionString = actionData.get(0);
franta-hg@63: }
franta-hg@43: }
franta-hg@68:
franta-hg@72: private enum PARAMETER_TYPE {
franta-hg@72:
franta-hg@72: // TODO: wait for Java 8 widespread and rewrite with lambdas :-)
franta-hg@72: STRING {
franta-hg@72: @Override
franta-hg@72: public Object parse(String value) {
franta-hg@72: return value;
franta-hg@72: }
franta-hg@72: },
franta-hg@72: BOOLEAN {
franta-hg@72: @Override
franta-hg@72: public Object parse(String value) {
franta-hg@72: return Boolean.valueOf(value);
franta-hg@72: }
franta-hg@72: },
franta-hg@72: INTEGER {
franta-hg@72: @Override
franta-hg@72: public Object parse(String value) {
franta-hg@72: return Integer.parseInt(value);
franta-hg@72: }
franta-hg@72: },
franta-hg@72: LONG {
franta-hg@72: @Override
franta-hg@72: public Object parse(String value) {
franta-hg@72: return Long.parseLong(value);
franta-hg@72: }
franta-hg@72: },
franta-hg@72: DOUBLE {
franta-hg@72: @Override
franta-hg@72: public Object parse(String value) {
franta-hg@72: return Double.parseDouble(value);
franta-hg@72: }
franta-hg@72: };
franta-hg@72:
franta-hg@72: public abstract Object parse(String value);
franta-hg@72:
franta-hg@72: }
franta-hg@72:
franta-hg@72: private static Object parseParameterValue(String type, String value) throws OutputActionException {
franta-hg@72:
franta-hg@72: try {
franta-hg@72: PARAMETER_TYPE parameterType = PARAMETER_TYPE.valueOf(type.toUpperCase());
franta-hg@72:
franta-hg@72: try {
franta-hg@72: return parameterType.parse(value);
franta-hg@72: } catch (IllegalArgumentException e) {
franta-hg@72: throw new OutputActionException("Unable to parse value: „" + value + "“ as " + parameterType.name());
franta-hg@72: }
franta-hg@72: } catch (IllegalArgumentException e) {
franta-hg@72: throw new OutputActionException(
franta-hg@72: "Invalid XPath parameter type: „" + type
franta-hg@72: + "“. Possible values are: " + Arrays.toString(PARAMETER_TYPE.values())
franta-hg@72: + " and are case insensitive.",
franta-hg@72: e);
franta-hg@72: }
franta-hg@69: }
franta-hg@69:
franta-hg@105: private String nodeToString(Node node) throws OutputActionException {
franta-hg@105: try {
franta-hg@105: StringWriter w = new StringWriter();
franta-hg@105: copyTransformer.transform(new DOMSource(node), new StreamResult(w));
franta-hg@105: return w.toString();
franta-hg@105: } catch (TransformerException e) {
franta-hg@105: throw new OutputActionException("Unable to convert node to string", e);
franta-hg@105: }
franta-hg@105: }
franta-hg@105:
franta-hg@68: @Override
franta-hg@68: public void run(DOMResult domResult) throws OutputActionException {
franta-hg@101: XPathExpression xpathExpression = null;
franta-hg@68: try {
franta-hg@101: Node document = domResult.getNode();
franta-hg@101:
franta-hg@101: xpath.setNamespaceContext(new DocumentNamespaceContext(document));
franta-hg@101:
franta-hg@101: try {
franta-hg@101: xpathExpression = xpath.compile(expressionString);
franta-hg@101: } catch (XPathExpressionException e) {
franta-hg@101: throw new OutputActionException("Unable to compile XPath: " + expressionString, e);
franta-hg@101: }
franta-hg@101:
franta-hg@68: try (PrintWriter out = new PrintWriter(getActionContext().getOutputStream())) {
franta-hg@105:
franta-hg@105: if (nodeSet) {
franta-hg@105: NodeList result = (NodeList) xpathExpression.evaluate(document, XPathConstants.NODESET);
franta-hg@105: for (int i = 0; i < result.getLength(); i++) {
franta-hg@105: Node node = result.item(i);
franta-hg@105:
franta-hg@105: log.log(Level.FINE, "Node type: {0}", node.getClass());
franta-hg@105:
franta-hg@105: final String stringValue;
franta-hg@105:
franta-hg@105: if (node instanceof Attr) {
franta-hg@105: Attr attribute = (Attr) node;
franta-hg@105: stringValue = attribute.getValue();
franta-hg@105: } else if (node instanceof Element) {
franta-hg@105: stringValue = nodeToString(node);
franta-hg@105: /**
franta-hg@105: * TODO: print/log node separator
franta-hg@105: */
franta-hg@105: } else {
franta-hg@105: stringValue = String.valueOf(node);
franta-hg@105: }
franta-hg@105:
franta-hg@105: out.print(stringValue);
franta-hg@108: /**
franta-hg@108: * TODO: support also null-byte-separated values
franta-hg@108: */
franta-hg@105: if (lineBreak) {
franta-hg@105: out.println();
franta-hg@105: }
franta-hg@105: }
franta-hg@105: } else {
franta-hg@105: log.log(Level.FINE, "String value, no node-set");
franta-hg@105: out.print(xpathExpression.evaluate(document));
franta-hg@105: if (lineBreak) {
franta-hg@105: out.println();
franta-hg@105: }
franta-hg@79: }
franta-hg@105:
franta-hg@68: }
franta-hg@68: } catch (XPathExpressionException e) {
franta-hg@68: throw new OutputActionException("Unable to evaluate XPath: " + xpathExpression, e);
franta-hg@68: }
franta-hg@68: }
franta-hg@43: }