improve Element.innerText formatting

This commit is contained in:
Pierre Tachoire
2025-12-08 12:22:34 +01:00
parent e74d45d6c2
commit 38030c7d21
3 changed files with 48 additions and 16 deletions

View File

@@ -7,6 +7,9 @@
This is a <br> This is a <br>
text text
</div> </div>
<p id=d3><span>Hello</span> <span>World</span></p>
<p id=d4><span>Hello</span>
<span>World</span></p>
<script id=innerHTML> <script id=innerHTML>
const d1 = $('#d1'); const d1 = $('#d1');
@@ -170,7 +173,7 @@
testing.expectEqual('new content', d1.innerText); testing.expectEqual('new content', d1.innerText);
testing.expectEqual(null, $('#link2')); testing.expectEqual(null, $('#link2'));
// TODO innerText is not rendered correctly for now. testing.expectEqual("This is a\ntext", d2.innerText);
//testing.expectEqual("This is a\ntext", d2.innerText); testing.expectEqual("Hello World", $('#d3').innerText);
testing.expectEqual(" This is a \n text ", d2.innerText); testing.expectEqual("Hello World", $('#d4').innerText);
</script> </script>

View File

@@ -71,7 +71,8 @@ pub const RenderOpts = struct {
}; };
// Replace successives whitespaces with one withespace. // Replace successives whitespaces with one withespace.
// Trims left and right according to the options. // Trims left and right according to the options.
pub fn render(self: *const CData, writer: *std.io.Writer, opts: RenderOpts) !void { // Returns true if the string ends with a trimmed whitespace.
pub fn render(self: *const CData, writer: *std.io.Writer, opts: RenderOpts) !bool {
var start: usize = 0; var start: usize = 0;
var prev_w: ?bool = null; var prev_w: ?bool = null;
var is_w: bool = undefined; var is_w: bool = undefined;
@@ -110,11 +111,15 @@ pub fn render(self: *const CData, writer: *std.io.Writer, opts: RenderOpts) !voi
// If the string contains only whitespaces, don't write it. // If the string contains only whitespaces, don't write it.
if (start > 0 and opts.trim_right == false) { if (start > 0 and opts.trim_right == false) {
try writer.writeByte(' '); try writer.writeByte(' ');
} else {
return true;
} }
} else { } else {
// last chunk is non whitespaces. // last chunk is non whitespaces.
try writer.writeAll(s[start..]); try writer.writeAll(s[start..]);
} }
return false;
} }
pub fn setData(self: *CData, value: ?[]const u8, page: *Page) !void { pub fn setData(self: *CData, value: ?[]const u8, page: *Page) !void {
@@ -288,19 +293,20 @@ test "WebApi: CData.render" {
const TestCase = struct { const TestCase = struct {
value: []const u8, value: []const u8,
expected: []const u8, expected: []const u8,
result: bool = false,
opts: RenderOpts = .{}, opts: RenderOpts = .{},
}; };
const test_cases = [_]TestCase{ const test_cases = [_]TestCase{
.{ .value = " ", .expected = "" }, .{ .value = " ", .expected = "", .result = true },
.{ .value = " ", .expected = "", .opts = .{ .trim_left = false, .trim_right = false } }, .{ .value = " ", .expected = "", .opts = .{ .trim_left = false, .trim_right = false }, .result = true },
.{ .value = "foo bar", .expected = "foo bar" }, .{ .value = "foo bar", .expected = "foo bar" },
.{ .value = "foo bar", .expected = "foo bar" }, .{ .value = "foo bar", .expected = "foo bar" },
.{ .value = " foo bar", .expected = "foo bar" }, .{ .value = " foo bar", .expected = "foo bar" },
.{ .value = "foo bar ", .expected = "foo bar" }, .{ .value = "foo bar ", .expected = "foo bar", .result = true },
.{ .value = " foo bar ", .expected = "foo bar" }, .{ .value = " foo bar ", .expected = "foo bar", .result = true },
.{ .value = "foo\n\tbar", .expected = "foo bar" }, .{ .value = "foo\n\tbar", .expected = "foo bar" },
.{ .value = "\tfoo bar baz \t\n yeah\r\n", .expected = "foo bar baz yeah" }, .{ .value = "\tfoo bar baz \t\n yeah\r\n", .expected = "foo bar baz yeah", .result = true },
.{ .value = " foo bar", .expected = " foo bar", .opts = .{ .trim_left = false } }, .{ .value = " foo bar", .expected = " foo bar", .opts = .{ .trim_left = false } },
.{ .value = "foo bar ", .expected = "foo bar ", .opts = .{ .trim_right = false } }, .{ .value = "foo bar ", .expected = "foo bar ", .opts = .{ .trim_right = false } },
.{ .value = " foo bar ", .expected = " foo bar ", .opts = .{ .trim_left = false, .trim_right = false } }, .{ .value = " foo bar ", .expected = " foo bar ", .opts = .{ .trim_left = false, .trim_right = false } },
@@ -317,8 +323,9 @@ test "WebApi: CData.render" {
._data = test_case.value, ._data = test_case.value,
}; };
try cdata.render(&buffer.writer, test_case.opts); const result = try cdata.render(&buffer.writer, test_case.opts);
try std.testing.expectEqualStrings(test_case.expected, buffer.written()); try std.testing.expectEqualStrings(test_case.expected, buffer.written());
try std.testing.expect(result == test_case.result);
} }
} }

View File

@@ -228,21 +228,43 @@ pub fn getNamespaceURI(self: *const Element) []const u8 {
// innerText represents the **rendered** text content of a node and its // innerText represents the **rendered** text content of a node and its
// descendants. // descendants.
pub fn getInnerText(self: *Element, writer: *std.Io.Writer) !void { pub fn getInnerText(self: *Element, writer: *std.Io.Writer) !void {
var state = innerTextState{};
return try self._getInnerText(writer, &state);
}
const innerTextState = struct {
pre_w: bool = false,
trim_left: bool = true,
};
fn _getInnerText(self: *Element, writer: *std.Io.Writer, state: *innerTextState) !void {
var it = self.asNode().childrenIterator(); var it = self.asNode().childrenIterator();
while (it.next()) |child| { while (it.next()) |child| {
switch (child._type) { switch (child._type) {
.element => |e| switch (e._type) { .element => |e| switch (e._type) {
.html => |he| switch (he._type) { .html => |he| switch (he._type) {
.br => try writer.writeByte('\n'), .br => {
.script, .style, .template => continue, try writer.writeByte('\n');
else => try e.getInnerText(writer), // TODO check if elt is hidden. state.pre_w = false; // prevent a next pre space.
state.trim_left = true;
},
.script, .style, .template => {
state.pre_w = false; // prevent a next pre space.
state.trim_left = true;
},
else => try e._getInnerText(writer, state), // TODO check if elt is hidden.
}, },
.svg => {}, .svg => {},
}, },
.cdata => |c| switch (c._type) { .cdata => |c| switch (c._type) {
.comment => continue, .comment => {
.text => try c.render(writer, .{ .trim_right = false, .trim_left = false }), state.pre_w = false; // prevent a next pre space.
.cdata_section => try writer.writeAll(c._data), state.trim_left = true;
},
.text => {
if (state.pre_w) try writer.writeByte(' ');
state.pre_w = try c.render(writer, .{ .trim_left = state.trim_left });
// if we had a pre space, trim left next one.
state.trim_left = state.pre_w;
},
}, },
.document => {}, .document => {},
.document_type => {}, .document_type => {},