Code

Mnemonics in "Fill and stroke", "Align and distribute", and "Transform" dialogs ...
[inkscape.git] / src / dom / cssreader.cpp
1 /**
2  * Phoebe DOM Implementation.
3  *
4  * This is a C++ approximation of the W3C DOM model, which follows
5  * fairly closely the specifications in the various .idl files, copies of
6  * which are provided for reference.  Most important is this one:
7  *
8  * http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/idl-definitions.html
9  *
10  * Authors:
11  *   Bob Jamison
12  *
13  * Copyright (C) 2005-2008 Bob Jamison
14  *
15  *  This library is free software; you can redistribute it and/or
16  *  modify it under the terms of the GNU Lesser General Public
17  *  License as published by the Free Software Foundation; either
18  *  version 2.1 of the License, or (at your option) any later version.
19  *
20  *  This library is distributed in the hope that it will be useful,
21  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
22  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
23  *  Lesser General Public License for more details.
24  *
25  *  You should have received a copy of the GNU Lesser General Public
26  *  License along with this library; if not, write to the Free Software
27  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
28  */
30 #include "cssreader.h"
31 #include "ucd.h"
33 #include <stdio.h>
34 #include <stdarg.h>
36 namespace org
37 {
38 namespace w3c
39 {
40 namespace dom
41 {
42 namespace css
43 {
45 //#########################################################################
46 //# M E S S A G E S
47 //#########################################################################
49 /**
50  * Get the column and row number of the given character position
51  */
52 void CssReader::getColumnAndRow(int p, int &colResult, int &rowResult, int &lastNL)
53 {
54     int col    = 1;
55     int row    = 1;
56     int lastnl = 0;
58     for (int i=0 ; i<p ; i++)
59         {
60         XMLCh ch = parsebuf[i];
61         if (ch == '\n')
62             {
63             lastnl = i;
64             row++;
65             col=0;
66             }
67         else
68             col++;
69         }
71     colResult  = col;
72     rowResult  = row;
73     lastNL     = lastnl;
74 }
76 /**
77  *
78  */
79 void CssReader::error(char const *fmt, ...)
80 {
81     int lineNr;
82     int colNr;
83     int lastNL;
84     getColumnAndRow(lastPosition, colNr, lineNr, lastNL);
86     va_list args;
87     fprintf(stderr, "CssReader:error at %d, line %d, column %d:",
88                         lastPosition, lineNr, colNr);
89     va_start(args, fmt);
90     vfprintf(stderr, fmt, args);
91     va_end(args) ;
92     fprintf(stderr, "\n");
94     /*
95     int lineLen = lastPosition - lastNL;
96     printf("lineLen:%d lastNL:%d\n", lineLen, lastNL);
97     for (int i=lastNL+1 ; i<lastPosition ; i++)
98         fprintf(stderr, "%c", parsebuf[i]);
99     fprintf(stderr, "\n");
100     for (int i=0 ; i<lineLen-1 ; i++)
101         fprintf(stderr, " ");
102     fprintf(stderr, "^\n");
103     */
104     for (int i=0 ; i<lastPosition ; i++)
105         fprintf(stderr, "%c", parsebuf[i]);
106     fprintf(stderr, "\n");
111 //#########################################################################
112 //# P A R S I N G
113 //#########################################################################
115 /**
116  *  Get the character at the position and record the fact
117  */
118 XMLCh CssReader::get(int p)
120     if (p >= parselen)
121         return 0;
122     XMLCh ch = parsebuf[p];
123     //printf("%c", ch);
124     lastPosition = p;
125     return ch;
130 /**
131  *  Test if the given substring exists at the given position
132  *  in parsebuf.  Use get() in case of out-of-bounds
133  */
134 bool CssReader::match(int pos, const char *str)
136     while (*str)
137        {
138        if (get(pos++) != (XMLCh) *str++)
139            return false;
140        }
141    return true;
144 /**
145  *
146  */
147 int CssReader::skipwhite(int p)
149   while (p < parselen)
150     {
151     //# XML COMMENT
152     if (match(p, "<!--"))
153         {
154         p+=4;
155         bool done=false;
156         while (p<parselen)
157             {
158             if (match(p, "-->"))
159                 {
160                 p+=3;
161                 done=true;
162                 break;
163                 }
164             p++;
165             }
166         lastPosition = p;
167         if (!done)
168             {
169             error("unterminated <!-- .. --> comment");
170             return -1;
171             }
172         }
173     //# C comment
174     else if (match(p, "/*"))
175         {
176         p+=2;
177         bool done=false;
178         while (p<parselen)
179             {
180             if (match(p, "*/"))
181                 {
182                 p+=2;
183                 done=true;
184                 break;
185                 }
186             p++;
187             }
188         lastPosition = p;
189         if (!done)
190             {
191             error("unterminated /* .. */ comment");
192             return -1;
193             }
194         }
195     else if (!uni_is_space(get(p)))
196         break;
197     else
198         p++;
199     }
200   lastPosition = p;
201   return p;
204 /**
205  * get a word from the buffer
206  */
207 int CssReader::getWord(int p, DOMString &result)
209     XMLCh ch = get(p);
210     if (!uni_is_letter(ch))
211         return p;
212     DOMString str;
213     str.push_back(ch);
214     p++;
216     while (p < parselen)
217         {
218         ch = get(p);
219         if (uni_is_letter_or_digit(ch) || ch=='-' || ch=='_')
220             {
221             str.push_back(ch);
222             p++;
223             }
224         else if (ch == '\\')
225             {
226             p+=2;
227             }
228         else
229             break;
230         }
231     result = str;
232     return p;
236 /**
237  * get a word from the buffer
238  */
239 int CssReader::getNumber(int p0, double &result)
241     int p=p0;
242     DOMString str;
243     while (p < parselen)
244         {
245         XMLCh ch = get(p);
246         if (ch<'0' || ch>'9')
247             break;
248         str.push_back(ch);
249         p++;
250         }
251     if (get(p) == '.' && get(p+1)>='0' && get(p+1)<='9')
252         {
253         p++;
254         str.push_back('.');
255         while (p < parselen)
256             {
257             XMLCh ch = get(p);
258             if (ch<'0' || ch>'9')
259                 break;
260             str.push_back(ch);
261             p++;
262             }
263         }
264     if (p>p0)
265         {
266         char *start = (char *)str.c_str();
267         char *end   = NULL;
268         double val = strtod(start, &end);
269         if (end > start)
270             {
271             result = val;
272             return p;
273             }
274         }
276     //not a number
277     return p0;
282 /**
283  * Assume that we are starting on a quote.  Ends on the char
284  * after the final '"'
285  */
286 int CssReader::getQuoted(int p0, DOMString &result)
289     int p = p0;
291     XMLCh quoteChar = get(p);
292     if (quoteChar != '"' && quoteChar != '\'')
293         return p0;
295     p++;
297     DOMString buf;
299     bool done = false;
300     while (p<parselen )
301         {
302         XMLCh ch = get(p);
303         if (ch == quoteChar)
304             {
305             done = true;
306             p++;
307             break;
308             }
309         else
310             {
311             buf.push_back(ch);
312             }
313         p++;
314         }
316     if (!done)
317         {
318         error("unterminated quoted string");
319         return -1;
320         }
322     result.append(buf);
324     return p;
327 /**
328  * Not in api.  replaces URI return by lexer
329  */
330 int CssReader::getUri(int p0, DOMString &str)
332     int p = p0;
333     if (!match(p, "url("))
334         return p0;
335     p+=4;
336     p = skipwhite(p);
337     DOMString buf;
338     XMLCh ch;
339     while (p < parselen)
340         {
341         ch = get(p);
342         if (isspace(ch) || ch==')')
343             break;
344         buf.push_back(ch);
345         p++;
346         }
347     p = skipwhite(p);
348     ch = get(p);
349     if (ch != ')')
350         {
351         error("no closing ')' on url spec");
352         return -1;
353         }
354     p++;
355     str = buf;
356     return p;
359 /**
360  * Skip to the end of the block
361  */
362 int CssReader::skipBlock(int p0)
364     int p = p0;
365     while (p < parselen)
366         {
367         XMLCh ch = get(p);
368         if (ch == '}')
369             {
370             p++;
371             break;
372             }
373         else
374             {
375             p++;
376             }
377         }
378     return p;
381 //#########################################################################
382 //# P R O D U C T I O N S
383 //#########################################################################
385 /**
386  * stylesheet
387  *   : [ CHARSET_SYM S* STRING S* ';' ]?
388  *     [S|CDO|CDC]* [ import [S|CDO|CDC]* ]*
389  *     [ [ ruleset | media | page ] [S|CDO|CDC]* ]*
390  *   ;
391  */
392 int CssReader::getStyleSheet(int p0)
394     int p = p0;
395     int p2 = p;
396     XMLCh ch;
398     //# CHARSET   0 or 1
399     if (match(p, "@charset"))
400         {
401         p+=8;
402         p = skipwhite(p);
403         DOMString str;
404         p2 = getQuoted(p, str);
405         if (p2<=p)
406             {
407             error("quoted string required after @charset");
408             return -1;
409             }
410         p = skipwhite(p2);
411         ch = get(p);
412         if (ch !=';')
413             {
414             error("';' required after @charset declaration");
415             return -1;
416             }
417         p++;
418         p = skipwhite(p);
419         }
421     //# IMPORT  0 to many
422     while (true)
423         {
424         p2 = getImport(p);
425         if (p2<0)
426             {
427             return -1;
428             }
429         if (p2<=p)
430             break;
431         p = p2;
432         }
434     //# RULESET | MEDIA | PAGE  0 to many
435     while (true)
436         {
437         //Ruleset
438         p2 = getRuleSet(p);
439         if (p2<0)
440             {
441             return -1;
442             }
443         if (p2>p)
444             {
445             p = p2;
446             continue;
447             }
449         //Media
450         p2 = getMedia(p);
451         if (p2<0)
452             {
453             return -1;
454             }
455         if (p2>p)
456             {
457             p = p2;
458             continue;
459             }
461         //Page
462         p2 = getPage(p);
463         if (p2<0)
464             {
465             return -1;
466             }
467         if (p2>p)
468             {
469             p = p2;
470             continue;
471             }
473         //none of the above
474         break;
475         }
477     return p;
480 /**
481  * import
482  *   : IMPORT_SYM S*
483  *     [STRING|URI] S* [ medium [ COMMA S* medium]* ]? ';' S*
484  *   ;
485  */
486 int CssReader::getImport(int p0)
488     int p = p0;
489     if (!match(p, "@import"))
490         return p0;
491     p+=7;
492     p = skipwhite(p);
494     //# STRING | URI
495     DOMString str;
496     int p2 = getQuoted(p, str);
497     if (p2<0)
498         {
499         return -1;
500         }
501     if (p2<=p)
502         {
503         p2 = getUri(p, str);
504         if (p2<0)
505             {
506             return -1;
507             }
508         if (p2<=p)
509             {
510             error("quoted string or URI required after @import");
511             return -1;
512             }
513         }
514     p = p2;
515     p2 = getMedium(p);
516     if (p2<0)
517         return -1;
519     p = p2;
520     p = skipwhite(p);
521     XMLCh ch = get(p);
522     if (ch != ';')
523         {
524         error("@import must be terminated with ';'");
525         return -1;
526         }
527     p++;
528     return p;
531 /**
532  * media
533  *   : MEDIA_SYM S* medium [ COMMA S* medium ]* LBRACE S* ruleset* '}' S*
534  *   ;
535  */
536 int CssReader::getMedia(int p0)
538     int p = p0;
539     XMLCh ch;
540     if (!match(p, "@media"))
541         return p0;
542     p+=6;
543     p = skipwhite(p);
545     //# MEDIUM LIST
546     int p2 = getMedium(p);
547     if (p2<0)
548         return -1;
549     if (p2<=p)
550         {
551         error("@media must be followed by medium");
552         return -1;
553         }
554     p = p2;
555     while (true)
556         {
557         ch = get(p);
558         if (ch != ',')
559             break;
560         p2 = getMedium(p);
561         if (p2<0)
562             return -1;
563         if (p2<=p)
564             {
565             error("',' in medium list must be followed by medium");
566             return -1;
567             }
568         p = p2;
569         }
571     p = skipwhite(p);
572     ch = get(p);
573     if (ch!='{')
574         {
575         error("@media requires '{' for ruleset");
576         return -1;
577         }
578     p++;
579     p2 = getRuleSet(p);
580     if (p2<0)
581         return -1;
582     if (p2<=p)
583         {
584         error("@media requires ruleset after '{'");
585         return -1;
586         }
587     p = p2;
588     ch = get(p);
589     if (ch != '}')
590         {
591         error("@media requires '}' after ruleset");
592         return -1;
593         }
594     p++;
595     return p0;
598 /**
599  * medium
600  *   : IDENT S*
601  *   ;
602  */
603 int CssReader::getMedium(int p0)
605     int p = p0;
606     p = skipwhite(p);
608     DOMString ident;
609     int p2 = getWord(p, ident);
610     if (p2<0)
611         return -1;
612     if (p2<=p)
613         return p0;
614     p = p2;
616     return p;
619 /**
620  * page
621  *   : PAGE_SYM S* pseudo_page? S*
622  *     LBRACE S* declaration [ ';' S* declaration ]* '}' S*
623  *   ;
624  */
625 int CssReader::getPage(int p0)
627     int p = p0;
629     //# @PAGE
630     p = skipwhite(p);
631     if (!match(p, "@page"))
632         return p0;
633     p+= 5;
635     //#PSEUDO PAGE 0 or 1
636     p = skipwhite(p);
637     int p2 = getPseudoPage(p);
638     if (p2<0)
639         return -1;
640     if (p2>p)
641         {
642         p = p2;
643         }
645     //# {
646     p=skipwhite(p);
647     XMLCh ch = get(p);
648     if (p != '{')
649         {
650         error("@page requires '{' before declarations");
651         }
652     p++;
654     //# DECLARATION LIST
655     p = skipwhite(p);
656     CSSStyleDeclaration declarationList;
657     p2 = getDeclaration(p, declarationList);
658     if (p2<0)
659         return -1;
660     if (p2<=p)
661         {
662         error("@page requires declaration(s) after '{'");
663         return -1;
664         }
665     while (true)
666         {
667         p = skipwhite(p2);
668         ch = get(p);
669         if (ch != ';')
670             break;
671         p++;
672         p = skipwhite(p);
673         p2 = getDeclaration(p, declarationList);
674         if (p2<0)
675             return -1;
676         if (p2<= p)
677             {
678             error("@page requires declaration after ';'");
679             return -1;
680             }
681         }
683     //# }
684     p=skipwhite(p);
685     ch = get(p);
686     if (p != '}')
687         {
688         error("@page requires '}' after declarations");
689         }
690     p++;
692     return p;
695 /**
696  * pseudo_page
697  *   : ':' IDENT
698  *   ;
699  */
700 int CssReader::getPseudoPage(int p0)
702     int p = p0;
703     if (!match(p, ":"))
704         return p0;
705     p++;
706     DOMString str;
707     int p2 = getWord(p, str);
708     if (p2<0)
709         return -1;
710     if (p2<=p)
711         {
712         error("pseudo-page requires identifier after ':'");
713         return -1;
714         }
715     p = p2;
716     return p;
719 /**
720  * ruleset
721  *   : selector [ COMMA S* selector ]*
722  *     LBRACE S* declaration [ ';' S* declaration ]* '}' S*
723  *   ;
724  */
725 int CssReader::getRuleSet(int p0)
727     int p = p0;
728     XMLCh ch;
730     //## SELECTOR
731     p = skipwhite(p);
732     int p2 = getSelector(p);
733     if (p2<0)
734         return -1;
735     if (p2<=p)  //no selector
736         {
737         if (get(p) != '{')//check for selector-less rule
738             return p0;//not me
739         }
740     p = p2;
741     while (true)
742         {
743         p = skipwhite(p);
744         ch = get(p);
745         if (ch != ',')
746             break;
747         p++;
748         p = skipwhite(p);
749         int p2 = getSelector(p);
750         if (p2<0)
751             return -1;
752         if (p2<=p)
753             {
754             error("selector required after ',' in list");
755             return -1;
756             }
757         p = p2;
758         }
760     //## {
761     ch = get(p);
762     if (ch != '{')
763         {
764         error("'{' required before declarations of ruleset");
765         return -1;
766         }
767     p++;
769     //## DECLARATIONS ( 0 to many )
770     CSSStyleDeclaration declarationList;
772     p = skipwhite(p);
773     p2 = getDeclaration(p, declarationList);
774     if (p2<0)
775         return -1;
776     if (p2>p)
777         {
778         p = p2;
779         while (true)
780             {
781             p = skipwhite(p);
782             ch = get(p);
783             if (ch != ';')
784                 break;
785             p++;
786             p = skipwhite(p);
787             p2 = getDeclaration(p, declarationList);
788             if (p2<0)
789                 return -1;
790             if (p2<=p)
791                 {
792                 //apparently this is ok
793                 //error("declaration required after ';' in ruleset");
794                 //return -1;
795                 break;
796                 }
797             p = p2;
798             }
799         }
800     //## }
801     ch = get(p);
802     if (ch != '}')
803         {
804         error("ruleset requires closing '}'");
805         return -1;
806         }
807     p++;
808     p = skipwhite(p);
810     return p;
813 /**
814  * selector
815  *   : simple_selector [ combinator simple_selector ]*
816  *   ;
817  */
818 int CssReader::getSelector(int p0)
820     int p = p0;
822     //## SIMPLE SELECTOR
823     p = skipwhite(p);
824     int p2 = getSimpleSelector(p);
825     if (p2<0)
826         return -1;
827     if (p2<=p)
828         return p0; //not me
829     p = p2;
831     //## COMBINATORS + MORE SELECTORS
832     while (true)
833         {
834         XMLCh ch = get(p);
835         bool wasSpace = isspace(ch);
836         p = skipwhite(p);
837         ch = get(p);
838         //# Combinators
839         //easier to do here than have a getCombinator()
840         int visibleCombinator = false;
841         if (ch == '+')
842             {
843             visibleCombinator = true;
844             p++;
845             }
846         else if (ch == '>')
847             {
848             visibleCombinator = true;
849             p++;
850             }
851         else if (wasSpace)
852             {
853             }
854         else
855             {
856             break;
857             }
858         p = skipwhite(p);
859         p2 = getSimpleSelector(p);
860         if (p2<0)
861             return -1;
862         if (p2<=p)
863             {
864             if (visibleCombinator)
865                 {
866                 error("need simple selector after combinator");
867                 return -1;
868                 }
869             else
870                 {
871                 break;
872                 }
873             }
874         p = p2;
875         }
876     return p;
879 /**
880  * simple_selector
881  *   : element_name [ HASH | class | attrib | pseudo ]*
882  *   | [ HASH | class | attrib | pseudo ]+
883  *   ;
884  */
885 int CssReader::getSimpleSelector(int p0)
887     int p = p0;
888     int p2;
890     DOMString str;
892     p = skipwhite(p);
894     int selectorItems = 0;
896     XMLCh ch = get(p);
898     //######################
899     //# Note: do NOT skipwhite between items.  Only within the
900     //# pseudo function and attrib below
901     //######################
903     //#Element name 0 or 1
904     if (uni_is_letter(ch))
905         {
906         p2 = getWord(p, str);
907         if (p2<0)
908             return -1;
909         if (p2<=p)
910             {
911             error("null element name");
912             return -1;
913             }
914         selectorItems++;
915         p = p2;
916         }
917     else if (ch == '*')
918         {
919         str = "*";
920         p++;
921         selectorItems++;
922         }
926     //## HASH, CLASS, ATTRIB, PSEUDO  (0 to many with elem name, 1 to many without)
927     while (true)
928         {
929         XMLCh ch = get(p);
931         //# HASH
932         if (ch == '#')
933             {
934             p++;
935             p2 = getWord(p, str);
936             if (p2<0)
937                 return -1;
938             if (p2<=p)
939                 {
940                 error("no name for hash");
941                 return -1;
942                 }
943             p = p2;
944             selectorItems++;
945             }
947         //# CLASS
948         else if (ch == '.')
949             {
950             p++;
951             p2 = getWord(p, str);
952             if (p2<0)
953                 return -1;
954             if (p2<=p)
955                 {
956                 error("no name for class");
957                 return -1;
958                 }
959             p = p2;
960             selectorItems++;
961             }
963         //# ATTRIB
964         else if (ch == '[')
965             {
966             p++;
967             p = skipwhite(p);
968             p2 = getWord(p, str);
969             if (p2<0)
970                 return -1;
971             if (p2<=p)
972                 {
973                 error("no name for class");
974                 return -1;
975                 }
976             p = skipwhite(p2);
977             bool getRHS=false;
978             if (match(p, "="))
979                 {
980                 p++;
981                 getRHS=true;
982                 }
983             else if (match(p, "~="))
984                 {
985                 p+=2;
986                 getRHS=true;
987                 }
988             else if (match(p, "|="))
989                 {
990                 p+=2;
991                 getRHS=true;
992                 }
993             if (getRHS)
994                 {
995                 p = skipwhite(p);
996                 ch = get(p);
997                 if (uni_is_letter(ch))
998                     {
999                     p2 = getWord(p, str);
1000                     if (p2<0)
1001                         return -1;
1002                     if (p2<=p)
1003                         {
1004                         error("null ident on rhs of attrib");
1005                         return -1;
1006                         }
1007                     p = p2;
1008                     }
1009                 else if (ch == '\'' || ch =='"')
1010                     {
1011                     p2 = getQuoted(p, str);
1012                     if (p2<0)
1013                         return -1;
1014                     if (p2<=p)
1015                         {
1016                         error("null literal string on rhs of attrib");
1017                         return -1;
1018                         }
1019                     p = p2;
1020                     }
1021                 }//getRHS
1022             p = skipwhite(p);
1023             ch = get(p);
1024             if (ch != ']')
1025                 {
1026                 error("attrib needs closing ']'");
1027                 //return -1;
1028                 p = skipBlock(p);
1029                 return p;
1030                 }
1031             p++;
1032             selectorItems++;
1033             }
1035         //# PSEUDO
1036         else if (ch == ':')
1037             {
1038             p++;
1039             p2 = getWord(p, str);
1040             if (p2<0)
1041                 return -1;
1042             if (p2<=p)
1043                 {
1044                 error("no name for pseudo");
1045                 return -1;
1046                 }
1047             p = p2;
1048             selectorItems++;
1049             ch = get(p);
1050             if (ch == '(')
1051                 {
1052                 p++;
1053                 p = skipwhite(p);
1054                 ch = get(p);
1055                 if (uni_is_letter(ch))
1056                     {
1057                     p2 = getWord(p, str);
1058                     if (p2<0)
1059                         return -1;
1060                     if (p2<=p)
1061                         {
1062                         error("null function parameter in pseudo");
1063                         return -1;
1064                         }
1065                     p = skipwhite(p2);
1066                     ch = get(p);
1067                     }
1068                 if (ch != ')')
1069                     {
1070                     error("function in pseudo needs ')'");
1071                     return -1;
1072                     }
1073                 p++;
1074                 }// ch==(   -function-
1075             }//pseudo
1077         //# none of the above
1078         else
1079             {
1080             break;
1081             }
1083         }//while
1086     if (selectorItems > 0)
1087         return p;
1088     return p0;
1091 /**
1092  * declaration
1093  *   : property ':' S* expr prio?
1094  *   | {empty}
1095  *   ;
1096  */
1097 int CssReader::getDeclaration(int p0, CSSStyleDeclaration &/*declarationList*/)
1099     int p = p0;
1101     //## PROPERTY
1102     p = skipwhite(p);
1103     XMLCh ch = get(p);
1104     if (!uni_is_letter(ch))
1105         return p0; //not me
1106     DOMString propName;
1107     int p2 = getWord(p, propName);
1108     if (p2<0)
1109         return -1;
1111     //## ':'
1112     p = skipwhite(p2);
1113     ch = get(p);
1114     if (ch != ':')
1115         {
1116         error("declaration requires ':' between name and value");
1117         return -1;
1118         }
1119     p++;
1121     //## EXPR
1122     p = skipwhite(p);
1123     p2 = getExpr(p);
1124     if (p2<0)
1125         return -1;
1126     if (p2<=p)
1127         {
1128         error("declaration requires value after ':'");
1129         return -1;
1130         }
1131     DOMString propVal;
1132     for (int i=p ; i<p2 ; i++)  //get our substring
1133         propVal.push_back(get(i));
1134     printf("propVal:%s\n", propVal.c_str());
1135     p = p2;
1137     //## PRIO (optional)
1138     p = skipwhite(p);
1139     DOMString prio;
1140     p2 = getPrio(p, prio);
1141     if (p2<0)
1142         return -1;
1143     if (p2>p)
1144         {
1145         //do something
1146         p = p2;
1147         }
1149     return p;
1152 /**
1153  * prio
1154  *   : IMPORTANT_SYM S*
1155  *   ;
1156  */
1157 int CssReader::getPrio(int p0, DOMString &val)
1159     int p = p0;
1161     //## '!"
1162     p = skipwhite(p);
1163     XMLCh ch = get(p);
1164     if (ch != '!')
1165         return p0;
1166     p++;
1168     //## "important"
1169     p = skipwhite(p);
1170     if (!match(p, "important"))
1171         {
1172         error("priority symbol is 'important'");
1173         return -1;
1174         }
1175     p += 9;
1176     val = "important";
1177     return p;
1180 /**
1181  * expr
1182  *   : term [ operator term ]*
1183  *   ;
1184  */
1185 int CssReader::getExpr(int p0)
1187     int p = p0;
1189     //## TERM
1190     p = skipwhite(p);
1191     int p2 = getTerm(p);
1192     if (p2<0)
1193         return -1;
1194     if (p2<=p)
1195         return p0;  //not me
1196     p = p2;
1197     while (p < parselen)
1198         {
1199         p = skipwhite(p);
1200         //#Operator.  do this instead of getOperator()
1201         XMLCh ch = get(p);
1202         int visibleTerm = false;
1203         if (ch == '/')
1204             {
1205             visibleTerm = true;
1206             p++;
1207             }
1208         else if (ch == ',')
1209             {
1210             visibleTerm = true;
1211             p++;
1212             }
1213         else
1214             {
1215             //just space.  this is allowable between terms,
1216             // so we still need to check for another term
1217             }
1218         p = skipwhite(p);
1219         p2 = getTerm(p);
1220         if (p2<0)
1221             return -1;
1222         if (p2<=p)
1223             {
1224             if (visibleTerm)
1225                 {
1226                 error("expression requires term after operator");
1227                 return -1;
1228                 }
1229             else
1230                 {
1231                 break;
1232                 }
1233             }
1234         p = p2;
1235         }
1237     return p;
1240 /**
1241  * term
1242  *   : unary_operator?
1243  *     [ NUMBER S* | PERCENTAGE S* | LENGTH S* | EMS S* | EXS S* | ANGLE S* |
1244  *       TIME S* | FREQ S* | function ]
1245  *   | STRING S* | IDENT S* | URI S* | hexcolor
1246  *   ;
1247  */
1248 int CssReader::getTerm(int p0)
1250     int p = p0;
1251     p = skipwhite(p);
1252     int unitType = CSSPrimitiveValue::CSS_UNKNOWN;
1253     //# Unary operator
1254     XMLCh ch = get(p);
1255     bool hasUnary = false;
1256     if (ch == '-')
1257         {
1258         p++;
1259         hasUnary = true;
1260         }
1261     else if (ch == '+')
1262         {
1263         p++;
1264         hasUnary = true;
1265         }
1266     //# NUMERIC
1267     double numVal;
1268     int p2 = getNumber(p, numVal);
1269     if (p2<0)
1270         return -1;
1271     if (p2>p)
1272         {
1273         p = p2;
1274         if (match(p, "%"))
1275             {
1276             unitType = CSSPrimitiveValue::CSS_PERCENTAGE;
1277             p++;
1278             }
1279         else if (match(p, "em"))
1280             {
1281             unitType = CSSPrimitiveValue::CSS_EMS;
1282             p+=2;
1283             }
1284         else if (match(p, "ex"))
1285             {
1286             unitType = CSSPrimitiveValue::CSS_EXS;
1287             p+=2;
1288             }
1289         else if (match(p, "px"))
1290             {
1291             unitType = CSSPrimitiveValue::CSS_PX;
1292             p+=2;
1293             }
1294         else if (match(p, "cm"))
1295             {
1296             unitType = CSSPrimitiveValue::CSS_CM;
1297             p+=2;
1298             }
1299         else if (match(p, "mm"))
1300             {
1301             unitType = CSSPrimitiveValue::CSS_MM;
1302             p+=2;
1303             }
1304         else if (match(p, "in"))
1305             {
1306             unitType = CSSPrimitiveValue::CSS_IN;
1307             p+=2;
1308             }
1309         else if (match(p, "pt"))
1310             {
1311             unitType = CSSPrimitiveValue::CSS_PT;
1312             p+=2;
1313             }
1314         else if (match(p, "pc"))
1315             {
1316             unitType = CSSPrimitiveValue::CSS_PC;
1317             p+=2;
1318             }
1319         else if (match(p, "deg"))
1320             {
1321             unitType = CSSPrimitiveValue::CSS_DEG;
1322             p+=3;
1323             }
1324         else if (match(p, "rad"))
1325             {
1326             unitType = CSSPrimitiveValue::CSS_RAD;
1327             p+=3;
1328             }
1329         else if (match(p, "grad"))
1330             {
1331             unitType = CSSPrimitiveValue::CSS_GRAD;
1332             p+=4;
1333             }
1334         else if (match(p, "ms"))
1335             {
1336             unitType = CSSPrimitiveValue::CSS_MS;
1337             p+=2;
1338             }
1339         else if (match(p, "s"))
1340             {
1341             unitType = CSSPrimitiveValue::CSS_S;
1342             p+=1;
1343             }
1344         else if (match(p, "Hz"))
1345             {
1346             unitType = CSSPrimitiveValue::CSS_HZ;
1347             p+=2;
1348             }
1349         else if (match(p, "kHz"))
1350             {
1351             unitType = CSSPrimitiveValue::CSS_KHZ;
1352             p+=2;
1353             }
1354         else if (uni_is_letter(get(p)))//some other string
1355             {
1356             DOMString suffix;
1357             p2 = getWord(p, suffix);
1358             if (p2<0)
1359                 return -1;
1360             unitType = CSSPrimitiveValue::CSS_DIMENSION;
1361             p = p2;
1362             }
1363         else //plain number
1364             {
1365             unitType = CSSPrimitiveValue::CSS_NUMBER;
1366             }
1367         return p;
1368         }
1370     DOMString str;
1372     //## URI --do before function, as syntax is similar
1373     p2 = getUri(p, str);
1374     if (p2<0)
1375         return -1;
1376     if (p2>p)
1377         {
1378         if (hasUnary)
1379             {
1380             error("+ or - not allowed on URI");
1381             return -1;
1382             }
1383         p = p2;
1384         unitType = CSSPrimitiveValue::CSS_URI;
1385         return p;
1386         }
1388     //## FUNCTION
1389     p2 = getFunction(p);
1390     if (p2<0)
1391         return -1;
1392     if (p2>p)
1393         {
1394         p = p2;
1395         return p;
1396         }
1398     //## STRING
1399     ch = get(p);
1400     if (ch == '"' || ch == '\'')
1401         {
1402         p2 = getQuoted(p, str);
1403         if (p2<0)
1404             return -1;
1405         if (p2>p)
1406             {
1407             if (hasUnary)
1408                 {
1409                 error("+ or - not allowed on a string");
1410                 return -1;
1411                 }
1412             p = p2;
1413             unitType = CSSPrimitiveValue::CSS_STRING;
1414             return p;
1415             }
1416         }
1418     //## IDENT
1419     ch = get(p);
1420     if (uni_is_letter(ch))
1421         {
1422         p2 = getWord(p, str);
1423         if (p2<0)
1424             return -1;
1425         if (p2>p)
1426             {
1427             if (hasUnary)
1428                 {
1429                 error("+ or - not allowed on an identifier");
1430                 return -1;
1431                 }
1432             p = p2;
1433             unitType = CSSPrimitiveValue::CSS_IDENT;
1434             return p;
1435             }
1436         }
1439     //## HEXCOLOR
1440     p2 = getHexColor(p);
1441     if (p2<0)
1442         return -1;
1443     if (p2>p)
1444         {
1445         if (hasUnary)
1446             {
1447             error("+ or - not allowed on hex color");
1448             return -1;
1449             }
1450         p = p2;
1451         unitType = CSSPrimitiveValue::CSS_RGBCOLOR;
1452         return p;
1453         }
1456     return p0;
1459 /**
1460  * function
1461  *   : FUNCTION S* expr ')' S*
1462  *   ;
1463  */
1464 int CssReader::getFunction(int p0)
1466     int p = p0;
1468     //## IDENT + (   (both)
1469     DOMString name;
1470     p = skipwhite(p);
1471     int p2 = getWord(p, name);
1472     if (p2<0)
1473         return -1;
1474     if (p2<=p)
1475         return p0; //not me
1476     if (name == "uri" || name=="url")
1477         return p0; //not me
1478     p = skipwhite(p2);
1479     XMLCh ch = get(p);
1480     if (ch != '(')
1481         return p0; //still not me
1482     p++;
1484     //## EXPR
1485     p = skipwhite(p);
1486     p2 = getExpr(p);
1487     if (p2<0)
1488         return -1;
1489     if (p2<=p)
1490         {
1491         error("function requires expression");
1492         return -1;
1493         }
1494     p = p2;
1496     //## ')'
1497     p = skipwhite(p);
1498     ch = get(p);
1499     if (ch != ')')
1500         {
1501         error("function requires closing ')'");
1502         return -1;
1503         }
1504     p++;
1505     p = skipwhite(p);
1507     return p;
1510 /**
1511  * There is a constraint on the color that it must
1512  * have either 3 or 6 hex-digits (i.e., [0-9a-fA-F])
1513  * after the "#"; e.g., "#000" is OK, but "#abcd" is not.
1514  * hexcolor
1515  *   : HASH S*
1516  *   ;
1517  */
1518 int CssReader::getHexColor(int p0)
1520     int p = p0;
1522     //## '#'
1523     p = skipwhite(p);
1524     if (!match(p, "#"))
1525         return p0;
1526     p++;
1528     //## HEX
1529     DOMString hex;
1530     long hexVal = 0;
1531     while (p < parselen)
1532         {
1533         XMLCh b = get(p);
1534         if (b>='0' && b<='9')
1535             {
1536             hexVal = (hexVal << 4) + (b - '0');
1537             hex.push_back(b);
1538             p++;
1539             }
1540         else if (b>='a' && b<='f')
1541             {
1542             hexVal = (hexVal << 4) + (b - 'a' + 10);
1543             hex.push_back(b);
1544             p++;
1545             }
1546         else if (b>='A' && b<='F')
1547             {
1548             hexVal = (hexVal << 4) + (b - 'A' + 10);
1549             hex.push_back(b);
1550             p++;
1551             }
1552         else
1553             {
1554             break;
1555             }
1556         }
1558     if (hex.size() != 3 && hex.size() != 6)
1559         {
1560         error("exactly 3 or 6 hex digits are required after '#'");
1561         return -1;
1562         }
1564     return p;
1569 /**
1570  *
1571  */
1572 bool CssReader::parse(const DOMString &str)
1574     /*
1575     int len = str.size();
1576     for (int i=0 ; i<len ; i++)
1577         {
1578         XMLCh ch = str[i];
1579         if (ch == '\\' && i<(len-1)) //escape!
1580             {
1581             i++;
1582             }
1583         else
1584             parsebuf.push_back(ch);
1585         }
1586     */
1587     parsebuf = str;
1589     parselen = parsebuf.size();
1590     //printf("==============================\n%s\n========================\n", str.c_str());
1592     lastPosition = 0;
1594     int p = getStyleSheet(0);
1595     if (p < parselen)
1596         {
1597         error("Not everything parsed");
1598         return false;
1599         }
1601     return true;
1605 /**
1606  *
1607  */
1608 bool CssReader::parseFile(const DOMString &fileName)
1610     DOMString tmp = fileName;
1611     char *fname = (char *)tmp.c_str();
1612     FILE *f = fopen(fname, "r");
1613     if (!f)
1614         {
1615         printf("Could not open %s for reading\n", fname);
1616         return false;
1617         }
1619     DOMString str;
1620     while (!feof(f))
1621         {
1622         int ch = fgetc(f);
1623         if (ch<0)
1624             break;
1625         str.push_back((XMLCh)ch);
1626         }
1627     fclose(f);
1629     bool ret = parse(str);
1631     return ret;
1640 } // namespace css
1641 } // namespace dom
1642 } // namespace w3c
1643 } // namespace org
1646 #ifdef CSSTEST
1648 static const char *fileNames[] =
1649     {
1650     "001.css",
1651     "acid.css",
1652     "base.css",
1653     "inkscape.css",
1654     "meyerweb.css",
1655     NULL
1656     };
1658 bool doTests()
1660     org::w3c::dom::css::CssReader parser;
1661     for (char **fname = (char **)fileNames ; *fname ; fname++)
1662        {
1663        DOMString fileName = *fname;
1664        if (!parser.parseFile(fileName))
1665            {
1666            printf("Test failed\n");
1667            return false;
1668            }
1669        }
1670     return true;
1673 int main(int argc, char **argv)
1675     if (!doTests())
1676         return 1;
1677     return 0;
1680 #endif /* CSSTEST */
1682 //#########################################################################
1683 //# E N D    O F    F I L E
1684 //#########################################################################