Skip to content
Newer
Older
100644 660 lines (557 sloc) 21.4 KB
7a327b8 @aaronsw initial commit
authored
1 #!/usr/bin/env python
2 """html2text: Turn HTML into equivalent Markdown-structured text."""
d9bf7d6 @aaronsw 3.02
authored
3 __version__ = "3.02"
7a327b8 @aaronsw initial commit
authored
4 __author__ = "Aaron Swartz (me@aaronsw.com)"
5 __copyright__ = "(C) 2004-2008 Aaron Swartz. GNU GPL 3."
6 __contributors__ = ["Martin 'Joey' Schulze", "Ricardo Reyes", "Kevin Jay North"]
7
8 # TODO:
9 # Support decoded entities with unifiable.
10
6c3406e @aaronsw add forward compatibility with Python3
authored
11 try:
12 True
13 except NameError:
14 setattr(__builtins__, 'True', 1)
15 setattr(__builtins__, 'False', 0)
16
17 def has_key(x, y):
18 if hasattr(x, 'has_key'): return x.has_key(y)
19 else: return y in x
20
21 try:
22 import htmlentitydefs
23 import urlparse
24 import HTMLParser
25 except ImportError: #Python3
26 import html.entities as htmlentitydefs
27 import urllib.parse as urlparse
28 import html.parser as HTMLParser
29 try: #Python3
30 import urllib.request as urllib
31 except:
32 import urllib
b7e2f15 @stefanor Use optparse for parsing and checking arguments
stefanor authored
33 import optparse, re, sys, codecs, types
7a327b8 @aaronsw initial commit
authored
34
35 try: from textwrap import wrap
36 except: pass
37
38 # Use Unicode characters instead of their ascii psuedo-replacements
39 UNICODE_SNOB = 0
40
41 # Put the links after each paragraph instead of at the end.
42 LINKS_EACH_PARAGRAPH = 0
43
44 # Wrap long lines at position. 0 for no wrapping. (Requires Python 2.3.)
45 BODY_WIDTH = 78
46
47 # Don't show internal links (href="#local-anchor") -- corresponding link targets
48 # won't be visible in the plain text file anyway.
2d4806b @maketolearn INLINE_LINKS setting so that links can be in inline format instead of…
maketolearn authored
49 SKIP_INTERNAL_LINKS = True
50
51 # Use inline, rather than reference, formatting for images and links
52 INLINE_LINKS = True
7a327b8 @aaronsw initial commit
authored
53
57f8c0d @nushoin added an option to set the amount of pixels Google indents nested lists
nushoin authored
54 # Number of pixels Google indents nested lists
55 GOOGLE_LIST_INDENT = 36
56
7a327b8 @aaronsw initial commit
authored
57 ### Entity Nonsense ###
58
59 def name2cp(k):
60 if k == 'apos': return ord("'")
61 if hasattr(htmlentitydefs, "name2codepoint"): # requires Python 2.3
62 return htmlentitydefs.name2codepoint[k]
63 else:
64 k = htmlentitydefs.entitydefs[k]
65 if k.startswith("&#") and k.endswith(";"): return int(k[2:-1]) # not in latin-1
66 return ord(codecs.latin_1_decode(k)[0])
67
68 unifiable = {'rsquo':"'", 'lsquo':"'", 'rdquo':'"', 'ldquo':'"',
69 'copy':'(C)', 'mdash':'--', 'nbsp':' ', 'rarr':'->', 'larr':'<-', 'middot':'*',
70 'ndash':'-', 'oelig':'oe', 'aelig':'ae',
71 'agrave':'a', 'aacute':'a', 'acirc':'a', 'atilde':'a', 'auml':'a', 'aring':'a',
72 'egrave':'e', 'eacute':'e', 'ecirc':'e', 'euml':'e',
73 'igrave':'i', 'iacute':'i', 'icirc':'i', 'iuml':'i',
74 'ograve':'o', 'oacute':'o', 'ocirc':'o', 'otilde':'o', 'ouml':'o',
75 'ugrave':'u', 'uacute':'u', 'ucirc':'u', 'uuml':'u'}
76
77 unifiable_n = {}
78
79 for k in unifiable.keys():
80 unifiable_n[name2cp(k)] = unifiable[k]
81
82 def charref(name):
83 if name[0] in ['x','X']:
84 c = int(name[1:], 16)
85 else:
86 c = int(name)
87
88 if not UNICODE_SNOB and c in unifiable_n.keys():
89 return unifiable_n[c]
90 else:
6c3406e @aaronsw add forward compatibility with Python3
authored
91 try:
92 return unichr(c)
93 except NameError: #Python3
94 return chr(c)
7a327b8 @aaronsw initial commit
authored
95
96 def entityref(c):
97 if not UNICODE_SNOB and c in unifiable.keys():
98 return unifiable[c]
99 else:
100 try: name2cp(c)
00c7aa2 @aaronsw 3.01: fix bug with unknown entities
authored
101 except KeyError: return "&" + c + ';'
6c3406e @aaronsw add forward compatibility with Python3
authored
102 else:
103 try:
104 return unichr(name2cp(c))
105 except NameError: #Python3
106 return chr(name2cp(c))
7a327b8 @aaronsw initial commit
authored
107
108 def replaceEntities(s):
109 s = s.group(1)
110 if s[0] == "#":
111 return charref(s[1:])
112 else: return entityref(s)
113
114 r_unescape = re.compile(r"&(#?[xX]?(?:[0-9a-fA-F]+|\w{1,8}));")
115 def unescape(s):
116 return r_unescape.sub(replaceEntities, s)
117
118 ### End Entity Nonsense ###
119
120 def onlywhite(line):
121 """Return true if the line does only consist of whitespace characters."""
122 for c in line:
123 if c is not ' ' and c is not ' ':
124 return c is ' '
125 return line
126
127 def optwrap(text):
128 """Wrap all paragraphs in the provided text."""
129 if not BODY_WIDTH:
130 return text
131
132 assert wrap, "Requires Python 2.3."
133 result = ''
134 newlines = 0
135 for para in text.split("\n"):
136 if len(para) > 0:
d6bcc60 @nene Fix double-newlines inside code blocks.
nene authored
137 if para[0] != ' ' and para[0] != '-' and para[0] != '*':
7a327b8 @aaronsw initial commit
authored
138 for line in wrap(para, BODY_WIDTH):
139 result += line + "\n"
140 result += "\n"
141 newlines = 2
142 else:
143 if not onlywhite(para):
144 result += para + "\n"
145 newlines = 1
146 else:
147 if newlines < 2:
148 result += "\n"
149 newlines += 1
150 return result
151
152 def hn(tag):
153 if tag[0] == 'h' and len(tag) == 2:
154 try:
155 n = int(tag[1])
156 if n in range(1, 10): return n
157 except ValueError: return 0
158
58860ee @nushoin dumb css parser basically working
nushoin authored
159 def dumb_css_parser(data):
09d6a48 @nushoin nested lists handling is more or less complete
nushoin authored
160 """returns a hash of css selectors, each of which contains a hash of css attributes"""
161 # remove @import sentences
162 importIndex = data.find('@import')
163 while importIndex != -1:
164 data = data[0:importIndex] + data[data.find(';', importIndex) + 1:]
165 importIndex = data.find('@import')
166
ff7350c @nushoin handling Google docs emphasized text (bold and italics)
nushoin authored
167 # parse the css. reverted from dictionary compehension in order to support older pythons
ff5e70d @nushoin google docs unordered lists are now displayed as such
nushoin authored
168 elements = [x.split('{') for x in data.split('}') if x.strip() != '']
ff7350c @nushoin handling Google docs emphasized text (bold and italics)
nushoin authored
169 elements = dict([(a.strip(),
170 dict([(x.strip(), y.strip()) for x, y in [z.split(':') for z in b.split(';')]]))
171 for a, b in elements])
09d6a48 @nushoin nested lists handling is more or less complete
nushoin authored
172
58860ee @nushoin dumb css parser basically working
nushoin authored
173 return elements
174
ff5e70d @nushoin google docs unordered lists are now displayed as such
nushoin authored
175 def google_list_style(attrs, style_def):
09d6a48 @nushoin nested lists handling is more or less complete
nushoin authored
176 """finds out whether this is an ordered or unordered list"""
c5e926e @nushoin code cleanup - made attrs a dictionary in handle_tag
nushoin authored
177 for css_class in attrs['class'].split():
09d6a48 @nushoin nested lists handling is more or less complete
nushoin authored
178 list_style = style_def['.' + css_class]['list-style-type']
179 if list_style in ['disc', 'circle', 'square', 'none']:
180 return 'ul'
181 return 'ol'
ff5e70d @nushoin google docs unordered lists are now displayed as such
nushoin authored
182
bdd5fb0 @nushoin fixed google docs nested lists indenting
nushoin authored
183 def google_nest_count(attrs, style_def):
09d6a48 @nushoin nested lists handling is more or less complete
nushoin authored
184 """calculate the nesting count of google doc lists"""
185 nest_count = 0
c5e926e @nushoin code cleanup - made attrs a dictionary in handle_tag
nushoin authored
186 for css_class in attrs['class'].split():
09d6a48 @nushoin nested lists handling is more or less complete
nushoin authored
187 css_style = style_def['.' + css_class]
188 if 'margin-left' in css_style:
57f8c0d @nushoin added an option to set the amount of pixels Google indents nested lists
nushoin authored
189 nest_count = int(css_style['margin-left'][:-2]) / GOOGLE_LIST_INDENT
bdd5fb0 @nushoin fixed google docs nested lists indenting
nushoin authored
190 return nest_count
191
da856dd @nushoin handling google paragraph seperators
nushoin authored
192 def google_has_height(attrs, style_def):
193 """calculate the nesting count of google doc lists"""
c5e926e @nushoin code cleanup - made attrs a dictionary in handle_tag
nushoin authored
194 if not 'class' in attrs:
da856dd @nushoin handling google paragraph seperators
nushoin authored
195 return False
c5e926e @nushoin code cleanup - made attrs a dictionary in handle_tag
nushoin authored
196 for css_class in attrs['class'].split():
da856dd @nushoin handling google paragraph seperators
nushoin authored
197 css_style = style_def['.' + css_class]
198 if 'height' in css_style:
199 return True
200 return False
201
ff7350c @nushoin handling Google docs emphasized text (bold and italics)
nushoin authored
202 def google_text_emphasis(attrs, style_def):
203 """calculate the nesting count of google doc lists"""
204 emphasis = []
205 if 'class' in attrs:
206 for css_class in attrs['class'].split():
207 css_style = style_def['.' + css_class]
208 if 'text-decoration' in css_style:
209 emphasis.append(css_style['text-decoration'])
210 if 'font-style' in css_style:
211 emphasis.append(css_style['font-style'])
212 if 'font-weight' in css_style:
213 emphasis.append(css_style['font-weight'])
214 return emphasis
215
dd94834 @nushoin added support to the 'start' attribute of html lists
nushoin authored
216 def list_numbering_start(attrs, style_def):
09d6a48 @nushoin nested lists handling is more or less complete
nushoin authored
217 """extract numbering from list element attributes"""
c5e926e @nushoin code cleanup - made attrs a dictionary in handle_tag
nushoin authored
218 if 'start' in attrs:
219 return int(attrs['start']) - 1
dd94834 @nushoin added support to the 'start' attribute of html lists
nushoin authored
220 else:
221 return 0
222
4630009 @aaronsw update to HTMLParser; https://gist.github.com/799839
authored
223 class _html2text(HTMLParser.HTMLParser):
7a327b8 @aaronsw initial commit
authored
224 def __init__(self, out=None, baseurl=''):
4630009 @aaronsw update to HTMLParser; https://gist.github.com/799839
authored
225 HTMLParser.HTMLParser.__init__(self)
7a327b8 @aaronsw initial commit
authored
226
227 if out is None: self.out = self.outtextf
228 else: self.out = out
6c3406e @aaronsw add forward compatibility with Python3
authored
229 try:
230 self.outtext = unicode()
231 except NameError: # Python3
232 self.outtext = str()
7a327b8 @aaronsw initial commit
authored
233 self.quiet = 0
c6d68c1 @nushoin removed redundant newline in nested lists
nushoin authored
234 self.p_p = 0 # number of newline character to print before next output
7a327b8 @aaronsw initial commit
authored
235 self.outcount = 0
236 self.start = 1
237 self.space = 0
238 self.a = []
239 self.astack = []
240 self.acount = 0
241 self.list = []
242 self.blockquote = 0
243 self.pre = 0
244 self.startpre = 0
4f5e385 @nushoin fixed redundant newlines when converting google docs
nushoin authored
245 self.br_toggle = ''
7a327b8 @aaronsw initial commit
authored
246 self.lastWasNL = 0
bdd5fb0 @nushoin fixed google docs nested lists indenting
nushoin authored
247 self.lastWasList = False
58860ee @nushoin dumb css parser basically working
nushoin authored
248 self.style = 0
ff5e70d @nushoin google docs unordered lists are now displayed as such
nushoin authored
249 self.style_def = {}
ff7350c @nushoin handling Google docs emphasized text (bold and italics)
nushoin authored
250 self.tag_stack = []
251 self.emphasis = 0
252 self.drop_white_space = False
253 self.inheader = False
7a327b8 @aaronsw initial commit
authored
254 self.abbr_title = None # current abbreviation definition
255 self.abbr_data = None # last inner HTML (for abbr being defined)
256 self.abbr_list = {} # stack of abbreviations to write later
257 self.baseurl = baseurl
258
259 def outtextf(self, s):
260 self.outtext += s
261
262 def close(self):
4630009 @aaronsw update to HTMLParser; https://gist.github.com/799839
authored
263 HTMLParser.HTMLParser.close(self)
7a327b8 @aaronsw initial commit
authored
264
265 self.pbr()
266 self.o('', 0, 'end')
267
268 return self.outtext
269
270 def handle_charref(self, c):
271 self.o(charref(c))
272
273 def handle_entityref(self, c):
274 self.o(entityref(c))
275
4630009 @aaronsw update to HTMLParser; https://gist.github.com/799839
authored
276 def handle_starttag(self, tag, attrs):
7a327b8 @aaronsw initial commit
authored
277 self.handle_tag(tag, attrs, 1)
278
4630009 @aaronsw update to HTMLParser; https://gist.github.com/799839
authored
279 def handle_endtag(self, tag):
7a327b8 @aaronsw initial commit
authored
280 self.handle_tag(tag, None, 0)
281
282 def previousIndex(self, attrs):
283 """ returns the index of certain set of attributes (of a link) in the
284 self.a list
285
286 If the set of attributes is not found, returns None
287 """
6c3406e @aaronsw add forward compatibility with Python3
authored
288 if not has_key(attrs, 'href'): return None
7a327b8 @aaronsw initial commit
authored
289
290 i = -1
291 for a in self.a:
292 i += 1
293 match = 0
294
6c3406e @aaronsw add forward compatibility with Python3
authored
295 if has_key(a, 'href') and a['href'] == attrs['href']:
296 if has_key(a, 'title') or has_key(attrs, 'title'):
297 if (has_key(a, 'title') and has_key(attrs, 'title') and
7a327b8 @aaronsw initial commit
authored
298 a['title'] == attrs['title']):
299 match = True
300 else:
301 match = True
302
303 if match: return i
304
305 def handle_tag(self, tag, attrs, start):
4630009 @aaronsw update to HTMLParser; https://gist.github.com/799839
authored
306 #attrs = fixattrs(attrs)
c5e926e @nushoin code cleanup - made attrs a dictionary in handle_tag
nushoin authored
307 if attrs is None:
308 attrs = {}
309 else:
310 attrs = dict(attrs)
7a327b8 @aaronsw initial commit
authored
311
312 if hn(tag):
ff7350c @nushoin handling Google docs emphasized text (bold and italics)
nushoin authored
313 if start:
314 self.inheader = True
315 else:
316 self.inheader = False
7a327b8 @aaronsw initial commit
authored
317 self.p()
318 if start: self.o(hn(tag)*"#" + ' ')
319
4f5e385 @nushoin fixed redundant newlines when converting google docs
nushoin authored
320 if tag in ['p', 'div']:
321 if options.google_doc:
da856dd @nushoin handling google paragraph seperators
nushoin authored
322 if google_has_height(attrs, self.style_def):
323 self.p()
324 else:
325 self.soft_br()
4f5e385 @nushoin fixed redundant newlines when converting google docs
nushoin authored
326 else:
327 self.p()
7a327b8 @aaronsw initial commit
authored
328
329 if tag == "br" and start: self.o(" \n")
330
331 if tag == "hr" and start:
332 self.p()
333 self.o("* * *")
334 self.p()
335
336 if tag in ["head", "style", 'script']:
337 if start: self.quiet += 1
338 else: self.quiet -= 1
339
58860ee @nushoin dumb css parser basically working
nushoin authored
340 if tag == "style":
341 if start: self.style += 1
342 else: self.style -= 1
343
7a327b8 @aaronsw initial commit
authored
344 if tag in ["body"]:
345 self.quiet = 0 # sites like 9rules.com never close <head>
346
347 if tag == "blockquote":
348 if start:
349 self.p(); self.o('> ', 0, 1); self.start = 1
350 self.blockquote += 1
351 else:
352 self.blockquote -= 1
353 self.p()
354
355 if tag in ['em', 'i', 'u']: self.o("_")
356 if tag in ['strong', 'b']: self.o("**")
ff7350c @nushoin handling Google docs emphasized text (bold and italics)
nushoin authored
357
358 if options.google_doc:
359 # handle Google's bold and italic
360 if start:
361 self.tag_stack.append((tag, attrs))
362 else:
363 dummy, attrs = self.tag_stack.pop()
364 if not self.inheader:
365 text_emphasis = google_text_emphasis(attrs, self.style_def)
366 if 'bold' in text_emphasis:
367 self.o("**")
368 if 'italic' in text_emphasis:
369 self.o("_")
370 if 'bold' in text_emphasis or 'italic' in text_emphasis:
371 if start:
372 self.emphasis += 1
373 self.drop_white_space = True
374 else:
375 self.emphasis -= 1
376 self.o(" ")
377
7a327b8 @aaronsw initial commit
authored
378 if tag == "code" and not self.pre: self.o('`') #TODO: `` `this` ``
379 if tag == "abbr":
380 if start:
381 self.abbr_title = None
382 self.abbr_data = ''
6c3406e @aaronsw add forward compatibility with Python3
authored
383 if has_key(attrs, 'title'):
7a327b8 @aaronsw initial commit
authored
384 self.abbr_title = attrs['title']
385 else:
386 if self.abbr_title != None:
387 self.abbr_list[self.abbr_data] = self.abbr_title
388 self.abbr_title = None
389 self.abbr_data = ''
390
391 if tag == "a":
392 if start:
6c3406e @aaronsw add forward compatibility with Python3
authored
393 if has_key(attrs, 'href') and not (SKIP_INTERNAL_LINKS and attrs['href'].startswith('#')):
7a327b8 @aaronsw initial commit
authored
394 self.astack.append(attrs)
395 self.o("[")
396 else:
397 self.astack.append(None)
398 else:
399 if self.astack:
400 a = self.astack.pop()
401 if a:
2d4806b @maketolearn INLINE_LINKS setting so that links can be in inline format instead of…
maketolearn authored
402 if INLINE_LINKS:
403 self.o("](" + a['href'] + ")")
7a327b8 @aaronsw initial commit
authored
404 else:
2d4806b @maketolearn INLINE_LINKS setting so that links can be in inline format instead of…
maketolearn authored
405 i = self.previousIndex(a)
406 if i is not None:
407 a = self.a[i]
408 else:
409 self.acount += 1
410 a['count'] = self.acount
411 a['outcount'] = self.outcount
412 self.a.append(a)
413 self.o("][" + str(a['count']) + "]")
7a327b8 @aaronsw initial commit
authored
414
415 if tag == "img" and start:
6c3406e @aaronsw add forward compatibility with Python3
authored
416 if has_key(attrs, 'src'):
7a327b8 @aaronsw initial commit
authored
417 attrs['href'] = attrs['src']
418 alt = attrs.get('alt', '')
2d4806b @maketolearn INLINE_LINKS setting so that links can be in inline format instead of…
maketolearn authored
419 if INLINE_LINKS:
420 self.o("![")
421 self.o(alt)
422 self.o("]("+ attrs['href'] +")")
7a327b8 @aaronsw initial commit
authored
423 else:
2d4806b @maketolearn INLINE_LINKS setting so that links can be in inline format instead of…
maketolearn authored
424 i = self.previousIndex(attrs)
425 if i is not None:
426 attrs = self.a[i]
427 else:
428 self.acount += 1
429 attrs['count'] = self.acount
430 attrs['outcount'] = self.outcount
431 self.a.append(attrs)
432 self.o("![")
433 self.o(alt)
434 self.o("]["+ str(attrs['count']) +"]")
7a327b8 @aaronsw initial commit
authored
435
436 if tag == 'dl' and start: self.p()
437 if tag == 'dt' and not start: self.pbr()
438 if tag == 'dd' and start: self.o(' ')
439 if tag == 'dd' and not start: self.pbr()
440
441 if tag in ["ol", "ul"]:
bdd5fb0 @nushoin fixed google docs nested lists indenting
nushoin authored
442 # Google Docs create sub lists as top level lists
443 if (not self.list) and (not self.lastWasList):
c6d68c1 @nushoin removed redundant newline in nested lists
nushoin authored
444 self.p()
7a327b8 @aaronsw initial commit
authored
445 if start:
ff5e70d @nushoin google docs unordered lists are now displayed as such
nushoin authored
446 if options.google_doc:
447 list_style = google_list_style(attrs, self.style_def)
448 else:
449 list_style = tag
dd94834 @nushoin added support to the 'start' attribute of html lists
nushoin authored
450 numbering_start = list_numbering_start(attrs, self.style_def)
451 self.list.append({'name':list_style, 'num':numbering_start})
7a327b8 @aaronsw initial commit
authored
452 else:
453 if self.list: self.list.pop()
bdd5fb0 @nushoin fixed google docs nested lists indenting
nushoin authored
454 self.lastWasList = True
455 else:
456 self.lastWasList = False
7a327b8 @aaronsw initial commit
authored
457
458 if tag == 'li':
c6d68c1 @nushoin removed redundant newline in nested lists
nushoin authored
459 self.pbr()
7a327b8 @aaronsw initial commit
authored
460 if start:
461 if self.list: li = self.list[-1]
462 else: li = {'name':'ul', 'num':0}
bdd5fb0 @nushoin fixed google docs nested lists indenting
nushoin authored
463 if options.google_doc:
464 nest_count = google_nest_count(attrs, self.style_def)
465 else:
466 nest_count = len(self.list)
467 self.o(" " * nest_count) #TODO: line up <ol><li>s > 9 correctly.
5e56246 @nushoin reverted unordered list item mark to the default star and added an op…
nushoin authored
468 if li['name'] == "ul": self.o(options.ul_item_mark + " ")
7a327b8 @aaronsw initial commit
authored
469 elif li['name'] == "ol":
470 li['num'] += 1
6c3406e @aaronsw add forward compatibility with Python3
authored
471 self.o(str(li['num'])+". ")
7a327b8 @aaronsw initial commit
authored
472 self.start = 1
473
474 if tag in ["table", "tr"] and start: self.p()
475 if tag == 'td': self.pbr()
476
477 if tag == "pre":
478 if start:
479 self.startpre = 1
480 self.pre = 1
481 else:
482 self.pre = 0
483 self.p()
484
485 def pbr(self):
486 if self.p_p == 0: self.p_p = 1
487
488 def p(self): self.p_p = 2
4f5e385 @nushoin fixed redundant newlines when converting google docs
nushoin authored
489
490 def soft_br(self):
491 self.pbr()
492 self.br_toggle = ' '
7a327b8 @aaronsw initial commit
authored
493
494 def o(self, data, puredata=0, force=0):
495 if self.abbr_data is not None: self.abbr_data += data
496
497 if not self.quiet:
498 if puredata and not self.pre:
499 data = re.sub('\s+', ' ', data)
500 if data and data[0] == ' ':
501 self.space = 1
502 data = data[1:]
ff7350c @nushoin handling Google docs emphasized text (bold and italics)
nushoin authored
503 if self.emphasis:
504 self.space = 0
7a327b8 @aaronsw initial commit
authored
505 if not data and not force: return
ff7350c @nushoin handling Google docs emphasized text (bold and italics)
nushoin authored
506
507 if options.google_doc:
508 # prevent white space immediately after emphasis marks ('**' and '_')
509 if self.drop_white_space:
510 data = data.lstrip()
511 if data != '':
512 self.drop_white_space = False
7a327b8 @aaronsw initial commit
authored
513
514 if self.startpre:
515 #self.out(" :") #TODO: not output when already one there
516 self.startpre = 0
517
518 bq = (">" * self.blockquote)
519 if not (force and data and data[0] == ">") and self.blockquote: bq += " "
520
521 if self.pre:
522 bq += " "
523 data = data.replace("\n", "\n"+bq)
524
525 if self.start:
526 self.space = 0
527 self.p_p = 0
528 self.start = 0
529
530 if force == 'end':
531 # It's the end.
532 self.p_p = 0
533 self.out("\n")
534 self.space = 0
535
536 if self.p_p:
4f5e385 @nushoin fixed redundant newlines when converting google docs
nushoin authored
537 self.out((self.br_toggle+'\n'+bq)*self.p_p)
7a327b8 @aaronsw initial commit
authored
538 self.space = 0
4f5e385 @nushoin fixed redundant newlines when converting google docs
nushoin authored
539 self.br_toggle = ''
7a327b8 @aaronsw initial commit
authored
540
541 if self.space:
542 if not self.lastWasNL: self.out(' ')
543 self.space = 0
544
545 if self.a and ((self.p_p == 2 and LINKS_EACH_PARAGRAPH) or force == "end"):
546 if force == "end": self.out("\n")
547
548 newa = []
549 for link in self.a:
550 if self.outcount > link['outcount']:
6c3406e @aaronsw add forward compatibility with Python3
authored
551 self.out(" ["+ str(link['count']) +"]: " + urlparse.urljoin(self.baseurl, link['href']))
552 if has_key(link, 'title'): self.out(" ("+link['title']+")")
7a327b8 @aaronsw initial commit
authored
553 self.out("\n")
554 else:
555 newa.append(link)
556
557 if self.a != newa: self.out("\n") # Don't need an extra line when nothing was done.
558
559 self.a = newa
560
561 if self.abbr_list and force == "end":
562 for abbr, definition in self.abbr_list.items():
563 self.out(" *[" + abbr + "]: " + definition + "\n")
564
565 self.p_p = 0
566 self.out(data)
567 self.lastWasNL = data and data[-1] == '\n'
568 self.outcount += 1
569
570 def handle_data(self, data):
571 if r'\/script>' in data: self.quiet -= 1
58860ee @nushoin dumb css parser basically working
nushoin authored
572
573 if self.style:
ff5e70d @nushoin google docs unordered lists are now displayed as such
nushoin authored
574 self.style_def = dumb_css_parser(data)
58860ee @nushoin dumb css parser basically working
nushoin authored
575
7a327b8 @aaronsw initial commit
authored
576 self.o(data, 1)
577
578 def unknown_decl(self, data): pass
579
6c72d90 @stefanor Encode all output as UTF-8
stefanor authored
580 def wrapwrite(text):
581 text = text.encode('utf-8')
582 try: #Python3
583 sys.stdout.buffer.write(text)
584 except AttributeError:
585 sys.stdout.write(text)
7a327b8 @aaronsw initial commit
authored
586
587 def html2text_file(html, out=wrapwrite, baseurl=''):
588 h = _html2text(out, baseurl)
589 h.feed(html)
590 h.feed("")
591 return h.close()
592
593 def html2text(html, baseurl=''):
594 return optwrap(html2text_file(html, None, baseurl))
595
76a353f @aaronsw Further fixes allowing it to be used as import
authored
596 class Storage: pass
597 options = Storage()
1f6d006 @aaronsw Allow module to be imported.
authored
598 options.google_doc = False
599 options.ul_item_mark = '*'
600
7a327b8 @aaronsw initial commit
authored
601 if __name__ == "__main__":
602 baseurl = ''
1f6d006 @aaronsw Allow module to be imported.
authored
603
7b86526 @stefanor Tell optparse the version
stefanor authored
604 p = optparse.OptionParser('%prog [(filename|url) [encoding]]',
605 version='%prog ' + __version__)
ff5e70d @nushoin google docs unordered lists are now displayed as such
nushoin authored
606 p.add_option("-g", "--google-doc", action="store_true", dest="google_doc",
607 default=False, help="convert an html-exported Google Document")
5e56246 @nushoin reverted unordered list item mark to the default star and added an op…
nushoin authored
608 p.add_option("-d", "--dash-unordered-list", action="store_true", dest="ul_style_dash",
609 default=False, help="use a dash rather than a star for unordered list items")
76f40cb @nushoin added an option to set line wrapping at the command line
nushoin authored
610 p.add_option("-b", "--body-width", dest="body_width", action="store", type="int",
611 default=78, help="number of characters per output line, 0 for no wrap")
57f8c0d @nushoin added an option to set the amount of pixels Google indents nested lists
nushoin authored
612 p.add_option("-i", "--google-list-indent", dest="list_indent", action="store", type="int",
613 default=GOOGLE_LIST_INDENT, help="number of pixels Google indents nested lists")
ff5e70d @nushoin google docs unordered lists are now displayed as such
nushoin authored
614 (options, args) = p.parse_args()
5e56246 @nushoin reverted unordered list item mark to the default star and added an op…
nushoin authored
615
76f40cb @nushoin added an option to set line wrapping at the command line
nushoin authored
616 # handle options
5e56246 @nushoin reverted unordered list item mark to the default star and added an op…
nushoin authored
617 if options.ul_style_dash:
618 options.ul_item_mark = '-'
619 else:
620 options.ul_item_mark = '*'
621
76f40cb @nushoin added an option to set line wrapping at the command line
nushoin authored
622 BODY_WIDTH = options.body_width
57f8c0d @nushoin added an option to set the amount of pixels Google indents nested lists
nushoin authored
623 GOOGLE_LIST_INDENT = options.list_indent
76f40cb @nushoin added an option to set line wrapping at the command line
nushoin authored
624
625 # process input
b7e2f15 @stefanor Use optparse for parsing and checking arguments
stefanor authored
626 if len(args) > 0:
627 file_ = args[0]
84a6147 @stefanor Accept optional encoding for local file or URL
stefanor authored
628 encoding = None
629 if len(args) == 2:
630 encoding = args[1]
b7e2f15 @stefanor Use optparse for parsing and checking arguments
stefanor authored
631 if len(args) > 2:
632 p.error('Too many arguments')
633
84a6147 @stefanor Accept optional encoding for local file or URL
stefanor authored
634 if file_.startswith('http://') or file_.startswith('https://'):
b7e2f15 @stefanor Use optparse for parsing and checking arguments
stefanor authored
635 baseurl = file_
7a327b8 @aaronsw initial commit
authored
636 j = urllib.urlopen(baseurl)
637 text = j.read()
84a6147 @stefanor Accept optional encoding for local file or URL
stefanor authored
638 if encoding is None:
639 try:
640 from feedparser import _getCharacterEncoding as enc
641 except ImportError:
642 enc = lambda x, y: ('utf-8', 1)
643 encoding = enc(j.headers, text)[0]
644 if encoding == 'us-ascii':
645 encoding = 'utf-8'
7a327b8 @aaronsw initial commit
authored
646 data = text.decode(encoding)
647
648 else:
84a6147 @stefanor Accept optional encoding for local file or URL
stefanor authored
649 data = open(file_, 'rb').read()
650 if encoding is None:
07da838 @stefanor Use chardet for guessing local file character sets
stefanor authored
651 try:
652 from chardet import detect
653 except ImportError:
654 detect = lambda x: {'encoding': 'utf-8'}
655 encoding = detect(data)['encoding']
84a6147 @stefanor Accept optional encoding for local file or URL
stefanor authored
656 data = data.decode(encoding)
7a327b8 @aaronsw initial commit
authored
657 else:
6c3406e @aaronsw add forward compatibility with Python3
authored
658 data = sys.stdin.read()
7a327b8 @aaronsw initial commit
authored
659 wrapwrite(html2text(data, baseurl))
Something went wrong with that request. Please try again.