0

I am having a lot of trouble translating examples I am finding into use with XML.

Starting with this (a very cut down version of the whole XML file)

<?xml version="1.0" encoding="UTF-8"?>
<MetadataSets>
     <MetadataSet MetadataSetID="999_never" >
         <MetadataSetItem>
             <TagName>NeverSeen</TagName>
             <ItemSeq>10</ItemSeq>
         </MetadataSetItem>
         <MetadataSetItem>
             <TagName>AlsoNeverSeen</TagName>
             <ItemSeq>20</ItemSeq>
         </MetadataSetItem>
    </MetadataSet>
</MetadataSets>

What I am trying to do is create an ordered list of 2 fields which, eventually, I hope to return as a List of Tuples

'NeverSeen'         10
'AlsoNeverSeen'     20

The following are a list of several approaches I have tried - mostly included to show I have made an effort before calling for help.

I would love some help to put me on the right trail for this specific problem but also does anybody have a suggestion for a decent, non trivial, tutorial on LINQ for XML.

I should say I am coming from a Database background; 30 seconds in SQL - 5 days and counting for XML and LINQ.

I am also getting confused with different approaches - and probably inadvertently mixing them eg those that use 'from'

var schema3 = from el in XDoc.Elements("MetadataDomain")
              where (String)el.Attribute("DomainUUID") == pDomainUUID
              select (el);  

then iterate the el even if there is only one.

Or those that don't use 'from' but use '.'

var schema4 = XDoc.Elements("MetadataDomain")
                  .Where(sch => (String)sch.Attribute("DomainUUID") == pDomainUUID)
                  .Select(sch => (String)sch.Element("SchemaName").Value);  

then you still have to iterate the result even if there is only one.

I was warming to XPath eg

string schema5 = root.XPathSelectElement($"MetadataSets/MetadataDomain[@DomainUUID='{pDomainUUID}']").Element("SchemaName").Value;

But went off that because it's all on one line so when it crashes there is no way to debug anything. And pretty much anything you type in will compile.

And how do I select 2 'fields', for example some say to select 2 'fields' use anonymous types eg

 select new { car.Make, car.VIN}
 when I try that with XML 
     where (String)el.Attribute("MetadataSetID") == pMetadataSetID
                     select new { el.Element("TagName").Value, el.Element("ItemSeq").Value } ; 

I get a compile error

Error CS0833: an anonymous type cannot have multiple properties with the same name

Seems the compiler cannot tell these apart and I don't know how to alias them.

Thanks JC

As usual I have shot myself in the foot by only showing a snippet of information In fact the data is normalised into at least 2 'tables' that are represented in XML The SchemaName(s) are perhaps subject area views. The MetadataSetID(s) give the subset and order of these Metadata items for a specific user audience.

The resultant data that has to be returned way up tree is the intersection of these for ONE SchemaName and ONE MetadataSetID As you can see even in this early stage there are many 'columns' / Properties involved. Way more than a dictionary can cope with for example.

Following the clarification Yong Shun yesterday I got furthur but then got stuck applying the filter for MetadataSetID it being 2 levels up from the Tags I was looking for. Having just clarified my thoughts on the end-state I will have a crack at the techniques decius suggested

<!-- semi denormalised - what a database would look like
SchemaName         TagName            TagLabel            TagType     TagSize     TagRepetative   TagDelimiter
EHGS-Standard      FamilyName         Family Name         string       255         true               ;
EHGS-Standard      BusinessName       Business Name       string       255         true               ;
EHGS-Standard      What               What (Categories)   string                   true               ;
EHGS-Standard      Where              Where               string                   true               ;
EHGS-Standard      When               When                string                   true               ;
EHGS-Standard      Contributor        Contributor         string                   true               ;
EHGS-Standard      Comments           Comments            string                   false              ;
EHGS-Standard      Title              Title               string                   true               ;
EHGS-Standard      Copyright          Copyright           string                   true               ;
EHGS-Standard      Subject            Subject             string                   true               ;
EHGS-Standard      Authors            Authors             string                   true               ;
EHGS-Standard      DocumentID         Document ID         string                   false              ;
testPretendSchema  Creator            Creator             string       255         true               ;
testPretendSchema  Reader             Last Reader         string       255         true               ,
testPretendSchema  What               What (whatever)     string                   true               ;

