Added the ability to disambiguate between citations using autobib.

This commit is contained in:
James Ian Johnson 2012-03-07 12:31:55 -05:00 committed by Sam Tobin-Hochstadt
parent ac99b732fa
commit 6f1c6b7d92
2 changed files with 200 additions and 144 deletions

View File

@ -5,6 +5,8 @@
scribble/decode
scribble/html-properties
scribble/latex-properties
(for-syntax syntax/parse
racket/base)
scheme/string
setup/main-collects)
@ -31,18 +33,33 @@
(define-struct (author-element element) (names cite))
(define-struct (other-author-element author-element) ())
(define (add-cite group bib-entry which with-specific?)
;; render the use of a citation.
(define (add-cite group bib-entry which with-specific? with-disambiguation?)
(let ([key (auto-bib-key bib-entry)])
(hash-set! (bib-group-ht group) key bib-entry)
(make-delayed-element
(lambda (renderer part ri)
(let ([s (resolve-get part ri `(,which ,key))])
(list (make-link-element #f
;; (list which key) should be mapped to the bibliography element.
(define s (resolve-get part ri `(,which ,key)))
(define disambiguation
(cond [(and with-disambiguation?
(resolve-get part ri `(autobib-disambiguation ,key)))
=>
(λ (dis)
(case dis
[(unambiguous) '()]
[else
(list
(make-link-element #f (list dis)
`(autobib ,(auto-bib-key bib-entry))))]))]
[else '()]))
(cons (make-link-element #f
(list (or s "???")
(if with-specific?
(auto-bib-specific bib-entry)
""))
`(autobib ,(auto-bib-key bib-entry))))))
`(autobib ,(auto-bib-key bib-entry)))
disambiguation))
(lambda () "(???)")
(lambda () "(???)"))))
@ -55,12 +72,12 @@
(map (compose author-element-names auto-bib-author) bib-entries)))
(make-element
#f
(list (add-cite group (car bib-entries) 'autobib-author #f)
(list (add-cite group (car bib-entries) 'autobib-author #f #f)
'nbsp
"("
(let loop ([keys bib-entries])
(if (null? (cdr keys))
(add-cite group (car keys) 'autobib-date #t)
(add-cite group (car keys) 'autobib-date #t #t)
(make-element
#f
(list (loop (list (car keys)))
@ -84,10 +101,10 @@
(make-element
#f
(list*
(add-cite group (car v) 'autobib-author #f)
(add-cite group (car v) 'autobib-author #f #f)
" "
(add-between
(for/list ([b v]) (add-cite group b 'autobib-date #t))
(for/list ([b v]) (add-cite group b 'autobib-date #t #t))
", ")))))
"; ")
(list ")"))))
@ -98,63 +115,94 @@
(define (extract-bib-year b)
(string->number (auto-bib-date b)))
;; 0 -> a, 1 -> b, etc.
(define (default-disambiguation n)
(when (>= n 26)
(error 'default-disambiguation "Citations too ambiguous for default disambiguation scheme."))
(make-element #f (list (format " ~a" (integer->char (+ 97 n))))))
(define (gen-bib tag group sec-title)
(let* ([author/date<?
(lambda (a b)
(or
(string-ci<? (extract-bib-key a) (extract-bib-key b))
(define (gen-bib tag group sec-title maybe-disambiguator)
(define disambiguator (or maybe-disambiguator default-disambiguation))
(define (author/date<? a b)
(or (string-ci<? (extract-bib-key a) (extract-bib-key b))
(and (string-ci=? (extract-bib-key a) (extract-bib-key b))
(extract-bib-year a) (extract-bib-year b)
(< (extract-bib-year a) (extract-bib-year b)))))]
[bibs (sort (hash-map (bib-group-ht group)
(lambda (k v) v))
author/date<?)])
(make-part
(extract-bib-year a)
(extract-bib-year b)
(< (extract-bib-year a) (extract-bib-year b)))))
(define (ambiguous? a b)
(and (string-ci=? (extract-bib-key a) (extract-bib-key b))
(extract-bib-year a)
(extract-bib-year b)
(= (extract-bib-year a) (extract-bib-year b))))
(define bibs (sort (hash-values (bib-group-ht group))
author/date<?))
(define disambiguated
(let ()
(define (bib->para bib [disambiguation #f])
(define collect-target
(list (make-target-element
#f
(list (auto-bib-entry-element bib))
`(autobib ,(auto-bib-key bib)))))
;; Communicate to scribble's resolve step.
(define (collect ci)
;; store the author
(collect-put! ci
`(autobib-author ,(auto-bib-key bib)) ;; (list which key)
(make-element
#f
(list (author-element-cite (auto-bib-author bib)))))
;; store the date
(collect-put! ci
`(autobib-date ,(auto-bib-key bib)) ;; (list which key)
(make-element #f (list (auto-bib-date bib))))
;; store how to disambiguate it from other like citations.
(collect-put! ci
`(autobib-disambiguation ,(auto-bib-key bib))
(or disambiguation 'unambiguous)))
(list
(make-paragraph plain
(list (make-collect-element #f collect-target collect)))))
;; create the bibliography with disambiguations added.
(define-values (last num-ambiguous rev-disambiguated*)
(for/fold ([last #f] [num-ambiguous 0] [rev-disambiguated '()]) ([bib (in-list bibs)])
(define ambiguous?? (and last (ambiguous? last bib)))
(define num-ambiguous*
(cond [ambiguous?? (add1 num-ambiguous)]
[else 0]))
;; the current entry is ambiguous with the last. Modify the last
;; to have the first disambiguation.
(define rev-disambiguated*
(cond [(and ambiguous?? (= 0 num-ambiguous))
(cons (bib->para last (disambiguator num-ambiguous))
(cdr rev-disambiguated))]
[else rev-disambiguated]))
(define para*
(bib->para bib (and ambiguous?? (disambiguator num-ambiguous*))))
(values bib num-ambiguous* (cons para* rev-disambiguated*))))
(reverse rev-disambiguated*)))
(make-part #f
`((part ,tag))
(list sec-title)
(make-style #f '(unnumbered))
null
(list
(make-table
bib-table-style
(map (lambda (k)
(list
(make-paragraph
plain
(list
(make-collect-element
#f
(list (make-target-element
#f
(list (auto-bib-entry-element k))
`(autobib ,(auto-bib-key k))))
(lambda (ci)
(collect-put! ci
`(autobib-author ,(auto-bib-key k))
(make-element
#f
(list
(author-element-cite (auto-bib-author k)))))
(collect-put! ci
`(autobib-date ,(auto-bib-key k))
(make-element
#f
(list
(auto-bib-date k))))))))))
bibs)))
null)))
(list (make-table bib-table-style disambiguated))
null))
(define-syntax-rule (define-cite ~cite citet generate-bibliography)
(define-syntax (define-cite stx)
(syntax-parse stx
[(_ cite* citet generate-bibliography
(~optional (~seq #:disambiguate fn)
#:defaults ([fn #'#f])))
(syntax/loc stx
(begin
(define group (make-bib-group (make-hasheq)))
(define (~cite #:sort? [sort? #t] bib-entry . bib-entries)
(define (cite* #:sort? [sort? #t] bib-entry . bib-entries)
(add-cites group (cons bib-entry bib-entries) sort?))
(define (citet bib-entry . bib-entries)
(add-inline-cite group (cons bib-entry bib-entries)))
(define (generate-bibliography #:tag [tag "doc-bibliography"] #:sec-title [sec-title "Bibliography"])
(gen-bib tag group sec-title))))
(gen-bib tag group sec-title fn))))]))
(define (ends-in-punc? e)
(regexp-match? #rx"[.!?,]$" (content->string e)))
@ -169,8 +217,7 @@
[(not author) #f]
[(author-element? author) author]
[else (parse-author author)])]
[elem (make-element
bibentry-style
[content
(append
(if author
`(,author
@ -190,7 +237,8 @@
`(" " ,@(decode-content (list location)) ,(if date "," "."))
null)
(if date `(" " ,@(decode-content (list (to-string date))) ".") null)
(if url `(" " ,(link url (make-element 'url (list url)))) null)))])
(if url `(" " ,(link url (make-element 'url (list url)))) null))]
[elem (make-element bibentry-style content)])
(make-auto-bib
(or author (org-author-name title))
(to-string date)
@ -204,22 +252,21 @@
(auto-bib-date bib)
(auto-bib-entry-element bib)
(auto-bib-key bib)
;; "where" is the only specific part of auto-bib elements currently.
(string-append (auto-bib-specific bib) where)))
(define (parse-author a)
(if (author-element? a)
a
(let* ([s (content->string a)]
[m (regexp-match #px"^(.*) (([\\-]|\\p{L})+)$" s)])
(make-author-element
#f
(list a)
(if m
(string-append (caddr m) " " (cadr m))
s)
(if m
(caddr m)
s)))))
(cond [(author-element? a) a]
[else
(define s (content->string a)) ;; plain text rendering
(define m (regexp-match #px"^(.*) (([\\-]|\\p{L})+)$" s))
(define names
(cond [m (string-append (caddr m) " " (cadr m))]
[else s]))
(define cite
(cond [m (caddr m)]
[else s]))
(make-author-element #f (list a) names cite)]))
(define (proceedings-location
location
@ -308,27 +355,10 @@
"al."
"al."))
(define (authors name . names)
(let ([names (map parse-author (cons name names))])
(make-author-element
#f
(let loop ([names names] [prefix 0])
(if (null? (cdr names))
(case prefix
[(0) (list (car names))]
[(1) (if (other-author-element? (car names))
(list " et al.")
(list " and " (car names)))]
[else (if (other-author-element? (car names))
(list ", et al.")
(list ", and " (car names)))])
(case prefix
[(0) (list* (car names)
(loop (cdr names) (add1 prefix)))]
[else (list* ", "
(car names)
(loop (cdr names) (add1 prefix)))])))
(string-join (map author-element-names names) " / ")
(define (authors name . names*)
(define names (map parse-author (cons name names*)))
(define slash-names (string-join (map author-element-names names) " / "))
(define cite
(case (length names)
[(1) (author-element-cite (car names))]
[(2) (if (other-author-element? (cadr names))
@ -336,7 +366,28 @@
(format "~a and ~a"
(author-element-cite (car names))
(author-element-cite (cadr names))))]
[else (format "~a et al." (author-element-cite (car names)))]))))
[else (format "~a et al." (author-element-cite (car names)))]))
(make-author-element
#f
(let loop ([names names] [prefix 0])
(cond [(null? (cdr names))
(case prefix
[(0) names]
[(1) (if (other-author-element? (car names))
(list " et al.")
(list " and " (car names)))]
[else (if (other-author-element? (car names))
(list ", et al.")
(list ", and " (car names)))])]
[else
(case prefix
[(0) (list* (car names)
(loop (cdr names) (add1 prefix)))]
[else (list* ", "
(car names)
(loop (cdr names) (add1 prefix)))])]))
slash-names
cite))
(define (editor name)
(let ([name (parse-author name)])

View File

@ -9,11 +9,16 @@
@defmodule[scriblib/autobib]
@defform[(define-cite ~cite-id citet-id generate-bibliography-id)]{
@defform[(define-cite ~cite-id citet-id generate-bibliography-id [#:disambiguate disambiguator])]{
Binds @racket[~cite-id], @racket[citet-id], and
@racket[generate-bibliography-id], which share state to accumulate and
render citations.
@racket[generate-bibliography-id], which share state to accumulate and render
citations. If two citations' references would render the same but are
different, the optionally provided disambiguation function is used to add an
extra element after the date. The default disambiguator will add "a", "b", etc
until "z". Anything more ambiguous will throw an error. It has the contract
@racketblock[(-> exact-nonnegative-integer? element?)]
The function bound to @racket[~cite-id] produces a citation referring
to one or more bibliography entries with a preceding non-breaking