0

I got this XML file:

<Msg UserText="start 0">
</Msg>
<Msg UserText="A">
</Msg>
<Msg UserText="A">
</Msg>
<Msg UserText="start 1">
</Msg>
<Msg UserText="A">
</Msg>
<Msg UserText="start 2">
</Msg>
<Msg UserText="A">
</Msg>
<Msg UserText="A">
</Msg>
<Msg UserText="A">
</Msg>

I need to count How many 'A's are between each "start x"

i.e. for the above i would output:

start 0 : 2 
start 1 : 1 
start 2 : 3

How should i go about this in C# ? i had a few directions but I'm sure there are easier ones out there (e.g. using linq)

4
  • 2
    Have you tried something (I mean in addition to posting this question here)? Like for example using Visual Studio to write some code and put the directions you've heard into action? Did you encounter some specific problems when you did this? Commented Jul 1, 2012 at 17:42
  • @DarinDimitrov I did, i tried to traverse all nodes, but code seemed cumbersome and i was hoping to get an insight as to same level parsing. Commented Jul 1, 2012 at 17:45
  • msdn.microsoft.com/en-us/library/bb352694 iterate though Msg descendants of whatever your parent element is? Commented Jul 1, 2012 at 17:47
  • @nbrooks already did that, i was hoping for a select ... clause that could group them up for me instead of keeping state of where i am. Commented Jul 1, 2012 at 17:51

4 Answers 4

2

Here is a grouping sample:

XDocument doc = XDocument.Load("../../XMLFile1.xml");

var groups = from msg in doc.Root.Elements("Msg")
             where !((string)msg.Attribute("UserText")).StartsWith("start")
             group msg by 
             msg.ElementsBeforeSelf("Msg").Where(m => 
                 ((string)m.Attribute("UserText")).StartsWith("start")).Last();

foreach (var group in groups)
{
    Console.WriteLine("Group starting with {0} has {1} member(s).", 
        group.Key.Attribute("UserText"), group.Count());
}

With the XML input sample XMLFile1.xml being

<Root>
  <Msg UserText="start 0">
  </Msg>
  <Msg UserText="A">
  </Msg>
  <Msg UserText="A">
  </Msg>
  <Msg UserText="start 1">
  </Msg>
  <Msg UserText="A">
  </Msg>
  <Msg UserText="start 2">
  </Msg>
  <Msg UserText="A">
  </Msg>
  <Msg UserText="A">
  </Msg>
  <Msg UserText="A">
  </Msg>
</Root>

I get the output

Group starting with UserText="start 0" has 2 member(s).
Group starting with UserText="start 1" has 1 member(s).
Group starting with UserText="start 2" has 3 member(s).
Sign up to request clarification or add additional context in comments.

2 Comments

Correct, but with large XML slow because of back seek in ElementsBeforeSelf and corresponding subquery.
Thank you, the code above really helps me understand how to use Linq-to-Xml !!
1

This should do it for you.

using System.Collections.Generic;
using System.Xml.Linq;

namespace TraverseXMLNodes
{
    public class Class1
    {
        public static void Main(string[] args)
        {
            XDocument doc = XDocument.Load(@"C:\Udvikling\StackOverflow\TraverseXMLNodesSln\TraverseXMLNodes\XMLFile1.xml");
            var msgs = doc.Element("root").Elements("Msg");

            List<int> numbers = new List<int>();
            List<string> numbersStr = new List<string>();
            int count = 0;
            foreach (var xElement in msgs)
            {
                string value = xElement.Attribute("UserText").Value;
                numbersStr.Add(value);
                if (value.Contains("start"))
                {
                    numbers.Add(count);
                    count = 0;
                }
                else
                {
                    count++;
                }
            }
            numbers.Add(count);
        }
    }
}

My XML file:

<?xml version="1.0" encoding="utf-8" ?>
<root>
  <Msg UserText="start 0">
  </Msg>
  <Msg UserText="A">
  </Msg>
  <Msg UserText="A">
  </Msg>
  <Msg UserText="start 1">
  </Msg>
  <Msg UserText="A">
  </Msg>
  <Msg UserText="start 2">
  </Msg>
  <Msg UserText="A">
  </Msg>
  <Msg UserText="A">
  </Msg>
  <Msg UserText="A">
  </Msg>
</root>

4 Comments

C:\Udvikling\StackOverflow - nice folder.
Correct, but it's not necessary to gather all keys and values in two separate lists. With large XML may cause memory wasting and performance drawback when resizing the list.
I added some different lists so it was easier for him to understand what the output was and how it was constructed. I assumed he wanted to debug it through later to learn som XML AND XDocument, in a level that would suit him at the moment.
@DarinDimitrov yes, it's a must have folder :) BTW "Udvikling" means development in english
1
    static void Main(string[] args)
    {
         const string xml = @"<SomeRootTag>
            <Msg UserText='start 0'>
            </Msg>
            <Msg UserText='A'>
            </Msg>
            <Msg UserText='A'>
            </Msg>
            <Msg UserText='start 1'>
            </Msg>
            <Msg UserText='A'>
            </Msg>
            <Msg UserText='start 2'>
            </Msg>
            <Msg UserText='A'>
            </Msg>
            <Msg UserText='A'>
            </Msg>
            <Msg UserText='A'>
            </Msg>
        </SomeRootTag>";

        var xDoc = XDocument.Load(new StringReader(xml));
        var msgs = xDoc.Root.Elements().Where(el => el.Name == "Msg").Select(el => el.Attribute("UserText").Value);
        var results = GetCounts(msgs);



        foreach (var keyValue in results)
        {
            Console.WriteLine("{0}:{1}", keyValue.Item1, keyValue.Item2);
        }

        Console.ReadKey();
    }

    private static IEnumerable<Tuple<string,int>> GetCounts(IEnumerable<string> msgs)
    {
        string last = null;
        int count = 0;
        foreach (var msg in msgs)
        {
            if (msg.StartsWith("start"))
            {
                if (last != null)
                {
                    yield return new Tuple<string, int>(last, count);
                }
                count = 0;
                last = msg;
            }
            else
            {
                count++;
            }
        }
        yield return new Tuple<string, int>(last, count);
    }  

Comments

1

Take advantage of the ElementsAfterSelf() method to get the following siblings. Then it's pretty easy from there:

var root = XElement.Parse(xmlStr);
var query =
    from msg in root.Elements("Msg")
    let message = (string)msg.Attribute("UserText")
    where message.StartsWith("start")
    select new
    {
        Message = message,
        FollowingAs = msg.ElementsAfterSelf("Msg")
            .TakeWhile(e => (string)e.Attribute("UserText") == "A")
            .Count(),
    };

1 Comment

Thanks ! great answer, I learnt a lot from this. its a shame i can only pick one answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.