Files
breakpilot-compliance/ai-compliance-sdk/internal/iace/document_export_docx_json.go
Sharang Parnerkar 9f96061631 refactor(go): split training/store, ucca/rules, ucca_handlers, document_export under 500 LOC
Each of the four oversized files (training/store.go 1569 LOC, ucca/rules.go 1231 LOC,
ucca_handlers.go 1135 LOC, document_export.go 1101 LOC) is split by logical group
into same-package files, all under the 500-line hard cap. Zero behavior changes,
no renamed exported symbols. Also fixed pre-existing hazard_library split (missing
functions and duplicate UUID keys from a prior session).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 09:29:54 +02:00

162 lines
5.7 KiB
Go

package iace
import (
"archive/zip"
"bytes"
"encoding/json"
"fmt"
"strings"
"time"
)
// ExportDOCX generates a minimal DOCX file containing the CE technical file sections
func (e *DocumentExporter) ExportDOCX(
project *Project,
sections []TechFileSection,
) ([]byte, error) {
if project == nil {
return nil, fmt.Errorf("project must not be nil")
}
var buf bytes.Buffer
zw := zip.NewWriter(&buf)
contentTypes := `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
<Default Extension="xml" ContentType="application/xml"/>
<Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>
</Types>`
if err := addZipEntry(zw, "[Content_Types].xml", contentTypes); err != nil {
return nil, fmt.Errorf("failed to write [Content_Types].xml: %w", err)
}
rels := `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/>
</Relationships>`
if err := addZipEntry(zw, "_rels/.rels", rels); err != nil {
return nil, fmt.Errorf("failed to write _rels/.rels: %w", err)
}
docRels := `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
</Relationships>`
if err := addZipEntry(zw, "word/_rels/document.xml.rels", docRels); err != nil {
return nil, fmt.Errorf("failed to write word/_rels/document.xml.rels: %w", err)
}
docXML := e.buildDocumentXML(project, sections)
if err := addZipEntry(zw, "word/document.xml", docXML); err != nil {
return nil, fmt.Errorf("failed to write word/document.xml: %w", err)
}
if err := zw.Close(); err != nil {
return nil, fmt.Errorf("failed to close ZIP: %w", err)
}
return buf.Bytes(), nil
}
func (e *DocumentExporter) buildDocumentXML(project *Project, sections []TechFileSection) string {
var body strings.Builder
body.WriteString(docxHeading(fmt.Sprintf("CE-Akte: %s", project.MachineName), 1))
metaLines := []string{
fmt.Sprintf("Hersteller: %s", project.Manufacturer),
fmt.Sprintf("Maschinentyp: %s", project.MachineType),
}
if project.CEMarkingTarget != "" {
metaLines = append(metaLines, fmt.Sprintf("CE-Kennzeichnungsziel: %s", project.CEMarkingTarget))
}
metaLines = append(metaLines,
fmt.Sprintf("Status: %s", project.Status),
fmt.Sprintf("Datum: %s", time.Now().Format("02.01.2006")),
)
for _, line := range metaLines {
body.WriteString(docxParagraph(line, false))
}
if project.Description != "" {
body.WriteString(docxParagraph("", false))
body.WriteString(docxParagraph(project.Description, true))
}
for _, section := range sections {
body.WriteString(docxHeading(section.Title, 2))
body.WriteString(docxParagraph(
fmt.Sprintf("Typ: %s | Status: %s | Version: %d",
section.SectionType, string(section.Status), section.Version),
true,
))
if section.Content != "" {
for _, line := range strings.Split(section.Content, "\n") {
body.WriteString(docxParagraph(line, false))
}
} else {
body.WriteString(docxParagraph("(Kein Inhalt vorhanden)", true))
}
}
body.WriteString(docxParagraph("", false))
body.WriteString(docxParagraph(
fmt.Sprintf("Generiert am %s mit BreakPilot AI Compliance SDK",
time.Now().Format("02.01.2006 15:04")),
true,
))
return fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:document xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math"
xmlns:v="urn:schemas-microsoft-com:vml"
xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
xmlns:w10="urn:schemas-microsoft-com:office:word"
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml"
xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup"
xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk"
xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml"
xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape"
mc:Ignorable="w14 wp14">
<w:body>
%s
</w:body>
</w:document>`, body.String())
}
// ExportJSON returns a JSON representation of the project export data
func (e *DocumentExporter) ExportJSON(
project *Project,
sections []TechFileSection,
hazards []Hazard,
assessments []RiskAssessment,
mitigations []Mitigation,
classifications []RegulatoryClassification,
) ([]byte, error) {
if project == nil {
return nil, fmt.Errorf("project must not be nil")
}
payload := map[string]interface{}{
"project": project,
"sections": sections,
"hazards": hazards,
"assessments": assessments,
"mitigations": mitigations,
"classifications": classifications,
"exported_at": time.Now().UTC().Format(time.RFC3339),
"format_version": "1.0",
}
data, err := json.MarshalIndent(payload, "", " ")
if err != nil {
return nil, fmt.Errorf("failed to marshal JSON: %w", err)
}
return data, nil
}