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