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 <wtf/ASCIICType.h>
33#include <wtf/text/StringBuilder.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    StringBuilder stringBuilder;
55
56    const UChar* characters = name.characters();
57    unsigned length = name.length();
58    for (unsigned i = 5; i < length; ++i) {
59        UChar character = characters[i];
60        if (character != '-')
61            stringBuilder.append(character);
62        else {
63            if ((i + 1 < length) && isASCIILower(characters[i + 1])) {
64                stringBuilder.append(toASCIIUpper(characters[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    const UChar* property = propertyName.characters();
80    const UChar* attribute = attributeName.characters();
81    unsigned propertyLength = propertyName.length();
82    unsigned attributeLength = attributeName.length();
83
84    unsigned a = 5;
85    unsigned p = 0;
86    bool wordBoundary = false;
87    while (a < attributeLength && p < propertyLength) {
88        if (attribute[a] == '-' && a + 1 < attributeLength && attribute[a + 1] != '-')
89            wordBoundary = true;
90        else {
91            if ((wordBoundary ? toASCIIUpper(attribute[a]) : attribute[a]) != property[p])
92                return false;
93            p++;
94            wordBoundary = false;
95        }
96        a++;
97    }
98
99    return (a == attributeLength && p == propertyLength);
100}
101
102static bool isValidPropertyName(const String& name)
103{
104    const UChar* characters = name.characters();
105    unsigned length = name.length();
106    for (unsigned i = 0; i < length; ++i) {
107        if (characters[i] == '-' && (i + 1 < length) && isASCIILower(characters[i + 1]))
108            return false;
109    }
110    return true;
111}
112
113static String convertPropertyNameToAttributeName(const String& name)
114{
115    StringBuilder builder;
116    builder.append("data-");
117
118    const UChar* characters = name.characters();
119    unsigned length = name.length();
120    for (unsigned i = 0; i < length; ++i) {
121        UChar character = characters[i];
122        if (isASCIIUpper(character)) {
123            builder.append('-');
124            builder.append(toASCIILower(character));
125        } else
126            builder.append(character);
127    }
128
129    return builder.toString();
130}
131
132void DatasetDOMStringMap::ref()
133{
134    m_element->ref();
135}
136
137void DatasetDOMStringMap::deref()
138{
139    m_element->deref();
140}
141
142void DatasetDOMStringMap::getNames(Vector<String>& names)
143{
144    if (!m_element->hasAttributes())
145        return;
146
147    unsigned length = m_element->attributeCount();
148    for (unsigned i = 0; i < length; i++) {
149        const Attribute* attribute = m_element->attributeItem(i);
150        if (isValidAttributeName(attribute->localName()))
151            names.append(convertAttributeNameToPropertyName(attribute->localName()));
152    }
153}
154
155String DatasetDOMStringMap::item(const String& name)
156{
157    if (!m_element->hasAttributes())
158        return String();
159
160    unsigned length = m_element->attributeCount();
161    for (unsigned i = 0; i < length; i++) {
162        const Attribute* attribute = m_element->attributeItem(i);
163        if (propertyNameMatchesAttributeName(name, attribute->localName()))
164            return attribute->value();
165    }
166
167    return String();
168}
169
170bool DatasetDOMStringMap::contains(const String& name)
171{
172    if (!m_element->hasAttributes())
173        return false;
174
175    unsigned length = m_element->attributeCount();
176    for (unsigned i = 0; i < length; i++) {
177        const Attribute* attribute = m_element->attributeItem(i);
178        if (propertyNameMatchesAttributeName(name, attribute->localName()))
179            return true;
180    }
181
182    return false;
183}
184
185void DatasetDOMStringMap::setItem(const String& name, const String& value, ExceptionCode& ec)
186{
187    if (!isValidPropertyName(name)) {
188        ec = SYNTAX_ERR;
189        return;
190    }
191
192    m_element->setAttribute(convertPropertyNameToAttributeName(name), value, ec);
193}
194
195void DatasetDOMStringMap::deleteItem(const String& name, ExceptionCode& ec)
196{
197    if (!isValidPropertyName(name)) {
198        ec = SYNTAX_ERR;
199        return;
200    }
201
202    m_element->removeAttribute(convertPropertyNameToAttributeName(name));
203}
204
205} // namespace WebCore
206