Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 : /* This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #include "nsSMILParserUtils.h"
8 : #include "nsSMILKeySpline.h"
9 : #include "nsISMILAttr.h"
10 : #include "nsSMILValue.h"
11 : #include "nsSMILTimeValue.h"
12 : #include "nsSMILTimeValueSpecParams.h"
13 : #include "nsSMILTypes.h"
14 : #include "nsSMILRepeatCount.h"
15 : #include "nsContentUtils.h"
16 : #include "nsCharSeparatedTokenizer.h"
17 : #include "SVGContentUtils.h"
18 :
19 : using namespace mozilla;
20 : using namespace mozilla::dom;
21 : //------------------------------------------------------------------------------
22 : // Helper functions and Constants
23 :
24 : namespace {
25 :
26 : const uint32_t MSEC_PER_SEC = 1000;
27 : const uint32_t MSEC_PER_MIN = 1000 * 60;
28 : const uint32_t MSEC_PER_HOUR = 1000 * 60 * 60;
29 :
30 : #define ACCESSKEY_PREFIX_LC NS_LITERAL_STRING("accesskey(") // SMIL2+
31 : #define ACCESSKEY_PREFIX_CC NS_LITERAL_STRING("accessKey(") // SVG/SMIL ANIM
32 : #define REPEAT_PREFIX NS_LITERAL_STRING("repeat(")
33 : #define WALLCLOCK_PREFIX NS_LITERAL_STRING("wallclock(")
34 :
35 : inline bool
36 0 : SkipWhitespace(RangedPtr<const char16_t>& aIter,
37 : const RangedPtr<const char16_t>& aEnd)
38 : {
39 0 : while (aIter != aEnd) {
40 0 : if (!IsSVGWhitespace(*aIter)) {
41 0 : return true;
42 : }
43 0 : ++aIter;
44 : }
45 0 : return false;
46 : }
47 :
48 : inline bool
49 0 : ParseColon(RangedPtr<const char16_t>& aIter,
50 : const RangedPtr<const char16_t>& aEnd)
51 : {
52 0 : if (aIter == aEnd || *aIter != ':') {
53 0 : return false;
54 : }
55 0 : ++aIter;
56 0 : return true;
57 : }
58 :
59 : /*
60 : * Exactly two digits in the range 00 - 59 are expected.
61 : */
62 : bool
63 0 : ParseSecondsOrMinutes(RangedPtr<const char16_t>& aIter,
64 : const RangedPtr<const char16_t>& aEnd,
65 : uint32_t& aValue)
66 : {
67 0 : if (aIter == aEnd || !SVGContentUtils::IsDigit(*aIter)) {
68 0 : return false;
69 : }
70 :
71 0 : RangedPtr<const char16_t> iter(aIter);
72 :
73 0 : if (++iter == aEnd || !SVGContentUtils::IsDigit(*iter)) {
74 0 : return false;
75 : }
76 :
77 0 : uint32_t value = 10 * SVGContentUtils::DecimalDigitValue(*aIter) +
78 0 : SVGContentUtils::DecimalDigitValue(*iter);
79 0 : if (value > 59) {
80 0 : return false;
81 : }
82 0 : if (++iter != aEnd && SVGContentUtils::IsDigit(*iter)) {
83 0 : return false;
84 : }
85 :
86 0 : aValue = value;
87 0 : aIter = iter;
88 0 : return true;
89 : }
90 :
91 : inline bool
92 0 : ParseClockMetric(RangedPtr<const char16_t>& aIter,
93 : const RangedPtr<const char16_t>& aEnd,
94 : uint32_t& aMultiplier)
95 : {
96 0 : if (aIter == aEnd) {
97 0 : aMultiplier = MSEC_PER_SEC;
98 0 : return true;
99 : }
100 :
101 0 : switch (*aIter) {
102 : case 'h':
103 0 : if (++aIter == aEnd) {
104 0 : aMultiplier = MSEC_PER_HOUR;
105 0 : return true;
106 : }
107 0 : return false;
108 : case 'm':
109 : {
110 0 : const nsAString& metric = Substring(aIter.get(), aEnd.get());
111 0 : if (metric.EqualsLiteral("min")) {
112 0 : aMultiplier = MSEC_PER_MIN;
113 0 : aIter = aEnd;
114 0 : return true;
115 : }
116 0 : if (metric.EqualsLiteral("ms")) {
117 0 : aMultiplier = 1;
118 0 : aIter = aEnd;
119 0 : return true;
120 : }
121 : }
122 0 : return false;
123 : case 's':
124 0 : if (++aIter == aEnd) {
125 0 : aMultiplier = MSEC_PER_SEC;
126 0 : return true;
127 : }
128 : }
129 0 : return false;
130 : }
131 :
132 : /**
133 : * See http://www.w3.org/TR/SVG/animate.html#ClockValueSyntax
134 : */
135 : bool
136 0 : ParseClockValue(RangedPtr<const char16_t>& aIter,
137 : const RangedPtr<const char16_t>& aEnd,
138 : nsSMILTimeValue* aResult)
139 : {
140 0 : if (aIter == aEnd) {
141 0 : return false;
142 : }
143 :
144 : // TIMECOUNT_VALUE ::= Timecount ("." Fraction)? (Metric)?
145 : // PARTIAL_CLOCK_VALUE ::= Minutes ":" Seconds ("." Fraction)?
146 : // FULL_CLOCK_VALUE ::= Hours ":" Minutes ":" Seconds ("." Fraction)?
147 : enum ClockType {
148 : TIMECOUNT_VALUE,
149 : PARTIAL_CLOCK_VALUE,
150 : FULL_CLOCK_VALUE
151 : };
152 :
153 0 : int32_t clockType = TIMECOUNT_VALUE;
154 :
155 0 : RangedPtr<const char16_t> iter(aIter);
156 :
157 : // Determine which type of clock value we have by counting the number
158 : // of colons in the string.
159 0 : do {
160 0 : switch (*iter) {
161 : case ':':
162 0 : if (clockType == FULL_CLOCK_VALUE) {
163 0 : return false;
164 : }
165 0 : ++clockType;
166 0 : break;
167 : case 'e':
168 : case 'E':
169 : case '-':
170 : case '+':
171 : // Exclude anything invalid (for clock values)
172 : // that number parsing might otherwise allow.
173 0 : return false;
174 : }
175 0 : ++iter;
176 : } while (iter != aEnd);
177 :
178 0 : iter = aIter;
179 :
180 0 : int32_t hours = 0, timecount;
181 0 : double fraction = 0.0;
182 : uint32_t minutes, seconds, multiplier;
183 :
184 0 : switch (clockType) {
185 : case FULL_CLOCK_VALUE:
186 0 : if (!SVGContentUtils::ParseInteger(iter, aEnd, hours) ||
187 0 : !ParseColon(iter, aEnd)) {
188 0 : return false;
189 : }
190 : MOZ_FALLTHROUGH;
191 : case PARTIAL_CLOCK_VALUE:
192 0 : if (!ParseSecondsOrMinutes(iter, aEnd, minutes) ||
193 0 : !ParseColon(iter, aEnd) ||
194 0 : !ParseSecondsOrMinutes(iter, aEnd, seconds)) {
195 0 : return false;
196 : }
197 0 : if (iter != aEnd &&
198 0 : (*iter != '.' ||
199 0 : !SVGContentUtils::ParseNumber(iter, aEnd, fraction))) {
200 0 : return false;
201 : }
202 0 : aResult->SetMillis(nsSMILTime(hours) * MSEC_PER_HOUR +
203 0 : minutes * MSEC_PER_MIN +
204 0 : seconds * MSEC_PER_SEC +
205 0 : NS_round(fraction * MSEC_PER_SEC));
206 0 : aIter = iter;
207 0 : return true;
208 : case TIMECOUNT_VALUE:
209 0 : if (!SVGContentUtils::ParseInteger(iter, aEnd, timecount)) {
210 0 : return false;
211 : }
212 0 : if (iter != aEnd && *iter == '.' &&
213 0 : !SVGContentUtils::ParseNumber(iter, aEnd, fraction)) {
214 0 : return false;
215 : }
216 0 : if (!ParseClockMetric(iter, aEnd, multiplier)) {
217 0 : return false;
218 : }
219 0 : aResult->SetMillis(nsSMILTime(timecount) * multiplier +
220 0 : NS_round(fraction * multiplier));
221 0 : aIter = iter;
222 0 : return true;
223 : }
224 :
225 0 : return false;
226 : }
227 :
228 : bool
229 0 : ParseOffsetValue(RangedPtr<const char16_t>& aIter,
230 : const RangedPtr<const char16_t>& aEnd,
231 : nsSMILTimeValue* aResult)
232 : {
233 0 : RangedPtr<const char16_t> iter(aIter);
234 :
235 : int32_t sign;
236 0 : if (!SVGContentUtils::ParseOptionalSign(iter, aEnd, sign) ||
237 0 : !SkipWhitespace(iter, aEnd) ||
238 0 : !ParseClockValue(iter, aEnd, aResult)) {
239 0 : return false;
240 : }
241 0 : if (sign == -1) {
242 0 : aResult->SetMillis(-aResult->GetMillis());
243 : }
244 0 : aIter = iter;
245 0 : return true;
246 : }
247 :
248 : bool
249 0 : ParseOffsetValue(const nsAString& aSpec,
250 : nsSMILTimeValue* aResult)
251 : {
252 0 : RangedPtr<const char16_t> iter(SVGContentUtils::GetStartRangedPtr(aSpec));
253 0 : const RangedPtr<const char16_t> end(SVGContentUtils::GetEndRangedPtr(aSpec));
254 :
255 0 : return ParseOffsetValue(iter, end, aResult) && iter == end;
256 : }
257 :
258 : bool
259 0 : ParseOptionalOffset(RangedPtr<const char16_t>& aIter,
260 : const RangedPtr<const char16_t>& aEnd,
261 : nsSMILTimeValue* aResult)
262 : {
263 0 : if (aIter == aEnd) {
264 0 : aResult->SetMillis(0L);
265 0 : return true;
266 : }
267 :
268 0 : return SkipWhitespace(aIter, aEnd) &&
269 0 : ParseOffsetValue(aIter, aEnd, aResult);
270 : }
271 :
272 : bool
273 0 : ParseAccessKey(const nsAString& aSpec, nsSMILTimeValueSpecParams& aResult)
274 : {
275 0 : MOZ_ASSERT(StringBeginsWith(aSpec, ACCESSKEY_PREFIX_CC) ||
276 : StringBeginsWith(aSpec, ACCESSKEY_PREFIX_LC),
277 : "Calling ParseAccessKey on non-accesskey-type spec");
278 :
279 0 : nsSMILTimeValueSpecParams result;
280 0 : result.mType = nsSMILTimeValueSpecParams::ACCESSKEY;
281 :
282 0 : MOZ_ASSERT(ACCESSKEY_PREFIX_LC.Length() == ACCESSKEY_PREFIX_CC.Length(),
283 : "Case variations for accesskey prefix differ in length");
284 :
285 0 : RangedPtr<const char16_t> iter(SVGContentUtils::GetStartRangedPtr(aSpec));
286 0 : RangedPtr<const char16_t> end(SVGContentUtils::GetEndRangedPtr(aSpec));
287 :
288 0 : iter += ACCESSKEY_PREFIX_LC.Length();
289 :
290 : // Expecting at least <accesskey> + ')'
291 0 : if (end - iter < 2)
292 0 : return false;
293 :
294 0 : uint32_t c = *iter++;
295 :
296 : // Process 32-bit codepoints
297 0 : if (NS_IS_HIGH_SURROGATE(c)) {
298 0 : if (end - iter < 2) // Expecting at least low-surrogate + ')'
299 0 : return false;
300 0 : uint32_t lo = *iter++;
301 0 : if (!NS_IS_LOW_SURROGATE(lo))
302 0 : return false;
303 0 : c = SURROGATE_TO_UCS4(c, lo);
304 : // XML 1.1 says that 0xFFFE and 0xFFFF are not valid characters
305 0 : } else if (NS_IS_LOW_SURROGATE(c) || c == 0xFFFE || c == 0xFFFF) {
306 0 : return false;
307 : }
308 :
309 0 : result.mRepeatIterationOrAccessKey = c;
310 :
311 0 : if (*iter++ != ')')
312 0 : return false;
313 :
314 0 : if (!ParseOptionalOffset(iter, end, &result.mOffset) || iter != end) {
315 0 : return false;
316 : }
317 0 : aResult = result;
318 0 : return true;
319 : }
320 :
321 : void
322 0 : MoveToNextToken(RangedPtr<const char16_t>& aIter,
323 : const RangedPtr<const char16_t>& aEnd,
324 : bool aBreakOnDot,
325 : bool& aIsAnyCharEscaped)
326 : {
327 0 : aIsAnyCharEscaped = false;
328 :
329 0 : bool isCurrentCharEscaped = false;
330 :
331 0 : while (aIter != aEnd && !IsSVGWhitespace(*aIter)) {
332 0 : if (isCurrentCharEscaped) {
333 0 : isCurrentCharEscaped = false;
334 : } else {
335 0 : if (*aIter == '+' || *aIter == '-' ||
336 0 : (aBreakOnDot && *aIter == '.')) {
337 0 : break;
338 : }
339 0 : if (*aIter == '\\') {
340 0 : isCurrentCharEscaped = true;
341 0 : aIsAnyCharEscaped = true;
342 : }
343 : }
344 0 : ++aIter;
345 : }
346 0 : }
347 :
348 : already_AddRefed<nsIAtom>
349 0 : ConvertUnescapedTokenToAtom(const nsAString& aToken)
350 : {
351 : // Whether the token is an id-ref or event-symbol it should be a valid NCName
352 0 : if (aToken.IsEmpty() || NS_FAILED(nsContentUtils::CheckQName(aToken, false)))
353 0 : return nullptr;
354 0 : return NS_Atomize(aToken);
355 : }
356 :
357 : already_AddRefed<nsIAtom>
358 0 : ConvertTokenToAtom(const nsAString& aToken,
359 : bool aUnescapeToken)
360 : {
361 : // Unescaping involves making a copy of the string which we'd like to avoid if possible
362 0 : if (!aUnescapeToken) {
363 0 : return ConvertUnescapedTokenToAtom(aToken);
364 : }
365 :
366 0 : nsAutoString token(aToken);
367 :
368 0 : const char16_t* read = token.BeginReading();
369 0 : const char16_t* const end = token.EndReading();
370 0 : char16_t* write = token.BeginWriting();
371 0 : bool escape = false;
372 :
373 0 : while (read != end) {
374 0 : MOZ_ASSERT(write <= read, "Writing past where we've read");
375 0 : if (!escape && *read == '\\') {
376 0 : escape = true;
377 0 : ++read;
378 : } else {
379 0 : *write++ = *read++;
380 0 : escape = false;
381 : }
382 : }
383 0 : token.Truncate(write - token.BeginReading());
384 :
385 0 : return ConvertUnescapedTokenToAtom(token);
386 : }
387 :
388 : bool
389 0 : ParseElementBaseTimeValueSpec(const nsAString& aSpec,
390 : nsSMILTimeValueSpecParams& aResult)
391 : {
392 0 : nsSMILTimeValueSpecParams result;
393 :
394 : //
395 : // The spec will probably look something like one of these
396 : //
397 : // element-name.begin
398 : // element-name.event-name
399 : // event-name
400 : // element-name.repeat(3)
401 : // event\.name
402 : //
403 : // Technically `repeat(3)' is permitted but the behaviour in this case is not
404 : // defined (for SMIL Animation) so we don't support it here.
405 : //
406 :
407 0 : RangedPtr<const char16_t> start(SVGContentUtils::GetStartRangedPtr(aSpec));
408 0 : RangedPtr<const char16_t> end(SVGContentUtils::GetEndRangedPtr(aSpec));
409 :
410 0 : if (start == end) {
411 0 : return false;
412 : }
413 :
414 0 : RangedPtr<const char16_t> tokenEnd(start);
415 :
416 : bool requiresUnescaping;
417 0 : MoveToNextToken(tokenEnd, end, true, requiresUnescaping);
418 :
419 : RefPtr<nsIAtom> atom =
420 0 : ConvertTokenToAtom(Substring(start.get(), tokenEnd.get()),
421 0 : requiresUnescaping);
422 0 : if (atom == nullptr) {
423 0 : return false;
424 : }
425 :
426 : // Parse the second token if there is one
427 0 : if (tokenEnd != end && *tokenEnd == '.') {
428 0 : result.mDependentElemID = atom;
429 :
430 0 : ++tokenEnd;
431 0 : start = tokenEnd;
432 0 : MoveToNextToken(tokenEnd, end, false, requiresUnescaping);
433 :
434 0 : const nsAString& token2 = Substring(start.get(), tokenEnd.get());
435 :
436 : // element-name.begin
437 0 : if (token2.EqualsLiteral("begin")) {
438 0 : result.mType = nsSMILTimeValueSpecParams::SYNCBASE;
439 0 : result.mSyncBegin = true;
440 : // element-name.end
441 0 : } else if (token2.EqualsLiteral("end")) {
442 0 : result.mType = nsSMILTimeValueSpecParams::SYNCBASE;
443 0 : result.mSyncBegin = false;
444 : // element-name.repeat(digit+)
445 0 : } else if (StringBeginsWith(token2, REPEAT_PREFIX)) {
446 0 : start += REPEAT_PREFIX.Length();
447 : int32_t repeatValue;
448 0 : if (start == tokenEnd || *start == '+' || *start == '-' ||
449 0 : !SVGContentUtils::ParseInteger(start, tokenEnd, repeatValue)) {
450 0 : return false;
451 : }
452 0 : if (start == tokenEnd || *start != ')') {
453 0 : return false;
454 : }
455 0 : result.mType = nsSMILTimeValueSpecParams::REPEAT;
456 0 : result.mRepeatIterationOrAccessKey = repeatValue;
457 : // element-name.event-symbol
458 : } else {
459 0 : atom = ConvertTokenToAtom(token2, requiresUnescaping);
460 0 : if (atom == nullptr) {
461 0 : return false;
462 : }
463 0 : result.mType = nsSMILTimeValueSpecParams::EVENT;
464 0 : result.mEventSymbol = atom;
465 : }
466 : } else {
467 : // event-symbol
468 0 : result.mType = nsSMILTimeValueSpecParams::EVENT;
469 0 : result.mEventSymbol = atom;
470 : }
471 :
472 : // We've reached the end of the token, so we should now be either looking at
473 : // a '+', '-' (possibly with whitespace before it), or the end.
474 0 : if (!ParseOptionalOffset(tokenEnd, end, &result.mOffset) || tokenEnd != end) {
475 0 : return false;
476 : }
477 0 : aResult = result;
478 0 : return true;
479 : }
480 :
481 : } // namespace
482 :
483 : //------------------------------------------------------------------------------
484 : // Implementation
485 :
486 : const nsDependentSubstring
487 0 : nsSMILParserUtils::TrimWhitespace(const nsAString& aString)
488 : {
489 0 : nsAString::const_iterator start, end;
490 :
491 0 : aString.BeginReading(start);
492 0 : aString.EndReading(end);
493 :
494 : // Skip whitespace characters at the beginning
495 0 : while (start != end && IsSVGWhitespace(*start)) {
496 0 : ++start;
497 : }
498 :
499 : // Skip whitespace characters at the end.
500 0 : while (end != start) {
501 0 : --end;
502 :
503 0 : if (!IsSVGWhitespace(*end)) {
504 : // Step back to the last non-whitespace character.
505 0 : ++end;
506 :
507 0 : break;
508 : }
509 : }
510 :
511 0 : return Substring(start, end);
512 : }
513 :
514 : bool
515 0 : nsSMILParserUtils::ParseKeySplines(const nsAString& aSpec,
516 : FallibleTArray<nsSMILKeySpline>& aKeySplines)
517 : {
518 0 : nsCharSeparatedTokenizerTemplate<IsSVGWhitespace> controlPointTokenizer(aSpec, ';');
519 0 : while (controlPointTokenizer.hasMoreTokens()) {
520 :
521 : nsCharSeparatedTokenizerTemplate<IsSVGWhitespace>
522 0 : tokenizer(controlPointTokenizer.nextToken(), ',',
523 0 : nsCharSeparatedTokenizer::SEPARATOR_OPTIONAL);
524 :
525 : double values[4];
526 0 : for (int i = 0 ; i < 4; i++) {
527 0 : if (!tokenizer.hasMoreTokens() ||
528 0 : !SVGContentUtils::ParseNumber(tokenizer.nextToken(), values[i]) ||
529 0 : values[i] > 1.0 || values[i] < 0.0) {
530 0 : return false;
531 : }
532 : }
533 0 : if (tokenizer.hasMoreTokens() ||
534 0 : tokenizer.separatorAfterCurrentToken() ||
535 0 : !aKeySplines.AppendElement(nsSMILKeySpline(values[0],
536 : values[1],
537 : values[2],
538 : values[3]),
539 : fallible)) {
540 0 : return false;
541 : }
542 : }
543 :
544 0 : return !aKeySplines.IsEmpty();
545 : }
546 :
547 : bool
548 0 : nsSMILParserUtils::ParseSemicolonDelimitedProgressList(const nsAString& aSpec,
549 : bool aNonDecreasing,
550 : FallibleTArray<double>& aArray)
551 : {
552 0 : nsCharSeparatedTokenizerTemplate<IsSVGWhitespace> tokenizer(aSpec, ';');
553 :
554 0 : double previousValue = -1.0;
555 :
556 0 : while (tokenizer.hasMoreTokens()) {
557 : double value;
558 0 : if (!SVGContentUtils::ParseNumber(tokenizer.nextToken(), value)) {
559 0 : return false;
560 : }
561 :
562 0 : if (value > 1.0 || value < 0.0 ||
563 0 : (aNonDecreasing && value < previousValue)) {
564 0 : return false;
565 : }
566 :
567 0 : if (!aArray.AppendElement(value, fallible)) {
568 0 : return false;
569 : }
570 0 : previousValue = value;
571 : }
572 :
573 0 : return !aArray.IsEmpty();
574 : }
575 :
576 : // Helper class for ParseValues
577 : class MOZ_STACK_CLASS SMILValueParser :
578 : public nsSMILParserUtils::GenericValueParser
579 : {
580 : public:
581 0 : SMILValueParser(const SVGAnimationElement* aSrcElement,
582 : const nsISMILAttr* aSMILAttr,
583 : FallibleTArray<nsSMILValue>* aValuesArray,
584 0 : bool* aPreventCachingOfSandwich) :
585 : mSrcElement(aSrcElement),
586 : mSMILAttr(aSMILAttr),
587 : mValuesArray(aValuesArray),
588 0 : mPreventCachingOfSandwich(aPreventCachingOfSandwich)
589 0 : {}
590 :
591 0 : virtual bool Parse(const nsAString& aValueStr) override {
592 0 : nsSMILValue newValue;
593 0 : bool tmpPreventCachingOfSandwich = false;
594 0 : if (NS_FAILED(mSMILAttr->ValueFromString(aValueStr, mSrcElement, newValue,
595 : tmpPreventCachingOfSandwich)))
596 0 : return false;
597 :
598 0 : if (!mValuesArray->AppendElement(newValue, fallible)) {
599 0 : return false;
600 : }
601 0 : if (tmpPreventCachingOfSandwich) {
602 0 : *mPreventCachingOfSandwich = true;
603 : }
604 0 : return true;
605 : }
606 : protected:
607 : const SVGAnimationElement* mSrcElement;
608 : const nsISMILAttr* mSMILAttr;
609 : FallibleTArray<nsSMILValue>* mValuesArray;
610 : bool* mPreventCachingOfSandwich;
611 : };
612 :
613 : bool
614 0 : nsSMILParserUtils::ParseValues(const nsAString& aSpec,
615 : const SVGAnimationElement* aSrcElement,
616 : const nsISMILAttr& aAttribute,
617 : FallibleTArray<nsSMILValue>& aValuesArray,
618 : bool& aPreventCachingOfSandwich)
619 : {
620 : // Assume all results can be cached, until we find one that can't.
621 0 : aPreventCachingOfSandwich = false;
622 : SMILValueParser valueParser(aSrcElement, &aAttribute,
623 0 : &aValuesArray, &aPreventCachingOfSandwich);
624 0 : return ParseValuesGeneric(aSpec, valueParser);
625 : }
626 :
627 : bool
628 0 : nsSMILParserUtils::ParseValuesGeneric(const nsAString& aSpec,
629 : GenericValueParser& aParser)
630 : {
631 0 : nsCharSeparatedTokenizerTemplate<IsSVGWhitespace> tokenizer(aSpec, ';');
632 0 : if (!tokenizer.hasMoreTokens()) { // Empty list
633 0 : return false;
634 : }
635 :
636 0 : while (tokenizer.hasMoreTokens()) {
637 0 : if (!aParser.Parse(tokenizer.nextToken())) {
638 0 : return false;
639 : }
640 : }
641 :
642 0 : return true;
643 : }
644 :
645 : bool
646 0 : nsSMILParserUtils::ParseRepeatCount(const nsAString& aSpec,
647 : nsSMILRepeatCount& aResult)
648 : {
649 : const nsAString& spec =
650 0 : nsSMILParserUtils::TrimWhitespace(aSpec);
651 :
652 0 : if (spec.EqualsLiteral("indefinite")) {
653 0 : aResult.SetIndefinite();
654 0 : return true;
655 : }
656 :
657 : double value;
658 0 : if (!SVGContentUtils::ParseNumber(spec, value) || value <= 0.0) {
659 0 : return false;
660 : }
661 0 : aResult = value;
662 0 : return true;
663 : }
664 :
665 : bool
666 0 : nsSMILParserUtils::ParseTimeValueSpecParams(const nsAString& aSpec,
667 : nsSMILTimeValueSpecParams& aResult)
668 : {
669 0 : const nsAString& spec = TrimWhitespace(aSpec);
670 :
671 0 : if (spec.EqualsLiteral("indefinite")) {
672 0 : aResult.mType = nsSMILTimeValueSpecParams::INDEFINITE;
673 0 : return true;
674 : }
675 :
676 : // offset type
677 0 : if (ParseOffsetValue(spec, &aResult.mOffset)) {
678 0 : aResult.mType = nsSMILTimeValueSpecParams::OFFSET;
679 0 : return true;
680 : }
681 :
682 : // wallclock type
683 0 : if (StringBeginsWith(spec, WALLCLOCK_PREFIX)) {
684 0 : return false; // Wallclock times not implemented
685 : }
686 :
687 : // accesskey type
688 0 : if (StringBeginsWith(spec, ACCESSKEY_PREFIX_LC) ||
689 0 : StringBeginsWith(spec, ACCESSKEY_PREFIX_CC)) {
690 0 : return ParseAccessKey(spec, aResult);
691 : }
692 :
693 : // event, syncbase, or repeat
694 0 : return ParseElementBaseTimeValueSpec(spec, aResult);
695 : }
696 :
697 : bool
698 0 : nsSMILParserUtils::ParseClockValue(const nsAString& aSpec,
699 : nsSMILTimeValue* aResult)
700 : {
701 0 : RangedPtr<const char16_t> iter(SVGContentUtils::GetStartRangedPtr(aSpec));
702 0 : RangedPtr<const char16_t> end(SVGContentUtils::GetEndRangedPtr(aSpec));
703 :
704 0 : return ::ParseClockValue(iter, end, aResult) && iter == end;
705 : }
706 :
707 : int32_t
708 0 : nsSMILParserUtils::CheckForNegativeNumber(const nsAString& aStr)
709 : {
710 0 : int32_t absValLocation = -1;
711 :
712 0 : RangedPtr<const char16_t> start(SVGContentUtils::GetStartRangedPtr(aStr));
713 0 : RangedPtr<const char16_t> iter = start;
714 0 : RangedPtr<const char16_t> end(SVGContentUtils::GetEndRangedPtr(aStr));
715 :
716 : // Skip initial whitespace
717 0 : while (iter != end && IsSVGWhitespace(*iter)) {
718 0 : ++iter;
719 : }
720 :
721 : // Check for dash
722 0 : if (iter != end && *iter == '-') {
723 0 : ++iter;
724 : // Check for numeric character
725 0 : if (iter != end && SVGContentUtils::IsDigit(*iter)) {
726 0 : absValLocation = iter - start;
727 : }
728 : }
729 0 : return absValLocation;
730 : }
|