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