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 -