1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.configuration2;
19
20 import java.io.PrintWriter;
21 import java.io.Reader;
22 import java.io.Writer;
23 import java.nio.charset.StandardCharsets;
24 import java.util.List;
25 import java.util.Objects;
26
27 import javax.xml.parsers.SAXParser;
28 import javax.xml.parsers.SAXParserFactory;
29
30 import org.apache.commons.configuration2.convert.ListDelimiterHandler;
31 import org.apache.commons.configuration2.ex.ConfigurationException;
32 import org.apache.commons.configuration2.io.FileLocator;
33 import org.apache.commons.configuration2.io.FileLocatorAware;
34 import org.apache.commons.text.StringEscapeUtils;
35 import org.w3c.dom.Document;
36 import org.w3c.dom.Element;
37 import org.w3c.dom.Node;
38 import org.w3c.dom.NodeList;
39 import org.xml.sax.Attributes;
40 import org.xml.sax.InputSource;
41 import org.xml.sax.XMLReader;
42 import org.xml.sax.helpers.DefaultHandler;
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67 public class XMLPropertiesConfiguration extends BaseConfiguration implements FileBasedConfiguration, FileLocatorAware {
68
69
70
71
72
73
74 private final class XMLPropertiesHandler extends DefaultHandler {
75
76 private String key;
77
78
79 private StringBuilder value = new StringBuilder();
80
81
82 private boolean inCommentElement;
83
84
85 private boolean inEntryElement;
86
87 @Override
88 public void characters(final char[] chars, final int start, final int length) {
89
90
91
92
93 value.append(chars, start, length);
94 }
95
96 @Override
97 public void endElement(final String uri, final String localName, final String qName) {
98 if (inCommentElement) {
99
100 setHeader(value.toString());
101 inCommentElement = false;
102 }
103
104 if (inEntryElement) {
105
106 addProperty(key, value.toString());
107 inEntryElement = false;
108 }
109
110
111 value = new StringBuilder();
112 }
113
114 @Override
115 public void startElement(final String uri, final String localName, final String qName, final Attributes attrs) {
116 if ("comment".equals(qName)) {
117 inCommentElement = true;
118 }
119
120 if ("entry".equals(qName)) {
121 key = attrs.getValue("key");
122 inEntryElement = true;
123 }
124 }
125 }
126
127
128
129
130 public static final String DEFAULT_ENCODING = StandardCharsets.UTF_8.name();
131
132
133
134
135 private static final String MALFORMED_XML_EXCEPTION = "Malformed XML";
136
137
138 private FileLocator locator;
139
140
141 private String header;
142
143
144
145
146
147
148 public XMLPropertiesConfiguration() {
149 }
150
151
152
153
154
155
156
157
158 public XMLPropertiesConfiguration(final Element element) throws ConfigurationException {
159 load(Objects.requireNonNull(element, "element"));
160 }
161
162
163
164
165
166
167
168 private String escapeValue(final Object value) {
169 final String v = StringEscapeUtils.escapeXml10(String.valueOf(value));
170 return String.valueOf(getListDelimiterHandler().escape(v, ListDelimiterHandler.NOOP_TRANSFORMER));
171 }
172
173
174
175
176
177
178 public String getHeader() {
179 return header;
180 }
181
182
183
184
185
186
187 @Override
188 public void initFileLocator(final FileLocator locator) {
189 this.locator = locator;
190 }
191
192
193
194
195
196
197
198
199
200 public void load(final Element element) throws ConfigurationException {
201 if (!element.getNodeName().equals("properties")) {
202 throw new ConfigurationException(MALFORMED_XML_EXCEPTION);
203 }
204 final NodeList childNodes = element.getChildNodes();
205 for (int i = 0; i < childNodes.getLength(); i++) {
206 final Node item = childNodes.item(i);
207 if (item instanceof Element) {
208 if (item.getNodeName().equals("comment")) {
209 setHeader(item.getTextContent());
210 } else if (item.getNodeName().equals("entry")) {
211 final String key = ((Element) item).getAttribute("key");
212 addProperty(key, item.getTextContent());
213 } else {
214 throw new ConfigurationException(MALFORMED_XML_EXCEPTION);
215 }
216 }
217 }
218 }
219
220 @Override
221 public void read(final Reader in) throws ConfigurationException {
222 final SAXParserFactory factory = SAXParserFactory.newInstance();
223 factory.setNamespaceAware(false);
224 factory.setValidating(true);
225
226 try {
227 final SAXParser parser = factory.newSAXParser();
228
229 final XMLReader xmlReader = parser.getXMLReader();
230 xmlReader.setEntityResolver((publicId, systemId) -> new InputSource(getClass().getClassLoader().getResourceAsStream("properties.dtd")));
231 xmlReader.setContentHandler(new XMLPropertiesHandler());
232 xmlReader.parse(new InputSource(in));
233 } catch (final Exception e) {
234 throw new ConfigurationException("Unable to parse the configuration file", e);
235 }
236
237
238 }
239
240
241
242
243
244
245
246
247 public void save(final Document document, final Node parent) {
248 final Element properties = document.createElement("properties");
249 parent.appendChild(properties);
250 if (getHeader() != null) {
251 final Element comment = document.createElement("comment");
252 properties.appendChild(comment);
253 comment.setTextContent(StringEscapeUtils.escapeXml10(getHeader()));
254 }
255 forEach((k, v) -> {
256 if (v instanceof List) {
257 writeProperty(document, properties, k, (List<?>) v);
258 } else {
259 writeProperty(document, properties, k, v);
260 }
261 });
262 }
263
264
265
266
267
268
269 public void setHeader(final String header) {
270 this.header = header;
271 }
272
273 @Override
274 public void write(final Writer out) throws ConfigurationException {
275 final PrintWriter writer = new PrintWriter(out);
276 String encoding = locator != null ? locator.getEncoding() : null;
277 if (encoding == null) {
278 encoding = DEFAULT_ENCODING;
279 }
280 writer.println("<?xml version=\"1.0\" encoding=\"" + encoding + "\"?>");
281 writer.println("<!DOCTYPE properties SYSTEM \"http://java.sun.com/dtd/properties.dtd\">");
282 writer.println("<properties>");
283 if (getHeader() != null) {
284 writer.println(" <comment>" + StringEscapeUtils.escapeXml10(getHeader()) + "</comment>");
285 }
286 forEach((k, v) -> {
287 if (v instanceof List) {
288 writeProperty(writer, k, (List<?>) v);
289 } else {
290 writeProperty(writer, k, v);
291 }
292 });
293 writer.println("</properties>");
294 writer.flush();
295 }
296
297 private void writeProperty(final Document document, final Node properties, final String key, final List<?> values) {
298 values.forEach(value -> writeProperty(document, properties, key, value));
299 }
300
301 private void writeProperty(final Document document, final Node properties, final String key, final Object value) {
302 final Element entry = document.createElement("entry");
303 properties.appendChild(entry);
304
305
306 final String k = StringEscapeUtils.escapeXml10(key);
307 entry.setAttribute("key", k);
308
309 if (value != null) {
310 final String v = escapeValue(value);
311 entry.setTextContent(v);
312 }
313 }
314
315
316
317
318
319
320
321
322 private void writeProperty(final PrintWriter out, final String key, final List<?> values) {
323 values.forEach(value -> writeProperty(out, key, value));
324 }
325
326
327
328
329
330
331
332
333 private void writeProperty(final PrintWriter out, final String key, final Object value) {
334
335 final String k = StringEscapeUtils.escapeXml10(key);
336
337 if (value != null) {
338 final String v = escapeValue(value);
339 out.println(" <entry key=\"" + k + "\">" + v + "</entry>");
340 } else {
341 out.println(" <entry key=\"" + k + "\"/>");
342 }
343 }
344 }