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