| 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 |