C# XML Deserializing specific elements

EDIT - I found a way to do what I wanted with reflection. See the solution below
Click for a Solution

This is slightly altered code I found from this page reflection - How to iterate a C# object, looking for all instances of a specific type, in order to build a separate list of those instances? - Stack Overflow
Click for Code

using System;
using System.Reflection;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public partial class ExtReflection
{
    const int iterateLimitAmount = 5000;

    public static List<T> FindAllInstancesRecursive<T>(object obj) where T : class
    {
        return FindAllInstancesRecursive<T>(obj, x => true);
    }

    public static List<T> FindAllInstancesRecursive<T>(object obj, Func<object, bool> limitSearchDelegate) where T : class
    {
        HashSet<object> exploredObjects = new HashSet<object>();
        List<T> foundObjects = new List<T>();

        int currentIteration = 0;
        FindAllInstancesRecursive(obj, exploredObjects, foundObjects, limitSearchDelegate, ref currentIteration);
     
        return foundObjects;
    }

    static void FindAllInstancesRecursive<T>(object obj, HashSet<object> exploredObjects, List<T> foundObjects, Func<object, bool> limitSearchDelegate, ref int currentIteration) where T : class
    {
        if(!CanContinue(obj, exploredObjects, limitSearchDelegate, currentIteration)) return;

        exploredObjects.Add(obj);

        IEnumerable iEnumerable = obj as IEnumerable;
        if(iEnumerable != null)
        {
            foreach(object item in iEnumerable)
            {
                FindAllInstancesRecursive<T>(item, exploredObjects, foundObjects, limitSearchDelegate, ref currentIteration);
                currentIteration++;
            }

        }else{
            T possibleMatch = obj as T;
            if(possibleMatch != null)
            {
                foundObjects.Add(possibleMatch);
            }

            Type type = obj.GetType();
            FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.GetProperty);
            foreach(FieldInfo field in fields)
            {
                object propertyValue = field.GetValue(obj);
                FindAllInstancesRecursive<T>(propertyValue, exploredObjects, foundObjects, limitSearchDelegate, ref currentIteration);
                currentIteration++;
            }
        }
    }

    static bool CanContinue(object obj, HashSet<object> exploredObjects, Func<object, bool> limitSearchDelegate, int currentIteration)
    {
        if(obj == null || exploredObjects.Contains(obj) || !limitSearchDelegate(obj)) return false;
        if(currentIteration >= iterateLimitAmount)
        {
            Debug.Log("FindAllInstancesRecursive reached iterateLimitAmount");
            return false;
        }

        return true;
    }
}

Note - I put in the iterateLimitAmount as an extra precaution. You might not need it.

I use that code in this…
Click for Code

using System;
using System.Collections.Generic;
using ColladaDeserialization;
using UnityEngine;

namespace ColladaDeserialized
{
    public class ColladaDataDeserialized
    {
        public ColladaDeserialize data {get; private set;}

        public ColladaDataDeserialized (string filePath)
        {
            data = ColladaDeserialize.DeserializeFile(filePath);
         
            StoreSources(data);
        }

        void StoreSources(ColladaDeserialize data)
        {
            List<ColladaSource> sourceList = ExtReflection.FindAllInstancesRecursive<ColladaSource>(data, x => x.GetType().Namespace == typeof(ColladaSource).Namespace);
            List<ColladaInputUnshared> inputList = ExtReflection.FindAllInstancesRecursive<ColladaInputUnshared>(data, x => x.GetType().Namespace == typeof(ColladaInputUnshared).Namespace);

            if(sourceList.Count > 0 && inputList.Count > 0)
            {
                foreach(ColladaInputUnshared input in inputList)
                {
                    input.sourceElement = input.FindSourceElement(sourceList);
                }
            }
        }
    }
}
using System;
using System.Linq;
using System.Collections.Generic;

namespace ColladaDeserialization
{
    public static partial class ColladaHelpers
    {
        public static ColladaSource FindSourceElement(this ColladaInputUnshared input, List<ColladaSource> sources)
        {
            return sources.FirstOrDefault(x => x.ID == input.source.Substring(1));
        }
    }
}

And in the end I can use it like this…

float[] vertexArray = collada.data.library_geometries.Geometry[0].Mesh.Vertices.Input[0].sourceElement.Float_Array.values;

I am trying to make a Collada (.dae) runtime importer and I’m having some issues with finding a good way to approach a certain problem.

Here is an example of a Collada file
Click for Collada Example

