Skip to content

Commit 54760aa

Browse files
authored
html export: improve custom url handling (#411)
Co-authored-by: viseshrp <[email protected]>
1 parent 3902ff2 commit 54760aa

File tree

3 files changed

+46
-48
lines changed

3 files changed

+46
-48
lines changed

src/ansys/dynamicreporting/core/serverless/html_exporter.py

Lines changed: 15 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ def _wrap_full_document(self, html_fragment_or_doc: str) -> str:
124124
text = html_fragment_or_doc.lstrip()
125125

126126
# Already a full document? Return as-is.
127+
# only look at the first ~2KB to avoid scanning huge strings.
127128
lowered = text[:2000].lower()
128129
if lowered.startswith("<!doctype") or "<html" in lowered:
129130
return html_fragment_or_doc
@@ -168,14 +169,10 @@ def _replace_files(self, text: str, inline: bool = False, size_check: bool = Fal
168169

169170
patterns = []
170171
if ver:
171-
patterns.append(f"{self._static_url}ansys{ver}/") # custom
172-
patterns.append(f"/static/ansys{ver}/") # legacy literal
173-
174-
# custom first, then legacy literals
175-
patterns.extend([self._static_url, self._media_url, "/static/", "/media/"])
176-
172+
patterns.append(f"{self._static_url}ansys{ver}/")
173+
patterns.extend([self._static_url, self._media_url])
177174
if ver:
178-
patterns.append(f"/ansys{ver}/") # server-root style
175+
patterns.append(f"/ansys{ver}/")
179176

180177
while True:
181178
# Find the next match using the legacy priority order
@@ -361,15 +358,10 @@ def _process_file(self, path_in_html: str, pathname: str, inline: bool = False)
361358
# Resolve source file location based on the raw pathname (no normalization)
362359
ver = str(self._ansys_version) if self._ansys_version is not None else ""
363360

364-
# Source resolution: custom first, then legacy literals
365361
if pathname.startswith(self._media_url):
366362
source_file = self._media_dir / pathname.replace(self._media_url, "", 1)
367363
elif pathname.startswith(self._static_url):
368364
source_file = self._static_dir / pathname.replace(self._static_url, "", 1)
369-
elif pathname.startswith("/media/"): # legacy literal
370-
source_file = self._media_dir / pathname.replace("/media/", "", 1)
371-
elif pathname.startswith("/static/"): # legacy literal
372-
source_file = self._static_dir / pathname.replace("/static/", "", 1)
373365
elif ver and pathname.startswith(f"/ansys{ver}/"):
374366
source_file = self._static_dir / pathname.lstrip("/")
375367
else:
@@ -417,19 +409,11 @@ def _process_file(self, path_in_html: str, pathname: str, inline: bool = False)
417409
else:
418410
content = self._fix_viewer_component_paths(basename, content)
419411

420-
# Output path:
421-
# keep ansys tree if the input path came from /static/ansys{ver}/ (custom OR legacy literal)
422-
if ver and (
423-
pathname.startswith(f"{self._static_url}ansys{ver}/")
424-
or pathname.startswith(f"/static/ansys{ver}/")
425-
):
426-
local_pathname = os.path.dirname(pathname)
427-
# normalize either custom or legacy /static/ to './'
428-
if local_pathname.startswith(self._static_url):
429-
local_pathname = local_pathname.replace(self._static_url, "./", 1)
430-
elif local_pathname.startswith("/static/"):
431-
local_pathname = local_pathname.replace("/static/", "./", 1)
432-
412+
# Output path (exact legacy behavior):
413+
# - If /static/ansys{ver}/ -> keep ansys tree, remove '/static/' -> './ansys{ver}/.../<basename>'
414+
# - Else -> './media/<basename>'
415+
if ver and pathname.startswith(f"{self._static_url}ansys{ver}/"):
416+
local_pathname = os.path.dirname(pathname).replace(self._static_url, "./", 1)
433417
result = f"{local_pathname}/{basename}"
434418
target_file = self._output_dir / local_pathname.lstrip("./") / basename
435419
else:
@@ -445,12 +429,12 @@ def _process_file(self, path_in_html: str, pathname: str, inline: bool = False)
445429
def _find_block(self, text: str, start: int, prefix: str, suffix: str) -> tuple[int, int, str]:
446430
"""
447431
Legacy-compatible: return the next [prefix ... suffix] block that contains at least
448-
one asset-like reference. Accept both the literal legacy prefixes and the configured
449-
custom prefixes. Also accept any '/ansys<ver>/' or generic '/ansys<digits>/'.
432+
one asset-like reference. Accept the configured custom prefixes and the dynamic
433+
/ansys<ver>/ root. No hard-coded legacy literals.
450434
"""
451-
# Normalize known prefixes (custom URLs may differ from /static/ and /media/)
452435
custom_static = (self._static_url or "").strip()
453436
custom_media = (self._media_url or "").strip()
437+
ver = str(self._ansys_version) if self._ansys_version is not None else ""
454438

455439
while True:
456440
try:
@@ -462,14 +446,13 @@ def _find_block(self, text: str, start: int, prefix: str, suffix: str) -> tuple[
462446
except ValueError:
463447
return -1, -1, ""
464448
idx2 += len(suffix)
449+
465450
block = text[idx1:idx2]
466451

467452
if (
468-
("/static/" in block)
469-
or ("/media/" in block)
470-
or (custom_static and custom_static in block)
453+
(custom_static and custom_static in block)
471454
or (custom_media and custom_media in block)
472-
or re.search(r"/ansys\d+/", block) is not None
455+
or (ver and f"/ansys{ver}/" in block)
473456
):
474457
return idx1, idx2, block
475458

tests/serverless/test_adr.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1044,18 +1044,19 @@ def test_export_report_as_html(adr_serverless, tmp_path, monkeypatch):
10441044

10451045
adr_serverless.create_template(BasicLayout, name="TestExportReport", parent=None)
10461046

1047-
# Return a fragment (not a full <html>) so wrapper kicks in
1047+
# Use whatever ADR is actually configured to serve as STATIC_URL
1048+
href = f"{adr_serverless.static_url}website/content/site.css"
1049+
10481050
def mock_render_report(self, name, **kwargs):
10491051
# Use the provided static asset path that is known to exist.
10501052
return (
10511053
'<div class="body-content">'
1052-
'<link rel="stylesheet" type="text/css" href="/static/website/content/site.css"/>'
1054+
f'<link rel="stylesheet" type="text/css" href="{href}"/>'
10531055
"</div>"
10541056
)
10551057

10561058
monkeypatch.setattr(ADR, "render_report", mock_render_report)
10571059

1058-
# Act: Call the method under test. The full exporter logic will run.
10591060
output_path = adr_serverless.export_report_as_html(
10601061
output_directory=tmp_path,
10611062
name="TestExportReport",
@@ -1064,7 +1065,7 @@ def mock_render_report(self, name, **kwargs):
10641065
assert output_path == tmp_path / "index.html"
10651066
assert output_path.exists()
10661067

1067-
# Since we flatten unknown /static/* (non-ansys) into ./media/
1068+
# Unknown /static/* (i.e., non-ansys path) should flatten to ./media/<basename>
10681069
assert (tmp_path / "media" / "site.css").exists()
10691070

10701071
html = output_path.read_text(encoding="utf-8")

tests/serverless/test_html_exporter.py

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def _write(p: Path, data: bytes | str):
2020
p.write_text(data, encoding="utf-8")
2121

2222

23-
def _fragment_with_static_ref(path: str = "/static/website/content/site.css") -> str:
23+
def _fragment_with_static_ref(path: str) -> str:
2424
# pure fragment (no <html>), to force the exporter to wrap into a full document
2525
return f'<div class="body-content m-1"><link rel="stylesheet" href="{path}"/></div>'
2626

@@ -40,8 +40,10 @@ def test_wraps_fragment_and_emits_title_and_favicon(adr_serverless, tmp_path: Pa
4040
_write(static_dir / "website/images/favicon.png", b"\x89PNG\x00")
4141
_write(static_dir / "website/content/site.css", "body{overflow:hidden;}")
4242

43+
href = f"{adr_serverless.static_url}website/content/site.css"
44+
4345
exporter = ServerlessReportExporter(
44-
html_content=_fragment_with_static_ref(),
46+
html_content=_fragment_with_static_ref(href),
4547
output_dir=tmp_path / "export1",
4648
static_dir=static_dir,
4749
media_dir=media_dir,
@@ -76,9 +78,12 @@ def test_static_is_flattened_media_and_ansys_tree_preserved(adr_serverless, tmp_
7678
# favicon for wrapper
7779
_write(static_dir / "website/images/favicon.png", b"\x89PNG\x00")
7880

79-
html = _fragment_with_static_ref("/static/website/scripts/plotly.min.js") + textwrap.dedent(
81+
plotly_href = f"{adr_serverless.static_url}website/scripts/plotly.min.js"
82+
ansys_util_src = f"{adr_serverless.static_url}ansys{ver}/nexus/utils/js-test.js"
83+
84+
html = _fragment_with_static_ref(plotly_href) + textwrap.dedent(
8085
f"""
81-
<script src="/static/ansys{ver}/nexus/utils/js-test.js"></script>
86+
<script src="{ansys_util_src}"></script>
8287
"""
8388
)
8489

@@ -112,8 +117,10 @@ def test_favicon_png_is_duplicated_as_ico(adr_serverless, tmp_path: Path):
112117
_write(static_dir / "website/images/favicon.png", b"PNGDATA")
113118
_write(static_dir / "website/content/site.css", "h2{}")
114119

120+
href = f"{adr_serverless.static_url}website/content/site.css"
121+
115122
exporter = ServerlessReportExporter(
116-
html_content=_fragment_with_static_ref(),
123+
html_content=_fragment_with_static_ref(href),
117124
output_dir=tmp_path / "export4",
118125
static_dir=static_dir,
119126
media_dir=media_dir,
@@ -140,10 +147,13 @@ def test_inline_viewer_size_exception_sets_proxy_only(adr_serverless, tmp_path:
140147
_write(media_dir / "preview.png", b"P")
141148
_write(static_dir / "website/images/favicon.png", b"P")
142149

150+
media_src = f"{adr_serverless.media_url}bigfile.stl"
151+
media_preview = f"{adr_serverless.media_url}preview.png"
152+
143153
html = textwrap.dedent(
144-
"""
154+
f"""
145155
<div>
146-
<ansys-nexus-viewer src="/media/bigfile.stl" proxy_img="/media/preview.png"></ansys-nexus-viewer>
156+
<ansys-nexus-viewer src="{media_src}" proxy_img="{media_preview}"></ansys-nexus-viewer>
147157
</div>
148158
"""
149159
)
@@ -174,11 +184,13 @@ def test_scene_js_inlines_binary_block_and_namespaces_filename(adr_serverless, t
174184
ver = str(adr_serverless.ansys_version)
175185

176186
# Babylon scene referencing a binary block; exporter will inline it
177-
_write(static_dir / "website/scenes/guid123.scene.js", "load_binary_block('/media/blob.bin');")
187+
media_blob = f"{adr_serverless.media_url}blob.bin"
188+
_write(static_dir / "website/scenes/guid123.scene.js", f"load_binary_block('{media_blob}');")
178189
_write(media_dir / "blob.bin", b"\x00\x01\x02\x03")
179190
_write(static_dir / "website/images/favicon.png", b"P")
180191

181-
html = '<div><script src="/static/website/scenes/guid123.scene.js"></script></div>'
192+
script_src = f"{adr_serverless.static_url}website/scenes/guid123.scene.js"
193+
html = f'<div><script src="{script_src}"></script></div>'
182194

183195
exporter = ServerlessReportExporter(
184196
html_content=html,
@@ -239,7 +251,8 @@ def test_no_inline_flag_forces_copy_not_data_uri(adr_serverless, tmp_path: Path)
239251
_write(static_dir / "website/images/favicon.png", b"P")
240252
_write(media_dir / "tiny.bin", b"ABCD")
241253

242-
html = '<div><a href="/media/tiny.bin">dl</a></div>'
254+
href = f"{adr_serverless.media_url}tiny.bin"
255+
html = f'<div><a href="{href}">dl</a></div>'
243256

244257
exporter = ServerlessReportExporter(
245258
html_content=html,
@@ -268,7 +281,8 @@ def test_missing_source_file_keeps_original_ref(adr_serverless, tmp_path: Path):
268281
_write(static_dir / "website/images/favicon.png", b"P")
269282

270283
# Refer to a media file that doesn't exist
271-
html = '<div><img src="/media/does_not_exist.xyz"/></div>'
284+
missing_href = f"{adr_serverless.media_url}does_not_exist.xyz"
285+
html = f'<div><img src="{missing_href}"/></div>'
272286

273287
exporter = ServerlessReportExporter(
274288
html_content=html,
@@ -283,4 +297,4 @@ def test_missing_source_file_keeps_original_ref(adr_serverless, tmp_path: Path):
283297

284298
out = (tmp_path / "export9" / "index.html").read_text(encoding="utf-8")
285299
# Exporter leaves the original path when it can't find a local file
286-
assert "/media/does_not_exist.xyz" in out
300+
assert missing_href in out

0 commit comments

Comments
 (0)