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}