【问题标题】:Is it possible to manually line break on a specific attribute when using XmlWriter in Indent mode?在缩进模式下使用 XmlWriter 时是否可以在特定属性上手动换行?
【发布时间】:2019-08-11 12:40:26
【问题描述】:

XmlWriter 允许在使用XmlWriter.CreateXmlWriterSettings 时配置缩进。

一般来说,我想要Indent = trueNewLineOnAttributes = falseexcept 在文件开头写xmlns 命名空间声明时,我想在每个@987654329 之间有新行@命名空间以提高可读性。

是否可以在写入特定属性后强制XmlWriter进行换行,否则遵循一般缩进规则?

我尝试将WriteWhitespaceWriteRaw\n 一起使用:

using System;
using System.Text;
using System.Xml;

namespace XmlWriterIndent
{
    class Program
    {
        static void Main(string[] args)
        {
            var output = new StringBuilder();
            using (var writer = XmlWriter.Create(output, new XmlWriterSettings { Indent = true, NewLineOnAttributes = true }))
            {
                writer.WriteStartDocument();
                writer.WriteStartElement("Node");

                writer.WriteAttributeString("key1", "value1");
                writer.WriteAttributeString("key2", "value2");
                writer.WriteAttributeString("xmlns", "n1", null, "scheme://mynamespace.com");
                writer.WriteRaw("\n");
                writer.WriteAttributeString("xmlns", "n2", null, "scheme://anothernamespace.com");

                writer.WriteEndElement();
                writer.WriteEndDocument();
            }

            var xml = output.ToString();
            Console.WriteLine(xml);
        }
    }
}

不幸的是,这会引发异常,说明 XML 文档无效。

更新:实际上,经过仔细检查,异常不在WriteRaw方法本身,而是在下面的WriteAttributeString调用中,因为我在循环中调用这些方法适用于所有命名空间。

看起来WriteRaw 以某种方式将XmlWriter 移动到元素内容状态。是否可以使用WriteRaw 或以某种方式在属性之间插入空格而不更改编写器状态?

更新:添加了独立的示例。实际上,即使使用NewLineOnAttributes,通常也会忽略命名空间声明,即所有属性都有新的行except命名空间声明,尽管是常规属性,但它们的处理方式有所不同。

不幸的是,我得出的结论是,XmlWriter API 完全被破坏了,因为无法对 XML 进行原始格式设置,因为WriteRaw 会强制更改写入器状态。

在参考源查看实际源代码表明,特殊的写入方法WriteIndent 用于处理XmlWriter 内部的缩进。此方法具有不会更改状态的特殊行为,但似乎无法访问它或底层数据流,因此如果不完全重新实现整个 XML 编写器堆栈,似乎不可能解决此问题: https://referencesource.microsoft.com/#System.Xml/System/Xml/Core/XmlEncodedRawTextWriter.cs,1739

【问题讨论】:

  • 您能否仅在使用 XML 命名空间编写元素/属性期间启用 NewLineOnAttributes
  • 在编写命名空间后设置 NewLineOnAttributes = false。
  • 很遗憾,在创建 XmlWriter 后无法更改 XmlWriterSettings 属性。出于某种原因,它们被标记为只读,如果您尝试更改任何内容,框架将抛出​​异常。
  • @glopes 请编辑您的问题以包含您作为minimal reproducible example 的完整源代码,其他人可以编译和测试。包括您为在 XML 元素中添加新行所做的所有尝试,并添加您从中获得的错误消息和结果。特别是在您的尝试中包含完整的异常消息。
  • 我添加了最小可重现示例,并进一步了解 API 和观察到的行为。我不认为异常消息会添加任何进一步的信息,无论如何谁想看到它只需要运行程序即可。

标签: c# xmlwriter


【解决方案1】:

