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