001package ezvcard.io.scribe;
002
003import static ezvcard.util.StringUtils.join;
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.VCardDataType;
011import ezvcard.VCardVersion;
012import ezvcard.io.ParseContext;
013import ezvcard.io.html.HCardElement;
014import ezvcard.io.json.JCardValue;
015import ezvcard.io.text.WriteContext;
016import ezvcard.io.xml.XCardElement;
017import ezvcard.parameter.VCardParameters;
018import ezvcard.property.StructuredName;
019
020/*
021 Copyright (c) 2012-2023, Michael Angstadt
022 All rights reserved.
023
024 Redistribution and use in source and binary forms, with or without
025 modification, are permitted provided that the following conditions are met: 
026
027 1. Redistributions of source code must retain the above copyright notice, this
028 list of conditions and the following disclaimer. 
029 2. Redistributions in binary form must reproduce the above copyright notice,
030 this list of conditions and the following disclaimer in the documentation
031 and/or other materials provided with the distribution. 
032
033 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
034 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
035 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
036 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
037 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
038 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
039 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
040 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
041 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
042 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
043 */
044
045/**
046 * Marshals {@link StructuredName} properties.
047 * @author Michael Angstadt
048 */
049public class StructuredNameScribe extends VCardPropertyScribe<StructuredName> {
050        public StructuredNameScribe() {
051                super(StructuredName.class, "N");
052        }
053
054        @Override
055        protected VCardDataType _defaultDataType(VCardVersion version) {
056                return VCardDataType.TEXT;
057        }
058
059        @Override
060        protected String _writeText(StructuredName property, WriteContext context) {
061                /*
062                 * StructuredValueBuilder cannot be used with 2.1 because it escapes
063                 * comma characters. For example, if someone's last name is "Foo,bar",
064                 * the comma character must NOT be escaped when written to a 2.1 vCard.
065                 * 
066                 * The reason commas are not escaped in 2.1 is because 2.1 does not
067                 * allow multi-valued components like 3.0 and 4.0 do (for example,
068                 * multiple suffixes).
069                 * 
070                 * If a StructuredName object has multi-valued components, and it is
071                 * being written to a 2.1 vCard, then ez-vcard will comma-delimit them
072                 * to prevent data loss. But this is not part of the 2.1 syntax.
073                 */
074                if (context.getVersion() == VCardVersion.V2_1) {
075                        SemiStructuredValueBuilder builder = new SemiStructuredValueBuilder();
076                        builder.append(property.getFamily());
077                        builder.append(property.getGiven());
078                        builder.append(join(property.getAdditionalNames(), ","));
079                        builder.append(join(property.getPrefixes(), ","));
080                        builder.append(join(property.getSuffixes(), ","));
081                        return builder.build(false, context.isIncludeTrailingSemicolons());
082                } else {
083                        StructuredValueBuilder builder = new StructuredValueBuilder();
084                        builder.append(property.getFamily());
085                        builder.append(property.getGiven());
086                        builder.append(property.getAdditionalNames());
087                        builder.append(property.getPrefixes());
088                        builder.append(property.getSuffixes());
089                        return builder.build(context.isIncludeTrailingSemicolons());
090                }
091        }
092
093        @Override
094        protected StructuredName _parseText(String value, VCardDataType dataType, VCardParameters parameters, ParseContext context) {
095                StructuredName property = new StructuredName();
096
097                if (context.getVersion() == VCardVersion.V2_1) {
098                        /*
099                         * 2.1 does not recognize multi-valued components.
100                         */
101                        SemiStructuredValueIterator it = new SemiStructuredValueIterator(value);
102                        property.setFamily(it.next());
103                        property.setGiven(it.next());
104
105                        String next = it.next();
106                        if (next != null) {
107                                property.getAdditionalNames().add(next);
108                        }
109
110                        next = it.next();
111                        if (next != null) {
112                                property.getPrefixes().add(next);
113                        }
114
115                        next = it.next();
116                        if (next != null) {
117                                property.getSuffixes().add(next);
118                        }
119                } else {
120                        StructuredValueIterator it = new StructuredValueIterator(value);
121                        property.setFamily(it.nextValue());
122                        property.setGiven(it.nextValue());
123                        property.getAdditionalNames().addAll(it.nextComponent());
124                        property.getPrefixes().addAll(it.nextComponent());
125                        property.getSuffixes().addAll(it.nextComponent());
126                }
127
128                return property;
129        }
130
131        @Override
132        protected void _writeXml(StructuredName property, XCardElement parent) {
133                parent.append("surname", property.getFamily()); //the XML element still needs to be printed if value == null
134                parent.append("given", property.getGiven());
135                parent.append("additional", property.getAdditionalNames());
136                parent.append("prefix", property.getPrefixes());
137                parent.append("suffix", property.getSuffixes());
138        }
139
140        @Override
141        protected StructuredName _parseXml(XCardElement element, VCardParameters parameters, ParseContext context) {
142                StructuredName property = new StructuredName();
143
144                property.setFamily(s(element.first("surname")));
145                property.setGiven(s(element.first("given")));
146                property.getAdditionalNames().addAll(element.all("additional"));
147                property.getPrefixes().addAll(element.all("prefix"));
148                property.getSuffixes().addAll(element.all("suffix"));
149
150                return property;
151        }
152
153        private static String s(String value) {
154                return (value == null || value.isEmpty()) ? null : value;
155        }
156
157        @Override
158        protected StructuredName _parseHtml(HCardElement element, ParseContext context) {
159                StructuredName property = new StructuredName();
160
161                property.setFamily(s(element.firstValue("family-name")));
162                property.setGiven(s(element.firstValue("given-name")));
163                property.getAdditionalNames().addAll(element.allValues("additional-name"));
164                property.getPrefixes().addAll(element.allValues("honorific-prefix"));
165                property.getSuffixes().addAll(element.allValues("honorific-suffix"));
166
167                return property;
168        }
169
170        @Override
171        protected JCardValue _writeJson(StructuredName property) {
172                return JCardValue.structured(property.getFamily(), property.getGiven(), property.getAdditionalNames(), property.getPrefixes(), property.getSuffixes());
173        }
174
175        @Override
176        protected StructuredName _parseJson(JCardValue value, VCardDataType dataType, VCardParameters parameters, ParseContext context) {
177                StructuredName property = new StructuredName();
178                StructuredValueIterator it = new StructuredValueIterator(value.asStructured());
179
180                property.setFamily(it.nextValue());
181                property.setGiven(it.nextValue());
182                property.getAdditionalNames().addAll(it.nextComponent());
183                property.getPrefixes().addAll(it.nextComponent());
184                property.getSuffixes().addAll(it.nextComponent());
185
186                return property;
187        }
188}