001package ezvcard.io.scribe; 002 003import java.time.temporal.Temporal; 004import java.util.Arrays; 005import java.util.List; 006 007import javax.xml.namespace.QName; 008 009import org.w3c.dom.Element; 010 011import com.github.mangstadt.vinnie.io.VObjectPropertyValues; 012 013import ezvcard.VCard; 014import ezvcard.VCardDataType; 015import ezvcard.VCardVersion; 016import ezvcard.io.CannotParseException; 017import ezvcard.io.EmbeddedVCardException; 018import ezvcard.io.ParseContext; 019import ezvcard.io.SkipMeException; 020import ezvcard.io.html.HCardElement; 021import ezvcard.io.json.JCardValue; 022import ezvcard.io.json.JsonValue; 023import ezvcard.io.text.WriteContext; 024import ezvcard.io.xml.XCardElement; 025import ezvcard.io.xml.XCardElement.XCardValue; 026import ezvcard.parameter.VCardParameters; 027import ezvcard.property.VCardProperty; 028import ezvcard.util.VCardDateFormat; 029 030/* 031 Copyright (c) 2012-2023, Michael Angstadt 032 All rights reserved. 033 034 Redistribution and use in source and binary forms, with or without 035 modification, are permitted provided that the following conditions are met: 036 037 1. Redistributions of source code must retain the above copyright notice, this 038 list of conditions and the following disclaimer. 039 2. Redistributions in binary form must reproduce the above copyright notice, 040 this list of conditions and the following disclaimer in the documentation 041 and/or other materials provided with the distribution. 042 043 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 044 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 045 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 046 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 047 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 048 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 049 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 050 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 051 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 052 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 053 */ 054 055/** 056 * Base class for vCard property scribes (aka "marshallers" or "serializers"). 057 * @param <T> the property class 058 * @author Michael Angstadt 059 */ 060public abstract class VCardPropertyScribe<T extends VCardProperty> { 061 protected final Class<T> clazz; 062 protected final String propertyName; 063 protected final QName qname; 064 065 /** 066 * Creates a new scribe. 067 * @param clazz the property class 068 * @param propertyName the property name (e.g. "FN") 069 */ 070 public VCardPropertyScribe(Class<T> clazz, String propertyName) { 071 this(clazz, propertyName, new QName(VCardVersion.V4_0.getXmlNamespace(), propertyName.toLowerCase())); 072 } 073 074 /** 075 * Creates a new scribe. 076 * @param clazz the property class 077 * @param propertyName the property name (e.g. "FN") 078 * @param qname the XML element name and namespace to use for xCard 079 * documents (by default, the XML element name is set to the lower-cased 080 * property name, and the element namespace is set to the xCard namespace) 081 */ 082 public VCardPropertyScribe(Class<T> clazz, String propertyName, QName qname) { 083 this.clazz = clazz; 084 this.propertyName = propertyName; 085 this.qname = qname; 086 } 087 088 /** 089 * Gets the property class. 090 * @return the property class 091 */ 092 public Class<T> getPropertyClass() { 093 return clazz; 094 } 095 096 /** 097 * Gets the property name. 098 * @return the property name (e.g. "FN") 099 */ 100 public String getPropertyName() { 101 return propertyName; 102 } 103 104 /** 105 * Gets this property's local name and namespace for xCard documents. 106 * @return the XML local name and namespace 107 */ 108 public QName getQName() { 109 return qname; 110 } 111 112 /** 113 * Sanitizes a property's parameters (called before the property is 114 * written). Note that a copy of the parameters is returned so that the 115 * property object does not get modified. 116 * @param property the property 117 * @param version the version of the vCard that is being generated 118 * @param vcard the vCard that the property belongs to 119 * @return the sanitized parameters 120 */ 121 public final VCardParameters prepareParameters(T property, VCardVersion version, VCard vcard) { 122 //make a copy because the property should not get modified when it is marshalled 123 VCardParameters copy = new VCardParameters(property.getParameters()); 124 _prepareParameters(property, copy, version, vcard); 125 return copy; 126 } 127 128 /** 129 * <p> 130 * Determines the property's default data type. 131 * </p> 132 * <p> 133 * When writing a plain-text vCard, if the data type of a property instance 134 * (as determined by the {@link #dataType} method) matches the default data 135 * type, then a VALUE parameter will *not* be written. 136 * </p> 137 * <p> 138 * When parsing a plain-text vCard, if a property has no VALUE parameter, 139 * then the property's default data type will be passed into the 140 * {@link #parseText} method. 141 * </p> 142 * @param version the vCard version 143 * @return the default data type or null if unknown 144 */ 145 public final VCardDataType defaultDataType(VCardVersion version) { 146 return _defaultDataType(version); 147 } 148 149 /** 150 * Determines the data type of a property instance. 151 * @param property the property 152 * @param version the version of the vCard that is being generated 153 * @return the data type or null if unknown 154 */ 155 public final VCardDataType dataType(T property, VCardVersion version) { 156 return _dataType(property, version); 157 } 158 159 /** 160 * Marshals a property's value to a string. 161 * @param property the property 162 * @param context contains information about the vCard being written, such 163 * as the target version 164 * @return the string value 165 * @throws SkipMeException if the property should not be written to the data 166 * stream 167 */ 168 public final String writeText(T property, WriteContext context) { 169 return _writeText(property, context); 170 } 171 172 /** 173 * Marshals a property's value to an XML element (xCard). 174 * @param property the property 175 * @param element the property's XML element. 176 * @throws SkipMeException if the property should not be written to the data 177 * stream 178 */ 179 public final void writeXml(T property, Element element) { 180 XCardElement xCardElement = new XCardElement(element); 181 _writeXml(property, xCardElement); 182 } 183 184 /** 185 * Marshals a property's value to a JSON data stream (jCard). 186 * @param property the property 187 * @return the marshalled value 188 * @throws SkipMeException if the property should not be written to the data 189 * stream 190 */ 191 public final JCardValue writeJson(T property) { 192 return _writeJson(property); 193 } 194 195 /** 196 * Unmarshals a property from a plain-text vCard. 197 * @param value the value as read off the wire 198 * @param dataType the data type of the property value. The property's VALUE 199 * parameter is used to determine the data type. If the property has no 200 * VALUE parameter, then this parameter will be set to the property's 201 * default datatype, as determined by the {@link #defaultDataType} method. 202 * Note that the VALUE parameter is removed from the property's parameter 203 * list after it has been read. 204 * @param parameters the parsed parameters 205 * @param context the parse context 206 * @return the unmarshalled property 207 * @throws CannotParseException if the marshaller could not parse the 208 * property's value 209 * @throws SkipMeException if the property should not be added to the final 210 * {@link VCard} object 211 * @throws EmbeddedVCardException if the property value is an embedded vCard 212 * (i.e. the AGENT property) 213 */ 214 public final T parseText(String value, VCardDataType dataType, VCardParameters parameters, ParseContext context) { 215 T property = _parseText(value, dataType, parameters, context); 216 property.setParameters(parameters); 217 return property; 218 } 219 220 /** 221 * Unmarshals a property's value from an XML document (xCard). 222 * @param element the property's XML element 223 * @param parameters the parsed parameters 224 * @param context the parse context 225 * @return the unmarshalled property 226 * @throws CannotParseException if the marshaller could not parse the 227 * property's value 228 * @throws SkipMeException if the property should not be added to the final 229 * {@link VCard} object 230 */ 231 public final T parseXml(Element element, VCardParameters parameters, ParseContext context) { 232 T property = _parseXml(new XCardElement(element), parameters, context); 233 property.setParameters(parameters); 234 return property; 235 } 236 237 /** 238 * Unmarshals the property from an HTML document (hCard). 239 * @param element the property's HTML element 240 * @param context the parse context 241 * @return the unmarshalled property 242 * @throws CannotParseException if the property value could not be parsed 243 * @throws SkipMeException if this type should NOT be added to the 244 * {@link VCard} object 245 * @throws EmbeddedVCardException if the property value is an embedded vCard 246 * (i.e. the AGENT property) 247 */ 248 public final T parseHtml(HCardElement element, ParseContext context) { 249 return _parseHtml(element, context); 250 } 251 252 /** 253 * Unmarshals a property's value from a JSON data stream (jCard). 254 * @param value the property's JSON value 255 * @param dataType the data type 256 * @param parameters the parsed parameters 257 * @param context the parse context 258 * @return the unmarshalled property 259 * @throws CannotParseException if the marshaller could not parse the 260 * property's value 261 * @throws SkipMeException if the property should not be added to the final 262 * {@link VCard} object 263 */ 264 public final T parseJson(JCardValue value, VCardDataType dataType, VCardParameters parameters, ParseContext context) { 265 T property = _parseJson(value, dataType, parameters, context); 266 property.setParameters(parameters); 267 return property; 268 } 269 270 /** 271 * <p> 272 * Sanitizes a property's parameters before the property is written. 273 * </p> 274 * <p> 275 * This method should be overridden by child classes that wish to tweak the 276 * property's parameters before the property is written. The default 277 * implementation of this method does nothing. 278 * </p> 279 * @param property the property 280 * @param copy the list of parameters to make modifications to (it is a copy 281 * of the property's parameters) 282 * @param version the version of the vCard that is being generated 283 * @param vcard the vCard that the property belongs to 284 */ 285 protected void _prepareParameters(T property, VCardParameters copy, VCardVersion version, VCard vcard) { 286 //do nothing 287 } 288 289 /** 290 * <p> 291 * Determines the property's default data type. 292 * </p> 293 * <p> 294 * When writing a plain-text vCard, if the data type of a property instance 295 * (as determined by the {@link #dataType} method) matches the default data 296 * type, then a VALUE parameter will *not* be written. 297 * </p> 298 * <p> 299 * When parsing a plain-text vCard, if a property has no VALUE parameter, 300 * then the property's default data type will be passed into the 301 * {@link #parseText} method. 302 * </p> 303 * @param version the vCard version 304 * @return the default data type or null if unknown 305 */ 306 protected abstract VCardDataType _defaultDataType(VCardVersion version); 307 308 /** 309 * <p> 310 * Determines the data type of a property instance. 311 * </p> 312 * <p> 313 * This method should be overridden by child classes if a property's data 314 * type changes depending on its value. The default implementation of this 315 * method calls {@link #_defaultDataType}. 316 * </p> 317 * @param property the property 318 * @param version the version of the vCard that is being generated 319 * @return the data type or null if unknown 320 */ 321 protected VCardDataType _dataType(T property, VCardVersion version) { 322 return _defaultDataType(version); 323 } 324 325 /** 326 * Marshals a property's value to a string. 327 * @param property the property 328 * @param context contains information about the vCard being written, such 329 * as the target version 330 * @return the marshalled value 331 * @throws SkipMeException if the property should not be written to the data 332 * stream 333 */ 334 protected abstract String _writeText(T property, WriteContext context); 335 336 /** 337 * <p> 338 * Marshals a property's value to an XML element (xCard). 339 * </p> 340 * <p> 341 * This method should be overridden by child classes that wish to support 342 * xCard. The default implementation of this method will append one child 343 * element to the property's XML element. The child element's name will be 344 * that of the property's data type (retrieved using the {@link #dataType} 345 * method), and the child element's text content will be set to the 346 * property's marshalled plain-text value (retrieved using the 347 * {@link #writeText} method). 348 * </p> 349 * @param property the property 350 * @param element the property's XML element 351 * @throws SkipMeException if the property should not be written to the data 352 * stream 353 */ 354 protected void _writeXml(T property, XCardElement element) { 355 String value = writeText(property, new WriteContext(VCardVersion.V4_0, null, false)); 356 VCardDataType dataType = dataType(property, VCardVersion.V4_0); 357 element.append(dataType, value); 358 } 359 360 /** 361 * <p> 362 * Marshals a property's value to a JSON data stream (jCard). 363 * </p> 364 * <p> 365 * This method should be overridden by child classes that wish to support 366 * jCard. The default implementation of this method will create a jCard 367 * property that has a single JSON string value (generated by the 368 * {@link #writeText} method). 369 * </p> 370 * @param property the property 371 * @return the marshalled value 372 * @throws SkipMeException if the property should not be written to the data 373 * stream 374 */ 375 protected JCardValue _writeJson(T property) { 376 String value = writeText(property, new WriteContext(VCardVersion.V4_0, null, false)); 377 return JCardValue.single(value); 378 } 379 380 /** 381 * Unmarshals a property from a plain-text vCard. 382 * @param value the value as read off the wire 383 * @param dataType the data type of the property value. The property's VALUE 384 * parameter is used to determine the data type. If the property has no 385 * VALUE parameter, then this parameter will be set to the property's 386 * default datatype, as determined by the {@link #defaultDataType} method. 387 * Note that the VALUE parameter is removed from the property's parameter 388 * list after it has been read. 389 * @param parameters the parsed parameters. These parameters will be 390 * assigned to the property object once this method returns. Therefore, do 391 * not assign any parameters to the property object itself whilst inside of 392 * this method, or else they will be overwritten. 393 * @param context the parse context 394 * @return the unmarshalled property object 395 * @throws CannotParseException if the marshaller could not parse the 396 * property's value 397 * @throws SkipMeException if the property should not be added to the final 398 * {@link VCard} object 399 */ 400 protected abstract T _parseText(String value, VCardDataType dataType, VCardParameters parameters, ParseContext context); 401 402 /** 403 * <p> 404 * Unmarshals a property from an XML document (xCard). 405 * </p> 406 * <p> 407 * This method should be overridden by child classes that wish to support 408 * xCard. The default implementation of this method will find the first 409 * child element with the xCard namespace. The element's name will be used 410 * as the property's data type and its text content (escaped for inclusion 411 * in a text-based vCard, e.g. escaping comma characters) will be passed 412 * into the {@link #_parseText} method. If no such child element is found, 413 * then the parent element's text content will be passed into 414 * {@link #_parseText} and the data type will be {@code null}. 415 * </p> 416 * @param element the property's XML element 417 * @param parameters the parsed parameters. These parameters will be 418 * assigned to the property object once this method returns. Therefore, do 419 * not assign any parameters to the property object itself whilst inside of 420 * this method, or else they will be overwritten. 421 * @param context the parse context 422 * @return the unmarshalled property object 423 * @throws CannotParseException if the marshaller could not parse the 424 * property's value 425 * @throws SkipMeException if the property should not be added to the final 426 * {@link VCard} object 427 */ 428 protected T _parseXml(XCardElement element, VCardParameters parameters, ParseContext context) { 429 XCardValue firstValue = element.firstValue(); 430 VCardDataType dataType = firstValue.getDataType(); 431 String value = VObjectPropertyValues.escape(firstValue.getValue()); 432 return _parseText(value, dataType, parameters, context); 433 } 434 435 /** 436 * <p> 437 * Unmarshals the property from an hCard (HTML document). 438 * </p> 439 * <p> 440 * This method should be overridden by child classes that wish to support 441 * hCard. The default implementation of this method will retrieve the HTML 442 * element's hCard value (as described in {@link HCardElement#value()}), and 443 * pass it into the {@link #_parseText} method. 444 * </p> 445 * @param element the property's HTML element 446 * @param context the parse context 447 * @return the unmarshalled property object 448 * @throws CannotParseException if the property value could not be parsed 449 * @throws SkipMeException if this property should NOT be added to the 450 * {@link VCard} object 451 * @throws EmbeddedVCardException if the value of this property is an 452 * embedded vCard (i.e. the AGENT property) 453 */ 454 protected T _parseHtml(HCardElement element, ParseContext context) { 455 String value = VObjectPropertyValues.escape(element.value()); 456 VCardParameters parameters = new VCardParameters(); 457 T property = _parseText(value, null, parameters, context); 458 property.setParameters(parameters); 459 return property; 460 } 461 462 /** 463 * <p> 464 * Unmarshals a property from a JSON data stream (jCard). 465 * </p> 466 * <p> 467 * This method should be overridden by child classes that wish to support 468 * jCard. The default implementation of this method will convert the jCard 469 * property value to a string and pass it into the {@link #_parseText} 470 * method. 471 * </p> 472 * 473 * <hr> 474 * 475 * <p> 476 * The following paragraphs describe the way in which this method's default 477 * implementation converts a jCard value to a string: 478 * </p> 479 * <p> 480 * If the jCard value consists of a single, non-array, non-object value, 481 * then the value is converted to a string. Special characters (backslashes, 482 * commas, and semicolons) are escaped in order to simulate what the value 483 * might look like in a plain-text vCard.<br> 484 * <code>["x-foo", {}, "text", "the;value"] --> "the\;value"</code><br> 485 * <code>["x-foo", {}, "text", 2] --> "2"</code> 486 * </p> 487 * <p> 488 * If the jCard value consists of multiple, non-array, non-object values, 489 * then all the values are appended together in a single string, separated 490 * by commas. Special characters (backslashes, commas, and semicolons) are 491 * escaped for each value in order to prevent commas from being treated as 492 * delimiters, and to simulate what the value might look like in a 493 * plain-text vCard.<br> 494 * <code>["x-foo", {}, "text", "one", "two,three"] --> 495 * "one,two\,three"</code> 496 * </p> 497 * <p> 498 * If the jCard value is a single array, then this array is treated as a 499 * "structured value", and converted its plain-text representation. Special 500 * characters (backslashes, commas, and semicolons) are escaped for each 501 * value in order to prevent commas and semicolons from being treated as 502 * delimiters.<br> 503 * <code>["x-foo", {}, "text", ["one", ["two", "three"], "four;five"]] 504 * --> "one;two,three;four\;five"</code> 505 * </p> 506 * <p> 507 * If the jCard value starts with a JSON object, then it is converted to an 508 * empty string (JSON objects are not supported by this method).<br> 509 * <code>["x-foo", , "text", {"one": 1}] --> ""}</code> 510 * </p> 511 * <p> 512 * For all other cases, behavior is undefined. 513 * </p> 514 * @param value the property's JSON value 515 * @param dataType the data type 516 * @param parameters the parsed parameters. These parameters will be 517 * assigned to the property object once this method returns. Therefore, do 518 * not assign any parameters to the property object itself whilst inside of 519 * this method, or else they will be overwritten. 520 * @param context the parse context 521 * @return the unmarshalled property object 522 * @throws CannotParseException if the marshaller could not parse the 523 * property's value 524 * @throws SkipMeException if the property should not be added to the final 525 * {@link VCard} object 526 */ 527 protected T _parseJson(JCardValue value, VCardDataType dataType, VCardParameters parameters, ParseContext context) { 528 String valueStr = jcardValueToString(value); 529 return _parseText(valueStr, dataType, parameters, context); 530 } 531 532 /** 533 * Converts a jCard value to its plain-text format representation. 534 * @param value the jCard value 535 * @return the plain-text format representation (for example, "1,2,3" for a 536 * list of values) 537 */ 538 private static String jcardValueToString(JCardValue value) { 539 List<JsonValue> values = value.getValues(); 540 if (values.size() > 1) { 541 List<String> multi = value.asMulti(); 542 if (!multi.isEmpty()) { 543 return VObjectPropertyValues.writeList(multi); 544 } 545 } 546 547 if (!values.isEmpty() && values.get(0).getArray() != null) { 548 List<List<String>> structured = value.asStructured(); 549 if (!structured.isEmpty()) { 550 return VObjectPropertyValues.writeStructured(structured, true); 551 } 552 } 553 554 return VObjectPropertyValues.escape(value.asSingle()); 555 } 556 557 /** 558 * Parses a date string. 559 * @param value the date string 560 * @return the parsed date 561 * @throws IllegalArgumentException if the date cannot be parsed 562 */ 563 protected static Temporal date(String value) { 564 return VCardDateFormat.parse(value); 565 } 566 567 /** 568 * Formats a {@link Temporal} object as a string. 569 * @param date the date 570 * @return a helper object for customizing the write operation 571 */ 572 protected static DateWriter date(Temporal date) { 573 return new DateWriter(date); 574 } 575 576 /** 577 * A helper class for writing date values. 578 */ 579 protected static class DateWriter { 580 private Temporal date; 581 private boolean extended = false; 582 583 /** 584 * Creates a new date writer object. 585 * @param date the date to format 586 */ 587 public DateWriter(Temporal date) { 588 this.date = date; 589 } 590 591 /** 592 * Sets whether to use extended format or basic. 593 * @param extended true to use extended format, false to use basic 594 * (defaults to "false") 595 * @return this 596 */ 597 public DateWriter extended(boolean extended) { 598 this.extended = extended; 599 return this; 600 } 601 602 /** 603 * Creates the date string. 604 * @return the date string 605 */ 606 public String write() { 607 VCardDateFormat format = extended ? VCardDateFormat.EXTENDED : VCardDateFormat.BASIC; 608 return format.format(date); 609 } 610 } 611 612 /** 613 * Creates a {@link CannotParseException} to indicate that a scribe could 614 * not find the necessary XML elements required in order to successfully 615 * parse a property (xCards only). 616 * @param dataTypes the expected data types (null for "unknown") 617 * @return the exception object (note that the exception is NOT thrown!) 618 */ 619 protected static CannotParseException missingXmlElements(VCardDataType... dataTypes) { 620 String[] elements = new String[dataTypes.length]; 621 for (int i = 0; i < dataTypes.length; i++) { 622 VCardDataType dataType = dataTypes[i]; 623 elements[i] = (dataType == null) ? "unknown" : dataType.getName().toLowerCase(); 624 } 625 return missingXmlElements(elements); 626 } 627 628 /** 629 * Creates a {@link CannotParseException} to indicate that a scribe could 630 * not find the necessary XML elements required in order to successfully 631 * parse a property (xCards only). 632 * @param elements the names of the expected XML elements. 633 * @return the exception object (note that the exception is NOT thrown!) 634 */ 635 protected static CannotParseException missingXmlElements(String... elements) { 636 return new CannotParseException(0, Arrays.toString(elements)); 637 } 638 639 /** 640 * A utility method for switching between the "PREF" and "TYPE=PREF" 641 * parameters when marshalling a property (version 4.0 vCards use "PREF=1", 642 * while version 3.0 vCards use "TYPE=PREF"). This method is meant to be 643 * called from a scribe's {@link #_prepareParameters} method. 644 * @param property the property that is being marshalled 645 * @param parameters the parameters that are being marshalled (this should 646 * be a copy of the property's parameters so that changes can be made to 647 * them without affecting the original object) 648 * @param version the vCard version that the vCard is being marshalled to 649 * @param vcard the vCard that's being marshalled 650 */ 651 protected static void handlePrefParam(VCardProperty property, VCardParameters parameters, VCardVersion version, VCard vcard) { 652 switch (version) { 653 case V2_1: 654 case V3_0: 655 parameters.setPref(null); 656 657 //find the property with the lowest PREF value in the vCard 658 VCardProperty mostPreferred = null; 659 Integer lowestPref = null; 660 for (VCardProperty p : vcard.getProperties(property.getClass())) { 661 Integer pref; 662 try { 663 pref = p.getParameters().getPref(); 664 } catch (IllegalStateException e) { 665 continue; 666 } 667 668 if (pref == null) { 669 continue; 670 } 671 672 if (lowestPref == null || pref < lowestPref) { 673 mostPreferred = p; 674 lowestPref = pref; 675 } 676 } 677 678 if (property == mostPreferred) { 679 parameters.put(VCardParameters.TYPE, "pref"); 680 } 681 682 break; 683 case V4_0: 684 for (String type : property.getParameters().get(VCardParameters.TYPE)) { 685 if ("pref".equalsIgnoreCase(type)) { 686 parameters.remove(VCardParameters.TYPE, type); 687 parameters.setPref(1); 688 break; 689 } 690 } 691 break; 692 } 693 } 694 695 /** 696 * <p> 697 * Escapes special characters in a property value for writing to a 698 * plain-text output stream. 699 * </p> 700 * <p> 701 * If the target version is 2.1, then the value will be returned unchanged. 702 * 2.1 only requires special characters to be escaped within properties that 703 * make use of those special characters. 704 * </p> 705 * @param value the value to escape 706 * @param context the write context 707 * @return the escaped value 708 */ 709 protected static String escape(String value, WriteContext context) { 710 if (context.getVersion() == VCardVersion.V2_1) { 711 return value; 712 } 713 714 return VObjectPropertyValues.escape(value); 715 } 716}