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 - pgajdos@suse.com + +- 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> </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 © 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 & 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='Isaac.Muse@gmail.com',
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 = *