No artigo passado da nossa série sobre TISS (Transferência de Informações na Saúde Suplementar), vimos como montar o arquivo XML que é lido pelas operadoras de plano de saúde, e principalmente uma forma de facilitar o cálculo do tão “temido” hash.

Mesmo que tenhamos feito tudo certo, chega uma hora que um arquivo é rejeitado pela operadora, seja que faltou algum dado, ou uma tag fora de lugar, entre outros motivos.

Cada operadora possui suas regras de negócio, e embora o padrão do arquivo seja o mesmo, algumas informações podem ser requeridas ou não. Porém se algo está mal-formatado no arquivo XML desde a origem, todas vão chiar.

Os arquivos XSD que descrevem o padrão TISS também servem para validá-los na origem. E este artigo mostrará como fazer isso de forma bem simples utilizando o .NET Framework.

Porém antes vamos fazer uma pequena alteração no arquivo XML.

Não sei se você reparou no arquivo que é gerado pelo código demonstrado no artigo anterior, logo na primeira tag. Embora na parte texto hard-coded essa tag esteja com o encoding ISO-8859-1, o arquivo gerado sai como UTF-8, e isto já é motivo para algumas operadoras rejeitarem o arquivo.

Isto ocorre quando carregamos o arquivo na classe XElement, na hora quando limpamos elementos em branco e convertemos os valores para maiúsculas, e depois salvamos esse arquivo. Temos que fazer com que o nosso arquivo saia com o encoding ISO 8859-1, então faremos duas pequenas alterações, logo após limparmos o arquivo.

//Elimina nós vazios, remove espaços (início e fim) dos valores e transforma em maiúsculos no XML
XElement docx = XElement.Parse(sbXML.ToString());
docx.Descendants().Where(e => String.IsNullOrEmpty(e.Value)).Remove();
docx.TrimWhiteSpaceFromValues();
docx.UpperValues();
/*até aqui, não muda nada*/

/*
Aqui tem uma pequena alteração. Vamos carregar o nosso XML limpo em um XDocument,
só que com a codificação ISO-8859-1, que é a que o padrão TISS pede.
Se salvar direto do XElement, gera um arquivo com codificação UTF-8
*/
XDocument xmlISO = new XDocument(new XDeclaration("1.0", "ISO-8859-1", "no"), docx);

//Recarrega o XML tratado no stringbuilder de origem, para gerar o hash.
using (MemoryStream ms = new MemoryStream())
{
	xmlISO.Save(ms); //Note que não salvamos mais o objeto "docx", e sim o "xmlISO".
	ms.Position = 0;
	
	/*
	Também alteramos o StreamReader, ele terá a entrada em ISO-8859-1
	*/
	using (StreamReader sr = new StreamReader(ms, Encoding.GetEncoding("ISO-8859-1")))
	{
		sbXML = new StringBuilder(sr.ReadToEnd());
	}
}

/*
O cálculo do hash não altera nada, porém ao finalizar o arquivo, tiraremos o atributo
standalone=no do arquivo.
A linha abaixo vai depois que substituímos o </ans:mensagemTISS> pelo novo trecho do arquivo
com cálculo do hash.
*/
sbXML.Replace("standalone=\"no\"", "");

Depois dessa pequena alteração, vamos enfim fazer uma pré-validação do arquivo na origem, através do XSD do padrão TISS.

1. Baixe os arquivos XSD, e grave-os em uma pasta da sua aplicação. Estes arquivos devem ser distribuídos com a aplicação, inclusive Web (devem ser colocados no servidor e acessíveis pela aplicação): http://leonelfraga.com/downloads/XSD-TISS-v30201.zip

2. Ao descompactar o arquivo, note que temos duas versões do arquivo “xmldsig-core-schema.xsd”. Se utilizarmos a versão original, ocorre um erro na validação, então modificamos esse arquivo e salvamos as alterações no arquivo “xmldsig-core-schema-2.xsd”.

3. Utilizaremos aqui uma classe para fazer a validação. Criamos um StringBuider global chamado ErrosXML, e três funções que farão a validação. Veja o código abaixo.

public class ValidadorXML
{
	private StringBuilder ErrosXML = new StringBuilder();
        
