0

I am upgrading a super old webpage to use Angular and I have a webpage that is loading data from an XML document. The XML document is currently laid out like so:

<xml>
<PREMout>
    <PolicyNumber>12345</PolicyNumber>
    <ModeCode>ANN</ModeCode>
    <PremFromDate>11/10/2015</PremFromDate>
    <PremToDate>12/12/2020</PremToDate>
    <AnnualPremium>400.00</AnnualPremium>
    <ModePremium>400.00</ModePremium>
    <PremFromDate>12/31/2020</PremFromDate>
    <PremToDate>12/12/2027</PremToDate>
    <AnnualPremium>5000.00</AnnualPremium>
    <ModePremium>5000.00</ModePremium>
    etc

Those four columns (PremFromDate, PremToDate, AnnualPremium, and ModePremium) are repeated many times. Since they are not grouped within a node...how would I group these fields together?

Currently, the old page is grabbing all of the PremFromDate fields and pushing them into a DataGrid bound column. It does this for the other fields, too. That seems like a very risky way of displaying data because what happens if something gets out of order?

Does anyone have an idea on how to group those four columns with them not being in a node together? Ideally speaking...the xml would be rewritten to not completely suck but that is not an option for this case :(

Holt
  • 430
  • 8
  • 25
  • You've tagged your question "c#" and "angularjs". In what language and framework do you want a solution? – dbc Nov 11 '15 at 09:48
  • I am upgrading the page to use Angular. – Holt Nov 11 '15 at 14:08
  • The reason that I tagged c# is that I am getting this XML data via a service. Which is done in C#. So I guess the language I want the solution in would be C#. Sorry for the confusion. – Holt Nov 11 '15 at 14:12

1 Answers1

1

You can use Linq to XML to load your XML into an XDocument, then reorder or restructure the appropriate child nodes.

For instance, the following will group together all child elements of PREMout by name in the order in which the first element of each group appears:

public static class XmlPremOutHelper
{
    public static void ReorderPremiumElements(TextReader from, TextWriter to)
    {
        var doc = XDocument.Load(from);
        foreach (var node in doc.Descendants("PREMout"))
        {
            var g = node.Elements().GroupBy(e => e.Name);
            foreach (var nodes in g)
            {
                nodes.Remove();
                node.Add(nodes);
            }
        }
        doc.Save(to);
    }
}

(Here I am streaming from a TextReader to a TextWriter.)

Alternatively, you could restructure each group of four elements into a single intermediate node, named e.g. PREMoutData:

public static class XmlPremOutHelper
{
    public static void GroupPremiumElements(TextReader from, TextWriter to)
    {
        var doc = XDocument.Load(from);
        foreach (var node in doc.Descendants("PREMout"))
        {
            var lists = new[] { 
                node.Elements("PremFromDate").ToList(),
                node.Elements("PremToDate").ToList(),
                node.Elements("AnnualPremium").ToList(),
                node.Elements("ModePremium").ToList(),
            };
            if (!lists.Skip(1).All(l => l.Count == lists[0].Count))
                throw new InvalidOperationException("Unequal list counts");
            foreach (var nodes in lists.ZipMany(l => l))
            {
                nodes.Remove();
                node.Add(new XElement("PREMoutData", nodes));
            }
        }
        doc.Save(to);
    }
}

public static class EnumerableExtensions
{
    // Taken from
    // https://stackoverflow.com/questions/17976823/how-to-zip-or-rotate-a-variable-number-of-lists
    public static IEnumerable<TResult> ZipMany<TSource, TResult>(
        this IEnumerable<IEnumerable<TSource>> source,
        Func<IEnumerable<TSource>, TResult> selector)
    {
        // ToList is necessary to avoid deferred execution
        var enumerators = source.Select(seq => seq.GetEnumerator()).ToList();
        try
        {
            while (true)
            {
                foreach (var e in enumerators)
                {
                    bool b = e.MoveNext();
                    if (!b)
                        yield break;
                }
                // Again, ToList (or ToArray) is necessary to avoid deferred execution
                yield return selector(enumerators.Select(e => e.Current).ToList());
            }
        }
        finally
        {
            foreach (var e in enumerators)
                e.Dispose();
        }
    }
}

Here I am using an extension method ZipMany() from this answer by Eric Lippert.

Prototype fiddle.

dbc
  • 104,963
  • 20
  • 228
  • 340
  • Awesome. That GroupPremiumElements function was exactly what I needed. Thanks man! Follow up question: What does this do? `if (!lists.Skip(1).All(l => l.Count == lists[0].Count))` How does this result in unequal list counts? – Holt Nov 12 '15 at 14:39
  • @Holt - I wanted to make sure that there are the same number of nodes of each of the four names, so I adapted a check from http://stackoverflow.com/questions/6681351 and threw an exception if some are missing. If the lists are of different length then [`Zip`](https://msdn.microsoft.com/en-us/library/dd267698.aspx) and `ZipMany` will skip the extras resulting in lost data. – dbc Nov 12 '15 at 17:46