Synch to No Gnus 200503230048.
[elisp/gnus.git-] / texi / xml2texi.scm
1 ;;; xml2texi.scm --- Convert gnus-faq.xml to gnus-faq.texi
2 ;; Copyright (C) 2005  Free Software Foundation, Inc.
3
4 ;; Author:  Karl Pflästerer <sigurd@12move.de>
5 ;; Keywords: tools
6
7 ;; This file is not part of GNU Emacs.
8
9 ;; GNU Emacs is free software; you can redistribute it and/or modify
10 ;; it under the terms of the GNU General Public License as published by
11 ;; the Free Software Foundation; either version 2, or (at your option)
12 ;; any later version.
13
14 ;; GNU Emacs is distributed in the hope that it will be useful,
15 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 ;; GNU General Public License for more details.
18
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
21 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22 ;; Boston, MA 02111-1307, USA.
23
24 ;;; Commentary:
25 ;; 
26
27 ;;; Code:
28
29 (require (lib "ssax.ss" "ssax")
30          (lib "sxpath.ss" "ssax")
31          (lib "sxml-tree-trans.ss" "ssax")
32          (lib "pregexp.ss")
33          (lib "list.ss")
34          (lib "etc.ss")
35          (rename (lib "1.ss" "srfi") list-index list-index)
36          (rename (lib "13.ss" "srfi") string-join string-join))
37
38
39 ;;; Constants
40 ;; In and out; for convenience if we work from the REPL
41 (define +infile+ "gnus-faq.xml")
42 (define +outfile+ "gnus-faq.texi")
43
44 ;; These are the names of the sections.  These variables hold the names
45 ;; of the sections where numbering starts in the main menu.
46 ;; Where we start numbering in menu
47 (define +first-numbered-section+ "Installation FAQ")
48 ;; Where we end numbering in menu
49 (define +last-numbered-section+ "Tuning Gnus")
50
51 ;; Which sections not to include; i.e. not to name a node.
52 (define +ignored-sections+ '("Frequently Asked Questions with Answers"))
53
54 ;; Names of menu entries and the corresponding descriptions (used in the
55 ;; main menu).
56 (define +section-comments-alist+
57     '(("Introduction" . "About Gnus and this FAQ.")
58       ("Installation FAQ" . "Installation of Gnus.")
59       ("Startup / Group buffer" . "Start up questions and the first buffer Gnus shows you.")
60       ("Getting Messages" . "Making Gnus read your mail and news.")
61       ("Reading messages" . "How to efficiently read messages.")
62       ("Composing messages" . "Composing mails or Usenet postings.")
63       ("Old messages" . "Importing, archiving, searching and deleting messages.")
64       ("Gnus in a dial-up environment" . "Reading mail and news while offline.")
65       ("Getting help" . "When this FAQ isn't enough.")
66       ("Tuning Gnus" .  "How to make Gnus faster.")
67       ("Glossary" . "Terms used in the FAQ explained.")))
68
69 ;; Where to break descriptions in menus
70 (define +width+ 72)
71
72 ;; The boilerplate text we include before the document
73 (define boilerplate
74     (lambda (titel)
75       (format
76        "\
77 @c \\input texinfo @c -*-texinfo-*-~%\
78 @c Uncomment 1st line before texing this file alone.~%\
79 @c %**start of header~%\
80 @c Copyright (C) 1995, 2001, 2002, 2003, 2004, 2005 Free Software Foundation, Inc.~%\
81 @setfilename gnus-faq.info~%\
82 @settitle ~A~%\
83 @c %**end of header~%\
84 " titel)))
85
86 ;; Inserted right before the end of the file
87 (define +tag-for-gnus-faq-texi+
88     (format "\
89 ~%\
90 @ignore~%\
91 arch-\
92 tag: 64dc5692-edb4-4848-a965-7aa0181acbb8~%\
93 @end ignore~%\
94 "))
95
96 ;;; Little Helpers
97 ;; (a b c) -> (1 2 3)
98 (define (number-list start inc lst)
99     (let loop ((lst lst) (lvl start) (acc '()))
100          (if (null? lst)
101            (reverse acc)
102            (loop (cdr lst) (+ inc lvl) (cons lvl acc)))))
103
104 ;; Given an alist made of regexps and their replacements (key and value
105 ;; are in a proper list) returns a function which given a string
106 ;; replaces all occurences of the regexps (from left to right).
107 ;; ((re1 repl1) (re2 repl2)) -> str -> str
108 (define make-reg-replacer
109     (lambda (defalist)
110       (let ((allreg (string-join (map car defalist) "|")))
111         (lambda (str)
112           (if (and (string? str) (pregexp-match allreg str))
113             (let loop ((lst defalist) (str str))
114                  (if (null? lst)
115                    str
116                    (loop (cdr lst) (pregexp-replace* (caar lst) str (cadar lst)))))
117             str)))))
118
119 (define escape-texi
120     (make-reg-replacer '(("@"  "@@") ("{"  "@{") ("}"  "@}"))))
121
122 (define normalize
123     (compose escape-texi (make-reg-replacer `((,(format "~%\\s+") ,(format "~%"))))))
124
125 (define normalize-example
126     (compose escape-texi (make-reg-replacer '(("^\\s+|\\s+$" "")))))
127
128 (define trim-ws (make-reg-replacer '(("^\\s+|\\s+$" ""))))
129
130 (define filter-sect
131     (lambda (lst)
132       (filter (lambda (e) (not (member e +ignored-sections+))) lst)))
133
134 ;;;; Para
135 (define format-para
136     (lambda (list-of-entries)
137       (format "~%~A~%" (trim-ws (apply string-append list-of-entries)))))
138
139 ;;;; Questions
140 (define format-q-level
141     (lambda (level)
142       (apply format "[~A.~A]" (reverse level))))
143
144 (define format-q-description
145     (compose trim-ws (make-reg-replacer `((,(format "~%") " ")))))
146
147 ;;;; Building nodes
148 ;; curr-node up-node (list of nodes) (list of node names) ->
149 ;;   ((curr-node curr-name) (next next-name) (prev prev-name) up)
150 (define (find-prev-next-up curr up search-list name-list)
151     (do ((lst   search-list (cdr lst))
152          (rlst  name-list   (cdr rlst))
153          (prev  up   (car lst))
154          (prevn up   (car rlst)))
155         ((or (null? lst) (equal? (car lst) curr))
156          (values (cons curr (if (pair? rlst) (car rlst) curr))
157                  (if (and (pair? lst) (pair? (cdr lst))) ;next
158                    (cons (cadr lst) (cadr rlst))
159                    (cons "" ""))
160                  (cons prev prevn)
161                  up))))
162
163
164 (define (format-node section title up lst-of-nodes lst-of-names)
165     (if (member title +ignored-sections+)
166       ()
167       (call-with-values
168        (lambda () (find-prev-next-up title up lst-of-nodes lst-of-names))
169        (lambda (currn prevn nextn up)
170          (format "~%@node ~A~%~A ~A~%"
171                  (cdr currn) ;; (cdr prevn) (cdr nextn) up
172                  section ;; @subsection etc.
173                  (if (pair? title)
174                    (apply format "~A.~A" (reverse title))
175                    title))))))
176
177 ;;;; Building menus
178
179 (define format-menu
180     (lambda (alist-of-entries)
181       (let ((len (apply max (map (lambda (s) (string-length (car s))) alist-of-entries))))
182         (format "~%@menu~%~A@end menu~%"
183                 (apply string-append
184                        (map (lambda (e)
185                               (format "* ~A::~A~A~%"
186                                       (car e) ;the entry
187                                       (make-string (- len (string-length (car e)) -3) #\ )
188                                       (format-menu-description (cdr e) +width+ (+ len 7))))
189                             alist-of-entries))))))
190
191
192 (define format-menu-description
193     (lambda (entry width offset)
194       (let loop ((lst (pregexp-split "\\s" entry)) (len 0) (acc '()))
195            (if (null? lst)
196              (apply string-append (reverse! acc))
197              (let ((slen (+ 1 (string-length (car lst))))) ; +1 because of whitespace added later
198                (if (> (+ slen len) (- width offset))
199                  (loop (cdr lst) 0 (cons
200                                     (format "~%~A ~A"                 ; start a new line
201                                             (make-string offset #\ ) ; the whitespace
202                                             (car lst))
203                                     acc))
204                  (loop (cdr lst) (+ slen len) (cons (format " ~A"(car lst)) acc))))))))
205
206
207 (define format-sub-titles
208     (lambda (list-of-entries first-number-entry last-number-entry)
209       (let ((offset (or (list-index (lambda (e) (equal? e first-number-entry)) list-of-entries) 0))
210             (end (or (list-index (lambda (e) (equal? e last-number-entry)) list-of-entries)
211                      (length list-of-entries))))
212       (map (lambda (entry ind)
213              (format "FAQ ~A ~A"
214                      (if (<= offset ind end)
215                        (format "~A -" (- ind offset -1)) ;numbered entry
216                        "-")
217                      entry))
218            list-of-entries (number-list 0 1 list-of-entries)))))
219
220 ;;;; We number some sections first
221
222 ;; ntags is an alist => ((tag startcounter increment)
223 (define (number-nodes tree level ntags)
224     (if (null? ntags)
225       tree
226       (let* ((vals  (car ntags))
227              (ntag  (car vals))
228              (start (second vals))
229              (inc   (third vals))
230              (ntags (cdr ntags)))
231
232         (map
233          (lambda (node sublevel)
234            (pre-post-order
235             node
236             `((,ntag *preorder*
237                      . ,(lambda (tag . entry)
238                           `(,tag ,(cons sublevel level)
239                                  ,@(number-nodes entry (cons sublevel level) ntags))))
240               (*default* . ,(lambda x x))
241               (*text* . ,(lambda (tag s) s)))))
242          tree (number-list start inc tree)))))
243
244
245 ;;(transform->numbered faqsxml '(section article qandaset ((qandadiv 1 1) (qandaentry 0 1))))
246 (define transform->numbered
247     (lambda (sxml rules)
248       (let* ((rules (reverse rules))
249              (rule (car rules))
250              (ntag (cadr rules))
251              (styles (map (lambda (tag) (cons tag (lambda x x))) (list-tail rules 2))))
252   (pre-post-order
253    sxml
254      `((*default* *preorder* . ,(lambda x x))
255        (*TOP* . ,(lambda x x))
256        ,@styles
257        (,ntag *preorder*
258         . ,(lambda (tag . nodes)
259              (cons tag (number-nodes nodes '() rule)))))))))
260
261
262 ;;;; The main transform function
263
264 (define (transform sxml)
265     (let* ((sxml (transform->numbered
266                   sxml '(section article qandaset ((qandadiv 1 1) (qandaentry 0 1)))))
267            (qandadivtitles (filter-sect (map second ((sxpath '(// qandadiv title)) sxml))))
268            (fqandadivtitles (format-sub-titles qandadivtitles "" ""))
269            (subtitles (filter-sect (append (map second ((sxpath '(// section title)) sxml))
270                                            qandadivtitles
271                                            (map second ((sxpath '(// glossary title)) sxml)))))
272            (fsubtitles (format-sub-titles subtitles +first-numbered-section+
273                                           +last-numbered-section+))
274            (questlevel (map second ((sxpath '(article section qandaset qandadiv qandaentry)) sxml)))
275            (up1 (cadar ((sxpath '(article articleinfo title)) sxml)))
276
277 ;;; ************************************************************
278 ;;; The Style Sheet
279 ;;; ************************************************************
280            (style-sheet
281              `(
282 ;;; ************************************************************
283 ;;; First the SXML special markers
284 ;;; ************************************************************
285                ;; *TOP* *PI* @ are markers from SXML
286                (*TOP* . ,(lambda (tag . x) x))
287                (*PI* . ,(lambda _ '()))
288                (@ . ,(lambda _ ""))
289
290                ;; Look for the example rule where we overwrite the *text* rule
291                ;; so code doesn't get mangled.
292                (*text*
293                 . ,(lambda (tag string)
294                      (normalize string)))
295                ;; If nothing else matches
296                (*default* . ,(lambda x x))
297 ;;; ************************************************************
298 ;;; Now to the tags of our FAQ
299 ;;; ************************************************************
300                (article . ,(lambda (tag . sects)
301                              (list (boilerplate up1) sects 
302                                    +tag-for-gnus-faq-texi+)))
303
304                (articleinfo
305                 ((*default* . ,(lambda _ '()))
306                  (title
307                   . ,(lambda (tag titel)
308                        (let ((menucom (map (lambda (entry)
309                                              (let ((e (assoc entry +section-comments-alist+)))
310                                                (if e (cdr e) "")))
311                                            subtitles)))
312                          (list (format-node '@section titel "" '() '())
313                                (format-menu (map cons fsubtitles menucom)))))))
314                 . ,(lambda (tag . info) info))
315
316                ;; Sections
317                (abstract
318                 . ,(lambda (tag . text)
319                      (cons (format "~%@subheading Abstract~%") text)))
320                (section
321                 ((title
322                   . ,(lambda (tag titel)
323                        (format-node '@subheading titel up1 subtitles fsubtitles))))
324                 . ,(lambda (tag . entry) entry))
325
326                ;; Q&A well it's called FAQ isn't it?
327                (qandaset . ,(lambda (tag . x) x))
328                (qandadiv
329                 ((title
330                   . ,(lambda (tag titel) titel)))
331                 . ,(lambda (tag level titel . entries)
332                      (let ((questions (map cadr entries))
333                            (nlevel (filter (lambda (lvl) (eq? (car level) (cadr lvl))) questlevel)))
334                        (list*
335                         (format-node '@subsection titel up1 subtitles fsubtitles)
336                         (format-menu (map (lambda (lvl quest)
337                                             (cons (format-q-level lvl)
338                                                   (format-q-description quest)))
339                                           nlevel questions))
340                         entries))))
341                (qandaentry
342                 . ,(lambda (tag level question answer)
343                      (let ((nodes
344                              (filter (lambda (lvl) (eq? (cadr lvl) (cadr level))) questlevel))
345                            (up (list-ref fqandadivtitles (- (cadr level) 1))))
346                        (list*
347                         (format-node "@subsubheading Question" level up nodes (map format-q-level nodes))
348                         question answer))))
349                (question . ,(lambda (tag quest) quest))
350                (answer
351                 . ,(lambda (tag  . answ) (list* (format "~%@subsubheading Answer~%") answ)))
352
353                ;; Para
354                (para . ,(lambda (tag . x) (format-para x)))
355                (simpara . ,(lambda (tag . x) (cons (format "~%")  x)))
356
357                ;; Itemized lists.
358                ;; We rewrite para here because it plays here the role of an
359                ;; item marker
360                (itemizedlist
361                 . ,(lambda (tag lstitem)
362                      (format "~%@itemize @bullet~%~A@end itemize~%" lstitem)))
363                (listitem
364                 ((para
365                   . ,(lambda (tag item)
366                        (format "~%@item~%~A~%" (trim-ws item)))))
367                 . ,(lambda (tag . x) (string-join x "")))
368
369                ;; The glossary.
370                (glossary
371                 ((title . ,(lambda _'())))
372                 . ,(lambda (tag . terms)
373                      (let ((titel (cadar ((sxpath '(article glossary title)) sxml))))
374                        (cons (format-node '@subsection titel up1 subtitles fsubtitles)
375                              (list (format "~%@table @dfn~%")
376                                    terms
377                                    (format "~%@end table~%"))))))
378                (glossentry . ,(lambda (tag . entry) entry))
379                (glossterm
380                 . ,(lambda (tag term)
381                      (format "~%@item ~A" term)))
382                (glossdef
383                 . ,(lambda (tag def) def))
384
385                ;; Lisp examples
386                ;; We rewrite the *text* rule so code stays the way it's writen.
387                (programlisting
388                 ((*text*
389                   . ,(lambda (tag exampl)
390                        (normalize-example exampl))))
391                 . ,(lambda (tag . exampl)
392                      (format "~%@example~%~A~%@end example~%@noindent~%" (string-join exampl ""))))
393
394                ;; The link handling
395                ;; Here we are interested in the attributes, so we rewrite the @
396                ;; rule.  If we find a value we look if it's an email or http
397                ;; uri.
398                (ulink
399                 ((@
400                   . ,(lambda (at val) val)))
401                 . ,(lambda (tag uri name)
402                      (if (pregexp-match "^http:|^ftp:" uri)
403                          (if (equal? uri name)
404                              (format "@uref{~A}"  uri)
405                              (format "@uref{~A, ~A}"  uri name))
406                          (format "@email{~A, ~A}" (substring uri 7) name))))
407                (url
408                 . ,(lambda (tag val) val))
409
410                ;; userinput
411                (userinput
412                 . ,(lambda (tag val)
413                      (format "@samp{~A}" val)))
414                )))
415       (pre-post-order sxml style-sheet)))
416
417 ;;;; We call main with infile and outfile as arguments
418 (define main
419     (lambda (in out)
420       (with-output-to-file out
421         (lambda ()
422           (call-with-input-file in
423             (lambda (port)
424               (SRV:send-reply (transform (ssax:xml->sxml port '()))))))
425         'replace)))
426
427 ;; Local Variables:
428 ;; coding: iso-8859-1
429 ;; End:
430
431 ;; arch-tag: cdd948f7-def9-4ea1-b5ae-b57c308097d7
432 ;;; xml2texi.scm ends here