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