1#!/usr/bin/python
2# Copyright 2006 Rene Rivera
3# Distributed under the Boost Software License, Version 1.0.
4# (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt)
5
6'''
7Processing of Doxygen generated XML.
8'''
9
10import os
11import os.path
12import sys
13import time
14import string
15import getopt
16import glob
17import re
18import xml.dom.minidom
19
20
21def usage():
22    print '''
23Usage:
24    %s options
25
26Options:
27    --xmldir        Directory with the Doxygen xml result files.
28    --output        Write the output BoostBook to the given location.
29    --id            The ID of the top level BoostBook section.
30    --title         The title of the top level BoostBook section.
31    --enable-index  Generate additional index sections for classes and
32                    types.
33''' % ( sys.argv[0] )
34
35
36def get_args( argv = sys.argv[1:] ):
37    spec = [
38        'xmldir=',
39        'output=',
40        'id=',
41        'title=',
42        'enable-index',
43        'help' ]
44    options = {
45        '--xmldir' : 'xml',
46        '--output' : None,
47        '--id' : 'dox',
48        '--title' : 'Doxygen'
49        }
50    ( option_pairs, other ) = getopt.getopt( argv, '', spec )
51    map( lambda x: options.__setitem__( x[0], x[1] ), option_pairs )
52
53    if options.has_key( '--help' ):
54        usage()
55        sys.exit(1)
56
57    return {
58        'xmldir' : options['--xmldir'],
59        'output' : options['--output'],
60        'id' : options['--id'],
61        'title' : options['--title'],
62        'index' : options.has_key('--enable-index')
63        }
64
65def if_attribute(node, attribute, true_value, false_value=None):
66    if node.getAttribute(attribute) == 'yes':
67        return true_value
68    else:
69        return false_value
70
71class Doxygen2BoostBook:
72
73    def __init__( self, **kwargs ):
74        ##
75        self.args = kwargs
76        self.args.setdefault('id','')
77        self.args.setdefault('title','')
78        self.args.setdefault('last_revision', time.asctime())
79        self.args.setdefault('index', False)
80        self.id = '%(id)s.reference' % self.args
81        self.args['id'] = self.id
82        #~ This is our template BoostBook document we insert the generated content into.
83        self.boostbook = xml.dom.minidom.parseString('''<?xml version="1.0" encoding="UTF-8"?>
84<section id="%(id)s" name="%(title)s" last-revision="%(last_revision)s">
85    <title>%(title)s</title>
86    <library-reference id="%(id)s.headers">
87        <title>Headers</title>
88    </library-reference>
89    <index id="%(id)s.classes">
90        <title>Classes</title>
91    </index>
92    <index id="%(id)s.index">
93        <title>Index</title>
94    </index>
95</section>
96''' % self.args )
97        self.section = {
98            'headers' : self._getChild('library-reference',id='%(id)s.headers' % self.args),
99            'classes' : self._getChild('index',id='%(id)s.classes' % self.args),
100            'index' : self._getChild('index',id='%(id)s.index' % self.args)
101            }
102        #~ Remove the index sections if we aren't generating it.
103        if not self.args['index']:
104            self.section['classes'].parentNode.removeChild(self.section['classes'])
105            self.section['classes'].unlink()
106            del self.section['classes']
107            self.section['index'].parentNode.removeChild(self.section['index'])
108            self.section['index'].unlink()
109            del self.section['index']
110        #~ The symbols, per Doxygen notion, that we translated.
111        self.symbols = {}
112        #~ Map of Doxygen IDs and BoostBook IDs, so we can translate as needed.
113        self.idmap = {}
114        #~ Marks generation, to prevent redoing it.
115        self.generated = False
116
117    #~ Add an Doxygen generated XML document to the content we are translating.
118    def addDox( self, document ):
119        self._translateNode(document.documentElement)
120
121    #~ Turns the internal XML tree into an output UTF-8 string.
122    def tostring( self ):
123        self._generate()
124        #~ return self.boostbook.toprettyxml('  ')
125        return self.boostbook.toxml('utf-8')
126
127    #~ Does post-processing on the partial generated content to generate additional info
128    #~ now that we have the complete source documents.
129    def _generate( self ):
130        if not self.generated:
131            self.generated = True
132            symbols = self.symbols.keys()
133            symbols.sort()
134            #~ Populate the header section.
135            for symbol in symbols:
136                if self.symbols[symbol]['kind'] in ('header'):
137                    self.section['headers'].appendChild(self.symbols[symbol]['dom'])
138            for symbol in symbols:
139                if self.symbols[symbol]['kind'] not in ('namespace', 'header'):
140                    container = self._resolveContainer(self.symbols[symbol],
141                        self.symbols[self.symbols[symbol]['header']]['dom'])
142                    if container.nodeName != 'namespace':
143                        ## The current BoostBook to Docbook translation doesn't
144                        ## respect, nor assign, IDs to inner types of any kind.
145                        ## So nuke the ID entry so as not create bogus links.
146                        del self.idmap[self.symbols[symbol]['id']]
147                    container.appendChild(self.symbols[symbol]['dom'])
148            self._rewriteIDs(self.boostbook.documentElement)
149
150    #~ Rewrite the various IDs from Doxygen references to the newly created
151    #~ BoostBook references.
152    def _rewriteIDs( self, node ):
153        if node.nodeName in ('link'):
154            if (self.idmap.has_key(node.getAttribute('linkend'))):
155                #~ A link, and we have someplace to repoint it at.
156                node.setAttribute('linkend',self.idmap[node.getAttribute('linkend')])
157            else:
158                #~ A link, but we don't have a generated target for it.
159                node.removeAttribute('linkend')
160        elif hasattr(node,'hasAttribute') and node.hasAttribute('id') and self.idmap.has_key(node.getAttribute('id')):
161            #~ Simple ID, and we have a translation.
162            node.setAttribute('id',self.idmap[node.getAttribute('id')])
163        #~ Recurse, and iterate, depth-first traversal which turns out to be
164        #~ left-to-right and top-to-bottom for the document.
165        if node.firstChild:
166            self._rewriteIDs(node.firstChild)
167        if node.nextSibling:
168            self._rewriteIDs(node.nextSibling)
169
170    def _resolveContainer( self, cpp, root ):
171        container = root
172        for ns in cpp['namespace']:
173            node = self._getChild('namespace',name=ns,root=container)
174            if not node:
175                node = container.appendChild(
176                    self._createNode('namespace',name=ns))
177            container = node
178        for inner in cpp['name'].split('::'):
179            node = self._getChild(name=inner,root=container)
180            if not node:
181                break
182            container = node
183        return container
184
185    def _setID( self, id, name ):
186        self.idmap[id] = name.replace('::','.').replace('/','.')
187        #~ print '--| setID:',id,'::',self.idmap[id]
188
189    #~ Translate a given node within a given context.
190    #~ The translation dispatches to a local method of the form
191    #~ "_translate[_context0,...,_contextN]", and the keyword args are
192    #~ passed along. If there is no translation handling method we
193    #~ return None.
194    def _translateNode( self, *context, **kwargs ):
195        node = None
196        names = [ ]
197        for c in context:
198            if c:
199                if not isinstance(c,xml.dom.Node):
200                    suffix = '_'+c.replace('-','_')
201                else:
202                    suffix = '_'+c.nodeName.replace('-','_')
203                    node = c
204                names.append('_translate')
205                names = map(lambda x: x+suffix,names)
206        if node:
207            for name in names:
208                if hasattr(self,name):
209                    return getattr(self,name)(node,**kwargs)
210        return None
211
212    #~ Translates the children of the given parent node, appending the results
213    #~ to the indicated target. For nodes not translated by the translation method
214    #~ it copies the child over and recurses on that child to translate any
215    #~ possible interior nodes. Hence this will translate the entire subtree.
216    def _translateChildren( self, parent, **kwargs ):
217        target = kwargs['target']
218        for n in parent.childNodes:
219            child = self._translateNode(n,target=target)
220            if child:
221                target.appendChild(child)
222            else:
223                child = n.cloneNode(False)
224                if hasattr(child,'data'):
225                    child.data = re.sub(r'\s+',' ',child.data)
226                target.appendChild(child)
227                self._translateChildren(n,target=child)
228
229    #~ Translate the given node as a description, into the description subnode
230    #~ of the target. If no description subnode is present in the target it
231    #~ is created.
232    def _translateDescription( self, node, target=None, tag='description', **kwargs ):
233        description = self._getChild(tag,root=target)
234        if not description:
235            description = target.appendChild(self._createNode(tag))
236        self._translateChildren(node,target=description)
237        return description
238
239    #~ Top level translation of: <doxygen ...>...</doxygen>,
240    #~ translates the children.
241    def _translate_doxygen( self, node ):
242        #~ print '_translate_doxygen:', node.nodeName
243        result = []
244        for n in node.childNodes:
245            newNode = self._translateNode(n)
246            if newNode:
247                result.append(newNode)
248        return result
249
250    #~ Top level translation of:
251    #~ <doxygenindex ...>
252    #~   <compound ...>
253    #~     <member ...>
254    #~       <name>...</name>
255    #~     </member>
256    #~     ...
257    #~   </compound>
258    #~   ...
259    #~ </doxygenindex>
260    #~ builds the class and symbol sections, if requested.
261    def _translate_doxygenindex( self, node ):
262        #~ print '_translate_doxygenindex:', node.nodeName
263        if self.args['index']:
264            entries = []
265            classes = []
266            #~ Accumulate all the index entries we care about.
267            for n in node.childNodes:
268                if n.nodeName == 'compound':
269                    if n.getAttribute('kind') not in ('file','dir','define'):
270                        cpp = self._cppName(self._getChildData('name',root=n))
271                        entry = {
272                            'name' : cpp['name'],
273                            'compoundname' : cpp['compoundname'],
274                            'id' : n.getAttribute('refid')
275                            }
276                        if n.getAttribute('kind') in ('class','struct'):
277                            classes.append(entry)
278                        entries.append(entry)
279                        for m in n.childNodes:
280                            if m.nodeName == 'member':
281                                cpp = self._cppName(self._getChildData('name',root=m))
282                                entry = {
283                                    'name' : cpp['name'],
284                                    'compoundname' : cpp['compoundname'],
285                                    'id' : n.getAttribute('refid')
286                                    }
287                                if hasattr(m,'getAttribute') and m.getAttribute('kind') in ('class','struct'):
288                                    classes.append(entry)
289                                entries.append(entry)
290            #~ Put them in a sensible order.
291            entries.sort(lambda x,y: cmp(x['name'].lower(),y['name'].lower()))
292            classes.sort(lambda x,y: cmp(x['name'].lower(),y['name'].lower()))
293            #~ And generate the BoostBook for them.
294            self._translate_index_(entries,target=self.section['index'])
295            self._translate_index_(classes,target=self.section['classes'])
296        return None
297
298    #~ Translate a set of index entries in the BoostBook output. The output
299    #~ is grouped into groups of the first letter of the entry names.
300    def _translate_index_(self, entries, target=None, **kwargs ):
301        i = 0
302        targetID = target.getAttribute('id')
303        while i < len(entries):
304            dividerKey = entries[i]['name'][0].upper()
305            divider = target.appendChild(self._createNode('indexdiv',id=targetID+'.'+dividerKey))
306            divider.appendChild(self._createText('title',dividerKey))
307            while i < len(entries) and dividerKey == entries[i]['name'][0].upper():
308                iename = entries[i]['name']
309                ie = divider.appendChild(self._createNode('indexentry'))
310                ie = ie.appendChild(self._createText('primaryie',iename))
311                while i < len(entries) and entries[i]['name'] == iename:
312                    ie.appendChild(self.boostbook.createTextNode(' ('))
313                    ie.appendChild(self._createText(
314                        'link',entries[i]['compoundname'],linkend=entries[i]['id']))
315                    ie.appendChild(self.boostbook.createTextNode(')'))
316                    i += 1
317
318    #~ Translate a <compounddef ...>...</compounddef>,
319    #~ by retranslating with the "kind" of compounddef.
320    def _translate_compounddef( self, node, target=None, **kwargs ):
321        return self._translateNode(node,node.getAttribute('kind'))
322
323    #~ Translate a <compounddef kind="namespace"...>...</compounddef>. For
324    #~ namespaces we just collect the information for later use as there is no
325    #~ currently namespaces are not included in the BoostBook format. In the future
326    #~ it might be good to generate a namespace index.
327    def _translate_compounddef_namespace( self, node, target=None, **kwargs ):
328        namespace = {
329            'id' : node.getAttribute('id'),
330            'kind' : 'namespace',
331            'name' : self._getChildData('compoundname',root=node),
332            'brief' : self._getChildData('briefdescription',root=node),
333            'detailed' : self._getChildData('detaileddescription',root=node),
334            'parsed' : False
335            }
336        if self.symbols.has_key(namespace['name']):
337            if not self.symbols[namespace['name']]['parsed']:
338                self.symbols[namespace['name']]['parsed'] = True
339                #~ for n in node.childNodes:
340                    #~ if hasattr(n,'getAttribute'):
341                        #~ self._translateNode(n,n.getAttribute('kind'),target=target,**kwargs)
342        else:
343            self.symbols[namespace['name']] = namespace
344            #~ self._setID(namespace['id'],namespace['name'])
345        return None
346
347    #~ Translate a <compounddef kind="class"...>...</compounddef>, which
348    #~ forwards to the kind=struct as they are the same.
349    def _translate_compounddef_class( self, node, target=None, **kwargs ):
350        return self._translate_compounddef_struct(node,tag='class',target=target,**kwargs)
351
352    #~ Translate a <compounddef kind="struct"...>...</compounddef> into:
353    #~ <header id="?" name="?">
354    #~   <struct name="?">
355    #~     ...
356    #~   </struct>
357    #~ </header>
358    def _translate_compounddef_struct( self, node, tag='struct', target=None, **kwargs ):
359        result = None
360        includes = self._getChild('includes',root=node)
361        if includes:
362            ## Add the header into the output table.
363            self._translate_compounddef_includes_(includes,includes,**kwargs)
364            ## Compounds are the declared symbols, classes, types, etc.
365            ## We add them to the symbol table, along with the partial DOM for them
366            ## so that they can be organized into the output later.
367            compoundname = self._getChildData('compoundname',root=node)
368            compoundname = self._cppName(compoundname)
369            self._setID(node.getAttribute('id'),compoundname['compoundname'])
370            struct = self._createNode(tag,name=compoundname['name'].split('::')[-1])
371            self.symbols[compoundname['compoundname']] = {
372                'header' : includes.firstChild.data,
373                'namespace' : compoundname['namespace'],
374                'id' : node.getAttribute('id'),
375                'kind' : tag,
376                'name' : compoundname['name'],
377                'dom' : struct
378                }
379            ## Add the children which will be the members of the struct.
380            for n in node.childNodes:
381                self._translateNode(n,target=struct,scope=compoundname['compoundname'])
382            result = struct
383        return result
384
385    #~ Translate a <compounddef ...><includes ...>...</includes></compounddef>,
386    def _translate_compounddef_includes_( self, node, target=None, **kwargs ):
387        name = node.firstChild.data
388        if not self.symbols.has_key(name):
389            self._setID(node.getAttribute('refid'),name)
390            self.symbols[name] = {
391                'kind' : 'header',
392                'id' : node.getAttribute('refid'),
393                'dom' : self._createNode('header',
394                    id=node.getAttribute('refid'),
395                    name=name)
396                }
397        return None
398
399    #~ Translate a <basecompoundref...>...</basecompoundref> into:
400    #~ <inherit access="?">
401    #~   ...
402    #~ </inherit>
403    def _translate_basecompoundref( self, ref, target=None, **kwargs ):
404        inherit = target.appendChild(self._createNode('inherit',
405            access=ref.getAttribute('prot')))
406        self._translateChildren(ref,target=inherit)
407        return
408
409    #~ Translate:
410    #~   <templateparamlist>
411    #~     <param>
412    #~       <type>...</type>
413    #~       <declname>...</declname>
414    #~       <defname>...</defname>
415    #~       <defval>...</defval>
416    #~     </param>
417    #~     ...
418    #~   </templateparamlist>
419    #~ Into:
420    #~   <template>
421    #~     <template-type-parameter name="?" />
422    #~     <template-nontype-parameter name="?">
423    #~       <type>?</type>
424    #~       <default>?</default>
425    #~     </template-nontype-parameter>
426    #~   </template>
427    def _translate_templateparamlist( self, templateparamlist, target=None, **kwargs ):
428        template = target.appendChild(self._createNode('template'))
429        for param in templateparamlist.childNodes:
430            if param.nodeName == 'param':
431                type = self._getChildData('type',root=param)
432                defval = self._getChild('defval',root=param)
433                paramKind = None
434                if type in ('class','typename'):
435                    paramKind = 'template-type-parameter'
436                else:
437                    paramKind = 'template-nontype-parameter'
438                templateParam = template.appendChild(
439                    self._createNode(paramKind,
440                        name=self._getChildData('declname',root=param)))
441                if paramKind == 'template-nontype-parameter':
442                    template_type = templateParam.appendChild(self._createNode('type'))
443                    self._translate_type(
444                        self._getChild('type',root=param),target=template_type)
445                if defval:
446                    value = self._getChildData('ref',root=defval.firstChild)
447                    if not value:
448                        value = self._getData(defval)
449                    templateParam.appendChild(self._createText('default',value))
450        return template
451
452    #~ Translate:
453    #~   <briefdescription>...</briefdescription>
454    #~ Into:
455    #~   <purpose>...</purpose>
456    def _translate_briefdescription( self, brief, target=None, **kwargs ):
457        self._translateDescription(brief,target=target,**kwargs)
458        return self._translateDescription(brief,target=target,tag='purpose',**kwargs)
459
460    #~ Translate:
461    #~   <detaileddescription>...</detaileddescription>
462    #~ Into:
463    #~   <description>...</description>
464    def _translate_detaileddescription( self, detailed, target=None, **kwargs ):
465        return self._translateDescription(detailed,target=target,**kwargs)
466
467    #~ Translate:
468    #~   <sectiondef kind="?">...</sectiondef>
469    #~ With kind specific translation.
470    def _translate_sectiondef( self, sectiondef, target=None, **kwargs ):
471        self._translateNode(sectiondef,sectiondef.getAttribute('kind'),target=target,**kwargs)
472
473    #~ Translate non-function sections.
474    def _translate_sectiondef_x_( self, sectiondef, target=None, **kwargs ):
475        for n in sectiondef.childNodes:
476            if hasattr(n,'getAttribute'):
477                self._translateNode(n,n.getAttribute('kind'),target=target,**kwargs)
478        return None
479
480    #~ Translate:
481    #~   <sectiondef kind="public-type">...</sectiondef>
482    def _translate_sectiondef_public_type( self, sectiondef, target=None, **kwargs ):
483        return self._translate_sectiondef_x_(sectiondef,target=target,**kwargs)
484
485    #~ Translate:
486    #~   <sectiondef kind="public-sttrib">...</sectiondef>
487    def _translate_sectiondef_public_attrib( self, sectiondef, target=None, **kwargs):
488        return self._translate_sectiondef_x_(sectiondef,target=target,**kwargs)
489
490    #~ Translate:
491    #~   <sectiondef kind="?-func">...</sectiondef>
492    #~ All the various function group translations end up here for which
493    #~ they are translated into:
494    #~   <method-group name="?">
495    #~   ...
496    #~   </method-group>
497    def _translate_sectiondef_func_( self, sectiondef, name='functions', target=None, **kwargs ):
498        members = target.appendChild(self._createNode('method-group',name=name))
499        for n in sectiondef.childNodes:
500            if hasattr(n,'getAttribute'):
501                self._translateNode(n,n.getAttribute('kind'),target=members,**kwargs)
502        return members
503
504    #~ Translate:
505    #~   <sectiondef kind="public-func">...</sectiondef>
506    def _translate_sectiondef_public_func( self, sectiondef, target=None, **kwargs ):
507        return self._translate_sectiondef_func_(sectiondef,
508            name='public member functions',target=target,**kwargs)
509
510    #~ Translate:
511    #~   <sectiondef kind="public-static-func">...</sectiondef>
512    def _translate_sectiondef_public_static_func( self, sectiondef, target=None, **kwargs):
513        return self._translate_sectiondef_func_(sectiondef,
514            name='public static functions',target=target,**kwargs)
515
516    #~ Translate:
517    #~   <sectiondef kind="protected-func">...</sectiondef>
518    def _translate_sectiondef_protected_func( self, sectiondef, target=None, **kwargs ):
519        return self._translate_sectiondef_func_(sectiondef,
520            name='protected member functions',target=target,**kwargs)
521
522    #~ Translate:
523    #~   <sectiondef kind="private-static-func">...</sectiondef>
524    def _translate_sectiondef_private_static_func( self, sectiondef, target=None, **kwargs):
525        return self._translate_sectiondef_func_(sectiondef,
526            name='private static functions',target=target,**kwargs)
527
528    #~ Translate:
529    #~   <sectiondef kind="public-func">...</sectiondef>
530    def _translate_sectiondef_private_func( self, sectiondef, target=None, **kwargs ):
531        return self._translate_sectiondef_func_(sectiondef,
532            name='private member functions',target=target,**kwargs)
533
534    #~ Translate:
535    #~   <sectiondef kind="user-defined"><header>...</header>...</sectiondef>
536    def _translate_sectiondef_user_defined( self, sectiondef, target=None, **kwargs ):
537        return self._translate_sectiondef_func_(sectiondef,
538            name=self._getChildData('header', root=sectiondef),target=target,**kwargs)
539
540    #~ Translate:
541    #~   <memberdef kind="typedef" id="?">
542    #~     <name>...</name>
543    #~   </memberdef>
544    #~ To:
545    #~   <typedef id="?" name="?">
546    #~     <type>...</type>
547    #~   </typedef>
548    def _translate_memberdef_typedef( self, memberdef, target=None, scope=None, **kwargs ):
549        self._setID(memberdef.getAttribute('id'),
550            scope+'::'+self._getChildData('name',root=memberdef))
551        typedef = target.appendChild(self._createNode('typedef',
552            id=memberdef.getAttribute('id'),
553            name=self._getChildData('name',root=memberdef)))
554        typedef_type = typedef.appendChild(self._createNode('type'))
555        self._translate_type(self._getChild('type',root=memberdef),target=typedef_type)
556        return typedef
557
558    #~ Translate:
559    #~   <memberdef kind="function" id="?" const="?" static="?" explicit="?" inline="?">
560    #~     <name>...</name>
561    #~   </memberdef>
562    #~ To:
563    #~   <method name="?" cv="?" specifiers="?">
564    #~     ...
565    #~   </method>
566    def _translate_memberdef_function( self, memberdef, target=None, scope=None, **kwargs ):
567        name = self._getChildData('name',root=memberdef)
568        self._setID(memberdef.getAttribute('id'),scope+'::'+name)
569        ## Check if we have some specific kind of method.
570        if name == scope.split('::')[-1]:
571            kind = 'constructor'
572            target = target.parentNode
573        elif name == '~'+scope.split('::')[-1]:
574            kind = 'destructor'
575            target = target.parentNode
576        elif name == 'operator=':
577            kind = 'copy-assignment'
578            target = target.parentNode
579        else:
580            kind = 'method'
581        method = target.appendChild(self._createNode(kind,
582            # id=memberdef.getAttribute('id'),
583            name=name,
584            cv=' '.join([
585                if_attribute(memberdef,'const','const','').strip()
586                ]),
587            specifiers=' '.join([
588                if_attribute(memberdef,'static','static',''),
589                if_attribute(memberdef,'explicit','explicit',''),
590                if_attribute(memberdef,'inline','inline','')
591                ]).strip()
592            ))
593        ## We iterate the children to translate each part of the function.
594        for n in memberdef.childNodes:
595            self._translateNode(memberdef,'function',n,target=method)
596        return method
597
598    #~ Translate:
599    #~   <memberdef kind="function"...><templateparamlist>...</templateparamlist></memberdef>
600    def _translate_memberdef_function_templateparamlist(
601        self, templateparamlist, target=None, **kwargs ):
602        return self._translate_templateparamlist(templateparamlist,target=target,**kwargs)
603
604    #~ Translate:
605    #~   <memberdef kind="function"...><type>...</type></memberdef>
606    #~ To:
607    #~   ...<type>?</type>
608    def _translate_memberdef_function_type( self, resultType, target=None, **kwargs ):
609        methodType = self._createNode('type')
610        self._translate_type(resultType,target=methodType)
611        if methodType.hasChildNodes():
612            target.appendChild(methodType)
613        return methodType
614
615    #~ Translate:
616    #~   <memberdef kind="function"...><briefdescription>...</briefdescription></memberdef>
617    def _translate_memberdef_function_briefdescription( self, description, target=None, **kwargs ):
618        result = self._translateDescription(description,target=target,**kwargs)
619        ## For functions if we translate the brief docs to the purpose they end up
620        ## right above the regular description. And since we just added the brief to that
621        ## on the previous line, don't bother with the repetition.
622        # result = self._translateDescription(description,target=target,tag='purpose',**kwargs)
623        return result
624
625    #~ Translate:
626    #~   <memberdef kind="function"...><detaileddescription>...</detaileddescription></memberdef>
627    def _translate_memberdef_function_detaileddescription( self, description, target=None, **kwargs ):
628        return self._translateDescription(description,target=target,**kwargs)
629
630    #~ Translate:
631    #~   <memberdef kind="function"...><inbodydescription>...</inbodydescription></memberdef>
632    def _translate_memberdef_function_inbodydescription( self, description, target=None, **kwargs ):
633        return self._translateDescription(description,target=target,**kwargs)
634
635    #~ Translate:
636    #~   <memberdef kind="function"...><param>...</param></memberdef>
637    def _translate_memberdef_function_param( self, param, target=None, **kwargs ):
638        return self._translate_param(param,target=target,**kwargs)
639
640    #~ Translate:
641    #~   <memberdef kind="variable" id="?">
642    #~     <name>...</name>
643    #~     <type>...</type>
644    #~   </memberdef>
645    #~ To:
646    #~   <data-member id="?" name="?">
647    #~     <type>...</type>
648    #~   </data-member>
649    def _translate_memberdef_variable( self, memberdef, target=None, scope=None, **kwargs ):
650        self._setID(memberdef.getAttribute('id'),
651            scope+'::'+self._getChildData('name',root=memberdef))
652        data_member = target.appendChild(self._createNode('data-member',
653            id=memberdef.getAttribute('id'),
654            name=self._getChildData('name',root=memberdef)))
655        data_member_type = data_member.appendChild(self._createNode('type'))
656        self._translate_type(self._getChild('type',root=memberdef),target=data_member_type)
657
658    #~ Translate:
659    #~   <memberdef kind="enum" id="?">
660    #~     <name>...</name>
661    #~     ...
662    #~   </memberdef>
663    #~ To:
664    #~   <enum id="?" name="?">
665    #~     ...
666    #~   </enum>
667    def _translate_memberdef_enum( self, memberdef, target=None, scope=None, **kwargs ):
668        self._setID(memberdef.getAttribute('id'),
669            scope+'::'+self._getChildData('name',root=memberdef))
670        enum = target.appendChild(self._createNode('enum',
671            id=memberdef.getAttribute('id'),
672            name=self._getChildData('name',root=memberdef)))
673        for n in memberdef.childNodes:
674            self._translateNode(memberdef,'enum',n,target=enum,scope=scope,**kwargs)
675        return enum
676
677    #~ Translate:
678    #~   <memberdef kind="enum"...>
679    #~     <enumvalue id="?">
680    #~       <name>...</name>
681    #~       <initializer>...</initializer>
682    #~     </enumvalue>
683    #~   </memberdef>
684    #~ To:
685    #~   <enumvalue id="?" name="?">
686    #~     <default>...</default>
687    #~   </enumvalue>
688    def _translate_memberdef_enum_enumvalue( self, enumvalue, target=None, scope=None, **kwargs ):
689        self._setID(enumvalue.getAttribute('id'),
690            scope+'::'+self._getChildData('name',root=enumvalue))
691        value = target.appendChild(self._createNode('enumvalue',
692            id=enumvalue.getAttribute('id'),
693            name=self._getChildData('name',root=enumvalue)))
694        initializer = self._getChild('initializer',root=enumvalue)
695        if initializer:
696            self._translateChildren(initializer,
697                target=target.appendChild(self._createNode('default')))
698        return value
699
700    #~ Translate:
701    #~   <param>
702    #~     <type>...</type>
703    #~     <declname>...</declname>
704    #~     <defval>...</defval>
705    #~   </param>
706    #~ To:
707    #~   <parameter name="?">
708    #~     <paramtype>...</paramtype>
709    #~     ...
710    #~   </parameter>
711    def _translate_param( self, param, target=None, **kwargs):
712        parameter = target.appendChild(self._createNode('parameter',
713            name=self._getChildData('declname',root=param)))
714        paramtype = parameter.appendChild(self._createNode('paramtype'))
715        self._translate_type(self._getChild('type',root=param),target=paramtype)
716        defval = self._getChild('defval',root=param)
717        if defval:
718            self._translateChildren(self._getChild('defval',root=param),target=parameter)
719        return parameter
720
721    #~ Translate:
722    #~   <ref kindref="?" ...>...</ref>
723    def _translate_ref( self, ref, **kwargs ):
724        return self._translateNode(ref,ref.getAttribute('kindref'))
725
726    #~ Translate:
727    #~   <ref refid="?" kindref="compound">...</ref>
728    #~ To:
729    #~   <link linkend="?"><classname>...</classname></link>
730    def _translate_ref_compound( self, ref, **kwargs ):
731        result = self._createNode('link',linkend=ref.getAttribute('refid'))
732        classname = result.appendChild(self._createNode('classname'))
733        self._translateChildren(ref,target=classname)
734        return result
735
736    #~ Translate:
737    #~   <ref refid="?" kindref="member">...</ref>
738    #~ To:
739    #~   <link linkend="?">...</link>
740    def _translate_ref_member( self, ref, **kwargs ):
741        result = self._createNode('link',linkend=ref.getAttribute('refid'))
742        self._translateChildren(ref,target=result)
743        return result
744
745    #~ Translate:
746    #~   <type>...</type>
747    def _translate_type( self, type, target=None, **kwargs ):
748        result = self._translateChildren(type,target=target,**kwargs)
749        #~ Filter types to clean up various readability problems, most notably
750        #~ with really long types.
751        xml = target.toxml('utf-8');
752        if (
753            xml.startswith('<type>boost::mpl::') or
754            xml.startswith('<type>BOOST_PP_') or
755            re.match('<type>boost::(lazy_)?(enable|disable)_if',xml)
756            ):
757            while target.firstChild:
758                target.removeChild(target.firstChild)
759            target.appendChild(self._createText('emphasis','unspecified'))
760        return result
761
762    def _getChild( self, tag = None, id = None, name = None, root = None ):
763        if not root:
764            root = self.boostbook.documentElement
765        for n in root.childNodes:
766            found = True
767            if tag and found:
768                found = found and tag == n.nodeName
769            if id and found:
770                if n.hasAttribute('id'):
771                    found = found and n.getAttribute('id') == id
772                else:
773                    found = found and n.hasAttribute('id') and n.getAttribute('id') == id
774            if name and found:
775                found = found and n.hasAttribute('name') and n.getAttribute('name') == name
776            if found:
777                #~ print '--|', n
778                return n
779        return None
780
781    def _getChildData( self, tag, **kwargs ):
782        return self._getData(self._getChild(tag,**kwargs),**kwargs)
783
784    def _getData( self, node, **kwargs ):
785        if node:
786            text = self._getChild('#text',root=node)
787            if text:
788                return text.data.strip()
789        return ''
790
791    def _cppName( self, type ):
792        parts = re.search('^([^<]+)[<]?(.*)[>]?$',type.strip().strip(':'))
793        result = {
794            'compoundname' : parts.group(1),
795            'namespace' : parts.group(1).split('::')[0:-1],
796            'name' : parts.group(1).split('::')[-1],
797            'specialization' : parts.group(2)
798            }
799        if result['namespace'] and len(result['namespace']) > 0:
800            namespace = '::'.join(result['namespace'])
801            while (
802                len(result['namespace']) > 0 and (
803                    not self.symbols.has_key(namespace) or
804                    self.symbols[namespace]['kind'] != 'namespace')
805                ):
806                result['name'] = result['namespace'].pop()+'::'+result['name']
807                namespace = '::'.join(result['namespace'])
808        return result
809
810    def _createNode( self, tag, **kwargs ):
811        result = self.boostbook.createElement(tag)
812        for k in kwargs.keys():
813            if kwargs[k] != '':
814                if k == 'id':
815                    result.setAttribute('id',kwargs[k])
816                else:
817                    result.setAttribute(k,kwargs[k])
818        return result
819
820    def _createText( self, tag, data, **kwargs ):
821        result = self._createNode(tag,**kwargs)
822        data = data.strip()
823        if len(data) > 0:
824            result.appendChild(self.boostbook.createTextNode(data))
825        return result
826
827
828def main( xmldir=None, output=None, id=None, title=None, index=False ):
829    #~ print '--- main: xmldir = %s, output = %s' % (xmldir,output)
830
831    input = glob.glob( os.path.abspath( os.path.join( xmldir, "*.xml" ) ) )
832    input.sort
833    translator = Doxygen2BoostBook(id=id, title=title, index=index)
834    #~ Feed in the namespaces first to build up the set of namespaces
835    #~ and definitions so that lookup is unambiguous when reading in the definitions.
836    namespace_files = filter(
837        lambda x:
838            os.path.basename(x).startswith('namespace'),
839        input)
840    decl_files = filter(
841        lambda x:
842            not os.path.basename(x).startswith('namespace') and not os.path.basename(x).startswith('_'),
843        input)
844    for dox in namespace_files:
845        #~ print '--|',os.path.basename(dox)
846        translator.addDox(xml.dom.minidom.parse(dox))
847    for dox in decl_files:
848        #~ print '--|',os.path.basename(dox)
849        translator.addDox(xml.dom.minidom.parse(dox))
850
851    if output:
852        output = open(output,'w')
853    else:
854        output = sys.stdout
855    if output:
856        output.write(translator.tostring())
857
858
859main( **get_args() )
860