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}