Skip to content
Open
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
1 change: 1 addition & 0 deletions pkgs/html/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## 0.15.7-wip

- Require Dart `3.6`
- Fix a bug in DOM parsing where `<br>` tags does not create a new line when html is converted to text.

## 0.15.6

Expand Down
47 changes: 45 additions & 2 deletions pkgs/html/lib/dom.dart
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,9 @@ abstract class Node {
}

// Implemented per: http://dom.spec.whatwg.org/#dom-node-textcontent
String? textContent({bool convertBRsToNewlines = false}) =>
_getTextContent(this, convertBRsToNewlines: convertBRsToNewlines);

String? get text => null;

set text(String? value) {}
Expand Down Expand Up @@ -1099,8 +1102,36 @@ class FilteredElementList extends IterableBase<Element>
}

// http://dom.spec.whatwg.org/#dom-node-textcontent
// For Element and DocumentFragment
String _getText(Node node) => (_ConcatTextVisitor()..visit(node)).toString();
String? _getTextContent(Node node, {bool convertBRsToNewlines = false}) {
// DocumentFragment or Element: return descendant text content
if (node is DocumentFragment || node is Element) {
return _getText(node, convertBRsToNewlines: convertBRsToNewlines);
}
// CharacterData (Text, Comment): return data
if (node is Text) {
return node.data;
}
if (node is Comment) {
return node.data;
}
// Otherwise: return null
return null;
}

/// Returns true if the element is an HTML <br> element.
/// Checks both the local name and namespace to ensure it's a proper HTML br element.
/// Note: null namespace is treated as HTML namespace for elements created by the HTML parser.
bool isElementBr(Element element) {
if (element.localName != 'br') return false;
final ns = element.namespaceUri;
return ns == null || ns == Namespaces.html;
}

// For Element and DocumentFragment (legacy helper)
String _getText(Node node, {bool convertBRsToNewlines = false}) =>
(_ConcatTextVisitor(convertBRsToNewlines: convertBRsToNewlines)
..visit(node))
.toString();

void _setText(Node node, String? value) {
node.nodes.clear();
Expand All @@ -1109,6 +1140,9 @@ void _setText(Node node, String? value) {

class _ConcatTextVisitor extends TreeVisitor {
final _str = StringBuffer();
final bool convertBRsToNewlines;

_ConcatTextVisitor({this.convertBRsToNewlines = false});

@override
String toString() => _str.toString();
Expand All @@ -1117,4 +1151,13 @@ class _ConcatTextVisitor extends TreeVisitor {
void visitText(Text node) {
_str.write(node.data);
}

@override
void visitElement(Element node) {
if (convertBRsToNewlines && isElementBr(node)) {
_str.write('\n');
return;
}
super.visitElement(node);
}
}
8 changes: 6 additions & 2 deletions pkgs/html/test/parser_feature_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -268,11 +268,13 @@ On line 4, column 3 of ParseError: Unexpected DOCTYPE. Ignored.
});

test('Element.text', () {
final doc = parseFragment('<div>foo<div>bar</div>baz</div>');
final doc = parseFragment('<div>foo<div><br>bar</div>baz<br></div>');
final e = doc.firstChild!;
final text = e.firstChild!;
expect((text as Text).data, 'foo');
expect(e.text, 'foobarbaz');
expect(e.textContent(convertBRsToNewlines: true), 'foo\nbarbaz\n');
expect(e.textContent(convertBRsToNewlines: false), 'foobarbaz');

e.text = 'FOO';
expect(e.nodes.length, 1);
Expand All @@ -282,7 +284,7 @@ On line 4, column 3 of ParseError: Unexpected DOCTYPE. Ignored.
});

test('Text.text', () {
final doc = parseFragment('<div>foo<div>bar</div>baz</div>');
final doc = parseFragment('<div>foo<div>bar</div><br>baz</div>');
final e = doc.firstChild!;
final text = e.firstChild as Text;
expect(text.data, 'foo');
Expand All @@ -291,6 +293,8 @@ On line 4, column 3 of ParseError: Unexpected DOCTYPE. Ignored.
text.text = 'FOO';
expect(text.data, 'FOO');
expect(e.text, 'FOObarbaz');
expect(e.textContent(convertBRsToNewlines: true), 'FOObar\nbaz');
expect(e.textContent(convertBRsToNewlines: false), 'FOObarbaz');
expect(text.text, 'FOO');
});

Expand Down