37 lines
1.4 KiB
Go
37 lines
1.4 KiB
Go
package bookfile
|
|
|
|
import (
|
|
"io"
|
|
"strings"
|
|
)
|
|
|
|
// txt.go: the plain-text book. No audit banners, no flags in brackets, no operator vocabulary — the file
|
|
// is prose plus what the model carries: the title first, the notice (when there is one), then every
|
|
// chapter as its title line followed by its paragraphs. Blocks are separated by ONE blank line, chapters
|
|
// by TWO, so a chapter boundary reads differently from a paragraph boundary even in a viewer that shows
|
|
// nothing but text. The file ends with a newline.
|
|
//
|
|
// The same block list the EPUB renders (Blocks) is what lands here, so the two formats say the same thing —
|
|
// cleaned the same way too: every string the EPUB passes through xmlText goes through CleanText here, so
|
|
// a control character in a title or a notice cannot reach the text reader when the EPUB reader is spared it.
|
|
|
|
// WriteTXT writes b as plain UTF-8 text to w. Deterministic.
|
|
func WriteTXT(w io.Writer, b *Book) error {
|
|
if err := b.validate(); err != nil {
|
|
return err
|
|
}
|
|
var sb strings.Builder
|
|
sb.WriteString(strings.TrimSpace(CleanText(b.Title)))
|
|
for _, p := range b.Notice {
|
|
sb.WriteString("\n\n" + CleanText(p))
|
|
}
|
|
for _, ch := range b.Chapters {
|
|
sb.WriteString("\n\n\n" + CleanText(ch.Title))
|
|
for _, p := range ch.Paragraphs {
|
|
sb.WriteString("\n\n" + CleanText(p))
|
|
}
|
|
}
|
|
sb.WriteString("\n")
|
|
_, err := io.WriteString(w, sb.String())
|
|
return err
|
|
}
|