1/*
2 * Copyright (C) 2010, 2014 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "DatasetDOMStringMap.h"
28
29#include "Element.h"
30#include <wtf/ASCIICType.h>
31#include <wtf/IsoMallocInlines.h>
32#include <wtf/text/AtomString.h>
33#include <wtf/text/StringBuilder.h>
34
35namespace WebCore {
36
37WTF_MAKE_ISO_ALLOCATED_IMPL(DatasetDOMStringMap);
38
39static bool isValidAttributeName(const String& name)
40{
41 if (!name.startsWith("data-"))
42 return false;
43
44 unsigned length = name.length();
45 for (unsigned i = 5; i < length; ++i) {
46 if (isASCIIUpper(name[i]))
47 return false;
48 }
49
50 return true;
51}
52
53static String convertAttributeNameToPropertyName(const String& name)
54{
55 StringBuilder stringBuilder;
56
57 unsigned length = name.length();
58 for (unsigned i = 5; i < length; ++i) {
59 UChar character = name[i];
60 if (character != '-')
61 stringBuilder.append(character);
62 else {
63 if ((i + 1 < length) && isASCIILower(name[i + 1])) {
64 stringBuilder.append(toASCIIUpper(name[i + 1]));
65 ++i;
66 } else
67 stringBuilder.append(character);
68 }
69 }
70
71 return stringBuilder.toString();
72}
73
74static bool propertyNameMatchesAttributeName(const String& propertyName, const String& attributeName)
75{
76 if (!attributeName.startsWith("data-"))
77 return false;
78
79 unsigned propertyLength = propertyName.length();
80 unsigned attributeLength = attributeName.length();
81
82 unsigned a = 5;
83 unsigned p = 0;
84 bool wordBoundary = false;
85 while (a < attributeLength && p < propertyLength) {
86 const UChar currentAttributeNameChar = attributeName[a];
87 if (currentAttributeNameChar == '-' && a + 1 < attributeLength && attributeName[a + 1] != '-')
88 wordBoundary = true;
89 else {
90 if ((wordBoundary ? toASCIIUpper(currentAttributeNameChar) : currentAttributeNameChar) != propertyName[p])
91 return false;
92 p++;
93 wordBoundary = false;
94 }
95 a++;
96 }
97
98 return (a == attributeLength && p == propertyLength);
99}
100
101static bool isValidPropertyName(const String& name)
102{
103 unsigned length = name.length();
104 for (unsigned i = 0; i < length; ++i) {
105 if (name[i] == '-' && (i + 1 < length) && isASCIILower(name[i + 1]))
106 return false;
107 }
108 return true;
109}
110
111template<typename CharacterType>
112static inline AtomString convertPropertyNameToAttributeName(const StringImpl& name)
113{
114 const CharacterType dataPrefix[] = { 'd', 'a', 't', 'a', '-' };
115
116 Vector<CharacterType, 32> buffer;
117
118 unsigned length = name.length();
119 buffer.reserveInitialCapacity(WTF_ARRAY_LENGTH(dataPrefix) + length);
120
121 buffer.append(dataPrefix, WTF_ARRAY_LENGTH(dataPrefix));
122
123 const CharacterType* characters = name.characters<CharacterType>();
124 for (unsigned i = 0; i < length; ++i) {
125 CharacterType character = characters[i];
126 if (isASCIIUpper(character)) {
127 buffer.append('-');
128 buffer.append(toASCIILower(character));
129 } else
130 buffer.append(character);
131 }
132 return AtomString(buffer.data(), buffer.size());
133}
134
135static AtomString convertPropertyNameToAttributeName(const String& name)
136{
137 if (name.isNull())
138 return nullAtom();
139
140 StringImpl* nameImpl = name.impl();
141 if (nameImpl->is8Bit())
142 return convertPropertyNameToAttributeName<LChar>(*nameImpl);
143 return convertPropertyNameToAttributeName<UChar>(*nameImpl);
144}
145
146void DatasetDOMStringMap::ref()
147{
148 m_element.ref();
149}
150
151void DatasetDOMStringMap::deref()
152{
153 m_element.deref();
154}
155
156bool DatasetDOMStringMap::isSupportedPropertyName(const String& propertyName) const
157{
158 if (!m_element.hasAttributes())
159 return false;
160
161 auto attributeIteratorAccessor = m_element.attributesIterator();
162 if (attributeIteratorAccessor.attributeCount() == 1) {
163 // If the node has a single attribute, it is the dataset member accessed in most cases.
164 // Building a new AtomString in that case is overkill so we do a direct character comparison.
165 const auto& attribute = *attributeIteratorAccessor.begin();
166 if (propertyNameMatchesAttributeName(propertyName, attribute.localName()))
167 return true;
168 } else {
169 auto attributeName = convertPropertyNameToAttributeName(propertyName);
170 for (const Attribute& attribute : attributeIteratorAccessor) {
171 if (attribute.localName() == attributeName)
172 return true;
173 }
174 }
175
176 return false;
177}
178
179Vector<String> DatasetDOMStringMap::supportedPropertyNames() const
180{
181 Vector<String> names;
182
183 if (!m_element.hasAttributes())
184 return names;
185
186 for (auto& attribute : m_element.attributesIterator()) {
187 if (isValidAttributeName(attribute.localName()))
188 names.append(convertAttributeNameToPropertyName(attribute.localName()));
189 }
190
191 return names;
192}
193
194const AtomString* DatasetDOMStringMap::item(const String& propertyName) const
195{
196 if (m_element.hasAttributes()) {
197 AttributeIteratorAccessor attributeIteratorAccessor = m_element.attributesIterator();
198
199 if (attributeIteratorAccessor.attributeCount() == 1) {
200 // If the node has a single attribute, it is the dataset member accessed in most cases.
201 // Building a new AtomString in that case is overkill so we do a direct character comparison.
202 const Attribute& attribute = *attributeIteratorAccessor.begin();
203 if (propertyNameMatchesAttributeName(propertyName, attribute.localName()))
204 return &attribute.value();
205 } else {
206 AtomString attributeName = convertPropertyNameToAttributeName(propertyName);
207 for (const Attribute& attribute : attributeIteratorAccessor) {
208 if (attribute.localName() == attributeName)
209 return &attribute.value();
210 }
211 }
212 }
213
214 return nullptr;
215}
216
217String DatasetDOMStringMap::namedItem(const AtomString& name) const
218{
219 if (const auto* value = item(name))
220 return *value;
221 return String { };
222}
223
224ExceptionOr<void> DatasetDOMStringMap::setNamedItem(const String& name, const String& value)
225{
226 if (!isValidPropertyName(name))
227 return Exception { SyntaxError };
228 return m_element.setAttribute(convertPropertyNameToAttributeName(name), value);
229}
230
231bool DatasetDOMStringMap::deleteNamedProperty(const String& name)
232{
233 return m_element.removeAttribute(convertPropertyNameToAttributeName(name));
234}
235
236} // namespace WebCore
237