michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsSMILParserUtils.h" michael@0: #include "nsSMILKeySpline.h" michael@0: #include "nsISMILAttr.h" michael@0: #include "nsSMILValue.h" michael@0: #include "nsSMILTimeValue.h" michael@0: #include "nsSMILTimeValueSpecParams.h" michael@0: #include "nsSMILTypes.h" michael@0: #include "nsSMILRepeatCount.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsCharSeparatedTokenizer.h" michael@0: #include "SVGContentUtils.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: //------------------------------------------------------------------------------ michael@0: // Helper functions and Constants michael@0: michael@0: namespace { michael@0: michael@0: const uint32_t MSEC_PER_SEC = 1000; michael@0: const uint32_t MSEC_PER_MIN = 1000 * 60; michael@0: const uint32_t MSEC_PER_HOUR = 1000 * 60 * 60; michael@0: michael@0: #define ACCESSKEY_PREFIX_LC NS_LITERAL_STRING("accesskey(") // SMIL2+ michael@0: #define ACCESSKEY_PREFIX_CC NS_LITERAL_STRING("accessKey(") // SVG/SMIL ANIM michael@0: #define REPEAT_PREFIX NS_LITERAL_STRING("repeat(") michael@0: #define WALLCLOCK_PREFIX NS_LITERAL_STRING("wallclock(") michael@0: michael@0: inline bool michael@0: SkipWhitespace(RangedPtr& aIter, michael@0: const RangedPtr& aEnd) michael@0: { michael@0: while (aIter != aEnd) { michael@0: if (!IsSVGWhitespace(*aIter)) { michael@0: return true; michael@0: } michael@0: ++aIter; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: inline bool michael@0: ParseColon(RangedPtr& aIter, michael@0: const RangedPtr& aEnd) michael@0: { michael@0: if (aIter == aEnd || *aIter != ':') { michael@0: return false; michael@0: } michael@0: ++aIter; michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Exactly two digits in the range 00 - 59 are expected. michael@0: */ michael@0: bool michael@0: ParseSecondsOrMinutes(RangedPtr& aIter, michael@0: const RangedPtr& aEnd, michael@0: uint32_t& aValue) michael@0: { michael@0: if (aIter == aEnd || !SVGContentUtils::IsDigit(*aIter)) { michael@0: return false; michael@0: } michael@0: michael@0: RangedPtr iter(aIter); michael@0: michael@0: if (++iter == aEnd || !SVGContentUtils::IsDigit(*iter)) { michael@0: return false; michael@0: } michael@0: michael@0: uint32_t value = 10 * SVGContentUtils::DecimalDigitValue(*aIter) + michael@0: SVGContentUtils::DecimalDigitValue(*iter); michael@0: if (value > 59) { michael@0: return false; michael@0: } michael@0: if (++iter != aEnd && SVGContentUtils::IsDigit(*iter)) { michael@0: return false; michael@0: } michael@0: michael@0: aValue = value; michael@0: aIter = iter; michael@0: return true; michael@0: } michael@0: michael@0: inline bool michael@0: ParseClockMetric(RangedPtr& aIter, michael@0: const RangedPtr& aEnd, michael@0: uint32_t& aMultiplier) michael@0: { michael@0: if (aIter == aEnd) { michael@0: aMultiplier = MSEC_PER_SEC; michael@0: return true; michael@0: } michael@0: michael@0: switch (*aIter) { michael@0: case 'h': michael@0: if (++aIter == aEnd) { michael@0: aMultiplier = MSEC_PER_HOUR; michael@0: return true; michael@0: } michael@0: return false; michael@0: case 'm': michael@0: { michael@0: const nsAString& metric = Substring(aIter.get(), aEnd.get()); michael@0: if (metric.EqualsLiteral("min")) { michael@0: aMultiplier = MSEC_PER_MIN; michael@0: aIter = aEnd; michael@0: return true; michael@0: } michael@0: if (metric.EqualsLiteral("ms")) { michael@0: aMultiplier = 1; michael@0: aIter = aEnd; michael@0: return true; michael@0: } michael@0: } michael@0: return false; michael@0: case 's': michael@0: if (++aIter == aEnd) { michael@0: aMultiplier = MSEC_PER_SEC; michael@0: return true; michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: /** michael@0: * See http://www.w3.org/TR/SVG/animate.html#ClockValueSyntax michael@0: */ michael@0: bool michael@0: ParseClockValue(RangedPtr& aIter, michael@0: const RangedPtr& aEnd, michael@0: nsSMILTimeValue* aResult) michael@0: { michael@0: if (aIter == aEnd) { michael@0: return false; michael@0: } michael@0: michael@0: // TIMECOUNT_VALUE ::= Timecount ("." Fraction)? (Metric)? michael@0: // PARTIAL_CLOCK_VALUE ::= Minutes ":" Seconds ("." Fraction)? michael@0: // FULL_CLOCK_VALUE ::= Hours ":" Minutes ":" Seconds ("." Fraction)? michael@0: enum ClockType { michael@0: TIMECOUNT_VALUE, michael@0: PARTIAL_CLOCK_VALUE, michael@0: FULL_CLOCK_VALUE michael@0: }; michael@0: michael@0: int32_t clockType = TIMECOUNT_VALUE; michael@0: michael@0: RangedPtr iter(aIter); michael@0: michael@0: // Determine which type of clock value we have by counting the number michael@0: // of colons in the string. michael@0: do { michael@0: switch (*iter) { michael@0: case ':': michael@0: if (clockType == FULL_CLOCK_VALUE) { michael@0: return false; michael@0: } michael@0: ++clockType; michael@0: break; michael@0: case 'e': michael@0: case 'E': michael@0: case '-': michael@0: case '+': michael@0: // Exclude anything invalid (for clock values) michael@0: // that number parsing might otherwise allow. michael@0: return false; michael@0: } michael@0: ++iter; michael@0: } while (iter != aEnd); michael@0: michael@0: iter = aIter; michael@0: michael@0: int32_t hours = 0, timecount; michael@0: double fraction = 0.0; michael@0: uint32_t minutes, seconds, multiplier; michael@0: michael@0: switch (clockType) { michael@0: case FULL_CLOCK_VALUE: michael@0: if (!SVGContentUtils::ParseInteger(iter, aEnd, hours) || michael@0: !ParseColon(iter, aEnd)) { michael@0: return false; michael@0: } michael@0: // intentional fall through michael@0: case PARTIAL_CLOCK_VALUE: michael@0: if (!ParseSecondsOrMinutes(iter, aEnd, minutes) || michael@0: !ParseColon(iter, aEnd) || michael@0: !ParseSecondsOrMinutes(iter, aEnd, seconds)) { michael@0: return false; michael@0: } michael@0: if (iter != aEnd && michael@0: (*iter != '.' || michael@0: !SVGContentUtils::ParseNumber(iter, aEnd, fraction))) { michael@0: return false; michael@0: } michael@0: aResult->SetMillis(nsSMILTime(hours) * MSEC_PER_HOUR + michael@0: minutes * MSEC_PER_MIN + michael@0: seconds * MSEC_PER_SEC + michael@0: NS_round(fraction * MSEC_PER_SEC)); michael@0: aIter = iter; michael@0: return true; michael@0: case TIMECOUNT_VALUE: michael@0: if (!SVGContentUtils::ParseInteger(iter, aEnd, timecount)) { michael@0: return false; michael@0: } michael@0: if (iter != aEnd && *iter == '.' && michael@0: !SVGContentUtils::ParseNumber(iter, aEnd, fraction)) { michael@0: return false; michael@0: } michael@0: if (!ParseClockMetric(iter, aEnd, multiplier)) { michael@0: return false; michael@0: } michael@0: aResult->SetMillis(nsSMILTime(timecount) * multiplier + michael@0: NS_round(fraction * multiplier)); michael@0: aIter = iter; michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: ParseOffsetValue(RangedPtr& aIter, michael@0: const RangedPtr& aEnd, michael@0: nsSMILTimeValue* aResult) michael@0: { michael@0: RangedPtr iter(aIter); michael@0: michael@0: int32_t sign; michael@0: if (!SVGContentUtils::ParseOptionalSign(iter, aEnd, sign) || michael@0: !SkipWhitespace(iter, aEnd) || michael@0: !ParseClockValue(iter, aEnd, aResult)) { michael@0: return false; michael@0: } michael@0: if (sign == -1) { michael@0: aResult->SetMillis(-aResult->GetMillis()); michael@0: } michael@0: aIter = iter; michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: ParseOffsetValue(const nsAString& aSpec, michael@0: nsSMILTimeValue* aResult) michael@0: { michael@0: RangedPtr iter(SVGContentUtils::GetStartRangedPtr(aSpec)); michael@0: const RangedPtr end(SVGContentUtils::GetEndRangedPtr(aSpec)); michael@0: michael@0: return ParseOffsetValue(iter, end, aResult) && iter == end; michael@0: } michael@0: michael@0: bool michael@0: ParseOptionalOffset(RangedPtr& aIter, michael@0: const RangedPtr& aEnd, michael@0: nsSMILTimeValue* aResult) michael@0: { michael@0: if (aIter == aEnd) { michael@0: aResult->SetMillis(0L); michael@0: return true; michael@0: } michael@0: michael@0: return SkipWhitespace(aIter, aEnd) && michael@0: ParseOffsetValue(aIter, aEnd, aResult); michael@0: } michael@0: michael@0: bool michael@0: ParseAccessKey(const nsAString& aSpec, nsSMILTimeValueSpecParams& aResult) michael@0: { michael@0: NS_ABORT_IF_FALSE(StringBeginsWith(aSpec, ACCESSKEY_PREFIX_CC) || michael@0: StringBeginsWith(aSpec, ACCESSKEY_PREFIX_LC), michael@0: "Calling ParseAccessKey on non-accesskey-type spec"); michael@0: michael@0: nsSMILTimeValueSpecParams result; michael@0: result.mType = nsSMILTimeValueSpecParams::ACCESSKEY; michael@0: michael@0: NS_ABORT_IF_FALSE( michael@0: ACCESSKEY_PREFIX_LC.Length() == ACCESSKEY_PREFIX_CC.Length(), michael@0: "Case variations for accesskey prefix differ in length"); michael@0: michael@0: RangedPtr iter(SVGContentUtils::GetStartRangedPtr(aSpec)); michael@0: RangedPtr end(SVGContentUtils::GetEndRangedPtr(aSpec)); michael@0: michael@0: iter += ACCESSKEY_PREFIX_LC.Length(); michael@0: michael@0: // Expecting at least + ')' michael@0: if (end - iter < 2) michael@0: return false; michael@0: michael@0: uint32_t c = *iter++; michael@0: michael@0: // Process 32-bit codepoints michael@0: if (NS_IS_HIGH_SURROGATE(c)) { michael@0: if (end - iter < 2) // Expecting at least low-surrogate + ')' michael@0: return false; michael@0: uint32_t lo = *iter++; michael@0: if (!NS_IS_LOW_SURROGATE(lo)) michael@0: return false; michael@0: c = SURROGATE_TO_UCS4(c, lo); michael@0: // XML 1.1 says that 0xFFFE and 0xFFFF are not valid characters michael@0: } else if (NS_IS_LOW_SURROGATE(c) || c == 0xFFFE || c == 0xFFFF) { michael@0: return false; michael@0: } michael@0: michael@0: result.mRepeatIterationOrAccessKey = c; michael@0: michael@0: if (*iter++ != ')') michael@0: return false; michael@0: michael@0: if (!ParseOptionalOffset(iter, end, &result.mOffset) || iter != end) { michael@0: return false; michael@0: } michael@0: aResult = result; michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: MoveToNextToken(RangedPtr& aIter, michael@0: const RangedPtr& aEnd, michael@0: bool aBreakOnDot, michael@0: bool& aIsAnyCharEscaped) michael@0: { michael@0: aIsAnyCharEscaped = false; michael@0: michael@0: bool isCurrentCharEscaped = false; michael@0: michael@0: while (aIter != aEnd && !IsSVGWhitespace(*aIter)) { michael@0: if (isCurrentCharEscaped) { michael@0: isCurrentCharEscaped = false; michael@0: } else { michael@0: if (*aIter == '+' || *aIter == '-' || michael@0: (aBreakOnDot && *aIter == '.')) { michael@0: break; michael@0: } michael@0: if (*aIter == '\\') { michael@0: isCurrentCharEscaped = true; michael@0: aIsAnyCharEscaped = true; michael@0: } michael@0: } michael@0: ++aIter; michael@0: } michael@0: } michael@0: michael@0: already_AddRefed michael@0: ConvertUnescapedTokenToAtom(const nsAString& aToken) michael@0: { michael@0: // Whether the token is an id-ref or event-symbol it should be a valid NCName michael@0: if (aToken.IsEmpty() || NS_FAILED(nsContentUtils::CheckQName(aToken, false))) michael@0: return nullptr; michael@0: return do_GetAtom(aToken); michael@0: } michael@0: michael@0: already_AddRefed michael@0: ConvertTokenToAtom(const nsAString& aToken, michael@0: bool aUnescapeToken) michael@0: { michael@0: // Unescaping involves making a copy of the string which we'd like to avoid if possible michael@0: if (!aUnescapeToken) { michael@0: return ConvertUnescapedTokenToAtom(aToken); michael@0: } michael@0: michael@0: nsAutoString token(aToken); michael@0: michael@0: const char16_t* read = token.BeginReading(); michael@0: const char16_t* const end = token.EndReading(); michael@0: char16_t* write = token.BeginWriting(); michael@0: bool escape = false; michael@0: michael@0: while (read != end) { michael@0: NS_ABORT_IF_FALSE(write <= read, "Writing past where we've read"); michael@0: if (!escape && *read == '\\') { michael@0: escape = true; michael@0: ++read; michael@0: } else { michael@0: *write++ = *read++; michael@0: escape = false; michael@0: } michael@0: } michael@0: token.Truncate(write - token.BeginReading()); michael@0: michael@0: return ConvertUnescapedTokenToAtom(token); michael@0: } michael@0: michael@0: bool michael@0: ParseElementBaseTimeValueSpec(const nsAString& aSpec, michael@0: nsSMILTimeValueSpecParams& aResult) michael@0: { michael@0: nsSMILTimeValueSpecParams result; michael@0: michael@0: // michael@0: // The spec will probably look something like one of these michael@0: // michael@0: // element-name.begin michael@0: // element-name.event-name michael@0: // event-name michael@0: // element-name.repeat(3) michael@0: // event\.name michael@0: // michael@0: // Technically `repeat(3)' is permitted but the behaviour in this case is not michael@0: // defined (for SMIL Animation) so we don't support it here. michael@0: // michael@0: michael@0: RangedPtr start(SVGContentUtils::GetStartRangedPtr(aSpec)); michael@0: RangedPtr end(SVGContentUtils::GetEndRangedPtr(aSpec)); michael@0: michael@0: if (start == end) { michael@0: return false; michael@0: } michael@0: michael@0: RangedPtr tokenEnd(start); michael@0: michael@0: bool requiresUnescaping; michael@0: MoveToNextToken(tokenEnd, end, true, requiresUnescaping); michael@0: michael@0: nsRefPtr atom = michael@0: ConvertTokenToAtom(Substring(start.get(), tokenEnd.get()), michael@0: requiresUnescaping); michael@0: if (atom == nullptr) { michael@0: return false; michael@0: } michael@0: michael@0: // Parse the second token if there is one michael@0: if (tokenEnd != end && *tokenEnd == '.') { michael@0: result.mDependentElemID = atom; michael@0: michael@0: ++tokenEnd; michael@0: start = tokenEnd; michael@0: MoveToNextToken(tokenEnd, end, false, requiresUnescaping); michael@0: michael@0: const nsAString& token2 = Substring(start.get(), tokenEnd.get()); michael@0: michael@0: // element-name.begin michael@0: if (token2.EqualsLiteral("begin")) { michael@0: result.mType = nsSMILTimeValueSpecParams::SYNCBASE; michael@0: result.mSyncBegin = true; michael@0: // element-name.end michael@0: } else if (token2.EqualsLiteral("end")) { michael@0: result.mType = nsSMILTimeValueSpecParams::SYNCBASE; michael@0: result.mSyncBegin = false; michael@0: // element-name.repeat(digit+) michael@0: } else if (StringBeginsWith(token2, REPEAT_PREFIX)) { michael@0: start += REPEAT_PREFIX.Length(); michael@0: int32_t repeatValue; michael@0: if (start == tokenEnd || *start == '+' || *start == '-' || michael@0: !SVGContentUtils::ParseInteger(start, tokenEnd, repeatValue)) { michael@0: return false; michael@0: } michael@0: if (start == tokenEnd || *start != ')') { michael@0: return false; michael@0: } michael@0: result.mType = nsSMILTimeValueSpecParams::REPEAT; michael@0: result.mRepeatIterationOrAccessKey = repeatValue; michael@0: // element-name.event-symbol michael@0: } else { michael@0: atom = ConvertTokenToAtom(token2, requiresUnescaping); michael@0: if (atom == nullptr) { michael@0: return false; michael@0: } michael@0: result.mType = nsSMILTimeValueSpecParams::EVENT; michael@0: result.mEventSymbol = atom; michael@0: } michael@0: } else { michael@0: // event-symbol michael@0: result.mType = nsSMILTimeValueSpecParams::EVENT; michael@0: result.mEventSymbol = atom; michael@0: } michael@0: michael@0: // We've reached the end of the token, so we should now be either looking at michael@0: // a '+', '-' (possibly with whitespace before it), or the end. michael@0: if (!ParseOptionalOffset(tokenEnd, end, &result.mOffset) || tokenEnd != end) { michael@0: return false; michael@0: } michael@0: aResult = result; michael@0: return true; michael@0: } michael@0: michael@0: } // end anonymous namespace block michael@0: michael@0: //------------------------------------------------------------------------------ michael@0: // Implementation michael@0: michael@0: const nsDependentSubstring michael@0: nsSMILParserUtils::TrimWhitespace(const nsAString& aString) michael@0: { michael@0: nsAString::const_iterator start, end; michael@0: michael@0: aString.BeginReading(start); michael@0: aString.EndReading(end); michael@0: michael@0: // Skip whitespace characters at the beginning michael@0: while (start != end && IsSVGWhitespace(*start)) { michael@0: ++start; michael@0: } michael@0: michael@0: // Skip whitespace characters at the end. michael@0: while (end != start) { michael@0: --end; michael@0: michael@0: if (!IsSVGWhitespace(*end)) { michael@0: // Step back to the last non-whitespace character. michael@0: ++end; michael@0: michael@0: break; michael@0: } michael@0: } michael@0: michael@0: return Substring(start, end); michael@0: } michael@0: michael@0: bool michael@0: nsSMILParserUtils::ParseKeySplines(const nsAString& aSpec, michael@0: FallibleTArray& aKeySplines) michael@0: { michael@0: nsCharSeparatedTokenizerTemplate controlPointTokenizer(aSpec, ';'); michael@0: while (controlPointTokenizer.hasMoreTokens()) { michael@0: michael@0: nsCharSeparatedTokenizerTemplate michael@0: tokenizer(controlPointTokenizer.nextToken(), ',', michael@0: nsCharSeparatedTokenizer::SEPARATOR_OPTIONAL); michael@0: michael@0: double values[4]; michael@0: for (int i = 0 ; i < 4; i++) { michael@0: if (!tokenizer.hasMoreTokens() || michael@0: !SVGContentUtils::ParseNumber(tokenizer.nextToken(), values[i]) || michael@0: values[i] > 1.0 || values[i] < 0.0) { michael@0: return false; michael@0: } michael@0: } michael@0: if (tokenizer.hasMoreTokens() || michael@0: tokenizer.separatorAfterCurrentToken() || michael@0: !aKeySplines.AppendElement(nsSMILKeySpline(values[0], michael@0: values[1], michael@0: values[2], michael@0: values[3]))) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: return !aKeySplines.IsEmpty(); michael@0: } michael@0: michael@0: bool michael@0: nsSMILParserUtils::ParseSemicolonDelimitedProgressList(const nsAString& aSpec, michael@0: bool aNonDecreasing, michael@0: FallibleTArray& aArray) michael@0: { michael@0: nsCharSeparatedTokenizerTemplate tokenizer(aSpec, ';'); michael@0: michael@0: double previousValue = -1.0; michael@0: michael@0: while (tokenizer.hasMoreTokens()) { michael@0: double value; michael@0: if (!SVGContentUtils::ParseNumber(tokenizer.nextToken(), value)) { michael@0: return false; michael@0: } michael@0: michael@0: if (value > 1.0 || value < 0.0 || michael@0: (aNonDecreasing && value < previousValue)) { michael@0: return false; michael@0: } michael@0: michael@0: if (!aArray.AppendElement(value)) { michael@0: return false; michael@0: } michael@0: previousValue = value; michael@0: } michael@0: michael@0: return !aArray.IsEmpty(); michael@0: } michael@0: michael@0: // Helper class for ParseValues michael@0: class MOZ_STACK_CLASS SMILValueParser : michael@0: public nsSMILParserUtils::GenericValueParser michael@0: { michael@0: public: michael@0: SMILValueParser(const SVGAnimationElement* aSrcElement, michael@0: const nsISMILAttr* aSMILAttr, michael@0: FallibleTArray* aValuesArray, michael@0: bool* aPreventCachingOfSandwich) : michael@0: mSrcElement(aSrcElement), michael@0: mSMILAttr(aSMILAttr), michael@0: mValuesArray(aValuesArray), michael@0: mPreventCachingOfSandwich(aPreventCachingOfSandwich) michael@0: {} michael@0: michael@0: virtual bool Parse(const nsAString& aValueStr) MOZ_OVERRIDE { michael@0: nsSMILValue newValue; michael@0: bool tmpPreventCachingOfSandwich = false; michael@0: if (NS_FAILED(mSMILAttr->ValueFromString(aValueStr, mSrcElement, newValue, michael@0: tmpPreventCachingOfSandwich))) michael@0: return false; michael@0: michael@0: if (!mValuesArray->AppendElement(newValue)) { michael@0: return false; michael@0: } michael@0: if (tmpPreventCachingOfSandwich) { michael@0: *mPreventCachingOfSandwich = true; michael@0: } michael@0: return true; michael@0: } michael@0: protected: michael@0: const SVGAnimationElement* mSrcElement; michael@0: const nsISMILAttr* mSMILAttr; michael@0: FallibleTArray* mValuesArray; michael@0: bool* mPreventCachingOfSandwich; michael@0: }; michael@0: michael@0: bool michael@0: nsSMILParserUtils::ParseValues(const nsAString& aSpec, michael@0: const SVGAnimationElement* aSrcElement, michael@0: const nsISMILAttr& aAttribute, michael@0: FallibleTArray& aValuesArray, michael@0: bool& aPreventCachingOfSandwich) michael@0: { michael@0: // Assume all results can be cached, until we find one that can't. michael@0: aPreventCachingOfSandwich = false; michael@0: SMILValueParser valueParser(aSrcElement, &aAttribute, michael@0: &aValuesArray, &aPreventCachingOfSandwich); michael@0: return ParseValuesGeneric(aSpec, valueParser); michael@0: } michael@0: michael@0: bool michael@0: nsSMILParserUtils::ParseValuesGeneric(const nsAString& aSpec, michael@0: GenericValueParser& aParser) michael@0: { michael@0: nsCharSeparatedTokenizerTemplate tokenizer(aSpec, ';'); michael@0: if (!tokenizer.hasMoreTokens()) { // Empty list michael@0: return false; michael@0: } michael@0: michael@0: while (tokenizer.hasMoreTokens()) { michael@0: if (!aParser.Parse(tokenizer.nextToken())) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsSMILParserUtils::ParseRepeatCount(const nsAString& aSpec, michael@0: nsSMILRepeatCount& aResult) michael@0: { michael@0: const nsAString& spec = michael@0: nsSMILParserUtils::TrimWhitespace(aSpec); michael@0: michael@0: if (spec.EqualsLiteral("indefinite")) { michael@0: aResult.SetIndefinite(); michael@0: return true; michael@0: } michael@0: michael@0: double value; michael@0: if (!SVGContentUtils::ParseNumber(spec, value) || value <= 0.0) { michael@0: return false; michael@0: } michael@0: aResult = value; michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsSMILParserUtils::ParseTimeValueSpecParams(const nsAString& aSpec, michael@0: nsSMILTimeValueSpecParams& aResult) michael@0: { michael@0: const nsAString& spec = TrimWhitespace(aSpec); michael@0: michael@0: if (spec.EqualsLiteral("indefinite")) { michael@0: aResult.mType = nsSMILTimeValueSpecParams::INDEFINITE; michael@0: return true; michael@0: } michael@0: michael@0: // offset type michael@0: if (ParseOffsetValue(spec, &aResult.mOffset)) { michael@0: aResult.mType = nsSMILTimeValueSpecParams::OFFSET; michael@0: return true; michael@0: } michael@0: michael@0: // wallclock type michael@0: if (StringBeginsWith(spec, WALLCLOCK_PREFIX)) { michael@0: return false; // Wallclock times not implemented michael@0: } michael@0: michael@0: // accesskey type michael@0: if (StringBeginsWith(spec, ACCESSKEY_PREFIX_LC) || michael@0: StringBeginsWith(spec, ACCESSKEY_PREFIX_CC)) { michael@0: return ParseAccessKey(spec, aResult); michael@0: } michael@0: michael@0: // event, syncbase, or repeat michael@0: return ParseElementBaseTimeValueSpec(spec, aResult); michael@0: } michael@0: michael@0: bool michael@0: nsSMILParserUtils::ParseClockValue(const nsAString& aSpec, michael@0: nsSMILTimeValue* aResult) michael@0: { michael@0: RangedPtr iter(SVGContentUtils::GetStartRangedPtr(aSpec)); michael@0: RangedPtr end(SVGContentUtils::GetEndRangedPtr(aSpec)); michael@0: michael@0: return ::ParseClockValue(iter, end, aResult) && iter == end; michael@0: } michael@0: michael@0: int32_t michael@0: nsSMILParserUtils::CheckForNegativeNumber(const nsAString& aStr) michael@0: { michael@0: int32_t absValLocation = -1; michael@0: michael@0: nsAString::const_iterator start, end; michael@0: aStr.BeginReading(start); michael@0: aStr.EndReading(end); michael@0: michael@0: // Skip initial whitespace michael@0: while (start != end && IsSVGWhitespace(*start)) { michael@0: ++start; michael@0: } michael@0: michael@0: // Check for dash michael@0: if (start != end && *start == '-') { michael@0: ++start; michael@0: // Check for numeric character michael@0: if (start != end && SVGContentUtils::IsDigit(*start)) { michael@0: absValLocation = start.get() - start.start(); michael@0: } michael@0: } michael@0: return absValLocation; michael@0: }