Skip to content

Commit b186497

Browse files
committed
implement XMLSerializer
1 parent 27f9963 commit b186497

File tree

5 files changed

+149
-70
lines changed

5 files changed

+149
-70
lines changed

src/apiweb.zig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ const XHR = @import("xhr/xhr.zig");
2727
const Storage = @import("storage/storage.zig");
2828
const URL = @import("url/url.zig");
2929
const Iterators = @import("iterator/iterator.zig");
30+
const XMLSerializer = @import("xmlserializer/xmlserializer.zig");
3031

3132
pub const HTMLDocument = @import("html/document.zig").HTMLDocument;
3233

@@ -40,6 +41,7 @@ pub const Interfaces = generate.Tuple(.{
4041
Storage.Interfaces,
4142
URL.Interfaces,
4243
Iterators.Interfaces,
44+
XMLSerializer.Interfaces,
4345
});
4446

4547
pub const UserContext = @import("user_context.zig").UserContext;

src/browser/dump.zig

Lines changed: 72 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -25,82 +25,86 @@ const Walker = @import("../dom/walker.zig").WalkerChildren;
2525
// writer must be a std.io.Writer
2626
pub fn writeHTML(doc: *parser.Document, writer: anytype) !void {
2727
try writer.writeAll("<!DOCTYPE html>\n");
28-
try writeNode(parser.documentToNode(doc), writer);
28+
try writeChildren(parser.documentToNode(doc), writer);
2929
try writer.writeAll("\n");
3030
}
3131

