From 7df53fc92fa5c726d840236dd16abdcf8cae7cbb Mon Sep 17 00:00:00 2001 From: Gujs Date: Tue, 18 Oct 2011 00:23:26 +0200 Subject: [PATCH] Project ION: remove unvanted files --- .../plugin.program.repo.installer/addon.xml | 18 - .../changelog.txt | 17 - .../plugin.program.repo.installer/default.py | 376 ---- .../plugin.program.repo.installer/icon.png | Bin 54033 -> 0 bytes .../resources/__init__.py | 1 - .../resources/language/english/strings.xml | 37 - .../resources/language/french/strings.xml | 37 - .../resources/lib/BeautifulSoup.py | 1965 ----------------- .../resources/lib/DialogRepoInfo.py | 121 - .../resources/lib/__init__.py | 1 - .../resources/lib/extractor.py | 242 -- .../resources/lib/rarfile.py | 488 ---- .../resources/lib/shutil2.py | 305 --- .../resources/lib/wikiparser.py | 71 - .../media/DefaultAddonRepository.png | Bin 33818 -> 0 bytes .../resources/settings.xml | 9 - .../skins/Default/720p/DialogRepoInfo.xml | 476 ---- .../skins/Default/media/DefaultIconError.png | Bin 16139 -> 0 bytes .../skins/Default/media/DialogBack.png | Bin 35982 -> 0 bytes .../Default/media/DialogCloseButton-focus.png | Bin 5216 -> 0 bytes .../skins/Default/media/DialogCloseButton.png | Bin 4522 -> 0 bytes .../skins/Default/media/GlassTitleBar.png | Bin 8023 -> 0 bytes .../Default/media/OverlayDialogBackground.png | Bin 7807 -> 0 bytes .../skins/Default/media/ScrollBarNib.png | Bin 2877 -> 0 bytes .../skins/Default/media/ScrollBarV.png | Bin 1235 -> 0 bytes .../skins/Default/media/ScrollBarV_bar.png | Bin 3454 -> 0 bytes .../Default/media/ScrollBarV_bar_focus.png | Bin 3497 -> 0 bytes .../skins/Default/media/black-back2.png | Bin 23797 -> 0 bytes .../skins/Default/media/scroll-up.png | Bin 3378 -> 0 bytes .../skins/Default/media/separator.png | Bin 2984 -> 0 bytes 30 files changed, 4164 deletions(-) delete mode 100644 projects/ION/filesystem/usr/share/xbmc/addons/plugin.program.repo.installer/addon.xml delete mode 100644 projects/ION/filesystem/usr/share/xbmc/addons/plugin.program.repo.installer/changelog.txt delete mode 100644 projects/ION/filesystem/usr/share/xbmc/addons/plugin.program.repo.installer/default.py delete mode 100644 projects/ION/filesystem/usr/share/xbmc/addons/plugin.program.repo.installer/icon.png delete mode 100644 projects/ION/filesystem/usr/share/xbmc/addons/plugin.program.repo.installer/resources/__init__.py delete mode 100644 projects/ION/filesystem/usr/share/xbmc/addons/plugin.program.repo.installer/resources/language/english/strings.xml delete mode 100644 projects/ION/filesystem/usr/share/xbmc/addons/plugin.program.repo.installer/resources/language/french/strings.xml delete mode 100644 projects/ION/filesystem/usr/share/xbmc/addons/plugin.program.repo.installer/resources/lib/BeautifulSoup.py delete mode 100644 projects/ION/filesystem/usr/share/xbmc/addons/plugin.program.repo.installer/resources/lib/DialogRepoInfo.py delete mode 100644 projects/ION/filesystem/usr/share/xbmc/addons/plugin.program.repo.installer/resources/lib/__init__.py delete mode 100644 projects/ION/filesystem/usr/share/xbmc/addons/plugin.program.repo.installer/resources/lib/extractor.py delete mode 100644 projects/ION/filesystem/usr/share/xbmc/addons/plugin.program.repo.installer/resources/lib/rarfile.py delete mode 100644 projects/ION/filesystem/usr/share/xbmc/addons/plugin.program.repo.installer/resources/lib/shutil2.py delete mode 100644 projects/ION/filesystem/usr/share/xbmc/addons/plugin.program.repo.installer/resources/lib/wikiparser.py delete mode 100644 projects/ION/filesystem/usr/share/xbmc/addons/plugin.program.repo.installer/resources/media/DefaultAddonRepository.png delete mode 100644 projects/ION/filesystem/usr/share/xbmc/addons/plugin.program.repo.installer/resources/settings.xml delete mode 100644 projects/ION/filesystem/usr/share/xbmc/addons/plugin.program.repo.installer/resources/skins/Default/720p/DialogRepoInfo.xml delete mode 100644 projects/ION/filesystem/usr/share/xbmc/addons/plugin.program.repo.installer/resources/skins/Default/media/DefaultIconError.png delete mode 100644 projects/ION/filesystem/usr/share/xbmc/addons/plugin.program.repo.installer/resources/skins/Default/media/DialogBack.png delete mode 100644 projects/ION/filesystem/usr/share/xbmc/addons/plugin.program.repo.installer/resources/skins/Default/media/DialogCloseButton-focus.png delete mode 100644 projects/ION/filesystem/usr/share/xbmc/addons/plugin.program.repo.installer/resources/skins/Default/media/DialogCloseButton.png delete mode 100644 projects/ION/filesystem/usr/share/xbmc/addons/plugin.program.repo.installer/resources/skins/Default/media/GlassTitleBar.png delete mode 100644 projects/ION/filesystem/usr/share/xbmc/addons/plugin.program.repo.installer/resources/skins/Default/media/OverlayDialogBackground.png delete mode 100644 projects/ION/filesystem/usr/share/xbmc/addons/plugin.program.repo.installer/resources/skins/Default/media/ScrollBarNib.png delete mode 100644 projects/ION/filesystem/usr/share/xbmc/addons/plugin.program.repo.installer/resources/skins/Default/media/ScrollBarV.png delete mode 100644 projects/ION/filesystem/usr/share/xbmc/addons/plugin.program.repo.installer/resources/skins/Default/media/ScrollBarV_bar.png delete mode 100644 projects/ION/filesystem/usr/share/xbmc/addons/plugin.program.repo.installer/resources/skins/Default/media/ScrollBarV_bar_focus.png delete mode 100644 projects/ION/filesystem/usr/share/xbmc/addons/plugin.program.repo.installer/resources/skins/Default/media/black-back2.png delete mode 100644 projects/ION/filesystem/usr/share/xbmc/addons/plugin.program.repo.installer/resources/skins/Default/media/scroll-up.png delete mode 100644 projects/ION/filesystem/usr/share/xbmc/addons/plugin.program.repo.installer/resources/skins/Default/media/separator.png diff --git a/projects/ION/filesystem/usr/share/xbmc/addons/plugin.program.repo.installer/addon.xml b/projects/ION/filesystem/usr/share/xbmc/addons/plugin.program.repo.installer/addon.xml deleted file mode 100644 index e4f298778c..0000000000 --- a/projects/ION/filesystem/usr/share/xbmc/addons/plugin.program.repo.installer/addon.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - executable - - - Installer of Add-on Repositories for XBMC - This Add-on allow to select (from XBMC Wiki) and install Repositories of XBMC Add-ons - all - - diff --git a/projects/ION/filesystem/usr/share/xbmc/addons/plugin.program.repo.installer/changelog.txt b/projects/ION/filesystem/usr/share/xbmc/addons/plugin.program.repo.installer/changelog.txt deleted file mode 100644 index 771352bde0..0000000000 --- a/projects/ION/filesystem/usr/share/xbmc/addons/plugin.program.repo.installer/changelog.txt +++ /dev/null @@ -1,17 +0,0 @@ -2011-03-21 Version 1.0.3 by Temhil -- Added Repository info window -- Set default title display option without description - -2011-03-17 Version 1.0.2 by Temhil -- Added option to add or not description from title -- Added option for activating or not color of description (set it by default) -- Removed Bold Title - -2011-03-15 - Version 1.0.1 by Temhil -- Added Icon (thank to Willynuisance) -- Added settings allowing to change color of description - -2011-03-13 - Version 1.0.0 by Temhil and Frost -- Creation (installation part based on Frost work with script.addon.installer) - - diff --git a/projects/ION/filesystem/usr/share/xbmc/addons/plugin.program.repo.installer/default.py b/projects/ION/filesystem/usr/share/xbmc/addons/plugin.program.repo.installer/default.py deleted file mode 100644 index 8c3b59c66e..0000000000 --- a/projects/ION/filesystem/usr/share/xbmc/addons/plugin.program.repo.installer/default.py +++ /dev/null @@ -1,376 +0,0 @@ -# -*- coding: cp1252 -*- -""" - Repository Installer Addon (plugin type) allowing to find and install addon repositories for XBMC - - Changelog: - - 03-21-2011 Version 1.0.3 by Temhil - - Added Repository info window - - Set default title display option without description - - 03-17-2011 Version 1.0.2 by Temhil - - Added option to add or not description from title - - Added option for activating or not color of description (set it by default) - - Removed Bold Title - - 03-15-2011 Version 1.0.1 by Temhil - - Added Icon (thank to Willynuisance) - - Added settings allowing to change color of description - - 03-13-2011 Version 1.0.0 by Temhil and Frost - - Creation (installation part based on Frost work with script.addon.installer) -""" - -REMOTE_DBG = False # For remote debugging with PyDev (Eclipse) - - -__script__ = "Unknown" -__plugin__ = "Repositories Installer" -__addonID__ = "plugin.program.repo.installer" -__author__ = "Temhil and Frost (http://passion-xbmc.org)" -__url__ = "http://passion-xbmc.org/index.php" -__svn_url__ = "http://passion-xbmc.googlecode.com/svn/trunk/addons/plugin.program.repository.installer/" -__credits__ = "Team XBMC Passion" -__platform__ = "xbmc media center" -__date__ = "03-21-2011" -__version__ = "1.0.3" -__svn_revision__ = 0 - - -import os -import urllib -from traceback import print_exc - -# xbmc modules -import xbmc -import xbmcplugin -import xbmcgui -import xbmcaddon - - - -__addon__ = xbmcaddon.Addon( __addonID__ ) -__settings__ = __addon__ -__language__ = __addon__.getLocalizedString -__addonDir__ = __settings__.getAddonInfo( "path" ) - - -# Remote debugger using Eclipse and Pydev -if REMOTE_DBG: - # Note pydevd module need to be copied in XBMC\system\python\Lib\pysrc - try: - import pysrc.pydevd as pydevd - pydevd.settrace('localhost', stdoutToServer=True, stderrToServer=True) - except ImportError: - sys.stderr.write("Error: " + - "You must add org.python.pydev.debug.pysrc to XBMC\system\python\Lib\pysrc") - sys.exit(1) - - -ROOTDIR = os.getcwd() -BASE_RESOURCE_PATH = os.path.join( ROOTDIR, "resources" ) -MEDIA_PATH = os.path.join( BASE_RESOURCE_PATH, "media" ) -ADDON_DATA = xbmc.translatePath( "special://profile/addon_data/%s/" % __addonID__ ) -REPO_LIST_URL = "http://wiki.xbmc.org/index.php?title=Unofficial_Add-on_Repositories" -REPO_PACKAGE_DIR = "special://home/addons/packages/" -REPO_INSTALL_DIR = "special://home/addons/" - -DIALOG_PROGRESS = xbmcgui.DialogProgress() - -#modules custom -try: - import resources.lib.wikiparser as wikiparser -except: - print_exc() - - - - -class RepoInstallerPlugin: - """ - main plugin class - """ - # define param key names - PARAM_NAME = 'name' - PARAM_ACTION = 'action' - PARAM_URL = 'url' - VALUE_INSTALL_FROM_ZIP = 'installfromzip' - VALUE_INSTALL_FROM_REPO = 'installfromrepo' - VALUE_INSTALL_ALL = 'installfromzip' - VALUE_DISPLAY_INFO = 'displayinfo' - - # Constant - colorList = ["red", "green", "yellow", "lightblue", None] - debugMode = False - shortTitleDisplay = False - - - def __init__( self, *args, **kwargs ): - - # Parse plugin parameters - self.parameters = self._parse_params() - - # Check settings - #if ( __settings__.getSetting('first_run') == 'true' ): - # #xbmcplugin.openSettings(sys.argv[0]) - #else: - # self.select() - self._set_title_display() - self.select() - - - def create_root_dir ( self ): - print "createRootDir" - xbmcplugin.setPluginCategory( handle=int( sys.argv[ 1 ] ), category=__language__( 30001 ) ) - print "Loading wiki page: %s"%REPO_LIST_URL - wikiparser.getRepoList(REPO_LIST_URL, addItemFunc=self._addLink, progressBar=None, msgFunc=None ) - self._add_sort_methods( True ) - self._end_of_directory( True ) - - - def install_repo(self, repoName, repoURL): - """ - Install a repository in XBMC - -> will need XBMC restart in order to have the new Repo taken in account by XBMC - """ - continueInstall = True - dialogYesNo = xbmcgui.Dialog() - if dialogYesNo.yesno(repoName, __language__( 30100 ), __language__( 30101 )): - if continueInstall: - ri = RepoInstaller() - - newRepo = ri.download( repoURL ) - print newRepo - - if newRepo: - fp, ok = ri.install( newRepo ) - print "---" - print fp, ok - xbmc.executebuiltin( 'XBMC.UpdateAddonRepos()' ) - try: - _N_ = Addon( os.path.basename( fp ) ) - print "Addon %s Installed"%s_N_ - ri.notification( _N_.getAddonInfo( "name" ), __language__( 24065 ).encode( "utf-8" ), 5000, _N_.getAddonInfo( "icon" ) ) - except: - xbmcgui.Dialog().ok( __settings__.getAddonInfo( "name" ), __language__( 30007 ) + " : " + repoName, __language__( 30010 ) ) - self._end_of_directory( True, update=False ) - - - - def select( self ): - try: - print "select" - print self.parameters - if len(self.parameters) < 1: - self.create_root_dir() - - elif self.PARAM_ACTION in self.parameters.keys(): - if self.parameters[self.PARAM_ACTION] == self.VALUE_INSTALL_FROM_ZIP: - repoName = self.parameters[self.PARAM_NAME] - repoURL = self.parameters[self.PARAM_URL] - #print repoName - #print repoURL - #xbmc.executebuiltin('XBMC.ActivateWindow(146)') - #xbmc.executebuiltin( "Action(Info)") - - self.install_repo(repoName, repoURL) - elif self.parameters[self.PARAM_ACTION] == self.VALUE_DISPLAY_INFO: - try: - from resources.lib.DialogRepoInfo import DialogRepoInfo - repoWindow = DialogRepoInfo( "DialogRepoInfo.xml", os.getcwd(), "Default", "720p" ) - del repoWindow - except: - print_exc() - self._end_of_directory( False ) - else: - self._end_of_directory( True, update=False ) - - except: - print_exc() - self._end_of_directory( False ) - - - def _parse_params( self ): - """ - Parses Plugin parameters and returns it as a dictionary - """ - paramDic={} - # Parameters are on the 3rd arg passed to the script - paramStr=sys.argv[2] - print paramStr - if len(paramStr)>1: - paramStr = paramStr.replace('?','') - - # Ignore last char if it is a '/' - if (paramStr[len(paramStr)-1]=='/'): - paramStr=paramStr[0:len(paramStr)-2] - - # Processing each parameter splited on '&' - for param in paramStr.split("&"): - try: - # Splitting couple key/value - key,value=param.split("=") - except: - key=param - value="" - - key = urllib.unquote_plus(key) - value = urllib.unquote_plus(value) - - # Filling dictionary - paramDic[key]=value - print paramDic - return paramDic - - - def _create_param_url(self, paramsDic): - """ - Create an plugin URL based on the key/value passed in a dictionary - """ - url = sys.argv[ 0 ] - sep = '?' - print paramsDic - try: - for param in paramsDic: - #TODO: solve error on name with non ascii char (generate exception) - url = url + sep + urllib.quote_plus( param ) + '=' + urllib.quote_plus( paramsDic[param] ) - sep = '&' - except: - url = None - print_exc() - return url - - def _set_title_display(self): - descriptInTitle =__settings__.getSetting('desintitle') - if descriptInTitle == 'true': - self.shortTitleDisplay = False - else: - self.shortTitleDisplay = True - - def _addLink( self, itemInfo ): - """ - Add a link to the list of items - """ - ok=True - - print itemInfo - - if itemInfo["ImageUrl"]: - icon = itemInfo["ImageUrl"] - else: - #icon = "DefaultFolder.png" - #icon = "DefaultAddon.png" - icon = os.path.join(MEDIA_PATH, "DefaultAddonRepository.png") - - descriptColor = self.colorList[ int( __settings__.getSetting( "descolor" ) ) ] - - if self.shortTitleDisplay: - labelTxt = itemInfo["name"] - else: - labelTxt = itemInfo["name"] + ": " + self._coloring( itemInfo["description"], descriptColor ) - liz=xbmcgui.ListItem( label=labelTxt, iconImage=icon, thumbnailImage=icon ) - liz.setInfo( type="addons", - infoLabels={ "title": itemInfo["name"], "Plot": itemInfo["description"] } ) - liz.setProperty("Addon.Name",itemInfo["name"]) - liz.setProperty("Addon.Version"," ") - liz.setProperty("Addon.Summary", "") - liz.setProperty("Addon.Description", itemInfo["description"]) - liz.setProperty("Addon.Type", __language__( 30011 )) - liz.setProperty("Addon.Creator", itemInfo["owner"]) - liz.setProperty("Addon.Disclaimer","") - liz.setProperty("Addon.Changelog", "") - liz.setProperty("Addon.ID", "") - liz.setProperty("Addon.Status", "Stable") - liz.setProperty("Addon.Broken", "Stable") - liz.setProperty("Addon.Path","") - liz.setProperty("Addon.Icon",icon) - - - - #dirItem.addContextMenuItem( self.Addon.getLocalizedString( 30900 ), "XBMC.RunPlugin(%s?showtimes=%s)" % ( sys.argv[ 0 ], urllib.quote_plus( repr( video[ "title" ] ) ), ) ) - paramsMenu = {} - paramsMenu[self.PARAM_NAME] = itemInfo["name"] - paramsMenu[self.PARAM_ACTION] = self.VALUE_DISPLAY_INFO - urlMenu = self._create_param_url( paramsMenu ) - if urlMenu: - c_items = [ ( __language__( 30012 ), "XBMC.RunPlugin(%s)" % ( urlMenu)) ] - liz.addContextMenuItems( c_items ) - params = {} - params[self.PARAM_NAME] = itemInfo["name"] - params[self.PARAM_ACTION] = self.VALUE_INSTALL_FROM_ZIP - params[self.PARAM_URL] = itemInfo["repoUrl"] - urlRepo = self._create_param_url( params ) - if urlRepo: - ok=xbmcplugin.addDirectoryItem( handle=int(sys.argv[1]), url=urlRepo, listitem=liz, isFolder=False ) - return ok - - - def _end_of_directory( self, OK, update=False ): - xbmcplugin.endOfDirectory( handle=int( sys.argv[ 1 ] ), succeeded=OK, updateListing=update )#, cacheToDisc=True )#updateListing = True, - - def _add_sort_methods( self, OK ): - if ( OK ): - try: - xbmcplugin.addSortMethod( handle=int( sys.argv[ 1 ] ), sortMethod=xbmcplugin.SORT_METHOD_UNSORTED ) - xbmcplugin.addSortMethod( handle=int( sys.argv[ 1 ] ), sortMethod=xbmcplugin.SORT_METHOD_LABEL ) - except: - print_exc() - - def _coloring( self, text , color ): - if color: - if color == "red": color="FFFF0000" - if color == "green": color="FF00FF00" - if color == "yellow": color="FFFFFF00" - if color == "lightblue": color="FFB1C7EC" - colored_text = "[COLOR=%s]%s[/COLOR]" % ( color , text ) - else: - colored_text = text - return colored_text - - def _bold_text( self, text ): - """ FONCTION POUR METTRE UN MOT GRAS """ - return "[B]%s[/B]" % ( text, ) - - - -class RepoInstaller: - """ - main plugin class - """ - def download( self, url, destination=REPO_PACKAGE_DIR ): - try: - DIALOG_PROGRESS.create( __settings__.getAddonInfo( "name" ) ) - destination = xbmc.translatePath( destination ) + os.path.basename( url ) - def _report_hook( count, blocksize, totalsize ): - percent = int( float( count * blocksize * 100 ) / totalsize ) - DIALOG_PROGRESS.update( percent, __language__( 30005 ) % url, __language__( 30006 ) % destination ) - fp, h = urllib.urlretrieve( url, destination, _report_hook ) - print fp, h - return fp - except: - print_exc() - DIALOG_PROGRESS.close() - return "" - - - def install( self, filename ): - from resources.lib.extractor import extract - return extract( filename, xbmc.translatePath( REPO_INSTALL_DIR ) ) - - - def notification( self, header="", message="", sleep=5000, icon=__settings__.getAddonInfo( "icon" ) ): - """ Will display a notification dialog with the specified header and message, - in addition you can set the length of time it displays in milliseconds and a icon image. - """ - xbmc.executebuiltin( "XBMC.Notification(%s,%s,%i,%s)" % ( header, message, sleep, icon ) ) - - -####################################################################################################################### -# BEGIN ! -####################################################################################################################### - -if ( __name__ == "__main__" ): - try: - RepoInstallerPlugin() - except: - print_exc() diff --git a/projects/ION/filesystem/usr/share/xbmc/addons/plugin.program.repo.installer/icon.png b/projects/ION/filesystem/usr/share/xbmc/addons/plugin.program.repo.installer/icon.png deleted file mode 100644 index 82cafbcb807fe7b0239051d016a0ae231cf79e11..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 54033 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 - - - - Repositories Installer - Add-ons Repositories - - Downloading: %s - to: %s - Repository Installed - XBMC requires to restart! - Repository - Repository Info - - - - - Are you sure you want to Install this repository? - This will overwrite any repository with the same name - This repository is already installed - Do you want to continue and overwrite the existing repository? - - Error! - Error during %s repository install - Please check the logs - - - Appearance - Text color of description: - Red - Green - Yellow - Light Blue - None - - Display description with title - diff --git a/projects/ION/filesystem/usr/share/xbmc/addons/plugin.program.repo.installer/resources/language/french/strings.xml b/projects/ION/filesystem/usr/share/xbmc/addons/plugin.program.repo.installer/resources/language/french/strings.xml deleted file mode 100644 index 590360f918..0000000000 --- a/projects/ION/filesystem/usr/share/xbmc/addons/plugin.program.repo.installer/resources/language/french/strings.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - Repositories Installer - Repositories d'Add-ons - - Téléchargement: %s - vers: %s - Repository Installé - XBMC doit redémarrer! - Repository - Infos Repository - - - - - Etes vous sure de vouloir installer ce repository? - Cela écrasera tout repository du meme nom - CE repository est déja installé - Voulez-vous continuer et écraser le repository existant? - - Erreur! - Erreur durant l'installation du repository %s - Veuillez vérifier les logs - - - Apparence - Couleur du texte de description: - Rouge - Vert - Jaune - Bleu Ciel - Aucune - - Afficher la description avec le titre - diff --git a/projects/ION/filesystem/usr/share/xbmc/addons/plugin.program.repo.installer/resources/lib/BeautifulSoup.py b/projects/ION/filesystem/usr/share/xbmc/addons/plugin.program.repo.installer/resources/lib/BeautifulSoup.py deleted file mode 100644 index 0e214630c8..0000000000 --- a/projects/ION/filesystem/usr/share/xbmc/addons/plugin.program.repo.installer/resources/lib/BeautifulSoup.py +++ /dev/null @@ -1,1965 +0,0 @@ -"""Beautiful Soup -Elixir and Tonic -"The Screen-Scraper's Friend" -http://www.crummy.com/software/BeautifulSoup/ - -Beautiful Soup parses a (possibly invalid) XML or HTML document into a -tree representation. It provides methods and Pythonic idioms that make -it easy to navigate, search, and modify the tree. - -A well-formed XML/HTML document yields a well-formed data -structure. An ill-formed XML/HTML document yields a correspondingly -ill-formed data structure. If your document is only locally -well-formed, you can use this library to find and process the -well-formed part of it. - -Beautiful Soup works with Python 2.2 and up. It has no external -dependencies, but you'll have more success at converting data to UTF-8 -if you also install these three packages: - -* chardet, for auto-detecting character encodings - http://chardet.feedparser.org/ -* cjkcodecs and iconv_codec, which add more encodings to the ones supported - by stock Python. - http://cjkpython.i18n.org/ - -Beautiful Soup defines classes for two main parsing strategies: - - * BeautifulStoneSoup, for parsing XML, SGML, or your domain-specific - language that kind of looks like XML. - - * BeautifulSoup, for parsing run-of-the-mill HTML code, be it valid - or invalid. This class has web browser-like heuristics for - obtaining a sensible parse tree in the face of common HTML errors. - -Beautiful Soup also defines a class (UnicodeDammit) for autodetecting -the encoding of an HTML or XML document, and converting it to -Unicode. Much of this code is taken from Mark Pilgrim's Universal Feed Parser. - -For more than you ever wanted to know about Beautiful Soup, see the -documentation: -http://www.crummy.com/software/BeautifulSoup/documentation.html - -Here, have some legalese: - -Copyright (c) 2004-2008, Leonard Richardson - -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - - * Neither the name of the the Beautiful Soup Consortium and All - Night Kosher Bakery nor the names of its contributors may be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR -CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE, DAMMIT. - -""" -from __future__ import generators - -__author__ = "Leonard Richardson (leonardr@segfault.org)" -__version__ = "3.0.7a" -__copyright__ = "Copyright (c) 2004-2008 Leonard Richardson" -__license__ = "New-style BSD" - -from sgmllib import SGMLParser, SGMLParseError -import codecs -import markupbase -import types -import re -import sgmllib -try: - from htmlentitydefs import name2codepoint -except ImportError: - name2codepoint = {} -try: - set -except NameError: - from sets import Set as set - -#These hacks make Beautiful Soup able to parse XML with namespaces -sgmllib.tagfind = re.compile('[a-zA-Z][-_.:a-zA-Z0-9]*') -markupbase._declname_match = re.compile(r'[a-zA-Z][-_.:a-zA-Z0-9]*\s*').match - -DEFAULT_OUTPUT_ENCODING = "utf-8" - -# First, the classes that represent markup elements. - -class PageElement: - """Contains the navigational information for some part of the page - (either a tag or a piece of text)""" - - def setup(self, parent=None, previous=None): - """Sets up the initial relations between this element and - other elements.""" - self.parent = parent - self.previous = previous - self.next = None - self.previousSibling = None - self.nextSibling = None - if self.parent and self.parent.contents: - self.previousSibling = self.parent.contents[-1] - self.previousSibling.nextSibling = self - - def replaceWith(self, replaceWith): - oldParent = self.parent - myIndex = self.parent.contents.index(self) - if hasattr(replaceWith, 'parent') and replaceWith.parent == self.parent: - # We're replacing this element with one of its siblings. - index = self.parent.contents.index(replaceWith) - if index and index < myIndex: - # Furthermore, it comes before this element. That - # means that when we extract it, the index of this - # element will change. - myIndex = myIndex - 1 - self.extract() - oldParent.insert(myIndex, replaceWith) - - def extract(self): - """Destructively rips this element out of the tree.""" - if self.parent: - try: - self.parent.contents.remove(self) - except ValueError: - pass - - #Find the two elements that would be next to each other if - #this element (and any children) hadn't been parsed. Connect - #the two. - lastChild = self._lastRecursiveChild() - nextElement = lastChild.next - - if self.previous: - self.previous.next = nextElement - if nextElement: - nextElement.previous = self.previous - self.previous = None - lastChild.next = None - - self.parent = None - if self.previousSibling: - self.previousSibling.nextSibling = self.nextSibling - if self.nextSibling: - self.nextSibling.previousSibling = self.previousSibling - self.previousSibling = self.nextSibling = None - return self - - def _lastRecursiveChild(self): - "Finds the last element beneath this object to be parsed." - lastChild = self - while hasattr(lastChild, 'contents') and lastChild.contents: - lastChild = lastChild.contents[-1] - return lastChild - - def insert(self, position, newChild): - if (isinstance(newChild, basestring) - or isinstance(newChild, unicode)) \ - and not isinstance(newChild, NavigableString): - newChild = NavigableString(newChild) - - position = min(position, len(self.contents)) - if hasattr(newChild, 'parent') and newChild.parent != None: - # We're 'inserting' an element that's already one - # of this object's children. - if newChild.parent == self: - index = self.find(newChild) - if index and index < position: - # Furthermore we're moving it further down the - # list of this object's children. That means that - # when we extract this element, our target index - # will jump down one. - position = position - 1 - newChild.extract() - - newChild.parent = self - previousChild = None - if position == 0: - newChild.previousSibling = None - newChild.previous = self - else: - previousChild = self.contents[position-1] - newChild.previousSibling = previousChild - newChild.previousSibling.nextSibling = newChild - newChild.previous = previousChild._lastRecursiveChild() - if newChild.previous: - newChild.previous.next = newChild - - newChildsLastElement = newChild._lastRecursiveChild() - - if position >= len(self.contents): - newChild.nextSibling = None - - parent = self - parentsNextSibling = None - while not parentsNextSibling: - parentsNextSibling = parent.nextSibling - parent = parent.parent - if not parent: # This is the last element in the document. - break - if parentsNextSibling: - newChildsLastElement.next = parentsNextSibling - else: - newChildsLastElement.next = None - else: - nextChild = self.contents[position] - newChild.nextSibling = nextChild - if newChild.nextSibling: - newChild.nextSibling.previousSibling = newChild - newChildsLastElement.next = nextChild - - if newChildsLastElement.next: - newChildsLastElement.next.previous = newChildsLastElement - self.contents.insert(position, newChild) - - def append(self, tag): - """Appends the given tag to the contents of this tag.""" - self.insert(len(self.contents), tag) - - def findNext(self, name=None, attrs={}, text=None, **kwargs): - """Returns the first item that matches the given criteria and - appears after this Tag in the document.""" - return self._findOne(self.findAllNext, name, attrs, text, **kwargs) - - def findAllNext(self, name=None, attrs={}, text=None, limit=None, - **kwargs): - """Returns all items that match the given criteria and appear - after this Tag in the document.""" - return self._findAll(name, attrs, text, limit, self.nextGenerator, - **kwargs) - - def findNextSibling(self, name=None, attrs={}, text=None, **kwargs): - """Returns the closest sibling to this Tag that matches the - given criteria and appears after this Tag in the document.""" - return self._findOne(self.findNextSiblings, name, attrs, text, - **kwargs) - - def findNextSiblings(self, name=None, attrs={}, text=None, limit=None, - **kwargs): - """Returns the siblings of this Tag that match the given - criteria and appear after this Tag in the document.""" - return self._findAll(name, attrs, text, limit, - self.nextSiblingGenerator, **kwargs) - fetchNextSiblings = findNextSiblings # Compatibility with pre-3.x - - def findPrevious(self, name=None, attrs={}, text=None, **kwargs): - """Returns the first item that matches the given criteria and - appears before this Tag in the document.""" - return self._findOne(self.findAllPrevious, name, attrs, text, **kwargs) - - def findAllPrevious(self, name=None, attrs={}, text=None, limit=None, - **kwargs): - """Returns all items that match the given criteria and appear - before this Tag in the document.""" - return self._findAll(name, attrs, text, limit, self.previousGenerator, - **kwargs) - fetchPrevious = findAllPrevious # Compatibility with pre-3.x - - def findPreviousSibling(self, name=None, attrs={}, text=None, **kwargs): - """Returns the closest sibling to this Tag that matches the - given criteria and appears before this Tag in the document.""" - return self._findOne(self.findPreviousSiblings, name, attrs, text, - **kwargs) - - def findPreviousSiblings(self, name=None, attrs={}, text=None, - limit=None, **kwargs): - """Returns the siblings of this Tag that match the given - criteria and appear before this Tag in the document.""" - return self._findAll(name, attrs, text, limit, - self.previousSiblingGenerator, **kwargs) - fetchPreviousSiblings = findPreviousSiblings # Compatibility with pre-3.x - - def findParent(self, name=None, attrs={}, **kwargs): - """Returns the closest parent of this Tag that matches the given - criteria.""" - # NOTE: We can't use _findOne because findParents takes a different - # set of arguments. - r = None - l = self.findParents(name, attrs, 1) - if l: - r = l[0] - return r - - def findParents(self, name=None, attrs={}, limit=None, **kwargs): - """Returns the parents of this Tag that match the given - criteria.""" - - return self._findAll(name, attrs, None, limit, self.parentGenerator, - **kwargs) - fetchParents = findParents # Compatibility with pre-3.x - - #These methods do the real heavy lifting. - - def _findOne(self, method, name, attrs, text, **kwargs): - r = None - l = method(name, attrs, text, 1, **kwargs) - if l: - r = l[0] - return r - - def _findAll(self, name, attrs, text, limit, generator, **kwargs): - "Iterates over a generator looking for things that match." - - if isinstance(name, SoupStrainer): - strainer = name - else: - # Build a SoupStrainer - strainer = SoupStrainer(name, attrs, text, **kwargs) - results = ResultSet(strainer) - g = generator() - while True: - try: - i = g.next() - except StopIteration: - break - if i: - found = strainer.search(i) - if found: - results.append(found) - if limit and len(results) >= limit: - break - return results - - #These Generators can be used to navigate starting from both - #NavigableStrings and Tags. - def nextGenerator(self): - i = self - while i: - i = i.next - yield i - - def nextSiblingGenerator(self): - i = self - while i: - i = i.nextSibling - yield i - - def previousGenerator(self): - i = self - while i: - i = i.previous - yield i - - def previousSiblingGenerator(self): - i = self - while i: - i = i.previousSibling - yield i - - def parentGenerator(self): - i = self - while i: - i = i.parent - yield i - - # Utility methods - def substituteEncoding(self, str, encoding=None): - encoding = encoding or "utf-8" - return str.replace("%SOUP-ENCODING%", encoding) - - def toEncoding(self, s, encoding=None): - """Encodes an object to a string in some encoding, or to Unicode. - .""" - if isinstance(s, unicode): - if encoding: - s = s.encode(encoding) - elif isinstance(s, str): - if encoding: - s = s.encode(encoding) - else: - s = unicode(s) - else: - if encoding: - s = self.toEncoding(str(s), encoding) - else: - s = unicode(s) - return s - -class NavigableString(unicode, PageElement): - - def __new__(cls, value): - """Create a new NavigableString. - - When unpickling a NavigableString, this method is called with - the string in DEFAULT_OUTPUT_ENCODING. That encoding needs to be - passed in to the superclass's __new__ or the superclass won't know - how to handle non-ASCII characters. - """ - if isinstance(value, unicode): - return unicode.__new__(cls, value) - return unicode.__new__(cls, value, DEFAULT_OUTPUT_ENCODING) - - def __getnewargs__(self): - return (NavigableString.__str__(self),) - - def __getattr__(self, attr): - """text.string gives you text. This is for backwards - compatibility for Navigable*String, but for CData* it lets you - get the string without the CData wrapper.""" - if attr == 'string': - return self - else: - raise AttributeError, "'%s' object has no attribute '%s'" % (self.__class__.__name__, attr) - - def __unicode__(self): - return str(self).decode(DEFAULT_OUTPUT_ENCODING) - - def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING): - if encoding: - return self.encode(encoding) - else: - return self - -class CData(NavigableString): - - def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING): - return "" % NavigableString.__str__(self, encoding) - -class ProcessingInstruction(NavigableString): - def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING): - output = self - if "%SOUP-ENCODING%" in output: - output = self.substituteEncoding(output, encoding) - return "" % self.toEncoding(output, encoding) - -class Comment(NavigableString): - def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING): - return "" % NavigableString.__str__(self, encoding) - -class Declaration(NavigableString): - def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING): - return "" % NavigableString.__str__(self, encoding) - -class Tag(PageElement): - - """Represents a found HTML tag with its attributes and contents.""" - - def _invert(h): - "Cheap function to invert a hash." - i = {} - for k,v in h.items(): - i[v] = k - return i - - XML_ENTITIES_TO_SPECIAL_CHARS = { "apos" : "'", - "quot" : '"', - "amp" : "&", - "lt" : "<", - "gt" : ">" } - - XML_SPECIAL_CHARS_TO_ENTITIES = _invert(XML_ENTITIES_TO_SPECIAL_CHARS) - - def _convertEntities(self, match): - """Used in a call to re.sub to replace HTML, XML, and numeric - entities with the appropriate Unicode characters. If HTML - entities are being converted, any unrecognized entities are - escaped.""" - x = match.group(1) - if self.convertHTMLEntities and x in name2codepoint: - return unichr(name2codepoint[x]) - elif x in self.XML_ENTITIES_TO_SPECIAL_CHARS: - if self.convertXMLEntities: - return self.XML_ENTITIES_TO_SPECIAL_CHARS[x] - else: - return u'&%s;' % x - elif len(x) > 0 and x[0] == '#': - # Handle numeric entities - if len(x) > 1 and x[1] == 'x': - return unichr(int(x[2:], 16)) - else: - return unichr(int(x[1:])) - - elif self.escapeUnrecognizedEntities: - return u'&%s;' % x - else: - return u'&%s;' % x - - def __init__(self, parser, name, attrs=None, parent=None, - previous=None): - "Basic constructor." - - # We don't actually store the parser object: that lets extracted - # chunks be garbage-collected - self.parserClass = parser.__class__ - self.isSelfClosing = parser.isSelfClosingTag(name) - self.name = name - if attrs == None: - attrs = [] - self.attrs = attrs - self.contents = [] - self.setup(parent, previous) - self.hidden = False - self.containsSubstitutions = False - self.convertHTMLEntities = parser.convertHTMLEntities - self.convertXMLEntities = parser.convertXMLEntities - self.escapeUnrecognizedEntities = parser.escapeUnrecognizedEntities - - # Convert any HTML, XML, or numeric entities in the attribute values. - convert = lambda(k, val): (k, - re.sub("&(#\d+|#x[0-9a-fA-F]+|\w+);", - self._convertEntities, - val)) - self.attrs = map(convert, self.attrs) - - def get(self, key, default=None): - """Returns the value of the 'key' attribute for the tag, or - the value given for 'default' if it doesn't have that - attribute.""" - return self._getAttrMap().get(key, default) - - def has_key(self, key): - return self._getAttrMap().has_key(key) - - def __getitem__(self, key): - """tag[key] returns the value of the 'key' attribute for the tag, - and throws an exception if it's not there.""" - return self._getAttrMap()[key] - - def __iter__(self): - "Iterating over a tag iterates over its contents." - return iter(self.contents) - - def __len__(self): - "The length of a tag is the length of its list of contents." - return len(self.contents) - - def __contains__(self, x): - return x in self.contents - - def __nonzero__(self): - "A tag is non-None even if it has no contents." - return True - - def __setitem__(self, key, value): - """Setting tag[key] sets the value of the 'key' attribute for the - tag.""" - self._getAttrMap() - self.attrMap[key] = value - found = False - for i in range(0, len(self.attrs)): - if self.attrs[i][0] == key: - self.attrs[i] = (key, value) - found = True - if not found: - self.attrs.append((key, value)) - self._getAttrMap()[key] = value - - def __delitem__(self, key): - "Deleting tag[key] deletes all 'key' attributes for the tag." - for item in self.attrs: - if item[0] == key: - self.attrs.remove(item) - #We don't break because bad HTML can define the same - #attribute multiple times. - self._getAttrMap() - if self.attrMap.has_key(key): - del self.attrMap[key] - - def __call__(self, *args, **kwargs): - """Calling a tag like a function is the same as calling its - findAll() method. Eg. tag('a') returns a list of all the A tags - found within this tag.""" - return apply(self.findAll, args, kwargs) - - def __getattr__(self, tag): - #print "Getattr %s.%s" % (self.__class__, tag) - if len(tag) > 3 and tag.rfind('Tag') == len(tag)-3: - return self.find(tag[:-3]) - elif tag.find('__') != 0: - return self.find(tag) - raise AttributeError, "'%s' object has no attribute '%s'" % (self.__class__, tag) - - def __eq__(self, other): - """Returns true iff this tag has the same name, the same attributes, - and the same contents (recursively) as the given tag. - - NOTE: right now this will return false if two tags have the - same attributes in a different order. Should this be fixed?""" - if not hasattr(other, 'name') or not hasattr(other, 'attrs') or not hasattr(other, 'contents') or self.name != other.name or self.attrs != other.attrs or len(self) != len(other): - return False - for i in range(0, len(self.contents)): - if self.contents[i] != other.contents[i]: - return False - return True - - def __ne__(self, other): - """Returns true iff this tag is not identical to the other tag, - as defined in __eq__.""" - return not self == other - - def __repr__(self, encoding=DEFAULT_OUTPUT_ENCODING): - """Renders this tag as a string.""" - return self.__str__(encoding) - - def __unicode__(self): - return self.__str__(None) - - BARE_AMPERSAND_OR_BRACKET = re.compile("([<>]|" - + "&(?!#\d+;|#x[0-9a-fA-F]+;|\w+;)" - + ")") - - def _sub_entity(self, x): - """Used with a regular expression to substitute the - appropriate XML entity for an XML special character.""" - return "&" + self.XML_SPECIAL_CHARS_TO_ENTITIES[x.group(0)[0]] + ";" - - def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING, - prettyPrint=False, indentLevel=0): - """Returns a string or Unicode representation of this tag and - its contents. To get Unicode, pass None for encoding. - - NOTE: since Python's HTML parser consumes whitespace, this - method is not certain to reproduce the whitespace present in - the original string.""" - - encodedName = self.toEncoding(self.name, encoding) - - attrs = [] - if self.attrs: - for key, val in self.attrs: - fmt = '%s="%s"' - if isString(val): - if self.containsSubstitutions and '%SOUP-ENCODING%' in val: - val = self.substituteEncoding(val, encoding) - - # The attribute value either: - # - # * Contains no embedded double quotes or single quotes. - # No problem: we enclose it in double quotes. - # * Contains embedded single quotes. No problem: - # double quotes work here too. - # * Contains embedded double quotes. No problem: - # we enclose it in single quotes. - # * Embeds both single _and_ double quotes. This - # can't happen naturally, but it can happen if - # you modify an attribute value after parsing - # the document. Now we have a bit of a - # problem. We solve it by enclosing the - # attribute in single quotes, and escaping any - # embedded single quotes to XML entities. - if '"' in val: - fmt = "%s='%s'" - if "'" in val: - # TODO: replace with apos when - # appropriate. - val = val.replace("'", "&squot;") - - # Now we're okay w/r/t quotes. But the attribute - # value might also contain angle brackets, or - # ampersands that aren't part of entities. We need - # to escape those to XML entities too. - val = self.BARE_AMPERSAND_OR_BRACKET.sub(self._sub_entity, val) - - attrs.append(fmt % (self.toEncoding(key, encoding), - self.toEncoding(val, encoding))) - close = '' - closeTag = '' - if self.isSelfClosing: - close = ' /' - else: - closeTag = '' % encodedName - - indentTag, indentContents = 0, 0 - if prettyPrint: - indentTag = indentLevel - space = (' ' * (indentTag-1)) - indentContents = indentTag + 1 - contents = self.renderContents(encoding, prettyPrint, indentContents) - if self.hidden: - s = contents - else: - s = [] - attributeString = '' - if attrs: - attributeString = ' ' + ' '.join(attrs) - if prettyPrint: - s.append(space) - s.append('<%s%s%s>' % (encodedName, attributeString, close)) - if prettyPrint: - s.append("\n") - s.append(contents) - if prettyPrint and contents and contents[-1] != "\n": - s.append("\n") - if prettyPrint and closeTag: - s.append(space) - s.append(closeTag) - if prettyPrint and closeTag and self.nextSibling: - s.append("\n") - s = ''.join(s) - return s - - def decompose(self): - """Recursively destroys the contents of this tree.""" - contents = [i for i in self.contents] - for i in contents: - if isinstance(i, Tag): - i.decompose() - else: - i.extract() - self.extract() - - def prettify(self, encoding=DEFAULT_OUTPUT_ENCODING): - return self.__str__(encoding, True) - - def renderContents(self, encoding=DEFAULT_OUTPUT_ENCODING, - prettyPrint=False, indentLevel=0): - """Renders the contents of this tag as a string in the given - encoding. If encoding is None, returns a Unicode string..""" - s=[] - for c in self: - text = None - if isinstance(c, NavigableString): - text = c.__str__(encoding) - elif isinstance(c, Tag): - s.append(c.__str__(encoding, prettyPrint, indentLevel)) - if text and prettyPrint: - text = text.strip() - if text: - if prettyPrint: - s.append(" " * (indentLevel-1)) - s.append(text) - if prettyPrint: - s.append("\n") - return ''.join(s) - - #Soup methods - - def find(self, name=None, attrs={}, recursive=True, text=None, - **kwargs): - """Return only the first child of this Tag matching the given - criteria.""" - r = None - l = self.findAll(name, attrs, recursive, text, 1, **kwargs) - if l: - r = l[0] - return r - findChild = find - - def findAll(self, name=None, attrs={}, recursive=True, text=None, - limit=None, **kwargs): - """Extracts a list of Tag objects that match the given - criteria. You can specify the name of the Tag and any - attributes you want the Tag to have. - - The value of a key-value pair in the 'attrs' map can be a - string, a list of strings, a regular expression object, or a - callable that takes a string and returns whether or not the - string matches for some custom definition of 'matches'. The - same is true of the tag name.""" - generator = self.recursiveChildGenerator - if not recursive: - generator = self.childGenerator - return self._findAll(name, attrs, text, limit, generator, **kwargs) - findChildren = findAll - - # Pre-3.x compatibility methods - first = find - fetch = findAll - - def fetchText(self, text=None, recursive=True, limit=None): - return self.findAll(text=text, recursive=recursive, limit=limit) - - def firstText(self, text=None, recursive=True): - return self.find(text=text, recursive=recursive) - - #Private methods - - def _getAttrMap(self): - """Initializes a map representation of this tag's attributes, - if not already initialized.""" - if not getattr(self, 'attrMap'): - self.attrMap = {} - for (key, value) in self.attrs: - self.attrMap[key] = value - return self.attrMap - - #Generator methods - def childGenerator(self): - for i in range(0, len(self.contents)): - yield self.contents[i] - raise StopIteration - - def recursiveChildGenerator(self): - stack = [(self, 0)] - while stack: - tag, start = stack.pop() - if isinstance(tag, Tag): - for i in range(start, len(tag.contents)): - a = tag.contents[i] - yield a - if isinstance(a, Tag) and tag.contents: - if i < len(tag.contents) - 1: - stack.append((tag, i+1)) - stack.append((a, 0)) - break - raise StopIteration - -# Next, a couple classes to represent queries and their results. -class SoupStrainer: - """Encapsulates a number of ways of matching a markup element (tag or - text).""" - - def __init__(self, name=None, attrs={}, text=None, **kwargs): - self.name = name - if isString(attrs): - kwargs['class'] = attrs - attrs = None - if kwargs: - if attrs: - attrs = attrs.copy() - attrs.update(kwargs) - else: - attrs = kwargs - self.attrs = attrs - self.text = text - - def __str__(self): - if self.text: - return self.text - else: - return "%s|%s" % (self.name, self.attrs) - - def searchTag(self, markupName=None, markupAttrs={}): - found = None - markup = None - if isinstance(markupName, Tag): - markup = markupName - markupAttrs = markup - callFunctionWithTagData = callable(self.name) \ - and not isinstance(markupName, Tag) - - if (not self.name) \ - or callFunctionWithTagData \ - or (markup and self._matches(markup, self.name)) \ - or (not markup and self._matches(markupName, self.name)): - if callFunctionWithTagData: - match = self.name(markupName, markupAttrs) - else: - match = True - markupAttrMap = None - for attr, matchAgainst in self.attrs.items(): - if not markupAttrMap: - if hasattr(markupAttrs, 'get'): - markupAttrMap = markupAttrs - else: - markupAttrMap = {} - for k,v in markupAttrs: - markupAttrMap[k] = v - attrValue = markupAttrMap.get(attr) - if not self._matches(attrValue, matchAgainst): - match = False - break - if match: - if markup: - found = markup - else: - found = markupName - return found - - def search(self, markup): - #print 'looking for %s in %s' % (self, markup) - found = None - # If given a list of items, scan it for a text element that - # matches. - if isList(markup) and not isinstance(markup, Tag): - for element in markup: - if isinstance(element, NavigableString) \ - and self.search(element): - found = element - break - # If it's a Tag, make sure its name or attributes match. - # Don't bother with Tags if we're searching for text. - elif isinstance(markup, Tag): - if not self.text: - found = self.searchTag(markup) - # If it's text, make sure the text matches. - elif isinstance(markup, NavigableString) or \ - isString(markup): - if self._matches(markup, self.text): - found = markup - else: - raise Exception, "I don't know how to match against a %s" \ - % markup.__class__ - return found - - def _matches(self, markup, matchAgainst): - #print "Matching %s against %s" % (markup, matchAgainst) - result = False - if matchAgainst == True and type(matchAgainst) == types.BooleanType: - result = markup != None - elif callable(matchAgainst): - result = matchAgainst(markup) - else: - #Custom match methods take the tag as an argument, but all - #other ways of matching match the tag name as a string. - if isinstance(markup, Tag): - markup = markup.name - if markup and not isString(markup): - markup = unicode(markup) - #Now we know that chunk is either a string, or None. - if hasattr(matchAgainst, 'match'): - # It's a regexp object. - result = markup and matchAgainst.search(markup) - elif isList(matchAgainst): - result = markup in matchAgainst - elif hasattr(matchAgainst, 'items'): - result = markup.has_key(matchAgainst) - elif matchAgainst and isString(markup): - if isinstance(markup, unicode): - matchAgainst = unicode(matchAgainst) - else: - matchAgainst = str(matchAgainst) - - if not result: - result = matchAgainst == markup - return result - -class ResultSet(list): - """A ResultSet is just a list that keeps track of the SoupStrainer - that created it.""" - def __init__(self, source): - list.__init__([]) - self.source = source - -# Now, some helper functions. - -def isList(l): - """Convenience method that works with all 2.x versions of Python - to determine whether or not something is listlike.""" - return hasattr(l, '__iter__') \ - or (type(l) in (types.ListType, types.TupleType)) - -def isString(s): - """Convenience method that works with all 2.x versions of Python - to determine whether or not something is stringlike.""" - try: - return isinstance(s, unicode) or isinstance(s, basestring) - except NameError: - return isinstance(s, str) - -def buildTagMap(default, *args): - """Turns a list of maps, lists, or scalars into a single map. - Used to build the SELF_CLOSING_TAGS, NESTABLE_TAGS, and - NESTING_RESET_TAGS maps out of lists and partial maps.""" - built = {} - for portion in args: - if hasattr(portion, 'items'): - #It's a map. Merge it. - for k,v in portion.items(): - built[k] = v - elif isList(portion): - #It's a list. Map each item to the default. - for k in portion: - built[k] = default - else: - #It's a scalar. Map it to the default. - built[portion] = default - return built - -# Now, the parser classes. - -class BeautifulStoneSoup(Tag, SGMLParser): - - """This class contains the basic parser and search code. It defines - a parser that knows nothing about tag behavior except for the - following: - - You can't close a tag without closing all the tags it encloses. - That is, "" actually means - "". - - [Another possible explanation is "", but since - this class defines no SELF_CLOSING_TAGS, it will never use that - explanation.] - - This class is useful for parsing XML or made-up markup languages, - or when BeautifulSoup makes an assumption counter to what you were - expecting.""" - - SELF_CLOSING_TAGS = {} - NESTABLE_TAGS = {} - RESET_NESTING_TAGS = {} - QUOTE_TAGS = {} - PRESERVE_WHITESPACE_TAGS = [] - - MARKUP_MASSAGE = [(re.compile('(<[^<>]*)/>'), - lambda x: x.group(1) + ' />'), - (re.compile(']*)>'), - lambda x: '') - ] - - ROOT_TAG_NAME = u'[document]' - - HTML_ENTITIES = "html" - XML_ENTITIES = "xml" - XHTML_ENTITIES = "xhtml" - # TODO: This only exists for backwards-compatibility - ALL_ENTITIES = XHTML_ENTITIES - - # Used when determining whether a text node is all whitespace and - # can be replaced with a single space. A text node that contains - # fancy Unicode spaces (usually non-breaking) should be left - # alone. - STRIP_ASCII_SPACES = { 9: None, 10: None, 12: None, 13: None, 32: None, } - - def __init__(self, markup="", parseOnlyThese=None, fromEncoding=None, - markupMassage=True, smartQuotesTo=XML_ENTITIES, - convertEntities=None, selfClosingTags=None, isHTML=False): - """The Soup object is initialized as the 'root tag', and the - provided markup (which can be a string or a file-like object) - is fed into the underlying parser. - - sgmllib will process most bad HTML, and the BeautifulSoup - class has some tricks for dealing with some HTML that kills - sgmllib, but Beautiful Soup can nonetheless choke or lose data - if your data uses self-closing tags or declarations - incorrectly. - - By default, Beautiful Soup uses regexes to sanitize input, - avoiding the vast majority of these problems. If the problems - don't apply to you, pass in False for markupMassage, and - you'll get better performance. - - The default parser massage techniques fix the two most common - instances of invalid HTML that choke sgmllib: - -
(No space between name of closing tag and tag close) - (Extraneous whitespace in declaration) - - You can pass in a custom list of (RE object, replace method) - tuples to get Beautiful Soup to scrub your input the way you - want.""" - - self.parseOnlyThese = parseOnlyThese - self.fromEncoding = fromEncoding - self.smartQuotesTo = smartQuotesTo - self.convertEntities = convertEntities - # Set the rules for how we'll deal with the entities we - # encounter - if self.convertEntities: - # It doesn't make sense to convert encoded characters to - # entities even while you're converting entities to Unicode. - # Just convert it all to Unicode. - self.smartQuotesTo = None - if convertEntities == self.HTML_ENTITIES: - self.convertXMLEntities = False - self.convertHTMLEntities = True - self.escapeUnrecognizedEntities = True - elif convertEntities == self.XHTML_ENTITIES: - self.convertXMLEntities = True - self.convertHTMLEntities = True - self.escapeUnrecognizedEntities = False - elif convertEntities == self.XML_ENTITIES: - self.convertXMLEntities = True - self.convertHTMLEntities = False - self.escapeUnrecognizedEntities = False - else: - self.convertXMLEntities = False - self.convertHTMLEntities = False - self.escapeUnrecognizedEntities = False - - self.instanceSelfClosingTags = buildTagMap(None, selfClosingTags) - SGMLParser.__init__(self) - - if hasattr(markup, 'read'): # It's a file-type object. - markup = markup.read() - self.markup = markup - self.markupMassage = markupMassage - try: - self._feed(isHTML=isHTML) - except StopParsing: - pass - self.markup = None # The markup can now be GCed - - def convert_charref(self, name): - """This method fixes a bug in Python's SGMLParser.""" - try: - n = int(name) - except ValueError: - return - if not 0 <= n <= 127 : # ASCII ends at 127, not 255 - return - return self.convert_codepoint(n) - - def _feed(self, inDocumentEncoding=None, isHTML=False): - # Convert the document to Unicode. - markup = self.markup - if isinstance(markup, unicode): - if not hasattr(self, 'originalEncoding'): - self.originalEncoding = None - else: - dammit = UnicodeDammit\ - (markup, [self.fromEncoding, inDocumentEncoding], - smartQuotesTo=self.smartQuotesTo, isHTML=isHTML) - markup = dammit.unicode - self.originalEncoding = dammit.originalEncoding - self.declaredHTMLEncoding = dammit.declaredHTMLEncoding - if markup: - if self.markupMassage: - if not isList(self.markupMassage): - self.markupMassage = self.MARKUP_MASSAGE - for fix, m in self.markupMassage: - markup = fix.sub(m, markup) - # TODO: We get rid of markupMassage so that the - # soup object can be deepcopied later on. Some - # Python installations can't copy regexes. If anyone - # was relying on the existence of markupMassage, this - # might cause problems. - del(self.markupMassage) - self.reset() - - SGMLParser.feed(self, markup) - # Close out any unfinished strings and close all the open tags. - self.endData() - while self.currentTag.name != self.ROOT_TAG_NAME: - self.popTag() - - def __getattr__(self, methodName): - """This method routes method call requests to either the SGMLParser - superclass or the Tag superclass, depending on the method name.""" - #print "__getattr__ called on %s.%s" % (self.__class__, methodName) - - if methodName.find('start_') == 0 or methodName.find('end_') == 0 \ - or methodName.find('do_') == 0: - return SGMLParser.__getattr__(self, methodName) - elif methodName.find('__') != 0: - return Tag.__getattr__(self, methodName) - else: - raise AttributeError - - def isSelfClosingTag(self, name): - """Returns true iff the given string is the name of a - self-closing tag according to this parser.""" - return self.SELF_CLOSING_TAGS.has_key(name) \ - or self.instanceSelfClosingTags.has_key(name) - - def reset(self): - Tag.__init__(self, self, self.ROOT_TAG_NAME) - self.hidden = 1 - SGMLParser.reset(self) - self.currentData = [] - self.currentTag = None - self.tagStack = [] - self.quoteStack = [] - self.pushTag(self) - - def popTag(self): - tag = self.tagStack.pop() - # Tags with just one string-owning child get the child as a - # 'string' property, so that soup.tag.string is shorthand for - # soup.tag.contents[0] - if len(self.currentTag.contents) == 1 and \ - isinstance(self.currentTag.contents[0], NavigableString): - self.currentTag.string = self.currentTag.contents[0] - - #print "Pop", tag.name - if self.tagStack: - self.currentTag = self.tagStack[-1] - return self.currentTag - - def pushTag(self, tag): - #print "Push", tag.name - if self.currentTag: - self.currentTag.contents.append(tag) - self.tagStack.append(tag) - self.currentTag = self.tagStack[-1] - - def endData(self, containerClass=NavigableString): - if self.currentData: - currentData = u''.join(self.currentData) - if (currentData.translate(self.STRIP_ASCII_SPACES) == '' and - not set([tag.name for tag in self.tagStack]).intersection( - self.PRESERVE_WHITESPACE_TAGS)): - if '\n' in currentData: - currentData = '\n' - else: - currentData = ' ' - self.currentData = [] - if self.parseOnlyThese and len(self.tagStack) <= 1 and \ - (not self.parseOnlyThese.text or \ - not self.parseOnlyThese.search(currentData)): - return - o = containerClass(currentData) - o.setup(self.currentTag, self.previous) - if self.previous: - self.previous.next = o - self.previous = o - self.currentTag.contents.append(o) - - - def _popToTag(self, name, inclusivePop=True): - """Pops the tag stack up to and including the most recent - instance of the given tag. If inclusivePop is false, pops the tag - stack up to but *not* including the most recent instqance of - the given tag.""" - #print "Popping to %s" % name - if name == self.ROOT_TAG_NAME: - return - - numPops = 0 - mostRecentTag = None - for i in range(len(self.tagStack)-1, 0, -1): - if name == self.tagStack[i].name: - numPops = len(self.tagStack)-i - break - if not inclusivePop: - numPops = numPops - 1 - - for i in range(0, numPops): - mostRecentTag = self.popTag() - return mostRecentTag - - def _smartPop(self, name): - - """We need to pop up to the previous tag of this type, unless - one of this tag's nesting reset triggers comes between this - tag and the previous tag of this type, OR unless this tag is a - generic nesting trigger and another generic nesting trigger - comes between this tag and the previous tag of this type. - - Examples: -

