Programação Por Equipe DevBras

Capa do artigo: Classe gerada do XSD: como trocar de versão do leiaute sem perder código

Classe gerada do XSD: como trocar de versão do leiaute sem perder código

Gere classes C# a partir do XSD de forma repetível, mantenha a customização fora do arquivo gerado e transforme a troca de versão do leiaute num diff revisável.

O leiaute do EFD-Reinf mudou de versão e o arquivo que o sistema gera começou a ser rejeitado. A equipe roda o xsd.exe no schema novo, substitui a classe e o projeto não compila mais: alguém, um ano atrás, acrescentou um método de formatação e um [XmlIgnore] no meio das 4.000 linhas geradas. Ninguém lembra quais foram as mudanças, nem qual comando gerou o arquivo original.

A resposta curta: classe gerada é artefato de build, não código-fonte. Guarde no repositório o XSD, a versão do leiaute e o comando exato; gere numa pasta que pode ser apagada; coloque toda customização em partial class ou num tipo seu fora dessa pasta. Assim a troca de versão vira "rodar o script e revisar o diff", não arqueologia.

Por que a classe gerada prende você a uma versão

Ferramentas como o xsd.exe, que acompanha o SDK do Windows para .NET Framework, ou o gerador open source XmlSchemaClassGenerator (disponível como ferramenta dotnet) leem o schema e produzem classes com atributos de serialização: nome do elemento, namespace, ordem, tipo. Cada regra do XSD vira uma decisão congelada no C#. Se o schema da versão nova renomeia um grupo, muda um tipo de string para enumeração ou troca o namespace, a classe velha continua compilando e passa a produzir um XML que o validador do órgão recusa.

Em leiautes fiscais, o namespace costuma carregar a versão. Um evento gerado com a classe da versão anterior sai com o namespace antigo e é rejeitado antes de qualquer validação de conteúdo. O erro devolvido fala de um campo ou de uma estrutura e não diz "sua classe é velha", então a equipe perde horas procurando um dado errado que na verdade é um tipo errado.

O que acontece quando alguém edita o arquivo gerado

Editar a classe gerada parece inofensivo: um ToString aqui, um valor padrão ali. O problema é que a próxima geração apaga a edição sem aviso. Ou, pior, a equipe tem medo de gerar de novo e passa a editar o schema novo à mão dentro da classe velha, campo por campo. A partir daí, o arquivo não corresponde a nenhum XSD, e o git não consegue mais separar o que veio do schema do que veio de uma pessoa.

Como organizar a geração para ser repetível

  1. Guarde os XSDs no repositório, numa pasta com a versão no nome, como schemas/reinf/v1_04_00/. Não dependa de baixar de novo do portal.
  2. Escreva o comando num script versionado, com namespace C# e pasta de saída explícitos.
  3. Gere numa pasta só de código gerado, como Gerado/, que o script pode apagar e recriar.
  4. Marque o arquivo como gerado no topo (o próprio xsd.exe já escreve um aviso de "auto-generated") e combine com a equipe que essa pasta não se edita.
  5. Customize com partial class em arquivos fora de Gerado/. O xsd.exe gera classes partial, então você pode acrescentar membros sem tocar no arquivo dele.
  6. Valide o XML produzido contra o XSD num teste automatizado antes de enviar qualquer coisa.
  7. Na troca de versão, gere numa pasta nova e revise o diff entre as duas gerações. Esse diff é a lista real de mudanças do leiaute para o seu código.

O script de geração

Um script PowerShell simples, guardado em tools/gerar-reinf.ps1. Quando o schema principal importa outros arquivos, passe todos na mesma chamada, senão os tipos importados não são encontrados:

# tools/gerar-reinf.ps1
param(
    [string]$Versao = "v1_04_00"
)
$ErrorActionPreference = "Stop"

$schemas = Join-Path $PSScriptRoot "../schemas/reinf/$Versao"
$saida   = Join-Path $PSScriptRoot "../src/Reinf/Gerado/$Versao"

if (Test-Path $saida) { Remove-Item $saida -Recurse -Force }
New-Item -ItemType Directory -Path $saida | Out-Null

$arquivos = Get-ChildItem $schemas -Filter *.xsd | ForEach-Object { $_.FullName }

& xsd.exe @arquivos /classes /namespace:"Loja.Reinf.$Versao" /out:$saida
if ($LASTEXITCODE -ne 0) { throw "xsd.exe falhou com código $LASTEXITCODE" }

