OLD | NEW |
| (Empty) |
1 // This code was auto-generated, is not intended to be edited, and is subject to | |
2 // significant change. Please see the README file for more information. | |
3 library engine.html; | |
4 import 'dart:collection'; | |
5 import 'java_core.dart'; | |
6 import 'java_engine.dart'; | |
7 import 'source.dart'; | |
8 import 'element.dart' show HtmlElementImpl; | |
9 import 'engine.dart' show AnalysisEngine; | |
10 /** | |
11 * Instances of the class `Token` represent a token that was scanned from the in
put. Each | |
12 * token knows which token follows it, acting as the head of a linked list of to
kens. | |
13 * | |
14 * @coverage dart.engine.html | |
15 */ | |
16 class Token { | |
17 | |
18 /** | |
19 * The offset from the beginning of the file to the first character in the tok
en. | |
20 */ | |
21 int offset = 0; | |
22 | |
23 /** | |
24 * The previous token in the token stream. | |
25 */ | |
26 Token previous; | |
27 | |
28 /** | |
29 * The next token in the token stream. | |
30 */ | |
31 Token next; | |
32 | |
33 /** | |
34 * The type of the token. | |
35 */ | |
36 TokenType type; | |
37 | |
38 /** | |
39 * The lexeme represented by this token. | |
40 */ | |
41 String lexeme; | |
42 | |
43 /** | |
44 * Initialize a newly created token. | |
45 * | |
46 * @param type the token type (not `null`) | |
47 * @param offset the offset from the beginning of the file to the first charac
ter in the token | |
48 */ | |
49 Token.con1(TokenType type, int offset) : this.con2(type, offset, type.lexeme); | |
50 | |
51 /** | |
52 * Initialize a newly created token. | |
53 * | |
54 * @param type the token type (not `null`) | |
55 * @param offset the offset from the beginning of the file to the first charac
ter in the token | |
56 * @param value the lexeme represented by this token (not `null`) | |
57 */ | |
58 Token.con2(TokenType type, int offset, String value) { | |
59 this.type = type; | |
60 this.lexeme = StringUtilities.intern(value); | |
61 this.offset = offset; | |
62 } | |
63 | |
64 /** | |
65 * Return the offset from the beginning of the file to the character after las
t character of the | |
66 * token. | |
67 * | |
68 * @return the offset from the beginning of the file to the first character af
ter last character | |
69 * of the token | |
70 */ | |
71 int get end => offset + length; | |
72 | |
73 /** | |
74 * Return the number of characters in the node's source range. | |
75 * | |
76 * @return the number of characters in the node's source range | |
77 */ | |
78 int get length => lexeme.length; | |
79 | |
80 /** | |
81 * Return `true` if this token is a synthetic token. A synthetic token is a to
ken that was | |
82 * introduced by the parser in order to recover from an error in the code. Syn
thetic tokens always | |
83 * have a length of zero (`0`). | |
84 * | |
85 * @return `true` if this token is a synthetic token | |
86 */ | |
87 bool get isSynthetic => length == 0; | |
88 | |
89 /** | |
90 * Set the next token in the token stream to the given token. This has the sid
e-effect of setting | |
91 * this token to be the previous token for the given token. | |
92 * | |
93 * @param token the next token in the token stream | |
94 * @return the token that was passed in | |
95 */ | |
96 Token setNext(Token token) { | |
97 next = token; | |
98 token.previous = this; | |
99 return token; | |
100 } | |
101 String toString() => lexeme; | |
102 } | |
103 /** | |
104 * Instances of `HtmlParseResult` hold the result of parsing an HTML file. | |
105 * | |
106 * @coverage dart.engine.html | |
107 */ | |
108 class HtmlParseResult extends HtmlScanResult { | |
109 | |
110 /** | |
111 * The unit containing the parsed information (not `null`). | |
112 */ | |
113 HtmlUnit htmlUnit; | |
114 HtmlParseResult(int modificationTime, Token token, List<int> lineStarts, HtmlU
nit unit) : super(modificationTime, token, lineStarts) { | |
115 this.htmlUnit = unit; | |
116 } | |
117 } | |
118 /** | |
119 * Instances of the class `RecursiveXmlVisitor` implement an XML visitor that wi
ll recursively | |
120 * visit all of the nodes in an XML structure. For example, using an instance of
this class to visit | |
121 * a [XmlTagNode] will also cause all of the contained [XmlAttributeNode]s and | |
122 * [XmlTagNode]s to be visited. | |
123 * | |
124 * Subclasses that override a visit method must either invoke the overridden vis
it method or must | |
125 * explicitly ask the visited node to visit its children. Failure to do so will
cause the children | |
126 * of the visited node to not be visited. | |
127 * | |
128 * @coverage dart.engine.html | |
129 */ | |
130 class RecursiveXmlVisitor<R> implements XmlVisitor<R> { | |
131 R visitHtmlUnit(HtmlUnit node) { | |
132 node.visitChildren(this); | |
133 return null; | |
134 } | |
135 R visitXmlAttributeNode(XmlAttributeNode node) { | |
136 node.visitChildren(this); | |
137 return null; | |
138 } | |
139 R visitXmlTagNode(XmlTagNode node) { | |
140 node.visitChildren(this); | |
141 return null; | |
142 } | |
143 } | |
144 /** | |
145 * The abstract class `XmlNode` defines behavior common to all XML/HTML nodes. | |
146 * | |
147 * @coverage dart.engine.html | |
148 */ | |
149 abstract class XmlNode { | |
150 | |
151 /** | |
152 * The parent of the node, or `null` if the node is the root of an AST structu
re. | |
153 */ | |
154 XmlNode _parent; | |
155 | |
156 /** | |
157 * Use the given visitor to visit this node. | |
158 * | |
159 * @param visitor the visitor that will visit this node | |
160 * @return the value returned by the visitor as a result of visiting this node | |
161 */ | |
162 accept(XmlVisitor visitor); | |
163 | |
164 /** | |
165 * Return the first token included in this node's source range. | |
166 * | |
167 * @return the first token or `null` if none | |
168 */ | |
169 Token get beginToken; | |
170 | |
171 /** | |
172 * Return the offset of the character immediately following the last character
of this node's | |
173 * source range. This is equivalent to `node.getOffset() + node.getLength()`.
For an html | |
174 * unit this will be equal to the length of the unit's source. | |
175 * | |
176 * @return the offset of the character just past the node's source range | |
177 */ | |
178 int get end => offset + length; | |
179 | |
180 /** | |
181 * Return the last token included in this node's source range. | |
182 * | |
183 * @return the last token or `null` if none | |
184 */ | |
185 Token get endToken; | |
186 | |
187 /** | |
188 * Return the number of characters in the node's source range. | |
189 * | |
190 * @return the number of characters in the node's source range | |
191 */ | |
192 int get length { | |
193 Token beginToken = this.beginToken; | |
194 Token endToken = this.endToken; | |
195 if (beginToken == null || endToken == null) { | |
196 return -1; | |
197 } | |
198 return endToken.offset + endToken.length - beginToken.offset; | |
199 } | |
200 | |
201 /** | |
202 * Return the offset from the beginning of the file to the first character in
the node's source | |
203 * range. | |
204 * | |
205 * @return the offset from the beginning of the file to the first character in
the node's source | |
206 * range | |
207 */ | |
208 int get offset { | |
209 Token beginToken = this.beginToken; | |
210 if (beginToken == null) { | |
211 return -1; | |
212 } | |
213 return this.beginToken.offset; | |
214 } | |
215 | |
216 /** | |
217 * Return this node's parent node, or `null` if this node is the root of an AS
T structure. | |
218 * | |
219 * Note that the relationship between an AST node and its parent node may chan
ge over the lifetime | |
220 * of a node. | |
221 * | |
222 * @return the parent of this node, or `null` if none | |
223 */ | |
224 XmlNode get parent => _parent; | |
225 String toString() { | |
226 PrintStringWriter writer = new PrintStringWriter(); | |
227 accept(new ToSourceVisitor(writer)); | |
228 return writer.toString(); | |
229 } | |
230 | |
231 /** | |
232 * Use the given visitor to visit all of the children of this node. The childr
en will be visited | |
233 * in source order. | |
234 * | |
235 * @param visitor the visitor that will be used to visit the children of this
node | |
236 */ | |
237 void visitChildren(XmlVisitor visitor); | |
238 | |
239 /** | |
240 * Make this node the parent of the given child nodes. | |
241 * | |
242 * @param children the nodes that will become the children of this node | |
243 * @return the nodes that were made children of this node | |
244 */ | |
245 List becomeParentOf(List children) { | |
246 if (children != null) { | |
247 for (JavaIterator iter = new JavaIterator(children); iter.hasNext;) { | |
248 XmlNode node = iter.next(); | |
249 node.parent = this; | |
250 } | |
251 return new List.from(children); | |
252 } | |
253 return children; | |
254 } | |
255 | |
256 /** | |
257 * Make this node the parent of the given child node. | |
258 * | |
259 * @param child the node that will become a child of this node | |
260 * @return the node that was made a child of this node | |
261 */ | |
262 XmlNode becomeParentOf2(XmlNode child) { | |
263 if (child != null) { | |
264 XmlNode node = child; | |
265 node.parent = this; | |
266 } | |
267 return child; | |
268 } | |
269 | |
270 /** | |
271 * This method exists for debugging purposes only. | |
272 */ | |
273 void appendIdentifier(JavaStringBuilder builder, XmlNode node) { | |
274 if (node is XmlTagNode) { | |
275 builder.append(((node as XmlTagNode)).tag.lexeme); | |
276 } else if (node is XmlAttributeNode) { | |
277 builder.append(((node as XmlAttributeNode)).name.lexeme); | |
278 } else { | |
279 builder.append("htmlUnit"); | |
280 } | |
281 } | |
282 | |
283 /** | |
284 * This method exists for debugging purposes only. | |
285 */ | |
286 String buildRecursiveStructureMessage(XmlNode newParent) { | |
287 JavaStringBuilder builder = new JavaStringBuilder(); | |
288 builder.append("Attempt to create recursive structure: "); | |
289 XmlNode current = newParent; | |
290 while (current != null) { | |
291 if (current != newParent) { | |
292 builder.append(" -> "); | |
293 } | |
294 if (identical(current, this)) { | |
295 builder.appendChar(0x2A); | |
296 appendIdentifier(builder, current); | |
297 builder.appendChar(0x2A); | |
298 } else { | |
299 appendIdentifier(builder, current); | |
300 } | |
301 current = current.parent; | |
302 } | |
303 return builder.toString(); | |
304 } | |
305 | |
306 /** | |
307 * Set the parent of this node to the given node. | |
308 * | |
309 * @param newParent the node that is to be made the parent of this node | |
310 */ | |
311 void set parent(XmlNode newParent) { | |
312 XmlNode current = newParent; | |
313 while (current != null) { | |
314 if (identical(current, this)) { | |
315 AnalysisEngine.instance.logger.logError3(new IllegalArgumentException(bu
ildRecursiveStructureMessage(newParent))); | |
316 return; | |
317 } | |
318 current = current.parent; | |
319 } | |
320 _parent = newParent; | |
321 } | |
322 } | |
323 /** | |
324 * Instances of the class `SimpleXmlVisitor` implement an AST visitor that will
do nothing | |
325 * when visiting an AST node. It is intended to be a superclass for classes that
use the visitor | |
326 * pattern primarily as a dispatch mechanism (and hence don't need to recursivel
y visit a whole | |
327 * structure) and that only need to visit a small number of node types. | |
328 */ | |
329 class SimpleXmlVisitor<R> implements XmlVisitor<R> { | |
330 R visitHtmlUnit(HtmlUnit htmlUnit) => null; | |
331 R visitXmlAttributeNode(XmlAttributeNode xmlAttributeNode) => null; | |
332 R visitXmlTagNode(XmlTagNode xmlTagNode) => null; | |
333 } | |
334 /** | |
335 * The abstract class `AbstractScanner` implements a scanner for HTML code. Subc
lasses are | |
336 * required to implement the interface used to access the characters being scann
ed. | |
337 * | |
338 * @coverage dart.engine.html | |
339 */ | |
340 abstract class AbstractScanner { | |
341 static List<String> _NO_PASS_THROUGH_ELEMENTS = <String> []; | |
342 | |
343 /** | |
344 * The source being scanned. | |
345 */ | |
346 Source source; | |
347 | |
348 /** | |
349 * The token pointing to the head of the linked list of tokens. | |
350 */ | |
351 Token _tokens; | |
352 | |
353 /** | |
354 * The last token that was scanned. | |
355 */ | |
356 Token _tail; | |
357 | |
358 /** | |
359 * A list containing the offsets of the first character of each line in the so
urce code. | |
360 */ | |
361 List<int> _lineStarts = new List<int>(); | |
362 | |
363 /** | |
364 * An array of element tags for which the content between tags should be consi
der a single token. | |
365 */ | |
366 List<String> _passThroughElements = _NO_PASS_THROUGH_ELEMENTS; | |
367 | |
368 /** | |
369 * Initialize a newly created scanner. | |
370 * | |
371 * @param source the source being scanned | |
372 */ | |
373 AbstractScanner(Source source) { | |
374 this.source = source; | |
375 _tokens = new Token.con1(TokenType.EOF, -1); | |
376 _tokens.setNext(_tokens); | |
377 _tail = _tokens; | |
378 recordStartOfLine(); | |
379 } | |
380 | |
381 /** | |
382 * Return an array containing the offsets of the first character of each line
in the source code. | |
383 * | |
384 * @return an array containing the offsets of the first character of each line
in the source code | |
385 */ | |
386 List<int> get lineStarts => _lineStarts; | |
387 | |
388 /** | |
389 * Return the current offset relative to the beginning of the file. Return the
initial offset if | |
390 * the scanner has not yet scanned the source code, and one (1) past the end o
f the source code if | |
391 * the source code has been scanned. | |
392 * | |
393 * @return the current offset of the scanner in the source | |
394 */ | |
395 int get offset; | |
396 | |
397 /** | |
398 * Set array of element tags for which the content between tags should be cons
ider a single token. | |
399 */ | |
400 void set passThroughElements(List<String> passThroughElements) { | |
401 this._passThroughElements = passThroughElements != null ? passThroughElement
s : _NO_PASS_THROUGH_ELEMENTS; | |
402 } | |
403 | |
404 /** | |
405 * Scan the source code to produce a list of tokens representing the source. | |
406 * | |
407 * @return the first token in the list of tokens that were produced | |
408 */ | |
409 Token tokenize() { | |
410 scan(); | |
411 appendEofToken(); | |
412 return firstToken(); | |
413 } | |
414 | |
415 /** | |
416 * Advance the current position and return the character at the new current po
sition. | |
417 * | |
418 * @return the character at the new current position | |
419 */ | |
420 int advance(); | |
421 | |
422 /** | |
423 * Return the substring of the source code between the start offset and the mo
dified current | |
424 * position. The current position is modified by adding the end delta. | |
425 * | |
426 * @param start the offset to the beginning of the string, relative to the sta
rt of the file | |
427 * @param endDelta the number of character after the current location to be in
cluded in the | |
428 * string, or the number of characters before the current location to
be excluded if the | |
429 * offset is negative | |
430 * @return the specified substring of the source code | |
431 */ | |
432 String getString(int start, int endDelta); | |
433 | |
434 /** | |
435 * Return the character at the current position without changing the current p
osition. | |
436 * | |
437 * @return the character at the current position | |
438 */ | |
439 int peek(); | |
440 | |
441 /** | |
442 * Record the fact that we are at the beginning of a new line in the source. | |
443 */ | |
444 void recordStartOfLine() { | |
445 _lineStarts.add(offset); | |
446 } | |
447 void appendEofToken() { | |
448 Token eofToken = new Token.con1(TokenType.EOF, offset); | |
449 eofToken.setNext(eofToken); | |
450 _tail = _tail.setNext(eofToken); | |
451 } | |
452 Token emit(Token token) { | |
453 _tail.setNext(token); | |
454 _tail = token; | |
455 return token; | |
456 } | |
457 Token emit2(TokenType type, int start) => emit(new Token.con1(type, start)); | |
458 Token emit3(TokenType type, int start, int count) => emit(new Token.con2(type,
start, getString(start, count))); | |
459 Token firstToken() => _tokens.next; | |
460 int recordStartOfLineAndAdvance(int c) { | |
461 if (c == 0xD) { | |
462 c = advance(); | |
463 if (c == 0xA) { | |
464 c = advance(); | |
465 } | |
466 recordStartOfLine(); | |
467 } else if (c == 0xA) { | |
468 c = advance(); | |
469 recordStartOfLine(); | |
470 } else { | |
471 c = advance(); | |
472 } | |
473 return c; | |
474 } | |
475 void scan() { | |
476 bool inBrackets = false; | |
477 String endPassThrough = null; | |
478 int c = advance(); | |
479 while (c >= 0) { | |
480 int start = offset; | |
481 if (c == 0x3C) { | |
482 c = advance(); | |
483 if (c == 0x21) { | |
484 c = advance(); | |
485 if (c == 0x2D && peek() == 0x2D) { | |
486 c = advance(); | |
487 int dashCount = 1; | |
488 while (c >= 0) { | |
489 if (c == 0x2D) { | |
490 dashCount++; | |
491 } else if (c == 0x3E && dashCount >= 2) { | |
492 c = advance(); | |
493 break; | |
494 } else { | |
495 dashCount = 0; | |
496 } | |
497 c = recordStartOfLineAndAdvance(c); | |
498 } | |
499 emit3(TokenType.COMMENT, start, -1); | |
500 if (_tail.length < 7) { | |
501 } | |
502 } else { | |
503 while (c >= 0) { | |
504 if (c == 0x3E) { | |
505 c = advance(); | |
506 break; | |
507 } | |
508 c = recordStartOfLineAndAdvance(c); | |
509 } | |
510 emit3(TokenType.DECLARATION, start, -1); | |
511 if (!_tail.lexeme.endsWith(">")) { | |
512 } | |
513 } | |
514 } else if (c == 0x3F) { | |
515 while (c >= 0) { | |
516 if (c == 0x3F) { | |
517 c = advance(); | |
518 if (c == 0x3E) { | |
519 c = advance(); | |
520 break; | |
521 } | |
522 } else { | |
523 c = recordStartOfLineAndAdvance(c); | |
524 } | |
525 } | |
526 emit3(TokenType.DIRECTIVE, start, -1); | |
527 if (_tail.length < 4) { | |
528 } | |
529 } else if (c == 0x2F) { | |
530 emit2(TokenType.LT_SLASH, start); | |
531 inBrackets = true; | |
532 c = advance(); | |
533 } else { | |
534 inBrackets = true; | |
535 emit2(TokenType.LT, start); | |
536 while (Character.isWhitespace(c)) { | |
537 c = recordStartOfLineAndAdvance(c); | |
538 } | |
539 if (Character.isLetterOrDigit(c)) { | |
540 int tagStart = offset; | |
541 c = advance(); | |
542 while (Character.isLetterOrDigit(c) || c == 0x2D || c == 0x5F) { | |
543 c = advance(); | |
544 } | |
545 emit3(TokenType.TAG, tagStart, -1); | |
546 String tag = _tail.lexeme; | |
547 for (String str in _passThroughElements) { | |
548 if (str == tag) { | |
549 endPassThrough = "</${str}>"; | |
550 break; | |
551 } | |
552 } | |
553 } | |
554 } | |
555 } else if (c == 0x3E) { | |
556 emit2(TokenType.GT, start); | |
557 inBrackets = false; | |
558 c = advance(); | |
559 if (endPassThrough != null) { | |
560 bool endFound = false; | |
561 int len = endPassThrough.length; | |
562 int firstC = endPassThrough.codeUnitAt(0); | |
563 int index = 0; | |
564 int nextC = firstC; | |
565 while (c >= 0) { | |
566 if (c == nextC) { | |
567 index++; | |
568 if (index == len) { | |
569 endFound = true; | |
570 break; | |
571 } | |
572 nextC = endPassThrough.codeUnitAt(index); | |
573 } else if (c == firstC) { | |
574 index = 1; | |
575 nextC = endPassThrough.codeUnitAt(1); | |
576 } else { | |
577 index = 0; | |
578 nextC = firstC; | |
579 } | |
580 c = recordStartOfLineAndAdvance(c); | |
581 } | |
582 if (start + 1 < offset) { | |
583 if (endFound) { | |
584 emit3(TokenType.TEXT, start + 1, -len); | |
585 emit2(TokenType.LT_SLASH, offset - len + 1); | |
586 emit3(TokenType.TAG, offset - len + 3, -1); | |
587 } else { | |
588 emit3(TokenType.TEXT, start + 1, -1); | |
589 } | |
590 } | |
591 endPassThrough = null; | |
592 } | |
593 } else if (c == 0x2F && peek() == 0x3E) { | |
594 advance(); | |
595 emit2(TokenType.SLASH_GT, start); | |
596 inBrackets = false; | |
597 c = advance(); | |
598 } else if (!inBrackets) { | |
599 c = recordStartOfLineAndAdvance(c); | |
600 while (c != 0x3C && c >= 0) { | |
601 c = recordStartOfLineAndAdvance(c); | |
602 } | |
603 emit3(TokenType.TEXT, start, -1); | |
604 } else if (c == 0x22 || c == 0x27) { | |
605 int endQuote = c; | |
606 c = advance(); | |
607 while (c >= 0) { | |
608 if (c == endQuote) { | |
609 c = advance(); | |
610 break; | |
611 } | |
612 c = recordStartOfLineAndAdvance(c); | |
613 } | |
614 emit3(TokenType.STRING, start, -1); | |
615 } else if (c == 0x3D) { | |
616 emit2(TokenType.EQ, start); | |
617 c = advance(); | |
618 } else if (Character.isWhitespace(c)) { | |
619 do { | |
620 c = recordStartOfLineAndAdvance(c); | |
621 } while (Character.isWhitespace(c)); | |
622 } else if (Character.isLetterOrDigit(c)) { | |
623 c = advance(); | |
624 while (Character.isLetterOrDigit(c) || c == 0x2D || c == 0x5F) { | |
625 c = advance(); | |
626 } | |
627 emit3(TokenType.TAG, start, -1); | |
628 } else { | |
629 emit3(TokenType.TEXT, start, 0); | |
630 c = advance(); | |
631 } | |
632 } | |
633 } | |
634 } | |
635 /** | |
636 * Instances of `HtmlScanResult` hold the result of scanning an HTML file. | |
637 * | |
638 * @coverage dart.engine.html | |
639 */ | |
640 class HtmlScanResult { | |
641 | |
642 /** | |
643 * The time at which the contents of the source were last set. | |
644 */ | |
645 int modificationTime = 0; | |
646 | |
647 /** | |
648 * The first token in the token stream (not `null`). | |
649 */ | |
650 Token token; | |
651 | |
652 /** | |
653 * The line start information that was produced. | |
654 */ | |
655 List<int> lineStarts; | |
656 HtmlScanResult(int modificationTime, Token token, List<int> lineStarts) { | |
657 this.modificationTime = modificationTime; | |
658 this.token = token; | |
659 this.lineStarts = lineStarts; | |
660 } | |
661 } | |
662 /** | |
663 * Instances of the class `StringScanner` implement a scanner that reads from a
string. The | |
664 * scanning logic is in the superclass. | |
665 * | |
666 * @coverage dart.engine.html | |
667 */ | |
668 class StringScanner extends AbstractScanner { | |
669 | |
670 /** | |
671 * The string from which characters will be read. | |
672 */ | |
673 String _string; | |
674 | |
675 /** | |
676 * The number of characters in the string. | |
677 */ | |
678 int _stringLength = 0; | |
679 | |
680 /** | |
681 * The index, relative to the string, of the last character that was read. | |
682 */ | |
683 int _charOffset = 0; | |
684 | |
685 /** | |
686 * Initialize a newly created scanner to scan the characters in the given stri
ng. | |
687 * | |
688 * @param source the source being scanned | |
689 * @param string the string from which characters will be read | |
690 */ | |
691 StringScanner(Source source, String string) : super(source) { | |
692 this._string = string; | |
693 this._stringLength = string.length; | |
694 this._charOffset = -1; | |
695 } | |
696 int get offset => _charOffset; | |
697 void set offset(int offset) { | |
698 _charOffset = offset; | |
699 } | |
700 int advance() { | |
701 if (++_charOffset < _stringLength) { | |
702 return _string.codeUnitAt(_charOffset); | |
703 } | |
704 _charOffset = _stringLength; | |
705 return -1; | |
706 } | |
707 String getString(int start, int endDelta) => _string.substring(start, _charOff
set + 1 + endDelta); | |
708 int peek() { | |
709 if (_charOffset + 1 < _stringLength) { | |
710 return _string.codeUnitAt(_charOffset + 1); | |
711 } | |
712 return -1; | |
713 } | |
714 } | |
715 /** | |
716 * Instances of the class `CharBufferScanner` implement a scanner that reads fro
m a character | |
717 * buffer. The scanning logic is in the superclass. | |
718 * | |
719 * @coverage dart.engine.html | |
720 */ | |
721 class CharBufferScanner extends AbstractScanner { | |
722 | |
723 /** | |
724 * The buffer from which characters will be read. | |
725 */ | |
726 CharSequence _buffer; | |
727 | |
728 /** | |
729 * The number of characters in the buffer. | |
730 */ | |
731 int _bufferLength = 0; | |
732 | |
733 /** | |
734 * The index of the last character that was read. | |
735 */ | |
736 int _charOffset = 0; | |
737 | |
738 /** | |
739 * Initialize a newly created scanner to scan the characters in the given char
acter buffer. | |
740 * | |
741 * @param source the source being scanned | |
742 * @param buffer the buffer from which characters will be read | |
743 */ | |
744 CharBufferScanner(Source source, CharSequence buffer) : super(source) { | |
745 this._buffer = buffer; | |
746 this._bufferLength = buffer.length(); | |
747 this._charOffset = -1; | |
748 } | |
749 int get offset => _charOffset; | |
750 int advance() { | |
751 if (++_charOffset < _bufferLength) { | |
752 return _buffer.charAt(_charOffset); | |
753 } | |
754 _charOffset = _bufferLength; | |
755 return -1; | |
756 } | |
757 String getString(int start, int endDelta) => _buffer.subSequence(start, _charO
ffset + 1 + endDelta).toString(); | |
758 int peek() { | |
759 if (_charOffset + 1 < _bufferLength) { | |
760 return _buffer.charAt(_charOffset + 1); | |
761 } | |
762 return -1; | |
763 } | |
764 } | |
765 /** | |
766 * Instances of the class `ToSourceVisitor` write a source representation of a v
isited XML | |
767 * node (and all of it's children) to a writer. | |
768 * | |
769 * @coverage dart.engine.html | |
770 */ | |
771 class ToSourceVisitor implements XmlVisitor<Object> { | |
772 | |
773 /** | |
774 * The writer to which the source is to be written. | |
775 */ | |
776 PrintWriter _writer; | |
777 | |
778 /** | |
779 * Initialize a newly created visitor to write source code representing the vi
sited nodes to the | |
780 * given writer. | |
781 * | |
782 * @param writer the writer to which the source is to be written | |
783 */ | |
784 ToSourceVisitor(PrintWriter writer) { | |
785 this._writer = writer; | |
786 } | |
787 Object visitHtmlUnit(HtmlUnit node) { | |
788 for (XmlTagNode child in node.tagNodes) { | |
789 visit(child); | |
790 } | |
791 return null; | |
792 } | |
793 Object visitXmlAttributeNode(XmlAttributeNode node) { | |
794 String name = node.name.lexeme; | |
795 Token value = node.value; | |
796 if (name.length == 0) { | |
797 _writer.print("__"); | |
798 } else { | |
799 _writer.print(name); | |
800 } | |
801 _writer.print("="); | |
802 if (value == null) { | |
803 _writer.print("__"); | |
804 } else { | |
805 _writer.print(value.lexeme); | |
806 } | |
807 return null; | |
808 } | |
809 Object visitXmlTagNode(XmlTagNode node) { | |
810 _writer.print("<"); | |
811 String tagName = node.tag.lexeme; | |
812 _writer.print(tagName); | |
813 for (XmlAttributeNode attribute in node.attributes) { | |
814 _writer.print(" "); | |
815 visit(attribute); | |
816 } | |
817 _writer.print(node.attributeEnd.lexeme); | |
818 if (node.closingTag != null) { | |
819 for (XmlTagNode child in node.tagNodes) { | |
820 visit(child); | |
821 } | |
822 _writer.print("</"); | |
823 _writer.print(tagName); | |
824 _writer.print(">"); | |
825 } | |
826 return null; | |
827 } | |
828 | |
829 /** | |
830 * Safely visit the given node. | |
831 * | |
832 * @param node the node to be visited | |
833 */ | |
834 void visit(XmlNode node) { | |
835 if (node != null) { | |
836 node.accept(this); | |
837 } | |
838 } | |
839 } | |
840 /** | |
841 * The enumeration `TokenType` defines the types of tokens that can be returned
by the | |
842 * scanner. | |
843 * | |
844 * @coverage dart.engine.html | |
845 */ | |
846 class TokenType extends Enum<TokenType> { | |
847 | |
848 /** | |
849 * The type of the token that marks the end of the input. | |
850 */ | |
851 static final TokenType EOF = new TokenType_EOF('EOF', 0, ""); | |
852 static final TokenType EQ = new TokenType('EQ', 1, "="); | |
853 static final TokenType GT = new TokenType('GT', 2, ">"); | |
854 static final TokenType LT_SLASH = new TokenType('LT_SLASH', 3, "</"); | |
855 static final TokenType LT = new TokenType('LT', 4, "<"); | |
856 static final TokenType SLASH_GT = new TokenType('SLASH_GT', 5, "/>"); | |
857 static final TokenType COMMENT = new TokenType('COMMENT', 6, null); | |
858 static final TokenType DECLARATION = new TokenType('DECLARATION', 7, null); | |
859 static final TokenType DIRECTIVE = new TokenType('DIRECTIVE', 8, null); | |
860 static final TokenType STRING = new TokenType('STRING', 9, null); | |
861 static final TokenType TAG = new TokenType('TAG', 10, null); | |
862 static final TokenType TEXT = new TokenType('TEXT', 11, null); | |
863 static final List<TokenType> values = [ | |
864 EOF, | |
865 EQ, | |
866 GT, | |
867 LT_SLASH, | |
868 LT, | |
869 SLASH_GT, | |
870 COMMENT, | |
871 DECLARATION, | |
872 DIRECTIVE, | |
873 STRING, | |
874 TAG, | |
875 TEXT]; | |
876 | |
877 /** | |
878 * The lexeme that defines this type of token, or `null` if there is more than
one possible | |
879 * lexeme for this type of token. | |
880 */ | |
881 String lexeme; | |
882 TokenType(String name, int ordinal, String lexeme) : super(name, ordinal) { | |
883 this.lexeme = lexeme; | |
884 } | |
885 } | |
886 class TokenType_EOF extends TokenType { | |
887 TokenType_EOF(String name, int ordinal, String arg0) : super(name, ordinal, ar
g0); | |
888 String toString() => "-eof-"; | |
889 } | |
890 /** | |
891 * Instances of `XmlAttributeNode` represent name/value pairs owned by an [XmlTa
gNode]. | |
892 * | |
893 * @coverage dart.engine.html | |
894 */ | |
895 class XmlAttributeNode extends XmlNode { | |
896 Token name; | |
897 Token equals; | |
898 Token value; | |
899 | |
900 /** | |
901 * Construct a new instance representing an XML attribute. | |
902 * | |
903 * @param name the name token (not `null`). This may be a zero length token if
the attribute | |
904 * is badly formed. | |
905 * @param equals the equals sign or `null` if none | |
906 * @param value the value token (not `null`) | |
907 */ | |
908 XmlAttributeNode(Token name, Token equals, Token value) { | |
909 this.name = name; | |
910 this.equals = equals; | |
911 this.value = value; | |
912 } | |
913 accept(XmlVisitor visitor) => visitor.visitXmlAttributeNode(this); | |
914 Token get beginToken => name; | |
915 Token get endToken => value; | |
916 | |
917 /** | |
918 * Answer the lexeme for the value token without the leading and trailing quot
es. | |
919 * | |
920 * @return the text or `null` if the value is not specified | |
921 */ | |
922 String get text { | |
923 if (value == null) { | |
924 return null; | |
925 } | |
926 String text = value.lexeme; | |
927 int len = text.length; | |
928 if (len > 0) { | |
929 if (text.codeUnitAt(0) == 0x22) { | |
930 if (len > 1 && text.codeUnitAt(len - 1) == 0x22) { | |
931 return text.substring(1, len - 1); | |
932 } else { | |
933 return text.substring(1); | |
934 } | |
935 } else if (text.codeUnitAt(0) == 0x27) { | |
936 if (len > 1 && text.codeUnitAt(len - 1) == 0x27) { | |
937 return text.substring(1, len - 1); | |
938 } else { | |
939 return text.substring(1); | |
940 } | |
941 } | |
942 } | |
943 return text; | |
944 } | |
945 void visitChildren(XmlVisitor visitor) { | |
946 } | |
947 } | |
948 /** | |
949 * The interface `XmlVisitor` defines the behavior of objects that can be used t
o visit an | |
950 * [XmlNode] structure. | |
951 * | |
952 * @coverage dart.engine.html | |
953 */ | |
954 abstract class XmlVisitor<R> { | |
955 R visitHtmlUnit(HtmlUnit htmlUnit); | |
956 R visitXmlAttributeNode(XmlAttributeNode xmlAttributeNode); | |
957 R visitXmlTagNode(XmlTagNode xmlTagNode); | |
958 } | |
959 /** | |
960 * Instances of `HtmlScanner` receive and scan HTML content from a [Source].<br/
> | |
961 * For example, the following code scans HTML source and returns the result: | |
962 * | |
963 * <pre> | |
964 * HtmlScanner scanner = new HtmlScanner(source); | |
965 * source.getContents(scanner); | |
966 * return scanner.getResult(); | |
967 * </pre> | |
968 * | |
969 * @coverage dart.engine.html | |
970 */ | |
971 class HtmlScanner implements Source_ContentReceiver { | |
972 List<String> _SCRIPT_TAG = <String> ["script"]; | |
973 | |
974 /** | |
975 * The source being scanned (not `null`) | |
976 */ | |
977 Source _source; | |
978 | |
979 /** | |
980 * The time at which the contents of the source were last set. | |
981 */ | |
982 int _modificationTime = 0; | |
983 | |
984 /** | |
985 * The scanner used to scan the source | |
986 */ | |
987 AbstractScanner _scanner; | |
988 | |
989 /** | |
990 * The first token in the token stream. | |
991 */ | |
992 Token _token; | |
993 | |
994 /** | |
995 * Construct a new instance to scan the specified source. | |
996 * | |
997 * @param source the source to be scanned (not `null`) | |
998 */ | |
999 HtmlScanner(Source source) { | |
1000 this._source = source; | |
1001 } | |
1002 void accept(CharBuffer contents, int modificationTime) { | |
1003 this._modificationTime = modificationTime; | |
1004 _scanner = new CharBufferScanner(_source, contents); | |
1005 _scanner.passThroughElements = _SCRIPT_TAG; | |
1006 _token = _scanner.tokenize(); | |
1007 } | |
1008 void accept2(String contents, int modificationTime) { | |
1009 this._modificationTime = modificationTime; | |
1010 _scanner = new StringScanner(_source, contents); | |
1011 _scanner.passThroughElements = _SCRIPT_TAG; | |
1012 _token = _scanner.tokenize(); | |
1013 } | |
1014 | |
1015 /** | |
1016 * Answer the result of scanning the source | |
1017 * | |
1018 * @return the result (not `null`) | |
1019 */ | |
1020 HtmlScanResult get result => new HtmlScanResult(_modificationTime, _token, _sc
anner.lineStarts); | |
1021 } | |
1022 /** | |
1023 * Instances of the class `XmlParser` are used to parse tokens into a AST struct
ure comprised | |
1024 * of [XmlNode]s. | |
1025 * | |
1026 * @coverage dart.engine.html | |
1027 */ | |
1028 class XmlParser { | |
1029 | |
1030 /** | |
1031 * The source being parsed. | |
1032 */ | |
1033 Source source; | |
1034 | |
1035 /** | |
1036 * The next token to be parsed. | |
1037 */ | |
1038 Token currentToken; | |
1039 | |
1040 /** | |
1041 * Construct a parser for the specified source. | |
1042 * | |
1043 * @param source the source being parsed | |
1044 */ | |
1045 XmlParser(Source source) { | |
1046 this.source = source; | |
1047 } | |
1048 | |
1049 /** | |
1050 * Answer `true` if the specified tag is self closing and thus should never ha
ve content or | |
1051 * child tag nodes. | |
1052 * | |
1053 * @param tag the tag (not `null`) | |
1054 * @return `true` if self closing | |
1055 */ | |
1056 bool isSelfClosing(Token tag) => false; | |
1057 | |
1058 /** | |
1059 * Parse the entire token stream and in the process, advance the current token
to the end of the | |
1060 * token stream. | |
1061 * | |
1062 * @return the list of tag nodes found (not `null`, contains no `null`) | |
1063 */ | |
1064 List<XmlTagNode> parseTopTagNodes(Token firstToken) { | |
1065 currentToken = firstToken; | |
1066 List<XmlTagNode> tagNodes = new List<XmlTagNode>(); | |
1067 while (true) { | |
1068 while (true) { | |
1069 if (currentToken.type == TokenType.LT) { | |
1070 tagNodes.add(parseTagNode()); | |
1071 } else if (currentToken.type == TokenType.DECLARATION || currentToken.ty
pe == TokenType.DIRECTIVE || currentToken.type == TokenType.COMMENT) { | |
1072 currentToken = currentToken.next; | |
1073 } else if (currentToken.type == TokenType.EOF) { | |
1074 return tagNodes; | |
1075 } else { | |
1076 reportUnexpectedToken(); | |
1077 currentToken = currentToken.next; | |
1078 } | |
1079 break; | |
1080 } | |
1081 } | |
1082 } | |
1083 | |
1084 /** | |
1085 * Insert a synthetic token of the specified type before the current token | |
1086 * | |
1087 * @param type the type of token to be inserted (not `null`) | |
1088 * @return the synthetic token that was inserted (not `null`) | |
1089 */ | |
1090 Token insertSyntheticToken(TokenType type) { | |
1091 Token token = new Token.con2(type, currentToken.offset, ""); | |
1092 currentToken.previous.setNext(token); | |
1093 token.setNext(currentToken); | |
1094 return token; | |
1095 } | |
1096 | |
1097 /** | |
1098 * Parse the token stream for an attribute. This method advances the current t
oken over the | |
1099 * attribute, but should not be called if the [currentToken] is not [TokenType
#TAG]. | |
1100 * | |
1101 * @return the attribute (not `null`) | |
1102 */ | |
1103 XmlAttributeNode parseAttribute() { | |
1104 Token name = currentToken; | |
1105 currentToken = currentToken.next; | |
1106 Token equals; | |
1107 if (identical(currentToken.type, TokenType.EQ)) { | |
1108 equals = currentToken; | |
1109 currentToken = currentToken.next; | |
1110 } else { | |
1111 reportUnexpectedToken(); | |
1112 equals = insertSyntheticToken(TokenType.EQ); | |
1113 } | |
1114 Token value; | |
1115 if (identical(currentToken.type, TokenType.STRING)) { | |
1116 value = currentToken; | |
1117 currentToken = currentToken.next; | |
1118 } else { | |
1119 reportUnexpectedToken(); | |
1120 value = insertSyntheticToken(TokenType.STRING); | |
1121 } | |
1122 return new XmlAttributeNode(name, equals, value); | |
1123 } | |
1124 | |
1125 /** | |
1126 * Parse the stream for a sequence of attributes. This method advances the cur
rent token to the | |
1127 * next [TokenType#GT], [TokenType#SLASH_GT], or [TokenType#EOF]. | |
1128 * | |
1129 * @return a collection of zero or more attributes (not `null`, contains no `n
ull`s) | |
1130 */ | |
1131 List<XmlAttributeNode> parseAttributes() { | |
1132 TokenType type = currentToken.type; | |
1133 if (identical(type, TokenType.GT) || identical(type, TokenType.SLASH_GT) ||
identical(type, TokenType.EOF)) { | |
1134 return XmlTagNode.NO_ATTRIBUTES; | |
1135 } | |
1136 List<XmlAttributeNode> attributes = new List<XmlAttributeNode>(); | |
1137 while (true) { | |
1138 while (true) { | |
1139 if (currentToken.type == TokenType.GT || currentToken.type == TokenType.
SLASH_GT || currentToken.type == TokenType.EOF) { | |
1140 return attributes; | |
1141 } else if (currentToken.type == TokenType.TAG) { | |
1142 attributes.add(parseAttribute()); | |
1143 } else { | |
1144 reportUnexpectedToken(); | |
1145 currentToken = currentToken.next; | |
1146 } | |
1147 break; | |
1148 } | |
1149 } | |
1150 } | |
1151 | |
1152 /** | |
1153 * Parse the stream for a sequence of tag nodes existing within a parent tag n
ode. This method | |
1154 * advances the current token to the next [TokenType#LT_SLASH] or [TokenType#E
OF]. | |
1155 * | |
1156 * @return a list of nodes (not `null`, contains no `null`s) | |
1157 */ | |
1158 List<XmlTagNode> parseChildTagNodes() { | |
1159 TokenType type = currentToken.type; | |
1160 if (identical(type, TokenType.LT_SLASH) || identical(type, TokenType.EOF)) { | |
1161 return XmlTagNode.NO_TAG_NODES; | |
1162 } | |
1163 List<XmlTagNode> nodes = new List<XmlTagNode>(); | |
1164 while (true) { | |
1165 while (true) { | |
1166 if (currentToken.type == TokenType.LT) { | |
1167 nodes.add(parseTagNode()); | |
1168 } else if (currentToken.type == TokenType.LT_SLASH || currentToken.type
== TokenType.EOF) { | |
1169 return nodes; | |
1170 } else if (currentToken.type == TokenType.COMMENT) { | |
1171 currentToken = currentToken.next; | |
1172 } else { | |
1173 reportUnexpectedToken(); | |
1174 currentToken = currentToken.next; | |
1175 } | |
1176 break; | |
1177 } | |
1178 } | |
1179 } | |
1180 | |
1181 /** | |
1182 * Parse the token stream for the next tag node. This method advances current
token over the | |
1183 * parsed tag node, but should only be called if the current token is [TokenTy
pe#LT] | |
1184 * | |
1185 * @return the tag node or `null` if none found | |
1186 */ | |
1187 XmlTagNode parseTagNode() { | |
1188 Token nodeStart = currentToken; | |
1189 currentToken = currentToken.next; | |
1190 Token tag; | |
1191 if (identical(currentToken.type, TokenType.TAG)) { | |
1192 tag = currentToken; | |
1193 currentToken = currentToken.next; | |
1194 } else { | |
1195 reportUnexpectedToken(); | |
1196 tag = insertSyntheticToken(TokenType.TAG); | |
1197 } | |
1198 List<XmlAttributeNode> attributes = parseAttributes(); | |
1199 Token attributeEnd; | |
1200 if (identical(currentToken.type, TokenType.GT) || identical(currentToken.typ
e, TokenType.SLASH_GT)) { | |
1201 attributeEnd = currentToken; | |
1202 currentToken = currentToken.next; | |
1203 } else { | |
1204 reportUnexpectedToken(); | |
1205 attributeEnd = insertSyntheticToken(TokenType.SLASH_GT); | |
1206 } | |
1207 if (identical(attributeEnd.type, TokenType.SLASH_GT) || isSelfClosing(tag))
{ | |
1208 return new XmlTagNode(nodeStart, tag, attributes, attributeEnd, XmlTagNode
.NO_TAG_NODES, currentToken, null, attributeEnd); | |
1209 } | |
1210 List<XmlTagNode> tagNodes = parseChildTagNodes(); | |
1211 Token contentEnd; | |
1212 if (identical(currentToken.type, TokenType.LT_SLASH)) { | |
1213 contentEnd = currentToken; | |
1214 currentToken = currentToken.next; | |
1215 } else { | |
1216 reportUnexpectedToken(); | |
1217 contentEnd = insertSyntheticToken(TokenType.LT_SLASH); | |
1218 } | |
1219 Token closingTag; | |
1220 if (identical(currentToken.type, TokenType.TAG)) { | |
1221 closingTag = currentToken; | |
1222 currentToken = currentToken.next; | |
1223 } else { | |
1224 reportUnexpectedToken(); | |
1225 closingTag = insertSyntheticToken(TokenType.TAG); | |
1226 } | |
1227 Token nodeEnd; | |
1228 if (identical(currentToken.type, TokenType.GT)) { | |
1229 nodeEnd = currentToken; | |
1230 currentToken = currentToken.next; | |
1231 } else { | |
1232 reportUnexpectedToken(); | |
1233 nodeEnd = insertSyntheticToken(TokenType.GT); | |
1234 } | |
1235 return new XmlTagNode(nodeStart, tag, attributes, attributeEnd, tagNodes, co
ntentEnd, closingTag, nodeEnd); | |
1236 } | |
1237 | |
1238 /** | |
1239 * Report the current token as unexpected | |
1240 */ | |
1241 void reportUnexpectedToken() { | |
1242 } | |
1243 } | |
1244 /** | |
1245 * Instances of `XmlTagNode` represent XML or HTML elements such as `` and | |
1246 * `<body foo="bar"> ... </body>`. | |
1247 * | |
1248 * @coverage dart.engine.html | |
1249 */ | |
1250 class XmlTagNode extends XmlNode { | |
1251 | |
1252 /** | |
1253 * Constant representing empty list of attributes. | |
1254 */ | |
1255 static List<XmlAttributeNode> NO_ATTRIBUTES = new UnmodifiableListView(new Lis
t<XmlAttributeNode>()); | |
1256 | |
1257 /** | |
1258 * Constant representing empty list of tag nodes. | |
1259 */ | |
1260 static List<XmlTagNode> NO_TAG_NODES = new UnmodifiableListView(new List<XmlTa
gNode>()); | |
1261 | |
1262 /** | |
1263 * The starting [TokenType#LT] token (not `null`). | |
1264 */ | |
1265 Token nodeStart; | |
1266 | |
1267 /** | |
1268 * The [TokenType#TAG] token after the starting '<' (not `null`). | |
1269 */ | |
1270 Token tag; | |
1271 | |
1272 /** | |
1273 * The attributes contained by the receiver (not `null`, contains no `null`s). | |
1274 */ | |
1275 List<XmlAttributeNode> attributes; | |
1276 | |
1277 /** | |
1278 * The [TokenType#GT] or [TokenType#SLASH_GT] token after the attributes (not | |
1279 * `null`). The token may be the same token as [nodeEnd] if there are no child | |
1280 * [tagNodes]. | |
1281 */ | |
1282 Token attributeEnd; | |
1283 | |
1284 /** | |
1285 * The tag nodes contained in the receiver (not `null`, contains no `null`s). | |
1286 */ | |
1287 List<XmlTagNode> tagNodes; | |
1288 | |
1289 /** | |
1290 * The token (not `null`) after the content, which may be | |
1291 * | |
1292 * * (1) [TokenType#LT_SLASH] for nodes with open and close tags, or | |
1293 * * (2) the [TokenType#LT] nodeStart of the next sibling node if this node is
self | |
1294 * closing or the attributeEnd is [TokenType#SLASH_GT], or | |
1295 * * (3) [TokenType#EOF] if the node does not have a closing tag and is the la
st node in | |
1296 * the stream [TokenType#LT_SLASH] token after the content, or `null` if there
is no | |
1297 * content and the attributes ended with [TokenType#SLASH_GT]. | |
1298 * | |
1299 */ | |
1300 Token contentEnd; | |
1301 | |
1302 /** | |
1303 * The closing [TokenType#TAG] after the child elements or `null` if there is
no | |
1304 * content and the attributes ended with [TokenType#SLASH_GT] | |
1305 */ | |
1306 Token closingTag; | |
1307 | |
1308 /** | |
1309 * The ending [TokenType#GT] or [TokenType#SLASH_GT] token (not `null`). | |
1310 */ | |
1311 Token nodeEnd; | |
1312 | |
1313 /** | |
1314 * Construct a new instance representing an XML or HTML element | |
1315 * | |
1316 * @param nodeStart the starting [TokenType#LT] token (not `null`) | |
1317 * @param tag the [TokenType#TAG] token after the starting '<' (not `null`)
. | |
1318 * @param attributes the attributes associated with this element or [NO_ATTRIB
UTES] (not | |
1319 * `null`, contains no `null`s) | |
1320 * @param attributeEnd The [TokenType#GT] or [TokenType#SLASH_GT] token after
the | |
1321 * attributes (not `null`). The token may be the same token as [nodeE
nd] if | |
1322 * there are no child [tagNodes]. | |
1323 * @param tagNodes child tag nodes of the receiver or [NO_TAG_NODES] (not `nul
l`, | |
1324 * contains no `null`s) | |
1325 * @param contentEnd the token (not `null`) after the content, which may be | |
1326 * | |
1327 * * (1) [TokenType#LT_SLASH] for nodes with open and close tags, or | |
1328 * * (2) the [TokenType#LT] nodeStart of the next sibling node if thi
s node is | |
1329 * self closing or the attributeEnd is [TokenType#SLASH_GT], or | |
1330 * * (3) [TokenType#EOF] if the node does not have a closing tag and
is the last | |
1331 * node in the stream [TokenType#LT_SLASH] token after the content, o
r `null` | |
1332 * if there is no content and the attributes ended with [TokenType#SL
ASH_GT]. | |
1333 * | |
1334 * @param closingTag the closing [TokenType#TAG] after the child elements or `
null` if | |
1335 * there is no content and the attributes ended with [TokenType#SLASH
_GT] | |
1336 * @param nodeEnd the ending [TokenType#GT] or [TokenType#SLASH_GT] token (not | |
1337 * `null`) | |
1338 */ | |
1339 XmlTagNode(Token nodeStart, Token tag, List<XmlAttributeNode> attributes, Toke
n attributeEnd, List<XmlTagNode> tagNodes, Token contentEnd, Token closingTag, T
oken nodeEnd) { | |
1340 this.nodeStart = nodeStart; | |
1341 this.tag = tag; | |
1342 this.attributes = becomeParentOfEmpty(attributes, NO_ATTRIBUTES); | |
1343 this.attributeEnd = attributeEnd; | |
1344 this.tagNodes = becomeParentOfEmpty(tagNodes, NO_TAG_NODES); | |
1345 this.contentEnd = contentEnd; | |
1346 this.closingTag = closingTag; | |
1347 this.nodeEnd = nodeEnd; | |
1348 } | |
1349 accept(XmlVisitor visitor) => visitor.visitXmlTagNode(this); | |
1350 | |
1351 /** | |
1352 * Answer the attribute with the specified name. | |
1353 * | |
1354 * @param name the attribute name | |
1355 * @return the attribute or `null` if no matching attribute is found | |
1356 */ | |
1357 XmlAttributeNode getAttribute(String name) { | |
1358 for (XmlAttributeNode attribute in attributes) { | |
1359 if (attribute.name.lexeme == name) { | |
1360 return attribute; | |
1361 } | |
1362 } | |
1363 return null; | |
1364 } | |
1365 | |
1366 /** | |
1367 * Find the attribute with the given name (see [getAttribute] and answer the l
exeme | |
1368 * for the attribute's value token without the leading and trailing quotes (se
e | |
1369 * [XmlAttributeNode#getText]). | |
1370 * | |
1371 * @param name the attribute name | |
1372 * @return the attribute text or `null` if no matching attribute is found | |
1373 */ | |
1374 String getAttributeText(String name) { | |
1375 XmlAttributeNode attribute = getAttribute(name); | |
1376 return attribute != null ? attribute.text : null; | |
1377 } | |
1378 Token get beginToken => nodeStart; | |
1379 | |
1380 /** | |
1381 * Answer a string representing the content contained in the receiver. This in
cludes the textual | |
1382 * representation of any child tag nodes ([getTagNodes]). Whitespace between '
<', | |
1383 * '</', and '>', '/>' is discarded, but all other whitespace is preserved. | |
1384 * | |
1385 * @return the content (not `null`) | |
1386 */ | |
1387 String get content { | |
1388 Token token = attributeEnd.next; | |
1389 if (identical(token, contentEnd)) { | |
1390 return ""; | |
1391 } | |
1392 String content = token.lexeme; | |
1393 token = token.next; | |
1394 if (identical(token, contentEnd)) { | |
1395 return content; | |
1396 } | |
1397 JavaStringBuilder buffer = new JavaStringBuilder(); | |
1398 while (token != contentEnd) { | |
1399 buffer.append(token.lexeme); | |
1400 token = token.next; | |
1401 } | |
1402 return buffer.toString(); | |
1403 } | |
1404 Token get endToken { | |
1405 if (nodeEnd != null) { | |
1406 return nodeEnd; | |
1407 } | |
1408 if (closingTag != null) { | |
1409 return closingTag; | |
1410 } | |
1411 if (contentEnd != null) { | |
1412 return contentEnd; | |
1413 } | |
1414 if (!tagNodes.isEmpty) { | |
1415 return tagNodes[tagNodes.length - 1].endToken; | |
1416 } | |
1417 if (attributeEnd != null) { | |
1418 return attributeEnd; | |
1419 } | |
1420 if (!attributes.isEmpty) { | |
1421 return attributes[attributes.length - 1].endToken; | |
1422 } | |
1423 return tag; | |
1424 } | |
1425 void visitChildren(XmlVisitor visitor) { | |
1426 for (XmlAttributeNode node in attributes) { | |
1427 node.accept(visitor); | |
1428 } | |
1429 for (XmlTagNode node in tagNodes) { | |
1430 node.accept(visitor); | |
1431 } | |
1432 } | |
1433 | |
1434 /** | |
1435 * Same as [becomeParentOf], but returns given "ifEmpty" if "children" is empt
y | |
1436 */ | |
1437 List becomeParentOfEmpty(List children, List ifEmpty) { | |
1438 if (children != null && children.isEmpty) { | |
1439 return ifEmpty; | |
1440 } | |
1441 return becomeParentOf(children); | |
1442 } | |
1443 } | |
1444 /** | |
1445 * Instances of the class `HtmlParser` are used to parse tokens into a AST struc
ture comprised | |
1446 * of [XmlNode]s. | |
1447 * | |
1448 * @coverage dart.engine.html | |
1449 */ | |
1450 class HtmlParser extends XmlParser { | |
1451 static Set<String> SELF_CLOSING = new Set<String>(); | |
1452 | |
1453 /** | |
1454 * Construct a parser for the specified source. | |
1455 * | |
1456 * @param source the source being parsed | |
1457 */ | |
1458 HtmlParser(Source source) : super(source); | |
1459 | |
1460 /** | |
1461 * Parse the tokens specified by the given scan result. | |
1462 * | |
1463 * @param scanResult the result of scanning an HTML source (not `null`) | |
1464 * @return the parse result (not `null`) | |
1465 */ | |
1466 HtmlParseResult parse(HtmlScanResult scanResult) { | |
1467 Token firstToken = scanResult.token; | |
1468 List<XmlTagNode> tagNodes = parseTopTagNodes(firstToken); | |
1469 HtmlUnit unit = new HtmlUnit(firstToken, tagNodes, currentToken); | |
1470 return new HtmlParseResult(scanResult.modificationTime, firstToken, scanResu
lt.lineStarts, unit); | |
1471 } | |
1472 | |
1473 /** | |
1474 * Scan then parse the specified source. | |
1475 * | |
1476 * @param source the source to be scanned and parsed (not `null`) | |
1477 * @return the parse result (not `null`) | |
1478 */ | |
1479 HtmlParseResult parse2(Source source) { | |
1480 HtmlScanner scanner = new HtmlScanner(source); | |
1481 source.getContents(scanner); | |
1482 return parse(scanner.result); | |
1483 } | |
1484 bool isSelfClosing(Token tag) => SELF_CLOSING.contains(tag.lexeme); | |
1485 } | |
1486 /** | |
1487 * Instances of the class `HtmlUnit` represent the contents of an HTML file. | |
1488 * | |
1489 * @coverage dart.engine.html | |
1490 */ | |
1491 class HtmlUnit extends XmlNode { | |
1492 | |
1493 /** | |
1494 * The first token in the token stream that was parsed to form this HTML unit. | |
1495 */ | |
1496 Token _beginToken; | |
1497 | |
1498 /** | |
1499 * The last token in the token stream that was parsed to form this compilation
unit. This token | |
1500 * should always have a type of [TokenType.EOF]. | |
1501 */ | |
1502 Token _endToken; | |
1503 | |
1504 /** | |
1505 * The tag nodes contained in the receiver (not `null`, contains no `null`s). | |
1506 */ | |
1507 List<XmlTagNode> tagNodes; | |
1508 | |
1509 /** | |
1510 * The element associated with this HTML unit or `null` if the receiver is not
resolved. | |
1511 */ | |
1512 HtmlElementImpl element; | |
1513 | |
1514 /** | |
1515 * Construct a new instance representing the content of an HTML file. | |
1516 * | |
1517 * @param beginToken the first token in the file (not `null`) | |
1518 * @param tagNodes child tag nodes of the receiver (not `null`, contains no `n
ull`s) | |
1519 * @param endToken the last token in the token stream which should be of type | |
1520 * [TokenType.EOF] | |
1521 */ | |
1522 HtmlUnit(Token beginToken, List<XmlTagNode> tagNodes, Token endToken) { | |
1523 this._beginToken = beginToken; | |
1524 this.tagNodes = becomeParentOf(tagNodes); | |
1525 this._endToken = endToken; | |
1526 } | |
1527 accept(XmlVisitor visitor) => visitor.visitHtmlUnit(this); | |
1528 Token get beginToken => _beginToken; | |
1529 Token get endToken => _endToken; | |
1530 void visitChildren(XmlVisitor visitor) { | |
1531 for (XmlTagNode node in tagNodes) { | |
1532 node.accept(visitor); | |
1533 } | |
1534 } | |
1535 } | |
OLD | NEW |