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"] --&gt; "the\;value"</code><br>
485         * <code>["x-foo", {}, "text", 2] --&gt; "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"] --&gt;
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         * --&gt; "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}] --&gt; ""}</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}