SF.net SVN: docutils:[9734 ] trunk/docutils

milde--- via Docutils-checkins <[email protected]>
Newsgroups gmane.text.docutils.cvs
Message-ID <[email protected]>
Revision: 9734
          http://sourceforge.net/p/docutils/code/9734
Author:   milde
Date:     2024-06-06 14:01:35 +0000 (Thu, 06 Jun 2024)
Log Message:
-----------
xml parser: new method `parse_element()`.

The method `Parser.parse_element()` does not require/populate
a <document> root node.
It parses an XML representation of a "document tree" element
(with possible sub-elements) and returns a `docutils.nodes.Element`
instance (with possible child nodes).

See the unittest script for usage examples.

Modified Paths:
--------------
    trunk/docutils/docutils/parsers/docutils_xml.py
    trunk/docutils/test/test_nodes.py

Added Paths:
-----------
    trunk/docutils/test/test_parsers/test_docutils_xml/test_parse_element.py

Modified: trunk/docutils/docutils/parsers/docutils_xml.py
===================================================================
--- trunk/docutils/docutils/parsers/docutils_xml.py	2024-06-06 14:01:22 UTC (rev 9733)
+++ trunk/docutils/docutils/parsers/docutils_xml.py	2024-06-06 14:01:35 UTC (rev 9734)
@@ -58,6 +58,26 @@
         self.finish_parse()
 
 
+def parse_element(inputstring):
+    """
+    Parse `inputstring` as "Docutils XML", return `nodes.Element` instance.
+
+    :inputstring: XML source.
+
+    Caution:
+      The function does not detect invalid XML.
+
+      To check the validity of the returned node,
+      you may use its `validate()` method::
+
+        node = parse_element('<tip><hint>text</hint></tip>')
+        node.validate()
+
+    Provisional.
+    """
+    return element2node(ET.fromstring(inputstring))
+
+
 def element2node(element):
     """
     Convert an `etree` element and its children to Docutils doctree nodes.

Modified: trunk/docutils/test/test_nodes.py
===================================================================
--- trunk/docutils/test/test_nodes.py	2024-06-06 14:01:22 UTC (rev 9733)
+++ trunk/docutils/test/test_nodes.py	2024-06-06 14:01:35 UTC (rev 9734)
@@ -1121,7 +1121,13 @@
 
 
 class AttributeTypeTests(unittest.TestCase):
+    """Test validator functions for the supported `attribute data types`__
 
