1/*
2 * Copyright (C) 2010 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 "Attribute.h"
30#include "Element.h"
31#include "ExceptionCode.h"
32#include "NamedNodeMap.h"
33#include <wtf/ASCIICType.h>
34
35namespace WebCore {
36
37static bool isValidAttributeName(const String& name)
38{
39    if (!name.startsWith("data-"))
40        return false;
41
42    const UChar* characters = name.characters();
43    unsigned length = name.length();
44    for (unsigned i = 5; i < length; ++i) {
45        if (isASCIIUpper(characters[i]))
46            return false;
47    }
48
49    return true;
50}
51
52static String convertAttributeNameToPropertyName(const String& name)
53{
54    Vector<UChar> newStringBuffer;
55
56    const UChar* characters = name.characters();
57    unsigned length = name.length();
58    for (unsigned i = 5; i < length; ++i) {
59        if (characters[i] != '-')
60            newStringBuffer.append(characters[i]);
61        else {
62            if ((i + 1 < length) && isASCIILower(characters[i + 1])) {
63                newStringBuffer.append(toASCIIUpper(characters[i + 1]));
64                ++i;
65            } else
66                newStringBuffer.append(characters[i]);
67        }
68    }
69
70    return String::adopt(newStringBuffer);
71}
72
73static bool propertyNameMatchesAttributeName(const String& propertyName, const String& attributeName)
74{
75    if (!attributeName.startsWith("data-"))
76        return false;
77
78    const UChar* property = propertyName.characters();
79    const UChar* attribute = attributeName.characters();
80    unsigned propertyLength = propertyName.length();
81    unsigned attributeLength = attributeName.length();
82
83    unsigned a = 5;
84    unsigned p = 0;
85    bool wordBoundary = false;
86    while (a < attributeLength && p < propertyLength) {
87        if (attribute[a] == '-' && a + 1 < attributeLength && attribute[a + 1] != '-')
88            wordBoundary = true;
89        else {
90            if ((wordBoundary ? toASCIIUpper(attribute[a]) : attribute[a]) != property[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    const UChar* characters = name.characters();
104    unsigned length = name.length();
105    for (unsigned i = 0; i < length; ++i) {
106        if (characters[i] == '-' && (i + 1 < length) && isASCIILower(characters[i + 1]))
107            return false;
108    }
109    return true;
110}
111
112static String convertPropertyNameToAttributeName(const String& name)
113{
114    Vector<UChar> newStringBuffer;
115
116    newStringBuffer.append('d');
117    newStringBuffer.append('a');
118    newStringBuffer.append('t');
119    newStringBuffer.append('a');
120    newStringBuffer.append('-');
121
122    const UChar* characters = name.characters();
123    unsigned length = name.length();
124    for (unsigned i = 0; i < length; ++i) {
125        if (isASCIIUpper(characters[i])) {
126            newStringBuffer.append('-');
127            newStringBuffer.append(toASCIILower(characters[i]));
128        } else
129            newStringBuffer.append(characters[i]);
130    }
131
132    return String::adopt(newStringBuffer);
133}
134
135
136void DatasetDOMStringMap::ref()
137{
138    m_element->ref();
139}
140
141void DatasetDOMStringMap::deref()
142{
143    m_element->deref();
144}
145
146void DatasetDOMStringMap::getNames(Vector<String>& names)
147{
148    NamedNodeMap* attributeMap = m_element->attributes(true);
149    if (attributeMap) {
150        unsigned length = attributeMap->length();
151        for (unsigned i = 0; i < length; i++) {
152            Attribute* attribute = attributeMap->attributeItem(i);
153            if (isValidAttributeName(attribute->localName()))
154                names.append(convertAttributeNameToPropertyName(attribute->localName()));
155        }
156    }
157}
158
159String DatasetDOMStringMap::item(const String& name)
160{
161    NamedNodeMap* attributeMap = m_element->attributes(true);
162    if (attributeMap) {
163        unsigned length = attributeMap->length();
164        for (unsigned i = 0; i < length; i++) {
165            Attribute* attribute = attributeMap->attributeItem(i);
166            if (propertyNameMatchesAttributeName(name, attribute->localName()))
167                return attribute->value();
168        }
169    }
170
171    return String();
172}
173
174bool DatasetDOMStringMap::contains(const String& name)
175{
176    NamedNodeMap* attributeMap = m_element->attributes(true);
177    if (attributeMap) {
178        unsigned length = attributeMap->length();
179        for (unsigned i = 0; i < length; i++) {
180            Attribute* attribute = attributeMap->attributeItem(i);
181            if (propertyNameMatchesAttributeName(name, attribute->localName()))
182                return true;
183        }
184    }
185    return false;
186}
187
188void DatasetDOMStringMap::setItem(const String& name, const String& value, ExceptionCode& ec)
189{
190    if (!isValidPropertyName(name)) {
191        ec = SYNTAX_ERR;
192        return;
193    }
194
195    m_element->setAttribute(convertPropertyNameToAttributeName(name), value, ec);
196}
197
198void DatasetDOMStringMap::deleteItem(const String& name, ExceptionCode& ec)
199{
200    if (!isValidPropertyName(name)) {
201        ec = SYNTAX_ERR;
202        return;
203    }
204
205    ExceptionCode dummy;
206    m_element->removeAttribute(convertPropertyNameToAttributeName(name), dummy);
207}
208
209} // namespace WebCore
210