32+
pub fn writeNode(node: *parser.Node, writer: anytype) anyerror!void {
33+
switch (try parser.nodeType(node)) {
34+
.element => {
35+
// open the tag
36+
const tag = try parser.nodeLocalName(node);
37+
try writer.writeAll("<");
38+
try writer.writeAll(tag);
39+
40+
// write the attributes
41+
const map = try parser.nodeGetAttributes(node);
42+
const ln = try parser.namedNodeMapGetLength(map);
43+
var i: u32 = 0;
44+
while (i < ln) {
45+
const attr = try parser.namedNodeMapItem(map, i) orelse break;
46+
try writer.writeAll(" ");
47+
try writer.writeAll(try parser.attributeGetName(attr));
48+
try writer.writeAll("=\"");
49+
try writer.writeAll(try parser.attributeGetValue(attr) orelse "");
50+
try writer.writeAll("\"");
51+
i += 1;
52+
}
53+
54+
try writer.writeAll(">");
55+
56+
// void elements can't have any content.
57+
if (try isVoid(parser.nodeToElement(node))) return;
58+
59+
// write the children
60+
// TODO avoid recursion
61+
try writeChildren(node, writer);
62+
63+
// close the tag
64+
try writer.writeAll("</");
65+
try writer.writeAll(tag);
66+
try writer.writeAll(">");
67+
},
68+
.text => {
69+
const v = try parser.nodeValue(node) orelse return;
70+
try writer.writeAll(v);
71+
},
72+
.cdata_section => {
73+
const v = try parser.nodeValue(node) orelse return;
74+
try writer.writeAll("<![CDATA[");
75+
try writer.writeAll(v);
76+
try writer.writeAll("]]>");
77+
},
78+
.comment => {
79+
const v = try parser.nodeValue(node) orelse return;
80+
try writer.writeAll("<!--");
81+
try writer.writeAll(v);
82+
try writer.writeAll("-->");
83+
},
84+
// TODO handle processing instruction dump
85+
.processing_instruction => return,
86+
// document fragment is outside of the main document DOM, so we
87+
// don't output it.
88+
.document_fragment => return,
89+
// document will never be called, but required for completeness.
90+
.document => return,
91+
// done globally instead, but required for completeness.
92+
.document_type => return,
93+
// deprecated
94+
.attribute => return,
95+
.entity_reference => return,
96+
.entity => return,
97+
.notation => return,
98+
}
99+
}
100+
32101
// writer must be a std.io.Writer
33-
pub fn writeNode(root: *parser.Node, writer: anytype) !void {
102+
pub fn writeChildren(root: *parser.Node, writer: anytype) !void {
34103
const walker = Walker{};
35104
var next: ?*parser.Node = null;
36105
while (true) {
37106
next = try walker.get_next(root, next) orelse break;
38-
switch (try parser.nodeType(next.?)) {
39-
.element => {
40-
// open the tag
41-
const tag = try parser.nodeLocalName(next.?);
42-
try writer.writeAll("<");
43-
try writer.writeAll(tag);
44-
45-
// write the attributes
46-
const map = try parser.nodeGetAttributes(next.?);
47-
const ln = try parser.namedNodeMapGetLength(map);
48-
var i: u32 = 0;
49-
while (i < ln) {
50-
const attr = try parser.namedNodeMapItem(map, i) orelse break;
51-
try writer.writeAll(" ");
52-
try writer.writeAll(try parser.attributeGetName(attr));
53-
try writer.writeAll("=\"");
54-
try writer.writeAll(try parser.attributeGetValue(attr) orelse "");
55-
try writer.writeAll("\"");
56-
i += 1;
57-
}
58-
59-
try writer.writeAll(">");
60-
61-
// void elements can't have any content.
62-
if (try isVoid(parser.nodeToElement(next.?))) continue;
63-
64-
// write the children
65-
// TODO avoid recursion
66-
try writeNode(next.?, writer);
67-
68-
// close the tag
69-
try writer.writeAll("</");
70-
try writer.writeAll(tag);
71-
try writer.writeAll(">");
72-
},
73-
.text => {
74-
const v = try parser.nodeValue(next.?) orelse continue;
75-
try writer.writeAll(v);
76-
},
77-
.cdata_section => {
78-
const v = try parser.nodeValue(next.?) orelse continue;
79-
try writer.writeAll("<![CDATA[");
80-
try writer.writeAll(v);
81-
try writer.writeAll("]]>");
82-
},
83-
.comment => {
84-
const v = try parser.nodeValue(next.?) orelse continue;
85-
try writer.writeAll("<!--");
86-
try writer.writeAll(v);
87-
try writer.writeAll("-->");
88-
},
89-
// TODO handle processing instruction dump
90-
.processing_instruction => continue,
91-
// document fragment is outside of the main document DOM, so we
92-
// don't output it.
93-
.document_fragment => continue,
94-
// document will never be called, but required for completeness.
95-
.document => continue,
96-
// done globally instead, but required for completeness.
97-
.document_type => continue,
98-
// deprecated
99-
.attribute => continue,
100-
.entity_reference => continue,
101-
.entity => continue,
102-
.notation => continue,
103-
}
107+
try writeNode(next.?, writer);
104108
}
105109
}
106110

src/dom/element.zig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ const checkCases = jsruntime.test_utils.checkCases;
2626
const Variadic = jsruntime.Variadic;
2727

2828
const collection = @import("html_collection.zig");
29-
const writeNode = @import("../browser/dump.zig").writeNode;
29+
const writeChildren = @import("../browser/dump.zig").writeChildren;
3030
const css = @import("css.zig");
3131

3232
const Node = @import("node.zig").Node;
@@ -102,7 +102,7 @@ pub const Element = struct {
102102
var buf = std.ArrayList(u8).init(alloc);
103103
defer buf.deinit();
104104

105-
try writeNode(parser.elementToNode(self), buf.writer());
105+
try writeChildren(parser.elementToNode(self), buf.writer());
106106
// TODO express the caller owned the slice.
107107
// https://github.com/lightpanda-io/jsruntime-lib/issues/195
108108
return buf.toOwnedSlice();

src/run_tests.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ fn testsAllExecFn(
147147
@import("html/navigator.zig").testExecFn,
148148
@import("html/history.zig").testExecFn,
149149
@import("html/location.zig").testExecFn,
150+
@import("xmlserializer/xmlserializer.zig").testExecFn,
150151
};
151152

152153
inline for (testFns) |testFn| {
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
2+
//
3+
// Francis Bouvier <[email protected]>
4+
// Pierre Tachoire <[email protected]>
5+
//
6+
// This program is free software: you can redistribute it and/or modify
7+
// it under the terms of the GNU Affero General Public License as
8+
// published by the Free Software Foundation, either version 3 of the
9+
// License, or (at your option) any later version.
10+
//
11+
// This program is distributed in the hope that it will be useful,
12+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
// GNU Affero General Public License for more details.
15+
//
16+
// You should have received a copy of the GNU Affero General Public License
17+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
18+
//
19+
const std = @import("std");
20+
21+
const jsruntime = @import("jsruntime");
22+
const Case = jsruntime.test_utils.Case;
23+
const checkCases = jsruntime.test_utils.checkCases;
24+
const generate = @import("../generate.zig");
25+
26+
const DOMError = @import("netsurf").DOMError;
27+
28+
const parser = @import("netsurf");
29+
const dump = @import("../browser/dump.zig");
30+
31+
pub const Interfaces = generate.Tuple(.{
32+
XMLSerializer,
33+
});
34+
35+
// https://w3c.github.io/DOM-Parsing/#dom-xmlserializer-constructor
36+
pub const XMLSerializer = struct {
37+
pub const mem_guarantied = true;
38+
39+
pub fn constructor() !XMLSerializer {
40+
return .{};
41+
}
42+
43+
pub fn deinit(_: *XMLSerializer, _: std.mem.Allocator) void {}
44+
45+
pub fn _serializeToString(_: XMLSerializer, alloc: std.mem.Allocator, root: *parser.Node) ![]const u8 {
46+
var buf = std.ArrayList(u8).init(alloc);
47+
defer buf.deinit();
48+
49+
if (try parser.nodeType(root) == .document) {
50+
try dump.writeHTML(@as(*parser.Document, @ptrCast(root)), buf.writer());
51+
} else {
52+
try dump.writeNode(root, buf.writer());
53+
}
54+
// TODO express the caller owned the slice.
55+
// https://github.com/lightpanda-io/jsruntime-lib/issues/195
56+
return try buf.toOwnedSlice();
57+
}
58+
};
59+
60+
// Tests
61+
// -----
62+
63+
pub fn testExecFn(
64+
_: std.mem.Allocator,
65+
js_env: *jsruntime.Env,
66+
) anyerror!void {
67+
var serializer = [_]Case{
68+
.{ .src = "const s = new XMLSerializer()", .ex = "undefined" },
69+
.{ .src = "s.serializeToString(document.getElementById('para'))", .ex = "<p id=\"para\"> And</p>" },
70+
};
71+
try checkCases(js_env, &serializer);
72+
}

0 commit comments

Comments
 (0)