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

milde--- via Docutils-checkins <[email protected]>
Newsgroups gmane.text.docutils.cvs
Message-ID <[email protected]>
Revision: 9996
          http://sourceforge.net/p/docutils/code/9996
Author:   milde
Date:     2024-12-04 13:33:06 +0000 (Wed, 04 Dec 2024)
Log Message:
-----------
Provide a generic Translator base class for writers.

Move the `uri2path()` method there.
Use it in LaTeX and HTML writers to reduce code duplication and
unify the handling of image URI references.

Using `uri2path()` in the ODT writer would be a backwards-incompatible
change: The ODT writer expects relative image paths to be relative to
the **source** directory. Announce upcoming change for the ODT writer.

Modified Paths:
--------------
    trunk/docutils/RELEASE-NOTES.rst
    trunk/docutils/docutils/writers/__init__.py
    trunk/docutils/docutils/writers/_html_base.py
    trunk/docutils/docutils/writers/latex2e/__init__.py

Modified: trunk/docutils/RELEASE-NOTES.rst
===================================================================
--- trunk/docutils/RELEASE-NOTES.rst	2024-12-04 13:32:53 UTC (rev 9995)
+++ trunk/docutils/RELEASE-NOTES.rst	2024-12-04 13:33:06 UTC (rev 9996)
@@ -124,6 +124,12 @@
 
     __ docs/user/latex.html#length-units
 
+* "odt" writer:
+
+  - Align adjustment of relative image paths with the behaviour of HTML
+    and LaTeX writers: assume them relative to the *output* directory (as
+    required for image references in HTML), not the *source* directory.
+
 Removals
 --------
 

Modified: trunk/docutils/docutils/writers/__init__.py
===================================================================
--- trunk/docutils/docutils/writers/__init__.py	2024-12-04 13:32:53 UTC (rev 9995)
+++ trunk/docutils/docutils/writers/__init__.py	2024-12-04 13:33:06 UTC (rev 9996)
@@ -11,18 +11,21 @@
 __docformat__ = 'reStructuredText'
 
 import importlib
+import sys
+import urllib
+from pathlib import Path
 from typing import TYPE_CHECKING
 
 import docutils
-from docutils import languages, Component
+from docutils import Component, languages, nodes, utils
 from docutils.transforms import universal
 
 if TYPE_CHECKING:
     from typing import Any, Final
 
-    from docutils import nodes
     from docutils.io import Output
     from docutils.languages import LanguageModule
+    from docutils.nodes import StrPath
     from docutils.transforms import Transform
 
 
@@ -137,7 +140,108 @@
         return Component.get_transforms(self)
 
 
-WRITER_ALIASES = {'html': 'html4css1',  # may change to html5 some day
+class DoctreeTranslator(nodes.NodeVisitor):
+    """
+    Generic Docutils document tree translator base class.
+
+    Adds auxiliary methods and attributes that are used by several
+    Docutils writers to the `nodes.NodeVisitor` abstract superclass.
+    """
+
+    def __init__(self, document) -> None:
+        super().__init__(document)
+        self.settings = document.settings
+
+    def uri2path(self, uri: str, output_path: StrPath|None = None) -> Path:
+        """Return filesystem path corresponding to a `URI reference`__.
+
+        The `root_prefix`__ setting` is applied to URI references starting
+        with "/" (but not to absolute Windows paths or "file" URIs).
+
+        If `output_path` (defaults to 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.)
+
+        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,
+          set `output_path` to the empty string).
+
+        Provisional: the function's location, interface and behaviour
+        may change without advance warning.
+
+        __ 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
+        """
+        if output_path is None:
+            output_path = self.settings.output_path
+        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)
+        # adjust relative paths (but not "d:/foo" or similar)
+        if output_path and not path.is_absolute():
+            dest_dir = Path(output_path).parent.resolve()
+            path = Path(utils.relative_path(None, dest_dir/path))
+            # TODO: support paths relative to the *source* directory?
+            # source_path, line = utils.get_source_line(node)
+            # if source_path:
+            #     source_dir = Path(source_path).parent.resolve()
+            #     path = Path(utils.relative_path(None, source_dir/path)
+        return path
+
+
+if sys.version_info[:2] < (3, 13):
+    # Backport `pathlib.Path.from_uri()` class method:
+    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.
+        """
+
+        # `from_uri()` is 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
+
+
+WRITER_ALIASES = {'html': 'html4css1',  # will change to html5 in Docutils 2.0
                   'html4': 'html4css1',
                   'xhtml10': 'html4css1',
                   'html5': 'html5_polyglot',

Modified: trunk/docutils/docutils/writers/_html_base.py
===================================================================
--- trunk/docutils/docutils/writers/_html_base.py	2024-12-04 13:32:53 UTC (rev 9995)
+++ trunk/docutils/docutils/writers/_html_base.py	2024-12-04 13:33:06 UTC (rev 9996)
@@ -25,8 +25,6 @@
 import os
 import os.path
 import re
-import sys
-import urllib.parse
 import warnings
 import xml.etree.ElementTree as ET
 from pathlib import Path
@@ -186,13 +184,13 @@
             self.parts[part] = ''.join(getattr(self, part))
 
 
-class HTMLTranslator(nodes.NodeVisitor):
+class HTMLTranslator(writers.DoctreeTranslator):
 
     """
     Generic Docutils to HTML translator.
 
     See the `html4css1` and `html5_polyglot` writers for full featured
