// 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/bootstrap@5.3.3/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 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
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 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 }