+    See also test_parsers/test_docutils_xml/test_parse_element.py.
+
+    __ https://docutils.sourceforge.io/docs/ref/doctree.html#attribute-types
+    """
+
     def test_validate_enumerated_type(self):
         # function factory for "choice validators"
         food = nodes.validate_enumerated_type('ham', 'spam')

Added: trunk/docutils/test/test_parsers/test_docutils_xml/test_parse_element.py
===================================================================
--- trunk/docutils/test/test_parsers/test_docutils_xml/test_parse_element.py	                        (rev 0)
+++ trunk/docutils/test/test_parsers/test_docutils_xml/test_parse_element.py	2024-06-06 14:01:35 UTC (rev 9734)
@@ -0,0 +1,207 @@
+#!/usr/bin/env python3
+# :Copyright: © 2024 Günter Milde.
+# :License: Released under the terms of the `2-Clause BSD license`_, in short:
+#
+#    Copying and distribution of this file, with or without modification,
+#    are permitted in any medium without royalty provided the copyright
+#    notice and this notice are preserved.
+#    This file is offered as-is, without any warranty.
+#
+# .. _2-Clause BSD license: https://opensource.org/licenses/BSD-2-Clause
+
+"""Tests for parsers/docutils_xml.py."""
+
+from pathlib import Path
+import sys
+import unittest
+import xml.etree.ElementTree as ET
+
+if __name__ == '__main__':
+    # prepend the "docutils root" to the Python library path
+    # so we import the local `docutils` package.
+    sys.path.insert(0, str(Path(__file__).resolve().parents[3]))
+
+from docutils.parsers import docutils_xml
+
+
+class ParseElementTestCase(unittest.TestCase):
+
+    def test_element_with_child_with_text(self):
+        xml = '<tip><paragraph>some text</paragraph></tip>'
+        node = docutils_xml.parse_element(xml)
+        self.assertEqual(xml, str(node))
+
+    def test_tailing_text(self):
+        xml = '<strong>text</strong>trailing text'
+        with self.assertRaisesRegex(ET.ParseError,
+                                    'junk after document element'):
+            docutils_xml.parse_element(xml)
+
+
+class XmlAttributesTestCase(unittest.TestCase):
+    """
+    Test correct parsing of the `supported element attributes`_.
+
+    See also `AttributeTypeTests` in ../../test_nodes.py.
+
+    __ https://docutils.sourceforge.io/
+       docs/ref/doctree.html#attribute-reference
+    """
+    common_attributes = {'classes': [],
+                         'dupnames': [],
+                         'ids': [],
+                         'names': []}
+
+    def test_alt(self):  # CDATA (str)
+        xml = ('<image alt="a barking dog" align="left" height="3ex"'
+               '       loading="embed" scale="3" uri="dog.jpg" width="4cm"/>')
+        expected = {'alt': 'a barking dog',
+                    'align': 'left',
+                    'height': '3ex',
+                    'loading': 'embed',
+                    'scale': 3,
+                    'uri': 'dog.jpg',
+                    'width': '4cm'}
+        node = docutils_xml.parse_element(xml)
+        self.assertEqual(node.attributes, self.common_attributes | expected)
+
+    # 'align': CDATA (str)  → test_alt
+
+    def test_anonymous(self):  # yesorno (int)
+        xml = '<target anonymous="1" ids="target-1" refuri="example.html" />'
+        expected = {'anonymous': 1,
+                    'ids': ['target-1'],
+                    'refuri': 'example.html'}
+        node = docutils_xml.parse_element(xml)
+        self.assertEqual(node.attributes, self.common_attributes | expected)
+
+    def test_auto(self):  # CDATA (str) number sequence: '1' or '*'
+        xml = '<footnote auto="*" backrefs="footnote-reference-2" />'
+        expected = {'auto': '*',
+                    'backrefs': ['footnote-reference-2']}
+        node = docutils_xml.parse_element(xml)
+        self.assertEqual(node.attributes, self.common_attributes | expected)
+
+    # 'backrefs':  idrefs.type (list[str])  → test_auto
+
+    def test_bullet(self):  # CDATA (str)
+        xml = '<bullet_list bullet="*" classes="first x-2nd" />'
+        expected = {'bullet': '*',
+                    'classes': ['first', 'x-2nd']}
+        node = docutils_xml.parse_element(xml)
+        self.assertEqual(node.attributes, self.common_attributes | expected)
+
+    # 'classes':  classnames.type (list[str])  → test_bullet
+
+    def test_colwidth(self):  # CDATA (int) sic!
+        xml = '<colspec colwidth="33" stub="1" />'
+        expected = {'colwidth': 33, 'stub': 1}
+        node = docutils_xml.parse_element(xml)
+        self.assertEqual(node.attributes, self.common_attributes | expected)
+
+    def test_delimiter(self):  # CDATA (str)
+        xml = '<option_argument delimiter="=">FILE</option_argument>'
+        expected = {'delimiter': '='}
+        node = docutils_xml.parse_element(xml)
+        self.assertEqual(node.attributes, self.common_attributes | expected)
+
+    def test_dupnames(self):  # refnames.type (list[str]).
+        xml = r'<section dupnames="title\ 1" ids="title-1" />'
+        expected = {'dupnames': ['title 1'],
+                    'ids': ['title-1']}
+        node = docutils_xml.parse_element(xml)
+        self.assertEqual(node.attributes, self.common_attributes | expected)
+
+    def test_enumtype(self):  # EnumeratedType (str)
+        xml = ('<enumerated_list enumtype="upperroman"'
+               '                 prefix="(" start="2" suffix=")" />')
+        expected = {'enumtype': 'upperroman',
+                    'prefix': '(',
+                    'start': 2,
+                    'suffix': ')'}
+        node = docutils_xml.parse_element(xml)
+        self.assertEqual(node.attributes, self.common_attributes | expected)
+
+    def test_format(self):  # NMTOKENS (str) (space-delimited list of keywords)
+        xml = '<raw format="html latex" xml:space="preserve" />'
+        expected = {'format': 'html latex',
+                    'xml:space': 'preserve'}
+        node = docutils_xml.parse_element(xml)
+        self.assertEqual(node.attributes, self.common_attributes | expected)
+
+    # 'height': measure (str)         → test_alt
+    # 'ids':    ids.type (list[str])  → test_names
+
+    def test_level(self):  # level (int)
+        xml = ('<system_message level="3" line="21" source="string"'
+               '                type="ERROR" />')
+        expected = {'backrefs': [],
+                    'level': 3,
+                    'line': 21,
+                    'source': 'string',
+                    'type': 'ERROR'}
+        node = docutils_xml.parse_element(xml)
+        self.assertEqual(node.attributes, self.common_attributes | expected)
+
+    def test_ltrim(self):  # yesorno (int)
+        xml = '<substitution_definition ltrim="1" names="nbsp" />'
+        expected = {'ltrim': 1, 'names': ['nbsp']}
+        node = docutils_xml.parse_element(xml)
+        self.assertEqual(node.attributes, self.common_attributes | expected)
+
+    # 'loading': EnumeratedType (str)  → test_alt
+
+    def test_morecols(self):  # number (int)
+        xml = '<entry morecols="1" />'
+        expected = {'morecols': 1}
+        node = docutils_xml.parse_element(xml)
+        self.assertEqual(node.attributes, self.common_attributes | expected)
+
+    def test_names(self):  # refnames.type (list[str])
+        #                    internal whitespace in XML escaped
+        xml = r'<section ids="title-2 title-1" names="title\ 2\\ title\ 1" />'
+        expected = {'ids': ['title-2', 'title-1'],
+                    'names': ['title 2\\', 'title 1']}
+        node = docutils_xml.parse_element(xml)
+        self.assertEqual(node.attributes, self.common_attributes | expected)
+
+    # 'prefix': CDATA (str)  → test_enumtype
+
+    def test_refid(self):  # idref.type (str)
+        xml = '<target refid="title-1-1"></target>'
+        expected = {'refid': 'title-1-1'}
+        node = docutils_xml.parse_element(xml)
+        self.assertEqual(node.attributes, self.common_attributes | expected)
+
+    def test_refname(self):  # refname.type (str)
+        xml = '<target refname="title 2"></target>'
+        expected = {'refname': 'title 2'}
+        node = docutils_xml.parse_element(xml)
+        self.assertEqual(node.attributes, self.common_attributes | expected)
+
+    # 'refuri: CDATA (str)  → test_anonymous
+
+    def test_rtrim(self):  # yesorno (int)
+        xml = '<substitution_definition ltrim="1" names="nbsp" />'
+        expected = {'ltrim': 1,
+                    'names': ['nbsp']}
+        node = docutils_xml.parse_element(xml)
+        self.assertEqual(node.attributes, self.common_attributes | expected)
+
+    # 'scale': number (int) → test_alt
+    # 'source': CDATA (str) → test_title
+    # 'start': number (int) → test_enumtype
+    # 'stub': yesorno (int) → test_colwidth
+    # 'suffix': CDATA (str) → test_enumtype
+
+    def test_title(self):  # CDATA (str)
+        ...
+        # TODO: <document> does not work with parse_element()
+
+    # 'uri': CDATA (str)                → test_alt
+    # 'width' measure (str)             → test_alt
+    # 'xml:space' EnumeratedType (str)  → test_format
+
+
+if __name__ == '__main__':
+    unittest.main()


Property changes on: trunk/docutils/test/test_parsers/test_docutils_xml/test_parse_element.py
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:keywords
## -0,0 +1 ##
+Author Date Id Revision
\ No newline at end of property
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.



_______________________________________________
Docutils-checkins mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/docutils-checkins
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.