From 6e881e0b5a784f2f93398f0a2da430dfde58c1f2 Mon Sep 17 00:00:00 2001 From: Tobias Deiminger Date: Fri, 26 Jul 2024 13:29:20 +0200 Subject: [PATCH] Fix html escaping of source line marks SourceFileViewHTMLGenerator escaping code had multiple issues: - When marking each line from range_start_pragma_processor as safe, we wrapped not only strings but also whole tuples in Markup(). - For tuple elements we didn't differentiate whether they from pygments (already escaped) or from source file (not yet escaped). Only the later must be explicitly escaped. Some changes are there to make mypy happy. Relates to #1921. --- .../generators/source_file_view_generator.py | 41 +++++++++++++------ .../view_objects/source_file_view_object.py | 23 +++++++++-- .../screens/source_file_view/main.jinja | 2 +- 3 files changed, 50 insertions(+), 16 deletions(-) diff --git a/strictdoc/export/html/generators/source_file_view_generator.py b/strictdoc/export/html/generators/source_file_view_generator.py index de36ccde1..ac485ce19 100644 --- a/strictdoc/export/html/generators/source_file_view_generator.py +++ b/strictdoc/export/html/generators/source_file_view_generator.py @@ -1,5 +1,5 @@ # mypy: disable-error-code="no-untyped-call,no-untyped-def,operator" -from typing import List, Tuple +from typing import List, Tuple, Union from markupsafe import Markup, escape from pygments import highlight @@ -28,10 +28,13 @@ from strictdoc.core.traceability_index import TraceabilityIndex from strictdoc.export.html.generators.view_objects.source_file_view_object import ( SourceFileViewObject, + SourceLineEntry, + SourceMarkerTuple, ) from strictdoc.export.html.html_templates import HTMLTemplates from strictdoc.export.html.renderers.link_renderer import LinkRenderer from strictdoc.export.html.renderers.markup_renderer import MarkupRenderer +from strictdoc.helpers.cast import assert_cast class SourceFileViewHTMLGenerator: @@ -46,7 +49,7 @@ def export( with open(source_file.full_path, encoding="utf-8") as opened_file: source_file_lines = opened_file.readlines() - pygmented_source_file_lines: List[Markup] = [] + pygmented_source_file_lines: List[SourceLineEntry] = [] pygments_styles: Markup = Markup("") if len(source_file_lines) > 0: @@ -89,7 +92,10 @@ def get_pygmented_source_lines( source_file: SourceFile, source_file_lines: List[str], coverage_info: SourceFileTraceabilityInfo, - ) -> Tuple[List[Markup], Markup]: + ) -> Tuple[ + List[SourceLineEntry], + Markup, + ]: assert isinstance(source_file, SourceFile) assert isinstance(source_file_lines, list) assert isinstance(coverage_info, SourceFileTraceabilityInfo) @@ -154,7 +160,9 @@ def get_pygmented_source_lines( pygmented_source_file_content = pygmented_source_file_content[ slice_start:slice_end ] - pygmented_source_file_lines = pygmented_source_file_content.split("\n") + pygmented_source_file_lines: List[Union[str, SourceMarkerTuple]] = list( + pygmented_source_file_content.split("\n") + ) if hack_first_line: pygmented_source_file_lines[0] = "" @@ -177,12 +185,14 @@ def get_pygmented_source_lines( for pragma in coverage_info.pragmas: pragma_line = pragma.ng_source_line_begin + assert isinstance(pragma_line, int) + pygmented_source_file_line = assert_cast( + pygmented_source_file_lines[pragma_line - 1], str + ) if isinstance(pragma, ForwardRangeMarker): + before_line = pygmented_source_file_line.rstrip("\n") + " " pygmented_source_file_lines[pragma_line - 1] = ( - pygmented_source_file_lines[pragma_line - 1].rstrip("\n") - + " ", - "\n", - pragma, + SourceMarkerTuple(Markup(before_line), Markup("\n"), pragma) ) continue @@ -202,7 +212,7 @@ def get_pygmented_source_lines( assert closing_bracket_index is not None after_line = source_line[closing_bracket_index:].rstrip() - pygmented_source_file_lines[pragma_line - 1] = ( + pygmented_source_file_lines[pragma_line - 1] = SourceMarkerTuple( escape(before_line), escape(after_line), pragma, @@ -212,6 +222,13 @@ def get_pygmented_source_lines( + html_formatter.get_style_defs(".highlight") ) - return list(map(Markup, pygmented_source_file_lines)), Markup( - pygments_styles - ) + return [ + SourceFileViewHTMLGenerator.mark_safe(line) + for line in pygmented_source_file_lines + ], Markup(pygments_styles) + + @staticmethod + def mark_safe( + line: Union[str, SourceMarkerTuple], + ) -> Union[Markup, SourceMarkerTuple]: + return line if isinstance(line, SourceMarkerTuple) else Markup(line) diff --git a/strictdoc/export/html/generators/view_objects/source_file_view_object.py b/strictdoc/export/html/generators/view_objects/source_file_view_object.py index 9ff0dbbb8..d5dea0060 100644 --- a/strictdoc/export/html/generators/view_objects/source_file_view_object.py +++ b/strictdoc/export/html/generators/view_objects/source_file_view_object.py @@ -1,11 +1,16 @@ # mypy: disable-error-code="no-any-return,no-untyped-call,no-untyped-def,union-attr" from dataclasses import dataclass from datetime import datetime -from typing import List +from typing import List, NamedTuple, Union from markupsafe import Markup from strictdoc import __version__ +from strictdoc.backend.sdoc_source_code.models.range_marker import ( + ForwardRangeMarker, + LineMarker, + RangeMarker, +) from strictdoc.core.document_tree_iterator import DocumentTreeIterator from strictdoc.core.finders.source_files_finder import SourceFile from strictdoc.core.project_config import ProjectConfig @@ -15,6 +20,18 @@ from strictdoc.export.html.renderers.markup_renderer import MarkupRenderer +class SourceMarkerTuple(NamedTuple): + before_line: Markup + after_line: Markup + pragma: Union[ForwardRangeMarker, LineMarker, RangeMarker] + + +SourceLineEntry = Union[ + Markup, + SourceMarkerTuple, +] + + @dataclass class SourceFileViewObject: def __init__( @@ -26,7 +43,7 @@ def __init__( markup_renderer: MarkupRenderer, source_file: SourceFile, pygments_styles: Markup, - pygmented_source_file_lines: List[Markup], + pygmented_source_file_lines: List[SourceLineEntry], ): self.traceability_index: TraceabilityIndex = traceability_index self.project_config: ProjectConfig = project_config @@ -34,7 +51,7 @@ def __init__( self.markup_renderer: MarkupRenderer = markup_renderer self.source_file: SourceFile = source_file self.pygments_styles: Markup = pygments_styles - self.pygmented_source_file_lines: List[Markup] = ( + self.pygmented_source_file_lines: List[SourceLineEntry] = ( pygmented_source_file_lines ) diff --git a/strictdoc/export/html/templates/screens/source_file_view/main.jinja b/strictdoc/export/html/templates/screens/source_file_view/main.jinja index 1e627a751..52a5696ae 100644 --- a/strictdoc/export/html/templates/screens/source_file_view/main.jinja +++ b/strictdoc/export/html/templates/screens/source_file_view/main.jinja @@ -6,7 +6,7 @@ {%- for line in view_object.pygmented_source_file_lines -%}
{{ loop.index }}
- {%- if line.__class__.__name__ == "tuple" -%} + {%- if line.__class__.__name__ == "SourceMarkerTuple" -%} {%- set replacement_before, replacement_after, pragma = line -%} {# Note: Cannot format HTML/Jinja blocks within 'pre' tags! #}
{{ replacement_before }}{%- for req in pragma.reqs_objs -%}