Programação Por Equipe DevBras

Capa do artigo: XML da NF-e no StringBuilder: o & no nome do cliente derruba a nota

XML da NF-e no StringBuilder: o & no nome do cliente derruba a nota

Concatenar texto para gerar o XML da nota passa no olho e quebra no parser e no schema. Veja em C# como gerar com XDocument, validar contra o XSD e não estragar a assinatura depois.

A distribuidora emitia nota havia meses sem problema. Num sábado entra o cliente "Silva & Filhos Ltda" e a emissão para com An error occurred while parsing EntityName. O XML era montado num StringBuilder, e o & foi parar cru no meio do arquivo. Quem revisou o código olhou o XML indentado de um teste e achou perfeito. A resposta curta: não monte XML de nota concatenando texto. Gere com uma API de XML (no .NET, XDocument ou classes serializadas a partir do XSD), que faz o escape sozinha, e valide contra o schema da versão antes de transmitir.

O caractere especial é só o sintoma mais visível. Ordem de elementos, namespace, tag vazia e espaço sobrando também passam no olho e morrem no schema. Este guia mostra os quatro problemas, o código em C# que evita cada um e o cuidado com a assinatura. Se o seu sistema é em PHP, o guia de gerar XML em PHP sem concatenar mostra o equivalente.

Por que o XML concatenado passa no olho e falha no schema

O olho lê estrutura visual: indentação, tags que abrem e fecham. O parser e o schema leem outra coisa:

  • Escape. Em XML, & e < dentro de texto precisam virar &amp; e &lt;. Concatenando, você depende de lembrar de escapar cada campo. Basta um esquecido, e o primeiro cliente com "&" no nome quebra o arquivo.
  • Ordem. O leiaute da NF-e usa sequências: dentro de dest, o documento vem antes de xNome, que vem antes do endereço e de indIEDest. Trocar a ordem é inválido, mesmo com todas as tags presentes.
  • Namespace. Os elementos pertencem a http://www.portalfiscal.inf.br/nfe. Uma tag sem namespace, ou com o prefixo errado, não é a tag que o schema espera.
  • Valor vazio e espaço. Os tipos de texto do leiaute não aceitam valor vazio nem espaço no começo ou no fim. Uma tag opcional sem dado deve ser omitida, não gerada vazia. Concatenação gera <xCpl></xCpl> sem perceber.

Como gerar e validar o XML em C#

  1. Modele os dados da nota como objetos, não como strings.
  2. Gere o XML com uma API que conhece XML: XDocument/XElement, ou classes geradas a partir do XSD e serializadas com XmlSerializer. O escape passa a ser responsabilidade do serializador.
  3. Omita os elementos opcionais sem valor e aplique Trim nos textos vindos do cadastro.
  4. Assine o XML gerado, sem reformatar.
  5. Valide o XML assinado contra o XSD da versão que você declara, antes de transmitir.
  6. Transmita e guarde o XML exatamente como foi assinado, junto com o retorno da Sefaz.

O código que quebrou

var sb = new StringBuilder();
sb.Append("<dest>");
sb.Append("<CNPJ>" + cliente.Cnpj + "</CNPJ>");
sb.Append("<xNome>" + cliente.Nome + "</xNome>");
sb.Append("<xCpl>" + cliente.Complemento + "</xCpl>");
sb.Append("</dest>");

Com cliente.Nome = "Silva & Filhos Ltda", o resultado tem um & solto, e qualquer parser recusa. Com complemento vazio, sai <xCpl></xCpl>, que o schema recusa. E xCpl nem pertence a dest: fica dentro de enderDest. Três erros em seis linhas, e nenhum aparece no teste com "Cliente Teste".

O mesmo trecho com XDocument

using System.Xml.Linq;

XNamespace nfe = "http://www.portalfiscal.inf.br/nfe";

static XElement? Opcional(XName nome, string? valor) =>
    string.IsNullOrWhiteSpace(valor) ? null : new XElement(nome, valor.Trim());

