1 ;;; xml2texi.scm --- Convert gnus-faq.xml to gnus-faq.texi
2 ;; Copyright (C) 2005 Free Software Foundation, Inc.
4 ;; Author: Karl Pflästerer <sigurd@12move.de>
7 ;; This file is not part of GNU Emacs.
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 3, or (at your option)
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.
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., 51 Franklin Street, Fifth Floor,
22 ;; Boston, MA 02110-1301, USA.
29 (require (lib "ssax.ss" "ssax")
30 (lib "sxpath.ss" "ssax")
31 (lib "sxml-tree-trans.ss" "ssax")
35 (rename (lib "1.ss" "srfi") list-index list-index)
36 (rename (lib "13.ss" "srfi") string-join string-join))
40 ;; In and out; for convenience if we work from the REPL
41 (define +infile+ "gnus-faq.xml")
42 (define +outfile+ "gnus-faq.texi")
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")
51 ;; Which sections not to include; i.e. not to name a node.
52 (define +ignored-sections+ '("Frequently Asked Questions with Answers"))
54 ;; Names of menu entries and the corresponding descriptions (used in the
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.")))
69 ;; Where to break descriptions in menus
72 ;; The boilerplate text we include before the document
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, 2006, 2007,
81 @c 2008 Free Software Foundation, Inc.~%\
83 @c Do not modify this file, it was generated from gnus-faq.xml, available from~%\
84 @c <URL:http://my.gnus.org/FAQ/>.~%\
86 @setfilename gnus-faq.info~%\
88 @c %**end of header~%\
92 ;; Inserted right before the end of the file
93 (define +tag-for-gnus-faq-texi+
98 tag: 64dc5692-edb4-4848-a965-7aa0181acbb8~%\
103 ;; (a b c) -> (1 2 3)
104 (define (number-list start inc lst)
105 (let loop ((lst lst) (lvl start) (acc '()))
108 (loop (cdr lst) (+ inc lvl) (cons lvl acc)))))
110 ;; Given an alist made of regexps and their replacements (key and value
111 ;; are in a proper list) returns a function which given a string
112 ;; replaces all occurences of the regexps (from left to right).
113 ;; ((re1 repl1) (re2 repl2)) -> str -> str
114 (define make-reg-replacer
116 (let ((allreg (string-join (map car defalist) "|")))
118 (if (and (string? str) (pregexp-match allreg str))
119 (let loop ((lst defalist) (str str))
122 (loop (cdr lst) (pregexp-replace* (caar lst) str (cadar lst)))))
126 (make-reg-replacer '(("@" "@@") ("{" "@{") ("}" "@}"))))
129 (compose escape-texi (make-reg-replacer `((,(format "~%\\s+") ,(format "~%"))))))
131 (define normalize-example
132 (compose escape-texi (make-reg-replacer '(("^\\s+|\\s+$" "")))))
134 (define trim-ws (make-reg-replacer '(("^\\s+|\\s+$" ""))))
138 (filter (lambda (e) (not (member e +ignored-sections+))) lst)))
142 (lambda (list-of-entries)
143 (format "~%~A~%" (trim-ws (apply string-append list-of-entries)))))
146 (define format-q-level
148 ;; (apply format "[~A.~A]" (reverse level))))
149 (apply format "FAQ ~A-~A" (reverse level))))
151 (define format-q-description
152 (compose trim-ws (make-reg-replacer `((,(format "~%") " ")))))
155 ;; curr-node up-node (list of nodes) (list of node names) ->
156 ;; ((curr-node curr-name) (next next-name) (prev prev-name) up)
157 (define (find-prev-next-up curr up search-list name-list)
158 (do ((lst search-list (cdr lst))
159 (rlst name-list (cdr rlst))
161 (prevn up (car rlst)))
162 ((or (null? lst) (equal? (car lst) curr))
163 (values (cons curr (if (pair? rlst) (car rlst) curr))
164 (if (and (pair? lst) (pair? (cdr lst))) ;next
165 (cons (cadr lst) (cadr rlst))
171 (define (format-node section title up lst-of-nodes lst-of-names)
172 (if (member title +ignored-sections+)
175 (lambda () (find-prev-next-up title up lst-of-nodes lst-of-names))
176 (lambda (currn prevn nextn up)
177 (format "~%@node ~A~%~A ~A~%"
178 (cdr currn) ;; (cdr prevn) (cdr nextn) up
179 section ;; @subsection etc.
181 (apply format "~A.~A" (reverse title))
187 (lambda (alist-of-entries)
188 (let ((len (apply max (map (lambda (s) (string-length (car s))) alist-of-entries))))
189 (format "~%@menu~%~A@end menu~%"
192 (format "* ~A::~A~A~%"
194 (make-string (- len (string-length (car e)) -3) #\ )
195 (format-menu-description (cdr e) +width+ (+ len 7))))
196 alist-of-entries))))))
199 (define format-menu-description
200 (lambda (entry width offset)
201 (let loop ((lst (pregexp-split "\\s" entry)) (len 0) (acc '()))
203 (apply string-append (reverse! acc))
204 (let ((slen (+ 1 (string-length (car lst))))) ; +1 because of whitespace added later
205 (if (> (+ slen len) (- width offset))
206 (loop (cdr lst) 0 (cons
207 (format "~%~A ~A" ; start a new line
208 (make-string offset #\ ) ; the whitespace
211 (loop (cdr lst) (+ slen len) (cons (format " ~A"(car lst)) acc))))))))
214 (define format-sub-titles
215 (lambda (list-of-entries first-number-entry last-number-entry)
216 (let ((offset (or (list-index (lambda (e) (equal? e first-number-entry)) list-of-entries) 0))
217 (end (or (list-index (lambda (e) (equal? e last-number-entry)) list-of-entries)
218 (length list-of-entries))))
219 (map (lambda (entry ind)
221 (if (<= offset ind end)
222 (format "~A -" (- ind offset -1)) ;numbered entry
225 list-of-entries (number-list 0 1 list-of-entries)))))
227 ;;;; We number some sections first
229 ;; ntags is an alist => ((tag startcounter increment)
230 (define (number-nodes tree level ntags)
233 (let* ((vals (car ntags))
235 (start (second vals))
240 (lambda (node sublevel)
244 . ,(lambda (tag . entry)
245 `(,tag ,(cons sublevel level)
246 ,@(number-nodes entry (cons sublevel level) ntags))))
247 (*default* . ,(lambda x x))
248 (*text* . ,(lambda (tag s) s)))))
249 tree (number-list start inc tree)))))
252 ;;(transform->numbered faqsxml '(section article qandaset ((qandadiv 1 1) (qandaentry 0 1))))
253 (define transform->numbered
255 (let* ((rules (reverse rules))
258 (styles (map (lambda (tag) (cons tag (lambda x x))) (list-tail rules 2))))
261 `((*default* *preorder* . ,(lambda x x))
262 (*TOP* . ,(lambda x x))
265 . ,(lambda (tag . nodes)
266 (cons tag (number-nodes nodes '() rule)))))))))
269 ;;;; The main transform function
271 (define (transform sxml)
272 (let* ((sxml (transform->numbered
273 sxml '(section article qandaset ((qandadiv 1 1) (qandaentry 0 1)))))
274 (qandadivtitles (filter-sect (map second ((sxpath '(// qandadiv title)) sxml))))
275 (fqandadivtitles (format-sub-titles qandadivtitles "" ""))
276 (subtitles (filter-sect (append (map second ((sxpath '(// section title)) sxml))
278 (map second ((sxpath '(// glossary title)) sxml)))))
279 (fsubtitles (format-sub-titles subtitles +first-numbered-section+
280 +last-numbered-section+))
281 (questlevel (map second ((sxpath '(article section qandaset qandadiv qandaentry)) sxml)))
282 (up1 (cadar ((sxpath '(article articleinfo title)) sxml)))
284 ;;; ************************************************************
286 ;;; ************************************************************
289 ;;; ************************************************************
290 ;;; First the SXML special markers
291 ;;; ************************************************************
292 ;; *TOP* *PI* @ are markers from SXML
293 (*TOP* . ,(lambda (tag . x) x))
294 (*PI* . ,(lambda _ '()))
297 ;; Look for the example rule where we overwrite the *text* rule
298 ;; so code doesn't get mangled.
300 . ,(lambda (tag string)
302 ;; If nothing else matches
303 (*default* . ,(lambda x x))
304 ;;; ************************************************************
305 ;;; Now to the tags of our FAQ
306 ;;; ************************************************************
307 (article . ,(lambda (tag . sects)
308 (list (boilerplate up1) sects
309 +tag-for-gnus-faq-texi+)))
312 ((*default* . ,(lambda _ '()))
314 . ,(lambda (tag titel)
315 (let ((menucom (map (lambda (entry)
316 (let ((e (assoc entry +section-comments-alist+)))
319 (list (format-node '@section titel "" '() '())
320 (format-menu (map cons fsubtitles menucom)))))))
321 . ,(lambda (tag . info) info))
325 . ,(lambda (tag . text)
326 (cons (format "~%@subheading Abstract~%") text)))
329 . ,(lambda (tag titel)
330 (format-node '@subheading titel up1 subtitles fsubtitles))))
331 . ,(lambda (tag . entry) entry))
333 ;; Q&A well it's called FAQ isn't it?
334 (qandaset . ,(lambda (tag . x) x))
337 . ,(lambda (tag titel) titel)))
338 . ,(lambda (tag level titel . entries)
339 (let ((questions (map cadr entries))
340 (nlevel (filter (lambda (lvl) (eq? (car level) (cadr lvl))) questlevel)))
342 (format-node '@subsection titel up1 subtitles fsubtitles)
343 (format-menu (map (lambda (lvl quest)
344 (cons (format-q-level lvl)
345 (format-q-description quest)))
349 . ,(lambda (tag level question answer)
351 (filter (lambda (lvl) (eq? (cadr lvl) (cadr level))) questlevel))
352 (up (list-ref fqandadivtitles (- (cadr level) 1))))
354 (format-node "@subsubheading Question" level up nodes (map format-q-level nodes))
356 (question . ,(lambda (tag quest) quest))
358 . ,(lambda (tag . answ) (list* (format "~%@subsubheading Answer~%") answ)))
361 (para . ,(lambda (tag . x) (format-para x)))
362 (simpara . ,(lambda (tag . x) (cons (format "~%") x)))
365 ;; We rewrite para here because it plays here the role of an
368 . ,(lambda (tag lstitem)
369 (format "~%@itemize @bullet~%~A@end itemize~%" lstitem)))
372 . ,(lambda (tag item)
373 (format "~%@item~%~A~%" (trim-ws item)))))
374 . ,(lambda (tag . x) (string-join x "")))
378 ((title . ,(lambda _'())))
379 . ,(lambda (tag . terms)
380 (let ((titel (cadar ((sxpath '(article glossary title)) sxml))))
381 (cons (format-node '@subsection titel up1 subtitles fsubtitles)
382 (list (format "~%@table @dfn~%")
384 (format "~%@end table~%"))))))
385 (glossentry . ,(lambda (tag . entry) entry))
387 . ,(lambda (tag term)
388 (format "~%@item ~A" term)))
390 . ,(lambda (tag def) def))
393 ;; We rewrite the *text* rule so code stays the way it's writen.
396 . ,(lambda (tag exampl)
397 (normalize-example exampl))))
398 . ,(lambda (tag . exampl)
399 (format "~%@example~%~A~%@end example~%@noindent~%" (string-join exampl ""))))
402 ;; Here we are interested in the attributes, so we rewrite the @
403 ;; rule. If we find a value we look if it's an email or http
407 . ,(lambda (at val) val)))
408 . ,(lambda (tag uri name)
409 (if (pregexp-match "^http:|^ftp:" uri)
410 (if (equal? uri name)
411 (format "@uref{~A}" uri)
412 (format "@uref{~A, ~A}" uri name))
413 (format "@email{~A, ~A}" (substring uri 7) name))))
415 . ,(lambda (tag val) val))
420 (format "@samp{~A}" val)))
422 (pre-post-order sxml style-sheet)))
424 ;;;; We call main with infile and outfile as arguments
427 (with-output-to-file out
429 (call-with-input-file in
431 (SRV:send-reply (transform (ssax:xml->sxml port '()))))))
435 ;; coding: iso-8859-1
438 ;;; xml2texi.scm ends here