<?xml version="1.0" encoding="utf-8"?>
<COLLADA xmlns="http://www.collada.org/2005/11/COLLADASchema" version="1.4.1">
  <asset>
    <contributor>
      <author>Blender User</author>
      <authoring_tool>Blender 2.72.0 commit date:2014-10-21, commit time:11:38, hash:9e963ae</authoring_tool>
    </contributor>
    <created>2015-04-15T18:40:57</created>
    <modified>2015-04-15T18:40:57</modified>
    <unit name="meter" meter="1"/>
    <up_axis>Z_UP</up_axis>
  </asset>
  <library_images/>
  <library_geometries>
    <geometry id="Cube_008-mesh" name="Cube.008">
      <mesh>
        <source id="Cube_008-mesh-positions">
          <float_array id="Cube_008-mesh-positions-array" count="12">1 1 -1 1 -1 -1 1 1 1 1 -1 1</float_array>
          <technique_common>
            <accessor source="#Cube_008-mesh-positions-array" count="4" stride="3">
              <param name="X" type="float"/>
              <param name="Y" type="float"/>
              <param name="Z" type="float"/>
            </accessor>
          </technique_common>
        </source>
        <source id="Cube_008-mesh-normals">
          <float_array id="Cube_008-mesh-normals-array" count="6">1 0 0 1 0 0</float_array>
          <technique_common>
            <accessor source="#Cube_008-mesh-normals-array" count="2" stride="3">
              <param name="X" type="float"/>
              <param name="Y" type="float"/>
              <param name="Z" type="float"/>
            </accessor>
          </technique_common>
        </source>
        <vertices id="Cube_008-mesh-vertices">
          <input semantic="POSITION" source="#Cube_008-mesh-positions"/>
        </vertices>
        <polylist count="2">
          <input semantic="VERTEX" source="#Cube_008-mesh-vertices" offset="0"/>
          <input semantic="NORMAL" source="#Cube_008-mesh-normals" offset="1"/>
          <vcount>3 3 </vcount>
          <p>2 0 3 0 1 0 0 1 2 1 1 1</p>
        </polylist>
      </mesh>
    </geometry>
  </library_geometries>
  <library_controllers/>
  <library_visual_scenes>
    <visual_scene id="Scene" name="Scene">
      <node id="Cube" name="Cube" type="NODE">
        <matrix sid="transform">0.5 0 0 0 0 0.5 0 0 0 0 0.5 0 0 0 0 1</matrix>
        <instance_geometry url="#Cube_008-mesh"/>
      </node>
    </visual_scene>
  </library_visual_scenes>
  <scene>
    <instance_visual_scene url="#Scene"/>
  </scene>
</COLLADA>

I downloaded some C# scripts I found on the web (C# Collada 1.5 Classes download | SourceForge.net) that deserializes the collada file into objects that I can access with code. So for example, in order to get to I would do something like collada.library_geometries.geometry[0].mesh.source, as you may notice, its basically set up in a tree just like the .dae document.
The issue I am having is, there is a pointer to a source in



The source=“#Cube_008-mesh-positions” points to the element with the ID “Cube_008-mesh-positions”

What is a good way for me to set things up so that any time there is a source=“#…” I can easily get the element the source is pointing to?

In order for me to do that at the moment, I would have to do something like
Click for Code Example

        Dictionary<string, Source> meshSources = new Dictionary<string, Source>();
        Dictionary<Semantic, Source> meshDatas = new Dictionary<Semantic, Source>();

        foreach(Geometry geometry in collada.library_geometries)
        {
            foreach(Source source in geometry.Mesh.Source)
            {
                meshSources.Add(source.ID, source);
            }

            foreach(Input input in geometry.Mesh.Vertices.Input)
            {
                if(input.Semantic == Semantic.POSITION)
                {
                    meshDatas.Add(input.Semantic, meshSources[input.source.Substring(1)]);
                }
            }
        }

And that is only for the geometry. What if other elements also have inputs, such as animation. Then I would need to do this for all of them. I then started to think about trying to do reflection and searching for any typeof source, but I never did reflection before so I was running into issues. So I started wondering about how I can just deserialize only all of the source elements into a source array, but I’m not sure how to do that either as this is my first time dealing with serializing and deserializing.

If anyone knows of a way, please let us know!

An example of what I would like to have is to do something like collada.library_geometries.geometry[0].mesh.vertices.input[0].source.GetSourceElement(); and it will give me its source element to which I can do stuff like
float[ ] vertices = collada.library_geometries.geometry[0].mesh.vertices.input[0].source.GetSourceElement().float_array.values;

