我不得不重新创建供应商的XML文件。我无法访问他们的代码,架构或任何内容,因此我使用XmlSerializer
和属性来执行此操作。我这样做是因为系统正在使用通用的XmlWriter
来构建其他系统XML文件,所以我一举两得。除了一个房产价值外,一切都很好。供应商XML看起来像这样:
<TextOutlTxt>
<p style="text-align:left;margin-top:0pt;margin-bottom:0pt;">
<span>SUBSTA SF6 CIRCUIT BKR CONC FDN "C"</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 &#x22;C&#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 所以...我被要求提供更多细节,特别是对我的代码目的的解释,以及一个可重复的例子。所以这两者都是:
功能齐全的示例代码....我尝试以可重现的形式概括代码。
[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 &#x22;C&#x22;</p>
</TextOutlTxt>
</OutlTxt>
编辑9/27/2017 根据下面解决方案中的请求,我遇到的第二个问题是保留十六进制代码。为了根据上面的例子说明这个问题,让我们说之间的值是
SUBSTA SF6 CIRCUIT BKR CONC FDN "C"
供应商文件期望文字是十六进制代码格式,如此
SUBSTA SF6 CIRCUIT BKR CONC FDN "C"
我已将示例代码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 &#x22;C&#x22;
但想要
SUBSTA SF6 CIRCUIT BKR CONC FDN "C"
答案 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 &#x22;C&#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)
您的问题现在有两个要求:
在序列化时抑制嵌入式xmlns="..."
或XElement
上的某些XmlNode
属性,并
强制转义元素文本中的某些字符(例如"
=&gt; "
)。尽管XML标准并不要求这样做,但您的传统接收系统显然需要这样做。
问题#1可以在this answer
中解决但是,对于问题#2,无法强制使用XmlNode
或XElement
对某些字符进行不必要的转义,因为在输出期间会在XmlWriter
级别处理转义。微软的XmlWriter
内置实现似乎没有任何settings可以强制某些不需要转义的字符被转义。您需要尝试子类化XmlWriter
或XmlTextWriter
(如所描述的here和here),然后在编写时拦截字符串值并根据需要转义引号字符。< / 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 "C"</span></p></OutlTxt>
请注意,如果使用WriteRaw()
,只需编写嵌入文本值的标记字符,即可轻松生成无效的XML。您应该确保添加验证不会发生的单元测试,例如: new OutlineText(@"<")
不会导致问题。 (快速检查似乎表明您的Regex
正在适当地转发<
和>
。)
新样本.Net fiddle。