Serialize part of xml file. Want namespace on root, not on serialized subelements

Deadly 提交于 2019-12-24 12:51:33

问题


I'm trying to make an Xml file that looks something like:

<RootLevel xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.MyCompany.com/MySchema.xsd">
    <Level1>
        <Level2>
        </Level2>
    </Level1 >
    <Level1>
        <Level2>
        </Level2>
    </Level1 >
    etc. repeats hundreds of times
</RootLevel>

I generated some classes from my xml schema definition file using the xsd.exe utility. They look like:

[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://MyCompany.com/MySchema.xsd")]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "http://www.MyCompany.com/MySchema.xsd", IsNullable = false)]
public partial class RootLevel
{
    private List<Level1> level1Field;

    public List<Level1> Level1Field 
    {
        get { return this.level1Field;}
        set {this.level1Field = value;}
    }
}

[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://www.MyCompany.com/MySchema.xsd")]
public partial class Level1
{
    private List<Level2> level2Field;

    public List<Level2> Level2Field 
    {
        get { return this.level2Field;}
        set {this.level2Field = value;}
    }

    /* other properties on Level1 go here*/
}

[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://www.MyCompany.com/MySchema.xsd")]
public partial class Level2
{
    /* properties on Level2 go here*/
}    

I'm making this file by writing out the RootLevel element using XmlWriter.WriteStartElement() and then I write out the rest of the file by creating Level1 objects and serializing them with XmlSerializer.

Goal

I want the file to only have the namespace on the RootLevel element.

In case you are interested, here is what I tried so far:

Starting Point

At the start, my RootLevel element did not have any namespaces. My Level1 and Level2 elements had namespaces.

Step 1:

I tried removing the namespaces from Level1 and Level2 elements by overriding the namespaces on the XmlTypeAttributes for classes Level1 and Level2.

XmlTypeAttribute attribute = new XmlTypeAttribute();
attribute.Namespace = string.Empty;

XmlAttributes attributes = new XmlAttributes();
attributes.XmlType = attribute;

XmlAttributeOverrides overrides = new XmlAttributeOverrides();
overrides.Add(typeof(Level1), attributes);
overrides.Add(typeof(Level2), attributes);

this.xmlSerializer = new XmlSerializer(typeof(Level1), overrides);

Step 1 Result

The namespace was removed from Level2 but not from Level1.

Step 2

Added some more code to try to remove the namespace from Level1. I tried using the namespaces parameter of the XmlSerializer.Serialize() method to use empty namespaces. Note "level1" is an object of type "Level1".

XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
namespaces.Add("", "");

this.xmlSerializer.Serialize(this.xmlFileWriter, level1, namespaces);

Step 2 Result

The namespace is removed from both Level1 and Level2. So far, so good! Now all I need to do is add the namespace stuff to RootLevel.

Step 3

Since RootLevel is not serialized, I added some XmlWriter code to try adding the namespaces to RootLevel.

string defaultNamespace = "http://www.MyCompany.com/MySchema.xsd";
this.xmlFileWriter.WriteStartElement("RootLevel", defaultNamespace);
this.xmlFileWriter.WriteAttributeString("xmlns", "xsi", "", "http://www.w3.org/2001/XMLSchema-instance");
this.xmlFileWriter.WriteAttributeString("xmlns", "xsd", "", "http://www.w3.org/2001/XMLSchema");

Step 3 Result

The namespaces were added to RootLevel. Yay!. But, now every Level1 element has an xmlns="" attribute. Grrr!

<RootLevel xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.MyCompany.com/MySchema.xsd">
    <Level1 xmlns="">
        <Level2>
        </Level2>
    </Level1 >
    <Level1 xmlns="">
        <Level2>
        </Level2>
    </Level1 >
    etc. repeats hundreds of times
</RootLevel>

So why did that happen?


回答1:


You didn't post your original code so I don't know exactly where you went wrong, but I was able to get the XML you want as follows:

  1. Extract a static helper class XmlNamespaces to hold namespace strings and declare them as const strings. In your question you sometimes use "http://MyCompany.com/MySchema.xsd" but sometimes "http://www.MyCompany.com/MySchema.xsd". This might just be a typo in the question, but if your code uses these inconsistently this will cause bugs.

  2. Apply XmlRootAttribute to the class Level1 in addition to XmlTypeAttribute

    [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = XmlNamespaces.Default)]
    [System.Xml.Serialization.XmlRootAttribute(Namespace = XmlNamespaces.Default, IsNullable = false)]
    public partial class Level1
    {
        // Properties 
    }
    
  3. When writing the root element, pass the default namespace string to XmlWriter.WriteStartElement(string, string).

Overall code is as follows:

public static class XmlNamespaces
{
    public const string Default = "http://MyCompany.com/MySchema.xsd";
    public const string xsi = "http://www.w3.org/2001/XMLSchema-instance";
    public const string xsd = "http://www.w3.org/2001/XMLSchema";

    public static XmlSerializerNamespaces XmlSerializerNamespaces
    {
        get
        {
            var namespaces = new XmlSerializerNamespaces();
            namespaces.Add("", XmlNamespaces.Default);
            namespaces.Add("xsi", XmlNamespaces.xsi);
            namespaces.Add("xsd", XmlNamespaces.xsd);
            return namespaces;
        }
    }
}

[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = XmlNamespaces.Default)]
[System.Xml.Serialization.XmlRootAttribute(Namespace = XmlNamespaces.Default, IsNullable = false)] // Added this and used const string from XmlNamespaces class
public partial class Level1
{
    private List<Level2> level2Field;

    public List<Level2> Level2Field
    {
        get { return this.level2Field; }
        set { this.level2Field = value; }
    }

    /* other properties on Level1 go here*/
}

[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = XmlNamespaces.Default)] // Used const string  from XmlNamespaces class
public partial class Level2
{
    /* properties on Level2 go here*/
}

