java/alt2xml-out-xpath/src/cz/frantovo/alt2xml/out/xpath/XPathAction.java
author František Kučera <franta-hg@frantovo.cz>
Thu, 24 Oct 2019 21:56:03 +0200
changeset 111 e4900596abdb
parent 108 bbb9b31255be
permissions -rw-r--r--
fix license version: GNU GPLv3
     1 /**
     2  * Alt2XML
     3  * Copyright © 2014 František Kučera (frantovo.cz)
     4  *
     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, version 3 of the License.
     8  *
     9  * This program is distributed in the hope that it will be useful,
    10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  * GNU General Public License for more details.
    13  *
    14  * You should have received a copy of the GNU General Public License
    15  * along with this program. If not, see <http://www.gnu.org/licenses/>.
    16  */
    17 package cz.frantovo.alt2xml.out.xpath;
    18 
    19 import cz.frantovo.alt2xml.out.AbstractDOMAction;
    20 import cz.frantovo.alt2xml.out.ActionContext;
    21 import cz.frantovo.alt2xml.out.OutputActionException;
    22 import java.io.PrintWriter;
    23 import java.io.StringWriter;
    24 import java.util.Arrays;
    25 import java.util.HashMap;
    26 import java.util.List;
    27 import java.util.Map;
    28 import java.util.logging.Level;
    29 import java.util.logging.Logger;
    30 import javax.xml.transform.OutputKeys;
    31 import javax.xml.transform.Transformer;
    32 import javax.xml.transform.TransformerConfigurationException;
    33 import javax.xml.transform.TransformerException;
    34 import javax.xml.transform.TransformerFactory;
    35 import javax.xml.transform.TransformerFactoryConfigurationError;
    36 import javax.xml.transform.dom.DOMResult;
    37 import javax.xml.transform.dom.DOMSource;
    38 import javax.xml.transform.stream.StreamResult;
    39 import javax.xml.xpath.XPath;
    40 import javax.xml.xpath.XPathConstants;
    41 import javax.xml.xpath.XPathExpression;
    42 import javax.xml.xpath.XPathExpressionException;
    43 import javax.xml.xpath.XPathFactory;
    44 import javax.xml.xpath.XPathVariableResolver;
    45 import org.w3c.dom.Attr;
    46 import org.w3c.dom.Element;
    47 import org.w3c.dom.Node;
    48 import org.w3c.dom.NodeList;
    49 
    50 /**
    51  *
    52  * @author Ing. František Kučera (frantovo.cz)
    53  */
    54 public class XPathAction extends AbstractDOMAction {
    55 
    56 	public static final String PARAMETER_TYPED_PARAMETERS = "typed-parameters";
    57 	public static final String PARAMETER_LINE_BREAK = "line-break";
    58 	public static final String PARAMETER_ENVIRONMENT_VARIABLES = "environment-variables";
    59 	public static final String PARAMETER_NODE_SET = "node-set";
    60 	private static final Logger log = Logger.getLogger(XPathAction.class.getName());
    61 	private final boolean typedParameters;
    62 	private final boolean lineBreak;
    63 	private final boolean environmentVariables;
    64 	private final boolean nodeSet;
    65 	private final TransformerFactory transformerFactory;
    66 	private final Transformer copyTransformer;
    67 	private final XPathFactory xpathFactory;
    68 	private final XPath xpath;
    69 	private final String expressionString;
    70 
    71 	public XPathAction(ActionContext actionContext) throws OutputActionException {
    72 		super(actionContext);
    73 		xpathFactory = XPathFactory.newInstance();
    74 		xpath = xpathFactory.newXPath();
    75 
    76 		try {
    77 			transformerFactory = TransformerFactory.newInstance();
    78 			copyTransformer = transformerFactory.newTransformer();
    79 			copyTransformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
    80 		} catch (TransformerConfigurationException | TransformerFactoryConfigurationError e) {
    81 			throw new OutputActionException("Unable to initialize XSLT copying transformer", e);
    82 		}
    83 
    84 		final List<String> actionData = getActionContext().getActionData();
    85 
    86 		if (actionData.size() < 1) {
    87 			throw new OutputActionException("Please specify the XPath expression as action data");
    88 		} else {
    89 
    90 			typedParameters = Boolean.parseBoolean(actionContext.getActionProperties().getProperty(PARAMETER_TYPED_PARAMETERS));
    91 			lineBreak = Boolean.parseBoolean(actionContext.getActionProperties().getProperty(PARAMETER_LINE_BREAK, Boolean.TRUE.toString()));
    92 			environmentVariables = Boolean.parseBoolean(actionContext.getActionProperties().getProperty(PARAMETER_ENVIRONMENT_VARIABLES, Boolean.FALSE.toString()));
    93 			nodeSet = Boolean.parseBoolean(actionContext.getActionProperties().getProperty(PARAMETER_NODE_SET, Boolean.FALSE.toString()));
    94 
    95 			Map<String, Object> xpathParameters = new HashMap<>();
    96 
    97 			for (int i = 1; i < actionData.size(); i++) {
    98 				String parameterName = actionData.get(i++);
    99 				String parameterType = typedParameters ? actionData.get(i++) : PARAMETER_TYPE.STRING.name();
   100 				Object parameterValue = parseParameterValue(parameterType, actionData.get(i));
   101 				xpathParameters.put(parameterName, parameterValue);
   102 			}
   103 
   104 			XPathVariableResolver cliVariableResolver = new PropertiesVariableResolver(xpathParameters);
   105 			XPathVariableResolver variableResolver = environmentVariables ? new CompoundVariableResolver(cliVariableResolver, new EnvironmentVariableResolver()) : cliVariableResolver;
   106 
   107 			xpath.setXPathVariableResolver(variableResolver);
   108 
   109 			expressionString = actionData.get(0);
   110 		}
   111 	}
   112 
   113 	private enum PARAMETER_TYPE {
   114 
   115 		// TODO: wait for Java 8 widespread and rewrite with lambdas :-)
   116 		STRING {
   117 					@Override
   118 					public Object parse(String value) {
   119 						return value;
   120 					}
   121 				},
   122 		BOOLEAN {
   123 					@Override
   124 					public Object parse(String value) {
   125 						return Boolean.valueOf(value);
   126 					}
   127 				},
   128 		INTEGER {
   129 					@Override
   130 					public Object parse(String value) {
   131 						return Integer.parseInt(value);
   132 					}
   133 				},
   134 		LONG {
   135 					@Override
   136 					public Object parse(String value) {
   137 						return Long.parseLong(value);
   138 					}
   139 				},
   140 		DOUBLE {
   141 					@Override
   142 					public Object parse(String value) {
   143 						return Double.parseDouble(value);
   144 					}
   145 				};
   146 
   147 		public abstract Object parse(String value);
   148 
   149 	}
   150 
   151 	private static Object parseParameterValue(String type, String value) throws OutputActionException {
   152 
   153 		try {
   154 			PARAMETER_TYPE parameterType = PARAMETER_TYPE.valueOf(type.toUpperCase());
   155 
   156 			try {
   157 				return parameterType.parse(value);
   158 			} catch (IllegalArgumentException e) {
   159 				throw new OutputActionException("Unable to parse value: „" + value + "“ as " + parameterType.name());
   160 			}
   161 		} catch (IllegalArgumentException e) {
   162 			throw new OutputActionException(
   163 					"Invalid XPath parameter type: „" + type
   164 					+ "“. Possible values are: " + Arrays.toString(PARAMETER_TYPE.values())
   165 					+ " and are case insensitive.",
   166 					e);
   167 		}
   168 	}
   169 
   170 	private String nodeToString(Node node) throws OutputActionException {
   171 		try {
   172 			StringWriter w = new StringWriter();
   173 			copyTransformer.transform(new DOMSource(node), new StreamResult(w));
   174 			return w.toString();
   175 		} catch (TransformerException e) {
   176 			throw new OutputActionException("Unable to convert node to string", e);
   177 		}
   178 	}
   179 
   180 	@Override
   181 	public void run(DOMResult domResult) throws OutputActionException {
   182 		XPathExpression xpathExpression = null;
   183 		try {
   184 			Node document = domResult.getNode();
   185 
   186 			xpath.setNamespaceContext(new DocumentNamespaceContext(document));
   187 
   188 			try {
   189 				xpathExpression = xpath.compile(expressionString);
   190 			} catch (XPathExpressionException e) {
   191 				throw new OutputActionException("Unable to compile XPath: " + expressionString, e);
   192 			}
   193 
   194 			try (PrintWriter out = new PrintWriter(getActionContext().getOutputStream())) {
   195 
   196 				if (nodeSet) {
   197 					NodeList result = (NodeList) xpathExpression.evaluate(document, XPathConstants.NODESET);
   198 					for (int i = 0; i < result.getLength(); i++) {
   199 						Node node = result.item(i);
   200 
   201 						log.log(Level.FINE, "Node type: {0}", node.getClass());
   202 
   203 						final String stringValue;
   204 
   205 						if (node instanceof Attr) {
   206 							Attr attribute = (Attr) node;
   207 							stringValue = attribute.getValue();
   208 						} else if (node instanceof Element) {
   209 							stringValue = nodeToString(node);
   210 							/**
   211 							 * TODO: print/log node separator
   212 							 */
   213 						} else {
   214 							stringValue = String.valueOf(node);
   215 						}
   216 
   217 						out.print(stringValue);
   218 						/**
   219 						 * TODO: support also null-byte-separated values
   220 						 */
   221 						if (lineBreak) {
   222 							out.println();
   223 						}
   224 					}
   225 				} else {
   226 					log.log(Level.FINE, "String value, no node-set");
   227 					out.print(xpathExpression.evaluate(document));
   228 					if (lineBreak) {
   229 						out.println();
   230 					}
   231 				}
   232 
   233 			}
   234 		} catch (XPathExpressionException e) {
   235 			throw new OutputActionException("Unable to evaluate XPath: " + xpathExpression, e);
   236 		}
   237 	}
   238 }