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