XML Dialog System, accessing child nodes

Hi all – I’m pretty new to XML, but I have a working Item and Weapon system and was going to create a basic dialog system in the same way, but ran into an issue going “one level deeper” in the XML.

As a quick example of my problem, this works and I can access everything in it:

<NPCs>
  <NPC name = "Bob">
   <Description> A description for the NPC. </Description>
    <Line>Hello!</Line>
</NPC>
</NPCs>

But I can’t access the event that is “one level down” here:

<NPCs>
  <NPC name = "Bob">
   <Description> A description for the NPC. </Description>
     <Event eventName = "firstMeet">
          <Line>Hello!</Line>
     </Event>
     <Event eventName = "alreadyMet">
         <Line>Hello again!</Line>
     </Event>
</NPC>
</NPCs>

I currently have it set up pretty simply – there is an Array of NPCs. Each NPC has a name attribute, and also has a description. NPCs also have different “events” that are exclusive to them and, on triggering that event, their dialog changes to the Lines listed under that eventName.

Xml document:

<?xml version="1.0" encoding="UTF-8"?>
<DialogCollection>
<NPCs>
        <NPC name = "Bob" >
                <Description>this is the npc descriptionr</Description> <!-- Descriptions may be used later for like a bestiary for npcs -->                                                       
                       
                <Event eventName = "ThisIsAnEvent">
                                    <Line>Hey. </Line>
                                    <Line>Hey 2. </Line>
                                    <Line>Hey 3 </Line>                       
                 </Event>

                <Event eventName = "test2">
                                    <Line> Hey test. </Line>
                                    <Line> Hey test 2. </Line>
                                    <Line> Hey test 3. </Line>           
                </Event>           
        </NPC>


        <NPC name = "Greg" >
                <Description>this is the npc descriptionGREG</Description> <!-- Descriptions may be used later for like a bestiary for npcs -->                                                       
                           
                <Event eventName = "Greg">
                                <Line> Hey. </Line>
                                <Line> Hey 2. </Line>
                                <Line> Greg hey. </Line>           
                </Event>   

                <Event eventName = "Greg2">
                                <Line> Hey test. </Line>
                                <Line> Hey test 2. </Line>
                                <Line> Greg hey 2. </Line>           
                </Event>   
        </NPC>
</NPCs>

</DialogCollection>

I have a DialogContainer and EventContainer that will store this XML information. The DialogContainer is working fine – I can see each NPC’s name and description and assign it to different NPCs without issue. For events, I get a nothing. :frowning:

Dialog and Event collection

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

[XmlRoot("DialogCollection")]
public class DialogContainer
{
    [XmlArray("NPCs")]
    [XmlArrayItem("NPC")]   
    public List<Dialog> dialogNPC = new List<Dialog>();

    public static DialogContainer Load(string path)
    {
        TextAsset _xml = Resources.Load<TextAsset>(path);

        XmlSerializer serializer = new XmlSerializer(typeof(DialogContainer));

        StringReader reader = new StringReader(_xml.text);

        DialogContainer dialogContainerLines = serializer.Deserialize(reader) as DialogContainer;
       
        reader.Close();

        return dialogContainerLines;
    }
}

[XmlRoot("DialogCollection")]
public class EventContainer
{
    //I presume this is the problem and have tried changing this in every way possible. :(
    [XmlArray("NPC")]
    [XmlArrayItem("Event")]
    public List<Event> dialogEvents = new List<Event>();

    public static EventContainer Load(string path)
    {
        TextAsset _xml = Resources.Load<TextAsset>(path);

        XmlSerializer serializer = new XmlSerializer(typeof(EventContainer));

        StringReader reader = new StringReader(_xml.text);

        EventContainer eventLines = serializer.Deserialize(reader) as EventContainer;

        reader.Close();

        return eventLines;
    }
}

I have a Dialog and Event class shown below. Again, I can pull the XmlAttribute “name” and get the element “Description” just fine, but cannot get the “eventName” or “Line”.

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

public class Dialog
{
    [XmlAttribute("name")]
    public string _name { get; set; }

    [XmlElement("Description")]
    public string _description { get; set; }   
}

public class Event
{

    [XmlAttribute("eventName")]
    public string _eventName { get; set; }
   
