001 package ezvcard.io.xml;
002
003 import static ezvcard.util.IOUtils.utf8Writer;
004
005 import java.io.File;
006 import java.io.FileInputStream;
007 import java.io.IOException;
008 import java.io.InputStream;
009 import java.io.OutputStream;
010 import java.io.Reader;
011 import java.io.StringWriter;
012 import java.io.Writer;
013 import java.util.ArrayList;
014 import java.util.Collections;
015 import java.util.HashMap;
016 import java.util.List;
017 import java.util.Map;
018
019 import javax.xml.namespace.QName;
020 import javax.xml.transform.OutputKeys;
021 import javax.xml.transform.TransformerException;
022 import javax.xml.xpath.XPath;
023 import javax.xml.xpath.XPathConstants;
024 import javax.xml.xpath.XPathExpressionException;
025 import javax.xml.xpath.XPathFactory;
026
027 import org.w3c.dom.Document;
028 import org.w3c.dom.Element;
029 import org.xml.sax.SAXException;
030
031 import ezvcard.Ezvcard;
032 import ezvcard.Messages;
033 import ezvcard.VCard;
034 import ezvcard.VCardDataType;
035 import ezvcard.VCardVersion;
036 import ezvcard.io.CannotParseException;
037 import ezvcard.io.EmbeddedVCardException;
038 import ezvcard.io.SkipMeException;
039 import ezvcard.io.scribe.ScribeIndex;
040 import ezvcard.io.scribe.VCardPropertyScribe;
041 import ezvcard.io.scribe.VCardPropertyScribe.Result;
042 import ezvcard.parameter.VCardParameters;
043 import ezvcard.property.ProductId;
044 import ezvcard.property.VCardProperty;
045 import ezvcard.property.Xml;
046 import ezvcard.util.IOUtils;
047 import ezvcard.util.ListMultimap;
048 import ezvcard.util.XmlUtils;
049
050 /*
051 Copyright (c) 2013, Michael Angstadt
052 All rights reserved.
053
054 Redistribution and use in source and binary forms, with or without
055 modification, are permitted provided that the following conditions are met:
056
057 1. Redistributions of source code must retain the above copyright notice, this
058 list of conditions and the following disclaimer.
059 2. Redistributions in binary form must reproduce the above copyright notice,
060 this list of conditions and the following disclaimer in the documentation
061 and/or other materials provided with the distribution.
062
063 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
064 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
065 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
066 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
067 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
068 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
069 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
070 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
071 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
072 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
073
074 The views and conclusions contained in the software and documentation are those
075 of the authors and should not be interpreted as representing official policies,
076 either expressed or implied, of the FreeBSD Project.
077 */
078
079 //@formatter:off
080 /**
081 * <p>
082 * Represents an XML document that contains vCard objects ("xCard" standard).
083 * This class can be used to read and write xCard documents.
084 * </p>
085 * <p>
086 * <b>Examples:</b>
087 *
088 * <pre class="brush:java">
089 * String xml =
090 * "<vcards xmlns=\"urn:ietf:params:xml:ns:vcard-4.0\">" +
091 * "<vcard>" +
092 * "<fn>" +
093 * "<text>John Doe</text>" +
094 * "</fn>" +
095 * "<n>" +
096 * "<surname>Doe</surname>" +
097 * "<given>Johnathan</given>" +
098 * "<additional>Jonny</additional>" +
099 * "<additional>John</additional>" +
100 * "<prefix>Mr.</prefix>" +
101 * "<suffix />" +
102 * "</n>" +
103 * "</vcard>" +
104 * "</vcards>";
105 *
106 * //parsing an existing xCard document
107 * XCardDocument xcard = new XCardDocument(xml);
108 * List<VCard> vcards = xcard.parseAll();
109 *
110 * //creating an empty xCard document
111 * XCardDocument xcard = new XCardDocument();
112 *
113 * //VCard objects can be added at any time
114 * VCard vcard = ...
115 * xcard.add(vcard);
116 *
117 * //retrieving the raw XML DOM
118 * Document document = xcard.getDocument();
119 *
120 * //call one of the "write()" methods to output the xCard document
121 * File file = new File("johndoe.xml");
122 * xcard.write(file);
123 * </pre>
124 *
125 * </p>
126 * @author Michael Angstadt
127 * @see <a href="http://tools.ietf.org/html/rfc6351">RFC 6351</a>
128 */
129 //@formatter:on
130 public class XCardDocument {
131 private static final VCardVersion version4 = VCardVersion.V4_0; //xCard only supports 4.0
132 private static final XCardNamespaceContext nsContext = new XCardNamespaceContext(version4, "v");
133
134 /**
135 * Defines the names of the XML elements that are used to hold each
136 * parameter's value.
137 */
138 private final Map<String, VCardDataType> parameterDataTypes = new HashMap<String, VCardDataType>();
139 {
140 registerParameterDataType(VCardParameters.ALTID, VCardDataType.TEXT);
141 registerParameterDataType(VCardParameters.CALSCALE, VCardDataType.TEXT);
142 registerParameterDataType(VCardParameters.GEO, VCardDataType.URI);
143 registerParameterDataType(VCardParameters.LABEL, VCardDataType.TEXT);
144 registerParameterDataType(VCardParameters.LANGUAGE, VCardDataType.LANGUAGE_TAG);
145 registerParameterDataType(VCardParameters.MEDIATYPE, VCardDataType.TEXT);
146 registerParameterDataType(VCardParameters.PID, VCardDataType.TEXT);
147 registerParameterDataType(VCardParameters.PREF, VCardDataType.INTEGER);
148 registerParameterDataType(VCardParameters.SORT_AS, VCardDataType.TEXT);
149 registerParameterDataType(VCardParameters.TYPE, VCardDataType.TEXT);
150 registerParameterDataType(VCardParameters.TZ, VCardDataType.URI);
151 }
152
153 private ScribeIndex index = new ScribeIndex();
154 private boolean addProdId = true;
155 private boolean versionStrict = true;
156 private final List<List<String>> parseWarnings = new ArrayList<List<String>>();
157 private Document document;
158 private Element root;
159
160 /**
161 * Creates an xCard document.
162 */
163 public XCardDocument() {
164 document = XmlUtils.createDocument();
165 root = createElement("vcards");
166 document.appendChild(root);
167 }
168
169 /**
170 * Parses an xCard document from a string.
171 * @param xml the XML string to read the vCards from
172 * @throws SAXException if there's a problem parsing the XML
173 */
174 public XCardDocument(String xml) throws SAXException {
175 this(XmlUtils.toDocument(xml));
176 }
177
178 /**
179 * Parses an xCard document from an input stream.
180 * @param in the input stream to read the vCards from
181 * @throws IOException if there's a problem reading from the input stream
182 * @throws SAXException if there's a problem parsing the XML
183 */
184 public XCardDocument(InputStream in) throws SAXException, IOException {
185 this(XmlUtils.toDocument(in));
186 }
187
188 /**
189 * Parses an xCard document from a file.
190 * @param file the file to read the vCards from
191 * @throws IOException if there's a problem reading from the file
192 * @throws SAXException if there's a problem parsing the XML
193 */
194 public XCardDocument(File file) throws SAXException, IOException {
195 InputStream in = new FileInputStream(file);
196 try {
197 init(XmlUtils.toDocument(in));
198 } finally {
199 IOUtils.closeQuietly(in);
200 }
201 }
202
203 /**
204 * <p>
205 * Parses an xCard document from a reader.
206 * </p>
207 * <p>
208 * Note that use of this constructor is discouraged. It ignores the
209 * character encoding that is defined within the XML document itself, and
210 * should only be used if the encoding is undefined or if the encoding needs
211 * to be ignored for whatever reason. The
212 * {@link #XCardDocument(InputStream)} constructor should be used instead,
213 * since it takes the XML document's character encoding into account when
214 * parsing.
215 * </p>
216 * @param reader the reader to read the vCards from
217 * @throws IOException if there's a problem reading from the reader
218 * @throws SAXException if there's a problem parsing the XML
219 */
220 public XCardDocument(Reader reader) throws SAXException, IOException {
221 this(XmlUtils.toDocument(reader));
222 }
223
224 /**
225 * Wraps an existing XML DOM object.
226 * @param document the XML DOM that contains the xCard document
227 */
228 public XCardDocument(Document document) {
229 init(document);
230 }
231
232 private void init(Document document) {
233 XPath xpath = XPathFactory.newInstance().newXPath();
234 xpath.setNamespaceContext(nsContext);
235
236 try {
237 //find the <vcards> element
238 String prefix = nsContext.getPrefix();
239 root = (Element) xpath.evaluate("//" + prefix + ":vcards", document, XPathConstants.NODE);
240 } catch (XPathExpressionException e) {
241 //never thrown, xpath expression is hard coded
242 }
243 }
244
245 /**
246 * Gets whether or not a "PRODID" property will be added to each vCard,
247 * saying that the vCard was generated by this library.
248 * @return true if the property will be added, false if not (defaults to
249 * true)
250 */
251 public boolean isAddProdId() {
252 return addProdId;
253 }
254
255 /**
256 * Sets whether or not to add a "PRODID" property to each vCard, saying that
257 * the vCard was generated by this library.
258 * @param addProdId true to add this property, false not to (defaults to
259 * true)
260 */
261 public void setAddProdId(boolean addProdId) {
262 this.addProdId = addProdId;
263 }
264
265 /**
266 * Gets whether properties that do not support xCard (vCard version 4.0)
267 * will be excluded from the written vCard.
268 * @return true to exclude properties that do not support xCard, false to
269 * include them anyway (defaults to true)
270 */
271 public boolean isVersionStrict() {
272 return versionStrict;
273 }
274
275 /**
276 * Sets whether properties that do not support xCard (vCard version 4.0)
277 * will be excluded from the written vCard.
278 * @param versionStrict true to exclude properties that do not support
279 * xCard, false to include them anyway (defaults to true)
280 */
281 public void setVersionStrict(boolean versionStrict) {
282 this.versionStrict = versionStrict;
283 }
284
285 /**
286 * Gets the warnings from the last parse operation.
287 * @return the warnings (it is a "list of lists"--each parsed {@link VCard}
288 * object has its own warnings list)
289 * @see #parseAll
290 * @see #parseFirst
291 */
292 public List<List<String>> getParseWarnings() {
293 return parseWarnings;
294 }
295
296 /**
297 * Registers the data type of an experimental parameter. Experimental
298 * parameters use the "unknown" data type by default.
299 * @param parameterName the parameter name (e.g. "x-foo")
300 * @param dataType the data type or null to remove
301 */
302 public void registerParameterDataType(String parameterName, VCardDataType dataType) {
303 parameterName = parameterName.toLowerCase();
304 if (dataType == null) {
305 parameterDataTypes.remove(parameterName);
306 } else {
307 parameterDataTypes.put(parameterName, dataType);
308 }
309 }
310
311 /**
312 * <p>
313 * Registers a property scribe. This is the same as calling:
314 * </p>
315 * <p>
316 * {@code getScribeIndex().register(scribe)}
317 * </p>
318 * @param scribe the scribe to register
319 */
320 public void registerScribe(VCardPropertyScribe<? extends VCardProperty> scribe) {
321 index.register(scribe);
322 }
323
324 /**
325 * Gets the scribe index.
326 * @return the scribe index
327 */
328 public ScribeIndex getScribeIndex() {
329 return index;
330 }
331
332 /**
333 * Sets the scribe index.
334 * @param index the scribe index
335 */
336 public void setScribeIndex(ScribeIndex index) {
337 this.index = index;
338 }
339
340 /**
341 * Gets the XML document that was generated.
342 * @return the XML document
343 */
344 public Document getDocument() {
345 return document;
346 }
347
348 /**
349 * Parses all the {@link VCard} objects from the xCard document.
350 * @return the vCard objects
351 */
352 public List<VCard> parseAll() {
353 parseWarnings.clear();
354
355 if (root == null) {
356 return Collections.emptyList();
357 }
358
359 List<VCard> vcards = new ArrayList<VCard>();
360 for (Element vcardElement : getVCardElements()) {
361 List<String> warnings = new ArrayList<String>();
362 parseWarnings.add(warnings);
363 VCard vcard = parseVCardElement(vcardElement, warnings);
364 vcards.add(vcard);
365 }
366
367 return vcards;
368 }
369
370 /**
371 * Parses the first the {@link VCard} object from the xCard document.
372 * @return the vCard object
373 */
374 public VCard parseFirst() {
375 parseWarnings.clear();
376
377 if (root == null) {
378 return null;
379 }
380
381 List<Element> vcardElements = getVCardElements();
382 if (vcardElements.isEmpty()) {
383 return null;
384 }
385
386 List<String> warnings = new ArrayList<String>();
387 parseWarnings.add(warnings);
388 return parseVCardElement(vcardElements.get(0), warnings);
389 }
390
391 private VCard parseVCardElement(Element vcardElement, List<String> warnings) {
392 VCard vcard = new VCard();
393 vcard.setVersion(version4);
394
395 String ns = version4.getXmlNamespace();
396 List<Element> children = XmlUtils.toElementList(vcardElement.getChildNodes());
397 for (Element child : children) {
398 if ("group".equals(child.getLocalName()) && ns.equals(child.getNamespaceURI())) {
399 String group = child.getAttribute("name");
400 if (group.length() == 0) {
401 group = null;
402 }
403 List<Element> propElements = XmlUtils.toElementList(child.getChildNodes());
404 for (Element propElement : propElements) {
405 parseAndAddElement(propElement, group, vcard, warnings);
406 }
407 } else {
408 parseAndAddElement(child, null, vcard, warnings);
409 }
410 }
411
412 return vcard;
413 }
414
415 /**
416 * Parses a property element from the XML document and adds the property to
417 * the vCard.
418 * @param element the element to parse
419 * @param group the group name or null if the property does not belong to a
420 * group
421 * @param vcard the vCard object
422 * @param warningsBuf the list to add the warnings to
423 */
424 private void parseAndAddElement(Element element, String group, VCard vcard, List<String> warnings) {
425 VCardParameters parameters = parseSubTypes(element);
426
427 VCardProperty property;
428 String propertyName = element.getLocalName();
429 String ns = element.getNamespaceURI();
430 VCardPropertyScribe<? extends VCardProperty> scribe = index.getPropertyScribe(new QName(ns, propertyName));
431 try {
432 Result<? extends VCardProperty> result = scribe.parseXml(element, parameters);
433
434 property = result.getProperty();
435 property.setGroup(group);
436
437 for (String warning : result.getWarnings()) {
438 addWarning(propertyName, warning, warnings);
439 }
440 } catch (SkipMeException e) {
441 addWarning(propertyName, warnings, 22, e.getMessage());
442 return;
443 } catch (CannotParseException e) {
444 String xml = XmlUtils.toString(element);
445 addWarning(propertyName, warnings, 33, xml, e.getMessage());
446
447 scribe = index.getPropertyScribe(Xml.class);
448 Result<? extends VCardProperty> result = scribe.parseXml(element, parameters);
449 property = result.getProperty();
450 property.setGroup(group);
451 } catch (EmbeddedVCardException e) {
452 addWarning(propertyName, warnings, 34);
453 return;
454 }
455
456 vcard.addProperty(property);
457 }
458
459 /**
460 * Parses the property parameters (aka "sub types").
461 * @param element the property's XML element
462 * @return the parsed parameters
463 */
464 private VCardParameters parseSubTypes(Element element) {
465 VCardParameters subTypes = new VCardParameters();
466
467 List<Element> parametersElements = XmlUtils.toElementList(element.getElementsByTagNameNS(version4.getXmlNamespace(), "parameters"));
468 for (Element parametersElement : parametersElements) { // foreach "<parameters>" element (there should only be 1 though)
469 List<Element> paramElements = XmlUtils.toElementList(parametersElement.getChildNodes());
470 for (Element paramElement : paramElements) {
471 String name = paramElement.getLocalName().toUpperCase();
472 List<Element> valueElements = XmlUtils.toElementList(paramElement.getChildNodes());
473 if (valueElements.isEmpty()) {
474 String value = paramElement.getTextContent();
475 subTypes.put(name, value);
476 } else {
477 for (Element valueElement : valueElements) {
478 String value = valueElement.getTextContent();
479 subTypes.put(name, value);
480 }
481 }
482 }
483 }
484
485 return subTypes;
486 }
487
488 /**
489 * Writes the XML document to a string without pretty-printing it.
490 * @return the XML string
491 */
492 public String write() {
493 return write(-1);
494 }
495
496 /**
497 * Writes the XML document to a string and pretty-prints it.
498 * @param indent the number of indent spaces to use for pretty-printing
499 * @return the XML string
500 */
501 public String write(int indent) {
502 StringWriter sw = new StringWriter();
503 try {
504 write(sw, indent);
505 } catch (TransformerException e) {
506 //writing to string
507 }
508 return sw.toString();
509 }
510
511 /**
512 * Writes the XML document to an output stream without pretty-printing it.
513 * @param out the output stream
514 * @throws TransformerException if there's a problem writing to the output
515 * stream
516 */
517 public void write(OutputStream out) throws TransformerException {
518 write(out, -1);
519 }
520
521 /**
522 * Writes the XML document to an output stream and pretty-prints it.
523 * @param out the output stream
524 * @param indent the number of indent spaces to use for pretty-printing
525 * @throws TransformerException if there's a problem writing to the output
526 * stream
527 */
528 public void write(OutputStream out, int indent) throws TransformerException {
529 write(utf8Writer(out), indent);
530 }
531
532 /**
533 * Writes the XML document to a file without pretty-printing it.
534 * @param file the file
535 * @throws TransformerException if there's a problem writing to the file
536 * @throws IOException if there's a problem writing to the file
537 */
538 public void write(File file) throws TransformerException, IOException {
539 write(file, -1);
540 }
541
542 /**
543 * Writes the XML document to a file and pretty-prints it.
544 * @param file the file stream
545 * @param indent the number of indent spaces to use for pretty-printing
546 * @throws TransformerException if there's a problem writing to the file
547 * @throws IOException if there's a problem writing to the file
548 */
549 public void write(File file, int indent) throws TransformerException, IOException {
550 Writer writer = utf8Writer(file);
551 try {
552 write(writer, indent);
553 } finally {
554 IOUtils.closeQuietly(writer);
555 }
556 }
557
558 /**
559 * Writes the XML document to a writer without pretty-printing it.
560 * @param writer the writer
561 * @throws TransformerException if there's a problem writing to the writer
562 */
563 public void write(Writer writer) throws TransformerException {
564 write(writer, -1);
565 }
566
567 /**
568 * Writes the XML document to a writer and pretty-prints it.
569 * @param writer the writer
570 * @param indent the number of indent spaces to use for pretty-printing
571 * @throws TransformerException if there's a problem writing to the writer
572 */
573 public void write(Writer writer, int indent) throws TransformerException {
574 Map<String, String> properties = new HashMap<String, String>();
575 if (indent >= 0) {
576 properties.put(OutputKeys.INDENT, "yes");
577 properties.put("{http://xml.apache.org/xslt}indent-amount", indent + "");
578 }
579 XmlUtils.toWriter(document, writer, properties);
580 }
581
582 /**
583 * Adds a vCard to the XML document.
584 * @param vcard the vCard to add
585 * @throws IllegalArgumentException if a scribe hasn't been registered for a
586 * custom property class (see: {@link #registerScribe})
587 */
588 public void add(VCard vcard) {
589 ListMultimap<String, VCardProperty> typesToAdd = new ListMultimap<String, VCardProperty>(); //group the types by group name (null = no group name)
590
591 for (VCardProperty type : vcard) {
592 if (addProdId && type instanceof ProductId) {
593 //do not add the PRODID in the vCard if "addProdId" is true
594 continue;
595 }
596
597 if (versionStrict && !type.getSupportedVersions().contains(version4)) {
598 //do not add the property to the vCard if it is not supported by the target version
599 continue;
600 }
601
602 //check for scribes before writing anything to the stream
603 if (index.getPropertyScribe(type) == null) {
604 throw new IllegalArgumentException("No scribe found for property class \"" + type.getClass().getName() + "\".");
605 }
606
607 typesToAdd.put(type.getGroup(), type);
608 }
609
610 //add an extended type saying it was generated by this library
611 if (addProdId) {
612 ProductId prodId = new ProductId("ez-vcard " + Ezvcard.VERSION);
613 typesToAdd.put(prodId.getGroup(), prodId);
614 }
615
616 //marshal each type object
617 Element vcardElement = createElement("vcard");
618 for (String groupName : typesToAdd.keySet()) {
619 Element parent;
620 if (groupName != null) {
621 Element groupElement = createElement("group");
622 groupElement.setAttribute("name", groupName);
623 vcardElement.appendChild(groupElement);
624 parent = groupElement;
625 } else {
626 parent = vcardElement;
627 }
628
629 for (VCardProperty type : typesToAdd.get(groupName)) {
630 try {
631 Element typeElement = marshalType(type, vcard);
632 parent.appendChild(typeElement);
633 } catch (SkipMeException e) {
634 //skip property
635 } catch (EmbeddedVCardException e) {
636 //skip property
637 }
638 }
639 }
640 root.appendChild(vcardElement);
641 }
642
643 /**
644 * Marshals a type object to an XML element.
645 * @param type the type object to marshal
646 * @param vcard the vcard the type belongs to
647 * @return the XML element
648 */
649 @SuppressWarnings({ "rawtypes", "unchecked" })
650 private Element marshalType(VCardProperty type, VCard vcard) {
651 VCardPropertyScribe scribe = index.getPropertyScribe(type);
652 if (scribe == null) {
653 throw new IllegalArgumentException("No marshaller found for property class \"" + type.getClass().getName() + "\".");
654 }
655
656 VCardParameters parameters = scribe.prepareParameters(type, version4, vcard);
657
658 QName qname = scribe.getQName();
659 Element typeElement = createElement(qname.getLocalPart(), qname.getNamespaceURI());
660
661 //marshal the sub types
662 if (!parameters.isEmpty()) {
663 Element parametersElement = marshalSubTypes(parameters);
664 typeElement.appendChild(parametersElement);
665 }
666
667 //marshal the value
668 scribe.writeXml(type, typeElement);
669
670 return typeElement;
671 }
672
673 private Element marshalSubTypes(VCardParameters parameters) {
674 Element parametersElement = createElement("parameters");
675
676 for (Map.Entry<String, List<String>> param : parameters) {
677 String parameterName = param.getKey().toLowerCase();
678 Element parameterElement = createElement(parameterName);
679
680 for (String paramValue : param.getValue()) {
681 VCardDataType dataType = parameterDataTypes.get(parameterName);
682 String dataTypeElementName = (dataType == null) ? "unknown" : dataType.getName().toLowerCase();
683 Element dataTypeElement = createElement(dataTypeElementName);
684 dataTypeElement.setTextContent(paramValue);
685 parameterElement.appendChild(dataTypeElement);
686 }
687
688 parametersElement.appendChild(parameterElement);
689 }
690
691 return parametersElement;
692 }
693
694 private List<Element> getVCardElements() {
695 return getChildElements(root, "vcard");
696 }
697
698 private List<Element> getChildElements(Element parent, String localName) {
699 List<Element> elements = new ArrayList<Element>();
700 for (Element child : XmlUtils.toElementList(parent.getChildNodes())) {
701 if (localName.equals(child.getLocalName()) && version4.getXmlNamespace().equals(child.getNamespaceURI())) {
702 elements.add(child);
703 }
704 }
705 return elements;
706 }
707
708 /**
709 * Creates a new XML element.
710 * @param name the name of the XML element
711 * @return the new XML element
712 */
713 private Element createElement(String name) {
714 return createElement(name, version4.getXmlNamespace());
715 }
716
717 /**
718 * Creates a new XML element.
719 * @param name the name of the XML element
720 * @param ns the namespace of the XML element
721 * @return the new XML element
722 */
723 private Element createElement(String name, String ns) {
724 return document.createElementNS(ns, name);
725 }
726
727 private void addWarning(String propertyName, List<String> warnings, int code, Object... args) {
728 String message = Messages.INSTANCE.getParseMessage(code, args);
729 addWarning(propertyName, message, warnings);
730 }
731
732 private void addWarning(String propertyName, String message, List<String> warnings) {
733 String warning = Messages.INSTANCE.getParseMessage(35, propertyName, message);
734 warnings.add(warning);
735 }
736 }