MetadataSetID   TagName     ItemSeq     TagScope    
001_basic       DocumentID      120     Restricted
001_basic       FamilyName      10
001_basic       BusinessName    20
001_basic       What            30
001_basic       Where           40
001_basic       When            50
001_basic       Comments        60
001_basic       Title           70
001_basic       Contributor     80
001_basic       Copyright       90
001_basic       Subject         100
001_basic       Authors         110
999_neverTest   NeverSeen       10
999_neverTest   AlsoNeverSeen   20
  -->
1
  • You should provide the property name in select: select new { TagName = el.Element("TagName").Value, ItemSeq = el.Element("ItemSeq").Value }
    – Yong Shun
    Commented Jul 6 at 3:49

3 Answers 3

0
  • I would suggest to create C# classes out of your XML (there are online tools to do that).

  • Serialize the XML into these classes

  • Use Linq to get your results

    [XmlRoot("MetadataSets")]
    public class MetadataSets
    {
        [XmlElement("MetadataSet")]
        public List<MetadataSet> MetadataSetList { get; set; }
    }
    
    public class MetadataSet
    {
        [XmlAttribute("MetadataSetID")]
        public string MetadataSetID { get; set; }
    
        [XmlElement("MetadataSetItem")]
        public List<MetadataSetItem> MetadataSetItems { get; set; }
    }
    
    public class MetadataSetItem
    {
        [XmlElement("TagName")]
        public string TagName { get; set; }
    
        [XmlElement("ItemSeq")]
        public int ItemSeq { get; set; }
    } 
    
    public class Program
    {
        public static void Main()
        {
            var xml = @"<MetadataSets>
                           <MetadataSet MetadataSetID=""999_never"">
                               <MetadataSetItem>
                                   <TagName>NeverSeen</TagName>
                                   <ItemSeq>10</ItemSeq>
                               </MetadataSetItem>
                               <MetadataSetItem>
                                   <TagName>AlsoNeverSeen</TagName>
                                   <ItemSeq>20</ItemSeq>
                               </MetadataSetItem>
                           </MetadataSet>
                         </MetadataSets>";
    
            var serializer = new XmlSerializer(typeof(MetadataSets));
    
            using var reader = new StringReader(xml);
            var metadataSets = (MetadataSets)serializer.Deserialize(reader);
    
            var items = metadataSets.MetadataSetList.SelectMany(x => x.MetadataSetItems);
    
            foreach(var item in items)
            {
                Console.WriteLine($"{item.TagName} = {item.ItemSeq}");
            }
        }
    }
    

Here is a working sample.

The Result:

enter image description here

0

Use Xml Linq with a SortDictionary :

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

namespace ConsoleApp10
{

    class Program
    {
        const string FILENAME = @"c:\temp\test.xml";
        static void Main(string[] args)
        {
            XDocument doc = XDocument.Load(FILENAME);
            SortedDictionary<string, string> dict = new SortedDictionary<string,string>( doc.Descendants("MetadataSetItem")
                .GroupBy(x => (string)x.Element("TagName"), y => (string)y.Element("ItemSeq"))
                .ToDictionary(x => x.Key, y => y.FirstOrDefault()));
 

        }
    }
}
0

Please try the following solution.

c#

void Main()
{
    const string FILENAME = @"e:\Temp\jc508.xml";

    XDocument xdoc = XDocument.Load(FILENAME);

    var MetadataSet = xdoc.Descendants("MetadataSetItem")
        .Select(val => new
        {
          TagName = val.Elements("TagName")?.SingleOrDefault().Value,
          ItemSeq = val.Elements("ItemSeq")?.SingleOrDefault().Value,
        }).ToList();

    Console.WriteLine(MetadataSet);
}

Not the answer you're looking for? Browse other questions tagged or ask your own question.