GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
GeezerVison is showing github.com — gh-149468: Add option to validate ElementTree during serialization by serhiy-storchaka · Pull Request #149469 · python/cpython Conversation
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
This PR also fixes some bugs in serialization to HTML. I am going to extract them into separate issue before merging this PR. But for now, it is more convenient to review them together. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| @@ -0,0 +1,3 @@ | |||
| Add the *validate* option to :mod:`xml.etree.ElementTree` serialization | |||
| functions, which allows to validate the element or element tree before | |||
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Suggestion on wording: "...which validates the element or element tree names and values only contain allowed/escaped characters prior to serialization"
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
No, this is not good. Characters should not be escaped prior to serialization. Element tree has no names, I wrote "the element or element tree" because some methods/functions serialize an element, and others work with the element tree. We check not only for invalid characters, but for invalid sequences of characters, and some characters/sequences can only be invalid at the start or the end of the name or content.
I updated the entry according to description proposed by @ezio-melotti.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
* The content of comments, processing instructions and elements "xmp", "iframe", "noembed", "noframes", and "plaintext" is no longer escaped. * The "plaintext" element no longer have the closing tag. * Add support of empty attributes (with value None).
73e1b24 to
a134c0b
Compare
|
Rebased it onto #149490. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
ca19970 to
c81fe70
Compare
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
| if text: | ||
| if validate: | ||
| if '\0' in text: | ||
| raise ValueError('invalid characters') |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
| raise ValueError('invalid characters') | |
| raise ValueError('invalid character ("\\0")') |
Similarly the other error messages could specify what is invalid.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
We perhaps should also forbid the surrogate codes. They are not explicitly mentioned in the tokenizer definition, unlike to the null character, but they are invalid in all standard encodings, and using numeric references for them is also invalid (https://html.spec.whatwg.org/multipage/parsing.html#numeric-character-reference-end-state).
As for what invalid, it is not always easily accessible. xml.is_valid_name() and xml.is_valid_text() simply return a boolean.
We could include the name of the parent element, but this may complicate the code to handle special cases of undefined or None.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
Co-authored-by: Ezio Melotti <[email protected]>
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Thank you for your review @ezio-melotti. See also the issue. "XML validation" has its own specific meaning, so there is a question about using the name "validate" for our much more lenient checks.
We do not check that the output is well-formed. There are much more strict rules for what the name of the element or attribute should be. I use the rules of the HTML parser, which accepts much more.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
| if text: | ||
| if validate: | ||
| if '\0' in text: | ||
| raise ValueError('invalid characters') |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
We perhaps should also forbid the surrogate codes. They are not explicitly mentioned in the tokenizer definition, unlike to the null character, but they are invalid in all standard encodings, and using numeric references for them is also invalid (https://html.spec.whatwg.org/multipage/parsing.html#numeric-character-reference-end-state).
As for what invalid, it is not always easily accessible. xml.is_valid_name() and xml.is_valid_text() simply return a boolean.
We could include the name of the parent element, but this may complicate the code to handle special cases of undefined or None.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
Documentation build overview
435 files changed ·
|
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| .. function:: tostring(element, encoding="us-ascii", method="xml", *, \ | ||
| xml_declaration=None, default_namespace=None, \ | ||
| short_empty_elements=True) | ||
| validate=False, short_empty_elements=True) |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Usually new parameters are added to the end, so the order gives an idea when a parameter was added. Why not putting it at the end? (It only matters for the documentation.)
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
If all parameters was keyword-only, I would place method and validate before other options, because they are more general and important. But keyword-or-positional parameters should keep their position, so I can only change order of validate and short_empty_elements.
Your arguments are also valid. I'll, change the order.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| like ``<script>`` do not contain illegal sequences according | ||
| to the selected *method* (``"xml"`` or ``"html"``). | ||
| Raise :exc:`ValueError` if any check fails. | ||
| By default, or if *method* is ``"text"``, no validation is performed. |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
I don't understand method="text" case. Does it force validate to False? How is it different from the default behavior (validate=False)?
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
validate has no effect for method="text".
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| if tag is Comment: | ||
| if validate: | ||
| if '--' in text or text.endswith('-') or not is_valid_text(text): | ||
| raise ValueError('invalid comment') |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
"invalid comment" error can be raised in 3 very different cases. Would it be possible to give the reason in the error message? For example: "invalid comment: illegal character U+0000". Same remark for _serialize_html().
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
We do not know what character is invalid. We need to reimplement is_valid_text and is_valid_name to get such information.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| _serialize_html(write, e, qnames, None, validate=validate) | ||
| else: | ||
| if validate: | ||
| if not re.fullmatch('[A-Za-z][^\0\t\n\r\f />\ud800-\udfff]*+', tag): |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
What is the purpose of + in [range]*+? Why not just writing [range]*?
Similar question on [range]++ below: re.fullmatch('[^\0\t\n\r\f />=\ud800-\udfff]++', k).
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Microoptimization. It disables backtracking.
For for example, we check the name "a0123456789/". The RE engine first matches prefix "a0123456789", but this is not the entire string, it backtracks:
We perhaps can make the RE engine smarter and automatically detect such pattern, but this is a separate issue.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| try: | ||
| if validate: | ||
| if not is_valid_text(text): | ||
| raise ValueError('invalid characters') |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Would it be possible to mention the first invalid character in the error message? Otherwise, it can be tricky to debug such issue.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
To do this, we need to murder is_valid_text and pull his insides out.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Or have is_valid_text / new function validate_text raise the exception directly.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
This can be done in a following issue, after we get more examples of its use.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
# Conflicts: # Doc/library/xml.etree.elementtree.rst # Doc/whatsnew/3.16.rst # Lib/xml/etree/ElementTree.py
The checked name is a prefix, not a namespace name, which is the URI.
The tests are parametrized by strings, and QName instances are created in the body, so nothing needs the module at class definition time. In the HTML tests the sets of the invalid string values and the invalid QName values are different, so such tests are split.
# Conflicts: # Doc/whatsnew/3.16.rst # Lib/xml/etree/ElementTree.py
There was an error while loading. Please reload this page.
Successfully merging this pull request may close these issues.
There was an error while loading. Please reload this page.