Melhor método de análise de arquivo de texto em C#?
Pergunta
Eu quero analisar um tipo de arquivo de configuração, assim:
[KEY:Value]
[SUBKEY:SubValue]
Agora comecei com um StreamReader
, convertendo linhas em matrizes de caracteres, quando percebi que deveria haver uma maneira melhor.Por isso peço a você, humilde leitor, que me ajude.
Uma restrição é que ele deve funcionar em um ambiente Linux/Mono (1.2.6 para ser exato).Não tenho a versão 2.0 mais recente (do Mono), então tente restringir os recursos da linguagem para C# 2.0 ou C# 1.0.
Solução
Eu considerei isso, mas não vou usar XML.Vou escrever essas coisas à mão, e editar XML manualmente faz meu cérebro doer.:')
Você já olhou YAML?
Você obtém os benefícios do XML sem toda a dor e sofrimento.É usado extensivamente na comunidade Ruby para coisas como arquivos de configuração, dados de banco de dados pré-preparados, etc.
aqui está um exemplo
customer:
name: Orion
age: 26
addresses:
- type: Work
number: 12
street: Bob Street
- type: Home
number: 15
street: Secret Road
Parece haver um Biblioteca C# aqui, que não usei pessoalmente, mas a YAML é bem simples, então "quão difícil pode ser?" :-)
Eu diria que é preferível inventar seu próprio formato ad-hoc (e lidar com bugs do analisador)
Outras dicas
Eu estava olhando quase exatamente para esse problema outro dia: Este artigo na tokenização de strings é exatamente o que você precisa.Você desejará definir seus tokens como algo como:
@"(?<level>\s) | " +
@"(?<term>[^:\s]) | " +
@"(?<separator>:)"
O artigo explica muito bem.A partir daí, você simplesmente começa a consumir fichas como achar melhor.
Dica:Para um Analisador LL(1) (ler:fácil), os tokens não podem compartilhar um prefixo.Se você tem abc
como um token, você não pode ter ace
como um token
Observação:O artigo está faltando o | Personagens em seus exemplos, basta jogá -los.
Há outra biblioteca YAML para .NET que está em desenvolvimento.No momento, ele suporta a leitura de fluxos YAML e foi testado em Windows e Mono.O suporte de gravação está sendo implementado atualmente.
Usar uma biblioteca é quase sempre preferível a criar a sua própria.Aqui está uma lista rápida de pontos do tipo "Oh, nunca vou precisar disso/não pensei nisso" que acabarão incomodando você mais tarde:
- Personagens escapando.E se você quiser um:na chave ou ] no valor?
- Escapando do caractere de escape.
- Unicode
- Mistura de tabulações e espaços (veja os problemas com a sintaxe sensível a espaços em branco do Python)
- Lidando com diferentes formatos de caracteres de retorno
- Tratamento de relatórios de erros de sintaxe
Como outros sugeriram, o YAML parece ser sua melhor aposta.
Você também pode usar uma pilha e um algoritmo push/pop.Este corresponde às tags de abertura/fechamento.
public string check()
{
ArrayList tags = getTags();
int stackSize = tags.Count;
Stack stack = new Stack(stackSize);
foreach (string tag in tags)
{
if (!tag.Contains('/'))
{
stack.push(tag);
}
else
{
if (!stack.isEmpty())
{
string startTag = stack.pop();
startTag = startTag.Substring(1, startTag.Length - 1);
string endTag = tag.Substring(2, tag.Length - 2);
if (!startTag.Equals(endTag))
{
return "Fout: geen matchende eindtag";
}
}
else
{
return "Fout: geen matchende openeningstag";
}
}
}
if (!stack.isEmpty())
{
return "Fout: geen matchende eindtag";
}
return "Xml is valid";
}
Provavelmente você pode se adaptar para poder ler o conteúdo do seu arquivo.Expressões regulares também são uma boa ideia.
Parece-me que seria melhor usar um arquivo de configuração baseado em XML, pois já existem classes .NET que podem ler e armazenar as informações para você com relativa facilidade.Existe uma razão para que isso não seja possível?
@Bernardo: É verdade que editar XML manualmente é entediante, mas a estrutura que você está apresentando já se parece muito com XML.
Aí sim, tem um bom método aí.
@Gishu
Na verdade, uma vez que eu acomodei os caracteres de escape, meu regex funcionou um pouco mais lento do que meu analisador recursivo escrito à mão de cima para baixo e isso sem o aninhamento (vinculando subitens aos seus pais) e erro ao relatar que o analisador escrito à mão tinha.
O regex foi um pouco mais rápido de escrever (embora eu tenha um pouco de experiência com analisadores manuais), mas isso sem um bom relatório de erros.Depois de adicionar isso, fica um pouco mais difícil e demorado de fazer.
Também acho o analisador escrito à mão mais fácil de entender a intenção.Por exemplo, aqui está um trecho do código:
private static Node ParseNode(TextReader reader)
{
Node node = new Node();
int indentation = ParseWhitespace(reader);
Expect(reader, '[');
node.Key = ParseTerminatedString(reader, ':');
node.Value = ParseTerminatedString(reader, ']');
}
Independentemente do formato persistente, usar um Regex seria a maneira mais rápida de análise.Em Ruby provavelmente seriam algumas linhas de código.
\[KEY:(.*)\]
\[SUBKEY:(.*)\]
Esses dois forneceriam o valor e o subvalor no primeiro grupo.Confira o MSDN sobre como combinar um regex com uma string.
Isso é algo que todos deveriam ter em seus gatinhos.Os dias pré-Regex pareceriam a Era do Gelo.