Skip to content

Commit

Permalink
feat: table html encoder and decoder added (#449)
Browse files Browse the repository at this point in the history
* feat: table html encoder and decoder added

* feat: html table encoder test added

* feat: fix the test ran issues

* fix: removed unused code
  • Loading branch information
alihassan143 authored Oct 22, 2023
1 parent 89b93d8 commit 522f17a
Show file tree
Hide file tree
Showing 9 changed files with 453 additions and 1 deletion.
1 change: 1 addition & 0 deletions lib/src/plugins/html/encoder/parser/html_parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ export 'html_node_parser.dart';
export 'image_node_parser.dart';
export 'numbered_list_node_parser.dart';
export 'quote_node_parser.dart';
export 'table_node_parser.dart';
export 'text_node_parser.dart';
export 'todo_list_node_parser.dart';
69 changes: 69 additions & 0 deletions lib/src/plugins/html/encoder/parser/table_node_parser.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:collection/collection.dart';
import 'package:html/dom.dart' as dom;

import '../../../../editor/block_component/table_block_component/util.dart';

class HtmlTableNodeParser extends HTMLNodeParser {
const HtmlTableNodeParser();

@override
String get id => TableBlockKeys.type;

@override
String transformNodeToHTMLString(
Node node, {
required List<HTMLNodeParser> encodeParsers,
}) {
assert(node.type == TableBlockKeys.type);

return toHTMLString(
transformNodeToDomNodes(node, encodeParsers: encodeParsers),
);
}

@override
List<dom.Node> transformNodeToDomNodes(
Node node, {
required List<HTMLNodeParser> encodeParsers,
}) {
final int rowsLen = node.attributes[TableBlockKeys.rowsLen],
colsLen = node.attributes[TableBlockKeys.colsLen];
final List<dom.Node> domNodes = [];

for (var i = 0; i < rowsLen; i++) {
final List<dom.Node> nodes = [];
for (var j = 0; j < colsLen; j++) {
final Node cell = getCellNode(node, j, i)!;

for (final childnode in cell.children) {
HTMLNodeParser? parser = encodeParsers.firstWhereOrNull(
(element) => element.id == childnode.type,
);

if (parser != null) {
nodes.add(
wrapChildrenNodesWithTagName(
HTMLTags.tabledata,
childNodes: parser.transformNodeToDomNodes(
childnode,
encodeParsers: encodeParsers,
),
),
);
}
}
}
final rowelement =
wrapChildrenNodesWithTagName(HTMLTags.tableRow, childNodes: nodes);

domNodes.add(rowelement);
}

final element =
wrapChildrenNodesWithTagName(HTMLTags.table, childNodes: domNodes);
return [
element,
];
}
}
1 change: 1 addition & 0 deletions lib/src/plugins/html/html_document.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ String documentToHTML(
const HTMLQuoteNodeParser(),
const HTMLHeadingNodeParser(),
const HTMLImageNodeParser(),
const HtmlTableNodeParser()
],
).encode(document);
}
Expand Down
138 changes: 138 additions & 0 deletions lib/src/plugins/html/html_document_decoder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'dart:collection';
import 'dart:convert';

import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_editor/src/editor/block_component/table_block_component/table_node.dart';
import 'package:html/dom.dart' as dom;
import 'package:html/parser.dart' show parse;

