【问题标题】:Remove Namespace attribute from Xml Node in Serialized Value从序列化值中的 Xml 节点中删除命名空间属性
【发布时间】:2017-09-25 23:54:31
【问题描述】:

我不得不重新创建供应商的 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。我从第一个系统读取数据,为用户提供处理数据的选项,然后提供将数据导出到第二个系统可以导入的文件的能力。它涉及物料清单系统,其中系统一是 CAD 图纸和这些图纸中的对象,系统二是企业估算系统,它也被配置为支持电子物料清单。供应商给了我重新创建的 XML。
  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 日编辑 根据以下解决方案中的请求,我遇到的第二个问题是保留十六进制代码。根据上面的例子来说明这个问题,假设之间的值是

SUBSTA SF6 CIRCUIT BKR CONC FDN "C"

供应商文件期望文字采用十六进制代码格式,如下所示

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

我已将示例代码的 Value 属性重新排列为:

        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;

【问题讨论】:

  • 您可能完全过度设计了问题,当我尝试您的代码时,我通过访问 OuterXml 属性摆脱了另一端"&lt;p style="text-align:left;margin-top:0pt;margin-bottom:0pt;"&gt;SUBSTA SF6 CIRCUIT BKR CONC FDN &amp;amp;#x26;amp;#x22;C&amp;amp;#x26;amp;#x22;&lt;/p&gt;"。当您说重新创建供应商 XML 文件时,您的意思是这样您就可以提出请求吗?或者这样你就可以拦截响应?不够清楚。
  • 您的问题不清楚,因为您没有展示如何初始化 _value 或您的容器类型是什么样的。但这是你需要的吗? dotnetfiddle.net/VtdFky 如果你提供了minimal reproducible example,我将能够明确回答。
  • 您的代码是 html 而不是 xml。该文件已针对 html 正确编码。参见维基:en.wikipedia.org/wiki/…。您可以使用 System.Net.WebUtility.HtmlDecode() 和 System.Net.WebUtility.HtmlEncode()
  • @jdweng 问题中的文档是有效的 XML。此外,我不知道您认为 HtmlDecodeHtmlEncode 会做什么,但我敢打赌,您不知道他们在做什么,或者您根本不理解这个问题。跨度>
  • @jdweng 对不起,但还是谢谢你。 HtmlEncode 非常适合将字符串编码为与 html 文本兼容,但这不是这里的问题。我已经按照供应商期望的格式解决了这个问题。现在我只想删除作为结果创建的命名空间元素。

标签: c# xml xmlserializer xmldocument xmlnode


【解决方案1】:

您将 xmlns="" 添加到嵌入式 XML 的原因是您的容器元素 &lt;OutlineText&gt;&lt;TextOutlTxt&gt; 通过使用 [XmlRootAttribute.Namespace] 属性被声明为位于 "http://www.mynamespace/09262017" 命名空间中,而嵌入的文字 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>

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

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

因此,对应的OutlTxt 根元素将位于http://www.mynamespace/09262017 命名空间中。 除非被覆盖,否则它的所有子元素也将默认使用此命名空间。 将嵌入的 XmlNode 放置在空命名空间中视为覆盖父命名空间,因此需要 xmlns="" 属性。 p>

避免此问题的最简单方法是将嵌入的 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

【讨论】:

  • 你是很棒的朋友。我大部分时间都在开会,但能够对您的建议进行更多研究,并在示例代码和实际项目中实现了它。它就像一个魅力。你的方法很有意义。如果您不介意我问,当我将字符串值更改为使用字符串文字的十六进制字符代码时?我重新安排了将 XElement.Value 设置为替换值的代码,因为 XElement.Parse() 将它们转换回它们的文字值。直接设置 Value 属性会导致 '&'='&'而是。
  • 如果有意义,我可以单独发布另一个问题。
  • @bjhuffin - 不确定。您可以编辑您的问题以演示转义问题吗?基本上我需要查看_value 的特定值(即调用new OutlineText(...)),它可以重现问题以及您要生成的确切XML。
  • 我已经对其进行了相应的编辑。一个具体的例子是如果 _value 包含像双引号这样的转义字符。所以使用 XElement.Parse() "成为双引号值。如果我尝试直接设置 element.Value 属性值,则 "变成 &#x22;不仅如此,它似乎也弄乱了元素结构。
  • @bjhuffine - 我刚刚重新运行了您的原始代码。只是为了确认,对于它输出的文本值 SUBSTA SF6 CIRCUIT BKR CONC FDN &amp;amp;#x22;C&amp;amp;#x22; 但你想要的是 SUBSTA SF6 CIRCUIT BKR CONC FDN &amp;#x22;C&amp;#x22;。对吗?
【解决方案2】:

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

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

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

问题 #1 可以在this answer 中解决

但是,对于问题 #2,无法强制使用 XmlNodeXElement 对某些字符进行不必要的转义,因为转义是在输出期间在 XmlWriter 级别处理的。并且微软的XmlWriter 的内置实现似乎没有任何settings 可以强制某些不需要转义的字符仍然被转义。您需要尝试子类化XmlWriterXmlTextWriter(如herehere 所述),然后在写入时截取字符串值并根据需要转义引号字符。

因此,作为解决 #1 和 #2 的替代方法,您可以实现 IXmlSerializable 并直接使用 XmlWriter.WriteRaw() 编写所需的 XML:

[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(@"&lt;") 不会引起问题。 (快速检查似乎表明您的 Regex 正在适当地转义 &lt;&gt;。)

新样本.Net fiddle

【讨论】:

  • 谢谢!我什至没有意识到我可以像这样拦截写入过程。您的帮助非常有用。我需要把你加入我的圣诞清单!不幸的是,我只能将一个标记为答案,但我确实对这个投了赞成票。明天我将进行一些创造性的重构,以尽量减少它的影响。
猜你喜欢
  • 2014-08-20
  • 2019-01-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-08
相关资源
最近更新 更多