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