FooBar *

* should pop to 'p', not 'b'. -

FooBar *

* should pop to 'table', not 'p'. -

Foo

Bar *

* should pop to 'tr', not 'p'. - -

    • *
    • * should pop to 'ul', not the first 'li'. -
  • ** should pop to 'table', not the first 'tr' - tag should - implicitly close the previous tag within the same
    ** should pop to 'tr', not the first 'td' - """ - - nestingResetTriggers = self.NESTABLE_TAGS.get(name) - isNestable = nestingResetTriggers != None - isResetNesting = self.RESET_NESTING_TAGS.has_key(name) - popTo = None - inclusive = True - for i in range(len(self.tagStack)-1, 0, -1): - p = self.tagStack[i] - if (not p or p.name == name) and not isNestable: - #Non-nestable tags get popped to the top or to their - #last occurance. - popTo = name - break - if (nestingResetTriggers != None - and p.name in nestingResetTriggers) \ - or (nestingResetTriggers == None and isResetNesting - and self.RESET_NESTING_TAGS.has_key(p.name)): - - #If we encounter one of the nesting reset triggers - #peculiar to this tag, or we encounter another tag - #that causes nesting to reset, pop up to but not - #including that tag. - popTo = p.name - inclusive = False - break - p = p.parent - if popTo: - self._popToTag(popTo, inclusive) - - def unknown_starttag(self, name, attrs, selfClosing=0): - #print "Start tag %s: %s" % (name, attrs) - if self.quoteStack: - #This is not a real tag. - #print "<%s> is not real!" % name - attrs = ''.join(map(lambda(x, y): ' %s="%s"' % (x, y), attrs)) - self.handle_data('<%s%s>' % (name, attrs)) - return - self.endData() - - if not self.isSelfClosingTag(name) and not selfClosing: - self._smartPop(name) - - if self.parseOnlyThese and len(self.tagStack) <= 1 \ - and (self.parseOnlyThese.text or not self.parseOnlyThese.searchTag(name, attrs)): - return - - tag = Tag(self, name, attrs, self.currentTag, self.previous) - if self.previous: - self.previous.next = tag - self.previous = tag - self.pushTag(tag) - if selfClosing or self.isSelfClosingTag(name): - self.popTag() - if name in self.QUOTE_TAGS: - #print "Beginning quote (%s)" % name - self.quoteStack.append(name) - self.literal = 1 - return tag - - def unknown_endtag(self, name): - #print "End tag %s" % name - if self.quoteStack and self.quoteStack[-1] != name: - #This is not a real end tag. - #print " is not real!" % name - self.handle_data('' % name) - return - self.endData() - self._popToTag(name) - if self.quoteStack and self.quoteStack[-1] == name: - self.quoteStack.pop() - self.literal = (len(self.quoteStack) > 0) - - def handle_data(self, data): - self.currentData.append(data) - - def _toStringSubclass(self, text, subclass): - """Adds a certain piece of text to the tree as a NavigableString - subclass.""" - self.endData() - self.handle_data(text) - self.endData(subclass) - - def handle_pi(self, text): - """Handle a processing instruction as a ProcessingInstruction - object, possibly one with a %SOUP-ENCODING% slot into which an - encoding will be plugged later.""" - if text[:3] == "xml": - text = u"xml version='1.0' encoding='%SOUP-ENCODING%'" - self._toStringSubclass(text, ProcessingInstruction) - - def handle_comment(self, text): - "Handle comments as Comment objects." - self._toStringSubclass(text, Comment) - - def handle_charref(self, ref): - "Handle character references as data." - if self.convertEntities: - data = unichr(int(ref)) - else: - data = '&#%s;' % ref - self.handle_data(data) - - def handle_entityref(self, ref): - """Handle entity references as data, possibly converting known - HTML and/or XML entity references to the corresponding Unicode - characters.""" - data = None - if self.convertHTMLEntities: - try: - data = unichr(name2codepoint[ref]) - except KeyError: - pass - - if not data and self.convertXMLEntities: - data = self.XML_ENTITIES_TO_SPECIAL_CHARS.get(ref) - - if not data and self.convertHTMLEntities and \ - not self.XML_ENTITIES_TO_SPECIAL_CHARS.get(ref): - # TODO: We've got a problem here. We're told this is - # an entity reference, but it's not an XML entity - # reference or an HTML entity reference. Nonetheless, - # the logical thing to do is to pass it through as an - # unrecognized entity reference. - # - # Except: when the input is "&carol;" this function - # will be called with input "carol". When the input is - # "AT&T", this function will be called with input - # "T". We have no way of knowing whether a semicolon - # was present originally, so we don't know whether - # this is an unknown entity or just a misplaced - # ampersand. - # - # The more common case is a misplaced ampersand, so I - # escape the ampersand and omit the trailing semicolon. - data = "&%s" % ref - if not data: - # This case is different from the one above, because we - # haven't already gone through a supposedly comprehensive - # mapping of entities to Unicode characters. We might not - # have gone through any mapping at all. So the chances are - # very high that this is a real entity, and not a - # misplaced ampersand. - data = "&%s;" % ref - self.handle_data(data) - - def handle_decl(self, data): - "Handle DOCTYPEs and the like as Declaration objects." - self._toStringSubclass(data, Declaration) - - def parse_declaration(self, i): - """Treat a bogus SGML declaration as raw data. Treat a CDATA - declaration as a CData object.""" - j = None - if self.rawdata[i:i+9] == '', i) - if k == -1: - k = len(self.rawdata) - data = self.rawdata[i+9:k] - j = k+3 - self._toStringSubclass(data, CData) - else: - try: - j = SGMLParser.parse_declaration(self, i) - except SGMLParseError: - toHandle = self.rawdata[i:] - self.handle_data(toHandle) - j = i + len(toHandle) - return j - -class BeautifulSoup(BeautifulStoneSoup): - - """This parser knows the following facts about HTML: - - * Some tags have no closing tag and should be interpreted as being - closed as soon as they are encountered. - - * The text inside some tags (ie. 'script') may contain tags which - are not really part of the document and which should be parsed - as text, not tags. If you want to parse the text as tags, you can - always fetch it and parse it explicitly. - - * Tag nesting rules: - - Most tags can't be nested at all. For instance, the occurance of - a

    tag should implicitly close the previous

    tag. - -

    Para1

    Para2 - should be transformed into: -

    Para1

    Para2 - - Some tags can be nested arbitrarily. For instance, the occurance - of a

    tag should _not_ implicitly close the previous -
    tag. - - Alice said:
    Bob said:
    Blah - should NOT be transformed into: - Alice said:
    Bob said:
    Blah - - Some tags can be nested, but the nesting is reset by the - interposition of other tags. For instance, a
    , - but not close a tag in another table. - -
    BlahBlah - should be transformed into: -
    BlahBlah - but, - Blah
    Blah - should NOT be transformed into - Blah
    Blah - - Differing assumptions about tag nesting rules are a major source - of problems with the BeautifulSoup class. If BeautifulSoup is not - treating as nestable a tag your page author treats as nestable, - try ICantBelieveItsBeautifulSoup, MinimalSoup, or - BeautifulStoneSoup before writing your own subclass.""" - - def __init__(self, *args, **kwargs): - if not kwargs.has_key('smartQuotesTo'): - kwargs['smartQuotesTo'] = self.HTML_ENTITIES - kwargs['isHTML'] = True - BeautifulStoneSoup.__init__(self, *args, **kwargs) - - SELF_CLOSING_TAGS = buildTagMap(None, - ['br' , 'hr', 'input', 'img', 'meta', - 'spacer', 'link', 'frame', 'base']) - - PRESERVE_WHITESPACE_TAGS = set(['pre', 'textarea']) - - QUOTE_TAGS = {'script' : None, 'textarea' : None} - - #According to the HTML standard, each of these inline tags can - #contain another tag of the same type. Furthermore, it's common - #to actually use these tags this way. - NESTABLE_INLINE_TAGS = ['span', 'font', 'q', 'object', 'bdo', 'sub', 'sup', - 'center'] - - #According to the HTML standard, these block tags can contain - #another tag of the same type. Furthermore, it's common - #to actually use these tags this way. - NESTABLE_BLOCK_TAGS = ['blockquote', 'div', 'fieldset', 'ins', 'del'] - - #Lists can contain other lists, but there are restrictions. - NESTABLE_LIST_TAGS = { 'ol' : [], - 'ul' : [], - 'li' : ['ul', 'ol'], - 'dl' : [], - 'dd' : ['dl'], - 'dt' : ['dl'] } - - #Tables can contain other tables, but there are restrictions. - NESTABLE_TABLE_TAGS = {'table' : [], - 'tr' : ['table', 'tbody', 'tfoot', 'thead'], - 'td' : ['tr'], - 'th' : ['tr'], - 'thead' : ['table'], - 'tbody' : ['table'], - 'tfoot' : ['table'], - } - - NON_NESTABLE_BLOCK_TAGS = ['address', 'form', 'p', 'pre'] - - #If one of these tags is encountered, all tags up to the next tag of - #this type are popped. - RESET_NESTING_TAGS = buildTagMap(None, NESTABLE_BLOCK_TAGS, 'noscript', - NON_NESTABLE_BLOCK_TAGS, - NESTABLE_LIST_TAGS, - NESTABLE_TABLE_TAGS) - - NESTABLE_TAGS = buildTagMap([], NESTABLE_INLINE_TAGS, NESTABLE_BLOCK_TAGS, - NESTABLE_LIST_TAGS, NESTABLE_TABLE_TAGS) - - # Used to detect the charset in a META tag; see start_meta - CHARSET_RE = re.compile("((^|;)\s*charset=)([^;]*)", re.M) - - def start_meta(self, attrs): - """Beautiful Soup can detect a charset included in a META tag, - try to convert the document to that charset, and re-parse the - document from the beginning.""" - httpEquiv = None - contentType = None - contentTypeIndex = None - tagNeedsEncodingSubstitution = False - - for i in range(0, len(attrs)): - key, value = attrs[i] - key = key.lower() - if key == 'http-equiv': - httpEquiv = value - elif key == 'content': - contentType = value - contentTypeIndex = i - - if httpEquiv and contentType: # It's an interesting meta tag. - match = self.CHARSET_RE.search(contentType) - if match: - if (self.declaredHTMLEncoding is not None or - self.originalEncoding == self.fromEncoding): - # An HTML encoding was sniffed while converting - # the document to Unicode, or an HTML encoding was - # sniffed during a previous pass through the - # document, or an encoding was specified - # explicitly and it worked. Rewrite the meta tag. - def rewrite(match): - return match.group(1) + "%SOUP-ENCODING%" - newAttr = self.CHARSET_RE.sub(rewrite, contentType) - attrs[contentTypeIndex] = (attrs[contentTypeIndex][0], - newAttr) - tagNeedsEncodingSubstitution = True - else: - # This is our first pass through the document. - # Go through it again with the encoding information. - newCharset = match.group(3) - if newCharset and newCharset != self.originalEncoding: - self.declaredHTMLEncoding = newCharset - self._feed(self.declaredHTMLEncoding) - raise StopParsing - pass - tag = self.unknown_starttag("meta", attrs) - if tag and tagNeedsEncodingSubstitution: - tag.containsSubstitutions = True - -class StopParsing(Exception): - pass - -class ICantBelieveItsBeautifulSoup(BeautifulSoup): - - """The BeautifulSoup class is oriented towards skipping over - common HTML errors like unclosed tags. However, sometimes it makes - errors of its own. For instance, consider this fragment: - - FooBar - - This is perfectly valid (if bizarre) HTML. However, the - BeautifulSoup class will implicitly close the first b tag when it - encounters the second 'b'. It will think the author wrote - "FooBar", and didn't close the first 'b' tag, because - there's no real-world reason to bold something that's already - bold. When it encounters '' it will close two more 'b' - tags, for a grand total of three tags closed instead of two. This - can throw off the rest of your document structure. The same is - true of a number of other tags, listed below. - - It's much more common for someone to forget to close a 'b' tag - than to actually use nested 'b' tags, and the BeautifulSoup class - handles the common case. This class handles the not-co-common - case: where you can't believe someone wrote what they did, but - it's valid HTML and BeautifulSoup screwed up by assuming it - wouldn't be.""" - - I_CANT_BELIEVE_THEYRE_NESTABLE_INLINE_TAGS = \ - ['em', 'big', 'i', 'small', 'tt', 'abbr', 'acronym', 'strong', - 'cite', 'code', 'dfn', 'kbd', 'samp', 'strong', 'var', 'b', - 'big'] - - I_CANT_BELIEVE_THEYRE_NESTABLE_BLOCK_TAGS = ['noscript'] - - NESTABLE_TAGS = buildTagMap([], BeautifulSoup.NESTABLE_TAGS, - I_CANT_BELIEVE_THEYRE_NESTABLE_BLOCK_TAGS, - I_CANT_BELIEVE_THEYRE_NESTABLE_INLINE_TAGS) - -class MinimalSoup(BeautifulSoup): - """The MinimalSoup class is for parsing HTML that contains - pathologically bad markup. It makes no assumptions about tag - nesting, but it does know which tags are self-closing, that -