From 59232657cb0de3f9a99f5f8e7d108064f47d0f30 Mon Sep 17 00:00:00 2001 From: Labrys of Knossos Date: Fri, 2 Dec 2022 21:20:25 -0500 Subject: [PATCH 1/3] Remove tests for Python 2.7 --- azure-pipelines.yml | 4 +--- tox.ini | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 644c9c34..82decb5d 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -13,8 +13,6 @@ jobs: vmImage: 'Ubuntu-latest' strategy: matrix: - Python27: - python.version: '2.7' Python36: python.version: '3.6' Python37: @@ -32,7 +30,7 @@ jobs: sudo apt-get update sudo apt-get install ffmpeg displayName: 'Install ffmpeg' - + - task: UsePythonVersion@0 inputs: versionSpec: '$(python.version)' diff --git a/tox.ini b/tox.ini index 849028da..9ccebaf4 100644 --- a/tox.ini +++ b/tox.ini @@ -4,12 +4,11 @@ envlist = clean, check, - {py27, py35, py36, py37}, + {py35, py36, py37}, report [testenv] basepython = - py27: {env:TOXPYTHON:python2.7} py35: {env:TOXPYTHON:python3.5} py36: {env:TOXPYTHON:python3.6} py37: {env:TOXPYTHON:python3.7} From f5f430878cc18e54982aff16ba9a71e87d9e3512 Mon Sep 17 00:00:00 2001 From: Labrys of Knossos Date: Fri, 2 Dec 2022 21:20:49 -0500 Subject: [PATCH 2/3] Remove Python 2.7 classifiers --- setup.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/setup.py b/setup.py index 10202e19..04cdceb4 100644 --- a/setup.py +++ b/setup.py @@ -73,8 +73,6 @@ setup( 'Operating System :: POSIX', 'Operating System :: Unix', 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', From f82ed0b52b3f65cb3dbe0302f3c88fe728978be7 Mon Sep 17 00:00:00 2001 From: Labrys of Knossos Date: Fri, 2 Dec 2022 21:22:57 -0500 Subject: [PATCH 3/3] Drop vendored libs for Python 2.7 --- cleanup.py | 1 - libs/py2/_yaml.pyd | Bin 225280 -> 0 bytes libs/py2/backports/__init__.py | 1 - libs/py2/backports/functools_lru_cache.py | 184 -- libs/py2/bs4/__init__.py | 584 ------ libs/py2/bs4/builder/__init__.py | 339 --- libs/py2/bs4/builder/_html5lib.py | 426 ---- libs/py2/bs4/builder/_htmlparser.py | 347 --- libs/py2/bs4/builder/_lxml.py | 262 --- libs/py2/bs4/dammit.py | 842 -------- libs/py2/bs4/diagnose.py | 225 -- libs/py2/bs4/element.py | 1885 ----------------- libs/py2/bs4/testing.py | 810 ------- libs/py2/bs4/tests/__init__.py | 1 - libs/py2/bs4/tests/test_builder_registry.py | 147 -- libs/py2/bs4/tests/test_docs.py | 36 - libs/py2/bs4/tests/test_html5lib.py | 130 -- libs/py2/bs4/tests/test_htmlparser.py | 49 - libs/py2/bs4/tests/test_lxml.py | 82 - libs/py2/bs4/tests/test_soup.py | 501 ----- libs/py2/bs4/tests/test_tree.py | 2090 ------------------- libs/py2/concurrent/__init__.py | 3 - libs/py2/concurrent/futures/__init__.py | 23 - libs/py2/concurrent/futures/_base.py | 667 ------ libs/py2/concurrent/futures/process.py | 363 ---- libs/py2/concurrent/futures/thread.py | 160 -- libs/py2/enum/LICENSE | 32 - libs/py2/enum/README | 3 - libs/py2/enum/__init__.py | 837 -------- libs/py2/yaml/__init__.py | 315 --- libs/py2/yaml/composer.py | 139 -- libs/py2/yaml/constructor.py | 675 ------ libs/py2/yaml/cyaml.py | 85 - libs/py2/yaml/dumper.py | 62 - libs/py2/yaml/emitter.py | 1140 ---------- libs/py2/yaml/error.py | 75 - libs/py2/yaml/events.py | 86 - libs/py2/yaml/loader.py | 40 - libs/py2/yaml/nodes.py | 49 - libs/py2/yaml/parser.py | 589 ------ libs/py2/yaml/reader.py | 190 -- libs/py2/yaml/representer.py | 486 ----- libs/py2/yaml/resolver.py | 227 -- libs/py2/yaml/scanner.py | 1453 ------------- libs/py2/yaml/serializer.py | 111 - libs/py2/yaml/tokens.py | 104 - libs/requirements-py2.txt | 5 - 47 files changed, 16861 deletions(-) delete mode 100644 libs/py2/_yaml.pyd delete mode 100644 libs/py2/backports/__init__.py delete mode 100644 libs/py2/backports/functools_lru_cache.py delete mode 100644 libs/py2/bs4/__init__.py delete mode 100644 libs/py2/bs4/builder/__init__.py delete mode 100644 libs/py2/bs4/builder/_html5lib.py delete mode 100644 libs/py2/bs4/builder/_htmlparser.py delete mode 100644 libs/py2/bs4/builder/_lxml.py delete mode 100644 libs/py2/bs4/dammit.py delete mode 100644 libs/py2/bs4/diagnose.py delete mode 100644 libs/py2/bs4/element.py delete mode 100644 libs/py2/bs4/testing.py delete mode 100644 libs/py2/bs4/tests/__init__.py delete mode 100644 libs/py2/bs4/tests/test_builder_registry.py delete mode 100644 libs/py2/bs4/tests/test_docs.py delete mode 100644 libs/py2/bs4/tests/test_html5lib.py delete mode 100644 libs/py2/bs4/tests/test_htmlparser.py delete mode 100644 libs/py2/bs4/tests/test_lxml.py delete mode 100644 libs/py2/bs4/tests/test_soup.py delete mode 100644 libs/py2/bs4/tests/test_tree.py delete mode 100644 libs/py2/concurrent/__init__.py delete mode 100644 libs/py2/concurrent/futures/__init__.py delete mode 100644 libs/py2/concurrent/futures/_base.py delete mode 100644 libs/py2/concurrent/futures/process.py delete mode 100644 libs/py2/concurrent/futures/thread.py delete mode 100644 libs/py2/enum/LICENSE delete mode 100644 libs/py2/enum/README delete mode 100644 libs/py2/enum/__init__.py delete mode 100644 libs/py2/yaml/__init__.py delete mode 100644 libs/py2/yaml/composer.py delete mode 100644 libs/py2/yaml/constructor.py delete mode 100644 libs/py2/yaml/cyaml.py delete mode 100644 libs/py2/yaml/dumper.py delete mode 100644 libs/py2/yaml/emitter.py delete mode 100644 libs/py2/yaml/error.py delete mode 100644 libs/py2/yaml/events.py delete mode 100644 libs/py2/yaml/loader.py delete mode 100644 libs/py2/yaml/nodes.py delete mode 100644 libs/py2/yaml/parser.py delete mode 100644 libs/py2/yaml/reader.py delete mode 100644 libs/py2/yaml/representer.py delete mode 100644 libs/py2/yaml/resolver.py delete mode 100644 libs/py2/yaml/scanner.py delete mode 100644 libs/py2/yaml/serializer.py delete mode 100644 libs/py2/yaml/tokens.py delete mode 100644 libs/requirements-py2.txt diff --git a/cleanup.py b/cleanup.py index bb4caf98..9628d55c 100644 --- a/cleanup.py +++ b/cleanup.py @@ -18,7 +18,6 @@ FOLDER_STRUCTURE = { 'libs': [ 'common', 'custom', - 'py2', 'win', ], 'core': [ diff --git a/libs/py2/_yaml.pyd b/libs/py2/_yaml.pyd deleted file mode 100644 index 3a505435678d4aa2689eef98b59417a2e217d8be..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 225280 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 OanDMRF~cqV(e=hWV=A4hok2jEpQeNhBBIPr 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 maxsize: - # use the old root to store the new key and result - oldroot = root - oldroot[KEY] = key - oldroot[RESULT] = result - # empty the oldest link and make it the new root - root = nonlocal_root[0] = oldroot[NEXT] - oldkey = root[KEY] - root[KEY] = root[RESULT] = None - # now update the cache dictionary for the new links - del cache[oldkey] - cache[key] = oldroot - else: - # put result in a new link at the front of the list - last = root[PREV] - link = [last, root, key, result] - last[NEXT] = root[PREV] = cache[key] = link - stats[MISSES] += 1 - return result - - def cache_info(): - """Report cache statistics""" - with lock: - return _CacheInfo(stats[HITS], stats[MISSES], maxsize, len(cache)) - - def cache_clear(): - """Clear the cache and cache statistics""" - with lock: - cache.clear() - root = nonlocal_root[0] - root[:] = [root, root, None, None] - stats[:] = [0, 0] - - wrapper.__wrapped__ = user_function - wrapper.cache_info = cache_info - wrapper.cache_clear = cache_clear - return update_wrapper(wrapper, user_function) - - return decorating_function diff --git a/libs/py2/bs4/__init__.py b/libs/py2/bs4/__init__.py deleted file mode 100644 index 470177fd..00000000 --- a/libs/py2/bs4/__init__.py +++ /dev/null @@ -1,584 +0,0 @@ -"""Beautiful Soup -Elixir and Tonic -"The Screen-Scraper's Friend" -http://www.crummy.com/software/BeautifulSoup/ - -Beautiful Soup uses a pluggable XML or HTML parser to parse a -(possibly invalid) document into a tree representation. Beautiful Soup -provides methods and Pythonic idioms that make it easy to navigate, -search, and modify the parse tree. - -Beautiful Soup works with Python 2.7 and up. It works better if lxml -and/or html5lib is installed. - -For more than you ever wanted to know about Beautiful Soup, see the -documentation: -http://www.crummy.com/software/BeautifulSoup/bs4/doc/ - -""" - -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -__author__ = "Leonard Richardson (leonardr@segfault.org)" -__version__ = "4.6.3" -__copyright__ = "Copyright (c) 2004-2018 Leonard Richardson" -__license__ = "MIT" - -__all__ = ['BeautifulSoup'] - -import os -import re -import sys -import traceback -import warnings - -from .builder import builder_registry, ParserRejectedMarkup -from .dammit import UnicodeDammit -from .element import ( - CData, - Comment, - DEFAULT_OUTPUT_ENCODING, - Declaration, - Doctype, - NavigableString, - PageElement, - ProcessingInstruction, - ResultSet, - SoupStrainer, - Tag, - ) - -# The very first thing we do is give a useful error if someone is -# running this code under Python 3 without converting it. -'You are trying to run the Python 2 version of Beautiful Soup under Python 3. This will not work.'<>'You need to convert the code, either by installing it (`python setup.py install`) or by running 2to3 (`2to3 -w bs4`).' - -class BeautifulSoup(Tag): - """ - This class defines the basic interface called by the tree builders. - - These methods will be called by the parser: - reset() - feed(markup) - - The tree builder may call these methods from its feed() implementation: - handle_starttag(name, attrs) # See note about return value - handle_endtag(name) - handle_data(data) # Appends to the current data node - endData(containerClass=NavigableString) # Ends the current data node - - No matter how complicated the underlying parser is, you should be - able to build a tree using 'start tag' events, 'end tag' events, - 'data' events, and "done with data" events. - - If you encounter an empty-element tag (aka a self-closing tag, - like HTML's
tag), call handle_starttag and then - handle_endtag. - """ - ROOT_TAG_NAME = u'[document]' - - # If the end-user gives no indication which tree builder they - # want, look for one with these features. - DEFAULT_BUILDER_FEATURES = ['html', 'fast'] - - ASCII_SPACES = '\x20\x0a\x09\x0c\x0d' - - NO_PARSER_SPECIFIED_WARNING = "No parser was explicitly specified, so I'm using the best available %(markup_type)s parser for this system (\"%(parser)s\"). This usually isn't a problem, but if you run this code on another system, or in a different virtual environment, it may use a different parser and behave differently.\n\nThe code that caused this warning is on line %(line_number)s of the file %(filename)s. To get rid of this warning, pass the additional argument 'features=\"%(parser)s\"' to the BeautifulSoup constructor.\n" - - def __init__(self, markup="", features=None, builder=None, - parse_only=None, from_encoding=None, exclude_encodings=None, - **kwargs): - """Constructor. - - :param markup: A string or a file-like object representing - markup to be parsed. - - :param features: Desirable features of the parser to be used. This - may be the name of a specific parser ("lxml", "lxml-xml", - "html.parser", or "html5lib") or it may be the type of markup - to be used ("html", "html5", "xml"). It's recommended that you - name a specific parser, so that Beautiful Soup gives you the - same results across platforms and virtual environments. - - :param builder: A specific TreeBuilder to use instead of looking one - up based on `features`. You shouldn't need to use this. - - :param parse_only: A SoupStrainer. Only parts of the document - matching the SoupStrainer will be considered. This is useful - when parsing part of a document that would otherwise be too - large to fit into memory. - - :param from_encoding: A string indicating the encoding of the - document to be parsed. Pass this in if Beautiful Soup is - guessing wrongly about the document's encoding. - - :param exclude_encodings: A list of strings indicating - encodings known to be wrong. Pass this in if you don't know - the document's encoding but you know Beautiful Soup's guess is - wrong. - - :param kwargs: For backwards compatibility purposes, the - constructor accepts certain keyword arguments used in - Beautiful Soup 3. None of these arguments do anything in - Beautiful Soup 4 and there's no need to actually pass keyword - arguments into the constructor. - """ - - if 'convertEntities' in kwargs: - warnings.warn( - "BS4 does not respect the convertEntities argument to the " - "BeautifulSoup constructor. Entities are always converted " - "to Unicode characters.") - - if 'markupMassage' in kwargs: - del kwargs['markupMassage'] - warnings.warn( - "BS4 does not respect the markupMassage argument to the " - "BeautifulSoup constructor. The tree builder is responsible " - "for any necessary markup massage.") - - if 'smartQuotesTo' in kwargs: - del kwargs['smartQuotesTo'] - warnings.warn( - "BS4 does not respect the smartQuotesTo argument to the " - "BeautifulSoup constructor. Smart quotes are always converted " - "to Unicode characters.") - - if 'selfClosingTags' in kwargs: - del kwargs['selfClosingTags'] - warnings.warn( - "BS4 does not respect the selfClosingTags argument to the " - "BeautifulSoup constructor. The tree builder is responsible " - "for understanding self-closing tags.") - - if 'isHTML' in kwargs: - del kwargs['isHTML'] - warnings.warn( - "BS4 does not respect the isHTML argument to the " - "BeautifulSoup constructor. Suggest you use " - "features='lxml' for HTML and features='lxml-xml' for " - "XML.") - - def deprecated_argument(old_name, new_name): - if old_name in kwargs: - warnings.warn( - 'The "%s" argument to the BeautifulSoup constructor ' - 'has been renamed to "%s."' % (old_name, new_name)) - value = kwargs[old_name] - del kwargs[old_name] - return value - return None - - parse_only = parse_only or deprecated_argument( - "parseOnlyThese", "parse_only") - - from_encoding = from_encoding or deprecated_argument( - "fromEncoding", "from_encoding") - - if from_encoding and isinstance(markup, unicode): - warnings.warn("You provided Unicode markup but also provided a value for from_encoding. Your from_encoding will be ignored.") - from_encoding = None - - if len(kwargs) > 0: - arg = kwargs.keys().pop() - raise TypeError( - "__init__() got an unexpected keyword argument '%s'" % arg) - - if builder is None: - original_features = features - if isinstance(features, basestring): - features = [features] - if features is None or len(features) == 0: - features = self.DEFAULT_BUILDER_FEATURES - builder_class = builder_registry.lookup(*features) - if builder_class is None: - raise FeatureNotFound( - "Couldn't find a tree builder with the features you " - "requested: %s. Do you need to install a parser library?" - % ",".join(features)) - builder = builder_class() - if not (original_features == builder.NAME or - original_features in builder.ALTERNATE_NAMES): - if builder.is_xml: - markup_type = "XML" - else: - markup_type = "HTML" - - # This code adapted from warnings.py so that we get the same line - # of code as our warnings.warn() call gets, even if the answer is wrong - # (as it may be in a multithreading situation). - caller = None - try: - caller = sys._getframe(1) - except ValueError: - pass - if caller: - globals = caller.f_globals - line_number = caller.f_lineno - else: - globals = sys.__dict__ - line_number= 1 - filename = globals.get('__file__') - if filename: - fnl = filename.lower() - if fnl.endswith((".pyc", ".pyo")): - filename = filename[:-1] - if filename: - # If there is no filename at all, the user is most likely in a REPL, - # and the warning is not necessary. - values = dict( - filename=filename, - line_number=line_number, - parser=builder.NAME, - markup_type=markup_type - ) - warnings.warn(self.NO_PARSER_SPECIFIED_WARNING % values, stacklevel=2) - - self.builder = builder - self.is_xml = builder.is_xml - self.known_xml = self.is_xml - self.builder.soup = self - - self.parse_only = parse_only - - if hasattr(markup, 'read'): # It's a file-type object. - markup = markup.read() - elif len(markup) <= 256 and ( - (isinstance(markup, bytes) and not b'<' in markup) - or (isinstance(markup, unicode) and not u'<' in markup) - ): - # Print out warnings for a couple beginner problems - # involving passing non-markup to Beautiful Soup. - # Beautiful Soup will still parse the input as markup, - # just in case that's what the user really wants. - if (isinstance(markup, unicode) - and not os.path.supports_unicode_filenames): - possible_filename = markup.encode("utf8") - else: - possible_filename = markup - is_file = False - try: - is_file = os.path.exists(possible_filename) - except Exception, e: - # This is almost certainly a problem involving - # characters not valid in filenames on this - # system. Just let it go. - pass - if is_file: - if isinstance(markup, unicode): - markup = markup.encode("utf8") - warnings.warn( - '"%s" looks like a filename, not markup. You should' - ' probably open this file and pass the filehandle into' - ' Beautiful Soup.' % markup) - self._check_markup_is_url(markup) - - for (self.markup, self.original_encoding, self.declared_html_encoding, - self.contains_replacement_characters) in ( - self.builder.prepare_markup( - markup, from_encoding, exclude_encodings=exclude_encodings)): - self.reset() - try: - self._feed() - break - except ParserRejectedMarkup: - pass - - # Clear out the markup and remove the builder's circular - # reference to this object. - self.markup = None - self.builder.soup = None - - def __copy__(self): - copy = type(self)( - self.encode('utf-8'), builder=self.builder, from_encoding='utf-8' - ) - - # Although we encoded the tree to UTF-8, that may not have - # been the encoding of the original markup. Set the copy's - # .original_encoding to reflect the original object's - # .original_encoding. - copy.original_encoding = self.original_encoding - return copy - - def __getstate__(self): - # Frequently a tree builder can't be pickled. - d = dict(self.__dict__) - if 'builder' in d and not self.builder.picklable: - d['builder'] = None - return d - - @staticmethod - def _check_markup_is_url(markup): - """ - Check if markup looks like it's actually a url and raise a warning - if so. Markup can be unicode or str (py2) / bytes (py3). - """ - if isinstance(markup, bytes): - space = b' ' - cant_start_with = (b"http:", b"https:") - elif isinstance(markup, unicode): - space = u' ' - cant_start_with = (u"http:", u"https:") - else: - return - - if any(markup.startswith(prefix) for prefix in cant_start_with): - if not space in markup: - if isinstance(markup, bytes): - decoded_markup = markup.decode('utf-8', 'replace') - else: - decoded_markup = markup - warnings.warn( - '"%s" looks like a URL. Beautiful Soup is not an' - ' HTTP client. You should probably use an HTTP client like' - ' requests to get the document behind the URL, and feed' - ' that document to Beautiful Soup.' % decoded_markup - ) - - def _feed(self): - # Convert the document to Unicode. - self.builder.reset() - - self.builder.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 reset(self): - Tag.__init__(self, self, self.builder, self.ROOT_TAG_NAME) - self.hidden = 1 - self.builder.reset() - self.current_data = [] - self.currentTag = None - self.tagStack = [] - self.preserve_whitespace_tag_stack = [] - self.pushTag(self) - - def new_tag(self, name, namespace=None, nsprefix=None, attrs={}, **kwattrs): - """Create a new tag associated with this soup.""" - kwattrs.update(attrs) - return Tag(None, self.builder, name, namespace, nsprefix, kwattrs) - - def new_string(self, s, subclass=NavigableString): - """Create a new NavigableString associated with this soup.""" - return subclass(s) - - def insert_before(self, successor): - raise NotImplementedError("BeautifulSoup objects don't support insert_before().") - - def insert_after(self, successor): - raise NotImplementedError("BeautifulSoup objects don't support insert_after().") - - def popTag(self): - tag = self.tagStack.pop() - if self.preserve_whitespace_tag_stack and tag == self.preserve_whitespace_tag_stack[-1]: - self.preserve_whitespace_tag_stack.pop() - #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] - if tag.name in self.builder.preserve_whitespace_tags: - self.preserve_whitespace_tag_stack.append(tag) - - def endData(self, containerClass=NavigableString): - if self.current_data: - current_data = u''.join(self.current_data) - # If whitespace is not preserved, and this string contains - # nothing but ASCII spaces, replace it with a single space - # or newline. - if not self.preserve_whitespace_tag_stack: - strippable = True - for i in current_data: - if i not in self.ASCII_SPACES: - strippable = False - break - if strippable: - if '\n' in current_data: - current_data = '\n' - else: - current_data = ' ' - - # Reset the data collector. - self.current_data = [] - - # Should we add this string to the tree at all? - if self.parse_only and len(self.tagStack) <= 1 and \ - (not self.parse_only.text or \ - not self.parse_only.search(current_data)): - return - - o = containerClass(current_data) - self.object_was_parsed(o) - - def object_was_parsed(self, o, parent=None, most_recent_element=None): - """Add an object to the parse tree.""" - parent = parent or self.currentTag - previous_element = most_recent_element or self._most_recent_element - - next_element = previous_sibling = next_sibling = None - if isinstance(o, Tag): - next_element = o.next_element - next_sibling = o.next_sibling - previous_sibling = o.previous_sibling - if not previous_element: - previous_element = o.previous_element - - o.setup(parent, previous_element, next_element, previous_sibling, next_sibling) - - self._most_recent_element = o - parent.contents.append(o) - - if parent.next_sibling: - # This node is being inserted into an element that has - # already been parsed. Deal with any dangling references. - index = len(parent.contents)-1 - while index >= 0: - if parent.contents[index] is o: - break - index -= 1 - else: - raise ValueError( - "Error building tree: supposedly %r was inserted " - "into %r after the fact, but I don't see it!" % ( - o, parent - ) - ) - if index == 0: - previous_element = parent - previous_sibling = None - else: - previous_element = previous_sibling = parent.contents[index-1] - if index == len(parent.contents)-1: - next_element = parent.next_sibling - next_sibling = None - else: - next_element = next_sibling = parent.contents[index+1] - - o.previous_element = previous_element - if previous_element: - previous_element.next_element = o - o.next_element = next_element - if next_element: - next_element.previous_element = o - o.next_sibling = next_sibling - if next_sibling: - next_sibling.previous_sibling = o - o.previous_sibling = previous_sibling - if previous_sibling: - previous_sibling.next_sibling = o - - def _popToTag(self, name, nsprefix=None, 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: - # The BeautifulSoup object itself can never be popped. - return - - most_recently_popped = None - - stack_size = len(self.tagStack) - for i in range(stack_size - 1, 0, -1): - t = self.tagStack[i] - if (name == t.name and nsprefix == t.prefix): - if inclusivePop: - most_recently_popped = self.popTag() - break - most_recently_popped = self.popTag() - - return most_recently_popped - - def handle_starttag(self, name, namespace, nsprefix, attrs): - """Push a start tag on to the stack. - - If this method returns None, the tag was rejected by the - SoupStrainer. You should proceed as if the tag had not occurred - in the document. For instance, if this was a self-closing tag, - don't call handle_endtag. - """ - - # print "Start tag %s: %s" % (name, attrs) - self.endData() - - if (self.parse_only and len(self.tagStack) <= 1 - and (self.parse_only.text - or not self.parse_only.search_tag(name, attrs))): - return None - - tag = Tag(self, self.builder, name, namespace, nsprefix, attrs, - self.currentTag, self._most_recent_element) - if tag is None: - return tag - if self._most_recent_element: - self._most_recent_element.next_element = tag - self._most_recent_element = tag - self.pushTag(tag) - return tag - - def handle_endtag(self, name, nsprefix=None): - #print "End tag: " + name - self.endData() - self._popToTag(name, nsprefix) - - def handle_data(self, data): - self.current_data.append(data) - - def decode(self, pretty_print=False, - eventual_encoding=DEFAULT_OUTPUT_ENCODING, - formatter="minimal"): - """Returns a string or Unicode representation of this document. - To get Unicode, pass None for encoding.""" - - if self.is_xml: - # Print the XML declaration - encoding_part = '' - if eventual_encoding != None: - encoding_part = ' encoding="%s"' % eventual_encoding - prefix = u'\n' % encoding_part - else: - prefix = u'' - if not pretty_print: - indent_level = None - else: - indent_level = 0 - return prefix + super(BeautifulSoup, self).decode( - indent_level, eventual_encoding, formatter) - -# Alias to make it easier to type import: 'from bs4 import _soup' -_s = BeautifulSoup -_soup = BeautifulSoup - -class BeautifulStoneSoup(BeautifulSoup): - """Deprecated interface to an XML parser.""" - - def __init__(self, *args, **kwargs): - kwargs['features'] = 'xml' - warnings.warn( - 'The BeautifulStoneSoup class is deprecated. Instead of using ' - 'it, pass features="xml" into the BeautifulSoup constructor.') - super(BeautifulStoneSoup, self).__init__(*args, **kwargs) - - -class StopParsing(Exception): - pass - -class FeatureNotFound(ValueError): - pass - - -#By default, act as an HTML pretty-printer. -if __name__ == '__main__': - import sys - soup = BeautifulSoup(sys.stdin) - print soup.prettify() diff --git a/libs/py2/bs4/builder/__init__.py b/libs/py2/bs4/builder/__init__.py deleted file mode 100644 index c9e3f3d3..00000000 --- a/libs/py2/bs4/builder/__init__.py +++ /dev/null @@ -1,339 +0,0 @@ -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -from collections import defaultdict -import itertools -import sys -from bs4.element import ( - CharsetMetaAttributeValue, - ContentMetaAttributeValue, - HTMLAwareEntitySubstitution, - whitespace_re - ) - -__all__ = [ - 'HTMLTreeBuilder', - 'SAXTreeBuilder', - 'TreeBuilder', - 'TreeBuilderRegistry', - ] - -# Some useful features for a TreeBuilder to have. -FAST = 'fast' -PERMISSIVE = 'permissive' -STRICT = 'strict' -XML = 'xml' -HTML = 'html' -HTML_5 = 'html5' - - -class TreeBuilderRegistry(object): - - def __init__(self): - self.builders_for_feature = defaultdict(list) - self.builders = [] - - def register(self, treebuilder_class): - """Register a treebuilder based on its advertised features.""" - for feature in treebuilder_class.features: - self.builders_for_feature[feature].insert(0, treebuilder_class) - self.builders.insert(0, treebuilder_class) - - def lookup(self, *features): - if len(self.builders) == 0: - # There are no builders at all. - return None - - if len(features) == 0: - # They didn't ask for any features. Give them the most - # recently registered builder. - return self.builders[0] - - # Go down the list of features in order, and eliminate any builders - # that don't match every feature. - features = list(features) - features.reverse() - candidates = None - candidate_set = None - while len(features) > 0: - feature = features.pop() - we_have_the_feature = self.builders_for_feature.get(feature, []) - if len(we_have_the_feature) > 0: - if candidates is None: - candidates = we_have_the_feature - candidate_set = set(candidates) - else: - # Eliminate any candidates that don't have this feature. - candidate_set = candidate_set.intersection( - set(we_have_the_feature)) - - # The only valid candidates are the ones in candidate_set. - # Go through the original list of candidates and pick the first one - # that's in candidate_set. - if candidate_set is None: - return None - for candidate in candidates: - if candidate in candidate_set: - return candidate - return None - -# The BeautifulSoup class will take feature lists from developers and use them -# to look up builders in this registry. -builder_registry = TreeBuilderRegistry() - -class TreeBuilder(object): - """Turn a document into a Beautiful Soup object tree.""" - - NAME = "[Unknown tree builder]" - ALTERNATE_NAMES = [] - features = [] - - is_xml = False - picklable = False - preserve_whitespace_tags = set() - empty_element_tags = None # A tag will be considered an empty-element - # tag when and only when it has no contents. - - # A value for these tag/attribute combinations is a space- or - # comma-separated list of CDATA, rather than a single CDATA. - cdata_list_attributes = {} - - - def __init__(self): - self.soup = None - - def reset(self): - pass - - def can_be_empty_element(self, tag_name): - """Might a tag with this name be an empty-element tag? - - The final markup may or may not actually present this tag as - self-closing. - - For instance: an HTMLBuilder does not consider a

