diff --git a/docs/source/usersguide/input.rst b/docs/source/usersguide/input.rst index 20fd66e2f7..9ebdaa1660 100644 --- a/docs/source/usersguide/input.rst +++ b/docs/source/usersguide/input.rst @@ -51,6 +51,55 @@ files are called: * ``plots.xml`` * ``cmfd.xml`` +-------------------- +Validating XML Files +-------------------- + +Input files can be checked before executing OpenMC using the ``xml_validate`` +script. It is located in ``src/utils/xml_validate.py`` in the source code or in +``bin/xml_validate`` in the install directory. Before use, the third party +package TRANG_ must be installed and in your ``PATH`` to convert the compact +RelaxNG schema to standard RelaxNG format. For Ubuntu, you can install with: + +.. code-block:: bash + + sudo apt-get install trang + +Two command line arguments can be set when running ``xml_validate``: + +* ``-i``, ``--input-path`` - Location of OpenMC input files. + *Default*: current working directory +* ``-r``, ``--relaxng-path`` - Location of OpenMC RelaxNG files. + *Default*: None + +If the RelaxNG path is not set, ``xml_validate`` will search for these files +because it expects that the user is either running the script located in the +install directory ``bin`` folder or in ``src/utils``. Once executed, it will +match OpenMC XML files with their RelaxNG schema and check if they are valid. +Below is a table of the messages that will be printed after each file is +checked. + +======================== =================================== +Message Description +======================== =================================== +[XML ERROR] Cannot parse XML file. +[NO RELAXNG FOUND] No RelaxNG file found for XML file. +[TRANG FAILED] TRANG not installed properly. +[NOT VALID] XML file does not match RelaxNG. +[VALID] XML file matches RelaxNG. +======================== =================================== + +As an example, if OpenMC is installed in the directory +``/opt/openmc/0.6.2`` and the current working directory is where +OpenMC XML input files are located, they can be validated using +the following command: + +.. code-block:: bash + + /opt/openmc/0.6.2/bin/xml_validate + +.. _TRANG: http://www.thaiopensource.com/relaxng/trang.html + -------------------------------------- Settings Specification -- settings.xml -------------------------------------- diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index dd920bfcc0..b3e1a88779 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -272,6 +272,10 @@ install(PROGRAMS utils/statepoint_meshplot.py install(PROGRAMS utils/update_inputs.py DESTINATION bin RENAME update_inputs) +install(PROGRAMS utils/xml_validate.py + DESTINATION bin + RENAME xml_validate) +install(DIRECTORY relaxng DESTINATION share) install(FILES ../man/man1/openmc.1 DESTINATION share/man/man1) install(FILES ../LICENSE DESTINATION "share/doc/${program}/copyright") diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 292b32e066..fc003d448a 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -904,7 +904,7 @@ contains integer :: universe_num integer :: n_cells_in_univ integer :: coeffs_reqd - integer :: temp_int_array3(3) + integer :: temp_double_array3(3) integer, allocatable :: temp_int_array(:) real(8) :: phi, theta, psi logical :: file_exists @@ -1052,10 +1052,10 @@ contains end if ! Copy rotation angles in x,y,z directions - call get_node_array(node_cell, "rotation", temp_int_array3) - phi = -temp_int_array3(1) * PI/180.0_8 - theta = -temp_int_array3(2) * PI/180.0_8 - psi = -temp_int_array3(3) * PI/180.0_8 + call get_node_array(node_cell, "rotation", temp_double_array3) + phi = -temp_double_array3(1) * PI/180.0_8 + theta = -temp_double_array3(2) * PI/180.0_8 + psi = -temp_double_array3(3) * PI/180.0_8 ! Calculate rotation matrix based on angles given allocate(c % rotation(3,3)) diff --git a/src/relaxng/tallies.rnc b/src/relaxng/tallies.rnc index 06959276bc..09a5a6eb85 100644 --- a/src/relaxng/tallies.rnc +++ b/src/relaxng/tallies.rnc @@ -26,10 +26,8 @@ element tallies { "surface" | "mesh" | "energy" | "energyout" ) } | attribute type { ( "cell" | "cellborn" | "material" | "universe" | "surface" | "mesh" | "energy" | "energyout" ) }) & - (element bins { list { xsd:string { maxLength = "20" }+ } } | - attribute bins { list { xsd:string { maxLength = "20" }+ } }) & - (element groups { xsd:string { maxLength = "20" }+ } | - attribute groups { xsd:string { maxLength = "20" }+ })? + (element bins { list { xsd:double+ } } | + attribute bins { list { xsd:double+ } }) }* & element nuclides { list { xsd:string { maxLength = "12" }+ } diff --git a/src/utils/xml_validate.py b/src/utils/xml_validate.py new file mode 100755 index 0000000000..aa56e6e0cf --- /dev/null +++ b/src/utils/xml_validate.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python + +from __future__ import print_function + +import os +import sys +import glob +import lxml.etree as etree +from subprocess import call +from optparse import OptionParser + +# Command line parsing +parser = OptionParser() +parser.add_option('-r', '--relaxng-path', dest='relaxng', + help="Path to RelaxNG files.") +parser.add_option('-i', '--input-path', dest='inputs', default=os.getcwd(), + help="Path to OpenMC input files." ) +(options, args) = parser.parse_args() + +# Colored output +if sys.stdout.isatty(): + OK = '\033[92m' + FAIL = '\033[91m' + NOT_FOUND = '\033[93m' + ENDC = '\033[0m' + BOLD = '\033[1m' +else: + OK = '' + FAIL = '' + ENDC = '' + BOLD = '' + NOT_FOUND = '' + +# Get absolute paths +if options.relaxng is not None: + relaxng_path = os.path.abspath(options.relaxng) +if options.inputs is not None: + inputs_path = os.path.abspath(options.inputs) + +# Search for relaxng path if not set +if options.relaxng is None: + xml_validate_path = os.path.abspath(os.path.dirname(sys.argv[0])) + if "bin" in xml_validate_path: + relaxng_path = os.path.join(xml_validate_path, "..", "share", "relaxng") + elif os.path.join("src", "utils") in xml_validate_path: + relaxng_path = os.path.join(xml_validate_path, "..", "relaxng") + else: + raise Exception("Set RelaxNG path with -r command line option.") +if not os.path.exists(relaxng_path): + raise Exception("RelaxNG path: {0} does not exist, set with -r " + "command line option.".format(relaxng_path)) + +# Make sure there are .rnc files in RelaxNG path +rnc_files = glob.glob(os.path.join(relaxng_path, "*.rnc")) +if len(rnc_files) == 0: + raise Exception("No .rnc files found in RelaxNG " + "path: {0}.".format(relaxng_path)) + +# Get list of xml input files +xml_files = glob.glob(os.path.join(inputs_path, "*.xml")) +if len(xml_files) == 0: + raise Exception("No .xml files found at input path: {0}" + ".".format(inputs_path)) + +# Begin loop around input files +for xml_file in xml_files: + + text = "Validating {0}".format(os.path.basename(xml_file)) + print(text + '.'*(30 - len(text)), end="") + + # Validate the XML file + try: + xml_tree = etree.parse(xml_file) + except etree.XMLSyntaxError as e: + print(BOLD + FAIL + '[XML ERROR]' + ENDC) + print(" {0}".format(e)) + continue + + # Get xml_filename prefix + xml_prefix = os.path.basename(xml_file) + xml_prefix = xml_prefix.split(".")[0] + + # Search for rnc file + rnc_file = os.path.join(relaxng_path, xml_prefix + ".rnc") + rng_file = xml_prefix + ".rng" + if rnc_file in rnc_files: + + # convert RNC to RNG file + rc = call("trang {0} {1}".format(rnc_file, rng_file), shell=True) + + # check return code + if rc == 0: + + # read in RelaxNG + relaxng_doc = etree.parse(rng_file) + relaxng = etree.RelaxNG(relaxng_doc) + + # validate xml file again RelaxNG + try: + relaxng.assertValid(xml_tree) + print(BOLD + OK + '[VALID]' + ENDC) + except (etree.DocumentInvalid, TypeError) as e: + print(BOLD + FAIL + '[NOT VALID]' + ENDC) + print(" {0}".format(e)) + + # remove rng file + os.remove(rng_file) + + # trang command failed + else: + print(BOLD + FAIL + '[TRANG FAILED]' + ENDC) + + # RNC file does not exist + else: + print(BOLD + NOT_FOUND + '[NO RELAXNG FOUND]' + ENDC)