1 package nl.dedicon.pipeline.braille.step;
2
3 import java.io.File;
4 import java.io.IOException;
5 import java.io.StringReader;
6 import java.nio.file.Files;
7 import javax.xml.parsers.DocumentBuilderFactory;
8 import javax.xml.parsers.ParserConfigurationException;
9 import javax.xml.transform.stream.StreamSource;
10 import net.sf.saxon.s9api.Axis;
11 import net.sf.saxon.s9api.DocumentBuilder;
12 import net.sf.saxon.s9api.QName;
13 import net.sf.saxon.s9api.SaxonApiException;
14 import net.sf.saxon.s9api.XdmNode;
15 import net.sf.saxon.s9api.XdmSequenceIterator;
16 import org.w3c.dom.Document;
17 import org.w3c.dom.Element;
18 import org.w3c.dom.Node;
19 import org.xml.sax.InputSource;
20 import org.xml.sax.SAXException;
21
22 /**
23 * Utilities for Java XProc steps
24 *
25 * @author Paul Rambags
26 */
27 public class Utils {
28
29 /**
30 * Convert an XdmNode to a Document node
31 *
32 * @param node XdmNode
33 * @return Document node
34 * @throws ParserConfigurationException thrown on parse exceptions
35 * @throws SAXException thrown on a SAX exception
36 * @throws IOException thrown on an IO exception
37 */
38 public static Document convertToDocument(XdmNode node) throws ParserConfigurationException, SAXException, IOException {
39 return convertToDocument(node.toString());
40 }
41
42 /**
43 * Convert an XML string to a Document node
44 *
45 * @param xml XML string
46 * @return Document node
47 * @throws ParserConfigurationException thrown on parse exceptions
48 * @throws SAXException thrown on a SAX exception
49 * @throws IOException thrown on an IO exception
50 */
51 public static Document convertToDocument(String xml) throws ParserConfigurationException, SAXException, IOException {
52 DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
53 builderFactory.setNamespaceAware(true);
54 javax.xml.parsers.DocumentBuilder documentBuilder = builderFactory.newDocumentBuilder();
55 Document document = documentBuilder.parse(new InputSource(new StringReader(xml)));
56 return document;
57 }
58
59 /**
60 * Convert a Document node to an XdmNode
61 *
62 * @param document Document node
63 * @param documentBuilder document builder
64 * @param useFile if true, the resulting XdmNode will be saved in a temporary file so that it has an URI
65 * @return XdmNode
66 * @throws IOException thrown on an IO exception
67 * @throws SaxonApiException thrown on a Saxon API exception
68 */
69 public static XdmNode convertToXdmNode(Document document, DocumentBuilder documentBuilder, boolean useFile) throws IOException, SaxonApiException {
70 String xml = toString(document, documentBuilder);
71 XdmNode node;
72 if (useFile) {
73 String className = new Object(){}.getClass().getName();
74 File tempFile = File.createTempFile(className, ".tmp");
75 tempFile.deleteOnExit();
76 Files.write(tempFile.toPath(), xml.getBytes());
77 node = documentBuilder.build(tempFile);
78 } else {
79 node = documentBuilder.build(new StreamSource(new StringReader(xml)));
80 }
81 return node;
82 }
83
84 /**
85 * Convert a Document node to a string
86 *
87 * @param document Document node
88 * @param documentBuilder document builder
89 * @return XML string
90 */
91 public static String toString(Document document, DocumentBuilder documentBuilder) {
92 return documentBuilder.wrap(document).toString();
93 }
94
95 /**
96 * Get the first child in the same namespace with a given name
97 *
98 * @param node node
99 * @param name child node name
100 * @return the first child in the same namespace with the given name, or null if it doesn't exist
101 */
102 public static Node getChild(Node node, String name) {
103 if (node == null) {
104 return null;
105 }
106 String namespace = node.getNamespaceURI();
107 if (namespace == null) {
108 return null;
109 }
110 Node child = node.getFirstChild();
111 while (child != null) {
112 if (namespace.equals(child.getNamespaceURI()) && name.equals(child.getNodeName())) {
113 return child;
114 }
115 child = child.getNextSibling();
116 }
117 return null;
118 }
119
120 /**
121 * Get the first child in the same namespace with a given name and a given attribute value
122 *
123 * @param node node
124 * @param name child node name
125 * @param attributeName attribute name
126 * @param attributeValue attribute value
127 * @return the first child in the same namespace with the given name and with the given attribute value, or null if it doesn't exist
128 */
129 public static Node getFirstChildWithAttribute(Node node, String name, String attributeName, String attributeValue) {
130 if (node == null) {
131 return null;
132 }
133 String namespace = node.getNamespaceURI();
134 if (namespace == null) {
135 return null;
136 }
137 Node child = node.getFirstChild();
138 while (child != null) {
139 if (namespace.equals(child.getNamespaceURI()) &&
140 name.equals(child.getNodeName()) &&
141 child.getNodeType() == Node.ELEMENT_NODE &&
142 attributeValue.equals(((Element)child).getAttribute(attributeName))) {
143 return child;
144 }
145 child = child.getNextSibling();
146 }
147 return null;
148 }
149
150 /**
151 * Get the first sibling in the same namespace with a given name and a given attribute value
152 *
153 * @param node node
154 * @param name sibling node name
155 * @param attributeName attribute name
156 * @param attributeValue attribute value
157 * @return the first sibling in the same namespace with the given name and with the given attribute value, or null if it doesn't exist
158 */
159 public static Node getNextSiblingWithAttribute(Node node, String name, String attributeName, String attributeValue) {
160 if (node == null) {
161 return null;
162 }
163 String namespace = node.getNamespaceURI();
164 if (namespace == null) {
165 return null;
166 }
167 Node child = node.getNextSibling();
168 while (child != null) {
169 if (namespace.equals(child.getNamespaceURI()) &&
170 name.equals(child.getNodeName()) &&
171 child.getNodeType() == Node.ELEMENT_NODE &&
172 attributeValue.equals(((Element)child).getAttribute(attributeName))) {
173 return child;
174 }
175 child = child.getNextSibling();
176 }
177 return null;
178 }
179
180 /**
181 * Add a new child node to a parent node
182 * The new child node will inherit the parent's namespace
183 *
184 * @param parent parent node
185 * @param name name of the child node
186 * @return the new child node
187 */
188 public static Element addChild(Node parent, String name) {
189 Element child = parent.getOwnerDocument().createElementNS(parent.getNamespaceURI(), name);
190 parent.appendChild(child);
191 return child;
192 }
193
194 /**
195 * Add a new child node to a parent node before a child node
196 * The new child node will inherit the parent's namespace
197 * If the child node is null, the new child node will be added at the end of the list of children
198 *
199 * @param parent parent node
200 * @param child child node, can be null
201 * @param name name of the child node
202 * @return the new child node
203 */
204 public static Element addChildBefore(Node parent, Node child, String name) {
205 Element newChild = parent.getOwnerDocument().createElementNS(parent.getNamespaceURI(), name);
206 parent.insertBefore(newChild, child);
207 return newChild;
208 }
209
210 /**
211 * Rename a node and maintain the namespace
212 *
213 * @param node the node that will get a new node name
214 * @param newNodeName the new node name
215 * @return the renamed node
216 */
217 public static Node renameNode(Node node, String newNodeName) {
218 Document document = node.getOwnerDocument();
219 String namespace = document.getDocumentElement().getNamespaceURI();
220 return document.renameNode(node, namespace, newNodeName);
221 }
222
223 /**
224 * Get the first child node
225 *
226 * @param node XdmNode
227 * @param childName child name
228 * @return XdmNode the first child node, or null if it doesn't exist
229 */
230 public static XdmNode getChildNode(XdmNode node, QName childName) {
231 XdmSequenceIterator childIterator = node.axisIterator(Axis.CHILD, childName);
232 if (!childIterator.hasNext()) {
233 return null;
234 }
235 return (XdmNode)childIterator.next();
236 }
237
238 /**
239 * Get the string value of a child node
240 *
241 * @param node XdmNode
242 * @param childName child name
243 * @return String value of the child, or null if it doesn't exist
244 */
245 public static String getValue(XdmNode node, QName childName) {
246 XdmNode childNode = getChildNode(node, childName);
247 return childNode != null ? childNode.getStringValue() : null;
248 }
249
250 /**
251 * Converts XML end tags to a HTML end tags
252 *
253 * Each "/>" is replaced by "></tag>" for the correct tag
254 *
255 * @param xml XML string
256 * @return HTML string
257 */
258
259 public static String convertXmlEndtagsToHtmlEndtags (String xml) {
260 return xml.replaceAll("<\\s*([^\\s>]+)([^>]*)/\\s*>", "<$1$2></$1>");
261 }
262
263 /**
264 * Determine whether a unicode character is in the braille range
265 *
266 * @param c unicode character
267 * @return true if it is in the braile range, false otherwise
268 */
269 public static boolean isBraille(char c) {
270 return 0x2800 <= c && c <= 0x283F;
271 }
272
273 /**
274 * Convert a character from braille to lowercase text
275 *
276 * Braille control characters are not changed
277 * For characters with more than one text representation,
278 * the most common one is chosen
279 * e.g. (123456) is displayed as é but can also be %
280 *
281 * @param braille Braille character
282 * @return text character
283 */
284 public static char convertBraille(char braille) {
285 if (!isBraille(braille)) {
286 return 0x0000;
287 }
288 return " a,b'k;l⠈cif/msp⠐e:h*o!r⠘djg@ntq⠠\\?ê-u(v⠨îöë§xè&⠰û.ü)z\"[⠸ôwï⠼y]é".charAt(braille - 0x2800);
289 }
290
291 /**
292 * Determine whether a braille character is a digit
293 *
294 * @param braille braille character
295 * @return true if it is a digit in the braille range
296 */
297 public static boolean isBrailleDigit(char braille) {
298 return isDigit(convertBrailleNumeric(braille));
299 }
300
301 /**
302 * Determine whether a character is a digit
303 *
304 * @param c character
305 * @return true if it is a digit
306 */
307 public static boolean isDigit (char c) {
308 return '0' <= c && c <= '9';
309 }
310
311 /**
312 * Convert a numeric character from braille to lowercase text
313 *
314 * @param braille Braille character
315 * @return numeric text character
316 */
317 public static char convertBrailleNumeric(char braille) {
318 if (isBraille(braille)) {
319 char converted = convertBraille(braille);
320 if ('a' <= converted && converted <= 'j') {
321 return "1234567890".charAt(converted - 'a');
322 }
323 return converted;
324 }
325 return 0x0000;
326 }
327
328 /**
329 * Determine whether a character is the minus sign
330 *
331 * @param braille braille character
332 * @return true when the character is '-'
333 */
334 public static boolean isBrailleMinus(char braille) {
335 char converted = convertBraille(braille);
336 return converted == '-';
337 }
338
339 /**
340 * Determine whether a braille character is a decimal separator
341 *
342 * @param braille braille character
343 * @return true when the braille character is a comma or period
344 */
345 public static boolean isBrailleDecimalSeparator(char braille) {
346 return isDecimalSeparator(convertBraille(braille));
347 }
348
349 /**
350 * Determine whether a character is a decimal separator
351 *
352 * @param c character
353 * @return true when the character is a comma or period
354 */
355 public static boolean isDecimalSeparator(char c) {
356 return c == '.' || c == ',';
357 }
358
359 /**
360 * Determine whether a braille character can be used as a shorthand for 00 at the end of a price
361 * E.g. the = sign in this price: € 6,=
362 *
363 * @param braille braille character
364 * @return true when the character is a - or a =
365 */
366 public static boolean isBrailleMoneyZeros(char braille) {
367 return isMoneyZeros(convertBraille(braille));
368 }
369
370 /**
371 * Determine whether a character can be used as a shorthand for 00 at the end of a price
372 * E.g. the = sign in this price: € 6,=
373 *
374 * @param c character
375 * @return true when the character is a - or a =
376 */
377 public static boolean isMoneyZeros(char c) {
378 return c == '-' || c == '=';
379 }
380 }