Linq Xml with node attributes and IList<> descendants

Festus Hagen 41 Reputation points
2022-04-25T16:08:41.077+00:00

Hi all,

Frustrated, having a tough time with linq ...

Read only xml configuration file with IList<> of objects.

The goal is to pass in an id (by constructor or routine), it reads that id's configuration from xml into properties of this class that can be publicly read.

Design is open, this is the rut my mind is currently in ...

<Root>
    <File FileID="0x01" Size="0x00034100" Description="Test 1 Description">
        <Sections>
            <Section Index="1" Start="0x00000000" Size="0x00000100">Description 1</Section>
            <Section Index="2" Start="0x00000100" Size="0x00001000">Description 2</Section>
            <Section Index="3" Start="0x00001100" Size="0x00010000">Description 3</Section>
        </Sections>
    </File>
    <File FileID="0x02" Size="0x00031100" Description="Test 2 Description">
        <Sections>
            <Section Index="1" Start="0x00000000" Size="0x00000100">1 Description</Section>
            <Section Index="2" Start="0x00000100" Size="0x00001000">2 Description</Section>
            <Section Index="3" Start="0x00001100" Size="0x00010000">3 Description</Section>
        </Sections>
    </File>
</Root>

    public class Section
    {
        // Note: The collection must remain in order, if the list keeps order, we don't need Index
        public UInt32 Index { get; set; }       // Node Section Attribute("Index")
        public UInt32 Start { get; set; }       // Node Section Attribute("Start")
        public UInt32 Size { get; set; }        // Node Section Attribute("Size")
        public string Description { get; set; } // Node Section Value
    }

    public class MyFiletype
    {
        public UInt32 FileID { get; set; }           // Node File Attribute("FileID")
        public UInt32 Size { get; set; }             // Node File Attribute("Size")
        public string Description { get; set; }      // Node File Attribute("Description")
        public IList<Section> Sections { get; set; } // Collection of Section objects

        public MyFiletype(UInt32 id)
        {
            string filename = @"..\..\Xml\Sections.xml";

            var doc = XDocument.Load(filename);  // XDocument
            var result = doc.Descendants("File") // IEnumerable<XElement>
                            // Find the chosen node, we only want one and it's descendants!
                            .Where(i => Convert.ToUInt32(i.Attribute("FileID").Value, 16) == id)
                            // This is where I get stumped ... Have read and tried so many ways I think I'm even more lost!
                            // I can get one or the other, not both Node File's Attributes and the Sections Collection.
                            .FirstOrDefault().Value; // test bit garbage
        }
    }

/*   
        // It's use ...
        UInt32 id = 0x02;
        MyFiletype myclass = new MyFiletype(id);
        //Debug.WriteLine($"File Description: {myclass.Description}");
*/    

Thank you
fh

C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
10,294 questions
{count} votes

2 answers

Sort by: Most helpful
  1. Michael Taylor 48,736 Reputation points
    2022-04-25T19:30:29.77+00:00

    It sounds like you're searching for files, not sections. So your LINQ query should return the file element that matches the ID you want. From there you can access its attributes and its child sections.

    I modified your code to use a helper type to handle the XML document as I find it cleaner. Personally I would also move the XML parsing to this helper class and leave the original data classes clean but that is a preference. The hardest issue here is that your FileID is a hex number so you have to convert it properly.

    public class MyFiletype
    {
        public UInt32 FileID { get; set; }           // Node File Attribute("FileID")
        public UInt32 Size { get; set; }             // Node File Attribute("Size")
        public string Description { get; set; }      // Node File Attribute("Description")
        public IList<Section> Sections  { get; set; } // Collection of Section objects
    
        public MyFiletype ( XElement element )
        {
            //Parse the data now or look it up as needed...
            _element = element;
        }
    
        private readonly XElement _element;
    }
    
    public class FileXmlDocument
    {
        public FileXmlDocument ( string filename )
        {
            _doc = XDocument.Load(filename);
        }
    
        public MyFiletype GetFile ( UInt32 id )
        {
            var element = _doc.Descendants("File").FirstOrDefault(x => ParseHexValue(x.Attribute("FileID")?.Value) == id);
    
            return (element != null) ? new MyFiletype(element) : null;
        }
    
        private uint ParseHexValue ( string value )
        {
            if (String.IsNullOrEmpty(value))
                return 0;
    
            if (value.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
                return UInt32.TryParse(value.Substring(2), NumberStyles.HexNumber, null, out var result2) ? result2 : 0;
    
            return UInt32.TryParse(value, out var result) ? result : 0;
        }
    
        private readonly XDocument _doc;
    }
    
    0 comments No comments

  2. Festus Hagen 41 Reputation points
    2022-05-03T14:04:06.937+00:00

    This POS site and it's 1600 byte limit is just wacked!
    So instead of multiple posts, I attached my own answer ...

    198417-myfiletype.txt

    If the technique is considered wrong, poor, bad design or whatever I would love to know!