| OLD | NEW |
| (Empty) | |
| 1 // Copyright (c) 2014, 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 |
| 5 /** |
| 6 * Code for converting HTML into text, for use in doc comments. |
| 7 */ |
| 8 library textFormatter; |
| 9 |
| 10 import 'package:html5lib/dom.dart' as dom; |
| 11 |
| 12 import 'codegen_tools.dart'; |
| 13 |
| 14 /** |
| 15 * Convert the HTML in [desc] into text, word wrapping at width [width]. |
| 16 * |
| 17 * If [javadocStyle] is true, then the output is compatable with Javadoc, |
| 18 * which understands certain HTML constructs. |
| 19 */ |
| 20 String nodesToText(List<dom.Node> desc, int width, bool javadocStyle) { |
| 21 _TextFormatter formatter = new _TextFormatter(width, javadocStyle); |
| 22 return formatter.collectCode(() { |
| 23 formatter.addAll(desc); |
| 24 formatter.lineBreak(false); |
| 25 }); |
| 26 } |
| 27 |
| 28 final RegExp whitespace = new RegExp(r'\s'); |
| 29 |
| 30 /** |
| 31 * Engine that transforms HTML to text. The input HTML is processed one |
| 32 * character at a time, gathering characters into words and words into lines. |
| 33 */ |
| 34 class _TextFormatter extends CodeGenerator { |
| 35 /** |
| 36 * Word-wrapping width. |
| 37 */ |
| 38 final int width; |
| 39 |
| 40 /** |
| 41 * The word currently being gathered. |
| 42 */ |
| 43 String word = ''; |
| 44 |
| 45 /** |
| 46 * The line currently being gathered. |
| 47 */ |
| 48 String line = ''; |
| 49 |
| 50 /** |
| 51 * True if a blank line should be inserted before the next word. |
| 52 */ |
| 53 bool verticalSpaceNeeded = false; |
| 54 |
| 55 /** |
| 56 * True if no text has been output yet. This suppresses blank lines. |
| 57 */ |
| 58 bool atStart = true; |
| 59 |
| 60 /** |
| 61 * True if we are processing a <pre> element, thus whitespace should be |
| 62 * preserved. |
| 63 */ |
| 64 bool preserveSpaces = false; |
| 65 |
| 66 /** |
| 67 * True if the output should be Javadoc compatible. |
| 68 */ |
| 69 final bool javadocStyle; |
| 70 |
| 71 _TextFormatter(this.width, this.javadocStyle); |
| 72 |
| 73 /** |
| 74 * Escape the given character for HTML. |
| 75 */ |
| 76 String escape(String char) { |
| 77 if (javadocStyle) { |
| 78 switch (char) { |
| 79 case '<': |
| 80 return '<'; |
| 81 case '>': |
| 82 return '>'; |
| 83 case '&': |
| 84 return '&'; |
| 85 } |
| 86 } |
| 87 return char; |
| 88 } |
| 89 |
| 90 /** |
| 91 * Process an HTML node. |
| 92 */ |
| 93 void add(dom.Node node) { |
| 94 if (node is dom.Text) { |
| 95 for (String char in node.text.split('')) { |
| 96 if (preserveSpaces) { |
| 97 wordBreak(); |
| 98 write(escape(char)); |
| 99 } else if (whitespace.hasMatch(char)) { |
| 100 wordBreak(); |
| 101 } else { |
| 102 resolveVerticalSpace(); |
| 103 word += escape(char); |
| 104 } |
| 105 } |
| 106 } else if (node is dom.Element) { |
| 107 switch (node.localName) { |
| 108 case 'br': |
| 109 lineBreak(false); |
| 110 break; |
| 111 case 'dl': |
| 112 case 'dt': |
| 113 case 'h1': |
| 114 case 'h2': |
| 115 case 'h3': |
| 116 case 'h4': |
| 117 case 'p': |
| 118 lineBreak(true); |
| 119 addAll(node.nodes); |
| 120 lineBreak(true); |
| 121 break; |
| 122 case 'ul': |
| 123 lineBreak(false); |
| 124 addAll(node.nodes); |
| 125 lineBreak(false); |
| 126 break; |
| 127 case 'li': |
| 128 lineBreak(false); |
| 129 resolveVerticalSpace(); |
| 130 indentSpecial('- ', ' ', () { |
| 131 addAll(node.nodes); |
| 132 lineBreak(false); |
| 133 }); |
| 134 break; |
| 135 case 'dd': |
| 136 lineBreak(true); |
| 137 indent(() { |
| 138 addAll(node.nodes); |
| 139 lineBreak(true); |
| 140 }); |
| 141 break; |
| 142 case 'pre': |
| 143 lineBreak(false); |
| 144 resolveVerticalSpace(); |
| 145 if (javadocStyle) { |
| 146 writeln('<pre>'); |
| 147 } |
| 148 bool oldPreserveSpaces = preserveSpaces; |
| 149 try { |
| 150 preserveSpaces = true; |
| 151 addAll(node.nodes); |
| 152 } finally { |
| 153 preserveSpaces = oldPreserveSpaces; |
| 154 } |
| 155 writeln(); |
| 156 if (javadocStyle) { |
| 157 writeln('</pre>'); |
| 158 } |
| 159 lineBreak(false); |
| 160 break; |
| 161 case 'a': |
| 162 case 'b': |
| 163 case 'body': |
| 164 case 'html': |
| 165 case 'i': |
| 166 case 'span': |
| 167 case 'tt': |
| 168 addAll(node.nodes); |
| 169 break; |
| 170 case 'head': |
| 171 break; |
| 172 default: |
| 173 throw new Exception('Unexpected HTML element: ${node.localName}'); |
| 174 } |
| 175 } else { |
| 176 throw new Exception('Unexpected HTML: $node'); |
| 177 } |
| 178 } |
| 179 |
| 180 /** |
| 181 * Insert vertical space if necessary. |
| 182 */ |
| 183 void resolveVerticalSpace() { |
| 184 if (verticalSpaceNeeded) { |
| 185 writeln(); |
| 186 verticalSpaceNeeded = false; |
| 187 } |
| 188 } |
| 189 |
| 190 /** |
| 191 * Terminate the current word, if a word is in progress. |
| 192 */ |
| 193 void wordBreak() { |
| 194 if (word.isNotEmpty) { |
| 195 atStart = false; |
| 196 if (line.isNotEmpty) { |
| 197 if (indentWidth + line.length + 1 + word.length <= width) |
| 198 { |
| 199 line += ' $word'; |
| 200 } else { |
| 201 writeln(line); |
| 202 line = word; |
| 203 } |
| 204 } else { |
| 205 line = word; |
| 206 } |
| 207 word = ''; |
| 208 } |
| 209 } |
| 210 |
| 211 /** |
| 212 * Terminate the current word and/or line, if either is in progress. |
| 213 */ |
| 214 void lineBreak(bool gap) { |
| 215 wordBreak(); |
| 216 if (line.isNotEmpty) { |
| 217 writeln(line); |
| 218 line = ''; |
| 219 } |
| 220 if (gap && !atStart) { |
| 221 verticalSpaceNeeded = true; |
| 222 } |
| 223 } |
| 224 |
| 225 /** |
| 226 * Process a list of HTML nodes. |
| 227 */ |
| 228 void addAll(List<dom.Node> nodes) { |
| 229 for (dom.Node node in nodes) { |
| 230 add(node); |
| 231 } |
| 232 } |
| 233 } |
| OLD | NEW |