mirror of https://github.com/Nonannet/pyladoc.git
Compare commits
3 Commits
Author | SHA1 | Date |
---|---|---|
|
cc3097e173 | |
|
2c0a3bd7ed | |
|
44e192f275 |
|
@ -6,7 +6,7 @@ authors = [
|
|||
]
|
||||
description = "Package for generating HTML and PDF/latex from python code"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.8"
|
||||
requires-python = ">=3.10"
|
||||
license = "MIT"
|
||||
classifiers = [
|
||||
"Programming Language :: Python :: 3",
|
||||
|
|
|
@ -8,6 +8,7 @@ from . import latex
|
|||
import pkgutil
|
||||
from html.parser import HTMLParser
|
||||
from io import StringIO
|
||||
from . import svg_tools
|
||||
|
||||
HTML_OUTPUT = 0
|
||||
LATEX_OUTPUT = 1
|
||||
|
@ -58,17 +59,6 @@ def _markdown_to_html(text: str) -> str:
|
|||
return html_text
|
||||
|
||||
|
||||
def _clean_svg(svg_text: str) -> str:
|
||||
# remove all tags not alllowd for inline svg from metadata:
|
||||
svg_text = re.sub(r'<metadata>.*?</metadata>', '', svg_text, flags=re.DOTALL)
|
||||
|
||||
# remove illegal path-tags without d attribute:
|
||||
return re.sub(r'<path(?![^>]*\sd=)\s.*?/>', '', svg_text, flags=re.DOTALL)
|
||||
|
||||
# def _get_templ_vars(template: str) -> list[str]:
|
||||
# return re.findall("<!---START (.+?)--->.*?<!---END .+?--->", template, re.DOTALL)
|
||||
|
||||
|
||||
def _drop_indent(text: str, amount: int) -> str:
|
||||
"""
|
||||
Drops a specific number of indentation spaces from a multiline text.
|
||||
|
@ -142,6 +132,7 @@ def escape_html(text: str) -> str:
|
|||
|
||||
|
||||
def figure_to_string(fig: Figure,
|
||||
unique_id: str,
|
||||
figure_format: FFormat = 'svg',
|
||||
font_family: str | None = None,
|
||||
scale: float = 1,
|
||||
|
@ -175,7 +166,7 @@ def figure_to_string(fig: Figure,
|
|||
elif figure_format == 'svg' and not base64:
|
||||
i = buff.read(2028).find(b'<svg') # skip xml and DOCTYPE header
|
||||
buff.seek(max(i, 0))
|
||||
return _clean_svg(buff.read().decode('utf-8'))
|
||||
return svg_tools.update_svg_ids(svg_tools.clean_svg(buff.read().decode('utf-8')), unique_id)
|
||||
|
||||
else:
|
||||
image_mime = {"png": "image/png", "svg": "image/svg+xml"}
|
||||
|
@ -334,9 +325,9 @@ class DocumentWriter():
|
|||
ref_type = parts[0]
|
||||
ref_id = parts[1]
|
||||
caption, reference = self._add_item(ref_id, ref_type, '({})')
|
||||
return (f'\n<latex type="block" reference="{reference}" caption="{caption}">{content}</latex>\n')
|
||||
return (f'<latex type="block" reference="{reference}" caption="{caption}">{content}</latex>')
|
||||
else:
|
||||
return f'\n<latex type="block">{content}</latex>\n'
|
||||
return f'<latex type="block">{content}</latex>'
|
||||
|
||||
result = block_pattern.sub(block_repl, text)
|
||||
|
||||
|
@ -351,13 +342,13 @@ class DocumentWriter():
|
|||
def _get_equation_html(self, latex_equation: str, caption: str, reference: str, block: bool = False) -> str:
|
||||
fig = latex_to_figure(latex_equation)
|
||||
if block:
|
||||
fig_str = figure_to_string(fig, self._figure_format, base64=self._base64_svgs)
|
||||
fig_str = figure_to_string(fig, reference, self._figure_format, base64=self._base64_svgs)
|
||||
ret = ('<div class="equation-container" '
|
||||
f'id="pyld-ref-{reference}">'
|
||||
f'<div class="equation">{fig_str}</div>'
|
||||
f'<div class="equation-number">{caption}</div></div>')
|
||||
else:
|
||||
ret = '<span class="inline-equation">' + figure_to_string(fig, self._figure_format, base64=self._base64_svgs) + '</span>'
|
||||
ret = '<span class="inline-equation">' + figure_to_string(fig, reference, self._figure_format, base64=self._base64_svgs) + '</span>'
|
||||
|
||||
plt.close(fig)
|
||||
return ret
|
||||
|
@ -373,34 +364,54 @@ class DocumentWriter():
|
|||
self.eq_caption: str = ''
|
||||
self.reference: str = ''
|
||||
self.block: bool = False
|
||||
self.p_tags: int = 0
|
||||
self.dw = document_writer
|
||||
self.latex_count = 0
|
||||
self.self_closing = False
|
||||
|
||||
def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None:
|
||||
if tag == 'hr':
|
||||
self.modified_html.write(f"<{tag}>")
|
||||
self.self_closing = True
|
||||
elif tag == 'latex':
|
||||
self.in_latex = True
|
||||
attr_dict = {k: v if v else '' for k, v in attrs}
|
||||
self.eq_caption = attr_dict.get('caption', '')
|
||||
self.reference = attr_dict.get('reference', '')
|
||||
if 'reference' in attr_dict:
|
||||
self.reference = attr_dict['reference']
|
||||
else:
|
||||
self.latex_count += 1
|
||||
self.reference = f"auto_id_{self.latex_count}"
|
||||
self.block = attr_dict.get('type') == 'block'
|
||||
elif not self.in_latex:
|
||||
tag_text = self.get_starttag_text()
|
||||
self.self_closing = tag_text.endswith('/>')
|
||||
if tag_text:
|
||||
self.modified_html.write(tag_text)
|
||||
if tag == 'p':
|
||||
self.p_tags += 1
|
||||
|
||||
def handle_data(self, data: str) -> None:
|
||||
if self.in_latex:
|
||||
self.modified_html.write(
|
||||
self.dw._get_equation_html(data, self.eq_caption, self.reference, self.block))
|
||||
eq_html = self.dw._get_equation_html(data, self.eq_caption, self.reference, self.block)
|
||||
if self.p_tags > 0 and self.block:
|
||||
# If a block equation (with divs) is inside a p tag: close and reopen it
|
||||
self.modified_html.write(f"</p>{eq_html}<p>")
|
||||
else:
|
||||
self.modified_html.write(eq_html)
|
||||
|
||||
else:
|
||||
self.modified_html.write(data)
|
||||
|
||||
def handle_endtag(self, tag: str) -> None:
|
||||
if tag == 'latex':
|
||||
self.in_latex = False
|
||||
elif self.self_closing:
|
||||
self.self_closing = False
|
||||
else:
|
||||
self.modified_html.write(f"</{tag}>")
|
||||
if tag == 'p' and self.p_tags > 0:
|
||||
self.p_tags -= 1
|
||||
|
||||
parser = HTMLPostProcessor(self)
|
||||
parser.feed(html_code)
|
||||
|
@ -435,14 +446,14 @@ class DocumentWriter():
|
|||
caption_prefix, reference = self._add_item(ref_id, ref_type, prefix_pattern)
|
||||
return '<div id="pyld-ref-%s" class="figure">%s%s</div>' % (
|
||||
reference,
|
||||
figure_to_string(fig, self._figure_format, base64=self._base64_svgs, scale=self._fig_scale),
|
||||
figure_to_string(fig, reference, self._figure_format, base64=self._base64_svgs, scale=self._fig_scale),
|
||||
'<br>' + caption_prefix + escape_html(caption) if caption else '')
|
||||
|
||||
def render_to_latex() -> str:
|
||||
_, reference = self._add_item(ref_id, ref_type, prefix_pattern)
|
||||
return '\\begin{figure}%s\n%s\n\\caption{%s}\n%s\\end{figure}' % (
|
||||
'\n\\centering' if centered else '',
|
||||
figure_to_string(fig, 'pgf', self._font_family, scale=self._fig_scale),
|
||||
figure_to_string(fig, reference, 'pgf', self._font_family, scale=self._fig_scale),
|
||||
latex.escape_text(caption),
|
||||
'\\label{%s}\n' % latex.normalize_label_text(reference) if ref_id else '')
|
||||
|
||||
|
@ -603,7 +614,7 @@ class DocumentWriter():
|
|||
norm_text = _normalize_text_indent(str(text))
|
||||
|
||||
def render_to_html() -> str:
|
||||
html = self._html_post_processing(_markdown_to_html(self._equation_embedding_reescaping(norm_text)))
|
||||
html = _markdown_to_html(self._equation_embedding_reescaping(norm_text))
|
||||
if section_class:
|
||||
return '<div class="' + section_class + '">' + html + '</div>'
|
||||
else:
|
||||
|
@ -637,7 +648,7 @@ class DocumentWriter():
|
|||
self._base64_svgs = base64_svgs
|
||||
self._fig_scale = figure_scale
|
||||
|
||||
return _fillin_reference_names(self._render_doc(HTML_OUTPUT), self._item_index)
|
||||
return self._html_post_processing(_fillin_reference_names(self._render_doc(HTML_OUTPUT), self._item_index))
|
||||
|
||||
def to_latex(self, font_family: Literal[None, 'serif', 'sans-serif'] = None,
|
||||
table_renderer: TRenderer = 'simple', figure_scale: float = 1) -> str:
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
import xml.etree.ElementTree as ET
|
||||
import re
|
||||
from re import Match
|
||||
|
||||
def update_svg_ids(input_svg: str, unique_id: str) -> str:
|
||||
"""Add a unique ID part to all svg IDs and update references ti these IDs"""
|
||||
id_mapping: dict[str, str] = {}
|
||||
|
||||
def update_ids(match: Match[str]) -> str:
|
||||
old_id = match.group(1)
|
||||
new_id = f"svg-{unique_id}-{old_id}"
|
||||
id_mapping[old_id] = new_id
|
||||
return f' id="{new_id}"'
|
||||
|
||||
def update_references(match: Match[str]) -> str:
|
||||
old_ref = match.group(1)
|
||||
new_ref = id_mapping.get(old_ref, old_ref)
|
||||
if match.group(0).startswith('xlink:href'):
|
||||
return f'xlink:href="#{new_ref}"'
|
||||
else:
|
||||
return f'url(#{new_ref})'
|
||||
|
||||
# Update IDs
|
||||
svg_string = re.sub(r'\sid="(.*?)"', update_ids, input_svg)
|
||||
|
||||
# Update references to IDs
|
||||
svg_string = re.sub(r'url\(#([^\)]+)\)', update_references, svg_string)
|
||||
svg_string = re.sub(r'xlink:href="#([^\"]+)"', update_references, svg_string)
|
||||
|
||||
return svg_string
|
||||
|
||||
|
||||
def clean_svg(svg_text: str) -> str:
|
||||
# remove all tags not alllowd for inline svg from metadata:
|
||||
svg_text = re.sub(r'<metadata>.*?</metadata>', '', svg_text, flags=re.DOTALL)
|
||||
|
||||
# remove illegal path-tags without d attribute:
|
||||
return re.sub(r'<path(?![^>]*\sd=)\s.*?/>', '', svg_text, flags=re.DOTALL)
|
|
@ -2,10 +2,7 @@ from typing import Generator, Any
|
|||
from lxml import etree
|
||||
from lxml.etree import _Element as EElement # type: ignore
|
||||
import requests
|
||||
|
||||
|
||||
with open('src/pyladoc/templates/test_template.html', mode='rt', encoding='utf-8') as f:
|
||||
html_test_template = f.read()
|
||||
import pyladoc
|
||||
|
||||
|
||||
def add_line_numbers(multiline_string: str) -> str:
|
||||
|
@ -53,7 +50,7 @@ def validate_html(html_string: str, validate_online: bool = False, check_for: li
|
|||
assert tag_type in tags, f"Tag {tag_type} not found in the html code"
|
||||
|
||||
if validate_online:
|
||||
test_page = html_test_template.replace('<!--CONTENT-->', html_string)
|
||||
test_page = pyladoc.inject_to_template(html_string, internal_template='templates/test_template.html')
|
||||
validation_result = validate_html_with_w3c(test_page)
|
||||
assert 'messages' in validation_result, 'Validate request failed'
|
||||
if validation_result['messages']:
|
||||
|
|
|
@ -22,9 +22,7 @@ def test_latex_embedding2():
|
|||
contains the interaction parameter <latex>\Phi_{ij}</latex>, which describes the influence of component
|
||||
<latex>j</latex> on the transport properties of component <latex>i</latex>.
|
||||
|
||||
The interaction parameter <latex>\Phi_{ij}</latex> is given by the relation shown in @eq:ExampleFormula2.
|
||||
<latex type="block" reference="eq:ExampleFormula2" caption="(1)">\Phi_{ij} = \frac{1}{\sqrt{8}} \left(1 + \frac{M_i}{M_j} \right)^{-1/2} \left[ 1 + \left( \frac{\lambda_i}{\lambda_j} \right)^{1/2} \left( \frac{M_j}{M_i} \right)^{1/4} \right]^2</latex>
|
||||
""")
|
||||
The interaction parameter <latex>\Phi_{ij}</latex> is given by the relation shown in @eq:ExampleFormula2.<latex type="block" reference="eq:ExampleFormula2" caption="(1)">\Phi_{ij} = \frac{1}{\sqrt{8}} \left(1 + \frac{M_i}{M_j} \right)^{-1/2} \left[ 1 + \left( \frac{\lambda_i}{\lambda_j} \right)^{1/2} \left( \frac{M_j}{M_i} \right)^{1/4} \right]^2</latex>""")
|
||||
|
||||
dummy = pyladoc.DocumentWriter()
|
||||
result_string = dummy._equation_embedding_reescaping(test_input)
|
||||
|
@ -44,9 +42,7 @@ def test_latex_embedding():
|
|||
""")
|
||||
|
||||
expected_output = pyladoc._normalize_text_indent(r"""
|
||||
# Test
|
||||
<latex type="block" reference="eq:ExampleFormula2" caption="(1)">\Phi_{ij} = \frac{1}{\sqrt{8}}</latex>
|
||||
This <latex>i</latex> is inline LaTeX.
|
||||
# Test<latex type="block" reference="eq:ExampleFormula2" caption="(1)">\Phi_{ij} = \frac{1}{\sqrt{8}}</latex>This <latex>i</latex> is inline LaTeX.
|
||||
""")
|
||||
|
||||
dummy = pyladoc.DocumentWriter()
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import pyladoc
|
||||
import matplotlib.pyplot as plt
|
||||
import pandas as pd
|
||||
import document_validation
|
||||
from . import document_validation
|
||||
import numpy as np
|
||||
|
||||
VALIDATE_HTML_CODE_ONLINE = False
|
||||
VALIDATE_HTML_CODE_ONLINE = True
|
||||
WRITE_RESULT_FILES = True
|
||||
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import pyladoc
|
||||
import matplotlib.pyplot as plt
|
||||
import pandas as pd
|
||||
import document_validation
|
||||
from . import document_validation
|
||||
|
||||
VALIDATE_HTML_CODE_ONLINE = False
|
||||
WRITE_RESULT_FILES = True
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import pyladoc
|
||||
import document_validation
|
||||
from . import document_validation
|
||||
|
||||
VALIDATE_HTML_CODE_ONLINE = False
|
||||
WRITE_RESULT_FILES = True
|
||||
|
|
|
@ -0,0 +1,151 @@
|
|||
import pyladoc
|
||||
|
||||
def test_update_svg_ids():
|
||||
test_str = r"""
|
||||
<g id="figure_1">
|
||||
<g id="patch_1">
|
||||
<path d="M 0 15.0336
|
||||
L 24.570183 15.0336
|
||||
L 24.570183 0
|
||||
L 0 0
|
||||
z
|
||||
" style="fill: #ffffff"/>
|
||||
</g>
|
||||
<g id="axes_1">
|
||||
<g id="text_1">
|
||||
<!-- $\lambda_{\text{mix}}$ -->
|
||||
<g transform="translate(3.042219 10.351343) scale(0.1 -0.1)">
|
||||
<defs>
|
||||
<path id="DejaVuSans-Oblique-3bb" d="M 2350 4316
|
||||
|
||||
|
||||
" clip-path="url(#p8dcad2f367)" style="fill: none; stroke: #000000; stroke-width: 1.5; stroke-linecap: square"/>
|
||||
|
||||
|
||||
<clipPath id="p8dcad2f367">
|
||||
<rect x="57.6" y="41.472" width="357.12" height="266.112"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
|
||||
|
||||
|
||||
<path id="DejaVuSans-Oblique-78" d="M 3841 3500
|
||||
L 2234 1784
|
||||
|
||||
</defs>
|
||||
<use xlink:href="#DejaVuSans-Oblique-78" transform="translate(0 0.3125)"/>
|
||||
<use xlink:href="#DejaVuSans-Oblique-69" transform="translate(59.179688 -16.09375) scale(0.7)"/>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<svg xmlns:xlink="http://www.w3.org/1999/xlink" width="24.570183pt" height="15.0336pt" viewBox="0 0 24.570183 15.0336" xmlns="http://www.w3.org/2000/svg" version="1.1">
|
||||
|
||||
<defs>
|
||||
<style type="text/css">*{stroke-linejoin: round; stroke-linecap: butt}</style>
|
||||
</defs>
|
||||
<g id="figure_1">
|
||||
<g id="patch_1">
|
||||
<path d="M 0 15.0336
|
||||
L 24.570183 15.0336
|
||||
L 24.570183 0
|
||||
L 0 0
|
||||
z
|
||||
" style="fill: #ffffff"/>
|
||||
</g>
|
||||
<g id="axes_1">
|
||||
<g id="text_1">
|
||||
<!-- $\lambda_{\text{mix}}$ -->
|
||||
<g transform="translate(3.042219 10.351343) scale(0.1 -0.1)">
|
||||
<defs>
|
||||
<path id="DejaVuSans-Oblique-3bb" d="M 2350 4316
|
||||
L 3125 0
|
||||
L 2516 0
|
||||
L 2038 2588
|
||||
L 328 0
|
||||
L -281 0
|
||||
L 1903 3356
|
||||
L 1794 3975
|
||||
Q 1725 4369 1391 4369
|
||||
L 1091 4369
|
||||
L 1184 4863
|
||||
L 1550 4856
|
||||
Q 2253 4847 2350 4316
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
<path id="DejaVuSans-6d" d="M 3328 2828
|
||||
Q 3544 3216 3844 3400
|
||||
Q 4144 3584 4550 3584
|
||||
Q 5097 3584 5394 3201
|
||||
Q 5691 2819 5691 2113
|
||||
L 5691 0
|
||||
L 5113 0
|
||||
L 5113 2094
|
||||
Q 5113 2597 4934 2840
|
||||
Q 4756 3084 4391 3084
|
||||
Q 3944 3084 3684 2787
|
||||
Q 3425 2491 3425 1978
|
||||
L 3425 0
|
||||
L 2847 0
|
||||
L 2847 2094
|
||||
Q 2847 2600 2669 2842
|
||||
Q 2491 3084 2119 3084
|
||||
Q 1678 3084 1418 2786
|
||||
Q 1159 2488 1159 1978
|
||||
L 1159 0
|
||||
L 581 0
|
||||
L 581 3500
|
||||
L 1159 3500
|
||||
L 1159 2956
|
||||
Q 1356 3278 1631 3431
|
||||
Q 1906 3584 2284 3584
|
||||
Q 2666 3584 2933 3390
|
||||
Q 3200 3197 3328 2828
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
<path id="DejaVuSans-69" d="M 603 3500
|
||||
L 1178 3500
|
||||
L 1178 0
|
||||
L 603 0
|
||||
L 603 3500
|
||||
z
|
||||
M 603 4863
|
||||
L 1178 4863
|
||||
L 1178 4134
|
||||
L 603 4134
|
||||
L 603 4863
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
<path id="DejaVuSans-78" d="M 3513 3500
|
||||
L 2247 1797
|
||||
L 3578 0
|
||||
L 2900 0
|
||||
L 1881 1375
|
||||
L 863 0
|
||||
L 184 0
|
||||
L 1544 1831
|
||||
L 300 3500
|
||||
L 978 3500
|
||||
L 1906 2253
|
||||
L 2834 3500
|
||||
L 3513 3500
|
||||
z
|
||||
" transform="scale(0.015625)"/>
|
||||
</defs>
|
||||
<use xlink:href="#DejaVuSans-Oblique-3bb" transform="translate(0 0.015625)"/>
|
||||
<use xlink:href="#DejaVuSans-6d" transform="translate(59.179688 -16.390625) scale(0.7)"/>
|
||||
<use xlink:href="#DejaVuSans-69" transform="translate(127.368164 -16.390625) scale(0.7)"/>
|
||||
<use xlink:href="#DejaVuSans-78" transform="translate(146.816406 -16.390625) scale(0.7)"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
"""
|
||||
|
||||
unique_id = 'xx-rgerergre-yy-trhsrthrst--xx'
|
||||
|
||||
result = pyladoc.svg_tools.update_svg_ids(test_str, unique_id)
|
||||
|
||||
print(result)
|
||||
|
||||
assert result.replace(f"svg-{unique_id}-", '') == test_str
|
Loading…
Reference in New Issue