-    HTML writers.
+    HTML translators.
 
     .. IMPORTANT::
       The `visit_*` and `depart_*` methods use a
@@ -292,9 +290,9 @@
     """MIME types supported by the HTML5 <video> element."""
 
     def __init__(self, document) -> None:
-        nodes.NodeVisitor.__init__(self, document)
+        super().__init__(document)
         # process settings
-        self.settings = settings = document.settings
+        settings = self.settings
         self.language = languages.get_language(
                             settings.language_code, document.reporter)
         self.initial_header_level = int(settings.initial_header_level)
@@ -626,48 +624,6 @@
             return
         child['classes'].append(class_)
 
-    def uri2path(self, uri: str) -> Path:
-        """Return filesystem path corresponding to a `URI reference`__.
-
-        The `root_prefix`__ setting` is applied to URI references starting
-        with "/" (but not to absolute Windows paths or "file" URIs).
-
-        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.)
-
-        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#section-4.1
-        __ https://docutils.sourceforge.io/docs/user/config.html#root-prefix
-        __ https://docutils.sourceforge.io/docs/user/config.html#output-path
-        """
-        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()
         encoded = self.encode(text)
@@ -1920,40 +1876,3 @@
     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/latex2e/__init__.py
===================================================================
--- trunk/docutils/docutils/writers/latex2e/__init__.py	2024-12-04 13:32:53 UTC (rev 9995)
+++ trunk/docutils/docutils/writers/latex2e/__init__.py	2024-12-04 13:33:06 UTC (rev 9996)
@@ -14,7 +14,6 @@
 
 import re
 import string
-import urllib.parse
 import warnings
 from pathlib import Path
 
@@ -1130,7 +1129,7 @@
         return False
 
 
-class LaTeXTranslator(nodes.NodeVisitor):
+class LaTeXTranslator(writers.DoctreeTranslator):
     """
     Generate code for 8-bit LaTeX from a Docutils document tree.
 
@@ -1187,7 +1186,7 @@
 
         # Settings
         # ~~~~~~~~
-        self.settings = settings = document.settings
+        settings = self.settings
         # warn of deprecated settings and changing defaults:
         if settings.use_latex_citations is None and not settings.use_bibtex:
             settings.use_latex_citations = False
@@ -2437,8 +2436,8 @@
     def visit_image(self, node) -> None:
         self.requirements['graphicx'] = self.graphicx_package
         attrs = node.attributes
-        # image URI may use %-encoding
-        imagepath = urllib.parse.unquote(attrs['uri'])
+        # convert image URI to filesystem path, do not adjust relative path:
+        imagepath = self.uri2path(attrs['uri'], output_path='')
         # alignment defaults:
         if 'align' not in attrs:
             # Set default align of image in a figure to 'center'

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.