Source code for merton.excel.manifest
"""Office.js add-in manifest XML generation.
Office.js (Excel for the web / Mac / Windows M365) sideloads add-ins via an
XML manifest that describes the add-in's identity, permissions, and the
URLs to the JavaScript / functions / settings files. We generate this
manifest at install time so it points at the user's locally-running
``merton excel server``.
The manifest schema is documented at
https://learn.microsoft.com/en-us/javascript/api/manifest .
"""
from __future__ import annotations
import uuid
from pathlib import Path
_MANIFEST_TEMPLATE = """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<OfficeApp
xmlns="http://schemas.microsoft.com/office/appforoffice/1.1"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:bt="http://schemas.microsoft.com/office/officeappbasictypes/1.0"
xmlns:ov="http://schemas.microsoft.com/office/taskpaneappversionoverrides"
xsi:type="TaskPaneApp">
<Id>{add_in_id}</Id>
<Version>{version}</Version>
<ProviderName>merton</ProviderName>
<DefaultLocale>en-US</DefaultLocale>
<DisplayName DefaultValue="merton" />
<Description DefaultValue="Merton structural credit-risk model — Excel custom functions." />
<IconUrl DefaultValue="{base_url}/static/icon-32.png" />
<HighResolutionIconUrl DefaultValue="{base_url}/static/icon-80.png" />
<SupportUrl DefaultValue="https://merton.readthedocs.io" />
<AppDomains>
<AppDomain>{base_url}</AppDomain>
</AppDomains>
<Hosts>
<Host Name="Workbook" />
</Hosts>
<DefaultSettings>
<SourceLocation DefaultValue="{base_url}/taskpane.html" />
</DefaultSettings>
<Permissions>ReadWriteDocument</Permissions>
<VersionOverrides xmlns="http://schemas.microsoft.com/office/taskpaneappversionoverrides" xsi:type="VersionOverridesV1_0">
<Hosts>
<Host xsi:type="Workbook">
<Runtimes>
<Runtime resid="JsRuntime.Url" lifetime="long" />
</Runtimes>
<AllFormFactors>
<ExtensionPoint xsi:type="CustomFunctions">
<Script>
<SourceLocation resid="JsRuntime.Url" />
</Script>
<Page>
<SourceLocation resid="Functions.Page.Url" />
</Page>
<Metadata>
<SourceLocation resid="Functions.Metadata.Url" />
</Metadata>
<Namespace resid="Functions.Namespace" />
</ExtensionPoint>
</AllFormFactors>
</Host>
</Hosts>
<Resources>
<bt:Urls>
<bt:Url id="JsRuntime.Url" DefaultValue="{base_url}/static/functions.js" />
<bt:Url id="Functions.Page.Url" DefaultValue="{base_url}/static/functions.html" />
<bt:Url id="Functions.Metadata.Url" DefaultValue="{base_url}/functions.json" />
</bt:Urls>
<bt:ShortStrings>
<bt:String id="Functions.Namespace" DefaultValue="MERTON" />
</bt:ShortStrings>
</Resources>
</VersionOverrides>
</OfficeApp>
"""
[docs]
def deterministic_add_in_id(base_url: str) -> str:
"""Generate a stable UUID derived from ``base_url`` so re-installing the
add-in for the same server URL keeps the same identity."""
return str(uuid.uuid5(uuid.NAMESPACE_URL, base_url))
[docs]
def render_manifest(
*,
base_url: str = "http://localhost:8000",
version: str = "1.0.0.0",
add_in_id: str | None = None,
) -> str:
"""Render the Office.js add-in manifest XML.
Parameters
----------
base_url
Where the merton excel server is reachable (default
``http://localhost:8000``).
version
Four-part add-in version (Microsoft requires this exact format).
add_in_id
Optional override. Defaults to a deterministic UUID derived from
``base_url`` so the same server URL always produces the same
manifest identity.
"""
if add_in_id is None:
add_in_id = deterministic_add_in_id(base_url)
return _MANIFEST_TEMPLATE.format(
add_in_id=add_in_id,
version=version,
base_url=base_url.rstrip("/"),
)
[docs]
def write_manifest(
path: str | Path, *, base_url: str = "http://localhost:8000", version: str = "1.0.0.0"
) -> Path:
"""Write the manifest to disk and return the resulting :class:`Path`."""
p = Path(path)
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text(render_manifest(base_url=base_url, version=version))
return p
__all__ = ["deterministic_add_in_id", "render_manifest", "write_manifest"]