@@ -46,7 +46,8 @@ pub fn writeNode(node: *parser.Node, writer: anytype) anyerror!void {
4646 try writer .writeAll (" " );
4747 try writer .writeAll (try parser .attributeGetName (attr ));
4848 try writer .writeAll ("=\" " );
49- try writer .writeAll (try parser .attributeGetValue (attr ) orelse "" );
49+ const attribute_value = try parser .attributeGetValue (attr ) orelse "" ;
50+ try writeEscapedAttributeValue (writer , attribute_value );
5051 try writer .writeAll ("\" " );
5152 i += 1 ;
5253 }
@@ -67,7 +68,7 @@ pub fn writeNode(node: *parser.Node, writer: anytype) anyerror!void {
6768 },
6869 .text = > {
6970 const v = try parser .nodeValue (node ) orelse return ;
70- try writer . writeAll ( v );
71+ try writeEscapedTextNode ( writer , v );
7172 },
7273 .cdata_section = > {
7374 const v = try parser .nodeValue (node ) orelse return ;
@@ -119,18 +120,85 @@ fn isVoid(elem: *parser.Element) !bool {
119120 };
120121}
121122
123+ fn writeEscapedTextNode (writer : anytype , value : []const u8 ) ! void {
124+ var v = value ;
125+ while (v .len > 0 ) {
126+ const index = std .mem .indexOfAnyPos (u8 , v , 0 , &.{'&' , '<' , '>' }) orelse {
127+ return writer .writeAll (v );
128+ };
129+ try writer .writeAll (v [0.. index ]);
130+ switch (v [index ]) {
131+ '&' = > try writer .writeAll ("&" ),
132+ '<' = > try writer .writeAll ("<" ),
133+ '>' = > try writer .writeAll (">" ),
134+ else = > unreachable ,
135+ }
136+ v = v [index + 1.. ];
137+ }
138+ }
139+
140+ fn writeEscapedAttributeValue (writer : anytype , value : []const u8 ) ! void {
141+ var v = value ;
142+ while (v .len > 0 ) {
143+ const index = std .mem .indexOfAnyPos (u8 , v , 0 , &.{'&' , '<' , '>' , '"' }) orelse {
144+ return writer .writeAll (v );
145+ };
146+ try writer .writeAll (v [0.. index ]);
147+ switch (v [index ]) {
148+ '&' = > try writer .writeAll ("&" ),
149+ '<' = > try writer .writeAll ("<" ),
150+ '>' = > try writer .writeAll (">" ),
151+ '"' = > try writer .writeAll (""" ),
152+ else = > unreachable ,
153+ }
154+ v = v [index + 1.. ];
155+ }
156+ }
157+
158+ const testing = std .testing ;
122159test "dump.writeHTML" {
123- const out = try std .fs .openFileAbsolute ("/dev/null" , .{ .mode = .write_only });
124- defer out .close ();
160+ try testWriteHTML (
161+ "<div id=\" content\" >Over 9000!</div>" ,
162+ "<div id=\" content\" >Over 9000!</div>"
163+ );
164+
165+ try testWriteHTML (
166+ "<root><!-- a comment --></root>" ,
167+ "<root><!-- a comment --></root>"
168+ );
169+
170+ try testWriteHTML (
171+ "<p>< > &</p>" ,
172+ "<p>< > &</p>"
173+ );
174+
175+ try testWriteHTML (
176+ "<p id=\" "><&"''\" >wat?</p>" ,
177+ "<p id='\" ><&"'''>wat?</p>"
178+ );
179+
180+ try testWriteFullHTML (
181+ \\<!DOCTYPE html>
182+ \\<html><head><title>It's over what?</title><meta name="a" value="b">
183+ \\</head><body>9000</body></html>
184+ \\
185+ ,
186+ "<html><title>It's over what?</title><meta name=a value=\" b\" >\n <body>9000"
187+ );
188+ }
125189
126- const file = try std .fs .cwd ().openFile ("test.html" , .{});
127- defer file .close ();
190+ fn testWriteHTML (comptime expected : []const u8 , src : []const u8 ) ! void {
191+ return testWriteFullHTML ("<!DOCTYPE html>\n <html><head></head><body>" ++ expected ++ "</body></html>\n " , src );
192+ }
128193
129- const doc_html = try parser .documentHTMLParse (file .reader (), "UTF-8" );
130- // ignore close error
194+ fn testWriteFullHTML (comptime expected : []const u8 , src : []const u8 ) ! void {
195+ var buf = std .ArrayListUnmanaged (u8 ){};
196+ defer buf .deinit (testing .allocator );
197+
198+ const doc_html = try parser .documentHTMLParseFromStr (src );
131199 defer parser .documentHTMLClose (doc_html ) catch {};
132200
133201 const doc = parser .documentHTMLToDocument (doc_html );
134-
135- try writeHTML ( doc , out );
202+ try writeHTML ( doc , buf . writer ( testing . allocator ));
203+ try testing . expectEqualStrings ( expected , buf . items );
136204}
0 commit comments