I have some issues with an attempt I’ve made.
Here is the code
Click for code

using System;
using System.Xml;
using System.Xml.Serialization;
using System.IO;
using System.Collections.Generic;
using ColladaDeserialization;
using UnityEngine;

namespace ColladaDeserialized
{
    public class ColladaDataDeserialized
    {
        public ColladaDeserialize collada {get; private set;}
        public Dictionary<Type, object[]> datas {get; private set;}

        static Dictionary<string, Type> elements = new Dictionary<string, Type>()
        {
            {"source", typeof(Grendgine_Collada_Source)}
        };

        public ColladaDataDeserialized (string filePath)
        {
            ColladaDeserialize colladaDeserialized = ColladaDeserialize.DeserializeFile(filePath);

            datas = SetupEasyAccessors(new StreamReader(filePath));

            collada = colladaDeserialized;
        }

        Dictionary<Type, object[]> SetupEasyAccessors(TextReader textReader)
        {
            XmlDocument colladaXmlDoc = new XmlDocument();
            colladaXmlDoc.Load(textReader);

            Dictionary<Type, object[]> datas = StoreElements(colladaXmlDoc);

            textReader.Close();

            return datas;
        }

        Dictionary<Type, object[]> StoreElements(XmlDocument xmlDocument)
        {
            Dictionary<Type, object[]> datas = new Dictionary<Type, object[]>();

            foreach(KeyValuePair<string, Type> element in elements)
            {
                XmlNodeList nodes = xmlDocument.GetElementsByTagName(element.Key);
                datas.Add(element.Value, new object[nodes.Count]);

                for(int i = 0; i < nodes.Count; i++)
                {
                    XmlSerializer xmlSerializer = new XmlSerializer(element.Value);
                    datas[element.Value][i] = Convert.ChangeType(xmlSerializer.Deserialize(new XmlNodeReader(nodes[i])), element.Value);
                }
            }

            return datas;
        }
    }
}

The issue I am having is with this part

xmlSerializer.Deserialize(new XmlNodeReader(nodes[i]))

It gives me this error…

“InvalidOperationException: was not expected System.Xml.Serialization.XmlSerializationReaderIterpreter.ReadRoot (System.Xml.Serialization.XmlTypeMapping rootMap).”

After searching this up, people said to do something with XmlRootAttribute root = new XmlRootAttribute(); then changing some of the root values and plugging it into the XmlSerializer xmlSerializer = new XmlSerializer(root, element.Value);, but I still got the same error.
It seems the xmlSerializer.Deserialize is reading the whole document and not just the node I am giving it to read, so when it is told to do something with the document, it doesnt know what to do since I am only showing it what to do with the node element I am giving it.

Does anyone know how to fix this?

In the mean time I guess I’m stuck with doing something like this…
Click for code

using System;
using System.Collections.Generic;
using ColladaDeserialization;
using UnityEngine;

namespace ColladaDeserialized
{
    public class ColladaDataDeserialized
    {
        public ColladaDeserialize data {get; private set;}
        public List<ColladaSource> sources {get; private set;}

        public ColladaDataDeserialized (string filePath)
        {
            data = ColladaDeserialize.DeserializeFile(filePath);
        
            GetSources(data);
        }

        void GetSources(ColladaDeserialize colladaDeserialized)
        {
            sources = new List<ColladaSource>();

            //Geometry sources
            foreach(ColladaGeometry geometry in data.library_geometries.Geometry)
            {
                foreach(ColladaSource source in geometry.Mesh.Source)
                {
                    sources.Add(source);
                }
            }

            //Animation sources
            foreach(ColladaAnimation animation in data.library_animations.Animation)
            {
                foreach(ColladaSource source in animation.Source)
                {
                    sources.Add(source);
                }
            }
        }
    }
}
using System;
using System.Linq;
using System.Collections.Generic;

namespace ColladaDeserialization
{
    public static partial class ColladaHelpers
    {
        public static ColladaSource SourceElement(this ColladaInputUnshared input, List<ColladaSource> sources)
        {
            return sources.First(x => x.ID == input.source.Substring(1));
        }
    }
}

and then I use it like this…

ColladaDataDeserialized collada = new ColladaDataDeserialized(path);
float[] vertexArray = collada.data.library_geometries.Geometry[0].Mesh.Vertices.Input[0].SourceElement(collada.sources).Float_Array.values;