Expand Down Expand Up @@ -78,6 +79,8 @@ class DocumentHTMLDecoder extends Converter<String, Document> {
return _parseUnOrderListElement(element);
case HTMLTags.orderedList:
return _parseOrderListElement(element);
case HTMLTags.table:
return _parseTable(element);
case HTMLTags.list:
return [
_parseListElement(
Expand All @@ -96,6 +99,134 @@ class DocumentHTMLDecoder extends Converter<String, Document> {
}
}

Iterable<Node> _parseTable(dom.Element element) {
final List<Node> tablenodes = [];
int columnLenth = 0;
int rowLength = 0;
for (final data in element.children) {
final (col, row, rwdata) = _parsetableRows(data);
columnLenth = columnLenth + col;
rowLength = rowLength + row;

tablenodes.addAll(rwdata);
}

return [
TableNode(
node: Node(
type: TableBlockKeys.type,
attributes: {
TableBlockKeys.rowsLen: rowLength,
TableBlockKeys.colsLen: columnLenth,
TableBlockKeys.colDefaultWidth: TableDefaults.colWidth,
TableBlockKeys.rowDefaultHeight: TableDefaults.rowHeight,
TableBlockKeys.colMinimumWidth: TableDefaults.colMinimumWidth,
},
children: tablenodes,
),
).node,
];
}

(int, int, List<Node>) _parsetableRows(dom.Element element) {
final List<Node> nodes = [];
int colLength = 0;
int rowLength = 0;

for (final data in element.children) {
final tabledata = _parsetableData(data, rowPosition: rowLength);
if (colLength == 0) {
colLength = tabledata.length;
}
nodes.addAll(tabledata);
rowLength++;
}
return (colLength, rowLength, nodes);
}

Iterable<Node> _parsetableData(
dom.Element element, {
required int rowPosition,
}) {
final List<Node> nodes = [];
int columnPosition = 0;

for (final data in element.children) {
Attributes attributes = {
TableCellBlockKeys.colPosition: columnPosition,
TableCellBlockKeys.rowPosition: rowPosition,
};
if (data.attributes.isNotEmpty) {
final deltaAttributes = _getDeltaAttributesFromHTMLAttributes(
element.attributes,
) ??
{};
attributes.addAll(deltaAttributes);
}

List<Node> children;
if (data.children.isEmpty) {
children = [paragraphNode(text: data.text)];
} else {
children = _parseTableSpecialNodes(data).toList();
}

final node = Node(
type: TableCellBlockKeys.type,
attributes: attributes,
children: children,
);

nodes.add(node);
columnPosition++;
}

return nodes;
}

Iterable<Node> _parseTableSpecialNodes(dom.Element element) {
final List<Node> nodes = [];

if (element.children.isNotEmpty) {
for (final childrens in element.children) {
nodes.addAll(_parseTableDataElementsData(childrens));
}
} else {
nodes.addAll(_parseTableDataElementsData(element));
}
return nodes;
}

List<Node> _parseTableDataElementsData(dom.Element element) {
final List<Node> nodes = [];
final delta = Delta();
final localName = element.localName;

if (HTMLTags.formattingElements.contains(localName)) {
final attributes = _parserFormattingElementAttributes(element);
delta.insert(element.text, attributes: attributes);
} else if (HTMLTags.specialElements.contains(localName)) {
if (delta.isNotEmpty) {
nodes.add(paragraphNode(delta: delta));
}
nodes.addAll(
_parseSpecialElements(
element,
type: ParagraphBlockKeys.type,
),
);
} else if (element is dom.Text) {
// skip the empty text node

delta.insert(element.text);
}

if (delta.isNotEmpty) {
nodes.add(paragraphNode(delta: delta));
}
return nodes;
}

Attributes _parserFormattingElementAttributes(
dom.Element element,
) {
Expand Down Expand Up @@ -130,6 +261,7 @@ class DocumentHTMLDecoder extends Converter<String, Document> {
attributes = {AppFlowyRichTextKeys.href: href};
}
break;

case HTMLTags.strikethrough:
attributes = {AppFlowyRichTextKeys.strikethrough: true};
break;
Expand Down Expand Up @@ -360,6 +492,10 @@ class HTMLTags {
static const blockQuote = 'blockquote';
static const div = 'div';
static const divider = 'hr';
static const table = 'table';
static const tableRow = 'tr';
static const tableheader = "th";
static const tabledata = "td";
static const section = 'section';
static const font = 'font';
static const mark = 'mark';
Expand Down Expand Up @@ -387,6 +523,7 @@ class HTMLTags {
HTMLTags.orderedList,
HTMLTags.div,
HTMLTags.list,
HTMLTags.table,
HTMLTags.paragraph,
HTMLTags.blockQuote,
HTMLTags.checkbox,
Expand All @@ -398,6 +535,7 @@ class HTMLTags {
return tag == h1 ||
tag == h2 ||
tag == h3 ||
tag == table ||
tag == checkbox ||
tag == paragraph ||
tag == div ||
Expand Down
Loading

0 comments on commit 522f17a

Please sign in to comment.