-
-
Couldn't load subscription status.
- Fork 2.2k
Expose stringify_id at top level and add optional parameter escape_css
#1637
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -192,10 +192,72 @@ def split_callback_id(callback_id): | |
| return {"id": id_, "property": prop} | ||
|
|
||
|
|
||
| def stringify_id(id_): | ||
| if isinstance(id_, dict): | ||
| return json.dumps(id_, sort_keys=True, separators=(",", ":")) | ||
| return id_ | ||
| def css_escape(stringified_id): | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would maybe rename to |
||
| """Escapes an ID string such that it can be used in CSS selectors. | ||
|
|
||
| The comments below in the different if-branches are taken from | ||
| https://drafts.csswg.org/cssom/#serialize-an-identifier. | ||
| """ | ||
|
|
||
| def code_point_range(from_unicode, to_unicode): | ||
| return list(range(ord(from_unicode), ord(to_unicode) + 1)) | ||
|
|
||
| CONTROL_CHARACTER_RANGE = code_point_range("\u0001", "\u001f") + [ord("\u007f")] | ||
| ALLOWED_FINITE_RANGES = ( | ||
| code_point_range("\u0030", "\u0039") | ||
| + code_point_range("\u0041", "\u005a") | ||
| + code_point_range("\u0061", "\u007a") | ||
| + [ord("\u002d"), ord("\u005f")] | ||
| ) | ||
|
|
||
| escaped_chars = [] | ||
|
|
||
| for i, char in enumerate(stringified_id): | ||
| if char == "\u0000": | ||
| # If the character is NULL (U+0000), then the REPLACEMENT CHARACTER (U+FFFD). | ||
| escaped_chars.append("\ufffd") | ||
| elif ord(char) in CONTROL_CHARACTER_RANGE: | ||
| # If the character is in the range U+0001 to U+001F or is U+007F, | ||
| # then the character escaped as code point. | ||
| escaped_chars.append("\\" + hex(ord(char)).replace("0x", "") + " ") | ||
| elif i == 0 and char.isdigit(): | ||
| # If the character is the first character and is in the range [0-9], | ||
| # then the character escaped as code point. | ||
| escaped_chars.append(r"\3" + char + " ") | ||
| elif i == 1 and char.isdigit() and stringified_id[0] == "-": | ||
| # If the character is the second character and is in the range [0-9] | ||
| # and the first character is a "-", then the character escaped as code point. | ||
| escaped_chars.append(r"\3" + char + " ") | ||
| elif i == 0 and char == "-" and len(stringified_id) == 1: | ||
| # If the character is the first character and is a "-", | ||
| # and there is no second character, then the escaped character. | ||
| escaped_chars.append("\\" + char) | ||
| elif ord(char) >= ord("\u0080") or ord(char) in ALLOWED_FINITE_RANGES: | ||
| # If the character is not handled by one of the above rules and is greater | ||
| # than or equal to U+0080, is "-" (U+002D) or "_" (U+005F), or is in one of | ||
| # the ranges [0-9] (U+0030 to U+0039), [A-Z] (U+0041 to U+005A), | ||
| # or \[a-z] (U+0061 to U+007A), then the character itself. | ||
| escaped_chars.append(char) | ||
| else: | ||
| # Otherwise, the escaped character. | ||
| escaped_chars.append("\\" + char) | ||
|
|
||
| return "".join(escaped_chars) | ||
|
|
||
|
|
||
| def stringify_id(id_, escape_css=False): | ||
| """Converts a dictionary ID (used in pattern-matching callbacks) to the | ||
| corresponding stringified ID used in the DOM. Use escape_css=True if the | ||
| returned string is to be used in CSS selectors. See this link for details: | ||
| https://developer.mozilla.org/en-US/docs/Web/API/CSS/escape | ||
| """ | ||
|
|
||
| stringified_id = ( | ||
| json.dumps(id_, sort_keys=True, separators=(",", ":")) | ||
| if isinstance(id_, dict) | ||
| else id_ | ||
| ) | ||
| return css_escape(stringified_id) if escape_css else stringified_id | ||
|
|
||
|
|
||
| def inputs_to_dict(inputs_list): | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,45 @@ | ||||||||||||||||||||||||||
| from __future__ import unicode_literals | ||||||||||||||||||||||||||
| import pytest | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| import dash | ||||||||||||||||||||||||||
| import dash_html_components as html | ||||||||||||||||||||||||||
| from dash._utils import css_escape | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| TEST_STRING_IDS = [ | ||||||||||||||||||||||||||
| "standard-sane-id", | ||||||||||||||||||||||||||
| "!sane~ID:at>all?" "-", | ||||||||||||||||||||||||||
| "-1a", | ||||||||||||||||||||||||||
| "0abc", | ||||||||||||||||||||||||||
| "{'a': 1, 'b': 2, 'c': [3, 4, 5]}", | ||||||||||||||||||||||||||
| '"string-in-string-with-escape-characters"', | ||||||||||||||||||||||||||
| "\u001fabc\u007f", | ||||||||||||||||||||||||||
| ] | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| TEST_DICT_IDS = [ | ||||||||||||||||||||||||||
| {"a": 1, "b": "some-string", "c": False}, | ||||||||||||||||||||||||||
| {"type": "some-type", "index": 42}, | ||||||||||||||||||||||||||
| ] | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| @pytest.mark.parametrize("id", TEST_STRING_IDS) | ||||||||||||||||||||||||||
| def test_css_escape(dash_duo, id): | ||||||||||||||||||||||||||
| assert dash_duo.driver.execute_script( | ||||||||||||||||||||||||||
| "return CSS.escape({!r})".format(id) | ||||||||||||||||||||||||||
| ) == css_escape(id) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| @pytest.mark.parametrize( | ||||||||||||||||||||||||||
| "id", TEST_STRING_IDS + TEST_DICT_IDS, | ||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||
| def test_found_by_css_selector(dash_duo, id): | ||||||||||||||||||||||||||
| # This test also indirectly checks that output from the JavaScript stringify_id | ||||||||||||||||||||||||||
| # function equals output from the corresponding Python function. | ||||||||||||||||||||||||||
| app = dash.Dash(__name__) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| app.layout = html.Div(id=id) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| dash_duo.start_server(app) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| assert dash_duo.wait_for_element_by_id(dash.stringify_id(id, escape_css=True)) | ||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is an easy possibility of extending Lines 277 to 288 in e8ac949
to check if input element_id is dict, and then do dash.stringify_id(element_id, escape_css=True) for the user in the wait_for_element_by_id function.
The user's test line here assert dash_duo.wait_for_element_by_id(dash.stringify_id(id, escape_css=True))then becomes assert dash_duo.wait_for_element_by_id(id)both in the case with string IDs, and dictionary IDs (when you use pattern-matching callbacks). Not sure if that is too magical 🔮 or a nice and easy to understand convenience. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good idea with extending the testing utilities, I think that is where most cases would be for escaping. |
||||||||||||||||||||||||||
| assert dash_duo.wait_for_element("#" + dash.stringify_id(id, escape_css=True)) | ||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could also expose
css_escape