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