Hello community,

here is the log from the commit of package python-soupsieve for 
openSUSE:Factory checked in at 2019-04-09 20:17:01
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-soupsieve (Old)
 and      /work/SRC/openSUSE:Factory/.python-soupsieve.new.3908 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-soupsieve"

Tue Apr  9 20:17:01 2019 rev:3 rq:691735 version:1.9

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-soupsieve/python-soupsieve.changes        
2019-03-12 09:44:31.099809235 +0100
+++ 
/work/SRC/openSUSE:Factory/.python-soupsieve.new.3908/python-soupsieve.changes  
    2019-04-09 20:17:02.489644874 +0200
@@ -1,0 +2,22 @@
+Fri Apr  5 08:26:37 UTC 2019 - [email protected]
+
+- version update to 1.9
+  * NEW: Allow :contains() to accept a list of text to search 
+    for. (#115)
+  * NEW: Add new escape function for escaping CSS identifiers. (#125)
+  * NEW: Deprecate comments and icomments functions in the API to ensure
+    Soup Sieve focuses only in CSS selectors. comments and icomments
+    will most likely be removed in 2.0. (#130)
+  * NEW: Add Python 3.8 support. (#133)
+  * FIX: Don't install test files when installing the soupsieve
+    package. (#111)
+  * FIX: Improve efficiency of :contains() comparison.
+  * FIX: Null characters should translate to the Unicode REPLACEMENT
+    CHARACTER (U+FFFD) according to the specification. This applies
+    to CSS escaped NULL characters as well. (#124)
+  * FIX: Escaped EOF should translate to U+FFFD outside of CSS strings.
+    In a string, they should just be ignored, but as there is no case
+    where we could resolve such a string and still have a valid selector,
+    string handling remains the same. (#128)
+
+-------------------------------------------------------------------

Old:
----
  soupsieve-1.8.tar.gz

New:
----
  soupsieve-1.9.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-soupsieve.spec ++++++
--- /var/tmp/diff_new_pack.cNoMVe/_old  2019-04-09 20:17:03.177646536 +0200
+++ /var/tmp/diff_new_pack.cNoMVe/_new  2019-04-09 20:17:03.181646546 +0200
@@ -26,7 +26,7 @@
 %bcond_with test
 %endif
 Name:           python-soupsieve%{psuffix}
-Version:        1.8
+Version:        1.9
 Release:        0
 Summary:        A modern CSS selector implementation for BeautifulSoup
 License:        MIT

++++++ soupsieve-1.8.tar.gz -> soupsieve-1.9.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/soupsieve-1.8/PKG-INFO new/soupsieve-1.9/PKG-INFO
--- old/soupsieve-1.8/PKG-INFO  2019-02-17 04:23:17.000000000 +0100
+++ new/soupsieve-1.9/PKG-INFO  2019-03-26 02:41:25.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: soupsieve
-Version: 1.8
+Version: 1.9
 Summary: A CSS4 selector implementation for Beautiful Soup.
 Home-page: https://github.com/facelessuser/soupsieve
 Author: Isaac Muse
@@ -112,6 +112,7 @@
 Classifier: Programming Language :: Python :: 3.5
 Classifier: Programming Language :: Python :: 3.6
 Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
 Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
 Classifier: Topic :: Software Development :: Libraries :: Python Modules
 Description-Content-Type: text/markdown
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/soupsieve-1.8/docs/src/dictionary/en-custom.txt 
new/soupsieve-1.9/docs/src/dictionary/en-custom.txt
--- old/soupsieve-1.8/docs/src/dictionary/en-custom.txt 2019-02-17 
04:22:03.000000000 +0100
+++ new/soupsieve-1.9/docs/src/dictionary/en-custom.txt 2019-03-26 
02:40:10.000000000 +0100
@@ -8,6 +8,7 @@
 Changelog
 Combinators
 DOM
+EOF
 GitHub
 Hashable
 JQuery
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/soupsieve-1.8/docs/src/markdown/about/changelog.md 
new/soupsieve-1.9/docs/src/markdown/about/changelog.md
--- old/soupsieve-1.8/docs/src/markdown/about/changelog.md      2019-02-17 
04:22:03.000000000 +0100
+++ new/soupsieve-1.9/docs/src/markdown/about/changelog.md      2019-03-26 
02:40:10.000000000 +0100
@@ -1,5 +1,20 @@
 # Changelog
 
+## 1.9.0
+
+- **NEW**: Allow `:contains()` to accept a list of text to search for. (#115)
+- **NEW**: Add new `escape` function for escaping CSS identifiers. (#125)
+- **NEW**: Deprecate `comments` and `icomments` functions in the API to ensure 
Soup Sieve focuses only on CSS selectors.
+`comments` and `icomments` will most likely be removed in 2.0. (#130)
+- **NEW**: Add Python 3.8 support. (#133)
+- **FIX**: Don't install test files when installing the `soupsieve` package. 
(#111)
+- **FIX**: Improve efficiency of `:contains()` comparison.
+- **FIX**: Null characters should translate to the Unicode REPLACEMENT 
CHARACTER (`U+FFFD`) according to the
+specification. This applies to CSS escaped NULL characters as well. (#124)
+- **FIX**: Escaped EOF should translate to `U+FFFD` outside of CSS strings. In 
a string, they should just be ignored,
+but as there is no case where we could resolve such a string and still have a 
valid selector, string handling remains
+the same. (#128)
+
 ## 1.8.0
 
 - **NEW**: Add custom selector support. (#92)(#108)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/soupsieve-1.8/docs/src/markdown/about/development.md 
new/soupsieve-1.9/docs/src/markdown/about/development.md
--- old/soupsieve-1.8/docs/src/markdown/about/development.md    2019-02-17 
04:22:03.000000000 +0100
+++ new/soupsieve-1.9/docs/src/markdown/about/development.md    2019-03-26 
02:40:10.000000000 +0100
@@ -239,7 +239,7 @@
 `selectors`     | Contains a tuple of `SelectorList` objects for each 
pseudo-class selector  part of the compound selector: `#!css :is()`, `#!css 
:not()`, `#!css :has()`, etc.
 `relation`      | This will contain a `SelectorList` object with one 
`Selector` object, which could in turn chain an additional relation depending 
on the complexity of the compound selector.  For instance, `div > p + a` would 
be a `Selector` for `a` that contains a `relation` for `p` (another 
`SelectorList` object) which also contains a relation of `div`.  When matching, 
we would match that the tag is `a`, and then walk its relation chain verifying 
that they all match. In this case, the relation chain would be a direct, 
previous sibling of `p`, which has a direct parent of `div`. A `:has()` 
pseudo-class would walk this in the opposite order. `div:has(> p + a)` would 
verify `div`, and then check for a child of `p` with a sibling of `a`.
 `rel_type`      | `rel_type` is attached to relational selectors. In the case 
of `#!css div > p + a`, the relational selectors of `div` and `p` would get a 
relational type of `>` and `+` respectively. `:has()` relational `rel_type` are 
preceded with `:` to signify a forward looking relation.
-`contains`      | Contains a tuple of strings of content to match in an 
element.
+`contains`      | Contains a tuple of [`SelectorContains`](#selectorcontains) 
objects. Each object contains the list of text to match an element's content 
against.
 `lang`          | Contains a tuple of [`SelectorLang`](#selectorlang) objects.
 `flags`         | Selector flags that used to signal a type of selector is 
present.
 
@@ -288,6 +288,20 @@
 `pattern`           | Contains a `re` regular expression object that matches 
the desired attribute value.
 `xml_type_pattern`  | As the default `type` pattern is case insensitive, when 
the attribute value is `type` and a case sensitivity has not been explicitly 
defined, a secondary case sensitive `type` pattern is compiled for use with XML 
documents when detected.
 
+### `SelectorContains`
+
+```py3
+class SelectorContains:
+    """Selector contains rule."""
+
+    def __init__(self, text):
+        """Initialize."""
+```
+
+Attribute           | Description
+------------------- | -----------
+`text`              | A tuple of acceptable text that that an element should 
match. An element only needs to match at least one.
+
 ### `SelectorNth`
 
 ```py3
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/soupsieve-1.8/docs/src/markdown/api.md 
new/soupsieve-1.9/docs/src/markdown/api.md
--- old/soupsieve-1.8/docs/src/markdown/api.md  2019-02-17 04:22:03.000000000 
+0100
+++ new/soupsieve-1.9/docs/src/markdown/api.md  2019-03-26 02:40:10.000000000 
+0100
@@ -141,7 +141,7 @@
 
 ## `soupsieve.comments()`
 
-```
+```py3
 def comments(tag, limit=0, flags=0, **kwargs):
     """Get comments only."""
 ```
@@ -151,15 +151,47 @@
 
 `comments` accepts a `Tag`/`BeautifulSoup` object, a `limit`, and flags.
 
+!!! warning "Deprecated in 1.9.0"
+    `comments` is deprecated so Soup Sieve can focus on only CSS selectors. 
`comments` will be removed in version 2.0.
+
 ## `soupsieve.icomments()`
 
-```
+```py3
 def icomments(node, limit=0, flags=0, **kwargs):
     """Get comments only."""
 ```
 
 `icomments` is exactly like `comments` except that it returns a generator 
instead of a list.
 
+!!! warning "Deprecated in 1.9.0"
+    `icomments` is deprecated so Soup Sieve can focus on only CSS selectors. 
`comments` will be removed in version 2.0.
+
+## `soupsieve.escape()`
+
+```py3
+def escape(ident):
+    """Escape CSS identifier."""
+```
+
+`escape` is used to escape CSS identifiers. It follows the [CSS 
specification][cssom] and escapes any character that
+would normally cause an identifier to be invalid.
+
+```pycon3
+>>> sv.escape(".foo#bar")
+'\\.foo\\#bar'
+>>> sv.escape("()[]{}")
+'\\(\\)\\[\\]\\{\\}'
+>>> sv.escape('--a')
+'--a'
+>>> sv.escape('0')
+'\\30 '
+>>> sv.escape('\0')
+'�'
+```
+
+!!! new "New in 1.9.0"
+    `escape` is a new API function added in 1.9.0.
+
 ## `soupsieve.compile()`
 
 ```py3
@@ -235,7 +267,7 @@
 """
 
 soup = bs4.BeautifulSoup(markup, 'lxml')
-print(sv.select(':--header', soup, custom={':--header', 'h1, h2, h3, h4, h5, 
h6'}))
+print(sv.select(':--header', soup, custom={':--header': 'h1, h2, h3, h4, h5, 
h6'}))
 ```
 
 The above code, when run, should yield the following output:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/soupsieve-1.8/docs/src/markdown/differences.md 
new/soupsieve-1.9/docs/src/markdown/differences.md
--- old/soupsieve-1.8/docs/src/markdown/differences.md  1970-01-01 
01:00:00.000000000 +0100
+++ new/soupsieve-1.9/docs/src/markdown/differences.md  2019-03-26 
02:40:10.000000000 +0100
@@ -0,0 +1,156 @@
+# Beautiful Soup Differences
+
+Soup Sieve is the official CSS "select" implementation of Beautiful Soup 
4.7.0+. While the inclusion of Soup Sieve fixes
+many issues and greatly expands CSS support in Beautiful Soup, it does 
introduce some differences which may surprise
+some who've become accustom to the old "select" implementation.
+
+Beautiful Soup's old select method had numerous limitations and quirks that do 
not align with the actual CSS
+specifications. Most are insignificant, but there are a couple differences 
that people over the years had come to rely
+on. Soup Sieve, which aims to follow the CSS specification closely, does not 
support these differences.
+
+## Attribute Values
+
+Beautiful Soup was very relaxed when it came to attribute values in selectors: 
`#!css [attribute=value]`. Beautiful
+Soup would allow almost anything for a valid unquoted value. Soup Sieve, on 
the other hand, follows the CSS
+specification and requires that a value be a valid identifier, or it must be 
quoted. If you get an error complaining
+about a malformed attribute, you may need to quote the value.
+
+For instance, if you previously used a selector like this:
+
+```py3
+soup.select('[div={}]')
+```
+
+You would need to quote the value as `{}` is not a valid CSS identifier, so it 
must be quoted:
+
+```py3
+soup.select('[div="{}"]')
+```
+
+## CSS Identifiers
+
+Since Soup Sieve follows the CSS specification, class names, id names, tag 
names, etc. must be valid identifiers. Since
+identifiers, according to the CSS specification, cannot *start* with a number, 
some users may find that their old class,
+id, or tag name selectors that started with numbers will not work. To specify 
such selectors, you'll have to use CSS
+escapes.
+
+So if you used to use:
+
+```py3
+soup.select('.2class')
+```
+
+You would need to update with:
+
+```py3
+soup.select(r'.\32 class')
+```
+
+Numbers in the middle or at the end of a class will work as they always did:
+
+```py3
+soup.select('.class2')
+```
+
+## Relative Selectors
+
+Whether on purpose or on accident, Beautiful Soup used to allow relative 
selectors:
+
+```py3
+soup.select('> div')
+```
+
+The above is not a valid CSS selector according the CSS specifications. 
Relative selector lists have only recently been
+added to the CSS specifications, and they are only allowed in a `#!css :has()` 
pseudo-class:
+
+```css
+article:has(> div)
+```
+
+But, in the level 4 CSS specifications, the `:scope` pseudo-class has been 
added which allows for the same feel as using
+`#!css > div`. Since Soup Sieve supports the `:scope` pseudo-class, it can be 
used to produce the same behavior as the
+legacy select method.
+
+In CSS, the `:scope` pseudo-class represents the element that the CSS select 
operation is called on. In supported
+browsers, the following JavaScript example would treats `:scope` as the 
element that `el` references:
+
+```js
+el.querySelectorAll(':scope > .class')
+```
+
+Just like in the JavaScript example above, Soup Sieve would also treat 
`:scope` as the element that `el` references:
+
+```py3
+el.select(':scope > .class')
+```
+
+In the case where the element is the document node, `:scope` would simply 
represent the root element of the document.
+
+So, if you used to to have selectors such as:
+
+```py3
+soup.select('> div')
+```
+
+You can simply add `:scope`, and it should work the same:
+
+```py3
+soup.select(':scope > div')
+```
+
+While this will generally give you what is expected for the relative, 
descendant selectors, this will not work for
+sibling selectors, and the reasons why are covered in more details in [Out of 
Scope Selectors](#out-of-scope-selectors).
+
+## Out of Scope Selectors
+
+In a browser, when requesting a selector via `querySelectorAll`, the element 
that `querySelectorAll` is called on is
+the *scoped* element. So in the following example, `el` is the *scoped* 
element.
+
+```js
+el.querySelectorAll('.class')
+```
+
+This same concept applies to Soup Sieve, where the element that `select` or 
`select_one` is called on is also the
+*scoped* element. So in the following example, `el` is also the *scoped* 
element:
+
+```py3
+el.select('.class')
+```
+
+In browsers, `querySelectorAll` and `querySelector` only return elements under 
the *scoped* element. They do not return
+the *scoped* element itself, its parents, or its siblings. Only when 
`querySelectorAll` or `querySelector` is called on
+the document node will it return the *scoped* selector, which would be the 
*root* element, as the query is being called
+on the document itself and not the *scoped* element.
+
+Soup Sieve aims to essentially mimic the browser functions such as 
`querySelector`, `querySelectorAll`, `matches`, etc.
+In Soup Sieve `select` and `select_one` are analogous to `querySelectorAll` 
and `querySelector` respectively. For this
+reason, Soup Sieve also only returns elements under the *scoped* element. The 
idea is to provide a familiar interface
+that behaves, as close as possible, to what people familiar with CSS selectors 
are used to.
+
+So while Soup Sieve will find elements relative to `:scope` with `>` or 
<code>&nbsp;</code>:
+
+```py3
+soup.select(':scope > div')
+```
+
+It will not find elements relative to `:scope` with `+` or `~` as siblings to 
the *scoped* element are not under the
+*scoped* element:
+
+```py3
+soup.select(':scope + div')
+```
+
+This is by design and is in align with the behavior exhibited in all web 
browsers.
+
+## Selected Element Order
+
+Another quirk of Beautiful Soup's old implementation was that it returned the 
HTML nodes in the order of how the
+selectors were defined. For instance, Beautiful Soup, if given the pattern 
`#!css article, body` would first return
+`#!html <article>` and then `#!html <body>`.
+
+Soup Sieve does not, and frankly cannot, honor Beautiful Soup's old ordering 
convention due to the way it is designed.
+Soup Sieve returns the nodes in the order they are defined in the document as 
that is how the elements are searched.
+This much more efficient and provides better performance.
+
+So, given the earlier selector pattern of `article, body`, Soup Sieve would 
return the element `#!html <body>` and then
+`#!html <article>` as that is how it is ordered in the HTML document.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/soupsieve-1.8/docs/src/markdown/index.md 
new/soupsieve-1.9/docs/src/markdown/index.md
--- old/soupsieve-1.8/docs/src/markdown/index.md        2019-02-17 
04:22:03.000000000 +0100
+++ new/soupsieve-1.9/docs/src/markdown/index.md        2019-03-26 
02:40:10.000000000 +0100
@@ -186,62 +186,6 @@
 >>> sv.purge()
 ```
 
-## Beautiful Soup Differences
-
-Soup Sieve is the official CSS "select" implementation of Beautiful Soup 
4.7.0+. While the inclusion of Soup Sieve fixes
-many issues and greatly expands CSS support in Beautiful Soup, it does 
introduce some differences which may surprise
-some who've become accustom to the old "select" implementation.
-
-Beautiful Soup's old select method had numerous limitations and quirks that do 
not align with the actual CSS
-specification. Most are insignificant, but there are a couple differences that 
people over the years had come to rely
-on. Soup Sieve, which aims to follow the CSS specification closely, does not 
support these differences.
-
-1. Beautiful Soup was very relaxed when it came to attribute values in 
selectors: `#!css [attribute=value]`. Beautiful
-Soup would allow almost anything for a valid unquoted value. Soup Sieve, on 
the other hand, follows the CSS
-specification and requires that a value be a valid identifier, or it must be 
quoted. If you get an error complaining
-about an invalid attribute, you may need to quote the value.
-
-    For instance, if you previously used a selector like this:
-
-    ```py3
-    soup.select('[div={}]')
-    ```
-
-    You would need to quote the value as `{}` is not a valid CSS identifier, 
so it must be quoted:
-
-    ```py3
-    soup.select('[div="{}"]')
-    ```
-
-2. Whether on purpose or on accident, Beautiful Soup used to allow relative 
selectors:
-
-    ```py3
-    soup.select('> div')
-    ```
-
-    The above is not a valid CSS selector according the CSS specifications. 
Relative selector lists have only recently
-    been added to the CSS specifications, and they are only allowed in a 
`#!css :has()` pseudo-class:
-
-    ```css
-    article:has(> div)
-    ```
-
-    But, in the level 4 CSS specifications, the `:scope` pseudo-class has been 
added which allows for the same feel as
-    using `#!css > div`. Since Soup Sieve supports the `:scope` pseudo-class, 
it can be used to produce the same
-    behavior as the legacy select method.
-
-    So, if you used to to have selectors such as:
-
-    ```py3
-    soup.select('> div')
-    ```
-
-    You can simply add `:scope`, and it should work the same:
-
-    ```py3
-    soup.select(':scope > div')
-    ```
-
 --8<--
 refs.txt
 --8<--
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/soupsieve-1.8/docs/src/markdown/selectors.md 
new/soupsieve-1.9/docs/src/markdown/selectors.md
--- old/soupsieve-1.8/docs/src/markdown/selectors.md    2019-02-17 
04:22:03.000000000 +0100
+++ new/soupsieve-1.9/docs/src/markdown/selectors.md    2019-03-26 
02:40:10.000000000 +0100
@@ -412,9 +412,14 @@
     input:checked
     ```
 
-### `:contains`<span class="star badge"></span> {:#:contains}
+### `:contains()`<span class="star badge"></span> {:#:contains}
 
-Selects elements that contain the text provided text. Text can be found in 
either itself, or its descendants.
+Selects elements that contain the provided text. Text can be found in either 
itself, or its descendants.
+
+Contains was originally included in a [CSS early draft][contains-draft], but 
was in the end dropped from the draft.
+Soup Sieve implements it how it was originally proposed in the draft with the 
addition that `:contains()` can accept
+either a single value, or a comma separated list of values. An element needs 
only to match at least one of the items
+in the comma separated list to be considered matching.
 
 !!! warning "Contains"
     `:contains()` is an expensive operation as it scans all the text nodes of 
an element under consideration, which
@@ -465,7 +470,7 @@
     input:default
     ```
 
-### `:defined`<span class="html5 badge"></span> {:#:defined}
+### `:defined`<span class="html5 badge"></span></span><span class="lab 
badge"></span> {:#:defined}
 
 Normally, this represents normal elements (names without hyphens) and custom 
elements (names with hyphens) that have
 been properly added to the custom element registry. Since elements cannot be 
added to a custom element registry in
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/soupsieve-1.8/docs/theme/extra-30d8e6755c.css 
new/soupsieve-1.9/docs/theme/extra-30d8e6755c.css
--- old/soupsieve-1.8/docs/theme/extra-30d8e6755c.css   1970-01-01 
01:00:00.000000000 +0100
+++ new/soupsieve-1.9/docs/theme/extra-30d8e6755c.css   2019-03-26 
02:40:10.000000000 +0100
@@ -0,0 +1 @@
+@charset "UTF-8";.md-typeset 
.magiclink:before{position:relative;padding-right:.25rem;font-family:FontAwesome;-moz-osx-font-smoothing:initial;-webkit-font-smoothing:initial;font-weight:400}.md-typeset
 .magiclink-repository.magiclink-github:before{content:""}.md-typeset 
.magiclink-repository.magiclink-gitlab:before{content:""}.md-typeset 
.magiclink-repository.magiclink-bitbucket:before{content:""}.md-typeset .keys 
kbd:after,.md-typeset .keys 
kbd:before{position:relative;margin:0;color:#bdbdbd;font-family:sans-serif;-moz-osx-font-smoothing:initial;-webkit-font-smoothing:initial;font-weight:400}.md-typeset
 .keys span{padding:0 .2rem;color:#bdbdbd}.md-typeset .keys 
.key-backspace:before{padding-left:.2rem;content:"←"}.md-typeset .keys 
.key-command:before{padding-left:.2rem;content:"⌘"}.md-typeset .keys 
.key-windows:before{padding-left:.2rem;content:"⊞"}.md-typeset .keys 
.key-caps-lock:before{padding-left:.2rem;content:"⇪"}.md-typeset .keys 
.key-control:before{padding-left:.2rem;content:"⌃"}.md-typeset .keys 
.key-meta:before{padding-left:.2rem;content:"◆"}.md-typeset .keys 
.key-shift:before{padding-left:.2rem;content:"⇧"}.md-typeset .keys 
.key-option:before{padding-left:.2rem;content:"⌥"}.md-typeset .keys 
.key-tab:after{padding-left:.2rem;content:"↹"}.md-typeset .keys 
.key-num-enter:after{padding-left:.2rem;content:"↵"}.md-typeset .keys 
.key-enter:after{padding-left:.2rem;content:"↩"}.md-typeset 
.admonition.settings,.md-typeset details.settings{border-left:.4rem solid 
#a0f}.md-typeset .admonition.settings>.admonition-title,.md-typeset 
details.settings>.admonition-title,.md-typeset 
details.settings>summary{border-bottom:.1rem solid 
rgba(170,0,255,.1);background-color:rgba(170,0,255,.1)}.md-typeset 
.admonition.settings>.admonition-title:before,.md-typeset 
details.settings>.admonition-title:before,.md-typeset 
details.settings>summary:before{color:#a0f;content:"settings"}.md-typeset 
.admonition.new,.md-typeset details.new{border-left:.4rem solid 
#ffd600}.md-typeset .admonition.new>.admonition-title,.md-typeset 
details.new>.admonition-title,.md-typeset 
details.new>summary{border-bottom:.1rem solid 
rgba(255,214,0,.1);background-color:rgba(255,214,0,.1)}.md-typeset 
.admonition.new>.admonition-title:before,.md-typeset 
details.new>.admonition-title:before,.md-typeset 
details.new>summary:before{color:#ffd600;content:"new_releases"}.md-typeset 
.uml-flowchart,.md-typeset .uml-sequence-diagram{width:100%;padding:1rem 
0;overflow:auto}.md-typeset .uml-flowchart svg,.md-typeset 
.uml-sequence-diagram svg{max-width:none}.md-typeset a>code{margin:0 
.29412em;padding:.07353em 
0;border-radius:.2rem;background-color:hsla(0,0%,93%,.5);box-shadow:.29412em 0 
0 hsla(0,0%,93%,.5),-.29412em 0 0 
hsla(0,0%,93%,.5);-webkit-box-decoration-break:clone;box-decoration-break:clone}.md-typeset
 .codehilitetable .linenos,.md-typeset .highlighttable 
.linenos{border-right:.0625rem solid 
#ddd;border-radius:0;background-color:hsla(0,0%,93%,.5)}.md-typeset 
.codehilitetable .linenodiv .special,.md-typeset .highlighttable .linenodiv 
.special{margin-right:-1.2rem;margin-left:-1.2rem;padding-right:1.2rem;padding-left:1.2rem;background-color:hsla(0,0%,60%,.2)}.md-typeset
 td code{word-break:normal}.md-typeset .codehilite,.md-typeset 
.highlight{-moz-tab-size:8;-o-tab-size:8;tab-size:8}.md-typeset .codehilite 
.hll,.md-typeset .highlight .hll{display:inline}.md-typeset .codehilite 
[data-linenos]:before,.md-typeset .highlight 
[data-linenos]:before{display:inline-block;margin-right:.5rem;margin-left:-1.2rem;padding-left:1.2rem;border-right:.0625rem
 solid 
#ddd;background-color:#f7f7f7;color:#999;content:attr(data-linenos);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.md-typeset
 .codehilite [data-linenos].special:before,.md-typeset .highlight 
[data-linenos].special:before{background-color:#e6e6e6}.md-typeset .codehilite 
[data-linenos]+.hll,.md-typeset .highlight [data-linenos]+.hll{margin:0 
-.5rem;padding:0 .5rem}.md-typeset .headerlink{font:normal 400 1rem Material 
Icons;vertical-align:middle}.md-typeset h1 
.headerlink{margin-top:-.3rem}.md-typeset h2 
.headerlink{margin-top:-.2rem}.md-typeset h3 
.headerlink{margin-top:-.15rem}.md-typeset h4 .headerlink,.md-typeset h5 
.headerlink,.md-typeset h6 .headerlink{margin-top:-.1rem}.md-typeset 
.progress-label{position:absolute;width:100%;margin:0;color:rgba(0,0,0,.5);font-weight:700;line-height:1.4rem;text-align:center;white-space:nowrap}.md-typeset
 .progress-bar{height:1.2rem;float:left;background-color:#2979ff}.md-typeset 
.candystripe-animate .progress-bar{-webkit-animation:a 3s linear 
infinite;animation:a 3s linear infinite}.md-typeset 
.progress{display:block;position:relative;width:100%;height:1.2rem;margin:.5rem 
0;background-color:#eee}.md-typeset 
.progress.thin{height:.4rem;margin-top:.9rem}.md-typeset .progress.thin 
.progress-label{margin-top:-.4rem}.md-typeset .progress.thin 
.progress-bar{height:.4rem}.md-typeset .progress.candystripe 
.progress-bar{background-image:linear-gradient(135deg,hsla(0,0%,100%,.8) 
27%,transparent 0,transparent 52%,hsla(0,0%,100%,.8) 0,hsla(0,0%,100%,.8) 
77%,transparent 0,transparent);background-size:2rem 2rem}.md-typeset 
.progress-80plus .progress-bar,.md-typeset .progress-100plus 
.progress-bar{background-color:#00e676}.md-typeset .progress-60plus 
.progress-bar{background-color:#fbc02d}.md-typeset .progress-40plus 
.progress-bar{background-color:#ff9100}.md-typeset .progress-20plus 
.progress-bar{background-color:#ff5252}.md-typeset .progress-0plus 
.progress-bar{background-color:#ff1744}.md-typeset .progress.note 
.progress-bar{background-color:#2979ff}.md-typeset .progress.summary 
.progress-bar{background-color:#00b0ff}.md-typeset .progress.tip 
.progress-bar{background-color:#00bfa5}.md-typeset .progress.success 
.progress-bar{background-color:#00e676}.md-typeset .progress.warning 
.progress-bar{background-color:#ff9100}.md-typeset .progress.failure 
.progress-bar{background-color:#ff5252}.md-typeset .progress.danger 
.progress-bar{background-color:#ff1744}.md-typeset .progress.bug 
.progress-bar{background-color:#f50057}.md-typeset .progress.quote 
.progress-bar{background-color:#9e9e9e}@-webkit-keyframes 
a{0%{background-position:0 0}to{background-position:6rem 0}}@keyframes 
a{0%{background-position:0 0}to{background-position:6rem 0}}.md-footer 
.md-footer-custom-text{color:hsla(0,0%,100%,.3)}@media only screen and 
(max-width:44.9375em){.md-typeset>.codehilite 
[data-linenos]:before,.md-typeset>.codehilitetable .linenodiv 
.special,.md-typeset>.highlight 
[data-linenos]:before,.md-typeset>.highlighttable .linenodiv 
.special{margin-left:-1.6rem;padding-left:1.6rem}}@media only screen and 
(min-width:76.1876em){.md-typeset 
.headerlink{margin-left:-1.2rem;float:left}.md-typeset h1 
.headerlink{margin-top:.4rem}.md-typeset h2 
.headerlink{margin-top:.3rem}.md-typeset h3 
.headerlink{margin-top:.2rem}.md-typeset h4 
.headerlink{margin-top:.1rem}.md-typeset h5 .headerlink,.md-typeset h6 
.headerlink{margin-top:0}}
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/soupsieve-1.8/docs/theme/extra-83f68d2c59.css 
new/soupsieve-1.9/docs/theme/extra-83f68d2c59.css
--- old/soupsieve-1.8/docs/theme/extra-83f68d2c59.css   2019-02-17 
04:22:03.000000000 +0100
+++ new/soupsieve-1.9/docs/theme/extra-83f68d2c59.css   1970-01-01 
01:00:00.000000000 +0100
@@ -1 +0,0 @@
-@charset "UTF-8";.md-typeset 
.magiclink:before{position:relative;padding-right:.25rem;font-family:FontAwesome;-moz-osx-font-smoothing:initial;-webkit-font-smoothing:initial;font-weight:400}.md-typeset
 .magiclink-repository.magiclink-github:before{content:""}.md-typeset 
.magiclink-repository.magiclink-gitlab:before{content:""}.md-typeset 
.magiclink-repository.magiclink-bitbucket:before{content:""}.md-typeset .keys 
kbd:after,.md-typeset .keys 
kbd:before{position:relative;margin:0;color:#bdbdbd;font-family:sans-serif;-moz-osx-font-smoothing:initial;-webkit-font-smoothing:initial;font-weight:400}.md-typeset
 .keys span{padding:0 .2rem;color:#bdbdbd}.md-typeset .keys 
.key-backspace:before{padding-left:.2rem;content:"←"}.md-typeset .keys 
.key-command:before{padding-left:.2rem;content:"⌘"}.md-typeset .keys 
.key-windows:before{padding-left:.2rem;content:"⊞"}.md-typeset .keys 
.key-caps-lock:before{padding-left:.2rem;content:"⇪"}.md-typeset .keys 
.key-control:before{padding-left:.2rem;content:"⌃"}.md-typeset .keys 
.key-meta:before{padding-left:.2rem;content:"◆"}.md-typeset .keys 
.key-shift:before{padding-left:.2rem;content:"⇧"}.md-typeset .keys 
.key-option:before{padding-left:.2rem;content:"⌥"}.md-typeset .keys 
.key-tab:after{padding-left:.2rem;content:"↹"}.md-typeset .keys 
.key-num-enter:after{padding-left:.2rem;content:"↵"}.md-typeset .keys 
.key-enter:after{padding-left:.2rem;content:"↩"}.md-typeset 
.admonition.settings,.md-typeset details.settings{border-left:.4rem solid 
#a0f}.md-typeset .admonition.settings>.admonition-title,.md-typeset 
details.settings>.admonition-title,.md-typeset 
details.settings>summary{border-bottom:.1rem solid 
rgba(170,0,255,.1);background-color:rgba(170,0,255,.1)}.md-typeset 
.admonition.settings>.admonition-title:before,.md-typeset 
details.settings>.admonition-title:before,.md-typeset 
details.settings>summary:before{color:#a0f;content:"settings"}.md-typeset 
.admonition.new,.md-typeset details.new{border-left:.4rem solid 
#ffd600}.md-typeset .admonition.new>.admonition-title,.md-typeset 
details.new>.admonition-title,.md-typeset 
details.new>summary{border-bottom:.1rem solid 
rgba(255,214,0,.1);background-color:rgba(255,214,0,.1)}.md-typeset 
.admonition.new>.admonition-title:before,.md-typeset 
details.new>.admonition-title:before,.md-typeset 
details.new>summary:before{color:#ffd600;content:"new_releases"}.md-typeset 
.uml-flowchart,.md-typeset .uml-sequence-diagram{width:100%;padding:1rem 
0;overflow:auto}.md-typeset .uml-flowchart svg,.md-typeset 
.uml-sequence-diagram svg{max-width:none}.md-typeset a>code{margin:0 
.29412em;padding:.07353em 
0;border-radius:.2rem;background-color:hsla(0,0%,93%,.5);box-shadow:.29412em 0 
0 hsla(0,0%,93%,.5),-.29412em 0 0 
hsla(0,0%,93%,.5);-webkit-box-decoration-break:clone;box-decoration-break:clone}.md-typeset
 .codehilitetable .linenos,.md-typeset .highlighttable 
.linenos{border-right:.0625rem solid 
#ddd;border-radius:0;background-color:hsla(0,0%,93%,.5)}.md-typeset 
.codehilitetable .linenodiv .special,.md-typeset .highlighttable .linenodiv 
.special{margin-right:-1.2rem;margin-left:-1.2rem;padding-right:1.2rem;padding-left:1.2rem;background-color:hsla(0,0%,60%,.2)}.md-typeset
 td code{word-break:normal}.md-typeset .codehilite,.md-typeset 
.highlight{-moz-tab-size:8;-o-tab-size:8;tab-size:8}.md-typeset .codehilite 
.hll,.md-typeset .highlight .hll{display:inline}.md-typeset .codehilite 
[data-linenos]:before,.md-typeset .highlight 
[data-linenos]:before{display:inline-block;margin-right:.5rem;margin-left:-1.2rem;padding-left:1.2rem;border-right:.0625rem
 solid 
#ddd;background-color:#f7f7f7;color:#999;content:attr(data-linenos);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.md-typeset
 .codehilite [data-linenos].special:before,.md-typeset .highlight 
[data-linenos].special:before{background-color:#e6e6e6}.md-typeset .codehilite 
[data-linenos]+.hll,.md-typeset .highlight [data-linenos]+.hll{margin:0 
-.5rem;padding:0 .5rem}.md-typeset .headerlink{font:normal 400 2rem Material 
Icons;vertical-align:middle}.md-typeset h1 
.headerlink{margin-top:-.6rem}.md-typeset h2 
.headerlink{margin-top:-.4rem}.md-typeset h3 
.headerlink{margin-top:-.3rem}.md-typeset h4 .headerlink,.md-typeset h5 
.headerlink,.md-typeset h6 .headerlink{margin-top:-.2rem}.md-typeset 
.progress-label{position:absolute;width:100%;margin:0;color:rgba(0,0,0,.5);font-weight:700;line-height:1.4rem;text-align:center;white-space:nowrap}.md-typeset
 .progress-bar{height:1.2rem;float:left;background-color:#2979ff}.md-typeset 
.candystripe-animate .progress-bar{-webkit-animation:a 3s linear 
infinite;animation:a 3s linear infinite}.md-typeset 
.progress{display:block;position:relative;width:100%;height:1.2rem;margin:.5rem 
0;background-color:#eee}.md-typeset 
.progress.thin{height:.4rem;margin-top:.9rem}.md-typeset .progress.thin 
.progress-label{margin-top:-.4rem}.md-typeset .progress.thin 
.progress-bar{height:.4rem}.md-typeset .progress.candystripe 
.progress-bar{background-image:linear-gradient(135deg,hsla(0,0%,100%,.8) 
27%,transparent 0,transparent 52%,hsla(0,0%,100%,.8) 0,hsla(0,0%,100%,.8) 
77%,transparent 0,transparent);background-size:2rem 2rem}.md-typeset 
.progress-80plus .progress-bar,.md-typeset .progress-100plus 
.progress-bar{background-color:#00e676}.md-typeset .progress-60plus 
.progress-bar{background-color:#fbc02d}.md-typeset .progress-40plus 
.progress-bar{background-color:#ff9100}.md-typeset .progress-20plus 
.progress-bar{background-color:#ff5252}.md-typeset .progress-0plus 
.progress-bar{background-color:#ff1744}.md-typeset .progress.note 
.progress-bar{background-color:#2979ff}.md-typeset .progress.summary 
.progress-bar{background-color:#00b0ff}.md-typeset .progress.tip 
.progress-bar{background-color:#00bfa5}.md-typeset .progress.success 
.progress-bar{background-color:#00e676}.md-typeset .progress.warning 
.progress-bar{background-color:#ff9100}.md-typeset .progress.failure 
.progress-bar{background-color:#ff5252}.md-typeset .progress.danger 
.progress-bar{background-color:#ff1744}.md-typeset .progress.bug 
.progress-bar{background-color:#f50057}.md-typeset .progress.quote 
.progress-bar{background-color:#9e9e9e}@-webkit-keyframes 
a{0%{background-position:0 0}to{background-position:6rem 0}}@keyframes 
a{0%{background-position:0 0}to{background-position:6rem 0}}.md-footer 
.md-footer-custom-text{color:hsla(0,0%,100%,.3)}@media only screen and 
(max-width:44.9375em){.md-typeset>.codehilite 
[data-linenos]:before,.md-typeset>.codehilitetable .linenodiv 
.special,.md-typeset>.highlight 
[data-linenos]:before,.md-typeset>.highlighttable .linenodiv 
.special{margin-left:-1.6rem;padding-left:1.6rem}}@media only screen and 
(min-width:76.1876em){.md-typeset 
.headerlink{margin-left:-2.4rem;float:left}.md-typeset h1 
.headerlink{margin-top:.8rem}.md-typeset h2 
.headerlink{margin-top:.6rem}.md-typeset h3 
.headerlink{margin-top:.4rem}.md-typeset h4 
.headerlink{margin-top:.2rem}.md-typeset h5 .headerlink,.md-typeset h6 
.headerlink{margin-top:0}}
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/soupsieve-1.8/mkdocs.yml new/soupsieve-1.9/mkdocs.yml
--- old/soupsieve-1.8/mkdocs.yml        2019-02-17 04:22:03.000000000 +0100
+++ new/soupsieve-1.9/mkdocs.yml        2019-03-26 02:40:10.000000000 +0100
@@ -2,7 +2,7 @@
 site_url: https://facelessuser.github.io/soupsieve
 repo_url: https://github.com/facelessuser/soupsieve
 edit_uri: tree/master/docs/src/markdown
-site_description: Python spell checker.
+site_description: A modern CSS selector library for Beautiful Soup.
 copyright: |
   Copyright &copy; 2018 <a href="https://github.com/facelessuser";>Isaac 
Muse</a>
   <br><span class="md-footer-custom-text">emoji provided free by <a 
href="http://www.emojione.com";>EmojiOne</a></span>
@@ -27,6 +27,7 @@
     - Soup Sieve: index.md
     - API: api.md
     - CSS Selectors: selectors.md
+    - Beautiful Soup Differences: differences.md
   - About:
     - Contributing &amp; Support: about/contributing.md
     - Development: about/development.md
@@ -91,6 +92,6 @@
     - type: github
       link: https://github.com/facelessuser
 extra_css:
-  - extra-83f68d2c59.css
+  - extra-30d8e6755c.css
 extra_javascript:
   - extra-0b9b22dd13.js
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/soupsieve-1.8/setup.py new/soupsieve-1.9/setup.py
--- old/soupsieve-1.8/setup.py  2019-02-17 04:22:03.000000000 +0100
+++ new/soupsieve-1.9/setup.py  2019-03-26 02:40:10.000000000 +0100
@@ -51,7 +51,7 @@
     author='Isaac Muse',
     author_email='[email protected]',
     url='https://github.com/facelessuser/soupsieve',
-    packages=find_packages(exclude=['tests', 'tools']),
+    packages=find_packages(exclude=['test*', 'tools']),
     install_requires=get_requirements(),
     license='MIT License',
     classifiers=[
@@ -67,6 +67,7 @@
         'Programming Language :: Python :: 3.5',
         'Programming Language :: Python :: 3.6',
         'Programming Language :: Python :: 3.7',
+        'Programming Language :: Python :: 3.8',
         'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
         'Topic :: Software Development :: Libraries :: Python Modules'
     ]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/soupsieve-1.8/soupsieve/__init__.py 
new/soupsieve-1.9/soupsieve/__init__.py
--- old/soupsieve-1.8/soupsieve/__init__.py     2019-02-17 04:22:03.000000000 
+0100
+++ new/soupsieve-1.9/soupsieve/__init__.py     2019-03-26 02:40:10.000000000 
+0100
@@ -30,7 +30,7 @@
 from . import css_parser as cp
 from . import css_match as cm
 from . import css_types as ct
-from .util import DEBUG, _QUIRKS, SelectorSyntaxError  # noqa: F401
+from .util import DEBUG, _QUIRKS, deprecated, SelectorSyntaxError  # noqa: F401
 
 __all__ = (
     'DEBUG', "_QUIRKS", 'SelectorSyntaxError', 'SoupSieve',
@@ -87,12 +87,14 @@
     return compile(select, namespaces, flags, **kwargs).filter(iterable)
 
 
+@deprecated("'comments' is not related to CSS selectors and will be removed in 
the future.")
 def comments(tag, limit=0, flags=0, **kwargs):
     """Get comments only."""
 
-    return list(icomments(tag, limit, flags))
+    return [comment for comment in cm.CommentsMatch(tag).get_comments(limit)]
 
 
+@deprecated("'icomments' is not related to CSS selectors and will be removed 
in the future.")
 def icomments(tag, limit=0, flags=0, **kwargs):
     """Iterate comments only."""
 
@@ -117,3 +119,9 @@
 
     for el in compile(select, namespaces, flags, **kwargs).iselect(tag, limit):
         yield el
+
+
+def escape(ident):
+    """Escape identifier."""
+
+    return cp.escape(ident)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/soupsieve-1.8/soupsieve/__meta__.py 
new/soupsieve-1.9/soupsieve/__meta__.py
--- old/soupsieve-1.8/soupsieve/__meta__.py     2019-02-17 04:22:03.000000000 
+0100
+++ new/soupsieve-1.9/soupsieve/__meta__.py     2019-03-26 02:40:10.000000000 
+0100
@@ -186,5 +186,5 @@
     return Version(major, minor, micro, release, pre, post, dev)
 
 
-__version_info__ = Version(1, 8, 0, "final")
+__version_info__ = Version(1, 9, 0, "final")
 __version__ = __version_info__._get_canonical()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/soupsieve-1.8/soupsieve/css_match.py 
new/soupsieve-1.9/soupsieve/css_match.py
--- old/soupsieve-1.8/soupsieve/css_match.py    2019-02-17 04:22:03.000000000 
+0100
+++ new/soupsieve-1.9/soupsieve/css_match.py    2019-03-26 02:40:10.000000000 
+0100
@@ -811,10 +811,17 @@
         """Match element if it contains text."""
 
         match = True
-        for c in contains:
-            if c not in self.get_text(el):
+        content = None
+        for contain_list in contains:
+            if content is None:
+                content = self.get_text(el)
+            found = False
+            for text in contain_list.text:
+                if text in content:
+                    found = True
+                    break
+            if not found:
                 match = False
-                break
         return match
 
     def match_default(self, el):
@@ -1290,11 +1297,13 @@
         else:
             return [node for node in iterable if not 
CSSMatch.is_navigable_string(node) and self.match(node)]
 
+    @util.deprecated("'comments' is not related to CSS selectors and will be 
removed in the future.")
     def comments(self, tag, limit=0):
         """Get comments only."""
 
-        return list(self.icomments(tag, limit))
+        return [comment for comment in CommentsMatch(tag).get_comments(limit)]
 
+    @util.deprecated("'icomments' is not related to CSS selectors and will be 
removed in the future.")
     def icomments(self, tag, limit=0):
         """Iterate comments only."""
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/soupsieve-1.8/soupsieve/css_parser.py 
new/soupsieve-1.9/soupsieve/css_parser.py
--- old/soupsieve-1.8/soupsieve/css_parser.py   2019-02-17 04:22:03.000000000 
+0100
+++ new/soupsieve-1.9/soupsieve/css_parser.py   2019-03-26 02:40:10.000000000 
+0100
@@ -7,6 +7,8 @@
 from collections import OrderedDict
 from .util import SelectorSyntaxError
 
+UNICODE_REPLACEMENT_CHAR = 0xFFFD
+
 # Simple pseudo classes that take no parameters
 PSEUDO_SIMPLE = {
     ":any-link",
@@ -92,8 +94,8 @@
 # Whitespace with comments included
 WSC = r'(?:{ws}|{comments})'.format(ws=WS, comments=COMMENTS)
 # CSS escapes
-CSS_ESCAPES = r'(?:\\[a-f0-9]{{1,6}}{ws}?|\\[^\r\n\f])'.format(ws=WS)
-CSS_STRING_ESCAPES = 
r'(?:\\[a-f0-9]{{1,6}}{ws}?|\\[^\r\n\f]|\\{nl})'.format(ws=WS, nl=NEWLINE)
+CSS_ESCAPES = r'(?:\\(?:[a-f0-9]{{1,6}}{ws}?|[^\r\n\f]|$))'.format(ws=WS)
+CSS_STRING_ESCAPES = 
r'(?:\\(?:[a-f0-9]{{1,6}}{ws}?|[^\r\n\f]|$|{nl}))'.format(ws=WS, nl=NEWLINE)
 # CSS Identifier
 IDENTIFIER = r'''
 (?:(?:-?(?:[^\x00-\x2f\x30-\x40\x5B-\x5E\x60\x7B-\x9f]|{esc})+|--)
@@ -149,25 +151,27 @@
 \({ws}*(?P<nth_type>{nth}|even|odd)){ws}*\)
 '''.format(ws=WSC, nth=NTH)
 # Pseudo class language (`:lang("*-de", en)`)
-PAT_PSEUDO_LANG = 
r':lang\({ws}*(?P<lang>{value}(?:{ws}*,{ws}*{value})*){ws}*\)'.format(ws=WSC, 
value=VALUE)
+PAT_PSEUDO_LANG = 
r':lang\({ws}*(?P<values>{value}(?:{ws}*,{ws}*{value})*){ws}*\)'.format(ws=WSC, 
value=VALUE)
 # Pseudo class direction (`:dir(ltr)`)
 PAT_PSEUDO_DIR = r':dir\({ws}*(?P<dir>ltr|rtl){ws}*\)'.format(ws=WSC)
 # Combining characters (`>`, `~`, ` `, `+`, `,`)
 PAT_COMBINE = 
r'{wsc}*?(?P<relation>[,+>~]|{ws}(?![,+>~])){wsc}*'.format(ws=WS, wsc=WSC)
 # Extra: Contains (`:contains(text)`)
-PAT_PSEUDO_CONTAINS = 
r':contains\({ws}*(?P<value>{value}){ws}*\)'.format(ws=WSC, value=VALUE)
+PAT_PSEUDO_CONTAINS = 
r':contains\({ws}*(?P<values>{value}(?:{ws}*,{ws}*{value})*){ws}*\)'.format(ws=WSC,
 value=VALUE)
 
 # Regular expressions
 # CSS escape pattern
-RE_CSS_ESC = 
re.compile(r'(?:(\\[a-f0-9]{{1,6}}{ws}?)|(\\[^\r\n\f]))'.format(ws=WSC), re.I)
-RE_CSS_STR_ESC = 
re.compile(r'(?:(\\[a-f0-9]{{1,6}}{ws}?)|(\\[^\r\n\f])|(\\{nl}))'.format(ws=WS, 
nl=NEWLINE), re.I)
+RE_CSS_ESC = 
re.compile(r'(?:(\\[a-f0-9]{{1,6}}{ws}?)|(\\[^\r\n\f])|(\\$))'.format(ws=WSC), 
re.I)
+RE_CSS_STR_ESC = re.compile(
+    r'(?:(\\[a-f0-9]{{1,6}}{ws}?)|(\\[^\r\n\f])|(\\$)|(\\{nl}))'.format(ws=WS, 
nl=NEWLINE), re.I
+)
 # Pattern to break up `nth` specifiers
 RE_NTH = re.compile(
     
r'(?P<s1>[-+])?(?P<a>[0-9]+n?|n)(?:(?<=n){ws}*(?P<s2>[-+]){ws}*(?P<b>[0-9]+))?'.format(ws=WSC),
     re.I
 )
-# Pattern to iterate multiple languages.
-RE_LANG = 
re.compile(r'(?:(?P<value>{value})|(?P<split>{ws}*,{ws}*))'.format(ws=WSC, 
value=VALUE), re.X)
+# Pattern to iterate multiple values.
+RE_VALUES = 
re.compile(r'(?:(?P<value>{value})|(?P<split>{ws}*,{ws}*))'.format(ws=WSC, 
value=VALUE), re.X)
 # Whitespace checks
 RE_WS = re.compile(WS)
 RE_WS_BEGIN = re.compile('^{}*'.format(WSC))
@@ -240,21 +244,49 @@
     def replace(m):
         """Replace with the appropriate substitute."""
 
-        return util.uchr(int(m.group(1)[1:], 16)) if m.group(1) else 
m.group(2)[1:]
-
-    def replace_string(m):
-        """Replace with the appropriate substitute for a string."""
-
         if m.group(1):
-            value = util.uchr(int(m.group(1)[1:], 16))
+            codepoint = int(m.group(1)[1:], 16)
+            if codepoint == 0:
+                codepoint = UNICODE_REPLACEMENT_CHAR
+            value = util.uchr(codepoint)
         elif m.group(2):
             value = m.group(2)[1:]
+        elif m.group(3):
+            value = '\ufffd'
         else:
             value = ''
 
         return value
 
-    return RE_CSS_ESC.sub(replace, content) if not string else 
RE_CSS_STR_ESC.sub(replace_string, content)
+    return (RE_CSS_ESC if not string else RE_CSS_STR_ESC).sub(replace, content)
+
+
+def escape(ident):
+    """Escape identifier."""
+
+    string = []
+    length = len(ident)
+    start_dash = length > 0 and ident[0] == '-'
+    if length == 1 and start_dash:
+        # Need to escape identifier that is a single `-` with no other 
characters
+        string.append('\\{}'.format(ident))
+    else:
+        for index, c in enumerate(ident):
+            codepoint = util.uord(c)
+            if codepoint == 0x00:
+                string.append('\ufffd')
+            elif (0x01 <= codepoint <= 0x1F) or codepoint == 0x7F:
+                string.append('\\{:x} '.format(codepoint))
+            elif (index == 0 or (start_dash and index == 1)) and (0x30 <= 
codepoint <= 0x39):
+                string.append('\\{:x} '.format(codepoint))
+            elif (
+                codepoint in (0x2D, 0x5F) or codepoint >= 0x80 or (0x30 <= 
codepoint <= 0x39) or
+                (0x30 <= codepoint <= 0x39) or (0x41 <= codepoint <= 0x5A) or 
(0x61 <= codepoint <= 0x7A)
+            ):
+                string.append(c)
+            else:
+                string.append('\\{}'.format(c))
+    return ''.join(string)
 
 
 class SelectorPattern(object):
@@ -376,7 +408,7 @@
     def __init__(self, selector, custom=None, flags=0):
         """Initialize."""
 
-        self.pattern = selector
+        self.pattern = selector.replace('\x00', '\ufffd')
         self.flags = flags
         self.debug = self.flags & util.DEBUG
         self.quirks = self.flags & util._QUIRKS
@@ -751,21 +783,27 @@
     def parse_pseudo_contains(self, sel, m, has_selector):
         """Parse contains."""
 
-        content = m.group('value')
-        if content.startswith(("'", '"')):
-            content = css_unescape(content[1:-1], True)
-        else:
-            content = css_unescape(content)
-        sel.contains.append(content)
+        values = m.group('values')
+        patterns = []
+        for token in RE_VALUES.finditer(values):
+            if token.group('split'):
+                continue
+            value = token.group('value')
+            if value.startswith(("'", '"')):
+                value = css_unescape(value[1:-1], True)
+            else:
+                value = css_unescape(value)
+            patterns.append(value)
+        sel.contains.append(ct.SelectorContains(tuple(patterns)))
         has_selector = True
         return has_selector
 
     def parse_pseudo_lang(self, sel, m, has_selector):
         """Parse pseudo language."""
 
-        lang = m.group('lang')
+        values = m.group('values')
         patterns = []
-        for token in RE_LANG.finditer(lang):
+        for token in RE_VALUES.finditer(values):
             if token.group('split'):
                 continue
             value = token.group('value')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/soupsieve-1.8/soupsieve/css_types.py 
new/soupsieve-1.9/soupsieve/css_types.py
--- old/soupsieve-1.8/soupsieve/css_types.py    2019-02-17 04:22:03.000000000 
+0100
+++ new/soupsieve-1.9/soupsieve/css_types.py    2019-03-26 02:40:10.000000000 
+0100
@@ -7,6 +7,7 @@
     'SelectorNull',
     'SelectorTag',
     'SelectorAttribute',
+    'SelectorContains',
     'SelectorNth',
     'SelectorLang',
     'SelectorList',
@@ -234,6 +235,19 @@
         )
 
 
+class SelectorContains(Immutable):
+    """Selector contains rule."""
+
+    __slots__ = ("text", "_hash")
+
+    def __init__(self, text):
+        """Initialize."""
+
+        super(SelectorContains, self).__init__(
+            text=text
+        )
+
+
 class SelectorNth(Immutable):
     """Selector nth type."""
 
@@ -324,6 +338,7 @@
 pickle_register(SelectorNull)
 pickle_register(SelectorTag)
 pickle_register(SelectorAttribute)
+pickle_register(SelectorContains)
 pickle_register(SelectorNth)
 pickle_register(SelectorLang)
 pickle_register(SelectorList)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/soupsieve-1.8/soupsieve/util.py 
new/soupsieve-1.9/soupsieve/util.py
--- old/soupsieve-1.8/soupsieve/util.py 2019-02-17 04:22:03.000000000 +0100
+++ new/soupsieve-1.9/soupsieve/util.py 2019-03-26 02:40:10.000000000 +0100
@@ -71,6 +71,18 @@
         return struct.pack('i', i).decode('utf-32')
 
 
+def uord(c):
+    """Get Unicode ordinal."""
+
+    if len(c) == 2:  # pragma: no cover
+        high, low = [ord(p) for p in c]
+        ordinal = (high - 0xD800) * 0x400 + low - 0xDC00 + 0x10000
+    else:
+        ordinal = ord(c)
+
+    return ordinal
+
+
 class SelectorSyntaxError(SyntaxError):
     """Syntax error in a CSS selector."""
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/soupsieve-1.8/soupsieve.egg-info/PKG-INFO 
new/soupsieve-1.9/soupsieve.egg-info/PKG-INFO
--- old/soupsieve-1.8/soupsieve.egg-info/PKG-INFO       2019-02-17 
04:23:16.000000000 +0100
+++ new/soupsieve-1.9/soupsieve.egg-info/PKG-INFO       2019-03-26 
02:41:25.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: soupsieve
-Version: 1.8
+Version: 1.9
 Summary: A CSS4 selector implementation for Beautiful Soup.
 Home-page: https://github.com/facelessuser/soupsieve
 Author: Isaac Muse
@@ -112,6 +112,7 @@
 Classifier: Programming Language :: Python :: 3.5
 Classifier: Programming Language :: Python :: 3.6
 Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
 Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
 Classifier: Topic :: Software Development :: Libraries :: Python Modules
 Description-Content-Type: text/markdown
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/soupsieve-1.8/soupsieve.egg-info/SOURCES.txt 
new/soupsieve-1.9/soupsieve.egg-info/SOURCES.txt
--- old/soupsieve-1.8/soupsieve.egg-info/SOURCES.txt    2019-02-17 
04:23:16.000000000 +0100
+++ new/soupsieve-1.9/soupsieve.egg-info/SOURCES.txt    2019-03-26 
02:41:25.000000000 +0100
@@ -8,6 +8,7 @@
 tox.ini
 docs/src/dictionary/en-custom.txt
 docs/src/markdown/api.md
+docs/src/markdown/differences.md
 docs/src/markdown/index.md
 docs/src/markdown/selectors.md
 docs/src/markdown/about/changelog.md
@@ -15,7 +16,7 @@
 docs/src/markdown/about/development.md
 docs/src/markdown/about/license.md
 docs/theme/extra-0b9b22dd13.js
-docs/theme/extra-83f68d2c59.css
+docs/theme/extra-30d8e6755c.css
 requirements/docs.txt
 requirements/flake8.txt
 requirements/project.txt
@@ -41,7 +42,6 @@
 tests/test_extra/test_attribute.py
 tests/test_extra/test_contains.py
 tests/test_extra/test_custom.py
-tests/test_extra/test_defined.py
 tests/test_level1/__init__.py
 tests/test_level1/test_active.py
 tests/test_level1/test_at_rule.py
@@ -90,6 +90,7 @@
 tests/test_level4/test_attribute.py
 tests/test_level4/test_current.py
 tests/test_level4/test_default.py
+tests/test_level4/test_defined.py
 tests/test_level4/test_dir.py
 tests/test_level4/test_focus_visible.py
 tests/test_level4/test_focus_within.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/soupsieve-1.8/soupsieve.egg-info/top_level.txt 
new/soupsieve-1.9/soupsieve.egg-info/top_level.txt
--- old/soupsieve-1.8/soupsieve.egg-info/top_level.txt  2019-02-17 
04:23:16.000000000 +0100
+++ new/soupsieve-1.9/soupsieve.egg-info/top_level.txt  2019-03-26 
02:41:25.000000000 +0100
@@ -1,2 +1 @@
 soupsieve
-tests
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/soupsieve-1.8/tests/test_api.py 
new/soupsieve-1.9/tests/test_api.py
--- old/soupsieve-1.8/tests/test_api.py 2019-02-17 04:22:03.000000000 +0100
+++ new/soupsieve-1.9/tests/test_api.py 2019-03-26 02:40:10.000000000 +0100
@@ -450,6 +450,39 @@
         self.assertTrue(sv.closest('div #div-05', el) is None)
         self.assertTrue(sv.closest('a', el) is None)
 
+    def test_escape_hyphen(self):
+        """Test escape hyphen cases."""
+
+        self.assertEqual(r'\-', sv.escape('-'))
+        self.assertEqual(r'--', sv.escape('--'))
+
+    def test_escape_numbers(self):
+        """Test escape hyphen cases."""
+
+        self.assertEqual(r'\33 ', sv.escape('3'))
+        self.assertEqual(r'-\33 ', sv.escape('-3'))
+        self.assertEqual(r'--3', sv.escape('--3'))
+
+    def test_escape_null(self):
+        """Test escape null character."""
+
+        self.assertEqual('\ufffdtest', sv.escape('\x00test'))
+
+    def test_escape_ctrl(self):
+        """Test escape control character."""
+
+        self.assertEqual(r'\1 test', sv.escape('\x01test'))
+
+    def test_escape_special(self):
+        """Test escape special character."""
+
+        self.assertEqual(r'\{\}\[\]\ \(\)', sv.escape('{}[] ()'))
+
+    def test_escape_wide_unicode(self):
+        """Test handling of wide Unicode."""
+
+        self.assertEqual('Emoji\\ \U0001F60D', sv.escape('Emoji \U0001F60D'))
+
     def test_copy_pickle(self):
         """Test copy and pickle."""
 
@@ -457,9 +490,9 @@
         # We force a pattern that contains all custom types:
         # `Selector`, `NullSelector`, `SelectorTag`, `SelectorAttribute`,
         # `SelectorNth`, `SelectorLang`, `SelectorList`, `Namespaces`,
-        # and `CustomSelectors`.
+        # `SelectorContains`, and `CustomSelectors`.
         p1 = sv.compile(
-            'p.class#id[id]:nth-child(2):lang(en):focus',
+            'p.class#id[id]:nth-child(2):lang(en):focus:contains("text", 
"other text")',
             {'html': 'http://www.w3.org/TR/html4/'},
             custom={':--header': 'h1, h2, h3, h4, h5, h6'}
         )
@@ -469,7 +502,7 @@
 
         # Test that we pull the same one from cache
         p2 = sv.compile(
-            'p.class#id[id]:nth-child(2):lang(en):focus',
+            'p.class#id[id]:nth-child(2):lang(en):focus:contains("text", 
"other text")',
             {'html': 'http://www.w3.org/TR/html4/'},
             custom={':--header': 'h1, h2, h3, h4, h5, h6'}
         )
@@ -477,7 +510,7 @@
 
         # Test that we compile a new one when providing a different flags
         p3 = sv.compile(
-            'p.class#id[id]:nth-child(2):lang(en):focus',
+            'p.class#id[id]:nth-child(2):lang(en):focus:contains("text", 
"other text")',
             {'html': 'http://www.w3.org/TR/html4/'},
             custom={':--header': 'h1, h2, h3, h4, h5, h6'},
             flags=0x10
@@ -675,6 +708,68 @@
             self.assertTrue(issubclass(w[-1].category, sv_util.QuirksWarning))
 
 
+class TestDeprecated(util.TestCase):
+    """Test deprecated."""
+
+    def test_comment(self):
+        """Test comment."""
+
+        html = '<div><!-- comments -->text</div>'
+        soup = self.soup(html, 'html.parser')
+
+        with warnings.catch_warnings(record=True) as w:
+            # Cause all warnings to always be triggered.
+            warnings.simplefilter("always")
+
+            sv.comments(soup)
+            self.assertTrue(len(w) == 1)
+            self.assertTrue(issubclass(w[-1].category, DeprecationWarning))
+
+    def test_comment_compilied(self):
+        """Test compiled comment."""
+
+        html = '<div><!-- comments -->text</div>'
+        soup = self.soup(html, 'html.parser')
+
+        with warnings.catch_warnings(record=True) as w:
+            # Cause all warnings to always be triggered.
+            warnings.simplefilter("always")
+
+            pattern = sv.compile('div')
+            pattern.comments(soup)
+            self.assertTrue(len(w) == 1)
+            self.assertTrue(issubclass(w[-1].category, DeprecationWarning))
+
+    def test_icomment(self):
+        """Test `icomment`."""
+
+        html = '<div><!-- comments -->text</div>'
+        soup = self.soup(html, 'html.parser')
+
+        with warnings.catch_warnings(record=True) as w:
+            # Cause all warnings to always be triggered.
+            warnings.simplefilter("always")
+
+            sv.icomments(soup)
+            self.assertTrue(len(w) == 1)
+            self.assertTrue(issubclass(w[-1].category, DeprecationWarning))
+
+    def test_icomment_compilied(self):
+        """Test compiled `icomment`."""
+
+        html = '<div><!-- comments -->text</div>'
+        soup = self.soup(html, 'html.parser')
+
+        with warnings.catch_warnings(record=True) as w:
+            # Cause all warnings to always be triggered.
+            warnings.simplefilter("always")
+
+            pattern = sv.compile('div')
+            pattern.icomments(soup)
+            self.assertTrue(len(w) == 1)
+            self.assertTrue(issubclass(w[-1].category, DeprecationWarning))
+
+
 class TestSyntaxErrorReporting(util.TestCase):
     """Test reporting of syntax errors."""
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/soupsieve-1.8/tests/test_extra/test_contains.py 
new/soupsieve-1.9/tests/test_extra/test_contains.py
--- old/soupsieve-1.8/tests/test_extra/test_contains.py 2019-02-17 
04:22:03.000000000 +0100
+++ new/soupsieve-1.9/tests/test_extra/test_contains.py 2019-03-26 
02:40:10.000000000 +0100
@@ -66,6 +66,46 @@
             flags=util.HTML
         )
 
+    def test_contains_list(self):
+        """Test contains list."""
+
+        self.assert_selector(
+            self.MARKUP,
+            'body span:contains("does not exist", "that")',
+            ['2'],
+            flags=util.HTML
+        )
+
+    def test_contains_multiple(self):
+        """Test contains multiple."""
+
+        self.assert_selector(
+            self.MARKUP,
+            'body span:contains("th"):contains("at")',
+            ['2'],
+            flags=util.HTML
+        )
+
+    def test_contains_multiple_not_match(self):
+        """Test contains multiple with "not" and with a match."""
+
+        self.assert_selector(
+            self.MARKUP,
+            'body span:not(:contains("does not exist")):contains("that")',
+            ['2'],
+            flags=util.HTML
+        )
+
+    def test_contains_multiple_not_no_match(self):
+        """Test contains multiple with "not" and no match."""
+
+        self.assert_selector(
+            self.MARKUP,
+            'body span:not(:contains("that")):contains("that")',
+            [],
+            flags=util.HTML
+        )
+
     def test_contains_with_descendants(self):
         """Test that contains returns descendants as well as the top level 
that contain."""
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/soupsieve-1.8/tests/test_extra/test_defined.py 
new/soupsieve-1.9/tests/test_extra/test_defined.py
--- old/soupsieve-1.8/tests/test_extra/test_defined.py  2019-02-17 
04:22:03.000000000 +0100
+++ new/soupsieve-1.9/tests/test_extra/test_defined.py  1970-01-01 
01:00:00.000000000 +0100
@@ -1,100 +0,0 @@
-"""Test defined selectors."""
-from __future__ import unicode_literals
-from .. import util
-
-
-class TestDefined(util.TestCase):
-    """Test defined selectors."""
-
-    def test_defined_html(self):
-        """Test defined HTML."""
-
-        markup = """
-        <!DOCTYPE html>
-        <html>
-        <head>
-        </head>
-        <body>
-        <div id="0"></div>
-        <div-custom id="1"></div-custom>
-        <prefix:div id="2"></prefix:div>
-        <prefix:div-custom id="3"></prefix:div-custom>
-        </body>
-        </html>
-        """
-
-        self.assert_selector(
-            markup,
-            'body :defined',
-            ['0', '2', '3'],
-            flags=util.HTML
-        )
-
-    def test_defined_xhtml(self):
-        """Test defined XHTML."""
-
-        markup = """
-        <?xml version="1.0" encoding="UTF-8"?>
-        <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
-            "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd";>
-        <html lang="en" xmlns="http://www.w3.org/1999/xhtml";>
-        <head>
-        </head>
-        <body>
-        <div id="0"></div>
-        <div-custom id="1"></div-custom>
-        <prefix:div id="2"></prefix:div>
-        <!--
-        lxml or BeautifulSoup seems to strip away the prefix.
-        This is most likely because prefix with no namespace is not really 
valid.
-        XML does allow colons in names, but encourages them to be used for 
namespaces.
-        Do we really care that the prefix is wiped out in XHTML if there is no 
namespace?
-        If we do, we should look into this in the future.
-        -->
-        <prefix:div-custom id="3"></prefix:div-custom>
-        </body>
-        </html>
-        """
-
-        self.assert_selector(
-            markup,
-            'body :defined',
-            ['0', '2'],  # We should get 3, but we don't for reasons stated 
above.
-            flags=util.XHTML
-        )
-
-    def test_defined_xml(self):
-        """Test defined HTML."""
-
-        markup = """
-        <?xml version="1.0" encoding="UTF-8"?>
-        <html>
-        <head>
-        </head>
-        <body>
-        <div id="0"></div>
-        <div-custom id="1"></div-custom>
-        <prefix:div id="2"></prefix:div>
-        <prefix:div-custom id="3"></prefix:div-custom>
-        </body>
-        </html>
-        """
-
-        # Defined is a browser thing.
-        # XML doesn't care about defined and this will match nothing in XML.
-        self.assert_selector(
-            markup,
-            'body :defined',
-            [],
-            flags=util.XML
-        )
-
-
-class TestDefinedQuirks(TestDefined):
-    """Test defined selectors with quirks."""
-
-    def setUp(self):
-        """Setup."""
-
-        self.purge()
-        self.quirks = True
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/soupsieve-1.8/tests/test_level1/test_class.py 
new/soupsieve-1.9/tests/test_level1/test_class.py
--- old/soupsieve-1.8/tests/test_level1/test_class.py   2019-02-17 
04:22:03.000000000 +0100
+++ new/soupsieve-1.9/tests/test_level1/test_class.py   2019-03-26 
02:40:10.000000000 +0100
@@ -15,6 +15,17 @@
     </div>
     """
 
+    # Browsers normally replace NULL with `\uFFFD`, but some of the parsers
+    # we test just strip out NULL, so we will simulate and just insert 
`\uFFFD` directly
+    # to ensure consistent behavior in our tests across parsers.
+    MARKUP_NULL = """
+    <div>
+    <p>Some text <span id="1" class="foo\ufffd"> in a paragraph</span>.
+    <a id="2" class="\ufffdbar" href="http://google.com";>Link</a>
+    </p>
+    </div>
+    """
+
     def test_class(self):
         """Test class."""
 
@@ -35,6 +46,26 @@
             flags=util.HTML
         )
 
+    def test_type_and_class_escaped_null(self):
+        """Test type and class with an escaped null character."""
+
+        self.assert_selector(
+            self.MARKUP_NULL,
+            r"a.\0 bar",
+            ["2"],
+            flags=util.HTML
+        )
+
+    def test_type_and_class_escaped_eof(self):
+        """Test type and class with an escaped EOF."""
+
+        self.assert_selector(
+            self.MARKUP_NULL,
+            "span.foo\\",
+            ["1"],
+            flags=util.HTML
+        )
+
     def test_malformed_class(self):
         """Test malformed class."""
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/soupsieve-1.8/tests/test_level2/test_attribute.py 
new/soupsieve-1.9/tests/test_level2/test_attribute.py
--- old/soupsieve-1.8/tests/test_level2/test_attribute.py       2019-02-17 
04:22:03.000000000 +0100
+++ new/soupsieve-1.9/tests/test_level2/test_attribute.py       2019-03-26 
02:40:10.000000000 +0100
@@ -33,6 +33,22 @@
     </div>
     """
 
+    # Browsers normally replace NULL with `\uFFFD`, but some of the parsers
+    # we test just strip out NULL, so we will simulate and just insert 
`\uFFFD` directly
+    # to ensure consistent behavior in our tests across parsers.
+    MARKUP_NULL = """
+    <div id="div">
+    <p id="0">Some text <span id="1"> in a paragraph</span>.</p>
+    <a id="2" href="http://google.com";>Link</a>
+    <span id="3">Direct child</span>
+    <pre id="\ufffdpre">
+    <span id="4">Child 1</span>
+    <span id="5">Child 2</span>
+    <span id="6">Child 3</span>
+    </pre>
+    </div>
+    """
+
     def test_attribute(self):
         """Test attribute."""
 
@@ -150,6 +166,26 @@
             flags=util.HTML
         )
 
+    def test_attribute_equal_literal_null(self):
+        """Test attribute with value that equals specified value with a 
literal null character."""
+
+        self.assert_selector(
+            self.MARKUP_NULL,
+            '[id="\x00pre"]',
+            ["\ufffdpre"],
+            flags=util.HTML
+        )
+
+    def test_attribute_equal_escaped_null(self):
+        """Test attribute with value that equals specified value with an 
escaped null character."""
+
+        self.assert_selector(
+            self.MARKUP_NULL,
+            r'[id="\0 pre"]',
+            ["\ufffdpre"],
+            flags=util.HTML
+        )
+
     def test_invalid_tag(self):
         """
         Test invalid tag.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/soupsieve-1.8/tests/test_level4/test_defined.py 
new/soupsieve-1.9/tests/test_level4/test_defined.py
--- old/soupsieve-1.8/tests/test_level4/test_defined.py 1970-01-01 
01:00:00.000000000 +0100
+++ new/soupsieve-1.9/tests/test_level4/test_defined.py 2019-03-26 
02:40:10.000000000 +0100
@@ -0,0 +1,100 @@
+"""Test defined selectors."""
+from __future__ import unicode_literals
+from .. import util
+
+
+class TestDefined(util.TestCase):
+    """Test defined selectors."""
+
+    def test_defined_html(self):
+        """Test defined HTML."""
+
+        markup = """
+        <!DOCTYPE html>
+        <html>
+        <head>
+        </head>
+        <body>
+        <div id="0"></div>
+        <div-custom id="1"></div-custom>
+        <prefix:div id="2"></prefix:div>
+        <prefix:div-custom id="3"></prefix:div-custom>
+        </body>
+        </html>
+        """
+
+        self.assert_selector(
+            markup,
+            'body :defined',
+            ['0', '2', '3'],
+            flags=util.HTML
+        )
+
+    def test_defined_xhtml(self):
+        """Test defined XHTML."""
+
+        markup = """
+        <?xml version="1.0" encoding="UTF-8"?>
+        <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+            "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd";>
+        <html lang="en" xmlns="http://www.w3.org/1999/xhtml";>
+        <head>
+        </head>
+        <body>
+        <div id="0"></div>
+        <div-custom id="1"></div-custom>
+        <prefix:div id="2"></prefix:div>
+        <!--
+        lxml or BeautifulSoup seems to strip away the prefix.
+        This is most likely because prefix with no namespace is not really 
valid.
+        XML does allow colons in names, but encourages them to be used for 
namespaces.
+        Do we really care that the prefix is wiped out in XHTML if there is no 
namespace?
+        If we do, we should look into this in the future.
+        -->
+        <prefix:div-custom id="3"></prefix:div-custom>
+        </body>
+        </html>
+        """
+
+        self.assert_selector(
+            markup,
+            'body :defined',
+            ['0', '2'],  # We should get 3, but we don't for reasons stated 
above.
+            flags=util.XHTML
+        )
+
+    def test_defined_xml(self):
+        """Test defined HTML."""
+
+        markup = """
+        <?xml version="1.0" encoding="UTF-8"?>
+        <html>
+        <head>
+        </head>
+        <body>
+        <div id="0"></div>
+        <div-custom id="1"></div-custom>
+        <prefix:div id="2"></prefix:div>
+        <prefix:div-custom id="3"></prefix:div-custom>
+        </body>
+        </html>
+        """
+
+        # Defined is a browser thing.
+        # XML doesn't care about defined and this will match nothing in XML.
+        self.assert_selector(
+            markup,
+            'body :defined',
+            [],
+            flags=util.XML
+        )
+
+
+class TestDefinedQuirks(TestDefined):
+    """Test defined selectors with quirks."""
+
+    def setUp(self):
+        """Setup."""
+
+        self.purge()
+        self.quirks = True
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/soupsieve-1.8/tox.ini new/soupsieve-1.9/tox.ini
--- old/soupsieve-1.8/tox.ini   2019-02-17 04:22:03.000000000 +0100
+++ new/soupsieve-1.9/tox.ini   2019-03-26 02:40:10.000000000 +0100
@@ -1,6 +1,6 @@
 [tox]
 envlist =
-    {py27,py34,py35,py36,py37,py38}, lint, documents, nolxml, nohtml5lib
+    py27,py34,py35,py36,py37,py38, lint, nolxml, nohtml5lib
 
 [testenv]
 passenv = *


Reply via email to