从序列化值

时间:2017-09-25 23:54:32

标签: c# xml xmlserializer xmldocument xmlnode

我不得不重新创建供应商的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);
            }
        }
    }

我确定我忽视了一些事情。任何帮助将不胜感激!

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

  1. XML的目的。我正在两个系统之间编写一个界面UI。我从一个读取数据,为用户提供按摩数据的选项,然后将数据导出到第二个系统可以导入的文件中。关于物料清单系统,其中系统1是这些图纸中的CAD图纸和对象,系统2是企业估算系统,其也被配置为支持电子物料清单。我从供应商处获得了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();
    }
    
  3. 结果产生:

    <?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>
    

    编辑9/27/2017 根据下面解决方案中的请求,我遇到的第二个问题是保留十六进制代码。为了根据上面的例子说明这个问题,让我们说之间的值是

    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()),它会改变&#34;到&amp;#x22;不仅如此,它似乎混淆了元素输出并添加了额外的元素,使它完全失控。

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

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

    但想要

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

2 个答案:

答案 0 :(得分:1)

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

这是XML文字。请注意,XML中的任何位置都没有xmlns="..."声明:

"http://www.mynamespace/09262017"

缺少这样的声明,<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属性为必需的。

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

xmlns=""

然后,在<p xmlns="http://www.mynamespace/09262017" style="text-align:left;margin-top:0pt;margin-bottom:0pt;"> <span>ReplaceMe</span> </p> 方法中,删除冗余的名称空间声明。使用LINQ to XML API更容易做到这一点:

Value

结果XML将如下所示:

[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;
    }
}

请注意,我已将<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更改为[XmlAnyElement]。我这样做是因为看起来您的[XmlElement] XML可能在根级别包含多个混合内容节点,例如:

value

使用Start Text <p>Middle Text</p> End Text 通过允许返回容器节点而不会导致额外级别的XML元素嵌套来启用此功能。

示例工作.Net fiddle

答案 1 :(得分:1)

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

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

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

  3. 问题#1可以在this answer

    中解决

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

    因此,作为解决#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(@"<")不会导致问题。 (快速检查似乎表明您的Regex正在适当地转发<>。)

    新样本.Net fiddle