0

I'd like to generate some XML like the following with C# code.

<card>
 <name>Cool Card</name>
 <set rarity="common">S1</set>
</card>

I have something like this.

public class card
{
    public string name = "Cool Card";
    [XmlAttribute]
    public string rarity = "common";
    public string set = "S1";
}

public static void WriteXml()
{
    var serializer = new XmlSerializer(typeof(card));
    var myCard = new();

    using var sw = new StringWriter();
    serializer.Serialize(sw, myCard);
    XDocument doc = XDocument.Parse(sw.ToString());
    XmlWriterSettings xws = new();
    xws.OmitXmlDeclaration = true;
    xws.Indent = true;
    using var xw = XmlWriter.Create(path, xws);
    doc.Save(xw);
}

The problem is that this doesn't add the "rarity" attribute to the "set" value. Trying to add [XmlAttribute] adds it to the parent element rather than the next sibling element and I can't figure out how to get it on a plain string element, so at present my output looks like.

<card rarity="common">
 <name>Cool Card</name>
 <set>S1</set>
</card>

The XML example doc shows an example of how to set the attribute on an element, but only one with nested fields and not one that's a plain string. Is it possible to add an attribute to a plain old string element in XML like my first posted example demonstrates?

2
  • Would it be an option to create another class for the set? Commented May 2, 2022 at 9:02
  • Easy : XElement card = new XElement("card", new object[] { new XElement("name", "Cool Card"), new XElement("set", new XAttribute("rarity","common"),"S1") }); Commented May 2, 2022 at 12:24

2 Answers 2

3

Try this:

public class card
{
    public string name = "Cool Card";
    public Set set = new();
}

public class Set
{
    [XmlText]
    public string value = "S1";
    [XmlAttribute]
    public string rarity = "common";
}

If you think about it there is no other way the xml attribute can be applied only to the element it is declared in. So you need to move it into another class. When you do this new property for value is required and that one needs to be flattened as value node is not required for that you need XmlText attribute.

enter image description here

Sign up to request clarification or add additional context in comments.

Comments

2

The cleanest option in this case is implement IXmlSerializable:

public class card : IXmlSerializable
{
    public string name = "Cool Card";
    public string rarity = "common";
    public string set = "S1";

    public System.Xml.Schema.XmlSchema GetSchema()
    {
        return null;
    }

    public void ReadXml(System.Xml.XmlReader reader)
    {
        reader.ReadStartElement(nameof(card));

        while (reader.NodeType != System.Xml.XmlNodeType.EndElement)
        {
            if (reader.Name == nameof(name))
            {
                this.name = reader.ReadElementContentAsString();
            }
            else if (reader.Name == nameof(set))
            {
                this.rarity = reader.GetAttribute(nameof(rarity));
                this.set = reader.ReadElementContentAsString();
            }
        }

        reader.ReadEndElement();
    }

    public void WriteXml(System.Xml.XmlWriter writer)
    {
        writer.WriteElementString(nameof(name), this.name);

        writer.WriteStartElement(nameof(set));
        writer.WriteAttributeString(nameof(rarity), this.rarity);
        writer.WriteString(this.set);
        writer.WriteEndElement();                
    }
}

If your class is big and you only need do a bit change in the XML, sometimes implement IXmlSerializable is a mess (you must save all the properties and only in one or two, do a bit change). In these cases, you can use attributes and some small helpers classes to get same results. The idea is tell to XML serializer that don't serialize some property and use another fake property to do the serialization.

For example, create a Set class with your desired XML structure:

public class XmlSet
{
    private readonly card _card;

    public XmlSet()
    {
        this._card = new card();
    }

    public XmlSet(card card)
    {
        this._card = card;
    }

    [XmlText]
    public string set
    {
        get { return this._card.set; }
        set { this._card.set = value; }
    }

    [XmlAttribute]
    public string rarity
    {
        get { return this._card.rarity; }
        set { this._card.rarity = value; }
    }
}

It's only a wrapper, with the XML sttributes that you want. You get/set the values from/to your card object.

Then, in your card class, Ignore the set property and serialize the fake property:

public class card
{
    public string name = "Cool Card";
    [XmlIgnore]
    public string rarity = "common";
    [XmlIgnore]
    public string set = "S1";

    // Added to serialization
    private XmlSet _xmlSetNode;

    [XmlElement("set")]
    public XmlSet XmlSet
    {
        get
        {
            this._xmlSetNode = this._xmlSetNode ?? new XmlSet(this);
            return this._xmlSetNode;
        }
        set { this._xmlSetNode = value; }
    }
}

1 Comment

Could only pick one answer but these are awesome tips. Thanks so much!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.