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 | |
33 | namespace WebCore { |
34 | |
35 | static 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 | |
64 | static 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 | |
93 | static 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 | |
104 | bool 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 | |
244 | static bool isASCIIAlphabetOrQuote(UChar ch) |
245 | { |
246 | return isASCIIAlpha(ch) || ch == '\''; |
247 | } |
248 | |
249 | void 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.appendLiteral("''" ); |
269 | else { |
270 | String escaped = literal.substring(i); |
271 | escaped.replace('\'', "''" ); |
272 | buffer.append('\''); |
273 | buffer.append(escaped); |
274 | buffer.append('\''); |
275 | return; |
276 | } |
277 | } |
278 | } |
279 | |
280 | } // namespace WebCore |
281 | |
282 | #endif |
283 | |