Skip to content

Commit b6b3f3a

Browse files
committed
Add first few CSS styles to CSS parser
1 parent b5046d9 commit b6b3f3a

File tree

4 files changed

+186
-14
lines changed

4 files changed

+186
-14
lines changed

example/lib/main.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ const htmlData = """
103103
<h2><li>Header 2</li></h2>
104104
</ol>
105105
<h3>Link support:</h3>
106-
<p>
106+
<p style='color:darkgreen; text-align: center; font-family: monospace;'>
107107
Linking to <a href='https://github.com'>websites</a> has never been easier.
108108
</p>
109109
<h3>Image support:</h3>

lib/html_parser.dart

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'package:csslib/parser.dart' as cssparser;
55
import 'package:csslib/visitor.dart' as css;
66
import 'package:flutter/material.dart';
77
import 'package:flutter_html/flutter_html.dart';
8+
import 'package:flutter_html/src/css_parser.dart';
89
import 'package:flutter_html/src/html_elements.dart';
910
import 'package:flutter_html/src/layout_element.dart';
1011
import 'package:flutter_html/src/utils.dart';
@@ -53,6 +54,7 @@ class HtmlParser extends StatelessWidget {
5354
customRender?.keys?.toList() ?? [],
5455
blacklistedElements,
5556
);
57+
// TODO(Sub6Resources): this could be simplified to a single recursive descent.
5658
StyledElement styledTree = applyCSS(lexedTree, sheet);
5759
StyledElement inlineStyledTree = applyInlineStyles(styledTree);
5860
StyledElement customStyledTree = _applyCustomStyles(inlineStyledTree);
@@ -145,13 +147,8 @@ class HtmlParser extends StatelessWidget {
145147

146148
///TODO document
147149
static StyledElement applyCSS(StyledElement tree, css.StyleSheet sheet) {
150+
148151
//TODO
149-
// sheet.topLevels.forEach((treeNode) {
150-
// if (treeNode is css.RuleSet) {
151-
// print(treeNode
152-
// .selectorGroup.selectors.first.simpleSelectorSequences.first.simpleSelector.name);
153-
// }
154-
// });
155152

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

166-
///TODO document
163+
/// [applyInlineStyle] applies inline styles (i.e. `style="..."`) recursively into the StyledElement tree.
167164
static StyledElement applyInlineStyles(StyledElement tree) {
168-
//TODO
165+
166+
if(tree.attributes.containsKey("style")) {
167+
tree.style = tree.style.merge(inlineCSSToStyle(tree.attributes['style']));
168+
}
169+
170+
tree.children?.forEach(applyInlineStyles);
169171

170172
return tree;
171173
}

lib/src/css_parser.dart

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import 'dart:ui';
2+
3+
import 'package:csslib/visitor.dart' as css;
4+
import 'package:csslib/parser.dart' as cssparser;
5+
import 'package:flutter_html/style.dart';
6+
7+
Map<String, Style> cssToStyles(css.StyleSheet sheet) {
8+
sheet.topLevels.forEach((treeNode) {
9+
if (treeNode is css.RuleSet) {
10+
print(
11+
treeNode.selectorGroup.selectors.first.simpleSelectorSequences.first.simpleSelector.name);
12+
}
13+
});
14+
}
15+
16+
Style declarationsToStyle(Map<String, List<css.Expression>> declarations) {
17+
Style style = new Style();
18+
declarations.forEach((property, value) {
19+
switch (property) {
20+
case 'background-color':
21+
style.backgroundColor = ExpressionMapping.expressionToColor(value.first);
22+
break;
23+
case 'color':
24+
style.color = ExpressionMapping.expressionToColor(value.first);
25+
break;
26+
case 'direction':
27+
style.direction = ExpressionMapping.expressionToDirection(value.first);
28+
break;
29+
case 'display':
30+
style.display = ExpressionMapping.expressionToDisplay(value.first);
31+
break;
32+
case 'font-family':
33+
style.fontFamily = ExpressionMapping.expressionToFontFamily(value.first);
34+
break;
35+
case 'font-feature-settings':
36+
style.fontFeatureSettings = ExpressionMapping.expressionToFontFeatureSettings(value);
37+
break;
38+
case 'text-shadow':
39+
style.textShadow = ExpressionMapping.expressionToTextShadow(value);
40+
break;
41+
case 'text-align':
42+
style.textAlign = ExpressionMapping.expressionToTextAlign(value.first);
43+
break;
44+
}
45+
});
46+
return style;
47+
}
48+
49+
Style inlineCSSToStyle(String inlineStyle) {
50+
final sheet = cssparser.parse("*{$inlineStyle}");
51+
final declarations = DeclarationVisitor().getDeclarations(sheet);
52+
return declarationsToStyle(declarations);
53+
}
54+
55+
class DeclarationVisitor extends css.Visitor {
56+
Map<String, List<css.Expression>> _result;
57+
String _currentProperty;
58+
59+
Map<String, List<css.Expression>> getDeclarations(css.StyleSheet sheet) {
60+
_result = new Map<String, List<css.Expression>>();
61+
sheet.visit(this);
62+
return _result;
63+
}
64+
65+
@override
66+
void visitDeclaration(css.Declaration node) {
67+
_currentProperty = node.property;
68+
_result[_currentProperty] = new List<css.Expression>();
69+
node.expression.visit(this);
70+
}
71+
72+
@override
73+
void visitExpressions(css.Expressions node) {
74+
node.expressions.forEach((expression) {
75+
_result[_currentProperty].add(expression);
76+
});
77+
}
78+
}
79+
80+
//Mapping functions
81+
class ExpressionMapping {
82+
83+
static Color expressionToColor(css.Expression value) {
84+
if (value is css.HexColorTerm) {
85+
return stringToColor(value.text);
86+
}
87+
//TODO(Sub6Resources): Support function-term values (rgba()/rgb())
88+
return null;
89+
}
90+
91+
static Color stringToColor(String _text) {
92+
var text = _text.replaceFirst('#', '');
93+
if (text.length == 3)
94+
text = text.replaceAllMapped(
95+
RegExp(r"[a-f]|\d"), (match) => '${match.group(0)}${match.group(0)}');
96+
int color = int.parse(text, radix: 16);
97+
98+
if (color <= 0xffffff) {
99+
return new Color(color).withAlpha(255);
100+
} else {
101+
return new Color(color);
102+
}
103+
}
104+
105+
static TextAlign expressionToTextAlign(css.Expression value) {
106+
if (value is css.LiteralTerm) {
107+
switch(value.text) {
108+
case "center":
109+
return TextAlign.center;
110+
case "left":
111+
return TextAlign.left;
112+
case "right":
113+
return TextAlign.right;
114+
case "justify":
115+
return TextAlign.justify;
116+
case "end":
117+
return TextAlign.end;
118+
case "start":
119+
return TextAlign.start;
120+
}
121+
}
122+
return TextAlign.start;
123+
}
124+
125+
static TextDirection expressionToDirection(css.Expression value) {
126+
if (value is css.LiteralTerm) {
127+
switch(value.text) {
128+
case "ltr":
129+
return TextDirection.ltr;
130+
case "rtl":
131+
return TextDirection.rtl;
132+
}
133+
}
134+
return TextDirection.ltr;
135+
}
136+
137+
static List<FontFeature> expressionToFontFeatureSettings(List<css.Expression> value) {
138+
//TODO
139+
return [];
140+
}
141+
142+
static List<Shadow> expressionToTextShadow(List<css.Expression> value) {
143+
//TODO
144+
return [];
145+
}
146+
147+
static Display expressionToDisplay(css.Expression value) {
148+
if (value is css.LiteralTerm) {
149+
switch(value.text) {
150+
case 'block':
151+
return Display.BLOCK;
152+
case 'inline-block':
153+
return Display.INLINE_BLOCK;
154+
case 'inline':
155+
return Display.INLINE;
156+
case 'list-item':
157+
return Display.LIST_ITEM;
158+
}
159+
}
160+
}
161+
162+
static String expressionToFontFamily(css.Expression value) {
163+
if(value is css.LiteralTerm)
164+
return value.text;
165+
}
166+
}
167+
168+

lib/src/styled_element.dart

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import 'package:flutter/material.dart';
22
import 'package:flutter_html/style.dart';
33
import 'package:html/dom.dart' as dom;
4-
//TODO(Sub6Resources): don't use the internal code of the html package as it may change unexpectedly.
54
import 'package:html/src/query_selector.dart';
65

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

25-
bool matchesSelector(String selector) =>
26-
_node != null && matches(_node, selector);
24+
bool matchesSelector(String selector) => _node != null && matches(_node, selector);
2725

28-
Map<String, String> get attributes => _node.attributes.map((key, value) {
26+
Map<String, String> get attributes {
27+
return _node?.attributes?.map(
28+
(key, value) {
2929
return MapEntry(key, value);
30-
});
30+
},
31+
) ?? Map<String, String>();
32+
}
3133

3234
dom.Element get element => _node;
3335

0 commit comments

Comments
 (0)