1/*
2 * Copyright (C) 2012 Google 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'' AND
14 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16 * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE
17 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
20 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
23 * SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "DateTimeFormat.h"
28
29#if ENABLE(DATE_AND_TIME_INPUT_TYPES)
30#include <wtf/ASCIICType.h>
31#include <wtf/text/StringBuilder.h>
32
33namespace WebCore {
34
35static const DateTimeFormat::FieldType lowerCaseToFieldTypeMap[26] = {
36    DateTimeFormat::FieldTypePeriod, // a
37    DateTimeFormat::FieldTypeInvalid, // b
38    DateTimeFormat::FieldTypeLocalDayOfWeekStandAlon, // c
39    DateTimeFormat::FieldTypeDayOfMonth, // d
40    DateTimeFormat::FieldTypeLocalDayOfWeek, // e
41    DateTimeFormat::FieldTypeInvalid, // f
42    DateTimeFormat::FieldTypeModifiedJulianDay, // g
43    DateTimeFormat::FieldTypeHour12, // h
44    DateTimeFormat::FieldTypeInvalid, // i
45    DateTimeFormat::FieldTypeInvalid, // j
46    DateTimeFormat::FieldTypeHour24, // k
47    DateTimeFormat::FieldTypeInvalid, // l
48    DateTimeFormat::FieldTypeMinute, // m
49    DateTimeFormat::FieldTypeInvalid, // n
50    DateTimeFormat::FieldTypeInvalid, // o
51    DateTimeFormat::FieldTypeInvalid, // p
52    DateTimeFormat::FieldTypeQuaterStandAlone, // q
53    DateTimeFormat::FieldTypeInvalid, // r
54    DateTimeFormat::FieldTypeSecond, // s
55    DateTimeFormat::FieldTypeInvalid, // t
56    DateTimeFormat::FieldTypeExtendedYear, // u
57    DateTimeFormat::FieldTypeNonLocationZone, // v
58    DateTimeFormat::FieldTypeWeekOfYear, // w
59    DateTimeFormat::FieldTypeInvalid, // x
60    DateTimeFormat::FieldTypeYear, // y
61    DateTimeFormat::FieldTypeZone, // z
62};
63
64static const DateTimeFormat::FieldType upperCaseToFieldTypeMap[26] = {
65    DateTimeFormat::FieldTypeMillisecondsInDay, // A
66    DateTimeFormat::FieldTypeInvalid, // B
67    DateTimeFormat::FieldTypeInvalid, // C
68    DateTimeFormat::FieldTypeDayOfYear, // D
69    DateTimeFormat::FieldTypeDayOfWeek, // E
70    DateTimeFormat::FieldTypeDayOfWeekInMonth, // F
71    DateTimeFormat::FieldTypeEra, // G
72    DateTimeFormat::FieldTypeHour23, // H
73    DateTimeFormat::FieldTypeInvalid, // I
74    DateTimeFormat::FieldTypeInvalid, // J
75    DateTimeFormat::FieldTypeHour11, // K
76    DateTimeFormat::FieldTypeMonthStandAlone, // L
77    DateTimeFormat::FieldTypeMonth, // M
78    DateTimeFormat::FieldTypeInvalid, // N
79    DateTimeFormat::FieldTypeInvalid, // O
80    DateTimeFormat::FieldTypeInvalid, // P
81    DateTimeFormat::FieldTypeQuater, // Q
82    DateTimeFormat::FieldTypeInvalid, // R
83    DateTimeFormat::FieldTypeFractionalSecond, // S
84    DateTimeFormat::FieldTypeInvalid, // T
85    DateTimeFormat::FieldTypeInvalid, // U
86    DateTimeFormat::FieldTypeInvalid, // V
87    DateTimeFormat::FieldTypeWeekOfMonth, // W
88    DateTimeFormat::FieldTypeInvalid, // X
89    DateTimeFormat::FieldTypeYearOfWeekOfYear, // Y
90    DateTimeFormat::FieldTypeRFC822Zone, // Z
91};
92
93static DateTimeFormat::FieldType mapCharacterToFieldType(const UChar ch)
94{
95    if (isASCIIUpper(ch))
96        return upperCaseToFieldTypeMap[ch - 'A'];
97
98    if (isASCIILower(ch))
99        return lowerCaseToFieldTypeMap[ch - 'a'];
100
101    return DateTimeFormat::FieldTypeLiteral;
102}
103
104bool DateTimeFormat::parse(const String& source, TokenHandler& tokenHandler)
105{
106    enum State {
107        StateInQuote,
108        StateInQuoteQuote,
109        StateLiteral,
110        StateQuote,
111        StateSymbol,
112    } state = StateLiteral;
113
114    FieldType fieldType = FieldTypeLiteral;
115    StringBuilder literalBuffer;
116    int fieldCounter = 0;
117
118    for (unsigned int index = 0; index < source.length(); ++index) {
119        const UChar ch = source[index];
120        switch (state) {
121        case StateInQuote:
122            if (ch == '\'') {
123                state = StateInQuoteQuote;
124                break;
125            }
126
127            literalBuffer.append(ch);
128            break;
129
130        case StateInQuoteQuote:
131            if (ch == '\'') {
132                literalBuffer.append('\'');
133                state = StateInQuote;
134                break;
135            }
136
137            fieldType = mapCharacterToFieldType(ch);
138            if (fieldType == FieldTypeInvalid)
139                return false;
140
141            if (fieldType == FieldTypeLiteral) {
142                literalBuffer.append(ch);
143                state = StateLiteral;
144                break;
145            }
146
147            if (literalBuffer.length()) {
148                tokenHandler.visitLiteral(literalBuffer.toString());
149                literalBuffer.clear();
150            }
151
152            fieldCounter = 1;
153            state = StateSymbol;
154            break;
155
156        case StateLiteral:
157            if (ch == '\'') {
158                state = StateQuote;
159                break;
160            }
161
162            fieldType = mapCharacterToFieldType(ch);
163            if (fieldType == FieldTypeInvalid)
164                return false;
165
166            if (fieldType == FieldTypeLiteral) {
167                literalBuffer.append(ch);
168                break;
169            }
170
171            if (literalBuffer.length()) {
172                tokenHandler.visitLiteral(literalBuffer.toString());
173                literalBuffer.clear();
174            }
175
176            fieldCounter = 1;
177            state = StateSymbol;
178            break;
179
180        case StateQuote:
181            literalBuffer.append(ch);
182            state = ch == '\'' ? StateLiteral : StateInQuote;
183            break;
184
185        case StateSymbol: {
186            ASSERT(fieldType != FieldTypeInvalid);
187            ASSERT(fieldType != FieldTypeLiteral);
188            ASSERT(literalBuffer.isEmpty());
189
190            FieldType fieldType2 = mapCharacterToFieldType(ch);
191            if (fieldType2 == FieldTypeInvalid)
192                return false;
193
194            if (fieldType == fieldType2) {
195                ++fieldCounter;
196                break;
197            }
198
199            tokenHandler.visitField(fieldType, fieldCounter);
200
201            if (fieldType2 == FieldTypeLiteral) {
202                if (ch == '\'')
203                    state = StateQuote;
204                else {
205                    literalBuffer.append(ch);
206                    state = StateLiteral;
207                }
208                break;
209            }
210
211            fieldCounter = 1;
212            fieldType = fieldType2;
213            break;
214        }
215        }
216    }
217
218    ASSERT(fieldType != FieldTypeInvalid);
219
220    switch (state) {
221    case StateLiteral:
222    case StateInQuoteQuote:
223        if (literalBuffer.length())
224            tokenHandler.visitLiteral(literalBuffer.toString());
225        return true;
226
227    case StateQuote:
228    case StateInQuote:
229        if (literalBuffer.length())
230            tokenHandler.visitLiteral(literalBuffer.toString());
231        return false;
232
233    case StateSymbol:
234        ASSERT(fieldType != FieldTypeLiteral);
235        ASSERT(!literalBuffer.length());
236        tokenHandler.visitField(fieldType, fieldCounter);
237        return true;
238    }
239
240    ASSERT_NOT_REACHED();
241    return false;
242}
243
244static bool isASCIIAlphabetOrQuote(UChar ch)
245{
246    return isASCIIAlpha(ch) || ch == '\'';
247}
248
249void DateTimeFormat::quoteAndAppendLiteral(const String& literal, StringBuilder& buffer)
250{
251    if (literal.length() <= 0)
252        return;
253
254    if (literal.find(isASCIIAlphabetOrQuote) == notFound) {
255        buffer.append(literal);
256        return;
257    }
258
259    if (literal.find('\'') == notFound) {
260        buffer.append("'");
261        buffer.append(literal);
262        buffer.append("'");
263        return;
264    }
265
266    for (unsigned i = 0; i < literal.length(); ++i) {
267        if (literal[i] == '\'')
268            buffer.append("''");
269        else {
270            String escaped = literal.substring(i);
271            escaped.replace(ASCIILiteral("'"), ASCIILiteral("''"));
272            buffer.append("'");
273            buffer.append(escaped);
274            buffer.append("'");
275            return;
276        }
277    }
278}
279
280} // namespace WebCore
281
282#endif
283