Source code for src.xmlChecks.layoutCheck

# SPDX-FileCopyrightText: Copyright © 2026 BBC
#
# SPDX-License-Identifier: BSD-3-Clause

from src.validationLogging.validationCodes import ValidationCode
from src.validationLogging.validationLogger import ValidationLogger
from xml.etree.ElementTree import Element
from src.xmlUtils import make_qname, xmlIdAttr
from .xmlCheck import XmlCheck
from .ttmlUtils import ns_ttml
from src.styleAttribs import getStyleAttributeKeys, validateStyleAttr


[docs] class layoutCheck(XmlCheck): """ Checks presence and contents of layout element """
[docs] def run( self, input: Element, context: dict, validation_results: ValidationLogger) -> bool: tt_ns = context.get('root_ns', ns_ttml) layout_el_tag = make_qname(tt_ns, 'layout') layout_els = [el for el in input if el.tag == layout_el_tag] valid = True if len(layout_els) == 0: valid = False validation_results.error( location='{}/{}'.format(input.tag, layout_el_tag), message='Required layout element absent', code=ValidationCode.ebuttd_layout_element_constraint ) elif len(layout_els) > 1: valid = False validation_results.error( location='{}/{}'.format(input.tag, layout_el_tag), message='{} layout elements found, expected 1'.format( len(layout_els)), code=ValidationCode.ttml_element_layout ) else: # 1 layout element validation_results.good( location='{}/{}'.format(input.tag, layout_el_tag), message='layout element found', code=ValidationCode.ebuttd_layout_element_constraint ) if not valid: validation_results.skip( location='{}/{}'.format(input.tag, layout_el_tag), message='Skipping region element checks', code=ValidationCode.ttml_element_region ) else: valid = self._checkRegions( layout_el=layout_els[0], context=context, validation_results=validation_results) return valid
def _checkRegion( self, region_el: Element, context: dict, validation_results: ValidationLogger) -> bool: valid = True tt_ns = context.get('root_ns', ns_ttml) if xmlIdAttr not in region_el.keys(): valid = False validation_results.error( location='{}@{}'.format(region_el.tag, xmlIdAttr), message='region element found with no xml:id', code=ValidationCode.ttml_element_region ) else: # Store in context for later use region_map = context.get('id_to_region_map', {}) region_map[region_el.get(xmlIdAttr)] = region_el context['id_to_region_map'] = region_map # region-only style attributes should be inline in EBU-TT-D style_attr_keys = set( getStyleAttributeKeys( tt_ns=tt_ns, elements=['region'])) if style_attr_keys.isdisjoint(region_el.keys()): validation_results.warn( location='{} {}'.format( region_el.tag, region_el.get(xmlIdAttr, '(no xml:id)')), message='Region element has no recognised style attributes', code=ValidationCode.ttml_element_region ) valid &= validateStyleAttr(style_el=region_el, context=context, validation_results=validation_results) return valid def _checkRegions( self, layout_el: Element, context: dict, validation_results: ValidationLogger) -> bool: tt_ns = context.get('root_ns', ns_ttml) region_el_tag = make_qname(tt_ns, 'region') valid = True region_els = [el for el in layout_el if el.tag == region_el_tag] if len(region_els) == 0: valid = False validation_results.error( location='{}/{}'.format(layout_el.tag, region_el_tag), message='At least one region element required, none found', code=ValidationCode.ebuttd_region_element_constraint ) context['id_to_region_map'] = {} # expected downstream else: for region_el in region_els: valid &= self._checkRegion( region_el=region_el, context=context, validation_results=validation_results ) if valid: validation_results.good( location='[{}/{}]'.format(layout_el.tag, region_el_tag), message='Region elements checked', code=ValidationCode.ttml_element_layout ) return valid