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 |