Update.
All checks were successful
CI / build-linux (push) Successful in 1m3s

This commit is contained in:
retoor 2026-01-26 16:28:49 +01:00
parent a96a518d42
commit 72cde36478
79 changed files with 5813 additions and 442 deletions

View File

@ -23,7 +23,7 @@ all.add(" - websocket library")
all.add("")
all.add("## Documentation")
for (path in Path.new("manual").rglob("*.html")) {
for (path in Path.new("bin/manual").rglob("*.html")) {
all.add("### " + path.name)
all.add("```html```")
all.add(path.readText())

116
example/html_dom_demo.wren vendored Normal file
View File

@ -0,0 +1,116 @@
// retoor <retoor@molodetz.nl>
import "html" for Document, Element, NodeList
System.print("=== HTML DOM Demo ===\n")
System.print("--- Parsing HTML ---")
var html = """
<html>
<head>
<title>My Page</title>
</head>
<body>
<div id="main" class="container">
<h1>Welcome</h1>
<p class="intro">Hello World!</p>
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
</div>
<footer>
<p>&copy; 2024</p>
</footer>
</body>
</html>
"""
var doc = Document.parse(html)
System.print("Document title: %(doc.title)")
System.print("Has body: %(doc.body != null)")
System.print("Has head: %(doc.head != null)")
System.print("\n--- Querying Elements ---")
var mainDiv = doc.getElementById("main")
System.print("Found #main: %(mainDiv.tagName)")
System.print("Class name: %(mainDiv.className)")
var intro = doc.querySelector(".intro")
System.print("Intro text: %(intro.textContent)")
var listItems = doc.querySelectorAll("li")
System.print("List items count: %(listItems.count)")
for (item in listItems) {
System.print(" - %(item.textContent)")
}
System.print("\n--- DOM Traversal ---")
var h1 = doc.querySelector("h1")
System.print("H1 parent: %(h1.parentElement.tagName)")
System.print("H1 next sibling: %(h1.nextElementSibling.tagName)")
var ul = doc.querySelector("ul")
System.print("UL first child: %(ul.firstElementChild.textContent)")
System.print("UL last child: %(ul.lastElementChild.textContent)")
System.print("UL has children: %(ul.hasChildNodes())")
System.print("\n--- Creating Elements ---")
var newP = doc.createElement("p")
newP.textContent = "New paragraph"
newP.className = "dynamic"
newP.setAttribute("data-index", "1")
mainDiv.appendChild(newP)
System.print("Added new paragraph to main div")
System.print("Main div children: %(mainDiv.children.count)")
System.print("\n--- Modifying Content ---")
var footer = doc.querySelector("footer p")
System.print("Footer original: %(footer.textContent)")
footer.textContent = "Updated footer"
System.print("Footer updated: %(footer.textContent)")
System.print("\n--- Working with Attributes ---")
var div = doc.getElementById("main")
System.print("ID: %(div.id)")
System.print("Has class attr: %(div.hasAttribute("class"))")
var classList = div.classList
System.print("Classes: %(classList.join(", "))")
div.setAttribute("role", "main")
System.print("New role attr: %(div.getAttribute("role"))")
System.print("\n--- Cloning Nodes ---")
var clone = intro.cloneNode(true)
System.print("Cloned text: %(clone.textContent)")
System.print("Clone has parent: %(clone.parentNode != null)")
System.print("\n--- Selector Matching ---")
System.print("H1 matches 'h1': %(h1.matches("h1"))")
System.print("H1 matches '.intro': %(h1.matches(".intro"))")
var closest = intro.closest("div")
System.print("Intro closest div: %(closest.id)")
System.print("\n--- Serialization ---")
var smallHtml = "<div><p>Test</p></div>"
var doc2 = Document.parse(smallHtml)
System.print("Serialized: %(doc2.outerHTML)")
System.print("\n--- Complex Selectors ---")
var byTag = doc.getElementsByTagName("p")
System.print("All p elements: %(byTag.count)")
var byClass = doc.getElementsByClassName("intro")
System.print("Elements with .intro: %(byClass.count)")
var descendant = doc.querySelector("div p")
System.print("div p found: %(descendant != null)")
var child = doc.querySelector("div > h1")
System.print("div > h1 found: %(child != null)")
System.print("\n=== Demo Complete ===")

View File

@ -9,9 +9,9 @@
{% block article %}
<h1>html</h1>
<p>The <code>html</code> module provides utilities for HTML and URL encoding/decoding, slug generation, and query string handling.</p>
<p>The <code>html</code> module provides HTML/URL utilities and a complete DOM implementation for parsing, querying, manipulating, and serializing HTML documents.</p>
<pre><code>import "html" for Html</code></pre>
<pre><code>import "html" for Html, Document, Element, TextNode, NodeList</code></pre>
<h2>Html Class</h2>
@ -23,163 +23,899 @@
<h3>Static Methods</h3>
<div class="method-signature">
<span class="method-name">Html.urlencode</span>(<span class="param">string</span>) <span class="type">String</span>
<span class="method-name">Html.urlencode</span>(<span class="param">string</span>) &rarr; <span class="type">String</span>
</div>
<p>URL-encodes a string for use in URLs and query parameters. Spaces become <code>+</code>, special characters become percent-encoded.</p>
<p>URL-encodes a string for use in URLs and query parameters.</p>
<ul class="param-list">
<li><span class="param-name">string</span> <span class="param-type">(String)</span> - The string to encode</li>
<li><span class="returns">Returns:</span> URL-encoded string</li>
</ul>
<pre><code>System.print(Html.urlencode("hello world")) // hello+world
System.print(Html.urlencode("a=b&c=d")) // a\%3Db\%26c\%3Dd
System.print(Html.urlencode("café")) // caf\%C3\%A9</code></pre>
<pre><code>System.print(Html.urlencode("hello world")) // hello+world</code></pre>
<div class="method-signature">
<span class="method-name">Html.urldecode</span>(<span class="param">string</span>) <span class="type">String</span>
<span class="method-name">Html.urldecode</span>(<span class="param">string</span>) &rarr; <span class="type">String</span>
</div>
<p>Decodes a URL-encoded string. Converts <code>+</code> to space and decodes percent-encoded characters.</p>
<p>Decodes a URL-encoded string.</p>
<ul class="param-list">
<li><span class="param-name">string</span> <span class="param-type">(String)</span> - The URL-encoded string</li>
<li><span class="returns">Returns:</span> Decoded string</li>
</ul>
<pre><code>System.print(Html.urldecode("hello+world")) // hello world
System.print(Html.urldecode("caf\%C3\%A9")) // café</code></pre>
<pre><code>System.print(Html.urldecode("hello+world")) // hello world</code></pre>
<div class="method-signature">
<span class="method-name">Html.slugify</span>(<span class="param">string</span>) <span class="type">String</span>
<span class="method-name">Html.slugify</span>(<span class="param">string</span>) &rarr; <span class="type">String</span>
</div>
<p>Converts a string to a URL-friendly slug. Lowercase, alphanumeric characters with hyphens.</p>
<p>Converts a string to a URL-friendly slug.</p>
<ul class="param-list">
<li><span class="param-name">string</span> <span class="param-type">(String)</span> - The string to slugify</li>
<li><span class="returns">Returns:</span> URL-friendly slug</li>
</ul>
<pre><code>System.print(Html.slugify("Hello World")) // hello-world
System.print(Html.slugify("My Blog Post!")) // my-blog-post
System.print(Html.slugify(" Multiple Spaces ")) // multiple-spaces</code></pre>
<pre><code>System.print(Html.slugify("Hello World!")) // hello-world</code></pre>
<div class="method-signature">
<span class="method-name">Html.quote</span>(<span class="param">string</span>) <span class="type">String</span>
<span class="method-name">Html.quote</span>(<span class="param">string</span>) &rarr; <span class="type">String</span>
</div>
<p>Escapes HTML special characters to prevent XSS attacks.</p>
<ul class="param-list">
<li><span class="param-name">string</span> <span class="param-type">(String)</span> - The string to escape</li>
<li><span class="returns">Returns:</span> HTML-escaped string</li>
</ul>
<pre><code>System.print(Html.quote("&lt;script&gt;alert('xss')&lt;/script&gt;"))
// &amp;lt;script&amp;gt;alert(&amp;#39;xss&amp;#39;)&amp;lt;/script&amp;gt;
System.print(Html.quote("A &amp; B")) // A &amp;amp; B
System.print(Html.quote("\"quoted\"")) // &amp;quot;quoted&amp;quot;</code></pre>
<pre><code>System.print(Html.quote("&lt;script&gt;")) // &amp;lt;script&amp;gt;</code></pre>
<div class="method-signature">
<span class="method-name">Html.unquote</span>(<span class="param">string</span>) <span class="type">String</span>
<span class="method-name">Html.unquote</span>(<span class="param">string</span>) &rarr; <span class="type">String</span>
</div>
<p>Unescapes HTML entities back to their original characters.</p>
<ul class="param-list">
<li><span class="param-name">string</span> <span class="param-type">(String)</span> - The HTML-escaped string</li>
<li><span class="returns">Returns:</span> Unescaped string</li>
</ul>
<pre><code>System.print(Html.unquote("&amp;lt;div&amp;gt;")) // &lt;div&gt;
System.print(Html.unquote("A &amp;amp; B")) // A &amp; B</code></pre>
<pre><code>System.print(Html.unquote("&amp;lt;div&amp;gt;")) // &lt;div&gt;</code></pre>
<div class="method-signature">
<span class="method-name">Html.encodeParams</span>(<span class="param">params</span>) <span class="type">String</span>
<span class="method-name">Html.encodeParams</span>(<span class="param">params</span>) &rarr; <span class="type">String</span>
</div>
<p>Encodes a map of key-value pairs into a URL query string.</p>
<ul class="param-list">
<li><span class="param-name">params</span> <span class="param-type">(Map)</span> - Map of parameters to encode</li>
<li><span class="returns">Returns:</span> URL-encoded query string</li>
</ul>
<pre><code>var params = {"name": "John Doe", "age": 30}
System.print(Html.encodeParams(params)) // name=John+Doe&amp;age=30
var search = {"q": "wren lang", "page": 1}
System.print(Html.encodeParams(search)) // q=wren+lang&amp;page=1</code></pre>
<pre><code>System.print(Html.encodeParams({"name": "John", "age": 30})) // name=John&amp;age=30</code></pre>
<div class="method-signature">
<span class="method-name">Html.decodeParams</span>(<span class="param">string</span>) <span class="type">Map</span>
<span class="method-name">Html.decodeParams</span>(<span class="param">string</span>) &rarr; <span class="type">Map</span>
</div>
<p>Decodes a URL query string into a map of key-value pairs.</p>
<ul class="param-list">
<li><span class="param-name">string</span> <span class="param-type">(String)</span> - URL-encoded query string</li>
<li><span class="returns">Returns:</span> Map of decoded parameters</li>
</ul>
<pre><code>var params = Html.decodeParams("name=John+Doe&amp;age=30")
System.print(params["name"]) // John Doe
System.print(params["age"]) // 30</code></pre>
<pre><code>var params = Html.decodeParams("name=John&amp;age=30")
System.print(params["name"]) // John</code></pre>
<h2>Entity Reference</h2>
<hr>
<h2>Document Class</h2>
<div class="class-header">
<h3>Document</h3>
<p>Represents a parsed HTML document. Provides methods to query and manipulate the DOM tree.</p>
</div>
<h3>Constructor</h3>
<div class="method-signature">
<span class="method-name">Document.parse</span>(<span class="param">html</span>) &rarr; <span class="type">Document</span>
</div>
<p>Parses an HTML string and returns a Document object.</p>
<ul class="param-list">
<li><span class="param-name">html</span> <span class="param-type">(String)</span> - HTML string to parse</li>
<li><span class="returns">Returns:</span> Document object representing the parsed HTML</li>
</ul>
<pre><code>var doc = Document.parse("&lt;div&gt;&lt;p&gt;Hello&lt;/p&gt;&lt;/div&gt;")
System.print(doc.body) // Element</code></pre>
<h3>Properties</h3>
<div class="method-signature">
<span class="method-name">document.documentElement</span> &rarr; <span class="type">Element</span>
</div>
<p>Returns the root element of the document (typically the html element).</p>
<div class="method-signature">
<span class="method-name">document.head</span> &rarr; <span class="type">Element|Null</span>
</div>
<p>Returns the head element of the document, or null if not present.</p>
<div class="method-signature">
<span class="method-name">document.body</span> &rarr; <span class="type">Element|Null</span>
</div>
<p>Returns the body element of the document, or null if not present.</p>
<div class="method-signature">
<span class="method-name">document.title</span> &rarr; <span class="type">String</span>
</div>
<p>Gets the document title (content of the title element).</p>
<pre><code>var doc = Document.parse("&lt;title&gt;My Page&lt;/title&gt;")
System.print(doc.title) // My Page</code></pre>
<div class="method-signature">
<span class="method-name">document.title=</span>(<span class="param">value</span>)
</div>
<p>Sets the document title.</p>
<pre><code>doc.title = "New Title"</code></pre>
<div class="method-signature">
<span class="method-name">document.innerHTML</span> &rarr; <span class="type">String</span>
</div>
<p>Returns the inner HTML content of the document element.</p>
<div class="method-signature">
<span class="method-name">document.outerHTML</span> &rarr; <span class="type">String</span>
</div>
<p>Returns the complete HTML serialization of the document.</p>
<pre><code>var doc = Document.parse("&lt;div&gt;Hello&lt;/div&gt;")
System.print(doc.outerHTML) // &lt;html&gt;&lt;div&gt;Hello&lt;/div&gt;&lt;/html&gt;</code></pre>
<h3>Query Methods</h3>
<div class="method-signature">
<span class="method-name">document.querySelector</span>(<span class="param">selector</span>) &rarr; <span class="type">Element|Null</span>
</div>
<p>Returns the first element matching the CSS selector, or null if not found.</p>
<ul class="param-list">
<li><span class="param-name">selector</span> <span class="param-type">(String)</span> - CSS selector string</li>
<li><span class="returns">Returns:</span> First matching Element or null</li>
</ul>
<pre><code>var doc = Document.parse("&lt;div class='box'&gt;Content&lt;/div&gt;")
var elem = doc.querySelector(".box")
System.print(elem.textContent) // Content</code></pre>
<div class="method-signature">
<span class="method-name">document.querySelectorAll</span>(<span class="param">selector</span>) &rarr; <span class="type">NodeList</span>
</div>
<p>Returns all elements matching the CSS selector.</p>
<ul class="param-list">
<li><span class="param-name">selector</span> <span class="param-type">(String)</span> - CSS selector string</li>
<li><span class="returns">Returns:</span> NodeList of matching elements</li>
</ul>
<pre><code>var doc = Document.parse("&lt;p&gt;One&lt;/p&gt;&lt;p&gt;Two&lt;/p&gt;")
var elems = doc.querySelectorAll("p")
System.print(elems.count) // 2</code></pre>
<div class="method-signature">
<span class="method-name">document.getElementById</span>(<span class="param">id</span>) &rarr; <span class="type">Element|Null</span>
</div>
<p>Returns the element with the specified ID, or null if not found.</p>
<ul class="param-list">
<li><span class="param-name">id</span> <span class="param-type">(String)</span> - The element ID to search for</li>
<li><span class="returns">Returns:</span> Element with matching ID or null</li>
</ul>
<pre><code>var doc = Document.parse("&lt;div id='main'&gt;Content&lt;/div&gt;")
var elem = doc.getElementById("main")
System.print(elem.tagName) // DIV</code></pre>
<div class="method-signature">
<span class="method-name">document.getElementsByClassName</span>(<span class="param">className</span>) &rarr; <span class="type">NodeList</span>
</div>
<p>Returns all elements with the specified class name.</p>
<ul class="param-list">
<li><span class="param-name">className</span> <span class="param-type">(String)</span> - The class name to search for</li>
<li><span class="returns">Returns:</span> NodeList of matching elements</li>
</ul>
<pre><code>var doc = Document.parse("&lt;div class='item'&gt;A&lt;/div&gt;&lt;div class='item'&gt;B&lt;/div&gt;")
var elems = doc.getElementsByClassName("item")
System.print(elems.count) // 2</code></pre>
<div class="method-signature">
<span class="method-name">document.getElementsByTagName</span>(<span class="param">tagName</span>) &rarr; <span class="type">NodeList</span>
</div>
<p>Returns all elements with the specified tag name.</p>
<ul class="param-list">
<li><span class="param-name">tagName</span> <span class="param-type">(String)</span> - The tag name to search for (case-insensitive)</li>
<li><span class="returns">Returns:</span> NodeList of matching elements</li>
</ul>
<pre><code>var doc = Document.parse("&lt;p&gt;One&lt;/p&gt;&lt;p&gt;Two&lt;/p&gt;&lt;span&gt;Three&lt;/span&gt;")
var elems = doc.getElementsByTagName("p")
System.print(elems.count) // 2</code></pre>
<h3>Element Creation</h3>
<div class="method-signature">
<span class="method-name">document.createElement</span>(<span class="param">tagName</span>) &rarr; <span class="type">Element</span>
</div>
<p>Creates a new element with the specified tag name.</p>
<ul class="param-list">
<li><span class="param-name">tagName</span> <span class="param-type">(String)</span> - The tag name for the new element</li>
<li><span class="returns">Returns:</span> New Element object</li>
</ul>
<pre><code>var doc = Document.parse("&lt;div&gt;&lt;/div&gt;")
var p = doc.createElement("p")
p.textContent = "Hello"
doc.querySelector("div").appendChild(p)</code></pre>
<div class="method-signature">
<span class="method-name">document.createTextNode</span>(<span class="param">text</span>) &rarr; <span class="type">TextNode</span>
</div>
<p>Creates a new text node with the specified content.</p>
<ul class="param-list">
<li><span class="param-name">text</span> <span class="param-type">(String)</span> - The text content</li>
<li><span class="returns">Returns:</span> New TextNode object</li>
</ul>
<pre><code>var doc = Document.parse("&lt;div&gt;&lt;/div&gt;")
var text = doc.createTextNode("Hello World")
doc.querySelector("div").appendChild(text)</code></pre>
<hr>
<h2>Element Class</h2>
<div class="class-header">
<h3>Element</h3>
<p>Represents an HTML element in the DOM tree. Provides methods for traversal, manipulation, and querying.</p>
</div>
<h3>Identity Properties</h3>
<div class="method-signature">
<span class="method-name">element.tagName</span> &rarr; <span class="type">String</span>
</div>
<p>Returns the tag name of the element in uppercase.</p>
<pre><code>var div = doc.querySelector("div")
System.print(div.tagName) // DIV</code></pre>
<div class="method-signature">
<span class="method-name">element.id</span> &rarr; <span class="type">String</span>
</div>
<p>Gets the ID attribute of the element.</p>
<div class="method-signature">
<span class="method-name">element.id=</span>(<span class="param">value</span>)
</div>
<p>Sets the ID attribute of the element.</p>
<pre><code>elem.id = "newId"
System.print(elem.id) // newId</code></pre>
<div class="method-signature">
<span class="method-name">element.className</span> &rarr; <span class="type">String</span>
</div>
<p>Gets the class attribute as a string.</p>
<div class="method-signature">
<span class="method-name">element.className=</span>(<span class="param">value</span>)
</div>
<p>Sets the class attribute.</p>
<pre><code>elem.className = "foo bar"
System.print(elem.className) // foo bar</code></pre>
<div class="method-signature">
<span class="method-name">element.classList</span> &rarr; <span class="type">List</span>
</div>
<p>Returns the class names as a list of strings.</p>
<pre><code>elem.className = "foo bar baz"
var classes = elem.classList
System.print(classes[0]) // foo
System.print(classes.count) // 3</code></pre>
<h3>Node Properties</h3>
<div class="method-signature">
<span class="method-name">element.nodeType</span> &rarr; <span class="type">Num</span>
</div>
<p>Returns the node type (1 for Element).</p>
<div class="method-signature">
<span class="method-name">element.nodeName</span> &rarr; <span class="type">String</span>
</div>
<p>Returns the node name (same as tagName for elements).</p>
<div class="method-signature">
<span class="method-name">element.nodeValue</span> &rarr; <span class="type">Null</span>
</div>
<p>Returns null for elements (nodeValue is only meaningful for text nodes).</p>
<h3>Content Properties</h3>
<div class="method-signature">
<span class="method-name">element.textContent</span> &rarr; <span class="type">String</span>
</div>
<p>Gets the text content of the element and all descendants.</p>
<div class="method-signature">
<span class="method-name">element.textContent=</span>(<span class="param">value</span>)
</div>
<p>Sets the text content, replacing all children with a single text node.</p>
<pre><code>elem.textContent = "New text"</code></pre>
<div class="method-signature">
<span class="method-name">element.innerHTML</span> &rarr; <span class="type">String</span>
</div>
<p>Gets the HTML content inside the element.</p>
<div class="method-signature">
<span class="method-name">element.innerHTML=</span>(<span class="param">html</span>)
</div>
<p>Sets the inner HTML, parsing and replacing all children.</p>
<pre><code>elem.innerHTML = "&lt;p&gt;New content&lt;/p&gt;"</code></pre>
<div class="method-signature">
<span class="method-name">element.outerHTML</span> &rarr; <span class="type">String</span>
</div>
<p>Gets the HTML serialization of the element including itself.</p>
<div class="method-signature">
<span class="method-name">element.outerHTML=</span>(<span class="param">html</span>)
</div>
<p>Replaces the element with the parsed HTML.</p>
<h3>Attribute Methods</h3>
<div class="method-signature">
<span class="method-name">element.getAttribute</span>(<span class="param">name</span>) &rarr; <span class="type">String|Null</span>
</div>
<p>Gets the value of the specified attribute, or null if not present.</p>
<ul class="param-list">
<li><span class="param-name">name</span> <span class="param-type">(String)</span> - Attribute name</li>
<li><span class="returns">Returns:</span> Attribute value or null</li>
</ul>
<pre><code>var href = elem.getAttribute("href")
System.print(href)</code></pre>
<div class="method-signature">
<span class="method-name">element.setAttribute</span>(<span class="param">name</span>, <span class="param">value</span>)
</div>
<p>Sets the value of the specified attribute.</p>
<ul class="param-list">
<li><span class="param-name">name</span> <span class="param-type">(String)</span> - Attribute name</li>
<li><span class="param-name">value</span> <span class="param-type">(String)</span> - Attribute value</li>
</ul>
<pre><code>elem.setAttribute("data-id", "123")</code></pre>
<div class="method-signature">
<span class="method-name">element.removeAttribute</span>(<span class="param">name</span>)
</div>
<p>Removes the specified attribute.</p>
<ul class="param-list">
<li><span class="param-name">name</span> <span class="param-type">(String)</span> - Attribute name to remove</li>
</ul>
<pre><code>elem.removeAttribute("disabled")</code></pre>
<div class="method-signature">
<span class="method-name">element.hasAttribute</span>(<span class="param">name</span>) &rarr; <span class="type">Bool</span>
</div>
<p>Returns true if the element has the specified attribute.</p>
<ul class="param-list">
<li><span class="param-name">name</span> <span class="param-type">(String)</span> - Attribute name</li>
<li><span class="returns">Returns:</span> Boolean indicating presence</li>
</ul>
<pre><code>if (elem.hasAttribute("disabled")) {
System.print("Element is disabled")
}</code></pre>
<div class="method-signature">
<span class="method-name">element.attributes</span> &rarr; <span class="type">Map</span>
</div>
<p>Returns a map of all attributes (name to value).</p>
<pre><code>var attrs = elem.attributes
for (name in attrs.keys) {
System.print("%(name)=%(attrs[name])")
}</code></pre>
<div class="method-signature">
<span class="method-name">element.dataset</span> &rarr; <span class="type">Map</span>
</div>
<p>Returns a map of all data-* attributes with camelCase keys.</p>
<pre><code>// &lt;div data-user-id="123" data-active="true"&gt;
var data = elem.dataset
System.print(data["userId"]) // 123
System.print(data["active"]) // true</code></pre>
<h3>Traversal Properties</h3>
<div class="method-signature">
<span class="method-name">element.parentNode</span> &rarr; <span class="type">Element|Null</span>
</div>
<p>Returns the parent node (element or document).</p>
<div class="method-signature">
<span class="method-name">element.parentElement</span> &rarr; <span class="type">Element|Null</span>
</div>
<p>Returns the parent element, or null if parent is not an element.</p>
<div class="method-signature">
<span class="method-name">element.children</span> &rarr; <span class="type">NodeList</span>
</div>
<p>Returns all child elements (excludes text nodes).</p>
<div class="method-signature">
<span class="method-name">element.childNodes</span> &rarr; <span class="type">NodeList</span>
</div>
<p>Returns all child nodes including text nodes.</p>
<div class="method-signature">
<span class="method-name">element.firstChild</span> &rarr; <span class="type">Element|TextNode|Null</span>
</div>
<p>Returns the first child node.</p>
<div class="method-signature">
<span class="method-name">element.lastChild</span> &rarr; <span class="type">Element|TextNode|Null</span>
</div>
<p>Returns the last child node.</p>
<div class="method-signature">
<span class="method-name">element.firstElementChild</span> &rarr; <span class="type">Element|Null</span>
</div>
<p>Returns the first child that is an element.</p>
<div class="method-signature">
<span class="method-name">element.lastElementChild</span> &rarr; <span class="type">Element|Null</span>
</div>
<p>Returns the last child that is an element.</p>
<div class="method-signature">
<span class="method-name">element.nextSibling</span> &rarr; <span class="type">Element|TextNode|Null</span>
</div>
<p>Returns the next sibling node.</p>
<div class="method-signature">
<span class="method-name">element.previousSibling</span> &rarr; <span class="type">Element|TextNode|Null</span>
</div>
<p>Returns the previous sibling node.</p>
<div class="method-signature">
<span class="method-name">element.nextElementSibling</span> &rarr; <span class="type">Element|Null</span>
</div>
<p>Returns the next sibling that is an element.</p>
<div class="method-signature">
<span class="method-name">element.previousElementSibling</span> &rarr; <span class="type">Element|Null</span>
</div>
<p>Returns the previous sibling that is an element.</p>
<h3>Manipulation Methods</h3>
<div class="method-signature">
<span class="method-name">element.appendChild</span>(<span class="param">child</span>) &rarr; <span class="type">Element|TextNode</span>
</div>
<p>Appends a child node to the end of the element's children.</p>
<ul class="param-list">
<li><span class="param-name">child</span> <span class="param-type">(Element|TextNode)</span> - Node to append</li>
<li><span class="returns">Returns:</span> The appended node</li>
</ul>
<pre><code>var p = doc.createElement("p")
p.textContent = "New paragraph"
container.appendChild(p)</code></pre>
<div class="method-signature">
<span class="method-name">element.insertBefore</span>(<span class="param">newNode</span>, <span class="param">refNode</span>) &rarr; <span class="type">Element|TextNode</span>
</div>
<p>Inserts a node before the reference node.</p>
<ul class="param-list">
<li><span class="param-name">newNode</span> <span class="param-type">(Element|TextNode)</span> - Node to insert</li>
<li><span class="param-name">refNode</span> <span class="param-type">(Element|TextNode|Null)</span> - Reference node (null appends)</li>
<li><span class="returns">Returns:</span> The inserted node</li>
</ul>
<pre><code>var newP = doc.createElement("p")
container.insertBefore(newP, existingP)</code></pre>
<div class="method-signature">
<span class="method-name">element.removeChild</span>(<span class="param">child</span>) &rarr; <span class="type">Element|TextNode</span>
</div>
<p>Removes a child node from the element.</p>
<ul class="param-list">
<li><span class="param-name">child</span> <span class="param-type">(Element|TextNode)</span> - Node to remove</li>
<li><span class="returns">Returns:</span> The removed node</li>
</ul>
<pre><code>var removed = container.removeChild(child)
System.print(removed.textContent)</code></pre>
<div class="method-signature">
<span class="method-name">element.replaceChild</span>(<span class="param">newChild</span>, <span class="param">oldChild</span>) &rarr; <span class="type">Element|TextNode</span>
</div>
<p>Replaces a child node with a new node.</p>
<ul class="param-list">
<li><span class="param-name">newChild</span> <span class="param-type">(Element|TextNode)</span> - Replacement node</li>
<li><span class="param-name">oldChild</span> <span class="param-type">(Element|TextNode)</span> - Node to replace</li>
<li><span class="returns">Returns:</span> The replaced (old) node</li>
</ul>
<pre><code>var newP = doc.createElement("p")
container.replaceChild(newP, oldP)</code></pre>
<div class="method-signature">
<span class="method-name">element.cloneNode</span>(<span class="param">deep</span>) &rarr; <span class="type">Element</span>
</div>
<p>Creates a copy of the element.</p>
<ul class="param-list">
<li><span class="param-name">deep</span> <span class="param-type">(Bool)</span> - If true, clones all descendants</li>
<li><span class="returns">Returns:</span> Cloned element</li>
</ul>
<pre><code>var shallow = elem.cloneNode(false) // Just the element
var deep = elem.cloneNode(true) // Element and all children</code></pre>
<div class="method-signature">
<span class="method-name">element.remove</span>()
</div>
<p>Removes the element from its parent.</p>
<pre><code>elem.remove() // Element is now detached</code></pre>
<div class="method-signature">
<span class="method-name">element.normalize</span>()
</div>
<p>Merges adjacent text nodes and removes empty text nodes.</p>
<h3>Query Methods</h3>
<div class="method-signature">
<span class="method-name">element.querySelector</span>(<span class="param">selector</span>) &rarr; <span class="type">Element|Null</span>
</div>
<p>Returns the first descendant matching the CSS selector.</p>
<div class="method-signature">
<span class="method-name">element.querySelectorAll</span>(<span class="param">selector</span>) &rarr; <span class="type">NodeList</span>
</div>
<p>Returns all descendants matching the CSS selector.</p>
<div class="method-signature">
<span class="method-name">element.getElementsByClassName</span>(<span class="param">className</span>) &rarr; <span class="type">NodeList</span>
</div>
<p>Returns all descendants with the specified class.</p>
<div class="method-signature">
<span class="method-name">element.getElementsByTagName</span>(<span class="param">tagName</span>) &rarr; <span class="type">NodeList</span>
</div>
<p>Returns all descendants with the specified tag.</p>
<div class="method-signature">
<span class="method-name">element.matches</span>(<span class="param">selector</span>) &rarr; <span class="type">Bool</span>
</div>
<p>Returns true if the element matches the CSS selector.</p>
<pre><code>if (elem.matches(".active")) {
System.print("Element is active")
}</code></pre>
<div class="method-signature">
<span class="method-name">element.closest</span>(<span class="param">selector</span>) &rarr; <span class="type">Element|Null</span>
</div>
<p>Returns the closest ancestor (or self) matching the selector.</p>
<pre><code>var container = elem.closest(".container")
if (container) {
System.print("Found container: %(container.id)")
}</code></pre>
<div class="method-signature">
<span class="method-name">element.contains</span>(<span class="param">node</span>) &rarr; <span class="type">Bool</span>
</div>
<p>Returns true if the node is a descendant of this element.</p>
<pre><code>if (container.contains(child)) {
System.print("Child is inside container")
}</code></pre>
<div class="method-signature">
<span class="method-name">element.hasChildNodes</span>() &rarr; <span class="type">Bool</span>
</div>
<p>Returns true if the element has any child nodes.</p>
<pre><code>if (elem.hasChildNodes()) {
System.print("Element has children")
}</code></pre>
<hr>
<h2>TextNode Class</h2>
<div class="class-header">
<h3>TextNode</h3>
<p>Represents a text node in the DOM tree.</p>
</div>
<h3>Properties</h3>
<div class="method-signature">
<span class="method-name">textNode.textContent</span> &rarr; <span class="type">String</span>
</div>
<p>Gets the text content.</p>
<div class="method-signature">
<span class="method-name">textNode.textContent=</span>(<span class="param">value</span>)
</div>
<p>Sets the text content.</p>
<div class="method-signature">
<span class="method-name">textNode.nodeType</span> &rarr; <span class="type">Num</span>
</div>
<p>Returns 3 (TEXT_NODE).</p>
<div class="method-signature">
<span class="method-name">textNode.nodeName</span> &rarr; <span class="type">String</span>
</div>
<p>Returns "#text".</p>
<div class="method-signature">
<span class="method-name">textNode.nodeValue</span> &rarr; <span class="type">String</span>
</div>
<p>Gets the text content (same as textContent).</p>
<div class="method-signature">
<span class="method-name">textNode.nodeValue=</span>(<span class="param">value</span>)
</div>
<p>Sets the text content.</p>
<h3>Traversal Properties</h3>
<div class="method-signature">
<span class="method-name">textNode.parentNode</span> &rarr; <span class="type">Element|Null</span>
</div>
<p>Returns the parent node.</p>
<div class="method-signature">
<span class="method-name">textNode.parentElement</span> &rarr; <span class="type">Element|Null</span>
</div>
<p>Returns the parent element.</p>
<div class="method-signature">
<span class="method-name">textNode.nextSibling</span> &rarr; <span class="type">Element|TextNode|Null</span>
</div>
<p>Returns the next sibling node.</p>
<div class="method-signature">
<span class="method-name">textNode.previousSibling</span> &rarr; <span class="type">Element|TextNode|Null</span>
</div>
<p>Returns the previous sibling node.</p>
<h3>Methods</h3>
<div class="method-signature">
<span class="method-name">textNode.cloneNode</span>(<span class="param">deep</span>) &rarr; <span class="type">TextNode</span>
</div>
<p>Creates a copy of the text node (deep parameter is ignored).</p>
<div class="method-signature">
<span class="method-name">textNode.remove</span>()
</div>
<p>Removes the text node from its parent.</p>
<hr>
<h2>NodeList Class</h2>
<div class="class-header">
<h3>NodeList</h3>
<p>An iterable collection of DOM nodes returned by query methods.</p>
</div>
<h3>Properties</h3>
<div class="method-signature">
<span class="method-name">nodeList.count</span> &rarr; <span class="type">Num</span>
</div>
<p>Returns the number of nodes in the list.</p>
<h3>Access Methods</h3>
<div class="method-signature">
<span class="method-name">nodeList[index]</span> &rarr; <span class="type">Element|TextNode|Null</span>
</div>
<p>Returns the node at the specified index, or null if out of bounds.</p>
<pre><code>var first = list[0]
var last = list[list.count - 1]</code></pre>
<div class="method-signature">
<span class="method-name">nodeList.item</span>(<span class="param">index</span>) &rarr; <span class="type">Element|TextNode|Null</span>
</div>
<p>Same as subscript access.</p>
<div class="method-signature">
<span class="method-name">nodeList.toList</span> &rarr; <span class="type">List</span>
</div>
<p>Converts the NodeList to a standard Wren List.</p>
<pre><code>var list = nodeList.toList
for (elem in list) {
System.print(elem.tagName)
}</code></pre>
<h3>Iteration</h3>
<p>NodeList supports iteration with for-in loops:</p>
<pre><code>for (elem in doc.querySelectorAll("p")) {
System.print(elem.textContent)
}</code></pre>
<div class="method-signature">
<span class="method-name">nodeList.forEach</span>(<span class="param">fn</span>)
</div>
<p>Calls the function for each node in the list.</p>
<pre><code>nodeList.forEach {|elem|
System.print(elem.tagName)
}</code></pre>
<hr>
<h2>CSS Selectors</h2>
<p>The DOM implementation supports the following CSS selector syntax:</p>
<table>
<tr>
<th>Character</th>
<th>Entity</th>
<th>Selector</th>
<th>Example</th>
<th>Description</th>
</tr>
<tr>
<td>&amp;</td>
<td>&amp;amp;</td>
<td>Tag</td>
<td><code>div</code>, <code>p</code></td>
<td>Matches elements by tag name</td>
</tr>
<tr>
<td>&lt;</td>
<td>&amp;lt;</td>
<td>Universal</td>
<td><code>*</code></td>
<td>Matches all elements</td>
</tr>
<tr>
<td>&gt;</td>
<td>&amp;gt;</td>
<td>ID</td>
<td><code>#myId</code></td>
<td>Matches element with id="myId"</td>
</tr>
<tr>
<td>"</td>
<td>&amp;quot;</td>
<td>Class</td>
<td><code>.myClass</code></td>
<td>Matches elements with class="myClass"</td>
</tr>
<tr>
<td>'</td>
<td>&amp;#39;</td>
<td>Attribute</td>
<td><code>[href]</code></td>
<td>Matches elements with href attribute</td>
</tr>
<tr>
<td>Attribute equals</td>
<td><code>[type="text"]</code></td>
<td>Matches elements with type="text"</td>
</tr>
<tr>
<td>Attribute contains word</td>
<td><code>[class~="item"]</code></td>
<td>Matches if class contains "item" as a word</td>
</tr>
<tr>
<td>Attribute starts with</td>
<td><code>[href^="https"]</code></td>
<td>Matches if href starts with "https"</td>
</tr>
<tr>
<td>Attribute ends with</td>
<td><code>[src$=".png"]</code></td>
<td>Matches if src ends with ".png"</td>
</tr>
<tr>
<td>Attribute contains</td>
<td><code>[title*="hello"]</code></td>
<td>Matches if title contains "hello"</td>
</tr>
<tr>
<td>Descendant</td>
<td><code>div p</code></td>
<td>Matches p anywhere inside div</td>
</tr>
<tr>
<td>Child</td>
<td><code>div &gt; p</code></td>
<td>Matches p that is direct child of div</td>
</tr>
<tr>
<td>Adjacent sibling</td>
<td><code>h1 + p</code></td>
<td>Matches p immediately after h1</td>
</tr>
<tr>
<td>General sibling</td>
<td><code>h1 ~ p</code></td>
<td>Matches any p after h1</td>
</tr>
<tr>
<td>:first-child</td>
<td><code>p:first-child</code></td>
<td>Matches first child element</td>
</tr>
<tr>
<td>:last-child</td>
<td><code>p:last-child</code></td>
<td>Matches last child element</td>
</tr>
<tr>
<td>:nth-child(n)</td>
<td><code>li:nth-child(2)</code></td>
<td>Matches 2nd child</td>
</tr>
<tr>
<td>:nth-child(odd/even)</td>
<td><code>tr:nth-child(odd)</code></td>
<td>Matches odd rows</td>
</tr>
<tr>
<td>:first-of-type</td>
<td><code>p:first-of-type</code></td>
<td>First p among siblings</td>
</tr>
<tr>
<td>:last-of-type</td>
<td><code>p:last-of-type</code></td>
<td>Last p among siblings</td>
</tr>
<tr>
<td>:only-child</td>
<td><code>p:only-child</code></td>
<td>Matches if only child</td>
</tr>
<tr>
<td>:empty</td>
<td><code>div:empty</code></td>
<td>Matches elements with no children</td>
</tr>
</table>
<h3>Compound Selectors</h3>
<pre><code>doc.querySelector("div.container#main") // Tag + class + ID
doc.querySelector("input[type='text'].large") // Tag + attribute + class
doc.querySelector("ul > li:first-child") // Combinator + pseudo-class</code></pre>
<hr>
<h2>Examples</h2>
<h3>Building URLs</h3>
<pre><code>import "html" for Html
<h3>Web Scraping</h3>
<pre><code>import "html" for Document
var baseUrl = "https://api.example.com/search"
var params = {
"query": "wren programming",
"limit": 10,
"offset": 0
}
var html = """
&lt;div class="products"&gt;
&lt;div class="product"&gt;
&lt;h2&gt;Widget&lt;/h2&gt;
&lt;span class="price"&gt;$9.99&lt;/span&gt;
&lt;/div&gt;
&lt;div class="product"&gt;
&lt;h2&gt;Gadget&lt;/h2&gt;
&lt;span class="price"&gt;$19.99&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
"""
var url = baseUrl + "?" + Html.encodeParams(params)
System.print(url)
// https://api.example.com/search?query=wren+programming&amp;limit=10&amp;offset=0</code></pre>
<h3>Safe HTML Output</h3>
<pre><code>import "html" for Html
var userInput = "&lt;script&gt;alert('xss')&lt;/script&gt;"
var safeHtml = "&lt;div class=\"comment\"&gt;" + Html.quote(userInput) + "&lt;/div&gt;"
System.print(safeHtml)</code></pre>
<h3>Generating Slugs for URLs</h3>
<pre><code>import "html" for Html
var title = "How to Build Web Apps with Wren!"
var slug = Html.slugify(title)
var url = "/blog/" + slug
System.print(url) // /blog/how-to-build-web-apps-with-wren</code></pre>
<h3>Parsing Query Strings</h3>
<pre><code>import "html" for Html
var queryString = "category=books&amp;sort=price&amp;order=asc"
var params = Html.decodeParams(queryString)
for (key in params.keys) {
System.print("%(key): %(params[key])")
var doc = Document.parse(html)
for (product in doc.querySelectorAll(".product")) {
var name = product.querySelector("h2").textContent
var price = product.querySelector(".price").textContent
System.print("%(name): %(price)")
}</code></pre>
<div class="admonition warning">
<div class="admonition-title">Warning</div>
<p>Always use <code>Html.quote()</code> when inserting user-provided content into HTML to prevent cross-site scripting (XSS) attacks.</p>
<h3>HTML Generation</h3>
<pre><code>import "html" for Document
var doc = Document.parse("&lt;div id='root'&gt;&lt;/div&gt;")
var root = doc.getElementById("root")
var items = ["Apple", "Banana", "Cherry"]
var ul = doc.createElement("ul")
for (item in items) {
var li = doc.createElement("li")
li.textContent = item
ul.appendChild(li)
}
root.appendChild(ul)
System.print(doc.outerHTML)</code></pre>
<h3>DOM Transformation</h3>
<pre><code>import "html" for Document
var doc = Document.parse("&lt;p&gt;Hello &lt;b&gt;World&lt;/b&gt;&lt;/p&gt;")
// Replace all &lt;b&gt; with &lt;strong&gt;
for (b in doc.querySelectorAll("b").toList) {
var strong = doc.createElement("strong")
strong.innerHTML = b.innerHTML
b.parentNode.replaceChild(strong, b)
}
System.print(doc.outerHTML)</code></pre>
<div class="admonition note">
<div class="admonition-title">Note</div>
<p>The DOM implementation automatically decodes HTML entities when parsing and encodes them when serializing.</p>
</div>
{% endblock %}

View File

@ -163,6 +163,7 @@ OBJECTS += $(OBJDIR)/pexpect.o
OBJECTS += $(OBJDIR)/tls.o
OBJECTS += $(OBJDIR)/udp_module.o
OBJECTS += $(OBJDIR)/jinja.o
OBJECTS += $(OBJDIR)/dom.o
OBJECTS += $(OBJDIR)/stream.o
OBJECTS += $(OBJDIR)/strscpy.o
OBJECTS += $(OBJDIR)/sysinfo-loadavg.o
@ -482,6 +483,9 @@ $(OBJDIR)/udp_module.o: ../../src/module/udp.c
$(OBJDIR)/jinja.o: ../../src/module/jinja.c
@echo $(notdir $<)
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
$(OBJDIR)/dom.o: ../../src/module/dom.c
@echo $(notdir $<)
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
-include $(OBJECTS:%.o=%.d)
ifneq (,$(PCH))

View File

@ -307,6 +307,93 @@ extern void jinjaWordcount(WrenVM* vm);
extern void jinjaRenderNodes(WrenVM* vm);
extern void jinjaTokenize(WrenVM* vm);
extern void jinjaJsonEscape(WrenVM* vm);
extern void domDocumentAllocate(WrenVM* vm);
extern void domDocumentFinalize(void* data);
extern void domDocumentQuerySelector(WrenVM* vm);
extern void domDocumentQuerySelectorAll(WrenVM* vm);
extern void domDocumentGetElementById(WrenVM* vm);
extern void domDocumentGetElementsByClassName(WrenVM* vm);
extern void domDocumentGetElementsByTagName(WrenVM* vm);
extern void domDocumentCreateElement(WrenVM* vm);
extern void domDocumentCreateTextNode(WrenVM* vm);
extern void domDocumentBody(WrenVM* vm);
extern void domDocumentHead(WrenVM* vm);
extern void domDocumentDocumentElement(WrenVM* vm);
extern void domDocumentTitle(WrenVM* vm);
extern void domDocumentSetTitle(WrenVM* vm);
extern void domDocumentOuterHTML(WrenVM* vm);
extern void domDocumentInnerHTML(WrenVM* vm);
extern void domElementAllocate(WrenVM* vm);
extern void domElementFinalize(void* data);
extern void domElementTagName(WrenVM* vm);
extern void domElementId(WrenVM* vm);
extern void domElementSetId(WrenVM* vm);
extern void domElementClassName(WrenVM* vm);
extern void domElementSetClassName(WrenVM* vm);
extern void domElementClassList(WrenVM* vm);
extern void domElementInnerHTML(WrenVM* vm);
extern void domElementSetInnerHTML(WrenVM* vm);
extern void domElementOuterHTML(WrenVM* vm);
extern void domElementSetOuterHTML(WrenVM* vm);
extern void domElementTextContent(WrenVM* vm);
extern void domElementSetTextContent(WrenVM* vm);
extern void domElementGetAttribute(WrenVM* vm);
extern void domElementSetAttribute(WrenVM* vm);
extern void domElementRemoveAttribute(WrenVM* vm);
extern void domElementHasAttribute(WrenVM* vm);
extern void domElementAttributes(WrenVM* vm);
extern void domElementParentNode(WrenVM* vm);
extern void domElementParentElement(WrenVM* vm);
extern void domElementChildren(WrenVM* vm);
extern void domElementChildNodes(WrenVM* vm);
extern void domElementFirstChild(WrenVM* vm);
extern void domElementLastChild(WrenVM* vm);
extern void domElementFirstElementChild(WrenVM* vm);
extern void domElementLastElementChild(WrenVM* vm);
extern void domElementNextSibling(WrenVM* vm);
extern void domElementPreviousSibling(WrenVM* vm);
extern void domElementNextElementSibling(WrenVM* vm);
extern void domElementPreviousElementSibling(WrenVM* vm);
extern void domElementAppendChild(WrenVM* vm);
extern void domElementInsertBefore(WrenVM* vm);
extern void domElementRemoveChild(WrenVM* vm);
extern void domElementReplaceChild(WrenVM* vm);
extern void domElementCloneNode(WrenVM* vm);
extern void domElementQuerySelector(WrenVM* vm);
extern void domElementQuerySelectorAll(WrenVM* vm);
extern void domElementGetElementsByClassName(WrenVM* vm);
extern void domElementGetElementsByTagName(WrenVM* vm);
extern void domElementMatches(WrenVM* vm);
extern void domElementClosest(WrenVM* vm);
extern void domElementRemove(WrenVM* vm);
extern void domElementContains(WrenVM* vm);
extern void domElementHasChildNodes(WrenVM* vm);
extern void domElementNormalize(WrenVM* vm);
extern void domElementNodeType(WrenVM* vm);
extern void domElementNodeName(WrenVM* vm);
extern void domElementNodeValue(WrenVM* vm);
extern void domElementSetNodeValue(WrenVM* vm);
extern void domElementDataset(WrenVM* vm);
extern void domTextNodeAllocate(WrenVM* vm);
extern void domTextNodeFinalize(void* data);
extern void domTextNodeTextContent(WrenVM* vm);
extern void domTextNodeSetTextContent(WrenVM* vm);
extern void domTextNodeNodeType(WrenVM* vm);
extern void domTextNodeNodeName(WrenVM* vm);
extern void domTextNodeNodeValue(WrenVM* vm);
extern void domTextNodeSetNodeValue(WrenVM* vm);
extern void domTextNodeParentNode(WrenVM* vm);
extern void domTextNodeParentElement(WrenVM* vm);
extern void domTextNodeNextSibling(WrenVM* vm);
extern void domTextNodePreviousSibling(WrenVM* vm);
extern void domTextNodeCloneNode(WrenVM* vm);
extern void domTextNodeRemove(WrenVM* vm);
extern void domNodeListAllocate(WrenVM* vm);
extern void domNodeListFinalize(void* data);
extern void domNodeListCount(WrenVM* vm);
extern void domNodeListSubscript(WrenVM* vm);
extern void domNodeListItem(WrenVM* vm);
extern void domNodeListToList(WrenVM* vm);
// The maximum number of foreign methods a single class defines. Ideally, we
// would use variable-length arrays for each class in the table below, but
@ -316,7 +403,7 @@ extern void jinjaJsonEscape(WrenVM* vm);
// If you add a new method to the longest class below, make sure to bump this.
// Note that it also includes an extra slot for the sentinel value indicating
// the end of the list.
#define MAX_METHODS_PER_CLASS 48
#define MAX_METHODS_PER_CLASS 64
// The maximum number of foreign classes a single built-in module defines.
//
@ -441,6 +528,101 @@ static ModuleRegistry modules[] =
END_CLASS
END_MODULE
MODULE(html)
CLASS(Document)
ALLOCATE(domDocumentAllocate)
FINALIZE(domDocumentFinalize)
METHOD("querySelector(_)", domDocumentQuerySelector)
METHOD("querySelectorAll(_)", domDocumentQuerySelectorAll)
METHOD("getElementById(_)", domDocumentGetElementById)
METHOD("getElementsByClassName(_)", domDocumentGetElementsByClassName)
METHOD("getElementsByTagName(_)", domDocumentGetElementsByTagName)
METHOD("createElement(_)", domDocumentCreateElement)
METHOD("createTextNode(_)", domDocumentCreateTextNode)
METHOD("body", domDocumentBody)
METHOD("head", domDocumentHead)
METHOD("documentElement", domDocumentDocumentElement)
METHOD("title", domDocumentTitle)
METHOD("title=(_)", domDocumentSetTitle)
METHOD("outerHTML", domDocumentOuterHTML)
METHOD("innerHTML", domDocumentInnerHTML)
END_CLASS
CLASS(Element)
ALLOCATE(domElementAllocate)
FINALIZE(domElementFinalize)
METHOD("tagName", domElementTagName)
METHOD("id", domElementId)
METHOD("id=(_)", domElementSetId)
METHOD("className", domElementClassName)
METHOD("className=(_)", domElementSetClassName)
METHOD("classList", domElementClassList)
METHOD("innerHTML", domElementInnerHTML)
METHOD("innerHTML=(_)", domElementSetInnerHTML)
METHOD("outerHTML", domElementOuterHTML)
METHOD("outerHTML=(_)", domElementSetOuterHTML)
METHOD("textContent", domElementTextContent)
METHOD("textContent=(_)", domElementSetTextContent)
METHOD("getAttribute(_)", domElementGetAttribute)
METHOD("setAttribute(_,_)", domElementSetAttribute)
METHOD("removeAttribute(_)", domElementRemoveAttribute)
METHOD("hasAttribute(_)", domElementHasAttribute)
METHOD("attributes", domElementAttributes)
METHOD("parentNode", domElementParentNode)
METHOD("parentElement", domElementParentElement)
METHOD("children", domElementChildren)
METHOD("childNodes", domElementChildNodes)
METHOD("firstChild", domElementFirstChild)
METHOD("lastChild", domElementLastChild)
METHOD("firstElementChild", domElementFirstElementChild)
METHOD("lastElementChild", domElementLastElementChild)
METHOD("nextSibling", domElementNextSibling)
METHOD("previousSibling", domElementPreviousSibling)
METHOD("nextElementSibling", domElementNextElementSibling)
METHOD("previousElementSibling", domElementPreviousElementSibling)
METHOD("appendChild(_)", domElementAppendChild)
METHOD("insertBefore(_,_)", domElementInsertBefore)
METHOD("removeChild(_)", domElementRemoveChild)
METHOD("replaceChild(_,_)", domElementReplaceChild)
METHOD("cloneNode(_)", domElementCloneNode)
METHOD("querySelector(_)", domElementQuerySelector)
METHOD("querySelectorAll(_)", domElementQuerySelectorAll)
METHOD("getElementsByClassName(_)", domElementGetElementsByClassName)
METHOD("getElementsByTagName(_)", domElementGetElementsByTagName)
METHOD("matches(_)", domElementMatches)
METHOD("closest(_)", domElementClosest)
METHOD("remove()", domElementRemove)
METHOD("contains(_)", domElementContains)
METHOD("hasChildNodes()", domElementHasChildNodes)
METHOD("normalize()", domElementNormalize)
METHOD("nodeType", domElementNodeType)
METHOD("nodeName", domElementNodeName)
METHOD("nodeValue", domElementNodeValue)
METHOD("nodeValue=(_)", domElementSetNodeValue)
METHOD("dataset", domElementDataset)
END_CLASS
CLASS(TextNode)
ALLOCATE(domTextNodeAllocate)
FINALIZE(domTextNodeFinalize)
METHOD("textContent", domTextNodeTextContent)
METHOD("textContent=(_)", domTextNodeSetTextContent)
METHOD("nodeType", domTextNodeNodeType)
METHOD("nodeName", domTextNodeNodeName)
METHOD("nodeValue", domTextNodeNodeValue)
METHOD("nodeValue=(_)", domTextNodeSetNodeValue)
METHOD("parentNode", domTextNodeParentNode)
METHOD("parentElement", domTextNodeParentElement)
METHOD("nextSibling", domTextNodeNextSibling)
METHOD("previousSibling", domTextNodePreviousSibling)
METHOD("cloneNode(_)", domTextNodeCloneNode)
METHOD("remove()", domTextNodeRemove)
END_CLASS
CLASS(NodeList)
ALLOCATE(domNodeListAllocate)
FINALIZE(domNodeListFinalize)
METHOD("count", domNodeListCount)
METHOD("[_]", domNodeListSubscript)
METHOD("item(_)", domNodeListItem)
METHOD("toList", domNodeListToList)
END_CLASS
END_MODULE
MODULE(http)
END_MODULE

2202
src/module/dom.c Normal file

File diff suppressed because it is too large Load Diff

143
src/module/dom.h Normal file
View File

@ -0,0 +1,143 @@
// retoor <retoor@molodetz.nl>
#ifndef dom_h
#define dom_h
#include "wren.h"
typedef enum {
DOM_ELEMENT_NODE = 1,
DOM_TEXT_NODE = 3,
DOM_COMMENT_NODE = 8,
DOM_DOCUMENT_NODE = 9
} DomNodeType;
typedef struct DomNode DomNode;
typedef struct DomDocument DomDocument;
struct DomNode {
DomNodeType type;
char* tagName;
char* textContent;
DomNode* parent;
DomNode* firstChild;
DomNode* lastChild;
DomNode* nextSibling;
DomNode* prevSibling;
char** attrNames;
char** attrValues;
int attrCount;
int attrCapacity;
DomDocument* ownerDocument;
};
struct DomDocument {
DomNode* root;
DomNode* documentElement;
DomNode* head;
DomNode* body;
WrenVM* vm;
WrenHandle* elementClass;
WrenHandle* textNodeClass;
WrenHandle* nodeListClass;
};
typedef struct {
DomNode** nodes;
int count;
int capacity;
DomDocument* ownerDocument;
} DomNodeList;
void domDocumentAllocate(WrenVM* vm);
void domDocumentFinalize(void* data);
void domDocumentQuerySelector(WrenVM* vm);
void domDocumentQuerySelectorAll(WrenVM* vm);
void domDocumentGetElementById(WrenVM* vm);
void domDocumentGetElementsByClassName(WrenVM* vm);
void domDocumentGetElementsByTagName(WrenVM* vm);
void domDocumentCreateElement(WrenVM* vm);
void domDocumentCreateTextNode(WrenVM* vm);
void domDocumentBody(WrenVM* vm);
void domDocumentHead(WrenVM* vm);
void domDocumentDocumentElement(WrenVM* vm);
void domDocumentTitle(WrenVM* vm);
void domDocumentSetTitle(WrenVM* vm);
void domDocumentOuterHTML(WrenVM* vm);
void domDocumentInnerHTML(WrenVM* vm);
void domElementAllocate(WrenVM* vm);
void domElementFinalize(void* data);
void domElementTagName(WrenVM* vm);
void domElementId(WrenVM* vm);
void domElementSetId(WrenVM* vm);
void domElementClassName(WrenVM* vm);
void domElementSetClassName(WrenVM* vm);
void domElementClassList(WrenVM* vm);
void domElementInnerHTML(WrenVM* vm);
void domElementSetInnerHTML(WrenVM* vm);
void domElementOuterHTML(WrenVM* vm);
void domElementSetOuterHTML(WrenVM* vm);
void domElementTextContent(WrenVM* vm);
void domElementSetTextContent(WrenVM* vm);
void domElementGetAttribute(WrenVM* vm);
void domElementSetAttribute(WrenVM* vm);
void domElementRemoveAttribute(WrenVM* vm);
void domElementHasAttribute(WrenVM* vm);
void domElementAttributes(WrenVM* vm);
void domElementParentNode(WrenVM* vm);
void domElementParentElement(WrenVM* vm);
void domElementChildren(WrenVM* vm);
void domElementChildNodes(WrenVM* vm);
void domElementFirstChild(WrenVM* vm);
void domElementLastChild(WrenVM* vm);
void domElementFirstElementChild(WrenVM* vm);
void domElementLastElementChild(WrenVM* vm);
void domElementNextSibling(WrenVM* vm);
void domElementPreviousSibling(WrenVM* vm);
void domElementNextElementSibling(WrenVM* vm);
void domElementPreviousElementSibling(WrenVM* vm);
void domElementAppendChild(WrenVM* vm);
void domElementInsertBefore(WrenVM* vm);
void domElementRemoveChild(WrenVM* vm);
void domElementReplaceChild(WrenVM* vm);
void domElementCloneNode(WrenVM* vm);
void domElementQuerySelector(WrenVM* vm);
void domElementQuerySelectorAll(WrenVM* vm);
void domElementGetElementsByClassName(WrenVM* vm);
void domElementGetElementsByTagName(WrenVM* vm);
void domElementMatches(WrenVM* vm);
void domElementClosest(WrenVM* vm);
void domElementRemove(WrenVM* vm);
void domElementContains(WrenVM* vm);
void domElementHasChildNodes(WrenVM* vm);
void domElementNormalize(WrenVM* vm);
void domElementNodeType(WrenVM* vm);
void domElementNodeName(WrenVM* vm);
void domElementNodeValue(WrenVM* vm);
void domElementSetNodeValue(WrenVM* vm);
void domElementDataset(WrenVM* vm);
void domTextNodeAllocate(WrenVM* vm);
void domTextNodeFinalize(void* data);
void domTextNodeTextContent(WrenVM* vm);
void domTextNodeSetTextContent(WrenVM* vm);
void domTextNodeNodeType(WrenVM* vm);
void domTextNodeNodeName(WrenVM* vm);
void domTextNodeNodeValue(WrenVM* vm);
void domTextNodeSetNodeValue(WrenVM* vm);
void domTextNodeParentNode(WrenVM* vm);
void domTextNodeParentElement(WrenVM* vm);
void domTextNodeNextSibling(WrenVM* vm);
void domTextNodePreviousSibling(WrenVM* vm);
void domTextNodeCloneNode(WrenVM* vm);
void domTextNodeRemove(WrenVM* vm);
void domNodeListAllocate(WrenVM* vm);
void domNodeListFinalize(void* data);
void domNodeListCount(WrenVM* vm);
void domNodeListSubscript(WrenVM* vm);
void domNodeListItem(WrenVM* vm);
void domNodeListToList(WrenVM* vm);
#endif

113
src/module/html.wren vendored
View File

@ -128,3 +128,116 @@ class Html {
return params
}
}
foreign class Document {
construct parse(html) {}
foreign querySelector(selector)
foreign querySelectorAll(selector)
foreign getElementById(id)
foreign getElementsByClassName(className)
foreign getElementsByTagName(tagName)
foreign createElement(tagName)
foreign createTextNode(text)
foreign body
foreign head
foreign documentElement
foreign title
foreign title=(value)
foreign outerHTML
foreign innerHTML
}
foreign class Element {
construct new_() {}
foreign tagName
foreign id
foreign id=(value)
foreign className
foreign className=(value)
foreign classList
foreign innerHTML
foreign innerHTML=(value)
foreign outerHTML
foreign outerHTML=(value)
foreign textContent
foreign textContent=(value)
foreign getAttribute(name)
foreign setAttribute(name, value)
foreign removeAttribute(name)
foreign hasAttribute(name)
foreign attributes
foreign parentNode
foreign parentElement
foreign children
foreign childNodes
foreign firstChild
foreign lastChild
foreign firstElementChild
foreign lastElementChild
foreign nextSibling
foreign previousSibling
foreign nextElementSibling
foreign previousElementSibling
foreign appendChild(child)
foreign insertBefore(newNode, refNode)
foreign removeChild(child)
foreign replaceChild(newChild, oldChild)
foreign cloneNode(deep)
foreign querySelector(selector)
foreign querySelectorAll(selector)
foreign getElementsByClassName(className)
foreign getElementsByTagName(tagName)
foreign matches(selector)
foreign closest(selector)
foreign remove()
foreign contains(node)
foreign hasChildNodes()
foreign normalize()
foreign nodeType
foreign nodeName
foreign nodeValue
foreign nodeValue=(value)
foreign dataset
}
foreign class TextNode {
construct new_() {}
foreign textContent
foreign textContent=(value)
foreign nodeType
foreign nodeName
foreign nodeValue
foreign nodeValue=(value)
foreign parentNode
foreign parentElement
foreign nextSibling
foreign previousSibling
foreign cloneNode(deep)
foreign remove()
}
foreign class NodeList {
construct new_() {}
foreign count
foreign [index]
foreign item(index)
foreign toList
iterate(iterator) {
if (iterator == null) return 0
if (iterator >= count - 1) return false
return iterator + 1
}
iteratorValue(iterator) { this[iterator] }
forEach(fn) {
for (node in this) {
fn.call(node)
}
}
}

View File

@ -131,4 +131,117 @@ static const char* htmlModuleSource =
" }\n"
" return params\n"
" }\n"
"}\n"
"\n"
"foreign class Document {\n"
" construct parse(html) {}\n"
"\n"
" foreign querySelector(selector)\n"
" foreign querySelectorAll(selector)\n"
" foreign getElementById(id)\n"
" foreign getElementsByClassName(className)\n"
" foreign getElementsByTagName(tagName)\n"
" foreign createElement(tagName)\n"
" foreign createTextNode(text)\n"
" foreign body\n"
" foreign head\n"
" foreign documentElement\n"
" foreign title\n"
" foreign title=(value)\n"
" foreign outerHTML\n"
" foreign innerHTML\n"
"}\n"
"\n"
"foreign class Element {\n"
" construct new_() {}\n"
"\n"
" foreign tagName\n"
" foreign id\n"
" foreign id=(value)\n"
" foreign className\n"
" foreign className=(value)\n"
" foreign classList\n"
" foreign innerHTML\n"
" foreign innerHTML=(value)\n"
" foreign outerHTML\n"
" foreign outerHTML=(value)\n"
" foreign textContent\n"
" foreign textContent=(value)\n"
" foreign getAttribute(name)\n"
" foreign setAttribute(name, value)\n"
" foreign removeAttribute(name)\n"
" foreign hasAttribute(name)\n"
" foreign attributes\n"
" foreign parentNode\n"
" foreign parentElement\n"
" foreign children\n"
" foreign childNodes\n"
" foreign firstChild\n"
" foreign lastChild\n"
" foreign firstElementChild\n"
" foreign lastElementChild\n"
" foreign nextSibling\n"
" foreign previousSibling\n"
" foreign nextElementSibling\n"
" foreign previousElementSibling\n"
" foreign appendChild(child)\n"
" foreign insertBefore(newNode, refNode)\n"
" foreign removeChild(child)\n"
" foreign replaceChild(newChild, oldChild)\n"
" foreign cloneNode(deep)\n"
" foreign querySelector(selector)\n"
" foreign querySelectorAll(selector)\n"
" foreign getElementsByClassName(className)\n"
" foreign getElementsByTagName(tagName)\n"
" foreign matches(selector)\n"
" foreign closest(selector)\n"
" foreign remove()\n"
" foreign contains(node)\n"
" foreign hasChildNodes()\n"
" foreign normalize()\n"
" foreign nodeType\n"
" foreign nodeName\n"
" foreign nodeValue\n"
" foreign nodeValue=(value)\n"
" foreign dataset\n"
"}\n"
"\n"
"foreign class TextNode {\n"
" construct new_() {}\n"
"\n"
" foreign textContent\n"
" foreign textContent=(value)\n"
" foreign nodeType\n"
" foreign nodeName\n"
" foreign nodeValue\n"
" foreign nodeValue=(value)\n"
" foreign parentNode\n"
" foreign parentElement\n"
" foreign nextSibling\n"
" foreign previousSibling\n"
" foreign cloneNode(deep)\n"
" foreign remove()\n"
"}\n"
"\n"
"foreign class NodeList {\n"
" construct new_() {}\n"
"\n"
" foreign count\n"
" foreign [index]\n"
" foreign item(index)\n"
" foreign toList\n"
"\n"
" iterate(iterator) {\n"
" if (iterator == null) return 0\n"
" if (iterator >= count - 1) return false\n"
" return iterator + 1\n"
" }\n"
"\n"
" iteratorValue(iterator) { this[iterator] }\n"
"\n"
" forEach(fn) {\n"
" for (node in this) {\n"
" fn.call(node)\n"
" }\n"
" }\n"
"}\n";

View File

@ -645,111 +645,181 @@ class Markdown {
static fromHtml(html) { fromHtml(html, {}) }
static fromHtml(html, options) {
var stripUnknown = options.containsKey("stripUnknown") ? options["stripUnknown"] : true
var text = html
text = removeHiddenTags_(text)
var text = removeHiddenTagsFast_(html)
text = processBlockElements_(text)
text = processInlineFromHtml_(text)
text = Html.unquote(text)
text = cleanupWhitespace_(text)
return text.trim()
}
static removeHiddenTags_(html) {
var result = html
var tags = ["script", "style", "head", "meta", "link", "noscript"]
for (tag in tags) {
result = removeTagWithContent_(result, tag)
}
return result
}
static removeHiddenTagsFast_(html) {
var hiddenTags = {"script": true, "style": true, "head": true, "meta": true, "link": true, "noscript": true}
var parts = []
var pos = 0
var len = html.count
static removeTagWithContent_(html, tag) {
var result = html
var openTag = "<" + tag
var closeTag = "</" + tag + ">"
while (true) {
var startLower = result.indexOf(openTag)
var startUpper = result.indexOf("<" + tag[0].codePoints.toList[0].toString)
var start = -1
while (pos < len) {
var tagStart = indexOfFrom_(html, "<", pos)
if (tagStart < 0) {
parts.add(html[pos..-1])
break
}
var i = 0
while (i < result.count - openTag.count) {
if (matchTagName_(result, i, tag)) {
start = i
break
var tagInfo = parseTagFast_(html, tagStart)
var tagName = tagInfo["name"]
var tagEnd = tagInfo["end"]
var isClose = tagInfo["isClose"]
var isSelfClose = tagInfo["isSelfClose"]
if (hiddenTags.containsKey(tagName)) {
if (tagStart > pos) {
parts.add(html[pos...tagStart])
}
if (isSelfClose || isClose) {
pos = tagEnd
continue
}
var closePos = findCloseTagFast_(html, tagEnd, tagName)
if (closePos < 0) {
pos = tagEnd
} else {
var closeEnd = indexOfFrom_(html, ">", closePos)
pos = (closeEnd >= 0) ? closeEnd + 1 : closePos
}
i = i + 1
}
if (start < 0) break
var tagEnd = start
while (tagEnd < result.count && result[tagEnd] != ">") {
tagEnd = tagEnd + 1
}
if (tagEnd >= result.count) break
if (result[tagEnd - 1] == "/") {
result = result[0...start] + result[tagEnd + 1..-1]
continue
}
var closeStart = findCloseTag_(result, tagEnd + 1, tag)
if (closeStart < 0) {
result = result[0...start] + result[tagEnd + 1..-1]
} else {
var closeEnd = closeStart
while (closeEnd < result.count && result[closeEnd] != ">") {
closeEnd = closeEnd + 1
}
result = result[0...start] + result[closeEnd + 1..-1]
parts.add(html[pos...tagEnd])
pos = tagEnd
}
return parts.join("")
}
static indexOfFrom_(str, char, start) {
var len = str.count
var i = start
while (i < len) {
if (str[i] == char) return i
i = i + 1
}
return -1
}
static parseTagFast_(html, pos) {
var len = html.count
var result = {"name": "", "end": pos + 1, "isClose": false, "isSelfClose": false}
if (pos >= len || html[pos] != "<") return result
var i = pos + 1
while (i < len && (html[i] == " " || html[i] == "\t" || html[i] == "\n")) {
i = i + 1
}
var isClose = false
if (i < len && html[i] == "/") {
isClose = true
i = i + 1
while (i < len && (html[i] == " " || html[i] == "\t" || html[i] == "\n")) {
i = i + 1
}
}
var nameStart = i
while (i < len) {
var c = html[i]
if (c == " " || c == ">" || c == "/" || c == "\t" || c == "\n") break
i = i + 1
}
var name = ""
if (i > nameStart) {
name = toLower_(html[nameStart...i])
}
var tagEnd = indexOfFrom_(html, ">", i)
if (tagEnd < 0) tagEnd = len
var isSelfClose = tagEnd > 0 && html[tagEnd - 1] == "/"
result["name"] = name
result["end"] = tagEnd + 1
result["isClose"] = isClose
result["isSelfClose"] = isSelfClose
return result
}
static toLower_(str) {
var parts = []
for (c in str) {
var code = c.bytes[0]
if (code >= 65 && code <= 90) {
parts.add(String.fromCodePoint(code + 32))
} else {
parts.add(c)
}
}
return parts.join("")
}
static findCloseTagFast_(html, start, tag) {
var len = html.count
var tagLen = tag.count
var i = start
while (i < len - tagLen - 2) {
var idx = indexOfFrom_(html, "<", i)
if (idx < 0) return -1
if (idx + 1 < len && html[idx + 1] == "/") {
var match = true
var j = 0
while (j < tagLen && idx + 2 + j < len) {
var c1 = html[idx + 2 + j].bytes[0]
var c2 = tag[j].bytes[0]
if (c1 >= 65 && c1 <= 90) c1 = c1 + 32
if (c1 != c2) {
match = false
break
}
j = j + 1
}
if (match && j == tagLen) {
var next = idx + 2 + tagLen
if (next >= len || html[next] == ">" || html[next] == " " || html[next] == "\t" || html[next] == "\n") {
return idx
}
}
}
i = idx + 1
}
return -1
}
static matchTagName_(html, pos, tag) {
if (pos >= html.count || html[pos] != "<") return false
var rest = html[pos + 1..-1].trim()
var tagLower = tag
for (i in 0...tag.count) {
if (i >= rest.count) return false
var c1 = rest[i].codePoints.toList[0]
var c2 = tag[i].codePoints.toList[0]
var len = html.count
var tagLen = tag.count
if (pos >= len || html[pos] != "<") return false
var i = pos + 1
while (i < len && (html[i] == " " || html[i] == "\t" || html[i] == "\n")) {
i = i + 1
}
for (j in 0...tagLen) {
if (i + j >= len) return false
var c1 = html[i + j].bytes[0]
var c2 = tag[j].bytes[0]
if (c1 >= 65 && c1 <= 90) c1 = c1 + 32
if (c2 >= 65 && c2 <= 90) c2 = c2 + 32
if (c1 != c2) return false
}
if (tag.count < rest.count) {
var next = rest[tag.count]
var nextPos = i + tagLen
if (nextPos < len) {
var next = html[nextPos]
if (next != " " && next != ">" && next != "/" && next != "\t" && next != "\n") return false
}
return true
}
static findCloseTag_(html, start, tag) {
var i = start
while (i < html.count - tag.count - 2) {
if (html[i] == "<" && html[i + 1] == "/") {
var match = true
for (j in 0...tag.count) {
var c1 = html[i + 2 + j].codePoints.toList[0]
var c2 = tag[j].codePoints.toList[0]
if (c1 >= 65 && c1 <= 90) c1 = c1 + 32
if (c2 >= 65 && c2 <= 90) c2 = c2 + 32
if (c1 != c2) {
match = false
break
}
}
if (match) return i
}
i = i + 1
}
return -1
return findCloseTagFast_(html, start, tag)
}
static processBlockElements_(html) {
@ -1142,52 +1212,63 @@ class Markdown {
static findTagStartFrom_(html, start, tag) {
var i = start
while (i < html.count) {
if (matchTagName_(html, i, tag)) return i
i = i + 1
var len = html.count
while (i < len) {
var idx = indexOfFrom_(html, "<", i)
if (idx < 0) return -1
if (matchTagName_(html, idx, tag)) return idx
i = idx + 1
}
return -1
}
static processHrFromHtml_(html) {
var parts = []
var i = 0
while (i < html.count) {
if (matchTagName_(html, i, "hr")) {
var end = i
while (end < html.count && html[end] != ">") {
end = end + 1
}
var pos = 0
var len = html.count
while (pos < len) {
var tagStart = indexOfFrom_(html, "<", pos)
if (tagStart < 0) {
parts.add(html[pos..-1])
break
}
if (matchTagName_(html, tagStart, "hr")) {
if (tagStart > pos) parts.add(html[pos...tagStart])
var end = indexOfFrom_(html, ">", tagStart)
if (end < 0) end = len - 1
parts.add("\n---\n")
i = end + 1
pos = end + 1
} else {
parts.add(html[i])
i = i + 1
parts.add(html[pos...tagStart + 1])
pos = tagStart + 1
}
}
return parts.join("")
}
static processParagraphsFromHtml_(html) {
var result = html
result = replaceTag_(result, "p", "", "\n\n")
return result
return replaceTag_(html, "p", "", "\n\n")
}
static processBrFromHtml_(html) {
var parts = []
var i = 0
while (i < html.count) {
if (matchTagName_(html, i, "br")) {
var end = i
while (end < html.count && html[end] != ">") {
end = end + 1
}
var pos = 0
var len = html.count
while (pos < len) {
var tagStart = indexOfFrom_(html, "<", pos)
if (tagStart < 0) {
parts.add(html[pos..-1])
break
}
if (matchTagName_(html, tagStart, "br")) {
if (tagStart > pos) parts.add(html[pos...tagStart])
var end = indexOfFrom_(html, ">", tagStart)
if (end < 0) end = len - 1
parts.add("\n")
i = end + 1
pos = end + 1
} else {
parts.add(html[i])
i = i + 1
parts.add(html[pos...tagStart + 1])
pos = tagStart + 1
}
}
return parts.join("")
@ -1242,71 +1323,95 @@ class Markdown {
}
static processLinksFromHtml_(html) {
var result = html
var parts = []
var pos = 0
var len = html.count
while (true) {
var start = findTagStart_(result, "a")
if (start < 0) break
var openEnd = start
while (openEnd < result.count && result[openEnd] != ">") {
openEnd = openEnd + 1
while (pos < len) {
var start = findTagStartFrom_(html, pos, "a")
if (start < 0) {
parts.add(html[pos..-1])
break
}
if (openEnd >= result.count) break
var tagContent = result[start...openEnd + 1]
if (start > pos) {
parts.add(html[pos...start])
}
var openEnd = indexOfFrom_(html, ">", start)
if (openEnd < 0) {
parts.add(html[pos..-1])
break
}
var tagContent = html[start...openEnd + 1]
var href = extractAttr_(tagContent, "href")
var closeStart = findCloseTag_(result, openEnd + 1, "a")
if (closeStart < 0) break
var linkText = result[openEnd + 1...closeStart]
var closeEnd = closeStart
while (closeEnd < result.count && result[closeEnd] != ">") {
closeEnd = closeEnd + 1
var closeStart = findCloseTagFast_(html, openEnd + 1, "a")
if (closeStart < 0) {
parts.add(html[pos...openEnd + 1])
pos = openEnd + 1
continue
}
var before = result[0...start]
var after = result[closeEnd + 1..-1]
var linkText = html[openEnd + 1...closeStart]
var closeEnd = indexOfFrom_(html, ">", closeStart)
if (closeEnd < 0) closeEnd = len - 1
if (href != null && href.count > 0) {
result = before + "[" + stripAllTags_(linkText) + "](" + href + ")" + after
parts.add("[")
parts.add(stripAllTags_(linkText))
parts.add("](")
parts.add(href)
parts.add(")")
} else {
result = before + stripAllTags_(linkText) + after
parts.add(stripAllTags_(linkText))
}
pos = closeEnd + 1
}
return result
return parts.join("")
}
static processImagesFromHtml_(html) {
var result = html
var i = 0
var parts = []
var pos = 0
var len = html.count
while (i < result.count) {
if (matchTagName_(result, i, "img")) {
var end = i
while (end < result.count && result[end] != ">") {
end = end + 1
while (pos < len) {
var tagStart = indexOfFrom_(html, "<", pos)
if (tagStart < 0) {
parts.add(html[pos..-1])
break
}
if (matchTagName_(html, tagStart, "img")) {
if (tagStart > pos) {
parts.add(html[pos...tagStart])
}
var end = indexOfFrom_(html, ">", tagStart)
if (end < 0) end = len - 1
var tagContent = result[i...end + 1]
var tagContent = html[tagStart...end + 1]
var src = extractAttr_(tagContent, "src")
var alt = extractAttr_(tagContent, "alt")
if (src == null) src = ""
if (alt == null) alt = ""
var mdImg = "![" + alt + "](" + src + ")"
result = result[0...i] + mdImg + result[end + 1..-1]
i = i + mdImg.count
parts.add("![")
parts.add(alt)
parts.add("](")
parts.add(src)
parts.add(")")
pos = end + 1
} else {
i = i + 1
parts.add(html[pos...tagStart + 1])
pos = tagStart + 1
}
}
return result
return parts.join("")
}
static extractAttr_(tag, name) {
@ -1329,42 +1434,49 @@ class Markdown {
}
static replaceTag_(html, tag, prefix, suffix) {
var result = html
var parts = []
var pos = 0
var len = html.count
while (true) {
var start = findTagStart_(result, tag)
if (start < 0) break
var openEnd = start
while (openEnd < result.count && result[openEnd] != ">") {
openEnd = openEnd + 1
while (pos < len) {
var start = findTagStartFrom_(html, pos, tag)
if (start < 0) {
parts.add(html[pos..-1])
break
}
if (openEnd >= result.count) break
if (result[openEnd - 1] == "/") {
result = result[0...start] + result[openEnd + 1..-1]
if (start > pos) {
parts.add(html[pos...start])
}
var openEnd = indexOfFrom_(html, ">", start)
if (openEnd < 0) {
parts.add(html[pos..-1])
break
}
if (html[openEnd - 1] == "/") {
pos = openEnd + 1
continue
}
var closeStart = findCloseTag_(result, openEnd + 1, tag)
var closeStart = findCloseTagFast_(html, openEnd + 1, tag)
if (closeStart < 0) {
result = result[0...start] + result[openEnd + 1..-1]
pos = openEnd + 1
continue
}
var content = result[openEnd + 1...closeStart]
var content = html[openEnd + 1...closeStart]
var closeEnd = indexOfFrom_(html, ">", closeStart)
if (closeEnd < 0) closeEnd = len - 1
var closeEnd = closeStart
while (closeEnd < result.count && result[closeEnd] != ">") {
closeEnd = closeEnd + 1
}
var before = result[0...start]
var after = result[closeEnd + 1..-1]
result = before + prefix + content + suffix + after
parts.add(prefix)
parts.add(content)
parts.add(suffix)
pos = closeEnd + 1
}
return result
return parts.join("")
}
static findTagStart_(html, tag) {
@ -1373,30 +1485,36 @@ class Markdown {
static stripAllTags_(html) {
var parts = []
var inTag = false
for (c in html) {
if (c == "<") {
inTag = true
} else if (c == ">") {
inTag = false
} else if (!inTag) {
parts.add(c)
var pos = 0
var len = html.count
while (pos < len) {
var tagStart = indexOfFrom_(html, "<", pos)
if (tagStart < 0) {
parts.add(html[pos..-1])
break
}
if (tagStart > pos) {
parts.add(html[pos...tagStart])
}
var tagEnd = indexOfFrom_(html, ">", tagStart)
if (tagEnd < 0) {
break
}
pos = tagEnd + 1
}
return parts.join("")
}
static cleanupWhitespace_(text) {
var result = text
while (result.contains("\n\n\n")) {
result = result.replace("\n\n\n", "\n\n")
}
var lines = result.split("\n")
var lines = text.split("\n")
var cleaned = []
var prevEmpty = false
for (line in lines) {
cleaned.add(line.trim())
var trimmed = line.trim()
var isEmpty = trimmed.count == 0
if (isEmpty && prevEmpty) continue
cleaned.add(trimmed)
prevEmpty = isEmpty
}
return cleaned.join("\n")

View File

@ -649,111 +649,181 @@ static const char* markdownModuleSource =
" static fromHtml(html) { fromHtml(html, {}) }\n"
"\n"
" static fromHtml(html, options) {\n"
" var stripUnknown = options.containsKey(\"stripUnknown\") ? options[\"stripUnknown\"] : true\n"
" var text = html\n"
"\n"
" text = removeHiddenTags_(text)\n"
" var text = removeHiddenTagsFast_(html)\n"
" text = processBlockElements_(text)\n"
" text = processInlineFromHtml_(text)\n"
" text = Html.unquote(text)\n"
" text = cleanupWhitespace_(text)\n"
"\n"
" return text.trim()\n"
" }\n"
"\n"
" static removeHiddenTags_(html) {\n"
" var result = html\n"
" var tags = [\"script\", \"style\", \"head\", \"meta\", \"link\", \"noscript\"]\n"
" for (tag in tags) {\n"
" result = removeTagWithContent_(result, tag)\n"
" }\n"
" return result\n"
" }\n"
" static removeHiddenTagsFast_(html) {\n"
" var hiddenTags = {\"script\": true, \"style\": true, \"head\": true, \"meta\": true, \"link\": true, \"noscript\": true}\n"
" var parts = []\n"
" var pos = 0\n"
" var len = html.count\n"
"\n"
" static removeTagWithContent_(html, tag) {\n"
" var result = html\n"
" var openTag = \"<\" + tag\n"
" var closeTag = \"</\" + tag + \">\"\n"
" while (true) {\n"
" var startLower = result.indexOf(openTag)\n"
" var startUpper = result.indexOf(\"<\" + tag[0].codePoints.toList[0].toString)\n"
" var start = -1\n"
" while (pos < len) {\n"
" var tagStart = indexOfFrom_(html, \"<\", pos)\n"
" if (tagStart < 0) {\n"
" parts.add(html[pos..-1])\n"
" break\n"
" }\n"
"\n"
" var i = 0\n"
" while (i < result.count - openTag.count) {\n"
" if (matchTagName_(result, i, tag)) {\n"
" start = i\n"
" break\n"
" var tagInfo = parseTagFast_(html, tagStart)\n"
" var tagName = tagInfo[\"name\"]\n"
" var tagEnd = tagInfo[\"end\"]\n"
" var isClose = tagInfo[\"isClose\"]\n"
" var isSelfClose = tagInfo[\"isSelfClose\"]\n"
"\n"
" if (hiddenTags.containsKey(tagName)) {\n"
" if (tagStart > pos) {\n"
" parts.add(html[pos...tagStart])\n"
" }\n"
" if (isSelfClose || isClose) {\n"
" pos = tagEnd\n"
" continue\n"
" }\n"
" var closePos = findCloseTagFast_(html, tagEnd, tagName)\n"
" if (closePos < 0) {\n"
" pos = tagEnd\n"
" } else {\n"
" var closeEnd = indexOfFrom_(html, \">\", closePos)\n"
" pos = (closeEnd >= 0) ? closeEnd + 1 : closePos\n"
" }\n"
" i = i + 1\n"
" }\n"
"\n"
" if (start < 0) break\n"
"\n"
" var tagEnd = start\n"
" while (tagEnd < result.count && result[tagEnd] != \">\") {\n"
" tagEnd = tagEnd + 1\n"
" }\n"
" if (tagEnd >= result.count) break\n"
"\n"
" if (result[tagEnd - 1] == \"/\") {\n"
" result = result[0...start] + result[tagEnd + 1..-1]\n"
" continue\n"
" }\n"
"\n"
" var closeStart = findCloseTag_(result, tagEnd + 1, tag)\n"
" if (closeStart < 0) {\n"
" result = result[0...start] + result[tagEnd + 1..-1]\n"
" } else {\n"
" var closeEnd = closeStart\n"
" while (closeEnd < result.count && result[closeEnd] != \">\") {\n"
" closeEnd = closeEnd + 1\n"
" }\n"
" result = result[0...start] + result[closeEnd + 1..-1]\n"
" parts.add(html[pos...tagEnd])\n"
" pos = tagEnd\n"
" }\n"
"\n"
" return parts.join(\"\")\n"
" }\n"
"\n"
" static indexOfFrom_(str, char, start) {\n"
" var len = str.count\n"
" var i = start\n"
" while (i < len) {\n"
" if (str[i] == char) return i\n"
" i = i + 1\n"
" }\n"
" return -1\n"
" }\n"
"\n"
" static parseTagFast_(html, pos) {\n"
" var len = html.count\n"
" var result = {\"name\": \"\", \"end\": pos + 1, \"isClose\": false, \"isSelfClose\": false}\n"
" if (pos >= len || html[pos] != \"<\") return result\n"
"\n"
" var i = pos + 1\n"
" while (i < len && (html[i] == \" \" || html[i] == \"\\t\" || html[i] == \"\\n\")) {\n"
" i = i + 1\n"
" }\n"
"\n"
" var isClose = false\n"
" if (i < len && html[i] == \"/\") {\n"
" isClose = true\n"
" i = i + 1\n"
" while (i < len && (html[i] == \" \" || html[i] == \"\\t\" || html[i] == \"\\n\")) {\n"
" i = i + 1\n"
" }\n"
" }\n"
"\n"
" var nameStart = i\n"
" while (i < len) {\n"
" var c = html[i]\n"
" if (c == \" \" || c == \">\" || c == \"/\" || c == \"\\t\" || c == \"\\n\") break\n"
" i = i + 1\n"
" }\n"
"\n"
" var name = \"\"\n"
" if (i > nameStart) {\n"
" name = toLower_(html[nameStart...i])\n"
" }\n"
"\n"
" var tagEnd = indexOfFrom_(html, \">\", i)\n"
" if (tagEnd < 0) tagEnd = len\n"
" var isSelfClose = tagEnd > 0 && html[tagEnd - 1] == \"/\"\n"
"\n"
" result[\"name\"] = name\n"
" result[\"end\"] = tagEnd + 1\n"
" result[\"isClose\"] = isClose\n"
" result[\"isSelfClose\"] = isSelfClose\n"
" return result\n"
" }\n"
"\n"
" static toLower_(str) {\n"
" var parts = []\n"
" for (c in str) {\n"
" var code = c.bytes[0]\n"
" if (code >= 65 && code <= 90) {\n"
" parts.add(String.fromCodePoint(code + 32))\n"
" } else {\n"
" parts.add(c)\n"
" }\n"
" }\n"
" return parts.join(\"\")\n"
" }\n"
"\n"
" static findCloseTagFast_(html, start, tag) {\n"
" var len = html.count\n"
" var tagLen = tag.count\n"
" var i = start\n"
" while (i < len - tagLen - 2) {\n"
" var idx = indexOfFrom_(html, \"<\", i)\n"
" if (idx < 0) return -1\n"
" if (idx + 1 < len && html[idx + 1] == \"/\") {\n"
" var match = true\n"
" var j = 0\n"
" while (j < tagLen && idx + 2 + j < len) {\n"
" var c1 = html[idx + 2 + j].bytes[0]\n"
" var c2 = tag[j].bytes[0]\n"
" if (c1 >= 65 && c1 <= 90) c1 = c1 + 32\n"
" if (c1 != c2) {\n"
" match = false\n"
" break\n"
" }\n"
" j = j + 1\n"
" }\n"
" if (match && j == tagLen) {\n"
" var next = idx + 2 + tagLen\n"
" if (next >= len || html[next] == \">\" || html[next] == \" \" || html[next] == \"\\t\" || html[next] == \"\\n\") {\n"
" return idx\n"
" }\n"
" }\n"
" }\n"
" i = idx + 1\n"
" }\n"
" return -1\n"
" }\n"
"\n"
" static matchTagName_(html, pos, tag) {\n"
" if (pos >= html.count || html[pos] != \"<\") return false\n"
" var rest = html[pos + 1..-1].trim()\n"
" var tagLower = tag\n"
" for (i in 0...tag.count) {\n"
" if (i >= rest.count) return false\n"
" var c1 = rest[i].codePoints.toList[0]\n"
" var c2 = tag[i].codePoints.toList[0]\n"
" var len = html.count\n"
" var tagLen = tag.count\n"
" if (pos >= len || html[pos] != \"<\") return false\n"
" var i = pos + 1\n"
" while (i < len && (html[i] == \" \" || html[i] == \"\\t\" || html[i] == \"\\n\")) {\n"
" i = i + 1\n"
" }\n"
" for (j in 0...tagLen) {\n"
" if (i + j >= len) return false\n"
" var c1 = html[i + j].bytes[0]\n"
" var c2 = tag[j].bytes[0]\n"
" if (c1 >= 65 && c1 <= 90) c1 = c1 + 32\n"
" if (c2 >= 65 && c2 <= 90) c2 = c2 + 32\n"
" if (c1 != c2) return false\n"
" }\n"
" if (tag.count < rest.count) {\n"
" var next = rest[tag.count]\n"
" var nextPos = i + tagLen\n"
" if (nextPos < len) {\n"
" var next = html[nextPos]\n"
" if (next != \" \" && next != \">\" && next != \"/\" && next != \"\\t\" && next != \"\\n\") return false\n"
" }\n"
" return true\n"
" }\n"
"\n"
" static findCloseTag_(html, start, tag) {\n"
" var i = start\n"
" while (i < html.count - tag.count - 2) {\n"
" if (html[i] == \"<\" && html[i + 1] == \"/\") {\n"
" var match = true\n"
" for (j in 0...tag.count) {\n"
" var c1 = html[i + 2 + j].codePoints.toList[0]\n"
" var c2 = tag[j].codePoints.toList[0]\n"
" if (c1 >= 65 && c1 <= 90) c1 = c1 + 32\n"
" if (c2 >= 65 && c2 <= 90) c2 = c2 + 32\n"
" if (c1 != c2) {\n"
" match = false\n"
" break\n"
" }\n"
" }\n"
" if (match) return i\n"
" }\n"
" i = i + 1\n"
" }\n"
" return -1\n"
" return findCloseTagFast_(html, start, tag)\n"
" }\n"
"\n"
" static processBlockElements_(html) {\n"
@ -1146,52 +1216,63 @@ static const char* markdownModuleSource =
"\n"
" static findTagStartFrom_(html, start, tag) {\n"
" var i = start\n"
" while (i < html.count) {\n"
" if (matchTagName_(html, i, tag)) return i\n"
" i = i + 1\n"
" var len = html.count\n"
" while (i < len) {\n"
" var idx = indexOfFrom_(html, \"<\", i)\n"
" if (idx < 0) return -1\n"
" if (matchTagName_(html, idx, tag)) return idx\n"
" i = idx + 1\n"
" }\n"
" return -1\n"
" }\n"
"\n"
" static processHrFromHtml_(html) {\n"
" var parts = []\n"
" var i = 0\n"
" while (i < html.count) {\n"
" if (matchTagName_(html, i, \"hr\")) {\n"
" var end = i\n"
" while (end < html.count && html[end] != \">\") {\n"
" end = end + 1\n"
" }\n"
" var pos = 0\n"
" var len = html.count\n"
" while (pos < len) {\n"
" var tagStart = indexOfFrom_(html, \"<\", pos)\n"
" if (tagStart < 0) {\n"
" parts.add(html[pos..-1])\n"
" break\n"
" }\n"
" if (matchTagName_(html, tagStart, \"hr\")) {\n"
" if (tagStart > pos) parts.add(html[pos...tagStart])\n"
" var end = indexOfFrom_(html, \">\", tagStart)\n"
" if (end < 0) end = len - 1\n"
" parts.add(\"\\n---\\n\")\n"
" i = end + 1\n"
" pos = end + 1\n"
" } else {\n"
" parts.add(html[i])\n"
" i = i + 1\n"
" parts.add(html[pos...tagStart + 1])\n"
" pos = tagStart + 1\n"
" }\n"
" }\n"
" return parts.join(\"\")\n"
" }\n"
"\n"
" static processParagraphsFromHtml_(html) {\n"
" var result = html\n"
" result = replaceTag_(result, \"p\", \"\", \"\\n\\n\")\n"
" return result\n"
" return replaceTag_(html, \"p\", \"\", \"\\n\\n\")\n"
" }\n"
"\n"
" static processBrFromHtml_(html) {\n"
" var parts = []\n"
" var i = 0\n"
" while (i < html.count) {\n"
" if (matchTagName_(html, i, \"br\")) {\n"
" var end = i\n"
" while (end < html.count && html[end] != \">\") {\n"
" end = end + 1\n"
" }\n"
" var pos = 0\n"
" var len = html.count\n"
" while (pos < len) {\n"
" var tagStart = indexOfFrom_(html, \"<\", pos)\n"
" if (tagStart < 0) {\n"
" parts.add(html[pos..-1])\n"
" break\n"
" }\n"
" if (matchTagName_(html, tagStart, \"br\")) {\n"
" if (tagStart > pos) parts.add(html[pos...tagStart])\n"
" var end = indexOfFrom_(html, \">\", tagStart)\n"
" if (end < 0) end = len - 1\n"
" parts.add(\"\\n\")\n"
" i = end + 1\n"
" pos = end + 1\n"
" } else {\n"
" parts.add(html[i])\n"
" i = i + 1\n"
" parts.add(html[pos...tagStart + 1])\n"
" pos = tagStart + 1\n"
" }\n"
" }\n"
" return parts.join(\"\")\n"
@ -1246,71 +1327,95 @@ static const char* markdownModuleSource =
" }\n"
"\n"
" static processLinksFromHtml_(html) {\n"
" var result = html\n"
" var parts = []\n"
" var pos = 0\n"
" var len = html.count\n"
"\n"
" while (true) {\n"
" var start = findTagStart_(result, \"a\")\n"
" if (start < 0) break\n"
"\n"
" var openEnd = start\n"
" while (openEnd < result.count && result[openEnd] != \">\") {\n"
" openEnd = openEnd + 1\n"
" while (pos < len) {\n"
" var start = findTagStartFrom_(html, pos, \"a\")\n"
" if (start < 0) {\n"
" parts.add(html[pos..-1])\n"
" break\n"
" }\n"
" if (openEnd >= result.count) break\n"
"\n"
" var tagContent = result[start...openEnd + 1]\n"
" if (start > pos) {\n"
" parts.add(html[pos...start])\n"
" }\n"
"\n"
" var openEnd = indexOfFrom_(html, \">\", start)\n"
" if (openEnd < 0) {\n"
" parts.add(html[pos..-1])\n"
" break\n"
" }\n"
"\n"
" var tagContent = html[start...openEnd + 1]\n"
" var href = extractAttr_(tagContent, \"href\")\n"
"\n"
" var closeStart = findCloseTag_(result, openEnd + 1, \"a\")\n"
" if (closeStart < 0) break\n"
"\n"
" var linkText = result[openEnd + 1...closeStart]\n"
"\n"
" var closeEnd = closeStart\n"
" while (closeEnd < result.count && result[closeEnd] != \">\") {\n"
" closeEnd = closeEnd + 1\n"
" var closeStart = findCloseTagFast_(html, openEnd + 1, \"a\")\n"
" if (closeStart < 0) {\n"
" parts.add(html[pos...openEnd + 1])\n"
" pos = openEnd + 1\n"
" continue\n"
" }\n"
"\n"
" var before = result[0...start]\n"
" var after = result[closeEnd + 1..-1]\n"
" var linkText = html[openEnd + 1...closeStart]\n"
" var closeEnd = indexOfFrom_(html, \">\", closeStart)\n"
" if (closeEnd < 0) closeEnd = len - 1\n"
"\n"
" if (href != null && href.count > 0) {\n"
" result = before + \"[\" + stripAllTags_(linkText) + \"](\" + href + \")\" + after\n"
" parts.add(\"[\")\n"
" parts.add(stripAllTags_(linkText))\n"
" parts.add(\"](\")\n"
" parts.add(href)\n"
" parts.add(\")\")\n"
" } else {\n"
" result = before + stripAllTags_(linkText) + after\n"
" parts.add(stripAllTags_(linkText))\n"
" }\n"
" pos = closeEnd + 1\n"
" }\n"
"\n"
" return result\n"
" return parts.join(\"\")\n"
" }\n"
"\n"
" static processImagesFromHtml_(html) {\n"
" var result = html\n"
" var i = 0\n"
" var parts = []\n"
" var pos = 0\n"
" var len = html.count\n"
"\n"
" while (i < result.count) {\n"
" if (matchTagName_(result, i, \"img\")) {\n"
" var end = i\n"
" while (end < result.count && result[end] != \">\") {\n"
" end = end + 1\n"
" while (pos < len) {\n"
" var tagStart = indexOfFrom_(html, \"<\", pos)\n"
" if (tagStart < 0) {\n"
" parts.add(html[pos..-1])\n"
" break\n"
" }\n"
"\n"
" if (matchTagName_(html, tagStart, \"img\")) {\n"
" if (tagStart > pos) {\n"
" parts.add(html[pos...tagStart])\n"
" }\n"
" var end = indexOfFrom_(html, \">\", tagStart)\n"
" if (end < 0) end = len - 1\n"
"\n"
" var tagContent = result[i...end + 1]\n"
" var tagContent = html[tagStart...end + 1]\n"
" var src = extractAttr_(tagContent, \"src\")\n"
" var alt = extractAttr_(tagContent, \"alt\")\n"
"\n"
" if (src == null) src = \"\"\n"
" if (alt == null) alt = \"\"\n"
"\n"
" var mdImg = \"![\" + alt + \"](\" + src + \")\"\n"
" result = result[0...i] + mdImg + result[end + 1..-1]\n"
" i = i + mdImg.count\n"
" parts.add(\"![\")\n"
" parts.add(alt)\n"
" parts.add(\"](\")\n"
" parts.add(src)\n"
" parts.add(\")\")\n"
" pos = end + 1\n"
" } else {\n"
" i = i + 1\n"
" parts.add(html[pos...tagStart + 1])\n"
" pos = tagStart + 1\n"
" }\n"
" }\n"
"\n"
" return result\n"
" return parts.join(\"\")\n"
" }\n"
"\n"
" static extractAttr_(tag, name) {\n"
@ -1333,42 +1438,49 @@ static const char* markdownModuleSource =
" }\n"
"\n"
" static replaceTag_(html, tag, prefix, suffix) {\n"
" var result = html\n"
" var parts = []\n"
" var pos = 0\n"
" var len = html.count\n"
"\n"
" while (true) {\n"
" var start = findTagStart_(result, tag)\n"
" if (start < 0) break\n"
"\n"
" var openEnd = start\n"
" while (openEnd < result.count && result[openEnd] != \">\") {\n"
" openEnd = openEnd + 1\n"
" while (pos < len) {\n"
" var start = findTagStartFrom_(html, pos, tag)\n"
" if (start < 0) {\n"
" parts.add(html[pos..-1])\n"
" break\n"
" }\n"
" if (openEnd >= result.count) break\n"
"\n"
" if (result[openEnd - 1] == \"/\") {\n"
" result = result[0...start] + result[openEnd + 1..-1]\n"
" if (start > pos) {\n"
" parts.add(html[pos...start])\n"
" }\n"
"\n"
" var openEnd = indexOfFrom_(html, \">\", start)\n"
" if (openEnd < 0) {\n"
" parts.add(html[pos..-1])\n"
" break\n"
" }\n"
"\n"
" if (html[openEnd - 1] == \"/\") {\n"
" pos = openEnd + 1\n"
" continue\n"
" }\n"
"\n"
" var closeStart = findCloseTag_(result, openEnd + 1, tag)\n"
" var closeStart = findCloseTagFast_(html, openEnd + 1, tag)\n"
" if (closeStart < 0) {\n"
" result = result[0...start] + result[openEnd + 1..-1]\n"
" pos = openEnd + 1\n"
" continue\n"
" }\n"
"\n"
" var content = result[openEnd + 1...closeStart]\n"
" var content = html[openEnd + 1...closeStart]\n"
" var closeEnd = indexOfFrom_(html, \">\", closeStart)\n"
" if (closeEnd < 0) closeEnd = len - 1\n"
"\n"
" var closeEnd = closeStart\n"
" while (closeEnd < result.count && result[closeEnd] != \">\") {\n"
" closeEnd = closeEnd + 1\n"
" }\n"
"\n"
" var before = result[0...start]\n"
" var after = result[closeEnd + 1..-1]\n"
" result = before + prefix + content + suffix + after\n"
" parts.add(prefix)\n"
" parts.add(content)\n"
" parts.add(suffix)\n"
" pos = closeEnd + 1\n"
" }\n"
"\n"
" return result\n"
" return parts.join(\"\")\n"
" }\n"
"\n"
" static findTagStart_(html, tag) {\n"
@ -1377,30 +1489,36 @@ static const char* markdownModuleSource =
"\n"
" static stripAllTags_(html) {\n"
" var parts = []\n"
" var inTag = false\n"
" for (c in html) {\n"
" if (c == \"<\") {\n"
" inTag = true\n"
" } else if (c == \">\") {\n"
" inTag = false\n"
" } else if (!inTag) {\n"
" parts.add(c)\n"
" var pos = 0\n"
" var len = html.count\n"
" while (pos < len) {\n"
" var tagStart = indexOfFrom_(html, \"<\", pos)\n"
" if (tagStart < 0) {\n"
" parts.add(html[pos..-1])\n"
" break\n"
" }\n"
" if (tagStart > pos) {\n"
" parts.add(html[pos...tagStart])\n"
" }\n"
" var tagEnd = indexOfFrom_(html, \">\", tagStart)\n"
" if (tagEnd < 0) {\n"
" break\n"
" }\n"
" pos = tagEnd + 1\n"
" }\n"
" return parts.join(\"\")\n"
" }\n"
"\n"
" static cleanupWhitespace_(text) {\n"
" var result = text\n"
"\n"
" while (result.contains(\"\\n\\n\\n\")) {\n"
" result = result.replace(\"\\n\\n\\n\", \"\\n\\n\")\n"
" }\n"
"\n"
" var lines = result.split(\"\\n\")\n"
" var lines = text.split(\"\\n\")\n"
" var cleaned = []\n"
" var prevEmpty = false\n"
" for (line in lines) {\n"
" cleaned.add(line.trim())\n"
" var trimmed = line.trim()\n"
" var isEmpty = trimmed.count == 0\n"
" if (isEmpty && prevEmpty) continue\n"
" cleaned.add(trimmed)\n"
" prevEmpty = isEmpty\n"
" }\n"
"\n"
" return cleaned.join(\"\\n\")\n"

24
test/html/document_createelement.wren vendored Normal file
View File

@ -0,0 +1,24 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div id='container'></div>")
var p = doc.createElement("p")
System.print(p != null) // expect: true
System.print(p.tagName) // expect: P
var span = doc.createElement("span")
System.print(span.tagName) // expect: SPAN
var div = doc.createElement("div")
System.print(div.tagName) // expect: DIV
p.textContent = "New paragraph"
System.print(p.textContent) // expect: New paragraph
var container = doc.getElementById("container")
container.appendChild(p)
System.print(container.children.count) // expect: 1
System.print(container.firstChild.tagName) // expect: P

21
test/html/document_createtextnode.wren vendored Normal file
View File

@ -0,0 +1,21 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div id='container'></div>")
var text = doc.createTextNode("Hello World")
System.print(text != null) // expect: true
System.print(text.textContent) // expect: Hello World
System.print(text.nodeType) // expect: 3
var empty = doc.createTextNode("")
System.print(empty.textContent) // expect:
var special = doc.createTextNode("Line1\nLine2")
System.print(special.textContent.contains("\n")) // expect: true
var container = doc.getElementById("container")
container.appendChild(text)
System.print(container.textContent) // expect: Hello World

22
test/html/document_getelementbyid.wren vendored Normal file
View File

@ -0,0 +1,22 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div id='first'>One</div><div id='second'>Two</div><div>No ID</div>")
var first = doc.getElementById("first")
System.print(first != null) // expect: true
System.print(first.textContent) // expect: One
var second = doc.getElementById("second")
System.print(second != null) // expect: true
System.print(second.textContent) // expect: Two
var notFound = doc.getElementById("nonexistent")
System.print(notFound == null) // expect: true
var nested = Document.parse("<div><span id='inner'>Nested</span></div>")
var inner = nested.getElementById("inner")
System.print(inner != null) // expect: true
System.print(inner.tagName) // expect: SPAN

View File

@ -0,0 +1,25 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div class='box'>A</div><div class='box'>B</div><div class='other'>C</div>")
var boxes = doc.getElementsByClassName("box")
System.print(boxes.count) // expect: 2
System.print(boxes[0].textContent) // expect: A
System.print(boxes[1].textContent) // expect: B
var other = doc.getElementsByClassName("other")
System.print(other.count) // expect: 1
System.print(other[0].textContent) // expect: C
var none = doc.getElementsByClassName("nonexistent")
System.print(none.count) // expect: 0
var multi = Document.parse("<div class='a b'>Multi</div><div class='a'>Single</div>")
var aClass = multi.getElementsByClassName("a")
System.print(aClass.count) // expect: 2
var bClass = multi.getElementsByClassName("b")
System.print(bClass.count) // expect: 1

View File

@ -0,0 +1,24 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div><p>One</p><p>Two</p><span>Three</span></div>")
var paragraphs = doc.getElementsByTagName("p")
System.print(paragraphs.count) // expect: 2
System.print(paragraphs[0].textContent) // expect: One
System.print(paragraphs[1].textContent) // expect: Two
var spans = doc.getElementsByTagName("span")
System.print(spans.count) // expect: 1
System.print(spans[0].textContent) // expect: Three
var divs = doc.getElementsByTagName("div")
System.print(divs.count) // expect: 1
var none = doc.getElementsByTagName("article")
System.print(none.count) // expect: 0
var caseInsensitive = doc.getElementsByTagName("P")
System.print(caseInsensitive.count) // expect: 2

18
test/html/document_parse.wren vendored Normal file
View File

@ -0,0 +1,18 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div>Hello</div>")
System.print(doc != null) // expect: true
var doc2 = Document.parse("<html><head></head><body></body></html>")
System.print(doc2.documentElement != null) // expect: true
var doc3 = Document.parse("")
System.print(doc3 != null) // expect: true
var doc4 = Document.parse("<p>Text</p>")
System.print(doc4.querySelector("p").textContent) // expect: Text
var doc5 = Document.parse("<div><span><a>Link</a></span></div>")
System.print(doc5.querySelector("a").textContent) // expect: Link

26
test/html/document_properties.wren vendored Normal file
View File

@ -0,0 +1,26 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<html><head><title>Test Title</title></head><body><div>Content</div></body></html>")
System.print(doc.documentElement != null) // expect: true
System.print(doc.documentElement.tagName) // expect: HTML
System.print(doc.head != null) // expect: true
System.print(doc.head.tagName) // expect: HEAD
System.print(doc.body != null) // expect: true
System.print(doc.body.tagName) // expect: BODY
System.print(doc.title) // expect: Test Title
doc.title = "New Title"
System.print(doc.title) // expect: New Title
var html = doc.outerHTML
System.print(html.indexOf("<html") >= 0) // expect: true
System.print(html.indexOf("</html>") >= 0) // expect: true
var inner = doc.innerHTML
System.print(inner.indexOf("<head") >= 0) // expect: true

29
test/html/document_queryselector.wren vendored Normal file
View File

@ -0,0 +1,29 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div id='main' class='container'><p class='text'>Hello</p><span>World</span></div>")
var byTag = doc.querySelector("p")
System.print(byTag != null) // expect: true
System.print(byTag.tagName) // expect: P
var byId = doc.querySelector("#main")
System.print(byId != null) // expect: true
System.print(byId.tagName) // expect: DIV
var byClass = doc.querySelector(".text")
System.print(byClass != null) // expect: true
System.print(byClass.textContent) // expect: Hello
var byCompound = doc.querySelector("div.container")
System.print(byCompound != null) // expect: true
var notFound = doc.querySelector(".nonexistent")
System.print(notFound == null) // expect: true
var descendant = doc.querySelector("div p")
System.print(descendant != null) // expect: true
var child = doc.querySelector("div > p")
System.print(child != null) // expect: true

View File

@ -0,0 +1,25 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div><p class='a'>One</p><p class='a'>Two</p><p class='b'>Three</p></div>")
var all = doc.querySelectorAll("p")
System.print(all.count) // expect: 3
var byClass = doc.querySelectorAll(".a")
System.print(byClass.count) // expect: 2
System.print(byClass[0].textContent) // expect: One
System.print(byClass[1].textContent) // expect: Two
var none = doc.querySelectorAll(".nonexistent")
System.print(none.count) // expect: 0
var single = doc.querySelectorAll(".b")
System.print(single.count) // expect: 1
System.print(single[0].textContent) // expect: Three
var nested = Document.parse("<div><span><a>Link1</a></span><a>Link2</a></div>")
var links = nested.querySelectorAll("a")
System.print(links.count) // expect: 2

34
test/html/dom_attributes.wren vendored Normal file
View File

@ -0,0 +1,34 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div id=\"test\" class=\"foo bar\" data-value=\"123\"></div>")
var div = doc.querySelector("div")
System.print(div.id) // expect: test
System.print(div.className) // expect: foo bar
System.print(div.hasAttribute("id")) // expect: true
System.print(div.hasAttribute("missing")) // expect: false
div.setAttribute("title", "My Title")
System.print(div.getAttribute("title")) // expect: My Title
div.removeAttribute("title")
System.print(div.getAttribute("title") == null) // expect: true
var attrs = div.attributes
System.print(attrs["id"]) // expect: test
var classList = div.classList
System.print(classList.count) // expect: 2
System.print(classList[0]) // expect: foo
System.print(classList[1]) // expect: bar
var dataset = div.dataset
System.print(dataset["value"]) // expect: 123
div.id = "newId"
System.print(div.id) // expect: newId
div.className = "single-class"
System.print(div.className) // expect: single-class

22
test/html/dom_content.wren vendored Normal file
View File

@ -0,0 +1,22 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div><p>Hello</p><span>World</span></div>")
var div = doc.querySelector("div")
System.print(div.textContent) // expect: HelloWorld
div.textContent = "New Content"
System.print(div.textContent) // expect: New Content
System.print(div.children.count) // expect: 0
var doc2 = Document.parse("<div id=\"test\"></div>")
var div2 = doc2.getElementById("test")
div2.innerHTML = "<p>First</p><p>Second</p>"
System.print(div2.children.count) // expect: 2
System.print(div2.querySelector("p").textContent) // expect: First
var outer = div2.outerHTML
System.print(outer.indexOf("<div") >= 0) // expect: true
System.print(outer.indexOf("</div>") >= 0) // expect: true

32
test/html/dom_manipulate.wren vendored Normal file
View File

@ -0,0 +1,32 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div id=\"container\"></div>")
var container = doc.getElementById("container")
var p = doc.createElement("p")
p.textContent = "Hello World"
container.appendChild(p)
System.print(container.innerHTML) // expect: <p>Hello World</p>
var span = doc.createElement("span")
span.textContent = "Inserted"
container.insertBefore(span, p)
System.print(container.children.count) // expect: 2
System.print(container.firstElementChild.tagName) // expect: SPAN
var removed = container.removeChild(span)
System.print(removed.tagName) // expect: SPAN
System.print(container.children.count) // expect: 1
var newP = doc.createElement("p")
newP.textContent = "Replaced"
container.replaceChild(newP, p)
System.print(container.innerHTML) // expect: <p>Replaced</p>
var clone = newP.cloneNode(true)
System.print(clone.textContent) // expect: Replaced
System.print(clone.parentNode == null) // expect: true

21
test/html/dom_parse.wren vendored Normal file
View File

@ -0,0 +1,21 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div><p>Hello</p></div>")
System.print(doc.documentElement != null) // expect: true
var doc2 = Document.parse("<html><head><title>Test</title></head><body><div id=\"main\">Content</div></body></html>")
System.print(doc2.head != null) // expect: true
System.print(doc2.body != null) // expect: true
System.print(doc2.title) // expect: Test
var doc3 = Document.parse("<div class=\"foo bar\" data-id=\"123\">Text</div>")
var div = doc3.querySelector("div")
System.print(div.className) // expect: foo bar
System.print(div.getAttribute("data-id")) // expect: 123
var doc4 = Document.parse("<img src=\"test.png\" />")
var img = doc4.querySelector("img")
System.print(img.tagName) // expect: IMG
System.print(img.getAttribute("src")) // expect: test.png

34
test/html/dom_query.wren vendored Normal file
View File

@ -0,0 +1,34 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var html = "<div id=\"root\"><p class=\"text first\">One</p><p class=\"text\">Two</p><span>Three</span></div>"
var doc = Document.parse(html)
var byId = doc.getElementById("root")
System.print(byId.tagName) // expect: DIV
var byTag = doc.querySelector("p")
System.print(byTag.textContent) // expect: One
var byClass = doc.querySelector(".text")
System.print(byClass.textContent) // expect: One
var allP = doc.querySelectorAll("p")
System.print(allP.count) // expect: 2
var allText = doc.getElementsByClassName("text")
System.print(allText.count) // expect: 2
var spans = doc.getElementsByTagName("span")
System.print(spans.count) // expect: 1
System.print(spans[0].textContent) // expect: Three
var compound = doc.querySelector("p.text.first")
System.print(compound.textContent) // expect: One
var descendant = doc.querySelector("div p")
System.print(descendant != null) // expect: true
var child = doc.querySelector("div > p")
System.print(child != null) // expect: true

21
test/html/dom_serialize.wren vendored Normal file
View File

@ -0,0 +1,21 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div id=\"test\" class=\"foo\"><p>Content</p></div>")
var html = doc.outerHTML
System.print(html.indexOf("<div") >= 0) // expect: true
System.print(html.indexOf("id=\"test\"") >= 0) // expect: true
System.print(html.indexOf("class=\"foo\"") >= 0) // expect: true
System.print(html.indexOf("<p>Content</p>") >= 0) // expect: true
System.print(html.indexOf("</div>") >= 0) // expect: true
var doc2 = Document.parse("<img src=\"test.png\" />")
var imgHtml = doc2.outerHTML
System.print(imgHtml.indexOf("<img") >= 0) // expect: true
System.print(imgHtml.indexOf("/>") >= 0) // expect: true
var doc3 = Document.parse("<p>Hello &amp; World</p>")
var p = doc3.querySelector("p")
System.print(p.textContent) // expect: Hello & World

28
test/html/dom_traverse.wren vendored Normal file
View File

@ -0,0 +1,28 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var html = "<div><p>First</p><span>Second</span><p>Third</p></div>"
var doc = Document.parse(html)
var div = doc.querySelector("div")
System.print(div.hasChildNodes()) // expect: true
System.print(div.children.count) // expect: 3
var first = div.firstElementChild
System.print(first.tagName) // expect: P
System.print(first.textContent) // expect: First
var last = div.lastElementChild
System.print(last.tagName) // expect: P
System.print(last.textContent) // expect: Third
var span = doc.querySelector("span")
System.print(span.previousElementSibling.tagName) // expect: P
System.print(span.nextElementSibling.tagName) // expect: P
System.print(span.parentElement.tagName) // expect: DIV
var p = doc.querySelector("p")
System.print(p.parentNode != null) // expect: true

25
test/html/element_appendchild.wren vendored Normal file
View File

@ -0,0 +1,25 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div id='container'></div>")
var container = doc.getElementById("container")
System.print(container.children.count) // expect: 0
var p = doc.createElement("p")
p.textContent = "First"
container.appendChild(p)
System.print(container.children.count) // expect: 1
System.print(container.firstChild.textContent) // expect: First
var span = doc.createElement("span")
span.textContent = "Second"
container.appendChild(span)
System.print(container.children.count) // expect: 2
System.print(container.lastChild.textContent) // expect: Second
var text = doc.createTextNode("Text node")
container.appendChild(text)
System.print(container.textContent.contains("Text node")) // expect: true

27
test/html/element_attributes.wren vendored Normal file
View File

@ -0,0 +1,27 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<a href='http://example.com' target='_blank'>Link</a>")
var a = doc.querySelector("a")
System.print(a.getAttribute("href")) // expect: http://example.com
System.print(a.getAttribute("target")) // expect: _blank
System.print(a.getAttribute("nonexistent") == null) // expect: true
System.print(a.hasAttribute("href")) // expect: true
System.print(a.hasAttribute("target")) // expect: true
System.print(a.hasAttribute("nonexistent")) // expect: false
a.setAttribute("title", "Example Link")
System.print(a.getAttribute("title")) // expect: Example Link
System.print(a.hasAttribute("title")) // expect: true
a.removeAttribute("target")
System.print(a.hasAttribute("target")) // expect: false
var attrs = a.attributes
System.print(attrs["href"]) // expect: http://example.com
System.print(attrs["title"]) // expect: Example Link

28
test/html/element_children.wren vendored Normal file
View File

@ -0,0 +1,28 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div><p>One</p><span>Two</span><a>Three</a></div>")
var div = doc.querySelector("div")
System.print(div.children.count) // expect: 3
System.print(div.children[0].tagName) // expect: P
System.print(div.children[1].tagName) // expect: SPAN
System.print(div.children[2].tagName) // expect: A
System.print(div.childNodes.count >= 3) // expect: true
System.print(div.firstChild != null) // expect: true
System.print(div.lastChild != null) // expect: true
System.print(div.firstElementChild.tagName) // expect: P
System.print(div.lastElementChild.tagName) // expect: A
var empty = doc.createElement("div")
System.print(empty.children.count) // expect: 0
System.print(empty.firstChild == null) // expect: true
System.print(empty.lastChild == null) // expect: true
System.print(empty.firstElementChild == null) // expect: true
System.print(empty.lastElementChild == null) // expect: true

23
test/html/element_classlist.wren vendored Normal file
View File

@ -0,0 +1,23 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div class='a b c'>Content</div>")
var div = doc.querySelector("div")
var list = div.classList
System.print(list.count) // expect: 3
System.print(list[0]) // expect: a
System.print(list[1]) // expect: b
System.print(list[2]) // expect: c
var noClass = Document.parse("<span>No Class</span>")
var span = noClass.querySelector("span")
var emptyList = span.classList
System.print(emptyList.count) // expect: 0
var single = Document.parse("<p class='only'>Text</p>")
var p = single.querySelector("p")
System.print(p.classList.count) // expect: 1
System.print(p.classList[0]) // expect: only

22
test/html/element_classname.wren vendored Normal file
View File

@ -0,0 +1,22 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div class='container main'>Content</div>")
var div = doc.querySelector("div")
System.print(div.className) // expect: container main
div.className = "box"
System.print(div.className) // expect: box
var noClass = Document.parse("<span>No Class</span>")
var span = noClass.querySelector("span")
System.print(span.className) // expect:
span.className = "highlight"
System.print(span.className) // expect: highlight
div.className = "one two three"
System.print(div.className) // expect: one two three

25
test/html/element_clonenode.wren vendored Normal file
View File

@ -0,0 +1,25 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div id='original' class='box'><p>Child</p><span>Another</span></div>")
var original = doc.getElementById("original")
var shallow = original.cloneNode(false)
System.print(shallow.tagName) // expect: DIV
System.print(shallow.id) // expect: original
System.print(shallow.className) // expect: box
System.print(shallow.children.count) // expect: 0
var deep = original.cloneNode(true)
System.print(deep.tagName) // expect: DIV
System.print(deep.id) // expect: original
System.print(deep.children.count) // expect: 2
System.print(deep.firstChild.tagName) // expect: P
System.print(deep.lastChild.tagName) // expect: SPAN
deep.id = "cloned"
System.print(original.id) // expect: original
System.print(deep.id) // expect: cloned

27
test/html/element_closest.wren vendored Normal file
View File

@ -0,0 +1,27 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div id='outer' class='wrapper'><section id='inner'><p id='text'>Content</p></section></div>")
var p = doc.getElementById("text")
var closestSection = p.closest("section")
System.print(closestSection != null) // expect: true
System.print(closestSection.id) // expect: inner
var closestDiv = p.closest("div")
System.print(closestDiv != null) // expect: true
System.print(closestDiv.id) // expect: outer
var closestWrapper = p.closest(".wrapper")
System.print(closestWrapper != null) // expect: true
System.print(closestWrapper.id) // expect: outer
var closestSelf = p.closest("p")
System.print(closestSelf != null) // expect: true
System.print(closestSelf.id) // expect: text
var notFound = p.closest("article")
System.print(notFound == null) // expect: true

23
test/html/element_contains.wren vendored Normal file
View File

@ -0,0 +1,23 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div id='outer'><section id='inner'><p id='para'>Text</p></section></div>")
var outer = doc.getElementById("outer")
var inner = doc.getElementById("inner")
var para = doc.getElementById("para")
System.print(outer.contains(inner)) // expect: true
System.print(outer.contains(para)) // expect: true
System.print(inner.contains(para)) // expect: true
System.print(inner.contains(outer)) // expect: false
System.print(para.contains(outer)) // expect: false
System.print(para.contains(inner)) // expect: false
System.print(outer.contains(outer)) // expect: true
var unrelated = doc.createElement("span")
System.print(outer.contains(unrelated)) // expect: false

23
test/html/element_dataset.wren vendored Normal file
View File

@ -0,0 +1,23 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div data-id='123' data-name='test' data-value='hello'>Content</div>")
var div = doc.querySelector("div")
var dataset = div.dataset
System.print(dataset["id"]) // expect: 123
System.print(dataset["name"]) // expect: test
System.print(dataset["value"]) // expect: hello
var noData = Document.parse("<span>No data</span>")
var span = noData.querySelector("span")
var emptyDataset = span.dataset
System.print(emptyDataset.count) // expect: 0
var partial = Document.parse("<p data-single='only'>Text</p>")
var p = partial.querySelector("p")
System.print(p.dataset["single"]) // expect: only
System.print(p.dataset.count) // expect: 1

View File

@ -0,0 +1,18 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div id='root'><p class='item'>A</p><span class='item'>B</span><section><a class='item'>C</a></section></div>")
var root = doc.getElementById("root")
var items = root.getElementsByClassName("item")
System.print(items.count) // expect: 3
var section = doc.querySelector("section")
var sectionItems = section.getElementsByClassName("item")
System.print(sectionItems.count) // expect: 1
System.print(sectionItems[0].tagName) // expect: A
var none = section.getElementsByClassName("nonexistent")
System.print(none.count) // expect: 0

View File

@ -0,0 +1,21 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div id='root'><p>A</p><span>B</span><section><p>C</p></section></div>")
var root = doc.getElementById("root")
var ps = root.getElementsByTagName("p")
System.print(ps.count) // expect: 2
var section = doc.querySelector("section")
var sectionPs = section.getElementsByTagName("p")
System.print(sectionPs.count) // expect: 1
System.print(sectionPs[0].textContent) // expect: C
var spans = root.getElementsByTagName("span")
System.print(spans.count) // expect: 1
var none = section.getElementsByTagName("span")
System.print(none.count) // expect: 0

21
test/html/element_haschildnodes.wren vendored Normal file
View File

@ -0,0 +1,21 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div><p>Text</p></div>")
var div = doc.querySelector("div")
System.print(div.hasChildNodes()) // expect: true
var p = doc.querySelector("p")
System.print(p.hasChildNodes()) // expect: true
var empty = doc.createElement("span")
System.print(empty.hasChildNodes()) // expect: false
empty.textContent = "Now has content"
System.print(empty.hasChildNodes()) // expect: true
var emptyDiv = Document.parse("<div></div>")
System.print(emptyDiv.querySelector("div").hasChildNodes()) // expect: false

22
test/html/element_id.wren vendored Normal file
View File

@ -0,0 +1,22 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div id='myDiv'>Content</div>")
var div = doc.querySelector("div")
System.print(div.id) // expect: myDiv
div.id = "newId"
System.print(div.id) // expect: newId
var found = doc.getElementById("newId")
System.print(found != null) // expect: true
var noId = Document.parse("<span>No ID</span>")
var span = noId.querySelector("span")
System.print(span.id) // expect:
span.id = "spanId"
System.print(span.id) // expect: spanId

23
test/html/element_innerhtml.wren vendored Normal file
View File

@ -0,0 +1,23 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div><p>Hello</p><span>World</span></div>")
var div = doc.querySelector("div")
var html = div.innerHTML
System.print(html.contains("<p>")) // expect: true
System.print(html.contains("Hello")) // expect: true
System.print(html.contains("<span>")) // expect: true
div.innerHTML = "<a>Link</a>"
System.print(div.innerHTML.contains("<a>")) // expect: true
System.print(div.children.count) // expect: 1
System.print(div.firstChild.tagName) // expect: A
var empty = doc.createElement("div")
System.print(empty.innerHTML) // expect:
empty.innerHTML = "Plain text"
System.print(empty.textContent) // expect: Plain text

25
test/html/element_insertbefore.wren vendored Normal file
View File

@ -0,0 +1,25 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div id='container'><span id='ref'>Reference</span></div>")
var container = doc.getElementById("container")
var ref = doc.getElementById("ref")
var p = doc.createElement("p")
p.textContent = "Before"
container.insertBefore(p, ref)
System.print(container.children.count) // expect: 2
System.print(container.firstChild.tagName) // expect: P
System.print(container.firstChild.textContent) // expect: Before
System.print(container.lastChild.tagName) // expect: SPAN
var a = doc.createElement("a")
a.textContent = "Link"
container.insertBefore(a, ref)
System.print(container.children.count) // expect: 3
System.print(container.children[1].tagName) // expect: A

23
test/html/element_matches.wren vendored Normal file
View File

@ -0,0 +1,23 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div id='test' class='box container'><p>Text</p></div>")
var div = doc.querySelector("div")
System.print(div.matches("div")) // expect: true
System.print(div.matches("#test")) // expect: true
System.print(div.matches(".box")) // expect: true
System.print(div.matches(".container")) // expect: true
System.print(div.matches("div.box")) // expect: true
System.print(div.matches("div#test")) // expect: true
System.print(div.matches("span")) // expect: false
System.print(div.matches("#other")) // expect: false
System.print(div.matches(".other")) // expect: false
var p = doc.querySelector("p")
System.print(p.matches("p")) // expect: true
System.print(p.matches("div")) // expect: false

24
test/html/element_nodeprops.wren vendored Normal file
View File

@ -0,0 +1,24 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div id='test'>Content</div>")
var div = doc.querySelector("div")
System.print(div.nodeType) // expect: 1
System.print(div.nodeName) // expect: DIV
System.print(div.nodeValue == null) // expect: true
var text = doc.createTextNode("Text content")
System.print(text.nodeType) // expect: 3
System.print(text.nodeName) // expect: #text
System.print(text.nodeValue) // expect: Text content
text.nodeValue = "Changed"
System.print(text.nodeValue) // expect: Changed
var p = doc.createElement("p")
System.print(p.nodeType) // expect: 1
System.print(p.nodeName) // expect: P

24
test/html/element_normalize.wren vendored Normal file
View File

@ -0,0 +1,24 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div id='container'></div>")
var container = doc.getElementById("container")
container.appendChild(doc.createTextNode("Hello "))
container.appendChild(doc.createTextNode("World"))
container.appendChild(doc.createTextNode("!"))
System.print(container.childNodes.count) // expect: 3
container.normalize()
System.print(container.childNodes.count) // expect: 1
System.print(container.textContent) // expect: Hello World!
var simple = Document.parse("<p>Simple text</p>")
var p = simple.querySelector("p")
var before = p.childNodes.count
p.normalize()
System.print(p.childNodes.count == before) // expect: true

22
test/html/element_outerhtml.wren vendored Normal file
View File

@ -0,0 +1,22 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div id='test'><p>Hello</p></div>")
var div = doc.querySelector("div")
var html = div.outerHTML
System.print(html.contains("<div")) // expect: true
System.print(html.contains("id=\"test\"") || html.contains("id='test'")) // expect: true
System.print(html.contains("</div>")) // expect: true
System.print(html.contains("<p>")) // expect: true
var p = doc.querySelector("p")
System.print(p.outerHTML.contains("<p>")) // expect: true
System.print(p.outerHTML.contains("Hello")) // expect: true
System.print(p.outerHTML.contains("</p>")) // expect: true
var selfClose = Document.parse("<br/>")
var br = selfClose.querySelector("br")
System.print(br != null) // expect: true

24
test/html/element_parent.wren vendored Normal file
View File

@ -0,0 +1,24 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div id='parent'><p id='child'>Text</p></div>")
var child = doc.getElementById("child")
var parent = child.parentNode
System.print(parent != null) // expect: true
System.print(parent.id) // expect: parent
var parentElem = child.parentElement
System.print(parentElem != null) // expect: true
System.print(parentElem.tagName) // expect: DIV
var div = doc.getElementById("parent")
var divParent = div.parentNode
System.print(divParent != null) // expect: true
var nested = Document.parse("<div><span><a>Link</a></span></div>")
var a = nested.querySelector("a")
System.print(a.parentNode.tagName) // expect: SPAN
System.print(a.parentElement.tagName) // expect: SPAN

25
test/html/element_queryselector.wren vendored Normal file
View File

@ -0,0 +1,25 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div id='root'><section><p class='text'>One</p></section><article><p class='text'>Two</p></article></div>")
var root = doc.getElementById("root")
var section = doc.querySelector("section")
var article = doc.querySelector("article")
var fromRoot = root.querySelector("p")
System.print(fromRoot.textContent) // expect: One
var fromSection = section.querySelector(".text")
System.print(fromSection.textContent) // expect: One
var fromArticle = article.querySelector(".text")
System.print(fromArticle.textContent) // expect: Two
var notFound = section.querySelector("article")
System.print(notFound == null) // expect: true
var nested = root.querySelector("section p")
System.print(nested.textContent) // expect: One

24
test/html/element_queryselectorall.wren vendored Normal file
View File

@ -0,0 +1,24 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div id='root'><section><p>A</p><p>B</p></section><article><p>C</p></article></div>")
var root = doc.getElementById("root")
var all = root.querySelectorAll("p")
System.print(all.count) // expect: 3
var section = doc.querySelector("section")
var sectionPs = section.querySelectorAll("p")
System.print(sectionPs.count) // expect: 2
System.print(sectionPs[0].textContent) // expect: A
System.print(sectionPs[1].textContent) // expect: B
var article = doc.querySelector("article")
var articlePs = article.querySelectorAll("p")
System.print(articlePs.count) // expect: 1
System.print(articlePs[0].textContent) // expect: C
var none = section.querySelectorAll("article")
System.print(none.count) // expect: 0

23
test/html/element_remove.wren vendored Normal file
View File

@ -0,0 +1,23 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div id='container'><p id='para'>Text</p><span>Other</span></div>")
var container = doc.getElementById("container")
System.print(container.children.count) // expect: 2
var para = doc.getElementById("para")
para.remove()
System.print(container.children.count) // expect: 1
System.print(container.firstChild.tagName) // expect: SPAN
var doc2 = Document.parse("<ul><li>A</li><li>B</li><li>C</li></ul>")
var ul = doc2.querySelector("ul")
var items = doc2.querySelectorAll("li")
System.print(items.count) // expect: 3
items[1].remove()
System.print(ul.children.count) // expect: 2

21
test/html/element_removechild.wren vendored Normal file
View File

@ -0,0 +1,21 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div id='container'><p>One</p><span>Two</span><a>Three</a></div>")
var container = doc.getElementById("container")
System.print(container.children.count) // expect: 3
var span = doc.querySelector("span")
container.removeChild(span)
System.print(container.children.count) // expect: 2
System.print(container.firstChild.tagName) // expect: P
System.print(container.lastChild.tagName) // expect: A
var p = doc.querySelector("p")
container.removeChild(p)
System.print(container.children.count) // expect: 1
System.print(container.firstChild.tagName) // expect: A

26
test/html/element_replacechild.wren vendored Normal file
View File

@ -0,0 +1,26 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div id='container'><p id='old'>Old</p></div>")
var container = doc.getElementById("container")
var old = doc.getElementById("old")
var newElem = doc.createElement("span")
newElem.textContent = "New"
container.replaceChild(newElem, old)
System.print(container.children.count) // expect: 1
System.print(container.firstChild.tagName) // expect: SPAN
System.print(container.firstChild.textContent) // expect: New
var doc2 = Document.parse("<div><a>First</a><b>Second</b></div>")
var div = doc2.querySelector("div")
var a = doc2.querySelector("a")
var replacement = doc2.createElement("em")
replacement.textContent = "Replaced"
div.replaceChild(replacement, a)
System.print(div.firstChild.tagName) // expect: EM

22
test/html/element_siblings.wren vendored Normal file
View File

@ -0,0 +1,22 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div><p>One</p><span>Two</span><a>Three</a></div>")
var span = doc.querySelector("span")
System.print(span.previousSibling != null) // expect: true
System.print(span.nextSibling != null) // expect: true
System.print(span.previousElementSibling.tagName) // expect: P
System.print(span.nextElementSibling.tagName) // expect: A
var p = doc.querySelector("p")
System.print(p.previousElementSibling == null) // expect: true
System.print(p.nextElementSibling.tagName) // expect: SPAN
var a = doc.querySelector("a")
System.print(a.previousElementSibling.tagName) // expect: SPAN
System.print(a.nextElementSibling == null) // expect: true

18
test/html/element_tagname.wren vendored Normal file
View File

@ -0,0 +1,18 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div><p>Para</p><span>Span</span><a>Link</a></div>")
var div = doc.querySelector("div")
System.print(div.tagName) // expect: DIV
var p = doc.querySelector("p")
System.print(p.tagName) // expect: P
var span = doc.querySelector("span")
System.print(span.tagName) // expect: SPAN
var a = doc.querySelector("a")
System.print(a.tagName) // expect: A

25
test/html/element_textcontent.wren vendored Normal file
View File

@ -0,0 +1,25 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div>Hello <span>World</span></div>")
var div = doc.querySelector("div")
System.print(div.textContent.contains("Hello")) // expect: true
System.print(div.textContent.contains("World")) // expect: true
var span = doc.querySelector("span")
System.print(span.textContent) // expect: World
span.textContent = "Universe"
System.print(span.textContent) // expect: Universe
var empty = doc.createElement("p")
System.print(empty.textContent) // expect:
empty.textContent = "New content"
System.print(empty.textContent) // expect: New content
var nested = Document.parse("<div><p><span>Deep</span></p></div>")
System.print(nested.querySelector("div").textContent.contains("Deep")) // expect: true

19
test/html/nodelist_count.wren vendored Normal file
View File

@ -0,0 +1,19 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div><p>A</p><p>B</p><p>C</p></div>")
var list = doc.querySelectorAll("p")
System.print(list.count) // expect: 3
var empty = doc.querySelectorAll(".nonexistent")
System.print(empty.count) // expect: 0
var single = doc.querySelectorAll("div")
System.print(single.count) // expect: 1
var many = Document.parse("<ul><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li></ul>")
var items = many.querySelectorAll("li")
System.print(items.count) // expect: 5

20
test/html/nodelist_foreach.wren vendored Normal file
View File

@ -0,0 +1,20 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div><p>A</p><p>B</p><p>C</p></div>")
var list = doc.querySelectorAll("p")
var count = 0
var texts = []
list.forEach {|elem|
count = count + 1
texts.add(elem.textContent)
}
System.print(count) // expect: 3
System.print(texts[0]) // expect: A
System.print(texts[1]) // expect: B
System.print(texts[2]) // expect: C

19
test/html/nodelist_item.wren vendored Normal file
View File

@ -0,0 +1,19 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div><p>A</p><p>B</p><p>C</p></div>")
var list = doc.querySelectorAll("p")
System.print(list.item(0).textContent) // expect: A
System.print(list.item(1).textContent) // expect: B
System.print(list.item(2).textContent) // expect: C
System.print(list.item(0).tagName) // expect: P
System.print(list.item(1).tagName) // expect: P
System.print(list.item(2).tagName) // expect: P
var single = doc.querySelectorAll("div")
System.print(single.item(0).tagName) // expect: DIV

22
test/html/nodelist_subscript.wren vendored Normal file
View File

@ -0,0 +1,22 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div><p>A</p><p>B</p><p>C</p></div>")
var list = doc.querySelectorAll("p")
System.print(list[0].textContent) // expect: A
System.print(list[1].textContent) // expect: B
System.print(list[2].textContent) // expect: C
System.print(list[0].tagName) // expect: P
System.print(list[1].tagName) // expect: P
System.print(list[2].tagName) // expect: P
var mixed = Document.parse("<div><span>X</span><a>Y</a><em>Z</em></div>")
var children = mixed.querySelector("div").children
System.print(children[0].tagName) // expect: SPAN
System.print(children[1].tagName) // expect: A
System.print(children[2].tagName) // expect: EM

20
test/html/nodelist_tolist.wren vendored Normal file
View File

@ -0,0 +1,20 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div><p>A</p><p>B</p><p>C</p></div>")
var nodeList = doc.querySelectorAll("p")
var list = nodeList.toList
System.print(list.count) // expect: 3
System.print(list[0].textContent) // expect: A
System.print(list[1].textContent) // expect: B
System.print(list[2].textContent) // expect: C
System.print(list is List) // expect: true
var empty = doc.querySelectorAll(".none").toList
System.print(empty.count) // expect: 0
System.print(empty is List) // expect: true

19
test/html/selectors_adjacent.wren vendored Normal file
View File

@ -0,0 +1,19 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div><p>Para</p><span>Span</span><a>Anchor</a></div>")
System.print(doc.querySelector("p + span") != null) // expect: true
System.print(doc.querySelector("p + span").textContent) // expect: Span
System.print(doc.querySelector("span + a") != null) // expect: true
System.print(doc.querySelector("span + a").textContent) // expect: Anchor
System.print(doc.querySelector("p + a") == null) // expect: true
System.print(doc.querySelector("a + p") == null) // expect: true
var multi = Document.parse("<div><p>A</p><p>B</p><p>C</p></div>")
System.print(multi.querySelectorAll("p + p").count) // expect: 2

21
test/html/selectors_attribute.wren vendored Normal file
View File

@ -0,0 +1,21 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<a href='http://example.com' target='_blank' data-id='123'>Link</a>")
System.print(doc.querySelector("[href]") != null) // expect: true
System.print(doc.querySelector("[target]") != null) // expect: true
System.print(doc.querySelector("[data-id]") != null) // expect: true
System.print(doc.querySelector("[nonexistent]") == null) // expect: true
System.print(doc.querySelector("[href='http://example.com']") != null) // expect: true
System.print(doc.querySelector("[target='_blank']") != null) // expect: true
System.print(doc.querySelector("[data-id='123']") != null) // expect: true
System.print(doc.querySelector("[href='wrong']") == null) // expect: true
System.print(doc.querySelector("a[href]") != null) // expect: true
System.print(doc.querySelector("a[target='_blank']") != null) // expect: true

17
test/html/selectors_child.wren vendored Normal file
View File

@ -0,0 +1,17 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div><p>Direct</p><span><p>Nested</p></span></div>")
System.print(doc.querySelector("div > p") != null) // expect: true
System.print(doc.querySelector("div > p").textContent) // expect: Direct
System.print(doc.querySelector("span > p") != null) // expect: true
System.print(doc.querySelector("span > p").textContent) // expect: Nested
System.print(doc.querySelectorAll("div > p").count) // expect: 1
var nested = Document.parse("<ul><li>A</li><li><ul><li>B</li></ul></li></ul>")
System.print(nested.querySelectorAll("ul > li").count) // expect: 3

20
test/html/selectors_class.wren vendored Normal file
View File

@ -0,0 +1,20 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div class='box'><p class='text highlight'>Text</p></div>")
System.print(doc.querySelector(".box") != null) // expect: true
System.print(doc.querySelector(".box").tagName) // expect: DIV
System.print(doc.querySelector(".text") != null) // expect: true
System.print(doc.querySelector(".highlight") != null) // expect: true
System.print(doc.querySelector(".nonexistent") == null) // expect: true
System.print(doc.querySelector("div.box") != null) // expect: true
System.print(doc.querySelector("p.text") != null) // expect: true
System.print(doc.querySelector("p.highlight") != null) // expect: true
System.print(doc.querySelector(".text.highlight") != null) // expect: true

19
test/html/selectors_combined.wren vendored Normal file
View File

@ -0,0 +1,19 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div id='main' class='container'><p class='text'>Content</p></div>")
System.print(doc.querySelector("div#main.container") != null) // expect: true
System.print(doc.querySelector("#main.container") != null) // expect: true
System.print(doc.querySelector("div.container#main") != null) // expect: true
System.print(doc.querySelector("div#main.container p.text") != null) // expect: true
System.print(doc.querySelector("#main > p.text") != null) // expect: true
System.print(doc.querySelector("div#main.wrong") == null) // expect: true
System.print(doc.querySelector("span#main.container") == null) // expect: true
var complex = Document.parse("<form id='login'><input type='text' class='field' name='user'/></form>")
System.print(complex.querySelector("form#login input.field[type='text']") != null) // expect: true

17
test/html/selectors_descendant.wren vendored Normal file
View File

@ -0,0 +1,17 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div><section><p>Deep</p></section></div>")
System.print(doc.querySelector("div p") != null) // expect: true
System.print(doc.querySelector("div section") != null) // expect: true
System.print(doc.querySelector("section p") != null) // expect: true
System.print(doc.querySelector("div section p") != null) // expect: true
System.print(doc.querySelector("div span") == null) // expect: true
var multi = Document.parse("<div><p>A</p><span><p>B</p></span></div>")
System.print(multi.querySelectorAll("div p").count) // expect: 2
System.print(multi.querySelectorAll("span p").count) // expect: 1

View File

@ -0,0 +1,18 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div><p>Para</p><span>Span</span><a>Anchor</a><em>Em</em></div>")
System.print(doc.querySelector("p ~ span") != null) // expect: true
System.print(doc.querySelector("p ~ a") != null) // expect: true
System.print(doc.querySelector("p ~ em") != null) // expect: true
System.print(doc.querySelector("span ~ a") != null) // expect: true
System.print(doc.querySelector("span ~ em") != null) // expect: true
System.print(doc.querySelector("a ~ p") == null) // expect: true
System.print(doc.querySelector("em ~ p") == null) // expect: true
System.print(doc.querySelectorAll("p ~ *").count) // expect: 3

18
test/html/selectors_id.wren vendored Normal file
View File

@ -0,0 +1,18 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div id='first'><p id='second'>Text</p></div>")
System.print(doc.querySelector("#first") != null) // expect: true
System.print(doc.querySelector("#first").tagName) // expect: DIV
System.print(doc.querySelector("#second") != null) // expect: true
System.print(doc.querySelector("#second").tagName) // expect: P
System.print(doc.querySelector("#nonexistent") == null) // expect: true
System.print(doc.querySelector("div#first") != null) // expect: true
System.print(doc.querySelector("p#second") != null) // expect: true
System.print(doc.querySelector("span#first") == null) // expect: true

View File

@ -0,0 +1,15 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<ul><li>A</li><li>B</li><li>C</li></ul>")
System.print(doc.querySelector("li:first-child") != null) // expect: true
System.print(doc.querySelector("li:first-child").textContent) // expect: A
var multi = Document.parse("<div><p>First</p><span>Second</span></div><div><a>Third</a></div>")
System.print(multi.querySelectorAll(":first-child").count >= 2) // expect: true
var nested = Document.parse("<div><span><em>Nested First</em></span></div>")
System.print(nested.querySelector("em:first-child") != null) // expect: true

View File

@ -0,0 +1,15 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<ul><li>A</li><li>B</li><li>C</li></ul>")
System.print(doc.querySelector("li:last-child") != null) // expect: true
System.print(doc.querySelector("li:last-child").textContent) // expect: C
var multi = Document.parse("<div><p>First</p><span>Last</span></div>")
System.print(multi.querySelector("span:last-child") != null) // expect: true
System.print(multi.querySelector("span:last-child").textContent) // expect: Last
System.print(multi.querySelector("p:last-child") == null) // expect: true

View File

@ -0,0 +1,17 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<ul><li>A</li><li>B</li><li>C</li><li>D</li><li>E</li></ul>")
System.print(doc.querySelector("li:nth-child(1)") != null) // expect: true
System.print(doc.querySelector("li:nth-child(1)").textContent) // expect: A
System.print(doc.querySelector("li:nth-child(3)") != null) // expect: true
System.print(doc.querySelector("li:nth-child(3)").textContent) // expect: C
System.print(doc.querySelector("li:nth-child(5)") != null) // expect: true
System.print(doc.querySelector("li:nth-child(5)").textContent) // expect: E
System.print(doc.querySelector("li:nth-child(6)") == null) // expect: true

20
test/html/selectors_tag.wren vendored Normal file
View File

@ -0,0 +1,20 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div><p>Para</p><span>Span</span><a>Anchor</a></div>")
System.print(doc.querySelector("div") != null) // expect: true
System.print(doc.querySelector("p") != null) // expect: true
System.print(doc.querySelector("span") != null) // expect: true
System.print(doc.querySelector("a") != null) // expect: true
System.print(doc.querySelector("article") == null) // expect: true
System.print(doc.querySelector("section") == null) // expect: true
System.print(doc.querySelectorAll("div").count) // expect: 1
System.print(doc.querySelectorAll("p").count) // expect: 1
var nested = Document.parse("<div><div><div></div></div></div>")
System.print(nested.querySelectorAll("div").count) // expect: 3

21
test/html/serialization_basic.wren vendored Normal file
View File

@ -0,0 +1,21 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div id='test' class='box'>Content</div>")
var html = doc.outerHTML
System.print(html.contains("<div")) // expect: true
System.print(html.contains("</div>")) // expect: true
System.print(html.contains("Content")) // expect: true
var div = doc.querySelector("div")
var outer = div.outerHTML
System.print(outer.contains("<div")) // expect: true
System.print(outer.contains("id=")) // expect: true
System.print(outer.contains("class=")) // expect: true
var inner = div.innerHTML
System.print(inner.contains("Content")) // expect: true
System.print(inner.contains("<div") == false) // expect: true

21
test/html/serialization_entities.wren vendored Normal file
View File

@ -0,0 +1,21 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<p>&lt;tag&gt;</p>")
System.print(doc.querySelector("p").textContent) // expect: <tag>
var doc2 = Document.parse("<p>&amp;</p>")
System.print(doc2.querySelector("p").textContent) // expect: &
var doc3 = Document.parse("<p>&quot;quoted&quot;</p>")
System.print(doc3.querySelector("p").textContent) // expect: "quoted"
var doc4 = Document.parse("<p>&nbsp;</p>")
var text4 = doc4.querySelector("p").textContent
System.print(text4.count > 0) // expect: true
var doc5 = Document.parse("<p>&copy; 2024</p>")
var text5 = doc5.querySelector("p").textContent
System.print(text5.contains("2024")) // expect: true

View File

@ -0,0 +1,19 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div><br/><hr/><img src='test.jpg'/><input type='text'/></div>")
var div = doc.querySelector("div")
var html = div.innerHTML
System.print(html.contains("br")) // expect: true
System.print(html.contains("hr")) // expect: true
System.print(html.contains("img")) // expect: true
System.print(html.contains("input")) // expect: true
System.print(doc.querySelector("br") != null) // expect: true
System.print(doc.querySelector("hr") != null) // expect: true
System.print(doc.querySelector("img") != null) // expect: true
System.print(doc.querySelector("input") != null) // expect: true

20
test/html/textnode_clonenode.wren vendored Normal file
View File

@ -0,0 +1,20 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div></div>")
var original = doc.createTextNode("Original text")
System.print(original.textContent) // expect: Original text
var clone = original.cloneNode(true)
System.print(clone.textContent) // expect: Original text
System.print(clone.nodeType) // expect: 3
clone.textContent = "Cloned text"
System.print(original.textContent) // expect: Original text
System.print(clone.textContent) // expect: Cloned text
var shallowClone = original.cloneNode(false)
System.print(shallowClone.textContent) // expect: Original text

25
test/html/textnode_properties.wren vendored Normal file
View File

@ -0,0 +1,25 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div id='container'></div>")
var container = doc.getElementById("container")
var text = doc.createTextNode("Hello World")
System.print(text.textContent) // expect: Hello World
System.print(text.nodeType) // expect: 3
System.print(text.nodeName) // expect: #text
System.print(text.nodeValue) // expect: Hello World
text.textContent = "Changed content"
System.print(text.textContent) // expect: Changed content
text.nodeValue = "Another change"
System.print(text.nodeValue) // expect: Another change
System.print(text.textContent) // expect: Another change
container.appendChild(text)
System.print(text.parentNode != null) // expect: true
System.print(text.parentElement.tagName) // expect: DIV

20
test/html/textnode_remove.wren vendored Normal file
View File

@ -0,0 +1,20 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div id='container'>Text content</div>")
var container = doc.getElementById("container")
System.print(container.textContent.contains("Text content")) // expect: true
var textNodes = container.childNodes
System.print(textNodes.count >= 1) // expect: true
var text = doc.createTextNode("Extra text")
container.appendChild(text)
var before = container.childNodes.count
text.remove()
var after = container.childNodes.count
System.print(after < before) // expect: true

24
test/html/textnode_siblings.wren vendored Normal file
View File

@ -0,0 +1,24 @@
// retoor <retoor@molodetz.nl>
import "html" for Document
var doc = Document.parse("<div id='container'></div>")
var container = doc.getElementById("container")
var text1 = doc.createTextNode("First")
var text2 = doc.createTextNode("Second")
var text3 = doc.createTextNode("Third")
container.appendChild(text1)
container.appendChild(text2)
container.appendChild(text3)
System.print(text1.previousSibling == null) // expect: true
System.print(text1.nextSibling != null) // expect: true
System.print(text2.previousSibling != null) // expect: true
System.print(text2.nextSibling != null) // expect: true
System.print(text3.previousSibling != null) // expect: true
System.print(text3.nextSibling == null) // expect: true