mirror of
				https://github.com/lightpanda-io/browser.git
				synced 2025-10-30 15:41:48 +00:00 
			
		
		
		
	Merge pull request #394 from lightpanda-io/xmlserializer
implement XMLSerializer
This commit is contained in:
		| @@ -27,6 +27,7 @@ const XHR = @import("xhr/xhr.zig"); | |||||||
| const Storage = @import("storage/storage.zig"); | const Storage = @import("storage/storage.zig"); | ||||||
| const URL = @import("url/url.zig"); | const URL = @import("url/url.zig"); | ||||||
| const Iterators = @import("iterator/iterator.zig"); | const Iterators = @import("iterator/iterator.zig"); | ||||||
|  | const XMLSerializer = @import("xmlserializer/xmlserializer.zig"); | ||||||
|  |  | ||||||
| pub const HTMLDocument = @import("html/document.zig").HTMLDocument; | pub const HTMLDocument = @import("html/document.zig").HTMLDocument; | ||||||
|  |  | ||||||
| @@ -40,6 +41,7 @@ pub const Interfaces = generate.Tuple(.{ | |||||||
|     Storage.Interfaces, |     Storage.Interfaces, | ||||||
|     URL.Interfaces, |     URL.Interfaces, | ||||||
|     Iterators.Interfaces, |     Iterators.Interfaces, | ||||||
|  |     XMLSerializer.Interfaces, | ||||||
| }); | }); | ||||||
|  |  | ||||||
| pub const UserContext = @import("user_context.zig").UserContext; | pub const UserContext = @import("user_context.zig").UserContext; | ||||||
|   | |||||||
| @@ -25,25 +25,20 @@ const Walker = @import("../dom/walker.zig").WalkerChildren; | |||||||
| // writer must be a std.io.Writer | // writer must be a std.io.Writer | ||||||
| pub fn writeHTML(doc: *parser.Document, writer: anytype) !void { | pub fn writeHTML(doc: *parser.Document, writer: anytype) !void { | ||||||
|     try writer.writeAll("<!DOCTYPE html>\n"); |     try writer.writeAll("<!DOCTYPE html>\n"); | ||||||
|     try writeNode(parser.documentToNode(doc), writer); |     try writeChildren(parser.documentToNode(doc), writer); | ||||||
|     try writer.writeAll("\n"); |     try writer.writeAll("\n"); | ||||||
| } | } | ||||||
|  |  | ||||||
| // writer must be a std.io.Writer | pub fn writeNode(node: *parser.Node, writer: anytype) anyerror!void { | ||||||
| pub fn writeNode(root: *parser.Node, writer: anytype) !void { |     switch (try parser.nodeType(node)) { | ||||||
|     const walker = Walker{}; |  | ||||||
|     var next: ?*parser.Node = null; |  | ||||||
|     while (true) { |  | ||||||
|         next = try walker.get_next(root, next) orelse break; |  | ||||||
|         switch (try parser.nodeType(next.?)) { |  | ||||||
|         .element => { |         .element => { | ||||||
|             // open the tag |             // open the tag | ||||||
|                 const tag = try parser.nodeLocalName(next.?); |             const tag = try parser.nodeLocalName(node); | ||||||
|             try writer.writeAll("<"); |             try writer.writeAll("<"); | ||||||
|             try writer.writeAll(tag); |             try writer.writeAll(tag); | ||||||
|  |  | ||||||
|             // write the attributes |             // write the attributes | ||||||
|                 const map = try parser.nodeGetAttributes(next.?); |             const map = try parser.nodeGetAttributes(node); | ||||||
|             const ln = try parser.namedNodeMapGetLength(map); |             const ln = try parser.namedNodeMapGetLength(map); | ||||||
|             var i: u32 = 0; |             var i: u32 = 0; | ||||||
|             while (i < ln) { |             while (i < ln) { | ||||||
| @@ -59,11 +54,11 @@ pub fn writeNode(root: *parser.Node, writer: anytype) !void { | |||||||
|             try writer.writeAll(">"); |             try writer.writeAll(">"); | ||||||
|  |  | ||||||
|             // void elements can't have any content. |             // void elements can't have any content. | ||||||
|                 if (try isVoid(parser.nodeToElement(next.?))) continue; |             if (try isVoid(parser.nodeToElement(node))) return; | ||||||
|  |  | ||||||
|             // write the children |             // write the children | ||||||
|             // TODO avoid recursion |             // TODO avoid recursion | ||||||
|                 try writeNode(next.?, writer); |             try writeChildren(node, writer); | ||||||
|  |  | ||||||
|             // close the tag |             // close the tag | ||||||
|             try writer.writeAll("</"); |             try writer.writeAll("</"); | ||||||
| @@ -71,37 +66,46 @@ pub fn writeNode(root: *parser.Node, writer: anytype) !void { | |||||||
|             try writer.writeAll(">"); |             try writer.writeAll(">"); | ||||||
|         }, |         }, | ||||||
|         .text => { |         .text => { | ||||||
|                 const v = try parser.nodeValue(next.?) orelse continue; |             const v = try parser.nodeValue(node) orelse return; | ||||||
|             try writer.writeAll(v); |             try writer.writeAll(v); | ||||||
|         }, |         }, | ||||||
|         .cdata_section => { |         .cdata_section => { | ||||||
|                 const v = try parser.nodeValue(next.?) orelse continue; |             const v = try parser.nodeValue(node) orelse return; | ||||||
|             try writer.writeAll("<![CDATA["); |             try writer.writeAll("<![CDATA["); | ||||||
|             try writer.writeAll(v); |             try writer.writeAll(v); | ||||||
|             try writer.writeAll("]]>"); |             try writer.writeAll("]]>"); | ||||||
|         }, |         }, | ||||||
|         .comment => { |         .comment => { | ||||||
|                 const v = try parser.nodeValue(next.?) orelse continue; |             const v = try parser.nodeValue(node) orelse return; | ||||||
|             try writer.writeAll("<!--"); |             try writer.writeAll("<!--"); | ||||||
|             try writer.writeAll(v); |             try writer.writeAll(v); | ||||||
|             try writer.writeAll("-->"); |             try writer.writeAll("-->"); | ||||||
|         }, |         }, | ||||||
|         // TODO handle processing instruction dump |         // TODO handle processing instruction dump | ||||||
|             .processing_instruction => continue, |         .processing_instruction => return, | ||||||
|         // document fragment is outside of the main document DOM, so we |         // document fragment is outside of the main document DOM, so we | ||||||
|         // don't output it. |         // don't output it. | ||||||
|             .document_fragment => continue, |         .document_fragment => return, | ||||||
|         // document will never be called, but required for completeness. |         // document will never be called, but required for completeness. | ||||||
|             .document => continue, |         .document => return, | ||||||
|         // done globally instead, but required for completeness. |         // done globally instead, but required for completeness. | ||||||
|             .document_type => continue, |         .document_type => return, | ||||||
|         // deprecated |         // deprecated | ||||||
|             .attribute => continue, |         .attribute => return, | ||||||
|             .entity_reference => continue, |         .entity_reference => return, | ||||||
|             .entity => continue, |         .entity => return, | ||||||
|             .notation => continue, |         .notation => return, | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // writer must be a std.io.Writer | ||||||
|  | pub fn writeChildren(root: *parser.Node, writer: anytype) !void { | ||||||
|  |     const walker = Walker{}; | ||||||
|  |     var next: ?*parser.Node = null; | ||||||
|  |     while (true) { | ||||||
|  |         next = try walker.get_next(root, next) orelse break; | ||||||
|  |         try writeNode(next.?, writer); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| // area, base, br, col, embed, hr, img, input, link, meta, source, track, wbr | // area, base, br, col, embed, hr, img, input, link, meta, source, track, wbr | ||||||
|   | |||||||
| @@ -26,7 +26,7 @@ const checkCases = jsruntime.test_utils.checkCases; | |||||||
| const Variadic = jsruntime.Variadic; | const Variadic = jsruntime.Variadic; | ||||||
|  |  | ||||||
| const collection = @import("html_collection.zig"); | const collection = @import("html_collection.zig"); | ||||||
| const writeNode = @import("../browser/dump.zig").writeNode; | const dump = @import("../browser/dump.zig"); | ||||||
| const css = @import("css.zig"); | const css = @import("css.zig"); | ||||||
|  |  | ||||||
| const Node = @import("node.zig").Node; | const Node = @import("node.zig").Node; | ||||||
| @@ -102,7 +102,17 @@ pub const Element = struct { | |||||||
|         var buf = std.ArrayList(u8).init(alloc); |         var buf = std.ArrayList(u8).init(alloc); | ||||||
|         defer buf.deinit(); |         defer buf.deinit(); | ||||||
|  |  | ||||||
|         try writeNode(parser.elementToNode(self), buf.writer()); |         try dump.writeChildren(parser.elementToNode(self), buf.writer()); | ||||||
|  |         // TODO express the caller owned the slice. | ||||||
|  |         // https://github.com/lightpanda-io/jsruntime-lib/issues/195 | ||||||
|  |         return buf.toOwnedSlice(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn get_outerHTML(self: *parser.Element, alloc: std.mem.Allocator) ![]const u8 { | ||||||
|  |         var buf = std.ArrayList(u8).init(alloc); | ||||||
|  |         defer buf.deinit(); | ||||||
|  |  | ||||||
|  |         try dump.writeNode(parser.elementToNode(self), buf.writer()); | ||||||
|         // TODO express the caller owned the slice. |         // TODO express the caller owned the slice. | ||||||
|         // https://github.com/lightpanda-io/jsruntime-lib/issues/195 |         // https://github.com/lightpanda-io/jsruntime-lib/issues/195 | ||||||
|         return buf.toOwnedSlice(); |         return buf.toOwnedSlice(); | ||||||
| @@ -470,4 +480,9 @@ pub fn testExecFn( | |||||||
|         .{ .src = "document.getElementById('para-empty').innerHTML.trim()", .ex = "<span id=\"para-empty-child\"></span>" }, |         .{ .src = "document.getElementById('para-empty').innerHTML.trim()", .ex = "<span id=\"para-empty-child\"></span>" }, | ||||||
|     }; |     }; | ||||||
|     try checkCases(js_env, &innerHTML); |     try checkCases(js_env, &innerHTML); | ||||||
|  |  | ||||||
|  |     var outerHTML = [_]Case{ | ||||||
|  |         .{ .src = "document.getElementById('para').outerHTML", .ex = "<p id=\"para\"> And</p>" }, | ||||||
|  |     }; | ||||||
|  |     try checkCases(js_env, &outerHTML); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -147,6 +147,7 @@ fn testsAllExecFn( | |||||||
|         @import("html/navigator.zig").testExecFn, |         @import("html/navigator.zig").testExecFn, | ||||||
|         @import("html/history.zig").testExecFn, |         @import("html/history.zig").testExecFn, | ||||||
|         @import("html/location.zig").testExecFn, |         @import("html/location.zig").testExecFn, | ||||||
|  |         @import("xmlserializer/xmlserializer.zig").testExecFn, | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     inline for (testFns) |testFn| { |     inline for (testFns) |testFn| { | ||||||
|   | |||||||
							
								
								
									
										72
									
								
								src/xmlserializer/xmlserializer.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								src/xmlserializer/xmlserializer.zig
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | |||||||
|  | // Copyright (C) 2023-2025  Lightpanda (Selecy SAS) | ||||||
|  | // | ||||||
|  | // Francis Bouvier <francis@lightpanda.io> | ||||||
|  | // Pierre Tachoire <pierre@lightpanda.io> | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU Affero General Public License as | ||||||
|  | // published by the Free Software Foundation, either version 3 of the | ||||||
|  | // License, or (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU Affero General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU Affero General Public License | ||||||
|  | // along with this program.  If not, see <https://www.gnu.org/licenses/>. | ||||||
|  | // | ||||||
|  | const std = @import("std"); | ||||||
|  |  | ||||||
|  | const jsruntime = @import("jsruntime"); | ||||||
|  | const Case = jsruntime.test_utils.Case; | ||||||
|  | const checkCases = jsruntime.test_utils.checkCases; | ||||||
|  | const generate = @import("../generate.zig"); | ||||||
|  |  | ||||||
|  | const DOMError = @import("netsurf").DOMError; | ||||||
|  |  | ||||||
|  | const parser = @import("netsurf"); | ||||||
|  | const dump = @import("../browser/dump.zig"); | ||||||
|  |  | ||||||
|  | pub const Interfaces = generate.Tuple(.{ | ||||||
|  |     XMLSerializer, | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | // https://w3c.github.io/DOM-Parsing/#dom-xmlserializer-constructor | ||||||
|  | pub const XMLSerializer = struct { | ||||||
|  |     pub const mem_guarantied = true; | ||||||
|  |  | ||||||
|  |     pub fn constructor() !XMLSerializer { | ||||||
|  |         return .{}; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn deinit(_: *XMLSerializer, _: std.mem.Allocator) void {} | ||||||
|  |  | ||||||
|  |     pub fn _serializeToString(_: XMLSerializer, alloc: std.mem.Allocator, root: *parser.Node) ![]const u8 { | ||||||
|  |         var buf = std.ArrayList(u8).init(alloc); | ||||||
|  |         defer buf.deinit(); | ||||||
|  |  | ||||||
|  |         if (try parser.nodeType(root) == .document) { | ||||||
|  |             try dump.writeHTML(@as(*parser.Document, @ptrCast(root)), buf.writer()); | ||||||
|  |         } else { | ||||||
|  |             try dump.writeNode(root, buf.writer()); | ||||||
|  |         } | ||||||
|  |         // TODO express the caller owned the slice. | ||||||
|  |         // https://github.com/lightpanda-io/jsruntime-lib/issues/195 | ||||||
|  |         return try buf.toOwnedSlice(); | ||||||
|  |     } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | // Tests | ||||||
|  | // ----- | ||||||
|  |  | ||||||
|  | pub fn testExecFn( | ||||||
|  |     _: std.mem.Allocator, | ||||||
|  |     js_env: *jsruntime.Env, | ||||||
|  | ) anyerror!void { | ||||||
|  |     var serializer = [_]Case{ | ||||||
|  |         .{ .src = "const s = new XMLSerializer()", .ex = "undefined" }, | ||||||
|  |         .{ .src = "s.serializeToString(document.getElementById('para'))", .ex = "<p id=\"para\"> And</p>" }, | ||||||
|  |     }; | ||||||
|  |     try checkCases(js_env, &serializer); | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user
	 Pierre Tachoire
					Pierre Tachoire