public static class XmlSerializationUtilities
{
    public static void WriteList<TItem>(string rootName, XmlSerializerNamespaces namespaces, IEnumerable<TItem> list, TextWriter textWriter)
    {
        var namespaceList = namespaces.ToArray();
        string defaultNamespace = null;
        foreach (var ns in namespaceList)
        {
            if (string.IsNullOrEmpty(ns.Name))
            {
                defaultNamespace = ns.Namespace;
                break;
            }
        }

        var settings = new XmlWriterSettings();
        settings.Indent = true;
        settings.IndentChars = "    ";

        using (var writer = XmlWriter.Create(textWriter, settings))
        {
            writer.WriteStartDocument();
            writer.WriteStartElement(rootName, defaultNamespace);

            foreach (var ns in namespaceList)
                if (!string.IsNullOrEmpty(ns.Name))
                    writer.WriteAttributeString("xmlns", ns.Name, null, ns.Namespace);

            var serializer = new XmlSerializer(typeof(TItem));
            foreach (var item in list)
            {
                serializer.Serialize(writer, item);
            }

            writer.WriteEndElement();
            writer.WriteEndDocument();
        }
    }
}

public static class TestClass
{
    public static void Test()
    {
        var list = new List<Level1> { new Level1 { Level2Field = new List<Level2> { new Level2(), new Level2() } }, new Level1 { Level2Field = new List<Level2> { new Level2(), new Level2(), new Level2(), new Level2() } } };

        string xml;

        using (var writer = new StringWriter())
        {
            XmlSerializationUtilities.WriteList("RootLevel", XmlNamespaces.XmlSerializerNamespaces, list, writer);
            xml = writer.ToString();

            Debug.WriteLine(xml);
        }
    }
}

And the output is

<?xml version="1.0" encoding="utf-16"?>
<RootLevel xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://MyCompany.com/MySchema.xsd">
    <Level1>
        <Level2Field>
            <Level2 />
            <Level2 />
        </Level2Field>
    </Level1>
    <Level1>
        <Level2Field>
            <Level2 />
            <Level2 />
            <Level2 />
            <Level2 />
        </Level2Field>
    </Level1>
</RootLevel>


来源:https://stackoverflow.com/questions/28571394/serialize-part-of-xml-file-want-namespace-on-root-not-on-serialized-subelement

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!