001package ezvcard.io.xml; 002 003import java.util.Arrays; 004import java.util.Collection; 005import java.util.Collections; 006import java.util.List; 007import java.util.stream.Collectors; 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-2026, 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 //@formatter:off 083 String[] names = Arrays.stream(dataTypes) 084 .map(XCardElement::toLocalName) 085 .toArray(len -> new String[len]); 086 //@formatter:on 087 088 return first(names); 089 } 090 091 /** 092 * Gets the value of the first child element with one of the given names. 093 * @param names the possible names of the element 094 * @return the element's text or null if not found 095 */ 096 public String first(String... names) { 097 List<String> localNamesList = Arrays.asList(names); 098 099 //@formatter:off 100 return children().stream() 101 .filter(child -> localNamesList.contains(child.getLocalName())) 102 .filter(child -> namespace.equals(child.getNamespaceURI())) 103 .map(Element::getTextContent) 104 .findFirst().orElse(null); 105 //@formatter:on 106 } 107 108 /** 109 * Gets all the values of a given data type. 110 * @param dataType the data type to look for 111 * @return the values 112 */ 113 public List<String> all(VCardDataType dataType) { 114 String dataTypeStr = toLocalName(dataType); 115 return all(dataTypeStr); 116 } 117 118 /** 119 * Gets the value of all non-empty child elements that have the given name. 120 * @param localName the element name 121 * @return the values of the child elements 122 */ 123 public List<String> all(String localName) { 124 //@formatter:off 125 return children().stream() 126 .filter(child -> localName.equals(child.getLocalName())) 127 .filter(child -> namespace.equals(child.getNamespaceURI())) 128 .map(Element::getTextContent) 129 .filter(text -> !text.isEmpty()) 130 .collect(Collectors.toList()); 131 //@formatter:on 132 } 133 134 /** 135 * Adds a value. 136 * @param dataType the data type or null for the "unknown" data type 137 * @param value the value 138 * @return the created element 139 */ 140 public Element append(VCardDataType dataType, String value) { 141 String dataTypeStr = toLocalName(dataType); 142 return append(dataTypeStr, value); 143 } 144 145 /** 146 * Adds a child element. 147 * @param name the name of the child element 148 * @param value the value of the child element. 149 * @return the created element 150 */ 151 public Element append(String name, String value) { 152 Element child = document.createElementNS(namespace, name); 153 child.setTextContent(value); 154 element.appendChild(child); 155 return child; 156 } 157 158 /** 159 * Adds multiple child elements, each with the same name. 160 * @param name the name for all the child elements 161 * @param values the values of each child element 162 * @return the created elements 163 */ 164 public List<Element> append(String name, Collection<String> values) { 165 if (values.isEmpty()) { 166 Element element = append(name, (String) null); 167 return Collections.singletonList(element); 168 } 169 170 //@formatter:off 171 return values.stream() 172 .map(value -> append(name, value)) 173 .collect(Collectors.toList()); 174 //@formatter:on 175 } 176 177 /** 178 * Gets the owner document. 179 * @return the owner document 180 */ 181 public Document document() { 182 return document; 183 } 184 185 /** 186 * Gets the wrapped XML element. 187 * @return the wrapped XML element 188 */ 189 public Element element() { 190 return element; 191 } 192 193 /** 194 * Gets the vCard version. 195 * @return the vCard version 196 */ 197 public VCardVersion version() { 198 return version; 199 } 200 201 /** 202 * Gets the child elements of the XML element. 203 * @return the child elements 204 */ 205 private List<Element> children() { 206 return XmlUtils.toElementList(element.getChildNodes()); 207 } 208 209 /** 210 * Finds the first child element that has the xCard namespace and returns 211 * its data type and value. If no such element is found, the parent 212 * {@link XCardElement}'s text content, along with a null data type, is 213 * returned. 214 * @return the value and data type 215 */ 216 public XCardValue firstValue() { 217 String elementNamespace = version.getXmlNamespace(); 218 219 //@formatter:off 220 return children().stream() 221 .filter(child -> elementNamespace.equals(child.getNamespaceURI())) 222 .map(child -> { 223 VCardDataType dataType = toDataType(child.getLocalName()); 224 String value = child.getTextContent(); 225 return new XCardValue(dataType, value); 226 }) 227 .findFirst().orElseGet(() -> new XCardValue(null, element.getTextContent())); 228 //@formatter:on 229 } 230 231 /** 232 * Gets the appropriate XML local name of a {@link VCardDataType} object. 233 * @param dataType the data type or null for "unknown" 234 * @return the local name (e.g. "text") 235 */ 236 private static String toLocalName(VCardDataType dataType) { 237 return (dataType == null) ? "unknown" : dataType.getName().toLowerCase(); 238 } 239 240 /** 241 * Converts an XML local name to the appropriate {@link VCardDataType} 242 * object. 243 * @param localName the local name (e.g. "text") 244 * @return the data type or null for "unknown" 245 */ 246 private static VCardDataType toDataType(String localName) { 247 return "unknown".equals(localName) ? null : VCardDataType.get(localName); 248 } 249 250 /** 251 * Represents the data type and value of a child element under an 252 * {@link XCardElement}. 253 */ 254 public static class XCardValue { 255 private final VCardDataType dataType; 256 private final String value; 257 258 /** 259 * @param dataType the data type or null if "unknown" 260 * @param value the value 261 */ 262 public XCardValue(VCardDataType dataType, String value) { 263 this.dataType = dataType; 264 this.value = value; 265 } 266 267 /** 268 * Gets the data type 269 * @return the data type or null if "unknown" 270 */ 271 public VCardDataType getDataType() { 272 return dataType; 273 } 274 275 /** 276 * Get the value. 277 * @return the value 278 */ 279 public String getValue() { 280 return value; 281 } 282 } 283}