| Index: pkg/third_party/html5lib/lib/src/treebuilder.dart
|
| diff --git a/pkg/third_party/html5lib/lib/src/treebuilder.dart b/pkg/third_party/html5lib/lib/src/treebuilder.dart
|
| deleted file mode 100644
|
| index 4b8dfc4255cb91848f112a999a557d639d9b1c6b..0000000000000000000000000000000000000000
|
| --- a/pkg/third_party/html5lib/lib/src/treebuilder.dart
|
| +++ /dev/null
|
| @@ -1,394 +0,0 @@
|
| -/// Internals to the tree builders.
|
| -library treebuilder;
|
| -
|
| -import 'dart:collection';
|
| -import 'package:html5lib/dom.dart';
|
| -import 'package:html5lib/parser.dart' show getElementNameTuple;
|
| -import 'package:source_span/source_span.dart';
|
| -import 'constants.dart';
|
| -import 'list_proxy.dart';
|
| -import 'token.dart';
|
| -import 'utils.dart';
|
| -
|
| -// The scope markers are inserted when entering object elements,
|
| -// marquees, table cells, and table captions, and are used to prevent formatting
|
| -// from "leaking" into tables, object elements, and marquees.
|
| -const Node Marker = null;
|
| -
|
| -// TODO(jmesserly): this should extend ListBase<Element>, but my simple attempt
|
| -// didn't work.
|
| -class ActiveFormattingElements extends ListProxy<Element> {
|
| - ActiveFormattingElements() : super();
|
| -
|
| - // Override the "add" method.
|
| - // TODO(jmesserly): I'd rather not override this; can we do this in the
|
| - // calling code instead?
|
| - void add(Element node) {
|
| - int equalCount = 0;
|
| - if (node != Marker) {
|
| - for (var element in reversed) {
|
| - if (element == Marker) {
|
| - break;
|
| - }
|
| - if (_nodesEqual(element, node)) {
|
| - equalCount += 1;
|
| - }
|
| - if (equalCount == 3) {
|
| - remove(element);
|
| - break;
|
| - }
|
| - }
|
| - }
|
| - super.add(node);
|
| - }
|
| -}
|
| -
|
| -// TODO(jmesserly): this should exist in corelib...
|
| -bool _mapEquals(Map a, Map b) {
|
| - if (a.length != b.length) return false;
|
| - if (a.length == 0) return true;
|
| -
|
| - for (var keyA in a.keys) {
|
| - var valB = b[keyA];
|
| - if (valB == null && !b.containsKey(keyA)) {
|
| - return false;
|
| - }
|
| -
|
| - if (a[keyA] != valB) {
|
| - return false;
|
| - }
|
| - }
|
| - return true;
|
| -}
|
| -
|
| -
|
| -bool _nodesEqual(Element node1, Element node2) {
|
| - return getElementNameTuple(node1) == getElementNameTuple(node2) &&
|
| - _mapEquals(node1.attributes, node2.attributes);
|
| -}
|
| -
|
| -/// Basic treebuilder implementation.
|
| -class TreeBuilder {
|
| - final String defaultNamespace;
|
| -
|
| - Document document;
|
| -
|
| - final List<Element> openElements = <Element>[];
|
| -
|
| - final activeFormattingElements = new ActiveFormattingElements();
|
| -
|
| - Node headPointer;
|
| -
|
| - Node formPointer;
|
| -
|
| - /// Switch the function used to insert an element from the
|
| - /// normal one to the misnested table one and back again
|
| - bool insertFromTable;
|
| -
|
| - TreeBuilder(bool namespaceHTMLElements)
|
| - : defaultNamespace = namespaceHTMLElements ? Namespaces.html : null {
|
| - reset();
|
| - }
|
| -
|
| - void reset() {
|
| - openElements.clear();
|
| - activeFormattingElements.clear();
|
| -
|
| - //XXX - rename these to headElement, formElement
|
| - headPointer = null;
|
| - formPointer = null;
|
| -
|
| - insertFromTable = false;
|
| -
|
| - document = new Document();
|
| - }
|
| -
|
| - bool elementInScope(target, {String variant}) {
|
| - //If we pass a node in we match that. if we pass a string
|
| - //match any node with that name
|
| - bool exactNode = target is Node;
|
| -
|
| - List listElements1 = scopingElements;
|
| - List listElements2 = const [];
|
| - bool invert = false;
|
| - if (variant != null) {
|
| - switch (variant) {
|
| - case "button":
|
| - listElements2 = const [const Pair(Namespaces.html, "button")];
|
| - break;
|
| - case "list":
|
| - listElements2 = const [const Pair(Namespaces.html, "ol"),
|
| - const Pair(Namespaces.html, "ul")];
|
| - break;
|
| - case "table":
|
| - listElements1 = const [const Pair(Namespaces.html, "html"),
|
| - const Pair(Namespaces.html, "table")];
|
| - break;
|
| - case "select":
|
| - listElements1 = const [const Pair(Namespaces.html, "optgroup"),
|
| - const Pair(Namespaces.html, "option")];
|
| - invert = true;
|
| - break;
|
| - default:
|
| - throw new StateError('We should never reach this point');
|
| - }
|
| - }
|
| -
|
| - for (var node in openElements.reversed) {
|
| - if (!exactNode && node.localName == target ||
|
| - exactNode && node == target) {
|
| - return true;
|
| - } else if (invert !=
|
| - (listElements1.contains(getElementNameTuple(node)) ||
|
| - listElements2.contains(getElementNameTuple(node)))) {
|
| - return false;
|
| - }
|
| - }
|
| -
|
| - throw new StateError('We should never reach this point');
|
| - }
|
| -
|
| - void reconstructActiveFormattingElements() {
|
| - // Within this algorithm the order of steps described in the
|
| - // specification is not quite the same as the order of steps in the
|
| - // code. It should still do the same though.
|
| -
|
| - // Step 1: stop the algorithm when there's nothing to do.
|
| - if (activeFormattingElements.length == 0) {
|
| - return;
|
| - }
|
| -
|
| - // Step 2 and step 3: we start with the last element. So i is -1.
|
| - int i = activeFormattingElements.length - 1;
|
| - var entry = activeFormattingElements[i];
|
| - if (entry == Marker || openElements.contains(entry)) {
|
| - return;
|
| - }
|
| -
|
| - // Step 6
|
| - while (entry != Marker && !openElements.contains(entry)) {
|
| - if (i == 0) {
|
| - //This will be reset to 0 below
|
| - i = -1;
|
| - break;
|
| - }
|
| - i -= 1;
|
| - // Step 5: let entry be one earlier in the list.
|
| - entry = activeFormattingElements[i];
|
| - }
|
| -
|
| - while (true) {
|
| - // Step 7
|
| - i += 1;
|
| -
|
| - // Step 8
|
| - entry = activeFormattingElements[i];
|
| -
|
| - // TODO(jmesserly): optimize this. No need to create a token.
|
| - var cloneToken = new StartTagToken(
|
| - entry.localName,
|
| - namespace: entry.namespaceUri,
|
| - data: new LinkedHashMap.from(entry.attributes))
|
| - ..span = entry.sourceSpan;
|
| -
|
| - // Step 9
|
| - var element = insertElement(cloneToken);
|
| -
|
| - // Step 10
|
| - activeFormattingElements[i] = element;
|
| -
|
| - // Step 11
|
| - if (element == activeFormattingElements.last) {
|
| - break;
|
| - }
|
| - }
|
| - }
|
| -
|
| - void clearActiveFormattingElements() {
|
| - var entry = activeFormattingElements.removeLast();
|
| - while (activeFormattingElements.length > 0 && entry != Marker) {
|
| - entry = activeFormattingElements.removeLast();
|
| - }
|
| - }
|
| -
|
| - /// Check if an element exists between the end of the active
|
| - /// formatting elements and the last marker. If it does, return it, else
|
| - /// return null.
|
| - Element elementInActiveFormattingElements(String name) {
|
| - for (var item in activeFormattingElements.reversed) {
|
| - // Check for Marker first because if it's a Marker it doesn't have a
|
| - // name attribute.
|
| - if (item == Marker) {
|
| - break;
|
| - } else if (item.localName == name) {
|
| - return item;
|
| - }
|
| - }
|
| - return null;
|
| - }
|
| -
|
| - void insertRoot(Token token) {
|
| - var element = createElement(token);
|
| - openElements.add(element);
|
| - document.nodes.add(element);
|
| - }
|
| -
|
| - void insertDoctype(DoctypeToken token) {
|
| - var doctype = new DocumentType(token.name, token.publicId, token.systemId)
|
| - ..sourceSpan = token.span;
|
| - document.nodes.add(doctype);
|
| - }
|
| -
|
| - void insertComment(StringToken token, [Node parent]) {
|
| - if (parent == null) {
|
| - parent = openElements.last;
|
| - }
|
| - parent.nodes.add(new Comment(token.data)..sourceSpan = token.span);
|
| - }
|
| -
|
| - /// Create an element but don't insert it anywhere
|
| - Element createElement(StartTagToken token) {
|
| - var name = token.name;
|
| - var namespace = token.namespace;
|
| - if (namespace == null) namespace = defaultNamespace;
|
| - var element = document.createElementNS(namespace, name)
|
| - ..attributes = token.data
|
| - ..sourceSpan = token.span;
|
| - return element;
|
| - }
|
| -
|
| - Element insertElement(StartTagToken token) {
|
| - if (insertFromTable) return insertElementTable(token);
|
| - return insertElementNormal(token);
|
| - }
|
| -
|
| - Element insertElementNormal(StartTagToken token) {
|
| - var name = token.name;
|
| - var namespace = token.namespace;
|
| - if (namespace == null) namespace = defaultNamespace;
|
| - var element = document.createElementNS(namespace, name)
|
| - ..attributes = token.data
|
| - ..sourceSpan = token.span;
|
| - openElements.last.nodes.add(element);
|
| - openElements.add(element);
|
| - return element;
|
| - }
|
| -
|
| - Element insertElementTable(token) {
|
| - /// Create an element and insert it into the tree
|
| - var element = createElement(token);
|
| - if (!tableInsertModeElements.contains(openElements.last.localName)) {
|
| - return insertElementNormal(token);
|
| - } else {
|
| - // We should be in the InTable mode. This means we want to do
|
| - // special magic element rearranging
|
| - var nodePos = getTableMisnestedNodePosition();
|
| - if (nodePos[1] == null) {
|
| - // TODO(jmesserly): I don't think this is reachable. If insertFromTable
|
| - // is true, there will be a <table> element open, and it always has a
|
| - // parent pointer.
|
| - nodePos[0].nodes.add(element);
|
| - } else {
|
| - nodePos[0].insertBefore(element, nodePos[1]);
|
| - }
|
| - openElements.add(element);
|
| - }
|
| - return element;
|
| - }
|
| -
|
| - /// Insert text data.
|
| - void insertText(String data, FileSpan span) {
|
| - var parent = openElements.last;
|
| -
|
| - if (!insertFromTable || insertFromTable &&
|
| - !tableInsertModeElements.contains(openElements.last.localName)) {
|
| - _insertText(parent, data, span);
|
| - } else {
|
| - // We should be in the InTable mode. This means we want to do
|
| - // special magic element rearranging
|
| - var nodePos = getTableMisnestedNodePosition();
|
| - _insertText(nodePos[0], data, span, nodePos[1]);
|
| - }
|
| - }
|
| -
|
| - /// Insert [data] as text in the current node, positioned before the
|
| - /// start of node [refNode] or to the end of the node's text.
|
| - static void _insertText(Node parent, String data, FileSpan span,
|
| - [Element refNode]) {
|
| - var nodes = parent.nodes;
|
| - if (refNode == null) {
|
| - if (nodes.length > 0 && nodes.last is Text) {
|
| - Text last = nodes.last;
|
| - last.data = '${last.data}$data';
|
| -
|
| - if (span != null) {
|
| - last.sourceSpan = span.file.span(last.sourceSpan.start.offset,
|
| - span.end.offset);
|
| - }
|
| - } else {
|
| - nodes.add(new Text(data)..sourceSpan = span);
|
| - }
|
| - } else {
|
| - int index = nodes.indexOf(refNode);
|
| - if (index > 0 && nodes[index - 1] is Text) {
|
| - Text last = nodes[index - 1];
|
| - last.data = '${last.data}$data';
|
| - } else {
|
| - nodes.insert(index, new Text(data)..sourceSpan = span);
|
| - }
|
| - }
|
| - }
|
| -
|
| - /// Get the foster parent element, and sibling to insert before
|
| - /// (or null) when inserting a misnested table node
|
| - List<Node> getTableMisnestedNodePosition() {
|
| - // The foster parent element is the one which comes before the most
|
| - // recently opened table element
|
| - // XXX - this is really inelegant
|
| - Node lastTable = null;
|
| - Node fosterParent = null;
|
| - var insertBefore = null;
|
| - for (var elm in openElements.reversed) {
|
| - if (elm.localName == "table") {
|
| - lastTable = elm;
|
| - break;
|
| - }
|
| - }
|
| - if (lastTable != null) {
|
| - // XXX - we should really check that this parent is actually a
|
| - // node here
|
| - if (lastTable.parentNode != null) {
|
| - fosterParent = lastTable.parentNode;
|
| - insertBefore = lastTable;
|
| - } else {
|
| - fosterParent = openElements[openElements.indexOf(lastTable) - 1];
|
| - }
|
| - } else {
|
| - fosterParent = openElements[0];
|
| - }
|
| - return [fosterParent, insertBefore];
|
| - }
|
| -
|
| - void generateImpliedEndTags([String exclude]) {
|
| - var name = openElements.last.localName;
|
| - // XXX td, th and tr are not actually needed
|
| - if (name != exclude && const ["dd", "dt", "li", "option", "optgroup", "p",
|
| - "rp", "rt"].contains(name)) {
|
| - openElements.removeLast();
|
| - // XXX This is not entirely what the specification says. We should
|
| - // investigate it more closely.
|
| - generateImpliedEndTags(exclude);
|
| - }
|
| - }
|
| -
|
| - /// Return the final tree.
|
| - Document getDocument() => document;
|
| -
|
| - /// Return the final fragment.
|
| - DocumentFragment getFragment() {
|
| - //XXX assert innerHTML
|
| - var fragment = new DocumentFragment();
|
| - openElements[0].reparentChildren(fragment);
|
| - return fragment;
|
| - }
|
| -}
|
|
|