从序列化值中的 Xml 节点中删除命名空间属性

Remove Namespace attribute from Xml Node in Serialized Value

我必须重新创建供应商的 XML 文件。我无权访问他们的代码、架构或任何东西,所以我使用 XmlSerializer 和属性来执行此操作。我这样做是因为系统正在使用我构建的通用 XmlWriter 来编写其他系统 XML 文件,所以我是一箭双雕。一切都很好,除了一个 属性 值。供应商 XML 看起来像这样:

<TextOutlTxt>
    <p style="text-align:left;margin-top:0pt;margin-bottom:0pt;">
       <span>SUBSTA SF6 CIRCUIT BKR CONC FDN &#x22;C&#x22;</span>
    </p>
</TextOutlTxt>

这是我的 属性 配置:

    private string _value;

    [XmlElement("TextOutlTxt")]
    public XmlNode Value
    {
        get
        {
            string text = _value;
            text = Regex.Replace(text, @"[\a\b\f\n\r\t\v\""'&<>]", m => string.Join(string.Empty, m.Value.Select(c => string.Format("&#x{0:X};", Convert.ToInt32(c))).ToArray()));
            string value = "\n<p style=\"text-align:left;margin-top:0pt;margin-bottom:0pt;\">\n<span>ReplaceMe</span>\n</p>\n";

            XmlDocument document = new XmlDocument();
            document.InnerXml = "<root>" + value + "</root>";

            XmlNode innerNode = document.DocumentElement.FirstChild;
            innerNode.InnerText = text;

            return innerNode;
        }
        set
        { }
    }

这给了我:

<TextOutlTxt>
  <p style="text-align:left;margin-top:0pt;margin-bottom:0pt;" xmlns="">SUBSTA SF6 CIRCUIT BKR CONC FDN &amp;#x22;C&amp;#x22;</p>
</TextOutlTxt>

所以我很接近,但没有雪茄。有一个不需要的 xmlns="..." 属性;它一定不存在。在我的 XmlWriter 中,我已完成以下操作以删除命名空间,除非在它正在序列化的对象之上找到它:

 protected override void OnWrite<T>(T sourceData, Stream outputStream)
    {
        IKnownTypesLocator knownTypesLocator = KnownTypesLocator.Instance;

        //Let's see if we can get the default namespace
        XmlRootAttribute xmlRootAttribute = sourceData.GetType().GetCustomAttributes<XmlRootAttribute>().FirstOrDefault();

        XmlSerializer serializer = null;

        if (xmlRootAttribute != null)
        {
            string nameSpace = xmlRootAttribute.Namespace ?? string.Empty;
            XmlSerializerNamespaces nameSpaces = new XmlSerializerNamespaces();
            nameSpaces.Add(string.Empty, nameSpace);
            serializer = new XmlSerializer(typeof(T), new XmlAttributeOverrides(), knownTypesLocator.XmlItems.ToArray(), xmlRootAttribute, nameSpace);

            //Now we can serialize
            using (StreamWriter writer = new StreamWriter(outputStream))
            {
                serializer.Serialize(writer, sourceData, nameSpaces);
            }
        }
        else
        {
            serializer = new XmlSerializer(typeof(T), knownTypesLocator.XmlItems.ToArray());

            //Now we can serialize
            using (StreamWriter writer = new StreamWriter(outputStream))
            {
                serializer.Serialize(writer, sourceData);
            }
        }
    }

我确定我忽略了一些东西。任何帮助将不胜感激!

2017 年 9 月 26 日更新 所以......我被要求提供更多细节,特别是对我的代码目的的解释,以及一个可重现的例子。所以这两个都是:

  1. 目的为XML。我正在编写两个系统之间的接口 UI。我从一个系统读取数据,为用户提供处理数据的选项,然后能够将数据导出到第二个系统可以导入的文件中。它与 material 系统的账单有关,其中系统一是 CAD 图纸和这些图纸中的对象,系统二是企业估算系统,该系统也被配置为支持 material 的电子账单。我从供应商那里得到了 XMLs 来重新创建。
  2. 功能齐全的示例代码....我尝试以可重现的形式概括代码。

    [XmlRoot("OutlTxt", Namespace = "http://www.mynamespace/09262017")]
    public class OutlineText
    {
        private string _value;
    
        [XmlElement("TextOutlTxt")]
        public XmlNode Value
        {
            get
            {
                string text = _value;
                text = Regex.Replace(text, @"[\a\b\f\n\r\t\v\""'&<>]", m => string.Join(string.Empty, m.Value.Select(c => string.Format("&#x{0:X};", Convert.ToInt32(c))).ToArray()));
                string value = "\n<p style=\"text-align:left;margin-top:0pt;margin-bottom:0pt;\">\n<span>ReplaceMe</span>\n</p>\n";
    
                XmlDocument document = new XmlDocument();
                document.InnerXml = "<root>" + value + "</root>";
    
                XmlNode innerNode = document.DocumentElement.FirstChild;
                innerNode.InnerText = text;
    
                return innerNode;
             }
            set
            { }
        }
    
        private OutlineText()
        { }
    
        public OutlineText(string text)
        {
            _value = text;
        }
    
    }
    
     public class XmlFileWriter
    {
        public void Write<T>(T sourceData, FileInfo targetFile) where T : class
        {
            //This is actually retrieved through a locator object, but surely no one will mind an empty
            //collection for the sake of an example
            Type[] knownTypes = new Type[] { };
    
            using (FileStream targetStream = targetFile.OpenWrite())
            {
                 //Let's see if we can get the default namespace
                 XmlRootAttribute xmlRootAttribute = sourceData.GetType().GetCustomAttributes<XmlRootAttribute>().FirstOrDefault();
    
                 XmlSerializer serializer = null;
    
                if (xmlRootAttribute != null)
                {
                     string nameSpace = xmlRootAttribute.Namespace ?? string.Empty;
                     XmlSerializerNamespaces nameSpaces = new XmlSerializerNamespaces();
                     nameSpaces.Add(string.Empty, nameSpace);
                     serializer = new XmlSerializer(typeof(T), new XmlAttributeOverrides(), knownTypes, xmlRootAttribute, nameSpace);
    
                     //Now we can serialize
                    using (StreamWriter writer = new StreamWriter(targetStream))
                    {
                         serializer.Serialize(writer, sourceData, nameSpaces);
                     }
                }
                else
                {
                    serializer = new XmlSerializer(typeof(T), knownTypes);
    
                    //Now we can serialize
                    using (StreamWriter writer = new StreamWriter(targetStream))
                    {
                        serializer.Serialize(writer, sourceData);
                    }
                }
            }
        }
    }
    
    
     public static void Main()
    {
        OutlineText outlineText = new OutlineText(@"SUBSTA SF6 CIRCUIT BKR CONC FDN ""C""");
    
        XmlFileWriter fileWriter = new XmlFileWriter();
        fileWriter.Write<OutlineText>(outlineText, new FileInfo(@"C:\MyDirectory\MyXml.xml"));
    
    
        Console.ReadLine();
    }
    

产生的结果:

<?xml version="1.0" encoding="utf-8"?>
<OutlTxt xmlns="http://www.mynamespace/09262017">
  <TextOutlTxt>
    <p style="text-align:left;margin-top:0pt;margin-bottom:0pt;" xmlns="">SUBSTA SF6 CIRCUIT BKR CONC FDN &amp;#x22;C&amp;#x22;</p>
  </TextOutlTxt>
</OutlTxt>

编辑 2017 年 9 月 27 日 根据下面解决方案中的要求,我 运行 遇到的第二个问题是保留十六进制代码。为了根据上面的例子来说明这个问题,假设 between 的值为

SUBSTA SF6 CIRCUIT BKR CONC FDN "C"

供应商文件期望文字采用十六进制代码格式,就像这样

SUBSTA SF6 CIRCUIT BKR CONC FDN &#x22;C&#x22;

我已经运行修改了示例代码值 属性 如下:

        private string _value;

    [XmlAnyElement("TextOutlTxt", Namespace = "http://www.mynamespace/09262017")]
    public XElement Value
    {
        get
        {
            string value = string.Format("<p xmlns=\"{0}\" style=\"text-align:left;margin-top:0pt;margin-bottom:0pt;\"><span>{1}</span></p>", "http://www.mynamespace/09262017", _value);


            string innerXml = string.Format("<TextOutlTxt xmlns=\"{0}\">{1}</TextOutlTxt>", "http://www.mynamespace/09262017", value);

            XElement element = XElement.Parse(innerXml);

            //Remove redundant xmlns attributes
            foreach (XElement descendant in element.DescendantsAndSelf())
            {
                descendant.Attributes().Where(att => att.IsNamespaceDeclaration && att.Value == "http://www.mynamespace/09262017").Remove();
            }

            return element;
        }
        set
        {
            _value = value == null ? null : value.ToString();
        }
    }

如果我使用代码

 string text = Regex.Replace(element.Value, @"[\a\b\f\n\r\t\v\""'&<>]", m => string.Join(string.Empty, m.Value.Select(c => string.Format("&#x{0:X};", Convert.ToInt32(c))).ToArray()));

为了在 XElement.Parse() 之前创建十六进制代码值,XElement 将它们转换回其文字值。如果我尝试在 XElement.Parse() 之后直接设置 XElement.Value(或通过 SetValue()),它会将 " 更改为 " 不仅如此,而且它似乎与元素混淆输出并添加额外的元素,把它全部扔掉。

编辑 9/27/2017 #2 澄清一下,原来的实现有一个相关的问题,即转义文本被重新转义。 IE。我得到

SUBSTA SF6 CIRCUIT BKR CONC FDN &amp;#x22;C&amp;#x22;

但想要

SUBSTA SF6 CIRCUIT BKR CONC FDN &#x22;C&#x22;

您将 xmlns="" 添加到嵌入式 XML 的原因是您的容器元素 <OutlineText><TextOutlTxt> 被声明为在 "http://www.mynamespace/09262017" 命名空间使用 [XmlRootAttribute.Namespace] 属性,而嵌入文字 XML 元素位于空命名空间中。要解决此问题,您嵌入的 XML 文字必须与其父元素位于同一名称空间中。

这是 XML 文字。请注意 XML:

中的任何地方都没有 xmlns="..." 声明
<p style="text-align:left;margin-top:0pt;margin-bottom:0pt;" xmlns="">SUBSTA SF6 CIRCUIT BKR CONC FDN &amp;#x22;C&amp;#x22;</p>

缺少这样的声明,<p> 元素位于空命名空间中。相反,您的 OutlineText 类型装饰有 [XmlRoot] 属性:

[XmlRoot("OutlTxt", Namespace = "http://www.mynamespace/09262017")]
public class OutlineText
{
}

因此相应的 OutlTxt 根元素将位于 http://www.mynamespace/09262017 命名空间中。 它的所有子元素也将默认为这个命名空间,除非被覆盖。将嵌入的 XmlNode 放置在空命名空间中算作覆盖父命名空间,因此 xmlns="" 属性是必需的。

避免此问题的最简单方法是将嵌入的 XML 字符串文字放置在正确的命名空间中,如下所示:

<p xmlns="http://www.mynamespace/09262017" style="text-align:left;margin-top:0pt;margin-bottom:0pt;">
<span>ReplaceMe</span>
</p>

然后,在您的 Value 方法中,删除多余的命名空间声明。使用 LINQ to XML API:

更容易做到这一点
[XmlRoot("OutlTxt", Namespace = OutlineText.Namespace)]
public class OutlineText
{
    public const string Namespace = "http://www.mynamespace/09262017";

    private string _value;

    [XmlAnyElement("TextOutlTxt", Namespace = OutlineText.Namespace)]
    public XElement Value
    {
        get
        {
            var escapedValue = EscapeTextValue(_value);

            var nestedXml = string.Format("<p xmlns=\"{0}\" style=\"text-align:left;margin-top:0pt;margin-bottom:0pt;\"><span>{1}</span></p>", Namespace, escapedValue);
            var outerXml = string.Format("<TextOutlTxt xmlns=\"{0}\">{1}</TextOutlTxt>", Namespace, nestedXml);

            var element = XElement.Parse(outerXml);

            //Remove redundant xmlns attributes
            element.DescendantsAndSelf().SelectMany(e => e.Attributes()).Where(a => a.IsNamespaceDeclaration && a.Value == Namespace).Remove();

            return element;
        }
        set
        {
            _value = value == null ? null : value.Value;
        }
    }

    static string EscapeTextValue(string text)
    {
        return Regex.Replace(text, @"[\a\b\f\n\r\t\v\""'&<>]", m => string.Join(string.Empty, m.Value.Select(c => string.Format("&#x{0:X};", Convert.ToInt32(c))).ToArray()));
    }

    private OutlineText()
    { }

    public OutlineText(string text)
    {
        _value = text;
    }
}

结果 XML 将如下所示:

<OutlTxt xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.mynamespace/09262017">
  <TextOutlTxt>
    <p style="text-align:left;margin-top:0pt;margin-bottom:0pt;">
      <span>SUBSTA SF6 CIRCUIT BKR CONC FDN "C"</span>
    </p>
  </TextOutlTxt>
</OutlTxt>

请注意,我已将 Value 上的属性从 [XmlElement] 更改为 [XmlAnyElement]。我这样做是因为您的 value XML 可能在根级别包含多个混合内容节点,例如:

Start Text <p>Middle Text</p> End Text

使用 [XmlAnyElement] 通过允许返回容器节点而不会导致额外级别的 XML 元素嵌套来实现这一点。

工作示例 .Net fiddle

您的问题现在有两个要求:

  1. 在序列化时抑制嵌入式 XElementXmlNode 上的某些 xmlns="..." 属性,AND

  2. 强制转义元素文本中的某些字符(例如 " => &#x22;)。尽管这不是 XML 标准所要求的,但您的旧接收系统显然需要它。

问题 #1 可以在

中解决

然而,对于问题 #2,无法强制使用 XmlNodeXElement 对某些字符进行不必要的转义,因为转义是在 XmlWriter 级别处理的输出。并且 Microsoft 的 XmlWriter 内置实现似乎没有任何 settings that can force certain characters that do not need to be escaped to nevertheless be escaped. You would need to try to subclass XmlWriter or XmlTextWriter (as described e.g. and here) 然后在写入字符串值时拦截它们并根据需要转义引号字符。

因此,作为同时解决#1 和#2 的替代方法,您可以实施 IXmlSerializable and write your desired XML directly with XmlWriter.WriteRaw():

[XmlRoot("OutlTxt", Namespace = OutlineText.Namespace)]
public class OutlineText : IXmlSerializable
{
    public const string Namespace = "http://www.mynamespace/09262017";

    private string _value;

    // For debugging purposes.
    internal string InnerValue { get { return _value; } }

    static string EscapeTextValue(string text)
    {
        return Regex.Replace(text, @"[\a\b\f\n\r\t\v\""'&<>]", m => string.Join(string.Empty, m.Value.Select(c => string.Format("&#x{0:X};", Convert.ToInt32(c))).ToArray()));
    }

    private OutlineText()
    { }

    public OutlineText(string text)
    {
        _value = text;
    }

    #region IXmlSerializable Members

    XmlSchema IXmlSerializable.GetSchema()
    {
        return null;
    }

    void IXmlSerializable.ReadXml(XmlReader reader)
    {
        _value = ((XElement)XNode.ReadFrom(reader)).Value;
    }

    void IXmlSerializable.WriteXml(XmlWriter writer)
    {
        var escapedValue = EscapeTextValue(_value);
        var nestedXml = string.Format("<p style=\"text-align:left;margin-top:0pt;margin-bottom:0pt;\"><span>{0}</span></p>", escapedValue);
        writer.WriteRaw(nestedXml);
    }

    #endregion
}

输出将是

<OutlTxt xmlns="http://www.mynamespace/09262017"><p style="text-align:left;margin-top:0pt;margin-bottom:0pt;"><span>SUBSTA SF6 CIRCUIT BKR CONC FDN &#x22;C&#x22;</span></p></OutlTxt>

请注意,如果您使用 WriteRaw(),您可以通过在文本值中嵌入标记字符轻松生成无效的 XML。您应该确保添加单元测试以验证不会发生,例如new OutlineText(@"<") 不会造成问题。 (快速检查似乎表明您的 Regex 正在适当地转义 <>。)

新样本.Net fiddle.