html_renderer.dart 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
  2. // for details. All rights reserved. Use of this source code is governed by a
  3. // BSD-style license that can be found in the LICENSE file.
  4. import 'ast.dart';
  5. import 'block_parser.dart';
  6. import 'document.dart';
  7. import 'extension_set.dart';
  8. import 'inline_parser.dart';
  9. /// Converts the given string of Markdown to HTML.
  10. String markdownToHtml(String markdown,
  11. {Iterable<BlockSyntax>? blockSyntaxes,
  12. Iterable<InlineSyntax>? inlineSyntaxes,
  13. ExtensionSet? extensionSet,
  14. Resolver? linkResolver,
  15. Resolver? imageLinkResolver,
  16. bool inlineOnly = false}) {
  17. final document = Document(
  18. blockSyntaxes: blockSyntaxes,
  19. inlineSyntaxes: inlineSyntaxes,
  20. extensionSet: extensionSet,
  21. linkResolver: linkResolver,
  22. imageLinkResolver: imageLinkResolver);
  23. if (inlineOnly) {
  24. return renderToHtml(document.parseInline(markdown)!);
  25. }
  26. // Replace windows line endings with unix line endings, and split.
  27. final lines = markdown.replaceAll('\r\n', '\n').split('\n');
  28. return '${renderToHtml(document.parseLines(lines))}\n';
  29. }
  30. /// Renders [nodes] to HTML.
  31. String renderToHtml(List<Node> nodes) => HtmlRenderer().render(nodes);
  32. /// Translates a parsed AST to HTML.
  33. class HtmlRenderer implements NodeVisitor {
  34. HtmlRenderer();
  35. static final _blockTags = RegExp('blockquote|h1|h2|h3|h4|h5|h6|hr|p|pre');
  36. late StringBuffer buffer;
  37. late Set<String> uniqueIds;
  38. String render(List<Node> nodes) {
  39. buffer = StringBuffer();
  40. uniqueIds = <String>{};
  41. for (final node in nodes) {
  42. node.accept(this);
  43. }
  44. return buffer.toString();
  45. }
  46. @override
  47. void visitText(Text text) {
  48. buffer.write(text.text);
  49. }
  50. @override
  51. bool visitElementBefore(Element element) {
  52. // Hackish. Separate block-level elements with newlines.
  53. if (buffer.isNotEmpty && _blockTags.firstMatch(element.tag) != null) {
  54. buffer.write('\n');
  55. }
  56. buffer.write('<${element.tag}');
  57. // Sort the keys so that we generate stable output.
  58. final attributeNames = element.attributes.keys.toList()
  59. ..sort((a, b) => a.compareTo(b));
  60. for (final name in attributeNames) {
  61. buffer.write(' $name="${element.attributes[name]}"');
  62. }
  63. // attach header anchor ids generated from text
  64. if (element.generatedId != null) {
  65. buffer.write(' id="${uniquifyId(element.generatedId!)}"');
  66. }
  67. if (element.isEmpty) {
  68. // Empty element like <hr/>.
  69. buffer.write(' />');
  70. if (element.tag == 'br') {
  71. buffer.write('\n');
  72. }
  73. return false;
  74. } else {
  75. buffer.write('>');
  76. return true;
  77. }
  78. }
  79. @override
  80. void visitElementAfter(Element element) {
  81. buffer.write('</${element.tag}>');
  82. }
  83. /// Uniquifies an id generated from text.
  84. String uniquifyId(String id) {
  85. if (!uniqueIds.contains(id)) {
  86. uniqueIds.add(id);
  87. return id;
  88. }
  89. var suffix = 2;
  90. var suffixedId = '$id-$suffix';
  91. while (uniqueIds.contains(suffixedId)) {
  92. suffixedId = '$id-${suffix++}';
  93. }
  94. uniqueIds.add(suffixedId);
  95. return suffixedId;
  96. }
  97. }