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