Source code for pvxmlgen
# python 2 compatibility
from __future__ import print_function
from io import open
import xml.etree.ElementTree as ET
import re
import traceback
import xml_state
from collections import namedtuple
import argparse
__version__ = '0.1.0'
[docs]class ParserException(BaseException):
def __init__(self, exception, line=None):
self.exception = exception
self.line = line
if hasattr(self.exception, 'lineno'):
self.exception.lineno += self.line - 1
def __str__(self):
return str(self.exception)
Block = namedtuple('Block', ['content', 'context', 'line_content', 'line_context'])
[docs]def indent(elem, indentation=4 * ' ', level=0):
i = '\n' + level * indentation
if len(elem):
if not elem.text or not elem.text.strip():
elem.text = i + indentation
for e in elem:
indent(e, indentation, level + 1)
if not e.tail or not e.tail.strip():
e.tail = i + indentation
if not e.tail or not e.tail.strip():
e.tail = i
else:
if level and (not elem.tail or not elem.tail.strip()):
elem.tail = i
if len(elem.attrib) > 2:
elem.attrib = {(i + indentation + k): v for k, v in elem.attrib.items()}
[docs]def parse_file(s):
result = []
left = s.split('pv_(')
for i, part in enumerate(left):
if i == 0:
continue
current_line = len('pv_('.join(left[:i]).splitlines())
right = part.split(')pv_')
if len(right) < 2:
raise Exception('"pv_(" in line {} has no matching ")pv_".'.format(current_line))
elif len(right) > 2:
raise Exception('"pv_(" in line {} has multiple ")pv_".'.format(current_line))
content_lines = right[0].splitlines()
while len(content_lines) > 0 and content_lines[0].strip() == '':
current_line += 1
content_lines = content_lines[1:]
content = right[0].strip()
context_line = current_line + len(content.splitlines())
context = right[1].splitlines()
if len(context) >= 2:
context = context[1]
else:
context = None
result.append(Block(content, context, current_line, context_line))
return result
[docs]def parse_declaration(s):
context_dict = {}
if s is None:
return context_dict
# variable declaration
if ';' in s:
declaration = s.split(';')[0]
# find type and name as whitespace-separated list left of '=', '{', or '['
lhs = re.split(r'[=\[\{]', declaration)[0].split()
if len(lhs) < 2:
raise Exception('Cannot determine variable type and name.')
data_type = ' '.join(lhs[:-1])
name = lhs[-1]
# find array declaration
number_of_elements = re.search(r'\[(.*?)\]', declaration)
number_of_elements = eval(number_of_elements.group(1)) if number_of_elements else 1
# find initializer-list and default-initialization
default_values = re.search(r'\{(.*?)\}', declaration)
if default_values:
default_values = default_values.group(1)
elif '=' in declaration:
default_values = declaration.split('=')[-1]
else:
default_values = None
if default_values is not None:
# rename boolean values if not a string
if '"' not in default_values:
default_values = default_values.replace('false', 'False')
default_values = default_values.replace('true', 'True')
default_values = eval('({})'.format(default_values))
context_dict['name'] = name
context_dict['type'] = data_type
context_dict['number_of_elements'] = number_of_elements
if default_values is not None:
context_dict['default_values'] = default_values
# class definition
elif 'class' in s:
# class name should be the right-most word left of ':' or '{'
declaration = re.split(r'\:|\{', s)[0]
declaration = declaration.split()[-1]
context_dict['class'] = declaration
return context_dict
[docs]def generate_xml(s):
blocks = parse_file(s)
root = xml_state.XMLNode()
current = root
for block in blocks:
try:
current.context = parse_declaration(block.context)
except Exception as e:
raise ParserException(e, block.line_context)
namespace = {'current': current}
try:
current = eval('current.' + block.content, namespace)
except Exception as e:
raise ParserException(e, block.line_content)
return root
if __name__ == '__main__':
parser = argparse.ArgumentParser(prog='pvxmlgen')
parser.add_argument('-v', '--version', action='version', version='%(prog)s {}'.format(__version__))
parser.add_argument('input', metavar='<input>', type=str, help='input C++ header file')
parser.add_argument('output', metavar='<output>', type=str,
help='output XML file (use "-" to print to standard output)')
args = parser.parse_args()
filename = args.input
with open(filename) as f:
s = f.read()
try:
root = generate_xml(s)
except ParserException as e:
print('Error in {}:{}:'.format(filename, e.line))
if hasattr(e.exception, 'lineno'):
e.exception.filename = filename
try:
raise e.exception
except:
pass
traceback.print_exc(limit=0)
else:
print(e.exception)
quit(1)
indent(root)
output_string = ET.tostring(root, encoding='utf-8').decode('utf-8')
if args.output == '-':
print(output_string)
else:
with open(args.output, 'w', encoding='utf-8') as f:
f.write(output_string)