1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is the Mozilla SMIL module.
16 : *
17 : * The Initial Developer of the Original Code is Brian Birtles.
18 : * Portions created by the Initial Developer are Copyright (C) 2005
19 : * the Initial Developer. All Rights Reserved.
20 : *
21 : * Contributor(s):
22 : * Brian Birtles <birtles@gmail.com>
23 : *
24 : * Alternatively, the contents of this file may be used under the terms of
25 : * either of the GNU General Public License Version 2 or later (the "GPL"),
26 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 : * in which case the provisions of the GPL or the LGPL are applicable instead
28 : * of those above. If you wish to allow use of your version of this file only
29 : * under the terms of either the GPL or the LGPL, and not to allow others to
30 : * use your version of this file under the terms of the MPL, indicate your
31 : * decision by deleting the provisions above and replace them with the notice
32 : * and other provisions required by the GPL or the LGPL. If you do not delete
33 : * the provisions above, a recipient may use your version of this file under
34 : * the terms of any one of the MPL, the GPL or the LGPL.
35 : *
36 : * ***** END LICENSE BLOCK ***** */
37 :
38 : #include "nsSMILParserUtils.h"
39 : #include "nsISMILAttr.h"
40 : #include "nsSMILValue.h"
41 : #include "nsSMILTimeValue.h"
42 : #include "nsSMILTimeValueSpecParams.h"
43 : #include "nsSMILTypes.h"
44 : #include "nsSMILRepeatCount.h"
45 : #include "nsContentUtils.h"
46 : #include "nsString.h"
47 : #include "prdtoa.h"
48 : #include "nsCRT.h"
49 : #include "nsCOMPtr.h"
50 : #include "prlong.h"
51 : #include "nsCharSeparatedTokenizer.h"
52 :
53 : //------------------------------------------------------------------------------
54 : // Helper functions and Constants
55 :
56 : namespace {
57 :
58 : const PRUint32 MSEC_PER_SEC = 1000;
59 : const PRUint32 MSEC_PER_MIN = 1000 * 60;
60 : const PRUint32 MSEC_PER_HOUR = 1000 * 60 * 60;
61 : const PRInt32 DECIMAL_BASE = 10;
62 :
63 : #define ACCESSKEY_PREFIX_LC NS_LITERAL_STRING("accesskey(") // SMIL2+
64 : #define ACCESSKEY_PREFIX_CC NS_LITERAL_STRING("accessKey(") // SVG/SMIL ANIM
65 : #define REPEAT_PREFIX NS_LITERAL_STRING("repeat(")
66 : #define WALLCLOCK_PREFIX NS_LITERAL_STRING("wallclock(")
67 :
68 : // NS_IS_SPACE relies on isspace which may return true for \xB and \xC but
69 : // SMILANIM does not consider these characters to be whitespace.
70 : inline bool
71 0 : IsSpace(const PRUnichar c)
72 : {
73 0 : return (c == 0x9 || c == 0xA || c == 0xD || c == 0x20);
74 : }
75 :
76 : template<class T>
77 : inline void
78 0 : SkipBeginWsp(T& aStart, T aEnd)
79 : {
80 0 : while (aStart != aEnd && IsSpace(*aStart)) {
81 0 : ++aStart;
82 : }
83 0 : }
84 :
85 : inline void
86 0 : SkipBeginEndWsp(const PRUnichar*& aStart, const PRUnichar*& aEnd)
87 : {
88 0 : SkipBeginWsp(aStart, aEnd);
89 0 : while (aEnd != aStart && IsSpace(*(aEnd - 1))) {
90 0 : --aEnd;
91 : }
92 0 : }
93 :
94 : double
95 0 : GetFloat(const char*& aStart, const char* aEnd, nsresult* aErrorCode)
96 : {
97 : char* floatEnd;
98 0 : double value = PR_strtod(aStart, &floatEnd);
99 :
100 : nsresult rv;
101 :
102 0 : if (floatEnd == aStart || floatEnd > aEnd) {
103 0 : rv = NS_ERROR_FAILURE;
104 : } else {
105 0 : aStart = floatEnd;
106 0 : rv = NS_OK;
107 : }
108 :
109 0 : if (aErrorCode) {
110 0 : *aErrorCode = rv;
111 : }
112 :
113 0 : return value;
114 : }
115 :
116 : size_t
117 0 : GetUnsignedInt(const nsAString& aStr, PRUint32& aResult)
118 : {
119 0 : NS_ConvertUTF16toUTF8 cstr(aStr);
120 0 : const char* str = cstr.get();
121 :
122 : char* rest;
123 0 : PRInt32 value = strtol(str, &rest, DECIMAL_BASE);
124 :
125 0 : if (rest == str || value < 0)
126 0 : return 0;
127 :
128 0 : aResult = static_cast<PRUint32>(value);
129 0 : return rest - str;
130 : }
131 :
132 : bool
133 0 : GetUnsignedIntAndEndParen(const nsAString& aStr, PRUint32& aResult)
134 : {
135 0 : size_t intLen = GetUnsignedInt(aStr, aResult);
136 :
137 0 : const PRUnichar* start = aStr.BeginReading();
138 0 : const PRUnichar* end = aStr.EndReading();
139 :
140 : // Make sure the string is only digit+')'
141 0 : if (intLen == 0 || start + intLen + 1 != end || *(start + intLen) != ')')
142 0 : return false;
143 :
144 0 : return true;
145 : }
146 :
147 : inline bool
148 0 : ConsumeSubstring(const char*& aStart, const char* aEnd, const char* aSubstring)
149 : {
150 0 : size_t substrLen = PL_strlen(aSubstring);
151 :
152 0 : if (static_cast<size_t>(aEnd - aStart) < substrLen)
153 0 : return false;
154 :
155 0 : bool result = false;
156 :
157 0 : if (PL_strstr(aStart, aSubstring) == aStart) {
158 0 : aStart += substrLen;
159 0 : result = true;
160 : }
161 :
162 0 : return result;
163 : }
164 :
165 : bool
166 0 : ParseClockComponent(const char*& aStart,
167 : const char* aEnd,
168 : double& aResult,
169 : bool& aIsReal,
170 : bool& aCouldBeMin,
171 : bool& aCouldBeSec)
172 : {
173 : nsresult rv;
174 0 : const char* begin = aStart;
175 0 : double value = GetFloat(aStart, aEnd, &rv);
176 :
177 : // Check a number was found
178 0 : if (NS_FAILED(rv))
179 0 : return false;
180 :
181 : // Check that it's not expressed in exponential form
182 0 : size_t len = aStart - begin;
183 0 : bool isExp = (PL_strnpbrk(begin, "eE", len) != nsnull);
184 0 : if (isExp)
185 0 : return false;
186 :
187 : // Don't allow real numbers of the form "23."
188 0 : if (*(aStart - 1) == '.')
189 0 : return false;
190 :
191 : // Number looks good
192 0 : aResult = value;
193 :
194 : // Set some flags so we can check this number is valid once we know
195 : // whether it's an hour, minute string etc.
196 0 : aIsReal = (PL_strnchr(begin, '.', len) != nsnull);
197 0 : aCouldBeMin = (value < 60.0 && (len == 2));
198 : aCouldBeSec = (value < 60.0 ||
199 0 : (value == 60.0 && begin[0] == '5')); // Take care of rounding error
200 : aCouldBeSec &= (len >= 2 &&
201 0 : (begin[2] == '\0' || begin[2] == '.' || IsSpace(begin[2])));
202 :
203 0 : return true;
204 : }
205 :
206 : bool
207 0 : ParseMetricMultiplicand(const char*& aStart,
208 : const char* aEnd,
209 : PRInt32& multiplicand)
210 : {
211 0 : bool result = false;
212 :
213 0 : size_t len = aEnd - aStart;
214 0 : const char* cur = aStart;
215 :
216 0 : if (len) {
217 0 : switch (*cur++)
218 : {
219 : case 'h':
220 0 : multiplicand = MSEC_PER_HOUR;
221 0 : result = true;
222 0 : break;
223 : case 'm':
224 0 : if (len >= 2) {
225 0 : if (*cur == 's') {
226 0 : ++cur;
227 0 : multiplicand = 1;
228 0 : result = true;
229 0 : } else if (len >= 3 && *cur++ == 'i' && *cur++ == 'n') {
230 0 : multiplicand = MSEC_PER_MIN;
231 0 : result = true;
232 : }
233 : }
234 0 : break;
235 : case 's':
236 0 : multiplicand = MSEC_PER_SEC;
237 0 : result = true;
238 0 : break;
239 : }
240 : }
241 :
242 0 : if (result) {
243 0 : aStart = cur;
244 : }
245 :
246 0 : return result;
247 : }
248 :
249 : nsresult
250 0 : ParseOptionalOffset(const nsAString& aSpec, nsSMILTimeValueSpecParams& aResult)
251 : {
252 0 : if (aSpec.IsEmpty()) {
253 0 : aResult.mOffset.SetMillis(0);
254 0 : return NS_OK;
255 : }
256 :
257 0 : if (aSpec.First() != '+' && aSpec.First() != '-')
258 0 : return NS_ERROR_FAILURE;
259 :
260 : return nsSMILParserUtils::ParseClockValue(aSpec, &aResult.mOffset,
261 0 : nsSMILParserUtils::kClockValueAllowSign);
262 : }
263 :
264 : nsresult
265 0 : ParseAccessKey(const nsAString& aSpec, nsSMILTimeValueSpecParams& aResult)
266 : {
267 0 : NS_ABORT_IF_FALSE(StringBeginsWith(aSpec, ACCESSKEY_PREFIX_CC) ||
268 : StringBeginsWith(aSpec, ACCESSKEY_PREFIX_LC),
269 : "Calling ParseAccessKey on non-accesskey-type spec");
270 :
271 0 : nsSMILTimeValueSpecParams result;
272 0 : result.mType = nsSMILTimeValueSpecParams::ACCESSKEY;
273 :
274 0 : NS_ABORT_IF_FALSE(
275 : ACCESSKEY_PREFIX_LC.Length() == ACCESSKEY_PREFIX_CC.Length(),
276 : "Case variations for accesskey prefix differ in length");
277 0 : const PRUnichar* start = aSpec.BeginReading() + ACCESSKEY_PREFIX_LC.Length();
278 0 : const PRUnichar* end = aSpec.EndReading();
279 :
280 : // Expecting at least <accesskey> + ')'
281 0 : if (end - start < 2)
282 0 : return NS_ERROR_FAILURE;
283 :
284 0 : PRUint32 c = *start++;
285 :
286 : // Process 32-bit codepoints
287 0 : if (NS_IS_HIGH_SURROGATE(c)) {
288 0 : if (end - start < 2) // Expecting at least low-surrogate + ')'
289 0 : return NS_ERROR_FAILURE;
290 0 : PRUint32 lo = *start++;
291 0 : if (!NS_IS_LOW_SURROGATE(lo))
292 0 : return NS_ERROR_FAILURE;
293 0 : c = SURROGATE_TO_UCS4(c, lo);
294 : // XML 1.1 says that 0xFFFE and 0xFFFF are not valid characters
295 0 : } else if (NS_IS_LOW_SURROGATE(c) || c == 0xFFFE || c == 0xFFFF) {
296 0 : return NS_ERROR_FAILURE;
297 : }
298 :
299 0 : result.mRepeatIterationOrAccessKey = c;
300 :
301 0 : if (*start++ != ')')
302 0 : return NS_ERROR_FAILURE;
303 :
304 0 : SkipBeginWsp(start, end);
305 :
306 0 : nsresult rv = ParseOptionalOffset(Substring(start, end), result);
307 0 : if (NS_FAILED(rv))
308 0 : return rv;
309 :
310 0 : aResult = result;
311 :
312 0 : return NS_OK;
313 : }
314 :
315 : const PRUnichar*
316 0 : GetTokenEnd(const nsAString& aStr, bool aBreakOnDot)
317 : {
318 0 : const PRUnichar* tokenEnd = aStr.BeginReading();
319 0 : const PRUnichar* const end = aStr.EndReading();
320 0 : bool escape = false;
321 0 : while (tokenEnd != end) {
322 0 : PRUnichar c = *tokenEnd;
323 0 : if (IsSpace(c) ||
324 0 : (!escape && (c == '+' || c == '-' || (aBreakOnDot && c == '.')))) {
325 0 : break;
326 : }
327 0 : escape = (!escape && c == '\\');
328 0 : ++tokenEnd;
329 : }
330 0 : return tokenEnd;
331 : }
332 :
333 : void
334 0 : Unescape(nsAString& aStr)
335 : {
336 0 : const PRUnichar* read = aStr.BeginReading();
337 0 : const PRUnichar* const end = aStr.EndReading();
338 0 : PRUnichar* write = aStr.BeginWriting();
339 0 : bool escape = false;
340 :
341 0 : while (read != end) {
342 0 : NS_ABORT_IF_FALSE(write <= read, "Writing past where we've read");
343 0 : if (!escape && *read == '\\') {
344 0 : escape = true;
345 0 : ++read;
346 : } else {
347 0 : *write++ = *read++;
348 0 : escape = false;
349 : }
350 : }
351 :
352 0 : aStr.SetLength(write - aStr.BeginReading());
353 0 : }
354 :
355 : nsresult
356 0 : ParseElementBaseTimeValueSpec(const nsAString& aSpec,
357 : nsSMILTimeValueSpecParams& aResult)
358 : {
359 0 : nsSMILTimeValueSpecParams result;
360 :
361 : //
362 : // The spec will probably look something like one of these
363 : //
364 : // element-name.begin
365 : // element-name.event-name
366 : // event-name
367 : // element-name.repeat(3)
368 : // event\.name
369 : //
370 : // Technically `repeat(3)' is permitted but the behaviour in this case is not
371 : // defined (for SMIL Animation) so we don't support it here.
372 : //
373 :
374 0 : const PRUnichar* tokenStart = aSpec.BeginReading();
375 0 : const PRUnichar* tokenEnd = GetTokenEnd(aSpec, true);
376 0 : nsAutoString token(Substring(tokenStart, tokenEnd));
377 0 : Unescape(token);
378 :
379 0 : if (token.IsEmpty())
380 0 : return NS_ERROR_FAILURE;
381 :
382 : // Whether the token is an id-ref or event-symbol it should be a valid NCName
383 0 : if (NS_FAILED(nsContentUtils::CheckQName(token, false)))
384 0 : return NS_ERROR_FAILURE;
385 :
386 : // Parse the second token if there is one
387 0 : if (tokenEnd != aSpec.EndReading() && *tokenEnd == '.') {
388 0 : result.mDependentElemID = do_GetAtom(token);
389 :
390 0 : tokenStart = ++tokenEnd;
391 0 : tokenEnd = GetTokenEnd(Substring(tokenStart, aSpec.EndReading()), false);
392 :
393 : // Don't unescape the token unless we need to and not until after we've
394 : // tested it
395 0 : const nsAString& rawToken2 = Substring(tokenStart, tokenEnd);
396 :
397 : // element-name.begin
398 0 : if (rawToken2.Equals(NS_LITERAL_STRING("begin"))) {
399 0 : result.mType = nsSMILTimeValueSpecParams::SYNCBASE;
400 0 : result.mSyncBegin = true;
401 : // element-name.end
402 0 : } else if (rawToken2.Equals(NS_LITERAL_STRING("end"))) {
403 0 : result.mType = nsSMILTimeValueSpecParams::SYNCBASE;
404 0 : result.mSyncBegin = false;
405 : // element-name.repeat(digit+)
406 0 : } else if (StringBeginsWith(rawToken2, REPEAT_PREFIX)) {
407 0 : result.mType = nsSMILTimeValueSpecParams::REPEAT;
408 0 : if (!GetUnsignedIntAndEndParen(
409 0 : Substring(tokenStart + REPEAT_PREFIX.Length(), tokenEnd),
410 0 : result.mRepeatIterationOrAccessKey))
411 0 : return NS_ERROR_FAILURE;
412 : // element-name.event-symbol
413 : } else {
414 0 : nsAutoString token2(rawToken2);
415 0 : Unescape(token2);
416 0 : result.mType = nsSMILTimeValueSpecParams::EVENT;
417 0 : if (token2.IsEmpty() ||
418 0 : NS_FAILED(nsContentUtils::CheckQName(token2, false)))
419 0 : return NS_ERROR_FAILURE;
420 0 : result.mEventSymbol = do_GetAtom(token2);
421 : }
422 : } else {
423 : // event-symbol
424 0 : result.mType = nsSMILTimeValueSpecParams::EVENT;
425 0 : result.mEventSymbol = do_GetAtom(token);
426 : }
427 :
428 : // We've reached the end of the token, so we should now be either looking at
429 : // a '+', '-', or the end.
430 0 : const PRUnichar* specEnd = aSpec.EndReading();
431 0 : SkipBeginWsp(tokenEnd, specEnd);
432 :
433 0 : nsresult rv = ParseOptionalOffset(Substring(tokenEnd, specEnd), result);
434 0 : if (NS_SUCCEEDED(rv)) {
435 0 : aResult = result;
436 : }
437 :
438 0 : return rv;
439 : }
440 :
441 : } // end anonymous namespace block
442 :
443 : //------------------------------------------------------------------------------
444 : // Implementation
445 :
446 : nsresult
447 0 : nsSMILParserUtils::ParseKeySplines(const nsAString& aSpec,
448 : nsTArray<double>& aSplineArray)
449 : {
450 0 : nsresult rv = NS_OK;
451 :
452 0 : NS_ConvertUTF16toUTF8 spec(aSpec);
453 0 : const char* start = spec.BeginReading();
454 0 : const char* end = spec.EndReading();
455 :
456 0 : SkipBeginWsp(start, end);
457 :
458 0 : int i = 0;
459 :
460 0 : while (start != end)
461 : {
462 0 : double value = GetFloat(start, end, &rv);
463 0 : if (NS_FAILED(rv))
464 0 : break;
465 :
466 0 : if (value > 1.0 || value < 0.0) {
467 0 : rv = NS_ERROR_FAILURE;
468 0 : break;
469 : }
470 :
471 0 : if (!aSplineArray.AppendElement(value)) {
472 0 : rv = NS_ERROR_OUT_OF_MEMORY;
473 0 : break;
474 : }
475 :
476 0 : ++i;
477 :
478 0 : SkipBeginWsp(start, end);
479 0 : if (start == end)
480 0 : break;
481 :
482 0 : if (i % 4) {
483 0 : if (*start == ',') {
484 0 : ++start;
485 : }
486 : } else {
487 0 : if (*start != ';') {
488 0 : rv = NS_ERROR_FAILURE;
489 0 : break;
490 : }
491 0 : ++start;
492 : }
493 :
494 0 : SkipBeginWsp(start, end);
495 : }
496 :
497 0 : if (i % 4) {
498 0 : rv = NS_ERROR_FAILURE; // wrong number of points
499 : }
500 :
501 0 : return rv;
502 : }
503 :
504 : nsresult
505 0 : nsSMILParserUtils::ParseSemicolonDelimitedProgressList(const nsAString& aSpec,
506 : bool aNonDecreasing,
507 : nsTArray<double>& aArray)
508 : {
509 0 : nsresult rv = NS_OK;
510 :
511 0 : NS_ConvertUTF16toUTF8 spec(aSpec);
512 0 : const char* start = spec.BeginReading();
513 0 : const char* end = spec.EndReading();
514 :
515 0 : SkipBeginWsp(start, end);
516 :
517 0 : double previousValue = -1.0;
518 :
519 0 : while (start != end) {
520 0 : double value = GetFloat(start, end, &rv);
521 0 : if (NS_FAILED(rv))
522 0 : break;
523 :
524 0 : if (value > 1.0 || value < 0.0 ||
525 : (aNonDecreasing && value < previousValue)) {
526 0 : rv = NS_ERROR_FAILURE;
527 0 : break;
528 : }
529 :
530 0 : if (!aArray.AppendElement(value)) {
531 0 : rv = NS_ERROR_OUT_OF_MEMORY;
532 0 : break;
533 : }
534 0 : previousValue = value;
535 :
536 0 : SkipBeginWsp(start, end);
537 0 : if (start == end)
538 0 : break;
539 :
540 0 : if (*start++ != ';') {
541 0 : rv = NS_ERROR_FAILURE;
542 0 : break;
543 : }
544 :
545 0 : SkipBeginWsp(start, end);
546 : }
547 :
548 0 : return rv;
549 : }
550 :
551 : // Helper class for ParseValues
552 : class SMILValueParser : public nsSMILParserUtils::GenericValueParser
553 : {
554 : public:
555 0 : SMILValueParser(const nsISMILAnimationElement* aSrcElement,
556 : const nsISMILAttr* aSMILAttr,
557 : nsTArray<nsSMILValue>* aValuesArray,
558 : bool* aPreventCachingOfSandwich) :
559 : mSrcElement(aSrcElement),
560 : mSMILAttr(aSMILAttr),
561 : mValuesArray(aValuesArray),
562 0 : mPreventCachingOfSandwich(aPreventCachingOfSandwich)
563 0 : {}
564 :
565 0 : virtual nsresult Parse(const nsAString& aValueStr) {
566 0 : nsSMILValue newValue;
567 0 : bool tmpPreventCachingOfSandwich = false;
568 : nsresult rv = mSMILAttr->ValueFromString(aValueStr, mSrcElement, newValue,
569 0 : tmpPreventCachingOfSandwich);
570 0 : if (NS_FAILED(rv))
571 0 : return rv;
572 :
573 0 : if (!mValuesArray->AppendElement(newValue)) {
574 0 : return NS_ERROR_OUT_OF_MEMORY;
575 : }
576 0 : if (tmpPreventCachingOfSandwich) {
577 0 : *mPreventCachingOfSandwich = true;
578 : }
579 0 : return NS_OK;
580 : }
581 : protected:
582 : const nsISMILAnimationElement* mSrcElement;
583 : const nsISMILAttr* mSMILAttr;
584 : nsTArray<nsSMILValue>* mValuesArray;
585 : bool* mPreventCachingOfSandwich;
586 : };
587 :
588 : nsresult
589 0 : nsSMILParserUtils::ParseValues(const nsAString& aSpec,
590 : const nsISMILAnimationElement* aSrcElement,
591 : const nsISMILAttr& aAttribute,
592 : nsTArray<nsSMILValue>& aValuesArray,
593 : bool& aPreventCachingOfSandwich)
594 : {
595 : // Assume all results can be cached, until we find one that can't.
596 0 : aPreventCachingOfSandwich = false;
597 : SMILValueParser valueParser(aSrcElement, &aAttribute,
598 0 : &aValuesArray, &aPreventCachingOfSandwich);
599 0 : return ParseValuesGeneric(aSpec, valueParser);
600 : }
601 :
602 : nsresult
603 0 : nsSMILParserUtils::ParseValuesGeneric(const nsAString& aSpec,
604 : GenericValueParser& aParser)
605 : {
606 0 : nsCharSeparatedTokenizer tokenizer(aSpec, ';');
607 0 : if (!tokenizer.hasMoreTokens()) { // Empty list
608 0 : return NS_ERROR_FAILURE;
609 : }
610 :
611 0 : while (tokenizer.hasMoreTokens()) {
612 0 : nsresult rv = aParser.Parse(tokenizer.nextToken());
613 0 : if (NS_FAILED(rv)) {
614 0 : return NS_ERROR_FAILURE;
615 : }
616 : }
617 :
618 0 : return NS_OK;
619 : }
620 :
621 : nsresult
622 0 : nsSMILParserUtils::ParseRepeatCount(const nsAString& aSpec,
623 : nsSMILRepeatCount& aResult)
624 : {
625 0 : nsresult rv = NS_OK;
626 :
627 0 : NS_ConvertUTF16toUTF8 spec(aSpec);
628 0 : const char* start = spec.BeginReading();
629 0 : const char* end = spec.EndReading();
630 :
631 0 : SkipBeginWsp(start, end);
632 :
633 0 : if (start != end)
634 : {
635 0 : if (ConsumeSubstring(start, end, "indefinite")) {
636 0 : aResult.SetIndefinite();
637 : } else {
638 0 : double value = GetFloat(start, end, &rv);
639 :
640 0 : if (NS_SUCCEEDED(rv))
641 : {
642 : /* Repeat counts must be > 0 */
643 0 : if (value <= 0.0) {
644 0 : rv = NS_ERROR_FAILURE;
645 : } else {
646 0 : aResult = value;
647 : }
648 : }
649 : }
650 :
651 : /* Check for trailing junk */
652 0 : SkipBeginWsp(start, end);
653 0 : if (start != end) {
654 0 : rv = NS_ERROR_FAILURE;
655 : }
656 : } else {
657 : /* Empty spec */
658 0 : rv = NS_ERROR_FAILURE;
659 : }
660 :
661 0 : if (NS_FAILED(rv)) {
662 0 : aResult.Unset();
663 : }
664 :
665 0 : return rv;
666 : }
667 :
668 : nsresult
669 0 : nsSMILParserUtils::ParseTimeValueSpecParams(const nsAString& aSpec,
670 : nsSMILTimeValueSpecParams& aResult)
671 : {
672 0 : nsresult rv = NS_ERROR_FAILURE;
673 :
674 0 : const PRUnichar* start = aSpec.BeginReading();
675 0 : const PRUnichar* end = aSpec.EndReading();
676 :
677 0 : SkipBeginEndWsp(start, end);
678 0 : if (start == end)
679 0 : return rv;
680 :
681 0 : const nsAString &spec = Substring(start, end);
682 :
683 : // offset type
684 0 : if (*start == '+' || *start == '-' || NS_IsAsciiDigit(*start)) {
685 : rv = ParseClockValue(spec, &aResult.mOffset,
686 0 : nsSMILParserUtils::kClockValueAllowSign);
687 0 : if (NS_SUCCEEDED(rv)) {
688 0 : aResult.mType = nsSMILTimeValueSpecParams::OFFSET;
689 : }
690 : }
691 :
692 : // indefinite
693 0 : else if (spec.Equals(NS_LITERAL_STRING("indefinite"))) {
694 0 : aResult.mType = nsSMILTimeValueSpecParams::INDEFINITE;
695 0 : rv = NS_OK;
696 : }
697 :
698 : // wallclock type
699 0 : else if (StringBeginsWith(spec, WALLCLOCK_PREFIX)) {
700 0 : rv = NS_ERROR_NOT_IMPLEMENTED;
701 : }
702 :
703 : // accesskey type
704 0 : else if (StringBeginsWith(spec, ACCESSKEY_PREFIX_LC) ||
705 0 : StringBeginsWith(spec, ACCESSKEY_PREFIX_CC)) {
706 0 : rv = ParseAccessKey(spec, aResult);
707 : }
708 :
709 : // event, syncbase, or repeat
710 : else {
711 0 : rv = ParseElementBaseTimeValueSpec(spec, aResult);
712 : }
713 :
714 0 : return rv;
715 : }
716 :
717 : nsresult
718 0 : nsSMILParserUtils::ParseClockValue(const nsAString& aSpec,
719 : nsSMILTimeValue* aResult,
720 : PRUint32 aFlags, // = 0
721 : bool* aIsMedia) // = nsnull
722 : {
723 0 : nsSMILTime offset = 0L;
724 0 : double component = 0.0;
725 :
726 0 : PRInt8 sign = 0;
727 0 : PRUint8 colonCount = 0;
728 :
729 : // Indicates we have started parsing a clock-value (not including the optional
730 : // +/- that precedes the clock-value) or keyword ("media", "indefinite")
731 0 : bool started = false;
732 :
733 0 : PRInt32 metricMultiplicand = MSEC_PER_SEC;
734 :
735 0 : bool numIsReal = false;
736 0 : bool prevNumCouldBeMin = false;
737 0 : bool numCouldBeMin = false;
738 0 : bool numCouldBeSec = false;
739 0 : bool isIndefinite = false;
740 :
741 0 : if (aIsMedia) {
742 0 : *aIsMedia = false;
743 : }
744 :
745 0 : NS_ConvertUTF16toUTF8 spec(aSpec);
746 0 : const char* start = spec.BeginReading();
747 0 : const char* end = spec.EndReading();
748 :
749 0 : while (start != end) {
750 0 : if (IsSpace(*start)) {
751 0 : ++start;
752 0 : if (started) {
753 0 : break;
754 : }
755 0 : } else if (!started && (aFlags & kClockValueAllowSign) &&
756 : (*start == '+' || *start == '-')) {
757 : // check sign has not already been set (e.g. ++10s)
758 0 : if (sign != 0) {
759 0 : return NS_ERROR_FAILURE;
760 : }
761 :
762 0 : sign = (*start == '+') ? 1 : -1;
763 0 : ++start;
764 : // The NS_IS_DIGIT etc. macros are not locale-specific
765 0 : } else if (NS_IS_DIGIT(*start)) {
766 0 : prevNumCouldBeMin = numCouldBeMin;
767 :
768 0 : if (!ParseClockComponent(start, end, component, numIsReal, numCouldBeMin,
769 0 : numCouldBeSec)) {
770 0 : return NS_ERROR_FAILURE;
771 : }
772 0 : started = true;
773 0 : } else if (started && *start == ':') {
774 0 : ++colonCount;
775 :
776 : // Neither minutes nor hours can be reals
777 0 : if (numIsReal) {
778 0 : return NS_ERROR_FAILURE;
779 : }
780 :
781 : // Can't have more than two colons
782 0 : if (colonCount > 2) {
783 0 : return NS_ERROR_FAILURE;
784 : }
785 :
786 : // Multiply the offset by 60 and add the last accumulated component
787 0 : offset = offset * 60 + nsSMILTime(component);
788 :
789 0 : component = 0.0;
790 0 : ++start;
791 0 : } else if (NS_IS_ALPHA(*start)) {
792 0 : if (colonCount > 0) {
793 0 : return NS_ERROR_FAILURE;
794 : }
795 :
796 0 : if (!started && (aFlags & kClockValueAllowIndefinite) &&
797 0 : ConsumeSubstring(start, end, "indefinite")) {
798 : // We set a separate flag because we don't know what the state of the
799 : // passed in time value is and we shouldn't change it in the case of a
800 : // bad input string (so we can't initialise it to 0ms for example).
801 0 : isIndefinite = true;
802 0 : if (aResult) {
803 0 : aResult->SetIndefinite();
804 : }
805 0 : started = true;
806 0 : } else if (!started && aIsMedia &&
807 0 : ConsumeSubstring(start, end, "media")) {
808 0 : *aIsMedia = true;
809 0 : started = true;
810 0 : } else if (!ParseMetricMultiplicand(start, end, metricMultiplicand)) {
811 0 : return NS_ERROR_FAILURE;
812 : }
813 :
814 : // Nothing must come after the string except whitespace
815 0 : break;
816 : } else {
817 0 : return NS_ERROR_FAILURE;
818 : }
819 : }
820 :
821 : // Whitespace/empty string
822 0 : if (!started) {
823 0 : return NS_ERROR_FAILURE;
824 : }
825 :
826 : // Process remainder of string (if any) to ensure it is only trailing
827 : // whitespace (embedded whitespace is not allowed)
828 0 : SkipBeginWsp(start, end);
829 0 : if (start != end) {
830 0 : return NS_ERROR_FAILURE;
831 : }
832 :
833 : // No more processing required if the value was "indefinite" or "media".
834 0 : if (isIndefinite || (aIsMedia && *aIsMedia)) {
835 0 : return NS_OK;
836 : }
837 :
838 : // If there is more than one colon then the previous component must be a
839 : // correctly formatted minute (i.e. two digits between 00 and 59) and the
840 : // latest component must be a correctly formatted second (i.e. two digits
841 : // before the .)
842 0 : if (colonCount > 0 && (!prevNumCouldBeMin || !numCouldBeSec)) {
843 0 : return NS_ERROR_FAILURE;
844 : }
845 :
846 : // Tack on the last component
847 0 : if (colonCount > 0) {
848 0 : offset *= 60 * 1000;
849 0 : component *= 1000;
850 : // rounding
851 0 : component = (component >= 0) ? component + 0.5 : component - 0.5;
852 0 : offset += nsSMILTime(component);
853 : } else {
854 0 : component *= metricMultiplicand;
855 : // rounding
856 0 : component = (component >= 0) ? component + 0.5 : component - 0.5;
857 0 : offset = nsSMILTime(component);
858 : }
859 :
860 : // we haven't applied the sign yet so if the result is negative we must have
861 : // overflowed
862 0 : if (offset < 0) {
863 0 : return NS_ERROR_FAILURE;
864 : }
865 :
866 0 : if (aResult) {
867 0 : if (sign == -1) {
868 0 : offset = -offset;
869 : }
870 0 : aResult->SetMillis(offset);
871 : }
872 :
873 0 : return NS_OK;
874 : }
875 :
876 : PRInt32
877 0 : nsSMILParserUtils::CheckForNegativeNumber(const nsAString& aStr)
878 : {
879 0 : PRInt32 absValLocation = -1;
880 :
881 0 : nsAString::const_iterator start, end;
882 0 : aStr.BeginReading(start);
883 0 : aStr.EndReading(end);
884 :
885 : // Skip initial whitespace
886 0 : SkipBeginWsp(start, end);
887 :
888 : // Check for dash
889 0 : if (start != end && *start == '-') {
890 0 : ++start;
891 : // Check for numeric character
892 0 : if (start != end && NS_IS_DIGIT(*start)) {
893 0 : absValLocation = start.get() - start.start();
894 : }
895 : }
896 0 : return absValLocation;
897 : }
|