OLD | NEW |
1 /* | 1 /* |
2 * Copyright (C) 2011 Google Inc. All rights reserved. | 2 * Copyright (C) 2011 Google Inc. All rights reserved. |
3 * | 3 * |
4 * Redistribution and use in source and binary forms, with or without | 4 * Redistribution and use in source and binary forms, with or without |
5 * modification, are permitted provided that the following conditions are | 5 * modification, are permitted provided that the following conditions are |
6 * met: | 6 * met: |
7 * | 7 * |
8 * * Redistributions of source code must retain the above copyright | 8 * * Redistributions of source code must retain the above copyright |
9 * notice, this list of conditions and the following disclaimer. | 9 * notice, this list of conditions and the following disclaimer. |
10 * * Redistributions in binary form must reproduce the above | 10 * * Redistributions in binary form must reproduce the above |
(...skipping 132 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
143 { | 143 { |
144 String textData = m_decoder->flush(); | 144 String textData = m_decoder->flush(); |
145 m_lineReader.append(textData); | 145 m_lineReader.append(textData); |
146 m_lineReader.setEndOfStream(); | 146 m_lineReader.setEndOfStream(); |
147 parse(); | 147 parse(); |
148 flushPendingCue(); | 148 flushPendingCue(); |
149 } | 149 } |
150 | 150 |
151 void WebVTTParser::parse() | 151 void WebVTTParser::parse() |
152 { | 152 { |
153 // 4.8.10.13.3 WHATWG WebVTT Parser algorithm. | 153 // WebVTT parser algorithm. (5.1 WebVTT file parsing.) |
154 // 1-3 - Initial setup. | 154 // Steps 1 - 3 - Initial setup. |
155 | 155 |
156 String line; | 156 String line; |
157 while (m_lineReader.getLine(line)) { | 157 while (m_lineReader.getLine(line)) { |
158 switch (m_state) { | 158 switch (m_state) { |
159 case Initial: | 159 case Initial: |
160 // 4-12 - Check for a valid WebVTT signature. | 160 // Steps 4 - 9 - Check for a valid WebVTT signature. |
161 if (!hasRequiredFileIdentifier(line)) { | 161 if (!hasRequiredFileIdentifier(line)) { |
162 if (m_client) | 162 if (m_client) |
163 m_client->fileFailedToParse(); | 163 m_client->fileFailedToParse(); |
164 return; | 164 return; |
165 } | 165 } |
166 | 166 |
167 m_state = Header; | 167 m_state = Header; |
168 break; | 168 break; |
169 | 169 |
170 case Header: | 170 case Header: |
| 171 // Steps 10 - 14 - Allow a header (comment area) under the WEBVTT li
ne. |
171 collectMetadataHeader(line); | 172 collectMetadataHeader(line); |
172 | 173 |
173 // 13-18 - Allow a header (comment area) under the WEBVTT line. | |
174 if (line.isEmpty()) { | 174 if (line.isEmpty()) { |
175 if (m_client && m_regionList.size()) | 175 if (m_client && m_regionList.size()) |
176 m_client->newRegionsParsed(); | 176 m_client->newRegionsParsed(); |
177 | 177 |
178 m_state = Id; | 178 m_state = Id; |
179 break; | 179 break; |
180 } | 180 } |
181 | 181 |
| 182 // Step 16 - Line is not the empty string and does not contain "-->"
. |
182 break; | 183 break; |
183 | 184 |
184 case Id: | 185 case Id: |
185 // 19-29 - Allow any number of line terminators, then initialize new
cue values. | 186 // Steps 17 - 20 - Allow any number of line terminators, then initia
lize new cue values. |
186 if (line.isEmpty()) | 187 if (line.isEmpty()) |
187 break; | 188 break; |
| 189 |
| 190 // Step 21 - Cue creation (start a new cue). |
188 resetCueValues(); | 191 resetCueValues(); |
189 | 192 |
190 // 30-39 - Check if this line contains an optional identifier or tim
ing data. | 193 // Steps 22 - 25 - Check if this line contains an optional identifie
r or timing data. |
191 m_state = collectCueId(line); | 194 m_state = collectCueId(line); |
192 break; | 195 break; |
193 | 196 |
194 case TimingsAndSettings: | 197 case TimingsAndSettings: |
| 198 // Steps 26 - 27 - Discard current cue if the line is empty. |
195 if (line.isEmpty()) { | 199 if (line.isEmpty()) { |
196 m_state = Id; | 200 m_state = Id; |
197 break; | 201 break; |
198 } | 202 } |
199 | 203 |
200 // 40 - Collect cue timings and settings. | 204 // Steps 28 - 29 - Collect cue timings and settings. |
201 m_state = collectTimingsAndSettings(line); | 205 m_state = collectTimingsAndSettings(line); |
202 break; | 206 break; |
203 | 207 |
204 case CueText: | 208 case CueText: |
205 // 41-53 - Collect the cue text, create a cue, and add it to the out
put. | 209 // Steps 31 - 41 - Collect the cue text, create a cue, and add it to
the output. |
206 m_state = collectCueText(line); | 210 m_state = collectCueText(line); |
207 break; | 211 break; |
208 | 212 |
209 case BadCue: | 213 case BadCue: |
210 // 54-62 - Collect and discard the remaining cue. | 214 // Steps 42 - 48 - Discard lines until an empty line or a potential
timing line is seen. |
211 m_state = ignoreBadCue(line); | 215 m_state = ignoreBadCue(line); |
212 break; | 216 break; |
213 } | 217 } |
214 } | 218 } |
215 } | 219 } |
216 | 220 |
217 void WebVTTParser::flushPendingCue() | 221 void WebVTTParser::flushPendingCue() |
218 { | 222 { |
219 ASSERT(m_lineReader.isAtEndOfStream()); | 223 ASSERT(m_lineReader.isAtEndOfStream()); |
220 // If we're in the CueText state when we run out of data, we emit the pendin
g cue. | 224 // If we're in the CueText state when we run out of data, we emit the pendin
g cue. |
221 if (m_state == CueText) | 225 if (m_state == CueText) |
222 createNewCue(); | 226 createNewCue(); |
223 } | 227 } |
224 | 228 |
225 bool WebVTTParser::hasRequiredFileIdentifier(const String& line) | 229 bool WebVTTParser::hasRequiredFileIdentifier(const String& line) |
226 { | 230 { |
227 // A WebVTT file identifier consists of an optional BOM character, | 231 // A WebVTT file identifier consists of an optional BOM character, |
228 // the string "WEBVTT" followed by an optional space or tab character, | 232 // the string "WEBVTT" followed by an optional space or tab character, |
229 // and any number of characters that are not line terminators ... | 233 // and any number of characters that are not line terminators ... |
230 if (!line.startsWith("WEBVTT", fileIdentifierLength)) | 234 if (!line.startsWith("WEBVTT", fileIdentifierLength)) |
231 return false; | 235 return false; |
232 if (line.length() > fileIdentifierLength && !isASpace(line[fileIdentifierLen
gth])) | 236 if (line.length() > fileIdentifierLength && !isASpace(line[fileIdentifierLen
gth])) |
233 return false; | 237 return false; |
234 | 238 |
235 return true; | 239 return true; |
236 } | 240 } |
237 | 241 |
238 void WebVTTParser::collectMetadataHeader(const String& line) | 242 void WebVTTParser::collectMetadataHeader(const String& line) |
239 { | 243 { |
240 // 4.1 Extension of WebVTT header parsing (11 - 15) | 244 // WebVTT header parsing (WebVTT parser algorithm step 12) |
241 DEFINE_STATIC_LOCAL(const AtomicString, regionHeaderName, ("Region", AtomicS
tring::ConstructFromLiteral)); | 245 DEFINE_STATIC_LOCAL(const AtomicString, regionHeaderName, ("Region", AtomicS
tring::ConstructFromLiteral)); |
242 | 246 |
243 // 15.4 If line contains the character ":" (A U+003A COLON), then set metada
ta's | 247 // Step 12.4 If line contains the character ":" (A U+003A COLON), then set m
etadata's |
244 // name to the substring of line before the first ":" character and | 248 // name to the substring of line before the first ":" character and |
245 // metadata's value to the substring after this character. | 249 // metadata's value to the substring after this character. |
246 if (!RuntimeEnabledFeatures::webVTTRegionsEnabled() || !line.contains(":")) | 250 if (!RuntimeEnabledFeatures::webVTTRegionsEnabled() || !line.contains(":")) |
247 return; | 251 return; |
248 | 252 |
249 unsigned colonPosition = line.find(":"); | 253 unsigned colonPosition = line.find(":"); |
250 String headerName = line.substring(0, colonPosition); | 254 String headerName = line.substring(0, colonPosition); |
251 | 255 |
252 // 15.5 If metadata's name equals "Region": | 256 // Steps 12.5 If metadata's name equals "Region": |
253 if (headerName == regionHeaderName) { | 257 if (headerName == regionHeaderName) { |
254 String headerValue = line.substring(colonPosition + 1); | 258 String headerValue = line.substring(colonPosition + 1); |
255 // 15.5.1 - 15.5.8 Region creation: Let region be a new text track regio
n [...] | 259 // Steps 12.5.1 - 12.5.11 Region creation: Let region be a new text trac
k region [...] |
256 createNewRegion(headerValue); | 260 createNewRegion(headerValue); |
257 } | 261 } |
258 } | 262 } |
259 | 263 |
260 WebVTTParser::ParseState WebVTTParser::collectCueId(const String& line) | 264 WebVTTParser::ParseState WebVTTParser::collectCueId(const String& line) |
261 { | 265 { |
262 if (line.contains("-->")) | 266 if (line.contains("-->")) |
263 return collectTimingsAndSettings(line); | 267 return collectTimingsAndSettings(line); |
264 m_currentId = line; | 268 m_currentId = line; |
265 return TimingsAndSettings; | 269 return TimingsAndSettings; |
266 } | 270 } |
267 | 271 |
268 WebVTTParser::ParseState WebVTTParser::collectTimingsAndSettings(const String& l
ine) | 272 WebVTTParser::ParseState WebVTTParser::collectTimingsAndSettings(const String& l
ine) |
269 { | 273 { |
270 // 4.8.10.13.3 Collect WebVTT cue timings and settings. | 274 // Collect WebVTT cue timings and settings. (5.3 WebVTT cue timings and sett
ings parsing.) |
271 // 1-3 - Let input be the string being parsed and position be a pointer into
input | 275 // Steps 1 - 3 - Let input be the string being parsed and position be a poin
ter into input. |
272 unsigned position = 0; | 276 unsigned position = 0; |
273 skipWhiteSpace(line, &position); | 277 skipWhiteSpace(line, &position); |
274 | 278 |
275 // 4-5 - Collect a WebVTT timestamp. If that fails, then abort and return fa
ilure. Otherwise, let cue's text track cue start time be the collected time. | 279 // Steps 4 - 5 - Collect a WebVTT timestamp. If that fails, then abort and r
eturn failure. Otherwise, let cue's text track cue start time be the collected t
ime. |
276 m_currentStartTime = collectTimeStamp(line, &position); | 280 m_currentStartTime = collectTimeStamp(line, &position); |
277 if (m_currentStartTime == malformedTime) | 281 if (m_currentStartTime == malformedTime) |
278 return BadCue; | 282 return BadCue; |
279 if (position >= line.length()) | 283 if (position >= line.length()) |
280 return BadCue; | 284 return BadCue; |
281 | 285 |
282 skipWhiteSpace(line, &position); | 286 skipWhiteSpace(line, &position); |
283 | 287 |
284 // 6-9 - If the next three characters are not "-->", abort and return failur
e. | 288 // Steps 6 - 9 - If the next three characters are not "-->", abort and retur
n failure. |
285 if (line.find("-->", position) == kNotFound) | 289 if (line.find("-->", position) == kNotFound) |
286 return BadCue; | 290 return BadCue; |
287 position += 3; | 291 position += 3; |
288 if (position >= line.length()) | 292 if (position >= line.length()) |
289 return BadCue; | 293 return BadCue; |
290 | 294 |
291 skipWhiteSpace(line, &position); | 295 skipWhiteSpace(line, &position); |
292 | 296 |
293 // 10-11 - Collect a WebVTT timestamp. If that fails, then abort and return
failure. Otherwise, let cue's text track cue end time be the collected time. | 297 // Steps 10 - 11 - Collect a WebVTT timestamp. If that fails, then abort and
return failure. Otherwise, let cue's text track cue end time be the collected t
ime. |
294 m_currentEndTime = collectTimeStamp(line, &position); | 298 m_currentEndTime = collectTimeStamp(line, &position); |
295 if (m_currentEndTime == malformedTime) | 299 if (m_currentEndTime == malformedTime) |
296 return BadCue; | 300 return BadCue; |
297 skipWhiteSpace(line, &position); | 301 skipWhiteSpace(line, &position); |
298 | 302 |
299 // 12 - Parse the WebVTT settings for the cue (conducted in TextTrackCue). | 303 // Step 12 - Parse the WebVTT settings for the cue (conducted in TextTrackCu
e). |
300 m_currentSettings = line.substring(position, line.length()-1); | 304 m_currentSettings = line.substring(position, line.length()-1); |
301 return CueText; | 305 return CueText; |
302 } | 306 } |
303 | 307 |
304 WebVTTParser::ParseState WebVTTParser::collectCueText(const String& line) | 308 WebVTTParser::ParseState WebVTTParser::collectCueText(const String& line) |
305 { | 309 { |
306 if (line.isEmpty()) { | 310 if (line.isEmpty()) { |
307 createNewCue(); | 311 createNewCue(); |
308 return Id; | 312 return Id; |
309 } | 313 } |
(...skipping 24 matching lines...) Expand all Loading... |
334 | 338 |
335 WebVTTToken m_token; | 339 WebVTTToken m_token; |
336 RefPtr<ContainerNode> m_currentNode; | 340 RefPtr<ContainerNode> m_currentNode; |
337 Vector<AtomicString> m_languageStack; | 341 Vector<AtomicString> m_languageStack; |
338 Document& m_document; | 342 Document& m_document; |
339 }; | 343 }; |
340 | 344 |
341 PassRefPtr<DocumentFragment> WebVTTTreeBuilder::buildFromString(const String& cu
eText) | 345 PassRefPtr<DocumentFragment> WebVTTTreeBuilder::buildFromString(const String& cu
eText) |
342 { | 346 { |
343 // Cue text processing based on | 347 // Cue text processing based on |
344 // 4.8.10.13.4 WebVTT cue text parsing rules and | 348 // 5.4 WebVTT cue text parsing rules, and |
345 // 4.8.10.13.5 WebVTT cue text DOM construction rules. | 349 // 5.5 WebVTT cue text DOM construction rules |
346 | 350 |
347 RefPtr<DocumentFragment> fragment = DocumentFragment::create(m_document); | 351 RefPtr<DocumentFragment> fragment = DocumentFragment::create(m_document); |
348 | 352 |
349 if (cueText.isEmpty()) { | 353 if (cueText.isEmpty()) { |
350 fragment->parserAppendChild(Text::create(m_document, "")); | 354 fragment->parserAppendChild(Text::create(m_document, "")); |
351 return fragment; | 355 return fragment; |
352 } | 356 } |
353 | 357 |
354 m_currentNode = fragment; | 358 m_currentNode = fragment; |
355 | 359 |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
388 m_currentStartTime = 0; | 392 m_currentStartTime = 0; |
389 m_currentEndTime = 0; | 393 m_currentEndTime = 0; |
390 m_currentContent.clear(); | 394 m_currentContent.clear(); |
391 } | 395 } |
392 | 396 |
393 void WebVTTParser::createNewRegion(const String& headerValue) | 397 void WebVTTParser::createNewRegion(const String& headerValue) |
394 { | 398 { |
395 if (headerValue.isEmpty()) | 399 if (headerValue.isEmpty()) |
396 return; | 400 return; |
397 | 401 |
| 402 // Steps 12.5.1 - 12.5.9 - Construct and initialize a WebVTT Region object. |
398 RefPtr<VTTRegion> region = VTTRegion::create(); | 403 RefPtr<VTTRegion> region = VTTRegion::create(); |
399 region->setRegionSettings(headerValue); | 404 region->setRegionSettings(headerValue); |
400 | 405 |
401 // 15.5.10 If the text track list of regions regions contains a region | 406 // Step 12.5.10 If the text track list of regions regions contains a region |
402 // with the same region identifier value as region, remove that region. | 407 // with the same region identifier value as region, remove that region. |
403 for (size_t i = 0; i < m_regionList.size(); ++i) | 408 for (size_t i = 0; i < m_regionList.size(); ++i) |
404 if (m_regionList[i]->id() == region->id()) { | 409 if (m_regionList[i]->id() == region->id()) { |
405 m_regionList.remove(i); | 410 m_regionList.remove(i); |
406 break; | 411 break; |
407 } | 412 } |
408 | 413 |
| 414 // Step 12.5.11 |
409 m_regionList.append(region); | 415 m_regionList.append(region); |
410 } | 416 } |
411 | 417 |
412 double WebVTTParser::collectTimeStamp(const String& line, unsigned* position) | 418 double WebVTTParser::collectTimeStamp(const String& line, unsigned* position) |
413 { | 419 { |
414 // 4.8.10.13.3 Collect a WebVTT timestamp. | 420 // Collect a WebVTT timestamp (5.3 WebVTT cue timings and settings parsing.) |
415 // 1-4 - Initial checks, let most significant units be minutes. | 421 // Steps 1 - 4 - Initial checks, let most significant units be minutes. |
416 enum Mode { minutes, hours }; | 422 enum Mode { minutes, hours }; |
417 Mode mode = minutes; | 423 Mode mode = minutes; |
418 if (*position >= line.length() || !isASCIIDigit(line[*position])) | 424 if (*position >= line.length() || !isASCIIDigit(line[*position])) |
419 return malformedTime; | 425 return malformedTime; |
420 | 426 |
421 // 5-6 - Collect a sequence of characters that are 0-9. | 427 // Steps 5 - 6 - Collect a sequence of characters that are 0-9. |
422 String digits1 = collectDigits(line, position); | 428 String digits1 = collectDigits(line, position); |
423 int value1 = digits1.toInt(); | 429 int value1 = digits1.toInt(); |
424 | 430 |
425 // 7 - If not 2 characters or value is greater than 59, interpret as hours. | 431 // Step 7 - If not 2 characters or value is greater than 59, interpret as ho
urs. |
426 if (digits1.length() != 2 || value1 > 59) | 432 if (digits1.length() != 2 || value1 > 59) |
427 mode = hours; | 433 mode = hours; |
428 | 434 |
429 // 8-12 - Collect the next sequence of 0-9 after ':' (must be 2 chars). | 435 // Steps 8 - 11 - Collect the next sequence of 0-9 after ':' (must be 2 char
s). |
430 if (*position >= line.length() || line[(*position)++] != ':') | 436 if (*position >= line.length() || line[(*position)++] != ':') |
431 return malformedTime; | 437 return malformedTime; |
432 if (*position >= line.length() || !isASCIIDigit(line[(*position)])) | 438 if (*position >= line.length() || !isASCIIDigit(line[(*position)])) |
433 return malformedTime; | 439 return malformedTime; |
434 String digits2 = collectDigits(line, position); | 440 String digits2 = collectDigits(line, position); |
435 int value2 = digits2.toInt(); | 441 int value2 = digits2.toInt(); |
436 if (digits2.length() != 2) | 442 if (digits2.length() != 2) |
437 return malformedTime; | 443 return malformedTime; |
438 | 444 |
439 // 13 - Detect whether this timestamp includes hours. | 445 // Step 12 - Detect whether this timestamp includes hours. |
440 int value3; | 446 int value3; |
441 if (mode == hours || (*position < line.length() && line[*position] == ':'))
{ | 447 if (mode == hours || (*position < line.length() && line[*position] == ':'))
{ |
442 if (*position >= line.length() || line[(*position)++] != ':') | 448 if (*position >= line.length() || line[(*position)++] != ':') |
443 return malformedTime; | 449 return malformedTime; |
444 if (*position >= line.length() || !isASCIIDigit(line[*position])) | 450 if (*position >= line.length() || !isASCIIDigit(line[*position])) |
445 return malformedTime; | 451 return malformedTime; |
446 String digits3 = collectDigits(line, position); | 452 String digits3 = collectDigits(line, position); |
447 if (digits3.length() != 2) | 453 if (digits3.length() != 2) |
448 return malformedTime; | 454 return malformedTime; |
449 value3 = digits3.toInt(); | 455 value3 = digits3.toInt(); |
450 } else { | 456 } else { |
451 value3 = value2; | 457 value3 = value2; |
452 value2 = value1; | 458 value2 = value1; |
453 value1 = 0; | 459 value1 = 0; |
454 } | 460 } |
455 | 461 |
456 // 14-19 - Collect next sequence of 0-9 after '.' (must be 3 chars). | 462 // Steps 13 - 17 - Collect next sequence of 0-9 after '.' (must be 3 chars). |
457 if (*position >= line.length() || line[(*position)++] != '.') | 463 if (*position >= line.length() || line[(*position)++] != '.') |
458 return malformedTime; | 464 return malformedTime; |
459 if (*position >= line.length() || !isASCIIDigit(line[*position])) | 465 if (*position >= line.length() || !isASCIIDigit(line[*position])) |
460 return malformedTime; | 466 return malformedTime; |
461 String digits4 = collectDigits(line, position); | 467 String digits4 = collectDigits(line, position); |
462 if (digits4.length() != 3) | 468 if (digits4.length() != 3) |
463 return malformedTime; | 469 return malformedTime; |
464 int value4 = digits4.toInt(); | 470 int value4 = digits4.toInt(); |
465 if (value2 > 59 || value3 > 59) | 471 if (value2 > 59 || value3 > 59) |
466 return malformedTime; | 472 return malformedTime; |
467 | 473 |
468 // 20-21 - Calculate result. | 474 // Steps 18 - 19 - Calculate result. |
469 return (value1 * secondsPerHour) + (value2 * secondsPerMinute) + value3 + (v
alue4 * secondsPerMillisecond); | 475 return (value1 * secondsPerHour) + (value2 * secondsPerMinute) + value3 + (v
alue4 * secondsPerMillisecond); |
470 } | 476 } |
471 | 477 |
472 static WebVTTNodeType tokenToNodeType(WebVTTToken& token) | 478 static WebVTTNodeType tokenToNodeType(WebVTTToken& token) |
473 { | 479 { |
474 switch (token.name().size()) { | 480 switch (token.name().size()) { |
475 case 1: | 481 case 1: |
476 if (token.name()[0] == 'c') | 482 if (token.name()[0] == 'c') |
477 return WebVTTNodeTypeClass; | 483 return WebVTTNodeTypeClass; |
478 if (token.name()[0] == 'v') | 484 if (token.name()[0] == 'v') |
(...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
558 } | 564 } |
559 | 565 |
560 void WebVTTParser::skipWhiteSpace(const String& line, unsigned* position) | 566 void WebVTTParser::skipWhiteSpace(const String& line, unsigned* position) |
561 { | 567 { |
562 while (*position < line.length() && isASpace(line[*position])) | 568 while (*position < line.length() && isASpace(line[*position])) |
563 (*position)++; | 569 (*position)++; |
564 } | 570 } |
565 | 571 |
566 } | 572 } |
567 | 573 |
OLD | NEW |