views:

1431

answers:

2

I have a situation where I have an xml file that I don't want to modify. The AddAnnotation function in XElement class provides an option to add memory-only data which is not serialized and not part of the XML.

I want to be able to save these annotations (for example: to another xml file) and then to deserialize both the xml and the annotations in order to get the same object I had.

I don't want to change the original xml and that's the reason that I use annotations.

To summarize, I want to be able to add custom data to an xml file. This data won't be a part of the xml when I serialize it or it will be a part of the xml but I would be able to retrieve the original xml easily.

Do you have any recommendation how I can do such a thing?

Edit: Should I use xml processing instructions? Are processing instructions intended for this kind of usage?

+2  A: 

It sounds to me like the simplest approach would be to use regular nodes, but in a different xml namespace - i.e.

<foo standardAttrubute="abc" myData:customAttribute="def">
    <standardElement>ghi</standardElement >
    <myData:customElement>jkl</myData:customElement>
</foo>

(where myData is an xmlns alias for the namespace-uri)

In many cases, readers are only checking for data in their namespace (or the default/blank namespace) - values in custom namespaces are generally skipped.

To get pack the original xml, one simple approach would be to run it through an xslt that only respects the default/original namespace.


XNamespace myData = XNamespace.Get("http://mycustomdata/");
XElement el = new XElement("foo",
    new XAttribute(XNamespace.Xmlns + "myData", myData.NamespaceName),
    new XAttribute("standardAttribute", "abc"),
    new XAttribute(myData + "customAttribute", "def"),
    new XElement("standardElement", "ghi"),
    new XElement(myData + "customAttribute", "jkl"));
string s = el.ToString();

To remove such data from an XElement, perhaps:

    static void Strip(XElement el, XNamespace ns) {
        List<XElement> remove = new List<XElement>();
        foreach (XElement child in el.Elements()) {
            if (child.Name.Namespace == ns) {
                remove.Add(child);
            } else {
                Strip(child, ns);
            }
        }
        remove.ForEach(child => child.Remove());

        foreach (XAttribute child in
            (from a in el.Attributes()
             where a.Name.Namespace == ns
             select a).ToList()) {
            child.Remove();
        }
    }
Marc Gravell
Sounds interesting... Can you provide an example (two examples actually) how I can load the xml using XElement, with and without the custom attribute?
Yuval Peled
Creation added - is that what you meant? Or something else?
Marc Gravell
No. I know how to create the xml. I don't know how do I load it without the custom data. tnx!
Yuval Peled
Added "Strip"
Marc Gravell
Tnx. Possible small improvement - strip the custom namespace declaration as well. In general, I was thinking maybe it will be better to do it with a custom XmlReader.
Yuval Peled
If you are going to drop to the XmlReader level, I'd look at xslt first... a lot easier to get right. XmlReader should not be taken lightly.
Marc Gravell
A: 

The original question used the word "Serialize" but then also mentioned XElement and annotation. To me these are two different things.

If you really want to use the XmlSerializer:
What I would do is use XmlAttributeOverrides, to differentiate the serialization. With the XmlAttributeOverrides you can programmatically, at runtime, override the xml serialization attributes that decorate your types.

Within your type, you can have a field/property that is intended to hold the annotation/documentation. Decorate that with XmlIgnore. Then, create one instance of the XmlSerializer that accepts no overrides. The annotation will not be serialized or de-serialized. Create another instance of the XmlSerializer for that type, using an XmlAttributeOverrides object. Specify an override for the XmlIgnore'd property (use XmlElementAttribute), as well as overrides for any attributes on any of the other members (use XmlIgnore=true).

Serialize the instance twice, one with each serializer.


Edit: here's the code:

public class DTO
{
    [XmlIgnore]
    public string additionalInformation;

    [XmlElement(Order=1)]
    public DateTime stamp;

    [XmlElement(Order=2)]
    public string name;

    [XmlElement(Order=3)]
    public double value;

    [XmlElement(Order=4)]
    public int index;
}



public class OverridesDemo
{ 
    public void Run()
    {
        DTO dto = new DTO
            {
                additionalInformation = "This will bbe serialized separately",
                stamp = DateTime.UtcNow,
                name = "Marley",
                value = 72.34,
                index = 7
            };


        // ---------------------------------------------------------------
        // 1. serialize normally
        // this will allow us to omit the xmlns:xsi namespace
        var ns = new XmlSerializerNamespaces();
        ns.Add( "", "" );

        XmlSerializer s1 = new XmlSerializer(typeof(DTO));

        var builder = new System.Text.StringBuilder();
        var settings = new XmlWriterSettings { OmitXmlDeclaration = true, Indent= true };

        Console.WriteLine("\nSerialize using the in-line attributes: ");
        using ( XmlWriter writer = XmlWriter.Create(builder, settings))
        {
            s1.Serialize(writer, dto, ns);
        }
        Console.WriteLine("{0}",builder.ToString());
        Console.WriteLine("\n");            
        // ---------------------------------------------------------------

        // ---------------------------------------------------------------
        // 2. serialize with attribute overrides
        // use a non-empty default namespace
        ns = new XmlSerializerNamespaces();
        string myns = "urn:www.example.org";
        ns.Add( "", myns);

        XmlAttributeOverrides overrides = new XmlAttributeOverrides();

        XmlAttributes attrs = new XmlAttributes();
        // override the (implicit) XmlRoot attribute
        XmlRootAttribute attr1 = new XmlRootAttribute
            {
                Namespace = myns,
                ElementName = "DTO-Annotations",
            };
        attrs.XmlRoot = attr1;

        overrides.Add(typeof(DTO), attrs);
        // "un-ignore" the first property
        // define an XmlElement attribute, for a type of "String", with no namespace
        var a2 = new XmlElementAttribute(typeof(String)) { ElementName="note", Namespace = myns };

        // add that XmlElement attribute to the 2nd bunch of attributes
        attrs = new XmlAttributes();
        attrs.XmlElements.Add(a2);
        attrs.XmlIgnore = false; 

        // add that bunch of attributes to the container for the type, and
        // specifically apply that bunch to the "additionalInformation" property 
        // on the type.
        overrides.Add(typeof(DTO), "additionalInformation", attrs);

        // now, XmlIgnore all the other properties
        attrs = new XmlAttributes();
        attrs.XmlIgnore = true;       
        overrides.Add(typeof(DTO), "stamp", attrs);
        overrides.Add(typeof(DTO), "name",  attrs);
        overrides.Add(typeof(DTO), "value", attrs);
        overrides.Add(typeof(DTO), "index", attrs);

        // create a serializer using those xml attribute overrides
        XmlSerializer s2 = new XmlSerializer(typeof(DTO), overrides);

        Console.WriteLine("\nSerialize using the override attributes: ");
        builder.Length = 0;
        using ( XmlWriter writer = XmlWriter.Create(builder, settings))
        {
            s2.Serialize(writer, dto, ns);
        }
        Console.WriteLine("{0}",builder.ToString());
        Console.WriteLine("\n");            
        // ---------------------------------------------------------------
    }
}

output, using the in-line attributes:

<DTO>
  <stamp>2009-06-30T02:17:35.918Z</stamp>
  <name>Marley</name>
  <value>72.34</value>
  <index>7</index>
</DTO>

output, using the override attributes:

<DTO-Annotations xmlns="urn:www.example.org">
  <note>This will bbe serialized separately</note>
</DTO-Annotations>
Cheeso
I'm not sure I followed. Can you provide a snippet? tnx...
Yuval Peled