#!/usr/local/bin/vipsi /* create online book from easy source ***TODO*** Tables authoritative file: /pub/Develop/projects/vipsi/libs/BOOK.vl */ var Startzeit = now var Statistics = {} var LibDir = "/usr/local/lib/vipsi/" var NumFiles = 0 var Seed = 0 if !exists verbose var verbose=0 then /* ---- Defaults ---------------------- */ var SourceFile = "*.toc" // list of source files, wildcards allowed var TargetDir = "" var FilenameIndex = "index.html" var FilenameBook = "z%%.htm" // name for target files; %% = sequential number var SplitFileSize = 20000 // limit for splitting chapters into multiple files var TabWidth = 4 // used to detab
sections var XrefMaxWords = 4 // max. significant words for automatic cross links generation var PageTocMinCount = 2 // min. number of links in TOC of single file for TOC present; else no TOC /* ---- Basic Html Templates ---------------------------- Template.Page für normale Seiten Template.Index für die Startseite Template.Split für die TOC-Seite von Kapiteln, die gesplittet wurden */ var Template.Page = «%%TITLE%% %%HEADLINE0%%%%HEADLINE1%% %%HEADLINE2%%%%HEADLINE3%%%%HEADLINE4%%%%HEADLINE5%%%%HEADLINE6%% %%TOC%%%%BODY%% » // Headlines as they appear at top of a page // called for %%HEADLINEx%% proc Style.Headline( i /* Buch[i] */ ) { var Tag = name Buch[i] var Url = Buch[i].Filename var Label = Buch[i].Text if Tag=="h0" var r = «\n» #Label# «
» try r #= «\n» #TocTitle# «
» del TocTitle then return r else return «\n<»#Tag#« class="head">» #Label# «»#Tag#«>» then } // Headlines as they appear in a TOC // called in preparation for %%TOC%% proc Style.TocItem ( i /* Buch[i] */ ) { var Label = Buch[i].Text var Url = "#" # Buch[i].Anker try Url = Buch[i].Filename # Url then return «» } // Table of contents: // called for %%TOC%% proc Style.Toc ( i /* Buch[i] */, TocListe ) { return exists Buch[i].Split || count TocListe >= PageTocMinCount ? «\n»#TEXT.JoinText(TocListe,nl)#«» : "" } // Querverweis-Linklist-Popup: // for Xref proc Style.Linklist ( Label, Linklist ) // Linklist = { {Url="",Label=""}, ... } { var i=0, Links="", id = ++Seed var ID="" do ID #= charstr('A'+id%26) id/=26 while id>=1 loop if count Linklist==1 return «» #Label# «» else do while ++i <= count Linklist Links #= «
» #Linklist[i].Label# «» loop return «»#Links[5 to]#«\ »#Label#«» then } /* ********************************************************************* TAG DEFINITIONS ********************************************************************* */ proc SkipBlock(i) // --> next block { var Tag = name Buch[i++] do while i <= count Buch && contains_tag(Tag,name Buch[i]) // rekursion notwendig, weil manche tags nur indirekt enthalten sein können, z.b. h4>ol>li i = SkipBlock(i) loop return i } proc SizeofBlock(i) { var j = SkipBlock(i) var n = 0 do n += count string(Buch[i]) while ++i < j loop return n } proc ReadContainedTags(i) { var Tag = name Buch[i++] do while i <= count Buch var Subtag = name Buch[i] while contains_tag(Tag,Subtag) i = Tags.@Subtag.Read(i) loop return i } proc WriteContainedTags(F,i) { var Tag = name Buch[i++] do while i <= count Buch var Subtag = name Buch[i] while contains_tag(Tag,Subtag) i = Tags.@Subtag.Write(F,i) loop return i } proc WriteStartTag(F,i) { var id = name Buch[i] try id #= « class=» # Buch[i].Class then // if exists try id #= « style="» # Buch[i].Style # «"» then // if exists try id #= « id=» # Buch[i].Anker then // if exists put #F, nl, "<" # id # ">" } proc WriteText(F,i) { put #F, Buch[i].Text } proc WriteEndTag(F,i) { put #F, "", name Buch[i], ">" } // default read/write handler proc r0(i) { if verbose put "["#name Buch[i]#"]" then return ReadContainedTags(i) } proc w0(F,i) { WriteStartTag(F,i); WriteText(F,i); WriteEndTag(F,i); return i+1 } /* ---- Tag Handler ------------------------------------------------------------------------- Class = PARA paragraphs: enthalten keine anderen Tags: z.B.BLOCK Block-Tags: enthalten andere Tags: alle Headlines und z.B.
tag // Zeilen mit Tag if Tag!="" TEXT.SpinWheel(); // split css class k = find(Tag,".") if k Buch[i] ##= { Class = Tag[k+1 to] } // store Class Tag = Tag[to k-1] j = k // kio 2015-01-14: sonst ist die 1. Zeile eines PRE-Tags immer zwangs-eingerückt then // Store Tag Tag = lowerstr(Tag) rename Buch[i] = Tag // ungültige Tags löschen if !is_valid_tag(Tag) log nl, « Unbekanntes Tag "», Tag, «": übersprungen» del Buch[i--]; next then // crop spaces // bei leerem Text die Folgezeile nachziehen do // crop spaces if is_pre_tag(Tag) Text = spacestr(j-1) # Text // overwrite Tag with spaces convert Text from @("tab"#TabWidth); // tabs durch spaces ersetzen Text = Text[TabWidth+1 to] Text = TEXT.CropSpaces_R(Text) else Text = TEXT.CropSpaces(Text) then // bei leerem Text nächste Zeile nachziehen while Text == "" // solange Text leer while iAbsatz elif is_pre_tag(PreTag) Text = TEXT.CropSpaces_R(Text) convert Text from @("tab"#TabWidth); // tabs durch spaces ersetzen Buch[i-1].Text #= nl # Text[TabWidth+1 to] // Absatz elif is_para_tag(PreTag) Text = TEXT.CropSpaces(Text) if Text!="" Buch[i-1].Text #= "
" # Text then // nach Definition Tag elif is_def_tag(PreTag) Text = TEXT.CropSpaces(Text) if Text!="" Buch[i-1].Text #= ", " # Text then else log nl, "this cannot happen" end then // Zeile wurde an vorherige angehängt => diese Zeile löschen del Buch[i--] then loop if verbose put nl, " ", count Buch, " Tags " else put " " then Statistics ##= { TagsGesamt = count Buch } // Leerzeilen am Ende vontags löschen i=0 do while ++i <= count Buch do while rightstr(Buch[i].Text,1)==nl Buch[i].Text = Buch[i].Text [ to count(Buch[i].Text) -1 ] loop loop // Hierarchie prüfen: put nl, "Hierarchie prüfen " if name Buch[1] != "h0" log nl, «Buch beginnt nicht mit Tag "h0"» log nl, "aborted.", nl DumpBuch() end 1 then var n=count Buch do var i = SkipBlock(1) while i<=count Buch log nl, « Tag "»#name Buch[i]#«" hier nicht erlaubt» del Buch[i] loop if n!=count Buch log nl, "aborted.", nl DumpBuch() end 1 then } proc TagsBehandeln() { put nl, "Tags behandeln " // Tags, die einen Anker benötigen, müssen hier ihr Keywords-Element erzeugen TEXT.SpinCount = 0 var i=1 do while i <= count Buch i = Tags.@(name Buch[i]).Read(i) loop if !verbose put "--> ", TEXT.SpinCount, " Überschriften" then } proc BuchAufDateienVerteilen() { put nl, "Buch auf mehrere Dateien verteilen " // Kapitel mit mehr als filesplitsize Zeichen splitten // --> Filesplit anmerken mit Buch[i].Split // --> Dateinamen eintragen in Buch[i].Filename // UP: Kapitel Buch[i] kommt in eine eigene Datei // Testen: noch weiter zerlegen? proc split_block(i) { var Tag = name Buch[i] Buch[i] ##= { Filename = NewFilename() } var j=find("h0h1h2h3h4",Tag) if verbose put nl, " "[to j+1], Buch[i].Filename, " --> ", Buch[i].Text else TEXT.SpinWheel() then if SizeofBlock(i)n Buch[i] ##= { Split=1 } then // else: Split n.ex. return j } TEXT.SpinCount=0 split_block(1) Statistics ##= { HtmlDateien = NumFiles } if verbose if NumFiles == 1 put nl, " Buch besteht aus einer Datei. " else put nl, " Buch besteht aus ", NumFiles, " Dateien. " then else put "--> ", TEXT.SpinCount, " Dateien" then } proc AnkerErzeugen() { put nl, "Verweisziele zusammentragen " var acnt = 0 proc anker_benennen(i,CurrentFile,HeadlineListe) { acnt++ if i!=1 HeadlineListe ##= Buch[i].Text then if count HeadlineListe > 2 del HeadlineListe[1] then var f = exists Buch[i].Filename if f var a = Seed // pro Datei wieder mit "A" beginnen Seed = 0 // fast die Frage, ob der Aufwand lohnt CurrentFile = Buch[i].Filename then try var Anker = Buch[i].Anker // falls Ankername vorgegeben wurde else var id = Seed++ var Anker = "" do Anker #= charstr('A'+id%26) id/=26 while id>=1 loop Buch[i] ##= { Anker = Anker } then var j=0 do while ++j <= count Buch[i].Keywords if count HeadlineListe>=2 var hl = TEXT.JoinText(HeadlineListe,": ") elif count HeadlineListe==1 var hl = HTML.B(HeadlineListe[1]) else var hl = HTML.B(Buch[1].Text) /* Buchtitel */ then AddXref( Buch[i].Keywords[j], hl, CurrentFile, Anker ) loop var Tag = name Buch[i] var j = i+1 do while j <= count Buch while contains_tag(Tag,name Buch[j]) if exists Buch[j].Keywords j = anker_benennen(j,CurrentFile,HeadlineListe) else j = SkipBlock(j) then loop if f Buch[i] ##= { Seed=Seed } // --> für IDs der Multilink-Popups Seed = a then return j } anker_benennen(1,"",{}) Statistics ##= { AnzahlAnker=acnt, AnzahlKeywords=count Xref } } proc QuerverweiseEinfügen() { put nl, "Querverweise einfügen " /* UP: Links in eine Zeile einfügen in: Zeile liegt als Wortliste {Sep,Wort,...Sep} vor aktuelle Wortzahl für Keyphrase-Suche EigeneDatei -> Urls ohne Dateinamen out: Zeile als Wort-Liste */ var xrefs=0 var xref={0,0,0,0,0,0,0,0,0,0,0,0}[to XrefMaxWords] proc add_refs ( Wortliste/*Zeile*/, Wortzahl/*Keyphrase*/, EigeneDatei/*wg.Link*/ ) { // put nl, Wortliste var Worte = {} // => Keyphrase var e_worte=0 // Worte <=> Wortliste[ a_worte to e_worte ] var a_worte=2 // sliding-window such nach Such-Phrasen in Anker-Liste do // erste Suchphrase basteln / Suchphrase komplettieren if count Worte < Wortzahl e_worte += 2 if e_worte > count Wortliste return Wortliste then if Wortliste[e_worte][1]=="<" && HTML.IsAnyTag(Wortliste[e_worte]) Worte = {} // avoid tag interleaving: .. .. .. a_worte = e_worte+2 // ***TODO***: .... .. is possible for certain tags next then Worte ##= Wortliste[e_worte] next then // suchen: var Keyphrase = TEXT.NormalizeName(Worte) var Ankerliste = FindXref(Keyphrase) if count Ankerliste == 0 // no match a_worte += 2 del Worte[1] next then xrefs++ xref[Wortzahl]++ // Einträge in Ankerliste umschrumpeln: //={Label=