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.Collections; 020import java.util.Iterator; 021import java.util.List; 022import java.util.Map; 023import java.util.stream.Collectors; 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-2026, 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 TransformerFactory factory = TransformerFactory.newInstance(); 472 XmlUtils.applyXXEProtection(factory); 473 transformer = factory.newTransformer(); 474 } catch (TransformerConfigurationException | TransformerFactoryConfigurationError e) { 475 //should never be thrown because we're not doing anything fancy with the configuration 476 throw new RuntimeException(e); 477 } 478 479 /* 480 * Using Transformer#setOutputProperties(Properties) doesn't work for 481 * some reason for setting the number of indentation spaces. 482 */ 483 outputProperties.forEach(transformer::setOutputProperty); 484 485 DOMSource source = new DOMSource(document); 486 StreamResult result = new StreamResult(writer); 487 transformer.transform(source, result); 488 } 489 490 private class XCardDocumentStreamReader extends StreamReader { 491 private final Iterator<Element> vcardElements; 492 493 private VCard vcard; 494 495 public XCardDocumentStreamReader() { 496 List<Element> list = (vcardsRootElement == null) ? Collections.<Element> emptyList() : getChildElements(vcardsRootElement, VCARD); 497 vcardElements = list.iterator(); 498 } 499 500 @Override 501 public VCard readNext() { 502 try { 503 return super.readNext(); 504 } catch (IOException e) { 505 //will not be thrown 506 throw new UncheckedIOException(e); 507 } 508 } 509 510 @Override 511 protected VCard _readNext() throws IOException { 512 if (!vcardElements.hasNext()) { 513 return null; 514 } 515 516 vcard = new VCard(); 517 vcard.setVersion(version4); 518 context.setVersion(version4); 519 parseVCardElement(vcardElements.next()); 520 return vcard; 521 } 522 523 public void close() { 524 //empty 525 } 526 527 private void parseVCardElement(Element vcardElement) { 528 List<Element> children = XmlUtils.toElementList(vcardElement.getChildNodes()); 529 for (Element child : children) { 530 if (XmlUtils.hasQName(child, GROUP)) { 531 String group = child.getAttribute("name"); 532 if (group.isEmpty()) { 533 group = null; 534 } 535 List<Element> grandChildren = XmlUtils.toElementList(child.getChildNodes()); 536 for (Element grandChild : grandChildren) { 537 parseAndAddElement(grandChild, group); 538 } 539 continue; 540 } 541 542 parseAndAddElement(child, null); 543 } 544 } 545 546 /** 547 * Parses a property element from the XML document and adds the property 548 * to the vCard. 549 * @param element the element to parse 550 * @param group the group name or null if the property does not belong 551 * to a group 552 */ 553 private void parseAndAddElement(Element element, String group) { 554 VCardParameters parameters = parseParameters(element); 555 556 VCardProperty property; 557 String propertyName = element.getLocalName(); 558 String ns = element.getNamespaceURI(); 559 QName qname = new QName(ns, propertyName); 560 VCardPropertyScribe<? extends VCardProperty> scribe = index.getPropertyScribe(qname); 561 562 context.getWarnings().clear(); 563 context.setPropertyName(propertyName); 564 try { 565 property = scribe.parseXml(element, parameters, context); 566 property.setGroup(group); 567 warnings.addAll(context.getWarnings()); 568 } catch (SkipMeException e) { 569 //@formatter:off 570 warnings.add(new ParseWarning.Builder(context) 571 .message(22, e.getMessage()) 572 .build() 573 ); 574 //@formatter:on 575 return; 576 } catch (CannotParseException e) { 577 //@formatter:off 578 warnings.add(new ParseWarning.Builder(context) 579 .message(e) 580 .build() 581 ); 582 //@formatter:on 583 584 scribe = index.getPropertyScribe(Xml.class); 585 property = scribe.parseXml(element, parameters, context); 586 property.setGroup(group); 587 } catch (EmbeddedVCardException e) { 588 //@formatter:off 589 warnings.add(new ParseWarning.Builder(context) 590 .message(34) 591 .build() 592 ); 593 //@formatter:on 594 return; 595 } 596 597 vcard.addProperty(property); 598 } 599 600 /** 601 * Parses the property parameters. 602 * @param element the property's XML element 603 * @return the parsed parameters 604 */ 605 private VCardParameters parseParameters(Element element) { 606 VCardParameters parameters = new VCardParameters(); 607 608 List<Element> roots = XmlUtils.toElementList(element.getElementsByTagNameNS(PARAMETERS.getNamespaceURI(), PARAMETERS.getLocalPart())); 609 for (Element root : roots) { // foreach "<parameters>" element (there should only be 1 though) 610 List<Element> parameterElements = XmlUtils.toElementList(root.getChildNodes()); 611 for (Element parameterElement : parameterElements) { 612 String name = parameterElement.getLocalName().toUpperCase(); 613 List<Element> valueElements = XmlUtils.toElementList(parameterElement.getChildNodes()); 614 for (Element valueElement : valueElements) { 615 String value = valueElement.getTextContent(); 616 parameters.put(name, value); 617 } 618 } 619 } 620 621 return parameters; 622 } 623 624 private List<Element> getChildElements(Element parent, QName qname) { 625 //@formatter:off 626 return XmlUtils.toElementStream(parent.getChildNodes()) 627 .filter(child -> XmlUtils.hasQName(child, qname)) 628 .collect(Collectors.toList()); 629 //@formatter:on 630 } 631 } 632 633 public class XCardDocumentStreamWriter extends XCardWriterBase { 634 @Override 635 public void write(VCard vcard) { 636 try { 637 super.write(vcard); 638 } catch (IOException ignore) { 639 //won't be thrown because we're writing to a DOM 640 } 641 } 642 643 @Override 644 protected void _write(VCard vcard, List<VCardProperty> properties) throws IOException { 645 //group properties by group name (null = no group name) 646 ListMultimap<String, VCardProperty> propertiesByGroup = new ListMultimap<>(); 647 properties.forEach(property -> propertiesByGroup.put(property.getGroup(), property)); 648 649 //marshal each property object 650 Element vcardElement = createElement(VCARD); 651 for (Map.Entry<String, List<VCardProperty>> entry : propertiesByGroup) { 652 String groupName = entry.getKey(); 653 Element parent; 654 if (groupName != null) { 655 Element groupElement = createElement(GROUP); 656 groupElement.setAttribute("name", groupName); 657 vcardElement.appendChild(groupElement); 658 parent = groupElement; 659 } else { 660 parent = vcardElement; 661 } 662 663 for (VCardProperty property : entry.getValue()) { 664 try { 665 Element propertyElement = marshalProperty(property, vcard); 666 parent.appendChild(propertyElement); 667 } catch (SkipMeException | EmbeddedVCardException e) { 668 //skip property 669 } 670 } 671 } 672 673 if (vcardsRootElement == null) { 674 vcardsRootElement = createElement(VCARDS); 675 Element documentRoot = document.getDocumentElement(); 676 if (documentRoot == null) { 677 document.appendChild(vcardsRootElement); 678 } else { 679 documentRoot.appendChild(vcardsRootElement); 680 } 681 } 682 vcardsRootElement.appendChild(vcardElement); 683 } 684 685 public void close() { 686 //empty 687 } 688 689 /** 690 * Marshals a type object to an XML element. 691 * @param property the property to marshal 692 * @param vcard the vcard the type belongs to 693 * @return the XML element 694 */ 695 @SuppressWarnings({ "rawtypes", "unchecked" }) 696 private Element marshalProperty(VCardProperty property, VCard vcard) { 697 VCardPropertyScribe scribe = index.getPropertyScribe(property); 698 699 Element propertyElement; 700 if (property instanceof Xml) { 701 Xml xml = (Xml) property; 702 Document propertyDocument = xml.getValue(); 703 if (propertyDocument == null) { 704 throw new SkipMeException(); 705 } 706 propertyElement = propertyDocument.getDocumentElement(); 707 propertyElement = (Element) document.importNode(propertyElement, true); 708 } else { 709 QName qname = scribe.getQName(); 710 propertyElement = createElement(qname); 711 scribe.writeXml(property, propertyElement); 712 } 713 714 //marshal the parameters 715 VCardParameters parameters = scribe.prepareParameters(property, targetVersion, vcard); 716 removeUnsupportedParameters(parameters); 717 if (!parameters.isEmpty()) { 718 Element parametersElement = marshalParameters(parameters); 719 Node firstChild = propertyElement.getFirstChild(); 720 propertyElement.insertBefore(parametersElement, firstChild); 721 } 722 723 return propertyElement; 724 } 725 726 private Element marshalParameters(VCardParameters parameters) { 727 Element parametersElement = createElement(PARAMETERS); 728 729 for (Map.Entry<String, List<String>> parameter : parameters) { 730 String parameterName = parameter.getKey().toLowerCase(); 731 Element parameterElement = createElement(parameterName); 732 VCardDataType dataType = parameterDataTypes.get(parameterName); 733 String dataTypeElementName = (dataType == null) ? "unknown" : dataType.getName().toLowerCase(); 734 735 for (String parameterValue : parameter.getValue()) { 736 Element dataTypeElement = createElement(dataTypeElementName); 737 dataTypeElement.setTextContent(parameterValue); 738 parameterElement.appendChild(dataTypeElement); 739 } 740 741 parametersElement.appendChild(parameterElement); 742 } 743 744 return parametersElement; 745 } 746 747 /** 748 * Creates a new XML element under the vCard namespace. 749 * @param name the name of the XML element 750 * @return the new XML element 751 */ 752 private Element createElement(String name) { 753 return createElement(name, targetVersion.getXmlNamespace()); 754 } 755 756 /** 757 * Creates a new XML element. 758 * @param name the name of the XML element 759 * @param ns the namespace of the XML element 760 * @return the new XML element 761 */ 762 private Element createElement(String name, String ns) { 763 return document.createElementNS(ns, name); 764 } 765 766 /** 767 * Creates a new XML element. 768 * @param qname the element name 769 * @return the new XML element 770 */ 771 private Element createElement(QName qname) { 772 return createElement(qname.getLocalPart(), qname.getNamespaceURI()); 773 } 774 } 775}