Merge pull request #41 from eSlider/fix/put-txt-fixed-width
Release / GoReleaser (push) Skipped
Release Please / Release Please (push) Skipped
Tests / Secret scan (gitleaks) (push) Failing after 4s
Tests / Test (Go 1.25) (push) Successful in 20s
Tests / Test (Go stable) (push) Successful in 27s

fix(docs): fixed-width txt as monospace code block
This commit is contained in:
2026-08-28 09:59:59 +01:00
committed by GitHub
co-authored by GitHub
2 changed files with 53 additions and 4 deletions
+34 -4
View File
@@ -101,11 +101,14 @@ func (t Tools) TXTToDOCX(txtPath, docxPath string) error {
return t.MDToDOCX(tmpMD, docxPath)
}
// TxtToMarkdown converts plain text to Markdown that preserves line breaks in DOCX output.
// Single newlines become hard breaks; blank lines stay paragraph separators.
// TxtToMarkdown converts plain text to Markdown for DOCX output.
// Prose text: each line is a hard break. Fixed-width extracts (INE, pdftotext -layout):
// wrapped in a fenced code block (monospace, columns preserved).
func TxtToMarkdown(content string) string {
content = strings.ReplaceAll(content, "\r\n", "\n")
content = strings.ReplaceAll(content, "\r", "\n")
content = normalizeTxtNewlines(content)
if isFixedWidthTxt(content) {
return "```\n" + content + "\n```\n"
}
var b strings.Builder
for _, line := range strings.Split(content, "\n") {
if strings.TrimSpace(line) == "" {
@@ -118,6 +121,33 @@ func TxtToMarkdown(content string) string {
return b.String()
}
func normalizeTxtNewlines(content string) string {
content = strings.ReplaceAll(content, "\r\n", "\n")
return strings.ReplaceAll(content, "\r", "\n")
}
// isFixedWidthTxt detects pdftotext -layout style extracts (many indented/spaced columns).
func isFixedWidthTxt(content string) bool {
lines := strings.Split(content, "\n")
if len(lines) < 8 {
return false
}
indented, long := 0, 0
for _, line := range lines {
if strings.TrimSpace(line) == "" {
continue
}
if len(line) >= 72 {
long++
}
if len(line) > 0 && (line[0] == ' ' || line[0] == '\t') {
indented++
}
}
n := len(lines)
return indented*100/n >= 20 || (long >= 5 && indented*100/n >= 10)
}
// MDToDOCX writes a DOCX next to or at outPath from a Markdown file.
func (t Tools) MDToDOCX(mdPath, docxPath string) error {
if Ext(mdPath) != ".md" && Ext(mdPath) != ".markdown" {
+19
View File
@@ -34,6 +34,25 @@ func TestNeedsOCR_Image(t *testing.T) {
}
}
func TestTxtToMarkdown_FixedWidthUsesCodeBlock(t *testing.T) {
var lines []string
for i := 0; i < 12; i++ {
lines = append(lines, " column layout line "+strings.Repeat("x", 40))
}
in := strings.Join(lines, "\n")
md := TxtToMarkdown(in)
if !strings.HasPrefix(md, "```\n") || !strings.Contains(md, "```") {
t.Fatalf("expected code block: %q", md[:min(80, len(md))])
}
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
func TestTxtToMarkdownPreservesLines(t *testing.T) {
in := "line1\nline2\n\nline4"
md := TxtToMarkdown(in)