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

milde--- via Docutils-checkins <[email protected]>
Newsgroups gmane.text.docutils.cvs
Message-ID <[email protected]>
Revision: 9995
          http://sourceforge.net/p/docutils/code/9995
Author:   milde
Date:     2024-12-04 13:32:53 +0000 (Wed, 04 Dec 2024)
Log Message:
-----------
Update/fix URI reference to filesystem path conversion.

Use "pathlib" instead of "os.path".
Return a `pathlib.Path` instance instead of a `str`.

Use Path.from_uri() for "file" URIs:

* Provide a backport for Python < 3.13.
* Don't apply "root_prefix" to "file:" URIs.
* "file:" URIs with relative path are invalid and now raise an Error.

Based on patch https://github.com/AA-Turner/docutils/pull/18
by Adam Turner.

Modified Paths:
--------------
    trunk/docutils/docs/ref/doctree.rst
    trunk/docutils/docs/user/config.rst
    trunk/docutils/docutils/writers/_html_base.py
    trunk/docutils/docutils/writers/html4css1/__init__.py
    trunk/docutils/test/functional/input/data/embed_images.rst
    trunk/docutils/test/test_writers/test_html5_polyglot_parts.py

Modified: trunk/docutils/docs/ref/doctree.rst
===================================================================
--- trunk/docutils/docs/ref/doctree.rst	2024-12-04 13:32:39 UTC (rev 9994)
+++ trunk/docutils/docs/ref/doctree.rst	2024-12-04 13:32:53 UTC (rev 9995)
@@ -4490,6 +4490,9 @@
 The ``uri`` attribute is used in the `\<image>`_ and `\<figure>`_
 elements to refer to the image via a `URI Reference`_ [#]_. [rfc3986]_
 
+The `root_prefix`_ configuration setting is applied when a URI Reference
+starting with "/" is converted to a local filesystem path.
+
 .. [#] Examples are a full URI, an *absolute-path reference* (begins with
    a single slash character) or a *relative-path reference* (does not
    begin with a slash character).
@@ -5198,6 +5201,7 @@
 .. _id_prefix:        ../user/config.html#id-prefix
 .. _image_loading:    ../user/config.html#image-loading
 .. _report_level:     ../user/config.html#report-level
+.. _root_prefix:     ../user/config.html#root-prefix
 .. _stylesheet:       ../user/config.html#stylesheet
 .. _syntax_highlight: ../user/config.html#syntax-highlight
 

Modified: trunk/docutils/docs/user/config.rst
===================================================================
--- trunk/docutils/docs/user/config.rst	2024-12-04 13:32:39 UTC (rev 9994)
+++ trunk/docutils/docs/user/config.rst	2024-12-04 13:32:53 UTC (rev 9995)
@@ -588,17 +588,28 @@
 Base directory, prepended to a filesystem path__ starting with "/" when
 including files with the `"include"`_, `"raw"`_, or `"csv-table"`_
 directives.
+Also applied to the `"uri" attribute`_ of an <image> or <figure> starting
+with "/" when it is converted to a local filesystem path.
+Not applied to absolute Windows paths and ``file:`` URIs.
 
-Also applied when a writer converts an image URI__ to a local filesystem
-path in order to determine the image size or embed the image in the output.
+Example:
+  The HTML server for a documentation project serves files from the
+  "DocumentRoot" ``/var/www/html/``.
+  Image files are stored in a dedicated directory ``/var/www/html/pictures/``.
 
-:Default: "".
+  With ``root-prefix=/var/www/html``, the rST "image" directive ::
+
+    .. image:: /pictures/mylogo.png
+
+  works for LaTeX output and HTML output with embedded images as well as
+  for HTML output with images included via URI reference.
+
+:Default: "" (empty string).
 :Option:  ``--root-prefix``.
 
 New in Docutils 0.21.
 
 __ ../ref/rst/directives.html#path
-__ ../ref/rst/directives.html#uri
 
 
 sectnum_xform
@@ -609,7 +620,7 @@
 with the `"sectnum" directive`_.
 
 If disabled, section numbers might be added to the output by the
-renderer (e.g. by LaTeX or via a CSS style definition).
+renderer (e.g. by CSS style rules or by LaTeX).
 
 :Default: True.
 :Options: ``--section-numbering``, ``--no-section-numbering``.

Modified: trunk/docutils/docutils/writers/_html_base.py
===================================================================
--- trunk/docutils/docutils/writers/_html_base.py	2024-12-04 13:32:39 UTC (rev 9994)
+++ trunk/docutils/docutils/writers/_html_base.py	2024-12-04 13:32:53 UTC (rev 9995)
@@ -25,8 +25,8 @@
 import os
 import os.path
 import re
+import sys
 import urllib.parse
-import urllib.request
 import warnings
 import xml.etree.ElementTree as ET
 from pathlib import Path
@@ -460,7 +460,7 @@
             reading_problems.append('Reading external files disabled.')
         if not reading_problems:
             try:
-                imagepath = self.uri2imagepath(uri)
+                imagepath = self.uri2path(uri)
                 with PIL.Image.open(imagepath) as img:
                     imgsize = img.size
             except (ValueError, OSError, UnicodeEncodeError) as err:
@@ -626,38 +626,47 @@
             return
         child['classes'].append(class_)
 
-    def uri2imagepath(self, uri):
-        """Get POSIX filesystem path corresponding to an URI.
+    def uri2path(self, uri: str) -> Path:
+        """Return filesystem path corresponding to a `URI reference`__.
 
-        The image directive expects an image URI__. Some writers require the
-        corresponding image path to read the image size from the file or to
-        embed the image in the output.
+        The `root_prefix`__ setting` is applied to URI references starting
+        with "/" (but not to absolute Windows paths or "file" URIs).
 
-        URIs with absolute "path" part consider the ``root_prefix`` setting.
+        If the `output_path`__ setting is not empty, relative paths are
+        adjusted.  (To work in the output document, URI references with
+        relative path relate to the output directory.  For access by the
+        writer, paths must be relative to the working directory.)
 
-        In order to work in the output document, URI references with relative
-        path relate to the output directory.  For access by the writer, the
-        corresponding image path must be relative to the current working
-        directory.
+        Use case:
+          The <image> element refers to the image via a "URI reference".
+          The corresponding filesystem path is required to read the
+          image size from the file or to embed the image in the output.
 
+          A filesystem path is also expected by the "LaTeX" output format
+          (with relative paths unchanged, relating to the output directory).
+
         Provisional: the function's location, interface and behaviour
         may change without advance warning.
 
-        __ https://www.rfc-editor.org/rfc/rfc3986.html
+        __ https://www.rfc-editor.org/rfc/rfc3986.html#section-4.1
+        __ https://docutils.sourceforge.io/docs/user/config.html#root-prefix
+        __ https://docutils.sourceforge.io/docs/user/config.html#output-path
         """
-        destination = self.settings.output_path or ''
-        uri_parts = urllib.parse.urlparse(uri)
-        if uri_parts.scheme not in ('', 'file'):
-            raise ValueError('Can only read local images.')
-        imagepath = urllib.parse.unquote(uri_parts.path)
-        if self.settings.root_prefix and imagepath.startswith('/'):
-            root_prefix = Path(self.settings.root_prefix)
-            imagepath = (root_prefix/imagepath.removeprefix('/')).as_posix()
-        elif not os.path.isabs(imagepath):  # path may be absolute Windows path
-            destdir = os.path.abspath(os.path.dirname(destination))
-            imagepath = utils.relative_path(None,
-                                            os.path.join(destdir, imagepath))
-        return imagepath
+        if uri.startswith('file:'):
+            return Path.from_uri(uri)
+        uri_parts = urllib.parse.urlsplit(uri)
+        if uri_parts.scheme != '':
+            raise ValueError(f'Cannot get file path corresponding to {uri}.')
+        # extract and adjust path from "relative URI reference"
+        path = urllib.parse.unquote(uri_parts.path)
+        if self.settings.root_prefix and path.startswith('/'):
+            return Path(self.settings.root_prefix) / path.removeprefix('/')
+        path = Path(path)
+        if self.settings.output_path and not path.is_absolute():
+            # rewrite relative paths, but not "d:/foo" or similar
+            dest_dir = Path(self.settings.output_path).parent.resolve()
+            path = Path(utils.relative_path(None, dest_dir/path))
+        return path
 
     def visit_Text(self, node) -> None:
         text = node.astext()
@@ -1187,7 +1196,7 @@
             atts['loading'] = 'lazy'
         elif loading == 'embed':
             try:
-                imagepath = self.uri2imagepath(uri)
+                imagepath = self.uri2path(uri)
                 if mimetype == 'image/svg+xml':
                     imagedata = Path(imagepath).read_text(encoding='utf-8')
                 else:
@@ -1911,3 +1920,40 @@
     visit_substitution_definition = ignore_node
     visit_target = ignore_node
     visit_pending = ignore_node
+
+
+# Backport `pathlib.Path.from_uri()` class method:
+
+if sys.version_info[:2] < (3, 13):
+    import pathlib
+
+    # subclassing from Path must consider the OS flavour
+    # https://stackoverflow.com/questions/29850801/subclass-pathlib-path-fails
+    class Path(type(pathlib.Path())):  # noqa F811 (redefinition of 'Path')
+        """`pathlib.Path` with `from_uri()` classmethod backported from 3.13.
+        """
+        # copied from
+        # https://github.com/python/cpython/blob/3.13/Lib/pathlib/_local.py
+        # with minor adaptions
+        @classmethod
+        def from_uri(cls, uri):
+            """Return a new path from the given 'file' URI."""
+            if not uri.startswith('file:'):
+                raise ValueError(f"URI does not start with 'file:': {uri!r}")
+            path = uri[5:]
+            if path[:3] == '///':
+                # Remove empty authority
+                path = path[2:]
+            elif path[:12] == '//localhost/':
+                # Remove 'localhost' authority
+                path = path[11:]
+            if path[:3] == '///' or (path[:1] == '/' and path[2:3] in ':|'):
+                # Remove slash before DOS device/UNC path
+                path = path[1:]
+            if path[1:2] == '|':
+                # Replace bar with colon in DOS drive
+                path = path[:1] + ':' + path[2:]
+            path = cls(urllib.parse.unquote(path))
+            if not path.is_absolute():
+                raise ValueError(f"URI is not absolute: {uri!r}")
+            return path

Modified: trunk/docutils/docutils/writers/html4css1/__init__.py
===================================================================
--- trunk/docutils/docutils/writers/html4css1/__init__.py	2024-12-04 13:32:39 UTC (rev 9994)
+++ trunk/docutils/docutils/writers/html4css1/__init__.py	2024-12-04 13:32:53 UTC (rev 9995)
@@ -572,7 +572,7 @@
             if (PIL and ('width' not in node or 'height' not in node)
                 and self.settings.file_insertion_enabled):
                 try:
-                    imagepath = self.uri2imagepath(uri)
+                    imagepath = self.uri2path(uri)
                     with PIL.Image.open(imagepath) as img:
                         img_size = img.size
                 except (ValueError, OSError, UnicodeEncodeError) as e:
@@ -579,8 +579,7 @@
                     self.document.reporter.warning(
                         f'Problem reading image file: {e}')
                 else:
-                    self.settings.record_dependencies.add(
-                        imagepath.replace('\\', '/'))
+                    self.settings.record_dependencies.add(imagepath.as_posix())
                     if 'width' not in atts:
                         atts['width'] = '%dpx' % img_size[0]
                     if 'height' not in atts:

Modified: trunk/docutils/test/functional/input/data/embed_images.rst
===================================================================
--- trunk/docutils/test/functional/input/data/embed_images.rst	2024-12-04 13:32:39 UTC (rev 9994)
+++ trunk/docutils/test/functional/input/data/embed_images.rst	2024-12-04 13:32:53 UTC (rev 9995)
@@ -6,7 +6,7 @@
 directly included, other images are base64_ encoded and included
 as a `data URI`_.
 
-.. figure:: file:../../../docs/user/rst/images/biohazard.png
+.. figure:: ../../../docs/user/rst/images/biohazard.png
    :alt: biohazard
    :align: left
    :width: 2em
@@ -15,7 +15,7 @@
 
    Embedded PNG image in a figure.
 
-.. figure:: file:../../../docs/user/rst/images/biohazard-scaling.svg
+.. figure:: ../../../docs/user/rst/images/biohazard-scaling.svg
    :alt: biohazard
    :align: right
    :width: 2em

Modified: trunk/docutils/test/test_writers/test_html5_polyglot_parts.py
===================================================================
--- trunk/docutils/test/test_writers/test_html5_polyglot_parts.py	2024-12-04 13:32:39 UTC (rev 9994)
+++ trunk/docutils/test/test_writers/test_html5_polyglot_parts.py	2024-12-04 13:32:53 UTC (rev 9995)
@@ -23,12 +23,6 @@
 import docutils.core
 from docutils.writers import html5_polyglot
 
-# TEST_ROOT is ./test/ from the docutils root
-TEST_ROOT = Path(__file__).parents[1]
-DATA_ROOT = TEST_ROOT / 'data'
-ROOT_PREFIX = (TEST_ROOT / 'functional/input').as_posix()
-
-
 # Parts returned by `publish_parts()` for the HTML5 writer by default:
 #   * empty input string
 #   * default configuration settings.

This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
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.