#!/usr/bin/env python3 # # Copyright (C) VyOS Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as # published by the Free Software Foundation. # # 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 . # # # Use lxml xpath capability to detect multiple properties elements at the # same path with differing content. import sys import glob import logging import io from lxml import etree debug = False logger = logging.getLogger(__name__) logs_handler = logging.StreamHandler() logger.addHandler(logs_handler) if debug: logger.setLevel(logging.DEBUG) else: logger.setLevel(logging.INFO) def canonical_form(e): # pylint: disable=c-extension-no-member out = io.BytesIO() etree.ElementTree(e).write_c14n(out) return out.getvalue() def check_properties_collision(dir_name): # pylint: disable=too-many-locals,too-many-branches,c-extension-no-member """ Collect elements with help tag into dictionary indexed by name attributes of ancestor path. """ buffer = io.StringIO() for fname in glob.glob(f'{dir_name}/*.xml'): tree = etree.parse(fname) defv = {} xpath_str = '//properties' xp = tree.xpath(xpath_str) for element in xp: ap = element.xpath('ancestor::*[@name]') ap_name = [el.get('name') for el in ap] ap_path_str = ' '.join(ap_name) defv.setdefault(ap_path_str, []).append(element) trivial = [] for k, v in defv.items(): if len(v) < 2: trivial.append(k) for i in trivial: del defv[i] for k, v in defv.items(): collisions = [] for i in v: # If properties contains more than just a help element: if len(list(i)) > 1: collisions.append(i) if len(collisions) > 1: property_set = set() for j in collisions: prop = canonical_form(j) property_set.add(prop) if len(property_set) > 1: buffer.write(f'Collision in file {fname}:\n') for e in collisions: buffer.write( f'Element {e.tag} at {e.sourceline} with content:\n {canonical_form(e)}\n' ) content = buffer.getvalue() if content: logger.info( 'Collisions detected: multiple elements at a path with differing content other than .\n' 'Information beyond the first instance will be ignored in the resulting node.def file.' ) logger.info(content) buffer.close() def main(): if len(sys.argv) < 2: logger.critical('Must specify XML directory!') sys.exit(1) dir_name = sys.argv[1] check_properties_collision(dir_name) if __name__ == '__main__': main()