001package ezvcard.io.scribe;
002
003import static ezvcard.util.StringUtils.join;
004
005import java.util.List;
006
007import com.github.mangstadt.vinnie.io.VObjectPropertyValues.SemiStructuredValueBuilder;
008import com.github.mangstadt.vinnie.io.VObjectPropertyValues.SemiStructuredValueIterator;
009import com.github.mangstadt.vinnie.io.VObjectPropertyValues.StructuredValueBuilder;
010import com.github.mangstadt.vinnie.io.VObjectPropertyValues.StructuredValueIterator;
011
012import ezvcard.VCard;
013import ezvcard.VCardDataType;
014import ezvcard.VCardVersion;
015import ezvcard.io.ParseContext;
016import ezvcard.io.html.HCardElement;
017import ezvcard.io.json.JCardValue;
018import ezvcard.io.text.WriteContext;
019import ezvcard.io.xml.XCardElement;
020import ezvcard.parameter.VCardParameters;
021import ezvcard.property.Address;
022
023/*
024 Copyright (c) 2012-2023, Michael Angstadt
025 All rights reserved.
026
027 Redistribution and use in source and binary forms, with or without
028 modification, are permitted provided that the following conditions are met: 
029
030 1. Redistributions of source code must retain the above copyright notice, this
031 list of conditions and the following disclaimer. 
032 2. Redistributions in binary form must reproduce the above copyright notice,
033 this list of conditions and the following disclaimer in the documentation
034 and/or other materials provided with the distribution. 
035
036 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
037 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
038 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
039 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
040 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
041 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
042 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
043 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
044 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
045 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
046 */
047
048/**
049 * Marshals {@link Address} properties.
050 * @author Michael Angstadt
051 */
052public class AddressScribe extends VCardPropertyScribe<Address> {
053        public AddressScribe() {
054                super(Address.class, "ADR");
055        }
056
057        @Override
058        protected VCardDataType _defaultDataType(VCardVersion version) {
059                return VCardDataType.TEXT;
060        }
061
062        @Override
063        protected void _prepareParameters(Address property, VCardParameters copy, VCardVersion version, VCard vcard) {
064                handlePrefParam(property, copy, version, vcard);
065
066                if (version == VCardVersion.V2_1 || version == VCardVersion.V3_0) {
067                        /*
068                         * Remove the LABEL parameter. By the time this line of code is
069                         * reached, VCardWriter will have created a LABEL property from this
070                         * property's LABEL parameter
071                         */
072                        copy.setLabel(null);
073                }
074        }
075
076        @Override
077        protected String _writeText(Address property, WriteContext context) {
078                /*
079                 * StructuredValueBuilder cannot be used with 2.1 because it escapes
080                 * comma characters. For example, if someone's street address is
081                 * "Foo,bar Lane", the comma character must NOT be escaped when written
082                 * to a 2.1 vCard.
083                 * 
084                 * The reason commas are not escaped in 2.1 is because 2.1 does not
085                 * allow multi-valued components like 3.0 and 4.0 do (for example,
086                 * multiple street addresses).
087                 * 
088                 * If an Address object has multi-valued components, and it is being
089                 * written to a 2.1 vCard, then ez-vcard will comma-delimit them to
090                 * prevent data loss. But this is not part of the 2.1 syntax.
091                 */
092                if (context.getVersion() == VCardVersion.V2_1) {
093                        SemiStructuredValueBuilder builder = new SemiStructuredValueBuilder();
094                        builder.append(join(property.getPoBoxes(), ","));
095                        builder.append(join(property.getExtendedAddresses(), ","));
096                        builder.append(join(property.getStreetAddresses(), ","));
097                        builder.append(join(property.getLocalities(), ","));
098                        builder.append(join(property.getRegions(), ","));
099                        builder.append(join(property.getPostalCodes(), ","));
100                        builder.append(join(property.getCountries(), ","));
101                        return builder.build(false, context.isIncludeTrailingSemicolons());
102                } else {
103                        StructuredValueBuilder builder = new StructuredValueBuilder();
104                        builder.append(property.getPoBoxes());
105                        builder.append(property.getExtendedAddresses());
106                        builder.append(property.getStreetAddresses());
107                        builder.append(property.getLocalities());
108                        builder.append(property.getRegions());
109                        builder.append(property.getPostalCodes());
110                        builder.append(property.getCountries());
111                        return builder.build(context.isIncludeTrailingSemicolons());
112                }
113        }
114
115        @Override
116        protected Address _parseText(String value, VCardDataType dataType, VCardParameters parameters, ParseContext context) {
117                if (context.getVersion() == VCardVersion.V2_1) {
118                        /*
119                         * 2.1 does not recognize multi-valued components.
120                         */
121                        SemiStructuredValueIterator it = new SemiStructuredValueIterator(value);
122                        return parseSemiStructuredValue(it);
123                } else {
124                        StructuredValueIterator it = new StructuredValueIterator(value);
125                        return parseStructuredValue(it);
126                }
127        }
128
129        @Override
130        protected void _writeXml(Address property, XCardElement parent) {
131                parent.append("pobox", property.getPoBoxes()); //Note: The XML element must always be added, even if the value is null
132                parent.append("ext", property.getExtendedAddresses());
133                parent.append("street", property.getStreetAddresses());
134                parent.append("locality", property.getLocalities());
135                parent.append("region", property.getRegions());
136                parent.append("code", property.getPostalCodes());
137                parent.append("country", property.getCountries());
138        }
139
140        @Override
141        protected Address _parseXml(XCardElement element, VCardParameters parameters, ParseContext context) {
142                Address property = new Address();
143                property.getPoBoxes().addAll(sanitizeXml(element, "pobox"));
144                property.getExtendedAddresses().addAll(sanitizeXml(element, "ext"));
145                property.getStreetAddresses().addAll(sanitizeXml(element, "street"));
146                property.getLocalities().addAll(sanitizeXml(element, "locality"));
147                property.getRegions().addAll(sanitizeXml(element, "region"));
148                property.getPostalCodes().addAll(sanitizeXml(element, "code"));
149                property.getCountries().addAll(sanitizeXml(element, "country"));
150                return property;
151        }
152
153        private List<String> sanitizeXml(XCardElement element, String name) {
154                return element.all(name);
155        }
156
157        @Override
158        protected Address _parseHtml(HCardElement element, ParseContext context) {
159                Address property = new Address();
160                property.getPoBoxes().addAll(element.allValues("post-office-box"));
161                property.getExtendedAddresses().addAll(element.allValues("extended-address"));
162                property.getStreetAddresses().addAll(element.allValues("street-address"));
163                property.getLocalities().addAll(element.allValues("locality"));
164                property.getRegions().addAll(element.allValues("region"));
165                property.getPostalCodes().addAll(element.allValues("postal-code"));
166                property.getCountries().addAll(element.allValues("country-name"));
167
168                List<String> types = element.types();
169                property.getParameters().putAll(VCardParameters.TYPE, types);
170
171                return property;
172        }
173
174        @Override
175        protected JCardValue _writeJson(Address property) {
176                //@formatter:off
177                return JCardValue.structured(
178                        property.getPoBoxes(),
179                        property.getExtendedAddresses(),
180                        property.getStreetAddresses(),
181                        property.getLocalities(),
182                        property.getRegions(),
183                        property.getPostalCodes(),
184                        property.getCountries()
185                );
186                //@formatter:on
187        }
188
189        @Override
190        protected Address _parseJson(JCardValue value, VCardDataType dataType, VCardParameters parameters, ParseContext context) {
191                StructuredValueIterator it = new StructuredValueIterator(value.asStructured());
192                return parseStructuredValue(it);
193        }
194
195        private static Address parseStructuredValue(StructuredValueIterator it) {
196                Address property = new Address();
197
198                property.getPoBoxes().addAll(it.nextComponent());
199                property.getExtendedAddresses().addAll(it.nextComponent());
200                property.getStreetAddresses().addAll(it.nextComponent());
201                property.getLocalities().addAll(it.nextComponent());
202                property.getRegions().addAll(it.nextComponent());
203                property.getPostalCodes().addAll(it.nextComponent());
204                property.getCountries().addAll(it.nextComponent());
205
206                return property;
207        }
208
209        private static Address parseSemiStructuredValue(SemiStructuredValueIterator it) {
210                Address property = new Address();
211
212                String next = it.next();
213                if (next != null) {
214                        property.getPoBoxes().add(next);
215                }
216
217                next = it.next();
218                if (next != null) {
219                        property.getExtendedAddresses().add(next);
220                }
221
222                next = it.next();
223                if (next != null) {
224                        property.getStreetAddresses().add(next);
225                }
226
227                next = it.next();
228                if (next != null) {
229                        property.getLocalities().add(next);
230                }
231
232                next = it.next();
233                if (next != null) {
234                        property.getRegions().add(next);
235                }
236
237                next = it.next();
238                if (next != null) {
239                        property.getPostalCodes().add(next);
240                }
241
242                next = it.next();
243                if (next != null) {
244                        property.getCountries().add(next);
245                }
246
247                return property;
248        }
249}