XML is a multilevel structure, and you’re trying to read "flat" form. What you are doing is basically the deserialization of XML for your C#objects, and the best way to do this is to use a serialization class (for example, Xmlserializer or Datacontractserializer).
Now, if you really want to do the job "at hand" (and I don’t advise, since the chance of you having errors in your implementation isn’t negligible) you need to store the context from where the nodes appear. One possibility is to store all the background nodes, and use this information, as in the example below:
class Program
{
const string XML = @"<produtos>
<produto>
<id>1</id>
<dados>
<categoria>cat dados 1</categoria>
</dados>
<formato>
<categoria>cat formato 1</categoria>
</formato>
</produto>
<produto>
<id>2</id>
<dados>
<categoria>cat dados 2</categoria>
</dados>
<formato>
<categoria>cat formato 2</categoria>
</formato>
</produto>
</produtos>";
static void Main()
{
Stack<string> pilha = new Stack<string>();
var produtos = new List<Produto>();
var produto = new Produto { Dados = new Dados(), Formato = new Formato() };
using (var reader = new XmlTextReader(new StringReader(XML)))
{
while (reader.Read())
{
if (reader.IsStartElement())
{
if (reader.Name == "ID")
{
produto.ID = reader.ReadString();
}
else if (reader.Name == "categoria")
{
var topoDaPilha = pilha.Peek();
if (topoDaPilha == "dados")
{
produto.Dados.Categoria = reader.ReadString();
}
else if (topoDaPilha == "formato")
{
produto.Formato.Categoria = reader.ReadString();
}
}
else if (!reader.IsEmptyElement)
{
pilha.Push(reader.Name);
}
}
else if (reader.NodeType == XmlNodeType.EndElement)
{
pilha.Pop();
if (reader.Name == "produto")
{
produtos.Add(produto);
produto = new Produto { Dados = new Dados(), Formato = new Formato() };
}
}
}
}
Console.WriteLine("Produtos:");
foreach (var prod in produtos)
{
Console.WriteLine("- {0}", prod);
}
}
}
class Produto
{
public string ID { get; set; }
public Dados Dados { get; set; }
public Formato Formato { get; set; }
public override string ToString()
{
return string.Format("Produto[ID={0}, Dados/Cat={1}, Formato/Cat={2}]", ID, Dados.Categoria, Formato.Categoria);
}
}
class Dados
{
public string Categoria { get; set; }
}
class Formato
{
public string Categoria { get; set; }
}
Another possibility is to accept the recursiveness of XML, and use functions to read each part of your document:
class Program
{
const string XML = @"<produtos>
<produto>
<id>1</id>
<dados>
<categoria>cat dados 1</categoria>
</dados>
<formato>
<categoria>cat formato 1</categoria>
</formato>
</produto>
<produto>
<id>2</id>
<dados>
<categoria>cat dados 2</categoria>
</dados>
<formato>
<categoria>cat formato 2</categoria>
</formato>
</produto>
</produtos>";
static void Main(string[] args)
{
var produtos = new List<Produto>();
using (var reader = new XmlTextReader(new StringReader(XML)))
{
reader.ReadToDescendant("produto");
while (reader.IsStartElement("produto"))
{
produtos.Add(ReadProduto(reader));
}
}
Console.WriteLine("Produtos:");
foreach (var produto in produtos)
{
Console.WriteLine("- {0}", produto);
}
}
static Produto ReadProduto(XmlReader reader)
{
Debug.Assert(reader.LocalName == "produto");
Produto produto = new Produto();
if (reader.IsEmptyElement)
{
// Vazio, não tem filhos. Avança pro próximo nó e retorna
ReadToNextElementOrEndElement(reader);
return produto;
}
ReadToNextElementOrEndElement(reader);
do
{
if (reader.NodeType == XmlNodeType.Element)
{
if (reader.Name == "id")
{
produto.ID = reader.ReadString();
if (reader.NodeType == XmlNodeType.EndElement) ReadToNextElementOrEndElement(reader);
}
else if (reader.Name == "dados")
{
produto.Dados = ReadDados(reader);
}
else if (reader.Name == "formato")
{
produto.Formato = ReadFormato(reader);
}
else
{
throw new ArgumentException("No do XML nao reconhecido");
}
}
else
{
ReadToNextElementOrEndElement(reader);
}
} while (reader.NodeType != XmlNodeType.EndElement);
Debug.Assert(reader.NodeType == XmlNodeType.EndElement);
ReadToNextElementOrEndElement(reader); // avança pro próximo produto ou fim
return produto;
}
static Dados ReadDados(XmlReader reader)
{
Debug.Assert(reader.NodeType == XmlNodeType.Element);
Debug.Assert(reader.LocalName == "dados");
Dados dados = new Dados();
if (reader.IsEmptyElement)
{
// Vazio, não tem filhos. Avança pro próximo nó e retorna
ReadToNextElementOrEndElement(reader);
return dados;
}
do
{
ReadToNextElementOrEndElement(reader);
if (reader.NodeType == XmlNodeType.Element && reader.Name == "categoria")
{
dados.Categoria = reader.ReadString();
if (reader.NodeType == XmlNodeType.EndElement) reader.Read();
}
} while (reader.NodeType != XmlNodeType.EndElement || reader.Name != "dados");
Debug.Assert(reader.NodeType == XmlNodeType.EndElement);
ReadToNextElementOrEndElement(reader); // avança pro próximo produto ou fim
return dados;
}
static Formato ReadFormato(XmlReader reader)
{
Debug.Assert(reader.NodeType == XmlNodeType.Element);
Debug.Assert(reader.LocalName == "formato");
Formato formato = new Formato();
if (reader.IsEmptyElement)
{
// Vazio, não tem filhos. Avança pro próximo nó e retorna
ReadToNextElementOrEndElement(reader);
return formato;
}
do
{
ReadToNextElementOrEndElement(reader);
if (reader.NodeType == XmlNodeType.Element && reader.Name == "categoria")
{
formato.Categoria = reader.ReadString();
if (reader.NodeType == XmlNodeType.EndElement) reader.Read();
}
} while (reader.NodeType != XmlNodeType.EndElement || reader.Name != "formato");
Debug.Assert(reader.NodeType == XmlNodeType.EndElement);
ReadToNextElementOrEndElement(reader); // avança pro próximo produto ou fim
return formato;
}
static bool ReadToNextElementOrEndElement(XmlReader reader)
{
do
{
if (!reader.Read()) return false;
} while (reader.NodeType != XmlNodeType.Element && reader.NodeType != XmlNodeType.EndElement);
return true;
}
}
class Produto
{
public string ID { get; set; }
public Dados Dados { get; set; }
public Formato Formato { get; set; }
public override string ToString()
{
return string.Format("Produto[ID={0}, Dados/Cat={1}, Formato/Cat={2}]", ID, Dados.Categoria, Formato.Categoria);
}
}
class Dados
{
public string Categoria { get; set; }
}
class Formato
{
public string Categoria { get; set; }
}
Um this may be a good tip, but what if XML is not standardized? ie some fields do not come filled or with a small change of structure? I would have an error or it would process?
– Dorathoto
The strategy is to formulate the class structure as comprehensively as possible. That is, suppose each node comes completely filled. The serializer will know what to do.
– Leonel Sanches da Silva