Initial commit: added Markdown to IHK Chemnitz PDF converter with core structure and features, including YAML config, Goldmark parser, and PDF renderer.
This commit is contained in:
47
README.md
47
README.md
@@ -0,0 +1,47 @@
|
||||
# Markdown to IHK Chemnitz PDF Converter
|
||||
|
||||
Dieses Tool konvertiert Markdown-Dateien in professionelle Projektdokumentationen, die den formalen Anforderungen der **IHK Chemnitz** (IT-Berufe, Stand 2020/2026) entsprechen.
|
||||
|
||||
## Features
|
||||
|
||||
- **IHK-konforme Formatierung**: Automatische Einhaltung von Seitenrändern (30mm links, 40mm Korrekturrand rechts), Schriftarten und Zeilenabständen.
|
||||
- **Paging in Tabellen**:
|
||||
- **Tabellen-Pagination**: Automatisches Wiederholen der Kopfzeile auf Folgeseiten.
|
||||
- **Fortsetzungs-Markierung**: Kennzeichnung von umgebrochenen Tabellen mit "(Fortsetzung)".
|
||||
- **Tabellen-Fußzeile**: Seitennummerierung und Projektdaten in einer sauberen Tabellenstruktur am Seitenende.
|
||||
- **Diagramme via Kroki**: Unterstützung für Mermaid, PlantUML und andere Formate direkt im Markdown.
|
||||
- **Automatisierte Verzeichnisse**:
|
||||
- Inhaltsverzeichnis mit korrekter Paginierung (römisch/arabisch).
|
||||
- Literaturverzeichnis (sortiert).
|
||||
- Anlagenverzeichnis.
|
||||
- **Metadaten**: Einfache Konfiguration von Schüler- und Projektdaten via YAML-Frontmatter.
|
||||
|
||||
## Installation
|
||||
|
||||
Stellen Sie sicher, dass [Go](https://golang.org/) installiert ist.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/ihk-markdown-renderer
|
||||
cd MarkdownToIHKChemnits
|
||||
go mod tidy
|
||||
```
|
||||
|
||||
## Benutzung
|
||||
|
||||
1. Erstellen Sie eine `report.md` mit YAML-Frontmatter (siehe Beispiel).
|
||||
2. Führen Sie den Konverter aus:
|
||||
|
||||
```bash
|
||||
go run . -i report.md -o projektarbeit.pdf
|
||||
```
|
||||
|
||||
## Projektstruktur
|
||||
|
||||
- `main.go`: Einstiegspunkt und Orchestrierung.
|
||||
- `markdown_parser.go`: AST-Parsing und Integration der Goldmark-Extensions.
|
||||
- `pdf_renderer.go`: FPDF-Logik für IHK-Layout, Tabellen-Paging und Fußzeilen.
|
||||
- `config.go`: Konfigurationsstrukturen.
|
||||
|
||||
## Lizenz
|
||||
|
||||
MIT
|
||||
|
||||
15
config.go
Normal file
15
config.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package main
|
||||
|
||||
type Config struct {
|
||||
Student struct {
|
||||
Name string `yaml:"name"`
|
||||
Profession string `yaml:"profession"`
|
||||
Company string `yaml:"company"`
|
||||
Supervisor string `yaml:"supervisor"`
|
||||
} `yaml:"student"`
|
||||
Project struct {
|
||||
Title string `yaml:"title"`
|
||||
Period string `yaml:"period"`
|
||||
Subtitle string `yaml:"subtitle"`
|
||||
} `yaml:"project"`
|
||||
}
|
||||
11
go.mod
Normal file
11
go.mod
Normal file
@@ -0,0 +1,11 @@
|
||||
module MarkdownToIHKChemnits
|
||||
|
||||
go 1.26.1
|
||||
|
||||
require (
|
||||
github.com/go-pdf/fpdf v0.9.0 // indirect
|
||||
github.com/yuin/goldmark v1.8.1 // indirect
|
||||
github.com/yuin/goldmark-meta v1.1.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.3.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
11
go.sum
Normal file
11
go.sum
Normal file
@@ -0,0 +1,11 @@
|
||||
github.com/go-pdf/fpdf v0.9.0 h1:PPvSaUuo1iMi9KkaAn90NuKi+P4gwMedWPHhj8YlJQw=
|
||||
github.com/go-pdf/fpdf v0.9.0/go.mod h1:oO8N111TkmKb9D7VvWGLvLJlaZUQVPM+6V42pp3iV4Y=
|
||||
github.com/yuin/goldmark v1.8.1 h1:id2TeYXe5FpqwLco0Pso4cNM5Z6Okt4g7kDw9QBMhTA=
|
||||
github.com/yuin/goldmark v1.8.1/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc=
|
||||
github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
BIN
img-000.png
Normal file
BIN
img-000.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 52 KiB |
BIN
img-001.png
Normal file
BIN
img-001.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
BIN
img-002.png
Normal file
BIN
img-002.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.6 KiB |
BIN
img-003.png
Normal file
BIN
img-003.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
61
main.go
Normal file
61
main.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
inputMd := flag.String("i", "report.md", "Input Markdown file")
|
||||
outputPdf := flag.String("o", "projektarbeit.pdf", "Output PDF file")
|
||||
flag.Parse()
|
||||
|
||||
config, doc, content, err := ParseMarkdown(*inputMd)
|
||||
if err != nil {
|
||||
log.Fatalf("Error parsing markdown: %v", err)
|
||||
}
|
||||
|
||||
// Pass 1: Dummy render to collect TOC info
|
||||
dummyRenderer := NewIHKRenderer(config)
|
||||
dummyRenderer.RenderTitlePage()
|
||||
dummyRenderer.RenderTOC() // This is the placeholder
|
||||
dummyRenderer.pageOffset = dummyRenderer.pdf.PageNo()
|
||||
RenderAST(doc, content, dummyRenderer)
|
||||
dummyRenderer.RenderBibliography()
|
||||
dummyRenderer.RenderListOfTables()
|
||||
dummyRenderer.RenderAppendices()
|
||||
|
||||
// Pass 2: Real render
|
||||
renderer := NewIHKRenderer(config)
|
||||
renderer.tocItems = dummyRenderer.tocItems
|
||||
renderer.tableItems = dummyRenderer.tableItems
|
||||
|
||||
renderer.RenderTitlePage()
|
||||
renderer.RenderTOC()
|
||||
|
||||
// Main Content
|
||||
err = RenderAST(doc, content, renderer)
|
||||
if err != nil {
|
||||
log.Fatalf("Error rendering PDF: %v", err)
|
||||
}
|
||||
|
||||
// 4. Bibliography
|
||||
renderer.RenderBibliography()
|
||||
|
||||
// List of Tables
|
||||
renderer.RenderListOfTables()
|
||||
|
||||
// 5. Appendices
|
||||
renderer.RenderAppendices()
|
||||
|
||||
// 6. Declaration of Authenticity
|
||||
renderer.RenderDeclarationPage()
|
||||
|
||||
err = renderer.Save(*outputPdf)
|
||||
if err != nil {
|
||||
log.Fatalf("Error saving PDF: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Successfully generated %s\n", *outputPdf)
|
||||
}
|
||||
222
markdown_parser.go
Normal file
222
markdown_parser.go
Normal file
@@ -0,0 +1,222 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/zlib"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"github.com/yuin/goldmark"
|
||||
"github.com/yuin/goldmark-meta"
|
||||
"github.com/yuin/goldmark/ast"
|
||||
"github.com/yuin/goldmark/extension"
|
||||
extast "github.com/yuin/goldmark/extension/ast"
|
||||
"github.com/yuin/goldmark/parser"
|
||||
"github.com/yuin/goldmark/text"
|
||||
"gopkg.in/yaml.v3"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func ParseMarkdown(mdPath string) (Config, ast.Node, []byte, error) {
|
||||
content, err := os.ReadFile(mdPath)
|
||||
if err != nil {
|
||||
return Config{}, nil, nil, err
|
||||
}
|
||||
|
||||
md := goldmark.New(
|
||||
goldmark.WithExtensions(meta.Meta, extension.Table),
|
||||
)
|
||||
|
||||
context := parser.NewContext()
|
||||
doc := md.Parser().Parse(text.NewReader(content), parser.WithContext(context))
|
||||
|
||||
metaData := meta.Get(context)
|
||||
|
||||
// Convert metaData map to Config struct
|
||||
var config Config
|
||||
yamlData, _ := yaml.Marshal(metaData)
|
||||
err = yaml.Unmarshal(yamlData, &config)
|
||||
if err != nil {
|
||||
return Config{}, nil, nil, fmt.Errorf("error parsing metadata: %v", err)
|
||||
}
|
||||
|
||||
return config, doc, content, nil
|
||||
}
|
||||
|
||||
type parserState struct {
|
||||
nextCodeIsAppendix bool
|
||||
appendixTitle string
|
||||
}
|
||||
|
||||
func RenderAST(doc ast.Node, content []byte, r *IHKRenderer) error {
|
||||
r.StartFrontMatter()
|
||||
state := &parserState{}
|
||||
|
||||
return ast.Walk(doc, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||
if !entering {
|
||||
return ast.WalkContinue, nil
|
||||
}
|
||||
|
||||
switch node := n.(type) {
|
||||
case *ast.Heading:
|
||||
if node.Level == 1 && r.numType == NumRoman {
|
||||
title := extractText(node, content)
|
||||
if title != "Vorwort" && title != "Abkürzungsverzeichnis" {
|
||||
r.StartMainBody()
|
||||
}
|
||||
}
|
||||
title := extractText(node, content)
|
||||
r.RenderHeader(node.Level, title)
|
||||
return ast.WalkSkipChildren, nil
|
||||
case *ast.Paragraph:
|
||||
text := extractText(node, content)
|
||||
lines := strings.Split(text, "\n")
|
||||
isMeta := false
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if strings.HasPrefix(line, "@Quelle:") {
|
||||
r.AddSource(strings.TrimPrefix(line, "@Quelle:"))
|
||||
isMeta = true
|
||||
} else if strings.HasPrefix(line, "@Anhang:") {
|
||||
r.AddAppendix(strings.TrimPrefix(line, "@Anhang:"))
|
||||
isMeta = true
|
||||
} else if strings.HasPrefix(line, "@AnhangUML:") {
|
||||
state.nextCodeIsAppendix = true
|
||||
state.appendixTitle = strings.TrimSpace(strings.TrimPrefix(line, "@AnhangUML:"))
|
||||
isMeta = true
|
||||
}
|
||||
}
|
||||
if isMeta {
|
||||
return ast.WalkSkipChildren, nil
|
||||
}
|
||||
r.RenderParagraph(text)
|
||||
return ast.WalkSkipChildren, nil
|
||||
case *ast.FencedCodeBlock:
|
||||
lang := string(node.Language(content))
|
||||
code := extractCode(node, content)
|
||||
if lang == "mermaid" || lang == "plantuml" || lang == "puml" {
|
||||
imgPath, err := RenderDiagramViaKroki(lang, code)
|
||||
if err == nil {
|
||||
if state.nextCodeIsAppendix {
|
||||
r.AddAppendix(state.appendixTitle + " | " + imgPath)
|
||||
state.nextCodeIsAppendix = false
|
||||
} else {
|
||||
r.RenderImage(imgPath, "Diagramm: "+lang)
|
||||
}
|
||||
return ast.WalkSkipChildren, nil
|
||||
}
|
||||
}
|
||||
case *ast.Image:
|
||||
imgPath := string(node.Destination)
|
||||
title := string(node.Title)
|
||||
r.RenderImage(imgPath, title)
|
||||
return ast.WalkSkipChildren, nil
|
||||
case *ast.Blockquote:
|
||||
// Check if first paragraph starts with "Quelle:"
|
||||
first := node.FirstChild()
|
||||
if first != nil {
|
||||
if para, ok := first.(*ast.Paragraph); ok {
|
||||
pText := extractText(para, content)
|
||||
if strings.HasPrefix(pText, "Quelle:") || strings.HasPrefix(pText, "Source:") {
|
||||
sourceText := strings.TrimPrefix(pText, "Quelle:")
|
||||
sourceText = strings.TrimPrefix(sourceText, "Source:")
|
||||
r.AddSource(sourceText)
|
||||
return ast.WalkSkipChildren, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
case *ast.List:
|
||||
// Items will be handled by ListItem
|
||||
case *ast.ListItem:
|
||||
text := extractText(node, content)
|
||||
r.RenderListItem(text, true, 0) // Basic bullet point for now
|
||||
return ast.WalkSkipChildren, nil
|
||||
case *extast.Table:
|
||||
var tableData [][]string
|
||||
for row := node.FirstChild(); row != nil; row = row.NextSibling() {
|
||||
var rowData []string
|
||||
for cell := row.FirstChild(); cell != nil; cell = cell.NextSibling() {
|
||||
rowData = append(rowData, extractText(cell, content))
|
||||
}
|
||||
tableData = append(tableData, rowData)
|
||||
}
|
||||
r.RenderTable(tableData)
|
||||
return ast.WalkSkipChildren, nil
|
||||
}
|
||||
return ast.WalkContinue, nil
|
||||
})
|
||||
}
|
||||
|
||||
func extractText(n ast.Node, content []byte) string {
|
||||
var textStr string
|
||||
for child := n.FirstChild(); child != nil; child = child.NextSibling() {
|
||||
if textNode, ok := child.(*ast.Text); ok {
|
||||
textStr += string(textNode.Segment.Value(content))
|
||||
if textNode.HardLineBreak() || textNode.SoftLineBreak() {
|
||||
textStr += "\n"
|
||||
}
|
||||
} else {
|
||||
textStr += extractText(child, content)
|
||||
}
|
||||
}
|
||||
if textStr == "" {
|
||||
// Fallback for simple nodes
|
||||
return string(n.Text(content))
|
||||
}
|
||||
return textStr
|
||||
}
|
||||
|
||||
func extractCode(n *ast.FencedCodeBlock, content []byte) string {
|
||||
var buf bytes.Buffer
|
||||
for i := 0; i < n.Lines().Len(); i++ {
|
||||
line := n.Lines().At(i)
|
||||
buf.Write(line.Value(content))
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func RenderDiagramViaKroki(lang string, code string) (string, error) {
|
||||
if lang == "puml" {
|
||||
lang = "plantuml"
|
||||
}
|
||||
|
||||
// Kroki encoding: zlib + base64url
|
||||
var b bytes.Buffer
|
||||
w := zlib.NewWriter(&b)
|
||||
w.Write([]byte(code))
|
||||
w.Close()
|
||||
|
||||
encoded := base64.URLEncoding.EncodeToString(b.Bytes())
|
||||
url := fmt.Sprintf("https://kroki.io/%s/png/%s", lang, encoded)
|
||||
|
||||
// Cache based on hash
|
||||
hash := fmt.Sprintf("%x", sha256.Sum256([]byte(code)))
|
||||
cachePath := filepath.Join(os.TempDir(), "ihk_cache_"+hash+".png")
|
||||
|
||||
if _, err := os.Stat(cachePath); err == nil {
|
||||
return cachePath, nil
|
||||
}
|
||||
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return "", fmt.Errorf("kroki error: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
out, err := os.Create(cachePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
_, err = io.Copy(out, resp.Body)
|
||||
return cachePath, err
|
||||
}
|
||||
549
pdf_renderer.go
Normal file
549
pdf_renderer.go
Normal file
@@ -0,0 +1,549 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/go-pdf/fpdf"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type NumberingType int
|
||||
|
||||
const (
|
||||
NumNone NumberingType = iota
|
||||
NumRoman
|
||||
NumArabic
|
||||
)
|
||||
|
||||
type TOCItem struct {
|
||||
Level int
|
||||
Title string
|
||||
PageStr string
|
||||
}
|
||||
|
||||
type TableItem struct {
|
||||
Title string
|
||||
PageStr string
|
||||
}
|
||||
|
||||
type Appendix struct {
|
||||
Title string
|
||||
Path string
|
||||
}
|
||||
|
||||
type IHKRenderer struct {
|
||||
pdf *fpdf.Fpdf
|
||||
config Config
|
||||
numType NumberingType
|
||||
tocItems []TOCItem
|
||||
tableItems []TableItem
|
||||
sources []string
|
||||
appendices []Appendix
|
||||
pageOffset int
|
||||
tableCount int
|
||||
tr func(string) string
|
||||
}
|
||||
|
||||
func NewIHKRenderer(config Config) *IHKRenderer {
|
||||
pdf := fpdf.New("P", "mm", "A4", "")
|
||||
|
||||
// Margins in mm: Top 20, Bottom 25, Left 30, Right 40
|
||||
pdf.SetMargins(30, 20, 40)
|
||||
pdf.SetAutoPageBreak(true, 25)
|
||||
|
||||
r := &IHKRenderer{
|
||||
pdf: pdf,
|
||||
config: config,
|
||||
numType: NumNone,
|
||||
tocItems: make([]TOCItem, 0),
|
||||
tableItems: make([]TableItem, 0),
|
||||
sources: make([]string, 0),
|
||||
appendices: make([]Appendix, 0),
|
||||
tr: pdf.UnicodeTranslatorFromDescriptor(""),
|
||||
}
|
||||
|
||||
pdf.SetFooterFunc(func() {
|
||||
if r.numType == NumNone {
|
||||
return
|
||||
}
|
||||
|
||||
pdf.SetY(-15)
|
||||
pdf.SetFont("Helvetica", "", 10)
|
||||
|
||||
var pageStr string
|
||||
if r.numType == NumRoman {
|
||||
pageStr = toRoman(pdf.PageNo())
|
||||
} else {
|
||||
// Arabic numbering starts at 1 for the main body
|
||||
displayPage := pdf.PageNo() - r.pageOffset
|
||||
if displayPage <= 0 {
|
||||
return
|
||||
}
|
||||
pageStr = strconv.Itoa(displayPage)
|
||||
}
|
||||
|
||||
pdf.CellFormat(0, 10, pageStr, "", 0, "C", false, 0, "")
|
||||
})
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *IHKRenderer) RenderTOC() {
|
||||
r.numType = NumRoman
|
||||
r.pdf.AddPage()
|
||||
r.pdf.SetFont("Helvetica", "B", 14)
|
||||
r.pdf.CellFormat(0, 20, r.tr("Inhaltsverzeichnis"), "", 1, "L", false, 0, "")
|
||||
r.pdf.Ln(5)
|
||||
|
||||
// TOC Header as a table
|
||||
totalWidth, _ := r.pdf.GetPageSize()
|
||||
lm, _, rm, _ := r.pdf.GetMargins()
|
||||
usableWidth := totalWidth - lm - rm
|
||||
|
||||
r.pdf.SetFont("Helvetica", "B", 12)
|
||||
r.pdf.CellFormat(usableWidth-20, 10, r.tr("Inhalt"), "", 0, "L", false, 0, "")
|
||||
r.pdf.CellFormat(20, 10, r.tr("Seite"), "", 1, "R", false, 0, "")
|
||||
r.pdf.Ln(2)
|
||||
|
||||
r.pdf.SetFont("Helvetica", "", 12)
|
||||
_, lineHt := r.pdf.GetFontSize()
|
||||
lineHt *= 1.5
|
||||
|
||||
for _, item := range r.tocItems {
|
||||
indent := float64((item.Level - 1) * 10)
|
||||
r.pdf.SetX(lm + indent)
|
||||
|
||||
title := r.tr(item.Title)
|
||||
pageStr := item.PageStr
|
||||
|
||||
titleWidth := r.pdf.GetStringWidth(title)
|
||||
pageWidth := r.pdf.GetStringWidth(pageStr)
|
||||
|
||||
// Available width for dots
|
||||
availableWidth := usableWidth - indent - titleWidth - pageWidth - 4
|
||||
|
||||
r.pdf.CellFormat(titleWidth+2, lineHt, title, "", 0, "L", false, 0, "")
|
||||
|
||||
if availableWidth > 0 {
|
||||
dots := ""
|
||||
dotWidth := r.pdf.GetStringWidth(".")
|
||||
for i := 0; float64(i)*dotWidth < availableWidth; i++ {
|
||||
dots += "."
|
||||
}
|
||||
r.pdf.CellFormat(availableWidth, lineHt, dots, "", 0, "L", false, 0, "")
|
||||
}
|
||||
|
||||
r.pdf.CellFormat(pageWidth+2, lineHt, pageStr, "", 1, "R", false, 0, "")
|
||||
}
|
||||
}
|
||||
|
||||
func (r *IHKRenderer) RecordHeader(level int, title string) {
|
||||
if r.numType != NumArabic {
|
||||
return // Exclude front matter from TOC per IHK example
|
||||
}
|
||||
|
||||
displayPage := r.pdf.PageNo() - r.pageOffset
|
||||
pageStr := strconv.Itoa(displayPage)
|
||||
if displayPage <= 0 {
|
||||
pageStr = "1" // Fallback
|
||||
}
|
||||
|
||||
r.tocItems = append(r.tocItems, TOCItem{
|
||||
Level: level,
|
||||
Title: title,
|
||||
PageStr: pageStr,
|
||||
})
|
||||
}
|
||||
|
||||
func (r *IHKRenderer) RecordTable(title string) {
|
||||
displayPage := r.pdf.PageNo() - r.pageOffset
|
||||
pageStr := strconv.Itoa(displayPage)
|
||||
if displayPage <= 0 {
|
||||
pageStr = "1"
|
||||
}
|
||||
r.tableItems = append(r.tableItems, TableItem{
|
||||
Title: title,
|
||||
PageStr: pageStr,
|
||||
})
|
||||
}
|
||||
|
||||
func (r *IHKRenderer) RenderListOfTables() {
|
||||
if len(r.tableItems) == 0 {
|
||||
return
|
||||
}
|
||||
r.pdf.AddPage()
|
||||
r.pdf.SetFont("Helvetica", "B", 14)
|
||||
r.pdf.CellFormat(0, 20, r.tr("Tabellenverzeichnis"), "", 1, "L", false, 0, "")
|
||||
r.pdf.Ln(5)
|
||||
|
||||
totalWidth, _ := r.pdf.GetPageSize()
|
||||
lm, _, rm, _ := r.pdf.GetMargins()
|
||||
usableWidth := totalWidth - lm - rm
|
||||
|
||||
r.pdf.SetFont("Helvetica", "B", 12)
|
||||
r.pdf.CellFormat(usableWidth-20, 10, r.tr("Titel"), "", 0, "L", false, 0, "")
|
||||
r.pdf.CellFormat(20, 10, r.tr("Seite"), "", 1, "R", false, 0, "")
|
||||
r.pdf.Ln(2)
|
||||
|
||||
r.pdf.SetFont("Helvetica", "", 12)
|
||||
_, lineHt := r.pdf.GetFontSize()
|
||||
lineHt *= 1.5
|
||||
|
||||
for _, item := range r.tableItems {
|
||||
title := r.tr(item.Title)
|
||||
pageStr := item.PageStr
|
||||
titleWidth := r.pdf.GetStringWidth(title)
|
||||
pageWidth := r.pdf.GetStringWidth(pageStr)
|
||||
availableWidth := usableWidth - titleWidth - pageWidth - 4
|
||||
|
||||
r.pdf.CellFormat(titleWidth+2, lineHt, title, "", 0, "L", false, 0, "")
|
||||
if availableWidth > 0 {
|
||||
dots := ""
|
||||
dotWidth := r.pdf.GetStringWidth(".")
|
||||
for i := 0; float64(i)*dotWidth < availableWidth; i++ {
|
||||
dots += "."
|
||||
}
|
||||
r.pdf.CellFormat(availableWidth, lineHt, dots, "", 0, "L", false, 0, "")
|
||||
}
|
||||
r.pdf.CellFormat(pageWidth+2, lineHt, pageStr, "", 1, "R", false, 0, "")
|
||||
}
|
||||
}
|
||||
|
||||
func (r *IHKRenderer) RenderTitlePage() {
|
||||
r.numType = NumNone
|
||||
|
||||
// Temporary symmetrical margins for title page to ensure visual centering
|
||||
oldLM, oldTM, oldRM, _ := r.pdf.GetMargins()
|
||||
r.pdf.SetMargins(30, 20, 30)
|
||||
|
||||
r.pdf.AddPage()
|
||||
|
||||
r.pdf.SetFont("Helvetica", "B", 16)
|
||||
r.pdf.CellFormat(0, 20, r.tr("Abschlussprüfung zum ..."), "", 1, "C", false, 0, "")
|
||||
r.pdf.CellFormat(0, 10, r.tr(r.config.Student.Profession), "", 1, "C", false, 0, "")
|
||||
|
||||
r.pdf.Ln(30)
|
||||
r.pdf.SetFont("Helvetica", "", 12)
|
||||
r.pdf.CellFormat(0, 10, r.tr("Projektarbeit von"), "", 1, "C", false, 0, "")
|
||||
r.pdf.Ln(5)
|
||||
r.pdf.SetFont("Helvetica", "B", 14)
|
||||
r.pdf.CellFormat(0, 10, r.tr(r.config.Student.Name), "", 1, "C", false, 0, "")
|
||||
|
||||
r.pdf.Ln(30)
|
||||
r.pdf.SetFont("Helvetica", "B", 16)
|
||||
r.pdf.MultiCell(0, 10, r.tr(r.config.Project.Title), "", "C", false)
|
||||
|
||||
r.pdf.SetY(-80)
|
||||
r.pdf.SetFont("Helvetica", "", 12)
|
||||
r.pdf.CellFormat(60, 10, r.tr("Prüfungsperiode:"), "", 0, "L", false, 0, "")
|
||||
r.pdf.CellFormat(0, 10, r.tr(r.config.Project.Period), "", 1, "L", false, 0, "")
|
||||
|
||||
r.pdf.CellFormat(60, 10, r.tr("Ausbildungsbetrieb:"), "", 0, "L", false, 0, "")
|
||||
r.pdf.CellFormat(0, 10, r.tr(r.config.Student.Company), "", 1, "L", false, 0, "")
|
||||
|
||||
r.pdf.CellFormat(60, 10, r.tr("Projektbetreuer:"), "", 0, "L", false, 0, "")
|
||||
r.pdf.CellFormat(0, 10, r.tr(r.config.Student.Supervisor), "", 1, "L", false, 0, "")
|
||||
|
||||
// Restore margins for the rest of the document
|
||||
r.pdf.SetMargins(oldLM, oldTM, oldRM)
|
||||
}
|
||||
|
||||
func (r *IHKRenderer) RenderDeclarationPage() {
|
||||
r.numType = NumNone
|
||||
r.pdf.AddPage()
|
||||
|
||||
r.pdf.SetFont("Helvetica", "B", 14)
|
||||
r.pdf.CellFormat(0, 20, r.tr("Erklärung"), "", 1, "C", false, 0, "")
|
||||
r.pdf.Ln(10)
|
||||
|
||||
r.pdf.SetFont("Helvetica", "", 12)
|
||||
text := fmt.Sprintf("Ich versichere durch meine Unterschrift, dass ich diese Projektarbeit mit dem Thema „%s“ selbstständig, ohne fremde Hilfe angefertigt, alle Stellen, die ich wörtlich oder annähernd wörtlich aus Veröffentlichungen entnommen, als solche kenntlich gemacht und mich auch keiner anderen als der angegebenen Literatur oder sonstiger Hilfsmittel bedient habe. Die Projektarbeit hat in dieser oder ähnlicher Form weder der Industrie- und Handelskammer Chemnitz noch einer anderen Prüfungsinstitution vorgelegen.", r.config.Project.Title)
|
||||
|
||||
r.pdf.MultiCell(0, 7.5, r.tr(text), "", "J", false)
|
||||
|
||||
r.pdf.Ln(20)
|
||||
r.pdf.CellFormat(0, 10, r.tr("Ort, Datum, Unterschrift (mit Vor- und Nachnamen)"), "", 1, "L", false, 0, "")
|
||||
}
|
||||
|
||||
func (r *IHKRenderer) RenderHeader(level int, title string) {
|
||||
if level == 1 {
|
||||
r.pdf.AddPage() // New page for major sections? Maybe optional.
|
||||
}
|
||||
r.RecordHeader(level, title)
|
||||
r.pdf.SetFont("Helvetica", "B", 14)
|
||||
r.pdf.Ln(5)
|
||||
r.pdf.CellFormat(0, 10, r.tr(title), "", 1, "L", false, 0, "")
|
||||
r.pdf.Ln(2)
|
||||
}
|
||||
|
||||
func (r *IHKRenderer) RenderParagraph(text string) {
|
||||
r.pdf.SetFont("Helvetica", "", 12)
|
||||
// Line height for 12pt is 12pt * 1.5 = 18pt.
|
||||
// 18pt is approx 6.35mm.
|
||||
r.pdf.MultiCell(0, 7.5, r.tr(text), "", "J", false)
|
||||
r.pdf.Ln(4)
|
||||
}
|
||||
|
||||
func (r *IHKRenderer) RenderListItem(text string, bullet bool, index int) {
|
||||
r.pdf.SetFont("Helvetica", "", 12)
|
||||
prefix := "• "
|
||||
if !bullet {
|
||||
prefix = strconv.Itoa(index) + ". "
|
||||
}
|
||||
|
||||
currentX := r.pdf.GetX()
|
||||
r.pdf.SetX(currentX + 10)
|
||||
r.pdf.MultiCell(0, 7.5, r.tr(prefix+text), "", "J", false)
|
||||
r.pdf.SetX(currentX)
|
||||
}
|
||||
|
||||
func (r *IHKRenderer) RenderTable(data [][]string) {
|
||||
if len(data) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
r.tableCount++
|
||||
tableTitle := fmt.Sprintf("Tab. %d", r.tableCount)
|
||||
r.RecordTable(tableTitle)
|
||||
|
||||
r.pdf.SetFont("Helvetica", "B", 10)
|
||||
header := data[0]
|
||||
|
||||
// Calculate column widths
|
||||
numCols := len(header)
|
||||
totalWidth, _ := r.pdf.GetPageSize()
|
||||
lm, _, rm, _ := r.pdf.GetMargins()
|
||||
usableWidth := totalWidth - lm - rm
|
||||
colWidth := usableWidth / float64(numCols)
|
||||
|
||||
// Function to render header with optional "(Fortsetzung)"
|
||||
renderHeader := func(continued bool) {
|
||||
r.pdf.SetFont("Helvetica", "B", 10)
|
||||
title := tableTitle
|
||||
if continued {
|
||||
title += " (Fortsetzung)"
|
||||
}
|
||||
r.pdf.CellFormat(0, 10, r.tr(title), "", 1, "L", false, 0, "")
|
||||
|
||||
r.pdf.SetFillColor(230, 230, 230)
|
||||
for _, col := range header {
|
||||
r.pdf.CellFormat(colWidth, 10, r.tr(col), "1", 0, "C", true, 0, "")
|
||||
}
|
||||
r.pdf.Ln(-1)
|
||||
}
|
||||
|
||||
renderHeader(false)
|
||||
|
||||
r.pdf.SetFont("Helvetica", "", 10)
|
||||
r.pdf.SetFillColor(255, 255, 255)
|
||||
|
||||
for i := 1; i < len(data); i++ {
|
||||
row := data[i]
|
||||
|
||||
// Check for page break
|
||||
_, pageH := r.pdf.GetPageSize()
|
||||
_, _, _, bm := r.pdf.GetMargins()
|
||||
if r.pdf.GetY()+10 > pageH-bm {
|
||||
r.pdf.AddPage()
|
||||
renderHeader(true)
|
||||
r.pdf.SetFont("Helvetica", "", 10)
|
||||
}
|
||||
|
||||
for _, col := range row {
|
||||
r.pdf.CellFormat(colWidth, 10, r.tr(col), "1", 0, "L", false, 0, "")
|
||||
}
|
||||
r.pdf.Ln(-1)
|
||||
}
|
||||
r.pdf.Ln(5)
|
||||
}
|
||||
|
||||
func (r *IHKRenderer) RenderImage(path string, caption string) {
|
||||
r.pdf.Ln(5)
|
||||
|
||||
info := r.pdf.GetImageInfo(path)
|
||||
if info == nil {
|
||||
// Try to register it first
|
||||
r.pdf.RegisterImageOptions(path, fpdf.ImageOptions{ReadDpi: true})
|
||||
info = r.pdf.GetImageInfo(path)
|
||||
}
|
||||
|
||||
if info == nil {
|
||||
r.pdf.CellFormat(0, 10, r.tr("[Fehler beim Laden des Bildes: "+path+"]"), "1", 1, "C", false, 0, "")
|
||||
return
|
||||
}
|
||||
|
||||
// Calculate dimensions in mm
|
||||
// 1 point = 0.352778 mm
|
||||
imgW := info.Width() * 0.352778
|
||||
imgH := info.Height() * 0.352778
|
||||
|
||||
maxWidth := 140.0
|
||||
displayW := imgW
|
||||
displayH := imgH
|
||||
|
||||
if displayW > maxWidth {
|
||||
ratio := maxWidth / displayW
|
||||
displayW = maxWidth
|
||||
displayH = displayH * ratio
|
||||
}
|
||||
|
||||
// Check if we need a new page
|
||||
_, pageH := r.pdf.GetPageSize()
|
||||
_, _, _, bottomMargin := r.pdf.GetMargins()
|
||||
currentY := r.pdf.GetY()
|
||||
|
||||
if currentY+displayH+15 > pageH-bottomMargin {
|
||||
r.pdf.AddPage()
|
||||
currentY = r.pdf.GetY()
|
||||
}
|
||||
|
||||
// Center horizontally
|
||||
posX := 30.0 + (maxWidth-displayW)/2
|
||||
|
||||
r.pdf.ImageOptions(path, posX, currentY, displayW, displayH, false, fpdf.ImageOptions{ReadDpi: true}, 0, "")
|
||||
|
||||
// Move Y after the image
|
||||
r.pdf.SetY(currentY + displayH + 2)
|
||||
|
||||
if caption != "" {
|
||||
r.pdf.SetFont("Helvetica", "I", 10)
|
||||
r.pdf.CellFormat(0, 10, r.tr(caption), "", 1, "C", false, 0, "")
|
||||
}
|
||||
r.pdf.Ln(5)
|
||||
}
|
||||
|
||||
func (r *IHKRenderer) StartMainBody() {
|
||||
r.numType = NumArabic
|
||||
// The current page is the last Roman page
|
||||
// So the next page will be display page 1
|
||||
r.pageOffset = r.pdf.PageNo()
|
||||
}
|
||||
|
||||
func (r *IHKRenderer) StartFrontMatter() {
|
||||
r.numType = NumRoman
|
||||
}
|
||||
|
||||
func (r *IHKRenderer) RenderBibliography() {
|
||||
if len(r.sources) == 0 {
|
||||
return
|
||||
}
|
||||
// RenderHeader(1, ...) already adds a page
|
||||
r.RenderHeader(1, "Literaturverzeichnis")
|
||||
r.pdf.Ln(5)
|
||||
|
||||
r.pdf.SetFont("Helvetica", "", 12)
|
||||
|
||||
// IHK Rule 2.8.1: Sort by author name
|
||||
sort.Strings(r.sources)
|
||||
|
||||
for _, source := range r.sources {
|
||||
r.pdf.MultiCell(0, 7.5, r.tr("- "+source), "", "J", false)
|
||||
r.pdf.Ln(2)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *IHKRenderer) RenderAppendices() {
|
||||
if len(r.appendices) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// RenderHeader(1, ...) already adds a page
|
||||
r.RenderHeader(1, "Anhang")
|
||||
r.pdf.Ln(5)
|
||||
|
||||
r.pdf.SetFont("Helvetica", "B", 12)
|
||||
r.pdf.CellFormat(0, 10, r.tr("Anlagenverzeichnis"), "", 1, "L", false, 0, "")
|
||||
r.pdf.SetFont("Helvetica", "", 12)
|
||||
for i, app := range r.appendices {
|
||||
r.pdf.CellFormat(0, 10, r.tr(fmt.Sprintf("Anlage %d: %s", i+1, app.Title)), "", 1, "L", false, 0, "")
|
||||
}
|
||||
|
||||
for i, app := range r.appendices {
|
||||
r.pdf.AddPage()
|
||||
r.pdf.SetFont("Helvetica", "B", 12)
|
||||
r.pdf.CellFormat(0, 10, r.tr(fmt.Sprintf("Anlage %d: %s", i+1, app.Title)), "", 1, "L", false, 0, "")
|
||||
r.pdf.Ln(5)
|
||||
|
||||
info := r.pdf.GetImageInfo(app.Path)
|
||||
if info == nil {
|
||||
r.pdf.RegisterImageOptions(app.Path, fpdf.ImageOptions{ReadDpi: true})
|
||||
info = r.pdf.GetImageInfo(app.Path)
|
||||
}
|
||||
|
||||
if info != nil {
|
||||
imgW := info.Width() * 0.352778
|
||||
imgH := info.Height() * 0.352778
|
||||
|
||||
maxWidth := 140.0
|
||||
// Scale to full width
|
||||
displayW := maxWidth
|
||||
ratio := displayW / imgW
|
||||
displayH := imgH * ratio
|
||||
|
||||
// Check if it fits on page height, if not, scale down
|
||||
_, pageH := r.pdf.GetPageSize()
|
||||
_, _, _, bottomMargin := r.pdf.GetMargins()
|
||||
availableH := pageH - r.pdf.GetY() - bottomMargin - 10
|
||||
|
||||
if displayH > availableH {
|
||||
ratio := availableH / displayH
|
||||
displayH = availableH
|
||||
displayW = displayW * ratio
|
||||
}
|
||||
|
||||
posX := 30.0 + (140.0-displayW)/2
|
||||
r.pdf.ImageOptions(app.Path, posX, r.pdf.GetY(), displayW, displayH, false, fpdf.ImageOptions{ReadDpi: true}, 0, "")
|
||||
} else {
|
||||
r.pdf.CellFormat(0, 10, r.tr("[Bild konnte nicht geladen werden]"), "1", 1, "C", false, 0, "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *IHKRenderer) AddAppendix(titlePath string) {
|
||||
parts := strings.Split(titlePath, "|")
|
||||
if len(parts) < 2 {
|
||||
return
|
||||
}
|
||||
title := strings.TrimSpace(parts[0])
|
||||
path := strings.TrimSpace(parts[1])
|
||||
|
||||
r.appendices = append(r.appendices, Appendix{Title: title, Path: path})
|
||||
}
|
||||
|
||||
func (r *IHKRenderer) AddSource(source string) {
|
||||
// Normalize and store
|
||||
source = strings.TrimSpace(source)
|
||||
if source == "" {
|
||||
return
|
||||
}
|
||||
|
||||
// Prevent duplicates
|
||||
for _, s := range r.sources {
|
||||
if s == source {
|
||||
return
|
||||
}
|
||||
}
|
||||
r.sources = append(r.sources, source)
|
||||
}
|
||||
|
||||
func (r *IHKRenderer) Save(filename string) error {
|
||||
return r.pdf.OutputFileAndClose(filename)
|
||||
}
|
||||
|
||||
func toRoman(n int) string {
|
||||
if n <= 0 {
|
||||
return ""
|
||||
}
|
||||
values := []int{1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1}
|
||||
symbols := []string{"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"}
|
||||
|
||||
res := ""
|
||||
for i := 0; i < len(values); i++ {
|
||||
for n >= values[i] {
|
||||
n -= values[i]
|
||||
res += symbols[i]
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
BIN
projektarbeit.pdf
Normal file
BIN
projektarbeit.pdf
Normal file
Binary file not shown.
84
report.md
Normal file
84
report.md
Normal file
@@ -0,0 +1,84 @@
|
||||
---
|
||||
student:
|
||||
name: "Max Mustermann"
|
||||
profession: "Fachinformatiker Fachrichtung Anwendungsentwicklung"
|
||||
company: "Musterfirma GmbH"
|
||||
supervisor: "Sabine Supervisor"
|
||||
project:
|
||||
title: "Entwicklung eines Markdown-zu-IHK-Konverters"
|
||||
subtitle: "Projektdokumentation zur Abschlussprüfung"
|
||||
period: "Frühjahr 2026"
|
||||
---
|
||||
|
||||
# Vorwort
|
||||
|
||||
Dieses Projekt entstand im Rahmen der Abschlussprüfung...
|
||||
|
||||
# 1. Problemstellung
|
||||
|
||||
## 1.1 Ausgangslage
|
||||
Aktuell müssen IHK-Dokumentationen mühsam in Word formatiert werden, was fehleranfällig ist und viel Zeit kostet. Besonders schwierig ist die Einhaltung der Formvorgaben für Umlaute wie Ä, Ö, Ü und das Eszett ß.
|
||||
|
||||
## 1.2 Zielsetzung
|
||||
Ziel ist ein Go-Tool, das Markdown in PDF umwandelt und dabei alle formalen Anforderungen der IHK Chemnitz erfüllt. Es soll die Prüfungsvorbereitung erleichtern und die Qualität der Dokumente erhöhen.
|
||||
|
||||
# 2. Projektablauf
|
||||
|
||||
## 2.1 Planung
|
||||
Die Planung umfasst die Analyse der IHK-Vorgaben und das Design der Software-Architektur.
|
||||
|
||||
### Architektur-Übersicht (Mermaid)
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Markdown] --> B(Go Parser)
|
||||
B --> C{Metadata?}
|
||||
C -->|Ja| D[Config]
|
||||
C -->|Nein| E[Default]
|
||||
D --> F[PDF Renderer]
|
||||
E --> F
|
||||
F --> G[IHK PDF]
|
||||
```
|
||||
|
||||
### Klassen-Diagramm (PlantUML)
|
||||
```puml
|
||||
@startuml
|
||||
class IHKRenderer {
|
||||
+RenderTOC()
|
||||
+RenderBibliography()
|
||||
+RenderAppendices()
|
||||
}
|
||||
IHKRenderer o-- TOCItem
|
||||
IHKRenderer o-- Appendix
|
||||
@enduml
|
||||
```
|
||||
|
||||
## 2.2 Realisierung
|
||||
Die Realisierung erfolgt in Go unter Verwendung von `goldmark` und `fpdf`.
|
||||
|
||||
| Tool | Zweck |
|
||||
|------|-------|
|
||||
| Go | Programmiersprache |
|
||||
| Goldmark | Markdown Parser |
|
||||
| FPDF | PDF Renderer |
|
||||
|
||||
@Quelle: Goldmark Documentation, https://github.com/yuin/goldmark, 2024
|
||||
> Quelle: Go-PDF/Fpdf Documentation, https://github.com/go-pdf/fpdf, 2025
|
||||
|
||||
# 3. Zusammenfassung
|
||||
|
||||
Das Tool ermöglicht eine effiziente Erstellung von Dokumentationen unter Einhaltung aller Formatvorgaben.
|
||||
|
||||
@Quelle: IHK Chemnitz, Hinweise zur Erarbeitung der Dokumentation, 2020
|
||||
|
||||
@Anhang: Architektur Diagramm | test.png
|
||||
@Anhang: Datenbank Schema | test.png
|
||||
|
||||
@AnhangUML: Sequenzdiagramm Generierung
|
||||
```puml
|
||||
@startuml
|
||||
User -> Generator: Markdown
|
||||
Generator -> Kroki: Code
|
||||
Kroki -> Generator: PNG
|
||||
Generator -> PDF: Embed
|
||||
@enduml
|
||||
```
|
||||
98
toc_pages.txt
Normal file
98
toc_pages.txt
Normal file
@@ -0,0 +1,98 @@
|
||||
Hinweise zur Erarbeitung der Dokumentation über die Projektarbeit
|
||||
für die Abschlussprüfung in IT-Berufen
|
||||
|
||||
1 VORBEMERKUNGEN
|
||||
In einer fachübergreifenden Projektarbeit soll nachgewiesen werden,
|
||||
eine komplexe Problemstellung der betrieblichen Praxis erfassen,
|
||||
darstellen, beurteilen und lösen zu können. Der Rahmen der The-
|
||||
menstellungen ergibt sich aus den Prüfungsanforderungen. Sie muss
|
||||
die betriebliche Praxis des Prüfungsteilnehmers berücksichtigen. Bei
|
||||
der Bearbeitung Ihrer Projektarbeit (PJA) übernehmen Sie die Rolle
|
||||
eines Mitarbeiters im IT-Bereich, der nicht nur Lösungsvorschläge in
|
||||
diesem Umfeld erarbeitet, sondern diese auch gegenüber einem
|
||||
Fachgremium (das auch die Geschäftsleitung oder der Kunde sein
|
||||
kann) zu vertreten hat (Präsentation/ Fachgespräch). Dabei sollen
|
||||
Sie nicht nur Ihre neu hinzugewonnenen technischen und kaufmän-
|
||||
nischen Kenntnisse anwenden, sondern auch durch Ihre Energie,
|
||||
Kreativität und Ideen unter Beweis stellen, dass Sie über die erfor-
|
||||
derliche Handlungskompetenz verfügen, um Ihr Team effektiver und
|
||||
das gesamte Unternehmen wettbewerbsfähiger zu machen.
|
||||
|
||||
Für die Gestaltung der PJA gibt es inzwischen reichlich Informations-
|
||||
schriften und Handreichungen. Aus den langjährigen Prüfungserfah-
|
||||
rungen der Prüfungsausschüsse der IHK Chemnitz ergeben sich As-
|
||||
pekte, aus denen sich einige Ratschläge ableiten.
|
||||
|
||||
Mit diesen Hinweisen will die IHK Chemnitz auf diese Belange ein-
|
||||
gehen und damit zur allgemeinen Information der Prüfungsteilneh-
|
||||
mer beitragen.
|
||||
2 AUFBAU EINER PROJEKTARBEIT
|
||||
2.1 BESTANDTEILE
|
||||
Übersicht Eine PJA sollte sich aus nachfolgenden Elementen zusam-
|
||||
mensetzen:
|
||||
|
||||
1 Titelblatt1
|
||||
|
||||
2 Inhaltsverzeichnis2
|
||||
|
||||
3 Ggf. Abkürzungsverzeichnis2
|
||||
|
||||
4 Vorwort/ Einleitung2
|
||||
Textteil (Problemstellung, betriebliche/ kundengerechte
|
||||
5
|
||||
Dokumentation der Ergebnisse, Zusammenfassung inkl.
|
||||
Zeitplanung)3
|
||||
6 Literaturverzeichnis3
|
||||
|
||||
7 Ggf. Abbildungs-/ Tabellenverzeichnis3
|
||||
|
||||
8 Ggf. Anlagenverzeichnis3
|
||||
|
||||
9 Ggf. Glossar3
|
||||
|
||||
10 Ggf. Stichwortverzeichnis3
|
||||
|
||||
11 Erklärung4
|
||||
|
||||
Tab. 1: Komponenten einer Projektarbeit
|
||||
2.2 TITELBLATT
|
||||
Übersicht Das Titelblatt enthält alle für diesen Prüfungsteil erforderlichen Da-
|
||||
ten.
|
||||
|
||||
|
||||
|
||||
|
||||
V orl ag e
|
||||
|
||||
Abb. 1: Tit el bla tt der Proj ekt ar b eit
|
||||
Abschlussprüfung zum
|
||||
|
||||
|
||||
Projektarbeit von
|
||||
|
||||
|
||||
Max Mustermann
|
||||
|
||||
|
||||
Thema der Projektarbeit
|
||||
|
||||
|
||||
|
||||
|
||||
Prüfungsperiode:
|
||||
|
||||
Ausbildungsbetrieb
|
||||
(bzw. Praktikumsbetrieb):
|
||||
|
||||
Projektbetreuer
|
||||
|
||||
|
||||
|
||||
|
||||
2.3 INHALTSVERZEICHNIS
|
||||
Inhalt Das Inhaltsverzeichnis zeigt die Gliederung der PJA und muss ent-
|
||||
sprechend der DIN 5008 durchnummeriert und mit Seitenangaben
|
||||
versehen werden. Es sollte das Thema differenziert aufschlüsseln,
|
||||
logisch aufgebaut, deutlich formuliert und übersichtlich dargestellt
|
||||
werden.
|
||||
|
||||
Reference in New Issue
Block a user