#!/usr/bin/env python3 """ DMN to HTML Transformation Script This standalone script transforms DMN (Decision Model and Notation) XML files to HTML tables using XSLT, designed to be run as part of the GitHub Actions CI/CD pipeline before the IG publisher. Usage: python transform_dmn.py [--dmn-dir DIR] [--output-dir DIR] [--xslt-file FILE] Arguments: --dmn-dir Directory containing .dmn files (default: input/dmn) --output-dir Directory to save HTML files (default: input/pagecontent) --xslt-file Path to XSLT transformation file (default: input/includes/dmn2html.xslt) """ import argparse import glob import logging import os import sys from pathlib import Path from typing import Optional import lxml.etree as ET def setup_logging() -> logging.Logger: """Set up logging configuration.""" logging.basicConfig( level=logging.INFO, format='%(levelname)s: %(message)s' ) return logging.getLogger(__name__) def load_xslt_transformer(xslt_path: Path, logger: logging.Logger) -> Optional[ET.XSLT]: """ Load and compile the XSLT transformer. Args: xslt_path: Path to the XSLT file logger: Logger instance Returns: Compiled XSLT transformer or None if failed """ try: # Register DMN namespace ET.register_namespace('dmn', "https://www.omg.org/spec/DMN/20240513/MODEL/") logger.info(f"Loading XSLT transformer from {xslt_path}") with open(xslt_path, "rb") as f: xslt_doc = ET.parse(f) return ET.XSLT(xslt_doc) except Exception as e: logger.error(f"Failed to load XSLT transformer from {xslt_path}: {e}") return None def transform_dmn_file(dmn_path: Path, output_path: Path, transformer: ET.XSLT, logger: logging.Logger) -> bool: """ Transform a single DMN file to HTML. Args: dmn_path: Path to the DMN file output_path: Path for the output HTML file transformer: Compiled XSLT transformer logger: Logger instance Returns: True if successful, False otherwise """ try: logger.info(f"Transforming {dmn_path} -> {output_path}") # Parse the DMN XML file with open(dmn_path, "rb") as f: dmn_doc = ET.parse(f) # Apply XSLT transformation result = transformer(dmn_doc) # Write the HTML output output_path.parent.mkdir(parents=True, exist_ok=True) with open(output_path, "w", encoding="utf-8") as f: f.write(str(ET.tostring( result.getroot(), encoding="unicode", pretty_print=True, doctype=None ))) logger.info(f"Successfully transformed {dmn_path.name}") return True except Exception as e: logger.error(f"Failed to transform {dmn_path}: {e}") return False def main(): """Main function to handle command line arguments and coordinate transformation.""" logger = setup_logging() # Check if first argument is an IG root path (from template execution) # If it doesn't start with '--', treat it as the IG root ig_root = Path(".") remaining_args = sys.argv[1:] if len(sys.argv) > 1 and not sys.argv[1].startswith('--'): ig_root = Path(sys.argv[1]) remaining_args = sys.argv[2:] logger.info(f"IG root directory: {ig_root.absolute()}") parser = argparse.ArgumentParser( description="Transform DMN files to HTML using XSLT", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=__doc__ ) parser.add_argument( "--dmn-dir", type=Path, default=None, help="Directory containing .dmn files (default: /input/dmn)" ) parser.add_argument( "--output-dir", type=Path, default=None, help="Directory to save HTML files (default: /input/pagecontent)" ) parser.add_argument( "--xslt-file", type=Path, default=None, help="Path to XSLT transformation file (default: /input/includes/dmn2html.xslt)" ) args = parser.parse_args(remaining_args) # Resolve paths relative to IG root dmn_dir = args.dmn_dir if args.dmn_dir else (ig_root / "input" / "dmn") output_dir = args.output_dir if args.output_dir else (ig_root / "input" / "pagecontent") xslt_file = args.xslt_file if args.xslt_file else (ig_root / "input" / "includes" / "dmn2html.xslt") logger.info(f"DMN directory: {dmn_dir}") logger.info(f"Output directory: {output_dir}") logger.info(f"XSLT file: {xslt_file}") # Validate inputs if not xslt_file.exists(): logger.warning(f"XSLT file not found: {xslt_file}") logger.info("No XSLT transformer available, skipping DMN transformation") sys.exit(0) if not dmn_dir.exists(): logger.info(f"DMN directory not found: {dmn_dir}") logger.info("No DMN files to transform") sys.exit(0) # Load XSLT transformer transformer = load_xslt_transformer(xslt_file, logger) if not transformer: sys.exit(1) # Find all DMN files dmn_files = list(dmn_dir.glob("*.dmn")) if not dmn_files: logger.info(f"No .dmn files found in {dmn_dir}") sys.exit(0) logger.info(f"Found {len(dmn_files)} DMN files to transform") # Transform each DMN file success_count = 0 for dmn_file in dmn_files: # Output file will have same name but .xml extension output_file = output_dir / f"{dmn_file.stem}.xml" if transform_dmn_file(dmn_file, output_file, transformer, logger): success_count += 1 logger.info(f"Successfully transformed {success_count}/{len(dmn_files)} DMN files") if success_count != len(dmn_files): sys.exit(1) if __name__ == "__main__": main()