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 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 "cssparser.h"
31 #include "charclass.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 CssParser::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 CssParser::error(char *fmt, ...)
80 {
81 int lineNr;
82 int colNr;
83 int lastNL;
84 getColumnAndRow(lastPosition, colNr, lineNr, lastNL);
86 va_list args;
87 fprintf(stderr, "CssParser: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");
107 }
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 CssParser::get(int p)
119 {
120 if (p >= parselen)
121 return 0;
122 XMLCh ch = parsebuf[p];
123 //printf("%c", ch);
124 lastPosition = p;
125 return ch;
126 }
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 CssParser::match(int pos, char *str)
135 {
136 while (*str)
137 {
138 if (get(pos++) != (XMLCh) *str++)
139 return false;
140 }
141 return true;
142 }
144 /**
145 *
146 */
147 int CssParser::skipwhite(int p)
148 {
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 (!isWhitespace(get(p)))
196 break;
197 else
198 p++;
199 }
200 lastPosition = p;
201 return p;
202 }
204 /**
205 * get a word from the buffer
206 */
207 int CssParser::getWord(int p, DOMString &result)
208 {
209 XMLCh ch = get(p);
210 if (!isLetter(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 (isLetterOrDigit(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;
233 }
236 /**
237 * get a word from the buffer
238 */
239 int CssParser::getNumber(int p0, double &result)
240 {
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;
278 }
282 /**
283 * Assume that we are starting on a quote. Ends on the char
284 * after the final '"'
285 */
286 int CssParser::getQuoted(int p0, DOMString &result)
287 {
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;
325 }
327 /**
328 * Not in api. replaces URI return by lexer
329 */
330 int CssParser::getUri(int p0, DOMString &str)
331 {
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;
357 }
359 /**
360 * Skip to the end of the block
361 */
362 int CssParser::skipBlock(int p0)
363 {
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;
379 }
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 CssParser::getStyleSheet(int p0)
393 {
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;
478 }
480 /**
481 * import
482 * : IMPORT_SYM S*
483 * [STRING|URI] S* [ medium [ COMMA S* medium]* ]? ';' S*
484 * ;
485 */
486 int CssParser::getImport(int p0)
487 {
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;
529 }
531 /**
532 * media
533 * : MEDIA_SYM S* medium [ COMMA S* medium ]* LBRACE S* ruleset* '}' S*
534 * ;
535 */
536 int CssParser::getMedia(int p0)
537 {
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;
596 }
598 /**
599 * medium
600 * : IDENT S*
601 * ;
602 */
603 int CssParser::getMedium(int p0)
604 {
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;
617 }
619 /**
620 * page
621 * : PAGE_SYM S* pseudo_page? S*
622 * LBRACE S* declaration [ ';' S* declaration ]* '}' S*
623 * ;
624 */
625 int CssParser::getPage(int p0)
626 {
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;
693 }
695 /**
696 * pseudo_page
697 * : ':' IDENT
698 * ;
699 */
700 int CssParser::getPseudoPage(int p0)
701 {
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;
717 }
719 /**
720 * ruleset
721 * : selector [ COMMA S* selector ]*
722 * LBRACE S* declaration [ ';' S* declaration ]* '}' S*
723 * ;
724 */
725 int CssParser::getRuleSet(int p0)
726 {
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;
811 }
813 /**
814 * selector
815 * : simple_selector [ combinator simple_selector ]*
816 * ;
817 */
818 int CssParser::getSelector(int p0)
819 {
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;
877 }
879 /**
880 * simple_selector
881 * : element_name [ HASH | class | attrib | pseudo ]*
882 * | [ HASH | class | attrib | pseudo ]+
883 * ;
884 */
885 int CssParser::getSimpleSelector(int p0)
886 {
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 (isLetter(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 (isLetter(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 (isLetter(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;
1089 }
1091 /**
1092 * declaration
1093 * : property ':' S* expr prio?
1094 * | {empty}
1095 * ;
1096 */
1097 int CssParser::getDeclaration(int p0, CSSStyleDeclaration &declarationList)
1098 {
1099 int p = p0;
1101 //## PROPERTY
1102 p = skipwhite(p);
1103 XMLCh ch = get(p);
1104 if (!isLetter(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;
1150 }
1152 /**
1153 * prio
1154 * : IMPORTANT_SYM S*
1155 * ;
1156 */
1157 int CssParser::getPrio(int p0, DOMString &val)
1158 {
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;
1178 }
1180 /**
1181 * expr
1182 * : term [ operator term ]*
1183 * ;
1184 */
1185 int CssParser::getExpr(int p0)
1186 {
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;
1238 }
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 CssParser::getTerm(int p0)
1249 {
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 (isLetter(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 (isLetter(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;
1457 }
1459 /**
1460 * function
1461 * : FUNCTION S* expr ')' S*
1462 * ;
1463 */
1464 int CssParser::getFunction(int p0)
1465 {
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;
1508 }
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 CssParser::getHexColor(int p0)
1519 {
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;
1565 }
1569 /**
1570 *
1571 */
1572 bool CssParser::parse(const DOMString &str)
1573 {
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;
1602 }
1605 /**
1606 *
1607 */
1608 bool CssParser::parseFile(const DOMString &fileName)
1609 {
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;
1632 }
1640 } // namespace css
1641 } // namespace dom
1642 } // namespace w3c
1643 } // namespace org
1646 #ifdef TEST
1648 int main(int argc, char **argv)
1649 {
1650 org::w3c::dom::css::CssParser parser;
1651 char *fileName;
1652 fileName = "001.css";
1653 //fileName = "acid.css";
1654 //fileName = "base.css";
1655 //fileName = "inkscape.css";
1656 //fileName = "meyerweb.css";
1657 if (!parser.parseFile(fileName))
1658 {
1659 printf("Test failed\n");
1660 return 1;
1661 }
1662 return 0;
1663 }
1665 #endif /* TEST */
1667 //#########################################################################
1668 //# E N D O F F I L E
1669 //#########################################################################