#!/usr/bin/env python3 """ Compile Odoo XML templates to a JS bundle with registerTemplate() calls. Mirrors: odoo/addons/base/models/assetsbundle.py generate_xml_bundle() Usage: python3 tools/compile_templates.py """ import os import sys import json from lxml import etree def main(): if len(sys.argv) < 3: print("Usage: compile_templates.py ") sys.exit(1) odoo_path = sys.argv[1] output_file = sys.argv[2] # Read XML file list bundle_file = os.path.join(os.path.dirname(__file__), '..', 'pkg', 'server', 'assets_xml.txt') with open(bundle_file) as f: xml_urls = [line.strip() for line in f if line.strip()] addons_dirs = [ os.path.join(odoo_path, 'addons'), os.path.join(odoo_path, 'odoo', 'addons'), ] content = [] count = 0 errors = 0 for url_path in xml_urls: rel_path = url_path.lstrip('/') source_file = None for addons_dir in addons_dirs: candidate = os.path.join(addons_dir, rel_path) if os.path.isfile(candidate): source_file = candidate break if not source_file: continue try: tree = etree.parse(source_file) root = tree.getroot() # Process each block if root.tag == 'templates': templates_el = root else: templates_el = root for template in templates_el.iter(): t_name = template.get('t-name') if not t_name: continue inherit_from = template.get('t-inherit') template.set('{http://www.w3.org/XML/1998/namespace}space', 'preserve') xml_string = etree.tostring(template, encoding='unicode') # Escape for JS template literal xml_string = xml_string.replace('\\', '\\\\').replace('`', '\\`').replace('${', '\\${') # Templates with a t-name are always registered as primary templates. # If they have t-inherit, the templates.js _getTemplate function # handles the inheritance (cloning parent + applying xpath modifications). # registerTemplateExtension is ONLY for anonymous patches without t-name. content.append(f'registerTemplate("{t_name}", `{url_path}`, `{xml_string}`);') count += 1 except Exception as e: print(f" ERROR parsing {url_path}: {e}") errors += 1 # Wrap in odoo.define with new-format module name so the loader resolves it js_output = f'''odoo.define("@web/bundle_xml", ["@web/core/templates"], function(require) {{ "use strict"; const {{ checkPrimaryTemplateParents, registerTemplate, registerTemplateExtension }} = require("@web/core/templates"); {chr(10).join(content)} }}); // Trigger the module in case the loader hasn't started it yet if (odoo.loader && odoo.loader.modules.has("@web/bundle_xml") === false) {{ odoo.loader.startModule("@web/bundle_xml"); }} ''' os.makedirs(os.path.dirname(output_file), exist_ok=True) with open(output_file, 'w', encoding='utf-8') as f: f.write(js_output) print(f"Done: {count} templates compiled, {errors} errors") print(f"Output: {output_file}") if __name__ == '__main__': main()