Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Add support for inline styles to new parser #228

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion example/lib/main.dart
Expand Up @@ -103,7 +103,7 @@ const htmlData = """
<h2><li>Header 2</li></h2>
</ol>
<h3>Link support:</h3>
<p>
<p style='color:darkgreen; text-align: center; font-family: monospace;'>
Linking to <a href='https://github.com'>websites</a> has never been easier.
</p>
<h3>Image support:</h3>
Expand Down
18 changes: 10 additions & 8 deletions lib/html_parser.dart
Expand Up @@ -5,6 +5,7 @@ import 'package:csslib/parser.dart' as cssparser;
import 'package:csslib/visitor.dart' as css;
import 'package:flutter/material.dart';
import 'package:flutter_html/flutter_html.dart';
import 'package:flutter_html/src/css_parser.dart';
import 'package:flutter_html/src/html_elements.dart';
import 'package:flutter_html/src/layout_element.dart';
import 'package:flutter_html/src/utils.dart';
Expand Down Expand Up @@ -53,6 +54,7 @@ class HtmlParser extends StatelessWidget {
customRender?.keys?.toList() ?? [],
blacklistedElements,
);
// TODO(Sub6Resources): this could be simplified to a single recursive descent.
StyledElement styledTree = applyCSS(lexedTree, sheet);
StyledElement inlineStyledTree = applyInlineStyles(styledTree);
StyledElement customStyledTree = _applyCustomStyles(inlineStyledTree);
Expand Down Expand Up @@ -145,13 +147,8 @@ class HtmlParser extends StatelessWidget {

///TODO document
static StyledElement applyCSS(StyledElement tree, css.StyleSheet sheet) {

//TODO
// sheet.topLevels.forEach((treeNode) {
// if (treeNode is css.RuleSet) {
// print(treeNode
// .selectorGroup.selectors.first.simpleSelectorSequences.first.simpleSelector.name);
// }
// });

//Make sure style is never null.
if (tree.style == null) {
Expand All @@ -163,9 +160,14 @@ class HtmlParser extends StatelessWidget {
return tree;
}

///TODO document
/// [applyInlineStyle] applies inline styles (i.e. `style="..."`) recursively into the StyledElement tree.
static StyledElement applyInlineStyles(StyledElement tree) {
//TODO

if(tree.attributes.containsKey("style")) {
tree.style = tree.style.merge(inlineCSSToStyle(tree.attributes['style']));
}

tree.children?.forEach(applyInlineStyles);

return tree;
}
Expand Down
168 changes: 168 additions & 0 deletions lib/src/css_parser.dart
@@ -0,0 +1,168 @@
import 'dart:ui';

import 'package:csslib/visitor.dart' as css;
import 'package:csslib/parser.dart' as cssparser;
import 'package:flutter_html/style.dart';

Map<String, Style> cssToStyles(css.StyleSheet sheet) {
sheet.topLevels.forEach((treeNode) {
if (treeNode is css.RuleSet) {
print(
treeNode.selectorGroup.selectors.first.simpleSelectorSequences.first.simpleSelector.name);
}
});
}

Style declarationsToStyle(Map<String, List<css.Expression>> declarations) {
Style style = new Style();
declarations.forEach((property, value) {
switch (property) {
case 'background-color':
style.backgroundColor = ExpressionMapping.expressionToColor(value.first);
break;
case 'color':
style.color = ExpressionMapping.expressionToColor(value.first);
break;
case 'direction':
style.direction = ExpressionMapping.expressionToDirection(value.first);
break;
case 'display':
style.display = ExpressionMapping.expressionToDisplay(value.first);
break;
case 'font-family':
style.fontFamily = ExpressionMapping.expressionToFontFamily(value.first);
break;
case 'font-feature-settings':
style.fontFeatureSettings = ExpressionMapping.expressionToFontFeatureSettings(value);
break;
case 'text-shadow':
style.textShadow = ExpressionMapping.expressionToTextShadow(value);
break;
case 'text-align':
style.textAlign = ExpressionMapping.expressionToTextAlign(value.first);
break;
}
});
return style;
}

Style inlineCSSToStyle(String inlineStyle) {
final sheet = cssparser.parse("*{$inlineStyle}");
final declarations = DeclarationVisitor().getDeclarations(sheet);
return declarationsToStyle(declarations);
}

class DeclarationVisitor extends css.Visitor {
Map<String, List<css.Expression>> _result;
String _currentProperty;

Map<String, List<css.Expression>> getDeclarations(css.StyleSheet sheet) {
_result = new Map<String, List<css.Expression>>();
sheet.visit(this);
return _result;
}

@override
void visitDeclaration(css.Declaration node) {
_currentProperty = node.property;
_result[_currentProperty] = new List<css.Expression>();
node.expression.visit(this);
}

@override
void visitExpressions(css.Expressions node) {
node.expressions.forEach((expression) {
_result[_currentProperty].add(expression);
});
}
}

//Mapping functions
class ExpressionMapping {

static Color expressionToColor(css.Expression value) {
if (value is css.HexColorTerm) {
return stringToColor(value.text);
}
//TODO(Sub6Resources): Support function-term values (rgba()/rgb())
return null;
}

static Color stringToColor(String _text) {
var text = _text.replaceFirst('#', '');
if (text.length == 3)
text = text.replaceAllMapped(
RegExp(r"[a-f]|\d"), (match) => '${match.group(0)}${match.group(0)}');
int color = int.parse(text, radix: 16);

if (color <= 0xffffff) {
return new Color(color).withAlpha(255);
} else {
return new Color(color);
}
}

static TextAlign expressionToTextAlign(css.Expression value) {
if (value is css.LiteralTerm) {
switch(value.text) {
case "center":
return TextAlign.center;
case "left":
return TextAlign.left;
case "right":
return TextAlign.right;
case "justify":
return TextAlign.justify;
case "end":
return TextAlign.end;
case "start":
return TextAlign.start;
}
}
return TextAlign.start;
}

static TextDirection expressionToDirection(css.Expression value) {
if (value is css.LiteralTerm) {
switch(value.text) {
case "ltr":
return TextDirection.ltr;
case "rtl":
return TextDirection.rtl;
}
}
return TextDirection.ltr;
}

static List<FontFeature> expressionToFontFeatureSettings(List<css.Expression> value) {
//TODO
return [];
}

static List<Shadow> expressionToTextShadow(List<css.Expression> value) {
//TODO
return [];
}

static Display expressionToDisplay(css.Expression value) {
if (value is css.LiteralTerm) {
switch(value.text) {
case 'block':
return Display.BLOCK;
case 'inline-block':
return Display.INLINE_BLOCK;
case 'inline':
return Display.INLINE;
case 'list-item':
return Display.LIST_ITEM;
}
}
}

static String expressionToFontFamily(css.Expression value) {
if(value is css.LiteralTerm)
return value.text;
}
}


12 changes: 7 additions & 5 deletions lib/src/styled_element.dart
@@ -1,7 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_html/style.dart';
import 'package:html/dom.dart' as dom;
//TODO(Sub6Resources): don't use the internal code of the html package as it may change unexpectedly.
import 'package:html/src/query_selector.dart';

/// A [StyledElement] applies a style to all of its children.
Expand All @@ -22,12 +21,15 @@ class StyledElement {
dom.Element node,
}) : this._node = node;

bool matchesSelector(String selector) =>
_node != null && matches(_node, selector);
bool matchesSelector(String selector) => _node != null && matches(_node, selector);

Map<String, String> get attributes => _node.attributes.map((key, value) {
Map<String, String> get attributes {
return _node?.attributes?.map(
(key, value) {
return MapEntry(key, value);
});
},
) ?? Map<String, String>();
}

dom.Element get element => _node;

Expand Down