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
32 namespace WebCore {
33
34 static 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
63 static 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
mapCharacterToFieldType(const UChar ch)92 static 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
parse(const String & source,TokenHandler & tokenHandler)103 bool 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
isASCIIAlphabetOrQuote(UChar ch)243 static bool isASCIIAlphabetOrQuote(UChar ch)
244 {
245 return isASCIIAlpha(ch) || ch == '\'';
246 }
247
quoteAndAppendLiteral(const String & literal,StringBuilder & buffer)248 void 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.append("''");
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 WebCore
280