]> git.phdru.name Git - bookmarks_db.git/blob - parse_html/bkmk_parse_html.py
862fa2b5b2fae79903a3440989fef0f41eec8ab4
[bookmarks_db.git] / parse_html / bkmk_parse_html.py
1 """HTML Parsers
2
3 This file is a part of Bookmarks database and Internet robot.
4 """
5
6 __author__ = "Oleg Broytman <phd@phdru.name>"
7 __copyright__ = "Copyright (C) 1997-2012 PhiloSoft Design"
8 __license__ = "GNU GPL"
9
10 __all__ = ['parse_html', 'universal_charset']
11
12
13 import codecs
14
15 universal_charset = "utf-8"
16 DEFAULT_CHARSET = "cp1251" # Stupid default for Russian Cyrillic
17
18 parsers = []
19
20 try:
21    from . import bkmk_ph_beautifulsoup
22 except ImportError:
23    pass
24 else:
25    bkmk_ph_beautifulsoup.DEFAULT_CHARSET = DEFAULT_CHARSET
26    parsers.append(bkmk_ph_beautifulsoup.parse_html)
27
28 try:
29    from . import bkmk_ph_html5
30 except ImportError:
31    pass
32 else:
33    parsers.append(bkmk_ph_html5.parse_html)
34
35 try:
36    from . import bkmk_ph_lxml
37 except ImportError:
38    pass
39 else:
40     parsers.append(bkmk_ph_lxml.parse_html)
41
42 try:
43    from . import bkmk_ph_htmlparser
44 except ImportError:
45    pass
46 else:
47     parsers.append(bkmk_ph_htmlparser.parse_html)
48
49 # ElementTidy often segfaults
50 #try:
51 #   from . import bkmk_ph_etreetidy
52 #except ImportError:
53 #   pass
54 #else:
55 #   parsers.append(bkmk_ph_etreetidy.parse_html)
56
57 import re
58 from htmlentitydefs import name2codepoint
59
60 entity_re = re.compile("(&\w+;)")
61 num_entity_re = re.compile("(&#[0-9]+;)")
62
63 def recode_entities(title, charset):
64    output = []
65    for part in entity_re.split(title):
66       if part not in ("&amp;", "&lt;", "&gt;", "&quot;") and \
67             entity_re.match(part):
68          _part = name2codepoint.get(part[1:-1], None)
69          if _part is not None:
70              part = unichr(_part).encode(charset)
71       output.append(part)
72    title = ''.join(output)
73
74    output = []
75    for part in num_entity_re.split(title):
76       if num_entity_re.match(part):
77          try:
78             part = unichr(int(part[2:-1])).encode(charset)
79          except UnicodeEncodeError:
80             pass # Leave the entity as is
81       output.append(part)
82
83    return ''.join(output)
84
85
86 import os
87 BKMK_DEBUG_HTML_PARSERS = os.environ.get("BKMK_DEBUG_HTML_PARSERS")
88
89 def parse_html(filename, charset=None, log=None):
90    if not parsers:
91        return None
92
93    if charset:
94       try:
95          codecs.lookup(charset) # In case of unknown charset...
96       except (ValueError, LookupError):
97          charset = None         # ...try charset from HTML
98
99    charsets = [universal_charset, DEFAULT_CHARSET]
100    if charset:
101       charset = charset.lower().replace("windows-", "cp")
102       if charset in charsets:
103          charsets.remove(charset)
104       charsets.insert(0, charset)
105
106    if BKMK_DEBUG_HTML_PARSERS:
107       _parsers = []
108    for p in parsers:
109       parser = None
110       for c in charsets:
111          try:
112             parser = p(filename, c, log)
113          except UnicodeError:
114             pass
115          else:
116             if parser:
117                if BKMK_DEBUG_HTML_PARSERS:
118                   if log: log("   Parser %s: ok" % p.__module__)
119                   _parsers.append((p, parser))
120                break
121       else:
122          if log: log("   Parser %s: fail" % p.__module__)
123       if not BKMK_DEBUG_HTML_PARSERS and parser:
124          break
125
126    if BKMK_DEBUG_HTML_PARSERS:
127       if not _parsers:
128          if log: log("   All parsers have failed")
129          return None
130    elif not parser:
131        if log: log("   All parsers have failed")
132        return None
133
134    if BKMK_DEBUG_HTML_PARSERS:
135       p, parser = _parsers[0]
136    if log: log("   Using %s" % p.__module__)
137
138    converted_title = title = parser.title
139    if title and (not parser.charset):
140       try:
141          unicode(title, "ascii")
142       except UnicodeDecodeError:
143          parser.charset = DEFAULT_CHARSET
144
145    if parser.charset:
146       parser.charset = parser.charset.lower().replace("windows-", "cp")
147
148    if title and parser.charset and (
149          (parser.charset <> universal_charset) or
150          ((not charset) or (charset <> parser.charset))):
151       try:
152          if parser.meta_charset:
153             if log: log("   META charset   : %s" % parser.charset)
154          elif (not charset) or (charset <> parser.charset):
155             if log: log("   guessed charset: %s" % parser.charset)
156          #if log: log("   current charset: %s" % universal_charset)
157          if log: log("   title          : %s" % title)
158          if parser.charset <> universal_charset:
159             try:
160                converted_title = unicode(title, parser.charset).encode(universal_charset)
161             except UnicodeError:
162                if log: log("   incorrect conversion from %s, converting from %s" % (parser.charset, DEFAULT_CHARSET))
163                converted_title = unicode(title, DEFAULT_CHARSET, "replace").encode(universal_charset, "replace")
164                parser.charset = DEFAULT_CHARSET
165          if log and (converted_title <> title): log("   converted title: %s" % converted_title)
166       except LookupError:
167          if log: log("   unknown charset: '%s'" % parser.charset)
168    else:
169       if log: log("   title          : %s" % title)
170
171    if title:
172       final_title = recode_entities(converted_title, universal_charset)
173       parts = [s.strip() for s in final_title.replace('\r', '').split('\n')]
174       final_title = ' '.join([s for s in parts if s])
175       if log and (final_title <> converted_title): log("   final title    : %s" % final_title)
176       parser.title = final_title
177
178    icon = parser.icon
179    if isinstance(icon, unicode):
180        try:
181            parser.icon = icon.encode('ascii')
182        except UnicodeEncodeError:
183            if parser.charset:
184                parser.icon = icon.encode(parser.charset)
185    return parser