Skip to content
Merged
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
6 changes: 4 additions & 2 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,11 @@ const htmlData = r"""
<li>a</li>
<li>nested</li>
<li>unordered
<ol>
<ol style="list-style-type: lower-alpha;" start="5">
<li>With a nested</li>
<li>ordered list.</li>
<li>ordered list</li>
<li>with a lower alpha list style</li>
<li>starting at letter e</li>
</ol>
</li>
<li>list</li>
Expand Down
110 changes: 105 additions & 5 deletions lib/html_parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import 'package:flutter_html/src/utils.dart';
import 'package:flutter_html/style.dart';
import 'package:html/dom.dart' as dom;
import 'package:html/parser.dart' as htmlparser;
import 'package:numerus/numerus.dart';
import 'package:webview_flutter/webview_flutter.dart';

typedef OnTap = void Function(
Expand Down Expand Up @@ -575,29 +576,107 @@ class HtmlParser extends StatelessWidget {
///
/// The function uses the [_processListCharactersRecursive] function to do most of its work.
static StyledElement _processListCharacters(StyledElement tree) {
final olStack = ListQueue<Context<int>>();
final olStack = ListQueue<Context>();
tree = _processListCharactersRecursive(tree, olStack);
return tree;
}

/// [_processListCharactersRecursive] uses a Stack of integers to properly number and
/// bullet all list items according to the [ListStyleType] they have been given.
static StyledElement _processListCharactersRecursive(
StyledElement tree, ListQueue<Context<int>> olStack) {
if (tree.name == 'ol') {
olStack.add(Context((tree.attributes['start'] != null ? int.tryParse(tree.attributes['start'] ?? "") ?? 1 : 1) - 1));
StyledElement tree, ListQueue<Context> olStack) {
if (tree.name == 'ol' && tree.style.listStyleType != null) {
switch (tree.style.listStyleType!) {
case ListStyleType.LOWER_LATIN:
case ListStyleType.LOWER_ALPHA:
case ListStyleType.UPPER_LATIN:
case ListStyleType.UPPER_ALPHA:
olStack.add(Context<String>('a'));
if ((tree.attributes['start'] != null ? int.tryParse(tree.attributes['start']!) : null) != null) {
var start = int.tryParse(tree.attributes['start']!) ?? 1;
var x = 1;
while (x < start) {
olStack.last.data = olStack.last.data.toString().nextLetter();
x++;
}
}
break;
default:
olStack.add(Context<int>((tree.attributes['start'] != null ? int.tryParse(tree.attributes['start'] ?? "") ?? 1 : 1) - 1));
break;
}
} else if (tree.style.display == Display.LIST_ITEM && tree.style.listStyleType != null) {
switch (tree.style.listStyleType!) {
case ListStyleType.CIRCLE:
tree.style.markerContent = '○';
break;
case ListStyleType.SQUARE:
tree.style.markerContent = '■';
break;
case ListStyleType.DISC:
tree.style.markerContent = '•';
break;
case ListStyleType.DECIMAL:
if (olStack.isEmpty) {
olStack.add(Context((tree.attributes['start'] != null ? int.tryParse(tree.attributes['start'] ?? "") ?? 1 : 1) - 1));
olStack.add(Context<int>((tree.attributes['start'] != null ? int.tryParse(tree.attributes['start'] ?? "") ?? 1 : 1) - 1));
}
olStack.last.data += 1;
tree.style.markerContent = '${olStack.last.data}.';
break;
case ListStyleType.LOWER_LATIN:
case ListStyleType.LOWER_ALPHA:
if (olStack.isEmpty) {
olStack.add(Context<String>('a'));
if ((tree.attributes['start'] != null ? int.tryParse(tree.attributes['start']!) : null) != null) {
var start = int.tryParse(tree.attributes['start']!) ?? 1;
var x = 1;
while (x < start) {
olStack.last.data = olStack.last.data.toString().nextLetter();
x++;
}
}
}
tree.style.markerContent = olStack.last.data.toString() + ".";
olStack.last.data = olStack.last.data.toString().nextLetter();
break;
case ListStyleType.UPPER_LATIN:
case ListStyleType.UPPER_ALPHA:
if (olStack.isEmpty) {
olStack.add(Context<String>('a'));
if ((tree.attributes['start'] != null ? int.tryParse(tree.attributes['start']!) : null) != null) {
var start = int.tryParse(tree.attributes['start']!) ?? 1;
var x = 1;
while (x < start) {
olStack.last.data = olStack.last.data.toString().nextLetter();
x++;
}
}
}
tree.style.markerContent = olStack.last.data.toString().toUpperCase() + ".";
olStack.last.data = olStack.last.data.toString().nextLetter();
break;
case ListStyleType.LOWER_ROMAN:
if (olStack.isEmpty) {
olStack.add(Context<int>((tree.attributes['start'] != null ? int.tryParse(tree.attributes['start'] ?? "") ?? 1 : 1) - 1));
}
olStack.last.data += 1;
if (olStack.last.data <= 0) {
tree.style.markerContent = '${olStack.last.data}.';
} else {
tree.style.markerContent = (olStack.last.data as int).toRomanNumeralString()!.toLowerCase() + ".";
}
break;
case ListStyleType.UPPER_ROMAN:
if (olStack.isEmpty) {
olStack.add(Context<int>((tree.attributes['start'] != null ? int.tryParse(tree.attributes['start'] ?? "") ?? 1 : 1) - 1));
}
olStack.last.data += 1;
if (olStack.last.data <= 0) {
tree.style.markerContent = '${olStack.last.data}.';
} else {
tree.style.markerContent = (olStack.last.data as int).toRomanNumeralString()! + ".";
}
break;
}
}

Expand Down Expand Up @@ -889,3 +968,24 @@ class StyledText extends StatelessWidget {
return null;
}
}

extension IterateLetters on String {
String nextLetter() {
String s = this.toLowerCase();
if (s == "z") {
return String.fromCharCode(s.codeUnitAt(0) - 25) + String.fromCharCode(s.codeUnitAt(0) - 25); // AA or aa
} else {
var lastChar = s.substring(s.length - 1);
var sub = s.substring(0, s.length - 1);
if (lastChar == "z") {
// If a string of length > 1 ends in Z/z,
// increment the string (excluding the last Z/z) recursively,
// and append A/a (depending on casing) to it
return sub.nextLetter() + 'a';
} else {
// (take till last char) append with (increment last char)
return sub + String.fromCharCode(lastChar.codeUnitAt(0) + 1);
}
}
}
}
31 changes: 31 additions & 0 deletions lib/src/css_parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,11 @@ Style declarationsToStyle(Map<String, List<css.Expression>> declarations) {
case 'font-weight':
style.fontWeight = ExpressionMapping.expressionToFontWeight(value.first);
break;
case 'list-style-type':
if (value.first is css.LiteralTerm) {
style.listStyleType = ExpressionMapping.expressionToListStyleType(value.first as css.LiteralTerm) ?? style.listStyleType;
}
break;
case 'margin':
List<css.LiteralTerm>? marginLengths = value.whereType<css.LiteralTerm>().toList();
/// List<css.LiteralTerm> might include other values than the ones we want for margin length, so make sure to remove those before passing it to [ExpressionMapping]
Expand Down Expand Up @@ -655,6 +660,32 @@ class ExpressionMapping {
return LineHeight.normal;
}

static ListStyleType? expressionToListStyleType(css.LiteralTerm value) {
switch (value.text) {
case 'disc':
return ListStyleType.DISC;
case 'circle':
return ListStyleType.CIRCLE;
case 'decimal':
return ListStyleType.DECIMAL;
case 'lower-alpha':
return ListStyleType.LOWER_ALPHA;
case 'lower-latin':
return ListStyleType.LOWER_LATIN;
case 'lower-roman':
return ListStyleType.LOWER_ROMAN;
case 'square':
return ListStyleType.SQUARE;
case 'upper-alpha':
return ListStyleType.UPPER_ALPHA;
case 'upper-latin':
return ListStyleType.UPPER_LATIN;
case 'upper-roman':
return ListStyleType.UPPER_ROMAN;
}
return null;
}

static List<double?> expressionToPadding(List<css.Expression>? lengths) {
double? left;
double? right;
Expand Down
8 changes: 8 additions & 0 deletions lib/style.dart
Original file line number Diff line number Diff line change
Expand Up @@ -520,8 +520,16 @@ class LineHeight {
}

enum ListStyleType {
LOWER_ALPHA,
UPPER_ALPHA,
LOWER_LATIN,
UPPER_LATIN,
CIRCLE,
DISC,
DECIMAL,
LOWER_ROMAN,
UPPER_ROMAN,
SQUARE,
}

enum ListStylePosition {
Expand Down
3 changes: 3 additions & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ dependencies:
# plugin for firstWhereOrNull extension on lists
collection: '>=1.15.0 <2.0.0'

# plugin to convert integers to numerals
numerus: '>=1.1.1 <2.0.0'

flutter:
sdk: flutter

Expand Down