| OLD | NEW |
| 1 /** | 1 /** |
| 2 * A simple tree API that results from parsing html. Intended to be compatible | 2 * A simple tree API that results from parsing html. Intended to be compatible |
| 3 * with dart:html, but right now it resembles the classic JS DOM. | 3 * with dart:html, but right now it resembles the classic JS DOM. |
| 4 */ | 4 */ |
| 5 library dom; | 5 library dom; |
| 6 | 6 |
| 7 import 'dart:collection'; | 7 import 'dart:collection'; |
| 8 import 'package:source_maps/span.dart' show FileSpan; | 8 import 'package:source_maps/span.dart' show FileSpan; |
| 9 | 9 |
| 10 import 'src/constants.dart'; | 10 import 'src/constants.dart'; |
| (...skipping 134 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 145 | 145 |
| 146 // TODO(jmesserly): needs to support deep clone. | 146 // TODO(jmesserly): needs to support deep clone. |
| 147 /** | 147 /** |
| 148 * Return a shallow copy of the current node i.e. a node with the same | 148 * Return a shallow copy of the current node i.e. a node with the same |
| 149 * name and attributes but with no parent or child nodes. | 149 * name and attributes but with no parent or child nodes. |
| 150 */ | 150 */ |
| 151 Node clone(); | 151 Node clone(); |
| 152 | 152 |
| 153 String get namespace => null; | 153 String get namespace => null; |
| 154 | 154 |
| 155 // TODO(jmesserly): do we need this here? | |
| 156 /** The value of the current node (applies to text nodes and comments). */ | |
| 157 String get value => null; | |
| 158 | |
| 159 // TODO(jmesserly): this is a workaround for http://dartbug.com/4754 | |
| 160 int get $dom_nodeType => nodeType; | |
| 161 | |
| 162 int get nodeType; | 155 int get nodeType; |
| 163 | 156 |
| 164 String get outerHtml { | 157 String get outerHtml { |
| 165 var str = new StringBuffer(); | 158 var str = new StringBuffer(); |
| 166 _addOuterHtml(str); | 159 _addOuterHtml(str); |
| 167 return str.toString(); | 160 return str.toString(); |
| 168 } | 161 } |
| 169 | 162 |
| 170 String get innerHtml { | 163 String get innerHtml { |
| 171 var str = new StringBuffer(); | 164 var str = new StringBuffer(); |
| 172 _addInnerHtml(str); | 165 _addInnerHtml(str); |
| 173 return str.toString(); | 166 return str.toString(); |
| 174 } | 167 } |
| 175 | 168 |
| 176 set innerHtml(String value) { | 169 set innerHtml(String value) { |
| 177 nodes.clear(); | 170 nodes.clear(); |
| 178 // TODO(jmesserly): should be able to get the same effect by adding the | 171 // TODO(jmesserly): should be able to get the same effect by adding the |
| 179 // fragment directly. | 172 // fragment directly. |
| 180 nodes.addAll(parseFragment(value, container: tagName).nodes); | 173 nodes.addAll(parseFragment(value, container: tagName).nodes); |
| 181 } | 174 } |
| 182 | 175 |
| 176 // Implemented per: http://dom.spec.whatwg.org/#dom-node-textcontent |
| 177 String get text => null; |
| 178 set text(String value) {} |
| 179 |
| 180 void append(Node node) => nodes.add(node); |
| 181 |
| 183 Node get firstChild => nodes.isNotEmpty ? nodes[0] : null; | 182 Node get firstChild => nodes.isNotEmpty ? nodes[0] : null; |
| 184 | 183 |
| 185 void _addOuterHtml(StringBuffer str); | 184 void _addOuterHtml(StringBuffer str); |
| 186 | 185 |
| 187 void _addInnerHtml(StringBuffer str) { | 186 void _addInnerHtml(StringBuffer str) { |
| 188 for (Node child in nodes) child._addOuterHtml(str); | 187 for (Node child in nodes) child._addOuterHtml(str); |
| 189 } | 188 } |
| 190 | 189 |
| 191 String toString() => tagName; | 190 String toString() => tagName; |
| 192 | 191 |
| (...skipping 196 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 389 | 388 |
| 390 class DocumentFragment extends Document { | 389 class DocumentFragment extends Document { |
| 391 DocumentFragment(); | 390 DocumentFragment(); |
| 392 factory DocumentFragment.html(String html) => parseFragment(html); | 391 factory DocumentFragment.html(String html) => parseFragment(html); |
| 393 | 392 |
| 394 int get nodeType => Node.DOCUMENT_FRAGMENT_NODE; | 393 int get nodeType => Node.DOCUMENT_FRAGMENT_NODE; |
| 395 | 394 |
| 396 String toString() => "#document-fragment"; | 395 String toString() => "#document-fragment"; |
| 397 | 396 |
| 398 DocumentFragment clone() => new DocumentFragment(); | 397 DocumentFragment clone() => new DocumentFragment(); |
| 398 |
| 399 String get text => _getText(this); |
| 400 set text(String value) => _setText(this, value); |
| 399 } | 401 } |
| 400 | 402 |
| 401 class DocumentType extends Node { | 403 class DocumentType extends Node { |
| 402 final String publicId; | 404 final String publicId; |
| 403 final String systemId; | 405 final String systemId; |
| 404 | 406 |
| 405 DocumentType(String name, this.publicId, this.systemId) : super(name); | 407 DocumentType(String name, this.publicId, this.systemId) : super(name); |
| 406 | 408 |
| 407 int get nodeType => Node.DOCUMENT_TYPE_NODE; | 409 int get nodeType => Node.DOCUMENT_TYPE_NODE; |
| 408 | 410 |
| (...skipping 11 matching lines...) Expand all Loading... |
| 420 | 422 |
| 421 | 423 |
| 422 void _addOuterHtml(StringBuffer str) { | 424 void _addOuterHtml(StringBuffer str) { |
| 423 str.write(toString()); | 425 str.write(toString()); |
| 424 } | 426 } |
| 425 | 427 |
| 426 DocumentType clone() => new DocumentType(tagName, publicId, systemId); | 428 DocumentType clone() => new DocumentType(tagName, publicId, systemId); |
| 427 } | 429 } |
| 428 | 430 |
| 429 class Text extends Node { | 431 class Text extends Node { |
| 430 // TODO(jmesserly): this should be text? | 432 String data; |
| 431 String value; | |
| 432 | 433 |
| 433 Text(this.value) : super(null); | 434 Text(this.data) : super(null); |
| 434 | 435 |
| 435 int get nodeType => Node.TEXT_NODE; | 436 int get nodeType => Node.TEXT_NODE; |
| 436 | 437 |
| 437 String toString() => '"$value"'; | 438 String toString() => '"$data"'; |
| 438 | 439 |
| 439 void _addOuterHtml(StringBuffer str) { | 440 void _addOuterHtml(StringBuffer str) { |
| 440 // Don't escape text for certain elements, notably <script>. | 441 // Don't escape text for certain elements, notably <script>. |
| 441 if (rcdataElements.contains(parent.tagName) || | 442 if (rcdataElements.contains(parent.tagName) || |
| 442 parent.tagName == 'plaintext') { | 443 parent.tagName == 'plaintext') { |
| 443 str.write(value); | 444 str.write(data); |
| 444 } else { | 445 } else { |
| 445 str.write(htmlSerializeEscape(value)); | 446 str.write(htmlSerializeEscape(data)); |
| 446 } | 447 } |
| 447 } | 448 } |
| 448 | 449 |
| 449 Text clone() => new Text(value); | 450 Text clone() => new Text(data); |
| 451 |
| 452 String get text => data; |
| 453 set text(String value) { data = value; } |
| 450 } | 454 } |
| 451 | 455 |
| 452 class Element extends Node { | 456 class Element extends Node { |
| 453 final String namespace; | 457 final String namespace; |
| 454 | 458 |
| 455 // TODO(jmesserly): deprecate in favor of Element.tag? Or rename? | 459 // TODO(jmesserly): deprecate in favor of Element.tag? Or rename? |
| 456 Element(String name, [this.namespace]) : super(name); | 460 Element(String name, [this.namespace]) : super(name); |
| 457 | 461 |
| 458 Element.tag(String name) : namespace = null, super(name); | 462 Element.tag(String name) : namespace = null, super(name); |
| 459 | 463 |
| (...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 511 return element; | 515 return element; |
| 512 } | 516 } |
| 513 | 517 |
| 514 int get nodeType => Node.ELEMENT_NODE; | 518 int get nodeType => Node.ELEMENT_NODE; |
| 515 | 519 |
| 516 String toString() { | 520 String toString() { |
| 517 if (namespace == null) return "<$tagName>"; | 521 if (namespace == null) return "<$tagName>"; |
| 518 return "<${Namespaces.getPrefix(namespace)} $tagName>"; | 522 return "<${Namespaces.getPrefix(namespace)} $tagName>"; |
| 519 } | 523 } |
| 520 | 524 |
| 525 String get text => _getText(this); |
| 526 set text(String value) => _setText(this, value); |
| 527 |
| 521 void _addOuterHtml(StringBuffer str) { | 528 void _addOuterHtml(StringBuffer str) { |
| 522 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#
serializing-html-fragments | 529 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#
serializing-html-fragments |
| 523 // Element is the most complicated one. | 530 // Element is the most complicated one. |
| 524 if (namespace == null || | 531 if (namespace == null || |
| 525 namespace == Namespaces.html || | 532 namespace == Namespaces.html || |
| 526 namespace == Namespaces.mathml || | 533 namespace == Namespaces.mathml || |
| 527 namespace == Namespaces.svg) { | 534 namespace == Namespaces.svg) { |
| 528 str.write('<$tagName'); | 535 str.write('<$tagName'); |
| 529 } else { | 536 } else { |
| 530 // TODO(jmesserly): the spec doesn't define "qualified name". | 537 // TODO(jmesserly): the spec doesn't define "qualified name". |
| 531 // I'm not sure if this is correct, but it should parse reasonably. | 538 // I'm not sure if this is correct, but it should parse reasonably. |
| 532 str.write('<${Namespaces.getPrefix(namespace)}:$tagName'); | 539 str.write('<${Namespaces.getPrefix(namespace)}:$tagName'); |
| 533 } | 540 } |
| 534 | 541 |
| 535 if (attributes.length > 0) { | 542 if (attributes.length > 0) { |
| 536 attributes.forEach((key, v) { | 543 attributes.forEach((key, v) { |
| 537 // Note: AttributeName.toString handles serialization of attribute | 544 // Note: AttributeName.toString handles serialization of attribute |
| 538 // namespace, if needed. | 545 // namespace, if needed. |
| 539 str.write(' $key="${htmlSerializeEscape(v, attributeMode: true)}"'); | 546 str.write(' $key="${htmlSerializeEscape(v, attributeMode: true)}"'); |
| 540 }); | 547 }); |
| 541 } | 548 } |
| 542 | 549 |
| 543 str.write('>'); | 550 str.write('>'); |
| 544 | 551 |
| 545 if (nodes.length > 0) { | 552 if (nodes.length > 0) { |
| 546 if (tagName == 'pre' || tagName == 'textarea' || tagName == 'listing') { | 553 if (tagName == 'pre' || tagName == 'textarea' || tagName == 'listing') { |
| 547 if (nodes[0] is Text && nodes[0].value.startsWith('\n')) { | 554 final first = nodes[0]; |
| 555 if (first is Text && first.data.startsWith('\n')) { |
| 548 // These nodes will remove a leading \n at parse time, so if we still | 556 // These nodes will remove a leading \n at parse time, so if we still |
| 549 // have one, it means we started with two. Add it back. | 557 // have one, it means we started with two. Add it back. |
| 550 str.write('\n'); | 558 str.write('\n'); |
| 551 } | 559 } |
| 552 } | 560 } |
| 553 | 561 |
| 554 _addInnerHtml(str); | 562 _addInnerHtml(str); |
| 555 } | 563 } |
| 556 | 564 |
| 557 // void elements must not have an end tag | 565 // void elements must not have an end tag |
| (...skipping 12 matching lines...) Expand all Loading... |
| 570 set id(String value) { | 578 set id(String value) { |
| 571 if (value == null) { | 579 if (value == null) { |
| 572 attributes.remove('id'); | 580 attributes.remove('id'); |
| 573 } else { | 581 } else { |
| 574 attributes['id'] = value; | 582 attributes['id'] = value; |
| 575 } | 583 } |
| 576 } | 584 } |
| 577 } | 585 } |
| 578 | 586 |
| 579 class Comment extends Node { | 587 class Comment extends Node { |
| 580 final String data; | 588 String data; |
| 581 | 589 |
| 582 Comment(this.data) : super(null); | 590 Comment(this.data) : super(null); |
| 583 | 591 |
| 584 int get nodeType => Node.COMMENT_NODE; | 592 int get nodeType => Node.COMMENT_NODE; |
| 585 | 593 |
| 586 String toString() => "<!-- $data -->"; | 594 String toString() => "<!-- $data -->"; |
| 587 | 595 |
| 588 void _addOuterHtml(StringBuffer str) { | 596 void _addOuterHtml(StringBuffer str) { |
| 589 str.write("<!--$data-->"); | 597 str.write("<!--$data-->"); |
| 590 } | 598 } |
| 591 | 599 |
| 592 Comment clone() => new Comment(data); | 600 Comment clone() => new Comment(data); |
| 601 |
| 602 String get text => data; |
| 603 set text(String value) { |
| 604 this.data = value; |
| 605 } |
| 593 } | 606 } |
| 594 | 607 |
| 595 | 608 |
| 596 // TODO(jmesserly): fix this to extend one of the corelib classes if possible. | 609 // TODO(jmesserly): fix this to extend one of the corelib classes if possible. |
| 597 // (The requirement to remove the node from the old node list makes it tricky.) | 610 // (The requirement to remove the node from the old node list makes it tricky.) |
| 598 // TODO(jmesserly): is there any way to share code with the _NodeListImpl? | 611 // TODO(jmesserly): is there any way to share code with the _NodeListImpl? |
| 599 class NodeList extends ListProxy<Node> { | 612 class NodeList extends ListProxy<Node> { |
| 600 // Note: this is conceptually final, but because of circular reference | 613 // Note: this is conceptually final, but because of circular reference |
| 601 // between Node and NodeList we initialize it after construction. | 614 // between Node and NodeList we initialize it after construction. |
| 602 Node _parent; | 615 Node _parent; |
| (...skipping 271 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 874 if (start == null) start = length - 1; | 887 if (start == null) start = length - 1; |
| 875 return _filtered.lastIndexOf(element, start); | 888 return _filtered.lastIndexOf(element, start); |
| 876 } | 889 } |
| 877 | 890 |
| 878 Element get first => _filtered.first; | 891 Element get first => _filtered.first; |
| 879 | 892 |
| 880 Element get last => _filtered.last; | 893 Element get last => _filtered.last; |
| 881 | 894 |
| 882 Element get single => _filtered.single; | 895 Element get single => _filtered.single; |
| 883 } | 896 } |
| 897 |
| 898 // http://dom.spec.whatwg.org/#dom-node-textcontent |
| 899 // For Element and DocumentFragment |
| 900 String _getText(Node node) => |
| 901 (new _ConcatTextVisitor()..visit(node)).toString(); |
| 902 |
| 903 void _setText(Node node, String value) { |
| 904 node.nodes.clear(); |
| 905 node.append(new Text(value)); |
| 906 } |
| 907 |
| 908 class _ConcatTextVisitor extends TreeVisitor { |
| 909 final _str = new StringBuffer(); |
| 910 |
| 911 String toString() => _str.toString(); |
| 912 |
| 913 visitText(Text node) { |
| 914 _str.write(node.data); |
| 915 } |
| 916 } |
| OLD | NEW |