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}