refactor include system

This commit is contained in:
tux
2025-08-18 09:41:52 +02:00
parent f824b23311
commit ca129eec79
29 changed files with 26 additions and 34 deletions

View File

@@ -0,0 +1,44 @@
appendix =
#(define-void-function (parser location title) (markup?)
(define (appendix-item->markup layout props appendix-item)
(interpret-markup layout props
(markup
#:override (cons 'appendixItem:heading (assoc-ref appendix-item "heading"))
#:override (cons 'appendixItem:text (assoc-ref appendix-item "text"))
(ly:output-def-lookup layout 'appendixItemMarkup))))
(ly:book-add-bookpart! (ly:parser-lookup '$current-book)
#{
\bookpart {
\markup { #title }
#(for-each
(lambda (item)
(add-score (ly:make-page-label-marker (string->symbol (car item))))
(add-text
(make-on-the-fly-markup
(lambda (layout props arg) (appendix-item->markup layout props (cdr item)))
(make-null-markup)))
)
(reverse APPENDIX_DATA))
}
#}))
#(define-markup-command (appendix-ref layout props label) (symbol?)
"call page-ref to appendix-item"
(interpret-markup layout props
(markup #:with-link label
#:override (cons 'appendixPage (make-page-ref-markup label "888" "?"))
(ly:output-def-lookup layout 'appendixReferenceMarkup))))
\paper {
appendixItemMarkup = \markup {
\left-column {
\line { \large \bold \fromproperty #'appendixItem:heading }
\vspace #0.2
\sans \wordwrap-field #'appendixItem:text
\vspace #0.7
}
}
appendixReferenceMarkup = \markup {
\fromproperty #'appendixPage
}
}

View File

@@ -0,0 +1,371 @@
#(define song-list '())
#(define song-number 0)
#(define (files-in-directory dirname)
;;; Generate list containing filenames
(let ((dir (opendir dirname)))
(let next ((f (readdir dir))
(files '()))
(cond ((eof-object? f)
(closedir dir)
files)
(else
(next (readdir dir) (if (ly:regex-match? (ly:regex-exec (ly:make-regex "^(0|\\.)") f)) files (cons f files))))))))
#(define (file-to-stats filename)
(set! song-list (cons filename song-list)))
#(define additional-page-switch-label-list '())
#(define additional-page-numbers #f)
normalPageNumbers =
#(define-void-function (parser location) ()
(set! additional-page-numbers #f)
)
additionalPageNumbers =
#(define-void-function (parser location) ()
(set! additional-page-numbers #t)
)
#(define (real-page-number layout label)
(let ((table (ly:output-def-lookup layout 'label-page-table)))
(if (list? table)
(assoc-get label table)
#f))
)
#(define display-pages-list '())
#(define (build-display-pages-list layout)
(if (null? display-pages-list)
(let calculate-display-page ((switch-label-list additional-page-switch-label-list))
(let* ((label (caar switch-label-list))
(is-additional (cdar switch-label-list))
(real-page (real-page-number layout label))
(rest-switch-label-list (cdr switch-label-list))
(display-page (if (null? rest-switch-label-list)
(if is-additional (- real-page 1) real-page)
(let* ((previous-label (caar rest-switch-label-list))
(previous-is-additional (cdar rest-switch-label-list))
(previous-display-page (calculate-display-page rest-switch-label-list)))
(+ previous-display-page
(if previous-is-additional
(if is-additional 0 1)
(if is-additional
(- (- real-page (real-page-number layout previous-label)) 1)
(- real-page (real-page-number layout previous-label)))
)
)
)
))
)
(set! display-pages-list (acons label display-page display-pages-list))
display-page
)))
display-pages-list
)
#(define-markup-command (custom-page-number layout props label real-current-page-number)
(symbol? number?)
#:category other
"
@cindex referencing page number, in text
Reference to a page number. @var{label} is the label set on the referenced
page (using the @code{\\label} command), @var{gauge} a markup used to estimate
the maximum width of the page number, and @var{default} the value to display
when @var{label} is not found.
(If the current book or bookpart is set to use roman numerals for page numbers,
the reference will be formatted accordingly -- in which case the @var{gauge}'s
width may require additional tweaking.)"
(let* ((gauge-stencil (interpret-markup layout props "XXX"))
(x-ext (ly:stencil-extent gauge-stencil X))
(y-ext (ly:stencil-extent gauge-stencil Y)))
(ly:stencil-outline
(ly:make-stencil
`(delay-stencil-evaluation
,(delay (ly:stencil-expr
(let* ((display-page (assq-ref (build-display-pages-list layout) label))
(real-current-page (if (negative? real-current-page-number) (real-page-number layout label) real-current-page-number))
(page-markup
(if (assq-ref additional-page-switch-label-list label)
(make-concat-markup (list (number-format 'arabic display-page)
(make-char-markup (+ 97 (- real-current-page (real-page-number layout
(let find-earliest-additional-label
((rest-additional-page-switch-label-list (member (cons label #t) additional-page-switch-label-list)))
(if (cdadr rest-additional-page-switch-label-list)
(find-earliest-additional-label (cdr rest-additional-page-switch-label-list))
(caar rest-additional-page-switch-label-list)))
))))))
(number-format 'arabic (+ display-page (- real-current-page (real-page-number layout label))))
))
(page-stencil (interpret-markup layout props page-markup))
(gap (- (interval-length x-ext)
(interval-length (ly:stencil-extent page-stencil X)))))
(interpret-markup layout props
(make-line-markup
(list
(make-hspace-markup gap)
page-markup)))))))
x-ext
y-ext)
(make-filled-box-stencil x-ext y-ext))))
includeSong =
#(define-void-function (parser location filename) (string?)
#{
\bookOutputName #filename
#}
(ly:parser-parse-string (ly:parser-clone)
(ly:format "\\include \"~a/~a/~a.ly\"" songPath filename filename))
(let ((label (gensym "index")))
(set! additional-page-switch-label-list
(acons label additional-page-numbers additional-page-switch-label-list))
(set! song-list
(acons (string->symbol filename)
(acons 'label label
(acons 'header HEADER
(acons 'music MUSIC
(acons 'layout LAYOUT
(acons 'text-pages
(map (lambda (text)
#{ \markuplist \setsongfilename $filename $text #})
TEXT_PAGES)
'())))))
song-list))
))
blankpage =
#(define-void-function (parser location) ()
(set! song-list
(acons 'emptyPage
'()
song-list)))
imagepage =
#(define-void-function (parser location xsize filename) (number? string?)
(set! song-list
(acons 'imagePage
(acons 'xsize xsize (acons 'filename filename '()))
song-list)))
#(define-markup-command (pagecenter layout props stuff)(markup?)
(interpret-markup layout props
(let ((halfpaperheight (/ (ly:output-def-lookup layout 'paper-height) 2))
(halfstuffheight (/ (interval-length (ly:stencil-extent (interpret-markup layout props stuff) Y)) 2)))
(make-fill-line-markup (list (make-pad-to-box-markup '(0 . 0) (cons (- (- halfpaperheight halfstuffheight)) (+ halfpaperheight halfstuffheight)) stuff)))
)))
songs =
#(define-void-function (parser location) ()
(for-each (lambda (songitems)
(ly:book-add-bookpart! (ly:parser-lookup '$current-book)
(let ((filename (car songitems))
(songvars (cdr songitems)))
(if (eq? filename 'emptyPage)
#{ \bookpart { \markup { \null } } #}
(if (eq? filename 'imagePage)
(let ((xsize (assq-ref songvars 'xsize))
(filename (ly:format "~a/~a" imagePagePath (assq-ref songvars 'filename))))
#{ \bookpart {
\paper {
%{
inner-margin = 0
outer-margin = 0
binding-offset = 0
top-margin = 0
bottom-margin = 0
%}
print-page-number = ##f
last-bottom-spacing = #'((basic-distance . 0) (minimum-distance . 0) (padding . 0))
page-count = 1
}
\markup { \pagecenter { \epsfile #X #xsize #filename } }
} #}
)
(let* ((newnumber (+ 1 song-number))
(header #{ \bookpart { $(assq-ref songvars 'header) \header {
songfilename = $(symbol->string filename)
myindexlabel = #(assq-ref songvars 'label)
songnumber = #(number->string newnumber)
} } #})
(music (assq-ref songvars 'music))
(layout (assq-ref songvars 'layout))
(text-pages (assq-ref songvars 'text-pages))
(label (assq-ref songvars 'label)))
(set! song-number newnumber)
#{
\bookpart {
$header
\headerToTOC #header #label
\score { $music \layout { $layout } }
$(add-text-pages text-pages)
}
#}))))))
(reverse song-list)
))
#(define (boernel-stats)
(let (
(songs (map (lambda (song) (symbol->string (car song))) (alist-delete 'emptyPage song-list)))
(opticalline "---------------------------------------------------------"))
(ly:warning (string-join (list
opticalline
(string-concatenate (list "Inkludiert: " (number->string (length songs)) " Lieder\n"))
;(string-join songs "\n")
"Nicht inkludiert:"
opticalline
(string-join (sort-list (lset-difference string=? (files-in-directory songPath) songs) string<?) "\n")
opticalline
) "\n" )
)))
%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Include Images once and reference them:
#(define bbox-regexp
(ly:make-regex "%%BoundingBox:[ \t]+([0-9-]+)[ \t]+([0-9-]+)[ \t]+([0-9-]+)[ \t]+([0-9-]+)"))
#(define (get-postscript-bbox string)
"Extract the bbox from STRING, or return #f if not present."
(let*
((match (ly:regex-exec bbox-regexp string)))
(if match
(map (lambda (x)
(string->number (ly:regex-match-substring match x)))
(cdr (iota 5)))
#f)))
#(define eps-references '())
#(define-public (eps-file-ref->stencil axis size just-embed file-name)
(let*
((already-embedded (assq (string->symbol file-name) eps-references))
(counter (if already-embedded (cadr already-embedded) (if (null-list? eps-references) 1 (+ 1 (cadar eps-references)))))
(form-name (ly:format "IDForm~a" counter))
(data-name (ly:format "ImageData~a" counter))
(eps-content (ly:gulp-file file-name))
(contents (ly:format "~a execform" form-name))
(bbox (if already-embedded (cddr already-embedded) (get-postscript-bbox (car (string-split eps-content #\nul)))))
(bbox-size (if (= axis X)
(- (list-ref bbox 2) (list-ref bbox 0))
(- (list-ref bbox 3) (list-ref bbox 1))
))
(factor (if (< 0 bbox-size)
(exact->inexact (/ size bbox-size))
0))
(scaled-bbox
(map (lambda (x) (* factor x)) bbox))
;; We need to shift the whole eps to (0,0), otherwise it will appear
;; displaced in lilypond (displacement will depend on the scaling!)
(translate-string (ly:format "~a ~a translate" (- (list-ref bbox 0)) (- (list-ref bbox 1))))
(clip-rect-string (ly:format
"~a ~a ~a ~a rectclip"
(list-ref bbox 0)
(list-ref bbox 1)
(- (list-ref bbox 2) (list-ref bbox 0))
(- (list-ref bbox 3) (list-ref bbox 1)))))
(if (not already-embedded) (set! eps-references
(acons (string->symbol file-name) (cons counter bbox) eps-references)))
(if bbox
(ly:make-stencil
(list
'embedded-ps
(string-append
(if (and already-embedded (not just-embed)) "" (ly:format
"
/~a
currentfile
<< /Filter /SubFileDecode
/DecodeParms << /EODCount 0 /EODString (*EOD*) >>
>> /ReusableStreamDecode filter
~a
*EOD*
def
/~a
<< /FormType 1
/BBox [~a ~a ~a ~a]
/Matrix [ 1 0 0 1 0 0]
/PaintProc
{ pop
/ostate save def
/showpage {} def
/setpagedevice /pop load def
~a 0 setfileposition ~a cvx exec
ostate restore
} bind
>> def
" data-name eps-content form-name (list-ref bbox 0) (list-ref bbox 1) (list-ref bbox 2) (list-ref bbox 3) data-name data-name))
(if just-embed "" (ly:format
"
gsave
currentpoint translate
BeginEPSF
~a dup scale
~a
~a
%%BeginDocument: ~a
~a
%%EndDocument
EndEPSF
grestore
" factor translate-string clip-rect-string file-name contents
))))
;; Stencil starts at (0,0), since we have shifted the eps, and its
;; size is exactly the size of the scaled bounding box
(if just-embed '(0 . 0) (cons 0 (- (list-ref scaled-bbox 2) (list-ref scaled-bbox 0))))
(if just-embed '(0 . 0) (cons 0 (- (list-ref scaled-bbox 3) (list-ref scaled-bbox 1)))))
(ly:make-stencil "" '(0 . 0) '(0 . 0)))
))
#(define-markup-command (epsfileref layout props axis size file-name)
(number? number? string?)
#:category graphic
"
@cindex inlining an Encapsulated PostScript image
Inline an EPS image. The image is scaled along @var{axis} to
@var{size}.
@lilypond[verbatim,quote]
\\markup {
\\general-align #Y #DOWN {
\\epsfile #X #20 #\"context-example.eps\"
\\epsfile #Y #20 #\"context-example.eps\"
}
}
@end lilypond"
(if (ly:get-option 'safe)
(interpret-markup layout props "not allowed in safe")
(eps-file-ref->stencil axis size #f file-name)
))
#(define-markup-command (epsfileembed layout props file-name)
(string?)
#:category graphic
"
@cindex inlining an Encapsulated PostScript image
Inline an EPS image. The image is scaled along @var{axis} to
@var{size}.
@lilypond[verbatim,quote]
\\markup {
\\general-align #Y #DOWN {
\\epsfile #X #20 #\"context-example.eps\"
\\epsfile #Y #20 #\"context-example.eps\"
}
}
@end lilypond"
(if (ly:get-option 'safe)
(interpret-markup layout props "not allowed in safe")
(eps-file-ref->stencil X 30 #t file-name)
))

View File

@@ -0,0 +1,445 @@
% embed all category images in postscript once
#(define-markup-list-command (embed-category-images layout props)()
(map (lambda (category)
(interpret-markup layout props
(markup #:epsfileembed (category-image-path (symbol->string (car category))))))
category-names))
% print a markup-list in columns
#(define-markup-list-command (columnlayout layout props cols margin heightpair lines) (integer? number? pair? markup-list?)
(let create-col-page ((line-width (- (/ (chain-assoc-get 'line-width props
(ly:output-def-lookup layout 'line-width))
cols) margin ))
(cols cols)
(height (car heightpair))
(restlines lines))
(cons
(interpret-markup layout props
(make-fill-line-markup
(map (lambda (foo)
(make-general-align-markup Y UP (make-override-markup '(baseline-skip . 1) (make-column-markup
(let add-to-col ((lines restlines) (height-left height))
(set! restlines lines)
(if (null? lines)
'()
(let* ((line-to-stencil (lambda (line) (interpret-markup layout (cons (list (cons 'line-width line-width) (cons 'baseline-skip 1)) props) (markup line))))
(stencil-height (lambda (stencil) (interval-length (ly:stencil-extent stencil Y))))
(linestencil (line-to-stencil (car lines)))
(current-line-height (stencil-height linestencil))
(new-height-left (- height-left current-line-height))
(next-line-height (if (null? (cdr lines)) current-line-height (stencil-height (line-to-stencil (cadr lines)))))
(no-space-for-next-line (and (< next-line-height current-line-height) (< new-height-left next-line-height)))
)
(if (or (< new-height-left 0) no-space-for-next-line)
'()
(cons (markup #:stencil linestencil) (add-to-col (cdr lines) new-height-left))))))))))
(make-list cols))))
(if (null? restlines)
(list)
(create-col-page line-width cols (cdr heightpair) restlines)))))
%%%%%%%%%%%%%%%%%%%%%%%
%%%Funktionen für Inhaltsverzeichnis
% geklaut von da:
% http://lsr.dsi.unimi.it/LSR/Snippet?id=763
% Usage:
% - define and index item with \indexItem $sortstring $markup
% - use \indexSection $sortstring $markup to divide the index into several sections
% - display the alphabetical index with \markuplines \index
% code ist mostly taken from ./ly/toc-init.ly and just renamed and slightly modfied
%% defined later, in a closure
#(define*-public (add-index-item! markup-symbol text sorttext #:optional label) #f)
#(define-public (index-items) #f)
#(let ((index-item-list (list)))
(set! add-index-item!
(lambda* (markup-symbol text sorttext #:optional (label (gensym "index")))
(set! index-item-list
;; We insert index items sorted from the beginning on and do
;; not sort them later - this saves pretty much computing time
(insert-alphabetical-sorted! (list label markup-symbol text
;; this crazy hack is necessary because lilypond depends on guile 1.8 atm
;; and so the cool unicode conversion functions cannot be used
(ly:string-substitute " " ""
(ly:string-substitute "…" ""
(ly:string-substitute "Č" "C"
(ly:string-substitute "Đ" "D"
(ly:string-substitute "Š" "S"
(ly:string-substitute "Т" "T"
(ly:string-substitute "Ä" "Ae"
(ly:string-substitute "ä" "ae"
(ly:string-substitute "Ö" "O"
(ly:string-substitute "ö" "oe"
(ly:string-substitute "Ü" "U"
(ly:string-substitute "ü" "ue" sorttext)))))))))))))
index-item-list))
(make-music 'EventChord
'page-marker #t
'page-label label
'elements (list (make-music 'LabelEvent
'page-label label)))))
(set! index-items (lambda ()
index-item-list)))
#(define (insert-alphabetical-sorted! iitem ilist)
(if (null? ilist)
(list iitem)
(if (string-ci<? (cadddr iitem) (cadddr (car ilist)))
(cons iitem ilist)
(cons (car ilist) (insert-alphabetical-sorted! iitem (cdr ilist))))))
% code for category index
#(define*-public (add-category-index-item! categories markup-symbol text #:optional label) #f)
#(define-public (category-index-items) #f)
#(let ((category-index-hash (make-hash-table)))
(set! add-category-index-item!
(lambda* (categories markup-symbol text #:optional (label (gensym "index")))
(for-each (lambda (category)
(let* ((catsym (string->symbol category))
(catlist (hashq-ref category-index-hash catsym
(list (list label 'indexCategoryMarkup category)))))
(if (assq catsym category-names)
(hashq-set! category-index-hash catsym
(cons (list label markup-symbol text) catlist))
(ly:error "song: <~a> category ~a is not defined!" (markup->string text) category))))
categories)
(make-music 'EventChord
'page-marker #t
'page-label label
'elements (list (make-music 'LabelEvent
'page-label label)))))
(set! category-index-items (lambda ()
(append-map (lambda (kv) (reverse (hashq-ref category-index-hash (car kv) (list)))) category-names))))
% code for author index
#(define*-public (add-author-index-item! authorIDs markup-symbol text #:optional label) #f)
#(define-public (author-index-items) #f)
#(let ((author-index-hash (make-hash-table)))
(set! add-author-index-item!
(lambda* (authorIDs markup-symbol text #:optional (label (gensym "index")))
(for-each (lambda (authorID)
(let* ((authorsym (string->symbol authorID))
(authorlist (hashq-ref author-index-hash authorsym
(list (list label 'indexAuthorMarkup authorID)))))
(hashq-set! author-index-hash authorsym
(cons (list label markup-symbol text) authorlist))
))
authorIDs)
(make-music 'EventChord
'page-marker #t
'page-label label
'elements (list (make-music 'LabelEvent
'page-label label)))))
(set! author-index-items (lambda ()
(append-map cdr
(sort-list
(hash-map->list
(lambda (authorsym authorlist) (cons (string-downcase (symbol->string authorsym)) (reverse authorlist)))
author-index-hash)
(lambda (a b) (string-ci<? (car a) (car b))))))))
#(define-markup-command (with-link-symbol-ref layout props symbol arg)
(symbol? markup?)
"call with-link with the label referenced by symbol"
(let ((label (chain-assoc-get symbol props)))
(interpret-markup layout props
(markup #:with-link label arg))))
#(define-markup-command (category-image-symbol-ref layout props size symbol)
(number? symbol?)
"call category-image with the category referenced by symbol"
(let ((category (chain-assoc-get symbol props)))
(interpret-markup layout props
(markup #:category-image size category))))
#(define-markup-command (category-name-symbol-ref layout props symbol)
(symbol?)
"get the name of a category referenced by symbol"
(let* ((category (chain-assoc-get symbol props))
(catname (assq (string->symbol category) category-names)))
(interpret-markup layout props
(markup #:override (cons 'baseline-skip 3.5) (if catname (make-left-column-markup (string-split (cadr catname) #\newline)) category)))))
#(define-markup-command (index-item-with-pattern layout props)()
#:properties ((index:text "")
(index:alternative #f)
(index:page #f)
(line-width))
(let* (
(width (-
line-width
(interval-length (ly:stencil-extent (interpret-markup layout props "XXXX") X))))
(lines-reversed
(reverse (map (lambda (stil) (markup #:stencil stil))
(wordwrap-string-internal-markup-list layout
(prepend-alist-chain 'line-width width
(if index:alternative
(prepend-alist-chain 'font-shape 'italic props)
props))
#f
index:text))))
(last-line-with-dots (make-fill-with-pattern-markup 1 RIGHT "." (car lines-reversed) index:page))
(lines-without-dots (cdr lines-reversed))
(target-line-size-markup
(make-column-markup
(list
(make-simple-markup "Agj")
(make-vspace-markup 0.2))))
)
(interpret-markup layout props
(make-size-box-to-box-markup #f #t
(make-with-link-symbol-ref-markup 'index:label
(make-column-markup
(reverse (cons
last-line-with-dots
(map (lambda (m) (make-size-box-to-box-markup #f #t m target-line-size-markup)) lines-without-dots)))))
; this column is just to have a reference height for resizing
(make-column-markup
(reverse (map (lambda (m) (make-size-box-to-box-markup #f #t m target-line-size-markup)) (cons last-line-with-dots lines-without-dots))))
))))
\paper {
indexItemMarkup = \markup {
\sans \index-item-with-pattern
}
indexSectionMarkup = \markup \override #'(baseline-skip . 1.5) \left-column {
\sans \bold \fontsize #3 \fromproperty #'index:text
\null
}
indexCategoryMarkup = \markup \override #'(baseline-skip . 1.5) \column {
\fill-line { \line { \vcenter \category-image-symbol-ref #7 #'index:text \hspace #3 \vcenter \sans \bold \fontsize #3 \category-name-symbol-ref #'index:text } \null }
\vspace #.4
}
indexAuthorMarkup = \markup \override #'(baseline-skip . 1.5) \left-column {
\vspace #1
\sans \bold \fontsize #3
#(make-on-the-fly-markup
(lambda (layout props m)
(interpret-markup layout props
(make-justify-string-markup (format-author (ly:output-def-lookup layout 'authorFormat) (chain-assoc-get 'index:text props #f) #t))))
(make-null-markup))
\vspace #.4
}
}
#(define (prepare-item-markup items layout)
(map (lambda (index-item)
(let* ((label (car index-item))
(index-markup (cadr index-item))
(textoptions (caddr index-item))
(text (chain-assoc-get 'rawtext textoptions))
(alternative (chain-assoc-get 'alternative textoptions))
(songnumber (chain-assoc-get 'songnumber textoptions)))
(markup #:override (cons 'index:label label)
#:override (cons 'index:page (markup #:custom-page-number label -1))
#:override (cons 'index:text text)
#:override (cons 'index:alternative alternative)
#:override (cons 'index:songnumber songnumber)
(ly:output-def-lookup layout index-markup))))
(items)))
#(define-markup-list-command (index-in-columns-with-title layout props index-type title-markup) (symbol? markup?)
( _i "Outputs index alphabetical sorted or in categories" )
(let ((items (case index-type
((alphabetical) index-items)
((categories) category-index-items)
((authors) author-index-items)))
(title (interpret-markup layout props title-markup)))
(cons title
(interpret-markup-list layout props
(make-columnlayout-markup-list songTocColumns 2
(let ((h (- (ly:output-def-lookup layout 'paper-height) 12)))
(cons (- h (interval-length (ly:stencil-extent title Y))) h))
(prepare-item-markup items layout))))))
indexItem =
#(define-music-function (parser location sorttext text) (string? markup?)
"Add a line to the alphabetical index, using the @code{indexItemMarkup} paper variable markup."
(add-index-item! 'indexItemMarkup (prepend-alist-chain 'rawtext text '()) sorttext))
indexSection =
#(define-music-function (parser location sorttext text) (string? markup?)
"Add a section line to the alphabetical index, using @code{indexSectionMarkup} paper variable markup. This can be used to divide the alphabetical index into different sections, for example one section for each first letter."
(add-index-item! 'indexSectionMarkup (prepend-alist-chain 'rawtext text '()) sorttext))
#(define (extract-and-check-vars-from-header bookheader varlist)
(let* ((headervars (hash-map->list cons (struct-ref (ly:book-header bookheader) 0)))
(extract-var-and-check (lambda (headervar)
(let* ((variableref (assoc-ref headervars headervar))
(extracted (if variableref (variable-ref variableref) #f)))
(if (and extracted (not (and (string? extracted) (string-null? extracted)))) extracted #f)))))
(map (lambda (varsymbol)
(cons varsymbol (extract-var-and-check varsymbol))
) varlist)))
headerToTOC = #(define-music-function (parser location header label) (ly:book? symbol?)
(define (all-author-ids authors)
(let ((poetIds (find-author-ids-by 'text authors))
(translatorIds (find-author-ids-by 'translation authors))
(versePoetData (find-author-id-with-part-numbers 'verse authors))
(composerIds (find-author-ids-by 'melody authors))
(verseComposerData (find-author-id-with-part-numbers 'meloverse authors))
(voiceComposerData (find-author-id-with-part-numbers 'voice authors))
(compositionIds (find-author-ids-by 'composition authors))
(adaptionIds (find-author-ids-by 'adaption authors))
(bridgeIds (find-author-ids-by 'bridge authors))
(interludeIds (find-author-ids-by 'interlude authors)))
(delete-duplicates
(append poetIds translatorIds (map car versePoetData) composerIds (map car verseComposerData) (map car voiceComposerData) compositionIds adaptionIds bridgeIds interludeIds))
))
(let*
(
(extractedheadervars (extract-and-check-vars-from-header header '(title starttext alttitle categorytitle categories authors songnumber)))
(title (assq-ref extractedheadervars 'title))
(starttext (assq-ref extractedheadervars 'starttext))
(alttitle (assq-ref extractedheadervars 'alttitle))
(categorytitle (assq-ref extractedheadervars 'categorytitle))
(categories (assq-ref extractedheadervars 'categories))
(authors (assq-ref extractedheadervars 'authors))
(songnumber (assq-ref extractedheadervars 'songnumber))
(textoptions (lambda (text alternative) `(((rawtext . ,text) (alternative . ,alternative) (songnumber . ,songnumber)))))
(add-to-toc! (lambda (toctitle alternative)
(add-index-item! 'indexItemMarkup (textoptions toctitle alternative) toctitle label)))
)
(if categories (add-category-index-item! (string-tokenize categories) 'indexItemMarkup (textoptions (if categorytitle categorytitle title) #f) label))
(if authors (add-author-index-item! (all-author-ids authors) 'indexItemMarkup (textoptions (if categorytitle categorytitle title) #f) label))
(if starttext (add-to-toc! starttext #t))
(if alttitle
(if (list? alttitle)
(for-each (lambda (alt)
(add-to-toc! alt #t))
alttitle)
(add-to-toc! alttitle #t)))
(if title (add-to-toc! title #f) #{ #})
))
%% https://github.com/NalaGinrut/guile-csv/blob/master/csv/csv.scm
#(define* (sxml->csv sxml port #:key (delimiter #\,))
(let* ((d (string delimiter))
(csv (map (lambda (l) (string-join l d)) sxml)))
(for-each (lambda (l)
(format port "~a~%" l))
csv)))
#(define csv-write sxml->csv)
#(define-markup-command (write-toc-csv layout props) ()
(define (csv-escape field)
(if (string-null? field)
field
(string-append
"\""
(ly:string-substitute "\n" "\\n"
(ly:string-substitute "\"" "\\\"" field))
"\"")))
(define (format-authors authorIds)
(string-join (map (lambda (authorId) (format-author (ly:output-def-lookup layout 'authorFormat) authorId #f)) authorIds) ", "))
(define cr-regex (ly:make-regex "\r"))
(define crlf-regex (ly:make-regex "\r\n"))
(define para-sep-regex (ly:make-regex "\n[ \t\n]*\n[ \t\n]*"))
(define whitespace-regex (ly:make-regex "[ \t\n]+"))
(define leading-whitespace-regex (ly:make-regex "^[ \t\n]+"))
(define trailing-whitespace-regex (ly:make-regex "[ \t\n]+$"))
(define (cleanup-whitespaces str)
(ly:regex-replace leading-whitespace-regex
(ly:regex-replace trailing-whitespace-regex
(ly:regex-replace whitespace-regex str " ")
"")
""))
(define (format-info-paragraphs text)
(let* ((para-strings (ly:regex-split
para-sep-regex
(ly:regex-replace
cr-regex
(ly:regex-replace crlf-regex text "\n")
"\n")))
(para-lines (map cleanup-whitespaces para-strings)))
(string-join para-lines "\n")))
(define (generate-toc-csv labelPageTable)
(let ((song-lines (map (lambda (song)
(let* ((filename (symbol->string (car song)))
(songvars (cdr song))
(page-number (number->string (assoc-get (assq-ref songvars 'label) labelPageTable)))
(extractedheadervars (extract-and-check-vars-from-header (assq-ref songvars 'header)
'(title starttext alttitle categorytitle categories authors year_text year_melody year_translation year_composition year_adaption infotext translation pronunciation copyright source)))
(headervar-or-empty (lambda (varsym)
(let ((extracted (assq-ref extractedheadervars varsym)))
(if extracted extracted ""))))
(authors (assq-ref extractedheadervars 'authors))
(poetIds (find-author-ids-by 'text authors))
(translatorIds (find-author-ids-by 'translation authors))
(versePoetData (find-author-id-with-part-numbers 'verse authors))
(composerIds (find-author-ids-by 'melody authors))
(verseComposerData (find-author-id-with-part-numbers 'meloverse authors))
(voiceComposerData (find-author-id-with-part-numbers 'voice authors))
(compositionIds (find-author-ids-by 'composition authors))
(adaptionIds (find-author-ids-by 'adaption authors))
(bridgeIds (find-author-ids-by 'bridge authors))
(interludeIds (find-author-ids-by 'interlude authors)))
(map csv-escape
(list
filename
page-number
(headervar-or-empty 'title)
(headervar-or-empty 'starttext)
(let ((alttitle-value (headervar-or-empty 'alttitle)))
(if (list? alttitle-value)
(string-join alttitle-value ", ") ; Wenn eine Liste, dann zusammenfügen
alttitle-value)) ; Wenn kein Liste, den originalen Wert verwenden
(headervar-or-empty 'categorytitle)
(headervar-or-empty 'categories)
(format-authors (append poetIds (map car versePoetData)))
(format-authors translatorIds)
(format-authors (append composerIds compositionIds adaptionIds bridgeIds interludeIds (map car voiceComposerData) (map car verseComposerData)))
(headervar-or-empty 'year_text)
(headervar-or-empty 'year_melody)
(headervar-or-empty 'year_translation)
(headervar-or-empty 'year_composition)
(headervar-or-empty 'year_adaption)
(headervar-or-empty 'copyright)
(headervar-or-empty 'source)
(format-info-paragraphs (headervar-or-empty 'infotext))
(format-info-paragraphs (headervar-or-empty 'translation))
(format-info-paragraphs (headervar-or-empty 'pronunciation))
))))
(alist-delete 'imagePage (alist-delete 'emptyPage song-list)))))
(call-with-output-file "toc.csv"
(lambda (port)
(csv-write (cons '(
"filename"
"page-number"
"title"
"starttext"
"alttitle"
"categorytitle"
"categories"
"poets"
"translators"
"composers"
"year_text"
"year_melody"
"year_translation"
"year_composition"
"year_adaption"
"copyright"
"source"
"infotext"
"translation"
"pronunciation"
) song-lines) port))
)))
; we use a delayed stencil to have all the page references available
(ly:make-stencil
`(delay-stencil-evaluation
,(delay (let* ((table (ly:output-def-lookup layout 'label-page-table)))
(generate-toc-csv (if (list? table) table '()))
empty-stencil)))))