/[suikacvs]/markup/html/whatpm/Whatpm/CSS/Parser.pm
Suika

Contents of /markup/html/whatpm/Whatpm/CSS/Parser.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.4 - (show annotations) (download)
Sun Dec 23 15:47:09 2007 UTC (18 years, 4 months ago) by wakaba
Branch: MAIN
Changes since 1.3: +61 -5 lines
++ whatpm/Whatpm/CSS/ChangeLog	23 Dec 2007 15:47:04 -0000
2007-12-24  Wakaba  <wakaba@suika.fam.cx>

	* Parser.pm: Support for |@namespace| rule.

	* SelectorsSerializer.pm: Support for |lookup_namespace_prefix|
	parameter is added.

1 package Whatpm::CSS::Parser;
2 use strict;
3 use Whatpm::CSS::Tokenizer qw(:token);
4 require Whatpm::CSS::SelectorsParser;
5
6 sub new ($) {
7 my $self = bless {onerror => sub { }, must_level => 'm',
8 unsupported_level => 'unsupported'}, shift;
9
10 return $self;
11 } # new
12
13 sub BEFORE_STATEMENT_STATE () { 0 }
14 sub BEFORE_DECLARATION_STATE () { 1 }
15 sub IGNORED_STATEMENT_STATE () { 2 }
16 sub IGNORED_DECLARATION_STATE () { 3 }
17
18 sub parse_char_string ($$) {
19 my $self = $_[0];
20
21 my $s = $_[1];
22 pos ($s) = 0;
23 my $line = 1;
24 my $column = 0;
25
26 my $_onerror = $self->{onerror};
27 my $onerror = sub {
28 $_onerror->(@_, line => $line, column => $column);
29 };
30
31 my $tt = Whatpm::CSS::Tokenizer->new;
32 $tt->{onerror} = $onerror;
33 $tt->{get_char} = sub {
34 if (pos $s < length $s) {
35 my $c = ord substr $s, pos ($s)++, 1;
36 if ($c == 0x000A) {
37 $line++;
38 $column = 0;
39 } elsif ($c == 0x000D) {
40 unless (substr ($s, pos ($s), 1) eq "\x0A") {
41 $line++;
42 $column = 0;
43 } else {
44 $column++;
45 }
46 } else {
47 $column++;
48 }
49 return $c;
50 } else {
51 return -1;
52 }
53 }; # $tt->{get_char}
54 $tt->init;
55
56 my $sp = Whatpm::CSS::SelectorsParser->new;
57 $sp->{onerror} = $onerror;
58 $sp->{must_level} = $self->{must_level};
59 $sp->{pseudo_element} = $self->{pseudo_element};
60 $sp->{pseudo_class} = $self->{pseudo_class};
61
62 my $nsmap = {};
63 $sp->{lookup_namespace_uri} = sub {
64 return $nsmap->{$_[0]}; # $_[0] is '' (default namespace) or prefix
65 }; # $sp->{lookup_namespace_uri}
66
67 ## TODO: Supported pseudo classes and elements...
68
69 require Message::DOM::CSSStyleSheet;
70 require Message::DOM::CSSRule;
71 require Message::DOM::CSSStyleDeclaration;
72
73 my $state = BEFORE_STATEMENT_STATE;
74 my $t = $tt->get_next_token;
75
76 my $open_rules = [[]];
77 my $current_rules = $open_rules->[-1];
78 my $current_decls;
79 my $closing_tokens = [];
80 my $charset_allowed = 1;
81 my $namespace_allowed = 1;
82
83 S: {
84 if ($state == BEFORE_STATEMENT_STATE) {
85 $t = $tt->get_next_token
86 while $t->{type} == S_TOKEN or
87 $t->{type} == CDO_TOKEN or
88 $t->{type} == CDC_TOKEN;
89
90 if ($t->{type} == ATKEYWORD_TOKEN) {
91 if ($t->{value} eq 'namespace') {
92 $t = $tt->get_next_token;
93 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
94
95 my $prefix;
96 if ($t->{type} == IDENT_TOKEN) {
97 $prefix = lc $t->{value};
98 ## TODO: Unicode lowercase
99
100 $t = $tt->get_next_token;
101 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
102 }
103
104 if ($t->{type} == STRING_TOKEN or $t->{type} == URI_TOKEN) {
105 my $uri = $t->{value};
106
107 $t = $tt->get_next_token;
108 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
109
110 ## ISSUE: On handling of empty namespace URI, Firefox 2 and
111 ## Opera 9 work differently (See SuikaWiki:namespace).
112 ## TODO: We need to check what we do once it is specced.
113
114 if ($t->{type} == SEMICOLON_TOKEN) {
115 if ($namespace_allowed) {
116 $nsmap->{defined $prefix ? $prefix : ''} = $uri;
117 push @$current_rules,
118 Message::DOM::CSSNamespaceRule->____new ($prefix, $uri);
119 undef $charset_allowed;
120 undef $namespace_allowed;
121 } else {
122 $onerror->(type => 'at:namespace:not allowed',
123 level => $self->{must_level},
124 token => $t);
125 }
126
127 $t = $tt->get_next_token;
128 ## Stay in the state.
129 redo S;
130 } else {
131 #
132 }
133 } else {
134 #
135 }
136
137 $onerror->(type => 'syntax error:at:namespace',
138 level => $self->{must_level},
139 token => $t);
140 #
141 } elsif ($t->{value} eq 'charset') {
142 $t = $tt->get_next_token;
143 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
144
145 if ($t->{type} == STRING_TOKEN) {
146 my $encoding = $t->{value};
147
148 $t = $tt->get_next_token;
149 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
150
151 if ($t->{type} == SEMICOLON_TOKEN) {
152 if ($charset_allowed) {
153 push @$current_rules,
154 Message::DOM::CSSCharsetRule->____new ($encoding);
155 undef $charset_allowed;
156 } else {
157 $onerror->(type => 'at:charset:not allowed',
158 level => $self->{must_level},
159 token => $t);
160 }
161
162 ## TODO: Detect the conformance errors for @charset...
163
164 $t = $tt->get_next_token;
165 ## Stay in the state.
166 redo S;
167 } else {
168 #
169 }
170 } else {
171 #
172 }
173
174 $onerror->(type => 'syntax error:at:charset',
175 level => $self->{must_level},
176 token => $t);
177 #
178 ## NOTE: When adding support for new at-rule, insert code
179 ## "undef $charset_allowed" and "undef $namespace_token" as
180 ## appropriate.
181 } else {
182 $onerror->(type => 'not supported:at:'.$t->{value},
183 level => $self->{unsupported_level},
184 token => $t);
185 }
186
187 $t = $tt->get_next_token;
188 $state = IGNORED_STATEMENT_STATE;
189 redo S;
190 } elsif (@$open_rules > 1 and $t->{type} == RBRACE_TOKEN) {
191 pop @$open_rules;
192 ## Stay in the state.
193 $t = $tt->get_next_token;
194 redo S;
195 } elsif ($t->{type} == EOF_TOKEN) {
196 if (@$open_rules > 1) {
197 $onerror->(type => 'syntax error:block not closed',
198 level => $self->{must_level},
199 token => $t);
200 }
201
202 last S;
203 } else {
204 undef $charset_allowed;
205 undef $namespace_allowed;
206
207 ($t, my $selectors) = $sp->_parse_selectors_with_tokenizer
208 ($tt, LBRACE_TOKEN, $t);
209
210 $t = $tt->get_next_token
211 while $t->{type} != LBRACE_TOKEN and $t->{type} != EOF_TOKEN;
212
213 if ($t->{type} == LBRACE_TOKEN) {
214 $current_decls = Message::DOM::CSSStyleDeclaration->____new;
215 my $rs = Message::DOM::CSSStyleRule->____new
216 ($selectors, $current_decls);
217 push @{$current_rules}, $rs if defined $selectors;
218
219 $state = BEFORE_DECLARATION_STATE;
220 $t = $tt->get_next_token;
221 redo S;
222 } else {
223 $onerror->(type => 'syntax error:after selectors',
224 level => $self->{must_level},
225 token => $t);
226
227 ## Stay in the state.
228 $t = $tt->get_next_token;
229 redo S;
230 }
231 }
232 } elsif ($state == BEFORE_DECLARATION_STATE) {
233 ## NOTE: DELIM? in declaration will be removed:
234 ## <http://csswg.inkedblade.net/spec/css2.1?s=declaration%20delim#issue-2>.
235
236 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
237 if ($t->{type} == IDENT_TOKEN) { # property
238 ## TODO: If supported, ...
239
240 $t = $tt->get_next_token;
241 #
242 } elsif ($t->{type} == RBRACE_TOKEN) {
243 $t = $tt->get_next_token;
244 $state = BEFORE_STATEMENT_STATE;
245 redo S;
246 } elsif ($t->{type} == EOF_TOKEN) {
247 $onerror->(type => 'syntax error:ruleset not closed',
248 level => $self->{must_level},
249 token => $t);
250 ## Reprocess.
251 $state = BEFORE_STATEMENT_STATE;
252 redo S;
253 }
254
255 #
256 $state = IGNORED_DECLARATION_STATE;
257 redo S;
258 } elsif ($state == IGNORED_STATEMENT_STATE or
259 $state == IGNORED_DECLARATION_STATE) {
260 if (@$closing_tokens) { ## Something is yet in opening state.
261 if ($t->{type} == EOF_TOKEN) {
262 @$closing_tokens = ();
263 ## Reprocess.
264 $state = $state == IGNORED_STATEMENT_STATE
265 ? BEFORE_STATEMENT_STATE : BEFORE_DECLARATION_STATE;
266 redo S;
267 } elsif ($t->{type} == $closing_tokens->[-1]) {
268 pop @$closing_tokens;
269 if (@$closing_tokens == 0 and
270 $t->{type} == RBRACE_TOKEN and
271 $state == IGNORED_STATEMENT_STATE) {
272 $t = $tt->get_next_token;
273 $state = BEFORE_STATEMENT_STATE;
274 redo S;
275 } else {
276 $t = $tt->get_next_token;
277 ## Stay in the state.
278 redo S;
279 }
280 } else {
281 #
282 }
283 } else {
284 if ($t->{type} == SEMICOLON_TOKEN) {
285 $t = $tt->get_next_token;
286 $state = $state == IGNORED_STATEMENT_STATE
287 ? BEFORE_STATEMENT_STATE : BEFORE_DECLARATION_STATE;
288 redo S;
289 } elsif ($state == IGNORED_DECLARATION_STATE and
290 $t->{type} == RBRACE_TOKEN) {
291 $t = $tt->get_next_token;
292 $state = BEFORE_STATEMENT_STATE;
293 redo S;
294 } elsif ($t->{type} == EOF_TOKEN) {
295 ## Reprocess.
296 $state = $state == IGNORED_STATEMENT_STATE
297 ? BEFORE_STATEMENT_STATE : BEFORE_DECLARATION_STATE;
298 redo S;
299 } else {
300 #
301 }
302 }
303
304 while (not {
305 EOF_TOKEN, 1,
306 RBRACE_TOKEN, 1,
307 RBRACKET_TOKEN, 1,
308 RPAREN_TOKEN, 1,
309 SEMICOLON_TOKEN, 1,
310 }->{$t->{type}}) {
311 if ($t->{type} == LBRACE_TOKEN) {
312 push @$closing_tokens, RBRACE_TOKEN;
313 } elsif ($t->{type} == LBRACKET_TOKEN) {
314 push @$closing_tokens, RBRACKET_TOKEN;
315 } elsif ($t->{type} == LPAREN_TOKEN or $t->{type} == FUNCTION_TOKEN) {
316 push @$closing_tokens, RPAREN_TOKEN;
317 }
318
319 $t = $tt->get_next_token;
320 }
321
322 #
323 ## Stay in the state.
324 redo S;
325 } else {
326 die "$0: parse_char_string: Unknown state: $state";
327 }
328 } # S
329
330 my $ss = Message::DOM::CSSStyleSheet->____new
331 (css_rules => $open_rules->[0],
332 ## TODO: href
333 ## TODO: owner_node
334 ## TODO: media
335 type => 'text/css', ## TODO: OK?
336 _parser => $self);
337 return $ss;
338 } # parse_char_string
339
340 1;
341 ## $Date: 2007/12/23 11:19:23 $

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24