【问题标题】:Is there a stylesheet or Windows commandline tool for controllable XML formatting, specifically putting attributes one-per-line?是否有用于可控 XML 格式的样式表或 Windows 命令行工具,特别是每行放置一个属性?
【发布时间】:2011-02-03 19:53:09
【问题描述】:

我正在寻找用于 Windows 的 XSLT 或命令行工具(或可制作成命令行工具的 C# 代码等),用于进行 XML 漂亮打印。具体来说,我想要一个能够将属性一对一放置的能力,例如:

<Node>
   <ChildNode 
      value1='5'
      value2='6'
      value3='happy' />
</Node>

它不必完全那样,但我想将它用于一个 XML 文件,该文件具有具有数十个属性的节点并将它们分布在多行中,使它们更易于阅读、编辑和文本差异。

注意:我认为我首选的解决方案是 XSLT 表,我可以通过 C# 方法传递,尽管 Windows 命令行工具也很好。

【问题讨论】:

  • 我更新了我的答案并发布了一个例子。
  • @stafford - 我还是会看看 Tidy(请参阅下面的答案)。如果您处理 XML,它是一个方便的命令行工具,即使您最终没有使用它来解决这个特定问题。
  • @Bert F:谢谢,我会的。我发现漂亮的打印和规范化工具对于保存在其他领域的工具箱中非常有用。

标签: xml pretty-print xml-formatting


【解决方案1】:

这是执行此操作的 PowerShell 脚本。它需要以下输入:

<?xml version="1.0" encoding="utf-8"?>
<Node>
    <ChildNode value1="5" value2="6" value3="happy" />
</Node>

...并将其作为输出生成:

<?xml version="1.0" encoding="utf-8"?>
<Node>
  <ChildNode
    value1="5"
    value2="6"
    value3="happy" />
</Node>

给你:

param(
    [string] $inputFile = $(throw "Please enter an input file name"),
    [string] $outputFile = $(throw "Please supply an output file name")
)

$data = [xml](Get-Content $inputFile)

$xws = new-object System.Xml.XmlWriterSettings
$xws.Indent = $true
$xws.IndentChars = "  "
$xws.NewLineOnAttributes = $true

$data.Save([Xml.XmlWriter]::Create($outputFile, $xws))

获取该脚本,将其保存为 C:\formatxml.ps1。然后,在 PowerShell 提示符下键入以下内容:

C:\formatxml.ps1 C:\Path\To\UglyFile.xml C:\Path\To\NeatAndTidyFile.xml

此脚本基本上只是使用 .NET 框架,因此您可以非常轻松地将其迁移到 C# 应用程序中。

注意:如果您之前没有从 PowerShell 运行脚本,则必须在提升的 PowerShell 提示符处执行以下命令,然后才能执行脚本:

Set-ExecutionPolicy RemoteSigned

你只需要这样做一次。

希望对你有用。

【讨论】:

  • 8 年后,我正在尝试这个并发现了一个问题。在关闭 Powershell 窗口之前,此脚本不会关闭输出文件。解决方案是将 XML 编写器创建为单独的对象,将该对象传递给 $data.Save(..) 调用,然后在 XML 编写器对象上调用 Dispose() 作为最后一件事
【解决方案2】:

这是一个小的 C# 示例,可以直接由您的代码使用,也可以内置到 exe 中并在命令行中以“myexe from.xml to.xml”的形式调用:

    using System.Xml;

    static void Main(string[] args)
    {
        XmlWriterSettings settings = new XmlWriterSettings {
            NewLineHandling = NewLineHandling.Entitize,
            NewLineOnAttributes = true, Indent = true, IndentChars = "  ",
            NewLineChars = Environment.NewLine
        };

        using (XmlReader reader = XmlReader.Create(args[0]))
        using (XmlWriter writer = XmlWriter.Create(args[1], settings)) {
            writer.WriteNode(reader, false);
            writer.Close();
        }
    }

示例输入:

<Node><ChildNode value1='5' value2='6' value3='happy' /></Node>

示例输出(注意您可以使用settings.OmitXmlDeclaration 删除&lt;?xml ...):

<?xml version="1.0" encoding="utf-8"?>
<Node>
  <ChildNode
    value1="5"
    value2="6"
    value3="happy" />
</Node>

请注意,如果您想要一个 字符串 而不是写入文件,只需与 StringBuilder 交换即可:

StringBuilder sb = new StringBuilder();
using (XmlReader reader = XmlReader.Create(new StringReader(oldXml)))
using (XmlWriter writer = XmlWriter.Create(sb, settings)) {
    writer.WriteNode(reader, false);
    writer.Close();
}
string newXml = sb.ToString();

【讨论】:

  • 这正是我所需要的,谢谢。我不知道 XmlWriterSettings 存在,我正在使用一个 XDocument,它有一个 .Save 和一组非常有限的 SaveOptions ......一旦我知道 C# 有 XmlWriterSettings 对象和(显然)NewLineOnAttributes,我很高兴去。
  • 这里您使用两个文件,一个用于源文件,一个用于输出文件。是否可以在同一个文件中执行此操作?使用整洁的 XML 更改源文件。提前致谢。
【解决方案3】:

在 SourceForge 上尝试Tidy。虽然它经常在 [X]HTML 上使用,但我之前在 XML 上成功使用过它——只要确保您使用 -xml 选项即可。

http://tidy.sourceforge.net/#docs

Tidy 读取 HTML、XHTML 和 XML 文件并编写清理标记。 ... 对于通用 XML 文件,Tidy 仅限于纠正基本的格式错误和漂亮的打印

人们已经移植到多个平台,它可以作为可执行和可调用的库使用。

Tidy 有一堆 options 包括:

http://api.html-tidy.org/tidy/quickref_5.0.0.html#indent

缩进属性
顶部类型:布尔值
默认值:no 示例:y/n、yes/no、t/f、true/false、1/0
此选项指定 Tidy 是否应在新行开始每个属性。 p>

一个警告:

对 XML 的有限支持

符合 W3C 的 XML 1.0 推荐标准的 XML 处理器对于它们将接受哪些文件非常挑剔。 Tidy 可以帮助您修复导致 XML 文件被拒绝的错误。不过,Tidy 还不能识别所有 XML 功能,例如它不理解 CDATA 部分或 DTD 子集。

但我怀疑除非您的 XML 非常高级,否则该工具应该可以正常工作。

【讨论】:

    【解决方案4】:

    有一个工具可以将属性拆分为每行一个:xmlpp。这是一个 perl 脚本,所以你必须安装 perl。用法:

    perl xmlpp.pl -t input.xml
    

    您还可以通过创建一个名为 attributeOrdering.txt 的文件并调用 perl xmlpp.pl -s -t input.xml 来确定属性的顺序。如需更多选项,请使用perl xmlpp.pl -h

    我希望它没有太多错误,但到目前为止它对我有用。

    【讨论】:

    • @chris_l - 谢谢。我的团队都是 ASP.NET C# 开发人员,所以没有人会安装 perl。让他们安装它可能是可能的,但并不理想。但我试过了,它确实做了它应该做的事!
    【解决方案5】:

    XML Notepad 2007 可以手动执行...让我看看是否可以编写脚本。

    不...它可以像这样启动它:

    XmlNotepad.exe a.xml
    

    剩下的就是点击保存按钮。 Power Shell,其他工具可以自动完成。

    【讨论】:

    • @Hamish Grubijan,这可能行得通,但自动化 GUI 非常麻烦——一定有更简单的方法!
    【解决方案6】:

    只需使用这个 xslt:

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
      <xsl:output method="xml" encoding="ISO-8859-1"/>
      <xsl:param name="indent-increment" select="'   '"/>
    
      <xsl:template name="newline">
        <xsl:text disable-output-escaping="yes">
    </xsl:text>
      </xsl:template>
    
      <xsl:template match="comment() | processing-instruction()">
        <xsl:param name="indent" select="''"/>
        <xsl:call-template name="newline"/>    
        <xsl:value-of select="$indent"/>
        <xsl:copy />
      </xsl:template>
    
      <xsl:template match="text()">
        <xsl:param name="indent" select="''"/>
        <xsl:call-template name="newline"/>    
        <xsl:value-of select="$indent"/>
        <xsl:value-of select="normalize-space(.)"/>
      </xsl:template>
    
      <xsl:template match="text()[normalize-space(.)='']"/>
    
      <xsl:template match="*">
        <xsl:param name="indent" select="''"/>
        <xsl:call-template name="newline"/>    
        <xsl:value-of select="$indent"/>
          <xsl:choose>
           <xsl:when test="count(child::*) > 0">
            <xsl:copy>
             <xsl:copy-of select="@*"/>
             <xsl:apply-templates select="*|text()">
               <xsl:with-param name="indent" select="concat ($indent, $indent-increment)"/>
             </xsl:apply-templates>
             <xsl:call-template name="newline"/>
             <xsl:value-of select="$indent"/>
            </xsl:copy>
           </xsl:when>       
           <xsl:otherwise>
            <xsl:copy-of select="."/>
           </xsl:otherwise>
         </xsl:choose>
      </xsl:template>    
    </xsl:stylesheet>
    

    或者,作为另一种选择,这里是一个 perl 脚本:http://software.decisionsoft.com/index.html

    【讨论】:

    • 看起来不错。为了完成这项工作,是否有命令行 xslt 处理器/我可以使用 c# 通过 XSLT 处理 XML 吗?
    • 两者都有,但是 saxon 命令行 xslt 处理器 (saxon.sourceforge.net) 应该足够了 :)
    • 我试过了,它似乎不起作用。它缩进每个节点,但不在每个属性上换行/缩进。我用 XYXY 替换了缩进增量字符串,以确保它正在处理,并且确实是,但属性仍然在一行中。
    • 我将把它作为一个单独的问题打开,因为我很好奇为什么这对我不起作用。
    【解决方案7】:

    您可以实现一个简单的 SAX 应用程序,它将复制 as is 的所有内容并按您喜欢的方式缩进属性。

    UPD

    SAX 代表Simple API for XML。它是 XML 解析的推送模型(Builder 设计模式的经典示例)。该 API 存在于大多数当前的开发平台中(尽管原生 .Net 类库缺少一个,但有 XMLReader intead)

    这是一个在 python 中的原始实现,它相当神秘,但你可以实现主要思想。

    from sys import stdout
    from xml.sax import parse
    from xml.sax.handler import ContentHandler
    from xml.sax.saxutils import escape
    
    class MyHandler(ContentHandler):
    
        def __init__(self, file_, encoding):
            self.level = 0
            self.elem_indent = '    '
    
            # should the next block make a line break
            self._allow_N = False
            # whether the opening tag was closed with > (to allow />)
            self._tag_open = False
    
            self._file = file_
            self._encoding = encoding
    
        def _write(self, string_):
            self._file.write(string_.encode(self._encoding))
    
        def startElement(self, name, attrs):
            if self._tag_open:
                self._write('>')
                self._tag_open = False
    
            if self._allow_N:
                self._write('\n')
                indent = self.elem_indent * self.level
            else:
                indent = ''
            self._write('%s<%s' % (indent, name))
    
            # attr indent equals to the element indent plus '  '
            attr_indent = self.elem_indent * self.level + '  '
            for name in attrs.getNames():
                # write indented attribute one per line
                self._write('\n%s%s="%s"' % (attr_indent, name, escape(attrs.getValue(name))))
    
            self._tag_open = True
    
            self.level += 1
            self._allow_N = True
    
        def endElement(self, name):
            self.level -= 1
            if self._tag_open:
                self._write(' />')
                self._tag_open = False
                return
    
            if self._allow_N:
                self._write('\n')
                indent = self.elem_indent * self.level
            else:
                indent = ''
            self._write('%s</%s>' % (indent, name))
            self._allow_N = True
    
        def characters(self, content):
            if self._tag_open:
                self._write('>')
                self._tag_open = False
    
            if content.strip():
                self._allow_N = False
                self._write(escape(content))
            else:
                self._allow_N = True
    
    
    if __name__ == '__main__':
        parser = parse('test.xsl', MyHandler(stdout, stdout.encoding))
    

    【讨论】:

    • @newtover:你能提供更多信息吗?我敢肯定这是真的,但如果我知道怎么做,我就不会问这个问题了。 :)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-01-08
    • 2021-02-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-02-04
    • 1970-01-01
    相关资源
    最近更新 更多