xx2html 0.18.0rc1
Convert XLSX workbooks to single styled HTML file with support for conditional formatting and in-cell images.
Requires Python: <4.0,>=3.10

# xx2html
[![PyPI Version](https://img.shields.io/pypi/v/xx2html.svg)](https://pypi.org/project/xx2html/)
[![CI](https://github.com/gocova/xx2html/actions/workflows/ci.yml/badge.svg)](https://github.com/gocova/xx2html/actions/workflows/ci.yml)
[![License](https://img.shields.io/badge/License-MIT%20%2F%20Apache%202.0-green.svg)](https://opensource.org/licenses/)
[![Buy Me a Coffee](https://img.shields.io/badge/Buy%20Me%20a%20Coffee-Support-orange?logo=buy-me-a-coffee&style=flat-square)](https://buymeacoffee.com/gocova)

`xx2html` converts Excel workbooks (`.xlsx`) into a single HTML file while preserving:

- Cell formatting and styles
- Conditional formatting classes (via `condif2css`)
- Worksheet link behavior
- Embedded worksheet images and in-cell rich-value images

Repository: <https://github.com/gocova/xx2html>  
Issues: <https://github.com/gocova/xx2html/issues>

## Installation

```bash
pip install xx2html
```

## Usage

```python
from xx2html import apply_openpyxl_patches, create_xlsx_transform

# Explicit entrypoint. Patches are also applied automatically on import.
apply_openpyxl_patches()

transform = create_xlsx_transform(
    sheet_html=(
        '<section id="{enc_sheet_name}" data-sheet-name="{sheet_name}">'
        "{table_generated_html}"
        "</section>"
    ),
    sheetname_html='<a class="sheet-nav" href="#{enc_sheet_name}">{sheet_name}</a>',
    index_html=(
        "<!doctype html><html><head>"
        "{fonts_html}{core_css_html}{user_css_html}{generated_css_html}"
        "{generated_incell_css_html}{conditional_css_html}"
        '</head><body data-source="{source_filename}">{sheets_names_generated_html}{sheets_generated_html}'
        "{safari_js}</body></html>"
    ),
    fonts_html="",
    core_css="",
    user_css="",
    safari_js="",
    apply_cf=True,
    max_sheets=3,   # optional preview limit
    max_rows=200,   # optional preview limit per sheet
    max_cols=20,    # optional preview limit per sheet
    raise_on_error=False,
)

ok, err = transform("input.xlsx", "output.html", "en_US")
if not ok:
    raise RuntimeError(err)
```

## API Map

Public API (`xx2html`):

- `apply_openpyxl_patches() -> None`
  - Applies required openpyxl monkey patches (idempotent).
- `create_xlsx_transform(...) -> Callable[[str, str, str], tuple[bool, str | None]]`
  - Returns a transformer callable with signature `(source_xlsx, dest_html, locale)`.
  - Returns `(True, None)` on success, `(False, "<error repr>")` on failure.
  - Optional preview controls:
    - `max_sheets`: convert only the first N visible sheets.
    - `max_rows`: convert only the first N rows per included sheet.
    - `max_cols`: convert only the first N columns per included sheet.
  - Optional error mode:
    - `raise_on_error=True` raises the original exception instead of returning `(False, ...)`.

Core helpers (`xx2html.core`, useful for advanced integrations):

- `get_worksheet_contents(...) -> WorksheetContents`
- `cova_render_table(worksheet_contents) -> str`
- `get_incell_images_refs(archive) -> tuple[dict[str, str], Exception | None]`
- `get_incell_css(...) -> str`
- `apply_cf_styles(html, cf_style_relations) -> str`
- `update_links(html, encoded_sheet_names, ...) -> str`

## Template Placeholders

`sheet_html` requires:
- `{enc_sheet_name}`
- `{sheet_name}`
- `{table_generated_html}`

`sheetname_html` requires:
- `{enc_sheet_name}`
- `{sheet_name}`

`index_html` requires:
- `{sheets_generated_html}`
- `{sheets_names_generated_html}`
- `{source_filename}`
- `{fonts_html}`
- `{core_css_html}`
- `{user_css_html}`
- `{generated_css_html}`
- `{generated_incell_css_html}`
- `{conditional_css_html}`

`index_html` optional:
- `{safari_js}`
  - If omitted while `safari_js` is non-empty, xx2html logs a warning and skips injection.

Generated output also includes:
- `<meta name="generator" content="xx2html {version}">` in `<head>`
- `<!-- Generated by xx2html {version} -->` as the first node in `<body>`

## Monkey Patching Behavior

`xx2html` relies on an `openpyxl` monkey patch to carry rich-value metadata used for in-cell images.

- The patch is applied automatically when `xx2html.core` is imported.
- The explicit API entrypoint is `apply_openpyxl_patches()`.
- `xx2html` validates the `openpyxl` major/minor version before patching.
  - Set `XX2HTML_ALLOW_UNSUPPORTED_OPENPYXL=1` to bypass the guard.

## Development

```bash
pdm sync --group dev --frozen-lockfile
python3 tests/scripts/generate_fixtures.py
pdm run python -m compileall src tests
ruff check src tests
mypy src/xx2html
pdm run pytest
```

## Release

- Stable releases are tag-driven and use SemVer tags: `vMAJOR.MINOR.PATCH` (for example `v1.2.3`).
- Push the tag to GitHub; the `publish` workflow builds from SCM metadata and publishes with PyPI Trusted Publishing.

## License

`xx2html` is dual-licensed under MIT or Apache-2.0.