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
77 private String key;
78
79
80 private StringBuilder value = new StringBuilder();
81
82
83 private boolean inCommentElement;
84
85
86 private boolean inEntryElement;
87
88 @Override
89 public void characters(final char[] chars, final int start, final int length) {
90
91
92
93
94
95 value.append(chars, start, length);
96 }
97
98 @Override
99 public void endElement(final String uri, final String localName, final String qName) {
100 if (inCommentElement) {
101
102 setHeader(value.toString());
103 inCommentElement = false;
104 }
105
106 if (inEntryElement) {
107
108 addProperty(key, value.toString());
109 inEntryElement = false;
110 }
111
112
113 value = new StringBuilder();
114 }
115
116 @Override
117 public void startElement(final String uri, final String localName, final String qName, final Attributes attrs) {
118 if ("comment".equals(qName)) {
119 inCommentElement = true;
120 }
121
122 if ("entry".equals(qName)) {
123 key = attrs.getValue("key");
124 inEntryElement = true;
125 }
126 }
127 }
128
129
130
131
132 public static final String DEFAULT_ENCODING = StandardCharsets.UTF_8.name();
133
134
135
136
137 private static final String MALFORMED_XML_EXCEPTION = "Malformed XML";
138
139
140 private FileLocator locator;
141
142
143 private String header;
144
145
146
147
148
149
150 public XMLPropertiesConfiguration() {
151 }
152
153
154
155
156
157
158
159
160 public XMLPropertiesConfiguration(final Element element) throws ConfigurationException {
161 load(Objects.requireNonNull(element, "element"));
162 }
163
164
165
166
167
168
169
170 private String escapeValue(final Object value) {
171 final String v = StringEscapeUtils.escapeXml10(String.valueOf(value));
172 return String.valueOf(getListDelimiterHandler().escape(v, ListDelimiterHandler.NOOP_TRANSFORMER));
173 }
174
175
176
177
178
179
180 public String getHeader() {
181 return header;
182 }
183
184
185
186
187
188
189 @Override
190 public void initFileLocator(final FileLocator locator) {
191 this.locator = locator;
192 }
193
194
195
196
197
198
199
200
201
202 public void load(final Element element) throws ConfigurationException {
203 if (!element.getNodeName().equals("properties")) {
204 throw new ConfigurationException(MALFORMED_XML_EXCEPTION);
205 }
206 final NodeList childNodes = element.getChildNodes();
207 for (int i = 0; i < childNodes.getLength(); i++) {
208 final Node item = childNodes.item(i);
209 if (item instanceof Element) {
210 if (item.getNodeName().equals("comment")) {
211 setHeader(item.getTextContent());
212 } else if (item.getNodeName().equals("entry")) {
213 final String key = ((Element) item).getAttribute("key");
214 addProperty(key, item.getTextContent());
215 } else {
216 throw new ConfigurationException(MALFORMED_XML_EXCEPTION);
217 }
218 }
219 }
220 }
221
222 @Override
223 public void read(final Reader in) throws ConfigurationException {
224 final SAXParserFactory factory = SAXParserFactory.newInstance();
225 factory.setNamespaceAware(false);
226 factory.setValidating(true);
227 try {
228 final SAXParser parser = factory.newSAXParser();
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 public void save(final Document document, final Node parent) {
247 final Element properties = document.createElement("properties");
248 parent.appendChild(properties);
249 if (getHeader() != null) {
250 final Element comment = document.createElement("comment");
251 properties.appendChild(comment);
252 comment.setTextContent(StringEscapeUtils.escapeXml10(getHeader()));
253 }
254 forEach((k, v) -> {
255 if (v instanceof List) {
256 writeProperty(document, properties, k, (List<?>) v);
257 } else {
258 writeProperty(document, properties, k, v);
259 }
260 });
261 }
262
263
264
265
266
267
268 public void setHeader(final String header) {
269 this.header = header;
270 }
271
272 @Override
273 public void write(final Writer out) throws ConfigurationException {
274 final PrintWriter writer = new PrintWriter(out);
275 String encoding = locator != null ? locator.getEncoding() : null;
276 if (encoding == null) {
277 encoding = DEFAULT_ENCODING;
278 }
279 writer.println("<?xml version=\"1.0\" encoding=\"" + encoding + "\"?>");
280 writer.println("<!DOCTYPE properties SYSTEM \"http://java.sun.com/dtd/properties.dtd\">");
281 writer.println("<properties>");
282 if (getHeader() != null) {
283 writer.println(" <comment>" + StringEscapeUtils.escapeXml10(getHeader()) + "</comment>");
284 }
285 forEach((k, v) -> {
286 if (v instanceof List) {
287 writeProperty(writer, k, (List<?>) v);
288 } else {
289 writeProperty(writer, k, v);
290 }
291 });
292 writer.println("</properties>");
293 writer.flush();
294 }
295
296 private void writeProperty(final Document document, final Node properties, final String key, final List<?> values) {
297 values.forEach(value -> writeProperty(document, properties, key, value));
298 }
299
300 private void writeProperty(final Document document, final Node properties, final String key, final Object value) {
301 final Element entry = document.createElement("entry");
302 properties.appendChild(entry);
303
304
305 final String k = StringEscapeUtils.escapeXml10(key);
306 entry.setAttribute("key", k);
307
308 if (value != null) {
309 final String v = escapeValue(value);
310 entry.setTextContent(v);
311 }
312 }
313
314
315
316
317
318
319
320
321 private void writeProperty(final PrintWriter out, final String key, final List<?> values) {
322 values.forEach(value -> writeProperty(out, key, value));
323 }
324
325
326
327
328
329
330
331
332 private void writeProperty(final PrintWriter out, final String key, final Object value) {
333
334 final String k = StringEscapeUtils.escapeXml10(key);
335
336 if (value != null) {
337 final String v = escapeValue(value);
338 out.println(" <entry key=\"" + k + "\">" + v + "</entry>");
339 } else {
340 out.println(" <entry key=\"" + k + "\"/>");
341 }
342 }
343 }