Generating Interactive Schema Documentation with SPA Mode
The final piece of the LXR workflow is generating interactive HTML documentation from your package. This post covers how SPA generation works, how to configure it fully, and how to deploy it.
Overview
When you have an LXR package, generating documentation is a single command:
lutaml-xsd doc spa urban_function.lxr --mode vue_inlined --output docs.html
The --mode vue_inlined flag produces a single, self-contained HTML file with all CSS and JavaScript embedded. No build step, no server required. Open it in any browser.
The output is a Vue.js 3 single-page application that provides:
- Searchable type browser across all namespaces
- Type detail views with elements, attributes, restrictions
- Namespace navigation sidebar
- Dark/light mode toggle
- Custom branding from your configuration
Architecture
Data Flow: Package to HTML
┌─────────────────────────────────────────────────────────────────────┐
│ SPA GENERATION FLOW │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ urban_function.lxr │
│ (ZIP archive) │
│ │ │
│ ▼ │
│ SchemaRepositoryPackage.load() │
│ │ │
│ ▼ │
│ SchemaSerializer.serialize() │
│ │ │
│ ├── build_metadata() → title, description, appearance │
│ ├── serialize_schemas() → elements, types, attributes │
│ ├── build_namespaces() → prefix → URI mapping │
│ ├── attach_used_by() → reverse references │
│ └── generate_diagrams() → SVG type diagrams │
│ │ │
│ ▼ │
│ { metadata, schemas[], namespaces[], index{} } (JSON) │
│ │ │
│ ▼ │
│ VueInlinedStrategy.generate() │
│ │ │
│ ├── read app.iife.js (pre-built Vue app) │
│ ├── read style.css (compiled styles) │
│ ├── inject schema JSON → window.SCHEMA_DATA │
│ └── build complete HTML → docs.html │
│ │
└─────────────────────────────────────────────────────────────────────┘
Two Output Modes
| Mode | Output | Server Required | Use Case |
|---|---|---|---|
vue_inlined |
Single HTML file | No (works with file://) |
Distribution, offline use |
vue_cdn |
HTML + separate JS/CSS | Yes (CORS requires HTTP) | Production web deployment |
vue_inlined is the default and recommended for most use cases.
Generating Documentation
From the CLI
# Build the LXR package first
lutaml-xsd build from-config config.yml --output urban_function.lxr
# Generate SPA documentation
lutaml-xsd doc spa urban_function.lxr --mode vue_inlined --output docs.html
From the Ruby API
require 'lutaml/xsd'
# Build the package
repository = Lutaml::Xsd::SchemaRepository.from_file('config.yml')
repository.parse.resolve
# Generate the SPA
generator = Lutaml::Xsd::Spa::Generator.new(
repository.to_package,
'urban_function_docs.html',
mode: 'vue_inlined'
)
generator.generate
Appearance Configuration
The appearance section of your config.yml controls the SPA’s visual presentation.
Complete Appearance Reference
appearance:
# ─── Logos ────────────────────────────────────────────────────────────
logos:
square:
light:
path: "images/logo-plateau_logo-square.svg"
url: "https://example.com/logo.svg" # OR use URL instead
dark:
path: "images/logo-plateau_logo-square-dark.svg"
long:
light:
path: "images/logo-plateau_logo-long.svg"
dark:
path: "images/logo-plateau_logo-long-dark.svg"
lutaml_logo:
light:
url: "https://raw.githubusercontent.com/lutaml/branding/main/svg/lutaml-logo_logo-full-light.svg"
dark:
url: "https://raw.githubusercontent.com/lutaml/branding/main/svg/lutaml-logo_logo-full-dark.svg"
# ─── Favicon ──────────────────────────────────────────────────────────
favicon:
- type: "image/png"
sizes: "96x96"
path: "images/favicon/favicon-96x96.png"
- type: "image/svg+xml"
path: "images/favicon/favicon.svg"
- type: "image/x-icon"
path: "images/favicon/favicon.ico"
- type: "image/png"
sizes: "180x180"
path: "images/favicon/apple-touch-icon.png"
- type: "application/manifest+json"
path: "images/favicon/site.webmanifest"
# ─── Colors ────────────────────────────────────────────────────────────
colors:
primary: "#5b9cd4" # Main brand color
primary_light: "#7eb3de" # Hover states
primary_dark: "#3d82bf" # Active states
accent: "#dbab3e" # Secondary accent
background_primary: "#ffffff"
background_secondary: "#f8fafc"
# ─── Typography ────────────────────────────────────────────────────────
typography:
font_family: "system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
mono_font_family: "'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace"
# ─── Layout ────────────────────────────────────────────────────────────
layout:
header_height: "64px"
sidebar_width: "280px"
content_padding: "24px"
# ─── Border Radius ─────────────────────────────────────────────────────
border_radius:
default: "0.25rem"
lg: "0.5rem"
xl: "0.75rem"
full: "9999px"
# ─── Shadows ───────────────────────────────────────────────────────────
shadows:
sm: "0 1px 2px 0 rgba(0, 0, 0, 0.05)"
default: "0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)"
md: "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)"
lg: "0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)"
# ─── Custom CSS ───────────────────────────────────────────────────────
custom_css: |
/* Inject additional styles */
.header-title { font-family: 'Noto Sans JP', sans-serif; }
Color Palette (Full Override)
The SPA supports a complete color palette override:
colors:
primary: "#5b9cd4"
primary_light: "#7eb3de"
primary_dark: "#3d82bf"
secondary: "#64748b"
secondary_light: "#94a3b8"
secondary_dark: "#475569"
accent: "#dbab3e"
background_primary: "#ffffff"
background_secondary: "#f8fafc"
text_primary: "#0f172a"
text_secondary: "#475569"
border: "#e2e8f0"
code_background: "#f1f5f9"
link: "#3b82f6"
link_hover: "#2563eb"
Deployment
Local File (No Server)
With vue_inlined mode, the output is a single HTML file that works via file://:
# Open directly in browser
open urban_function_docs.html
# Or serve locally
python3 -m http.server 8000
# Then visit http://localhost:8000/urban_function_docs.html
GitHub Pages (Recommended)
The PLATEAU i-UR Schema Browser is deployed on GitHub Pages. Here’s the complete workflow:
1. Project Structure
plateau-iur-schema-browser/
├── config.yml # Package + appearance configuration
├── schemas/ # Local schema files
│ ├── i-ur/
│ └── codesynthesis-gml-3.2.1/
├── images/ # Logos and favicons
│ ├── logo-plateau_logo-long.svg
│ └── favicon/
├── .github/
│ └── workflows/
│ └── deploy.yml # CI/CD pipeline
└── urban_function_docs.html # Generated (in .gitignore)
2. GitHub Actions Workflow
# .github/workflows/deploy.yml
name: Build and Deploy Schema Browser
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
jobs:
build-deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
if: ${{ github.ref == 'refs/heads/main' }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Clone lutaml-xsd
uses: actions/checkout@v6
with:
repository: lutaml/lutaml-xsd
ref: main
path: lutaml-xsd
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '4.0'
bundler-cache: true
- name: Build frontend SPA assets
run: |
cd lutaml-xsd/frontend
npm install
npm run build
- name: Build LXR package
run: bundle exec lutaml-xsd build from-config config.yml --output urban_function.lxr
- name: Generate SPA documentation
run: bundle exec lutaml-xsd doc spa urban_function.lxr --mode vue_inlined --output urban_function_docs.html
- name: Prepare artifact
run: |
mkdir -p _site
cp urban_function_docs.html _site/index.html
cp -r images _site/
- name: Upload artifact
uses: actions/upload-pages-artifact@v4
with:
path: _site
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
3. Build Pipeline Steps
┌─────────────────────────────────────────────────────────────────────┐
│ CI/CD PIPELINE │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 1. Checkout code │
│ │ │
│ ▼ │
│ 2. Clone lutaml-xsd (for frontend build) │
│ │ │
│ ▼ │
│ 3. Build frontend assets │
│ npm install → npm run build │
│ (Builds frontend/dist/app.iife.js and style.css) │
│ │ │
│ ▼ │
│ 4. Build LXR package │
│ lutaml-xsd build from-config config.yml │
│ (Resolves all schemas, creates urban_function.lxr) │
│ │ │
│ ▼ │
│ 5. Generate SPA documentation │
│ lutaml-xsd doc spa urban_function.lxr │
│ (Creates urban_function_docs.html) │
│ │ │
│ ▼ │
│ 6. Prepare artifact │
│ Copy HTML to _site/, copy images/ │
│ │ │
│ ▼ │
│ 7. Deploy to GitHub Pages │
│ actions/deploy-pages@v4 │
│ │
└─────────────────────────────────────────────────────────────────────┘
The Generated SPA Features
The resulting SPA at metanorma.github.io/plateau-iur-schema-browser/ provides:
Home View
- Schema cards showing package metadata (title, version, description, authors)
- Type statistics (total types by category)
- Namespace listing with type counts per namespace
- Quick search prominently displayed
Type Browser
- Filterable grid of all types (complex types, simple types, elements)
- Category tabs for filtering by type
- Namespace filter dropdown
Type Detail View
- Overview tab: Documentation, base type, namespace info
- Definition tab: XML-like type structure showing elements/attributes
- Source tab: Raw XSD source
- Diagram tab: SVG visualization of type structure
Navigation
- Sidebar with collapsible namespace tree
- Breadcrumb navigation
- Back/forward browser history support
- Dark/light mode toggle in header
Configuration File Reference
For the complete schema of the configuration file, see lxr-package-config.schema.json.
Summary
SPA generation transforms your LXR package into an interactive documentation site:
- Single command to generate from any LXR package
- Self-contained HTML — no build server required for
vue_inlined - Full appearance customization — logos, colors, typography, favicons
- GitHub Pages deployment via simple CI/CD workflow
- Production-ready — Vue 3, responsive design, dark mode
The PLATEAU i-UR Schema Browser demonstrates a complete deployment — generating documentation for 50+ schemas with custom PLATEAU branding.