tag to be - an empty-element tag (it's not in - HTMLBuilder.empty_element_tags). This means an empty

tag - will be presented as "

", not "

". - - The default implementation has no opinion about which tags are - empty-element tags, so a tag will be presented as an - empty-element tag if and only if it has no contents. - "" will become "", and "bar" will - be left alone. - """ - if self.empty_element_tags is None: - return True - return tag_name in self.empty_element_tags - - def feed(self, markup): - raise NotImplementedError() - - def prepare_markup(self, markup, user_specified_encoding=None, - document_declared_encoding=None): - return markup, None, None, False - - def test_fragment_to_document(self, fragment): - """Wrap an HTML fragment to make it look like a document. - - Different parsers do this differently. For instance, lxml - introduces an empty tag, and html5lib - doesn't. Abstracting this away lets us write simple tests - which run HTML fragments through the parser and compare the - results against other HTML fragments. - - This method should not be used outside of tests. - """ - return fragment - - def set_up_substitutions(self, tag): - return False - - def _replace_cdata_list_attribute_values(self, tag_name, attrs): - """Replaces class="foo bar" with class=["foo", "bar"] - - Modifies its input in place. - """ - if not attrs: - return attrs - if self.cdata_list_attributes: - universal = self.cdata_list_attributes.get('*', []) - tag_specific = self.cdata_list_attributes.get( - tag_name.lower(), None) - for attr in attrs.keys(): - if attr in universal or (tag_specific and attr in tag_specific): - # We have a "class"-type attribute whose string - # value is a whitespace-separated list of - # values. Split it into a list. - value = attrs[attr] - if isinstance(value, basestring): - values = whitespace_re.split(value) - else: - # html5lib sometimes calls setAttributes twice - # for the same tag when rearranging the parse - # tree. On the second call the attribute value - # here is already a list. If this happens, - # leave the value alone rather than trying to - # split it again. - values = value - attrs[attr] = values - return attrs - -class SAXTreeBuilder(TreeBuilder): - """A Beautiful Soup treebuilder that listens for SAX events.""" - - def feed(self, markup): - raise NotImplementedError() - - def close(self): - pass - - def startElement(self, name, attrs): - attrs = dict((key[1], value) for key, value in list(attrs.items())) - #print "Start %s, %r" % (name, attrs) - self.soup.handle_starttag(name, attrs) - - def endElement(self, name): - #print "End %s" % name - self.soup.handle_endtag(name) - - def startElementNS(self, nsTuple, nodeName, attrs): - # Throw away (ns, nodeName) for now. - self.startElement(nodeName, attrs) - - def endElementNS(self, nsTuple, nodeName): - # Throw away (ns, nodeName) for now. - self.endElement(nodeName) - #handler.endElementNS((ns, node.nodeName), node.nodeName) - - def startPrefixMapping(self, prefix, nodeValue): - # Ignore the prefix for now. - pass - - def endPrefixMapping(self, prefix): - # Ignore the prefix for now. - # handler.endPrefixMapping(prefix) - pass - - def characters(self, content): - self.soup.handle_data(content) - - def startDocument(self): - pass - - def endDocument(self): - pass - - -class HTMLTreeBuilder(TreeBuilder): - """This TreeBuilder knows facts about HTML. - - Such as which tags are empty-element tags. - """ - - preserve_whitespace_tags = HTMLAwareEntitySubstitution.preserve_whitespace_tags - empty_element_tags = set([ - # These are from HTML5. - 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'menuitem', 'meta', 'param', 'source', 'track', 'wbr', - - # These are from earlier versions of HTML and are removed in HTML5. - 'basefont', 'bgsound', 'command', 'frame', 'image', 'isindex', 'nextid', 'spacer' - ]) - - # The HTML standard defines these as block-level elements. Beautiful - # Soup does not treat these elements differently from other elements, - # but it may do so eventually, and this information is available if - # you need to use it. - block_elements = set(["address", "article", "aside", "blockquote", "canvas", "dd", "div", "dl", "dt", "fieldset", "figcaption", "figure", "footer", "form", "h1", "h2", "h3", "h4", "h5", "h6", "header", "hr", "li", "main", "nav", "noscript", "ol", "output", "p", "pre", "section", "table", "tfoot", "ul", "video"]) - - # The HTML standard defines these attributes as containing a - # space-separated list of values, not a single value. That is, - # class="foo bar" means that the 'class' attribute has two values, - # 'foo' and 'bar', not the single value 'foo bar'. When we - # encounter one of these attributes, we will parse its value into - # a list of values if possible. Upon output, the list will be - # converted back into a string. - cdata_list_attributes = { - "*" : ['class', 'accesskey', 'dropzone'], - "a" : ['rel', 'rev'], - "link" : ['rel', 'rev'], - "td" : ["headers"], - "th" : ["headers"], - "td" : ["headers"], - "form" : ["accept-charset"], - "object" : ["archive"], - - # These are HTML5 specific, as are *.accesskey and *.dropzone above. - "area" : ["rel"], - "icon" : ["sizes"], - "iframe" : ["sandbox"], - "output" : ["for"], - } - - def set_up_substitutions(self, tag): - # We are only interested in tags - if tag.name != 'meta': - return False - - http_equiv = tag.get('http-equiv') - content = tag.get('content') - charset = tag.get('charset') - - # We are interested in tags that say what encoding the - # document was originally in. This means HTML 5-style - # tags that provide the "charset" attribute. It also means - # HTML 4-style tags that provide the "content" - # attribute and have "http-equiv" set to "content-type". - # - # In both cases we will replace the value of the appropriate - # attribute with a standin object that can take on any - # encoding. - meta_encoding = None - if charset is not None: - # HTML 5 style: - # - meta_encoding = charset - tag['charset'] = CharsetMetaAttributeValue(charset) - - elif (content is not None and http_equiv is not None - and http_equiv.lower() == 'content-type'): - # HTML 4 style: - # - tag['content'] = ContentMetaAttributeValue(content) - - return (meta_encoding is not None) - -def register_treebuilders_from(module): - """Copy TreeBuilders from the given module into this module.""" - # I'm fairly sure this is not the best way to do this. - this_module = sys.modules['bs4.builder'] - for name in module.__all__: - obj = getattr(module, name) - - if issubclass(obj, TreeBuilder): - setattr(this_module, name, obj) - this_module.__all__.append(name) - # Register the builder while we're at it. - this_module.builder_registry.register(obj) - -class ParserRejectedMarkup(Exception): - pass - -# Builders are registered in reverse order of priority, so that custom -# builder registrations will take precedence. In general, we want lxml -# to take precedence over html5lib, because it's faster. And we only -# want to use HTMLParser as a last result. -from . import _htmlparser -register_treebuilders_from(_htmlparser) -try: - from . import _html5lib - register_treebuilders_from(_html5lib) -except ImportError: - # They don't have html5lib installed. - pass -try: - from . import _lxml - register_treebuilders_from(_lxml) -except ImportError: - # They don't have lxml installed. - pass diff --git a/libs/py2/bs4/builder/_html5lib.py b/libs/py2/bs4/builder/_html5lib.py deleted file mode 100644 index 5f548935..00000000 --- a/libs/py2/bs4/builder/_html5lib.py +++ /dev/null @@ -1,426 +0,0 @@ -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -__all__ = [ - 'HTML5TreeBuilder', - ] - -import warnings -import re -from bs4.builder import ( - PERMISSIVE, - HTML, - HTML_5, - HTMLTreeBuilder, - ) -from bs4.element import ( - NamespacedAttribute, - whitespace_re, -) -import html5lib -from html5lib.constants import ( - namespaces, - prefixes, - ) -from bs4.element import ( - Comment, - Doctype, - NavigableString, - Tag, - ) - -try: - # Pre-0.99999999 - from html5lib.treebuilders import _base as treebuilder_base - new_html5lib = False -except ImportError, e: - # 0.99999999 and up - from html5lib.treebuilders import base as treebuilder_base - new_html5lib = True - -class HTML5TreeBuilder(HTMLTreeBuilder): - """Use html5lib to build a tree.""" - - NAME = "html5lib" - - features = [NAME, PERMISSIVE, HTML_5, HTML] - - def prepare_markup(self, markup, user_specified_encoding, - document_declared_encoding=None, exclude_encodings=None): - # Store the user-specified encoding for use later on. - self.user_specified_encoding = user_specified_encoding - - # document_declared_encoding and exclude_encodings aren't used - # ATM because the html5lib TreeBuilder doesn't use - # UnicodeDammit. - if exclude_encodings: - warnings.warn("You provided a value for exclude_encoding, but the html5lib tree builder doesn't support exclude_encoding.") - yield (markup, None, None, False) - - # These methods are defined by Beautiful Soup. - def feed(self, markup): - if self.soup.parse_only is not None: - warnings.warn("You provided a value for parse_only, but the html5lib tree builder doesn't support parse_only. The entire document will be parsed.") - parser = html5lib.HTMLParser(tree=self.create_treebuilder) - - extra_kwargs = dict() - if not isinstance(markup, unicode): - if new_html5lib: - extra_kwargs['override_encoding'] = self.user_specified_encoding - else: - extra_kwargs['encoding'] = self.user_specified_encoding - doc = parser.parse(markup, **extra_kwargs) - - # Set the character encoding detected by the tokenizer. - if isinstance(markup, unicode): - # We need to special-case this because html5lib sets - # charEncoding to UTF-8 if it gets Unicode input. - doc.original_encoding = None - else: - original_encoding = parser.tokenizer.stream.charEncoding[0] - if not isinstance(original_encoding, basestring): - # In 0.99999999 and up, the encoding is an html5lib - # Encoding object. We want to use a string for compatibility - # with other tree builders. - original_encoding = original_encoding.name - doc.original_encoding = original_encoding - - def create_treebuilder(self, namespaceHTMLElements): - self.underlying_builder = TreeBuilderForHtml5lib( - namespaceHTMLElements, self.soup) - return self.underlying_builder - - def test_fragment_to_document(self, fragment): - """See `TreeBuilder`.""" - return u'%s' % fragment - - -class TreeBuilderForHtml5lib(treebuilder_base.TreeBuilder): - - def __init__(self, namespaceHTMLElements, soup=None): - if soup: - self.soup = soup - else: - from bs4 import BeautifulSoup - self.soup = BeautifulSoup("", "html.parser") - super(TreeBuilderForHtml5lib, self).__init__(namespaceHTMLElements) - - def documentClass(self): - self.soup.reset() - return Element(self.soup, self.soup, None) - - def insertDoctype(self, token): - name = token["name"] - publicId = token["publicId"] - systemId = token["systemId"] - - doctype = Doctype.for_name_and_ids(name, publicId, systemId) - self.soup.object_was_parsed(doctype) - - def elementClass(self, name, namespace): - tag = self.soup.new_tag(name, namespace) - return Element(tag, self.soup, namespace) - - def commentClass(self, data): - return TextNode(Comment(data), self.soup) - - def fragmentClass(self): - from bs4 import BeautifulSoup - self.soup = BeautifulSoup("", "html.parser") - self.soup.name = "[document_fragment]" - return Element(self.soup, self.soup, None) - - def appendChild(self, node): - # XXX This code is not covered by the BS4 tests. - self.soup.append(node.element) - - def getDocument(self): - return self.soup - - def getFragment(self): - return treebuilder_base.TreeBuilder.getFragment(self).element - - def testSerializer(self, element): - from bs4 import BeautifulSoup - rv = [] - doctype_re = re.compile(r'^(.*?)(?: PUBLIC "(.*?)"(?: "(.*?)")?| SYSTEM "(.*?)")?$') - - def serializeElement(element, indent=0): - if isinstance(element, BeautifulSoup): - pass - if isinstance(element, Doctype): - m = doctype_re.match(element) - if m: - name = m.group(1) - if m.lastindex > 1: - publicId = m.group(2) or "" - systemId = m.group(3) or m.group(4) or "" - rv.append("""|%s""" % - (' ' * indent, name, publicId, systemId)) - else: - rv.append("|%s" % (' ' * indent, name)) - else: - rv.append("|%s" % (' ' * indent,)) - elif isinstance(element, Comment): - rv.append("|%s" % (' ' * indent, element)) - elif isinstance(element, NavigableString): - rv.append("|%s\"%s\"" % (' ' * indent, element)) - else: - if element.namespace: - name = "%s %s" % (prefixes[element.namespace], - element.name) - else: - name = element.name - rv.append("|%s<%s>" % (' ' * indent, name)) - if element.attrs: - attributes = [] - for name, value in element.attrs.items(): - if isinstance(name, NamespacedAttribute): - name = "%s %s" % (prefixes[name.namespace], name.name) - if isinstance(value, list): - value = " ".join(value) - attributes.append((name, value)) - - for name, value in sorted(attributes): - rv.append('|%s%s="%s"' % (' ' * (indent + 2), name, value)) - indent += 2 - for child in element.children: - serializeElement(child, indent) - serializeElement(element, 0) - - return "\n".join(rv) - -class AttrList(object): - def __init__(self, element): - self.element = element - self.attrs = dict(self.element.attrs) - def __iter__(self): - return list(self.attrs.items()).__iter__() - def __setitem__(self, name, value): - # If this attribute is a multi-valued attribute for this element, - # turn its value into a list. - list_attr = HTML5TreeBuilder.cdata_list_attributes - if (name in list_attr['*'] - or (self.element.name in list_attr - and name in list_attr[self.element.name])): - # A node that is being cloned may have already undergone - # this procedure. - if not isinstance(value, list): - value = whitespace_re.split(value) - self.element[name] = value - def items(self): - return list(self.attrs.items()) - def keys(self): - return list(self.attrs.keys()) - def __len__(self): - return len(self.attrs) - def __getitem__(self, name): - return self.attrs[name] - def __contains__(self, name): - return name in list(self.attrs.keys()) - - -class Element(treebuilder_base.Node): - def __init__(self, element, soup, namespace): - treebuilder_base.Node.__init__(self, element.name) - self.element = element - self.soup = soup - self.namespace = namespace - - def appendChild(self, node): - string_child = child = None - if isinstance(node, basestring): - # Some other piece of code decided to pass in a string - # instead of creating a TextElement object to contain the - # string. - string_child = child = node - elif isinstance(node, Tag): - # Some other piece of code decided to pass in a Tag - # instead of creating an Element object to contain the - # Tag. - child = node - elif node.element.__class__ == NavigableString: - string_child = child = node.element - node.parent = self - else: - child = node.element - node.parent = self - - if not isinstance(child, basestring) and child.parent is not None: - node.element.extract() - - if (string_child and self.element.contents - and self.element.contents[-1].__class__ == NavigableString): - # We are appending a string onto another string. - # TODO This has O(n^2) performance, for input like - # "aaa..." - old_element = self.element.contents[-1] - new_element = self.soup.new_string(old_element + string_child) - old_element.replace_with(new_element) - self.soup._most_recent_element = new_element - else: - if isinstance(node, basestring): - # Create a brand new NavigableString from this string. - child = self.soup.new_string(node) - - # Tell Beautiful Soup to act as if it parsed this element - # immediately after the parent's last descendant. (Or - # immediately after the parent, if it has no children.) - if self.element.contents: - most_recent_element = self.element._last_descendant(False) - elif self.element.next_element is not None: - # Something from further ahead in the parse tree is - # being inserted into this earlier element. This is - # very annoying because it means an expensive search - # for the last element in the tree. - most_recent_element = self.soup._last_descendant() - else: - most_recent_element = self.element - - self.soup.object_was_parsed( - child, parent=self.element, - most_recent_element=most_recent_element) - - def getAttributes(self): - if isinstance(self.element, Comment): - return {} - return AttrList(self.element) - - def setAttributes(self, attributes): - - if attributes is not None and len(attributes) > 0: - - converted_attributes = [] - for name, value in list(attributes.items()): - if isinstance(name, tuple): - new_name = NamespacedAttribute(*name) - del attributes[name] - attributes[new_name] = value - - self.soup.builder._replace_cdata_list_attribute_values( - self.name, attributes) - for name, value in attributes.items(): - self.element[name] = value - - # The attributes may contain variables that need substitution. - # Call set_up_substitutions manually. - # - # The Tag constructor called this method when the Tag was created, - # but we just set/changed the attributes, so call it again. - self.soup.builder.set_up_substitutions(self.element) - attributes = property(getAttributes, setAttributes) - - def insertText(self, data, insertBefore=None): - text = TextNode(self.soup.new_string(data), self.soup) - if insertBefore: - self.insertBefore(text, insertBefore) - else: - self.appendChild(text) - - def insertBefore(self, node, refNode): - index = self.element.index(refNode.element) - if (node.element.__class__ == NavigableString and self.element.contents - and self.element.contents[index-1].__class__ == NavigableString): - # (See comments in appendChild) - old_node = self.element.contents[index-1] - new_str = self.soup.new_string(old_node + node.element) - old_node.replace_with(new_str) - else: - self.element.insert(index, node.element) - node.parent = self - - def removeChild(self, node): - node.element.extract() - - def reparentChildren(self, new_parent): - """Move all of this tag's children into another tag.""" - # print "MOVE", self.element.contents - # print "FROM", self.element - # print "TO", new_parent.element - - element = self.element - new_parent_element = new_parent.element - # Determine what this tag's next_element will be once all the children - # are removed. - final_next_element = element.next_sibling - - new_parents_last_descendant = new_parent_element._last_descendant(False, False) - if len(new_parent_element.contents) > 0: - # The new parent already contains children. We will be - # appending this tag's children to the end. - new_parents_last_child = new_parent_element.contents[-1] - new_parents_last_descendant_next_element = new_parents_last_descendant.next_element - else: - # The new parent contains no children. - new_parents_last_child = None - new_parents_last_descendant_next_element = new_parent_element.next_element - - to_append = element.contents - if len(to_append) > 0: - # Set the first child's previous_element and previous_sibling - # to elements within the new parent - first_child = to_append[0] - if new_parents_last_descendant: - first_child.previous_element = new_parents_last_descendant - else: - first_child.previous_element = new_parent_element - first_child.previous_sibling = new_parents_last_child - if new_parents_last_descendant: - new_parents_last_descendant.next_element = first_child - else: - new_parent_element.next_element = first_child - if new_parents_last_child: - new_parents_last_child.next_sibling = first_child - - # Find the very last element being moved. It is now the - # parent's last descendant. It has no .next_sibling and - # its .next_element is whatever the previous last - # descendant had. - last_childs_last_descendant = to_append[-1]._last_descendant(False, True) - - last_childs_last_descendant.next_element = new_parents_last_descendant_next_element - if new_parents_last_descendant_next_element: - # TODO: This code has no test coverage and I'm not sure - # how to get html5lib to go through this path, but it's - # just the other side of the previous line. - new_parents_last_descendant_next_element.previous_element = last_childs_last_descendant - last_childs_last_descendant.next_sibling = None - - for child in to_append: - child.parent = new_parent_element - new_parent_element.contents.append(child) - - # Now that this element has no children, change its .next_element. - element.contents = [] - element.next_element = final_next_element - - # print "DONE WITH MOVE" - # print "FROM", self.element - # print "TO", new_parent_element - - def cloneNode(self): - tag = self.soup.new_tag(self.element.name, self.namespace) - node = Element(tag, self.soup, self.namespace) - for key,value in self.attributes: - node.attributes[key] = value - return node - - def hasContent(self): - return self.element.contents - - def getNameTuple(self): - if self.namespace == None: - return namespaces["html"], self.name - else: - return self.namespace, self.name - - nameTuple = property(getNameTuple) - -class TextNode(Element): - def __init__(self, element, soup): - treebuilder_base.Node.__init__(self, None) - self.element = element - self.soup = soup - - def cloneNode(self): - raise NotImplementedError diff --git a/libs/py2/bs4/builder/_htmlparser.py b/libs/py2/bs4/builder/_htmlparser.py deleted file mode 100644 index ee6c685d..00000000 --- a/libs/py2/bs4/builder/_htmlparser.py +++ /dev/null @@ -1,347 +0,0 @@ -# encoding: utf-8 -"""Use the HTMLParser library to parse HTML files that aren't too bad.""" - -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -__all__ = [ - 'HTMLParserTreeBuilder', - ] - -from HTMLParser import HTMLParser - -try: - from HTMLParser import HTMLParseError -except ImportError, e: - # HTMLParseError is removed in Python 3.5. Since it can never be - # thrown in 3.5, we can just define our own class as a placeholder. - class HTMLParseError(Exception): - pass - -import sys -import warnings - -# Starting in Python 3.2, the HTMLParser constructor takes a 'strict' -# argument, which we'd like to set to False. Unfortunately, -# http://bugs.python.org/issue13273 makes strict=True a better bet -# before Python 3.2.3. -# -# At the end of this file, we monkeypatch HTMLParser so that -# strict=True works well on Python 3.2.2. -major, minor, release = sys.version_info[:3] -CONSTRUCTOR_TAKES_STRICT = major == 3 and minor == 2 and release >= 3 -CONSTRUCTOR_STRICT_IS_DEPRECATED = major == 3 and minor == 3 -CONSTRUCTOR_TAKES_CONVERT_CHARREFS = major == 3 and minor >= 4 - - -from bs4.element import ( - CData, - Comment, - Declaration, - Doctype, - ProcessingInstruction, - ) -from bs4.dammit import EntitySubstitution, UnicodeDammit - -from bs4.builder import ( - HTML, - HTMLTreeBuilder, - STRICT, - ) - - -HTMLPARSER = 'html.parser' - -class BeautifulSoupHTMLParser(HTMLParser): - - def __init__(self, *args, **kwargs): - HTMLParser.__init__(self, *args, **kwargs) - - # Keep a list of empty-element tags that were encountered - # without an explicit closing tag. If we encounter a closing tag - # of this type, we'll associate it with one of those entries. - # - # This isn't a stack because we don't care about the - # order. It's a list of closing tags we've already handled and - # will ignore, assuming they ever show up. - self.already_closed_empty_element = [] - - def error(self, msg): - """In Python 3, HTMLParser subclasses must implement error(), although this - requirement doesn't appear to be documented. - - In Python 2, HTMLParser implements error() as raising an exception. - - In any event, this method is called only on very strange markup and our best strategy - is to pretend it didn't happen and keep going. - """ - warnings.warn(msg) - - def handle_startendtag(self, name, attrs): - # This is only called when the markup looks like - # . - - # is_startend() tells handle_starttag not to close the tag - # just because its name matches a known empty-element tag. We - # know that this is an empty-element tag and we want to call - # handle_endtag ourselves. - tag = self.handle_starttag(name, attrs, handle_empty_element=False) - self.handle_endtag(name) - - def handle_starttag(self, name, attrs, handle_empty_element=True): - # XXX namespace - attr_dict = {} - for key, value in attrs: - # Change None attribute values to the empty string - # for consistency with the other tree builders. - if value is None: - value = '' - attr_dict[key] = value - attrvalue = '""' - #print "START", name - tag = self.soup.handle_starttag(name, None, None, attr_dict) - if tag and tag.is_empty_element and handle_empty_element: - # Unlike other parsers, html.parser doesn't send separate end tag - # events for empty-element tags. (It's handled in - # handle_startendtag, but only if the original markup looked like - # .) - # - # So we need to call handle_endtag() ourselves. Since we - # know the start event is identical to the end event, we - # don't want handle_endtag() to cross off any previous end - # events for tags of this name. - self.handle_endtag(name, check_already_closed=False) - - # But we might encounter an explicit closing tag for this tag - # later on. If so, we want to ignore it. - self.already_closed_empty_element.append(name) - - def handle_endtag(self, name, check_already_closed=True): - #print "END", name - if check_already_closed and name in self.already_closed_empty_element: - # This is a redundant end tag for an empty-element tag. - # We've already called handle_endtag() for it, so just - # check it off the list. - # print "ALREADY CLOSED", name - self.already_closed_empty_element.remove(name) - else: - self.soup.handle_endtag(name) - - def handle_data(self, data): - self.soup.handle_data(data) - - def handle_charref(self, name): - # XXX workaround for a bug in HTMLParser. Remove this once - # it's fixed in all supported versions. - # http://bugs.python.org/issue13633 - if name.startswith('x'): - real_name = int(name.lstrip('x'), 16) - elif name.startswith('X'): - real_name = int(name.lstrip('X'), 16) - else: - real_name = int(name) - - data = None - if real_name < 256: - # HTML numeric entities are supposed to reference Unicode - # code points, but sometimes they reference code points in - # some other encoding (ahem, Windows-1252). E.g. “ - # instead of É for LEFT DOUBLE QUOTATION MARK. This - # code tries to detect this situation and compensate. - for encoding in (self.soup.original_encoding, 'windows-1252'): - if not encoding: - continue - try: - data = bytearray([real_name]).decode(encoding) - except UnicodeDecodeError, e: - pass - if not data: - try: - data = unichr(real_name) - except (ValueError, OverflowError), e: - pass - data = data or u"\N{REPLACEMENT CHARACTER}" - self.handle_data(data) - - def handle_entityref(self, name): - character = EntitySubstitution.HTML_ENTITY_TO_CHARACTER.get(name) - if character is not None: - data = character - else: - # If this were XML, it would be ambiguous whether "&foo" - # was an character entity reference with a missing - # semicolon or the literal string "&foo". Since this is - # HTML, we have a complete list of all character entity references, - # and this one wasn't found, so assume it's the literal string "&foo". - data = "&%s" % name - self.handle_data(data) - - def handle_comment(self, data): - self.soup.endData() - self.soup.handle_data(data) - self.soup.endData(Comment) - - def handle_decl(self, data): - self.soup.endData() - if data.startswith("DOCTYPE "): - data = data[len("DOCTYPE "):] - elif data == 'DOCTYPE': - # i.e. "" - data = '' - self.soup.handle_data(data) - self.soup.endData(Doctype) - - def unknown_decl(self, data): - if data.upper().startswith('CDATA['): - cls = CData - data = data[len('CDATA['):] - else: - cls = Declaration - self.soup.endData() - self.soup.handle_data(data) - self.soup.endData(cls) - - def handle_pi(self, data): - self.soup.endData() - self.soup.handle_data(data) - self.soup.endData(ProcessingInstruction) - - -class HTMLParserTreeBuilder(HTMLTreeBuilder): - - is_xml = False - picklable = True - NAME = HTMLPARSER - features = [NAME, HTML, STRICT] - - def __init__(self, *args, **kwargs): - if CONSTRUCTOR_TAKES_STRICT and not CONSTRUCTOR_STRICT_IS_DEPRECATED: - kwargs['strict'] = False - if CONSTRUCTOR_TAKES_CONVERT_CHARREFS: - kwargs['convert_charrefs'] = False - self.parser_args = (args, kwargs) - - def prepare_markup(self, markup, user_specified_encoding=None, - document_declared_encoding=None, exclude_encodings=None): - """ - :return: A 4-tuple (markup, original encoding, encoding - declared within markup, whether any characters had to be - replaced with REPLACEMENT CHARACTER). - """ - if isinstance(markup, unicode): - yield (markup, None, None, False) - return - - try_encodings = [user_specified_encoding, document_declared_encoding] - dammit = UnicodeDammit(markup, try_encodings, is_html=True, - exclude_encodings=exclude_encodings) - yield (dammit.markup, dammit.original_encoding, - dammit.declared_html_encoding, - dammit.contains_replacement_characters) - - def feed(self, markup): - args, kwargs = self.parser_args - parser = BeautifulSoupHTMLParser(*args, **kwargs) - parser.soup = self.soup - try: - parser.feed(markup) - parser.close() - except HTMLParseError, e: - warnings.warn(RuntimeWarning( - "Python's built-in HTMLParser cannot parse the given document. This is not a bug in Beautiful Soup. The best solution is to install an external parser (lxml or html5lib), and use Beautiful Soup with that parser. See http://www.crummy.com/software/BeautifulSoup/bs4/doc/#installing-a-parser for help.")) - raise e - parser.already_closed_empty_element = [] - -# Patch 3.2 versions of HTMLParser earlier than 3.2.3 to use some -# 3.2.3 code. This ensures they don't treat markup like

as a -# string. -# -# XXX This code can be removed once most Python 3 users are on 3.2.3. -if major == 3 and minor == 2 and not CONSTRUCTOR_TAKES_STRICT: - import re - attrfind_tolerant = re.compile( - r'\s*((?<=[\'"\s])[^\s/>][^\s/=>]*)(\s*=+\s*' - r'(\'[^\']*\'|"[^"]*"|(?![\'"])[^>\s]*))?') - HTMLParserTreeBuilder.attrfind_tolerant = attrfind_tolerant - - locatestarttagend = re.compile(r""" - <[a-zA-Z][-.a-zA-Z0-9:_]* # tag name - (?:\s+ # whitespace before attribute name - (?:[a-zA-Z_][-.:a-zA-Z0-9_]* # attribute name - (?:\s*=\s* # value indicator - (?:'[^']*' # LITA-enclosed value - |\"[^\"]*\" # LIT-enclosed value - |[^'\">\s]+ # bare value - ) - )? - ) - )* - \s* # trailing whitespace -""", re.VERBOSE) - BeautifulSoupHTMLParser.locatestarttagend = locatestarttagend - - from html.parser import tagfind, attrfind - - def parse_starttag(self, i): - self.__starttag_text = None - endpos = self.check_for_whole_start_tag(i) - if endpos < 0: - return endpos - rawdata = self.rawdata - self.__starttag_text = rawdata[i:endpos] - - # Now parse the data between i+1 and j into a tag and attrs - attrs = [] - match = tagfind.match(rawdata, i+1) - assert match, 'unexpected call to parse_starttag()' - k = match.end() - self.lasttag = tag = rawdata[i+1:k].lower() - while k < endpos: - if self.strict: - m = attrfind.match(rawdata, k) - else: - m = attrfind_tolerant.match(rawdata, k) - if not m: - break - attrname, rest, attrvalue = m.group(1, 2, 3) - if not rest: - attrvalue = None - elif attrvalue[:1] == '\'' == attrvalue[-1:] or \ - attrvalue[:1] == '"' == attrvalue[-1:]: - attrvalue = attrvalue[1:-1] - if attrvalue: - attrvalue = self.unescape(attrvalue) - attrs.append((attrname.lower(), attrvalue)) - k = m.end() - - end = rawdata[k:endpos].strip() - if end not in (">", "/>"): - lineno, offset = self.getpos() - if "\n" in self.__starttag_text: - lineno = lineno + self.__starttag_text.count("\n") - offset = len(self.__starttag_text) \ - - self.__starttag_text.rfind("\n") - else: - offset = offset + len(self.__starttag_text) - if self.strict: - self.error("junk characters in start tag: %r" - % (rawdata[k:endpos][:20],)) - self.handle_data(rawdata[i:endpos]) - return endpos - if end.endswith('/>'): - # XHTML-style empty tag: - self.handle_startendtag(tag, attrs) - else: - self.handle_starttag(tag, attrs) - if tag in self.CDATA_CONTENT_ELEMENTS: - self.set_cdata_mode(tag) - return endpos - - def set_cdata_mode(self, elem): - self.cdata_elem = elem.lower() - self.interesting = re.compile(r'' % self.cdata_elem, re.I) - - BeautifulSoupHTMLParser.parse_starttag = parse_starttag - BeautifulSoupHTMLParser.set_cdata_mode = set_cdata_mode - - CONSTRUCTOR_TAKES_STRICT = True diff --git a/libs/py2/bs4/builder/_lxml.py b/libs/py2/bs4/builder/_lxml.py deleted file mode 100644 index 4a0f7de4..00000000 --- a/libs/py2/bs4/builder/_lxml.py +++ /dev/null @@ -1,262 +0,0 @@ -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. -__all__ = [ - 'LXMLTreeBuilderForXML', - 'LXMLTreeBuilder', - ] - -try: - from collections.abc import Callable # Python 3.6 -except ImportError , e: - from collections import Callable - -from io import BytesIO -from StringIO import StringIO -from lxml import etree -from bs4.element import ( - Comment, - Doctype, - NamespacedAttribute, - ProcessingInstruction, - XMLProcessingInstruction, -) -from bs4.builder import ( - FAST, - HTML, - HTMLTreeBuilder, - PERMISSIVE, - ParserRejectedMarkup, - TreeBuilder, - XML) -from bs4.dammit import EncodingDetector - -LXML = 'lxml' - -class LXMLTreeBuilderForXML(TreeBuilder): - DEFAULT_PARSER_CLASS = etree.XMLParser - - is_xml = True - processing_instruction_class = XMLProcessingInstruction - - NAME = "lxml-xml" - ALTERNATE_NAMES = ["xml"] - - # Well, it's permissive by XML parser standards. - features = [NAME, LXML, XML, FAST, PERMISSIVE] - - CHUNK_SIZE = 512 - - # This namespace mapping is specified in the XML Namespace - # standard. - DEFAULT_NSMAPS = {'http://www.w3.org/XML/1998/namespace' : "xml"} - - def default_parser(self, encoding): - # This can either return a parser object or a class, which - # will be instantiated with default arguments. - if self._default_parser is not None: - return self._default_parser - return etree.XMLParser( - target=self, strip_cdata=False, recover=True, encoding=encoding) - - def parser_for(self, encoding): - # Use the default parser. - parser = self.default_parser(encoding) - - if isinstance(parser, Callable): - # Instantiate the parser with default arguments - parser = parser(target=self, strip_cdata=False, encoding=encoding) - return parser - - def __init__(self, parser=None, empty_element_tags=None): - # TODO: Issue a warning if parser is present but not a - # callable, since that means there's no way to create new - # parsers for different encodings. - self._default_parser = parser - if empty_element_tags is not None: - self.empty_element_tags = set(empty_element_tags) - self.soup = None - self.nsmaps = [self.DEFAULT_NSMAPS] - - def _getNsTag(self, tag): - # Split the namespace URL out of a fully-qualified lxml tag - # name. Copied from lxml's src/lxml/sax.py. - if tag[0] == '{': - return tuple(tag[1:].split('}', 1)) - else: - return (None, tag) - - def prepare_markup(self, markup, user_specified_encoding=None, - exclude_encodings=None, - document_declared_encoding=None): - """ - :yield: A series of 4-tuples. - (markup, encoding, declared encoding, - has undergone character replacement) - - Each 4-tuple represents a strategy for parsing the document. - """ - # Instead of using UnicodeDammit to convert the bytestring to - # Unicode using different encodings, use EncodingDetector to - # iterate over the encodings, and tell lxml to try to parse - # the document as each one in turn. - is_html = not self.is_xml - if is_html: - self.processing_instruction_class = ProcessingInstruction - else: - self.processing_instruction_class = XMLProcessingInstruction - - if isinstance(markup, unicode): - # We were given Unicode. Maybe lxml can parse Unicode on - # this system? - yield markup, None, document_declared_encoding, False - - if isinstance(markup, unicode): - # No, apparently not. Convert the Unicode to UTF-8 and - # tell lxml to parse it as UTF-8. - yield (markup.encode("utf8"), "utf8", - document_declared_encoding, False) - - try_encodings = [user_specified_encoding, document_declared_encoding] - detector = EncodingDetector( - markup, try_encodings, is_html, exclude_encodings) - for encoding in detector.encodings: - yield (detector.markup, encoding, document_declared_encoding, False) - - def feed(self, markup): - if isinstance(markup, bytes): - markup = BytesIO(markup) - elif isinstance(markup, unicode): - markup = StringIO(markup) - - # Call feed() at least once, even if the markup is empty, - # or the parser won't be initialized. - data = markup.read(self.CHUNK_SIZE) - try: - self.parser = self.parser_for(self.soup.original_encoding) - self.parser.feed(data) - while len(data) != 0: - # Now call feed() on the rest of the data, chunk by chunk. - data = markup.read(self.CHUNK_SIZE) - if len(data) != 0: - self.parser.feed(data) - self.parser.close() - except (UnicodeDecodeError, LookupError, etree.ParserError), e: - raise ParserRejectedMarkup(str(e)) - - def close(self): - self.nsmaps = [self.DEFAULT_NSMAPS] - - def start(self, name, attrs, nsmap={}): - # Make sure attrs is a mutable dict--lxml may send an immutable dictproxy. - attrs = dict(attrs) - nsprefix = None - # Invert each namespace map as it comes in. - if len(nsmap) == 0 and len(self.nsmaps) > 1: - # There are no new namespaces for this tag, but - # non-default namespaces are in play, so we need a - # separate tag stack to know when they end. - self.nsmaps.append(None) - elif len(nsmap) > 0: - # A new namespace mapping has come into play. - inverted_nsmap = dict((value, key) for key, value in nsmap.items()) - self.nsmaps.append(inverted_nsmap) - # Also treat the namespace mapping as a set of attributes on the - # tag, so we can recreate it later. - attrs = attrs.copy() - for prefix, namespace in nsmap.items(): - attribute = NamespacedAttribute( - "xmlns", prefix, "http://www.w3.org/2000/xmlns/") - attrs[attribute] = namespace - - # Namespaces are in play. Find any attributes that came in - # from lxml with namespaces attached to their names, and - # turn then into NamespacedAttribute objects. - new_attrs = {} - for attr, value in attrs.items(): - namespace, attr = self._getNsTag(attr) - if namespace is None: - new_attrs[attr] = value - else: - nsprefix = self._prefix_for_namespace(namespace) - attr = NamespacedAttribute(nsprefix, attr, namespace) - new_attrs[attr] = value - attrs = new_attrs - - namespace, name = self._getNsTag(name) - nsprefix = self._prefix_for_namespace(namespace) - self.soup.handle_starttag(name, namespace, nsprefix, attrs) - - def _prefix_for_namespace(self, namespace): - """Find the currently active prefix for the given namespace.""" - if namespace is None: - return None - for inverted_nsmap in reversed(self.nsmaps): - if inverted_nsmap is not None and namespace in inverted_nsmap: - return inverted_nsmap[namespace] - return None - - def end(self, name): - self.soup.endData() - completed_tag = self.soup.tagStack[-1] - namespace, name = self._getNsTag(name) - nsprefix = None - if namespace is not None: - for inverted_nsmap in reversed(self.nsmaps): - if inverted_nsmap is not None and namespace in inverted_nsmap: - nsprefix = inverted_nsmap[namespace] - break - self.soup.handle_endtag(name, nsprefix) - if len(self.nsmaps) > 1: - # This tag, or one of its parents, introduced a namespace - # mapping, so pop it off the stack. - self.nsmaps.pop() - - def pi(self, target, data): - self.soup.endData() - self.soup.handle_data(target + ' ' + data) - self.soup.endData(self.processing_instruction_class) - - def data(self, content): - self.soup.handle_data(content) - - def doctype(self, name, pubid, system): - self.soup.endData() - doctype = Doctype.for_name_and_ids(name, pubid, system) - self.soup.object_was_parsed(doctype) - - def comment(self, content): - "Handle comments as Comment objects." - self.soup.endData() - self.soup.handle_data(content) - self.soup.endData(Comment) - - def test_fragment_to_document(self, fragment): - """See `TreeBuilder`.""" - return u'\n%s' % fragment - - -class LXMLTreeBuilder(HTMLTreeBuilder, LXMLTreeBuilderForXML): - - NAME = LXML - ALTERNATE_NAMES = ["lxml-html"] - - features = ALTERNATE_NAMES + [NAME, HTML, FAST, PERMISSIVE] - is_xml = False - processing_instruction_class = ProcessingInstruction - - def default_parser(self, encoding): - return etree.HTMLParser - - def feed(self, markup): - encoding = self.soup.original_encoding - try: - self.parser = self.parser_for(encoding) - self.parser.feed(markup) - self.parser.close() - except (UnicodeDecodeError, LookupError, etree.ParserError), e: - raise ParserRejectedMarkup(str(e)) - - - def test_fragment_to_document(self, fragment): - """See `TreeBuilder`.""" - return u'%s' % fragment diff --git a/libs/py2/bs4/dammit.py b/libs/py2/bs4/dammit.py deleted file mode 100644 index be46b394..00000000 --- a/libs/py2/bs4/dammit.py +++ /dev/null @@ -1,842 +0,0 @@ -# -*- coding: utf-8 -*- -"""Beautiful Soup bonus library: Unicode, Dammit - -This library converts a bytestream to Unicode through any means -necessary. It is heavily based on code from Mark Pilgrim's Universal -Feed Parser. It works best on XML and HTML, but it does not rewrite the -XML or HTML to reflect a new encoding; that's the tree builder's job. -""" -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. -__license__ = "MIT" - -import codecs -from htmlentitydefs import codepoint2name -import re -import logging -import string - -# Import a library to autodetect character encodings. -chardet_type = None -try: - # First try the fast C implementation. - # PyPI package: cchardet - import cchardet - def chardet_dammit(s): - return cchardet.detect(s)['encoding'] -except ImportError: - try: - # Fall back to the pure Python implementation - # Debian package: python-chardet - # PyPI package: chardet - import chardet - def chardet_dammit(s): - return chardet.detect(s)['encoding'] - #import chardet.constants - #chardet.constants._debug = 1 - except ImportError: - # No chardet available. - def chardet_dammit(s): - return None - -# Available from http://cjkpython.i18n.org/. -try: - import iconv_codec -except ImportError: - pass - -xml_encoding_re = re.compile( - '^<\\?.*encoding=[\'"](.*?)[\'"].*\\?>'.encode(), re.I) -html_meta_re = re.compile( - '<\\s*meta[^>]+charset\\s*=\\s*["\']?([^>]*?)[ /;\'">]'.encode(), re.I) - -class EntitySubstitution(object): - - """Substitute XML or HTML entities for the corresponding characters.""" - - def _populate_class_variables(): - lookup = {} - reverse_lookup = {} - characters_for_re = [] - for codepoint, name in list(codepoint2name.items()): - character = unichr(codepoint) - if codepoint != 34: - # There's no point in turning the quotation mark into - # ", unless it happens within an attribute value, which - # is handled elsewhere. - characters_for_re.append(character) - lookup[character] = name - # But we do want to turn " into the quotation mark. - reverse_lookup[name] = character - re_definition = "[%s]" % "".join(characters_for_re) - return lookup, reverse_lookup, re.compile(re_definition) - (CHARACTER_TO_HTML_ENTITY, HTML_ENTITY_TO_CHARACTER, - CHARACTER_TO_HTML_ENTITY_RE) = _populate_class_variables() - - CHARACTER_TO_XML_ENTITY = { - "'": "apos", - '"': "quot", - "&": "amp", - "<": "lt", - ">": "gt", - } - - BARE_AMPERSAND_OR_BRACKET = re.compile("([<>]|" - "&(?!#\\d+;|#x[0-9a-fA-F]+;|\\w+;)" - ")") - - AMPERSAND_OR_BRACKET = re.compile("([<>&])") - - @classmethod - def _substitute_html_entity(cls, matchobj): - entity = cls.CHARACTER_TO_HTML_ENTITY.get(matchobj.group(0)) - return "&%s;" % entity - - @classmethod - def _substitute_xml_entity(cls, matchobj): - """Used with a regular expression to substitute the - appropriate XML entity for an XML special character.""" - entity = cls.CHARACTER_TO_XML_ENTITY[matchobj.group(0)] - return "&%s;" % entity - - @classmethod - def quoted_attribute_value(self, value): - """Make a value into a quoted XML attribute, possibly escaping it. - - Most strings will be quoted using double quotes. - - Bob's Bar -> "Bob's Bar" - - If a string contains double quotes, it will be quoted using - single quotes. - - Welcome to "my bar" -> 'Welcome to "my bar"' - - If a string contains both single and double quotes, the - double quotes will be escaped, and the string will be quoted - using double quotes. - - Welcome to "Bob's Bar" -> "Welcome to "Bob's bar" - """ - quote_with = '"' - if '"' in value: - if "'" in value: - # The string contains both single and double - # quotes. Turn the double quotes into - # entities. We quote the double quotes rather than - # the single quotes because the entity name is - # """ whether this is HTML or XML. If we - # quoted the single quotes, we'd have to decide - # between ' and &squot;. - replace_with = """ - value = value.replace('"', replace_with) - else: - # There are double quotes but no single quotes. - # We can use single quotes to quote the attribute. - quote_with = "'" - return quote_with + value + quote_with - - @classmethod - def substitute_xml(cls, value, make_quoted_attribute=False): - """Substitute XML entities for special XML characters. - - :param value: A string to be substituted. The less-than sign - will become <, the greater-than sign will become >, - and any ampersands will become &. If you want ampersands - that appear to be part of an entity definition to be left - alone, use substitute_xml_containing_entities() instead. - - :param make_quoted_attribute: If True, then the string will be - quoted, as befits an attribute value. - """ - # Escape angle brackets and ampersands. - value = cls.AMPERSAND_OR_BRACKET.sub( - cls._substitute_xml_entity, value) - - if make_quoted_attribute: - value = cls.quoted_attribute_value(value) - return value - - @classmethod - def substitute_xml_containing_entities( - cls, value, make_quoted_attribute=False): - """Substitute XML entities for special XML characters. - - :param value: A string to be substituted. The less-than sign will - become <, the greater-than sign will become >, and any - ampersands that are not part of an entity defition will - become &. - - :param make_quoted_attribute: If True, then the string will be - quoted, as befits an attribute value. - """ - # Escape angle brackets, and ampersands that aren't part of - # entities. - value = cls.BARE_AMPERSAND_OR_BRACKET.sub( - cls._substitute_xml_entity, value) - - if make_quoted_attribute: - value = cls.quoted_attribute_value(value) - return value - - @classmethod - def substitute_html(cls, s): - """Replace certain Unicode characters with named HTML entities. - - This differs from data.encode(encoding, 'xmlcharrefreplace') - in that the goal is to make the result more readable (to those - with ASCII displays) rather than to recover from - errors. There's absolutely nothing wrong with a UTF-8 string - containg a LATIN SMALL LETTER E WITH ACUTE, but replacing that - character with "é" will make it more readable to some - people. - """ - return cls.CHARACTER_TO_HTML_ENTITY_RE.sub( - cls._substitute_html_entity, s) - - -class EncodingDetector: - """Suggests a number of possible encodings for a bytestring. - - Order of precedence: - - 1. Encodings you specifically tell EncodingDetector to try first - (the override_encodings argument to the constructor). - - 2. An encoding declared within the bytestring itself, either in an - XML declaration (if the bytestring is to be interpreted as an XML - document), or in a tag (if the bytestring is to be - interpreted as an HTML document.) - - 3. An encoding detected through textual analysis by chardet, - cchardet, or a similar external library. - - 4. UTF-8. - - 5. Windows-1252. - """ - def __init__(self, markup, override_encodings=None, is_html=False, - exclude_encodings=None): - self.override_encodings = override_encodings or [] - exclude_encodings = exclude_encodings or [] - self.exclude_encodings = set([x.lower() for x in exclude_encodings]) - self.chardet_encoding = None - self.is_html = is_html - self.declared_encoding = None - - # First order of business: strip a byte-order mark. - self.markup, self.sniffed_encoding = self.strip_byte_order_mark(markup) - - def _usable(self, encoding, tried): - if encoding is not None: - encoding = encoding.lower() - if encoding in self.exclude_encodings: - return False - if encoding not in tried: - tried.add(encoding) - return True - return False - - @property - def encodings(self): - """Yield a number of encodings that might work for this markup.""" - tried = set() - for e in self.override_encodings: - if self._usable(e, tried): - yield e - - # Did the document originally start with a byte-order mark - # that indicated its encoding? - if self._usable(self.sniffed_encoding, tried): - yield self.sniffed_encoding - - # Look within the document for an XML or HTML encoding - # declaration. - if self.declared_encoding is None: - self.declared_encoding = self.find_declared_encoding( - self.markup, self.is_html) - if self._usable(self.declared_encoding, tried): - yield self.declared_encoding - - # Use third-party character set detection to guess at the - # encoding. - if self.chardet_encoding is None: - self.chardet_encoding = chardet_dammit(self.markup) - if self._usable(self.chardet_encoding, tried): - yield self.chardet_encoding - - # As a last-ditch effort, try utf-8 and windows-1252. - for e in ('utf-8', 'windows-1252'): - if self._usable(e, tried): - yield e - - @classmethod - def strip_byte_order_mark(cls, data): - """If a byte-order mark is present, strip it and return the encoding it implies.""" - encoding = None - if isinstance(data, unicode): - # Unicode data cannot have a byte-order mark. - return data, encoding - if (len(data) >= 4) and (data[:2] == b'\xfe\xff') \ - and (data[2:4] != '\x00\x00'): - encoding = 'utf-16be' - data = data[2:] - elif (len(data) >= 4) and (data[:2] == b'\xff\xfe') \ - and (data[2:4] != '\x00\x00'): - encoding = 'utf-16le' - data = data[2:] - elif data[:3] == b'\xef\xbb\xbf': - encoding = 'utf-8' - data = data[3:] - elif data[:4] == b'\x00\x00\xfe\xff': - encoding = 'utf-32be' - data = data[4:] - elif data[:4] == b'\xff\xfe\x00\x00': - encoding = 'utf-32le' - data = data[4:] - return data, encoding - - @classmethod - def find_declared_encoding(cls, markup, is_html=False, search_entire_document=False): - """Given a document, tries to find its declared encoding. - - An XML encoding is declared at the beginning of the document. - - An HTML encoding is declared in a tag, hopefully near the - beginning of the document. - """ - if search_entire_document: - xml_endpos = html_endpos = len(markup) - else: - xml_endpos = 1024 - html_endpos = max(2048, int(len(markup) * 0.05)) - - declared_encoding = None - declared_encoding_match = xml_encoding_re.search(markup, endpos=xml_endpos) - if not declared_encoding_match and is_html: - declared_encoding_match = html_meta_re.search(markup, endpos=html_endpos) - if declared_encoding_match is not None: - declared_encoding = declared_encoding_match.groups()[0].decode( - 'ascii', 'replace') - if declared_encoding: - return declared_encoding.lower() - return None - -class UnicodeDammit: - """A class for detecting the encoding of a *ML document and - converting it to a Unicode string. If the source encoding is - windows-1252, can replace MS smart quotes with their HTML or XML - equivalents.""" - - # This dictionary maps commonly seen values for "charset" in HTML - # meta tags to the corresponding Python codec names. It only covers - # values that aren't in Python's aliases and can't be determined - # by the heuristics in find_codec. - CHARSET_ALIASES = {"macintosh": "mac-roman", - "x-sjis": "shift-jis"} - - ENCODINGS_WITH_SMART_QUOTES = [ - "windows-1252", - "iso-8859-1", - "iso-8859-2", - ] - - def __init__(self, markup, override_encodings=[], - smart_quotes_to=None, is_html=False, exclude_encodings=[]): - self.smart_quotes_to = smart_quotes_to - self.tried_encodings = [] - self.contains_replacement_characters = False - self.is_html = is_html - self.log = logging.getLogger(__name__) - self.detector = EncodingDetector( - markup, override_encodings, is_html, exclude_encodings) - - # Short-circuit if the data is in Unicode to begin with. - if isinstance(markup, unicode) or markup == '': - self.markup = markup - self.unicode_markup = unicode(markup) - self.original_encoding = None - return - - # The encoding detector may have stripped a byte-order mark. - # Use the stripped markup from this point on. - self.markup = self.detector.markup - - u = None - for encoding in self.detector.encodings: - markup = self.detector.markup - u = self._convert_from(encoding) - if u is not None: - break - - if not u: - # None of the encodings worked. As an absolute last resort, - # try them again with character replacement. - - for encoding in self.detector.encodings: - if encoding != "ascii": - u = self._convert_from(encoding, "replace") - if u is not None: - self.log.warning( - "Some characters could not be decoded, and were " - "replaced with REPLACEMENT CHARACTER." - ) - self.contains_replacement_characters = True - break - - # If none of that worked, we could at this point force it to - # ASCII, but that would destroy so much data that I think - # giving up is better. - self.unicode_markup = u - if not u: - self.original_encoding = None - - def _sub_ms_char(self, match): - """Changes a MS smart quote character to an XML or HTML - entity, or an ASCII character.""" - orig = match.group(1) - if self.smart_quotes_to == 'ascii': - sub = self.MS_CHARS_TO_ASCII.get(orig).encode() - else: - sub = self.MS_CHARS.get(orig) - if type(sub) == tuple: - if self.smart_quotes_to == 'xml': - sub = '&#x'.encode() + sub[1].encode() + ';'.encode() - else: - sub = '&'.encode() + sub[0].encode() + ';'.encode() - else: - sub = sub.encode() - return sub - - def _convert_from(self, proposed, errors="strict"): - proposed = self.find_codec(proposed) - if not proposed or (proposed, errors) in self.tried_encodings: - return None - self.tried_encodings.append((proposed, errors)) - markup = self.markup - # Convert smart quotes to HTML if coming from an encoding - # that might have them. - if (self.smart_quotes_to is not None - and proposed in self.ENCODINGS_WITH_SMART_QUOTES): - smart_quotes_re = b"([\x80-\x9f])" - smart_quotes_compiled = re.compile(smart_quotes_re) - markup = smart_quotes_compiled.sub(self._sub_ms_char, markup) - - try: - #print "Trying to convert document to %s (errors=%s)" % ( - # proposed, errors) - u = self._to_unicode(markup, proposed, errors) - self.markup = u - self.original_encoding = proposed - except Exception as e: - #print "That didn't work!" - #print e - return None - #print "Correct encoding: %s" % proposed - return self.markup - - def _to_unicode(self, data, encoding, errors="strict"): - '''Given a string and its encoding, decodes the string into Unicode. - %encoding is a string recognized by encodings.aliases''' - return unicode(data, encoding, errors) - - @property - def declared_html_encoding(self): - if not self.is_html: - return None - return self.detector.declared_encoding - - def find_codec(self, charset): - value = (self._codec(self.CHARSET_ALIASES.get(charset, charset)) - or (charset and self._codec(charset.replace("-", ""))) - or (charset and self._codec(charset.replace("-", "_"))) - or (charset and charset.lower()) - or charset - ) - if value: - return value.lower() - return None - - def _codec(self, charset): - if not charset: - return charset - codec = None - try: - codecs.lookup(charset) - codec = charset - except (LookupError, ValueError): - pass - return codec - - - # A partial mapping of ISO-Latin-1 to HTML entities/XML numeric entities. - MS_CHARS = {b'\x80': ('euro', '20AC'), - b'\x81': ' ', - b'\x82': ('sbquo', '201A'), - b'\x83': ('fnof', '192'), - b'\x84': ('bdquo', '201E'), - b'\x85': ('hellip', '2026'), - b'\x86': ('dagger', '2020'), - b'\x87': ('Dagger', '2021'), - b'\x88': ('circ', '2C6'), - b'\x89': ('permil', '2030'), - b'\x8A': ('Scaron', '160'), - b'\x8B': ('lsaquo', '2039'), - b'\x8C': ('OElig', '152'), - b'\x8D': '?', - b'\x8E': ('#x17D', '17D'), - b'\x8F': '?', - b'\x90': '?', - b'\x91': ('lsquo', '2018'), - b'\x92': ('rsquo', '2019'), - b'\x93': ('ldquo', '201C'), - b'\x94': ('rdquo', '201D'), - b'\x95': ('bull', '2022'), - b'\x96': ('ndash', '2013'), - b'\x97': ('mdash', '2014'), - b'\x98': ('tilde', '2DC'), - b'\x99': ('trade', '2122'), - b'\x9a': ('scaron', '161'), - b'\x9b': ('rsaquo', '203A'), - b'\x9c': ('oelig', '153'), - b'\x9d': '?', - b'\x9e': ('#x17E', '17E'), - b'\x9f': ('Yuml', ''),} - - # A parochial partial mapping of ISO-Latin-1 to ASCII. Contains - # horrors like stripping diacritical marks to turn á into a, but also - # contains non-horrors like turning “ into ". - MS_CHARS_TO_ASCII = { - b'\x80' : 'EUR', - b'\x81' : ' ', - b'\x82' : ',', - b'\x83' : 'f', - b'\x84' : ',,', - b'\x85' : '...', - b'\x86' : '+', - b'\x87' : '++', - b'\x88' : '^', - b'\x89' : '%', - b'\x8a' : 'S', - b'\x8b' : '<', - b'\x8c' : 'OE', - b'\x8d' : '?', - b'\x8e' : 'Z', - b'\x8f' : '?', - b'\x90' : '?', - b'\x91' : "'", - b'\x92' : "'", - b'\x93' : '"', - b'\x94' : '"', - b'\x95' : '*', - b'\x96' : '-', - b'\x97' : '--', - b'\x98' : '~', - b'\x99' : '(TM)', - b'\x9a' : 's', - b'\x9b' : '>', - b'\x9c' : 'oe', - b'\x9d' : '?', - b'\x9e' : 'z', - b'\x9f' : 'Y', - b'\xa0' : ' ', - b'\xa1' : '!', - b'\xa2' : 'c', - b'\xa3' : 'GBP', - b'\xa4' : '$', #This approximation is especially parochial--this is the - #generic currency symbol. - b'\xa5' : 'YEN', - b'\xa6' : '|', - b'\xa7' : 'S', - b'\xa8' : '..', - b'\xa9' : '', - b'\xaa' : '(th)', - b'\xab' : '<<', - b'\xac' : '!', - b'\xad' : ' ', - b'\xae' : '(R)', - b'\xaf' : '-', - b'\xb0' : 'o', - b'\xb1' : '+-', - b'\xb2' : '2', - b'\xb3' : '3', - b'\xb4' : ("'", 'acute'), - b'\xb5' : 'u', - b'\xb6' : 'P', - b'\xb7' : '*', - b'\xb8' : ',', - b'\xb9' : '1', - b'\xba' : '(th)', - b'\xbb' : '>>', - b'\xbc' : '1/4', - b'\xbd' : '1/2', - b'\xbe' : '3/4', - b'\xbf' : '?', - b'\xc0' : 'A', - b'\xc1' : 'A', - b'\xc2' : 'A', - b'\xc3' : 'A', - b'\xc4' : 'A', - b'\xc5' : 'A', - b'\xc6' : 'AE', - b'\xc7' : 'C', - b'\xc8' : 'E', - b'\xc9' : 'E', - b'\xca' : 'E', - b'\xcb' : 'E', - b'\xcc' : 'I', - b'\xcd' : 'I', - b'\xce' : 'I', - b'\xcf' : 'I', - b'\xd0' : 'D', - b'\xd1' : 'N', - b'\xd2' : 'O', - b'\xd3' : 'O', - b'\xd4' : 'O', - b'\xd5' : 'O', - b'\xd6' : 'O', - b'\xd7' : '*', - b'\xd8' : 'O', - b'\xd9' : 'U', - b'\xda' : 'U', - b'\xdb' : 'U', - b'\xdc' : 'U', - b'\xdd' : 'Y', - b'\xde' : 'b', - b'\xdf' : 'B', - b'\xe0' : 'a', - b'\xe1' : 'a', - b'\xe2' : 'a', - b'\xe3' : 'a', - b'\xe4' : 'a', - b'\xe5' : 'a', - b'\xe6' : 'ae', - b'\xe7' : 'c', - b'\xe8' : 'e', - b'\xe9' : 'e', - b'\xea' : 'e', - b'\xeb' : 'e', - b'\xec' : 'i', - b'\xed' : 'i', - b'\xee' : 'i', - b'\xef' : 'i', - b'\xf0' : 'o', - b'\xf1' : 'n', - b'\xf2' : 'o', - b'\xf3' : 'o', - b'\xf4' : 'o', - b'\xf5' : 'o', - b'\xf6' : 'o', - b'\xf7' : '/', - b'\xf8' : 'o', - b'\xf9' : 'u', - b'\xfa' : 'u', - b'\xfb' : 'u', - b'\xfc' : 'u', - b'\xfd' : 'y', - b'\xfe' : 'b', - b'\xff' : 'y', - } - - # A map used when removing rogue Windows-1252/ISO-8859-1 - # characters in otherwise UTF-8 documents. - # - # Note that \x81, \x8d, \x8f, \x90, and \x9d are undefined in - # Windows-1252. - WINDOWS_1252_TO_UTF8 = { - 0x80 : b'\xe2\x82\xac', # € - 0x82 : b'\xe2\x80\x9a', # ‚ - 0x83 : b'\xc6\x92', # ƒ - 0x84 : b'\xe2\x80\x9e', # „ - 0x85 : b'\xe2\x80\xa6', # … - 0x86 : b'\xe2\x80\xa0', # † - 0x87 : b'\xe2\x80\xa1', # ‡ - 0x88 : b'\xcb\x86', # ˆ - 0x89 : b'\xe2\x80\xb0', # ‰ - 0x8a : b'\xc5\xa0', # Š - 0x8b : b'\xe2\x80\xb9', # ‹ - 0x8c : b'\xc5\x92', # Œ - 0x8e : b'\xc5\xbd', # Ž - 0x91 : b'\xe2\x80\x98', # ‘ - 0x92 : b'\xe2\x80\x99', # ’ - 0x93 : b'\xe2\x80\x9c', # “ - 0x94 : b'\xe2\x80\x9d', # ” - 0x95 : b'\xe2\x80\xa2', # • - 0x96 : b'\xe2\x80\x93', # – - 0x97 : b'\xe2\x80\x94', # — - 0x98 : b'\xcb\x9c', # ˜ - 0x99 : b'\xe2\x84\xa2', # ™ - 0x9a : b'\xc5\xa1', # š - 0x9b : b'\xe2\x80\xba', # › - 0x9c : b'\xc5\x93', # œ - 0x9e : b'\xc5\xbe', # ž - 0x9f : b'\xc5\xb8', # Ÿ - 0xa0 : b'\xc2\xa0', #   - 0xa1 : b'\xc2\xa1', # ¡ - 0xa2 : b'\xc2\xa2', # ¢ - 0xa3 : b'\xc2\xa3', # £ - 0xa4 : b'\xc2\xa4', # ¤ - 0xa5 : b'\xc2\xa5', # ¥ - 0xa6 : b'\xc2\xa6', # ¦ - 0xa7 : b'\xc2\xa7', # § - 0xa8 : b'\xc2\xa8', # ¨ - 0xa9 : b'\xc2\xa9', # © - 0xaa : b'\xc2\xaa', # ª - 0xab : b'\xc2\xab', # « - 0xac : b'\xc2\xac', # ¬ - 0xad : b'\xc2\xad', # ­ - 0xae : b'\xc2\xae', # ® - 0xaf : b'\xc2\xaf', # ¯ - 0xb0 : b'\xc2\xb0', # ° - 0xb1 : b'\xc2\xb1', # ± - 0xb2 : b'\xc2\xb2', # ² - 0xb3 : b'\xc2\xb3', # ³ - 0xb4 : b'\xc2\xb4', # ´ - 0xb5 : b'\xc2\xb5', # µ - 0xb6 : b'\xc2\xb6', # ¶ - 0xb7 : b'\xc2\xb7', # · - 0xb8 : b'\xc2\xb8', # ¸ - 0xb9 : b'\xc2\xb9', # ¹ - 0xba : b'\xc2\xba', # º - 0xbb : b'\xc2\xbb', # » - 0xbc : b'\xc2\xbc', # ¼ - 0xbd : b'\xc2\xbd', # ½ - 0xbe : b'\xc2\xbe', # ¾ - 0xbf : b'\xc2\xbf', # ¿ - 0xc0 : b'\xc3\x80', # À - 0xc1 : b'\xc3\x81', # Á - 0xc2 : b'\xc3\x82', #  - 0xc3 : b'\xc3\x83', # à - 0xc4 : b'\xc3\x84', # Ä - 0xc5 : b'\xc3\x85', # Å - 0xc6 : b'\xc3\x86', # Æ - 0xc7 : b'\xc3\x87', # Ç - 0xc8 : b'\xc3\x88', # È - 0xc9 : b'\xc3\x89', # É - 0xca : b'\xc3\x8a', # Ê - 0xcb : b'\xc3\x8b', # Ë - 0xcc : b'\xc3\x8c', # Ì - 0xcd : b'\xc3\x8d', # Í - 0xce : b'\xc3\x8e', # Î - 0xcf : b'\xc3\x8f', # Ï - 0xd0 : b'\xc3\x90', # Ð - 0xd1 : b'\xc3\x91', # Ñ - 0xd2 : b'\xc3\x92', # Ò - 0xd3 : b'\xc3\x93', # Ó - 0xd4 : b'\xc3\x94', # Ô - 0xd5 : b'\xc3\x95', # Õ - 0xd6 : b'\xc3\x96', # Ö - 0xd7 : b'\xc3\x97', # × - 0xd8 : b'\xc3\x98', # Ø - 0xd9 : b'\xc3\x99', # Ù - 0xda : b'\xc3\x9a', # Ú - 0xdb : b'\xc3\x9b', # Û - 0xdc : b'\xc3\x9c', # Ü - 0xdd : b'\xc3\x9d', # Ý - 0xde : b'\xc3\x9e', # Þ - 0xdf : b'\xc3\x9f', # ß - 0xe0 : b'\xc3\xa0', # à - 0xe1 : b'\xa1', # á - 0xe2 : b'\xc3\xa2', # â - 0xe3 : b'\xc3\xa3', # ã - 0xe4 : b'\xc3\xa4', # ä - 0xe5 : b'\xc3\xa5', # å - 0xe6 : b'\xc3\xa6', # æ - 0xe7 : b'\xc3\xa7', # ç - 0xe8 : b'\xc3\xa8', # è - 0xe9 : b'\xc3\xa9', # é - 0xea : b'\xc3\xaa', # ê - 0xeb : b'\xc3\xab', # ë - 0xec : b'\xc3\xac', # ì - 0xed : b'\xc3\xad', # í - 0xee : b'\xc3\xae', # î - 0xef : b'\xc3\xaf', # ï - 0xf0 : b'\xc3\xb0', # ð - 0xf1 : b'\xc3\xb1', # ñ - 0xf2 : b'\xc3\xb2', # ò - 0xf3 : b'\xc3\xb3', # ó - 0xf4 : b'\xc3\xb4', # ô - 0xf5 : b'\xc3\xb5', # õ - 0xf6 : b'\xc3\xb6', # ö - 0xf7 : b'\xc3\xb7', # ÷ - 0xf8 : b'\xc3\xb8', # ø - 0xf9 : b'\xc3\xb9', # ù - 0xfa : b'\xc3\xba', # ú - 0xfb : b'\xc3\xbb', # û - 0xfc : b'\xc3\xbc', # ü - 0xfd : b'\xc3\xbd', # ý - 0xfe : b'\xc3\xbe', # þ - } - - MULTIBYTE_MARKERS_AND_SIZES = [ - (0xc2, 0xdf, 2), # 2-byte characters start with a byte C2-DF - (0xe0, 0xef, 3), # 3-byte characters start with E0-EF - (0xf0, 0xf4, 4), # 4-byte characters start with F0-F4 - ] - - FIRST_MULTIBYTE_MARKER = MULTIBYTE_MARKERS_AND_SIZES[0][0] - LAST_MULTIBYTE_MARKER = MULTIBYTE_MARKERS_AND_SIZES[-1][1] - - @classmethod - def detwingle(cls, in_bytes, main_encoding="utf8", - embedded_encoding="windows-1252"): - """Fix characters from one encoding embedded in some other encoding. - - Currently the only situation supported is Windows-1252 (or its - subset ISO-8859-1), embedded in UTF-8. - - The input must be a bytestring. If you've already converted - the document to Unicode, you're too late. - - The output is a bytestring in which `embedded_encoding` - characters have been converted to their `main_encoding` - equivalents. - """ - if embedded_encoding.replace('_', '-').lower() not in ( - 'windows-1252', 'windows_1252'): - raise NotImplementedError( - "Windows-1252 and ISO-8859-1 are the only currently supported " - "embedded encodings.") - - if main_encoding.lower() not in ('utf8', 'utf-8'): - raise NotImplementedError( - "UTF-8 is the only currently supported main encoding.") - - byte_chunks = [] - - chunk_start = 0 - pos = 0 - while pos < len(in_bytes): - byte = in_bytes[pos] - if not isinstance(byte, int): - # Python 2.x - byte = ord(byte) - if (byte >= cls.FIRST_MULTIBYTE_MARKER - and byte <= cls.LAST_MULTIBYTE_MARKER): - # This is the start of a UTF-8 multibyte character. Skip - # to the end. - for start, end, size in cls.MULTIBYTE_MARKERS_AND_SIZES: - if byte >= start and byte <= end: - pos += size - break - elif byte >= 0x80 and byte in cls.WINDOWS_1252_TO_UTF8: - # We found a Windows-1252 character! - # Save the string up to this point as a chunk. - byte_chunks.append(in_bytes[chunk_start:pos]) - - # Now translate the Windows-1252 character into UTF-8 - # and add it as another, one-byte chunk. - byte_chunks.append(cls.WINDOWS_1252_TO_UTF8[byte]) - pos += 1 - chunk_start = pos - else: - # Go on to the next character. - pos += 1 - if chunk_start == 0: - # The string is unchanged. - return in_bytes - else: - # Store the final chunk. - byte_chunks.append(in_bytes[chunk_start:]) - return b''.join(byte_chunks) - diff --git a/libs/py2/bs4/diagnose.py b/libs/py2/bs4/diagnose.py deleted file mode 100644 index 7a28c09a..00000000 --- a/libs/py2/bs4/diagnose.py +++ /dev/null @@ -1,225 +0,0 @@ -"""Diagnostic functions, mainly for use when doing tech support.""" - -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. -__license__ = "MIT" - -import cProfile -from StringIO import StringIO -from HTMLParser import HTMLParser -import bs4 -from bs4 import BeautifulSoup, __version__ -from bs4.builder import builder_registry - -import os -import pstats -import random -import tempfile -import time -import traceback -import sys -import cProfile - -def diagnose(data): - """Diagnostic suite for isolating common problems.""" - print "Diagnostic running on Beautiful Soup %s" % __version__ - print "Python version %s" % sys.version - - basic_parsers = ["html.parser", "html5lib", "lxml"] - for name in basic_parsers: - for builder in builder_registry.builders: - if name in builder.features: - break - else: - basic_parsers.remove(name) - print ( - "I noticed that %s is not installed. Installing it may help." % - name) - - if 'lxml' in basic_parsers: - basic_parsers.append("lxml-xml") - try: - from lxml import etree - print "Found lxml version %s" % ".".join(map(str,etree.LXML_VERSION)) - except ImportError, e: - print ( - "lxml is not installed or couldn't be imported.") - - - if 'html5lib' in basic_parsers: - try: - import html5lib - print "Found html5lib version %s" % html5lib.__version__ - except ImportError, e: - print ( - "html5lib is not installed or couldn't be imported.") - - if hasattr(data, 'read'): - data = data.read() - elif data.startswith("http:") or data.startswith("https:"): - print '"%s" looks like a URL. Beautiful Soup is not an HTTP client.' % data - print "You need to use some other library to get the document behind the URL, and feed that document to Beautiful Soup." - return - else: - try: - if os.path.exists(data): - print '"%s" looks like a filename. Reading data from the file.' % data - with open(data) as fp: - data = fp.read() - except ValueError: - # This can happen on some platforms when the 'filename' is - # too long. Assume it's data and not a filename. - pass - print - - for parser in basic_parsers: - print "Trying to parse your markup with %s" % parser - success = False - try: - soup = BeautifulSoup(data, features=parser) - success = True - except Exception, e: - print "%s could not parse the markup." % parser - traceback.print_exc() - if success: - print "Here's what %s did with the markup:" % parser - print soup.prettify() - - print "-" * 80 - -def lxml_trace(data, html=True, **kwargs): - """Print out the lxml events that occur during parsing. - - This lets you see how lxml parses a document when no Beautiful - Soup code is running. - """ - from lxml import etree - for event, element in etree.iterparse(StringIO(data), html=html, **kwargs): - print("%s, %4s, %s" % (event, element.tag, element.text)) - -class AnnouncingParser(HTMLParser): - """Announces HTMLParser parse events, without doing anything else.""" - - def _p(self, s): - print(s) - - def handle_starttag(self, name, attrs): - self._p("%s START" % name) - - def handle_endtag(self, name): - self._p("%s END" % name) - - def handle_data(self, data): - self._p("%s DATA" % data) - - def handle_charref(self, name): - self._p("%s CHARREF" % name) - - def handle_entityref(self, name): - self._p("%s ENTITYREF" % name) - - def handle_comment(self, data): - self._p("%s COMMENT" % data) - - def handle_decl(self, data): - self._p("%s DECL" % data) - - def unknown_decl(self, data): - self._p("%s UNKNOWN-DECL" % data) - - def handle_pi(self, data): - self._p("%s PI" % data) - -def htmlparser_trace(data): - """Print out the HTMLParser events that occur during parsing. - - This lets you see how HTMLParser parses a document when no - Beautiful Soup code is running. - """ - parser = AnnouncingParser() - parser.feed(data) - -_vowels = "aeiou" -_consonants = "bcdfghjklmnpqrstvwxyz" - -def rword(length=5): - "Generate a random word-like string." - s = '' - for i in range(length): - if i % 2 == 0: - t = _consonants - else: - t = _vowels - s += random.choice(t) - return s - -def rsentence(length=4): - "Generate a random sentence-like string." - return " ".join(rword(random.randint(4,9)) for i in range(length)) - -def rdoc(num_elements=1000): - """Randomly generate an invalid HTML document.""" - tag_names = ['p', 'div', 'span', 'i', 'b', 'script', 'table'] - elements = [] - for i in range(num_elements): - choice = random.randint(0,3) - if choice == 0: - # New tag. - tag_name = random.choice(tag_names) - elements.append("<%s>" % tag_name) - elif choice == 1: - elements.append(rsentence(random.randint(1,4))) - elif choice == 2: - # Close a tag. - tag_name = random.choice(tag_names) - elements.append("" % tag_name) - return "" + "\n".join(elements) + "" - -def benchmark_parsers(num_elements=100000): - """Very basic head-to-head performance benchmark.""" - print "Comparative parser benchmark on Beautiful Soup %s" % __version__ - data = rdoc(num_elements) - print "Generated a large invalid HTML document (%d bytes)." % len(data) - - for parser in ["lxml", ["lxml", "html"], "html5lib", "html.parser"]: - success = False - try: - a = time.time() - soup = BeautifulSoup(data, parser) - b = time.time() - success = True - except Exception, e: - print "%s could not parse the markup." % parser - traceback.print_exc() - if success: - print "BS4+%s parsed the markup in %.2fs." % (parser, b-a) - - from lxml import etree - a = time.time() - etree.HTML(data) - b = time.time() - print "Raw lxml parsed the markup in %.2fs." % (b-a) - - import html5lib - parser = html5lib.HTMLParser() - a = time.time() - parser.parse(data) - b = time.time() - print "Raw html5lib parsed the markup in %.2fs." % (b-a) - -def profile(num_elements=100000, parser="lxml"): - - filehandle = tempfile.NamedTemporaryFile() - filename = filehandle.name - - data = rdoc(num_elements) - vars = dict(bs4=bs4, data=data, parser=parser) - cProfile.runctx('bs4.BeautifulSoup(data, parser)' , vars, vars, filename) - - stats = pstats.Stats(filename) - # stats.strip_dirs() - stats.sort_stats("cumulative") - stats.print_stats('_html5lib|bs4', 50) - -if __name__ == '__main__': - diagnose(sys.stdin.read()) diff --git a/libs/py2/bs4/element.py b/libs/py2/bs4/element.py deleted file mode 100644 index 886eb91f..00000000 --- a/libs/py2/bs4/element.py +++ /dev/null @@ -1,1885 +0,0 @@ -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. -__license__ = "MIT" - -try: - from collections.abc import Callable # Python 3.6 -except ImportError , e: - from collections import Callable -import re -import shlex -import sys -import warnings -from bs4.dammit import EntitySubstitution - -DEFAULT_OUTPUT_ENCODING = "utf-8" -PY3K = (sys.version_info[0] > 2) - -whitespace_re = re.compile(r"\s+") - -def _alias(attr): - """Alias one attribute name to another for backward compatibility""" - @property - def alias(self): - return getattr(self, attr) - - @alias.setter - def alias(self): - return setattr(self, attr) - return alias - - -class NamespacedAttribute(unicode): - - def __new__(cls, prefix, name, namespace=None): - if name is None: - obj = unicode.__new__(cls, prefix) - elif prefix is None: - # Not really namespaced. - obj = unicode.__new__(cls, name) - else: - obj = unicode.__new__(cls, prefix + ":" + name) - obj.prefix = prefix - obj.name = name - obj.namespace = namespace - return obj - -class AttributeValueWithCharsetSubstitution(unicode): - """A stand-in object for a character encoding specified in HTML.""" - -class CharsetMetaAttributeValue(AttributeValueWithCharsetSubstitution): - """A generic stand-in for the value of a meta tag's 'charset' attribute. - - When Beautiful Soup parses the markup '', the - value of the 'charset' attribute will be one of these objects. - """ - - def __new__(cls, original_value): - obj = unicode.__new__(cls, original_value) - obj.original_value = original_value - return obj - - def encode(self, encoding): - return encoding - - -class ContentMetaAttributeValue(AttributeValueWithCharsetSubstitution): - """A generic stand-in for the value of a meta tag's 'content' attribute. - - When Beautiful Soup parses the markup: - - - The value of the 'content' attribute will be one of these objects. - """ - - CHARSET_RE = re.compile(r"((^|;)\s*charset=)([^;]*)", re.M) - - def __new__(cls, original_value): - match = cls.CHARSET_RE.search(original_value) - if match is None: - # No substitution necessary. - return unicode.__new__(unicode, original_value) - - obj = unicode.__new__(cls, original_value) - obj.original_value = original_value - return obj - - def encode(self, encoding): - def rewrite(match): - return match.group(1) + encoding - return self.CHARSET_RE.sub(rewrite, self.original_value) - -class HTMLAwareEntitySubstitution(EntitySubstitution): - - """Entity substitution rules that are aware of some HTML quirks. - - Specifically, the contents of - -Hello, world! - - -''' - soup = self.soup(html) - self.assertEqual("text/javascript", soup.find('script')['type']) - - def test_comment(self): - # Comments are represented as Comment objects. - markup = "

foobaz

" - self.assertSoupEquals(markup) - - soup = self.soup(markup) - comment = soup.find(text="foobar") - self.assertEqual(comment.__class__, Comment) - - # The comment is properly integrated into the tree. - foo = soup.find(text="foo") - self.assertEqual(comment, foo.next_element) - baz = soup.find(text="baz") - self.assertEqual(comment, baz.previous_element) - - def test_preserved_whitespace_in_pre_and_textarea(self): - """Whitespace must be preserved in
 and "
-        self.assertSoupEquals(pre_markup)
-        self.assertSoupEquals(textarea_markup)
-
-        soup = self.soup(pre_markup)
-        self.assertEqual(soup.pre.prettify(), pre_markup)
-
-        soup = self.soup(textarea_markup)
-        self.assertEqual(soup.textarea.prettify(), textarea_markup)
-
-        soup = self.soup("")
-        self.assertEqual(soup.textarea.prettify(), "")
-
-    def test_nested_inline_elements(self):
-        """Inline elements can be nested indefinitely."""
-        b_tag = "Inside a B tag"
-        self.assertSoupEquals(b_tag)
-
-        nested_b_tag = "

A nested tag

" - self.assertSoupEquals(nested_b_tag) - - double_nested_b_tag = "

A doubly nested tag

" - self.assertSoupEquals(nested_b_tag) - - def test_nested_block_level_elements(self): - """Block elements can be nested.""" - soup = self.soup('

Foo

') - blockquote = soup.blockquote - self.assertEqual(blockquote.p.b.string, 'Foo') - self.assertEqual(blockquote.b.string, 'Foo') - - def test_correctly_nested_tables(self): - """One table can go inside another one.""" - markup = ('' - '' - "') - - self.assertSoupEquals( - markup, - '
Here's another table:" - '' - '' - '
foo
Here\'s another table:' - '
foo
' - '
') - - self.assertSoupEquals( - "" - "" - "
Foo
Bar
Baz
") - - def test_deeply_nested_multivalued_attribute(self): - # html5lib can set the attributes of the same tag many times - # as it rearranges the tree. This has caused problems with - # multivalued attributes. - markup = '
' - soup = self.soup(markup) - self.assertEqual(["css"], soup.div.div['class']) - - def test_multivalued_attribute_on_html(self): - # html5lib uses a different API to set the attributes ot the - # tag. This has caused problems with multivalued - # attributes. - markup = '' - soup = self.soup(markup) - self.assertEqual(["a", "b"], soup.html['class']) - - def test_angle_brackets_in_attribute_values_are_escaped(self): - self.assertSoupEquals('', '') - - def test_strings_resembling_character_entity_references(self): - # "&T" and "&p" look like incomplete character entities, but they are - # not. - self.assertSoupEquals( - u"

• AT&T is in the s&p 500

", - u"

\u2022 AT&T is in the s&p 500

" - ) - - def test_entities_in_foreign_document_encoding(self): - # “ and ” are invalid numeric entities referencing - # Windows-1252 characters. - references a character common - # to Windows-1252 and Unicode, and ☃ references a - # character only found in Unicode. - # - # All of these entities should be converted to Unicode - # characters. - markup = "

“Hello” -☃

" - soup = self.soup(markup) - self.assertEquals(u"“Hello” -☃", soup.p.string) - - def test_entities_in_attributes_converted_to_unicode(self): - expect = u'

' - self.assertSoupEquals('

', expect) - self.assertSoupEquals('

', expect) - self.assertSoupEquals('

', expect) - self.assertSoupEquals('

', expect) - - def test_entities_in_text_converted_to_unicode(self): - expect = u'

pi\N{LATIN SMALL LETTER N WITH TILDE}ata

' - self.assertSoupEquals("

piñata

", expect) - self.assertSoupEquals("

piñata

", expect) - self.assertSoupEquals("

piñata

", expect) - self.assertSoupEquals("

piñata

", expect) - - def test_quot_entity_converted_to_quotation_mark(self): - self.assertSoupEquals("

I said "good day!"

", - '

I said "good day!"

') - - def test_out_of_range_entity(self): - expect = u"\N{REPLACEMENT CHARACTER}" - self.assertSoupEquals("�", expect) - self.assertSoupEquals("�", expect) - self.assertSoupEquals("�", expect) - - def test_multipart_strings(self): - "Mostly to prevent a recurrence of a bug in the html5lib treebuilder." - soup = self.soup("

\nfoo

") - self.assertEqual("p", soup.h2.string.next_element.name) - self.assertEqual("p", soup.p.name) - self.assertConnectedness(soup) - - def test_empty_element_tags(self): - """Verify consistent handling of empty-element tags, - no matter how they come in through the markup. - """ - self.assertSoupEquals('


', "


") - self.assertSoupEquals('


', "


") - - def test_head_tag_between_head_and_body(self): - "Prevent recurrence of a bug in the html5lib treebuilder." - content = """ - - foo - -""" - soup = self.soup(content) - self.assertNotEqual(None, soup.html.body) - self.assertConnectedness(soup) - - def test_multiple_copies_of_a_tag(self): - "Prevent recurrence of a bug in the html5lib treebuilder." - content = """ - - - - - -""" - soup = self.soup(content) - self.assertConnectedness(soup.article) - - def test_basic_namespaces(self): - """Parsers don't need to *understand* namespaces, but at the - very least they should not choke on namespaces or lose - data.""" - - markup = b'4' - soup = self.soup(markup) - self.assertEqual(markup, soup.encode()) - html = soup.html - self.assertEqual('http://www.w3.org/1999/xhtml', soup.html['xmlns']) - self.assertEqual( - 'http://www.w3.org/1998/Math/MathML', soup.html['xmlns:mathml']) - self.assertEqual( - 'http://www.w3.org/2000/svg', soup.html['xmlns:svg']) - - def test_multivalued_attribute_value_becomes_list(self): - markup = b'' - soup = self.soup(markup) - self.assertEqual(['foo', 'bar'], soup.a['class']) - - # - # Generally speaking, tests below this point are more tests of - # Beautiful Soup than tests of the tree builders. But parsers are - # weird, so we run these tests separately for every tree builder - # to detect any differences between them. - # - - def test_can_parse_unicode_document(self): - # A seemingly innocuous document... but it's in Unicode! And - # it contains characters that can't be represented in the - # encoding found in the declaration! The horror! - markup = u'Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!' - soup = self.soup(markup) - self.assertEqual(u'Sacr\xe9 bleu!', soup.body.string) - - def test_soupstrainer(self): - """Parsers should be able to work with SoupStrainers.""" - strainer = SoupStrainer("b") - soup = self.soup("A bold statement", - parse_only=strainer) - self.assertEqual(soup.decode(), "bold") - - def test_single_quote_attribute_values_become_double_quotes(self): - self.assertSoupEquals("", - '') - - def test_attribute_values_with_nested_quotes_are_left_alone(self): - text = """a""" - self.assertSoupEquals(text) - - def test_attribute_values_with_double_nested_quotes_get_quoted(self): - text = """a""" - soup = self.soup(text) - soup.foo['attr'] = 'Brawls happen at "Bob\'s Bar"' - self.assertSoupEquals( - soup.foo.decode(), - """a""") - - def test_ampersand_in_attribute_value_gets_escaped(self): - self.assertSoupEquals('', - '') - - self.assertSoupEquals( - 'foo', - 'foo') - - def test_escaped_ampersand_in_attribute_value_is_left_alone(self): - self.assertSoupEquals('') - - def test_entities_in_strings_converted_during_parsing(self): - # Both XML and HTML entities are converted to Unicode characters - # during parsing. - text = "

<<sacré bleu!>>

" - expected = u"

<<sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!>>

" - self.assertSoupEquals(text, expected) - - def test_smart_quotes_converted_on_the_way_in(self): - # Microsoft smart quotes are converted to Unicode characters during - # parsing. - quote = b"

\x91Foo\x92

" - soup = self.soup(quote) - self.assertEqual( - soup.p.string, - u"\N{LEFT SINGLE QUOTATION MARK}Foo\N{RIGHT SINGLE QUOTATION MARK}") - - def test_non_breaking_spaces_converted_on_the_way_in(self): - soup = self.soup("  ") - self.assertEqual(soup.a.string, u"\N{NO-BREAK SPACE}" * 2) - - def test_entities_converted_on_the_way_out(self): - text = "

<<sacré bleu!>>

" - expected = u"

<<sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!>>

".encode("utf-8") - soup = self.soup(text) - self.assertEqual(soup.p.encode("utf-8"), expected) - - def test_real_iso_latin_document(self): - # Smoke test of interrelated functionality, using an - # easy-to-understand document. - - # Here it is in Unicode. Note that it claims to be in ISO-Latin-1. - unicode_html = u'

Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!

' - - # That's because we're going to encode it into ISO-Latin-1, and use - # that to test. - iso_latin_html = unicode_html.encode("iso-8859-1") - - # Parse the ISO-Latin-1 HTML. - soup = self.soup(iso_latin_html) - # Encode it to UTF-8. - result = soup.encode("utf-8") - - # What do we expect the result to look like? Well, it would - # look like unicode_html, except that the META tag would say - # UTF-8 instead of ISO-Latin-1. - expected = unicode_html.replace("ISO-Latin-1", "utf-8") - - # And, of course, it would be in UTF-8, not Unicode. - expected = expected.encode("utf-8") - - # Ta-da! - self.assertEqual(result, expected) - - def test_real_shift_jis_document(self): - # Smoke test to make sure the parser can handle a document in - # Shift-JIS encoding, without choking. - shift_jis_html = ( - b'
'
-            b'\x82\xb1\x82\xea\x82\xcdShift-JIS\x82\xc5\x83R\x81[\x83f'
-            b'\x83B\x83\x93\x83O\x82\xb3\x82\xea\x82\xbd\x93\xfa\x96{\x8c'
-            b'\xea\x82\xcc\x83t\x83@\x83C\x83\x8b\x82\xc5\x82\xb7\x81B'
-            b'
') - unicode_html = shift_jis_html.decode("shift-jis") - soup = self.soup(unicode_html) - - # Make sure the parse tree is correctly encoded to various - # encodings. - self.assertEqual(soup.encode("utf-8"), unicode_html.encode("utf-8")) - self.assertEqual(soup.encode("euc_jp"), unicode_html.encode("euc_jp")) - - def test_real_hebrew_document(self): - # A real-world test to make sure we can convert ISO-8859-9 (a - # Hebrew encoding) to UTF-8. - hebrew_document = b'Hebrew (ISO 8859-8) in Visual Directionality

Hebrew (ISO 8859-8) in Visual Directionality

\xed\xe5\xec\xf9' - soup = self.soup( - hebrew_document, from_encoding="iso8859-8") - # Some tree builders call it iso8859-8, others call it iso-8859-9. - # That's not a difference we really care about. - assert soup.original_encoding in ('iso8859-8', 'iso-8859-8') - self.assertEqual( - soup.encode('utf-8'), - hebrew_document.decode("iso8859-8").encode("utf-8")) - - def test_meta_tag_reflects_current_encoding(self): - # Here's the tag saying that a document is - # encoded in Shift-JIS. - meta_tag = ('') - - # Here's a document incorporating that meta tag. - shift_jis_html = ( - '\n%s\n' - '' - 'Shift-JIS markup goes here.') % meta_tag - soup = self.soup(shift_jis_html) - - # Parse the document, and the charset is seemingly unaffected. - parsed_meta = soup.find('meta', {'http-equiv': 'Content-type'}) - content = parsed_meta['content'] - self.assertEqual('text/html; charset=x-sjis', content) - - # But that value is actually a ContentMetaAttributeValue object. - self.assertTrue(isinstance(content, ContentMetaAttributeValue)) - - # And it will take on a value that reflects its current - # encoding. - self.assertEqual('text/html; charset=utf8', content.encode("utf8")) - - # For the rest of the story, see TestSubstitutions in - # test_tree.py. - - def test_html5_style_meta_tag_reflects_current_encoding(self): - # Here's the tag saying that a document is - # encoded in Shift-JIS. - meta_tag = ('') - - # Here's a document incorporating that meta tag. - shift_jis_html = ( - '\n%s\n' - '' - 'Shift-JIS markup goes here.') % meta_tag - soup = self.soup(shift_jis_html) - - # Parse the document, and the charset is seemingly unaffected. - parsed_meta = soup.find('meta', id="encoding") - charset = parsed_meta['charset'] - self.assertEqual('x-sjis', charset) - - # But that value is actually a CharsetMetaAttributeValue object. - self.assertTrue(isinstance(charset, CharsetMetaAttributeValue)) - - # And it will take on a value that reflects its current - # encoding. - self.assertEqual('utf8', charset.encode("utf8")) - - def test_tag_with_no_attributes_can_have_attributes_added(self): - data = self.soup("text") - data.a['foo'] = 'bar' - self.assertEqual('text', data.a.decode()) - -class XMLTreeBuilderSmokeTest(object): - - def test_pickle_and_unpickle_identity(self): - # Pickling a tree, then unpickling it, yields a tree identical - # to the original. - tree = self.soup("foo") - dumped = pickle.dumps(tree, 2) - loaded = pickle.loads(dumped) - self.assertEqual(loaded.__class__, BeautifulSoup) - self.assertEqual(loaded.decode(), tree.decode()) - - def test_docstring_generated(self): - soup = self.soup("") - self.assertEqual( - soup.encode(), b'\n') - - def test_xml_declaration(self): - markup = b"""\n""" - soup = self.soup(markup) - self.assertEqual(markup, soup.encode("utf8")) - - def test_processing_instruction(self): - markup = b"""\n""" - soup = self.soup(markup) - self.assertEqual(markup, soup.encode("utf8")) - - def test_real_xhtml_document(self): - """A real XHTML document should come out *exactly* the same as it went in.""" - markup = b""" - - -Hello. -Goodbye. -""" - soup = self.soup(markup) - self.assertEqual( - soup.encode("utf-8"), markup) - - def test_nested_namespaces(self): - doc = b""" - - - - - -""" - soup = self.soup(doc) - self.assertEqual(doc, soup.encode()) - - def test_formatter_processes_script_tag_for_xml_documents(self): - doc = """ - -""" - soup = BeautifulSoup(doc, "lxml-xml") - # lxml would have stripped this while parsing, but we can add - # it later. - soup.script.string = 'console.log("< < hey > > ");' - encoded = soup.encode() - self.assertTrue(b"< < hey > >" in encoded) - - def test_can_parse_unicode_document(self): - markup = u'Sacr\N{LATIN SMALL LETTER E WITH ACUTE} bleu!' - soup = self.soup(markup) - self.assertEqual(u'Sacr\xe9 bleu!', soup.root.string) - - def test_popping_namespaced_tag(self): - markup = 'b2012-07-02T20:33:42Zcd' - soup = self.soup(markup) - self.assertEqual( - unicode(soup.rss), markup) - - def test_docstring_includes_correct_encoding(self): - soup = self.soup("") - self.assertEqual( - soup.encode("latin1"), - b'\n') - - def test_large_xml_document(self): - """A large XML document should come out the same as it went in.""" - markup = (b'\n' - + b'0' * (2**12) - + b'') - soup = self.soup(markup) - self.assertEqual(soup.encode("utf-8"), markup) - - - def test_tags_are_empty_element_if_and_only_if_they_are_empty(self): - self.assertSoupEquals("

", "

") - self.assertSoupEquals("

foo

") - - def test_namespaces_are_preserved(self): - markup = 'This tag is in the a namespaceThis tag is in the b namespace' - soup = self.soup(markup) - root = soup.root - self.assertEqual("http://example.com/", root['xmlns:a']) - self.assertEqual("http://example.net/", root['xmlns:b']) - - def test_closing_namespaced_tag(self): - markup = '

20010504

' - soup = self.soup(markup) - self.assertEqual(unicode(soup.p), markup) - - def test_namespaced_attributes(self): - markup = '' - soup = self.soup(markup) - self.assertEqual(unicode(soup.foo), markup) - - def test_namespaced_attributes_xml_namespace(self): - markup = 'bar' - soup = self.soup(markup) - self.assertEqual(unicode(soup.foo), markup) - - def test_find_by_prefixed_name(self): - doc = """ -foo - bar - baz - -""" - soup = self.soup(doc) - - # There are three tags. - self.assertEqual(3, len(soup.find_all('tag'))) - - # But two of them are ns1:tag and one of them is ns2:tag. - self.assertEqual(2, len(soup.find_all('ns1:tag'))) - self.assertEqual(1, len(soup.find_all('ns2:tag'))) - - self.assertEqual(1, len(soup.find_all('ns2:tag', key='value'))) - self.assertEqual(3, len(soup.find_all(['ns1:tag', 'ns2:tag']))) - - def test_copy_tag_preserves_namespace(self): - xml = """ -""" - - soup = self.soup(xml) - tag = soup.document - duplicate = copy.copy(tag) - - # The two tags have the same namespace prefix. - self.assertEqual(tag.prefix, duplicate.prefix) - - -class HTML5TreeBuilderSmokeTest(HTMLTreeBuilderSmokeTest): - """Smoke test for a tree builder that supports HTML5.""" - - def test_real_xhtml_document(self): - # Since XHTML is not HTML5, HTML5 parsers are not tested to handle - # XHTML documents in any particular way. - pass - - def test_html_tags_have_namespace(self): - markup = "" - soup = self.soup(markup) - self.assertEqual("http://www.w3.org/1999/xhtml", soup.a.namespace) - - def test_svg_tags_have_namespace(self): - markup = '' - soup = self.soup(markup) - namespace = "http://www.w3.org/2000/svg" - self.assertEqual(namespace, soup.svg.namespace) - self.assertEqual(namespace, soup.circle.namespace) - - - def test_mathml_tags_have_namespace(self): - markup = '5' - soup = self.soup(markup) - namespace = 'http://www.w3.org/1998/Math/MathML' - self.assertEqual(namespace, soup.math.namespace) - self.assertEqual(namespace, soup.msqrt.namespace) - - def test_xml_declaration_becomes_comment(self): - markup = '' - soup = self.soup(markup) - self.assertTrue(isinstance(soup.contents[0], Comment)) - self.assertEqual(soup.contents[0], '?xml version="1.0" encoding="utf-8"?') - self.assertEqual("html", soup.contents[0].next_element.name) - -def skipIf(condition, reason): - def nothing(test, *args, **kwargs): - return None - - def decorator(test_item): - if condition: - return nothing - else: - return test_item - - return decorator diff --git a/libs/py2/bs4/tests/__init__.py b/libs/py2/bs4/tests/__init__.py deleted file mode 100644 index 142c8cc3..00000000 --- a/libs/py2/bs4/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"The beautifulsoup tests." diff --git a/libs/py2/bs4/tests/test_builder_registry.py b/libs/py2/bs4/tests/test_builder_registry.py deleted file mode 100644 index 90cad829..00000000 --- a/libs/py2/bs4/tests/test_builder_registry.py +++ /dev/null @@ -1,147 +0,0 @@ -"""Tests of the builder registry.""" - -import unittest -import warnings - -from bs4 import BeautifulSoup -from bs4.builder import ( - builder_registry as registry, - HTMLParserTreeBuilder, - TreeBuilderRegistry, -) - -try: - from bs4.builder import HTML5TreeBuilder - HTML5LIB_PRESENT = True -except ImportError: - HTML5LIB_PRESENT = False - -try: - from bs4.builder import ( - LXMLTreeBuilderForXML, - LXMLTreeBuilder, - ) - LXML_PRESENT = True -except ImportError: - LXML_PRESENT = False - - -class BuiltInRegistryTest(unittest.TestCase): - """Test the built-in registry with the default builders registered.""" - - def test_combination(self): - if LXML_PRESENT: - self.assertEqual(registry.lookup('fast', 'html'), - LXMLTreeBuilder) - - if LXML_PRESENT: - self.assertEqual(registry.lookup('permissive', 'xml'), - LXMLTreeBuilderForXML) - self.assertEqual(registry.lookup('strict', 'html'), - HTMLParserTreeBuilder) - if HTML5LIB_PRESENT: - self.assertEqual(registry.lookup('html5lib', 'html'), - HTML5TreeBuilder) - - def test_lookup_by_markup_type(self): - if LXML_PRESENT: - self.assertEqual(registry.lookup('html'), LXMLTreeBuilder) - self.assertEqual(registry.lookup('xml'), LXMLTreeBuilderForXML) - else: - self.assertEqual(registry.lookup('xml'), None) - if HTML5LIB_PRESENT: - self.assertEqual(registry.lookup('html'), HTML5TreeBuilder) - else: - self.assertEqual(registry.lookup('html'), HTMLParserTreeBuilder) - - def test_named_library(self): - if LXML_PRESENT: - self.assertEqual(registry.lookup('lxml', 'xml'), - LXMLTreeBuilderForXML) - self.assertEqual(registry.lookup('lxml', 'html'), - LXMLTreeBuilder) - if HTML5LIB_PRESENT: - self.assertEqual(registry.lookup('html5lib'), - HTML5TreeBuilder) - - self.assertEqual(registry.lookup('html.parser'), - HTMLParserTreeBuilder) - - def test_beautifulsoup_constructor_does_lookup(self): - - with warnings.catch_warnings(record=True) as w: - # This will create a warning about not explicitly - # specifying a parser, but we'll ignore it. - - # You can pass in a string. - BeautifulSoup("", features="html") - # Or a list of strings. - BeautifulSoup("", features=["html", "fast"]) - - # You'll get an exception if BS can't find an appropriate - # builder. - self.assertRaises(ValueError, BeautifulSoup, - "", features="no-such-feature") - -class RegistryTest(unittest.TestCase): - """Test the TreeBuilderRegistry class in general.""" - - def setUp(self): - self.registry = TreeBuilderRegistry() - - def builder_for_features(self, *feature_list): - cls = type('Builder_' + '_'.join(feature_list), - (object,), {'features' : feature_list}) - - self.registry.register(cls) - return cls - - def test_register_with_no_features(self): - builder = self.builder_for_features() - - # Since the builder advertises no features, you can't find it - # by looking up features. - self.assertEqual(self.registry.lookup('foo'), None) - - # But you can find it by doing a lookup with no features, if - # this happens to be the only registered builder. - self.assertEqual(self.registry.lookup(), builder) - - def test_register_with_features_makes_lookup_succeed(self): - builder = self.builder_for_features('foo', 'bar') - self.assertEqual(self.registry.lookup('foo'), builder) - self.assertEqual(self.registry.lookup('bar'), builder) - - def test_lookup_fails_when_no_builder_implements_feature(self): - builder = self.builder_for_features('foo', 'bar') - self.assertEqual(self.registry.lookup('baz'), None) - - def test_lookup_gets_most_recent_registration_when_no_feature_specified(self): - builder1 = self.builder_for_features('foo') - builder2 = self.builder_for_features('bar') - self.assertEqual(self.registry.lookup(), builder2) - - def test_lookup_fails_when_no_tree_builders_registered(self): - self.assertEqual(self.registry.lookup(), None) - - def test_lookup_gets_most_recent_builder_supporting_all_features(self): - has_one = self.builder_for_features('foo') - has_the_other = self.builder_for_features('bar') - has_both_early = self.builder_for_features('foo', 'bar', 'baz') - has_both_late = self.builder_for_features('foo', 'bar', 'quux') - lacks_one = self.builder_for_features('bar') - has_the_other = self.builder_for_features('foo') - - # There are two builders featuring 'foo' and 'bar', but - # the one that also features 'quux' was registered later. - self.assertEqual(self.registry.lookup('foo', 'bar'), - has_both_late) - - # There is only one builder featuring 'foo', 'bar', and 'baz'. - self.assertEqual(self.registry.lookup('foo', 'bar', 'baz'), - has_both_early) - - def test_lookup_fails_when_cannot_reconcile_requested_features(self): - builder1 = self.builder_for_features('foo', 'bar') - builder2 = self.builder_for_features('foo', 'baz') - self.assertEqual(self.registry.lookup('bar', 'baz'), None) diff --git a/libs/py2/bs4/tests/test_docs.py b/libs/py2/bs4/tests/test_docs.py deleted file mode 100644 index 5b9f6770..00000000 --- a/libs/py2/bs4/tests/test_docs.py +++ /dev/null @@ -1,36 +0,0 @@ -"Test harness for doctests." - -# pylint: disable-msg=E0611,W0142 - -__metaclass__ = type -__all__ = [ - 'additional_tests', - ] - -import atexit -import doctest -import os -#from pkg_resources import ( -# resource_filename, resource_exists, resource_listdir, cleanup_resources) -import unittest - -DOCTEST_FLAGS = ( - doctest.ELLIPSIS | - doctest.NORMALIZE_WHITESPACE | - doctest.REPORT_NDIFF) - - -# def additional_tests(): -# "Run the doc tests (README.txt and docs/*, if any exist)" -# doctest_files = [ -# os.path.abspath(resource_filename('bs4', 'README.txt'))] -# if resource_exists('bs4', 'docs'): -# for name in resource_listdir('bs4', 'docs'): -# if name.endswith('.txt'): -# doctest_files.append( -# os.path.abspath( -# resource_filename('bs4', 'docs/%s' % name))) -# kwargs = dict(module_relative=False, optionflags=DOCTEST_FLAGS) -# atexit.register(cleanup_resources) -# return unittest.TestSuite(( -# doctest.DocFileSuite(*doctest_files, **kwargs))) diff --git a/libs/py2/bs4/tests/test_html5lib.py b/libs/py2/bs4/tests/test_html5lib.py deleted file mode 100644 index 0f89d624..00000000 --- a/libs/py2/bs4/tests/test_html5lib.py +++ /dev/null @@ -1,130 +0,0 @@ -"""Tests to ensure that the html5lib tree builder generates good trees.""" - -import warnings - -try: - from bs4.builder import HTML5TreeBuilder - HTML5LIB_PRESENT = True -except ImportError, e: - HTML5LIB_PRESENT = False -from bs4.element import SoupStrainer -from bs4.testing import ( - HTML5TreeBuilderSmokeTest, - SoupTest, - skipIf, -) - -@skipIf( - not HTML5LIB_PRESENT, - "html5lib seems not to be present, not testing its tree builder.") -class HTML5LibBuilderSmokeTest(SoupTest, HTML5TreeBuilderSmokeTest): - """See ``HTML5TreeBuilderSmokeTest``.""" - - @property - def default_builder(self): - return HTML5TreeBuilder() - - def test_soupstrainer(self): - # The html5lib tree builder does not support SoupStrainers. - strainer = SoupStrainer("b") - markup = "

A bold statement.

" - with warnings.catch_warnings(record=True) as w: - soup = self.soup(markup, parse_only=strainer) - self.assertEqual( - soup.decode(), self.document_for(markup)) - - self.assertTrue( - "the html5lib tree builder doesn't support parse_only" in - str(w[0].message)) - - def test_correctly_nested_tables(self): - """html5lib inserts tags where other parsers don't.""" - markup = ('' - '' - "') - - self.assertSoupEquals( - markup, - '
Here's another table:" - '' - '' - '
foo
Here\'s another table:' - '
foo
' - '
') - - self.assertSoupEquals( - "" - "" - "
Foo
Bar
Baz
") - - def test_xml_declaration_followed_by_doctype(self): - markup = ''' - - - - - -

foo

- -''' - soup = self.soup(markup) - # Verify that we can reach the

tag; this means the tree is connected. - self.assertEqual(b"

foo

", soup.p.encode()) - - def test_reparented_markup(self): - markup = '

foo

\n

bar

' - soup = self.soup(markup) - self.assertEqual(u"

foo

\n

bar

", soup.body.decode()) - self.assertEqual(2, len(soup.find_all('p'))) - - - def test_reparented_markup_ends_with_whitespace(self): - markup = '

foo

\n

bar

\n' - soup = self.soup(markup) - self.assertEqual(u"

foo

\n

bar

\n", soup.body.decode()) - self.assertEqual(2, len(soup.find_all('p'))) - - def test_reparented_markup_containing_identical_whitespace_nodes(self): - """Verify that we keep the two whitespace nodes in this - document distinct when reparenting the adjacent tags. - """ - markup = '
' - soup = self.soup(markup) - space1, space2 = soup.find_all(string=' ') - tbody1, tbody2 = soup.find_all('tbody') - assert space1.next_element is tbody1 - assert tbody2.next_element is space2 - - def test_reparented_markup_containing_children(self): - markup = '' - soup = self.soup(markup) - noscript = soup.noscript - self.assertEqual("target", noscript.next_element) - target = soup.find(string='target') - - # The 'aftermath' string was duplicated; we want the second one. - final_aftermath = soup.find_all(string='aftermath')[-1] - - # The