DOMParser

This commit is contained in:
Karl Seguin
2025-11-13 20:57:17 +08:00
parent 6cf01631ad
commit 6742646e89
4 changed files with 184 additions and 2 deletions

View File

@@ -489,6 +489,7 @@ pub const JsApis = flattenTypes(&.{
@import("../webapi/DOMTreeWalker.zig"),
@import("../webapi/DOMNodeIterator.zig"),
@import("../webapi/DOMRect.zig"),
@import("../webapi/DOMParser.zig"),
@import("../webapi/NodeFilter.zig"),
@import("../webapi/Element.zig"),
@import("../webapi/element/DOMStringMap.zig"),

View File

@@ -0,0 +1,121 @@
<!DOCTYPE html>
<script src="testing.js"></script>
<!-- <script id=basic>
{
const parser = new DOMParser();
testing.expectEqual('object', typeof parser);
testing.expectEqual('function', typeof parser.parseFromString);
}
</script> -->
<script id=parseSimpleHTML>
{
const parser = new DOMParser();
const doc = parser.parseFromString('<div>Hello World</div>', 'text/html');
testing.expectEqual('object', typeof doc);
testing.expectEqual('[object HTMLDocument]', doc.toString());
const div = doc.querySelector('div');
testing.expectEqual('DIV', div.tagName);
testing.expectEqual('Hello World', div.textContent);
}
</script>
<!-- <script id=parseWithAttributes>
{
const parser = new DOMParser();
const doc = parser.parseFromString('<div id="test" class="foo">Content</div>', 'text/html');
const div = doc.querySelector('div');
testing.expectEqual('test', div.id);
testing.expectEqual('foo', div.className);
testing.expectEqual('Content', div.textContent);
}
</script>
<script id=parseMultipleElements>
{
const parser = new DOMParser();
const doc = parser.parseFromString('<div>First</div><span>Second</span>', 'text/html');
const div = doc.querySelector('div');
const span = doc.querySelector('span');
testing.expectEqual('DIV', div.tagName);
testing.expectEqual('First', div.textContent);
testing.expectEqual('SPAN', span.tagName);
testing.expectEqual('Second', span.textContent);
}
</script>
<script id=parseNestedElements>
{
const parser = new DOMParser();
const doc = parser.parseFromString('<div><p><span>Nested</span></p></div>', 'text/html');
const div = doc.querySelector('div');
const p = doc.querySelector('p');
const span = doc.querySelector('span');
testing.expectEqual('DIV', div.tagName);
testing.expectEqual('P', p.tagName);
testing.expectEqual('SPAN', span.tagName);
testing.expectEqual('Nested', span.textContent);
testing.expectEqual(p, div.firstChild);
testing.expectEqual(span, p.firstChild);
}
</script>
<script id=parseEmptyString>
{
const parser = new DOMParser();
const doc = parser.parseFromString('', 'text/html');
testing.expectEqual('object', typeof doc);
testing.expectEqual('[object HTMLDocument]', doc.toString());
}
</script>
<script id=parsedDocumentIsIndependent>
{
const parser = new DOMParser();
const doc = parser.parseFromString('<div id="parsed">Parsed</div>', 'text/html');
// The parsed document should be independent from the current document
const currentDiv = document.querySelector('div');
const parsedDiv = doc.querySelector('div');
testing.expectEqual(null, currentDiv);
testing.expectEqual('parsed', parsedDiv.id);
testing.expectEqual('Parsed', parsedDiv.textContent);
}
</script>
<script id=malformedHTMLDoesNotThrow>
{
const parser = new DOMParser();
// HTML parsers should be forgiving and not throw on malformed HTML
const doc1 = parser.parseFromString('<div><p>unclosed', 'text/html');
testing.expectEqual('object', typeof doc1);
const doc2 = parser.parseFromString('<<<invalid>>>', 'text/html');
testing.expectEqual('object', typeof doc2);
}
</script>
<script id=unsupportedMimeType>
{
const parser = new DOMParser();
// Should throw an error for unsupported MIME types
testing.withError((err) => {
testing.expectEqual('NotSupported', err.message);
}, () => {
parser.parseFromString('<div>test</div>', 'application/xml');
});
}
</script>
-->

View File

@@ -0,0 +1,57 @@
const std = @import("std");
const js = @import("../js/js.zig");
const Page = @import("../Page.zig");
const Document = @import("Document.zig");
const HTMLDocument = @import("HTMLDocument.zig");
const DOMParser = @This();
// @ZIGDOM support empty structs
_: u8 = 0,
pub fn init() DOMParser {
return .{};
}
pub fn parseFromString(self: *const DOMParser, html: []const u8, mime_type: []const u8, page: *Page) !*HTMLDocument {
_ = self;
// For now, only support text/html
if (!std.mem.eql(u8, mime_type, "text/html")) {
return error.NotSupported;
}
// Create a new HTMLDocument
const doc = try page._factory.document(HTMLDocument{
._proto = undefined,
});
// Parse HTML into the document
const Parser = @import("../parser/Parser.zig");
var parser = Parser.init(page.arena, doc.asNode(), page);
parser.parse(html);
if (parser.err) |pe| {
return pe.err;
}
return doc;
}
pub const JsApi = struct {
pub const bridge = js.Bridge(DOMParser);
pub const Meta = struct {
pub const name = "DOMParser";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
};
pub const constructor = bridge.constructor(DOMParser.init, .{});
pub const parseFromString = bridge.function(DOMParser.parseFromString, .{});
};
const testing = @import("../../testing.zig");
test "WebApi: DOMParser" {
try testing.htmlRunner("domparser.html", .{});
}

View File

@@ -122,8 +122,11 @@ pub fn querySelectorAll(self: *Document, input: []const u8, page: *Page) !*Selec
return Selector.querySelectorAll(self.asNode(), input, page);
}
pub fn className(_: *const Document) []const u8 {
return "[object Document]";
pub fn className(self: *const Document) []const u8 {
return switch (self._type) {
.generic => "[object Document]",
.html => "[object HTMLDocument]",
};
}
pub fn getImplementation(_: *const Document) DOMImplementation {