fix: inline device images as data URIs in offline SVG export
strip_svg_hyperlinks() removed every href, including on <image> elements that reference device-type photos, leaving devices with images blank in the exported file. Non-anchor hrefs (currently only <image>) are now resolved through an optional image_loader callback and embedded as base64 data URIs instead of being stripped; anchor unwrapping is unchanged. RackElevationOfflineSVGView wires this up to NetBox's MEDIA storage. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,20 +7,27 @@ ET.register_namespace("", SVG_NS)
|
||||
ET.register_namespace("xlink", XLINK_NS)
|
||||
|
||||
_ANCHOR_TAGS = (f"{{{SVG_NS}}}a", "a")
|
||||
_IMAGE_TAGS = (f"{{{SVG_NS}}}image", "image")
|
||||
_HREF_ATTRS = ("href", f"{{{XLINK_NS}}}href")
|
||||
|
||||
|
||||
def strip_svg_hyperlinks(svg_source):
|
||||
"""Return svg_source with every <a> wrapper unwrapped and href/xlink:href removed.
|
||||
def strip_svg_hyperlinks(svg_source, image_loader=None):
|
||||
"""Return svg_source with every <a> wrapper unwrapped and dead links removed.
|
||||
|
||||
NetBox wraps rack elevation devices in hyperlinks pointing at internal,
|
||||
relative object URLs. Those links are dead weight (and broken paths) once
|
||||
the SVG is saved and opened outside of a NetBox session, so this produces
|
||||
a self-contained file with the same visuals but no link markup.
|
||||
the SVG is saved and opened outside of a NetBox session, so this unwraps
|
||||
them while keeping their contents.
|
||||
|
||||
<image> elements reference device photos by the same kind of internal,
|
||||
relative URL. Simply stripping their href would leave the device blank,
|
||||
so image_loader (href -> "data:...;base64,..." or None) is used to embed
|
||||
the image data directly instead. Without an image_loader, image hrefs are
|
||||
left untouched.
|
||||
"""
|
||||
root = ET.fromstring(svg_source)
|
||||
_unwrap_anchors(root)
|
||||
_strip_href_attributes(root)
|
||||
_resolve_hrefs(root, image_loader)
|
||||
return ET.tostring(root, encoding="unicode")
|
||||
|
||||
|
||||
@@ -41,8 +48,20 @@ def _unwrap_anchors(parent):
|
||||
parent.append(child)
|
||||
|
||||
|
||||
def _strip_href_attributes(element):
|
||||
for attr in _HREF_ATTRS:
|
||||
element.attrib.pop(attr, None)
|
||||
def _resolve_hrefs(element, image_loader):
|
||||
if element.tag in _IMAGE_TAGS:
|
||||
if image_loader is not None:
|
||||
for attr in _HREF_ATTRS:
|
||||
href = element.attrib.get(attr)
|
||||
if not href:
|
||||
continue
|
||||
data_uri = image_loader(href)
|
||||
if data_uri:
|
||||
element.attrib[attr] = data_uri
|
||||
else:
|
||||
element.attrib.pop(attr, None)
|
||||
else:
|
||||
for attr in _HREF_ATTRS:
|
||||
element.attrib.pop(attr, None)
|
||||
for child in element:
|
||||
_strip_href_attributes(child)
|
||||
_resolve_hrefs(child, image_loader)
|
||||
|
||||
Reference in New Issue
Block a user