1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package org.opencastproject.oaipmh.util;
22
23 import static org.opencastproject.util.IoSupport.withResource;
24 import static org.opencastproject.util.data.functions.Misc.chuck;
25
26 import org.opencastproject.metadata.dublincore.DublinCore;
27 import org.opencastproject.util.XmlSafeParser;
28
29
30 import org.apache.commons.io.output.ByteArrayOutputStream;
31 import org.apache.commons.lang3.ObjectUtils;
32 import org.apache.commons.lang3.StringUtils;
33 import org.w3c.dom.Attr;
34 import org.w3c.dom.Document;
35 import org.w3c.dom.Element;
36 import org.w3c.dom.Node;
37 import org.w3c.dom.NodeList;
38
39 import java.io.OutputStream;
40 import java.util.ArrayList;
41 import java.util.Arrays;
42 import java.util.Collections;
43 import java.util.List;
44 import java.util.Optional;
45
46 import javax.xml.XMLConstants;
47 import javax.xml.parsers.DocumentBuilder;
48 import javax.xml.parsers.DocumentBuilderFactory;
49 import javax.xml.parsers.ParserConfigurationException;
50 import javax.xml.transform.OutputKeys;
51 import javax.xml.transform.Transformer;
52 import javax.xml.transform.TransformerException;
53 import javax.xml.transform.dom.DOMSource;
54 import javax.xml.transform.stream.StreamResult;
55
56
57
58
59
60
61
62 public abstract class XmlGen {
63 private final Document document;
64 private final Optional<String> defaultNamespace;
65
66
67
68
69 public XmlGen(Optional<String> defaultNamespace) {
70 document = createDocument();
71 this.defaultNamespace = defaultNamespace;
72 }
73
74 private Document createDocument() {
75 try {
76 DocumentBuilderFactory factory = XmlSafeParser.newDocumentBuilderFactory();
77 factory.setNamespaceAware(true);
78 DocumentBuilder builder = factory.newDocumentBuilder();
79 return builder.newDocument();
80 } catch (ParserConfigurationException e) {
81 return chuck(e);
82 }
83 }
84
85 private void write(OutputStream out) {
86 try {
87 Transformer transformer = XmlSafeParser.newTransformerFactory().newTransformer();
88 transformer.setOutputProperty(OutputKeys.METHOD, "xml");
89 transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
90 transformer.setOutputProperty(OutputKeys.INDENT, "yes");
91 transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
92 DOMSource source = new DOMSource(document);
93 StreamResult result = new StreamResult(out);
94 transformer.transform(source, result);
95 } catch (TransformerException e) {
96 throw new RuntimeException(e);
97 }
98 }
99
100
101
102
103 public void generate(OutputStream out) {
104 generate();
105 write(out);
106 }
107
108
109
110
111 public Document generate() {
112 final Node node = document.importNode(create(), true);
113 final Element docElem = document.getDocumentElement();
114 if (docElem != null) {
115 document.removeChild(docElem);
116 }
117 document.appendChild(node);
118 return document;
119 }
120
121
122 public String generateAsString() {
123 return withResource(new ByteArrayOutputStream(), out -> {
124 generate(out);
125 return out.toString();
126 });
127 }
128
129
130
131
132 public abstract Element create();
133
134
135
136 protected Namespace ns(String prefix, String namespace) {
137 return new Namespace(prefix, namespace);
138 }
139
140 protected Node schemaLocation(String location) {
141 return $a("xsi:schemaLocation", location);
142 }
143
144
145
146 protected Node $langNode(String language) {
147 if (StringUtils.isBlank(language) || DublinCore.LANGUAGE_UNDEFINED.equals(language)
148 || DublinCore.LANGUAGE_ANY.equals(language))
149 return nodeZero();
150
151 Attr a = document.createAttributeNS(XMLConstants.XML_NS_URI, "xml:lang");
152 a.setValue(language);
153 return a;
154 }
155
156 protected Node $a(String name, String value) {
157 Attr a = document.createAttribute(name);
158 a.setValue(value);
159 return a;
160 }
161
162 protected Node $aBlank(String name, String value) {
163 if (StringUtils.isNotBlank(value)) {
164 Attr a = document.createAttribute(name);
165 a.setValue(value);
166 return a;
167 } else {
168 return nodeZero();
169 }
170 }
171
172 protected Node $aSome(final String name, final Optional<String> value) {
173 return value
174 .map(v -> {
175 Attr a = document.createAttribute(name);
176 a.setValue(v);
177 return (Node) a;
178 })
179 .orElseGet(this::nodeZero);
180 }
181
182 protected Element $e(String qname, Optional<String> namespace, List<Node> nodes) {
183 return appendTo(createElemNs(namespace, qname), nodes);
184 }
185
186
187
188
189
190 protected Element $e(String qname, Optional<String> namespace, NodeList nodes) {
191 return appendTo(createElemNs(namespace, qname), nodes);
192 }
193
194 protected Element $e(String qname, Optional<String> namespace, Node... nodes) {
195 return $e(qname, namespace, Arrays.asList(nodes));
196 }
197
198 protected Element $e(String name, Node... nodes) {
199 return $e(name, defaultNamespace, Arrays.asList(nodes));
200 }
201
202 protected Element $e(String name, List<Node> nodes) {
203 return $e(name, defaultNamespace, Collections.unmodifiableList(nodes));
204 }
205
206
207
208
209
210 protected Element $e(String qname, String namespace, Node... nodes) {
211 return $e(qname, Optional.of(namespace), Arrays.asList(nodes));
212 }
213
214 protected Element $e(String qname, String namespace, List<Node> nodes) {
215 return $e(qname, Optional.of(namespace), nodes);
216 }
217
218 protected Node $eTxtBlank(final String name, String text) {
219 Optional<Node> txtNodeOpt = $txtBlank(text);
220 if (txtNodeOpt.isPresent()) {
221 Node txtNode = txtNodeOpt.get();
222 final Element e = createElemDefaultNs(name);
223 e.appendChild(txtNode);
224 return e;
225 } else {
226 return nodeZero();
227 }
228 }
229
230 protected Node $eTxt(final String name, String text) {
231 final Element e = createElemDefaultNs(name);
232 e.appendChild($txt(text));
233 return e;
234 }
235
236 protected Node $eTxt(final String qname, final String namespace, String text) {
237 final Element e = createElemNs(namespace, qname);
238 e.appendChild($txt(text));
239 return e;
240 }
241
242 protected Element $e(String name, List<Namespace> namespaces, Node... nodes) {
243 return appendTo(appendNs(createElemDefaultNs(name), namespaces), Arrays.asList(nodes));
244 }
245
246 protected Element $e(String name, List<Namespace> namespaces, NodeList nodes) {
247 return appendTo(appendNs(createElemDefaultNs(name), namespaces), nodes);
248 }
249
250 protected Element $e(String name, List<Namespace> namespaces, List<Node> nodes) {
251 return appendTo(appendNs(createElemDefaultNs(name), namespaces), nodes);
252 }
253
254 protected Element $e(String qname, String namespace, List<Namespace> namespaces, Node... nodes) {
255 return appendTo(appendNs(createElemNs(namespace, qname), namespaces), Arrays.asList(nodes));
256 }
257
258 private Element createElemDefaultNs(String name) {
259 return createElemNs(defaultNamespace, name);
260 }
261
262 private Element createElemNs(Optional<String> namespace, String qname) {
263 return createElemNs(namespace.orElse(null), qname);
264 }
265
266
267
268
269
270 private Element createElemNs(String namespace, String qname) {
271 return document.createElementNS(namespace, qname);
272 }
273
274
275
276
277
278
279
280
281
282
283
284
285
286 protected Element $e(String qname, String namespace, List<Namespace> namespaces, List<Node> nodes) {
287 return appendTo(appendNs(createElemNs(namespace, qname), namespaces), nodes);
288 }
289
290
291
292
293
294 protected Node $e(String name, Optional<Node>... nodes) {
295 final List<Node> existing = filter(Arrays.asList(nodes));
296 if (!existing.isEmpty()) {
297 return $e(name, existing);
298 } else {
299 return nodeZero();
300 }
301 }
302
303 protected Node $txt(String text) {
304 return document.createTextNode(text);
305 }
306
307 protected Node $cdata(String text) {
308 return document.createCDATASection(text);
309 }
310
311
312
313
314 protected Optional<Node> $txtBlank(String text) {
315 return StringUtils.isNotBlank(text) ? Optional.of($txt(text)) : Optional.<Node>empty();
316 }
317
318
319
320
321
322 private List<Node> filter(List<Optional<Node>> nodes) {
323 List<Node> result = new ArrayList<>();
324 for (Optional<Node> opt : nodes) {
325 opt.ifPresent(result::add);
326 }
327 return result;
328 }
329
330
331 private Element appendNs(Element e, List<Namespace> namespaces) {
332 for (Namespace n : namespaces) {
333 e.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, XMLConstants.XMLNS_ATTRIBUTE + ":" + n.getPrefix(),
334 n.getNamespace());
335 }
336 return e;
337 }
338
339
340
341
342 private Element appendTo(Element e, List<Node> nodes) {
343 for (Node node : nodes)
344 appendTo(e, node);
345 return e;
346 }
347
348
349
350
351 private Element appendTo(Element e, NodeList nodes) {
352 for (int i = 0; i < nodes.getLength(); i++)
353 appendTo(e, nodes.item(i));
354 return e;
355 }
356
357
358
359
360 private void appendTo(Element e, Node n) {
361 Node toAppend = ObjectUtils.equals(n.getOwnerDocument(), document) ? n : document.importNode(n, true);
362 if (toAppend instanceof Attr) {
363 e.setAttributeNode((Attr) toAppend);
364 } else {
365 e.appendChild(toAppend);
366 }
367 }
368
369
370
371
372 protected Node nodeZero() {
373 return document.createTextNode("");
374 }
375
376 protected class Namespace {
377 private final String prefix;
378 private final String namespace;
379
380 Namespace(String prefix, String namespace) {
381 this.prefix = prefix;
382 this.namespace = namespace;
383 }
384
385 public String getPrefix() {
386 return prefix;
387 }
388
389 public String getNamespace() {
390 return namespace;
391 }
392 }
393 }