java/alt2xml-out-xpath/src/cz/frantovo/alt2xml/out/xpath/XPathAction.java
author František Kučera <franta-hg@frantovo.cz>
Sun, 02 Oct 2016 15:22:06 +0200
changeset 108 bbb9b31255be
parent 105 e62a3e498212
child 111 e4900596abdb
permissions -rw-r--r--
out-xpath: TODO comment
     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, either version 3 of the License, or
     8  * (at your option) any later version.
     9  *
    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.
    14  *
    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/>.
    17  */
    18 package cz.frantovo.alt2xml.out.xpath;
    19 
    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;
    28 import java.util.Map;
    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;
    50 
    51 /**
    52  *
    53  * @author Ing. František Kučera (frantovo.cz)
    54  */
    55 public class XPathAction extends AbstractDOMAction {
    56 
    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;
    71 
    72 	public XPathAction(ActionContext actionContext) throws OutputActionException {
    73 		super(actionContext);
    74 		xpathFactory = XPathFactory.newInstance();
    75 		xpath = xpathFactory.newXPath();
    76 
    77 		try {
    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);
    83 		}
    84 
    85 		final List<String> actionData = getActionContext().getActionData();
    86 
    87 		if (actionData.size() < 1) {
    88 			throw new OutputActionException("Please specify the XPath expression as action data");
    89 		} else {
    90 
    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()));
    95 
    96 			Map<String, Object> xpathParameters = new HashMap<>();
    97 
    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);
   103 			}
   104 
   105 			XPathVariableResolver cliVariableResolver = new PropertiesVariableResolver(xpathParameters);
   106 			XPathVariableResolver variableResolver = environmentVariables ? new CompoundVariableResolver(cliVariableResolver, new EnvironmentVariableResolver()) : cliVariableResolver;
   107 
   108 			xpath.setXPathVariableResolver(variableResolver);
   109 
   110 			expressionString = actionData.get(0);
   111 		}
   112 	}
   113 
   114 	private enum PARAMETER_TYPE {
   115 
   116 		// TODO: wait for Java 8 widespread and rewrite with lambdas :-)
   117 		STRING {
   118 					@Override
   119 					public Object parse(String value) {
   120 						return value;
   121 					}
   122 				},
   123 		BOOLEAN {
   124 					@Override
   125 					public Object parse(String value) {
   126 						return Boolean.valueOf(value);
   127 					}
   128 				},
   129 		INTEGER {
   130 					@Override
   131 					public Object parse(String value) {
   132 						return Integer.parseInt(value);
   133 					}
   134 				},
   135 		LONG {
   136 					@Override
   137 					public Object parse(String value) {
   138 						return Long.parseLong(value);
   139 					}
   140 				},
   141 		DOUBLE {
   142 					@Override
   143 					public Object parse(String value) {
   144 						return Double.parseDouble(value);
   145 					}
   146 				};
   147 
   148 		public abstract Object parse(String value);
   149 
   150 	}
   151 
   152 	private static Object parseParameterValue(String type, String value) throws OutputActionException {
   153 
   154 		try {
   155 			PARAMETER_TYPE parameterType = PARAMETER_TYPE.valueOf(type.toUpperCase());
   156 
   157 			try {
   158 				return parameterType.parse(value);
   159 			} catch (IllegalArgumentException e) {
   160 				throw new OutputActionException("Unable to parse value: „" + value + "“ as " + parameterType.name());
   161 			}
   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.",
   167 					e);
   168 		}
   169 	}
   170 
   171 	private String nodeToString(Node node) throws OutputActionException {
   172 		try {
   173 			StringWriter w = new StringWriter();
   174 			copyTransformer.transform(new DOMSource(node), new StreamResult(w));
   175 			return w.toString();
   176 		} catch (TransformerException e) {
   177 			throw new OutputActionException("Unable to convert node to string", e);
   178 		}
   179 	}
   180 
   181 	@Override
   182 	public void run(DOMResult domResult) throws OutputActionException {
   183 		XPathExpression xpathExpression = null;
   184 		try {
   185 			Node document = domResult.getNode();
   186 
   187 			xpath.setNamespaceContext(new DocumentNamespaceContext(document));
   188 
   189 			try {
   190 				xpathExpression = xpath.compile(expressionString);
   191 			} catch (XPathExpressionException e) {
   192 				throw new OutputActionException("Unable to compile XPath: " + expressionString, e);
   193 			}
   194 
   195 			try (PrintWriter out = new PrintWriter(getActionContext().getOutputStream())) {
   196 
   197 				if (nodeSet) {
   198 					NodeList result = (NodeList) xpathExpression.evaluate(document, XPathConstants.NODESET);
   199 					for (int i = 0; i < result.getLength(); i++) {
   200 						Node node = result.item(i);
   201 
   202 						log.log(Level.FINE, "Node type: {0}", node.getClass());
   203 
   204 						final String stringValue;
   205 
   206 						if (node instanceof Attr) {
   207 							Attr attribute = (Attr) node;
   208 							stringValue = attribute.getValue();
   209 						} else if (node instanceof Element) {
   210 							stringValue = nodeToString(node);
   211 							/**
   212 							 * TODO: print/log node separator
   213 							 */
   214 						} else {
   215 							stringValue = String.valueOf(node);
   216 						}
   217 
   218 						out.print(stringValue);
   219 						/**
   220 						 * TODO: support also null-byte-separated values
   221 						 */
   222 						if (lineBreak) {
   223 							out.println();
   224 						}
   225 					}
   226 				} else {
   227 					log.log(Level.FINE, "String value, no node-set");
   228 					out.print(xpathExpression.evaluate(document));
   229 					if (lineBreak) {
   230 						out.println();
   231 					}
   232 				}
   233 
   234 			}
   235 		} catch (XPathExpressionException e) {
   236 			throw new OutputActionException("Unable to evaluate XPath: " + xpathExpression, e);
   237 		}
   238 	}
   239 }