1/*
2 * Copyright (C) 2002, 2003 The Karbon Developers
3 * Copyright (C) 2006 Alexander Kellett <lypanov@kde.org>
4 * Copyright (C) 2006, 2007 Rob Buis <buis@kde.org>
5 * Copyright (C) 2007, 2009, 2013 Apple Inc. All rights reserved.
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 * Library General Public License for more details.
16 *
17 * You should have received a copy of the GNU Library General Public License
18 * along with this library; see the file COPYING.LIB.  If not, write to
19 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 * Boston, MA 02110-1301, USA.
21 */
22
23#include "config.h"
24#include "core/svg/SVGParserUtilities.h"
25
26#include "core/dom/Document.h"
27#include "core/svg/SVGPointList.h"
28#include "core/svg/SVGTransformList.h"
29#include "platform/geometry/FloatRect.h"
30#include "platform/transforms/AffineTransform.h"
31#include "wtf/ASCIICType.h"
32#include <limits>
33
34namespace WebCore {
35
36template <typename FloatType>
37static inline bool isValidRange(const FloatType& x)
38{
39    static const FloatType max = std::numeric_limits<FloatType>::max();
40    return x >= -max && x <= max;
41}
42
43// We use this generic parseNumber function to allow the Path parsing code to work
44// at a higher precision internally, without any unnecessary runtime cost or code
45// complexity.
46template <typename CharType, typename FloatType>
47static bool genericParseNumber(const CharType*& ptr, const CharType* end, FloatType& number, bool skip)
48{
49    FloatType integer, decimal, frac, exponent;
50    int sign, expsign;
51    const CharType* start = ptr;
52
53    exponent = 0;
54    integer = 0;
55    frac = 1;
56    decimal = 0;
57    sign = 1;
58    expsign = 1;
59
60    // read the sign
61    if (ptr < end && *ptr == '+')
62        ptr++;
63    else if (ptr < end && *ptr == '-') {
64        ptr++;
65        sign = -1;
66    }
67
68    if (ptr == end || ((*ptr < '0' || *ptr > '9') && *ptr != '.'))
69        // The first character of a number must be one of [0-9+-.]
70        return false;
71
72    // read the integer part, build right-to-left
73    const CharType* ptrStartIntPart = ptr;
74    while (ptr < end && *ptr >= '0' && *ptr <= '9')
75        ++ptr; // Advance to first non-digit.
76
77    if (ptr != ptrStartIntPart) {
78        const CharType* ptrScanIntPart = ptr - 1;
79        FloatType multiplier = 1;
80        while (ptrScanIntPart >= ptrStartIntPart) {
81            integer += multiplier * static_cast<FloatType>(*(ptrScanIntPart--) - '0');
82            multiplier *= 10;
83        }
84        // Bail out early if this overflows.
85        if (!isValidRange(integer))
86            return false;
87    }
88
89    if (ptr < end && *ptr == '.') { // read the decimals
90        ptr++;
91
92        // There must be a least one digit following the .
93        if (ptr >= end || *ptr < '0' || *ptr > '9')
94            return false;
95
96        while (ptr < end && *ptr >= '0' && *ptr <= '9')
97            decimal += (*(ptr++) - '0') * (frac *= static_cast<FloatType>(0.1));
98    }
99
100    // read the exponent part
101    if (ptr != start && ptr + 1 < end && (*ptr == 'e' || *ptr == 'E')
102        && (ptr[1] != 'x' && ptr[1] != 'm')) {
103        ptr++;
104
105        // read the sign of the exponent
106        if (*ptr == '+')
107            ptr++;
108        else if (*ptr == '-') {
109            ptr++;
110            expsign = -1;
111        }
112
113        // There must be an exponent
114        if (ptr >= end || *ptr < '0' || *ptr > '9')
115            return false;
116
117        while (ptr < end && *ptr >= '0' && *ptr <= '9') {
118            exponent *= static_cast<FloatType>(10);
119            exponent += *ptr - '0';
120            ptr++;
121        }
122        // Make sure exponent is valid.
123        if (!isValidRange(exponent) || exponent > std::numeric_limits<FloatType>::max_exponent)
124            return false;
125    }
126
127    number = integer + decimal;
128    number *= sign;
129
130    if (exponent)
131        number *= static_cast<FloatType>(pow(10.0, expsign * static_cast<int>(exponent)));
132
133    // Don't return Infinity() or NaN().
134    if (!isValidRange(number))
135        return false;
136
137    if (start == ptr)
138        return false;
139
140    if (skip)
141        skipOptionalSVGSpacesOrDelimiter(ptr, end);
142
143    return true;
144}
145
146template <typename CharType>
147bool parseSVGNumber(CharType* begin, size_t length, double& number)
148{
149    const CharType* ptr = begin;
150    const CharType* end = ptr + length;
151    return genericParseNumber(ptr, end, number, false);
152}
153
154// Explicitly instantiate the two flavors of parseSVGNumber() to satisfy external callers
155template bool parseSVGNumber(LChar* begin, size_t length, double&);
156template bool parseSVGNumber(UChar* begin, size_t length, double&);
157
158bool parseNumber(const LChar*& ptr, const LChar* end, float& number, bool skip)
159{
160    return genericParseNumber(ptr, end, number, skip);
161}
162
163bool parseNumber(const UChar*& ptr, const UChar* end, float& number, bool skip)
164{
165    return genericParseNumber(ptr, end, number, skip);
166}
167
168bool parseNumberFromString(const String& string, float& number, bool skip)
169{
170    if (string.isEmpty())
171        return false;
172    if (string.is8Bit()) {
173        const LChar* ptr = string.characters8();
174        const LChar* end = ptr + string.length();
175        return genericParseNumber(ptr, end, number, skip) && ptr == end;
176    }
177    const UChar* ptr = string.characters16();
178    const UChar* end = ptr + string.length();
179    return genericParseNumber(ptr, end, number, skip) && ptr == end;
180}
181
182// only used to parse largeArcFlag and sweepFlag which must be a "0" or "1"
183// and might not have any whitespace/comma after it
184template <typename CharType>
185bool genericParseArcFlag(const CharType*& ptr, const CharType* end, bool& flag)
186{
187    if (ptr >= end)
188        return false;
189    const CharType flagChar = *ptr++;
190    if (flagChar == '0')
191        flag = false;
192    else if (flagChar == '1')
193        flag = true;
194    else
195        return false;
196
197    skipOptionalSVGSpacesOrDelimiter(ptr, end);
198
199    return true;
200}
201
202bool parseArcFlag(const LChar*& ptr, const LChar* end, bool& flag)
203{
204    return genericParseArcFlag(ptr, end, flag);
205}
206
207bool parseArcFlag(const UChar*& ptr, const UChar* end, bool& flag)
208{
209    return genericParseArcFlag(ptr, end, flag);
210}
211
212template<typename CharType>
213static bool genericParseNumberOptionalNumber(const CharType*& ptr, const CharType* end, float& x, float& y)
214{
215    if (!parseNumber(ptr, end, x))
216        return false;
217
218    if (ptr == end)
219        y = x;
220    else if (!parseNumber(ptr, end, y, false))
221        return false;
222
223    return ptr == end;
224}
225
226bool parseNumberOptionalNumber(const String& string, float& x, float& y)
227{
228    if (string.isEmpty())
229        return false;
230    if (string.is8Bit()) {
231        const LChar* ptr = string.characters8();
232        const LChar* end = ptr + string.length();
233        return genericParseNumberOptionalNumber(ptr, end, x, y);
234    }
235    const UChar* ptr = string.characters16();
236    const UChar* end = ptr + string.length();
237    return genericParseNumberOptionalNumber(ptr, end, x, y);
238}
239
240template<typename CharType>
241static bool genericParseRect(const CharType*& ptr, const CharType* end, FloatRect& rect)
242{
243    skipOptionalSVGSpaces(ptr, end);
244
245    float x = 0;
246    float y = 0;
247    float width = 0;
248    float height = 0;
249    bool valid = parseNumber(ptr, end, x) && parseNumber(ptr, end, y) && parseNumber(ptr, end, width) && parseNumber(ptr, end, height, false);
250    rect = FloatRect(x, y, width, height);
251    return valid;
252}
253
254bool parseRect(const String& string, FloatRect& rect)
255{
256    if (string.isEmpty())
257        return false;
258    if (string.is8Bit()) {
259        const LChar* ptr = string.characters8();
260        const LChar* end = ptr + string.length();
261        return genericParseRect(ptr, end, rect);
262    }
263    const UChar* ptr = string.characters16();
264    const UChar* end = ptr + string.length();
265    return genericParseRect(ptr, end, rect);
266}
267
268template<typename CharType>
269static bool genericParsePointsList(SVGPointList& pointsList, const CharType*& ptr, const CharType* end)
270{
271    skipOptionalSVGSpaces(ptr, end);
272
273    bool delimParsed = false;
274    while (ptr < end) {
275        delimParsed = false;
276        float xPos = 0.0f;
277        if (!parseNumber(ptr, end, xPos))
278           return false;
279
280        float yPos = 0.0f;
281        if (!parseNumber(ptr, end, yPos, false))
282            return false;
283
284        skipOptionalSVGSpaces(ptr, end);
285
286        if (ptr < end && *ptr == ',') {
287            delimParsed = true;
288            ptr++;
289        }
290        skipOptionalSVGSpaces(ptr, end);
291
292        pointsList.append(FloatPoint(xPos, yPos));
293    }
294    return ptr == end && !delimParsed;
295}
296
297// FIXME: Why is the out parameter first?
298bool pointsListFromSVGData(SVGPointList& pointsList, const String& points)
299{
300    if (points.isEmpty())
301        return true;
302    if (points.is8Bit()) {
303        const LChar* ptr = points.characters8();
304        const LChar* end = ptr + points.length();
305        return genericParsePointsList(pointsList, ptr, end);
306    }
307    const UChar* ptr = points.characters16();
308    const UChar* end = ptr + points.length();
309    return genericParsePointsList(pointsList, ptr, end);
310}
311
312template<typename CharType>
313static bool parseGlyphName(const CharType*& ptr, const CharType* end, HashSet<String>& values)
314{
315    skipOptionalSVGSpaces(ptr, end);
316
317    while (ptr < end) {
318        // Leading and trailing white space, and white space before and after separators, will be ignored.
319        const CharType* inputStart = ptr;
320        while (ptr < end && *ptr != ',')
321            ++ptr;
322
323        if (ptr == inputStart)
324            break;
325
326        // walk backwards from the ; to ignore any whitespace
327        const CharType* inputEnd = ptr - 1;
328        while (inputStart < inputEnd && isSVGSpace(*inputEnd))
329            --inputEnd;
330
331        values.add(String(inputStart, inputEnd - inputStart + 1));
332        skipOptionalSVGSpacesOrDelimiter(ptr, end, ',');
333    }
334
335    return true;
336}
337
338bool parseGlyphName(const String& input, HashSet<String>& values)
339{
340    // FIXME: Parsing error detection is missing.
341    values.clear();
342    if (input.isEmpty())
343        return true;
344    if (input.is8Bit()) {
345        const LChar* ptr = input.characters8();
346        const LChar* end = ptr + input.length();
347        return parseGlyphName(ptr, end, values);
348    }
349    const UChar* ptr = input.characters16();
350    const UChar* end = ptr + input.length();
351    return parseGlyphName(ptr, end, values);
352}
353
354template<typename CharType>
355static bool parseUnicodeRange(const CharType* characters, unsigned length, UnicodeRange& range)
356{
357    if (length < 2 || characters[0] != 'U' || characters[1] != '+')
358        return false;
359
360    // Parse the starting hex number (or its prefix).
361    unsigned startRange = 0;
362    unsigned startLength = 0;
363
364    const CharType* ptr = characters + 2;
365    const CharType* end = characters + length;
366    while (ptr < end) {
367        if (!isASCIIHexDigit(*ptr))
368            break;
369        ++startLength;
370        if (startLength > 6)
371            return false;
372        startRange = (startRange << 4) | toASCIIHexValue(*ptr);
373        ++ptr;
374    }
375
376    // Handle the case of ranges separated by "-" sign.
377    if (2 + startLength < length && *ptr == '-') {
378        if (!startLength)
379            return false;
380
381        // Parse the ending hex number (or its prefix).
382        unsigned endRange = 0;
383        unsigned endLength = 0;
384        ++ptr;
385        while (ptr < end) {
386            if (!isASCIIHexDigit(*ptr))
387                break;
388            ++endLength;
389            if (endLength > 6)
390                return false;
391            endRange = (endRange << 4) | toASCIIHexValue(*ptr);
392            ++ptr;
393        }
394
395        if (!endLength)
396            return false;
397
398        range.first = startRange;
399        range.second = endRange;
400        return true;
401    }
402
403    // Handle the case of a number with some optional trailing question marks.
404    unsigned endRange = startRange;
405    while (ptr < end) {
406        if (*ptr != '?')
407            break;
408        ++startLength;
409        if (startLength > 6)
410            return false;
411        startRange <<= 4;
412        endRange = (endRange << 4) | 0xF;
413        ++ptr;
414    }
415
416    if (!startLength)
417        return false;
418
419    range.first = startRange;
420    range.second = endRange;
421    return true;
422}
423
424template<typename CharType>
425static bool genericParseKerningUnicodeString(const CharType*& ptr, const CharType* end, UnicodeRanges& rangeList, HashSet<String>& stringList)
426{
427    while (ptr < end) {
428        const CharType* inputStart = ptr;
429        while (ptr < end && *ptr != ',')
430            ++ptr;
431
432        if (ptr == inputStart)
433            break;
434
435        // Try to parse unicode range first
436        UnicodeRange range;
437        if (parseUnicodeRange(inputStart, ptr - inputStart, range))
438            rangeList.append(range);
439        else
440            stringList.add(String(inputStart, ptr - inputStart));
441        ++ptr;
442    }
443
444    return true;
445}
446
447bool parseKerningUnicodeString(const String& input, UnicodeRanges& rangeList, HashSet<String>& stringList)
448{
449    // FIXME: Parsing error detection is missing.
450    if (input.isEmpty())
451        return true;
452    if (input.is8Bit()) {
453        const LChar* ptr = input.characters8();
454        const LChar* end = ptr + input.length();
455        return genericParseKerningUnicodeString(ptr, end, rangeList, stringList);
456    }
457    const UChar* ptr = input.characters16();
458    const UChar* end = ptr + input.length();
459    return genericParseKerningUnicodeString(ptr, end, rangeList, stringList);
460}
461
462template<typename CharType>
463static Vector<String> genericParseDelimitedString(const CharType*& ptr, const CharType* end, const char seperator)
464{
465    Vector<String> values;
466
467    skipOptionalSVGSpaces(ptr, end);
468
469    while (ptr < end) {
470        // Leading and trailing white space, and white space before and after semicolon separators, will be ignored.
471        const CharType* inputStart = ptr;
472        while (ptr < end && *ptr != seperator) // careful not to ignore whitespace inside inputs
473            ptr++;
474
475        if (ptr == inputStart)
476            break;
477
478        // walk backwards from the ; to ignore any whitespace
479        const CharType* inputEnd = ptr - 1;
480        while (inputStart < inputEnd && isSVGSpace(*inputEnd))
481            inputEnd--;
482
483        values.append(String(inputStart, inputEnd - inputStart + 1));
484        skipOptionalSVGSpacesOrDelimiter(ptr, end, seperator);
485    }
486
487    return values;
488}
489
490Vector<String> parseDelimitedString(const String& input, const char seperator)
491{
492    if (input.isEmpty())
493        return Vector<String>();
494    if (input.is8Bit()) {
495        const LChar* ptr = input.characters8();
496        const LChar* end = ptr + input.length();
497        return genericParseDelimitedString(ptr, end, seperator);
498    }
499    const UChar* ptr = input.characters16();
500    const UChar* end = ptr + input.length();
501    return genericParseDelimitedString(ptr, end, seperator);
502}
503
504template <typename CharType>
505bool parseFloatPoint(const CharType*& current, const CharType* end, FloatPoint& point)
506{
507    float x;
508    float y;
509    if (!parseNumber(current, end, x)
510        || !parseNumber(current, end, y))
511        return false;
512    point = FloatPoint(x, y);
513    return true;
514}
515
516template bool parseFloatPoint(const LChar*& current, const LChar* end, FloatPoint& point1);
517template bool parseFloatPoint(const UChar*& current, const UChar* end, FloatPoint& point1);
518
519template <typename CharType>
520inline bool parseFloatPoint2(const CharType*& current, const CharType* end, FloatPoint& point1, FloatPoint& point2)
521{
522    float x1;
523    float y1;
524    float x2;
525    float y2;
526    if (!parseNumber(current, end, x1)
527        || !parseNumber(current, end, y1)
528        || !parseNumber(current, end, x2)
529        || !parseNumber(current, end, y2))
530        return false;
531    point1 = FloatPoint(x1, y1);
532    point2 = FloatPoint(x2, y2);
533    return true;
534}
535
536template bool parseFloatPoint2(const LChar*& current, const LChar* end, FloatPoint& point1, FloatPoint& point2);
537template bool parseFloatPoint2(const UChar*& current, const UChar* end, FloatPoint& point1, FloatPoint& point2);
538
539template <typename CharType>
540bool parseFloatPoint3(const CharType*& current, const CharType* end, FloatPoint& point1, FloatPoint& point2, FloatPoint& point3)
541{
542    float x1;
543    float y1;
544    float x2;
545    float y2;
546    float x3;
547    float y3;
548    if (!parseNumber(current, end, x1)
549        || !parseNumber(current, end, y1)
550        || !parseNumber(current, end, x2)
551        || !parseNumber(current, end, y2)
552        || !parseNumber(current, end, x3)
553        || !parseNumber(current, end, y3))
554        return false;
555    point1 = FloatPoint(x1, y1);
556    point2 = FloatPoint(x2, y2);
557    point3 = FloatPoint(x3, y3);
558    return true;
559}
560
561template bool parseFloatPoint3(const LChar*& current, const LChar* end, FloatPoint& point1, FloatPoint& point2, FloatPoint& point3);
562template bool parseFloatPoint3(const UChar*& current, const UChar* end, FloatPoint& point1, FloatPoint& point2, FloatPoint& point3);
563
564template<typename CharType>
565static int parseTransformParamList(const CharType*& ptr, const CharType* end, float* values, int required, int optional)
566{
567    int optionalParams = 0, requiredParams = 0;
568
569    if (!skipOptionalSVGSpaces(ptr, end) || *ptr != '(')
570        return -1;
571
572    ptr++;
573
574    skipOptionalSVGSpaces(ptr, end);
575
576    while (requiredParams < required) {
577        if (ptr >= end || !parseNumber(ptr, end, values[requiredParams], false))
578            return -1;
579        requiredParams++;
580        if (requiredParams < required)
581            skipOptionalSVGSpacesOrDelimiter(ptr, end);
582    }
583    if (!skipOptionalSVGSpaces(ptr, end))
584        return -1;
585
586    bool delimParsed = skipOptionalSVGSpacesOrDelimiter(ptr, end);
587
588    if (ptr >= end)
589        return -1;
590
591    if (*ptr == ')') { // skip optionals
592        ptr++;
593        if (delimParsed)
594            return -1;
595    } else {
596        while (optionalParams < optional) {
597            if (ptr >= end || !parseNumber(ptr, end, values[requiredParams + optionalParams], false))
598                return -1;
599            optionalParams++;
600            if (optionalParams < optional)
601                skipOptionalSVGSpacesOrDelimiter(ptr, end);
602        }
603
604        if (!skipOptionalSVGSpaces(ptr, end))
605            return -1;
606
607        delimParsed = skipOptionalSVGSpacesOrDelimiter(ptr, end);
608
609        if (ptr >= end || *ptr != ')' || delimParsed)
610            return -1;
611        ptr++;
612    }
613
614    return requiredParams + optionalParams;
615}
616
617// These should be kept in sync with enum SVGTransformType
618static const int requiredValuesForType[] =  {0, 6, 1, 1, 1, 1, 1};
619static const int optionalValuesForType[] =  {0, 0, 1, 1, 2, 0, 0};
620
621template<typename CharType>
622static bool parseTransformValueInternal(unsigned type, const CharType*& ptr, const CharType* end, SVGTransform& transform)
623{
624    if (type == SVGTransform::SVG_TRANSFORM_UNKNOWN)
625        return false;
626
627    int valueCount = 0;
628    float values[] = {0, 0, 0, 0, 0, 0};
629    if ((valueCount = parseTransformParamList(ptr, end, values, requiredValuesForType[type], optionalValuesForType[type])) < 0)
630        return false;
631
632    switch (type) {
633    case SVGTransform::SVG_TRANSFORM_SKEWX:
634        transform.setSkewX(values[0]);
635        break;
636    case SVGTransform::SVG_TRANSFORM_SKEWY:
637        transform.setSkewY(values[0]);
638        break;
639    case SVGTransform::SVG_TRANSFORM_SCALE:
640        if (valueCount == 1) // Spec: if only one param given, assume uniform scaling
641            transform.setScale(values[0], values[0]);
642        else
643            transform.setScale(values[0], values[1]);
644        break;
645    case SVGTransform::SVG_TRANSFORM_TRANSLATE:
646        if (valueCount == 1) // Spec: if only one param given, assume 2nd param to be 0
647            transform.setTranslate(values[0], 0);
648        else
649            transform.setTranslate(values[0], values[1]);
650        break;
651    case SVGTransform::SVG_TRANSFORM_ROTATE:
652        if (valueCount == 1)
653            transform.setRotate(values[0], 0, 0);
654        else
655            transform.setRotate(values[0], values[1], values[2]);
656        break;
657    case SVGTransform::SVG_TRANSFORM_MATRIX:
658        transform.setMatrix(AffineTransform(values[0], values[1], values[2], values[3], values[4], values[5]));
659        break;
660    }
661
662    return true;
663}
664
665bool parseTransformValue(unsigned type, const LChar*& ptr, const LChar* end, SVGTransform& transform)
666{
667    return parseTransformValueInternal(type, ptr, end, transform);
668}
669
670bool parseTransformValue(unsigned type, const UChar*& ptr, const UChar* end, SVGTransform& transform)
671{
672    return parseTransformValueInternal(type, ptr, end, transform);
673}
674
675static const LChar skewXDesc[] =  {'s', 'k', 'e', 'w', 'X'};
676static const LChar skewYDesc[] =  {'s', 'k', 'e', 'w', 'Y'};
677static const LChar scaleDesc[] =  {'s', 'c', 'a', 'l', 'e'};
678static const LChar translateDesc[] =  {'t', 'r', 'a', 'n', 's', 'l', 'a', 't', 'e'};
679static const LChar rotateDesc[] =  {'r', 'o', 't', 'a', 't', 'e'};
680static const LChar matrixDesc[] =  {'m', 'a', 't', 'r', 'i', 'x'};
681
682template<typename CharType>
683static inline bool parseAndSkipType(const CharType*& ptr, const CharType* end, unsigned short& type)
684{
685    if (ptr >= end)
686        return false;
687
688    if (*ptr == 's') {
689        if (skipString(ptr, end, skewXDesc, WTF_ARRAY_LENGTH(skewXDesc)))
690            type = SVGTransform::SVG_TRANSFORM_SKEWX;
691        else if (skipString(ptr, end, skewYDesc, WTF_ARRAY_LENGTH(skewYDesc)))
692            type = SVGTransform::SVG_TRANSFORM_SKEWY;
693        else if (skipString(ptr, end, scaleDesc, WTF_ARRAY_LENGTH(scaleDesc)))
694            type = SVGTransform::SVG_TRANSFORM_SCALE;
695        else
696            return false;
697    } else if (skipString(ptr, end, translateDesc, WTF_ARRAY_LENGTH(translateDesc)))
698        type = SVGTransform::SVG_TRANSFORM_TRANSLATE;
699    else if (skipString(ptr, end, rotateDesc, WTF_ARRAY_LENGTH(rotateDesc)))
700        type = SVGTransform::SVG_TRANSFORM_ROTATE;
701    else if (skipString(ptr, end, matrixDesc, WTF_ARRAY_LENGTH(matrixDesc)))
702        type = SVGTransform::SVG_TRANSFORM_MATRIX;
703    else
704        return false;
705
706    return true;
707}
708
709SVGTransform::SVGTransformType parseTransformType(const String& string)
710{
711    if (string.isEmpty())
712        return SVGTransform::SVG_TRANSFORM_UNKNOWN;
713    unsigned short type = SVGTransform::SVG_TRANSFORM_UNKNOWN;
714    if (string.is8Bit()) {
715        const LChar* ptr = string.characters8();
716        const LChar* end = ptr + string.length();
717        parseAndSkipType(ptr, end, type);
718    } else {
719        const UChar* ptr = string.characters16();
720        const UChar* end = ptr + string.length();
721        parseAndSkipType(ptr, end, type);
722    }
723    return static_cast<SVGTransform::SVGTransformType>(type);
724}
725
726template<typename CharType>
727bool parseTransformAttributeInternal(SVGTransformList& list, const CharType*& ptr, const CharType* end, TransformParsingMode mode)
728{
729    if (mode == ClearList)
730        list.clear();
731
732    bool delimParsed = false;
733    while (ptr < end) {
734        delimParsed = false;
735        unsigned short type = SVGTransform::SVG_TRANSFORM_UNKNOWN;
736        skipOptionalSVGSpaces(ptr, end);
737
738        if (!parseAndSkipType(ptr, end, type))
739            return false;
740
741        SVGTransform transform;
742        if (!parseTransformValue(type, ptr, end, transform))
743            return false;
744
745        list.append(transform);
746        skipOptionalSVGSpaces(ptr, end);
747        if (ptr < end && *ptr == ',') {
748            delimParsed = true;
749            ++ptr;
750        }
751        skipOptionalSVGSpaces(ptr, end);
752    }
753
754    return !delimParsed;
755}
756
757bool parseTransformAttribute(SVGTransformList& list, const LChar*& ptr, const LChar* end, TransformParsingMode mode)
758{
759    return parseTransformAttributeInternal(list, ptr, end, mode);
760}
761
762bool parseTransformAttribute(SVGTransformList& list, const UChar*& ptr, const UChar* end, TransformParsingMode mode)
763{
764    return parseTransformAttributeInternal(list, ptr, end, mode);
765}
766
767}
768