1#!/usr/bin/env python
2
3r"""
4natbib
5
6TODO:
7    - compress multiple years
8    - \shortcites
9    - Indexing features
10    - Bibliography preamble
11
12"""
13
14import plasTeX, re, string
15from plasTeX import Base, Text
16
17log = plasTeX.Logging.getLogger()
18
19PackageOptions = {}
20
21def ProcessOptions(options, document):
22    """ Process package options """
23    context = document.context
24    if options is None:
25        return
26    PackageOptions.update(options)
27    for key, value in list(options.items()):
28        if key == 'numbers':
29            bibpunct.punctuation['style'] = 'n'
30            ProcessOptions({'square':True, 'comma':True}, document)
31        elif key == 'super':
32            bibpunct.punctuation['style'] = 's'
33            bibpunct.punctuation['open'] = ''
34            bibpunct.punctuation['close'] = ''
35        elif key == 'authoryear':
36            ProcessOptions({'round':True, 'colon':True}, document)
37        elif key == 'round':
38            bibpunct.punctuation['open'] = '('
39            bibpunct.punctuation['close'] = ')'
40        elif key == 'square':
41            bibpunct.punctuation['open'] = '['
42            bibpunct.punctuation['close'] = ']'
43        elif key == 'angle':
44            bibpunct.punctuation['open'] = '<'
45            bibpunct.punctuation['close'] = '>'
46        elif key == 'curly':
47            bibpunct.punctuation['open'] = '{'
48            bibpunct.punctuation['close'] = '}'
49        elif key == 'comma':
50            bibpunct.punctuation['sep'] = ','
51        elif key == 'colon':
52            bibpunct.punctuation['sep'] = ';'
53        elif key == 'sectionbib':
54            Base.bibliography.level = Base.section.level
55        elif key == 'sort':
56            pass
57        elif key in ['sort&compress','sortandcompress']:
58            pass
59        elif key == 'longnamesfirst':
60            pass
61        elif key == 'nonamebreak':
62            pass
63
64class bibliography(Base.bibliography):
65
66    class setcounter(Base.Command):
67        # Added so that setcounters in the aux file don't mess counters up
68        args = 'name:nox num:nox'
69
70    def loadBibliographyFile(self, tex):
71        doc = self.ownerDocument
72        # Clear out any bib info from the standard package.
73        # We have to get our info from the aux file.
74        doc.userdata.setPath('bibliography/bibcites', {})
75        self.ownerDocument.context.push(self)
76        self.ownerDocument.context['setcounter'] = self.setcounter
77        tex.loadAuxiliaryFile()
78        self.ownerDocument.context.pop(self)
79        Base.bibliography.loadBibliographyFile(self, tex)
80
81class bibstyle(Base.Command):
82    args = 'style:str'
83
84class citestyle(Base.Command):
85    args = 'style:str'
86
87    styles = {
88        'plain'  : [', ','[',']',',','n','',','],
89        'plainnat':[', ','[',']',',','a',',',','],
90        'chicago': [', ','(',')',';','a',',',','],
91        'chicago': [', ','(',')',';','a',',',','],
92        'named'  : [', ','[',']',';','a',',',','],
93        'agu'    : [', ','[',']',';','a',',',', '],
94        'egs'    : [', ','(',')',';','a',',',','],
95        'agsm'   : [', ','(',')',';','a','',','],
96        'kluwer' : [', ','(',')',';','a','',','],
97        'dcu'    : [', ','(',')',';','a',';',','],
98        'aa'     : [', ','(',')',';','a','',','],
99        'pass'   : [', ','(',')',';','a',',',','],
100        'anngeo' : [', ','(',')',';','a',',',','],
101        'nlinproc':[', ','(',')',';','a',',',','],
102        'cospar' : [', ','/','/',',','n','',''],
103        'esa'    : [', ','(Ref. ',')',',','n','',''],
104        'nature' : [', ','','',',','s','',','],
105    }
106
107    def invoke(self, tex):
108        res = Base.Command.invoke(self, tex)
109        try:
110            s = self.styles[self.attributes['style']]
111        except KeyError:
112        #    log.warning('Could not find bibstyle: "%s"',
113        #                 self.attributes['style'])
114            return res
115        p = bibpunct.punctuation
116        for i, opt in enumerate(['post','open','close','sep','style','dates','years']):
117            p[opt] = s[i]
118        return res
119
120class bstyleoption(Text):
121    """ Option that can only be overridden by package options,
122        citestyle, or bibpunct """
123
124class bibliographystyle(citestyle):
125    def invoke(self, tex):
126        res = Base.Command.invoke(self, tex)
127        try:
128            s = self.styles[self.attributes['style']]
129        except KeyError:
130        #    log.warning('Could not find bibstyle: "%s"',
131        #                 self.attributes['style'])
132            return res
133        p = bibpunct.punctuation
134        for i, opt in enumerate(['post','open','close','sep','style','dates','years']):
135            if isinstance(p[opt], bstyleoption):
136                p[opt] = s[i]
137        return res
138
139class bibpunct(Base.Command):
140    """ Set up punctuation of citations """
141    args = '[ post:str ] open:str close:str sep:str ' + \
142           'style:str dates:str years:str'
143    punctuation = {'post': bstyleoption(', '),
144                   'open': bstyleoption('('),
145                   'close':bstyleoption(')'),
146                   'sep':  bstyleoption(';'),
147                   'style':bstyleoption('a'),
148                   'dates':bstyleoption(','),
149                   'years':bstyleoption(',')}
150    def invoke(self, tex):
151        res = Base.Command.invoke(self, tex)
152        for key, value in list(self.attributes.items()):
153            if value is None:
154                continue
155            elif type(value) == str or type(value) == str:
156                bibpunct.punctuation[key] = value
157            else:
158                bibpunct.punctuation[key] = value.textContent
159        return res
160
161class bibcite(Base.Command):
162    """ Auxiliary file information """
163    args = 'key:str info'
164
165    def invoke(self, tex):
166        Base.Command.invoke(self, tex)
167        value, year, author, fullauthor = list(self.attributes['info'])
168        value.attributes['year'] = year
169        value.attributes['author'] = author
170        if not fullauthor.textContent.strip():
171            value.attributes['fullauthor'] = author
172        else:
173            value.attributes['fullauthor'] = fullauthor
174        doc = self.ownerDocument
175        bibcites = doc.userdata.getPath('bibliography/bibcites', {})
176        bibcites[self.attributes['key']] = value
177        doc.userdata.setPath('bibliography/bibcites', bibcites)
178
179class thebibliography(Base.thebibliography):
180
181    class bibitem(Base.thebibliography.bibitem):
182
183        @property
184        def bibcite(self):
185            try:
186                doc = self.ownerDocument
187                return doc.userdata.getPath('bibliography/bibcites', {})[self.attributes['key']]
188            except KeyError as msg:
189                pass
190            # We don't have a citation that matches, fill the fields
191            # with dummy data
192            value = self.ownerDocument.createElement('citation')
193            value.parentNode = self
194            value.append('??')
195            for item in ['year','author','fullauthor']:
196                obj = self.ownerDocument.createDocumentFragment()
197                obj.parentNode = value
198                obj.append('??')
199                value.attributes[item] = obj
200            return value
201
202        def ref():
203            def fset(self, value):
204                pass
205            def fget(self):
206                return self.bibcite.textContent
207            return locals()
208        ref = property(**ref())
209
210class harvarditem(thebibliography.bibitem):
211    args = '[ abbrlabel ] label year key:str'
212
213class NatBibCite(Base.cite):
214    """ Base class for all natbib-style cite commands """
215    args = '* [ text ] [ text2 ] bibkeys:list:str'
216
217    class Connector(str):
218        pass
219
220    @property
221    def bibitems(self):
222        items = []
223        opts = PackageOptions
224        doc = self.ownerDocument
225        b = doc.userdata.getPath('bibliography/bibitems', {})
226        for key in self.attributes['bibkeys']:
227            if key in b:
228                items.append(b[key])
229        if bibpunct.punctuation['style'] in 'ns' and \
230           ('sort' in opts or 'sort&compress' in opts or 'sortandcompress' in opts):
231            items.sort(lambda x, y: int(x.ref) - int(y.ref))
232            if 'sort&compress' in opts or 'sortandcompress' in opts:
233                items = self.compressRange(items)
234        return items
235
236    def compressRange(self, items):
237        """ Compress ranges of numbers """
238        idx, idxdict = [], {}
239        for i, value in enumerate(items):
240            idx.append(int(value.ref))
241            idxdict[int(value.ref)] = value
242        output = []
243        for i, value in enumerate(idx):
244            if i == 0:
245                output.append(' ')
246                output.append(value)
247            elif idx[i-1] == (value-1):
248                output.append('-')
249                output.append(value)
250            else:
251                output.append(' ')
252                output.append(value)
253        output.append(' ')
254        output = ''.join([str(x) for x in output])
255        output = re.sub(r'( \d+)-(\d+ )', r'\1 \2', output)
256        while re.search(r'-\d+-', output):
257            output = re.sub(r'-\d+-', r'-', output)
258        output = [x for x in re.split(r'([ -])', output) if x.strip()]
259        for i, value in enumerate(output):
260            if value in string.digits:
261                output[i] = idxdict[int(value)]
262            else:
263                output[i] = self.Connector(value)
264        return output
265
266    def isConnector(self, value):
267        return isinstance(value, self.Connector)
268
269    @property
270    def prenote(self):
271        """ Text that comes before the citation """
272        a = self.attributes
273        if a.get('text2') is not None and a.get('text') is not None:
274            if not a.get('text').textContent.strip():
275                return ''
276            out = self.ownerDocument.createElement('bgroup')
277            out.extend(a['text'])
278            out.append(' ')
279            return out
280        return ''
281
282    @property
283    def postnote(self):
284        """ Text that comes after the citation """
285        a = self.attributes
286        if a.get('text2') is not None and a.get('text') is not None:
287            if not a.get('text2').textContent.strip():
288                return ''
289            out = self.ownerDocument.createElement('bgroup')
290            out.append(bibpunct.punctuation['post'])
291            out.extend(a['text2'])
292            return out
293        elif a.get('text') is not None:
294            if not a.get('text').textContent.strip():
295                return ''
296            out = self.ownerDocument.createElement('bgroup')
297            out.append(bibpunct.punctuation['post'])
298            out.extend(a['text'])
299            return out
300        return ''
301
302    @property
303    def separator(self):
304        """ Separator for multiple items """
305        return bibpunct.punctuation['sep']
306
307    @property
308    def dates(self):
309        """ Separator between author and dates """
310        return bibpunct.punctuation['dates']
311
312    @property
313    def years(self):
314        """ Separator for multiple years """
315        return bibpunct.punctuation['years']
316
317    def selectAuthorField(self, key, full=False):
318        """ Determine if author should be a full name or shortened """
319        if full or self.attributes.get('*modifier*'):
320            return 'fullauthor'
321        doc = self.ownerDocument
322        # longnamesfirst means that only the first reference
323        # gets the full length name, the rest use short names.
324        cited = doc.userdata.getPath('bibliography/cited', [])
325        if 'longnamesfirst' in PackageOptions and key not in cited:
326            full = True
327            cited.append(key)
328        doc.userdata.setPath('bibliography/cited', cited)
329        if full:
330            return 'fullauthor'
331        return 'author'
332
333    def isNumeric(self):
334        return bibpunct.punctuation['style'] in ['n','s']
335
336    def isSuperScript(self):
337        return bibpunct.punctuation['style'] == 's'
338
339    def citeValue(self, item, text=None):
340        """ Return cite value based on current style """
341        b = self.ownerDocument.createElement('bibliographyref')
342        b.idref['bibitem'] = item
343        if text is not None:
344            b.append(text)
345        elif bibpunct.punctuation['style'] in ['n','s']:
346            b.append(item.bibcite)
347        else:
348            b.append(item.bibcite.attributes['year'])
349        return b
350
351    def capitalize(self, item):
352        """ Capitalize the first text node """
353        item = item.cloneNode(True)
354        textnodes = [x for x in item.allChildNodes
355                       if x.nodeType == self.TEXT_NODE]
356        if not textnodes:
357            return item
358        node = textnodes.pop(0)
359        node.parentNode.replaceChild(node.cloneNode(True).capitalize() ,node)
360        return item
361
362# class citep(NatBibCite):
363
364#     def numcitation(self):
365#         """ Numeric style citations """
366#         res = []
367#         res.append(bibpunct.punctuation['open'])
368#         for i, item in enumerate(self.bibitems):
369#             frag = self.ownerDocument.createDocumentFragment()
370#             frag.append(item.ref)
371#             frag.idref = item
372#             res.append(frag)
373#             res.append(bibpunct.punctuation['sep'])
374#         res.pop()
375#         res.append(bibpunct.punctuation['close'])
376#         return res
377
378#     def citation(self):
379#         if bibpunct.punctuation['style'] == 'n':
380#             return self.numcitation()
381#         elif bibpunct.punctuation['style'] == 's':
382#             return self.numcitation()
383
384#         res = []
385#         res.append(bibpunct.punctuation['open'] + self.prenote)
386#         prevauthor = None
387#         prevyear = None
388#         duplicateyears = 0
389#         previtem = None
390#         for i, item in enumerate(self.bibitems):
391#             currentauthor = item.citeauthor().textContent
392#             currentyear = item.citeyear().textContent
393
394#             # Previous author and year are the same
395#             if prevauthor == currentauthor and prevyear == currentyear:
396#                 res.pop()
397#                 # This is the first duplicate
398#                 if duplicateyears == 0:
399#                     # Make a reference that points to the same item as
400#                     # the first citation in this set.  This will make
401#                     # hyperlinked output prettier since the 'a' will
402#                     # be linked to the same place as the reference that
403#                     # we just put out.
404#                     res.append('')
405#                     frag = self.ownerDocument.createDocumentFragment()
406#                     frag.append('a')
407#                     frag.idref = previtem
408#                     res.append(frag)
409#                     res.append(bibpunct.punctuation['years'])
410#                 else:
411#                     res.append(bibpunct.punctuation['years'])
412#                 # Create a new fragment with b,c,d... in it
413#                 frag = self.ownerDocument.createDocumentFragment()
414#                 frag.append(chr(duplicateyears+ord('b')))
415#                 frag.idref = item
416#                 res.append(frag)
417#                 res.append(bibpunct.punctuation['sep']+' ')
418#                 duplicateyears += 1
419
420#             # Previous author is the same
421#             elif prevauthor == currentauthor:
422#                 duplicateyears = 0
423#                 res.pop()
424#                 res.append(bibpunct.punctuation['years']+' ')
425#                 res.append(item.citeyear())
426#                 res.append(bibpunct.punctuation['sep']+' ')
427
428#             # Nothing about the previous citation is the same
429#             else:
430#                 doc = self.ownerDocument
431#                 cited = doc.userdata.getPath('bibliography/cited', [])
432#                 duplicateyears = 0
433#                 if 'longnamesfirst' in PackageOptions and \
434#                    item.attributes['key'] not in cited:
435#                     cited.append(item.attributes['key'])
436#                     doc.userdata.setPath('bibliography/cited', cited)
437#                     res.append(item.citealp(full=True))
438#                 else:
439#                     res.append(item.citealp())
440#                 res.append(bibpunct.punctuation['sep']+' ')
441
442#             prevauthor = currentauthor
443#             prevyear = currentyear
444#             previtem = item
445
446#         res.pop()
447#         res.append(self.postnote + bibpunct.punctuation['close'])
448#         return res
449
450class citet(NatBibCite):
451
452    def citation(self, full=False, capitalize=False, text=None):
453        """ Jones et al. (1990) """
454        if text is None and self.isNumeric():
455            return self.numcitation()
456        res = self.ownerDocument.createDocumentFragment()
457        i = 0
458        sameauthor = prevauthor = None
459        for i, item in enumerate(self.bibitems):
460            if text is None:
461                if not item.bibcite.attributes:
462                    continue
463                fullauthor = item.bibcite.attributes['fullauthor'].textContent
464                sameauthor = (prevauthor == fullauthor)
465                prevauthor = fullauthor
466                # Author, only print author if it wasn't equal to the last author
467                if sameauthor:
468                    # Pop punctuation from previous year
469                    res.pop()
470                    res.pop()
471                    # Add year separator
472                    res.append(self.years+' ')
473                else:
474                    author = self.selectAuthorField(item.attributes['key'], full=full)
475                    if i == 0 and capitalize:
476                        res.extend(self.capitalize(item.bibcite.attributes[author]))
477                    else:
478                        res.extend(item.bibcite.attributes[author])
479                    res.append(' ')
480                    res.append(bibpunct.punctuation['open'])
481                    # Prenote
482                    res.append(self.prenote)
483            # Year or text
484            res.append(self.citeValue(item, text=text))
485            # Separator, postnote, and closing punctuation
486            if i < (len(self.bibitems)-1):
487                if text is None:
488                    res.append(bibpunct.punctuation['close'])
489                    res.append(self.separator+' ')
490            else:
491                res.append(self.postnote)
492                if text is None:
493                    res.append(bibpunct.punctuation['close'])
494        return res
495
496    def numcitation(self, full=False, capitalize=False):
497        """ (1, 2) """
498        element = self.ownerDocument.createElement
499        orig = res = self.ownerDocument.createDocumentFragment()
500        if self.isSuperScript():
501            group = element('bgroup')
502            orig.append(group)
503            res = element('active::^')
504            group.append(res)
505        i = 0
506        res.append(self.prenote)
507        res.append(bibpunct.punctuation['open'])
508        for i, item in enumerate(self.bibitems):
509            if self.isConnector(item):
510                res.pop()
511                res.append('-')
512                continue
513            res.append(self.citeValue(item))
514            if i < (len(self.bibitems)-1):
515                res.append(self.separator+' ')
516        res.append(bibpunct.punctuation['close'])
517        res.append(self.postnote)
518        return orig
519
520class citetfull(citet):
521
522    def citation(self):
523        """ Jones, Baker, and Williams (1990) """
524        return citet.citation(self, full=True)
525
526class Citet(citet):
527
528    def citation(self):
529        return citet.citation(self, capitalize=True)
530
531class citep(NatBibCite):
532
533    def citation(self, full=False, capitalize=False, text=None):
534        """ (Jones et al., 1990) """
535        if text is None and self.isNumeric():
536            return self.numcitation()
537        res = self.ownerDocument.createDocumentFragment()
538        res.append(bibpunct.punctuation['open'])
539        res.append(self.prenote)
540        i = 0
541        sameauthor = prevauthor = None
542        for i, item in enumerate(self.bibitems):
543            if text is None:
544                if item.bibcite.attributes is None:
545                    continue
546                fullauthor = item.bibcite.attributes['fullauthor'].textContent
547                sameauthor = (prevauthor == fullauthor)
548                prevauthor = fullauthor
549                # Author, only print author if it wasn't equal to the last author
550                if sameauthor:
551                    res.pop()
552                    res.append(self.years+' ')
553                else:
554                    author = self.selectAuthorField(item.attributes['key'], full=full)
555                    if i == 0 and capitalize:
556                        res.extend(self.capitalize(item.bibcite.attributes[author]))
557                    else:
558                        res.extend(item.bibcite.attributes[author])
559                    res.append(self.dates+' ')
560            res.append(self.citeValue(item, text=text))
561            if i < (len(self.bibitems)-1):
562                res.append(self.separator+' ')
563            else:
564                res.append(self.postnote)
565                res.append(bibpunct.punctuation['close'])
566        return res
567
568    def numcitation(self):
569        """ (1, 2) """
570        element = self.ownerDocument.createElement
571        orig = res = self.ownerDocument.createDocumentFragment()
572        if self.isSuperScript():
573            group = element('bgroup')
574            orig.append(group)
575            res = element('active::^')
576            group.append(res)
577        res.append(bibpunct.punctuation['open'])
578        res.append(self.prenote)
579        i = 0
580        for i, item in enumerate(self.bibitems):
581            if self.isConnector(item):
582                res.pop()
583                res.append('-')
584                continue
585            res.append(self.citeValue(item))
586            if i < (len(self.bibitems)-1):
587                res.append(self.separator+' ')
588            else:
589                res.append(self.postnote)
590                res.append(bibpunct.punctuation['close'])
591        return orig
592
593class cite(citep, citet):
594
595    def citation(self, full=False, capitalize=False):
596        if self.prenote or self.postnote:
597            return citep.citation(self, full=full, capitalize=capitalize)
598        return citet.citation(self, full=full, capitalize=capitalize)
599
600class Cite(cite):
601
602    def citation(self):
603        return cite.citation(self, capitalize=True)
604
605class citepfull(citep):
606
607    def citation(self):
608        """ (Jones, Baker, and Williams, 1990) """
609        return citep.citation(self, full=True)
610
611class Citep(citep):
612
613    def citation(self):
614        return citep.citation(self, capitalize=True)
615
616class citealt(NatBibCite):
617
618    def citation(self, full=False, capitalize=False):
619        """ Jones et al. 1990 """
620        if self.isNumeric():
621            return self.numcitation()
622        res = self.ownerDocument.createDocumentFragment()
623        i = 0
624        prevauthor = sameauthor = None
625        for i, item in enumerate(self.bibitems):
626            if not item.bibcite.attributes:
627                fullauthor = '???'
628            else:
629                fullauthor = item.bibcite.attributes['fullauthor'].textContent
630            sameauthor = (prevauthor == fullauthor)
631            prevauthor = fullauthor
632            # Author, only print author if it wasn't equal to the last author
633            if sameauthor:
634                res.pop()
635                res.append(self.years+' ')
636            else:
637                author = self.selectAuthorField(item.attributes['key'], full=full)
638                if not item.bibcite.attributes:
639                    res.extend('???')
640                elif i == 0 and capitalize:
641                    res.extend(self.capitalize(item.bibcite.attributes[author]))
642                else:
643                    res.extend(item.bibcite.attributes[author])
644                res.append(' ')
645                res.append(self.prenote)
646            res.append(self.citeValue(item))
647            if i < (len(self.bibitems)-1):
648                res.append(self.separator+' ')
649            else:
650                res.append(self.postnote)
651        return res
652
653    def numcitation(self):
654        """ 1, 2 """
655        element = self.ownerDocument.createElement
656        orig = res = self.ownerDocument.createDocumentFragment()
657        if self.isSuperScript():
658            group = element('bgroup')
659            orig.append(group)
660            res = element('active::^')
661            group.append(res)
662        i = 0
663        res.append(self.prenote)
664        for i, item in enumerate(self.bibitems):
665            if self.isConnector(item):
666                res.pop()
667                res.append('-')
668                continue
669            res.append(self.citeValue(item))
670            if i < (len(self.bibitems)-1):
671                res.append(self.separator+' ')
672            else:
673                res.append(self.postnote)
674        return orig
675
676class citealtfull(citealt):
677
678    def citation(self):
679        """ Jones, Baker, and Williams 1990 """
680        return citealt.citation(self, full=True)
681
682class Citealt(citealt):
683
684    def citation(self):
685        return citealt.citation(self, capitalize=True)
686
687class citealp(NatBibCite):
688
689    def citation(self, full=False, capitalize=False):
690        """ Jones et al., 1990 """
691        if self.isNumeric():
692            return self.numcitation()
693        res = self.ownerDocument.createDocumentFragment()
694        res.append(self.prenote)
695        i = 0
696        prevauthor = sameauthor = None
697        for i, item in enumerate(self.bibitems):
698            if not item.bibcite.attributes:
699                fullauthor = '???'
700            else:
701                fullauthor = item.bibcite.attributes['fullauthor'].textContent
702            sameauthor = (prevauthor == fullauthor)
703            prevauthor = fullauthor
704            # Author, only print author if it wasn't equal to the last author
705            if sameauthor:
706                res.pop()
707                res.append(self.years+' ')
708            else:
709                author = self.selectAuthorField(item.attributes['key'], full=full)
710                if not item.bibcite.attributes:
711                    res.extend('???')
712                elif i == 0 and capitalize:
713                    res.extend(self.capitalize(item.bibcite.attributes[author]))
714                else:
715                    res.extend(item.bibcite.attributes[author])
716                res.append(self.separator+' ')
717            res.append(self.citeValue(item))
718            if i < (len(self.bibitems)-1):
719                res.append(self.separator+' ')
720            else:
721                res.append(self.postnote)
722        return res
723
724    def numcitation(self):
725        """ 1, 2 """
726        element = self.ownerDocument.createElement
727        orig = res = self.ownerDocument.createDocumentFragment()
728        if self.isSuperScript():
729            group = element('bgroup')
730            orig.append(group)
731            res = element('active::^')
732            group.append(res)
733        res.append(self.prenote)
734        i = 0
735        for i, item in enumerate(self.bibitems):
736            if self.isConnector(item):
737                res.pop()
738                res.append('-')
739                continue
740            res.append(self.citeValue(item))
741            if i < (len(self.bibitems)-1):
742                res.append(self.separator+' ')
743        res.append(self.postnote)
744        return orig
745
746class citealpfull(citealp):
747
748    def citation(self):
749        """ Jones, Baker, and Williams, 1990 """
750        return citealp.citation(self, full=True)
751
752class Citealp(citealp):
753
754    def citation(self):
755        return citealp.citation(self, capitalize=True)
756
757class citeauthor(NatBibCite):
758
759    def citation(self, full=False, capitalize=False):
760        """ Jones et al. """
761        if self.isNumeric():
762            return
763        res = self.ownerDocument.createDocumentFragment()
764        #res.append(self.prenote)
765        i = 0
766        for i, item in enumerate(self.bibitems):
767            author = self.selectAuthorField(item.attributes['key'], full=full)
768            b = self.ownerDocument.createElement('bibliographyref')
769            b.idref['bibitem'] = item
770            if not item.bibcite.attributes:
771                b.append('???')
772            elif i == 0 and capitalize:
773                b.append(self.capitalize(item.bibcite.attributes[author]))
774            else:
775                b.append(item.bibcite.attributes[author])
776            res.append(b)
777            if i < (len(self.bibitems)-1):
778                res.append(self.separator+' ')
779            else:
780                res.append(self.postnote)
781        return res
782
783class citefullauthor(citeauthor):
784
785    def citation(self):
786        """ Jones, Baker, and Williams """
787        return citeauthor.citation(self, full=True)
788
789class Citeauthor(citeauthor):
790
791    def citation(self):
792        return citeauthor.citation(self, capitalize=True)
793
794class citeyear(NatBibCite):
795
796    def citation(self):
797        """ 1990 """
798        if self.isNumeric():
799            return
800        res = self.ownerDocument.createDocumentFragment()
801        #res.append(self.prenote)
802        i = 0
803        for i, item in enumerate(self.bibitems):
804            b = self.ownerDocument.createElement('bibliographyref')
805            b.idref['bibitem'] = item
806            if not item.bibcite.attributes:
807                b.append('???')
808            else:
809                b.append(item.bibcite.attributes['year'])
810            res.append(b)
811            if i < (len(self.bibitems)-1):
812                res.append(self.separator+' ')
813            else:
814                res.append(self.postnote)
815        return res
816
817class citeyearpar(NatBibCite):
818
819    def citation(self):
820        """ (1990) """
821        if self.isNumeric():
822            return
823        res = self.ownerDocument.createDocumentFragment()
824        res.append(bibpunct.punctuation['open'])
825        res.append(self.prenote)
826        i = 0
827        for i, item in enumerate(self.bibitems):
828            b = self.ownerDocument.createElement('bibliographyref')
829            b.idref['bibitem'] = item
830            if not item.bibcite.attributes:
831                b.append('???')
832            else:
833                b.append(item.bibcite.attributes['year'])
834            res.append(b)
835            if i < (len(self.bibitems)-1):
836                res.append(self.separator+' ')
837            else:
838                res.append(self.postnote)
839        res.append(bibpunct.punctuation['close'])
840        return res
841
842class citetext(Base.Command):
843    args = 'self'
844    def digest(self, tokens):
845        self.insert(0, bibpunct.punctuation['open'])
846        self.append(bibpunct.punctuation['close'])
847
848class defcitealias(Base.Command):
849    args = 'key:str text'
850    aliases = {}
851    def invoke(self, tex):
852        res = Base.Command.invoke(self, tex)
853        defcitealias.aliases[self.attributes['key']] = self.attributes['text']
854        return res
855
856class citetalias(citet):
857    args = 'bibkeys:list:str'
858    def citation(self):
859        return citet.citation(self, text=defcitealias.aliases.get(self.attributes['bibkeys'][0],''))
860
861class citepalias(citep):
862    args = 'bibkeys:list:str'
863    def citation(self):
864        return citep.citation(self, text=defcitealias.aliases.get(self.attributes['bibkeys'][0],''))
865
866class shortcites(Base.Command):
867    args = 'bibkeys:list:str'
868
869class urlstyle(Base.Command):
870    pass
871