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&e<. 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 dexNome, que vem antes do endereço e deindIEDest. 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#
- Modele os dados da nota como objetos, não como strings.
- Gere o XML com uma API que conhece XML:
XDocument/XElement, ou classes geradas a partir do XSD e serializadas comXmlSerializer. O escape passa a ser responsabilidade do serializador. - Omita os elementos opcionais sem valor e aplique
Trimnos textos vindos do cadastro. - Assine o XML gerado, sem reformatar.
- Valide o XML assinado contra o XSD da versão que você declara, antes de transmitir.
- 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 & 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 comSaveOptions.DisableFormatting. - Ao carregar um XML assinado para conferir ou reenviar, preserve os espaços em branco (
LoadOptions.PreserveWhitespacenoXDocument,PreserveWhitespace = truenoXmlDocument). - 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("&", "&"). Funciona até alguém esquecer um campo, ou escapar duas vezes e mandar&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
Trimantes 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?