001 package ezvcard.io; 002 003 import java.io.Closeable; 004 import java.io.File; 005 import java.io.FileWriter; 006 import java.io.IOException; 007 import java.io.OutputStream; 008 import java.io.OutputStreamWriter; 009 import java.io.StringWriter; 010 import java.io.Writer; 011 import java.util.ArrayList; 012 import java.util.Arrays; 013 import java.util.BitSet; 014 import java.util.HashMap; 015 import java.util.List; 016 import java.util.Map; 017 import java.util.regex.Pattern; 018 019 import org.apache.commons.codec.EncoderException; 020 import org.apache.commons.codec.net.QuotedPrintableCodec; 021 022 import ezvcard.VCard; 023 import ezvcard.VCardSubTypes; 024 import ezvcard.VCardVersion; 025 import ezvcard.parameters.AddressTypeParameter; 026 import ezvcard.parameters.EncodingParameter; 027 import ezvcard.parameters.TypeParameter; 028 import ezvcard.types.AddressType; 029 import ezvcard.types.LabelType; 030 import ezvcard.types.MemberType; 031 import ezvcard.types.ProdIdType; 032 import ezvcard.types.TextType; 033 import ezvcard.types.VCardType; 034 import ezvcard.util.VCardStringUtils; 035 036 /* 037 Copyright (c) 2012, Michael Angstadt 038 All rights reserved. 039 040 Redistribution and use in source and binary forms, with or without 041 modification, are permitted provided that the following conditions are met: 042 043 1. Redistributions of source code must retain the above copyright notice, this 044 list of conditions and the following disclaimer. 045 2. Redistributions in binary form must reproduce the above copyright notice, 046 this list of conditions and the following disclaimer in the documentation 047 and/or other materials provided with the distribution. 048 049 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 050 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 051 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 052 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 053 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 054 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 055 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 056 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 057 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 058 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 059 060 The views and conclusions contained in the software and documentation are those 061 of the authors and should not be interpreted as representing official policies, 062 either expressed or implied, of the FreeBSD Project. 063 */ 064 065 /** 066 * Converts vCards to string representations. 067 * @author Michael Angstadt 068 */ 069 public class VCardWriter implements Closeable { 070 private static final Pattern quoteMeRegex = Pattern.compile(".*?[,:;].*"); 071 private static final Pattern newlineRegex = Pattern.compile("\\r\\n|\\r|\\n"); 072 073 /** 074 * The characters that are not valid in parameter values and that should be 075 * removed. 076 */ 077 private static final Map<VCardVersion, BitSet> invalidParamValueChars = new HashMap<VCardVersion, BitSet>(); 078 static { 079 BitSet controlChars = new BitSet(128); 080 controlChars.set(0, 31); 081 controlChars.set(127); 082 controlChars.set('\t', false); //allow 083 controlChars.set('\n', false); //allow 084 controlChars.set('\r', false); //allow 085 086 //2.1 087 { 088 BitSet bitSet = new BitSet(128); 089 bitSet.or(controlChars); 090 091 bitSet.set(','); 092 bitSet.set('.'); 093 bitSet.set(':'); 094 bitSet.set('='); 095 bitSet.set('['); 096 bitSet.set(']'); 097 098 invalidParamValueChars.put(VCardVersion.V2_1, bitSet); 099 } 100 101 //3.0, 4.0 102 { 103 BitSet bitSet = new BitSet(128); 104 bitSet.or(controlChars); 105 106 invalidParamValueChars.put(VCardVersion.V3_0, bitSet); 107 invalidParamValueChars.put(VCardVersion.V4_0, bitSet); 108 } 109 } 110 111 private CompatibilityMode compatibilityMode = CompatibilityMode.RFC; 112 private VCardVersion targetVersion = VCardVersion.V3_0; 113 private String newline; 114 private boolean addProdId = true; 115 private boolean caretEncodingEnabled = false; 116 private FoldingScheme foldingScheme; 117 private List<String> warnings = new ArrayList<String>(); 118 private final Writer writer; 119 120 /** 121 * Creates a vCard writer (writes v3.0 vCards and uses the standard folding 122 * scheme and newline sequence). 123 * @param out the output stream to write the vCard to 124 */ 125 public VCardWriter(OutputStream out) { 126 this(new OutputStreamWriter(out)); 127 } 128 129 /** 130 * Creates a vCard writer (uses the standard folding scheme and newline 131 * sequence). 132 * @param out the output stream to write the vCard to 133 * @param targetVersion the version that the vCards should conform to 134 */ 135 public VCardWriter(OutputStream out, VCardVersion targetVersion) { 136 this(new OutputStreamWriter(out), targetVersion); 137 } 138 139 /** 140 * Creates a vCard writer. 141 * @param out the output stream to write the vCard to 142 * @param targetVersion the version that the vCards should conform to 143 * @param foldingScheme the folding scheme to use or null not to fold at all 144 * @param newline the newline sequence to use 145 */ 146 public VCardWriter(OutputStream out, VCardVersion targetVersion, FoldingScheme foldingScheme, String newline) { 147 this(new OutputStreamWriter(out), targetVersion, foldingScheme, newline); 148 } 149 150 /** 151 * Creates a vCard writer (writes v3.0 vCards and uses the standard folding 152 * scheme and newline sequence). 153 * @param file the file to write the vCard to 154 * @throws IOException if there's a problem opening the file 155 */ 156 public VCardWriter(File file) throws IOException { 157 this(new FileWriter(file)); 158 } 159 160 /** 161 * Creates a vCard writer (uses the standard folding scheme and newline 162 * sequence). 163 * @param file the file to write the vCard to 164 * @param targetVersion the version that the vCards should conform to 165 * @throws IOException if there's a problem opening the file 166 */ 167 public VCardWriter(File file, VCardVersion targetVersion) throws IOException { 168 this(new FileWriter(file), targetVersion); 169 } 170 171 /** 172 * Creates a vCard writer. 173 * @param file the file to write the vCard to 174 * @param targetVersion the version that the vCards should conform to 175 * @param foldingScheme the folding scheme to use or null not to fold at all 176 * @param newline the newline sequence to use 177 * @throws IOException if there's a problem opening the file 178 */ 179 public VCardWriter(File file, VCardVersion targetVersion, FoldingScheme foldingScheme, String newline) throws IOException { 180 this(new FileWriter(file), targetVersion, foldingScheme, newline); 181 } 182 183 /** 184 * Creates a vCard writer (writes v3.0 vCards and uses the standard folding 185 * scheme and newline sequence). 186 * @param writer the writer to write the vCard to 187 */ 188 public VCardWriter(Writer writer) { 189 this(writer, VCardVersion.V3_0); 190 } 191 192 /** 193 * Creates a vCard writer (uses the standard folding scheme and newline 194 * sequence). 195 * @param writer the writer to write the vCard to 196 * @param targetVersion the version that the vCards should conform to 197 */ 198 public VCardWriter(Writer writer, VCardVersion targetVersion) { 199 this(writer, targetVersion, FoldingScheme.MIME_DIR, "\r\n"); 200 } 201 202 /** 203 * Creates a vCard writer. 204 * @param writer the writer to write the vCard to 205 * @param targetVersion the version that the vCards should conform to 206 * @param foldingScheme the folding scheme to use or null not to fold at all 207 * @param newline the newline sequence to use 208 */ 209 public VCardWriter(Writer writer, VCardVersion targetVersion, FoldingScheme foldingScheme, String newline) { 210 if (writer instanceof FoldedLineWriter || foldingScheme == null) { 211 //the check for FoldedLineWriter is for writing nested 2.1 vCards (i.e. the AGENT type) 212 this.writer = writer; 213 } else { 214 this.writer = new FoldedLineWriter(writer, foldingScheme.getLineLength(), foldingScheme.getIndent(), newline); 215 } 216 this.targetVersion = targetVersion; 217 this.foldingScheme = foldingScheme; 218 this.newline = newline; 219 } 220 221 /** 222 * Gets the compatibility mode. Used for customizing the marshalling process 223 * to target a particular application. 224 * @return the compatibility mode 225 */ 226 @Deprecated 227 public CompatibilityMode getCompatibilityMode() { 228 return compatibilityMode; 229 } 230 231 /** 232 * Sets the compatibility mode. Used for customizing the marshalling process 233 * to target a particular application. 234 * @param compatibilityMode the compatibility mode 235 */ 236 @Deprecated 237 public void setCompatibilityMode(CompatibilityMode compatibilityMode) { 238 this.compatibilityMode = compatibilityMode; 239 } 240 241 /** 242 * Gets the version that the vCards should adhere to. 243 * @return the vCard version 244 */ 245 public VCardVersion getTargetVersion() { 246 return targetVersion; 247 } 248 249 /** 250 * Sets the version that the vCards should adhere to. 251 * @param targetVersion the vCard version 252 */ 253 public void setTargetVersion(VCardVersion targetVersion) { 254 this.targetVersion = targetVersion; 255 } 256 257 /** 258 * Gets whether or not a "PRODID" type will be added to each vCard, saying 259 * that the vCard was generated by this library. For 2.1 vCards, the 260 * extended type "X-PRODID" will be added, since "PRODID" is not supported 261 * by that version. 262 * @return true if it will be added, false if not (defaults to true) 263 */ 264 public boolean isAddProdId() { 265 return addProdId; 266 } 267 268 /** 269 * Sets whether or not to add a "PRODID" type to each vCard, saying that the 270 * vCard was generated by this library. For 2.1 vCards, the extended type 271 * "X-PRODID" will be added, since "PRODID" is not supported by that 272 * version. 273 * @param addProdId true to add this type, false not to (defaults to true) 274 */ 275 public void setAddProdId(boolean addProdId) { 276 this.addProdId = addProdId; 277 } 278 279 /** 280 * <p> 281 * Gets whether the writer will use circumflex accent encoding for vCard 3.0 282 * and 4.0 parameter values. This escaping mechanism allows for newlines and 283 * double quotes to be included in parameter values. 284 * </p> 285 * 286 * <table border="1"> 287 * <tr> 288 * <th>Character</th> 289 * <th>Replacement</th> 290 * </tr> 291 * <tr> 292 * <td><code>"</code></td> 293 * <td><code>^'</code></td> 294 * </tr> 295 * <tr> 296 * <td><i>newline</i></td> 297 * <td><code>^n</code></td> 298 * </tr> 299 * <tr> 300 * <td><code>^</code></td> 301 * <td><code>^^</code></td> 302 * </tr> 303 * </table> 304 * 305 * <p> 306 * This setting is disabled by default and is only used with 3.0 and 4.0 307 * vCards. When writing a vCard with this setting disabled, newlines will be 308 * escaped as "\n", backslashes will be escaped as "\\", and double quotes 309 * will be replaced with single quotes. 310 * </p> 311 * 312 * <p> 313 * Example: 314 * </p> 315 * 316 * <pre> 317 * GEO;X-ADDRESS="Pittsburgh Pirates^n115 Federal St^nPitt 318 * sburgh, PA 15212":geo:40.446816,-80.00566 319 * </pre> 320 * 321 * @return true if circumflex accent encoding is enabled, false if not 322 * @see <a href="http://tools.ietf.org/html/rfc6868">RFC 6868</a> 323 */ 324 public boolean isCaretEncodingEnabled() { 325 return caretEncodingEnabled; 326 } 327 328 /** 329 * <p> 330 * Sets whether the writer will use circumflex accent encoding for vCard 3.0 331 * and 4.0 parameter values. This escaping mechanism allows for newlines and 332 * double quotes to be included in parameter values. 333 * </p> 334 * 335 * <table border="1"> 336 * <tr> 337 * <th>Character</th> 338 * <th>Replacement</th> 339 * </tr> 340 * <tr> 341 * <td><code>"</code></td> 342 * <td><code>^'</code></td> 343 * </tr> 344 * <tr> 345 * <td><i>newline</i></td> 346 * <td><code>^n</code></td> 347 * </tr> 348 * <tr> 349 * <td><code>^</code></td> 350 * <td><code>^^</code></td> 351 * </tr> 352 * </table> 353 * 354 * <p> 355 * This setting is disabled by default and is only used with 3.0 and 4.0 356 * vCards. When writing a vCard with this setting disabled, newlines will be 357 * escaped as "\n", backslashes will be escaped as "\\", and double quotes 358 * will be replaced with single quotes. 359 * </p> 360 * 361 * <p> 362 * Example: 363 * </p> 364 * 365 * <pre> 366 * GEO;X-ADDRESS="Pittsburgh Pirates^n115 Federal St^nPitt 367 * sburgh, PA 15212":geo:40.446816,-80.00566 368 * </pre> 369 * 370 * @param enable true to use circumflex accent encoding, false not to 371 * @see <a href="http://tools.ietf.org/html/rfc6868">RFC 6868</a> 372 */ 373 public void setCaretEncodingEnabled(boolean enable) { 374 caretEncodingEnabled = enable; 375 } 376 377 /** 378 * Gets the newline sequence that is used to separate lines. 379 * @return the newline sequence 380 */ 381 public String getNewline() { 382 return newline; 383 } 384 385 /** 386 * Gets the rules for how each line is folded. 387 * @return the folding scheme or null if the lines are not folded 388 */ 389 public FoldingScheme getFoldingScheme() { 390 return foldingScheme; 391 } 392 393 /** 394 * Gets the warnings from the last vCard that was marshalled. This list is 395 * reset every time a new vCard is written. 396 * @return the warnings or empty list if there were no warnings 397 */ 398 public List<String> getWarnings() { 399 return new ArrayList<String>(warnings); 400 } 401 402 /** 403 * Writes a vCard 404 * @param vcard the vCard to write 405 * @throws IOException if there's a problem writing to the output stream 406 */ 407 public void write(final VCard vcard) throws IOException { 408 warnings.clear(); 409 410 if (targetVersion == VCardVersion.V2_1 || targetVersion == VCardVersion.V3_0) { 411 if (vcard.getStructuredName() == null) { 412 warnings.add("vCard version " + targetVersion + " requires that a structured name be defined."); 413 } 414 } 415 416 if (targetVersion == VCardVersion.V3_0 || targetVersion == VCardVersion.V4_0) { 417 if (vcard.getFormattedName() == null) { 418 warnings.add("vCard version " + targetVersion + " requires that a formatted name be defined."); 419 } 420 } 421 422 List<VCardType> typesToAdd = new ArrayList<VCardType>(); 423 typesToAdd.add(new TextType("BEGIN", "VCARD")); 424 typesToAdd.add(new TextType("VERSION", targetVersion.getVersion())); 425 426 for (VCardType type : vcard) { 427 if (addProdId && type instanceof ProdIdType) { 428 //do not add the PRODID in the vCard if "addProdId" is true 429 continue; 430 } 431 432 //determine if this type is supported by the target version 433 if (!supportsTargetVersion(type)) { 434 warnings.add(type.getTypeName() + " is not supported by vCard version " + targetVersion + " and will not be added to the vCard. Supported versions are: " + Arrays.toString(type.getSupportedVersions())); 435 continue; 436 } 437 438 //check for correct KIND value if there are MEMBER types 439 if (type instanceof MemberType && (vcard.getKind() == null || !vcard.getKind().isGroup())) { 440 warnings.add("KIND must be set to \"group\" in order to add MEMBER properties to the vCard."); 441 continue; 442 } 443 444 typesToAdd.add(type); 445 446 //add LABEL types for each ADR type if the target version is 2.1 or 3.0 447 if (type instanceof AddressType && targetVersion != VCardVersion.V4_0) { 448 AddressType adr = (AddressType) type; 449 String labelStr = adr.getLabel(); 450 if (labelStr != null) { 451 LabelType label = new LabelType(labelStr); 452 for (AddressTypeParameter t : adr.getTypes()) { 453 label.addType(t); 454 } 455 typesToAdd.add(label); 456 } 457 } 458 } 459 460 //add an extended type saying it was generated by this library 461 if (addProdId) { 462 EzvcardProdIdType prodId = new EzvcardProdIdType(targetVersion); 463 typesToAdd.add(prodId); 464 } 465 466 typesToAdd.add(new TextType("END", "VCARD")); 467 468 List<String> warningsBuf = new ArrayList<String>(); 469 for (VCardType type : typesToAdd) { 470 //marshal the value 471 warningsBuf.clear(); 472 String value = null; 473 VCard nested = null; 474 try { 475 value = type.marshalText(targetVersion, warningsBuf, compatibilityMode); 476 } catch (SkipMeException e) { 477 warningsBuf.add(type.getTypeName() + " property will not be marshalled: " + e.getMessage()); 478 continue; 479 } catch (EmbeddedVCardException e) { 480 nested = e.getVCard(); 481 } finally { 482 warnings.addAll(warningsBuf); 483 } 484 485 //marshal the sub types 486 warningsBuf.clear(); 487 VCardSubTypes subTypes; 488 try { 489 subTypes = type.marshalSubTypes(targetVersion, warningsBuf, compatibilityMode, vcard); 490 } finally { 491 warnings.addAll(warningsBuf); 492 } 493 494 //sanitize value for safe inclusion in the vCard 495 if (value != null) { 496 if (targetVersion == VCardVersion.V2_1) { 497 if (VCardStringUtils.containsNewlines(value)) { 498 //2.1 does not support the "\n" escape sequence (see "Delimiters" sub-section in section 2 of the specs) 499 QuotedPrintableCodec codec = new QuotedPrintableCodec(); 500 try { 501 value = codec.encode(value); 502 subTypes.setEncoding(EncodingParameter.QUOTED_PRINTABLE); 503 } catch (EncoderException e) { 504 warnings.add("A unexpected error occurred while encoding the value of the " + type.getTypeName() + " property in \"quoted-printable\" encoding. Value will not be encoded.\n" + e.getMessage()); 505 value = VCardStringUtils.escapeNewlines(value); 506 } 507 } 508 } else { 509 value = VCardStringUtils.escapeNewlines(value); 510 } 511 } 512 513 StringBuilder sb = new StringBuilder(); 514 515 //write the group 516 if (type.getGroup() != null) { 517 sb.append(type.getGroup()); 518 sb.append('.'); 519 } 520 521 //write the type name 522 sb.append(type.getTypeName()); 523 524 //write the Sub Types 525 for (String subTypeName : subTypes.getNames()) { 526 List<String> subTypeValues = subTypes.get(subTypeName); 527 if (!subTypeValues.isEmpty()) { 528 if (targetVersion == VCardVersion.V2_1) { 529 boolean typeSubType = TypeParameter.NAME.equalsIgnoreCase(subTypeName); 530 for (String subTypeValue : subTypeValues) { 531 subTypeValue = sanitizeSubTypeValue(subTypeValue, subTypeName, type.getTypeName()); 532 533 if (typeSubType) { 534 //e.g. ADR;HOME;WORK: 535 sb.append(';').append(subTypeValue.toUpperCase()); 536 } else { 537 //e.g. ADR;FOO=bar;FOO=car: 538 sb.append(';').append(subTypeName).append('=').append(subTypeValue); 539 } 540 } 541 } else { 542 //e.g. ADR;TYPE=home,work,"another,value": 543 544 sb.append(';').append(subTypeName).append('='); 545 for (String subTypeValue : subTypeValues) { 546 subTypeValue = sanitizeSubTypeValue(subTypeValue, subTypeName, type.getTypeName()); 547 548 //surround with double quotes if contains special chars 549 if (quoteMeRegex.matcher(subTypeValue).matches()) { 550 sb.append('"'); 551 sb.append(subTypeValue); 552 sb.append('"'); 553 } else { 554 sb.append(subTypeValue); 555 } 556 557 sb.append(','); 558 } 559 sb.deleteCharAt(sb.length() - 1); //chomp last comma 560 } 561 } 562 } 563 564 sb.append(':'); 565 566 writer.write(sb.toString()); 567 568 //write the value 569 if (nested == null) { 570 writer.write(value); 571 writer.write(newline); 572 } else { 573 if (targetVersion == VCardVersion.V2_1) { 574 writer.write(newline); 575 576 //write a nested vCard (2.1 style) 577 VCardWriter agentWriter = new VCardWriter(writer, targetVersion); 578 agentWriter.setAddProdId(false); 579 agentWriter.setCompatibilityMode(compatibilityMode); 580 try { 581 agentWriter.write(nested); 582 } finally { 583 for (String w : agentWriter.getWarnings()) { 584 warnings.add(type.getTypeName() + " marshal warning: " + w); 585 } 586 } 587 } else { 588 //write an embedded vCard (3.0 style) 589 StringWriter sw = new StringWriter(); 590 VCardWriter agentWriter = new VCardWriter(sw, targetVersion, null, "\n"); 591 agentWriter.setAddProdId(false); 592 agentWriter.setCompatibilityMode(compatibilityMode); 593 try { 594 agentWriter.write(nested); 595 } finally { 596 for (String w : agentWriter.getWarnings()) { 597 warnings.add("Problem marshalling nested vCard for " + type.getTypeName() + ": " + w); 598 } 599 } 600 601 String vCardStr = sw.toString(); 602 vCardStr = VCardStringUtils.escape(vCardStr); 603 vCardStr = VCardStringUtils.escapeNewlines(vCardStr); 604 writer.write(vCardStr); 605 writer.write(newline); 606 } 607 } 608 } 609 } 610 611 private String sanitizeSubTypeValue(String value, String subTypeName, String typeName) { 612 String modifiedValue = null; 613 boolean subTypeValueChanged = false; 614 615 switch (targetVersion) { 616 case V2_1: 617 //remove invalid characters 618 modifiedValue = removeInvalidSubTypeValueChars(value); 619 620 //replace newlines with spaces 621 modifiedValue = newlineRegex.matcher(modifiedValue).replaceAll(" "); 622 623 //check to see if value was changed 624 subTypeValueChanged = (value != modifiedValue); 625 626 //escape backslashes 627 modifiedValue = modifiedValue.replace("\\", "\\\\"); 628 629 //escape semi-colons (see section 2) 630 modifiedValue = modifiedValue.replace(";", "\\;"); 631 632 break; 633 634 case V3_0: 635 case V4_0: 636 //remove invalid characters 637 modifiedValue = removeInvalidSubTypeValueChars(value); 638 639 if (caretEncodingEnabled) { 640 subTypeValueChanged = (modifiedValue != value); 641 modifiedValue = applyCaretEncoding(modifiedValue); 642 } else { 643 //replace double quotes with single quotes 644 modifiedValue = modifiedValue.replace('"', '\''); 645 subTypeValueChanged = (modifiedValue != value); 646 647 //escape backslashes 648 modifiedValue = modifiedValue.replace("\\", "\\\\"); 649 650 //escape newlines 651 if (targetVersion == VCardVersion.V3_0) { 652 modifiedValue = newlineRegex.matcher(modifiedValue).replaceAll(" "); 653 } else { 654 //for the "LABEL" parameter 655 modifiedValue = newlineRegex.matcher(modifiedValue).replaceAll("\\\\\\n"); 656 } 657 } 658 659 break; 660 } 661 662 if (subTypeValueChanged) { 663 warnings.add("Parameter \"" + subTypeName + "\" of property \"" + typeName + "\" contained one or more characters which are not allowed in vCard " + targetVersion.getVersion() + " parameter values. These characters were removed."); 664 } 665 666 return modifiedValue; 667 } 668 669 private String removeInvalidSubTypeValueChars(String value) { 670 BitSet invalidChars = invalidParamValueChars.get(targetVersion); 671 StringBuilder sb = new StringBuilder(value.length()); 672 673 for (int i = 0; i < value.length(); i++) { 674 char ch = value.charAt(i); 675 if (!invalidChars.get(ch)) { 676 sb.append(ch); 677 } 678 } 679 680 return (sb.length() == value.length()) ? value : sb.toString(); 681 } 682 683 private String applyCaretEncoding(String value) { 684 value = value.replace("^", "^^"); 685 value = newlineRegex.matcher(value).replaceAll("^n"); 686 value = value.replace("\"", "^'"); 687 return value; 688 } 689 690 private boolean supportsTargetVersion(VCardType type) { 691 for (VCardVersion version : type.getSupportedVersions()) { 692 if (version == targetVersion) { 693 return true; 694 } 695 } 696 return false; 697 } 698 699 /** 700 * Closes the underlying {@link Writer} object. 701 */ 702 public void close() throws IOException { 703 writer.close(); 704 } 705 }