如何使用 XDocument.Save 使用属性的自定义缩进保存文件
How to use XDocument.Save to save a file using custom indentation for attributes
我的目标是输出修改后的 XML 文件并保留原始文件中存在的特殊缩进。 objective 是为了让生成的文件看起来仍然像原始文件,使它们更容易通过源代码管理进行比较和合并。
我的程序将读取 XML 文件并添加或更改一个特定属性。
这是我试图实现/保留的格式:
<Base Import="..\commom\style.xml">
<Item Width="480"
Height="500"
VAlign="Center"
Style="level1header">
(...)
在这种情况下,我只是希望将第一个属性之后的所有属性与第一个属性对齐。
XmlWriterSettings
提供格式选项,但它们达不到我想要的结果。
settings.Indent = true;
settings.NewLineOnAttributes = true;
这些设置会将第一个属性放在换行符上,而不是将其与节点放在同一行,并将属性与节点对齐。
这是 Load
调用,它要求保留空格:
MyXml = XDocument.Load(filepath, LoadOptions.PreserveWhitespace);
但似乎并没有达到我的预期。
我试图提供一个自定义的 class,它从 XmlWriter
派生到 XDocument.Save
调用,但如果没有 运行,我无法正确插入空格进入 InvalidOperationException
。另外,该解决方案对于我正在寻找的小添加物来说似乎有点过分了。
作为参考,这是我的保存调用,没有使用我的自定义 xml 编写器(无论如何都不起作用)
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.NewLineOnAttributes = true;
settings.OmitXmlDeclaration = true;
using (XmlWriter writer = XmlWriter.Create(filepath + "_auto", settings))
{
MyXml.Save(writer);
}
我最终没有完全使用 XDocument.Save,而是创建了一个包含 XDocument、XmlWriter 和 TextWriter 的 class。
class 解析 XDocument 中的所有节点,TextWriter 绑定到磁盘上的文件,XmlWriter 将其用作其输出管道。
我的class然后使用XmlWriter输出xml。为了实现额外的间距,我使用了此处描述的解决方案 ,这就是我还使用底层 TextWriter 的原因。
这是一个解决方案示例。
正在调用 class 保存文档:
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.NewLineOnAttributes = false; // Behavior changed in PrettyXmlWriter
settings.OmitXmlDeclaration = true;
using(TextWriter rawwriter = File.CreateText(filepath))
using (XmlWriter writer = XmlWriter.Create(rawwriter, settings))
{
// rawwriter is used both by XmlWriter and PrettyXmlWriter
PrettyXmlWriter outputter = new PrettyXmlWriter(writer, rawwriter);
outputter.Write(MyXml);
writer.Flush();
writer.Close();
}
PrettyXmlWriter 内部:
private XmlWriter Writer { get; set; }
private TextWriter InnerTextWriter { get; set; }
public void Write(XDocument doc)
{
XElement root = doc.Root;
WriteNode(root, 0);
}
private void WriteNode(XNode node, int currentNodeDepth)
{
if(node.NodeType == XmlNodeType.Element)
{
WriteElement((XElement)node, currentNodeDepth);
}
else if(node.NodeType == XmlNodeType.Text)
{
WriteTextNode((XText)node, currentNodeDepth, doIndentAttributes);
}
}
private void WriteElement(XElement node, int currentNodeDepth)
{
Writer.WriteStartElement(node.Name.LocalName);
// Write attributes with indentation
XAttribute[] attributes = node.Attributes().ToArray();
if(attributes.Length > 0)
{
// First attribute, unindented.
Writer.WriteAttributeString(attributes[0].Name.LocalName, attributes[0].Value);
for(int i=1; i<attributes.Length; ++i)
{
// Write indentation
Writer.Flush();
string indentation = Writer.Settings.NewLineChars + string.Concat(Enumerable.Repeat(Writer.Settings.IndentChars, currentNodeDepth));
indentation += string.Concat(Enumerable.Repeat(" ", node.Name.LocalName.Length + 1));
// Using Underlying TextWriter trick to output whitespace
InnerTextWriter.Write(indentation);
Writer.WriteAttributeString(attributes[i].Name.LocalName, attributes[i].Value);
}
}
// output children
foreach(XNode child in node.Nodes())
{
WriteNode(child, currentNodeDepth + 1);
}
Writer.WriteEndElement();
}
我的目标是输出修改后的 XML 文件并保留原始文件中存在的特殊缩进。 objective 是为了让生成的文件看起来仍然像原始文件,使它们更容易通过源代码管理进行比较和合并。
我的程序将读取 XML 文件并添加或更改一个特定属性。
这是我试图实现/保留的格式:
<Base Import="..\commom\style.xml">
<Item Width="480"
Height="500"
VAlign="Center"
Style="level1header">
(...)
在这种情况下,我只是希望将第一个属性之后的所有属性与第一个属性对齐。
XmlWriterSettings
提供格式选项,但它们达不到我想要的结果。
settings.Indent = true;
settings.NewLineOnAttributes = true;
这些设置会将第一个属性放在换行符上,而不是将其与节点放在同一行,并将属性与节点对齐。
这是 Load
调用,它要求保留空格:
MyXml = XDocument.Load(filepath, LoadOptions.PreserveWhitespace);
但似乎并没有达到我的预期。
我试图提供一个自定义的 class,它从 XmlWriter
派生到 XDocument.Save
调用,但如果没有 运行,我无法正确插入空格进入 InvalidOperationException
。另外,该解决方案对于我正在寻找的小添加物来说似乎有点过分了。
作为参考,这是我的保存调用,没有使用我的自定义 xml 编写器(无论如何都不起作用)
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.NewLineOnAttributes = true;
settings.OmitXmlDeclaration = true;
using (XmlWriter writer = XmlWriter.Create(filepath + "_auto", settings))
{
MyXml.Save(writer);
}
我最终没有完全使用 XDocument.Save,而是创建了一个包含 XDocument、XmlWriter 和 TextWriter 的 class。 class 解析 XDocument 中的所有节点,TextWriter 绑定到磁盘上的文件,XmlWriter 将其用作其输出管道。
我的class然后使用XmlWriter输出xml。为了实现额外的间距,我使用了此处描述的解决方案 ,这就是我还使用底层 TextWriter 的原因。
这是一个解决方案示例。
正在调用 class 保存文档:
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.NewLineOnAttributes = false; // Behavior changed in PrettyXmlWriter
settings.OmitXmlDeclaration = true;
using(TextWriter rawwriter = File.CreateText(filepath))
using (XmlWriter writer = XmlWriter.Create(rawwriter, settings))
{
// rawwriter is used both by XmlWriter and PrettyXmlWriter
PrettyXmlWriter outputter = new PrettyXmlWriter(writer, rawwriter);
outputter.Write(MyXml);
writer.Flush();
writer.Close();
}
PrettyXmlWriter 内部:
private XmlWriter Writer { get; set; }
private TextWriter InnerTextWriter { get; set; }
public void Write(XDocument doc)
{
XElement root = doc.Root;
WriteNode(root, 0);
}
private void WriteNode(XNode node, int currentNodeDepth)
{
if(node.NodeType == XmlNodeType.Element)
{
WriteElement((XElement)node, currentNodeDepth);
}
else if(node.NodeType == XmlNodeType.Text)
{
WriteTextNode((XText)node, currentNodeDepth, doIndentAttributes);
}
}
private void WriteElement(XElement node, int currentNodeDepth)
{
Writer.WriteStartElement(node.Name.LocalName);
// Write attributes with indentation
XAttribute[] attributes = node.Attributes().ToArray();
if(attributes.Length > 0)
{
// First attribute, unindented.
Writer.WriteAttributeString(attributes[0].Name.LocalName, attributes[0].Value);
for(int i=1; i<attributes.Length; ++i)
{
// Write indentation
Writer.Flush();
string indentation = Writer.Settings.NewLineChars + string.Concat(Enumerable.Repeat(Writer.Settings.IndentChars, currentNodeDepth));
indentation += string.Concat(Enumerable.Repeat(" ", node.Name.LocalName.Length + 1));
// Using Underlying TextWriter trick to output whitespace
InnerTextWriter.Write(indentation);
Writer.WriteAttributeString(attributes[i].Name.LocalName, attributes[i].Value);
}
}
// output children
foreach(XNode child in node.Nodes())
{
WriteNode(child, currentNodeDepth + 1);
}
Writer.WriteEndElement();
}