diff --git a/.github/ISSUE_TEMPLATE/html.md b/.github/ISSUE_TEMPLATE/html.md new file mode 100644 index 000000000..ec1e01d85 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/html.md @@ -0,0 +1,5 @@ +--- +name: "package:html" +about: "Create a bug or file a feature request against package:html." +labels: "package:html" +--- \ No newline at end of file diff --git a/.github/labeler.yml b/.github/labeler.yml index 91184961c..3f56dafac 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -60,6 +60,10 @@ - changed-files: - any-glob-to-any-file: 'pkgs/graphs/**' +'package:html': + - changed-files: + - any-glob-to-any-file: 'pkgs/html/**' + 'package:json_rpc_2': - changed-files: - any-glob-to-any-file: 'pkgs/json_rpc_2/**' diff --git a/.github/workflows/html.yaml b/.github/workflows/html.yaml new file mode 100644 index 000000000..fe75a0dfb --- /dev/null +++ b/.github/workflows/html.yaml @@ -0,0 +1,61 @@ +name: package:html + +on: + # Run on PRs and pushes to the default branch. + push: + branches: [ main ] + paths: + - '.github/workflows/html.yml' + - 'pkgs/html/**' + pull_request: + branches: [ main ] + paths: + - '.github/workflows/html.yml' + - 'pkgs/html/**' + schedule: + - cron: "0 0 * * 0" + +env: + PUB_ENVIRONMENT: bot.github + + +defaults: + run: + working-directory: pkgs/html/ + +jobs: + analyze: + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 + with: + sdk: dev + - id: install + run: dart pub get + - run: dart format --output=none --set-exit-if-changed . + if: steps.install.outcome == 'success' + - run: dart analyze --fatal-infos + if: steps.install.outcome == 'success' + + test: + needs: analyze + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + sdk: [3.2, stable, dev] + steps: + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 + with: + sdk: ${{ matrix.sdk }} + - id: install + run: dart pub get + - run: dart test --platform vm + if: steps.install.outcome == 'success' + - run: dart test --platform chrome + if: steps.install.outcome == 'success' diff --git a/README.md b/README.md index dc8401823..868a71088 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ don't naturally belong to other topic monorepos (like | [file](pkgs/file/) | A pluggable, mockable file system abstraction for Dart. | [![pub package](https://img.shields.io/pub/v/file.svg)](https://pub.dev/packages/file) | | [file_testing](pkgs/file_testing/) | Testing utilities for package:file (published but unlisted). | [![pub package](https://img.shields.io/pub/v/file_testing.svg)](https://pub.dev/packages/file_testing) | | [graphs](pkgs/graphs/) | Graph algorithms that operate on graphs in any representation | [![pub package](https://img.shields.io/pub/v/graphs.svg)](https://pub.dev/packages/graphs) | +| [html](pkgs/html/) | APIs for parsing and manipulating HTML content outside the browser. | [![pub package](https://img.shields.io/pub/v/html.svg)](https://pub.dev/packages/html) | | [json_rpc_2](pkgs/json_rpc_2/) | Utilities to write a client or server using the JSON-RPC 2.0 spec. | [![pub package](https://img.shields.io/pub/v/json_rpc_2.svg)](https://pub.dev/packages/json_rpc_2) | | [mime](pkgs/mime/) | Utilities for handling media (MIME) types. | [![pub package](https://img.shields.io/pub/v/mime.svg)](https://pub.dev/packages/mime) | | [oauth2](pkgs/oauth2/) | A client library for authenticatingand making requests via OAuth2. | [![pub package](https://img.shields.io/pub/v/oauth2.svg)](https://pub.dev/packages/oauth2) | diff --git a/pkgs/html/.gitignore b/pkgs/html/.gitignore new file mode 100644 index 000000000..3ab3a4493 --- /dev/null +++ b/pkgs/html/.gitignore @@ -0,0 +1,19 @@ +# Don’t commit the following directories created by pub. +.pub +.dart_tool/ +build/ +packages +.packages + +# Or the files created by dart2js. +*.dart.js +*.dart.precompiled.js +*.js_ +*.js.deps +*.js.map +*.sw? +.idea/ +.pub/ + +# Include when developing application packages. +pubspec.lock diff --git a/pkgs/html/CHANGELOG.md b/pkgs/html/CHANGELOG.md new file mode 100644 index 000000000..9a881e9d7 --- /dev/null +++ b/pkgs/html/CHANGELOG.md @@ -0,0 +1,149 @@ +## 0.15.5 + +- Require Dart `3.2`. +- Move to `dart-lang/tools` monorepo. + +## 0.15.4 + +- Widen the dependency on `package:csslib`. +- Require Dart `2.19`. + +## 0.15.3 + +- Added package topics to the pubspec file. + +## 0.15.2 + +- Add additional types at the API boundary (in `lib/parser.dart` and others). +- Adopted the `package:dart_flutter_team_lints` linting rules. +- Fixed an issue with `querySelector` where it would fail in some cases with + descendant or sibling combinators (#157). +- Add an API example in `example/`. + +## 0.15.1 + +- Move `htmlSerializeEscape` to its own library, + `package:html/html_escape.dart`, which is exported from + `package:html/dom_parsing.dart`. +- Use more non-growable lists, and type annotations on List literals. +- Switch analysis option `implicit-casts: false` to `strict-casts: true`. + +## 0.15.0 + +- Migrate to null safety. +- Drop `lastPhase`, `beforeRcDataPhase`, and `container` fields from + `HtmlParser` class. These fields never had a value other than `null`. + +## 0.14.0+4 + +- Fix a bug parsing bad HTML where a 'button' end tag needs to close other + elements. + +## 0.14.0+3 + +- Fix spans generated for HTML with higher-plane unicode characters + (eg. emojis). + +## 0.14.0+2 + +- Support `package:css` `>=0.13.2 <0.17.0`. + +## 0.14.0+1 + +- Support `package:css` `>=0.13.2 <0.16.0`. + +## 0.14.0 + +*BREAKING CHANGES* + +- Drop support for encodings other than UTF-8 and ASCII. +- Removed `parser_console.dart` library. + +## 0.13.4+1 + +* Fixes to readme and pubspec. + +## 0.13.4 + +* Require Dart 2.0 stable. + +## 0.13.3+3 + +* Do not use this tag in our systems - there was an earlier version of it + pointing to a different commit, that is still in some caches. + +* Fix missing_return analyzer errors in `processStartTag` and `processEndTag` + methods. + +## 0.13.3+2 + +* Set max SDK version to `<3.0.0`, and adjust other dependencies. + +## 0.13.3+1 + + * Updated SDK version to 2.0.0-dev.17.0 + +## 0.13.3 + + * Update the signatures of `FilteredElementList.indexOf` and + `FilteredElementList.lastIndexOf` to include type annotations. + +## 0.13.2+2 + + * Update signature for implementations of `Iterable.singleWhere` to include + optional argument. + +## 0.13.2+1 + + * Changed the implementation of `Set` and `List` classes to use base classes + from `dart:collection`. + +## 0.13.2 + + * Support the latest release of `pkg/csslib`. + +## 0.13.1 + * Update Set.difference to take a Set. + +## 0.13.0 + + * **BREAKING** Fix all [strong mode][] errors and warnings. + This involved adding more precise types on some public APIs, which is why it + may break users. + +[strong mode]: https://github.com/dart-lang/dev_compiler/blob/master/STRONG_MODE.md + +#### Pub version 0.12.2+2 + * Support `csslib` versions `0.13.x`. + +#### Pub version 0.12.2+1 + * Exclude `.packages` file from the published package. + +#### Pub version 0.12.2 + * Added `Element.endSourceSpan`, containing the span of a closing tag. + +#### Pub version 0.12.0+1 + * Support `csslib` version `0.12.0`. + +#### Rename to package:html 0.12.0 + * package has been renamed to `html` + +#### Pub version 0.12.0 + * switch from `source_maps`' `Span` class to `source_span`'s + `SourceSpan` class. + +#### Pub version 0.11.0+2 + * expand the version constraint for csslib. + +#### Pub version 0.10.0+1 + * use a more recent source_maps version. + +#### Pub version 0.10.0 + * fix how document fragments are added in NodeList.add/addAll/insertAll. + +#### Pub version 0.9.2-dev + * add Node.text, Node.append, Document.documentElement + * add Text.data, deprecate Node.value and Text.value. + * deprecate Node.$dom_nodeType + * added querySelector/querySelectorAll, deprecated query/queryAll. + This matches the current APIs in dart:html. diff --git a/pkgs/html/LICENSE b/pkgs/html/LICENSE new file mode 100644 index 000000000..1d776b2d3 --- /dev/null +++ b/pkgs/html/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2006-2012 The Authors + +Contributors: +James Graham - jg307@cam.ac.uk +Anne van Kesteren - annevankesteren@gmail.com +Lachlan Hunt - lachlan.hunt@lachy.id.au +Matt McDonald - kanashii@kanashii.ca +Sam Ruby - rubys@intertwingly.net +Ian Hickson (Google) - ian@hixie.ch +Thomas Broyer - t.broyer@ltgt.net +Jacques Distler - distler@golem.ph.utexas.edu +Henri Sivonen - hsivonen@iki.fi +Adam Barth - abarth@webkit.org +Eric Seidel - eric@webkit.org +The Mozilla Foundation (contributions from Henri Sivonen since 2008) +David Flanagan (Mozilla) - dflanagan@mozilla.com +Google LLC (contributed the Dart port) - misc@dartlang.org + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/pkgs/html/README.md b/pkgs/html/README.md new file mode 100644 index 000000000..ed1d000e0 --- /dev/null +++ b/pkgs/html/README.md @@ -0,0 +1,28 @@ +[![Build Status](https://github.com/dart-lang/tools/actions/workflows/html.yaml/badge.svg)](https://github.com/dart-lang/tools/actions/workflows/html.yaml) +[![pub package](https://img.shields.io/pub/v/html.svg)](https://pub.dev/packages/html) +[![package publisher](https://img.shields.io/pub/publisher/html.svg)](https://pub.dev/packages/html/publisher) + +A Dart implementation of an HTML5 parser. + +## Usage + +Parsing HTML is easy! + +```dart +import 'package:html/parser.dart'; + +void main() { + var document = parse( + 'Hello world! HTML5 rocks!'); + print(document.outerHtml); +} +``` + +You can pass a String or list of bytes to `parse`. There's also `parseFragment` +for parsing a document fragment, and `HtmlParser` if you want more low level +control. + +## Background + +This package was a port of the Python +[html5lib](https://github.com/html5lib/html5lib-python) library. diff --git a/pkgs/html/analysis_options.yaml b/pkgs/html/analysis_options.yaml new file mode 100644 index 000000000..90d920ee7 --- /dev/null +++ b/pkgs/html/analysis_options.yaml @@ -0,0 +1,10 @@ +include: package:dart_flutter_team_lints/analysis_options.yaml + +analyzer: + language: + strict-casts: true + strict-raw-types: true + errors: + lines_longer_than_80_chars: ignore + # https://github.com/dart-lang/linter/issues/1649 + prefer_collection_literals: ignore diff --git a/pkgs/html/example/main.dart b/pkgs/html/example/main.dart new file mode 100644 index 000000000..309ed7c61 --- /dev/null +++ b/pkgs/html/example/main.dart @@ -0,0 +1,57 @@ +import 'package:html/dom.dart'; +import 'package:html/dom_parsing.dart'; +import 'package:html/parser.dart'; + +void main(List args) { + var document = parse(''' + +

Header 1

+

Text.

+

Header 2

