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