#!/usr/bin/env python3
'''
Copyright (C) 2011-2013 Povilas Kanapickas
This file is part of cppreference-doc
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see http://www.gnu.org/licenses/.
'''
import lxml.etree as e
'''
This is a python script for various transformations of the index.
Concrete transformations can be implemented by subclassing IndexTransform
and overriding the provided hooks.
process_item_hook:
Called to output information of a feature and continue the processing of the
children. By default just processes the children.
'''
class IndexTransform:
def __init__(self, ignore_typedefs=False, ignore_inherits=False):
self.ignore_typedefs = ignore_typedefs
self.ignore_inherits = ignore_inherits
""" Returns the attribute 'attr' of 'el', raises exception on error
"""
def get_attr(self, el, attr):
a = el.get(attr)
if not a:
nm = el.get('name')
if nm:
nm_str = '( name: ' + nm + ' )'
else:
nm_str = ''
msg = 'Element \'{0}\' does not have attribute \'{1}\' {2}'.format(
el.tag, attr, nm_str)
raise Exception(msg)
return str(a)
# Returns the relative link of 'el' to its parent, if any
def get_link(self, el, default=None):
if not default:
default = self.get_name(el)
link = el.get('link')
if link is None:
return default
if link == '.':
return ''
return str(link)
""" Appends two possible empty relative links
"""
def link_append(self, el, link, parent_link):
if parent_link != '' and link != '':
return parent_link + '/' + link
return parent_link + link
""" Returns the absolute link of el
"""
def get_full_link(self, el, parent_link):
if el.tag == 'typedef':
alias_name = el.get('alias')
if alias_name:
return self.get_link(self.get_alias(el, alias_name))
return self.link_append(el, self.get_link(el), parent_link)
if el.tag == 'constructor':
d_link = parent_link.split('/')[-1]
return self.link_append(el, self.get_link(el, default=d_link),
parent_link)
if el.tag == 'destructor':
d_link = '~' + parent_link.split('/')[-1]
return self.link_append(el, self.get_link(el, default=d_link),
parent_link)
return self.link_append(el, self.get_link(el), parent_link)
""" Returns the name of el """
def get_name(self, el):
return self.get_attr(el, 'name')
""" Returns the full name (with the namespace qualification) of el """
def get_full_name(self, el, parent_name):
if not parent_name and el.tag in ['constructor', 'destructor',
'overload', 'specialization']:
raise Exception('element \'' + el.tag + '\' does not have a parent')
if el.tag == 'constructor':
return parent_name + '::' + parent_name.split('::')[-1]
if el.tag == 'destructor':
return parent_name + '::~' + parent_name.split('::')[-1]
if el.tag == 'specialization':
return self.get_name(el) + '<' + parent_name + '>'
if el.tag == 'overload':
return self.get_name(el) + '(' + parent_name + ')'
name = ''
if parent_name:
name += parent_name + '::'
name += self.get_name(el)
return name
""" Returns the element within the document that has a name that matches
'name'
"""
def get_alias(self, el, name):
aliases = el.xpath('/index/class[@name = \'' + name + '\'] |' +
'/index/enum[@name = \'' + name + '\']')
if len(aliases) == 0:
raise Exception('No aliases found for \'' + name + '\'')
if len(aliases) > 1:
raise Exception('More than one alias found for \'' + name + '\'')
return aliases[0]
""" Processes one item """
def process_item(self, el, parent_name, parent_link):
if el.tag in ['const', 'function', 'class', 'enum', 'variable',
'typedef', 'constructor', 'destructor', 'specialization',
'overload']:
full_name = self.get_full_name(el, parent_name)
full_link = self.get_full_link(el, parent_link)
self.process_item_hook(el, full_name, full_link)
elif el.tag == 'inherits' and \
el.getparent().xpath('child::inherits')[0] == el:
if self.ignore_inherits:
return
pending = el.getparent().xpath('child::inherits')
self.inherits_worker(parent_name, pending, list())
""" Processes children of an item """
def process_children(self, el, parent_name, parent_link):
if el.tag == 'class' or el.tag == 'enum':
for child in el:
self.process_item(child, parent_name, parent_link)
elif el.tag == 'typedef':
if self.ignore_typedefs:
return
alias_name = el.get('alias')
if alias_name:
target = self.get_alias(el, alias_name)
else:
return
link = self.get_link(target)
for target_ch in target:
self.process_item(target_ch, parent_name, link)
""" Pulls the contents of the inherited classes. Diamond inheritance is
handled properly
"""
def inherits_worker(self, parent_name, pending, finished):
if len(pending) == 0:
return
current = pending.pop(0)
# find the source class/enum
source = self.get_alias(current, self.get_attr(current, 'name'))
if source not in finished:
finished.append(source)
parent_link = self.get_attr(source, 'link')
for source_ch in source:
ignore_tags = ['constructor', 'destructor', 'inherits',
'specialization', 'overload']
if source_ch.tag in ignore_tags:
pass
elif source_ch.tag == 'function' and \
source_ch.get('name') == 'operator=':
pass
else:
self.process_item(source_ch, parent_name, parent_link)
# append new elements
more_pending = source.xpath('child::inherits')
more_pending = [p for p in more_pending if p is not current]
pending.extend(more_pending)
self.inherits_worker(parent_name, pending, finished)
""" Transforms the index from the given path """
def transform_file(self, fn):
root = e.parse(fn)
self.transform_xml(root)
""" Transforms the index from the given XML tree """
def transform_xml(self, root):
elems = root.xpath('/index/*')
for el in elems:
self.process_item(el, '', '')
""" Hooks """
def process_item_hook(self, el, full_name, full_link):
self.process_children(el, full_name, full_link)