001    package ezvcard.io.xml;
002    
003    import java.util.ArrayList;
004    import java.util.Arrays;
005    import java.util.Collection;
006    import java.util.List;
007    
008    import org.w3c.dom.Document;
009    import org.w3c.dom.Element;
010    
011    import ezvcard.VCardDataType;
012    import ezvcard.VCardVersion;
013    import ezvcard.util.XmlUtils;
014    
015    /*
016     Copyright (c) 2013, 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}.
046     * @author Michael Angstadt
047     */
048    public 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             * Creates a new XML element under its own XML document.
056             * @param propertyName the property name (e.g. "adr")
057             */
058            public XCardElement(String propertyName) {
059                    this(propertyName, VCardVersion.V4_0);
060            }
061    
062            /**
063             * Creates a new XML element under its own XML document.
064             * @param propertyName the property name (e.g. "adr")
065             * @param version the vCard version
066             */
067            public XCardElement(String propertyName, VCardVersion version) {
068                    this.version = version;
069                    namespace = version.getXmlNamespace();
070                    document = XmlUtils.createDocument();
071                    element = document.createElementNS(namespace, propertyName);
072                    document.appendChild(element);
073            }
074    
075            /**
076             * Wraps an existing XML element.
077             * @param element the XML element
078             */
079            public XCardElement(Element element) {
080                    this(element, VCardVersion.V4_0);
081            }
082    
083            /**
084             * Wraps an existing XML element.
085             * @param element the XML element
086             * @param version the vCard version
087             */
088            public XCardElement(Element element, VCardVersion version) {
089                    this.document = element.getOwnerDocument();
090                    this.element = element;
091                    this.version = version;
092                    namespace = version.getXmlNamespace();
093            }
094    
095            /**
096             * Gets the first value with one of the given data types.
097             * @param dataTypes the data type(s) to look for (null signifies the
098             * "unknown" data type)
099             * @return the value or null if not found
100             */
101            public String first(VCardDataType... dataTypes) {
102                    String names[] = new String[dataTypes.length];
103                    for (int i = 0; i < dataTypes.length; i++) {
104                            VCardDataType dataType = dataTypes[i];
105                            names[i] = toLocalName(dataType);
106                    }
107                    return first(names);
108            }
109    
110            /**
111             * Gets the value of the first child element with one of the given names.
112             * @param names the possible names of the element
113             * @return the element's text or null if not found
114             */
115            public String first(String... names) {
116                    List<String> localNamesList = Arrays.asList(names);
117                    for (Element child : children()) {
118                            if (localNamesList.contains(child.getLocalName()) && namespace.equals(child.getNamespaceURI())) {
119                                    return child.getTextContent();
120                            }
121                    }
122                    return null;
123            }
124    
125            /**
126             * Gets all the values of a given data type.
127             * @param dataType the data type to look for
128             * @return the values
129             */
130            public List<String> all(VCardDataType dataType) {
131                    String dataTypeStr = toLocalName(dataType);
132                    return all(dataTypeStr);
133            }
134    
135            /**
136             * Gets the value of all non-empty child elements that have the given name.
137             * @param localName the element name
138             * @return the values of the child elements
139             */
140            public List<String> all(String localName) {
141                    List<String> childrenText = new ArrayList<String>();
142                    for (Element child : children()) {
143                            if (localName.equals(child.getLocalName()) && namespace.equals(child.getNamespaceURI())) {
144                                    String text = child.getTextContent();
145                                    if (text.length() > 0) {
146                                            childrenText.add(child.getTextContent());
147                                    }
148                            }
149                    }
150                    return childrenText;
151            }
152    
153            /**
154             * Adds a value.
155             * @param dataType the data type or null for the "unknown" data type
156             * @param value the value
157             * @return the created element
158             */
159            public Element append(VCardDataType dataType, String value) {
160                    String dataTypeStr = toLocalName(dataType);
161                    return append(dataTypeStr, value);
162            }
163    
164            /**
165             * Adds a child element.
166             * @param name the name of the child element
167             * @param value the value of the child element.
168             * @return the created element
169             */
170            public Element append(String name, String value) {
171                    Element child = document.createElementNS(namespace, name);
172                    child.setTextContent(value);
173                    element.appendChild(child);
174                    return child;
175            }
176    
177            /**
178             * Adds multiple child elements, each with the same name.
179             * @param name the name for all the child elements
180             * @param values the values of each child element
181             * @return the created elements
182             */
183            public List<Element> append(String name, Collection<String> values) {
184                    if (values.isEmpty()) {
185                            Element element = append(name, (String) null);
186                            return Arrays.asList(element);
187                    }
188    
189                    List<Element> elements = new ArrayList<Element>(values.size());
190                    for (String value : values) {
191                            elements.add(append(name, value));
192                    }
193                    return elements;
194            }
195    
196            /**
197             * Gets the owner document.
198             * @return the owner document
199             */
200            public Document document() {
201                    return document;
202            }
203    
204            /**
205             * Gets the wrapped XML element.
206             * @return the wrapped XML element
207             */
208            public Element element() {
209                    return element;
210            }
211    
212            /**
213             * Gets the vCard version.
214             * @return the vCard version
215             */
216            public VCardVersion version() {
217                    return version;
218            }
219    
220            /**
221             * Gets the child elements of the XML element.
222             * @return the child elements
223             */
224            private List<Element> children() {
225                    return XmlUtils.toElementList(element.getChildNodes());
226            }
227    
228            private String toLocalName(VCardDataType dataType) {
229                    return (dataType == null) ? "unknown" : dataType.getName().toLowerCase();
230            }
231    }