var dest = new XElement(nfe + "dest",
    new XElement(nfe + "CNPJ", cliente.Cnpj),
    new XElement(nfe + "xNome", cliente.Nome.Trim()),
    new XElement(nfe + "enderDest",
        new XElement(nfe + "xLgr", cliente.Logradouro.Trim()),
        new XElement(nfe + "nro", cliente.Numero.Trim()),
        Opcional(nfe + "xCpl", cliente.Complemento),
        new XElement(nfe + "xBairro", cliente.Bairro.Trim()),
        new XElement(nfe + "cMun", cliente.CodigoMunicipio),
        new XElement(nfe + "xMun", cliente.Municipio.Trim()),
        new XElement(nfe + "UF", cliente.Uf)),
    new XElement(nfe + "indIEDest", cliente.IndicadorIe));

O XElement escreve Silva &amp; Filhos Ltda sozinho. O null devolvido por Opcional é ignorado pelo construtor, então a tag vazia não existe. O namespace está em todos os elementos. A ordem ainda depende de você seguir o leiaute, e é por isso que o passo seguinte existe.

Validar contra o XSD antes de transmitir

using System.Xml;
using System.Xml.Linq;
using System.Xml.Schema;

static List<string> ValidarNota(XDocument nota, string pastaSchemas)
{
    var schemas = new XmlSchemaSet { XmlResolver = new XmlUrlResolver() };
    schemas.Add("http://www.portalfiscal.inf.br/nfe",
        Path.Combine(pastaSchemas, "nfe_v4.00.xsd"));

    var erros = new List<string>();
    nota.Validate(schemas, (_, e) => erros.Add(e.Severity + ": " + e.Message));
    return erros;
}

Use os schemas baixados do Portal Nacional da NF-e para a versão que você declara. O arquivo da nota inclui os de tipos e o da assinatura, por isso o resolvedor precisa estar configurado e a pasta precisa ter o pacote inteiro. Se a lista de erros não estiver vazia, a nota não sai: registre os erros, mostre ao operador o campo com problema e não gaste uma tentativa na Sefaz. O guia de conferir o leiaute que vale hoje explica de onde vêm esses schemas e como acompanhar as mudanças.

A assinatura não perdoa edição

A assinatura digital da NF-e cobre o conteúdo de infNFe. Qualquer mudança depois de assinar, inclusive reindentar, trocar quebra de linha ou "corrigir" um campo direto no texto, invalida a assinatura. Na prática:

  • Gere o XML sem indentação e não o reformate antes de assinar ou depois. Com XDocument, salve com SaveOptions.DisableFormatting.
  • Ao carregar um XML assinado para conferir ou reenviar, preserve os espaços em branco (LoadOptions.PreserveWhitespace no XDocument, PreserveWhitespace = true no XmlDocument).
  • Se um dado está errado, corrija na origem, gere de novo e assine de novo. Nunca edite o arquivo assinado.

Erros comuns

  • Escapar "na mão" com Replace("&", "&amp;"). Funciona até alguém esquecer um campo, ou escapar duas vezes e mandar &amp;amp; para o nome do cliente.
  • Testar só com dados limpos. "Cliente Teste" não tem acento, "&", aspas nem espaço sobrando. O cadastro real tem.
  • Gerar tag vazia para campo opcional. O schema recusa, e a rejeição aparece longe do campo que a causou.
  • Validar o XML antes da última alteração. Quem valida e depois acrescenta um grupo está validando outro arquivo.
  • Guardar só o print da rejeição. Guarde o código e a mensagem de retorno da Sefaz junto do XML enviado; é isso que permite reproduzir o problema.
  • Editar o XML depois de assinado para corrigir um detalhe. A assinatura deixa de conferir e a nota é rejeitada.

Checklist

  • Nenhuma parte do XML da nota é montada por concatenação de string.
  • Todos os elementos estão no namespace http://www.portalfiscal.inf.br/nfe.
  • Campos opcionais sem valor não geram tag.
  • Os textos passam por Trim antes de entrar no XML.
  • O XML assinado é validado contra o XSD da versão declarada antes de transmitir.
  • Há um teste automatizado com cliente "Silva & Filhos Ltda", acentos e complemento vazio.
  • O arquivo guardado é byte a byte o que foi assinado e autorizado.

Cadastre em homologação um cliente chamado "Silva & Filhos Ltda", com complemento em branco, e emita uma nota para ele: o seu sistema gera um XML que passa no schema?

Avalie este artigo

Referência: https://social.msdn.microsoft.com/Forums/vstudio/pt-BR/1685c7f2-7e6c-4f16-9f8e-2bf996610af5/gerar-xml-pra-nfe?forum=xmlpt