1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache license, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the license for the specific language governing permissions and
15 * limitations under the license.
16 */
17
18 package org.apache.commons.text.lookup;
19
20 import java.io.InputStream;
21 import java.nio.file.Files;
22 import java.nio.file.Path;
23 import java.util.HashMap;
24 import java.util.Map;
25 import java.util.Map.Entry;
26 import java.util.Objects;
27
28 import javax.xml.XMLConstants;
29 import javax.xml.parsers.DocumentBuilderFactory;
30 import javax.xml.xpath.XPathFactory;
31
32 import org.apache.commons.lang3.StringUtils;
33 import org.w3c.dom.Document;
34
35 /**
36 * Looks up values in an XML document in the format {@code "DocumentPath:XPath"}.
37 * <p>
38 * For example:
39 * </p>
40 * <ul>
41 * <li>{@code "com/domain/document.xml:/path/to/node"}</li>
42 * </ul>
43 * <p>
44 * Secure processing is enabled by default and can be overridden with {@link StringLookupFactory#xmlStringLookup(Map, Path...)}.
45 * </p>
46 *
47 * @since 1.5
48 */
49 final class XmlStringLookup extends AbstractPathFencedLookup {
50
51 /**
52 * The number of key parts.
53 */
54 private static final int KEY_PARTS_LEN = 2;
55
56 /**
57 * Defines default XPath factory features.
58 */
59 static final Map<String, Boolean> DEFAULT_XPATH_FEATURES;
60
61 /**
62 * Defines default XML factory features.
63 */
64 static final Map<String, Boolean> DEFAULT_XML_FEATURES;
65 static {
66 DEFAULT_XPATH_FEATURES = new HashMap<>(1);
67 DEFAULT_XPATH_FEATURES.put(XMLConstants.FEATURE_SECURE_PROCESSING, Boolean.TRUE);
68 DEFAULT_XML_FEATURES = new HashMap<>(1);
69 DEFAULT_XML_FEATURES.put(XMLConstants.FEATURE_SECURE_PROCESSING, Boolean.TRUE);
70 }
71
72 /**
73 * Defines the singleton for this class with secure processing enabled by default.
74 * <p>
75 * Secure processing is enabled by default and can be overridden with {@link StringLookupFactory#xmlStringLookup(Map, Path...)}.
76 * </p>
77 */
78 static final XmlStringLookup INSTANCE = new XmlStringLookup(DEFAULT_XML_FEATURES, DEFAULT_XPATH_FEATURES, (Path[]) null);
79
80 /**
81 * Defines XPath factory features.
82 */
83 private final Map<String, Boolean> xPathFactoryFeatures;
84
85 /**
86 * Defines XML factory features.
87 */
88 private final Map<String, Boolean> xmlFactoryFeatures;
89
90 /**
91 * Constructs a new instance.
92 *
93 * @param xmlFactoryFeatures The {@link DocumentBuilderFactory} features to set.
94 * @param xPathFactoryFeatures The {@link XPathFactory} features to set.
95 * @see DocumentBuilderFactory#setFeature(String, boolean)
96 * @see XPathFactory#setFeature(String, boolean)
97 */
98 XmlStringLookup(final Map<String, Boolean> xmlFactoryFeatures, final Map<String, Boolean> xPathFactoryFeatures, final Path... fences) {
99 super(fences);
100 this.xmlFactoryFeatures = Objects.requireNonNull(xmlFactoryFeatures, "xmlFactoryFeatures");
101 this.xPathFactoryFeatures = Objects.requireNonNull(xPathFactoryFeatures, "xPathFfactoryFeatures");
102 }
103
104 /**
105 * Looks up a value for the key in the format {@code "DocumentPath:XPath"}.
106 * <p>
107 * For example:
108 * </p>
109 * <ul>
110 * <li>{@code "com/domain/document.xml:/path/to/node"}</li>
111 * </ul>
112 * <p>
113 * Secure processing is enabled by default and can be overridden with {@link StringLookupFactory#xmlStringLookup(Map, Path...)}.
114 * </p>
115 *
116 * @param key the key to be looked up, may be null.
117 * @return The value associated with the key.
118 */
119 @Override
120 public String lookup(final String key) {
121 if (key == null) {
122 return null;
123 }
124 final String[] keys = key.split(SPLIT_STR);
125 final int keyLen = keys.length;
126 if (keyLen != KEY_PARTS_LEN) {
127 throw IllegalArgumentExceptions.format("Bad XML key format '%s'; the expected format is 'DocumentPath:XPath'.", key);
128 }
129 final String documentPath = keys[0];
130 final String xpath = StringUtils.substringAfterLast(key, SPLIT_CH);
131 final DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
132 try {
133 for (final Entry<String, Boolean> p : xmlFactoryFeatures.entrySet()) {
134 dbFactory.setFeature(p.getKey(), p.getValue());
135 }
136 try (InputStream inputStream = Files.newInputStream(getPath(documentPath))) {
137 final Document doc = dbFactory.newDocumentBuilder().parse(inputStream);
138 final XPathFactory xpFactory = XPathFactory.newInstance();
139 for (final Entry<String, Boolean> p : xPathFactoryFeatures.entrySet()) {
140 xpFactory.setFeature(p.getKey(), p.getValue());
141 }
142 return xpFactory.newXPath().evaluate(xpath, doc);
143 }
144 } catch (final Exception e) {
145 throw new IllegalArgumentException(e);
146 }
147 }
148 }