Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 606 lines (507 sloc) 19.758 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
167 # parse the css
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() != '']
58860ee @nushoin dumb css parser basically working
nushoin authored
169 elements = {a.strip() :
170 {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
196 nest_count = 0
c5e926e @nushoin code cleanup - made attrs a dictionary in handle_tag
nushoin authored
197 for css_class in attrs['class'].split():
da856dd @nushoin handling google paragraph seperators
nushoin authored
198 css_style = style_def['.' + css_class]
199 if 'height' in css_style:
200 return True
201 return False
202
dd94834 @nushoin added support to the 'start' attribute of html lists
nushoin authored
203 def list_numbering_start(attrs, style_def):
09d6a48 @nushoin nested lists handling is more or less complete
nushoin authored
204 """extract numbering from list element attributes"""
c5e926e @nushoin code cleanup - made attrs a dictionary in handle_tag
nushoin authored
205 if 'start' in attrs:
206 return int(attrs['start']) - 1
dd94834 @nushoin added support to the 'start' attribute of html lists
nushoin authored
207 else:
208 return 0
209
4630009 @aaronsw update to HTMLParser; https://gist.github.com/799839
authored
210 class _html2text(HTMLParser.HTMLParser):
7a327b8 @aaronsw initial commit
authored
211 def __init__(self, out=None, baseurl=''):
4630009 @aaronsw update to HTMLParser; https://gist.github.com/799839
authored
212 HTMLParser.HTMLParser.__init__(self)
7a327b8 @aaronsw initial commit
authored
213
214 if out is None: self.out = self.outtextf
215 else: self.out = out
6c3406e @aaronsw add forward compatibility with Python3
authored
216 try:
217 self.outtext = unicode()
218 except NameError: # Python3
219 self.outtext = str()
7a327b8 @aaronsw initial commit
authored
220 self.quiet = 0
c6d68c1 @nushoin removed redundant newline in nested lists
nushoin authored
221 self.p_p = 0 # number of newline character to print before next output
7a327b8 @aaronsw initial commit
authored
222 self.outcount = 0
223 self.start = 1
224 self.space = 0
225 self.a = []
226 self.astack = []
227 self.acount = 0
228 self.list = []
229 self.blockquote = 0
230 self.pre = 0
231 self.startpre = 0
4f5e385 @nushoin fixed redundant newlines when converting google docs
nushoin authored
232 self.br_toggle = ''
7a327b8 @aaronsw initial commit
authored
233 self.lastWasNL = 0
bdd5fb0 @nushoin fixed google docs nested lists indenting
nushoin authored
234 self.lastWasList = False
58860ee @nushoin dumb css parser basically working
nushoin authored
235 self.style = 0
ff5e70d @nushoin google docs unordered lists are now displayed as such
nushoin authored
236 self.style_def = {}
7a327b8 @aaronsw initial commit
authored
237 self.abbr_title = None # current abbreviation definition
238 self.abbr_data = None # last inner HTML (for abbr being defined)
239 self.abbr_list = {} # stack of abbreviations to write later
240 self.baseurl = baseurl
241
242 def outtextf(self, s):
243 self.outtext += s
244
245 def close(self):
4630009 @aaronsw update to HTMLParser; https://gist.github.com/799839
authored
246 HTMLParser.HTMLParser.close(self)
7a327b8 @aaronsw initial commit
authored
247
248 self.pbr()
249 self.o('', 0, 'end')
250
251 return self.outtext
252
253 def handle_charref(self, c):
254 self.o(charref(c))
255
256 def handle_entityref(self, c):
257 self.o(entityref(c))
258
4630009 @aaronsw update to HTMLParser; https://gist.github.com/799839
authored
259 def handle_starttag(self, tag, attrs):
7a327b8 @aaronsw initial commit
authored
260 self.handle_tag(tag, attrs, 1)
261
4630009 @aaronsw update to HTMLParser; https://gist.github.com/799839
authored
262 def handle_endtag(self, tag):
7a327b8 @aaronsw initial commit
authored
263 self.handle_tag(tag, None, 0)
264
265 def previousIndex(self, attrs):
266 """ returns the index of certain set of attributes (of a link) in the
267 self.a list
268
269 If the set of attributes is not found, returns None
270 """
6c3406e @aaronsw add forward compatibility with Python3
authored
271 if not has_key(attrs, 'href'): return None
7a327b8 @aaronsw initial commit
authored
272
273 i = -1
274 for a in self.a:
275 i += 1
276 match = 0
277
6c3406e @aaronsw add forward compatibility with Python3
authored
278 if has_key(a, 'href') and a['href'] == attrs['href']:
279 if has_key(a, 'title') or has_key(attrs, 'title'):
280 if (has_key(a, 'title') and has_key(attrs, 'title') and
7a327b8 @aaronsw initial commit
authored
281 a['title'] == attrs['title']):
282 match = True
283 else:
284 match = True
285
286 if match: return i
287
288 def handle_tag(self, tag, attrs, start):
4630009 @aaronsw update to HTMLParser; https://gist.github.com/799839
authored
289 #attrs = fixattrs(attrs)
c5e926e @nushoin code cleanup - made attrs a dictionary in handle_tag
nushoin authored
290 if attrs is None:
291 attrs = {}
292 else:
293 attrs = dict(attrs)
7a327b8 @aaronsw initial commit
authored
294
295 if hn(tag):
296 self.p()
297 if start: self.o(hn(tag)*"#" + ' ')
298
4f5e385 @nushoin fixed redundant newlines when converting google docs
nushoin authored
299 if tag in ['p', 'div']:
300 if options.google_doc:
da856dd @nushoin handling google paragraph seperators
nushoin authored
301 if google_has_height(attrs, self.style_def):
302 self.p()
303 else:
304 self.soft_br()
4f5e385 @nushoin fixed redundant newlines when converting google docs
nushoin authored
305 else:
306 self.p()
7a327b8 @aaronsw initial commit
authored
307
308 if tag == "br" and start: self.o(" \n")
309
310 if tag == "hr" and start:
311 self.p()
312 self.o("* * *")
313 self.p()
314
315 if tag in ["head", "style", 'script']:
316 if start: self.quiet += 1
317 else: self.quiet -= 1
318
58860ee @nushoin dumb css parser basically working
nushoin authored
319 if tag == "style":
320 if start: self.style += 1
321 else: self.style -= 1
322
7a327b8 @aaronsw initial commit
authored
323 if tag in ["body"]:
324 self.quiet = 0 # sites like 9rules.com never close <head>
325
326 if tag == "blockquote":
327 if start:
328 self.p(); self.o('> ', 0, 1); self.start = 1
329 self.blockquote += 1
330 else:
331 self.blockquote -= 1
332 self.p()
333
334 if tag in ['em', 'i', 'u']: self.o("_")
335 if tag in ['strong', 'b']: self.o("**")
336 if tag == "code" and not self.pre: self.o('`') #TODO: `` `this` ``
337 if tag == "abbr":
338 if start:
339 self.abbr_title = None
340 self.abbr_data = ''
6c3406e @aaronsw add forward compatibility with Python3
authored
341 if has_key(attrs, 'title'):
7a327b8 @aaronsw initial commit
authored
342 self.abbr_title = attrs['title']
343 else:
344 if self.abbr_title != None:
345 self.abbr_list[self.abbr_data] = self.abbr_title
346 self.abbr_title = None
347 self.abbr_data = ''
348
349 if tag == "a":
350 if start:
6c3406e @aaronsw add forward compatibility with Python3
authored
351 if has_key(attrs, 'href') and not (SKIP_INTERNAL_LINKS and attrs['href'].startswith('#')):
7a327b8 @aaronsw initial commit
authored
352 self.astack.append(attrs)
353 self.o("[")
354 else:
355 self.astack.append(None)
356 else:
357 if self.astack:
358 a = self.astack.pop()
359 if a:
2d4806b @maketolearn INLINE_LINKS setting so that links can be in inline format instead of…
maketolearn authored
360 if INLINE_LINKS:
361 self.o("](" + a['href'] + ")")
7a327b8 @aaronsw initial commit
authored
362 else:
2d4806b @maketolearn INLINE_LINKS setting so that links can be in inline format instead of…
maketolearn authored
363 i = self.previousIndex(a)
364 if i is not None:
365 a = self.a[i]
366 else:
367 self.acount += 1
368 a['count'] = self.acount
369 a['outcount'] = self.outcount
370 self.a.append(a)
371 self.o("][" + str(a['count']) + "]")
7a327b8 @aaronsw initial commit
authored
372
373 if tag == "img" and start:
6c3406e @aaronsw add forward compatibility with Python3
authored
374 if has_key(attrs, 'src'):
7a327b8 @aaronsw initial commit
authored
375 attrs['href'] = attrs['src']
376 alt = attrs.get('alt', '')
2d4806b @maketolearn INLINE_LINKS setting so that links can be in inline format instead of…
maketolearn authored
377 if INLINE_LINKS:
378 self.o("![")
379 self.o(alt)
380 self.o("]("+ attrs['href'] +")")
7a327b8 @aaronsw initial commit
authored
381 else:
2d4806b @maketolearn INLINE_LINKS setting so that links can be in inline format instead of…
maketolearn authored
382 i = self.previousIndex(attrs)
383 if i is not None:
384 attrs = self.a[i]
385 else:
386 self.acount += 1
387 attrs['count'] = self.acount
388 attrs['outcount'] = self.outcount
389 self.a.append(attrs)
390 self.o("![")
391 self.o(alt)
392 self.o("]["+ str(attrs['count']) +"]")
7a327b8 @aaronsw initial commit
authored
393
394 if tag == 'dl' and start: self.p()
395 if tag == 'dt' and not start: self.pbr()
396 if tag == 'dd' and start: self.o(' ')
397 if tag == 'dd' and not start: self.pbr()
398
399 if tag in ["ol", "ul"]:
bdd5fb0 @nushoin fixed google docs nested lists indenting
nushoin authored
400 # Google Docs create sub lists as top level lists
401 if (not self.list) and (not self.lastWasList):
c6d68c1 @nushoin removed redundant newline in nested lists
nushoin authored
402 self.p()
7a327b8 @aaronsw initial commit
authored
403 if start:
ff5e70d @nushoin google docs unordered lists are now displayed as such
nushoin authored
404 if options.google_doc:
405 list_style = google_list_style(attrs, self.style_def)
406 else:
407 list_style = tag
dd94834 @nushoin added support to the 'start' attribute of html lists
nushoin authored
408 numbering_start = list_numbering_start(attrs, self.style_def)
409 self.list.append({'name':list_style, 'num':numbering_start})
7a327b8 @aaronsw initial commit
authored
410 else:
411 if self.list: self.list.pop()
bdd5fb0 @nushoin fixed google docs nested lists indenting
nushoin authored
412 self.lastWasList = True
413 else:
414 self.lastWasList = False
7a327b8 @aaronsw initial commit
authored
415
416 if tag == 'li':
c6d68c1 @nushoin removed redundant newline in nested lists
nushoin authored
417 self.pbr()
7a327b8 @aaronsw initial commit
authored
418 if start:
419 if self.list: li = self.list[-1]
420 else: li = {'name':'ul', 'num':0}
bdd5fb0 @nushoin fixed google docs nested lists indenting
nushoin authored
421 if options.google_doc:
422 nest_count = google_nest_count(attrs, self.style_def)
423 else:
424 nest_count = len(self.list)
425 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
426 if li['name'] == "ul": self.o(options.ul_item_mark + " ")
7a327b8 @aaronsw initial commit
authored
427 elif li['name'] == "ol":
428 li['num'] += 1
6c3406e @aaronsw add forward compatibility with Python3
authored
429 self.o(str(li['num'])+". ")
7a327b8 @aaronsw initial commit
authored
430 self.start = 1
431
432 if tag in ["table", "tr"] and start: self.p()
433 if tag == 'td': self.pbr()
434
435 if tag == "pre":
436 if start:
437 self.startpre = 1
438 self.pre = 1
439 else:
440 self.pre = 0
441 self.p()
442
443 def pbr(self):
444 if self.p_p == 0: self.p_p = 1
445
446 def p(self): self.p_p = 2
4f5e385 @nushoin fixed redundant newlines when converting google docs
nushoin authored
447
448 def soft_br(self):
449 self.pbr()
450 self.br_toggle = ' '
7a327b8 @aaronsw initial commit
authored
451
452 def o(self, data, puredata=0, force=0):
453 if self.abbr_data is not None: self.abbr_data += data
454
455 if not self.quiet:
456 if puredata and not self.pre:
457 data = re.sub('\s+', ' ', data)
458 if data and data[0] == ' ':
459 self.space = 1
460 data = data[1:]
461 if not data and not force: return
462
463 if self.startpre:
464 #self.out(" :") #TODO: not output when already one there
465 self.startpre = 0
466
467 bq = (">" * self.blockquote)
468 if not (force and data and data[0] == ">") and self.blockquote: bq += " "
469
470 if self.pre:
471 bq += " "
472 data = data.replace("\n", "\n"+bq)
473
474 if self.start:
475 self.space = 0
476 self.p_p = 0
477 self.start = 0
478
479 if force == 'end':
480 # It's the end.
481 self.p_p = 0
482 self.out("\n")
483 self.space = 0
484
485
486 if self.p_p:
4f5e385 @nushoin fixed redundant newlines when converting google docs
nushoin authored
487 self.out((self.br_toggle+'\n'+bq)*self.p_p)
7a327b8 @aaronsw initial commit
authored
488 self.space = 0
4f5e385 @nushoin fixed redundant newlines when converting google docs
nushoin authored
489 self.br_toggle = ''
7a327b8 @aaronsw initial commit
authored
490
491 if self.space:
492 if not self.lastWasNL: self.out(' ')
493 self.space = 0
494
495 if self.a and ((self.p_p == 2 and LINKS_EACH_PARAGRAPH) or force == "end"):
496 if force == "end": self.out("\n")
497
498 newa = []
499 for link in self.a:
500 if self.outcount > link['outcount']:
6c3406e @aaronsw add forward compatibility with Python3
authored
501 self.out(" ["+ str(link['count']) +"]: " + urlparse.urljoin(self.baseurl, link['href']))
502 if has_key(link, 'title'): self.out(" ("+link['title']+")")
7a327b8 @aaronsw initial commit
authored
503 self.out("\n")
504 else:
505 newa.append(link)
506
507 if self.a != newa: self.out("\n") # Don't need an extra line when nothing was done.
508
509 self.a = newa
510
511 if self.abbr_list and force == "end":
512 for abbr, definition in self.abbr_list.items():
513 self.out(" *[" + abbr + "]: " + definition + "\n")
514
515 self.p_p = 0
516 self.out(data)
517 self.lastWasNL = data and data[-1] == '\n'
518 self.outcount += 1
519
520 def handle_data(self, data):
521 if r'\/script>' in data: self.quiet -= 1
58860ee @nushoin dumb css parser basically working
nushoin authored
522
523 if self.style:
ff5e70d @nushoin google docs unordered lists are now displayed as such
nushoin authored
524 self.style_def = dumb_css_parser(data)
58860ee @nushoin dumb css parser basically working
nushoin authored
525
7a327b8 @aaronsw initial commit
authored
526 self.o(data, 1)
527
528 def unknown_decl(self, data): pass
529
6c72d90 @stefanor Encode all output as UTF-8
stefanor authored
530 def wrapwrite(text):
531 text = text.encode('utf-8')
532 try: #Python3
533 sys.stdout.buffer.write(text)
534 except AttributeError:
535 sys.stdout.write(text)
7a327b8 @aaronsw initial commit
authored
536
537 def html2text_file(html, out=wrapwrite, baseurl=''):
538 h = _html2text(out, baseurl)
539 h.feed(html)
540 h.feed("")
541 return h.close()
542
543 def html2text(html, baseurl=''):
544 return optwrap(html2text_file(html, None, baseurl))
545
546 if __name__ == "__main__":
547 baseurl = ''
b7e2f15 @stefanor Use optparse for parsing and checking arguments
stefanor authored
548
ff5e70d @nushoin google docs unordered lists are now displayed as such
nushoin authored
549 global options
7b86526 @stefanor Tell optparse the version
stefanor authored
550 p = optparse.OptionParser('%prog [(filename|url) [encoding]]',
551 version='%prog ' + __version__)
ff5e70d @nushoin google docs unordered lists are now displayed as such
nushoin authored
552 p.add_option("-g", "--google-doc", action="store_true", dest="google_doc",
553 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
554 p.add_option("-d", "--dash-unordered-list", action="store_true", dest="ul_style_dash",
555 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
556 p.add_option("-b", "--body-width", dest="body_width", action="store", type="int",
557 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
558 p.add_option("-i", "--google-list-indent", dest="list_indent", action="store", type="int",
559 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
560 (options, args) = p.parse_args()
5e56246 @nushoin reverted unordered list item mark to the default star and added an op…
nushoin authored
561
76f40cb @nushoin added an option to set line wrapping at the command line
nushoin authored
562 # handle options
5e56246 @nushoin reverted unordered list item mark to the default star and added an op…
nushoin authored
563 if options.ul_style_dash:
564 options.ul_item_mark = '-'
565 else:
566 options.ul_item_mark = '*'
567
76f40cb @nushoin added an option to set line wrapping at the command line
nushoin authored
568 BODY_WIDTH = options.body_width
57f8c0d @nushoin added an option to set the amount of pixels Google indents nested lists
nushoin authored
569 GOOGLE_LIST_INDENT = options.list_indent
76f40cb @nushoin added an option to set line wrapping at the command line
nushoin authored
570
571 # process input
b7e2f15 @stefanor Use optparse for parsing and checking arguments
stefanor authored
572 if len(args) > 0:
573 file_ = args[0]
84a6147 @stefanor Accept optional encoding for local file or URL
stefanor authored
574 encoding = None
575 if len(args) == 2:
576 encoding = args[1]
b7e2f15 @stefanor Use optparse for parsing and checking arguments
stefanor authored
577 if len(args) > 2:
578 p.error('Too many arguments')
579
84a6147 @stefanor Accept optional encoding for local file or URL
stefanor authored
580 if file_.startswith('http://') or file_.startswith('https://'):
b7e2f15 @stefanor Use optparse for parsing and checking arguments
stefanor authored
581 baseurl = file_
7a327b8 @aaronsw initial commit
authored
582 j = urllib.urlopen(baseurl)
583 text = j.read()
84a6147 @stefanor Accept optional encoding for local file or URL
stefanor authored
584 if encoding is None:
585 try:
586 from feedparser import _getCharacterEncoding as enc
587 except ImportError:
588 enc = lambda x, y: ('utf-8', 1)
589 encoding = enc(j.headers, text)[0]
590 if encoding == 'us-ascii':
591 encoding = 'utf-8'
7a327b8 @aaronsw initial commit
authored
592 data = text.decode(encoding)
593
594 else:
84a6147 @stefanor Accept optional encoding for local file or URL
stefanor authored
595 data = open(file_, 'rb').read()
596 if encoding is None:
07da838 @stefanor Use chardet for guessing local file character sets
stefanor authored
597 try:
598 from chardet import detect
599 except ImportError:
600 detect = lambda x: {'encoding': 'utf-8'}
601 encoding = detect(data)['encoding']
84a6147 @stefanor Accept optional encoding for local file or URL
stefanor authored
602 data = data.decode(encoding)
7a327b8 @aaronsw initial commit
authored
603 else:
6c3406e @aaronsw add forward compatibility with Python3
authored
604 data = sys.stdin.read()
7a327b8 @aaronsw initial commit
authored
605 wrapwrite(html2text(data, baseurl))
Something went wrong with that request. Please try again.