← Files MyBI IAARCHIVED FILE

skills/criar-dashboard-mybi/scripts/mybi_formatting.py

5.29 KB · Sep 30, 2026 · 23:16 UTC

↓ Download file

"""Explicit INI edits only; no visual defaults and no network access."""
import configparser
import io
import re

DASHBOARD = set('dashboard-color-background dashboard-title-font-bold dashboard-title-font-italic dashboard-title-font-underline dashboard-title-font-color dashboard-title-font-size dashboard-title-font-color-background dashboard-title-icon-color dashboard-menu-color dashboard-menu-icon-color dashboard-menu-text-color dashboard-menu-text-dash-color dashboard-menu-left-background-transparency leftwidth dashboard-altura ativar-configuracao-geral-componente-geral component-title-centralizar-geral component-title-font-bold-geral component-title-font-italic-geral component-title-font-underline-geral component-title-font-color-geral component-title-font-size-geral component-title-color-icon-geral component-title-color-background-geral component-aplicar-background-transparency-geral component-remover-background-transparency-title-geral component-color-background-geral'.split())
COMPONENT = set('title-font-center title-font-bold title-font-italic title-font-underline title-font-color title-font-size title-font-color-background title-icon-color background-transparency remove-background-transparency-title extend-background-color-to-title color-background color-marcador-background color-moldura-chart apply-background-image-title componente-modo-image-background'.split())
for suffix in ('conteudo', 'titulo-tabela'):
    COMPONENT.update('title-font-' + key + '-' + suffix for key in ('bold', 'italic', 'underline', 'color', 'size', 'color-background', 'align'))
DASHBOARD.update({'dashboard-image-background', 'modo-image-background'})
COMPONENT.add('componente-image-background')

def validate_values(values, allowed):
    if not isinstance(values, dict) or set(values) - allowed:
        raise ValueError('Propriedades INI não suportadas')
    for key, value in values.items():
        if not isinstance(value, str) or len(value) > 128 or re.search(r'[\r\n\x00-\x1f;\[\]]', value):
            raise ValueError('Valor INI inválido: ' + key)
        if value == '': continue  # Explicitly unset/inherit, never fill automatically.
        if key in ('dashboard-image-background', 'componente-image-background'):
            if not re.fullmatch(r'(?:\.\./SystemImages/)?[A-Za-z0-9_/-]+\.(?:png|jpg|jpeg|svg|webp)', value) or '..' in value.replace('../SystemImages/', '', 1):
                raise ValueError('Imagem deve ser um caminho relativo aprovado, sem URL externa')
        elif 'color' in key and key != 'extend-background-color-to-title':
            rgb = re.fullmatch(r'rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)', value)
            if not re.fullmatch(r'#[0-9A-Fa-f]{6}', value) and not (rgb and all(int(n) <= 255 for n in rgb.groups())):
                raise ValueError('Cor deve ser #RRGGBB ou rgb(R, G, B): ' + key)
        elif 'size' in key or key in ('leftwidth', 'dashboard-altura'):
            if not re.fullmatch(r'\d+(?:\.\d+)?', value) or float(value) <= 0:
                raise ValueError('Tamanho deve ser positivo: ' + key)
        elif 'align' in key:
            if value not in ('left', 'center', 'right', 'justify'): raise ValueError('Alinhamento inválido')
        elif key in ('componente-modo-image-background', 'modo-image-background'):
            if value not in ('default',): raise ValueError('Modo de imagem exige confirmação no contrato')
        elif value not in ('true', 'false'):
            raise ValueError('Booleano INI deve ser true/false: ' + key)

def validate_formatting(fmt):
    if not isinstance(fmt, dict) or set(fmt) - {'dashboard', 'components'}: raise ValueError('formatting inválido')
    validate_values(fmt.get('dashboard', {}), DASHBOARD)
    components = fmt.get('components', {})
    if not isinstance(components, dict): raise ValueError('formatting.components inválido')
    for name, values in components.items():
        if not re.fullmatch(r'[A-Za-z_][A-Za-z0-9_]*', name): raise ValueError('ComponentName inválido')
        validate_values(values, COMPONENT)

def merge_ini(original, edits):
    """Validate uniqueness, preserve all untouched lines, sections and comments."""
    parser = configparser.ConfigParser(interpolation=None, strict=True)
    parser.optionxform = str
    try: parser.read_string(original)
    except configparser.Error as exc: raise ValueError('INI-base inválido ou ambíguo') from exc
    pending = {section: dict(values) for section, values in edits.items() if values}
    lines, current = [], None
    def remaining():
        if current in pending:
            for key, value in pending.pop(current).items(): lines.append(key + '=' + value + '\n')
    for line in original.splitlines(keepends=True):
        section = re.fullmatch(r'\s*\[([^\]]+)\]\s*', line.strip())
        if section:
            remaining()
            current = section[1]
        key = line.split('=', 1)[0].strip() if '=' in line and not line.lstrip().startswith(('#', ';')) else None
        if current in pending and key in pending[current]:
            line = key + '=' + pending[current].pop(key) + '\n'
        lines.append(line if line.endswith(('\n', '\r')) else line + '\n')
    remaining()
    for section, values in pending.items():
        lines.append('\n[' + section + ']\n')
        lines.extend(key + '=' + value + '\n' for key, value in values.items())
    return ''.join(lines)

SHA-256: 07ce074059d3d7b342591797d557701ace37495c61a4aaa52532afd8ee7bffd7