1
+ import 'dart:collection' ;
1
2
import 'dart:math' ;
2
3
3
4
import 'package:flutter_html/flutter_html.dart' ;
4
5
import 'package:flutter_html/src/layout_element.dart' ;
6
+ import 'package:flutter_html/src/utils.dart' ;
5
7
import 'package:flutter_html/style.dart' ;
6
8
import 'package:flutter/material.dart' ;
7
9
import 'package:csslib/visitor.dart' as css;
@@ -39,7 +41,8 @@ class HtmlParser extends StatelessWidget {
39
41
StyledElement styledTree = applyCSS (lexedTree, sheet);
40
42
StyledElement inlineStyledTree = applyInlineStyles (styledTree);
41
43
StyledElement customStyledTree = _applyCustomStyles (inlineStyledTree);
42
- StyledElement cleanedTree = cleanTree (customStyledTree);
44
+ StyledElement cascadedStyledTree = _cascadeStyles (customStyledTree);
45
+ StyledElement cleanedTree = cleanTree (cascadedStyledTree);
43
46
InlineSpan parsedTree = parseTree (
44
47
RenderContext (style: Style .fromTextStyle (Theme .of (context).textTheme.body1)),
45
48
cleanedTree,
@@ -153,6 +156,15 @@ class HtmlParser extends StatelessWidget {
153
156
return tree;
154
157
}
155
158
159
+ StyledElement _cascadeStyles (StyledElement tree) {
160
+ tree.children? .forEach ((child) {
161
+ child.style = tree.style.copyOnlyInherited (child.style);
162
+ _cascadeStyles (child);
163
+ });
164
+
165
+ return tree;
166
+ }
167
+
156
168
/// [cleanTree] optimizes the [StyledElement] tree so all [BlockElement] s are
157
169
/// on the first level, redundant levels are collapsed, empty elements are
158
170
/// removed, and specialty elements are processed.
@@ -177,13 +189,11 @@ class HtmlParser extends StatelessWidget {
177
189
if (customRender? .containsKey (tree.name) ?? false ) {
178
190
return WidgetSpan (
179
191
child: ContainerSpan (
180
- thisContext: context,
181
192
newContext: newContext,
182
193
style: tree.style,
183
194
child: customRender[tree.name].call (
184
195
newContext,
185
196
ContainerSpan (
186
- thisContext: context,
187
197
newContext: newContext,
188
198
style: tree.style,
189
199
children: tree.children? .map ((tree) => parseTree (newContext, tree))? .toList () ?? [],
@@ -199,7 +209,6 @@ class HtmlParser extends StatelessWidget {
199
209
return WidgetSpan (
200
210
child: ContainerSpan (
201
211
newContext: newContext,
202
- thisContext: context,
203
212
style: tree.style,
204
213
children: tree.children? .map ((tree) => parseTree (newContext, tree))? .toList () ?? [],
205
214
),
@@ -208,16 +217,16 @@ class HtmlParser extends StatelessWidget {
208
217
return WidgetSpan (
209
218
child: ContainerSpan (
210
219
newContext: newContext,
211
- thisContext: context,
212
220
style: tree.style,
213
221
child: Stack (
214
222
children: < Widget > [
215
- SizedBox (
216
- width: newContext.style.fontSize * 1.5 , //TODO(Sub6Resources): this is a somewhat arbitrary constant.
217
- child: Text (newContext.style.markerContent ?? "" , textAlign: TextAlign .center, style: newContext.style.generateTextStyle ()),
223
+ PositionedDirectional (
224
+ width: 30 , //TODO derive this from list padding.
225
+ start: 0 ,
226
+ child: Text ('${newContext .style .markerContent }\t ' , textAlign: TextAlign .right, style: newContext.style.generateTextStyle ()),
218
227
),
219
228
Padding (
220
- padding: const EdgeInsets .only (left: 30 ),
229
+ padding: EdgeInsets .only (left: 30 ), //TODO derive this from list padding.
221
230
child: RichText (
222
231
text: TextSpan (
223
232
children: tree.children? .map ((tree) => parseTree (newContext, tree))? .toList () ?? [],
@@ -282,27 +291,27 @@ class HtmlParser extends StatelessWidget {
282
291
283
292
///TODO document
284
293
static StyledElement _processInlineWhitespace (StyledElement tree) {
285
- final whitespaceParsingContext = WhitespaceParsingContext (false );
294
+ final whitespaceParsingContext = Context (false );
286
295
tree = _processInlineWhitespaceRecursive (tree, whitespaceParsingContext);
287
296
return tree;
288
297
}
289
298
290
299
///TODO document
291
- static StyledElement _processInlineWhitespaceRecursive (StyledElement tree, WhitespaceParsingContext wpc) {
300
+ static StyledElement _processInlineWhitespaceRecursive (StyledElement tree, Context < bool > wpc) {
292
301
293
302
if (tree.style.display == Display .BLOCK ) {
294
- wpc.inTrailingSpaceContext = false ;
303
+ wpc.data = false ;
295
304
}
296
305
297
306
if (tree is TextContentElement ) {
298
- if (wpc.inTrailingSpaceContext && tree.text.startsWith (' ' )) {
307
+ if (wpc.data && tree.text.startsWith (' ' )) {
299
308
tree.text = tree.text.replaceFirst (' ' , '' );
300
309
}
301
310
302
311
if (tree.text.endsWith (' ' )) {
303
- wpc.inTrailingSpaceContext = true ;
312
+ wpc.data = true ;
304
313
} else {
305
- wpc.inTrailingSpaceContext = false ;
314
+ wpc.data = false ;
306
315
}
307
316
}
308
317
@@ -320,8 +329,8 @@ class HtmlParser extends StatelessWidget {
320
329
/// (4) Replace any instances of two or more spaces with a single space.
321
330
static String _removeUnnecessaryWhitespace (String text) {
322
331
return text
323
- .replaceAll (RegExp ("\ *(?=\n )" ), "" )
324
- .replaceAll (RegExp ("(?:\n )\ *" ), "" )
332
+ .replaceAll (RegExp ("\ *(?=\n )" ), "\n " )
333
+ .replaceAll (RegExp ("(?:\n )\ *" ), "\n " )
325
334
.replaceAll ("\n " , " " )
326
335
.replaceAll ("\t " , " " )
327
336
.replaceAll (RegExp (" {2,}" ), " " );
@@ -330,20 +339,45 @@ class HtmlParser extends StatelessWidget {
330
339
/// [processListCharacters] adds list characters to the front of all list items.
331
340
/// TODO document better
332
341
static StyledElement _processListCharacters (StyledElement tree) {
333
- if (tree.name == "ol" || tree.name == "ul" ) {
334
- for (int i = 0 ; i < tree.children? .length; i++ ) {
335
- if (tree.children[i].name == "li" ) {
336
- switch (tree.style.listStyleType) {
337
- case ListStyleType .DISC :
338
- tree.children[i].style.markerContent = '•' ;
339
- break ;
340
- case ListStyleType .DECIMAL :
341
- tree.children[i].style.markerContent = '${i + 1 }.' ;
342
- break ;
343
- }}
342
+ // if (tree.name == "ol" || tree.name == "ul") {
343
+ // for (int i = 0; i < tree.children?.length; i++) {
344
+ // if (tree.children[i].name == "li") {
345
+ // switch(tree.style.listStyleType) {
346
+ // case ListStyleType.DISC:
347
+ // tree.children[i].style.markerContent = '•';
348
+ // break;
349
+ // case ListStyleType.DECIMAL:
350
+ // tree.children[i].style.markerContent = '${i + 1}.';
351
+ // break;
352
+ // }}
353
+ // }
354
+ // }
355
+ // tree.children?.forEach(_processListCharacters);
356
+ final olStack = ListQueue <Context <int >>();
357
+ tree = _processListCharactersRecursive (tree, olStack);
358
+ return tree;
359
+ }
360
+
361
+ static StyledElement _processListCharactersRecursive (StyledElement tree, ListQueue <Context <int >> olStack) {
362
+ if (tree.name == 'ol' ) {
363
+ olStack.add (Context (0 ));
364
+ } else if (tree.style.display == Display .LIST_ITEM ) {
365
+ switch (tree.style.listStyleType) {
366
+ case ListStyleType .DISC :
367
+ tree.style.markerContent = '•' ;
368
+ break ;
369
+ case ListStyleType .DECIMAL :
370
+ olStack.last.data += 1 ;
371
+ tree.style.markerContent = '${olStack .last .data }.' ;
344
372
}
345
373
}
346
- tree.children? .forEach (_processListCharacters);
374
+
375
+ tree.children? .forEach ((e) => _processListCharactersRecursive (e, olStack));
376
+
377
+ if (tree.name == 'ol' ) {
378
+ olStack.removeLast ();
379
+ }
380
+
347
381
return tree;
348
382
}
349
383
@@ -463,6 +497,12 @@ class HtmlParser extends StatelessWidget {
463
497
toRemove.add (child);
464
498
} else if (child is TextContentElement && (child.text.isEmpty)) {
465
499
toRemove.add (child);
500
+ } else if (child is TextContentElement &&
501
+ child.style.whiteSpace != WhiteSpace .PRE &&
502
+ tree.style.display == Display .BLOCK &&
503
+ child.text.trim ().isEmpty) {
504
+ //TODO should this be moved to the whitespace functions?
505
+ toRemove.add (child);
466
506
} else {
467
507
_removeEmptyElements (child);
468
508
}
@@ -482,26 +522,17 @@ class RenderContext {
482
522
});
483
523
}
484
524
485
- ///TODO document
486
- class WhitespaceParsingContext {
487
- bool inTrailingSpaceContext;
488
-
489
- WhitespaceParsingContext (this .inTrailingSpaceContext);
490
- }
491
-
492
525
///TODO document
493
526
class ContainerSpan extends StatelessWidget {
494
527
final Widget child;
495
528
final List <InlineSpan > children;
496
529
final Style style;
497
- final RenderContext thisContext;
498
530
final RenderContext newContext;
499
531
500
532
ContainerSpan ({
501
533
this .child,
502
534
this .children,
503
535
this .style,
504
- this .thisContext,
505
536
this .newContext,
506
537
});
507
538
@@ -520,7 +551,7 @@ class ContainerSpan extends StatelessWidget {
520
551
child: child ??
521
552
RichText (
522
553
text: TextSpan (
523
- style: thisContext .style. merge (style) .generateTextStyle (),
554
+ style: newContext .style.generateTextStyle (),
524
555
children: children,
525
556
),
526
557
),
0 commit comments