O namespace C# com a versão no nome não é capricho. Durante a transição entre leiautes, é comum precisar ler ou retransmitir eventos da versão antiga enquanto os novos já saem na versão nova. Com namespaces separados, as duas gerações convivem no mesmo projeto sem conflito de nome, e o compilador impede que você misture uma classe de cada versão no mesmo evento.

A customização fora do arquivo gerado

// src/Reinf/Extensoes/EventoPartial.cs
namespace Loja.Reinf.v1_04_00;

public partial class Reinf
{
    public string Descrever() => "Reinf v1_04_00";
}

Melhor ainda é não pendurar regra de negócio na classe gerada. Monte seus objetos de domínio (RetencaoServico, Prestador) e escreva um mapeador explícito que converte o domínio para a classe da versão vigente. Na troca de leiaute, quem muda é o mapeador, e o compilador aponta cada campo que deixou de existir.

O teste que valida contra o schema

using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;

public static class ValidadorXsd
{
    public static List<string> Validar<T>(T objeto, string pastaSchemas)
    {
        var erros = new List<string>();
        var schemas = new XmlSchemaSet();
        foreach (var xsd in Directory.GetFiles(pastaSchemas, "*.xsd"))
            schemas.Add(null, xsd);

        var xml = new StringWriter();
        new XmlSerializer(typeof(T)).Serialize(xml, objeto);

        var settings = new XmlReaderSettings
        {
            ValidationType = ValidationType.Schema,
            Schemas = schemas
        };
        settings.ValidationEventHandler += (_, e) => erros.Add(e.Message);

        using var leitor = XmlReader.Create(new StringReader(xml.ToString()), settings);
        while (leitor.Read()) { }
        return erros;
    }
}

Rode esse validador num teste com um evento montado pelo mapeador. Se a lista voltar vazia, o XML está de acordo com o schema que você guardou. Isso não garante que o órgão vá aceitar (há regras de negócio que o XSD não expressa, e a assinatura digital é outra etapa), mas elimina a classe inteira de rejeições estruturais antes do envio.

A troca de versão como um diff

Quando sai o leiaute novo: baixe os XSDs para schemas/reinf/vNOVA/, rode o script com a nova versão e compare as duas pastas geradas. O diff mostra campos que surgiram, campos que sumiram, tipos que mudaram e o namespace novo. Cada item vira uma tarefa no mapeador. Junte a isso a nota técnica do órgão, porque regras de preenchimento costumam mudar sem alterar o schema. Para o XML de outros contextos, como compras públicas, a mesma lógica aparece em a versão do leiaute é parte do arquivo, e montar XML sem classe gerada tem seus próprios cuidados em gerar XML sem concatenar texto.

Erros comuns

  • Editar a classe gerada à mão. A próxima geração apaga a mudança ou, por medo disso, ninguém gera de novo e o código deixa de corresponder a qualquer schema.
  • Não guardar o XSD nem o comando. Daqui a um ano ninguém sabe com que versão e opções a classe foi produzida, e reproduzir vira tentativa e erro.
  • Gerar só o schema principal e ignorar os importados. A ferramenta falha ao resolver tipos, ou alguém "resolve" copiando tipos à mão.
  • Misturar classes de duas versões no mesmo evento. O XML sai com namespaces ou estruturas incompatíveis e é rejeitado com uma mensagem que não aponta a causa.
  • Usar o envio oficial como primeiro teste. Cada rejeição em produção custa prazo; o validador local pega os erros estruturais em segundos.
  • Tratar o XSD como a regra inteira. O schema valida forma; regras de conteúdo estão no manual e nas notas técnicas.

Checklist

  • Os XSDs de cada versão estão no repositório, em pasta com a versão no nome.
  • Existe um script versionado que gera as classes do zero.
  • Nenhum arquivo dentro da pasta de código gerado foi editado à mão (o git log confirma).
  • Customizações estão em partial class ou num mapeador fora da pasta gerada.
  • O namespace C# indica a versão do leiaute.
  • Um teste automatizado valida o XML produzido contra o XSD.
  • A troca de versão começa por um diff entre gerações, não por edição manual.

Apague agora a pasta de classes geradas do seu projeto e rode o script de geração: o projeto compila igual, ou você descobre uma alteração manual escondida no meio do arquivo?

Avalie este artigo

Referência: https://pt.stackoverflow.com/questions/347864/xsd-exe-gerar-classes-no-c-efd-reinf-v1-04-00