parse method

List<TextSpan> parse()

Converts HTML content to a list of TextSpan objects

Implementation

List<TextSpan> parse() {
  List<TextSpan> spans = <TextSpan>[];
  for (final XmlEvent event in _events) {
    if (event is XmlStartElementEvent) {
      if (!event.isSelfClosing) {
        String styles = '';
        final String tagName = event.name.toLowerCase();
        TextStyle? overrideStyles;
        final double? defaultFontSize = defaultTextStyle?.fontSize;

        if (overrideStyleMap?.containsKey(tagName) == true) {
          overrideStyles = overrideStyleMap?[tagName];
        }

        switch (tagName) {
          case 'h1':
            double h1;
            if (defaultFontSize == null) {
              h1 = Theme.of(context).textTheme.headline5?.fontSize ?? 24.0;
            } else {
              h1 = defaultFontSize * 2;
            }
            styles = 'font-size: ${h1}px;';
            break;

          case 'h2':
            double h2;
            if (defaultFontSize == null) {
              h2 = Theme.of(context).textTheme.headline6?.fontSize ?? 20.0;
            } else {
              h2 = defaultFontSize * 1.5;
            }
            styles = 'font-size: ${h2}px; font-weight: medium;';
            break;

          case 'h3':
            double h3;
            if (defaultFontSize == null) {
              h3 = Theme.of(context).textTheme.subtitle1?.fontSize ?? 16.0;
            } else {
              h3 = defaultFontSize * 1.17;
            }
            styles = 'font-size: ${h3}px;';
            break;

          case 'h4':
            double h4;
            if (defaultFontSize == null) {
              h4 = Theme.of(context).textTheme.bodyText1?.fontSize ?? 16.0;
            } else {
              h4 = defaultFontSize;
            }
            styles = 'font-size: ${h4}px; font-weight: medium;';
            break;

          case 'h5':
            double h5;
            if (defaultFontSize == null) {
              h5 = Theme.of(context).textTheme.bodyText1?.fontSize ?? 16.0;
            } else {
              h5 = defaultFontSize * .83;
            }
            styles = 'font-size: ${h5}px; font-weight: bold;';
            break;

          case 'h6':
            double h6;
            if (defaultFontSize == null) {
              h6 = Theme.of(context).textTheme.bodyText2?.fontSize ?? 14.0;
            } else {
              h6 = defaultFontSize * .67;
            }
            styles = 'font-size: ${h6}px; font-weight: bold;';
            break;

          case 'b':
            styles = 'font-weight: bold;';
            break;

          case 'strong':
            styles = 'font-weight: bold;';
            break;

          case 'i':
            styles = 'font-style: italic;';
            break;

          case 'em':
            styles = 'font-style: italic;';
            break;

          case 'u':
            styles = 'text-decoration: underline;';
            break;

          case 'strike':
            styles = 'text-decoration: line-through;';
            break;

          case 'del':
            styles = 'text-decoration: line-through;';
            break;

          case 's':
            styles = 'text-decoration: line-through;';
            break;

          case 'a':
            styles =
                '''visit_link:__#TO_GET#__; text-decoration: underline; color: #4287f5;''';
            break;

// dropping partial support for ul-li bullets
//            case 'li':
//              styles = 'list_item:ul;';
//              break;
//              RichText(
//                text: TextSpan(
//                  text:'',
//                  style: TextStyle(color: Colors.black),
//                  children: <InlineSpan>[
//                    WidgetSpan(
//                        alignment: PlaceholderAlignment.baseline,
//                        baseline: TextBaseline.alphabetic,
//                        child: Row(
//                          crossAxisAlignment: CrossAxisAlignment.start,
//                          children: <Widget>[
//                            Text( '• '),
//                            SizedBox(width: 20,),
//                            Expanded(child: Text('Example text',)),
//                          ],
//                        )
//                    ),
//                  ],
//                ),
//              )
        }

        for (final XmlEventAttribute attribute in event.attributes) {
          if (attribute.name == 'style') {
            styles = '$styles;${attribute.value}';
          } else if (attribute.name == 'href') {
            styles = styles.replaceFirst('__#TO_GET#__',
                attribute.value.replaceAll(':', '__#COLON#__'));
          }
        }
        _stack.add(_Tag(event.name, styles, overrideStyles));
      } else {
        if (event.name == 'br') {
          spans.add(const TextSpan(text: '\n'));
        }
      }
    }

    // TODO: see if there is a better way to add space after these tags
    // maybe use widget spans
    if (event is XmlEndElementEvent) {
      if (event.name == 'p' ||
          event.name == 'h1' ||
          event.name == 'h2' ||
          event.name == 'h3' ||
          event.name == 'h4' ||
          event.name == 'h5' ||
          event.name == 'h6' ||
          event.name == 'div') {
        spans.add(const TextSpan(text: '\n\n'));
      } else if (event.name == 'li') {
        spans.add(const TextSpan(text: '\n'));
      } else if (event.name == 'ul' || event.name == 'ol') {
        spans.add(const TextSpan(text: '\n'));
      }

      if (_stack.isNotEmpty) {
        final _Tag top = _stack.removeLast();
        if (top.name != event.name) {
          debugPrint('Malformed HTML');
          return const <TextSpan>[];
        }
      } else {
        debugPrint('Malformed HTML. Starting TAG missing');
      }
    }

    if (event is XmlTextEvent) {
      final TextSpan currentSpan = _handleText(event.text);
      if (currentSpan.text?.isNotEmpty == true) {
        spans.add(currentSpan);
      }
    }
  }

  // removing all extra new line textSpans to avoid space at the bottom
  if (spans.isNotEmpty) {
    final List<TextSpan> reversed = spans.reversed.toList();

    while (reversed.isNotEmpty &&
        (reversed.first.text == '\n\n' || reversed.first.text == '\n')) {
      reversed.removeAt(0);
    }

    spans = reversed.reversed.toList();
  } else {
    debugPrint('Empty HTML content');
  }
  return spans;
}