ElementInclude no Python: use XInclude

Publicado em: 23/08/2026
Tempo de leitura: 5 minutos
A laptop screen showing a code editor with visible programming code in a dimly lit environment.

O módulo xml.etree.ElementInclude adiciona suporte limitado a XInclude para árvores criadas com xml.etree.ElementTree. XInclude permite que um documento XML indique que outro arquivo XML ou texto deve ser inserido em determinado ponto da árvore.

Esse recurso pode simplificar configurações, documentação, catálogos e arquivos grandes divididos em partes. Porém, inclusões também criam riscos: leitura de arquivos fora do diretório esperado, acesso a URLs, ciclos, explosão de conteúdo e dependências difíceis de rastrear. Por isso, loaders personalizados, caminhos permitidos e profundidade máxima são essenciais.

O que é XInclude

XInclude usa o namespace http://www.w3.org/2001/XInclude. O elemento xi:include possui um atributo href e pode usar parse="xml" ou parse="text".

<documento xmlns:xi="http://www.w3.org/2001/XInclude">
  <titulo>Relatório</titulo>
  <xi:include href="secoes/resumo.xml" parse="xml"/>
</documento>

Ao expandir a inclusão, o elemento xi:include é substituído pela raiz do XML indicado. Para texto, o conteúdo do arquivo é inserido como string.

Primeiro exemplo

from xml.etree import ElementTree, ElementInclude

arvore = ElementTree.parse("documento.xml")
raiz = arvore.getroot()

ElementInclude.include(raiz)

arvore.write(
    "resultado.xml",
    encoding="utf-8",
    xml_declaration=True,
)

O loader padrão trata o href como nome de arquivo. Isso é conveniente para documentos locais confiáveis, mas não deve ser usado sem restrições quando o XML ou os caminhos podem ser controlados por usuários.

Integração com ElementTree

ElementInclude trabalha sobre elementos ou árvores de ElementTree. Antes de usar XInclude, domine parsing, namespaces, busca e serialização. O guia de ElementTree no Python cobre essas operações.

A expansão ocorre in-place: a árvore original é modificada. Se você precisar preservar o documento antes da inclusão, faça uma cópia segura ou carregue novamente a fonte.

parse=”xml” e parse=”text”

Com parse="xml", o loader deve retornar um Element. Com parse="text", deve retornar uma string. O encoding padrão do loader de texto é UTF-8, mas pode ser informado no elemento.

<documento xmlns:xi="http://www.w3.org/2001/XInclude">
  <rodape>
    <xi:include href="ano.txt" parse="text" encoding="utf-8"/>
  </rodape>
</documento>

Texto incluído não vira automaticamente markup XML. Ele é inserido como conteúdo textual e será escapado durante a serialização.

base_url para referências relativas

O parâmetro base_url ajuda a resolver caminhos relativos com base na localização do documento principal.

from pathlib import Path
from xml.etree import ElementTree, ElementInclude

arquivo = Path("configs/principal.xml").resolve()
arvore = ElementTree.parse(arquivo)

ElementInclude.include(
    arvore.getroot(),
    base_url=arquivo.as_uri(),
    max_depth=4,
)

Não confie apenas em base_url como proteção. Ele resolve referências, mas não impede ../, URLs externas ou caminhos absolutos quando o loader aceita esses valores.

Profundidade máxima

include() usa max_depth=6 por padrão. O limite reduz o risco de recursão excessiva e explosão de inclusões. Passar None remove o limite e raramente é uma boa ideia.

Escolha um valor menor quando a estrutura do projeto for conhecida. Para configurações com apenas um ou dois níveis, max_depth=2 pode ser suficiente.

Ciclos de inclusão

Considere a.xml incluindo b.xml e b.xml incluindo a.xml. A profundidade máxima interrompe o ciclo depois de algumas expansões, mas o sistema deveria detectar a repetição e produzir um erro claro.

Um loader personalizado pode manter um conjunto de caminhos já visitados. Como o loader recebe apenas href, parse e encoding, o estado pode ficar em um closure ou objeto chamável.

Loader seguro com diretório permitido

from pathlib import Path
from xml.etree import ElementTree

RAIZ = Path("conteudos").resolve()

class LoaderLocal:
    def __init__(self):
        self.visitados = set()

    def __call__(self, href, parse, encoding=None):
        caminho = (RAIZ / href).resolve()

        if RAIZ not in caminho.parents and caminho != RAIZ:
            raise ValueError("Inclusão fora do diretório permitido")

        if caminho in self.visitados:
            raise ValueError("Ciclo de XInclude detectado")
        self.visitados.add(caminho)

        if caminho.stat().st_size > 2 * 1024 * 1024:
            raise ValueError("Arquivo incluído é grande demais")

        if parse == "xml":
            return ElementTree.parse(caminho).getroot()
        if parse == "text":
            return caminho.read_text(encoding=encoding or "utf-8")

        raise ValueError(f"Modo de parsing inválido: {parse}")

Depois, passe a instância para include():

loader = LoaderLocal()
ElementInclude.include(
    arvore.getroot(),
    loader=loader,
    max_depth=4,
)

