Repository: facelessuser/soupsieve

Issue:
[attr~=value] spec issue
According to the spec

> [att~=val]
Represents an element with the att attribute whose value is a whitespace-separated list of words, one of which is exactly "val". If "val" contains whitespace, it will never represent anything (since the words are separated by spaces). Also if "val" is the empty string, it will never represent anything.

We are currently not enforcing this, but we should (the whitespace part).

Discussion:

Patch:
diff --git a/docs/src/markdown/about/changelog.md b/docs/src/markdown/about/changelog.md
index 1218fe9..dcdd656 100644
--- a/docs/src/markdown/about/changelog.md
+++ b/docs/src/markdown/about/changelog.md
@@ -1,5 +1,9 @@
 # Changelog
 
+## 1.2.2
+
+- **FIX**: Fix `[attr~=value]` handling of whitespace. According to the spec, if the value contains whitespace, or is an empty string, it should not match anything.
+
 ## 1.2.1
 
 - **FIX**: More descriptive exceptions. Exceptions will also now mention position in the pattern that is problematic.
diff --git a/soupsieve/__meta__.py b/soupsieve/__meta__.py
index 209f355..c414e70 100644
--- a/soupsieve/__meta__.py
+++ b/soupsieve/__meta__.py
@@ -186,5 +186,5 @@ def parse_version(ver, pre=False):
     return Version(major, minor, micro, release, pre, post, dev)
 
 
-__version_info__ = Version(1, 2, 1, "final")
+__version_info__ = Version(1, 2, 2, ".dev")
 __version__ = __version_info__._get_canonical()
diff --git a/soupsieve/css_parser.py b/soupsieve/css_parser.py
index 355bfec..dfe796d 100644
--- a/soupsieve/css_parser.py
+++ b/soupsieve/css_parser.py
@@ -132,6 +132,8 @@ RE_NTH = re.compile(r'(?P<s1>[-+])?(?P<a>\d+n?|n)(?:(?<=n){ws}*(?P<s2>[-+]){ws}*
 
 RE_LANG = re.compile(r'(?:(?P<value>{value})|(?P<split>{ws}*,{ws}*))'.format(ws=WS, value=VALUE), re.X)
 
+RE_WS = re.compile(WS)
+
 SPLIT = ','
 REL_HAS_CHILD = ": "
 
@@ -345,7 +347,10 @@ class CSSParser(object):
                 pattern = re.compile(r'.*?%s.*' % re.escape(value), flags)
             elif op.startswith('~'):
                 # Value contains word within space separated list
-                pattern = re.compile(r'.*?(?:(?<=^)|(?<= ))%s(?=(?:[ ]|$)).*' % re.escape(value), flags)
+                # `~=` should match nothing if it is empty or contains whitespace,
+                # so if either of these cases is present, use `[^\s\S]` which cannot be matched.
+                value = r'[^\s\S]' if not value or RE_WS.search(value) else re.escape(value)
+                pattern = re.compile(r'.*?(?:(?<=^)|(?<= ))%s(?=(?:[ ]|$)).*' % value, flags)
             elif op.startswith('|'):
                 # Value starts with word in dash separated list
                 pattern = re.compile(r'^%s(?:-.*)?$' % re.escape(value), flags)


Test patch:
diff --git a/tests/test_level2.py b/tests/test_level2.py
index c9bf1ed..033f5f0 100644
--- a/tests/test_level2.py
+++ b/tests/test_level2.py
@@ -328,6 +328,26 @@ class TestLevel2(util.TestCase):
             flags=util.HTML5
         )
 
+        # Shouldn't match anything
+        self.assert_selector(
+            markup,
+            '[class~="test1 test2"]',
+            [],
+            flags=util.HTML5
+        )
+        self.assert_selector(
+            markup,
+            '[class~=""]',
+            [],
+            flags=util.HTML5
+        )
+        self.assert_selector(
+            markup,
+            '[class~="test1\\ test2"]',
+            [],
+            flags=util.HTML5
+        )
+
         # Start of list
         self.assert_selector(
             markup,