001package ezvcard.io.xml;
002
003import java.util.ArrayList;
004import java.util.Arrays;
005import java.util.Collection;
006import java.util.Collections;
007import java.util.List;
008
009import org.w3c.dom.Document;
010import org.w3c.dom.Element;
011
012import ezvcard.VCardDataType;
013import ezvcard.VCardVersion;
014import ezvcard.util.XmlUtils;
015
016/*
017 Copyright (c) 2012-2023, Michael Angstadt
018 All rights reserved.
019
020 Redistribution and use in source and binary forms, with or without
021 modification, are permitted provided that the following conditions are met: 
022
023 1. Redistributions of source code must retain the above copyright notice, this
024 list of conditions and the following disclaimer. 
025 2. Redistributions in binary form must reproduce the above copyright notice,
026 this list of conditions and the following disclaimer in the documentation
027 and/or other materials provided with the distribution. 
028
029 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
030 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
031 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
032 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
033 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
034 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
035 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
036 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
037 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
038 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
039
040 The views and conclusions contained in the software and documentation are those
041 of the authors and should not be interpreted as representing official policies, 
042 either expressed or implied, of the FreeBSD Project.
043 */
044
045/**
046 * Wraps xCard functionality around an XML {@link Element} object.
047 * @author Michael Angstadt
048 */
049public class XCardElement {
050        private final Document document;
051        private final Element element;
052        private final VCardVersion version;
053        private final String namespace;
054
055        /**
056         * Wraps an existing XML element.
057         * @param element the XML element
058         */
059        public XCardElement(Element element) {
060                this(element, VCardVersion.V4_0);
061        }
062
063        /**
064         * Wraps an existing XML element.
065         * @param element the XML element
066         * @param version the vCard version
067         */
068        public XCardElement(Element element, VCardVersion version) {
069                this.document = element.getOwnerDocument();
070                this.element = element;
071                this.version = version;
072                namespace = version.getXmlNamespace();
073        }
074
075        /**
076         * Gets the first value with one of the given data types.
077         * @param dataTypes the data type(s) to look for (null signifies the
078         * "unknown" data type)
079         * @return the value or null if not found
080         */
081        public String first(VCardDataType... dataTypes) {
082                String names[] = new String[dataTypes.length];
083                for (int i = 0; i < dataTypes.length; i++) {
084                        VCardDataType dataType = dataTypes[i];
085                        names[i] = toLocalName(dataType);
086                }
087                return first(names);
088        }
089
090        /**
091         * Gets the value of the first child element with one of the given names.
092         * @param names the possible names of the element
093         * @return the element's text or null if not found
094         */
095        public String first(String... names) {
096                List<String> localNamesList = Arrays.asList(names);
097                for (Element child : children()) {
098                        if (localNamesList.contains(child.getLocalName()) && namespace.equals(child.getNamespaceURI())) {
099                                return child.getTextContent();
100                        }
101                }
102                return null;
103        }
104
105        /**
106         * Gets all the values of a given data type.
107         * @param dataType the data type to look for
108         * @return the values
109         */
110        public List<String> all(VCardDataType dataType) {
111                String dataTypeStr = toLocalName(dataType);
112                return all(dataTypeStr);
113        }
114
115        /**
116         * Gets the value of all non-empty child elements that have the given name.
117         * @param localName the element name
118         * @return the values of the child elements
119         */
120        public List<String> all(String localName) {
121                List<String> childrenText = new ArrayList<>();
122                for (Element child : children()) {
123                        if (localName.equals(child.getLocalName()) && namespace.equals(child.getNamespaceURI())) {
124                                String text = child.getTextContent();
125                                if (text.length() > 0) {
126                                        childrenText.add(child.getTextContent());
127                                }
128                        }
129                }
130                return childrenText;
131        }
132
133        /**
134         * Adds a value.
135         * @param dataType the data type or null for the "unknown" data type
136         * @param value the value
137         * @return the created element
138         */
139        public Element append(VCardDataType dataType, String value) {
140                String dataTypeStr = toLocalName(dataType);
141                return append(dataTypeStr, value);
142        }
143
144        /**
145         * Adds a child element.
146         * @param name the name of the child element
147         * @param value the value of the child element.
148         * @return the created element
149         */
150        public Element append(String name, String value) {
151                Element child = document.createElementNS(namespace, name);
152                child.setTextContent(value);
153                element.appendChild(child);
154                return child;
155        }
156
157        /**
158         * Adds multiple child elements, each with the same name.
159         * @param name the name for all the child elements
160         * @param values the values of each child element
161         * @return the created elements
162         */
163        public List<Element> append(String name, Collection<String> values) {
164                if (values.isEmpty()) {
165                        Element element = append(name, (String) null);
166                        return Collections.singletonList(element);
167                }
168
169                List<Element> elements = new ArrayList<>(values.size());
170                for (String value : values) {
171                        elements.add(append(name, value));
172                }
173                return elements;
174        }
175
176        /**
177         * Gets the owner document.
178         * @return the owner document
179         */
180        public Document document() {
181                return document;
182        }
183
184        /**
185         * Gets the wrapped XML element.
186         * @return the wrapped XML element
187         */
188        public Element element() {
189                return element;
190        }
191
192        /**
193         * Gets the vCard version.
194         * @return the vCard version
195         */
196        public VCardVersion version() {
197                return version;
198        }
199
200        /**
201         * Gets the child elements of the XML element.
202         * @return the child elements
203         */
204        private List<Element> children() {
205                return XmlUtils.toElementList(element.getChildNodes());
206        }
207
208        /**
209         * Finds the first child element that has the xCard namespace and returns
210         * its data type and value. If no such element is found, the parent
211         * {@link XCardElement}'s text content, along with a null data type, is
212         * returned.
213         * @return the value and data type
214         */
215        public XCardValue firstValue() {
216                String elementNamespace = version.getXmlNamespace();
217                for (Element child : children()) {
218                        String childNamespace = child.getNamespaceURI();
219                        if (elementNamespace.equals(childNamespace)) {
220                                VCardDataType dataType = toDataType(child.getLocalName());
221                                String value = child.getTextContent();
222                                return new XCardValue(dataType, value);
223                        }
224                }
225
226                return new XCardValue(null, element.getTextContent());
227        }
228
229        /**
230         * Gets the appropriate XML local name of a {@link VCardDataType} object.
231         * @param dataType the data type or null for "unknown"
232         * @return the local name (e.g. "text")
233         */
234        private static String toLocalName(VCardDataType dataType) {
235                return (dataType == null) ? "unknown" : dataType.getName().toLowerCase();
236        }
237
238        /**
239         * Converts an XML local name to the appropriate {@link VCardDataType}
240         * object.
241         * @param localName the local name (e.g. "text")
242         * @return the data type or null for "unknown"
243         */
244        private static VCardDataType toDataType(String localName) {
245                return "unknown".equals(localName) ? null : VCardDataType.get(localName);
246        }
247
248        /**
249         * Represents the data type and value of a child element under an
250         * {@link XCardElement}.
251         */
252        public static class XCardValue {
253                private final VCardDataType dataType;
254                private final String value;
255
256                /**
257                 * @param dataType the data type or null if "unknown"
258                 * @param value the value
259                 */
260                public XCardValue(VCardDataType dataType, String value) {
261                        this.dataType = dataType;
262                        this.value = value;
263                }
264
265                /**
266                 * Gets the data type
267                 * @return the data type or null if "unknown"
268                 */
269                public VCardDataType getDataType() {
270                        return dataType;
271                }
272
273                /**
274                 * Get the value.
275                 * @return the value
276                 */
277                public String getValue() {
278                        return value;
279                }
280        }
281}