]> git.phdru.name Git - bookmarks_db.git/blob - parse_html/bkmk_ph_beautifulsoup.py
Fixed a bug - restored DEFAULT_CHARSET.
[bookmarks_db.git] / parse_html / bkmk_ph_beautifulsoup.py
1 """HTML Parser using BeautifulSoup
2
3 This file is a part of Bookmarks database and Internet robot.
4 """
5
6 __version__ = "$Revision$"[11:-2]
7 __revision__ = "$Id$"[5:-2]
8 __date__ = "$Date$"[7:-2]
9 __author__ = "Oleg Broytman <phd@phdru.name>"
10 __copyright__ = "Copyright (C) 2007-2011 PhiloSoft Design"
11 __license__ = "GNU GPL"
12
13 __all__ = ['parse_html']
14
15
16 import re
17 from sgmllib import SGMLParser, SGMLParseError
18 from BeautifulSoup import BeautifulSoup, CData
19 from .bkmk_ph_util import HTMLParser
20
21 DEFAULT_CHARSET = "cp1251" # Stupid default for Russian Cyrillic
22
23 # http://groups.google.com/group/beautifulsoup/browse_thread/thread/69093cb0d3a3cf63
24 class BadDeclParser(BeautifulSoup):
25     def parse_declaration(self, i):
26          """Treat a bogus SGML declaration as raw data. Treat a CDATA
27          declaration as a CData object."""
28          j = None
29          if self.rawdata[i:i+9] == '<![CDATA[':
30               k = self.rawdata.find(']]>', i)
31               if k == -1:
32                   k = len(self.rawdata)
33               data = self.rawdata[i+9:k]
34               j = k+3
35               self._toStringSubclass(data, CData)
36          else:
37              try:
38                  j = SGMLParser.parse_declaration(self, i)
39              except SGMLParseError:
40                  # Could not parse the DOCTYPE declaration
41                  # Try to just skip the actual declaration
42                  match = re.search(r'<!DOCTYPE([^>]*?)>', self.rawdata[i:], re.MULTILINE|re.IGNORECASE)
43                  if match:
44                      toHandle = self.rawdata[i:match.end()]
45                  else:
46                      toHandle = self.rawdata[i:]
47                  self.handle_data(toHandle)
48                  j = i + len(toHandle)
49          return j
50
51
52 def _parse_html(filename, charset):
53    infile = open(filename, 'r')
54    try:
55       return BadDeclParser(infile, fromEncoding=charset)
56    except TypeError:
57       return None
58    finally:
59       infile.close()
60
61 def parse_html(filename, charset=None, log=None):
62    root = _parse_html(filename, charset)
63    if root is None:
64       return None
65
66    _charset = root.originalEncoding
67    if _charset in ("ISO-8859-2", "windows-1252", "MacCyrillic"): # Replace default
68       _charset = DEFAULT_CHARSET
69       root = _parse_html(filename, _charset)
70       if root is None:
71          return None
72
73    html = root.html
74    if html is None:
75       html = root
76
77    head = html.head
78    if head is None:
79       head = html # Some sites put TITLE in HTML without HEAD
80
81    title = head.title
82    if (title is None) and (html is not head):
83       # Some sites put TITLE in HTML outside of HEAD
84       title = html.title
85
86    if title is None:
87       # Lookup TITLE in the root
88       title = root.title
89
90    if title is None:
91       return None
92
93    if title.string:
94       title = title.string
95    else:
96       parts = []
97       for part in title:
98          if not isinstance(part, basestring):
99             part = unicode(part)
100          parts.append(part.strip())
101       title = ''.join(parts)
102
103    meta = head.find(_find_contenttype, recursive=False)
104    if meta:
105       try:
106          meta_content = meta.get("content")
107          if meta_content:
108              __charset = meta_content.lower().split('charset=')[1].split(';')[0]
109          else:
110              __charset = False
111       except IndexError: # No charset in the META Content-Type
112          meta_charset = False
113       else:
114          meta_charset = _charset == __charset
115    else:
116       meta_charset = False
117
118    if _charset or meta_charset:
119       title = title.encode(_charset or meta_charset)
120
121    meta = head.find(_find_refresh, recursive=False)
122    if meta:
123       refresh = meta.get("content")
124    else:
125       refresh = None
126
127    meta = head.find(_find_icon, recursive=False)
128    if meta:
129       icon = meta.get("href")
130    else:
131       icon = None
132
133    return HTMLParser(_charset, meta_charset, title, refresh, icon)
134
135 def _find_contenttype(Tag):
136    return (Tag.name == "meta") and \
137       (Tag.get("http-equiv", '').lower() == "content-type")
138
139 def _find_refresh(Tag):
140    return (Tag.name == "meta") and \
141       (Tag.get("http-equiv", '').lower() == "refresh")
142
143 def _find_icon(Tag):
144    return (Tag.name == "link") and \
145       (Tag.get("rel", '').lower() in ('icon', 'shortcut icon'))