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