	public string ValidarXML(string xmlOrigem)
	{
		ErrosXML = new StringBuilder();
		try
		{
			//Pasta onde estão localizados os arquivos XSD
			string xsdRoot = @"C:\GeradorTISS\xsdv30201";

			XmlDocument xml = new XmlDocument();
			xml.Schemas.Add("http://www.ans.gov.br/padroes/tiss/schemas", xsdRoot + @"\tissV3_02_01.xsd");
			xml.Schemas.Add("http://www.w3.org/2000/09/xmldsig#", xsdRoot + @"\xmldsig-core-schema-2.xsd");
			xml.LoadXml(xmlOrigem);
			xml.Validate(xmlSettings_ValidationEventHandler);
		}
		catch (Exception ex)
		{
			ErrosXML.AppendLine(String.Format("Erro na validação do arquivo XML: {0}", ex.Message));
			ErrosXML.AppendLine("------------");
		}

		return ErrosXML.ToString();
	}

	public void xmlSettings_ValidationEventHandler(object sender, ValidationEventArgs e)
	{
		if (e.Severity == XmlSeverityType.Error || e.Severity == XmlSeverityType.Warning)
		{
			XmlSchemaValidationException ex = (XmlSchemaValidationException)e.Exception;
			
			XmlNode noGuia = capturaNoGuia((XmlElement)ex.SourceObject); //Se o nó é uma guia
			if (noGuia != null) //Se o erro foi em um nó que representa uma guia
			{
				XmlNode numeroguia = noGuia.SelectSingleNode("descendant::*[local-name()='numeroGuiaPrestador']");
				ErrosXML.AppendLine(String.Format("Erro na guia {0}: {1}", numeroguia.InnerText, e.Message));
				ErrosXML.AppendLine("------------");
			}
			else //Se o erro foi em outra parte do arquivo que não seja uma guia
			{
				ErrosXML.AppendLine(String.Format("Erro no arquivo XML: {0}", e.Message));
				ErrosXML.AppendLine("------------");
			}
		}
	}

	private XmlNode capturaNoGuia(XmlNode src)
	{
		if (src != null)
		{
			if ("guiaSP-SADT|guiaResumoInternacao".Contains(src.LocalName))
				return src;
			else
				return capturaNoGuia(src.ParentNode);
		}
		else
		{
			return src;
		}
	}
}

A função ValidarXML() recebe o conteúdo do arquivo XML, e dentro dela instanciamos um XMLDocument, onde adicionamos na coleção Schemas o arquivo principal do esquema TISS (tissV3_02_01.xsd, ou outra versão) e o arquivo “xmldsig-core-schema-2” (a versão alterada). Feito isso, carregamos a string de entrada no nosso objeto xml e executamos o método Validate().

Este método Validate() recebe como parâmetro um delegate do tipo ValidationEventHandler para capturar os erros a serem gerados. Criamos uma função nomeada xmlSettings_ValidationEventHandler() e passamos ela no Validate().

Dentro desse handler de validação, capturaremos a exceção que é gerada pela validação (XmlSchemaValidationException) e qual nó está disparando essa exceção. Para a mensagem ser um pouco mais amigável e dar uma indicação para o usuário de qual guia está dando problema, verificamos se o nó é uma guia através da função capturaNoGuia(). Feito isso, alimentamos o StrinBbuilder que criamos no início com as mensagens de erro.

Se o StringBuilder estiver vazio, não existem erros de formatação no arquivo XML. Caso alguma tag cujo tipo seja enumeração e nela estiver um valor não aceito, ou uma tag esteja fora de lugar, ou outra inconsistência estiver presente no arquivo de acordo com o XSD, serão disparados erros que alimentarão o StringBuilder.

Vimos que não é complicado fazer a pré-validação do arquivo TISS utilizando o próprio XSD para validá-lo, e ao fazer essa validação as chances do arquivo ser rejeitado pelas operadoras diminui bastante, pois elas também fazem essa validação após o upload. Agora, a validação das regras internas e distintas das operadoras são outra história.

Com isso, terminamos a nossa série sobre TISS. Espero que tenham aproveitado e que seja útil de alguma forma!