O exemplo bloqueia path traversal, ciclos simples e arquivos grandes. Em sistemas reais, também limite a soma de bytes de todas as inclusões, quantidade de arquivos, profundidade e tempo total.

Path.resolve() ajuda a revelar links simbólicos e componentes ... Ainda assim, existe risco de condições de corrida entre validação e abertura. Para ambientes hostis, trabalhe com diretórios controlados, permissões restritas e APIs de abertura seguras do sistema operacional.

Evitar inclusões pela rede

Um loader pode buscar URLs, mas isso cria riscos de SSRF, redirects, DNS rebinding, respostas enormes e indisponibilidade. Para XML recebido de terceiros, prefira proibir HTTP e HTTPS.

Quando a rede for realmente necessária, mantenha uma allowlist de hosts, valide IPs públicos e privados, limite redirects, timeout e bytes. O artigo de urllib.request no Python mostra controles para downloads.

Arquivos temporários e cache

Se inclusões remotas forem baixadas antes do processamento, use arquivos temporários seguros e cache com integridade. Veja tempfile no Python e hashlib no Python.

Não use o valor de href diretamente como nome de arquivo. Gere nomes internos e armazene o mapeamento separadamente.

Fallback e limitações

O suporte do Python é limitado e não oferece XPointer completo. Não presuma compatibilidade com toda a especificação XInclude ou com ferramentas XML avançadas. Teste os documentos reais do seu ecossistema.

Quando a aplicação precisa de schemas, validação complexa, XPath completo ou políticas avançadas de resolução, uma biblioteca XML especializada pode ser mais adequada.

Validação depois da expansão

A inclusão pode inserir elementos inesperados, namespaces diferentes ou dados que violam regras de negócio. Valide a árvore resultante, não apenas cada arquivo isolado.

itens = arvore.getroot().findall(".//item")
if len(itens) > 10_000:
    raise ValueError("Quantidade excessiva de itens")

for item in itens:
    if not item.get("id"):
        raise ValueError("Item sem ID")

Serialização e atomicidade

Ao salvar o resultado, escreva em um arquivo temporário no mesmo diretório e substitua o destino somente após sucesso. Isso evita deixar um XML parcial se ocorrer erro durante a expansão ou escrita.

Logs e diagnóstico

Registre o documento principal, o caminho normalizado de cada inclusão, tamanho, profundidade e resultado. Não registre conteúdo sensível. Mensagens de erro devem indicar qual inclusão falhou sem expor caminhos internos desnecessários.

Testes recomendados

Teste inclusão XML e texto, encoding inválido, arquivo ausente, caminho absoluto, ../, link simbólico, ciclo direto e indireto, profundidade excedida, arquivo muito grande, modo parse inválido, namespaces e árvore resultante.

Erros comuns

Os erros mais frequentes são usar o loader padrão com XML externo, remover max_depth, permitir URLs livres, não detectar ciclos, validar o caminho antes de resolve(), confiar no href como nome local e não validar a árvore expandida.

Conclusão

xml.etree.ElementInclude permite dividir XML em arquivos menores e expandir XInclude dentro de árvores ElementTree. O recurso é simples, mas a resolução de caminhos e recursos precisa ser tratada como operação sensível.

Use loader personalizado, diretório permitido, limites de profundidade, bytes e quantidade, e valide o resultado final. Consulte a documentação oficial de XInclude no ElementTree e a recomendação XInclude do W3C.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Conteúdo do artigo

    Artigos relacionados

    Desenvolvedor configurando servidor HTTPS e certificado TLS com Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    HTTPSServer: crie servidor HTTPS local no Python

    Aprenda HTTPSServer no Python para servir HTTPS localmente, configurar certificados, usar threads e entender limites de segurança.

    Ler mais

    Tempo de leitura: 5 minutos
    08/10/2026
    Arquivos protegidos representando extração segura de TAR com Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    tarfile extraction_filter: extraia TAR com segurança

    Aprenda tarfile extraction_filter no Python para extrair arquivos TAR com validação, segurança e controle de caminhos.

    Ler mais

    Tempo de leitura: 6 minutos
    08/10/2026
    Código Python exibindo avisos controlados com catch_warnings
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    catch_warnings: capture warnings em testes Python

    Aprenda catch_warnings no Python para capturar, testar e controlar avisos com filtros específicos e segurança.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Código Python representando referências persistentes do pickle
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    pickle persistent_id: serialize referências externas

    Aprenda pickle persistent_id no Python para serializar referências externas com IDs estáveis, validação, segurança e compatibilidade.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Código binário representando buffers e memória no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    memoryview.count: conte valores sem copiar buffers

    Aprenda memoryview.count no Python para contar bytes e valores em buffers sem cópias, com formatos, limites e boas práticas.

    Ler mais

    Tempo de leitura: 6 minutos
    06/10/2026
    Código Python com anotações de tipos
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    annotationlib: evite imports circulares em anotações

    Aprenda annotationlib no Python para ler anotações adiadas, evitar imports circulares e criar ferramentas de introspecção seguras.

    Ler mais

    Tempo de leitura: 6 minutos
    06/10/2026