91 lines
3.2 KiB
Go
91 lines
3.2 KiB
Go
// Package html renders an internal.AggregatorWriter as a complete,
|
|
// standalone HTML document built around the same table fragment produced by
|
|
// table.Printer.RenderHTML. The document includes the Bootstrap 5.3
|
|
// stylesheet via CDN and embeds the raw table fragment without HTML-escaping
|
|
// it so the result opens directly in a browser.
|
|
package html
|
|
|
|
import (
|
|
_ "embed"
|
|
"fmt"
|
|
"html/template"
|
|
"io"
|
|
"time"
|
|
|
|
"github.com/nmoniz/any2anexoj/internal"
|
|
"github.com/nmoniz/any2anexoj/internal/writing/table"
|
|
)
|
|
|
|
// translator is the contract the HTML writer relies on for localized
|
|
// document text such as the page title. *writing.Localizer satisfies it.
|
|
type translator interface {
|
|
Translate(key string, count int, values map[string]any) string
|
|
}
|
|
|
|
// bootstrapCSSURL is the jsDelivr CDN URL for Bootstrap 5.3.3.
|
|
const bootstrapCSSURL = "https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
|
|
|
|
// bootstrapIntegrity is the official SRI hash for Bootstrap 5.3.3 css.
|
|
const bootstrapIntegrity = "sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"
|
|
|
|
// Writer writes a complete HTML document for an AggregatorWriter.
|
|
type Writer struct {
|
|
output io.Writer
|
|
printer *table.Printer
|
|
translator translator
|
|
lang string
|
|
}
|
|
|
|
// NewWriter builds a Writer that writes a complete HTML document to
|
|
// w. The printer is used purely as a source for the raw <table> fragment via
|
|
// RenderHTML; this writer never invokes Render or any method that writes to
|
|
// printer's own io.Writer. The document title is looked up from the
|
|
// "report_title" translation via tr, so callers never pass a pre-baked title.
|
|
func NewWriter(w io.Writer, printer *table.Printer, tr translator, lang string) *Writer {
|
|
return &Writer{
|
|
output: w,
|
|
printer: printer,
|
|
translator: tr,
|
|
lang: lang,
|
|
}
|
|
}
|
|
|
|
//go:embed document.tmpl
|
|
var documentTemplateBody string
|
|
|
|
// documentTemplate renders the full HTML page from document.tmpl. The table
|
|
// fragment is typed as template.HTML so the template engine emits it
|
|
// verbatim without escaping the angle brackets in the <table> markup.
|
|
var documentTemplate = template.Must(template.New("html").Parse(documentTemplateBody))
|
|
|
|
// Render writes a complete standalone HTML document for aw to the configured
|
|
// output. The same Writer may be reused; each call rebuilds the table
|
|
// fragment from aw so the rendered <tr> count reflects aw's current items and
|
|
// no rows are duplicated across invocations.
|
|
func (hw *Writer) Render(aw *internal.AggregatorWriter) error {
|
|
if hw.printer == nil {
|
|
return fmt.Errorf("html: nil printer")
|
|
}
|
|
fragment := hw.printer.RenderHTML(aw)
|
|
title := hw.translator.Translate("report_title", 1, nil)
|
|
data := struct {
|
|
Lang string
|
|
Title string
|
|
CSSURL string
|
|
Integrity string
|
|
TableFragment template.HTML
|
|
GeneratedAt string
|
|
}{
|
|
Lang: hw.lang,
|
|
Title: title,
|
|
CSSURL: bootstrapCSSURL,
|
|
Integrity: bootstrapIntegrity,
|
|
TableFragment: template.HTML(fragment),
|
|
GeneratedAt: time.Now().UTC().Format(time.RFC3339),
|
|
}
|
|
if err := documentTemplate.Execute(hw.output, data); err != nil {
|
|
return fmt.Errorf("render html document: %w", err)
|
|
}
|
|
return nil
|
|
}
|