    [XmlElement("Line")]
    public string[] _line { get; set; }
   
}

To test this, I’ve added the following Debug lines into my DialogHolder script that is attached to the NPC. I get the correct NPC count (2, Bob and Greg) but get an Event count of 0.

               DialogContainer dc = DialogContainer.Load(path);
                EventContainer ec = EventContainer.Load(path);
               
                Debug.Log(dc.dialogNPC.Count + " NPC count");
               
                Debug.Log(ec.dialogEvents.Count + "  events count");

If I rearrange my XML document and put “Event” directly underneath NPCs, I can read the eventName and Lines just fine. The only issue there is that I then can’t create unique events and lines for each NPC!

My ultimate goal is to do something like this in the DialogHolder script attached to an NPC:

                DialogContainer dc = DialogContainer.Load(path);
                EventContainer ec = EventContainer.Load(path);

  foreach (Dialog dialogNPC in dc.dialogNPC)
                {
                    if (dialogNPC._name == npcName)
                    {
npcDescriptoin = dialogNPC._description;
}
     foreach (Event dialogEvents in ec.dialogEvents)
                        {                      
                            if (dialogEvents._eventName == eventName)
                            {
//change dialog lines to be used based on that Event's lines
}
}

But while I can get the NPC’s name and description, I can’t get the event or the lines associated with the event. What can I do to be able to access those child nodes (if that is the right terminology)? I’m sure I’m missing something and you can go “one layer down” into the XML, but I’m too inexperienced with XML to really know how.

You’ve got your Array and ArrayItem annotations mixed up. Array represents the “root” element of the array and ArrayItem is what each element’s tag should be. You probably don’t need the array defs at all. Should be something like this

[XmlRoot("DialogCollection")]
public class DialogList
{
    [XmlElement("NPC")]
    public NPC[] Npcs;
}

public class NPC
{
    [XmlElement("Description")]
    public string Description;

    [XmlElement("Event")]
    public Event[] Events;
}

public class Event
{
    [XmlAttribute("EventName")]
    public string Name;

    [XmlElement("Line")]
    public string[] Lines;
}

Edit: I missed a level in your object model. So you’d need Array for your NPCs which it looks like you have working in your original post.

Pardon my ignorance :slight_smile:

I’m assuming the code you posted goes in what I previously had in the Dialog script here:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Xml;
using System.Xml.Serialization;
public class Dialog
{
    [XmlAttribute("name")]
    public string _name { get; set; }
    [XmlElement("Description")]
    public string _description { get; set; } 
}
public class Event
{
    [XmlAttribute("eventName")]
    public string _eventName { get; set; }
  
    [XmlElement("Line")]
    public string[] _line { get; set; }
  
}

How would I change the DialogContainer to be able to access this information?

Removing the:

[XmlArray(“NPCs”)]
[XmlArrayItem(“NPC”)]

Results in my NPC count disappearing, and I’m not familiar with other ways of accessing the information in the Dialog.cs script, since it doesn’t derive from Monobehavior.

See my edit - your NPCs->NPC array was correct.

Yeah, I can still access the NPCs. I’m not sure what to do for the EventContainer though.

Is there another way I should be accessing the information in the Dialog.cs script, other than what I have in the EventContainer here:

[XmlRoot("DialogCollection")]
public class EventContainer
{
    //I presume this is the problem and have tried changing this in every way possible. :(   
    [XmlArrayItem("Event")]
    public List<Event> dialogEvents = new List<Event>();
   
    public static EventContainer Load(string path)
    {
        TextAsset _xml = Resources.Load<TextAsset>(path);

        XmlSerializer serializer = new XmlSerializer(typeof(EventContainer));

        StringReader reader = new StringReader(_xml.text);

        EventContainer eventLines = serializer.Deserialize(reader) as EventContainer;

        reader.Close();

        return eventLines;
    }
}

What is EventContainer? Given your XML structure you shouldn’t even need it - the NPC is the one who has an array of Events. XmlElement can be bound to collections in which case they don’t have a wrapper.

So your two options are this:

[XmlElement("Foo")]
public class Foo
{
    [XmlElement("Bar")]
    public Bar[] Bars;
}
<Foo>
    <Bar />
    <Bar />
</Foo>

or this:

[XmlElement("Foo")]
public class Foo
{
    [XmlArray("Bars")]
    [XmlArrayItem("Bar")]
    public Bar[] Bars;
}
<Foo>
    <Bars>
        <Bar />
        <Bar />
    </Bars>
</Foo>

The EventContainer is the second one here – I am not sure how I’d access this information otherwise. If I remove the DialogContainer, I can no longer access the NPC data with my current scripts.

The only issue I’m having is that I have an XmlArray of NPCs, and an ArrayItem of NPC, but one level below that I have a “something” of Events and I am unable to access them or the underneath Event.

//This is the DialogContainer. It works fine, and I can access the NPC name and description without issue.
[XmlRoot("DialogCollection")]
public class DialogContainer
{
    [XmlArray("NPCs")]
    [XmlArrayItem("NPC")] 
    public List<Dialog> dialogNPC = new List<Dialog>();
    public static DialogContainer Load(string path)
    {
        TextAsset _xml = Resources.Load<TextAsset>(path);

        XmlSerializer serializer = new XmlSerializer(typeof(DialogContainer));

        StringReader reader = new StringReader(_xml.text);

        DialogContainer dialogContainerLines = serializer.Deserialize(reader) as DialogContainer;
     
        reader.Close();

        return dialogContainerLines;
    }
}

//This is the event container. It does not work
[XmlRoot("DialogCollection")]
public class EventContainer
{
    //I presume this is the problem and have tried changing this in every way possible. :( 
    //I tried having [XmlArray("NPC"] and [XmlArrayItem("Event")] and whatever other combination I could think of,
    //but none seemed to allow me to get anything but Null responses.

    [XmlElement("Event")]
    public List<Event> dialogEvents = new List<Event>();

    public static EventContainer Load(string path)
    {
        TextAsset _xml = Resources.Load<TextAsset>(path);

        XmlSerializer serializer = new XmlSerializer(typeof(EventContainer));

        StringReader reader = new StringReader(_xml.text);

        EventContainer eventLines = serializer.Deserialize(reader) as EventContainer;

        reader.Close();

        return eventLines;
    }
}

In the DialogHolder script attached to the NPC, I can then do this:

    DialogContainer dc = DialogContainer.Load(path);
                EventContainer ec = EventContainer.Load(path);          
                             
                foreach (Event dialogEvents in ec.dialogEvents)
                {                  
                    Debug.Log(dialogEvents._eventName + " is an eventName");
                    Debug.Log(dialogEvents._line[0] + " is the first line");
                }

      foreach (Dialog dialogNPC in dc.dialogNPC)
                {
                    Debug.Log(dialogNPC._name + " NPC name");
                }

In the example above, the count for dialgNPC._name debug comes up fine. The dialogEvents._eventName and _line[0] don’t come up at all because the dialogEvents, called from the Event Container, comes up with a Count of 0.

It’s the same process for both, except one is an ArrayItem one level down and the other is one level below that.

The example you give with and I can already do – what I have trouble with is:

<Foo>
   <Bars>
      <NextLevelDown>
      </NextLevelDown>
    </Bars>
</Foo>

In the above example, I can’t access .

Why is your EventContainer decorated with an XmlRoot attribute? It’s not a root - it’s a child of DialogContainer. Nothing in your XML mapping links EventContainer with DialogContainer.

Your NPC array is a List of Dialog types - so Dialog needs to have a child list of Events

I feel like I must not be explaining something correctly or I’m missing some piece of information.

I’ll try to simplify. There are 4 files I’m utilizing:

The dialog.xml document (truncated to show only the first NPC):

<DialogCollection>
<NPCs>
        <NPC name = "Bob" >
                <Description>this is the npc descriptionr</Description> <!-- Descriptions may be used later for like a bestiary for npcs -->                                                     
                     
                <Event eventName = "ThisIsAnEvent">
                                    <Line>Hey. </Line>
                                    <Line>Hey 2. </Line>
                                    <Line>Hey 3 </Line>                     
                 </Event>

                <Event eventName = "test2">
                                    <Line> Hey test. </Line>
                                    <Line> Hey test 2. </Line>
                                    <Line> Hey test 3. </Line>         
                </Event> 
             
        </NPC>
</NPCs>
</DialogCollection>

From that XML document, I want to be able to check what the name and description are and by doing a comparison check of a “current event” status, I can then populate my dialog by adding each of the under that specific event.

To do this, I create a Dialog Container:

[XmlRoot("DialogCollection")]
public class DialogContainer
{
    [XmlArray("NPCs")]
    [XmlArrayItem("NPC")] 
    public List<Dialog> dialogNPC = new List<Dialog>();
    public static DialogContainer Load(string path)
    {
        TextAsset _xml = Resources.Load<TextAsset>(path);

        XmlSerializer serializer = new XmlSerializer(typeof(DialogContainer));

        StringReader reader = new StringReader(_xml.text);

        DialogContainer dialogContainerLines = serializer.Deserialize(reader) as DialogContainer;
     
        reader.Close();

        return dialogContainerLines;
    }
}

Without this Dialog Container, I don’t know how to access the XML at all. If there’s another way I should be doing this, I’m unaware of it.

I also have a Dialog script:

public class Dialog
{
    [XmlAttribute("name")]
    public string _name { get; set; }

    [XmlElement("Description")]
    public string _description { get; set; } 
}

public class Event
{

    [XmlAttribute("eventName")]
    public string _eventName { get; set; }
 
    [XmlElement("Line")]
    public string[] _line { get; set; }
 
}

The above code was my original code – I am able to successfully get the “name” and “Description” without issue by doing this in my DialogHolder script:

          DialogContainer dc = DialogContainer.Load(path);
                 Debug.Log(dc.dialogNPC.Count + " count");
       foreach (Dialog dialogNPC in dc.dialogNPC)
                {
                    Debug.Log(dialogNPC._name);
                }

So the XML holds the data, the DialogContainer loads it, and then I can do a foreach loop to quickly test if it’s working – it checks each dialogNPC in the DialogContainer, which was loaded from the XML.

This I know works – I can get a count of 2 and pull the names from the XML.

My assumption was I could do the same to pull the eventName and then load the dialog lines from there.

I then changed the Dialog script by adding two lines to get the XmlAttribute eventName:

public class Dialog
{
    [XmlAttribute("name")]
    public string _name { get; set; }

    [XmlElement("Description")]
    public string _description { get; set; }

    [XmlAttribute("eventName")]
    public string _eventName { get; set; }
}

And my DialogHolder script now checks for the eventName:

                DialogContainer dc = DialogContainer.Load(path);
                Debug.Log(dc.dialogNPC.Count + " count");
                foreach (Dialog dialogNPC in dc.dialogNPC)
                {
                    Debug.Log(dialogNPC._name);
                    Debug.Log(dialogNPC._eventName);
                }

But while I would expect to see four events from my Xml document (ThisIsAnEvent, test2, Greg, Greg2), I instead get “Null”, shown above – and when I check the count, the count is 0. The DialogContainer isn’t pulling it in, and I’m not sure how to get it to do so.

I tried to create an EventContainer in the same fashion as the DialogContainer, but that didn’t seem to work.

I see that you posted this:

[XmlRoot("DialogCollection")]
public class DialogList
{
    [XmlElement("NPC")]
    public NPC[] Npcs;
}
public class NPC
{
    [XmlElement("Description")]
    public string Description;
    [XmlElement("Event")]
    public Event[] Events;
}
public class Event
{
    [XmlAttribute("EventName")]
    public string Name;
    [XmlElement("Line")]
    public string[] Lines;
}

But I’m unsure how that information is populated nor how to access any of it. I assume it would replace what I currently have in my Dialog class, but I’m not sure what to do with it or how to alter the DialogContainer.

I have found other people have similar issues with similar frameworks:

But I haven’t found a solution in any of the threads posted. :frowning:

Your Dialog class has no information about the Event classes you’re trying to reference. So you need to add that first. Given the structure that you have it should be something like this

[XmlElement("Event")]
Event[] Events;

This means the serializer will look for repeating Event elements underneath your NPC element. Your Event class should have an XmlAttribute for the event name and then an XmlElement(“Line”) backed by a collection of strings. Something akin to

public class Event
{
    [XmlAttribute("EventName")]
    string EventName;

    [XmlElement("Line")]
    string[] Lines;
}