Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/browser/tests/element/inner.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<div id=d1>hello <em>world</em></div>
<div id=d2>
<style>h1 { font-size: 1em; }</style>
<!-- this is a comment -->
This is a <br>
text
</div>

<script id=innerHTML>
const d1 = $('#d1');
Expand All @@ -20,6 +26,9 @@
d1.innerHTML = '<script src=inner.js>';
testing.expectEqual('<script src="inner.js"><\/script>', d1.innerHTML);
testing.expectEqual(false, inner_loaded);

const d2 = $('#d2');
testing.expectEqual("\n <style>h1 { font-size: 1em; }</style>\n <!-- this is a comment -->\n This is a <br>\n text\n", d2.innerHTML);
</script>

<script id=ids>
Expand Down Expand Up @@ -160,4 +169,8 @@
d1.innerText = 'new content';
testing.expectEqual('new content', d1.innerText);
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 \n text ", d2.innerText);
</script>
17 changes: 13 additions & 4 deletions src/browser/tests/node/text_content.html
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<div id=id1>d1 <p>hello</p></div>
<div id=id2>
<style>h1 { font-size: 1em; }</style>
<!-- this is a comment -->
This is a <br>
text
</div>

<script id=element>
const div = $('#id1');
testing.expectEqual('d1 hello', div.textContent);

div.textContent = 'world <p>!</p>';
testing.expectEqual('world <p>!</p>', div.textContent);

const div2 = $('#id2');
testing.expectEqual("\n h1 { font-size: 1em; }\n \n This is a \n text\n", div2.textContent);
</script>

<script id=document>
Expand All @@ -28,8 +37,8 @@
const attr = div.getAttributeNode('id');
testing.expectEqual('id1', attr.value);
testing.expectEqual('id1', attr.textContent);
attr.textContent = 'id2';
testing.expectEqual('id2', attr.value);
testing.expectEqual('id2', attr.textContent);
testing.expectEqual(div, $('#id2'));
attr.textContent = 'attr2';
testing.expectEqual('attr2', attr.value);
testing.expectEqual('attr2', attr.textContent);
testing.expectEqual(div, $('#attr2'));
</script>
93 changes: 93 additions & 0 deletions src/browser/webapi/CData.zig
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,58 @@ pub fn getData(self: *const CData) []const u8 {
return self._data;
}

pub const RenderOpts = struct {
trim_left: bool = true,
trim_right: bool = true,
};
// Replace successives whitespaces with one withespace.
// Trims left and right according to the options.
pub fn render(self: *const CData, writer: *std.io.Writer, opts: RenderOpts) !void {
var start: usize = 0;
var prev_w: ?bool = null;
var is_w: bool = undefined;
const s = self._data;

for (s, 0..) |c, i| {
is_w = std.ascii.isWhitespace(c);

// Detect the first char type.
if (prev_w == null) {
prev_w = is_w;
}
// The current char is the same kind of char, the chunk continues.
if (prev_w.? == is_w) {
continue;
}

// Starting here, the chunk changed.
if (is_w) {
// We have a chunk of non-whitespaces, we write it as it.
try writer.writeAll(s[start..i]);
} else {
// We have a chunk of whitespaces, replace with one space,
// depending the position.
if (start > 0 or !opts.trim_left) {
try writer.writeByte(' ');
}
}
// Start the new chunk.
prev_w = is_w;
start = i;
}
// Write the reminder chunk.
if (is_w) {
// Last chunk is whitespaces.
// If the string contains only whitespaces, don't write it.
if (start > 0 and opts.trim_right == false) {
try writer.writeByte(' ');
}
} else {
// last chunk is non whitespaces.
try writer.writeAll(s[start..]);
}
}

pub fn setData(self: *CData, value: ?[]const u8, page: *Page) !void {
const old_value = self._data;

Expand Down Expand Up @@ -223,3 +275,44 @@ const testing = @import("../../testing.zig");
test "WebApi: CData" {
try testing.htmlRunner("cdata", .{});
}

test "WebApi: CData.render" {
const allocator = std.testing.allocator;

const TestCase = struct {
value: []const u8,
expected: []const u8,
opts: RenderOpts = .{},
};

const test_cases = [_]TestCase{
.{ .value = " ", .expected = "" },
.{ .value = " ", .expected = "", .opts = .{ .trim_left = false, .trim_right = false } },
.{ .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\n\tbar", .expected = "foo bar" },
.{ .value = "\tfoo bar baz \t\n yeah\r\n", .expected = "foo bar baz yeah" },
.{ .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_left = false, .trim_right = false } },
};

var buffer = std.io.Writer.Allocating.init(allocator);
defer buffer.deinit();
for (test_cases) |test_case| {
buffer.clearRetainingCapacity();

const cdata = CData{
._type = .{ .text = undefined },
._proto = undefined,
._data = test_case.value,
};

try cdata.render(&buffer.writer, test_case.opts);

try std.testing.expectEqualStrings(test_case.expected, buffer.written());
}
}
21 changes: 20 additions & 1 deletion src/browser/webapi/Element.zig
Original file line number Diff line number Diff line change
Expand Up @@ -223,10 +223,29 @@ pub fn getNamespaceURI(self: *const Element) []const u8 {
return self._namespace.toUri();
}

// innerText represents the **rendered** text content of a node and its
// descendants.
pub fn getInnerText(self: *Element, writer: *std.Io.Writer) !void {
var it = self.asNode().childrenIterator();
while (it.next()) |child| {
try child.getTextContent(writer);
switch (child._type) {
.element => |e| switch (e._type) {
.html => |he| switch (he._type) {
.br => try writer.writeByte('\n'),
.script, .style, .template => continue,
else => try e.getInnerText(writer), // TODO check if elt is hidden.
},
.svg => {},
},
.cdata => |c| switch (c._type) {
.comment => continue,
.text => try c.render(writer, .{ .trim_right = false, .trim_left = false }),
},
.document => {},
.document_type => {},
.document_fragment => {},
.attribute => |attr| try writer.writeAll(attr._value),
}
}
}

Expand Down
13 changes: 11 additions & 2 deletions src/browser/webapi/Node.zig
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,16 @@ pub fn childNodes(self: *const Node, page: *Page) !*collections.ChildNodes {

pub fn getTextContent(self: *Node, writer: *std.Io.Writer) error{WriteFailed}!void {
switch (self._type) {
.element => |el| return el.getInnerText(writer),
.element => {
var it = self.childrenIterator();
while (it.next()) |child| {
// ignore comments and TODO processing instructions.
if (child.is(CData.Comment) != null) {
continue;
}
try child.getTextContent(writer);
}
},
.cdata => |c| try writer.writeAll(c.getData()),
.document => {},
.document_type => {},
Expand Down Expand Up @@ -719,7 +728,7 @@ pub const JsApi = struct {
switch (self._type) {
.element => |el| {
var buf = std.Io.Writer.Allocating.init(page.call_arena);
try el.getInnerText(&buf.writer);
try el.asNode().getTextContent(&buf.writer);
return buf.written();
},
.cdata => |cdata| return cdata.getData(),
Expand Down
Loading