+ More text. +
+'''); + + // outerHtml output + print('outer html:'); + print(document.outerHtml); + + print(''); + + // visitor output + print('html visitor:'); + _Visitor().visit(document); +} + +// Note: this example visitor doesn't handle things like printing attributes and +// such. +class _Visitor extends TreeVisitor { + String indent = ''; + + @override + void visitText(Text node) { + if (node.data.trim().isNotEmpty) { + print('$indent${node.data.trim()}'); + } + } + + @override + void visitElement(Element node) { + if (isVoidElement(node.localName)) { + print('$indent<${node.localName}/>'); + } else { + print('$indent<${node.localName}>'); + indent += ' '; + visitChildren(node); + indent = indent.substring(0, indent.length - 2); + print('$indent'); + } + } + + @override + void visitChildren(Node node) { + for (var child in node.nodes) { + visit(child); + } + } +} diff --git a/pkgs/html/lib/dom.dart b/pkgs/html/lib/dom.dart new file mode 100644 index 000000000..0c6b38e58 --- /dev/null +++ b/pkgs/html/lib/dom.dart @@ -0,0 +1,1120 @@ +/// A simple tree API that results from parsing html. Intended to be compatible +/// with dart:html, but it is missing many types and APIs. +library; + +// ignore_for_file: constant_identifier_names + +// TODO(jmesserly): lots to do here. Originally I wanted to generate this using +// our Blink IDL generator, but another idea is to directly use the excellent +// http://dom.spec.whatwg.org/ and http://html.spec.whatwg.org/ and just +// implement that. + +import 'dart:collection'; + +import 'package:source_span/source_span.dart'; + +import 'dom_parsing.dart'; +import 'parser.dart'; +import 'src/constants.dart'; +import 'src/css_class_set.dart'; +import 'src/list_proxy.dart'; +import 'src/query_selector.dart' as query; +import 'src/token.dart'; +import 'src/tokenizer.dart'; + +export 'src/css_class_set.dart' show CssClassSet; + +// TODO(jmesserly): this needs to be replaced by an AttributeMap for attributes +// that exposes namespace info. +class AttributeName implements Comparable { + /// The namespace prefix, e.g. `xlink`. + final String? prefix; + + /// The attribute name, e.g. `title`. + final String name; + + /// The namespace url, e.g. `http://www.w3.org/1999/xlink` + final String namespace; + + const AttributeName(this.prefix, this.name, this.namespace); + + @override + String toString() { + // Implement: + // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#serializing-html-fragments + // If we get here we know we are xml, xmlns, or xlink, because of + // [HtmlParser.adjustForeignAttriubtes] is the only place we create + // an AttributeName. + return prefix != null ? '$prefix:$name' : name; + } + + @override + int get hashCode { + var h = prefix.hashCode; + h = 37 * (h & 0x1FFFFF) + name.hashCode; + h = 37 * (h & 0x1FFFFF) + namespace.hashCode; + return h & 0x3FFFFFFF; + } + + @override + int compareTo(Object other) { + // Not sure about this sort order + if (other is! AttributeName) return 1; + var cmp = (prefix ?? '').compareTo(other.prefix ?? ''); + if (cmp != 0) return cmp; + cmp = name.compareTo(other.name); + if (cmp != 0) return cmp; + return namespace.compareTo(other.namespace); + } + + @override + bool operator ==(Object other) => + other is AttributeName && + prefix == other.prefix && + name == other.name && + namespace == other.namespace; +} + +// http://dom.spec.whatwg.org/#parentnode +mixin _ParentNode implements Node { + // TODO(jmesserly): this is only a partial implementation + + /// Seaches for the first descendant node matching the given selectors, using + /// a preorder traversal. + /// + /// NOTE: Not all selectors from + /// [selectors level 4](http://dev.w3.org/csswg/selectors-4/) + /// are implemented. For example, nth-child does not implement An+B syntax + /// and *-of-type is not implemented. If a selector is not implemented this + /// method will throw [UnimplementedError]. + Element? querySelector(String selector) => + query.querySelector(this, selector); + + /// Returns all descendant nodes matching the given selectors, using a + /// preorder traversal. + /// + /// NOTE: Not all selectors from + /// [selectors level 4](http://dev.w3.org/csswg/selectors-4/) + /// are implemented. For example, nth-child does not implement An+B syntax + /// and *-of-type is not implemented. If a selector is not implemented this + /// method will throw [UnimplementedError]. + List querySelectorAll(String selector) => + query.querySelectorAll(this, selector); +} + +// http://dom.spec.whatwg.org/#interface-nonelementparentnode +mixin _NonElementParentNode implements _ParentNode { + // TODO(jmesserly): could be faster, should throw on invalid id. + Element? getElementById(String id) => querySelector('#$id'); +} + +// This doesn't exist as an interface in the spec, but it's useful to merge +// common methods from these: +// http://dom.spec.whatwg.org/#interface-document +// http://dom.spec.whatwg.org/#element +abstract mixin class _ElementAndDocument implements _ParentNode { + // TODO(jmesserly): could be faster, should throw on invalid tag/class names. + + List getElementsByTagName(String localName) => + querySelectorAll(localName); + + List getElementsByClassName(String classNames) => + querySelectorAll(classNames.splitMapJoin(' ', + onNonMatch: (m) => m.isNotEmpty ? '.$m' : m, onMatch: (m) => '')); +} + +/// Really basic implementation of a DOM-core like Node. +abstract class Node { + static const int ATTRIBUTE_NODE = 2; + static const int CDATA_SECTION_NODE = 4; + static const int COMMENT_NODE = 8; + static const int DOCUMENT_FRAGMENT_NODE = 11; + static const int DOCUMENT_NODE = 9; + static const int DOCUMENT_TYPE_NODE = 10; + static const int ELEMENT_NODE = 1; + static const int ENTITY_NODE = 6; + static const int ENTITY_REFERENCE_NODE = 5; + static const int NOTATION_NODE = 12; + static const int PROCESSING_INSTRUCTION_NODE = 7; + static const int TEXT_NODE = 3; + + /// The parent of the current node (or null for the document node). + Node? parentNode; + + /// The parent element of this node. + /// + /// Returns null if this node either does not have a parent or its parent is + /// not an element. + Element? get parent { + final parentNode = this.parentNode; + return parentNode is Element ? parentNode : null; + } + + // TODO(jmesserly): should move to Element. + /// A map holding name, value pairs for attributes of the node. + /// + /// Note that attribute order needs to be stable for serialization, so we use + /// a LinkedHashMap. Each key is a [String] or [AttributeName]. + LinkedHashMap attributes = LinkedHashMap(); + + /// A list of child nodes of the current node. This must + /// include all elements but not necessarily other node types. + late final nodes = NodeList._(this); + + late final List children = FilteredElementList(this); + + // TODO(jmesserly): consider using an Expando for this, and put it in + // dom_parsing. Need to check the performance affect. + /// The source span of this node, if it was created by the [HtmlParser]. + FileSpan? sourceSpan; + + /// The attribute spans if requested. Otherwise null. + LinkedHashMap? _attributeSpans; + LinkedHashMap? _attributeValueSpans; + + Node._(); + + /// If [sourceSpan] is available, this contains the spans of each attribute. + /// The span of an attribute is the entire attribute, including the name and + /// quotes (if any). For example, the span of "attr" in `` + /// would be the text `attr="value"`. + LinkedHashMap? get attributeSpans { + _ensureAttributeSpans(); + return _attributeSpans; + } + + /// If [sourceSpan] is available, this contains the spans of each attribute's + /// value. Unlike [attributeSpans], this span will include only the value. + /// For example, the value span of "attr" in `` would be the + /// text `value`. + LinkedHashMap? get attributeValueSpans { + _ensureAttributeSpans(); + return _attributeValueSpans; + } + + /// Returns a copy of this node. + /// + /// If [deep] is `true`, then all of this node's children and decendents are + /// copied as well. If [deep] is `false`, then only this node is copied. + Node clone(bool deep); + + int get nodeType; + + // http://domparsing.spec.whatwg.org/#extensions-to-the-element-interface + String get _outerHtml { + final str = StringBuffer(); + _addOuterHtml(str); + return str.toString(); + } + + String get _innerHtml { + final str = StringBuffer(); + _addInnerHtml(str); + return str.toString(); + } + + // Implemented per: http://dom.spec.whatwg.org/#dom-node-textcontent + String? get text => null; + + set text(String? value) {} + + void append(Node node) => nodes.add(node); + + Node? get firstChild => nodes.isNotEmpty ? nodes[0] : null; + + void _addOuterHtml(StringBuffer str); + + void _addInnerHtml(StringBuffer str) { + for (var child in nodes) { + child._addOuterHtml(str); + } + } + + Node remove() { + // TODO(jmesserly): is parent == null an error? + parentNode?.nodes.remove(this); + return this; + } + + /// Insert [node] as a child of the current node, before [refNode] in the + void insertBefore(Node node, Node? refNode) { + if (refNode == null) { + nodes.add(node); + } else { + nodes.insert(nodes.indexOf(refNode), node); + } + } + + /// Replaces this node with another node. + Node replaceWith(Node otherNode) { + if (parentNode == null) { + throw UnsupportedError('Node must have a parent to replace it.'); + } + parentNode!.nodes[parentNode!.nodes.indexOf(this)] = otherNode; + return this; + } + + // TODO(jmesserly): should this be a property or remove? + /// Return true if the node has children or text. + bool hasContent() => nodes.isNotEmpty; + + /// Move all the children of the current node to [newParent]. + /// This is needed so that trees that don't store text as nodes move the + /// text in the correct way. + void reparentChildren(Node newParent) { + newParent.nodes.addAll(nodes); + nodes.clear(); + } + + bool hasChildNodes() => nodes.isNotEmpty; + + bool contains(Node node) => nodes.contains(node); + + /// Initialize [attributeSpans] using [sourceSpan]. + void _ensureAttributeSpans() { + if (_attributeSpans != null) return; + + final attributeSpans = _attributeSpans = LinkedHashMap(); + final attributeValueSpans = + _attributeValueSpans = LinkedHashMap(); + + if (sourceSpan == null) return; + + final tokenizer = HtmlTokenizer(sourceSpan!.text, + generateSpans: true, attributeSpans: true); + + tokenizer.moveNext(); + final token = tokenizer.current as StartTagToken; + + if (token.attributeSpans == null) return; // no attributes + + for (var attr in token.attributeSpans!) { + final offset = sourceSpan!.start.offset; + final name = attr.name!; + attributeSpans[name] = + sourceSpan!.file.span(offset + attr.start, offset + attr.end); + if (attr.startValue != null) { + attributeValueSpans[name] = sourceSpan!.file + .span(offset + attr.startValue!, offset + attr.endValue); + } + } + } + + T _clone(T shallowClone, bool deep) { + if (deep) { + for (var child in nodes) { + shallowClone.append(child.clone(true)); + } + } + return shallowClone; + } +} + +class Document extends Node + with _ParentNode, _NonElementParentNode, _ElementAndDocument { + Document() : super._(); + + factory Document.html(String html) => parse(html); + + @override + int get nodeType => Node.DOCUMENT_NODE; + + // TODO(jmesserly): optmize this if needed + Element? get documentElement => querySelector('html'); + + Element? get head => documentElement?.querySelector('head'); + + Element? get body => documentElement?.querySelector('body'); + + /// Returns a fragment of HTML or XML that represents the element and its + /// contents. + // TODO(jmesserly): this API is not specified in: + // nor is it in dart:html, instead + // only Element has outerHtml. However it is quite useful. Should we move it + // to dom_parsing, where we keep other custom APIs? + String get outerHtml => _outerHtml; + + @override + String toString() => '#document'; + + @override + void _addOuterHtml(StringBuffer str) => _addInnerHtml(str); + + @override + Document clone(bool deep) => _clone(Document(), deep); + + Element createElement(String tag) => Element.tag(tag); + + // TODO(jmesserly): this is only a partial implementation of: + // http://dom.spec.whatwg.org/#dom-document-createelementns + Element createElementNS(String? namespaceUri, String? tag) { + if (namespaceUri == '') namespaceUri = null; + return Element._(tag, namespaceUri); + } + + DocumentFragment createDocumentFragment() => DocumentFragment(); +} + +class DocumentFragment extends Node with _ParentNode, _NonElementParentNode { + DocumentFragment() : super._(); + + factory DocumentFragment.html(String html) => parseFragment(html); + + @override + int get nodeType => Node.DOCUMENT_FRAGMENT_NODE; + + /// Returns a fragment of HTML or XML that represents the element and its + /// contents. + // TODO(jmesserly): this API is not specified in: + // nor is it in dart:html, instead + // only Element has outerHtml. However it is quite useful. Should we move it + // to dom_parsing, where we keep other custom APIs? + String get outerHtml => _outerHtml; + + @override + String toString() => '#document-fragment'; + + @override + DocumentFragment clone(bool deep) => _clone(DocumentFragment(), deep); + + @override + void _addOuterHtml(StringBuffer str) => _addInnerHtml(str); + + @override + String? get text => _getText(this); + + @override + set text(String? value) => _setText(this, value); +} + +class DocumentType extends Node { + final String? name; + final String? publicId; + final String? systemId; + + DocumentType(this.name, this.publicId, this.systemId) : super._(); + + @override + int get nodeType => Node.DOCUMENT_TYPE_NODE; + + @override + String toString() { + if (publicId != null || systemId != null) { + // TODO(jmesserly): the html5 serialization spec does not add these. But + // it seems useful, and the parser can handle it, so for now keeping it. + final pid = publicId ?? ''; + final sid = systemId ?? ''; + return ''; + } else { + return ''; + } + } + + @override + void _addOuterHtml(StringBuffer str) { + str.write(toString()); + } + + @override + DocumentType clone(bool deep) => DocumentType(name, publicId, systemId); +} + +class Text extends Node { + /// The text node's data, stored as either a String or StringBuffer. + /// We support storing a StringBuffer here to support fast [appendData]. + /// It will flatten back to a String on read. + Object _data; + + Text(String? data) + : _data = data ?? '', + super._(); + + @override + int get nodeType => Node.TEXT_NODE; + + String get data => _data = _data.toString(); + + set data(String value) { + // Handle unsound null values. + _data = identical(value, null) ? '' : value; + } + + @override + String toString() => '"$data"'; + + @override + void _addOuterHtml(StringBuffer str) => writeTextNodeAsHtml(str, this); + + @override + Text clone(bool deep) => Text(data); + + void appendData(String data) { + if (_data is! StringBuffer) _data = StringBuffer(_data); + final sb = _data as StringBuffer; + sb.write(data); + } + + @override + String get text => data; + + @override + // Text has a non-nullable `text` field, while Node has a nullable field. + set text(covariant String value) { + data = value; + } +} + +// TODO(jmesserly): Elements should have a pointer back to their document +class Element extends Node with _ParentNode, _ElementAndDocument { + final String? namespaceUri; + + /// The [local name](http://dom.spec.whatwg.org/#concept-element-local-name) + /// of this element. + final String? localName; + + // TODO(jmesserly): consider using an Expando for this, and put it in + // dom_parsing. Need to check the performance affect. + /// The source span of the end tag this element, if it was created by the + /// [HtmlParser]. May be `null` if does not have an implicit end tag. + FileSpan? endSourceSpan; + + Element._(this.localName, [this.namespaceUri]) : super._(); + + Element.tag(this.localName) + : namespaceUri = Namespaces.html, + super._(); + + static final _startTagRegexp = RegExp('<(\\w+)'); + + static final _customParentTagMap = const { + 'body': 'html', + 'head': 'html', + 'caption': 'table', + 'td': 'tr', + 'colgroup': 'table', + 'col': 'colgroup', + 'tr': 'tbody', + 'tbody': 'table', + 'tfoot': 'table', + 'thead': 'table', + 'track': 'audio', + }; + + // TODO(jmesserly): this is from dart:html _ElementFactoryProvider... + // TODO(jmesserly): have a look at fixing some things in dart:html, in + // particular: is the parent tag map complete? Is it faster without regexp? + // TODO(jmesserly): for our version we can do something smarter in the parser. + // All we really need is to set the correct parse state. + factory Element.html(String html) { + // TODO(jacobr): this method can be made more robust and performant. + // 1) Cache the dummy parent elements required to use innerHTML rather than + // creating them every call. + // 2) Verify that the html does not contain leading or trailing text nodes. + // 3) Verify that the html does not contain both and tags. + // 4) Detach the created element from its dummy parent. + var parentTag = 'div'; + String? tag; + final match = _startTagRegexp.firstMatch(html); + if (match != null) { + tag = match.group(1)!.toLowerCase(); + if (_customParentTagMap.containsKey(tag)) { + parentTag = _customParentTagMap[tag]!; + } + } + + final fragment = parseFragment(html, container: parentTag); + Element element; + if (fragment.children.length == 1) { + element = fragment.children[0]; + } else if (parentTag == 'html' && fragment.children.length == 2) { + // You'll always get a head and a body when starting from html. + element = fragment.children[tag == 'head' ? 0 : 1]; + } else { + throw ArgumentError('HTML had ${fragment.children.length} ' + 'top level elements but 1 expected'); + } + element.remove(); + return element; + } + + @override + int get nodeType => Node.ELEMENT_NODE; + + // TODO(jmesserly): we can make this faster + Element? get previousElementSibling { + if (parentNode == null) return null; + final siblings = parentNode!.nodes; + for (var i = siblings.indexOf(this) - 1; i >= 0; i--) { + final s = siblings[i]; + if (s is Element) return s; + } + return null; + } + + Element? get nextElementSibling { + final parentNode = this.parentNode; + if (parentNode == null) return null; + final siblings = parentNode.nodes; + for (var i = siblings.indexOf(this) + 1; i < siblings.length; i++) { + final s = siblings[i]; + if (s is Element) return s; + } + return null; + } + + @override + String toString() { + final prefix = Namespaces.getPrefix(namespaceUri); + return "<${prefix == null ? '' : '$prefix '}$localName>"; + } + + @override + String get text => _getText(this); + + @override + set text(String? value) => _setText(this, value); + + /// Returns a fragment of HTML or XML that represents the element and its + /// contents. + String get outerHtml => _outerHtml; + + /// Returns a fragment of HTML or XML that represents the element's contents. + /// Can be set, to replace the contents of the element with nodes parsed from + /// the given string. + String get innerHtml => _innerHtml; + + // TODO(jmesserly): deprecate in favor of: + // + set innerHtml(String value) { + nodes.clear(); + // TODO(jmesserly): should be able to get the same effect by adding the + // fragment directly. + nodes.addAll(parseFragment(value, container: localName!).nodes); + } + + @override + void _addOuterHtml(StringBuffer str) { + // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#serializing-html-fragments + // Element is the most complicated one. + str.write('<'); + str.write(_getSerializationPrefix(namespaceUri)); + str.write(localName); + + if (attributes.isNotEmpty) { + attributes.forEach((key, v) { + // Note: AttributeName.toString handles serialization of attribute + // namespace, if needed. + str.write(' '); + str.write(key); + str.write('="'); + str.write(htmlSerializeEscape(v, attributeMode: true)); + str.write('"'); + }); + } + + str.write('>'); + + if (nodes.isNotEmpty) { + if (localName == 'pre' || + localName == 'textarea' || + localName == 'listing') { + final first = nodes[0]; + if (first is Text && first.data.startsWith('\n')) { + // These nodes will remove a leading \n at parse time, so if we still + // have one, it means we started with two. Add it back. + str.write('\n'); + } + } + + _addInnerHtml(str); + } + + // void elements must not have an end tag + // http://dev.w3.org/html5/markup/syntax.html#void-elements + if (!isVoidElement(localName)) str.write(''); + } + + static String _getSerializationPrefix(String? uri) { + if (uri == null || + uri == Namespaces.html || + uri == Namespaces.mathml || + uri == Namespaces.svg) { + return ''; + } + final prefix = Namespaces.getPrefix(uri); + // TODO(jmesserly): the spec doesn't define "qualified name". + // I'm not sure if this is correct, but it should parse reasonably. + return prefix == null ? '' : '$prefix:'; + } + + @override + Element clone(bool deep) { + final result = Element._(localName, namespaceUri) + ..attributes = LinkedHashMap.from(attributes); + return _clone(result, deep); + } + + // http://dom.spec.whatwg.org/#dom-element-id + String get id { + final result = attributes['id']; + return result ?? ''; + } + + set id(String value) { + attributes['id'] = value; + } + + // http://dom.spec.whatwg.org/#dom-element-classname + String get className { + final result = attributes['class']; + return result ?? ''; + } + + set className(String value) { + attributes['class'] = value; + } + + /// The set of CSS classes applied to this element. + /// + /// This set makes it easy to add, remove or toggle the classes applied to + /// this element. + /// + /// element.classes.add('selected'); + /// element.classes.toggle('isOnline'); + /// element.classes.remove('selected'); + CssClassSet get classes => ElementCssClassSet(this); +} + +class Comment extends Node { + String? data; + + Comment(this.data) : super._(); + + @override + int get nodeType => Node.COMMENT_NODE; + + @override + String toString() => ''; + + @override + void _addOuterHtml(StringBuffer str) { + str.write(''); + } + + @override + Comment clone(bool deep) => Comment(data); + + @override + String? get text => data; + + @override + set text(String? value) { + data = value; + } +} + +// TODO(jmesserly): fix this to extend one of the corelib classes if possible. +// (The requirement to remove the node from the old node list makes it tricky.) +// TODO(jmesserly): is there any way to share code with the _NodeListImpl? +class NodeList extends ListProxy { + final Node _parent; + + NodeList._(this._parent); + + Node _setParent(Node node) { + // Note: we need to remove the node from its previous parent node, if any, + // before updating its parent pointer to point at our parent. + node.remove(); + node.parentNode = _parent; + return node; + } + + @override + void add(Node element) { + if (element is DocumentFragment) { + addAll(element.nodes); + } else { + super.add(_setParent(element)); + } + } + + void addLast(Node value) => add(value); + + @override + void addAll(Iterable iterable) { + // Note: we need to be careful if collection is another NodeList. + // In particular: + // 1. we need to copy the items before updating their parent pointers, + // _flattenDocFragments does a copy internally. + // 2. we should update parent pointers in reverse order. That way they + // are removed from the original NodeList (if any) from the end, which + // is faster. + final list = _flattenDocFragments(iterable); + for (var node in list.reversed) { + _setParent(node); + } + super.addAll(list); + } + + @override + void insert(int index, Node element) { + if (element is DocumentFragment) { + insertAll(index, element.nodes); + } else { + super.insert(index, _setParent(element)); + } + } + + @override + Node removeLast() => super.removeLast()..parentNode = null; + + @override + Node removeAt(int index) => super.removeAt(index)..parentNode = null; + + @override + void clear() { + for (var node in this) { + node.parentNode = null; + } + super.clear(); + } + + @override + void operator []=(int index, Node value) { + if (value is DocumentFragment) { + removeAt(index); + insertAll(index, value.nodes); + } else { + this[index].parentNode = null; + super[index] = _setParent(value); + } + } + + // TODO(jmesserly): These aren't implemented in DOM _NodeListImpl, see + // http://code.google.com/p/dart/issues/detail?id=5371 + @override + void setRange(int start, int end, Iterable iterable, + [int skipCount = 0]) { + var fromVar = iterable as List; + if (fromVar is NodeList) { + // Note: this is presumed to make a copy + fromVar = fromVar.sublist(skipCount, skipCount + end); + } + // Note: see comment in [addAll]. We need to be careful about the order of + // operations if [from] is also a NodeList. + for (var i = end - 1; i >= 0; i--) { + this[start + i] = fromVar[skipCount + i]; + } + } + + @override + void replaceRange(int start, int end, Iterable newContents) { + removeRange(start, end); + insertAll(start, newContents); + } + + @override + void removeRange(int start, int end) { + for (var i = start; i < end; i++) { + this[i].parentNode = null; + } + super.removeRange(start, end); + } + + @override + void removeWhere(bool Function(Node) test) { + for (var node in where(test)) { + node.parentNode = null; + } + super.removeWhere(test); + } + + @override + void retainWhere(bool Function(Node) test) { + for (var node in where((n) => !test(n))) { + node.parentNode = null; + } + super.retainWhere(test); + } + + @override + void insertAll(int index, Iterable iterable) { + // Note: we need to be careful how we copy nodes. See note in addAll. + final list = _flattenDocFragments(iterable); + for (var node in list.reversed) { + _setParent(node); + } + super.insertAll(index, list); + } + + List _flattenDocFragments(Iterable collection) { + // Note: this function serves two purposes: + // * it flattens document fragments + // * it creates a copy of [collections] when `collection is NodeList`. + final result = []; + for (var node in collection) { + if (node is DocumentFragment) { + result.addAll(node.nodes); + } else { + result.add(node); + } + } + return result; + } +} + +/// An indexable collection of a node's descendants in the document tree, +/// filtered so that only elements are in the collection. +// TODO(jmesserly): this was copied from dart:html +// TODO(jmesserly): "implements List" is a workaround for analyzer bug. +class FilteredElementList extends IterableBase + with ListMixin + implements List { + final List _childNodes; + + /// Creates a collection of the elements that descend from a node. + /// + /// Example usage: + /// + /// var filteredElements = new FilteredElementList(query("#container")); + /// // filteredElements is [a, b, c]. + FilteredElementList(Node node) : _childNodes = node.nodes; + + // We can't memoize this, since it's possible that children will be messed + // with externally to this class. + // + // TODO(nweiz): we don't always need to create a new list. For example + // forEach, every, any, ... could directly work on the _childNodes. + List get _filtered => + _childNodes.whereType().toList(growable: false); + + @override + void forEach(void Function(Element) action) { + _filtered.forEach(action); + } + + @override + void operator []=(int index, Element value) { + this[index].replaceWith(value); + } + + @override + set length(int newLength) { + final len = length; + if (newLength >= len) { + return; + } else if (newLength < 0) { + throw ArgumentError('Invalid list length'); + } + + removeRange(newLength, len); + } + + @override + String join([String separator = '']) => _filtered.join(separator); + + @override + void add(Element element) { + _childNodes.add(element); + } + + @override + void addAll(Iterable iterable) { + for (var element in iterable) { + add(element); + } + } + + @override + bool contains(Object? element) { + return element is Element && _childNodes.contains(element); + } + + @override + Iterable get reversed => _filtered.reversed; + + @override + void sort([int Function(Element, Element)? compare]) { + throw UnsupportedError('TODO(jacobr): should we impl?'); + } + + @override + void setRange(int start, int end, Iterable iterable, + [int skipCount = 0]) { + throw UnimplementedError(); + } + + @override + void fillRange(int start, int end, [Element? fill]) { + throw UnimplementedError(); + } + + @override + void replaceRange(int start, int end, Iterable newContents) { + throw UnimplementedError(); + } + + @override + void removeRange(int start, int end) { + _filtered.sublist(start, end).forEach((el) => el.remove()); + } + + @override + void clear() { + // Currently, ElementList#clear clears even non-element nodes, so we follow + // that behavior. + _childNodes.clear(); + } + + @override + Element removeLast() { + return last..remove(); + } + + @override + Iterable map(T Function(Element) f) => _filtered.map(f); + + @override + Iterable where(bool Function(Element) test) => _filtered.where(test); + + @override + Iterable expand(Iterable Function(Element) f) => _filtered.expand(f); + + @override + void insert(int index, Element element) { + _childNodes.insert(index, element); + } + + @override + void insertAll(int index, Iterable iterable) { + _childNodes.insertAll(index, iterable); + } + + @override + Element removeAt(int index) { + final result = this[index]; + result.remove(); + return result; + } + + @override + bool remove(Object? element) { + if (element is! Element) return false; + for (var i = 0; i < length; i++) { + final indexElement = this[i]; + if (identical(indexElement, element)) { + indexElement.remove(); + return true; + } + } + return false; + } + + @override + Element reduce(Element Function(Element, Element) combine) { + return _filtered.reduce(combine); + } + + @override + T fold( + T initialValue, T Function(T previousValue, Element element) combine) { + return _filtered.fold(initialValue, combine); + } + + @override + bool every(bool Function(Element) test) => _filtered.every(test); + + @override + bool any(bool Function(Element) test) => _filtered.any(test); + + @override + List toList({bool growable = true}) => + List.of(this, growable: growable); + + @override + Set toSet() => Set.from(this); + + @override + Element firstWhere(bool Function(Element) test, + {Element Function()? orElse}) { + return _filtered.firstWhere(test, orElse: orElse); + } + + @override + Element lastWhere(bool Function(Element) test, {Element Function()? orElse}) { + return _filtered.lastWhere(test, orElse: orElse); + } + + @override + Element singleWhere(bool Function(Element) test, + {Element Function()? orElse}) { + if (orElse != null) throw UnimplementedError('orElse'); + return _filtered.singleWhere(test); + } + + @override + Element elementAt(int index) { + return this[index]; + } + + @override + bool get isEmpty => _filtered.isEmpty; + + @override + int get length => _filtered.length; + + @override + Element operator [](int index) => _filtered[index]; + + @override + Iterator get iterator => _filtered.iterator; + + @override + List sublist(int start, [int? end]) => _filtered.sublist(start, end); + + @override + Iterable getRange(int start, int end) => + _filtered.getRange(start, end); + + @override + int indexOf(Object? element, [int start = 0]) => + // Cast forced by ListMixin https://github.com/dart-lang/sdk/issues/31311 + _filtered.indexOf(element as Element, start); + + @override + int lastIndexOf(Object? element, [int? start]) { + start ??= length - 1; + // Cast forced by ListMixin https://github.com/dart-lang/sdk/issues/31311 + return _filtered.lastIndexOf(element as Element, start); + } + + @override + Element get first => _filtered.first; + + @override + Element get last => _filtered.last; + + @override + Element get single => _filtered.single; +} + +// http://dom.spec.whatwg.org/#dom-node-textcontent +// For Element and DocumentFragment +String _getText(Node node) => (_ConcatTextVisitor()..visit(node)).toString(); + +void _setText(Node node, String? value) { + node.nodes.clear(); + node.append(Text(value)); +} + +class _ConcatTextVisitor extends TreeVisitor { + final _str = StringBuffer(); + + @override + String toString() => _str.toString(); + + @override + void visitText(Text node) { + _str.write(node.data); + } +} diff --git a/pkgs/html/lib/dom_parsing.dart b/pkgs/html/lib/dom_parsing.dart new file mode 100644 index 000000000..69b0bbd4d --- /dev/null +++ b/pkgs/html/lib/dom_parsing.dart @@ -0,0 +1,157 @@ +/// This library contains extra APIs that aren't in the DOM, but are useful +/// when interacting with the parse tree. +library; + +import 'dom.dart'; +import 'html_escape.dart'; +import 'src/constants.dart' show rcdataElements; + +// Export a function which was previously declared here. +export 'html_escape.dart'; + +/// A simple tree visitor for the DOM nodes. +class TreeVisitor { + void visit(Node node) { + return switch (node.nodeType) { + Node.ELEMENT_NODE => visitElement(node as Element), + Node.TEXT_NODE => visitText(node as Text), + Node.COMMENT_NODE => visitComment(node as Comment), + Node.DOCUMENT_FRAGMENT_NODE => + visitDocumentFragment(node as DocumentFragment), + Node.DOCUMENT_NODE => visitDocument(node as Document), + Node.DOCUMENT_TYPE_NODE => visitDocumentType(node as DocumentType), + _ => throw UnsupportedError('DOM node type ${node.nodeType}') + }; + } + + void visitChildren(Node node) { + // Allow for mutations (remove works) while iterating. + for (var child in node.nodes.toList(growable: false)) { + visit(child); + } + } + + /// The fallback handler if the more specific visit method hasn't been + /// overriden. Only use this from a subclass of [TreeVisitor], otherwise + /// call [visit] instead. + void visitNodeFallback(Node node) => visitChildren(node); + + void visitDocument(Document node) => visitNodeFallback(node); + + void visitDocumentType(DocumentType node) => visitNodeFallback(node); + + void visitText(Text node) => visitNodeFallback(node); + + // TODO(jmesserly): visit attributes. + void visitElement(Element node) => visitNodeFallback(node); + + void visitComment(Comment node) => visitNodeFallback(node); + + void visitDocumentFragment(DocumentFragment node) => visitNodeFallback(node); +} + +/// Converts the DOM tree into an HTML string with code markup suitable for +/// displaying the HTML's source code with CSS colors for different parts of the +/// markup. See also [CodeMarkupVisitor]. +String htmlToCodeMarkup(Node node) { + return (CodeMarkupVisitor()..visit(node)).toString(); +} + +/// Converts the DOM tree into an HTML string with code markup suitable for +/// displaying the HTML's source code with CSS colors for different parts of the +/// markup. See also [htmlToCodeMarkup]. +class CodeMarkupVisitor extends TreeVisitor { + final StringBuffer _str; + + CodeMarkupVisitor() : _str = StringBuffer(); + + @override + String toString() => _str.toString(); + + @override + void visitDocument(Document node) { + _str.write('
');
+    visitChildren(node);
+    _str.write('
'); + } + + @override + void visitDocumentType(DocumentType node) { + _str.write('<!DOCTYPE ${node.name}>' + ''); + } + + @override + void visitText(Text node) { + writeTextNodeAsHtml(_str, node); + } + + @override + void visitElement(Element node) { + final tag = node.localName; + _str.write('<$tag'); + if (node.attributes.isNotEmpty) { + node.attributes.forEach((key, v) { + v = htmlSerializeEscape(v, attributeMode: true); + _str.write(' $key' + '="$v"'); + }); + } + if (node.nodes.isNotEmpty) { + _str.write('>'); + visitChildren(node); + } else if (isVoidElement(tag)) { + _str.write('>'); + return; + } + _str.write('</$tag>'); + } + + @override + void visitComment(Comment node) { + final data = htmlSerializeEscape(node.data!); + _str.write('<!--$data-->'); + } +} + +/// Returns true if this tag name is a void element. +/// This method is useful to a pretty printer, because void elements must not +/// have an end tag. +/// See also: . +bool isVoidElement(String? tagName) { + switch (tagName) { + case 'area': + case 'base': + case 'br': + case 'col': + case 'command': + case 'embed': + case 'hr': + case 'img': + case 'input': + case 'keygen': + case 'link': + case 'meta': + case 'param': + case 'source': + case 'track': + case 'wbr': + return true; + } + return false; +} + +/// Serialize text node according to: +/// +void writeTextNodeAsHtml(StringBuffer str, Text node) { + // Don't escape text for certain elements, notably BAR +#errors +#document +| +| +| +| "FOO" +| BAR +#errors +#document +| +| +| +| "FOO" +| BAR +#errors +#document +| +| +| +| "FOO" +| BAR +#errors +#document +| +| +| +| "FOO" +| BAR +#errors +#document +| +| +| +| "FOO" +| BAR +#errors +#document +| +| +| +| "FOO" +| BAR +#errors +#document +| +| +| +| "FOO" +| BAR +#errors +#document +| +| +| +| "FOO" +| BAR +#errors +#document +| +| +| +| "FOO" +| BAR +#errors +#document +| +| +| +| "FOO" +| BAR +#errors +#document +| +| +| +| "FOO" +| BAR +#errors +#document +| +| +| +| "FOO" +| BAR +#errors +#document +| +| +| +| "FOO" +| BAR +#errors +#document +| +| +| +| "FOO" +| QUX +#errors +#document +| +| +| +| "FOO" +| --> EOF +#errors +#document +| +| +| +| +#errors +Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE. +Line: 1 Col: 51 Unexpected end of file. Expected end tag (style). +#document +| +| +|

+#errors +Line: 1 Col: 9 Unexpected end tag (strong). Expected DOCTYPE. +Line: 1 Col: 9 Unexpected end tag (strong) after the (implied) root element. +Line: 1 Col: 13 Unexpected end tag (b) after the (implied) root element. +Line: 1 Col: 18 Unexpected end tag (em) after the (implied) root element. +Line: 1 Col: 22 Unexpected end tag (i) after the (implied) root element. +Line: 1 Col: 26 Unexpected end tag (u) after the (implied) root element. +Line: 1 Col: 35 Unexpected end tag (strike) after the (implied) root element. +Line: 1 Col: 39 Unexpected end tag (s) after the (implied) root element. +Line: 1 Col: 47 Unexpected end tag (blink) after the (implied) root element. +Line: 1 Col: 52 Unexpected end tag (tt) after the (implied) root element. +Line: 1 Col: 58 Unexpected end tag (pre) after the (implied) root element. +Line: 1 Col: 64 Unexpected end tag (big) after the (implied) root element. +Line: 1 Col: 72 Unexpected end tag (small) after the (implied) root element. +Line: 1 Col: 79 Unexpected end tag (font) after the (implied) root element. +Line: 1 Col: 88 Unexpected end tag (select) after the (implied) root element. +Line: 1 Col: 93 Unexpected end tag (h1) after the (implied) root element. +Line: 1 Col: 98 Unexpected end tag (h2) after the (implied) root element. +Line: 1 Col: 103 Unexpected end tag (h3) after the (implied) root element. +Line: 1 Col: 108 Unexpected end tag (h4) after the (implied) root element. +Line: 1 Col: 113 Unexpected end tag (h5) after the (implied) root element. +Line: 1 Col: 118 Unexpected end tag (h6) after the (implied) root element. +Line: 1 Col: 125 Unexpected end tag (body) after the (implied) root element. +Line: 1 Col: 130 Unexpected end tag (br). Treated as br element. +Line: 1 Col: 134 End tag (a) violates step 1, paragraph 1 of the adoption agency algorithm. +Line: 1 Col: 140 This element (img) has no end tag. +Line: 1 Col: 148 Unexpected end tag (title). Ignored. +Line: 1 Col: 155 Unexpected end tag (span). Ignored. +Line: 1 Col: 163 Unexpected end tag (style). Ignored. +Line: 1 Col: 172 Unexpected end tag (script). Ignored. +Line: 1 Col: 180 Unexpected end tag (table). Ignored. +Line: 1 Col: 185 Unexpected end tag (th). Ignored. +Line: 1 Col: 190 Unexpected end tag (td). Ignored. +Line: 1 Col: 195 Unexpected end tag (tr). Ignored. +Line: 1 Col: 203 This element (frame) has no end tag. +Line: 1 Col: 210 This element (area) has no end tag. +Line: 1 Col: 217 Unexpected end tag (link). Ignored. +Line: 1 Col: 225 This element (param) has no end tag. +Line: 1 Col: 230 This element (hr) has no end tag. +Line: 1 Col: 238 This element (input) has no end tag. +Line: 1 Col: 244 Unexpected end tag (col). Ignored. +Line: 1 Col: 251 Unexpected end tag (base). Ignored. +Line: 1 Col: 258 Unexpected end tag (meta). Ignored. +Line: 1 Col: 269 This element (basefont) has no end tag. +Line: 1 Col: 279 This element (bgsound) has no end tag. +Line: 1 Col: 287 This element (embed) has no end tag. +Line: 1 Col: 296 This element (spacer) has no end tag. +Line: 1 Col: 300 Unexpected end tag (p). Ignored. +Line: 1 Col: 305 End tag (dd) seen too early. Expected other end tag. +Line: 1 Col: 310 End tag (dt) seen too early. Expected other end tag. +Line: 1 Col: 320 Unexpected end tag (caption). Ignored. +Line: 1 Col: 331 Unexpected end tag (colgroup). Ignored. +Line: 1 Col: 339 Unexpected end tag (tbody). Ignored. +Line: 1 Col: 347 Unexpected end tag (tfoot). Ignored. +Line: 1 Col: 355 Unexpected end tag (thead). Ignored. +Line: 1 Col: 365 End tag (address) seen too early. Expected other end tag. +Line: 1 Col: 378 End tag (blockquote) seen too early. Expected other end tag. +Line: 1 Col: 387 End tag (center) seen too early. Expected other end tag. +Line: 1 Col: 393 Unexpected end tag (dir). Ignored. +Line: 1 Col: 399 End tag (div) seen too early. Expected other end tag. +Line: 1 Col: 404 End tag (dl) seen too early. Expected other end tag. +Line: 1 Col: 415 End tag (fieldset) seen too early. Expected other end tag. +Line: 1 Col: 425 End tag (listing) seen too early. Expected other end tag. +Line: 1 Col: 432 End tag (menu) seen too early. Expected other end tag. +Line: 1 Col: 437 End tag (ol) seen too early. Expected other end tag. +Line: 1 Col: 442 End tag (ul) seen too early. Expected other end tag. +Line: 1 Col: 447 End tag (li) seen too early. Expected other end tag. +Line: 1 Col: 454 End tag (nobr) violates step 1, paragraph 1 of the adoption agency algorithm. +Line: 1 Col: 460 This element (wbr) has no end tag. +Line: 1 Col: 476 End tag (button) seen too early. Expected other end tag. +Line: 1 Col: 486 End tag (marquee) seen too early. Expected other end tag. +Line: 1 Col: 495 End tag (object) seen too early. Expected other end tag. +Line: 1 Col: 513 Unexpected end tag (html). Ignored. +Line: 1 Col: 513 Unexpected end tag (frameset). Ignored. +Line: 1 Col: 520 Unexpected end tag (head). Ignored. +Line: 1 Col: 529 Unexpected end tag (iframe). Ignored. +Line: 1 Col: 537 This element (image) has no end tag. +Line: 1 Col: 547 This element (isindex) has no end tag. +Line: 1 Col: 557 Unexpected end tag (noembed). Ignored. +Line: 1 Col: 568 Unexpected end tag (noframes). Ignored. +Line: 1 Col: 579 Unexpected end tag (noscript). Ignored. +Line: 1 Col: 590 Unexpected end tag (optgroup). Ignored. +Line: 1 Col: 599 Unexpected end tag (option). Ignored. +Line: 1 Col: 611 Unexpected end tag (plaintext). Ignored. +Line: 1 Col: 622 Unexpected end tag (textarea). Ignored. +#document +| +| +| +|
+|

+ +#data +

+#errors +Line: 1 Col: 7 Unexpected start tag (table). Expected DOCTYPE. +Line: 1 Col: 20 Unexpected end tag (strong) in table context caused voodoo mode. +Line: 1 Col: 20 End tag (strong) violates step 1, paragraph 1 of the adoption agency algorithm. +Line: 1 Col: 24 Unexpected end tag (b) in table context caused voodoo mode. +Line: 1 Col: 24 End tag (b) violates step 1, paragraph 1 of the adoption agency algorithm. +Line: 1 Col: 29 Unexpected end tag (em) in table context caused voodoo mode. +Line: 1 Col: 29 End tag (em) violates step 1, paragraph 1 of the adoption agency algorithm. +Line: 1 Col: 33 Unexpected end tag (i) in table context caused voodoo mode. +Line: 1 Col: 33 End tag (i) violates step 1, paragraph 1 of the adoption agency algorithm. +Line: 1 Col: 37 Unexpected end tag (u) in table context caused voodoo mode. +Line: 1 Col: 37 End tag (u) violates step 1, paragraph 1 of the adoption agency algorithm. +Line: 1 Col: 46 Unexpected end tag (strike) in table context caused voodoo mode. +Line: 1 Col: 46 End tag (strike) violates step 1, paragraph 1 of the adoption agency algorithm. +Line: 1 Col: 50 Unexpected end tag (s) in table context caused voodoo mode. +Line: 1 Col: 50 End tag (s) violates step 1, paragraph 1 of the adoption agency algorithm. +Line: 1 Col: 58 Unexpected end tag (blink) in table context caused voodoo mode. +Line: 1 Col: 58 Unexpected end tag (blink). Ignored. +Line: 1 Col: 63 Unexpected end tag (tt) in table context caused voodoo mode. +Line: 1 Col: 63 End tag (tt) violates step 1, paragraph 1 of the adoption agency algorithm. +Line: 1 Col: 69 Unexpected end tag (pre) in table context caused voodoo mode. +Line: 1 Col: 69 End tag (pre) seen too early. Expected other end tag. +Line: 1 Col: 75 Unexpected end tag (big) in table context caused voodoo mode. +Line: 1 Col: 75 End tag (big) violates step 1, paragraph 1 of the adoption agency algorithm. +Line: 1 Col: 83 Unexpected end tag (small) in table context caused voodoo mode. +Line: 1 Col: 83 End tag (small) violates step 1, paragraph 1 of the adoption agency algorithm. +Line: 1 Col: 90 Unexpected end tag (font) in table context caused voodoo mode. +Line: 1 Col: 90 End tag (font) violates step 1, paragraph 1 of the adoption agency algorithm. +Line: 1 Col: 99 Unexpected end tag (select) in table context caused voodoo mode. +Line: 1 Col: 99 Unexpected end tag (select). Ignored. +Line: 1 Col: 104 Unexpected end tag (h1) in table context caused voodoo mode. +Line: 1 Col: 104 End tag (h1) seen too early. Expected other end tag. +Line: 1 Col: 109 Unexpected end tag (h2) in table context caused voodoo mode. +Line: 1 Col: 109 End tag (h2) seen too early. Expected other end tag. +Line: 1 Col: 114 Unexpected end tag (h3) in table context caused voodoo mode. +Line: 1 Col: 114 End tag (h3) seen too early. Expected other end tag. +Line: 1 Col: 119 Unexpected end tag (h4) in table context caused voodoo mode. +Line: 1 Col: 119 End tag (h4) seen too early. Expected other end tag. +Line: 1 Col: 124 Unexpected end tag (h5) in table context caused voodoo mode. +Line: 1 Col: 124 End tag (h5) seen too early. Expected other end tag. +Line: 1 Col: 129 Unexpected end tag (h6) in table context caused voodoo mode. +Line: 1 Col: 129 End tag (h6) seen too early. Expected other end tag. +Line: 1 Col: 136 Unexpected end tag (body) in the table row phase. Ignored. +Line: 1 Col: 141 Unexpected end tag (br) in table context caused voodoo mode. +Line: 1 Col: 141 Unexpected end tag (br). Treated as br element. +Line: 1 Col: 145 Unexpected end tag (a) in table context caused voodoo mode. +Line: 1 Col: 145 End tag (a) violates step 1, paragraph 1 of the adoption agency algorithm. +Line: 1 Col: 151 Unexpected end tag (img) in table context caused voodoo mode. +Line: 1 Col: 151 This element (img) has no end tag. +Line: 1 Col: 159 Unexpected end tag (title) in table context caused voodoo mode. +Line: 1 Col: 159 Unexpected end tag (title). Ignored. +Line: 1 Col: 166 Unexpected end tag (span) in table context caused voodoo mode. +Line: 1 Col: 166 Unexpected end tag (span). Ignored. +Line: 1 Col: 174 Unexpected end tag (style) in table context caused voodoo mode. +Line: 1 Col: 174 Unexpected end tag (style). Ignored. +Line: 1 Col: 183 Unexpected end tag (script) in table context caused voodoo mode. +Line: 1 Col: 183 Unexpected end tag (script). Ignored. +Line: 1 Col: 196 Unexpected end tag (th). Ignored. +Line: 1 Col: 201 Unexpected end tag (td). Ignored. +Line: 1 Col: 206 Unexpected end tag (tr). Ignored. +Line: 1 Col: 214 This element (frame) has no end tag. +Line: 1 Col: 221 This element (area) has no end tag. +Line: 1 Col: 228 Unexpected end tag (link). Ignored. +Line: 1 Col: 236 This element (param) has no end tag. +Line: 1 Col: 241 This element (hr) has no end tag. +Line: 1 Col: 249 This element (input) has no end tag. +Line: 1 Col: 255 Unexpected end tag (col). Ignored. +Line: 1 Col: 262 Unexpected end tag (base). Ignored. +Line: 1 Col: 269 Unexpected end tag (meta). Ignored. +Line: 1 Col: 280 This element (basefont) has no end tag. +Line: 1 Col: 290 This element (bgsound) has no end tag. +Line: 1 Col: 298 This element (embed) has no end tag. +Line: 1 Col: 307 This element (spacer) has no end tag. +Line: 1 Col: 311 Unexpected end tag (p). Ignored. +Line: 1 Col: 316 End tag (dd) seen too early. Expected other end tag. +Line: 1 Col: 321 End tag (dt) seen too early. Expected other end tag. +Line: 1 Col: 331 Unexpected end tag (caption). Ignored. +Line: 1 Col: 342 Unexpected end tag (colgroup). Ignored. +Line: 1 Col: 350 Unexpected end tag (tbody). Ignored. +Line: 1 Col: 358 Unexpected end tag (tfoot). Ignored. +Line: 1 Col: 366 Unexpected end tag (thead). Ignored. +Line: 1 Col: 376 End tag (address) seen too early. Expected other end tag. +Line: 1 Col: 389 End tag (blockquote) seen too early. Expected other end tag. +Line: 1 Col: 398 End tag (center) seen too early. Expected other end tag. +Line: 1 Col: 404 Unexpected end tag (dir). Ignored. +Line: 1 Col: 410 End tag (div) seen too early. Expected other end tag. +Line: 1 Col: 415 End tag (dl) seen too early. Expected other end tag. +Line: 1 Col: 426 End tag (fieldset) seen too early. Expected other end tag. +Line: 1 Col: 436 End tag (listing) seen too early. Expected other end tag. +Line: 1 Col: 443 End tag (menu) seen too early. Expected other end tag. +Line: 1 Col: 448 End tag (ol) seen too early. Expected other end tag. +Line: 1 Col: 453 End tag (ul) seen too early. Expected other end tag. +Line: 1 Col: 458 End tag (li) seen too early. Expected other end tag. +Line: 1 Col: 465 End tag (nobr) violates step 1, paragraph 1 of the adoption agency algorithm. +Line: 1 Col: 471 This element (wbr) has no end tag. +Line: 1 Col: 487 End tag (button) seen too early. Expected other end tag. +Line: 1 Col: 497 End tag (marquee) seen too early. Expected other end tag. +Line: 1 Col: 506 End tag (object) seen too early. Expected other end tag. +Line: 1 Col: 524 Unexpected end tag (html). Ignored. +Line: 1 Col: 524 Unexpected end tag (frameset). Ignored. +Line: 1 Col: 531 Unexpected end tag (head). Ignored. +Line: 1 Col: 540 Unexpected end tag (iframe). Ignored. +Line: 1 Col: 548 This element (image) has no end tag. +Line: 1 Col: 558 This element (isindex) has no end tag. +Line: 1 Col: 568 Unexpected end tag (noembed). Ignored. +Line: 1 Col: 579 Unexpected end tag (noframes). Ignored. +Line: 1 Col: 590 Unexpected end tag (noscript). Ignored. +Line: 1 Col: 601 Unexpected end tag (optgroup). Ignored. +Line: 1 Col: 610 Unexpected end tag (option). Ignored. +Line: 1 Col: 622 Unexpected end tag (plaintext). Ignored. +Line: 1 Col: 633 Unexpected end tag (textarea). Ignored. +#document +| +| +| +|
+| +| +| +|

+ +#data + +#errors +Line: 1 Col: 10 Unexpected start tag (frameset). Expected DOCTYPE. +Line: 1 Col: 10 Expected closing tag. Unexpected end of file. +#document +| +| +| diff --git a/pkgs/html/test/data/tree-construction/tests10.dat b/pkgs/html/test/data/tree-construction/tests10.dat new file mode 100644 index 000000000..4f8df86f2 --- /dev/null +++ b/pkgs/html/test/data/tree-construction/tests10.dat @@ -0,0 +1,799 @@ +#data + +#errors +#document +| +| +| +| +| + +#data +a +#errors +29: Bogus comment +#document +| +| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| + +#data + +#errors +35: Stray “svg” start tag. +42: Stray end tag “svg” +#document +| +| +| +| +| +#errors +43: Stray “svg” start tag. +50: Stray end tag “svg” +#document +| +| +| +| +|

+#errors +34: Start tag “svg” seen in “table”. +41: Stray end tag “svg”. +#document +| +| +| +| +| +| + +#data +
foo
+#errors +34: Start tag “svg” seen in “table”. +46: Stray end tag “g”. +53: Stray end tag “svg”. +#document +| +| +| +| +| +| +| "foo" +| + +#data +
foobar
+#errors +34: Start tag “svg” seen in “table”. +46: Stray end tag “g”. +58: Stray end tag “g”. +65: Stray end tag “svg”. +#document +| +| +| +| +| +| +| "foo" +| +| "bar" +| + +#data +
foobar
+#errors +41: Start tag “svg” seen in “table”. +53: Stray end tag “g”. +65: Stray end tag “g”. +72: Stray end tag “svg”. +#document +| +| +| +| +| +| +| "foo" +| +| "bar" +| +| + +#data +
foobar
+#errors +45: Start tag “svg” seen in “table”. +57: Stray end tag “g”. +69: Stray end tag “g”. +76: Stray end tag “svg”. +#document +| +| +| +| +| +| +| "foo" +| +| "bar" +| +| +| + +#data +
foobar
+#errors +#document +| +| +| +| +| +| +| +|
+| +| +| "foo" +| +| "bar" + +#data +
foobar

baz

+#errors +#document +| +| +| +| +| +| +| +|
+| +| +| "foo" +| +| "bar" +|

+| "baz" + +#data +
foobar

baz

+#errors +#document +| +| +| +| +| +|
+| +| +| "foo" +| +| "bar" +|

+| "baz" + +#data +
foobar

baz

quux +#errors +70: HTML start tag “p” in a foreign namespace context. +81: “table” closed but “caption” was still open. +#document +| +| +| +| +| +|
+| +| +| "foo" +| +| "bar" +|

+| "baz" +|

+| "quux" + +#data +
foobarbaz

quux +#errors +78: “table” closed but “caption” was still open. +78: Unclosed elements on stack. +#document +| +| +| +| +| +|
+| +| +| "foo" +| +| "bar" +| "baz" +|

+| "quux" + +#data +foobar

baz

quux +#errors +44: Start tag “svg” seen in “table”. +56: Stray end tag “g”. +68: Stray end tag “g”. +71: HTML start tag “p” in a foreign namespace context. +71: Start tag “p” seen in “table”. +#document +| +| +| +| +| +| +| "foo" +| +| "bar" +|

+| "baz" +| +| +|

+| "quux" + +#data +

quux +#errors +50: Stray “svg” start tag. +54: Stray “g” start tag. +62: Stray end tag “g” +66: Stray “g” start tag. +74: Stray end tag “g” +77: Stray “p” start tag. +88: “table” end tag with “select” open. +#document +| +| +| +| +| +| +| +|
+|

quux +#errors +36: Start tag “select” seen in “table”. +42: Stray “svg” start tag. +46: Stray “g” start tag. +54: Stray end tag “g” +58: Stray “g” start tag. +66: Stray end tag “g” +69: Stray “p” start tag. +80: “table” end tag with “select” open. +#document +| +| +| +| +| +|

+| "quux" + +#data +foobar

baz +#errors +41: Stray “svg” start tag. +68: HTML start tag “p” in a foreign namespace context. +#document +| +| +| +| +| +| +| "foo" +| +| "bar" +|

+| "baz" + +#data +foobar

baz +#errors +34: Stray “svg” start tag. +61: HTML start tag “p” in a foreign namespace context. +#document +| +| +| +| +| +| +| "foo" +| +| "bar" +|

+| "baz" + +#data +

+#errors +31: Stray “svg” start tag. +35: Stray “g” start tag. +40: Stray end tag “g” +44: Stray “g” start tag. +49: Stray end tag “g” +52: Stray “p” start tag. +58: Stray “span” start tag. +58: End of file seen and there were open elements. +#document +| +| +| +| + +#data +

+#errors +42: Stray “svg” start tag. +46: Stray “g” start tag. +51: Stray end tag “g” +55: Stray “g” start tag. +60: Stray end tag “g” +63: Stray “p” start tag. +69: Stray “span” start tag. +#document +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| xlink:href="foo" +| +| xlink href="foo" + +#data + +#errors +#document +| +| +| +| +| xlink:href="foo" +| xml:lang="en" +| +| +| xlink href="foo" +| xml lang="en" + +#data + +#errors +#document +| +| +| +| +| xlink:href="foo" +| xml:lang="en" +| +| +| xlink href="foo" +| xml lang="en" + +#data +bar +#errors +#document +| +| +| +| +| xlink:href="foo" +| xml:lang="en" +| +| +| xlink href="foo" +| xml lang="en" +| "bar" + +#data + +#errors +#document +| +| +| +| + +#data +

a +#errors +#document +| +| +| +|
+| +| "a" + +#data +
a +#errors +#document +| +| +| +|
+| +| +| "a" + +#data +
+#errors +#document +| +| +| +|
+| +| +| + +#data +
a +#errors +#document +| +| +| +|
+| +| +| +| +| "a" + +#data +

a +#errors +#document +| +| +| +|

+| +| +| +|

+| "a" + +#data +
    a +#errors +40: HTML start tag “ul” in a foreign namespace context. +41: End of file in a foreign namespace context. +#document +| +| +| +| +| +| +|
    +| +|
      +| "a" + +#data +
        a +#errors +35: HTML start tag “ul” in a foreign namespace context. +36: End of file in a foreign namespace context. +#document +| +| +| +| +| +| +| +|
          +| "a" + +#data +

          +#errors +#document +| +| +| +| +|

          +| +| +|

          + +#data +

          +#errors +#document +| +| +| +| +|

          +| +| +|

          + +#data +

          +#errors +#document +| +| +| +|

          +| +| +| +|

          +|

          + +#data +
          +#errors +#document +| +| +| +| +| +|
          +| +|
          +| +| + +#data +
          +#errors +#document +| +| +| +| +| +| +| +|
          +|
          +| + +#data + +#errors +#document +| +| +| +| +| +| + +#data +

+#errors +#document +| +| +| +| +|
+| +| + +#data + +#errors +#document +| +| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| +| +| + +#data +
+#errors +#document +| +| +| +| +| +| +| +|
+| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| +| +| +| +| +| +| +| +| +| diff --git a/pkgs/html/test/data/tree-construction/tests11.dat b/pkgs/html/test/data/tree-construction/tests11.dat new file mode 100644 index 000000000..638cde479 --- /dev/null +++ b/pkgs/html/test/data/tree-construction/tests11.dat @@ -0,0 +1,482 @@ +#data + +#errors +#document +| +| +| +| +| +| attributeName="" +| attributeType="" +| baseFrequency="" +| baseProfile="" +| calcMode="" +| clipPathUnits="" +| contentScriptType="" +| contentStyleType="" +| diffuseConstant="" +| edgeMode="" +| externalResourcesRequired="" +| filterRes="" +| filterUnits="" +| glyphRef="" +| gradientTransform="" +| gradientUnits="" +| kernelMatrix="" +| kernelUnitLength="" +| keyPoints="" +| keySplines="" +| keyTimes="" +| lengthAdjust="" +| limitingConeAngle="" +| markerHeight="" +| markerUnits="" +| markerWidth="" +| maskContentUnits="" +| maskUnits="" +| numOctaves="" +| pathLength="" +| patternContentUnits="" +| patternTransform="" +| patternUnits="" +| pointsAtX="" +| pointsAtY="" +| pointsAtZ="" +| preserveAlpha="" +| preserveAspectRatio="" +| primitiveUnits="" +| refX="" +| refY="" +| repeatCount="" +| repeatDur="" +| requiredExtensions="" +| requiredFeatures="" +| specularConstant="" +| specularExponent="" +| spreadMethod="" +| startOffset="" +| stdDeviation="" +| stitchTiles="" +| surfaceScale="" +| systemLanguage="" +| tableValues="" +| targetX="" +| targetY="" +| textLength="" +| viewBox="" +| viewTarget="" +| xChannelSelector="" +| yChannelSelector="" +| zoomAndPan="" + +#data + +#errors +#document +| +| +| +| +| +| attributeName="" +| attributeType="" +| baseFrequency="" +| baseProfile="" +| calcMode="" +| clipPathUnits="" +| contentScriptType="" +| contentStyleType="" +| diffuseConstant="" +| edgeMode="" +| externalResourcesRequired="" +| filterRes="" +| filterUnits="" +| glyphRef="" +| gradientTransform="" +| gradientUnits="" +| kernelMatrix="" +| kernelUnitLength="" +| keyPoints="" +| keySplines="" +| keyTimes="" +| lengthAdjust="" +| limitingConeAngle="" +| markerHeight="" +| markerUnits="" +| markerWidth="" +| maskContentUnits="" +| maskUnits="" +| numOctaves="" +| pathLength="" +| patternContentUnits="" +| patternTransform="" +| patternUnits="" +| pointsAtX="" +| pointsAtY="" +| pointsAtZ="" +| preserveAlpha="" +| preserveAspectRatio="" +| primitiveUnits="" +| refX="" +| refY="" +| repeatCount="" +| repeatDur="" +| requiredExtensions="" +| requiredFeatures="" +| specularConstant="" +| specularExponent="" +| spreadMethod="" +| startOffset="" +| stdDeviation="" +| stitchTiles="" +| surfaceScale="" +| systemLanguage="" +| tableValues="" +| targetX="" +| targetY="" +| textLength="" +| viewBox="" +| viewTarget="" +| xChannelSelector="" +| yChannelSelector="" +| zoomAndPan="" + +#data + +#errors +#document +| +| +| +| +| +| attributeName="" +| attributeType="" +| baseFrequency="" +| baseProfile="" +| calcMode="" +| clipPathUnits="" +| contentScriptType="" +| contentStyleType="" +| diffuseConstant="" +| edgeMode="" +| externalResourcesRequired="" +| filterRes="" +| filterUnits="" +| glyphRef="" +| gradientTransform="" +| gradientUnits="" +| kernelMatrix="" +| kernelUnitLength="" +| keyPoints="" +| keySplines="" +| keyTimes="" +| lengthAdjust="" +| limitingConeAngle="" +| markerHeight="" +| markerUnits="" +| markerWidth="" +| maskContentUnits="" +| maskUnits="" +| numOctaves="" +| pathLength="" +| patternContentUnits="" +| patternTransform="" +| patternUnits="" +| pointsAtX="" +| pointsAtY="" +| pointsAtZ="" +| preserveAlpha="" +| preserveAspectRatio="" +| primitiveUnits="" +| refX="" +| refY="" +| repeatCount="" +| repeatDur="" +| requiredExtensions="" +| requiredFeatures="" +| specularConstant="" +| specularExponent="" +| spreadMethod="" +| startOffset="" +| stdDeviation="" +| stitchTiles="" +| surfaceScale="" +| systemLanguage="" +| tableValues="" +| targetX="" +| targetY="" +| textLength="" +| viewBox="" +| viewTarget="" +| xChannelSelector="" +| yChannelSelector="" +| zoomAndPan="" + +#data + +#errors +#document +| +| +| +| +| +| attributename="" +| attributetype="" +| basefrequency="" +| baseprofile="" +| calcmode="" +| clippathunits="" +| contentscripttype="" +| contentstyletype="" +| diffuseconstant="" +| edgemode="" +| externalresourcesrequired="" +| filterres="" +| filterunits="" +| glyphref="" +| gradienttransform="" +| gradientunits="" +| kernelmatrix="" +| kernelunitlength="" +| keypoints="" +| keysplines="" +| keytimes="" +| lengthadjust="" +| limitingconeangle="" +| markerheight="" +| markerunits="" +| markerwidth="" +| maskcontentunits="" +| maskunits="" +| numoctaves="" +| pathlength="" +| patterncontentunits="" +| patterntransform="" +| patternunits="" +| pointsatx="" +| pointsaty="" +| pointsatz="" +| preservealpha="" +| preserveaspectratio="" +| primitiveunits="" +| refx="" +| refy="" +| repeatcount="" +| repeatdur="" +| requiredextensions="" +| requiredfeatures="" +| specularconstant="" +| specularexponent="" +| spreadmethod="" +| startoffset="" +| stddeviation="" +| stitchtiles="" +| surfacescale="" +| systemlanguage="" +| tablevalues="" +| targetx="" +| targety="" +| textlength="" +| viewbox="" +| viewtarget="" +| xchannelselector="" +| ychannelselector="" +| zoomandpan="" + +#data + +#errors +#document +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| +| diff --git a/pkgs/html/test/data/tree-construction/tests12.dat b/pkgs/html/test/data/tree-construction/tests12.dat new file mode 100644 index 000000000..63107d277 --- /dev/null +++ b/pkgs/html/test/data/tree-construction/tests12.dat @@ -0,0 +1,62 @@ +#data +

foobazeggs

spam

quuxbar +#errors +#document +| +| +| +| +|

+| "foo" +| +| +| +| "baz" +| +| +| +| +| "eggs" +| +| +|

+| "spam" +| +| +| +|
+| +| +| "quux" +| "bar" + +#data +foobazeggs

spam
quuxbar +#errors +#document +| +| +| +| +| "foo" +| +| +| +| "baz" +| +| +| +| +| "eggs" +| +| +|

+| "spam" +| +| +| +|
+| +| +| "quux" +| "bar" diff --git a/pkgs/html/test/data/tree-construction/tests14.dat b/pkgs/html/test/data/tree-construction/tests14.dat new file mode 100644 index 000000000..b8713f885 --- /dev/null +++ b/pkgs/html/test/data/tree-construction/tests14.dat @@ -0,0 +1,74 @@ +#data + +#errors +#document +| +| +| +| +| + +#data + +#errors +#document +| +| +| +| +| +| + +#data + +#errors +15: Unexpected start tag html +#document +| +| +| abc:def="gh" +| +| +| + +#data + +#errors +15: Unexpected start tag html +#document +| +| +| xml:lang="bar" +| +| + +#data + +#errors +#document +| +| +| 123="456" +| +| + +#data + +#errors +#document +| +| +| 123="456" +| 789="012" +| +| + +#data + +#errors +#document +| +| +| +| +| 789="012" diff --git a/pkgs/html/test/data/tree-construction/tests15.dat b/pkgs/html/test/data/tree-construction/tests15.dat new file mode 100644 index 000000000..6ce1c0d16 --- /dev/null +++ b/pkgs/html/test/data/tree-construction/tests15.dat @@ -0,0 +1,208 @@ +#data +

X +#errors +Line: 1 Col: 31 Unexpected end tag (p). Ignored. +Line: 1 Col: 36 Expected closing tag. Unexpected end of file. +#document +| +| +| +| +|

+| +| +| +| +| +| +| " " +|

+| "X" + +#data +

+

X +#errors +Line: 1 Col: 3 Unexpected start tag (p). Expected DOCTYPE. +Line: 1 Col: 16 Unexpected end tag (p). Ignored. +Line: 2 Col: 4 Expected closing tag. Unexpected end of file. +#document +| +| +| +|

+| +| +| +| +| +| +| " +" +|

+| "X" + +#data + +#errors +Line: 1 Col: 22 Unexpected end tag (html) after the (implied) root element. +#document +| +| +| +| +| " " + +#data + +#errors +Line: 1 Col: 22 Unexpected end tag (body) after the (implied) root element. +#document +| +| +| +| +| + +#data + +#errors +Line: 1 Col: 6 Unexpected start tag (html). Expected DOCTYPE. +Line: 1 Col: 13 Unexpected end tag (html) after the (implied) root element. +#document +| +| +| +| + +#data +X +#errors +Line: 1 Col: 22 Unexpected end tag (body) after the (implied) root element. +#document +| +| +| +| +| +| "X" + +#data +<!doctype html><table> X<meta></table> +#errors +Line: 1 Col: 24 Unexpected non-space characters in table context caused voodoo mode. +Line: 1 Col: 30 Unexpected start tag (meta) in table context caused voodoo mode. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| " X" +| <meta> +| <table> + +#data +<!doctype html><table> x</table> +#errors +Line: 1 Col: 24 Unexpected non-space characters in table context caused voodoo mode. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| " x" +| <table> + +#data +<!doctype html><table> x </table> +#errors +Line: 1 Col: 25 Unexpected non-space characters in table context caused voodoo mode. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| " x " +| <table> + +#data +<!doctype html><table><tr> x</table> +#errors +Line: 1 Col: 28 Unexpected non-space characters in table context caused voodoo mode. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| " x" +| <table> +| <tbody> +| <tr> + +#data +<!doctype html><table>X<style> <tr>x </style> </table> +#errors +Line: 1 Col: 23 Unexpected non-space characters in table context caused voodoo mode. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| "X" +| <table> +| <style> +| " <tr>x " +| " " + +#data +<!doctype html><div><table><a>foo</a> <tr><td>bar</td> </tr></table></div> +#errors +Line: 1 Col: 30 Unexpected start tag (a) in table context caused voodoo mode. +Line: 1 Col: 37 Unexpected end tag (a) in table context caused voodoo mode. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <div> +| <a> +| "foo" +| <table> +| " " +| <tbody> +| <tr> +| <td> +| "bar" +| " " + +#data +<frame></frame></frame><frameset><frame><frameset><frame></frameset><noframes></frameset><noframes> +#errors +6: Start tag seen without seeing a doctype first. Expected “<!DOCTYPE html>”. +13: Stray start tag “frame”. +21: Stray end tag “frame”. +29: Stray end tag “frame”. +39: “frameset” start tag after “body” already open. +105: End of file seen inside an [R]CDATA element. +105: End of file seen and there were open elements. +XXX: These errors are wrong, please fix me! +#document +| <html> +| <head> +| <frameset> +| <frame> +| <frameset> +| <frame> +| <noframes> +| "</frameset><noframes>" + +#data +<!DOCTYPE html><object></html> +#errors +1: Expected closing tag. Unexpected end of file +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <object> diff --git a/pkgs/html/test/data/tree-construction/tests16.dat b/pkgs/html/test/data/tree-construction/tests16.dat new file mode 100644 index 000000000..c8ef66f0e --- /dev/null +++ b/pkgs/html/test/data/tree-construction/tests16.dat @@ -0,0 +1,2299 @@ +#data +<!doctype html><script> +#errors +Line: 1 Col: 23 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| <body> + +#data +<!doctype html><script>a +#errors +Line: 1 Col: 24 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "a" +| <body> + +#data +<!doctype html><script>< +#errors +Line: 1 Col: 24 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<" +| <body> + +#data +<!doctype html><script></ +#errors +Line: 1 Col: 25 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "</" +| <body> + +#data +<!doctype html><script></S +#errors +Line: 1 Col: 26 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "</S" +| <body> + +#data +<!doctype html><script></SC +#errors +Line: 1 Col: 27 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "</SC" +| <body> + +#data +<!doctype html><script></SCR +#errors +Line: 1 Col: 28 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "</SCR" +| <body> + +#data +<!doctype html><script></SCRI +#errors +Line: 1 Col: 29 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "</SCRI" +| <body> + +#data +<!doctype html><script></SCRIP +#errors +Line: 1 Col: 30 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "</SCRIP" +| <body> + +#data +<!doctype html><script></SCRIPT +#errors +Line: 1 Col: 31 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "</SCRIPT" +| <body> + +#data +<!doctype html><script></SCRIPT +#errors +Line: 1 Col: 32 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| <body> + +#data +<!doctype html><script></s +#errors +Line: 1 Col: 26 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "</s" +| <body> + +#data +<!doctype html><script></sc +#errors +Line: 1 Col: 27 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "</sc" +| <body> + +#data +<!doctype html><script></scr +#errors +Line: 1 Col: 28 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "</scr" +| <body> + +#data +<!doctype html><script></scri +#errors +Line: 1 Col: 29 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "</scri" +| <body> + +#data +<!doctype html><script></scrip +#errors +Line: 1 Col: 30 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "</scrip" +| <body> + +#data +<!doctype html><script></script +#errors +Line: 1 Col: 31 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "</script" +| <body> + +#data +<!doctype html><script></script +#errors +Line: 1 Col: 32 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| <body> + +#data +<!doctype html><script><! +#errors +Line: 1 Col: 25 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!" +| <body> + +#data +<!doctype html><script><!a +#errors +Line: 1 Col: 26 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!a" +| <body> + +#data +<!doctype html><script><!- +#errors +Line: 1 Col: 26 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!-" +| <body> + +#data +<!doctype html><script><!-a +#errors +Line: 1 Col: 27 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!-a" +| <body> + +#data +<!doctype html><script><!-- +#errors +Line: 1 Col: 27 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--" +| <body> + +#data +<!doctype html><script><!--a +#errors +Line: 1 Col: 28 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--a" +| <body> + +#data +<!doctype html><script><!--< +#errors +Line: 1 Col: 28 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<" +| <body> + +#data +<!doctype html><script><!--<a +#errors +Line: 1 Col: 29 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<a" +| <body> + +#data +<!doctype html><script><!--</ +#errors +Line: 1 Col: 27 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--</" +| <body> + +#data +<!doctype html><script><!--</script +#errors +Line: 1 Col: 35 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--</script" +| <body> + +#data +<!doctype html><script><!--</script +#errors +Line: 1 Col: 36 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--" +| <body> + +#data +<!doctype html><script><!--<s +#errors +Line: 1 Col: 29 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<s" +| <body> + +#data +<!doctype html><script><!--<script +#errors +Line: 1 Col: 34 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script" +| <body> + +#data +<!doctype html><script><!--<script +#errors +Line: 1 Col: 35 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script " +| <body> + +#data +<!doctype html><script><!--<script < +#errors +Line: 1 Col: 36 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script <" +| <body> + +#data +<!doctype html><script><!--<script <a +#errors +Line: 1 Col: 37 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script <a" +| <body> + +#data +<!doctype html><script><!--<script </ +#errors +Line: 1 Col: 37 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script </" +| <body> + +#data +<!doctype html><script><!--<script </s +#errors +Line: 1 Col: 38 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script </s" +| <body> + +#data +<!doctype html><script><!--<script </script +#errors +Line: 1 Col: 43 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script </script" +| <body> + +#data +<!doctype html><script><!--<script </scripta +#errors +Line: 1 Col: 44 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script </scripta" +| <body> + +#data +<!doctype html><script><!--<script </script +#errors +Line: 1 Col: 44 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script </script " +| <body> + +#data +<!doctype html><script><!--<script </script> +#errors +Line: 1 Col: 44 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script </script>" +| <body> + +#data +<!doctype html><script><!--<script </script/ +#errors +Line: 1 Col: 44 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script </script/" +| <body> + +#data +<!doctype html><script><!--<script </script < +#errors +Line: 1 Col: 45 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script </script <" +| <body> + +#data +<!doctype html><script><!--<script </script <a +#errors +Line: 1 Col: 46 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script </script <a" +| <body> + +#data +<!doctype html><script><!--<script </script </ +#errors +Line: 1 Col: 46 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script </script </" +| <body> + +#data +<!doctype html><script><!--<script </script </script +#errors +Line: 1 Col: 52 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script </script </script" +| <body> + +#data +<!doctype html><script><!--<script </script </script +#errors +Line: 1 Col: 53 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script </script " +| <body> + +#data +<!doctype html><script><!--<script </script </script/ +#errors +Line: 1 Col: 53 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script </script " +| <body> + +#data +<!doctype html><script><!--<script </script </script> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script </script " +| <body> + +#data +<!doctype html><script><!--<script - +#errors +Line: 1 Col: 36 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script -" +| <body> + +#data +<!doctype html><script><!--<script -a +#errors +Line: 1 Col: 37 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script -a" +| <body> + +#data +<!doctype html><script><!--<script -< +#errors +Line: 1 Col: 37 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script -<" +| <body> + +#data +<!doctype html><script><!--<script -- +#errors +Line: 1 Col: 37 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script --" +| <body> + +#data +<!doctype html><script><!--<script --a +#errors +Line: 1 Col: 38 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script --a" +| <body> + +#data +<!doctype html><script><!--<script --< +#errors +Line: 1 Col: 38 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script --<" +| <body> + +#data +<!doctype html><script><!--<script --> +#errors +Line: 1 Col: 38 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script -->" +| <body> + +#data +<!doctype html><script><!--<script -->< +#errors +Line: 1 Col: 39 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script --><" +| <body> + +#data +<!doctype html><script><!--<script --></ +#errors +Line: 1 Col: 40 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script --></" +| <body> + +#data +<!doctype html><script><!--<script --></script +#errors +Line: 1 Col: 46 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script --></script" +| <body> + +#data +<!doctype html><script><!--<script --></script +#errors +Line: 1 Col: 47 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script -->" +| <body> + +#data +<!doctype html><script><!--<script --></script/ +#errors +Line: 1 Col: 47 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script -->" +| <body> + +#data +<!doctype html><script><!--<script --></script> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script -->" +| <body> + +#data +<!doctype html><script><!--<script><\/script>--></script> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script><\/script>-->" +| <body> + +#data +<!doctype html><script><!--<script></scr'+'ipt>--></script> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script></scr'+'ipt>-->" +| <body> + +#data +<!doctype html><script><!--<script></script><script></script></script> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script></script><script></script>" +| <body> + +#data +<!doctype html><script><!--<script></script><script></script>--><!--</script> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script></script><script></script>--><!--" +| <body> + +#data +<!doctype html><script><!--<script></script><script></script>-- ></script> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script></script><script></script>-- >" +| <body> + +#data +<!doctype html><script><!--<script></script><script></script>- -></script> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script></script><script></script>- ->" +| <body> + +#data +<!doctype html><script><!--<script></script><script></script>- - ></script> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script></script><script></script>- - >" +| <body> + +#data +<!doctype html><script><!--<script></script><script></script>-></script> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script></script><script></script>->" +| <body> + +#data +<!doctype html><script><!--<script>--!></script>X +#errors +Line: 1 Col: 49 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script>--!></script>X" +| <body> + +#data +<!doctype html><script><!--<scr'+'ipt></script>--></script> +#errors +Line: 1 Col: 59 Unexpected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<scr'+'ipt>" +| <body> +| "-->" + +#data +<!doctype html><script><!--<script></scr'+'ipt></script>X +#errors +Line: 1 Col: 57 Unexpected end of file. Expected end tag (script). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| "<!--<script></scr'+'ipt></script>X" +| <body> + +#data +<!doctype html><style><!--<style></style>--></style> +#errors +Line: 1 Col: 52 Unexpected end tag (style). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <style> +| "<!--<style>" +| <body> +| "-->" + +#data +<!doctype html><style><!--</style>X +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <style> +| "<!--" +| <body> +| "X" + +#data +<!doctype html><style><!--...</style>...--></style> +#errors +Line: 1 Col: 51 Unexpected end tag (style). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <style> +| "<!--..." +| <body> +| "...-->" + +#data +<!doctype html><style><!--<br><html xmlns:v="urn:schemas-microsoft-com:vml"><!--[if !mso]><style></style>X +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <style> +| "<!--<br><html xmlns:v="urn:schemas-microsoft-com:vml"><!--[if !mso]><style>" +| <body> +| "X" + +#data +<!doctype html><style><!--...<style><!--...--!></style>--></style> +#errors +Line: 1 Col: 66 Unexpected end tag (style). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <style> +| "<!--...<style><!--...--!>" +| <body> +| "-->" + +#data +<!doctype html><style><!--...</style><!-- --><style>@import ...</style> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <style> +| "<!--..." +| <!-- --> +| <style> +| "@import ..." +| <body> + +#data +<!doctype html><style>...<style><!--...</style><!-- --></style> +#errors +Line: 1 Col: 63 Unexpected end tag (style). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <style> +| "...<style><!--..." +| <!-- --> +| <body> + +#data +<!doctype html><style>...<!--[if IE]><style>...</style>X +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <style> +| "...<!--[if IE]><style>..." +| <body> +| "X" + +#data +<!doctype html><title><!--<title>--> +#errors +Line: 1 Col: 52 Unexpected end tag (title). +#document +| +| +| +| +| "<!--<title>" +| <body> +| "-->" + +#data +<!doctype html><title></title> +#errors +#document +| +| +| +| +| "" +| + +#data +foo/title><link></head><body>X +#errors +Line: 1 Col: 52 Unexpected end of file. Expected end tag (title). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <title> +| "foo/title><link></head><body>X" +| <body> + +#data +<!doctype html><noscript><!--<noscript></noscript>--></noscript> +#errors +Line: 1 Col: 64 Unexpected end tag (noscript). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <noscript> +| "<!--<noscript>" +| <body> +| "-->" + +#data +<!doctype html><noscript><!--</noscript>X<noscript>--></noscript> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <noscript> +| "<!--" +| <body> +| "X" +| <noscript> +| "-->" + +#data +<!doctype html><noscript><iframe></noscript>X +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <noscript> +| "<iframe>" +| <body> +| "X" + +#data +<!doctype html><noframes><!--<noframes></noframes>--></noframes> +#errors +Line: 1 Col: 64 Unexpected end tag (noframes). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <noframes> +| "<!--<noframes>" +| <body> +| "-->" + +#data +<!doctype html><noframes><body><script><!--...</script></body></noframes></html> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <noframes> +| "<body><script><!--...</script></body>" +| <body> + +#data +<!doctype html><textarea><!--<textarea></textarea>--></textarea> +#errors +Line: 1 Col: 64 Unexpected end tag (textarea). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <textarea> +| "<!--<textarea>" +| "-->" + +#data +<!doctype html><textarea></textarea></textarea> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <textarea> +| "</textarea>" + +#data +<!doctype html><textarea><</textarea> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <textarea> +| "<" + +#data +<!doctype html><textarea>a<b</textarea> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <textarea> +| "a<b" + +#data +<!doctype html><iframe><!--<iframe></iframe>--></iframe> +#errors +Line: 1 Col: 56 Unexpected end tag (iframe). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <iframe> +| "<!--<iframe>" +| "-->" + +#data +<!doctype html><iframe>...<!--X->...<!--/X->...</iframe> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <iframe> +| "...<!--X->...<!--/X->..." + +#data +<!doctype html><xmp><!--<xmp></xmp>--></xmp> +#errors +Line: 1 Col: 44 Unexpected end tag (xmp). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <xmp> +| "<!--<xmp>" +| "-->" + +#data +<!doctype html><noembed><!--<noembed></noembed>--></noembed> +#errors +Line: 1 Col: 60 Unexpected end tag (noembed). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <noembed> +| "<!--<noembed>" +| "-->" + +#data +<script> +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 8 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| <body> + +#data +<script>a +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 9 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "a" +| <body> + +#data +<script>< +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 9 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<" +| <body> + +#data +<script></ +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 10 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "</" +| <body> + +#data +<script></S +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 11 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "</S" +| <body> + +#data +<script></SC +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 12 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "</SC" +| <body> + +#data +<script></SCR +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 13 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "</SCR" +| <body> + +#data +<script></SCRI +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 14 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "</SCRI" +| <body> + +#data +<script></SCRIP +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 15 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "</SCRIP" +| <body> + +#data +<script></SCRIPT +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 16 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "</SCRIPT" +| <body> + +#data +<script></SCRIPT +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 17 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| <body> + +#data +<script></s +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 11 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "</s" +| <body> + +#data +<script></sc +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 12 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "</sc" +| <body> + +#data +<script></scr +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 13 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "</scr" +| <body> + +#data +<script></scri +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 14 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "</scri" +| <body> + +#data +<script></scrip +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 15 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "</scrip" +| <body> + +#data +<script></script +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 16 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "</script" +| <body> + +#data +<script></script +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 17 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| <body> + +#data +<script><! +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 10 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!" +| <body> + +#data +<script><!a +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 11 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!a" +| <body> + +#data +<script><!- +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 11 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!-" +| <body> + +#data +<script><!-a +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 12 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!-a" +| <body> + +#data +<script><!-- +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 12 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--" +| <body> + +#data +<script><!--a +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 13 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--a" +| <body> + +#data +<script><!--< +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 13 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<" +| <body> + +#data +<script><!--<a +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 14 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<a" +| <body> + +#data +<script><!--</ +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 14 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--</" +| <body> + +#data +<script><!--</script +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 20 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--</script" +| <body> + +#data +<script><!--</script +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 21 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--" +| <body> + +#data +<script><!--<s +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 14 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<s" +| <body> + +#data +<script><!--<script +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 19 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script" +| <body> + +#data +<script><!--<script +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 20 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script " +| <body> + +#data +<script><!--<script < +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 21 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script <" +| <body> + +#data +<script><!--<script <a +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 22 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script <a" +| <body> + +#data +<script><!--<script </ +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 22 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script </" +| <body> + +#data +<script><!--<script </s +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 23 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script </s" +| <body> + +#data +<script><!--<script </script +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 28 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script </script" +| <body> + +#data +<script><!--<script </scripta +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 29 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script </scripta" +| <body> + +#data +<script><!--<script </script +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 29 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script </script " +| <body> + +#data +<script><!--<script </script> +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 29 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script </script>" +| <body> + +#data +<script><!--<script </script/ +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 29 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script </script/" +| <body> + +#data +<script><!--<script </script < +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 30 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script </script <" +| <body> + +#data +<script><!--<script </script <a +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 31 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script </script <a" +| <body> + +#data +<script><!--<script </script </ +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 31 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script </script </" +| <body> + +#data +<script><!--<script </script </script +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 38 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script </script </script" +| <body> + +#data +<script><!--<script </script </script +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 38 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script </script " +| <body> + +#data +<script><!--<script </script </script/ +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 38 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script </script " +| <body> + +#data +<script><!--<script </script </script> +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +#document +| <html> +| <head> +| <script> +| "<!--<script </script " +| <body> + +#data +<script><!--<script - +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 21 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script -" +| <body> + +#data +<script><!--<script -a +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 22 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script -a" +| <body> + +#data +<script><!--<script -- +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 22 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script --" +| <body> + +#data +<script><!--<script --a +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 23 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script --a" +| <body> + +#data +<script><!--<script --> +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 23 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script -->" +| <body> + +#data +<script><!--<script -->< +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 24 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script --><" +| <body> + +#data +<script><!--<script --></ +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 25 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script --></" +| <body> + +#data +<script><!--<script --></script +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 31 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script --></script" +| <body> + +#data +<script><!--<script --></script +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 32 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script -->" +| <body> + +#data +<script><!--<script --></script/ +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 32 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script -->" +| <body> + +#data +<script><!--<script --></script> +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +#document +| <html> +| <head> +| <script> +| "<!--<script -->" +| <body> + +#data +<script><!--<script><\/script>--></script> +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +#document +| <html> +| <head> +| <script> +| "<!--<script><\/script>-->" +| <body> + +#data +<script><!--<script></scr'+'ipt>--></script> +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +#document +| <html> +| <head> +| <script> +| "<!--<script></scr'+'ipt>-->" +| <body> + +#data +<script><!--<script></script><script></script></script> +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +#document +| <html> +| <head> +| <script> +| "<!--<script></script><script></script>" +| <body> + +#data +<script><!--<script></script><script></script>--><!--</script> +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +#document +| <html> +| <head> +| <script> +| "<!--<script></script><script></script>--><!--" +| <body> + +#data +<script><!--<script></script><script></script>-- ></script> +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +#document +| <html> +| <head> +| <script> +| "<!--<script></script><script></script>-- >" +| <body> + +#data +<script><!--<script></script><script></script>- -></script> +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +#document +| <html> +| <head> +| <script> +| "<!--<script></script><script></script>- ->" +| <body> + +#data +<script><!--<script></script><script></script>- - ></script> +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +#document +| <html> +| <head> +| <script> +| "<!--<script></script><script></script>- - >" +| <body> + +#data +<script><!--<script></script><script></script>-></script> +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +#document +| <html> +| <head> +| <script> +| "<!--<script></script><script></script>->" +| <body> + +#data +<script><!--<script>--!></script>X +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 34 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script>--!></script>X" +| <body> + +#data +<script><!--<scr'+'ipt></script>--></script> +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 44 Unexpected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<scr'+'ipt>" +| <body> +| "-->" + +#data +<script><!--<script></scr'+'ipt></script>X +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 42 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "<!--<script></scr'+'ipt></script>X" +| <body> + +#data +<style><!--<style></style>--></style> +#errors +Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE. +Line: 1 Col: 37 Unexpected end tag (style). +#document +| <html> +| <head> +| <style> +| "<!--<style>" +| <body> +| "-->" + +#data +<style><!--</style>X +#errors +Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE. +#document +| <html> +| <head> +| <style> +| "<!--" +| <body> +| "X" + +#data +<style><!--...</style>...--></style> +#errors +Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE. +Line: 1 Col: 36 Unexpected end tag (style). +#document +| <html> +| <head> +| <style> +| "<!--..." +| <body> +| "...-->" + +#data +<style><!--<br><html xmlns:v="urn:schemas-microsoft-com:vml"><!--[if !mso]><style></style>X +#errors +Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE. +#document +| <html> +| <head> +| <style> +| "<!--<br><html xmlns:v="urn:schemas-microsoft-com:vml"><!--[if !mso]><style>" +| <body> +| "X" + +#data +<style><!--...<style><!--...--!></style>--></style> +#errors +Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE. +Line: 1 Col: 51 Unexpected end tag (style). +#document +| <html> +| <head> +| <style> +| "<!--...<style><!--...--!>" +| <body> +| "-->" + +#data +<style><!--...</style><!-- --><style>@import ...</style> +#errors +Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE. +#document +| <html> +| <head> +| <style> +| "<!--..." +| <!-- --> +| <style> +| "@import ..." +| <body> + +#data +<style>...<style><!--...</style><!-- --></style> +#errors +Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE. +Line: 1 Col: 48 Unexpected end tag (style). +#document +| <html> +| <head> +| <style> +| "...<style><!--..." +| <!-- --> +| <body> + +#data +<style>...<!--[if IE]><style>...</style>X +#errors +Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE. +#document +| <html> +| <head> +| <style> +| "...<!--[if IE]><style>..." +| <body> +| "X" + +#data +<title><!--<title>--> +#errors +Line: 1 Col: 7 Unexpected start tag (title). Expected DOCTYPE. +Line: 1 Col: 37 Unexpected end tag (title). +#document +| +| +| +| "<!--<title>" +| <body> +| "-->" + +#data +<title></title> +#errors +Line: 1 Col: 7 Unexpected start tag (title). Expected DOCTYPE. +#document +| +| +| +| "" +| + +#data +foo/title><link></head><body>X +#errors +Line: 1 Col: 7 Unexpected start tag (title). Expected DOCTYPE. +Line: 1 Col: 37 Unexpected end of file. Expected end tag (title). +#document +| <html> +| <head> +| <title> +| "foo/title><link></head><body>X" +| <body> + +#data +<noscript><!--<noscript></noscript>--></noscript> +#errors +Line: 1 Col: 10 Unexpected start tag (noscript). Expected DOCTYPE. +Line: 1 Col: 49 Unexpected end tag (noscript). +#document +| <html> +| <head> +| <noscript> +| "<!--<noscript>" +| <body> +| "-->" + +#data +<noscript><!--</noscript>X<noscript>--></noscript> +#errors +Line: 1 Col: 10 Unexpected start tag (noscript). Expected DOCTYPE. +#document +| <html> +| <head> +| <noscript> +| "<!--" +| <body> +| "X" +| <noscript> +| "-->" + +#data +<noscript><iframe></noscript>X +#errors +Line: 1 Col: 10 Unexpected start tag (noscript). Expected DOCTYPE. +#document +| <html> +| <head> +| <noscript> +| "<iframe>" +| <body> +| "X" + +#data +<noframes><!--<noframes></noframes>--></noframes> +#errors +Line: 1 Col: 10 Unexpected start tag (noframes). Expected DOCTYPE. +Line: 1 Col: 49 Unexpected end tag (noframes). +#document +| <html> +| <head> +| <noframes> +| "<!--<noframes>" +| <body> +| "-->" + +#data +<noframes><body><script><!--...</script></body></noframes></html> +#errors +Line: 1 Col: 10 Unexpected start tag (noframes). Expected DOCTYPE. +#document +| <html> +| <head> +| <noframes> +| "<body><script><!--...</script></body>" +| <body> + +#data +<textarea><!--<textarea></textarea>--></textarea> +#errors +Line: 1 Col: 10 Unexpected start tag (textarea). Expected DOCTYPE. +Line: 1 Col: 49 Unexpected end tag (textarea). +#document +| <html> +| <head> +| <body> +| <textarea> +| "<!--<textarea>" +| "-->" + +#data +<textarea></textarea></textarea> +#errors +Line: 1 Col: 10 Unexpected start tag (textarea). Expected DOCTYPE. +#document +| <html> +| <head> +| <body> +| <textarea> +| "</textarea>" + +#data +<iframe><!--<iframe></iframe>--></iframe> +#errors +Line: 1 Col: 8 Unexpected start tag (iframe). Expected DOCTYPE. +Line: 1 Col: 41 Unexpected end tag (iframe). +#document +| <html> +| <head> +| <body> +| <iframe> +| "<!--<iframe>" +| "-->" + +#data +<iframe>...<!--X->...<!--/X->...</iframe> +#errors +Line: 1 Col: 8 Unexpected start tag (iframe). Expected DOCTYPE. +#document +| <html> +| <head> +| <body> +| <iframe> +| "...<!--X->...<!--/X->..." + +#data +<xmp><!--<xmp></xmp>--></xmp> +#errors +Line: 1 Col: 5 Unexpected start tag (xmp). Expected DOCTYPE. +Line: 1 Col: 29 Unexpected end tag (xmp). +#document +| <html> +| <head> +| <body> +| <xmp> +| "<!--<xmp>" +| "-->" + +#data +<noembed><!--<noembed></noembed>--></noembed> +#errors +Line: 1 Col: 9 Unexpected start tag (noembed). Expected DOCTYPE. +Line: 1 Col: 45 Unexpected end tag (noembed). +#document +| <html> +| <head> +| <body> +| <noembed> +| "<!--<noembed>" +| "-->" + +#data +<!doctype html><table> + +#errors +Line 2 Col 0 Unexpected end of file. Expected table content. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <table> +| " +" + +#data +<!doctype html><table><td><span><font></span><span> +#errors +Line 1 Col 26 Unexpected table cell start tag (td) in the table body phase. +Line 1 Col 45 Unexpected end tag (span). +Line 1 Col 51 Expected closing tag. Unexpected end of file. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <table> +| <tbody> +| <tr> +| <td> +| <span> +| <font> +| <font> +| <span> + +#data +<!doctype html><form><table></form><form></table></form> +#errors +35: Stray end tag “form”. +41: Start tag “form” seen in “table”. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <form> +| <table> +| <form> diff --git a/pkgs/html/test/data/tree-construction/tests17.dat b/pkgs/html/test/data/tree-construction/tests17.dat new file mode 100644 index 000000000..7b555f888 --- /dev/null +++ b/pkgs/html/test/data/tree-construction/tests17.dat @@ -0,0 +1,153 @@ +#data +<!doctype html><table><tbody><select><tr> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> +| <table> +| <tbody> +| <tr> + +#data +<!doctype html><table><tr><select><td> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> +| <table> +| <tbody> +| <tr> +| <td> + +#data +<!doctype html><table><tr><td><select><td> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <table> +| <tbody> +| <tr> +| <td> +| <select> +| <td> + +#data +<!doctype html><table><tr><th><select><td> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <table> +| <tbody> +| <tr> +| <th> +| <select> +| <td> + +#data +<!doctype html><table><caption><select><tr> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <table> +| <caption> +| <select> +| <tbody> +| <tr> + +#data +<!doctype html><select><tr> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> + +#data +<!doctype html><select><td> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> + +#data +<!doctype html><select><th> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> + +#data +<!doctype html><select><tbody> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> + +#data +<!doctype html><select><thead> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> + +#data +<!doctype html><select><tfoot> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> + +#data +<!doctype html><select><caption> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> + +#data +<!doctype html><table><tr></table>a +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <table> +| <tbody> +| <tr> +| "a" diff --git a/pkgs/html/test/data/tree-construction/tests18.dat b/pkgs/html/test/data/tree-construction/tests18.dat new file mode 100644 index 000000000..680e1f068 --- /dev/null +++ b/pkgs/html/test/data/tree-construction/tests18.dat @@ -0,0 +1,269 @@ +#data +<!doctype html><plaintext></plaintext> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <plaintext> +| "</plaintext>" + +#data +<!doctype html><table><plaintext></plaintext> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <plaintext> +| "</plaintext>" +| <table> + +#data +<!doctype html><table><tbody><plaintext></plaintext> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <plaintext> +| "</plaintext>" +| <table> +| <tbody> + +#data +<!doctype html><table><tbody><tr><plaintext></plaintext> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <plaintext> +| "</plaintext>" +| <table> +| <tbody> +| <tr> + +#data +<!doctype html><table><tbody><tr><plaintext></plaintext> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <plaintext> +| "</plaintext>" +| <table> +| <tbody> +| <tr> + +#data +<!doctype html><table><td><plaintext></plaintext> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <table> +| <tbody> +| <tr> +| <td> +| <plaintext> +| "</plaintext>" + +#data +<!doctype html><table><caption><plaintext></plaintext> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <table> +| <caption> +| <plaintext> +| "</plaintext>" + +#data +<!doctype html><table><tr><style></script></style>abc +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| "abc" +| <table> +| <tbody> +| <tr> +| <style> +| "</script>" + +#data +<!doctype html><table><tr><script></style></script>abc +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| "abc" +| <table> +| <tbody> +| <tr> +| <script> +| "</style>" + +#data +<!doctype html><table><caption><style></script></style>abc +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <table> +| <caption> +| <style> +| "</script>" +| "abc" + +#data +<!doctype html><table><td><style></script></style>abc +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <table> +| <tbody> +| <tr> +| <td> +| <style> +| "</script>" +| "abc" + +#data +<!doctype html><select><script></style></script>abc +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> +| <script> +| "</style>" +| "abc" + +#data +<!doctype html><table><select><script></style></script>abc +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> +| <script> +| "</style>" +| "abc" +| <table> + +#data +<!doctype html><table><tr><select><script></style></script>abc +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> +| <script> +| "</style>" +| "abc" +| <table> +| <tbody> +| <tr> + +#data +<!doctype html><frameset></frameset><noframes>abc +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> +| <noframes> +| "abc" + +#data +<!doctype html><frameset></frameset><noframes>abc</noframes><!--abc--> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> +| <noframes> +| "abc" +| <!-- abc --> + +#data +<!doctype html><frameset></frameset></html><noframes>abc +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> +| <noframes> +| "abc" + +#data +<!doctype html><frameset></frameset></html><noframes>abc</noframes><!--abc--> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> +| <noframes> +| "abc" +| <!-- abc --> + +#data +<!doctype html><table><tr></tbody><tfoot> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <table> +| <tbody> +| <tr> +| <tfoot> + +#data +<!doctype html><table><td><svg></svg>abc<td> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <table> +| <tbody> +| <tr> +| <td> +| <svg svg> +| "abc" +| <td> diff --git a/pkgs/html/test/data/tree-construction/tests19.dat b/pkgs/html/test/data/tree-construction/tests19.dat new file mode 100644 index 000000000..0d62f5a5b --- /dev/null +++ b/pkgs/html/test/data/tree-construction/tests19.dat @@ -0,0 +1,1237 @@ +#data +<!doctype html><math><mn DefinitionUrl="foo"> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <math math> +| <math mn> +| definitionURL="foo" + +#data +<!doctype html><html></p><!--foo--> +#errors +#document +| <!DOCTYPE html> +| <html> +| <!-- foo --> +| <head> +| <body> + +#data +<!doctype html><head></head></p><!--foo--> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <!-- foo --> +| <body> + +#data +<!doctype html><body><p><pre> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| <pre> + +#data +<!doctype html><body><p><listing> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| <listing> + +#data +<!doctype html><p><plaintext> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| <plaintext> + +#data +<!doctype html><p><h1> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| <h1> + +#data +<!doctype html><form><isindex> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <form> + +#data +<!doctype html><isindex action="POST"> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <form> +| action="POST" +| <hr> +| <label> +| "This is a searchable index. Enter search keywords: " +| <input> +| name="isindex" +| <hr> + +#data +<!doctype html><isindex prompt="this is isindex"> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <form> +| <hr> +| <label> +| "this is isindex" +| <input> +| name="isindex" +| <hr> + +#data +<!doctype html><isindex type="hidden"> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <form> +| <hr> +| <label> +| "This is a searchable index. Enter search keywords: " +| <input> +| name="isindex" +| type="hidden" +| <hr> + +#data +<!doctype html><isindex name="foo"> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <form> +| <hr> +| <label> +| "This is a searchable index. Enter search keywords: " +| <input> +| name="isindex" +| <hr> + +#data +<!doctype html><ruby><p><rp> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <ruby> +| <p> +| <rp> + +#data +<!doctype html><ruby><div><span><rp> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <ruby> +| <div> +| <span> +| <rp> + +#data +<!doctype html><ruby><div><p><rp> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <ruby> +| <div> +| <p> +| <rp> + +#data +<!doctype html><ruby><p><rt> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <ruby> +| <p> +| <rt> + +#data +<!doctype html><ruby><div><span><rt> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <ruby> +| <div> +| <span> +| <rt> + +#data +<!doctype html><ruby><div><p><rt> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <ruby> +| <div> +| <p> +| <rt> + +#data +<!doctype html><math/><foo> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <math math> +| <foo> + +#data +<!doctype html><svg/><foo> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <svg svg> +| <foo> + +#data +<!doctype html><div></body><!--foo--> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <div> +| <!-- foo --> + +#data +<!doctype html><h1><div><h3><span></h1>foo +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <h1> +| <div> +| <h3> +| <span> +| "foo" + +#data +<!doctype html><p></h3>foo +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| "foo" + +#data +<!doctype html><h3><li>abc</h2>foo +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <h3> +| <li> +| "abc" +| "foo" + +#data +<!doctype html><table>abc<!--foo--> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| "abc" +| <table> +| <!-- foo --> + +#data +<!doctype html><table> <!--foo--> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <table> +| " " +| <!-- foo --> + +#data +<!doctype html><table> b <!--foo--> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| " b " +| <table> +| <!-- foo --> + +#data +<!doctype html><select><option><option> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> +| <option> +| <option> + +#data +<!doctype html><select><option></optgroup> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> +| <option> + +#data +<!doctype html><select><option></optgroup> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> +| <option> + +#data +<!doctype html><p><math><mi><p><h1> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| <math math> +| <math mi> +| <p> +| <h1> + +#data +<!doctype html><p><math><mo><p><h1> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| <math math> +| <math mo> +| <p> +| <h1> + +#data +<!doctype html><p><math><mn><p><h1> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| <math math> +| <math mn> +| <p> +| <h1> + +#data +<!doctype html><p><math><ms><p><h1> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| <math math> +| <math ms> +| <p> +| <h1> + +#data +<!doctype html><p><math><mtext><p><h1> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| <math math> +| <math mtext> +| <p> +| <h1> + +#data +<!doctype html><frameset></noframes> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> + +#data +<!doctype html><html c=d><body></html><html a=b> +#errors +#document +| <!DOCTYPE html> +| <html> +| a="b" +| c="d" +| <head> +| <body> + +#data +<!doctype html><html c=d><frameset></frameset></html><html a=b> +#errors +#document +| <!DOCTYPE html> +| <html> +| a="b" +| c="d" +| <head> +| <frameset> + +#data +<!doctype html><html><frameset></frameset></html><!--foo--> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> +| <!-- foo --> + +#data +<!doctype html><html><frameset></frameset></html> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> +| " " + +#data +<!doctype html><html><frameset></frameset></html>abc +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> + +#data +<!doctype html><html><frameset></frameset></html><p> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> + +#data +<!doctype html><html><frameset></frameset></html></p> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> + +#data +<html><frameset></frameset></html><!doctype html> +#errors +#document +| <html> +| <head> +| <frameset> + +#data +<!doctype html><body><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> + +#data +<!doctype html><p><frameset><frame> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> +| <frame> + +#data +<!doctype html><p>a<frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| "a" + +#data +<!doctype html><p> <frameset><frame> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> +| <frame> + +#data +<!doctype html><pre><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <pre> + +#data +<!doctype html><listing><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <listing> + +#data +<!doctype html><li><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <li> + +#data +<!doctype html><dd><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <dd> + +#data +<!doctype html><dt><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <dt> + +#data +<!doctype html><button><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <button> + +#data +<!doctype html><applet><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <applet> + +#data +<!doctype html><marquee><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <marquee> + +#data +<!doctype html><object><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <object> + +#data +<!doctype html><table><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <table> + +#data +<!doctype html><area><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <area> + +#data +<!doctype html><basefont><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <basefont> +| <frameset> + +#data +<!doctype html><bgsound><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <bgsound> +| <frameset> + +#data +<!doctype html><br><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <br> + +#data +<!doctype html><embed><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <embed> + +#data +<!doctype html><img><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <img> + +#data +<!doctype html><input><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <input> + +#data +<!doctype html><keygen><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <keygen> + +#data +<!doctype html><wbr><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <wbr> + +#data +<!doctype html><hr><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <hr> + +#data +<!doctype html><textarea></textarea><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <textarea> + +#data +<!doctype html><xmp></xmp><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <xmp> + +#data +<!doctype html><iframe></iframe><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <iframe> + +#data +<!doctype html><select></select><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> + +#data +<!doctype html><svg></svg><frameset><frame> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> +| <frame> + +#data +<!doctype html><math></math><frameset><frame> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> +| <frame> + +#data +<!doctype html><svg><foreignObject><div> <frameset><frame> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> +| <frame> + +#data +<!doctype html><svg>a</svg><frameset><frame> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <svg svg> +| "a" + +#data +<!doctype html><svg> </svg><frameset><frame> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> +| <frame> + +#data +<html>aaa<frameset></frameset> +#errors +#document +| <html> +| <head> +| <body> +| "aaa" + +#data +<html> a <frameset></frameset> +#errors +#document +| <html> +| <head> +| <body> +| "a " + +#data +<!doctype html><div><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> + +#data +<!doctype html><div><body><frameset> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <div> + +#data +<!doctype html><p><math></p>a +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| <math math> +| "a" + +#data +<!doctype html><p><math><mn><span></p>a +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| <math math> +| <math mn> +| <span> +| <p> +| "a" + +#data +<!doctype html><math></html> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <math math> + +#data +<!doctype html><meta charset="ascii"> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <meta> +| charset="ascii" +| <body> + +#data +<!doctype html><meta http-equiv="content-type" content="text/html;charset=ascii"> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <meta> +| content="text/html;charset=ascii" +| http-equiv="content-type" +| <body> + +#data +<!doctype html><head><!--aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa--><meta charset="utf8"> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <!-- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa --> +| <meta> +| charset="utf8" +| <body> + +#data +<!doctype html><html a=b><head></head><html c=d> +#errors +#document +| <!DOCTYPE html> +| <html> +| a="b" +| c="d" +| <head> +| <body> + +#data +<!doctype html><image/> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <img> + +#data +<!doctype html>a<i>b<table>c<b>d</i>e</b>f +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| "a" +| <i> +| "bc" +| <b> +| "de" +| "f" +| <table> + +#data +<!doctype html><table><i>a<b>b<div>c<a>d</i>e</b>f +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <i> +| "a" +| <b> +| "b" +| <b> +| <div> +| <b> +| <i> +| "c" +| <a> +| "d" +| <a> +| "e" +| <a> +| "f" +| <table> + +#data +<!doctype html><i>a<b>b<div>c<a>d</i>e</b>f +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <i> +| "a" +| <b> +| "b" +| <b> +| <div> +| <b> +| <i> +| "c" +| <a> +| "d" +| <a> +| "e" +| <a> +| "f" + +#data +<!doctype html><table><i>a<b>b<div>c</i> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <i> +| "a" +| <b> +| "b" +| <b> +| <div> +| <i> +| "c" +| <table> + +#data +<!doctype html><table><i>a<b>b<div>c<a>d</i>e</b>f +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <i> +| "a" +| <b> +| "b" +| <b> +| <div> +| <b> +| <i> +| "c" +| <a> +| "d" +| <a> +| "e" +| <a> +| "f" +| <table> + +#data +<!doctype html><table><i>a<div>b<tr>c<b>d</i>e +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <i> +| "a" +| <div> +| "b" +| <i> +| "c" +| <b> +| "d" +| <b> +| "e" +| <table> +| <tbody> +| <tr> + +#data +<!doctype html><table><td><table><i>a<div>b<b>c</i>d +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <table> +| <tbody> +| <tr> +| <td> +| <i> +| "a" +| <div> +| <i> +| "b" +| <b> +| "c" +| <b> +| "d" +| <table> + +#data +<!doctype html><body><bgsound> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <bgsound> + +#data +<!doctype html><body><basefont> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <basefont> + +#data +<!doctype html><a><b></a><basefont> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <a> +| <b> +| <basefont> + +#data +<!doctype html><a><b></a><bgsound> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <a> +| <b> +| <bgsound> + +#data +<!doctype html><figcaption><article></figcaption>a +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <figcaption> +| <article> +| "a" + +#data +<!doctype html><summary><article></summary>a +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <summary> +| <article> +| "a" + +#data +<!doctype html><p><a><plaintext>b +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| <a> +| <plaintext> +| <a> +| "b" + +#data +<!DOCTYPE html><div>a<a></div>b<p>c</p>d +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <div> +| "a" +| <a> +| <a> +| "b" +| <p> +| "c" +| "d" diff --git a/pkgs/html/test/data/tree-construction/tests2.dat b/pkgs/html/test/data/tree-construction/tests2.dat new file mode 100644 index 000000000..60d859221 --- /dev/null +++ b/pkgs/html/test/data/tree-construction/tests2.dat @@ -0,0 +1,763 @@ +#data +<!DOCTYPE html>Test +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| "Test" + +#data +<textarea>test</div>test +#errors +Line: 1 Col: 10 Unexpected start tag (textarea). Expected DOCTYPE. +Line: 1 Col: 24 Expected closing tag. Unexpected end of file. +#document +| <html> +| <head> +| <body> +| <textarea> +| "test</div>test" + +#data +<table><td> +#errors +Line: 1 Col: 7 Unexpected start tag (table). Expected DOCTYPE. +Line: 1 Col: 11 Unexpected table cell start tag (td) in the table body phase. +Line: 1 Col: 11 Expected closing tag. Unexpected end of file. +#document +| <html> +| <head> +| <body> +| <table> +| <tbody> +| <tr> +| <td> + +#data +<table><td>test</tbody></table> +#errors +Line: 1 Col: 7 Unexpected start tag (table). Expected DOCTYPE. +Line: 1 Col: 11 Unexpected table cell start tag (td) in the table body phase. +#document +| <html> +| <head> +| <body> +| <table> +| <tbody> +| <tr> +| <td> +| "test" + +#data +<frame>test +#errors +Line: 1 Col: 7 Unexpected start tag (frame). Expected DOCTYPE. +Line: 1 Col: 7 Unexpected start tag frame. Ignored. +#document +| <html> +| <head> +| <body> +| "test" + +#data +<!DOCTYPE html><frameset>test +#errors +Line: 1 Col: 29 Unepxected characters in the frameset phase. Characters ignored. +Line: 1 Col: 29 Expected closing tag. Unexpected end of file. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> + +#data +<!DOCTYPE html><frameset><!DOCTYPE html> +#errors +Line: 1 Col: 40 Unexpected DOCTYPE. Ignored. +Line: 1 Col: 40 Expected closing tag. Unexpected end of file. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <frameset> + +#data +<!DOCTYPE html><font><p><b>test</font> +#errors +Line: 1 Col: 38 End tag (font) violates step 1, paragraph 3 of the adoption agency algorithm. +Line: 1 Col: 38 End tag (font) violates step 1, paragraph 3 of the adoption agency algorithm. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <font> +| <p> +| <font> +| <b> +| "test" + +#data +<!DOCTYPE html><dt><div><dd> +#errors +Line: 1 Col: 28 Missing end tag (div, dt). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <dt> +| <div> +| <dd> + +#data +<script></x +#errors +Line: 1 Col: 8 Unexpected start tag (script). Expected DOCTYPE. +Line: 1 Col: 11 Unexpected end of file. Expected end tag (script). +#document +| <html> +| <head> +| <script> +| "</x" +| <body> + +#data +<table><plaintext><td> +#errors +Line: 1 Col: 7 Unexpected start tag (table). Expected DOCTYPE. +Line: 1 Col: 18 Unexpected start tag (plaintext) in table context caused voodoo mode. +Line: 1 Col: 22 Unexpected end of file. Expected table content. +#document +| <html> +| <head> +| <body> +| <plaintext> +| "<td>" +| <table> + +#data +<plaintext></plaintext> +#errors +Line: 1 Col: 11 Unexpected start tag (plaintext). Expected DOCTYPE. +Line: 1 Col: 23 Expected closing tag. Unexpected end of file. +#document +| <html> +| <head> +| <body> +| <plaintext> +| "</plaintext>" + +#data +<!DOCTYPE html><table><tr>TEST +#errors +Line: 1 Col: 30 Unexpected non-space characters in table context caused voodoo mode. +Line: 1 Col: 30 Unexpected end of file. Expected table content. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| "TEST" +| <table> +| <tbody> +| <tr> + +#data +<!DOCTYPE html><body t1=1><body t2=2><body t3=3 t4=4> +#errors +Line: 1 Col: 37 Unexpected start tag (body). +Line: 1 Col: 53 Unexpected start tag (body). +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| t1="1" +| t2="2" +| t3="3" +| t4="4" + +#data +</b test +#errors +Line: 1 Col: 8 Unexpected end of file in attribute name. +Line: 1 Col: 8 End tag contains unexpected attributes. +Line: 1 Col: 8 Unexpected end tag (b). Expected DOCTYPE. +Line: 1 Col: 8 Unexpected end tag (b) after the (implied) root element. +#document +| <html> +| <head> +| <body> + +#data +<!DOCTYPE html></b test<b &=&>X +#errors +Line: 1 Col: 32 Named entity didn't end with ';'. +Line: 1 Col: 33 End tag contains unexpected attributes. +Line: 1 Col: 33 Unexpected end tag (b) after the (implied) root element. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| "X" + +#data +<!doctypehtml><scrIPt type=text/x-foobar;baz>X</SCRipt +#errors +Line: 1 Col: 9 No space after literal string 'DOCTYPE'. +Line: 1 Col: 54 Unexpected end of file in the tag name. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <script> +| type="text/x-foobar;baz" +| "X</SCRipt" +| <body> + +#data +& +#errors +Line: 1 Col: 1 Unexpected non-space characters. Expected DOCTYPE. +#document +| <html> +| <head> +| <body> +| "&" + +#data +&# +#errors +Line: 1 Col: 1 Numeric entity expected. Got end of file instead. +Line: 1 Col: 1 Unexpected non-space characters. Expected DOCTYPE. +#document +| <html> +| <head> +| <body> +| "&#" + +#data +&#X +#errors +Line: 1 Col: 3 Numeric entity expected but none found. +Line: 1 Col: 3 Unexpected non-space characters. Expected DOCTYPE. +#document +| <html> +| <head> +| <body> +| "&#X" + +#data +&#x +#errors +Line: 1 Col: 3 Numeric entity expected but none found. +Line: 1 Col: 3 Unexpected non-space characters. Expected DOCTYPE. +#document +| <html> +| <head> +| <body> +| "&#x" + +#data +- +#errors +Line: 1 Col: 4 Numeric entity didn't end with ';'. +Line: 1 Col: 4 Unexpected non-space characters. Expected DOCTYPE. +#document +| <html> +| <head> +| <body> +| "-" + +#data +&x-test +#errors +Line: 1 Col: 1 Named entity expected. Got none. +Line: 1 Col: 1 Unexpected non-space characters. Expected DOCTYPE. +#document +| <html> +| <head> +| <body> +| "&x-test" + +#data +<!doctypehtml><p><li> +#errors +Line: 1 Col: 9 No space after literal string 'DOCTYPE'. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| <li> + +#data +<!doctypehtml><p><dt> +#errors +Line: 1 Col: 9 No space after literal string 'DOCTYPE'. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| <dt> + +#data +<!doctypehtml><p><dd> +#errors +Line: 1 Col: 9 No space after literal string 'DOCTYPE'. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| <dd> + +#data +<!doctypehtml><p><form> +#errors +Line: 1 Col: 9 No space after literal string 'DOCTYPE'. +Line: 1 Col: 23 Expected closing tag. Unexpected end of file. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| <form> + +#data +<!DOCTYPE html><p></P>X +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <p> +| "X" + +#data +& +#errors +Line: 1 Col: 4 Named entity didn't end with ';'. +Line: 1 Col: 4 Unexpected non-space characters. Expected DOCTYPE. +#document +| <html> +| <head> +| <body> +| "&" + +#data +&AMp; +#errors +Line: 1 Col: 1 Named entity expected. Got none. +Line: 1 Col: 1 Unexpected non-space characters. Expected DOCTYPE. +#document +| <html> +| <head> +| <body> +| "&AMp;" + +#data +<!DOCTYPE html><html><head></head><body><thisISasillyTESTelementNameToMakeSureCrazyTagNamesArePARSEDcorrectLY> +#errors +Line: 1 Col: 110 Expected closing tag. Unexpected end of file. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <thisisasillytestelementnametomakesurecrazytagnamesareparsedcorrectly> + +#data +<!DOCTYPE html>X</body>X +#errors +Line: 1 Col: 24 Unexpected non-space characters in the after body phase. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| "XX" + +#data +<!DOCTYPE html><!-- X +#errors +Line: 1 Col: 21 Unexpected end of file in comment. +#document +| <!DOCTYPE html> +| <!-- X --> +| <html> +| <head> +| <body> + +#data +<!DOCTYPE html><table><caption>test TEST</caption><td>test +#errors +Line: 1 Col: 54 Unexpected table cell start tag (td) in the table body phase. +Line: 1 Col: 58 Expected closing tag. Unexpected end of file. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <table> +| <caption> +| "test TEST" +| <tbody> +| <tr> +| <td> +| "test" + +#data +<!DOCTYPE html><select><option><optgroup> +#errors +Line: 1 Col: 41 Expected closing tag. Unexpected end of file. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> +| <option> +| <optgroup> + +#data +<!DOCTYPE html><select><optgroup><option></optgroup><option><select><option> +#errors +Line: 1 Col: 68 Unexpected select start tag in the select phase treated as select end tag. +Line: 1 Col: 76 Expected closing tag. Unexpected end of file. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> +| <optgroup> +| <option> +| <option> +| <option> + +#data +<!DOCTYPE html><select><optgroup><option><optgroup> +#errors +Line: 1 Col: 51 Expected closing tag. Unexpected end of file. +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> +| <optgroup> +| <option> +| <optgroup> + +#data +<!DOCTYPE html><datalist><option>foo</datalist>bar +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <datalist> +| <option> +| "foo" +| "bar" + +#data +<!DOCTYPE html><font><input><input></font> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <font> +| <input> +| <input> + +#data +<!DOCTYPE html><!-- XXX - XXX --> +#errors +#document +| <!DOCTYPE html> +| <!-- XXX - XXX --> +| <html> +| <head> +| <body> + +#data +<!DOCTYPE html><!-- XXX - XXX +#errors +Line: 1 Col: 29 Unexpected end of file in comment (-) +#document +| <!DOCTYPE html> +| <!-- XXX - XXX --> +| <html> +| <head> +| <body> + +#data +<!DOCTYPE html><!-- XXX - XXX - XXX --> +#errors +#document +| <!DOCTYPE html> +| <!-- XXX - XXX - XXX --> +| <html> +| <head> +| <body> + +#data +<isindex test=x name=x> +#errors +Line: 1 Col: 23 Unexpected start tag (isindex). Expected DOCTYPE. +Line: 1 Col: 23 Unexpected start tag isindex. Don't use it! +#document +| <html> +| <head> +| <body> +| <form> +| <hr> +| <label> +| "This is a searchable index. Enter search keywords: " +| <input> +| name="isindex" +| test="x" +| <hr> + +#data +test +test +#errors +Line: 2 Col: 4 Unexpected non-space characters. Expected DOCTYPE. +#document +| <html> +| <head> +| <body> +| "test +test" + +#data +<!DOCTYPE html><body><title>test</body> +#errors +#document +| +| +| +| +| +| "test</body>" + +#data +<!DOCTYPE html><body><title>X +#errors +#document +| +| +| +| +| +| "X" +| <meta> +| name="z" +| <link> +| rel="foo" +| <style> +| " +x { content:"</style" } " + +#data +<!DOCTYPE html><select><optgroup></optgroup></select> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> +| <select> +| <optgroup> + +#data + + +#errors +Line: 2 Col: 1 Unexpected End of file. Expected DOCTYPE. +#document +| <html> +| <head> +| <body> + +#data +<!DOCTYPE html> <html> +#errors +#document +| <!DOCTYPE html> +| <html> +| <head> +| <body> + +#data +<!DOCTYPE html><script> +</script> <title>x +#errors +#document +| +| +| +| +#errors +Line: 1 Col: 6 Unexpected start tag (head). Expected DOCTYPE. +Line: 1 Col: 21 Unexpected start tag (script) that can be in head. Moved. +#document +| +| +| +#errors +Line: 1 Col: 6 Unexpected start tag (head). Expected DOCTYPE. +Line: 1 Col: 28 Unexpected start tag (style) that can be in head. Moved. +#document +| +| +| +#errors +Line: 1 Col: 6 Unexpected start tag (head). Expected DOCTYPE. +#document +| +| +| +| +| "x" +| x +#errors +Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE. +Line: 1 Col: 22 Unexpected end of file. Expected end tag (style). +#document +| +| +| --> x +#errors +Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE. +#document +| +| +| x +#errors +Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE. +#document +| +| +| x +#errors +Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE. +#document +| +| +| x +#errors +Line: 1 Col: 7 Unexpected start tag (style). Expected DOCTYPE. +#document +| +| +|

+#errors +#document +| +| +| +| +| +| ddd +#errors +#document +| +| +| +#errors +#document +| +| +| +| +|
  • +| +| + + +
    +
    + +
    + +
    +
    +
    +
    +
    +

    +
    
    +		
    +
      + + + + +
      + +
      +
      +
      +
      +
      + +
      + + + + + + + + + + + +
      +
      + +
      +
      +
      +
      + +
      +
      + + + + + + + + + +

      +
      + +
      +
      +
      +
      +
      +
      + +
      + + + + +
      +
      +
      +
      +
      + +

      +
      + +
      + + + + +
      +
      +
      +
      + +

      +
      + +
      + + + + +
      +
      +
      +
      +
      +
      + +

      +
      + +
      +
    • + + + +
      + +

        +
      1. +
      2. +
      3. +
      4. +
      5. +
      6. +
      7. +
      8. +
      9. +
      10. +
      11. +
      12. +
      + +

      + span1 + em1 + + em2 + span2 + strong1 + em3 + span3 + span4 + strong2 + em4 +

      + + +
      +
      +
      +
      + +

      +

      +

      +
      + +
      +

      +

      +

      + +
      +
      +
      +
      + +
      +

      + +

      +

      + + +

      +

      + + + +

      +
      > + +
      +

      +

      +

      +

      Text node

      +

      +
      + + + +
      +
      +
      +
      +
      +
      + +
      + + + + + + + + + + + + + + + + + + + + + + + +
      + +
      +
      +
      +
      + +

      +

      +

      +
      + +
      All pseudo-element tests
      + +
      +

      +

      +

      + + +
      +
      +

      +
      +

      +
      +
      +
      +
      + + + + + + +
      + +
      +
      +
      + +
        +
      • +
      • +
      • +
      • +
      + + + + + + +
      + +
      +
      +
      +
      +
      +
      +
      +
      +
      + +
      +
      +
      +
      +
      +
      +
      +
      +
      + +
      +
      +
      +
      +
      +
      +

      +
      +
      +
      +

      +

      +
      + +
      +
      +
      +
      +
      +
      +

      +
      +
      +
      +

      +

      +
      + +
      + + +
      + + + diff --git a/pkgs/html/test/selectors/level1_baseline_test.dart b/pkgs/html/test/selectors/level1_baseline_test.dart new file mode 100644 index 000000000..d5614e960 --- /dev/null +++ b/pkgs/html/test/selectors/level1_baseline_test.dart @@ -0,0 +1,126 @@ +/// Test for the Selectors API ported from +/// +/// +/// Note, unlike the original we don't operate in-browser on a DOM loaded into +/// an iframe, but instead operate over a parsed DOM. + +@TestOn('vm') +library; + +import 'dart:io'; + +import 'package:html/dom.dart'; +import 'package:html/parser.dart'; +import 'package:path/path.dart' as p; +import 'package:test/test.dart'; + +import '../support.dart'; +import 'level1_lib.dart'; +import 'selectors.dart'; + +Future testContentDocument() async { + final testPath = + p.join(await testDirectory, 'selectors', 'level1-content.html'); + return parse(File(testPath).readAsStringSync()); +} + +int testType = testQsaBaseline; // Only run baseline tests. +String docType = 'html'; // Only run tests suitable for HTML + +void main() async { + /* + * This test suite tests Selectors API methods in 4 different contexts: + * 1. Document node + * 2. In-document Element node + * 3. Detached Element node (an element with no parent, not in the document) + * 4. Document Fragment node + * + * For each context, the following tests are run: + * + * The interface check tests ensure that each type of node exposes the Selectors API methods + * + * The special selector tests verify the result of passing special values for the selector parameter, + * to ensure that the correct WebIDL processing is performed, such as stringification of null and + * undefined and missing parameter. The universal selector is also tested here, rather than with the + * rest of ordinary selectors for practical reasons. + * + * The static list verification tests ensure that the node lists returned by the method remain unchanged + * due to subsequent document modication, and that a new list is generated each time the method is + * invoked based on the current state of the document. + * + * The invalid selector tests ensure that SyntaxError is thrown for invalid forms of selectors + * + * The valid selector tests check the result from querying many different types of selectors, with a + * list of expected elements. This checks that querySelector() always returns the first result from + * querySelectorAll(), and that all matching elements are correctly returned in tree-order. The tests + * can be limited by specifying the test types to run, using the testType variable. The constants for this + * can be found in selectors.js. + * + * All the selectors tested for both the valid and invalid selector tests are found in selectors.js. + * See comments in that file for documentation of the format used. + * + * The level1-lib.js file contains all the common test functions for running each of the aforementioned tests + */ + + // Prepare the nodes for testing + //doc = frame.contentDocument; // Document Node tests + doc = await testContentDocument(); + + final element = doc.getElementById('root')!; // In-document Element Node tests + + //Setup the namespace tests + setupSpecialElements(element); + + final outOfScope = element + .clone(true); // Append this to the body before running the in-document + // Element tests, but after running the Document tests. This + // tests that no elements that are not descendants of element + // are selected. + + traverse(outOfScope, (elem) { + // Annotate each element as being a clone; used for verifying + elem.attributes['data-clone'] = + ''; // that none of these elements ever match. + }); + + final detached = element.clone(true); // Detached Element Node tests + + final fragment = doc.createDocumentFragment(); // Fragment Node tests + fragment.append(element.clone(true)); + + // Setup Tests + runSpecialSelectorTests('Document', SelectorAdaptor.document(doc)); + runSpecialSelectorTests( + 'Detached Element', SelectorAdaptor.element(detached)); + runSpecialSelectorTests('Fragment', SelectorAdaptor.fragment(fragment)); + runSpecialSelectorTests( + 'In-document Element', SelectorAdaptor.element(element)); + + verifyStaticList('Document', SelectorAdaptor.document(doc)); + verifyStaticList('Detached Element', SelectorAdaptor.element(detached)); + verifyStaticList('Fragment', SelectorAdaptor.fragment(fragment)); + verifyStaticList('In-document Element', SelectorAdaptor.element(element)); + + // TODO(jmesserly): fix negative tests + //runInvalidSelectorTest('Document', doc, invalidSelectors); + //runInvalidSelectorTest('Detached Element', detached, invalidSelectors); + //runInvalidSelectorTest('Fragment', fragment, invalidSelectors); + //runInvalidSelectorTest('In-document Element', element, invalidSelectors); + + runValidSelectorTest('Document', SelectorAdaptor.document(doc), + validSelectors, testType, docType); + runValidSelectorTest('Detached Element', SelectorAdaptor.element(detached), + validSelectors, testType, docType); + runValidSelectorTest('Fragment', SelectorAdaptor.fragment(fragment), + validSelectors, testType, docType); + + group('out of scope', () { + setUp(() { + doc.body!.append(outOfScope); // Append before in-document Element tests. + // None of these elements should match + }); + tearDown(outOfScope.remove); + runValidSelectorTest('In-document Element', + SelectorAdaptor.element(element), validSelectors, testType, docType); + }); +} diff --git a/pkgs/html/test/selectors/level1_lib.dart b/pkgs/html/test/selectors/level1_lib.dart new file mode 100644 index 000000000..d77aac796 --- /dev/null +++ b/pkgs/html/test/selectors/level1_lib.dart @@ -0,0 +1,347 @@ +/// Test for the Selectors API ported from +/// +/// +/// Note: tried to make minimal changes possible here. Hence some oddities such +/// as [runTest] arguments having a different order, long lines, etc. +/// +/// As usual with ports: being faithful to the original style is more important +/// than other style goals, as it reduces friction to integrating changes +/// from upstream. +library; + +import 'package:html/dom.dart'; +import 'package:test/test.dart' as unittest; + +late Document doc; + +// Create and append special elements that cannot be created correctly with HTML +// markup alone. +void setupSpecialElements(Element parent) { + // Setup null and undefined tests + parent.append(doc.createElement('null')); + parent.append(doc.createElement('undefined')); + + // Setup namespace tests + final anyNS = doc.createElement('div'); + final noNS = doc.createElement('div'); + anyNS.id = 'any-namespace'; + noNS.id = 'no-namespace'; + + var div = [ + doc.createElement('div'), + doc.createElementNS('http://www.w3.org/1999/xhtml', 'div'), + doc.createElementNS('', 'div'), + doc.createElementNS('http://www.example.org/ns', 'div') + ]; + + div[0].id = 'any-namespace-div1'; + div[1].id = 'any-namespace-div2'; + div[2].attributes['id'] = + 'any-namespace-div3'; // Non-HTML elements can't use .id property + div[3].attributes['id'] = 'any-namespace-div4'; + + for (var i = 0; i < div.length; i++) { + anyNS.append(div[i]); + } + + div = [ + doc.createElement('div'), + doc.createElementNS('http://www.w3.org/1999/xhtml', 'div'), + doc.createElementNS('', 'div'), + doc.createElementNS('http://www.example.org/ns', 'div') + ]; + + div[0].id = 'no-namespace-div1'; + div[1].id = 'no-namespace-div2'; + div[2].attributes['id'] = + 'no-namespace-div3'; // Non-HTML elements can't use .id property + div[3].attributes['id'] = 'no-namespace-div4'; + + for (var i = 0; i < div.length; i++) { + noNS.append(div[i]); + } + + parent.append(anyNS); + parent.append(noNS); +} + +/// Verify that the NodeList returned by querySelectorAll is static and and that +/// a new list is created after each call. A static list should not be affected +/// by subsequent changes to the DOM. +void verifyStaticList(String type, SelectorAdaptor root) { + List pre; + List post; + late int preLength; + + runTest(() { + pre = root.querySelectorAll('div'); + preLength = pre.length; + + final div = doc.createElement('div'); + root.isDocument ? root.body!.append(div) : root.append(div); + + assertEquals( + pre.length, preLength, 'The length of the NodeList should not change.'); + }, '$type: static NodeList'); + + runTest(() { + post = root.querySelectorAll('div'); + assertEquals(post.length, preLength + 1, + 'The length of the new NodeList should be 1 more than the previous list.'); + }, '$type: new NodeList'); +} + +/// Verify handling of special values for the selector parameter, including +/// stringification of null and undefined, and the handling of the empty string. +void runSpecialSelectorTests(String type, SelectorAdaptor root) { + // Dart note: changed these tests because we don't have auto conversion to + // String like JavaScript does. + runTest(() { + // 1 + assertEquals(root.querySelectorAll('null').length, 1, + "This should find one element with the tag name 'NULL'."); + }, '$type.querySelectorAll null'); + + runTest(() { + // 2 + assertEquals(root.querySelectorAll('undefined').length, 1, + "This should find one element with the tag name 'UNDEFINED'."); + }, '$type.querySelectorAll undefined'); + + // runTest(() { + // // 3 + // assertThrows((e) => e is NoSuchMethodError, () { + // root.querySelectorAll(); + // }, 'This should throw a TypeError.'); + // }, '$type.querySelectorAll no parameter'); + + runTest(() { + // 4 + final elm = root.querySelector('null'); + assertNotEquals(elm, null, 'This should find an element.'); + // TODO(jmesserly): change "localName" back to "tagName" once implemented. + assertEquals(elm!.localName?.toUpperCase(), 'NULL', + "The tag name should be 'NULL'."); + }, '$type.querySelector null'); + + runTest(() { + // 5 + final elm = root.querySelector('undefined'); + assertNotEquals(elm, 'undefined', 'This should find an element.'); + // TODO(jmesserly): change "localName" back to "tagName" once implemented. + assertEquals(elm!.localName?.toUpperCase(), 'UNDEFINED', + "The tag name should be 'UNDEFINED'."); + }, '$type.querySelector undefined'); + + // runTest(() { + // // 6 + // assertThrows((e) => e is NoSuchMethodError, () { + // root.querySelector(); + // }, 'This should throw a TypeError.'); + // }, '$type.querySelector no parameter'); + + runTest(() { + // 7 + final result = root.querySelectorAll('*'); + var i = 0; + traverse(root.asNode!, (elem) { + if (!identical(elem, root.asNode)) { + assertEquals( + elem, result[i], 'The result in index $i should be in tree order.'); + i++; + } + }); + }, '$type.querySelectorAll tree order'); +} + +/// Tests containing this string fail for an unknown reason +final _failureName = 'matching custom data-* attribute with'; + +String? _getSkip(String name) { + if (name.contains(_failureName)) { + return 'Tests related to `$_failureName` fail for an unknown reason.'; + } + return null; +} + +/* + * Execute queries with the specified valid selectors for both querySelector() and querySelectorAll() + * Only run these tests when results are expected. Don't run for syntax error tests. + */ +void runValidSelectorTest(String type, SelectorAdaptor root, + List> selectors, int testType, String docType) { + var nodeType = ''; + switch (root.nodeType) { + case Node.DOCUMENT_NODE: + nodeType = 'document'; + break; + case Node.ELEMENT_NODE: + nodeType = root.parentNode != null ? 'element' : 'detached'; + break; + case Node.DOCUMENT_FRAGMENT_NODE: + nodeType = 'fragment'; + break; + default: + throw StateError('Reached unreachable code path.'); + } + + for (var i = 0; i < selectors.length; i++) { + final s = selectors[i]; + final n = s['name'] as String; + final skip = _getSkip(n); + final q = s['selector'] as String; + final e = s['expect'] as List?; + + final exclude = s['exclude']; + + if ((exclude is! List || + (!exclude.contains(nodeType) && !exclude.contains(docType))) && + ((s['testType'] as int) & testType != 0)) { + //console.log("Running tests " + nodeType + ": " + s["testType"] + "&" + testType + "=" + (s["testType"] & testType) + ": " + JSON.stringify(s)) + late List foundall; + Element? found; + + runTest(() { + foundall = root.querySelectorAll(q); + assertNotEquals(foundall, null, 'The method should not return null.'); + assertEquals(foundall.length, e!.length, + 'The method should return the expected number of matches.'); + + for (var i = 0; i < e.length; i++) { + assertNotEquals( + foundall[i], null, 'The item in index $i should not be null.'); + assertEquals(foundall[i].attributes['id'], e[i], + 'The item in index $i should have the expected ID.'); + assertFalse(foundall[i].attributes.containsKey('data-clone'), + 'This should not be a cloned element.'); + } + }, '$type.querySelectorAll: $n:$q', skip: skip); + + runTest(() { + found = root.querySelector(q); + + if (e!.isNotEmpty) { + assertNotEquals(found, null, 'The method should return a match.'); + assertEquals(found!.attributes['id'], e[0], + 'The method should return the first match.'); + assertEquals(found, foundall[0], + 'The result should match the first item from querySelectorAll.'); + assertFalse(found!.attributes.containsKey('data-clone'), + 'This should not be annotated as a cloned element.'); + } else { + assertEquals(found, null, 'The method should not match anything.'); + } + }, '$type.querySelector: $n : $q', skip: skip); + } else { + //console.log("Excluding for " + nodeType + ": " + s["testType"] + "&" + testType + "=" + (s["testType"] & testType) + ": " + JSON.stringify(s)) + } + } +} + +/* + * Execute queries with the specified invalid selectors for both querySelector() and querySelectorAll() + * Only run these tests when errors are expected. Don't run for valid selector tests. + */ +void runInvalidSelectorTest( + String type, SelectorAdaptor root, List> selectors) { + for (var i = 0; i < selectors.length; i++) { + final s = selectors[i]; + final n = s['name'] as String; + final q = s['selector'] as String; + + // Dart note: FormatException seems a reasonable mapping of SyntaxError + runTest(() { + assertThrows((Object e) => e is FormatException, () { + root.querySelector(q); + }); + }, '$type.querySelector: $n:$q'); + + runTest(() { + assertThrows((Object e) => e is FormatException, () { + root.querySelectorAll(q); + }); + }, '$type.querySelectorAll: $n:$q'); + } +} + +void traverse(Node elem, void Function(Node) fn) { + if (elem.nodeType == Node.ELEMENT_NODE) { + fn(elem); + } + + // Dart note: changed this since our DOM API doesn't support nextNode yet. + for (var node in elem.nodes) { + traverse(node, fn); + } +} + +void runTest(dynamic Function() body, String name, {String? skip}) => + unittest.test(name, body, skip: skip); + +void assertTrue(bool value, String reason) => + unittest.expect(value, unittest.isTrue, reason: reason); + +void assertFalse(bool value, String reason) => + unittest.expect(value, unittest.isFalse, reason: reason); + +void assertEquals(dynamic x, dynamic y, String reason) => + unittest.expect(x, y, reason: reason); + +void assertNotEquals(dynamic x, dynamic y, String reason) => + unittest.expect(x, unittest.isNot(y), reason: reason); + +void assertThrows(Object exception, void Function() body, [String? reason]) => + unittest.expect(body, unittest.throwsA(exception), reason: reason); + +/// Used for testing. +/// +/// This class delegates to one of three different kinds of objects. They share +/// methods with similar signatures but do not share a type hierarchy. +/// Previously these methods were invoked through `dynamic`. +class SelectorAdaptor { + final Document? document; + final Element? element; + final DocumentFragment? fragment; + + SelectorAdaptor.document(this.document) + : element = null, + fragment = null; + + SelectorAdaptor.element(this.element) + : document = null, + fragment = null; + + SelectorAdaptor.fragment(this.fragment) + : document = null, + element = null; + + bool get isDocument => document != null; + + Element? get body => document?.body; + + Node? get asNode => document ?? element ?? fragment; + + int get nodeType => asNode!.nodeType; + + Node? get parentNode => asNode!.parentNode; + + void append(Node node) { + asNode!.append(node); + } + + Element? querySelector(String selector) { + if (document != null) return document!.querySelector(selector); + if (element != null) return element!.querySelector(selector); + if (fragment != null) return fragment!.querySelector(selector); + + throw StateError('unsupported'); + } + + List querySelectorAll(String selector) { + if (document != null) return document!.querySelectorAll(selector); + if (element != null) return element!.querySelectorAll(selector); + if (fragment != null) return fragment!.querySelectorAll(selector); + + throw StateError('unsupported'); + } +} diff --git a/pkgs/html/test/selectors/selectors.dart b/pkgs/html/test/selectors/selectors.dart new file mode 100644 index 000000000..81e69c693 --- /dev/null +++ b/pkgs/html/test/selectors/selectors.dart @@ -0,0 +1,1875 @@ +/// Test for the Selectors API ported from +/// +library; + +// Bit-mapped flags to indicate which tests the selector is suitable for +final int testQsaBaseline = + 0x01; // querySelector() and querySelectorAll() baseline tests +final int testQsaAdditional = + 0x02; // querySelector() and querySelectorAll() additional tests +final int testFindBaseline = + 0x04; // find() and findAll() baseline tests, may be unsuitable for querySelector[All] +final int testFindAdditional = + 0x08; // find() and findAll() additional tests, may be unsuitable for querySelector[All] +final int testMatchBaseline = 0x10; // matches() baseline tests +int testMatchAdditional = 0x20; // matches() additional tests + +/* + * All of these invalid selectors should result in a SyntaxError being thrown by the APIs. + * + * name: A descriptive name of the selector being tested + * selector: The selector to test + */ +final invalidSelectors = [ + {'name': 'Empty String', 'selector': ''}, + {'name': 'Invalid character', 'selector': '['}, + {'name': 'Invalid character', 'selector': ']'}, + {'name': 'Invalid character', 'selector': '('}, + {'name': 'Invalid character', 'selector': ')'}, + {'name': 'Invalid character', 'selector': '{'}, + {'name': 'Invalid character', 'selector': '}'}, + {'name': 'Invalid character', 'selector': '<'}, + {'name': 'Invalid character', 'selector': '>'}, + {'name': 'Invalid ID', 'selector': '#'}, + {'name': 'Invalid group of selectors', 'selector': 'div,'}, + {'name': 'Invalid class', 'selector': '.'}, + {'name': 'Invalid class', 'selector': '.5cm'}, + {'name': 'Invalid class', 'selector': '..test'}, + {'name': 'Invalid class', 'selector': '.foo..quux'}, + {'name': 'Invalid class', 'selector': '.bar.'}, + {'name': 'Invalid combinator', 'selector': 'div & address, p'}, + {'name': 'Invalid combinator', 'selector': 'div >> address, p'}, + {'name': 'Invalid combinator', 'selector': 'div ++ address, p'}, + {'name': 'Invalid combinator', 'selector': 'div ~~ address, p'}, + {'name': 'Invalid [att=value] selector', 'selector': '[*=test]'}, + {'name': 'Invalid [att=value] selector', 'selector': '[*|*=test]'}, + { + 'name': 'Invalid [att=value] selector', + 'selector': '[class= space unquoted ]' + }, + {'name': 'Unknown pseudo-class', 'selector': 'div:example'}, + {'name': 'Unknown pseudo-class', 'selector': ':example'}, + {'name': 'Unknown pseudo-element', 'selector': 'div::example'}, + {'name': 'Unknown pseudo-element', 'selector': '::example'}, + {'name': 'Invalid pseudo-element', 'selector': ':::before'}, + {'name': 'Undeclared namespace', 'selector': 'ns|div'}, + {'name': 'Undeclared namespace', 'selector': ':not(ns|div)'}, + {'name': 'Invalid namespace', 'selector': '^|div'}, + {'name': 'Invalid namespace', 'selector': '\$|div'} +]; + +/* + * All of these should be valid selectors, expected to match zero or more elements in the document. + * None should throw any errors. + * + * name: A descriptive name of the selector being tested + * selector: The selector to test + * 'expect': A list of IDs of the elements expected to be matched. List must be given in tree order. + * 'exclude': An array of contexts to exclude from testing. The valid values are: + * ["document", "element", "fragment", "detached", "html", "xhtml"] + * The "html" and "xhtml" values represent the type of document being queried. These are useful + * for tests that are affected by differences between HTML and XML, such as case sensitivity. + * 'level': An integer indicating the CSS or Selectors level in which the selector being tested was introduced. + * 'testType': A bit-mapped flag indicating the type of test. + * + * Note: Interactive pseudo-classes (:active :hover and :focus) have not been tested in this test suite. + */ +final List> validSelectors = [ + // Type Selector + { + 'name': 'Type selector, matching html element', + 'selector': 'html', + 'expect': ['html'], + 'exclude': ['element', 'fragment', 'detached'], + 'level': 1, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': 'Type selector, matching html element', + 'selector': 'html', + 'expect': [] /*no matches*/, + 'exclude': ['document'], + 'level': 1, + 'testType': testQsaBaseline + }, + { + 'name': 'Type selector, matching body element', + 'selector': 'body', + 'expect': ['body'], + 'exclude': ['element', 'fragment', 'detached'], + 'level': 1, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': 'Type selector, matching body element', + 'selector': 'body', + 'expect': [] /*no matches*/, + 'exclude': ['document'], + 'level': 1, + 'testType': testQsaBaseline + }, + + // Universal Selector + // Testing "*" for entire an entire context node is handled separately. + { + 'name': + 'Universal selector, matching all children of element with specified ID', + 'selector': '#universal>*', + 'expect': [ + 'universal-p1', + 'universal-hr1', + 'universal-pre1', + 'universal-p2', + 'universal-address1' + ], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + 'Universal selector, matching all grandchildren of element with specified ID', + 'selector': '#universal>*>*', + 'expect': [ + 'universal-code1', + 'universal-span1', + 'universal-a1', + 'universal-code2' + ], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + 'Universal selector, matching all children of empty element with specified ID', + 'selector': '#empty>*', + 'expect': [] /*no matches*/, + 'level': 2, + 'testType': testQsaBaseline + }, + { + 'name': + 'Universal selector, matching all descendants of element with specified ID', + 'selector': '#universal *', + 'expect': [ + 'universal-p1', + 'universal-code1', + 'universal-hr1', + 'universal-pre1', + 'universal-span1', + 'universal-p2', + 'universal-a1', + 'universal-address1', + 'universal-code2', + 'universal-a2' + ], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + + // Attribute Selectors + // - presence [att] + { + 'name': 'Attribute presence selector, matching align attribute with value', + 'selector': '.attr-presence-div1[align]', + 'expect': ['attr-presence-div1'], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + 'Attribute presence selector, matching align attribute with empty value', + 'selector': '.attr-presence-div2[align]', + 'expect': ['attr-presence-div2'], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + 'Attribute presence selector, matching title attribute, case insensitivity', + 'selector': '#attr-presence [TiTlE]', + 'expect': ['attr-presence-a1', 'attr-presence-span1'], + 'exclude': ['xhtml'], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + 'Attribute presence selector, not matching title attribute, case sensitivity', + 'selector': '#attr-presence [TiTlE]', + 'expect': [], + 'exclude': ['html'], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': 'Attribute presence selector, matching custom data-* attribute', + 'selector': '[data-attr-presence]', + 'expect': ['attr-presence-pre1', 'attr-presence-blockquote1'], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + 'Attribute presence selector, not matching attribute with similar name', + 'selector': '.attr-presence-div3[align], .attr-presence-div4[align]', + 'expect': [] /*no matches*/, + 'level': 2, + 'testType': testQsaBaseline + }, + { + 'name': + 'Attribute presence selector, matching attribute with non-ASCII characters', + 'selector': 'ul[data-中文]', + 'expect': ['attr-presence-ul1'], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + 'Attribute presence selector, not matching default option without selected attribute', + 'selector': '#attr-presence-select1 option[selected]', + 'expect': [] /* no matches */, + 'level': 2, + 'testType': testQsaBaseline + }, + { + 'name': + 'Attribute presence selector, matching option with selected attribute', + 'selector': '#attr-presence-select2 option[selected]', + 'expect': ['attr-presence-select2-option4'], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + 'Attribute presence selector, matching multiple options with selected attributes', + 'selector': '#attr-presence-select3 option[selected]', + 'expect': [ + 'attr-presence-select3-option2', + 'attr-presence-select3-option3' + ], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + + // - value [att=val] + { + 'name': 'Attribute value selector, matching align attribute with value', + 'selector': '#attr-value [align="center"]', + 'expect': ['attr-value-div1'], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + 'Attribute value selector, matching align attribute with empty value', + 'selector': '#attr-value [align=""]', + 'expect': ['attr-value-div2'], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + 'Attribute value selector, not matching align attribute with partial value', + 'selector': '#attr-value [align="c"]', + 'expect': [] /*no matches*/, + 'level': 2, + 'testType': testQsaBaseline + }, + { + 'name': + 'Attribute value selector, not matching align attribute with incorrect value', + 'selector': '#attr-value [align="centera"]', + 'expect': [] /*no matches*/, + 'level': 2, + 'testType': testQsaBaseline + }, + { + 'name': + 'Attribute value selector, matching custom data-* attribute with unicode escaped value', + 'selector': '[data-attr-value="\\e9"]', + 'expect': ['attr-value-div3'], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + 'Attribute value selector, matching custom data-* attribute with escaped character', + 'selector': '[data-attr-value_foo="\\e9"]', + 'expect': ['attr-value-div4'], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + 'Attribute value selector with single-quoted value, matching multiple inputs with type attributes', + 'selector': + "#attr-value input[type='hidden'],#attr-value input[type='radio']", + 'expect': [ + 'attr-value-input3', + 'attr-value-input4', + 'attr-value-input6', + 'attr-value-input8', + 'attr-value-input9' + ], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + 'Attribute value selector with double-quoted value, matching multiple inputs with type attributes', + 'selector': + "#attr-value input[type=\"hidden\"],#attr-value input[type='radio']", + 'expect': [ + 'attr-value-input3', + 'attr-value-input4', + 'attr-value-input6', + 'attr-value-input8', + 'attr-value-input9' + ], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + 'Attribute value selector with unquoted value, matching multiple inputs with type attributes', + 'selector': '#attr-value input[type=hidden],#attr-value input[type=radio]', + 'expect': [ + 'attr-value-input3', + 'attr-value-input4', + 'attr-value-input6', + 'attr-value-input8', + 'attr-value-input9' + ], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + 'Attribute value selector, matching attribute with value using non-ASCII characters', + 'selector': '[data-attr-value=中文]', + 'expect': ['attr-value-div5'], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + + // - whitespace-separated list [att~=val] + { + 'name': + 'Attribute whitespace-separated list selector, matching class attribute with value', + 'selector': '#attr-whitespace [class~="div1"]', + 'expect': ['attr-whitespace-div1'], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + 'Attribute whitespace-separated list selector, not matching class attribute with empty value', + 'selector': '#attr-whitespace [class~=""]', + 'expect': [] /*no matches*/, + 'level': 2, + 'testType': testQsaBaseline + }, + { + 'name': + 'Attribute whitespace-separated list selector, not matching class attribute with partial value', + 'selector': '[data-attr-whitespace~="div"]', + 'expect': [] /*no matches*/, + 'level': 2, + 'testType': testQsaBaseline + }, + { + 'name': + 'Attribute whitespace-separated list selector, matching custom data-* attribute with unicode escaped value', + 'selector': '[data-attr-whitespace~="\\0000e9"]', + 'expect': ['attr-whitespace-div4'], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + 'Attribute whitespace-separated list selector, matching custom data-* attribute with escaped character', + 'selector': '[data-attr-whitespace_foo~="\\e9"]', + 'expect': ['attr-whitespace-div5'], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + 'Attribute whitespace-separated list selector with single-quoted value, matching multiple links with rel attributes', + 'selector': + "#attr-whitespace a[rel~='bookmark'], #attr-whitespace a[rel~='nofollow']", + 'expect': [ + 'attr-whitespace-a1', + 'attr-whitespace-a2', + 'attr-whitespace-a3', + 'attr-whitespace-a5', + 'attr-whitespace-a7' + ], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + 'Attribute whitespace-separated list selector with double-quoted value, matching multiple links with rel attributes', + 'selector': + "#attr-whitespace a[rel~=\"bookmark\"],#attr-whitespace a[rel~='nofollow']", + 'expect': [ + 'attr-whitespace-a1', + 'attr-whitespace-a2', + 'attr-whitespace-a3', + 'attr-whitespace-a5', + 'attr-whitespace-a7' + ], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + 'Attribute whitespace-separated list selector with unquoted value, matching multiple links with rel attributes', + 'selector': + '#attr-whitespace a[rel~=bookmark], #attr-whitespace a[rel~=nofollow]', + 'expect': [ + 'attr-whitespace-a1', + 'attr-whitespace-a2', + 'attr-whitespace-a3', + 'attr-whitespace-a5', + 'attr-whitespace-a7' + ], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + 'Attribute whitespace-separated list selector with double-quoted value, not matching value with space', + 'selector': '#attr-whitespace a[rel~="book mark"]', + 'expect': [] /* no matches */, + 'level': 2, + 'testType': testQsaBaseline + }, + { + 'name': + 'Attribute whitespace-separated list selector, matching title attribute with value using non-ASCII characters', + 'selector': '#attr-whitespace [title~=中文]', + 'expect': ['attr-whitespace-p1'], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + + // - hyphen-separated list [att|=val] + { + 'name': + 'Attribute hyphen-separated list selector, not matching unspecified lang attribute', + 'selector': '#attr-hyphen-div1[lang|="en"]', + 'expect': [] /*no matches*/, + 'level': 2, + 'testType': testQsaBaseline + }, + { + 'name': + 'Attribute hyphen-separated list selector, matching lang attribute with exact value', + 'selector': '#attr-hyphen-div2[lang|="fr"]', + 'expect': ['attr-hyphen-div2'], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + 'Attribute hyphen-separated list selector, matching lang attribute with partial value', + 'selector': '#attr-hyphen-div3[lang|="en"]', + 'expect': ['attr-hyphen-div3'], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + 'Attribute hyphen-separated list selector, not matching incorrect value', + 'selector': '#attr-hyphen-div4[lang|="es-AR"]', + 'expect': [] /*no matches*/, + 'level': 2, + 'testType': testQsaBaseline + }, + + // - substring begins-with [att^=val] (Level 3) + { + 'name': + 'Attribute begins with selector, matching href attributes beginning with specified substring', + 'selector': '#attr-begins a[href^="http://www"]', + 'expect': ['attr-begins-a1', 'attr-begins-a3'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': + 'Attribute begins with selector, matching lang attributes beginning with specified substring, ', + 'selector': '#attr-begins [lang^="en-"]', + 'expect': ['attr-begins-div2', 'attr-begins-div4'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': + 'Attribute begins with selector, not matching class attribute not beginning with specified substring', + 'selector': '#attr-begins [class^=apple]', + 'expect': [] /*no matches*/, + 'level': 3, + 'testType': testQsaAdditional + }, + { + 'name': + 'Attribute begins with selector with single-quoted value, matching class attribute beginning with specified substring', + 'selector': "#attr-begins [class^=' apple']", + 'expect': ['attr-begins-p1'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': + 'Attribute begins with selector with double-quoted value, matching class attribute beginning with specified substring', + 'selector': '#attr-begins [class^=" apple"]', + 'expect': ['attr-begins-p1'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': + 'Attribute begins with selector with unquoted value, not matching class attribute not beginning with specified substring', + 'selector': '#attr-begins [class^= apple]', + 'expect': [] /*no matches*/, + 'level': 3, + 'testType': testQsaAdditional + }, + + // - substring ends-with [att\$=val] (Level 3) + { + 'name': + 'Attribute ends with selector, matching href attributes ending with specified substring', + 'selector': '#attr-ends a[href\$=".org"]', + 'expect': ['attr-ends-a1', 'attr-ends-a3'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': + 'Attribute ends with selector, matching lang attributes ending with specified substring, ', + 'selector': '#attr-ends [lang\$="-CH"]', + 'expect': ['attr-ends-div2', 'attr-ends-div4'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': + 'Attribute ends with selector, not matching class attribute not ending with specified substring', + 'selector': '#attr-ends [class\$=apple]', + 'expect': [] /*no matches*/, + 'level': 3, + 'testType': testQsaAdditional + }, + { + 'name': + 'Attribute ends with selector with single-quoted value, matching class attribute ending with specified substring', + 'selector': "#attr-ends [class\$='apple ']", + 'expect': ['attr-ends-p1'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': + 'Attribute ends with selector with double-quoted value, matching class attribute ending with specified substring', + 'selector': '#attr-ends [class\$="apple "]', + 'expect': ['attr-ends-p1'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': + 'Attribute ends with selector with unquoted value, not matching class attribute not ending with specified substring', + 'selector': '#attr-ends [class\$=apple ]', + 'expect': [] /*no matches*/, + 'level': 3, + 'testType': testQsaAdditional + }, + + // - substring contains [att*=val] (Level 3) + { + 'name': + 'Attribute contains selector, matching href attributes beginning with specified substring', + 'selector': '#attr-contains a[href*="http://www"]', + 'expect': ['attr-contains-a1', 'attr-contains-a3'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': + 'Attribute contains selector, matching href attributes ending with specified substring', + 'selector': '#attr-contains a[href*=".org"]', + 'expect': ['attr-contains-a1', 'attr-contains-a2'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': + 'Attribute contains selector, matching href attributes containing specified substring', + 'selector': '#attr-contains a[href*=".example."]', + 'expect': ['attr-contains-a1', 'attr-contains-a3'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': + 'Attribute contains selector, matching lang attributes beginning with specified substring, ', + 'selector': '#attr-contains [lang*="en-"]', + 'expect': ['attr-contains-div2', 'attr-contains-div6'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': + 'Attribute contains selector, matching lang attributes ending with specified substring, ', + 'selector': '#attr-contains [lang*="-CH"]', + 'expect': ['attr-contains-div3', 'attr-contains-div5'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': + 'Attribute contains selector with single-quoted value, matching class attribute beginning with specified substring', + 'selector': "#attr-contains [class*=' apple']", + 'expect': ['attr-contains-p1'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': + 'Attribute contains selector with single-quoted value, matching class attribute ending with specified substring', + 'selector': "#attr-contains [class*='orange ']", + 'expect': ['attr-contains-p1'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': + 'Attribute contains selector with single-quoted value, matching class attribute containing specified substring', + 'selector': "#attr-contains [class*='ple banana ora']", + 'expect': ['attr-contains-p1'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': + 'Attribute contains selector with double-quoted value, matching class attribute beginning with specified substring', + 'selector': '#attr-contains [class*=" apple"]', + 'expect': ['attr-contains-p1'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': + 'Attribute contains selector with double-quoted value, matching class attribute ending with specified substring', + 'selector': '#attr-contains [class*="orange "]', + 'expect': ['attr-contains-p1'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': + 'Attribute contains selector with double-quoted value, matching class attribute containing specified substring', + 'selector': '#attr-contains [class*="ple banana ora"]', + 'expect': ['attr-contains-p1'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': + 'Attribute contains selector with unquoted value, matching class attribute beginning with specified substring', + 'selector': '#attr-contains [class*= apple]', + 'expect': ['attr-contains-p1'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': + 'Attribute contains selector with unquoted value, matching class attribute ending with specified substring', + 'selector': '#attr-contains [class*=orange ]', + 'expect': ['attr-contains-p1'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': + 'Attribute contains selector with unquoted value, matching class attribute containing specified substring', + 'selector': '#attr-contains [class*= banana ]', + 'expect': ['attr-contains-p1'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + + // Pseudo-classes + // - :root (Level 3) + { + 'name': ':root pseudo-class selector, matching document root element', + 'selector': ':root', + 'expect': ['html'], + 'exclude': ['element', 'fragment', 'detached'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': ':root pseudo-class selector, not matching document root element', + 'selector': ':root', + 'expect': [] /*no matches*/, + 'exclude': ['document'], + 'level': 3, + 'testType': testQsaAdditional + }, + + // - :nth-child(n) (Level 3) + { + 'name': ':nth-child selector, matching the third child element', + 'selector': '#pseudo-nth-table1 :nth-child(3)', + 'expect': [ + 'pseudo-nth-td3', + 'pseudo-nth-td9', + 'pseudo-nth-tr3', + 'pseudo-nth-td15' + ], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': ':nth-child selector, matching every third child element', + 'selector': '#pseudo-nth li:nth-child(3n)', + 'expect': [ + 'pseudo-nth-li3', + 'pseudo-nth-li6', + 'pseudo-nth-li9', + 'pseudo-nth-li12' + ], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': + ':nth-child selector, matching every second child element, starting from the fourth', + 'selector': '#pseudo-nth li:nth-child(2n+4)', + 'expect': [ + 'pseudo-nth-li4', + 'pseudo-nth-li6', + 'pseudo-nth-li8', + 'pseudo-nth-li10', + 'pseudo-nth-li12' + ], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': + ':nth-child selector, matching every fourth child element, starting from the third', + 'selector': '#pseudo-nth-p1 :nth-child(4n-1)', + 'expect': ['pseudo-nth-em2', 'pseudo-nth-span3'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + + // - :nth-last-child (Level 3) + { + 'name': ':nth-last-child selector, matching the third last child element', + 'selector': '#pseudo-nth-table1 :nth-last-child(3)', + 'expect': [ + 'pseudo-nth-tr1', + 'pseudo-nth-td4', + 'pseudo-nth-td10', + 'pseudo-nth-td16' + ], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': + ':nth-last-child selector, matching every third child element from the end', + 'selector': '#pseudo-nth li:nth-last-child(3n)', + 'expect': [ + 'pseudo-nth-li1', + 'pseudo-nth-li4', + 'pseudo-nth-li7', + 'pseudo-nth-li10' + ], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': + ':nth-last-child selector, matching every second child element from the end, starting from the fourth last', + 'selector': '#pseudo-nth li:nth-last-child(2n+4)', + 'expect': [ + 'pseudo-nth-li1', + 'pseudo-nth-li3', + 'pseudo-nth-li5', + 'pseudo-nth-li7', + 'pseudo-nth-li9' + ], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': + ':nth-last-child selector, matching every fourth element from the end, starting from the third last', + 'selector': '#pseudo-nth-p1 :nth-last-child(4n-1)', + 'expect': ['pseudo-nth-span2', 'pseudo-nth-span4'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + + // - :nth-of-type(n) (Level 3) + { + 'name': ':nth-of-type selector, matching the third em element', + 'selector': '#pseudo-nth-p1 em:nth-of-type(3)', + 'expect': ['pseudo-nth-em3'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': + ':nth-of-type selector, matching every second element of their type', + 'selector': '#pseudo-nth-p1 :nth-of-type(2n)', + 'expect': [ + 'pseudo-nth-em2', + 'pseudo-nth-span2', + 'pseudo-nth-span4', + 'pseudo-nth-strong2', + 'pseudo-nth-em4' + ], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': + ':nth-of-type selector, matching every second elemetn of their type, starting from the first', + 'selector': '#pseudo-nth-p1 span:nth-of-type(2n-1)', + 'expect': ['pseudo-nth-span1', 'pseudo-nth-span3'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + + // - :nth-last-of-type(n) (Level 3) + { + 'name': ':nth-last-of-type selector, matching the thrid last em element', + 'selector': '#pseudo-nth-p1 em:nth-last-of-type(3)', + 'expect': ['pseudo-nth-em2'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': + ':nth-last-of-type selector, matching every second last element of their type', + 'selector': '#pseudo-nth-p1 :nth-last-of-type(2n)', + 'expect': [ + 'pseudo-nth-span1', + 'pseudo-nth-em1', + 'pseudo-nth-strong1', + 'pseudo-nth-em3', + 'pseudo-nth-span3' + ], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': + ':nth-last-of-type selector, matching every second last element of their type, starting from the last', + 'selector': '#pseudo-nth-p1 span:nth-last-of-type(2n-1)', + 'expect': ['pseudo-nth-span2', 'pseudo-nth-span4'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + + // - :first-of-type (Level 3) + { + 'name': ':first-of-type selector, matching the first em element', + 'selector': '#pseudo-nth-p1 em:first-of-type', + 'expect': ['pseudo-nth-em1'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': + ':first-of-type selector, matching the first of every type of element', + 'selector': '#pseudo-nth-p1 :first-of-type', + 'expect': ['pseudo-nth-span1', 'pseudo-nth-em1', 'pseudo-nth-strong1'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': + ':first-of-type selector, matching the first td element in each table row', + 'selector': '#pseudo-nth-table1 tr :first-of-type', + 'expect': ['pseudo-nth-td1', 'pseudo-nth-td7', 'pseudo-nth-td13'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + + // - :last-of-type (Level 3) + { + 'name': ':last-of-type selector, matching the last em elemnet', + 'selector': '#pseudo-nth-p1 em:last-of-type', + 'expect': ['pseudo-nth-em4'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': + ':last-of-type selector, matching the last of every type of element', + 'selector': '#pseudo-nth-p1 :last-of-type', + 'expect': ['pseudo-nth-span4', 'pseudo-nth-strong2', 'pseudo-nth-em4'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': + ':last-of-type selector, matching the last td element in each table row', + 'selector': '#pseudo-nth-table1 tr :last-of-type', + 'expect': ['pseudo-nth-td6', 'pseudo-nth-td12', 'pseudo-nth-td18'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + + // - :first-child + { + 'name': + ':first-child pseudo-class selector, matching first child div element', + 'selector': '#pseudo-first-child div:first-child', + 'expect': ['pseudo-first-child-div1'], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + ":first-child pseudo-class selector, doesn't match non-first-child elements", + 'selector': + '.pseudo-first-child-div2:first-child, .pseudo-first-child-div3:first-child', + 'expect': [] /*no matches*/, + 'level': 2, + 'testType': testQsaBaseline + }, + { + 'name': + ':first-child pseudo-class selector, matching first-child of multiple elements', + 'selector': '#pseudo-first-child span:first-child', + 'expect': [ + 'pseudo-first-child-span1', + 'pseudo-first-child-span3', + 'pseudo-first-child-span5' + ], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + + // - :last-child (Level 3) + { + 'name': + ':last-child pseudo-class selector, matching last child div element', + 'selector': '#pseudo-last-child div:last-child', + 'expect': ['pseudo-last-child-div3'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': + ":last-child pseudo-class selector, doesn't match non-last-child elements", + 'selector': + '.pseudo-last-child-div1:last-child, .pseudo-last-child-div2:first-child', + 'expect': [] /*no matches*/, + 'level': 3, + 'testType': testQsaAdditional + }, + { + 'name': + ':last-child pseudo-class selector, matching first-child of multiple elements', + 'selector': '#pseudo-last-child span:last-child', + 'expect': [ + 'pseudo-last-child-span2', + 'pseudo-last-child-span4', + 'pseudo-last-child-span6' + ], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + + // - :only-child (Level 3) + { + 'name': + ':pseudo-only-child pseudo-class selector, matching all only-child elements', + 'selector': '#pseudo-only :only-child', + 'expect': ['pseudo-only-span1'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': + ':pseudo-only-child pseudo-class selector, matching only-child em elements', + 'selector': '#pseudo-only em:only-child', + 'expect': [] /*no matches*/, + 'level': 3, + 'testType': testQsaAdditional + }, + + // - :only-of-type (Level 3) + { + 'name': + ':pseudo-only-of-type pseudo-class selector, matching all elements with no siblings of the same type', + 'selector': '#pseudo-only :only-of-type', + 'expect': ['pseudo-only-span1', 'pseudo-only-em1'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': + ':pseudo-only-of-type pseudo-class selector, matching em elements with no siblings of the same type', + 'selector': '#pseudo-only em:only-of-type', + 'expect': ['pseudo-only-em1'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + + // - :empty (Level 3) + { + 'name': ':empty pseudo-class selector, matching empty p elements', + 'selector': '#pseudo-empty p:empty', + 'expect': ['pseudo-empty-p1', 'pseudo-empty-p2'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': ':empty pseudo-class selector, matching all empty elements', + 'selector': '#pseudo-empty :empty', + 'expect': ['pseudo-empty-p1', 'pseudo-empty-p2', 'pseudo-empty-span1'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + + // - :link and :visited + // Implementations may treat all visited links as unvisited, so these cannot be tested separately. + // The only guarantee is that ":link,:visited" matches the set of all visited and unvisited links and that they are individually mutually exclusive sets. + { + 'name': + ':link and :visited pseudo-class selectors, matching a and area elements with href attributes', + 'selector': '#pseudo-link :link, #pseudo-link :visited', + 'expect': ['pseudo-link-a1', 'pseudo-link-a2', 'pseudo-link-area1'], + 'level': 1, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + ':link and :visited pseudo-class selectors, matching link elements with href attributes', + 'selector': '#head :link, #head :visited', + 'expect': ['pseudo-link-link1', 'pseudo-link-link2'], + 'exclude': ['element', 'fragment', 'detached'], + 'level': 1, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + ':link and :visited pseudo-class selectors, not matching link elements with href attributes', + 'selector': '#head :link, #head :visited', + 'expect': [] /*no matches*/, + 'exclude': ['document'], + 'level': 1, + 'testType': testQsaBaseline + }, + { + 'name': + ':link and :visited pseudo-class selectors, chained, mutually exclusive pseudo-classes match nothing', + 'selector': ':link:visited', + 'expect': [] /*no matches*/, + 'exclude': ['document'], + 'level': 1, + 'testType': testQsaBaseline + }, + + // - :target (Level 3) + { + 'name': + ':target pseudo-class selector, matching the element referenced by the URL fragment identifier', + 'selector': ':target', + 'expect': [] /*no matches*/, + 'exclude': ['document', 'element'], + 'level': 3, + 'testType': testQsaAdditional + }, + { + 'name': + ':target pseudo-class selector, matching the element referenced by the URL fragment identifier', + 'selector': ':target', + 'expect': ['target'], + 'exclude': ['fragment', 'detached'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + + // - :lang() + { + 'name': ':lang pseudo-class selector, matching inherited language', + 'selector': '#pseudo-lang-div1:lang(en)', + 'expect': ['pseudo-lang-div1'], + 'exclude': ['detached', 'fragment'], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + ':lang pseudo-class selector, not matching element with no inherited language', + 'selector': '#pseudo-lang-div1:lang(en)', + 'expect': [] /*no matches*/, + 'exclude': ['document', 'element'], + 'level': 2, + 'testType': testQsaBaseline + }, + { + 'name': + ':lang pseudo-class selector, matching specified language with exact value', + 'selector': '#pseudo-lang-div2:lang(fr)', + 'expect': ['pseudo-lang-div2'], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + ':lang pseudo-class selector, matching specified language with partial value', + 'selector': '#pseudo-lang-div3:lang(en)', + 'expect': ['pseudo-lang-div3'], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': ':lang pseudo-class selector, not matching incorrect language', + 'selector': '#pseudo-lang-div4:lang(es-AR)', + 'expect': [] /*no matches*/, + 'level': 2, + 'testType': testQsaBaseline + }, + + // - :enabled (Level 3) + { + 'name': + ':enabled pseudo-class selector, matching all enabled form controls', + 'selector': '#pseudo-ui :enabled', + 'expect': [ + 'pseudo-ui-input1', + 'pseudo-ui-input2', + 'pseudo-ui-input3', + 'pseudo-ui-input4', + 'pseudo-ui-input5', + 'pseudo-ui-input6', + 'pseudo-ui-input7', + 'pseudo-ui-input8', + 'pseudo-ui-input9', + 'pseudo-ui-textarea1', + 'pseudo-ui-button1' + ], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + + // - :disabled (Level 3) + { + 'name': + ':enabled pseudo-class selector, matching all disabled form controls', + 'selector': '#pseudo-ui :disabled', + 'expect': [ + 'pseudo-ui-input10', + 'pseudo-ui-input11', + 'pseudo-ui-input12', + 'pseudo-ui-input13', + 'pseudo-ui-input14', + 'pseudo-ui-input15', + 'pseudo-ui-input16', + 'pseudo-ui-input17', + 'pseudo-ui-input18', + 'pseudo-ui-textarea2', + 'pseudo-ui-button2' + ], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + + // - :checked (Level 3) + { + 'name': + ':checked pseudo-class selector, matching checked radio buttons and checkboxes', + 'selector': '#pseudo-ui :checked', + 'expect': [ + 'pseudo-ui-input4', + 'pseudo-ui-input6', + 'pseudo-ui-input13', + 'pseudo-ui-input15' + ], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + + // - :not(s) (Level 3) + { + 'name': ':not pseudo-class selector, matching ', + 'selector': '#not>:not(div)', + 'expect': ['not-p1', 'not-p2', 'not-p3'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': ':not pseudo-class selector, matching ', + 'selector': '#not * :not(:first-child)', + 'expect': ['not-em1', 'not-em2', 'not-em3'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': ':not pseudo-class selector, matching nothing', + 'selector': ':not(*)', + 'expect': [] /* no matches */, + 'level': 3, + 'testType': testQsaAdditional + }, + { + 'name': ':not pseudo-class selector, matching nothing', + 'selector': ':not(*|*)', + 'expect': [] /* no matches */, + 'level': 3, + 'testType': testQsaAdditional + }, + + // Pseudo-elements + // - ::first-line + { + 'name': + ':first-line pseudo-element (one-colon syntax) selector, not matching any elements', + 'selector': '#pseudo-element:first-line', + 'expect': [] /*no matches*/, + 'level': 2, + 'testType': testQsaBaseline + }, + { + 'name': + '::first-line pseudo-element (two-colon syntax) selector, not matching any elements', + 'selector': '#pseudo-element::first-line', + 'expect': [] /*no matches*/, + 'level': 3, + 'testType': testQsaAdditional + }, + + // - ::first-letter + { + 'name': + ':first-letter pseudo-element (one-colon syntax) selector, not matching any elements', + 'selector': '#pseudo-element:first-letter', + 'expect': [] /*no matches*/, + 'level': 2, + 'testType': testQsaBaseline + }, + { + 'name': + '::first-letter pseudo-element (two-colon syntax) selector, not matching any elements', + 'selector': '#pseudo-element::first-letter', + 'expect': [] /*no matches*/, + 'level': 3, + 'testType': testQsaAdditional + }, + + // - ::before + { + 'name': + ':before pseudo-element (one-colon syntax) selector, not matching any elements', + 'selector': '#pseudo-element:before', + 'expect': [] /*no matches*/, + 'level': 2, + 'testType': testQsaBaseline + }, + { + 'name': + '::before pseudo-element (two-colon syntax) selector, not matching any elements', + 'selector': '#pseudo-element::before', + 'expect': [] /*no matches*/, + 'level': 3, + 'testType': testQsaAdditional + }, + + // - ::after + { + 'name': + ':after pseudo-element (one-colon syntax) selector, not matching any elements', + 'selector': '#pseudo-element:after', + 'expect': [] /*no matches*/, + 'level': 2, + 'testType': testQsaBaseline + }, + { + 'name': + '::after pseudo-element (two-colon syntax) selector, not matching any elements', + 'selector': '#pseudo-element::after', + 'expect': [] /*no matches*/, + 'level': 3, + 'testType': testQsaAdditional + }, + + // Class Selectors + { + 'name': 'Class selector, matching element with specified class', + 'selector': '.class-p', + 'expect': ['class-p1', 'class-p2', 'class-p3'], + 'level': 1, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + 'Class selector, chained, matching only elements with all specified classes', + 'selector': '#class .apple.orange.banana', + 'expect': [ + 'class-div1', + 'class-div2', + 'class-p4', + 'class-div3', + 'class-p6', + 'class-div4' + ], + 'level': 1, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': 'Class Selector, chained, with type selector', + 'selector': 'div.apple.banana.orange', + 'expect': ['class-div1', 'class-div2', 'class-div3', 'class-div4'], + 'level': 1, + 'testType': testQsaBaseline | testMatchBaseline + }, + // Caution: If copying and pasting the folowing non-ASCII classes, ensure unicode normalisation is not performed in the process. + { + 'name': + 'Class selector, matching element with class value using non-ASCII characters', + 'selector': '.台北Táiběi', + 'expect': ['class-span1'], + 'level': 1, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + 'Class selector, matching multiple elements with class value using non-ASCII characters', + 'selector': '.台北', + 'expect': ['class-span1', 'class-span2'], + 'level': 1, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + 'Class selector, chained, matching element with multiple class values using non-ASCII characters', + 'selector': '.台北Táiběi.台北', + 'expect': ['class-span1'], + 'level': 1, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + 'Class selector, matching element with class with escaped character', + 'selector': '.foo\\:bar', + 'expect': ['class-span3'], + 'level': 1, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + 'Class selector, matching element with class with escaped character', + 'selector': '.test\\.foo\\[5\\]bar', + 'expect': ['class-span4'], + 'level': 1, + 'testType': testQsaBaseline | testMatchBaseline + }, + + // ID Selectors + { + 'name': 'ID selector, matching element with specified id', + 'selector': '#id #id-div1', + 'expect': ['id-div1'], + 'level': 1, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': 'ID selector, chained, matching element with specified id', + 'selector': '#id-div1, #id-div1', + 'expect': ['id-div1'], + 'level': 1, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': 'ID selector, chained, matching element with specified id', + 'selector': '#id-div1, #id-div2', + 'expect': ['id-div1', 'id-div2'], + 'level': 1, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': 'ID Selector, chained, with type selector', + 'selector': 'div#id-div1, div#id-div2', + 'expect': ['id-div1', 'id-div2'], + 'level': 1, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': 'ID selector, not matching non-existent descendant', + 'selector': '#id #none', + 'expect': [] /*no matches*/, + 'level': 1, + 'testType': testQsaBaseline + }, + { + 'name': 'ID selector, not matching non-existent ancestor', + 'selector': '#none #id-div1', + 'expect': [] /*no matches*/, + 'level': 1, + 'testType': testQsaBaseline + }, + { + 'name': 'ID selector, matching multiple elements with duplicate id', + 'selector': '#id-li-duplicate', + 'expect': [ + 'id-li-duplicate', + 'id-li-duplicate', + 'id-li-duplicate', + 'id-li-duplicate' + ], + 'level': 1, + 'testType': testQsaBaseline | testMatchBaseline + }, + + // Caution: If copying and pasting the folowing non-ASCII IDs, ensure unicode normalisation is not performed in the process. + { + 'name': 'ID selector, matching id value using non-ASCII characters', + 'selector': '#台北Táiběi', + 'expect': ['台北Táiběi'], + 'level': 1, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': 'ID selector, matching id value using non-ASCII characters', + 'selector': '#台北', + 'expect': ['台北'], + 'level': 1, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': 'ID selector, matching id values using non-ASCII characters', + 'selector': '#台北Táiběi, #台北', + 'expect': ['台北Táiběi', '台北'], + 'level': 1, + 'testType': testQsaBaseline | testMatchBaseline + }, + + // XXX runMatchesTest() in level2-lib.js can't handle this because obtaining the expected nodes requires escaping characters when generating the selector from 'expect' values + { + 'name': 'ID selector, matching element with id with escaped character', + 'selector': '#\\#foo\\:bar', + 'expect': ['#foo:bar'], + 'level': 1, + 'testType': testQsaBaseline + }, + { + 'name': 'ID selector, matching element with id with escaped character', + 'selector': '#test\\.foo\\[5\\]bar', + 'expect': ['test.foo[5]bar'], + 'level': 1, + 'testType': testQsaBaseline + }, + + // Namespaces + // XXX runMatchesTest() in level2-lib.js can't handle these because non-HTML elements don't have a recognised id + { + 'name': 'Namespace selector, matching element with any namespace', + 'selector': '#any-namespace *|div', + 'expect': [ + 'any-namespace-div1', + 'any-namespace-div2', + 'any-namespace-div3', + 'any-namespace-div4' + ], + 'level': 3, + 'testType': testQsaBaseline + }, + { + 'name': 'Namespace selector, matching div elements in no namespace only', + 'selector': '#no-namespace |div', + 'expect': ['no-namespace-div3'], + 'level': 3, + 'testType': testQsaBaseline + }, + { + 'name': 'Namespace selector, matching any elements in no namespace only', + 'selector': '#no-namespace |*', + 'expect': ['no-namespace-div3'], + 'level': 3, + 'testType': testQsaBaseline + }, + + // Combinators + // - Descendant combinator ' ' + { + 'name': + 'Descendant combinator, matching element that is a descendant of an element with id', + 'selector': '#descendant div', + 'expect': [ + 'descendant-div1', + 'descendant-div2', + 'descendant-div3', + 'descendant-div4' + ], + 'level': 1, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + 'Descendant combinator, matching element with id that is a descendant of an element', + 'selector': 'body #descendant-div1', + 'expect': ['descendant-div1'], + 'exclude': ['detached', 'fragment'], + 'level': 1, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + 'Descendant combinator, matching element with id that is a descendant of an element', + 'selector': 'div #descendant-div1', + 'expect': ['descendant-div1'], + 'level': 1, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + 'Descendant combinator, matching element with id that is a descendant of an element with id', + 'selector': '#descendant #descendant-div2', + 'expect': ['descendant-div2'], + 'level': 1, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + 'Descendant combinator, matching element with class that is a descendant of an element with id', + 'selector': '#descendant .descendant-div2', + 'expect': ['descendant-div2'], + 'level': 1, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + 'Descendant combinator, matching element with class that is a descendant of an element with class', + 'selector': '.descendant-div1 .descendant-div3', + 'expect': ['descendant-div3'], + 'level': 1, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + 'Descendant combinator, not matching element with id that is not a descendant of an element with id', + 'selector': '#descendant-div1 #descendant-div4', + 'expect': [] /*no matches*/, + 'level': 1, + 'testType': testQsaBaseline + }, + { + 'name': 'Descendant combinator, whitespace characters', + 'selector': '#descendant\t\r\n#descendant-div2', + 'expect': ['descendant-div2'], + 'level': 1, + 'testType': testQsaBaseline | testMatchBaseline + }, + + // - Child combinator '>' + { + 'name': + 'Child combinator, matching element that is a child of an element with id', + 'selector': '#child>div', + 'expect': ['child-div1', 'child-div4'], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + 'Child combinator, matching element with id that is a child of an element', + 'selector': 'div>#child-div1', + 'expect': ['child-div1'], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + 'Child combinator, matching element with id that is a child of an element with id', + 'selector': '#child>#child-div1', + 'expect': ['child-div1'], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + 'Child combinator, matching element with id that is a child of an element with class', + 'selector': '#child-div1>.child-div2', + 'expect': ['child-div2'], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + 'Child combinator, matching element with class that is a child of an element with class', + 'selector': '.child-div1>.child-div2', + 'expect': ['child-div2'], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + 'Child combinator, not matching element with id that is not a child of an element with id', + 'selector': '#child>#child-div3', + 'expect': [] /*no matches*/, + 'level': 2, + 'testType': testQsaBaseline + }, + { + 'name': + 'Child combinator, not matching element with id that is not a child of an element with class', + 'selector': '#child-div1>.child-div3', + 'expect': [] /*no matches*/, + 'level': 2, + 'testType': testQsaBaseline + }, + { + 'name': + 'Child combinator, not matching element with class that is not a child of an element with class', + 'selector': '.child-div1>.child-div3', + 'expect': [] /*no matches*/, + 'level': 2, + 'testType': testQsaBaseline + }, + { + 'name': 'Child combinator, surrounded by whitespace', + 'selector': '#child-div1\t\r\n>\t\r\n#child-div2', + 'expect': ['child-div2'], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': 'Child combinator, whitespace after', + 'selector': '#child-div1>\t\r\n#child-div2', + 'expect': ['child-div2'], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': 'Child combinator, whitespace before', + 'selector': '#child-div1\t\r\n>#child-div2', + 'expect': ['child-div2'], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': 'Child combinator, no whitespace', + 'selector': '#child-div1>#child-div2', + 'expect': ['child-div2'], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + + // - Adjacent sibling combinator '+' + { + 'name': + 'Adjacent sibling combinator, matching element that is an adjacent sibling of an element with id', + 'selector': '#adjacent-div2+div', + 'expect': ['adjacent-div4'], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + 'Adjacent sibling combinator, matching element with id that is an adjacent sibling of an element', + 'selector': 'div+#adjacent-div4', + 'expect': ['adjacent-div4'], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + 'Adjacent sibling combinator, matching element with id that is an adjacent sibling of an element with id', + 'selector': '#adjacent-div2+#adjacent-div4', + 'expect': ['adjacent-div4'], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + 'Adjacent sibling combinator, matching element with class that is an adjacent sibling of an element with id', + 'selector': '#adjacent-div2+.adjacent-div4', + 'expect': ['adjacent-div4'], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + 'Adjacent sibling combinator, matching element with class that is an adjacent sibling of an element with class', + 'selector': '.adjacent-div2+.adjacent-div4', + 'expect': ['adjacent-div4'], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + 'Adjacent sibling combinator, matching p element that is an adjacent sibling of a div element', + 'selector': '#adjacent div+p', + 'expect': ['adjacent-p2'], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': + 'Adjacent sibling combinator, not matching element with id that is not an adjacent sibling of an element with id', + 'selector': '#adjacent-div2+#adjacent-p2, #adjacent-div2+#adjacent-div1', + 'expect': [] /*no matches*/, + 'level': 2, + 'testType': testQsaBaseline + }, + { + 'name': 'Adjacent sibling combinator, surrounded by whitespace', + 'selector': '#adjacent-p2\t\r\n+\t\r\n#adjacent-p3', + 'expect': ['adjacent-p3'], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': 'Adjacent sibling combinator, whitespace after', + 'selector': '#adjacent-p2+\t\r\n#adjacent-p3', + 'expect': ['adjacent-p3'], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': 'Adjacent sibling combinator, whitespace before', + 'selector': '#adjacent-p2\t\r\n+#adjacent-p3', + 'expect': ['adjacent-p3'], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': 'Adjacent sibling combinator, no whitespace', + 'selector': '#adjacent-p2+#adjacent-p3', + 'expect': ['adjacent-p3'], + 'level': 2, + 'testType': testQsaBaseline | testMatchBaseline + }, + + // - General sibling combinator ~ (Level 3) + { + 'name': + 'General sibling combinator, matching element that is a sibling of an element with id', + 'selector': '#sibling-div2~div', + 'expect': ['sibling-div4', 'sibling-div6'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': + 'General sibling combinator, matching element with id that is a sibling of an element', + 'selector': 'div~#sibling-div4', + 'expect': ['sibling-div4'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': + 'General sibling combinator, matching element with id that is a sibling of an element with id', + 'selector': '#sibling-div2~#sibling-div4', + 'expect': ['sibling-div4'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': + 'General sibling combinator, matching element with class that is a sibling of an element with id', + 'selector': '#sibling-div2~.sibling-div', + 'expect': ['sibling-div4', 'sibling-div6'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': + 'General sibling combinator, matching p element that is a sibling of a div element', + 'selector': '#sibling div~p', + 'expect': ['sibling-p2', 'sibling-p3'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': + 'General sibling combinator, not matching element with id that is not a sibling after a p element', + 'selector': '#sibling>p~div', + 'expect': [] /*no matches*/, + 'level': 3, + 'testType': testQsaAdditional + }, + { + 'name': + 'General sibling combinator, not matching element with id that is not a sibling after an element with id', + 'selector': '#sibling-div2~#sibling-div3, #sibling-div2~#sibling-div1', + 'expect': [] /*no matches*/, + 'level': 3, + 'testType': testQsaAdditional + }, + { + 'name': 'General sibling combinator, surrounded by whitespace', + 'selector': '#sibling-p2\t\r\n~\t\r\n#sibling-p3', + 'expect': ['sibling-p3'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': 'General sibling combinator, whitespace after', + 'selector': '#sibling-p2~\t\r\n#sibling-p3', + 'expect': ['sibling-p3'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': 'General sibling combinator, whitespace before', + 'selector': '#sibling-p2\t\r\n~#sibling-p3', + 'expect': ['sibling-p3'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + { + 'name': 'General sibling combinator, no whitespace', + 'selector': '#sibling-p2~#sibling-p3', + 'expect': ['sibling-p3'], + 'level': 3, + 'testType': testQsaAdditional | testMatchBaseline + }, + + // Group of selectors (comma) + { + 'name': 'Syntax, group of selectors separator, surrounded by whitespace', + 'selector': '#group em\t\r \n,\t\r \n#group strong', + 'expect': ['group-em1', 'group-strong1'], + 'level': 1, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': 'Syntax, group of selectors separator, whitespace after', + 'selector': '#group em,\t\r\n#group strong', + 'expect': ['group-em1', 'group-strong1'], + 'level': 1, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': 'Syntax, group of selectors separator, whitespace before', + 'selector': '#group em\t\r\n,#group strong', + 'expect': ['group-em1', 'group-strong1'], + 'level': 1, + 'testType': testQsaBaseline | testMatchBaseline + }, + { + 'name': 'Syntax, group of selectors separator, no whitespace', + 'selector': '#group em,#group strong', + 'expect': ['group-em1', 'group-strong1'], + 'level': 1, + 'testType': testQsaBaseline | testMatchBaseline + }, +]; diff --git a/pkgs/html/test/support.dart b/pkgs/html/test/support.dart new file mode 100644 index 000000000..82f8e62d8 --- /dev/null +++ b/pkgs/html/test/support.dart @@ -0,0 +1,186 @@ +/// Support code for the tests in this directory. +library; + +import 'dart:collection'; +import 'dart:io'; +import 'dart:isolate'; + +import 'package:html/dom.dart'; +import 'package:html/dom_parsing.dart'; +import 'package:html/src/treebuilder.dart'; +import 'package:path/path.dart' as p; + +typedef TreeBuilderFactory = TreeBuilder Function(bool namespaceHTMLElements); + +Map? _treeTypes; +Map? get treeTypes { + // TODO(jmesserly): add DOM here once it's implemented + _treeTypes ??= {'simpletree': TreeBuilder.new}; + return _treeTypes; +} + +Future get testDirectory async { + final packageUriDir = p.dirname(p.fromUri(await Isolate.resolvePackageUri( + Uri(scheme: 'package', path: 'html/html.dart')))); + // Assume pub layout - root is parent directory to package URI (`lib/`). + final rootPackageDir = p.dirname(packageUriDir); + return p.join(rootPackageDir, 'test'); +} + +Stream dataFiles(String subdirectory) async* { + final dir = Directory(p.join(await testDirectory, 'data', subdirectory)); + await for (final file in dir.list()) { + if (file is! File) continue; + yield file.path; + } +} + +// TODO(jmesserly): make this class simpler. We could probably split on +// "\n#" instead of newline and remove a lot of code. +class TestData extends IterableBase> { + final String _text; + final String newTestHeading; + + TestData(String filename, [this.newTestHeading = 'data']) + // Note: can't use readAsLinesSync here because it splits on \r + : _text = File(filename).readAsStringSync(); + + // Note: in Python this was a generator, but since we can't do that in Dart, + // it's easier to convert it into an upfront computation. + @override + Iterator> get iterator => _getData().iterator; + + List> _getData() { + var data = {}; + String? key; + final List> result = >[]; + final lines = _text.split('\n'); + // Remove trailing newline to match Python + if (lines.last == '') { + lines.removeLast(); + } + for (var line in lines) { + final heading = sectionHeading(line); + if (heading != null) { + if (data.isNotEmpty && heading == newTestHeading) { + // Remove trailing newline + data[key!] = data[key]!.substring(0, data[key]!.length - 1); + result.add(normaliseOutput(data)); + data = {}; + } + key = heading; + data[key] = ''; + } else if (key != null) { + data[key] = '${data[key]}$line\n'; + } + } + + if (data.isNotEmpty) { + result.add(normaliseOutput(data)); + } + return result; + } + + /// If the current heading is a test section heading return the heading, + /// otherwise return null. + static String? sectionHeading(String line) { + return line.startsWith('#') ? line.substring(1).trim() : null; + } + + static Map normaliseOutput(Map data) { + // Remove trailing newlines + data.forEach((key, value) { + if (value.endsWith('\n')) { + data[key] = value.substring(0, value.length - 1); + } + }); + return data; + } +} + +/// Serialize the [document] into the html5 test data format. +String testSerializer(Node document) { + return (TestSerializer()..visit(document)).toString(); +} + +/// Serializes the DOM into test format. See [testSerializer]. +class TestSerializer extends TreeVisitor { + final StringBuffer _str; + int _indent = 0; + String _spaces = ''; + + TestSerializer() : _str = StringBuffer(); + + @override + String toString() => _str.toString(); + + int get indent => _indent; + + set indent(int value) { + if (_indent == value) return; + _spaces = ' ' * value; + _indent = value; + } + + void _newline() { + if (_str.length > 0) _str.write('\n'); + _str.write('|$_spaces'); + } + + @override + void visitNodeFallback(Node node) { + _newline(); + _str.write(node); + visitChildren(node); + } + + @override + void visitChildren(Node node) { + indent += 2; + for (var child in node.nodes) { + visit(child); + } + indent -= 2; + } + + @override + void visitDocument(Document node) => _visitDocumentOrFragment(node); + + void _visitDocumentOrFragment(Node node) { + indent += 1; + for (var child in node.nodes) { + visit(child); + } + indent -= 1; + } + + @override + void visitDocumentFragment(DocumentFragment node) => + _visitDocumentOrFragment(node); + + @override + void visitElement(Element node) { + _newline(); + _str.write(node); + if (node.attributes.isNotEmpty) { + indent += 2; + final keys = node.attributes.keys.toList(); + keys.sort((x, y) { + if (x is String) return x.compareTo(y as String); + if (x is AttributeName) return x.compareTo(y as AttributeName); + throw StateError('Cannot sort'); + }); + for (var key in keys) { + final v = node.attributes[key]; + if (key is AttributeName) { + final attr = key; + key = '${attr.prefix} ${attr.name}'; + } + _newline(); + _str.write('$key="$v"'); + } + indent -= 2; + } + visitChildren(node); + } +} diff --git a/pkgs/html/test/tokenizer_test.dart b/pkgs/html/test/tokenizer_test.dart new file mode 100644 index 000000000..92103ef23 --- /dev/null +++ b/pkgs/html/test/tokenizer_test.dart @@ -0,0 +1,291 @@ +@TestOn('vm') +library; + +import 'dart:convert'; +import 'dart:io'; +// Note: mirrors used to match the getattr usage in the original test +import 'dart:mirrors' show reflect; + +import 'package:html/src/token.dart'; +import 'package:html/src/tokenizer.dart'; +import 'package:path/path.dart' as pathos; +import 'package:test/test.dart'; + +import 'support.dart'; + +void main() async { + await for (var path in dataFiles('tokenizer')) { + if (!path.endsWith('.test')) continue; + + final text = File(path).readAsStringSync(); + final tests = jsonDecode(text) as Map; + final testName = pathos.basenameWithoutExtension(path); + final testList = tests['tests'] as List?; + if (testList == null) continue; + + group(testName, () { + for (var index = 0; index < testList.length; index++) { + final testInfo = testList[index] as Map; + + testInfo.putIfAbsent('initialStates', () => ['Data state']); + for (var initialState in testInfo['initialStates'] as List) { + test(testInfo['description'], () { + testInfo['initialState'] = camelCase(initialState as String); + runTokenizerTest(testInfo); + }); + } + } + }); + } +} + +class TokenizerTestParser { + final String? _state; + final String? _lastStartTag; + final bool _generateSpans; + List>? outputTokens; + + TokenizerTestParser(String? initialState, + [String? lastStartTag, bool generateSpans = false]) + : _state = initialState, + _lastStartTag = lastStartTag, + _generateSpans = generateSpans; + + List? parse(String str) { + // Note: we need to pass bytes to the tokenizer if we want it to handle BOM. + final bytes = utf8.encode(str); + final tokenizer = + HtmlTokenizer(bytes, encoding: 'utf-8', generateSpans: _generateSpans); + outputTokens = []; + + // Note: we can't get a closure of the state method. However, we can + // create a new closure to invoke it via mirrors. + final mtok = reflect(tokenizer); + tokenizer.state = + () => mtok.invoke(Symbol(_state!), const []).reflectee as bool; + + if (_lastStartTag != null) { + tokenizer.currentToken = StartTagToken(_lastStartTag); + } + + while (tokenizer.moveNext()) { + final token = tokenizer.current; + switch (token.kind) { + case TokenKind.characters: + processCharacters(token as CharactersToken); + break; + case TokenKind.spaceCharacters: + processSpaceCharacters(token as SpaceCharactersToken); + break; + case TokenKind.startTag: + processStartTag(token as StartTagToken); + break; + case TokenKind.endTag: + processEndTag(token as EndTagToken); + break; + case TokenKind.comment: + processComment(token as CommentToken); + break; + case TokenKind.doctype: + processDoctype(token as DoctypeToken); + break; + case TokenKind.parseError: + processParseError(token as ParseErrorToken); + break; + } + } + + return outputTokens; + } + + void processDoctype(DoctypeToken token) { + addOutputToken(token, + ['DOCTYPE', token.name, token.publicId, token.systemId, token.correct]); + } + + void processStartTag(StartTagToken token) { + addOutputToken( + token, ['StartTag', token.name, token.data, token.selfClosing]); + } + + void processEndTag(EndTagToken token) { + addOutputToken(token, ['EndTag', token.name, token.selfClosing]); + } + + void processComment(StringToken token) { + addOutputToken(token, ['Comment', token.data]); + } + + void processSpaceCharacters(StringToken token) { + processCharacters(token); + } + + void processCharacters(StringToken token) { + addOutputToken(token, ['Character', token.data]); + } + + void processParseError(StringToken token) { + // TODO(jmesserly): when debugging test failures it can be useful to add + // logging here like `print('ParseError $token');`. It would be nice to + // use the actual logging library. + addOutputToken(token, ['ParseError', token.data]); + } + + void addOutputToken(Token token, List array) { + outputTokens!.add([ + ...array, + if (token.span != null && _generateSpans) token.span!.start.offset, + if (token.span != null && _generateSpans) token.span!.end.offset, + ]); + } +} + +/// [tokens] can contain strings, lists, and maps. +List concatenateCharacterTokens(List tokens) { + final outputTokens = []; + for (var token in tokens) { + if (token != 'ParseError' && (token as List)[0] == 'Character') { + if (outputTokens.isNotEmpty && + outputTokens.last != 'ParseError' && + (outputTokens.last as List)[0] == 'Character') { + (outputTokens.last as List)[1] = + '${(outputTokens.last as List)[1]}${token[1]}'; + } else { + outputTokens.add(token); + } + } else { + outputTokens.add(token); + } + } + return outputTokens; +} + +List normalizeTokens(List tokens) { + // TODO: convert tests to reflect arrays + for (var i = 0; i < tokens.length; i++) { + final token = tokens[i] as List; + if (token[0] == 'ParseError') { + tokens[i] = token[0]; + } + } + return tokens; +} + +/// Test whether the test has passed or failed +/// +/// If the ignoreErrorOrder flag is set to true we don't test the relative +/// positions of parse errors and non parse errors. +void expectTokensMatch(List expectedTokens, + List receivedTokens, bool ignoreErrorOrder, + [bool ignoreErrors = false, String? message]) { + // If the 'selfClosing' attribute is not included in the expected test tokens, + // remove it from the received token. + var removeSelfClosing = false; + for (var token in expectedTokens.whereType>()) { + if (token[0] == 'StartTag' && token.length == 3 || + token[0] == 'EndTag' && token.length == 2) { + removeSelfClosing = true; + break; + } + } + + if (removeSelfClosing) { + for (var token in receivedTokens.whereType>()) { + if (token[0] == 'StartTag' || token[0] == 'EndTag') { + token.removeLast(); + } + } + } + + if (!ignoreErrorOrder && !ignoreErrors) { + expect(receivedTokens, equals(expectedTokens), reason: message); + } else { + // Sort the tokens into two groups; non-parse errors and parse errors + final expectedNonErrors = expectedTokens.where((t) => t != 'ParseError'); + final receivedNonErrors = receivedTokens.where((t) => t != 'ParseError'); + + expect(receivedNonErrors, equals(expectedNonErrors), reason: message); + if (!ignoreErrors) { + final expectedParseErrors = + expectedTokens.where((t) => t == 'ParseError'); + final receivedParseErrors = + receivedTokens.where((t) => t == 'ParseError'); + expect(receivedParseErrors, equals(expectedParseErrors), reason: message); + } + } +} + +void runTokenizerTest(Map testInfo) { + // XXX - move this out into the setup function + // concatenate all consecutive character tokens into a single token + if (testInfo.containsKey('doubleEscaped')) { + testInfo = unescape(testInfo); + } + + final expected = concatenateCharacterTokens(testInfo['output'] as List); + if (!testInfo.containsKey('lastStartTag')) { + testInfo['lastStartTag'] = null; + } + final parser = TokenizerTestParser( + testInfo['initialState'] as String?, + testInfo['lastStartTag'] as String?, + testInfo['generateSpans'] as bool? ?? false); + var tokens = parser.parse(testInfo['input'] as String)!; + tokens = concatenateCharacterTokens(tokens); + final received = normalizeTokens(tokens); + final errorMsg = [ + '\n\nInitial state:', + testInfo['initialState'], + '\nInput:', + testInfo['input'], + '\nExpected:', + expected, + '\nreceived:', + tokens + ].map((s) => '$s').join('\n'); + final ignoreErrorOrder = testInfo['ignoreErrorOrder'] as bool? ?? false; + + expectTokensMatch(expected, received, ignoreErrorOrder, true, errorMsg); +} + +Map unescape(Map testInfo) { + // TODO(sigmundch,jmesserly): we currently use jsonDecode to unescape the + // unicode characters in the string, we should use a decoding that works with + // any control characters. + dynamic decode(String inp) => inp == '\u0000' ? inp : jsonDecode('"$inp"'); + + testInfo['input'] = decode(testInfo['input'] as String); + + for (var token in testInfo['output'] as List) { + if (token == 'ParseError') { + continue; + } + + token as List; + token[1] = decode(token[1] as String); + + if (token.length > 2) { + for (var pair in token[2] as List) { + pair as List; + final key = pair[0] as String; + final value = pair[1] as String; + + (token[2] as Map).remove(key); + (token[2] as Map)[decode(key)] = decode(value); + } + } + } + + return testInfo; +} + +String camelCase(String s) { + s = s.toLowerCase(); + final result = StringBuffer(); + for (var match in RegExp(r'\W+(\w)(\w+)').allMatches(s)) { + if (result.length == 0) result.write(s.substring(0, match.start)); + result.write(match.group(1)!.toUpperCase()); + result.write(match.group(2)); + } + return result.toString(); +}