001package ezvcard.io.scribe;
002
003import com.github.mangstadt.vinnie.io.VObjectPropertyValues;
004
005import ezvcard.VCardDataType;
006import ezvcard.VCardVersion;
007import ezvcard.io.CannotParseException;
008import ezvcard.io.ParseContext;
009import ezvcard.io.html.HCardElement;
010import ezvcard.io.json.JCardValue;
011import ezvcard.io.text.WriteContext;
012import ezvcard.io.xml.XCardElement;
013import ezvcard.parameter.VCardParameters;
014import ezvcard.property.Geo;
015import ezvcard.util.GeoUri;
016import ezvcard.util.VCardFloatFormatter;
017
018/*
019 Copyright (c) 2012-2023, Michael Angstadt
020 All rights reserved.
021
022 Redistribution and use in source and binary forms, with or without
023 modification, are permitted provided that the following conditions are met: 
024
025 1. Redistributions of source code must retain the above copyright notice, this
026 list of conditions and the following disclaimer. 
027 2. Redistributions in binary form must reproduce the above copyright notice,
028 this list of conditions and the following disclaimer in the documentation
029 and/or other materials provided with the distribution. 
030
031 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
032 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
033 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
034 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
035 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
036 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
037 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
038 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
039 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
040 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
041 */
042
043/**
044 * Marshals {@link Geo} properties.
045 * @author Michael Angstadt
046 */
047public class GeoScribe extends VCardPropertyScribe<Geo> {
048        public GeoScribe() {
049                super(Geo.class, "GEO");
050        }
051
052        @Override
053        protected VCardDataType _defaultDataType(VCardVersion version) {
054                switch (version) {
055                case V2_1:
056                case V3_0:
057                        return null;
058                case V4_0:
059                        return VCardDataType.URI;
060                }
061                return null;
062        }
063
064        @Override
065        protected String _writeText(Geo property, WriteContext context) {
066                return write(property, context.getVersion());
067        }
068
069        @Override
070        protected Geo _parseText(String value, VCardDataType dataType, VCardParameters parameters, ParseContext context) {
071                if (value.isEmpty()) {
072                        return new Geo((GeoUri) null);
073                }
074
075                switch (context.getVersion()) {
076                case V2_1:
077                case V3_0:
078                        int pos = value.indexOf(';');
079                        if (pos < 0) {
080                                throw new CannotParseException(11);
081                        }
082
083                        String latitudeStr = value.substring(0, pos);
084                        String longitudeStr = value.substring(pos + 1);
085
086                        Double latitude;
087                        try {
088                                latitude = Double.valueOf(latitudeStr);
089                        } catch (NumberFormatException e) {
090                                throw new CannotParseException(8, latitudeStr);
091                        }
092
093                        Double longitude;
094                        try {
095                                longitude = Double.valueOf(longitudeStr);
096                        } catch (NumberFormatException e) {
097                                throw new CannotParseException(10, longitudeStr);
098                        }
099
100                        return new Geo(latitude, longitude);
101                case V4_0:
102                        value = VObjectPropertyValues.unescape(value);
103                        return parseGeoUri(value);
104                }
105                return null;
106        }
107
108        @Override
109        protected void _writeXml(Geo property, XCardElement parent) {
110                parent.append(VCardDataType.URI, write(property, parent.version()));
111        }
112
113        @Override
114        protected Geo _parseXml(XCardElement element, VCardParameters parameters, ParseContext context) {
115                String value = element.first(VCardDataType.URI);
116                if (value != null) {
117                        if (value.isEmpty()) {
118                                return new Geo((GeoUri) null);
119                        }
120                        return parseGeoUri(value);
121                }
122
123                throw missingXmlElements(VCardDataType.URI);
124        }
125
126        @Override
127        protected Geo _parseHtml(HCardElement element, ParseContext context) {
128                String latitudeStr = element.firstValue("latitude");
129                if (latitudeStr == null) {
130                        throw new CannotParseException(7);
131                }
132
133                Double latitude;
134                try {
135                        latitude = Double.parseDouble(latitudeStr);
136                } catch (NumberFormatException e) {
137                        throw new CannotParseException(8, latitudeStr);
138                }
139
140                String longitudeStr = element.firstValue("longitude");
141                if (longitudeStr == null) {
142                        throw new CannotParseException(9);
143                }
144
145                Double longitude;
146                try {
147                        longitude = Double.parseDouble(longitudeStr);
148                } catch (NumberFormatException e) {
149                        throw new CannotParseException(10, longitudeStr);
150                }
151
152                return new Geo(latitude, longitude);
153        }
154
155        @Override
156        protected JCardValue _writeJson(Geo property) {
157                return JCardValue.single(write(property, VCardVersion.V4_0));
158        }
159
160        @Override
161        protected Geo _parseJson(JCardValue value, VCardDataType dataType, VCardParameters parameters, ParseContext context) {
162                String valueStr = value.asSingle();
163                if (valueStr.isEmpty()) {
164                        return new Geo((GeoUri) null);
165                }
166                return parseGeoUri(valueStr);
167        }
168
169        private Geo parseGeoUri(String value) {
170                try {
171                        return new Geo(GeoUri.parse(value));
172                } catch (IllegalArgumentException e) {
173                        throw new CannotParseException(12);
174                }
175        }
176
177        private String write(Geo property, VCardVersion version) {
178                if (property.getGeoUri() == null) {
179                        return "";
180                }
181
182                switch (version) {
183                case V2_1:
184                case V3_0:
185                        VCardFloatFormatter formatter = new VCardFloatFormatter(6);
186                        String latitudeStr = formatter.format(property.getLatitude());
187                        String longitudeStr = formatter.format(property.getLongitude());
188                        return latitudeStr + ';' + longitudeStr;
189                case V4_0:
190                        return property.getGeoUri().toString(6);
191                }
192                return null;
193        }
194}