import 'package:flutter/material.dart'; import 'package:html/parser.dart'; import 'package:html/dom.dart' as dom; import 'package:url_launcher/url_launcher.dart'; import 'package:voyagehandbook/api/classes.dart'; import 'package:voyagehandbook/util/styles.dart'; final _ignoredTags = ["style", "script"]; /// Used to create [TextSpan] from raw HTML from WM API List renderFromPageHTML(RawPage page) { var out = [ const SizedBox( height: 10, ), Text( page.title, textAlign: TextAlign.center, style: PageStyles.h1, ), const SizedBox( height: 10, ), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Text("From"), const SizedBox( width: 5, ), GestureDetector( onTap: () => launchUrl( Uri.parse("https://en.wikivoyage.org/wiki/${page.key}"), mode: LaunchMode.externalApplication), child: const Text( "WikiVoyage", style: TextStyle( decoration: TextDecoration.underline, fontWeight: FontWeight.bold, ), ), ), ], ), Row( children: [ const Text("under"), const SizedBox( width: 5, ), GestureDetector( onTap: () => launchUrl(Uri.parse(page.license.url), mode: LaunchMode.externalApplication), child: Text( page.license.title, style: const TextStyle( decoration: TextDecoration.underline, ), ), ) ], ) ]; var document = parse(page.html); var sections = document.body!.getElementsByTagName("section"); for (var sec in sections) { if (sec.localName == "section") { out.addAll(_renderSection(sec)); } } return out; } List _renderSection(dom.Element sec) { var out = []; // Get Section Title var headings = sec.children .where((element) => ["h2", "h3", "h4", "h5"].contains(element.localName)); var sectionTitle = (headings.isNotEmpty) ? headings.first : null; if (sectionTitle != null) { switch (sectionTitle.localName) { case "h2": out.add( const SizedBox( height: 10, ), ); out.add(Text(sectionTitle.text, style: PageStyles.h2)); break; case "h3": out.add( Align( alignment: Alignment.centerLeft, child: Text( sectionTitle.text, style: PageStyles.h3, ), ), ); break; case "h4": out.add( Align( alignment: Alignment.centerLeft, child: Text(sectionTitle.text, style: PageStyles.h4), ), ); break; case "h5": out.add( Align( alignment: Alignment.centerLeft, child: Text(sectionTitle.text, style: PageStyles.h5), ), ); break; default: out.add( Align( alignment: Alignment.centerLeft, child: Text(sectionTitle.text), ), ); break; } // out.add( // const SizedBox( // height: 5, // ), // ); } // create TextSpans from text content for (var element in sec.children) { // Go through all section's children for (var t in _ignoredTags) { var ignored = element.getElementsByTagName(t); if (ignored.isNotEmpty) { // Remove ignored tags for (var element in ignored) { element.remove(); } } } switch (element.localName) { case "p": case "link": var paraSpans = []; var input = element.innerHtml .replaceAll(RegExp(r"<(?!(?:\/b)|(?:\/i)|b|i).+?>"), "") .replaceAll("\\n", "\r"); var noFormatting = input.split(RegExp(r"(?:.+?<\/b>)|.+?<\/i>")); var needToFormat = RegExp(r"(?:.+?<\/b>)|.+?<\/i>") .allMatches(input) .toList(); for (var s in needToFormat) { paraSpans.add(TextSpan( text: noFormatting[ needToFormat.indexOf(s)])); // add text before styled var raw = s.group(0)!; paraSpans.add( TextSpan( text: RegExp(r">(.+?)<").firstMatch(raw)!.group(1), style: TextStyle( fontWeight: (raw.contains("