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