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}