6e458ae76f
ASPICE 4.0 / ISO 26262 Entwicklungsprozess fuer kleine Teams. Inhalte: - README mit hybrider Format-Strategie (Word + Markdown) - Toolstack (Gitea, Doorstop, Cppcheck, gcov, CppUTest, pandoc) - Markdown-Vorlagen fuer Requirements + Architektur (SA, SWA) - Markdown-Vorlagen fuer formelle Dokumente (PID, PM-Plan, QA-Plan, SWE-Plan, Test-Plan, Reviews, Non-Conformity, MISRA Permits/Records) - Word-Master-Template (slohmaier-doc-template.docx) mit ISO-9001- konformer Document Control, Formatvorlagen, Auto-Verzeichnissen - Build-Scripts (build_word_template.py, generate_word_vorlagen.sh) - gitea-aspice-setup.md, V-Modell-Infografik
584 lines
19 KiB
Python
584 lines
19 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Build the neutral slohmaier Word document template.
|
|
|
|
Output: vorlagen-word/slohmaier-doc-template.docx
|
|
|
|
The template is ISO 9001 / ASPICE-friendly:
|
|
- Cover page with project, doc-ID, version, classification
|
|
- Document Control (Approvals, Revision History, Distribution)
|
|
- Inhaltsverzeichnis / Abbildungsverzeichnis / Tabellenverzeichnis (auto-fields)
|
|
- Custom styles: Heading 1-4, Body, Caption, Code, Note, Warning, Requirement
|
|
- Header/Footer with project + doc-ID + classification + page
|
|
|
|
Run: python3 tools/build_word_template.py
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
from docx import Document
|
|
from docx.enum.style import WD_STYLE_TYPE
|
|
from docx.enum.table import WD_ALIGN_VERTICAL, WD_TABLE_ALIGNMENT
|
|
from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_BREAK
|
|
from docx.oxml import OxmlElement
|
|
from docx.oxml.ns import qn
|
|
from docx.shared import Cm, Pt, RGBColor
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# OXML helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def _qn_set(elem, attr, value):
|
|
elem.set(qn(attr), value)
|
|
|
|
|
|
def add_field(paragraph, instr_text, default_text="<Feld aktualisieren mit F9>"):
|
|
"""Insert a Word field (e.g. TOC, PAGE) into a paragraph."""
|
|
run = paragraph.add_run()
|
|
r_elem = run._r
|
|
|
|
fld_begin = OxmlElement("w:fldChar")
|
|
_qn_set(fld_begin, "w:fldCharType", "begin")
|
|
r_elem.append(fld_begin)
|
|
|
|
instr = OxmlElement("w:instrText")
|
|
_qn_set(instr, "xml:space", "preserve")
|
|
instr.text = instr_text
|
|
r_elem.append(instr)
|
|
|
|
fld_sep = OxmlElement("w:fldChar")
|
|
_qn_set(fld_sep, "w:fldCharType", "separate")
|
|
r_elem.append(fld_sep)
|
|
|
|
default_t = OxmlElement("w:t")
|
|
default_t.text = default_text
|
|
r_elem.append(default_t)
|
|
|
|
fld_end = OxmlElement("w:fldChar")
|
|
_qn_set(fld_end, "w:fldCharType", "end")
|
|
r_elem.append(fld_end)
|
|
|
|
|
|
def set_cell_shading(cell, fill_hex):
|
|
tc_pr = cell._tc.get_or_add_tcPr()
|
|
shd = OxmlElement("w:shd")
|
|
_qn_set(shd, "w:val", "clear")
|
|
_qn_set(shd, "w:color", "auto")
|
|
_qn_set(shd, "w:fill", fill_hex)
|
|
tc_pr.append(shd)
|
|
|
|
|
|
def set_cell_borders(cell, color="808080", size=4):
|
|
tc_pr = cell._tc.get_or_add_tcPr()
|
|
tc_borders = OxmlElement("w:tcBorders")
|
|
for edge in ("top", "left", "bottom", "right"):
|
|
b = OxmlElement(f"w:{edge}")
|
|
_qn_set(b, "w:val", "single")
|
|
_qn_set(b, "w:sz", str(size))
|
|
_qn_set(b, "w:space", "0")
|
|
_qn_set(b, "w:color", color)
|
|
tc_borders.append(b)
|
|
tc_pr.append(tc_borders)
|
|
|
|
|
|
def set_paragraph_border(paragraph, color="808080", size=12, side="left"):
|
|
p_pr = paragraph._p.get_or_add_pPr()
|
|
p_bdr = OxmlElement("w:pBdr")
|
|
b = OxmlElement(f"w:{side}")
|
|
_qn_set(b, "w:val", "single")
|
|
_qn_set(b, "w:sz", str(size))
|
|
_qn_set(b, "w:space", "8")
|
|
_qn_set(b, "w:color", color)
|
|
p_bdr.append(b)
|
|
p_pr.append(p_bdr)
|
|
|
|
|
|
def set_paragraph_shading(paragraph, fill_hex):
|
|
p_pr = paragraph._p.get_or_add_pPr()
|
|
shd = OxmlElement("w:shd")
|
|
_qn_set(shd, "w:val", "clear")
|
|
_qn_set(shd, "w:color", "auto")
|
|
_qn_set(shd, "w:fill", fill_hex)
|
|
p_pr.append(shd)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Styles
|
|
# ---------------------------------------------------------------------------
|
|
|
|
NEUTRAL_GREY = "595959"
|
|
SOFT_GREY = "808080"
|
|
LIGHT_GREY = "F2F2F2"
|
|
ACCENT_DARK = "1F3864"
|
|
NOTE_BG = "E7F0FA"
|
|
NOTE_BORDER = "2E74B5"
|
|
WARN_BG = "FFF4E5"
|
|
WARN_BORDER = "C55A11"
|
|
REQ_BG = "EAEAEA"
|
|
REQ_BORDER = "404040"
|
|
|
|
|
|
def configure_styles(doc):
|
|
styles = doc.styles
|
|
|
|
# --- Normal / Body Text ---
|
|
normal = styles["Normal"]
|
|
normal.font.name = "Calibri"
|
|
normal.font.size = Pt(11)
|
|
normal.font.color.rgb = RGBColor(0x20, 0x20, 0x20)
|
|
normal.paragraph_format.space_after = Pt(6)
|
|
normal.paragraph_format.line_spacing = 1.15
|
|
|
|
# --- Headings ---
|
|
heading_specs = [
|
|
("Heading 1", 18, True, RGBColor(0x1F, 0x38, 0x64), 18, 6),
|
|
("Heading 2", 14, True, RGBColor(0x1F, 0x38, 0x64), 12, 6),
|
|
("Heading 3", 12, True, RGBColor(0x40, 0x40, 0x40), 8, 4),
|
|
("Heading 4", 11, True, RGBColor(0x40, 0x40, 0x40), 6, 4),
|
|
]
|
|
for name, size, bold, color, before, after in heading_specs:
|
|
s = styles[name]
|
|
s.font.name = "Calibri"
|
|
s.font.size = Pt(size)
|
|
s.font.bold = bold
|
|
s.font.color.rgb = color
|
|
s.paragraph_format.space_before = Pt(before)
|
|
s.paragraph_format.space_after = Pt(after)
|
|
s.paragraph_format.keep_with_next = True
|
|
|
|
# --- Title ---
|
|
title = styles["Title"]
|
|
title.font.name = "Calibri"
|
|
title.font.size = Pt(36)
|
|
title.font.bold = True
|
|
title.font.color.rgb = RGBColor(0x1F, 0x38, 0x64)
|
|
|
|
# --- Subtitle ---
|
|
subtitle = styles["Subtitle"]
|
|
subtitle.font.name = "Calibri"
|
|
subtitle.font.size = Pt(16)
|
|
subtitle.font.color.rgb = RGBColor(0x59, 0x59, 0x59)
|
|
|
|
# --- Caption (figure/table caption) ---
|
|
caption = styles["Caption"]
|
|
caption.font.name = "Calibri"
|
|
caption.font.size = Pt(10)
|
|
caption.font.italic = True
|
|
caption.font.color.rgb = RGBColor(0x59, 0x59, 0x59)
|
|
|
|
# --- Code (custom paragraph style) ---
|
|
if "Code" not in [s.name for s in styles]:
|
|
code = styles.add_style("Code", WD_STYLE_TYPE.PARAGRAPH)
|
|
code.base_style = styles["Normal"]
|
|
code.font.name = "Consolas"
|
|
code.font.size = Pt(10)
|
|
code.paragraph_format.left_indent = Cm(0.5)
|
|
code.paragraph_format.space_before = Pt(4)
|
|
code.paragraph_format.space_after = Pt(4)
|
|
|
|
# --- Note (custom paragraph style) ---
|
|
if "Note" not in [s.name for s in styles]:
|
|
note = styles.add_style("Note", WD_STYLE_TYPE.PARAGRAPH)
|
|
note.base_style = styles["Normal"]
|
|
note.font.name = "Calibri"
|
|
note.font.size = Pt(10)
|
|
note.paragraph_format.left_indent = Cm(0.4)
|
|
note.paragraph_format.space_before = Pt(6)
|
|
note.paragraph_format.space_after = Pt(6)
|
|
|
|
# --- Warning ---
|
|
if "Warning" not in [s.name for s in styles]:
|
|
warn = styles.add_style("Warning", WD_STYLE_TYPE.PARAGRAPH)
|
|
warn.base_style = styles["Normal"]
|
|
warn.font.name = "Calibri"
|
|
warn.font.size = Pt(10)
|
|
warn.font.bold = True
|
|
warn.paragraph_format.left_indent = Cm(0.4)
|
|
|
|
# --- Requirement Box ---
|
|
if "Requirement" not in [s.name for s in styles]:
|
|
req = styles.add_style("Requirement", WD_STYLE_TYPE.PARAGRAPH)
|
|
req.base_style = styles["Normal"]
|
|
req.font.name = "Calibri"
|
|
req.font.size = Pt(11)
|
|
req.paragraph_format.left_indent = Cm(0.4)
|
|
req.paragraph_format.space_before = Pt(6)
|
|
req.paragraph_format.space_after = Pt(6)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Page setup, header/footer
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def setup_page(doc):
|
|
for section in doc.sections:
|
|
section.top_margin = Cm(2.5)
|
|
section.bottom_margin = Cm(2.5)
|
|
section.left_margin = Cm(2.5)
|
|
section.right_margin = Cm(2.5)
|
|
section.header_distance = Cm(1.25)
|
|
section.footer_distance = Cm(1.25)
|
|
|
|
|
|
def build_header_footer(doc, doc_id_placeholder="<DOC-ID>", classification="VERTRAULICH"):
|
|
section = doc.sections[0]
|
|
section.different_first_page_header_footer = True
|
|
|
|
# --- Default header (skipped on cover page) ---
|
|
header = section.header
|
|
header_para = header.paragraphs[0]
|
|
header_para.alignment = WD_ALIGN_PARAGRAPH.LEFT
|
|
tabs = header_para.paragraph_format.tab_stops
|
|
tabs.add_tab_stop(Cm(8), WD_ALIGN_PARAGRAPH.CENTER)
|
|
tabs.add_tab_stop(Cm(16), WD_ALIGN_PARAGRAPH.RIGHT)
|
|
r1 = header_para.add_run("<PROJEKT>")
|
|
r1.font.size = Pt(9)
|
|
r1.font.color.rgb = RGBColor(0x59, 0x59, 0x59)
|
|
header_para.add_run("\t")
|
|
r2 = header_para.add_run("<DOKUMENT-TITEL>")
|
|
r2.font.size = Pt(9)
|
|
r2.font.color.rgb = RGBColor(0x59, 0x59, 0x59)
|
|
r2.bold = True
|
|
header_para.add_run("\t")
|
|
r3 = header_para.add_run(doc_id_placeholder)
|
|
r3.font.size = Pt(9)
|
|
r3.font.color.rgb = RGBColor(0x59, 0x59, 0x59)
|
|
|
|
# --- Default footer (skipped on cover page) ---
|
|
footer = section.footer
|
|
footer_para = footer.paragraphs[0]
|
|
footer_para.alignment = WD_ALIGN_PARAGRAPH.LEFT
|
|
f_tabs = footer_para.paragraph_format.tab_stops
|
|
f_tabs.add_tab_stop(Cm(8), WD_ALIGN_PARAGRAPH.CENTER)
|
|
f_tabs.add_tab_stop(Cm(16), WD_ALIGN_PARAGRAPH.RIGHT)
|
|
fr1 = footer_para.add_run("© slohmaier.com")
|
|
fr1.font.size = Pt(9)
|
|
fr1.font.color.rgb = RGBColor(0x59, 0x59, 0x59)
|
|
footer_para.add_run("\t")
|
|
fr2 = footer_para.add_run(classification)
|
|
fr2.font.size = Pt(9)
|
|
fr2.font.color.rgb = RGBColor(0x59, 0x59, 0x59)
|
|
fr2.bold = True
|
|
footer_para.add_run("\t")
|
|
fr3 = footer_para.add_run("Seite ")
|
|
fr3.font.size = Pt(9)
|
|
fr3.font.color.rgb = RGBColor(0x59, 0x59, 0x59)
|
|
add_field(footer_para, "PAGE", "1")
|
|
fr4 = footer_para.add_run(" / ")
|
|
fr4.font.size = Pt(9)
|
|
fr4.font.color.rgb = RGBColor(0x59, 0x59, 0x59)
|
|
add_field(footer_para, "NUMPAGES", "1")
|
|
|
|
# --- First-page header/footer (cover): empty ---
|
|
fp_header = section.first_page_header
|
|
fp_header.paragraphs[0].text = ""
|
|
fp_footer = section.first_page_footer
|
|
fp_footer.paragraphs[0].text = ""
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Content
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def add_cover_page(doc):
|
|
# Top logo placeholder
|
|
p = doc.add_paragraph()
|
|
p.alignment = WD_ALIGN_PARAGRAPH.RIGHT
|
|
r = p.add_run("[LOGO]")
|
|
r.font.size = Pt(10)
|
|
r.font.color.rgb = RGBColor(0x80, 0x80, 0x80)
|
|
|
|
# Vertical space
|
|
for _ in range(8):
|
|
doc.add_paragraph()
|
|
|
|
# Classification banner
|
|
cls_p = doc.add_paragraph()
|
|
cls_p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
cls_r = cls_p.add_run("VERTRAULICH")
|
|
cls_r.font.size = Pt(11)
|
|
cls_r.font.bold = True
|
|
cls_r.font.color.rgb = RGBColor(0xC5, 0x5A, 0x11)
|
|
|
|
# Title
|
|
title_p = doc.add_paragraph(style="Title")
|
|
title_p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
title_p.add_run("<Dokument-Titel>")
|
|
|
|
# Subtitle
|
|
sub_p = doc.add_paragraph(style="Subtitle")
|
|
sub_p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
sub_p.add_run("<Untertitel oder Kurzbeschreibung>")
|
|
|
|
for _ in range(4):
|
|
doc.add_paragraph()
|
|
|
|
# Metadata block
|
|
meta_tbl = doc.add_table(rows=6, cols=2)
|
|
meta_tbl.alignment = WD_TABLE_ALIGNMENT.CENTER
|
|
meta_tbl.autofit = False
|
|
meta_tbl.columns[0].width = Cm(4.5)
|
|
meta_tbl.columns[1].width = Cm(8.5)
|
|
meta_data = [
|
|
("Projekt", "<Projektname>"),
|
|
("Dokument-ID", "<DOC-ID>"),
|
|
("Version", "1.0"),
|
|
("Datum", "YYYY-MM-DD"),
|
|
("Status", "Entwurf"),
|
|
("Klassifikation", "Vertraulich"),
|
|
]
|
|
for i, (k, v) in enumerate(meta_data):
|
|
c1 = meta_tbl.cell(i, 0)
|
|
c1.width = Cm(4.5)
|
|
c1.text = ""
|
|
c1_p = c1.paragraphs[0]
|
|
c1_r = c1_p.add_run(k)
|
|
c1_r.bold = True
|
|
c1_r.font.size = Pt(11)
|
|
set_cell_shading(c1, LIGHT_GREY)
|
|
set_cell_borders(c1, SOFT_GREY)
|
|
|
|
c2 = meta_tbl.cell(i, 1)
|
|
c2.width = Cm(8.5)
|
|
c2.text = ""
|
|
c2_p = c2.paragraphs[0]
|
|
c2_r = c2_p.add_run(v)
|
|
c2_r.font.size = Pt(11)
|
|
set_cell_borders(c2, SOFT_GREY)
|
|
|
|
# Page break
|
|
doc.add_page_break()
|
|
|
|
|
|
def add_document_control(doc):
|
|
doc.add_heading("Dokumentenlenkung", level=1)
|
|
doc.add_paragraph(
|
|
"Diese Seite dokumentiert die formale Lenkung dieses Dokuments gemaess "
|
|
"ISO 9001. Aenderungen werden nur ueber den Freigabeprozess wirksam."
|
|
)
|
|
|
|
doc.add_heading("Freigaben", level=2)
|
|
appr_tbl = doc.add_table(rows=4, cols=4)
|
|
appr_tbl.style = "Light Grid Accent 1"
|
|
hdr = appr_tbl.rows[0].cells
|
|
for i, h in enumerate(["Rolle", "Name", "Unterschrift / Datum", "Bemerkung"]):
|
|
hdr[i].text = ""
|
|
r = hdr[i].paragraphs[0].add_run(h)
|
|
r.bold = True
|
|
set_cell_shading(hdr[i], LIGHT_GREY)
|
|
roles = ["Erstellt von", "Geprueft von", "Freigegeben von"]
|
|
for i, role in enumerate(roles, start=1):
|
|
appr_tbl.rows[i].cells[0].text = role
|
|
for j in (1, 2, 3):
|
|
appr_tbl.rows[i].cells[j].text = ""
|
|
|
|
doc.add_heading("Aenderungshistorie", level=2)
|
|
rev_tbl = doc.add_table(rows=3, cols=4)
|
|
rev_tbl.style = "Light Grid Accent 1"
|
|
rev_hdr = rev_tbl.rows[0].cells
|
|
for i, h in enumerate(["Version", "Datum", "Aenderung", "Autor"]):
|
|
rev_hdr[i].text = ""
|
|
r = rev_hdr[i].paragraphs[0].add_run(h)
|
|
r.bold = True
|
|
set_cell_shading(rev_hdr[i], LIGHT_GREY)
|
|
rev_data = [
|
|
("0.1", "YYYY-MM-DD", "Initialer Entwurf", "<Autor>"),
|
|
("1.0", "YYYY-MM-DD", "Erstfreigabe", "<Autor>"),
|
|
]
|
|
for i, row in enumerate(rev_data, start=1):
|
|
for j, v in enumerate(row):
|
|
rev_tbl.rows[i].cells[j].text = v
|
|
|
|
doc.add_heading("Verteilerkreis", level=2)
|
|
dist_tbl = doc.add_table(rows=3, cols=3)
|
|
dist_tbl.style = "Light Grid Accent 1"
|
|
dist_hdr = dist_tbl.rows[0].cells
|
|
for i, h in enumerate(["Empfaenger", "Rolle", "Organisation"]):
|
|
dist_hdr[i].text = ""
|
|
r = dist_hdr[i].paragraphs[0].add_run(h)
|
|
r.bold = True
|
|
set_cell_shading(dist_hdr[i], LIGHT_GREY)
|
|
for i in range(1, 3):
|
|
for j in range(3):
|
|
dist_tbl.rows[i].cells[j].text = ""
|
|
|
|
doc.add_page_break()
|
|
|
|
|
|
def add_toc_pages(doc):
|
|
doc.add_heading("Inhaltsverzeichnis", level=1)
|
|
p = doc.add_paragraph()
|
|
add_field(
|
|
p,
|
|
'TOC \\o "1-3" \\h \\z \\u',
|
|
"Inhaltsverzeichnis aktualisieren: F9 (rechte Maustaste auf TOC > Felder aktualisieren)",
|
|
)
|
|
doc.add_page_break()
|
|
|
|
doc.add_heading("Abbildungsverzeichnis", level=1)
|
|
p = doc.add_paragraph()
|
|
add_field(
|
|
p,
|
|
'TOC \\h \\z \\c "Abbildung"',
|
|
"Abbildungsverzeichnis aktualisieren: F9",
|
|
)
|
|
doc.add_page_break()
|
|
|
|
doc.add_heading("Tabellenverzeichnis", level=1)
|
|
p = doc.add_paragraph()
|
|
add_field(
|
|
p,
|
|
'TOC \\h \\z \\c "Tabelle"',
|
|
"Tabellenverzeichnis aktualisieren: F9",
|
|
)
|
|
doc.add_page_break()
|
|
|
|
|
|
def add_abbreviations(doc):
|
|
doc.add_heading("Abkuerzungsverzeichnis", level=1)
|
|
tbl = doc.add_table(rows=4, cols=2)
|
|
tbl.style = "Light Grid Accent 1"
|
|
hdr = tbl.rows[0].cells
|
|
for i, h in enumerate(["Abkuerzung", "Bedeutung"]):
|
|
hdr[i].text = ""
|
|
r = hdr[i].paragraphs[0].add_run(h)
|
|
r.bold = True
|
|
set_cell_shading(hdr[i], LIGHT_GREY)
|
|
examples = [
|
|
("ASIL", "Automotive Safety Integrity Level"),
|
|
("ECU", "Electronic Control Unit"),
|
|
("MISRA", "Motor Industry Software Reliability Association"),
|
|
]
|
|
for i, (k, v) in enumerate(examples, start=1):
|
|
tbl.rows[i].cells[0].text = k
|
|
tbl.rows[i].cells[1].text = v
|
|
doc.add_page_break()
|
|
|
|
|
|
def add_main_content(doc):
|
|
# Section 1
|
|
doc.add_heading("1. Einleitung", level=1)
|
|
doc.add_heading("1.1 Zweck", level=2)
|
|
doc.add_paragraph(
|
|
"<Kurzer Absatz: Wozu existiert dieses Dokument? Welche Frage beantwortet es?>"
|
|
)
|
|
doc.add_heading("1.2 Geltungsbereich", level=2)
|
|
doc.add_paragraph("<Welches Produkt, welches Projekt, welche Phase?>")
|
|
doc.add_heading("1.3 Definitionen", level=2)
|
|
doc.add_paragraph(
|
|
"<Spezielle Begriffe, die im Dokument verwendet werden. Allgemeine Abkuerzungen "
|
|
"siehe Abkuerzungsverzeichnis.>"
|
|
)
|
|
doc.add_heading("1.4 Referenzen", level=2)
|
|
ref_tbl = doc.add_table(rows=3, cols=3)
|
|
ref_tbl.style = "Light Grid Accent 1"
|
|
ref_hdr = ref_tbl.rows[0].cells
|
|
for i, h in enumerate(["ID", "Titel", "Version / Ort"]):
|
|
ref_hdr[i].text = ""
|
|
r = ref_hdr[i].paragraphs[0].add_run(h)
|
|
r.bold = True
|
|
set_cell_shading(ref_hdr[i], LIGHT_GREY)
|
|
for i in range(1, 3):
|
|
for j in range(3):
|
|
ref_tbl.rows[i].cells[j].text = ""
|
|
|
|
# Section 2
|
|
doc.add_heading("2. Hauptinhalt", level=1)
|
|
doc.add_paragraph(
|
|
"<Hier beginnt der dokumenttyp-spezifische Inhalt. Die Vorlage liefert Struktur "
|
|
"und Formatvorlagen; konkrete Sektionen kommen aus der jeweiligen Dokumentart "
|
|
"(PID, QA-Plan, SWE-Plan, ...).>"
|
|
)
|
|
|
|
# Demonstrate styles
|
|
doc.add_heading("2.1 Beispiel: Formatvorlagen", level=2)
|
|
doc.add_paragraph(
|
|
"Body-Text in der Vorlage. Schriftart Calibri 11 mit 1,15-fachem Zeilenabstand."
|
|
)
|
|
|
|
code_p = doc.add_paragraph(style="Code")
|
|
code_p.add_run(
|
|
"// Beispiel-Code im Code-Stil (Consolas 10)\n"
|
|
"Status epb_apply(uint8_t force_percent);"
|
|
)
|
|
set_paragraph_shading(code_p, LIGHT_GREY)
|
|
|
|
note_p = doc.add_paragraph(style="Note")
|
|
note_p.add_run("HINWEIS: ").bold = True
|
|
note_p.add_run("Hinweis-Stil fuer ergaenzende Informationen.")
|
|
set_paragraph_border(note_p, NOTE_BORDER, size=18, side="left")
|
|
set_paragraph_shading(note_p, NOTE_BG)
|
|
|
|
warn_p = doc.add_paragraph(style="Warning")
|
|
warn_p.add_run("ACHTUNG: ").bold = True
|
|
warn_p.add_run("Warn-Stil fuer sicherheitsrelevante Hinweise.")
|
|
set_paragraph_border(warn_p, WARN_BORDER, size=18, side="left")
|
|
set_paragraph_shading(warn_p, WARN_BG)
|
|
|
|
req_p = doc.add_paragraph(style="Requirement")
|
|
req_p.add_run("REQ-001: ").bold = True
|
|
req_p.add_run(
|
|
"Requirement-Stil fuer in-line Anforderungen "
|
|
"(meist in Markdown via Doorstop, in Word fuer formelle Berichte)."
|
|
)
|
|
set_paragraph_border(req_p, REQ_BORDER, size=18, side="left")
|
|
set_paragraph_shading(req_p, REQ_BG)
|
|
|
|
doc.add_heading("2.2 Beispiel-Tabelle", level=2)
|
|
tbl = doc.add_table(rows=4, cols=3)
|
|
tbl.style = "Light Grid Accent 1"
|
|
hdr = tbl.rows[0].cells
|
|
for i, h in enumerate(["ID", "Beschreibung", "ASIL"]):
|
|
hdr[i].text = ""
|
|
r = hdr[i].paragraphs[0].add_run(h)
|
|
r.bold = True
|
|
set_cell_shading(hdr[i], LIGHT_GREY)
|
|
rows = [
|
|
("F-01", "Apply bei Fahrer-Anforderung", "D"),
|
|
("F-05", "Release bei Fahrer-Anforderung", "B"),
|
|
("F-10", "HMI: LED-Steuerung", "QM"),
|
|
]
|
|
for i, row in enumerate(rows, start=1):
|
|
for j, v in enumerate(row):
|
|
tbl.rows[i].cells[j].text = v
|
|
|
|
# Caption demo
|
|
cap_p = doc.add_paragraph(style="Caption")
|
|
cap_p.add_run("Tabelle 1: Beispiel-Anforderungen mit ASIL-Klassifikation")
|
|
|
|
# Section 3 — Anhang
|
|
doc.add_heading("3. Anhang", level=1)
|
|
doc.add_paragraph(
|
|
"<Anhaenge: Detail-Berechnungen, Skripte, Zusatz-Tabellen, Glossar.>"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Main
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def build_template(out_path: Path):
|
|
doc = Document()
|
|
setup_page(doc)
|
|
configure_styles(doc)
|
|
build_header_footer(doc)
|
|
|
|
add_cover_page(doc)
|
|
add_document_control(doc)
|
|
add_toc_pages(doc)
|
|
add_abbreviations(doc)
|
|
add_main_content(doc)
|
|
|
|
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
doc.save(out_path)
|
|
print(f"Wrote: {out_path}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
out = Path(sys.argv[1] if len(sys.argv) > 1 else
|
|
Path(__file__).resolve().parent.parent / "vorlagen-word" / "slohmaier-doc-template.docx")
|
|
build_template(out)
|