XmlWriter API 无法对 XML 属性进行原始格式设置。 WriteRaw 将是适当的调用方法,但XmlWriter.Create 返回的内部XmlWellFormedWriter 在调用此方法时总是推进写入器状态,从而将XML 状态机推进到内容。如果我们正在编写属性,这将完成元素的开始标记并移动到内容,这是 not 我们要编写自定义缩进的地方。

几个内部XmlWriter 类实现了更底层的WriteRaw 方法,但似乎无法访问它们,因为XmlWriter.Create 在返回之前总是用XmlWellFormedWriter 实例包装创建的写入器。

因此,解决此问题的唯一方法是定义和实例化一个自定义XmlWriter 类,该类控制基础流基础XmlWriter。这样我们就可以绕过XmlWriter API,在需要进行自定义缩进时直接写入流中。

目前的解决方案有几个限制:

  1. XmlWriter 实现不直接写入流,而是保留自己的内部缓冲区以提高效率。这意味着每当我们想要绕过XmlWriter 时,我们都需要调用Flush 以确保我们的流在正确的位置;

  2. 为了使缩进有意义,我们需要跟踪正确的缩进级别。对整个文档执行此操作是可能的,但很乏味。为简单起见,此解决方案仅格式化顶级 xmlns 声明;

  3. 为了完整起见,我们需要同时处理StreamTextWriter 作为可能的输出类型。

最后,XmlWriter 抽象类有几十个我们不关心的方法可以覆盖,但需要桥接到底层编写器。为简洁起见,我省略了除相关覆盖之外的所有内容:

class XmlnsIndentedWriter : XmlWriter
{
    bool isRootElement;
    int indentLevel = -1;
    readonly Stream stream;
    readonly TextWriter textWriter;
    readonly XmlWriter writer;

    private XmlnsIndentedWriter(Stream output, XmlWriter baseWriter)
    {
        stream = output;
        writer = baseWriter;
    }

    private XmlnsIndentedWriter(TextWriter output, XmlWriter baseWriter)
    {
        textWriter = output;
        writer = baseWriter;
    }

    public static new XmlWriter Create(StringBuilder output, XmlWriterSettings settings)
    {
        var writer = XmlWriter.Create(output, settings);
        return new XmlnsIndentedWriter(new StringWriter(output, CultureInfo.InvariantCulture), writer);
    }

    public static new XmlWriter Create(Stream stream, XmlWriterSettings settings)
    {
        var writer = XmlWriter.Create(stream, settings);
        return new XmlnsIndentedWriter(stream, writer);
    }

    // snip: override all methods in the XmlWriter class

    private void WriteRawText(string text)
    {
        writer.Flush();
        if (stream != null)
        {
            // example only, this could be optimized with buffers, etc.
            var buf = writer.Settings.Encoding.GetBytes(text);
            stream.Write(buf, 0, buf.Length);
        }
        else if (textWriter != null)
        {
            textWriter.Write(text);
        }
    }

    public override void WriteStartDocument()
    {
        isRootElement = true;
        writer.WriteStartDocument();
    }

    public override void WriteStartElement(string prefix, string localName, string ns)
    {
        if (isRootElement)
        {
            if (indentLevel < 0)
            {
                // initialize the indent level;
                // length of local name + any control characters / prefixes, etc. 
                indentLevel = localName.Length + 1;
            }
            else
            {
                // do not track indent for the whole document;
                // when second element starts, we are done
                isRootElement = false;
                indentLevel = -1;
            }
        }
        writer.WriteStartElement(prefix, localName, ns);
    }

    public override void WriteEndAttribute()
    {
        writer.WriteEndAttribute();
        if (indentLevel >= 0)
        {
            RawText(Environment.NewLine + new string(' ', indentLevel));
        }
    }
}

可以选择在每个属性之前或之后添加缩进。

这里我选择了后者,因为如果您还想缩进默认的xmlns 声明,它似乎是唯一的选择。这个声明是在 writer 状态移动到 content 之后写出来的,否则似乎没有办法拦截它。

【讨论】:

    猜你喜欢
    • 2013-03-30
    • 2012-07-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-13
    • 2011-07-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多