001 package biweekly.io.xml;
002
003 import static biweekly.io.xml.XCalNamespaceContext.XCAL_NS;
004 import static biweekly.util.IOUtils.utf8Writer;
005
006 import java.io.File;
007 import java.io.FileInputStream;
008 import java.io.IOException;
009 import java.io.InputStream;
010 import java.io.OutputStream;
011 import java.io.Reader;
012 import java.io.StringWriter;
013 import java.io.Writer;
014 import java.util.ArrayList;
015 import java.util.Collections;
016 import java.util.HashMap;
017 import java.util.List;
018 import java.util.Map;
019
020 import javax.xml.namespace.QName;
021 import javax.xml.transform.OutputKeys;
022 import javax.xml.transform.TransformerException;
023 import javax.xml.xpath.XPath;
024 import javax.xml.xpath.XPathConstants;
025 import javax.xml.xpath.XPathExpressionException;
026 import javax.xml.xpath.XPathFactory;
027
028 import org.w3c.dom.Document;
029 import org.w3c.dom.Element;
030 import org.xml.sax.SAXException;
031
032 import biweekly.ICalDataType;
033 import biweekly.ICalendar;
034 import biweekly.Messages;
035 import biweekly.Warning;
036 import biweekly.component.ICalComponent;
037 import biweekly.component.marshaller.ICalComponentMarshaller;
038 import biweekly.component.marshaller.ICalendarMarshaller;
039 import biweekly.io.CannotParseException;
040 import biweekly.io.ICalMarshallerRegistrar;
041 import biweekly.io.SkipMeException;
042 import biweekly.parameter.ICalParameters;
043 import biweekly.property.ICalProperty;
044 import biweekly.property.Xml;
045 import biweekly.property.marshaller.ICalPropertyMarshaller;
046 import biweekly.property.marshaller.ICalPropertyMarshaller.Result;
047 import biweekly.util.IOUtils;
048 import biweekly.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
075 //@formatter:off
076 /**
077 * <p>
078 * Represents an XML document that contains iCalendar objects ("xCal" standard).
079 * This class can be used to read and write xCal documents.
080 * </p>
081 * <p>
082 * <b>Examples:</b>
083 *
084 * <pre class="brush:java">
085 * String xml =
086 * "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" +
087 * "<icalendar xmlns=\"urn:ietf:params:xml:ns:icalendar-2.0\">" +
088 * "<vcalendar>" +
089 * "<properties>" +
090 * "<prodid><text>-//Example Inc.//Example Client//EN</text></prodid>" +
091 * "<version><text>2.0</text></version>" +
092 * "</properties>" +
093 * "<components>" +
094 * "<vevent>" +
095 * "<properties>" +
096 * "<dtstart><date-time>2013-06-27T13:00:00Z</date-time></dtstart>" +
097 * "<dtend><date-time>2013-06-27T15:00:00Z</date-time></dtend>" +
098 * "<summary><text>Team Meeting</text></summary>" +
099 * "</properties>" +
100 * "</vevent>" +
101 * "</components>" +
102 * "</vcalendar>" +
103 * "</icalendar>";
104 *
105 * //parsing an existing xCal document
106 * XCalDocument xcal = new XCalDocument(xml);
107 * List<ICalendar> icals = xcal.parseAll();
108 *
109 * //creating an empty xCal document
110 * XCalDocument xcal = new XCalDocument();
111 *
112 * //ICalendar objects can be added at any time
113 * ICalendar ical = new ICalendar();
114 * xcal.add(ical);
115 *
116 * //retrieving the raw XML DOM
117 * Document document = xcal.getDocument();
118 *
119 * //call one of the "write()" methods to output the xCal document
120 * File file = new File("meeting.xml");
121 * xcal.write(file);
122 * </pre>
123 *
124 * </p>
125 * @author Michael Angstadt
126 * @rfc 6321
127 */
128 //@formatter:on
129 public class XCalDocument {
130 private static final ICalendarMarshaller icalMarshaller = ICalMarshallerRegistrar.getICalendarMarshaller();
131 private static final XCalNamespaceContext nsContext = new XCalNamespaceContext("xcal");
132
133 /**
134 * Defines the names of the XML elements that are used to hold each
135 * parameter's value.
136 */
137 private final Map<String, ICalDataType> parameterDataTypes = new HashMap<String, ICalDataType>();
138 {
139 registerParameterDataType(ICalParameters.CN, ICalDataType.TEXT);
140 registerParameterDataType(ICalParameters.ALTREP, ICalDataType.URI);
141 registerParameterDataType(ICalParameters.CUTYPE, ICalDataType.TEXT);
142 registerParameterDataType(ICalParameters.DELEGATED_FROM, ICalDataType.CAL_ADDRESS);
143 registerParameterDataType(ICalParameters.DELEGATED_TO, ICalDataType.CAL_ADDRESS);
144 registerParameterDataType(ICalParameters.DIR, ICalDataType.URI);
145 registerParameterDataType(ICalParameters.ENCODING, ICalDataType.TEXT);
146 registerParameterDataType(ICalParameters.FMTTYPE, ICalDataType.TEXT);
147 registerParameterDataType(ICalParameters.FBTYPE, ICalDataType.TEXT);
148 registerParameterDataType(ICalParameters.LANGUAGE, ICalDataType.TEXT);
149 registerParameterDataType(ICalParameters.MEMBER, ICalDataType.CAL_ADDRESS);
150 registerParameterDataType(ICalParameters.PARTSTAT, ICalDataType.TEXT);
151 registerParameterDataType(ICalParameters.RANGE, ICalDataType.TEXT);
152 registerParameterDataType(ICalParameters.RELATED, ICalDataType.TEXT);
153 registerParameterDataType(ICalParameters.RELTYPE, ICalDataType.TEXT);
154 registerParameterDataType(ICalParameters.ROLE, ICalDataType.TEXT);
155 registerParameterDataType(ICalParameters.RSVP, ICalDataType.BOOLEAN);
156 registerParameterDataType(ICalParameters.SENT_BY, ICalDataType.CAL_ADDRESS);
157 registerParameterDataType(ICalParameters.TZID, ICalDataType.TEXT);
158 }
159
160 private ICalMarshallerRegistrar registrar = new ICalMarshallerRegistrar();
161 private final List<List<String>> parseWarnings = new ArrayList<List<String>>();
162 private Document document;
163 private Element root;
164
165 /**
166 * Parses an xCal document from a string.
167 * @param xml the xCal document in the form of a string
168 * @throws SAXException if there's a problem parsing the XML
169 */
170 public XCalDocument(String xml) throws SAXException {
171 this(XmlUtils.toDocument(xml));
172 }
173
174 /**
175 * Parses an xCal document from an input stream.
176 * @param in the input stream to read the the xCal document from
177 * @throws IOException if there's a problem reading from the input stream
178 * @throws SAXException if there's a problem parsing the XML
179 */
180 public XCalDocument(InputStream in) throws SAXException, IOException {
181 this(XmlUtils.toDocument(in));
182 }
183
184 /**
185 * Parses an xCal document from a file.
186 * @param file the file containing the xCal document
187 * @throws IOException if there's a problem reading from the file
188 * @throws SAXException if there's a problem parsing the XML
189 */
190 public XCalDocument(File file) throws SAXException, IOException {
191 InputStream in = new FileInputStream(file);
192 try {
193 init(XmlUtils.toDocument(in));
194 } finally {
195 IOUtils.closeQuietly(in);
196 }
197 }
198
199 /**
200 * <p>
201 * Parses an xCal document from a reader.
202 * </p>
203 * <p>
204 * Note that use of this constructor is discouraged. It ignores the
205 * character encoding that is defined within the XML document itself, and
206 * should only be used if the encoding is undefined or if the encoding needs
207 * to be ignored for whatever reason. The {@link #XCalDocument(InputStream)}
208 * constructor should be used instead, since it takes the XML document's
209 * character encoding into account when parsing.
210 * </p>
211 * @param reader the reader to read the xCal document from
212 * @throws IOException if there's a problem reading from the reader
213 * @throws SAXException if there's a problem parsing the XML
214 */
215 public XCalDocument(Reader reader) throws SAXException, IOException {
216 this(XmlUtils.toDocument(reader));
217 }
218
219 /**
220 * Wraps an existing XML DOM object.
221 * @param document the XML DOM that contains the xCal document
222 */
223 public XCalDocument(Document document) {
224 init(document);
225 }
226
227 /**
228 * Creates an empty xCal document.
229 */
230 public XCalDocument() {
231 document = XmlUtils.createDocument();
232 root = document.createElementNS(XCAL_NS, "icalendar");
233 document.appendChild(root);
234 }
235
236 private void init(Document document) {
237 this.document = document;
238
239 XPath xpath = XPathFactory.newInstance().newXPath();
240 xpath.setNamespaceContext(nsContext);
241
242 try {
243 //find the <icalendar> element
244 String prefix = nsContext.getPrefix();
245 root = (Element) xpath.evaluate("//" + prefix + ":icalendar", document, XPathConstants.NODE);
246 } catch (XPathExpressionException e) {
247 //never thrown, xpath expression is hard coded
248 }
249 }
250
251 /**
252 * <p>
253 * Registers an experimental property marshaller. Can also be used to
254 * override the marshaller of a standard property (such as DTSTART). Calling
255 * this method is the same as calling:
256 * </p>
257 * <p>
258 * {@code getRegistrar().register(marshaller)}.
259 * </p>
260 * @param marshaller the marshaller to register
261 */
262 public void registerMarshaller(ICalPropertyMarshaller<? extends ICalProperty> marshaller) {
263 registrar.register(marshaller);
264 }
265
266 /**
267 * <p>
268 * Registers an experimental component marshaller. Can also be used to
269 * override the marshaller of a standard component (such as VEVENT). Calling
270 * this method is the same as calling:
271 * </p>
272 * <p>
273 * {@code getRegistrar().register(marshaller)}.
274 * </p>
275 * @param marshaller the marshaller to register
276 */
277 public void registerMarshaller(ICalComponentMarshaller<? extends ICalComponent> marshaller) {
278 registrar.register(marshaller);
279 }
280
281 /**
282 * Gets the object that manages the component/property marshaller objects.
283 * @return the marshaller registrar
284 */
285 public ICalMarshallerRegistrar getRegistrar() {
286 return registrar;
287 }
288
289 /**
290 * Sets the object that manages the component/property marshaller objects.
291 * @param registrar the marshaller registrar
292 */
293 public void setRegistrar(ICalMarshallerRegistrar registrar) {
294 this.registrar = registrar;
295 }
296
297 /**
298 * Registers the data type of an experimental parameter. Experimental
299 * parameters use the "unknown" xCal data type by default.
300 * @param parameterName the parameter name (e.g. "x-foo")
301 * @param dataType the data type or null to remove
302 */
303 public void registerParameterDataType(String parameterName, ICalDataType dataType) {
304 parameterName = parameterName.toLowerCase();
305 if (dataType == null) {
306 parameterDataTypes.remove(parameterName);
307 } else {
308 parameterDataTypes.put(parameterName, dataType);
309 }
310 }
311
312 /**
313 * Gets the raw XML DOM object.
314 * @return the XML DOM
315 */
316 public Document getDocument() {
317 return document;
318 }
319
320 /**
321 * Gets the warnings from the last parse operation.
322 * @return the warnings (it is a "list of lists"--each parsed
323 * {@link ICalendar} object has its own warnings list)
324 * @see #parseAll
325 * @see #parseFirst
326 */
327 public List<List<String>> getParseWarnings() {
328 return parseWarnings;
329 }
330
331 /**
332 * Parses all the {@link ICalendar} objects from the xCal document.
333 * @return the iCalendar objects
334 */
335 public List<ICalendar> parseAll() {
336 parseWarnings.clear();
337
338 if (root == null) {
339 return Collections.emptyList();
340 }
341
342 List<ICalendar> icals = new ArrayList<ICalendar>();
343 for (Element vcalendarElement : getVCalendarElements()) {
344 List<String> warnings = new ArrayList<String>();
345 ICalendar ical = parseICal(vcalendarElement, warnings);
346 icals.add(ical);
347 this.parseWarnings.add(warnings);
348 }
349
350 return icals;
351 }
352
353 /**
354 * Parses the first {@link ICalendar} object from the xCal document.
355 * @return the iCalendar object or null if there are none
356 */
357 public ICalendar parseFirst() {
358 parseWarnings.clear();
359
360 if (root == null) {
361 return null;
362 }
363
364 List<String> warnings = new ArrayList<String>();
365 parseWarnings.add(warnings);
366
367 List<Element> vcalendarElements = getVCalendarElements();
368 if (vcalendarElements.isEmpty()) {
369 return null;
370 }
371 return parseICal(vcalendarElements.get(0), warnings);
372 }
373
374 /**
375 * Adds an iCalendar object to the xCal document. This marshals the
376 * {@link ICalendar} object to the XML DOM. This means that any changes that
377 * are made to the {@link ICalendar} object after calling this method will
378 * NOT be applied to the xCal document.
379 * @param ical the iCalendar object to add
380 * @throws IllegalArgumentException if the marshaller class for a component
381 * or property object cannot be found (only happens when an experimental
382 * property/component marshaller is not registered with the
383 * {@code registerMarshaller} method.)
384 */
385 public void add(ICalendar ical) {
386 Element element = buildComponentElement(ical);
387 if (root == null) {
388 root = document.createElementNS(XCAL_NS, "icalendar");
389 document.appendChild(root);
390 }
391 root.appendChild(element);
392 }
393
394 /**
395 * Writes the xCal document to a string without pretty-printing it.
396 * @return the XML string
397 */
398 public String write() {
399 return write(-1);
400 }
401
402 /**
403 * Writes the xCal document to a string and pretty-prints it.
404 * @param indent the number of indent spaces to use for pretty-printing
405 * @return the XML string
406 */
407 public String write(int indent) {
408 StringWriter sw = new StringWriter();
409 try {
410 write(sw, indent);
411 } catch (TransformerException e) {
412 //writing to string
413 }
414 return sw.toString();
415 }
416
417 /**
418 * Writes the xCal document to an output stream without pretty-printing it.
419 * @param out the output stream
420 * @throws TransformerException if there's a problem writing to the output
421 * stream
422 */
423 public void write(OutputStream out) throws TransformerException {
424 write(out, -1);
425 }
426
427 /**
428 * Writes the xCal document to an output stream and pretty-prints it.
429 * @param out the output stream
430 * @param indent the number of indent spaces to use for pretty-printing
431 * @throws TransformerException if there's a problem writing to the output
432 * stream
433 */
434 public void write(OutputStream out, int indent) throws TransformerException {
435 write(utf8Writer(out), indent);
436 }
437
438 /**
439 * Writes the xCal document to a file without pretty-printing it.
440 * @param file the file
441 * @throws IOException if there's a problem writing to the file
442 * @throws TransformerException if there's a problem writing the XML
443 */
444 public void write(File file) throws TransformerException, IOException {
445 write(file, -1);
446 }
447
448 /**
449 * Writes the xCal document to a file and pretty-prints it.
450 * @param file the file stream
451 * @param indent the number of indent spaces to use for pretty-printing
452 * @throws IOException if there's a problem writing to the file
453 * @throws TransformerException if there's a problem writing the XML
454 */
455 public void write(File file, int indent) throws TransformerException, IOException {
456 Writer writer = utf8Writer(file);
457 try {
458 write(writer, indent);
459 } finally {
460 IOUtils.closeQuietly(writer);
461 }
462 }
463
464 /**
465 * Writes the xCal document to a writer without pretty-printing it.
466 * @param writer the writer
467 * @throws TransformerException if there's a problem writing to the writer
468 */
469 public void write(Writer writer) throws TransformerException {
470 write(writer, -1);
471 }
472
473 /**
474 * Writes the xCal document to a writer and pretty-prints it.
475 * @param writer the writer
476 * @param indent the number of indent spaces to use for pretty-printing
477 * @throws TransformerException if there's a problem writing to the writer
478 */
479 public void write(Writer writer, int indent) throws TransformerException {
480 Map<String, String> properties = new HashMap<String, String>();
481 if (indent >= 0) {
482 properties.put(OutputKeys.INDENT, "yes");
483 properties.put("{http://xml.apache.org/xslt}indent-amount", indent + "");
484 }
485 XmlUtils.toWriter(document, writer, properties);
486 }
487
488 @SuppressWarnings({ "rawtypes", "unchecked" })
489 private Element buildComponentElement(ICalComponent component) {
490 ICalComponentMarshaller m = registrar.getComponentMarshaller(component);
491 if (m == null) {
492 throw new IllegalArgumentException("No marshaller found for component class \"" + component.getClass().getName() + "\".");
493 }
494
495 Element componentElement = buildElement(m.getComponentName().toLowerCase());
496
497 Element propertiesWrapperElement = buildElement("properties");
498 for (Object obj : m.getProperties(component)) {
499 ICalProperty property = (ICalProperty) obj;
500
501 //create property element
502 Element propertyElement = buildPropertyElement(property);
503 if (propertyElement != null) {
504 propertiesWrapperElement.appendChild(propertyElement);
505 }
506 }
507 if (propertiesWrapperElement.hasChildNodes()) {
508 componentElement.appendChild(propertiesWrapperElement);
509 }
510
511 Element componentsWrapperElement = buildElement("components");
512 for (Object obj : m.getComponents(component)) {
513 ICalComponent subComponent = (ICalComponent) obj;
514 Element subComponentElement = buildComponentElement(subComponent);
515 if (subComponentElement != null) {
516 componentsWrapperElement.appendChild(subComponentElement);
517 }
518 }
519 if (componentsWrapperElement.hasChildNodes()) {
520 componentElement.appendChild(componentsWrapperElement);
521 }
522
523 return componentElement;
524 }
525
526 @SuppressWarnings({ "rawtypes", "unchecked" })
527 private Element buildPropertyElement(ICalProperty property) {
528 Element propertyElement;
529 ICalParameters parameters;
530
531 if (property instanceof Xml) {
532 Xml xml = (Xml) property;
533
534 Document value = xml.getValue();
535 if (value == null) {
536 return null;
537 }
538
539 //import the XML element into the xCal DOM
540 propertyElement = XmlUtils.getRootElement(value);
541 propertyElement = (Element) document.importNode(propertyElement, true);
542
543 //get parameters
544 parameters = property.getParameters();
545 } else {
546 ICalPropertyMarshaller pm = registrar.getPropertyMarshaller(property);
547 if (pm == null) {
548 throw new IllegalArgumentException("No marshaller found for property class \"" + property.getClass().getName() + "\".");
549 }
550
551 propertyElement = buildElement(pm.getQName());
552
553 //marshal value
554 try {
555 pm.writeXml(property, propertyElement);
556 } catch (SkipMeException e) {
557 return null;
558 }
559
560 //get parameters
561 parameters = pm.prepareParameters(property);
562 }
563
564 //build parameters
565 Element parametersWrapperElement = buildParametersElement(parameters);
566 if (parametersWrapperElement.hasChildNodes()) {
567 propertyElement.insertBefore(parametersWrapperElement, propertyElement.getFirstChild());
568 }
569
570 return propertyElement;
571 }
572
573 private Element buildParametersElement(ICalParameters parameters) {
574 Element parametersWrapperElement = buildElement("parameters");
575
576 for (Map.Entry<String, List<String>> parameter : parameters) {
577 String name = parameter.getKey().toLowerCase();
578 ICalDataType dataType = parameterDataTypes.get(name);
579 String dataTypeStr = (dataType == null) ? "unknown" : dataType.getName().toLowerCase();
580
581 Element parameterElement = buildAndAppendElement(name, parametersWrapperElement);
582 for (String parameterValue : parameter.getValue()) {
583 Element parameterValueElement = buildAndAppendElement(dataTypeStr, parameterElement);
584 parameterValueElement.setTextContent(parameterValue);
585 }
586 }
587
588 return parametersWrapperElement;
589 }
590
591 private ICalendar parseICal(Element icalElement, List<String> warnings) {
592 ICalComponent root = parseComponent(icalElement, warnings);
593
594 ICalendar ical;
595 if (root instanceof ICalendar) {
596 ical = (ICalendar) root;
597 } else {
598 //shouldn't happen, since only <vcalendar> elements are passed into this method
599 ical = icalMarshaller.emptyInstance();
600 ical.addComponent(root);
601 }
602 return ical;
603 }
604
605 private ICalComponent parseComponent(Element componentElement, List<String> warnings) {
606 //create the component object
607 ICalComponentMarshaller<? extends ICalComponent> m = registrar.getComponentMarshaller(componentElement.getLocalName());
608 ICalComponent component = m.emptyInstance();
609
610 //parse properties
611 for (Element propertyWrapperElement : getChildElements(componentElement, "properties")) { //there should be only one <properties> element, but parse them all incase there are more
612 for (Element propertyElement : XmlUtils.toElementList(propertyWrapperElement.getChildNodes())) {
613 ICalProperty property = parseProperty(propertyElement, warnings);
614 if (property != null) {
615 component.addProperty(property);
616 }
617 }
618 }
619
620 //parse sub-components
621 for (Element componentWrapperElement : getChildElements(componentElement, "components")) { //there should be only one <components> element, but parse them all incase there are more
622 for (Element subComponentElement : XmlUtils.toElementList(componentWrapperElement.getChildNodes())) {
623 if (!XCAL_NS.equals(subComponentElement.getNamespaceURI())) {
624 continue;
625 }
626
627 ICalComponent subComponent = parseComponent(subComponentElement, warnings);
628 component.addComponent(subComponent);
629 }
630 }
631
632 return component;
633 }
634
635 private ICalProperty parseProperty(Element propertyElement, List<String> warnings) {
636 ICalParameters parameters = parseParameters(propertyElement);
637 String propertyName = propertyElement.getLocalName();
638 QName qname = new QName(propertyElement.getNamespaceURI(), propertyName);
639
640 ICalPropertyMarshaller<? extends ICalProperty> m = registrar.getPropertyMarshaller(qname);
641
642 ICalProperty property = null;
643 try {
644 Result<? extends ICalProperty> result = m.parseXml(propertyElement, parameters);
645
646 for (Warning warning : result.getWarnings()) {
647 addWarning(propertyName, warnings, warning);
648 }
649
650 property = result.getProperty();
651 } catch (SkipMeException e) {
652 addWarning(propertyName, warnings, Warning.parse(0, e.getMessage()));
653 return null;
654 } catch (CannotParseException e) {
655 addWarning(propertyName, warnings, Warning.parse(16, e.getMessage()));
656 }
657
658 //unmarshal as an XML property
659 if (property == null) {
660 m = registrar.getPropertyMarshaller(Xml.class);
661
662 Result<? extends ICalProperty> result = m.parseXml(propertyElement, parameters);
663
664 for (Warning warning : result.getWarnings()) {
665 addWarning(propertyName, warnings, warning);
666 }
667
668 property = result.getProperty();
669 }
670
671 return property;
672 }
673
674 private ICalParameters parseParameters(Element propertyElement) {
675 ICalParameters parameters = new ICalParameters();
676
677 for (Element parametersElement : getChildElements(propertyElement, "parameters")) { //there should be only one <parameters> element, but parse them all incase there are more
678 List<Element> paramElements = XmlUtils.toElementList(parametersElement.getChildNodes());
679 for (Element paramElement : paramElements) {
680 String name = paramElement.getLocalName().toUpperCase();
681 List<Element> valueElements = XmlUtils.toElementList(paramElement.getChildNodes());
682 if (valueElements.isEmpty()) { //this should never be true if the xCal follows the specs
683 String value = paramElement.getTextContent();
684 parameters.put(name, value);
685 } else {
686 for (Element valueElement : valueElements) {
687 String value = valueElement.getTextContent();
688 parameters.put(name, value);
689 }
690 }
691 }
692 }
693
694 return parameters;
695 }
696
697 private Element buildElement(String localName) {
698 return buildElement(new QName(XCAL_NS, localName));
699 }
700
701 private Element buildElement(QName qname) {
702 return document.createElementNS(qname.getNamespaceURI(), qname.getLocalPart());
703 }
704
705 private Element buildAndAppendElement(String localName, Element parent) {
706 return buildAndAppendElement(new QName(XCAL_NS, localName), parent);
707 }
708
709 private Element buildAndAppendElement(QName qname, Element parent) {
710 Element child = document.createElementNS(qname.getNamespaceURI(), qname.getLocalPart());
711 parent.appendChild(child);
712 return child;
713 }
714
715 private List<Element> getVCalendarElements() {
716 return getChildElements(root, "vcalendar");
717 }
718
719 private List<Element> getChildElements(Element parent, String localName) {
720 List<Element> elements = new ArrayList<Element>();
721 for (Element child : XmlUtils.toElementList(parent.getChildNodes())) {
722 if (localName.equals(child.getLocalName()) && XCAL_NS.equals(child.getNamespaceURI())) {
723 elements.add(child);
724 }
725 }
726 return elements;
727 }
728
729 private void addWarning(String propertyName, List<String> warnings, Warning warning) {
730 String message = Messages.INSTANCE.getMessage("parse.xml", propertyName, warning);
731 warnings.add(message);
732 }
733
734 @Override
735 public String toString() {
736 return write(2);
737 }
738 }