自动格式化 OS X/macOS XML 属性 列表的缩进和自闭合标签样式

Automatically Format an OS X/macOS XML Property List's Indentation and Self-Closing Tag Styles

我想自动格式化一个或多个 OS X/macOS XML 属性 列表文件以使用像 运行 那样的格式'xmllint --format',除了:

我还希望所有自闭合标签在其结束斜杠之前都有空格。我将使用什么 XSLT(编辑:或其他)转换来实现这一目标?


根据要求,这是一个最小的工作示例(根据 this piece of Apple documentation: 稍作改编)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Year Of Birth</key>
    <integer>1965</integer>
    <key>Pets&apos; Names</key>
    <array/>
    <key>Picture</key>
    <data>
        PEKBpYGlmYFCPA==
    </data>
    <key>City of Birth</key>
    <string>Springfield</string>
    <key>Name</key>
    <string>John Doe</string>
    <key>Kids&apos; Names</key>
    <array>
        <string>John</string>
        <string>Kyra</string>
    </array>
    <key>Pangram</key>
    <data>
    VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZy4gIA
    </data>
    <key>FollowingKeyDataLengthIsInsane</key>
    <true/>
    <key>&apos;Lorem Ipsum&apos; Sample Text</key>
    <data>
    TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdCwg
    c2VkIGRvIGVpdXNtb2QgdGVtcG9yIGluY2lkaWR1bnQgdXQgbGFib3JlIGV0IGRvbG9yZSBtYWdu
    YSBhbGlxdWEuIFV0IGVuaW0gYWQgbWluaW0gdmVuaWFtLCBxdWlzIG5vc3RydWQgZXhlcmNpdGF0
    aW9uIHVsbGFtY28gbGFib3JpcyBuaXNpIHV0IGFsaXF1aXAgZXggZWEgY29tbW9kbyBjb25zZXF1
    YXQuIER1aXMgYXV0ZSBpcnVyZSBkb2xvciBpbiByZXByZWhlbmRlcml0IGluIHZvbHVwdGF0ZSB2
    ZWxpdCBlc3NlIGNpbGx1bSBkb2xvcmUgZXUgZnVnaWF0IG51bGxhIHBhcmlhdHVyLiBFeGNlcHRl
    dXIgc2ludCBvY2NhZWNhdCBjdXBpZGF0YXQgbm9uIHByb2lkZW50LCBzdW50IGluIGN1bHBhIHF1
    aSBvZmZpY2lhIGRlc2VydW50IG1vbGxpdCBhbmltIGlkIGVzdCBsYWJvcnVtLgpDdXJhYml0dXIg
    cHJldGl1bSB0aW5jaWR1bnQgbGFjdXMuIE51bGxhIGdyYXZpZGEgb3JjaSBhIG9kaW8uIE51bGxh
    bSB2YXJpdXMsIHR1cnBpcyBldCBjb21tb2RvIHBoYXJldHJhLCBlc3QgZXJvcyBiaWJlbmR1bSBl
    bGl0LCBuZWMgbHVjdHVzIG1hZ25hIGZlbGlzIHNvbGxpY2l0dWRpbiBtYXVyaXMuIEludGVnZXIg
    aW4gbWF1cmlzIGV1IG5pYmggZXVpc21vZCBncmF2aWRhLiBEdWlzIGFjIHRlbGx1cyBldCByaXN1
    cyB2dWxwdXRhdGUgdmVoaWN1bGEuIERvbmVjIGxvYm9ydGlzIHJpc3VzIGEgZWxpdC4gRXRpYW0g
    dGVtcG9yLiBVdCB1bGxhbWNvcnBlciwgbGlndWxhIGV1IHRlbXBvciBjb25ndWUsIGVyb3MgZXN0
    IGV1aXNtb2QgdHVycGlzLCBpZCB0aW5jaWR1bnQgc2FwaWVuIHJpc3VzIGEgcXVhbS4gTWFlY2Vu
    YXMgZmVybWVudHVtIGNvbnNlcXVhdCBtaS4gRG9uZWMgZmVybWVudHVtLiBQZWxsZW50ZXNxdWUg
    bWFsZXN1YWRhIG51bGxhIGEgbWkuIER1aXMgc2FwaWVuIHNlbSwgYWxpcXVldCBuZWMsIGNvbW1v
    ZG8gZWdldCwgY29uc2VxdWF0IHF1aXMsIG5lcXVlLiBBbGlxdWFtIGZhdWNpYnVzLCBlbGl0IHV0
    IGRpY3R1bSBhbGlxdWV0LCBmZWxpcyBuaXNsIGFkaXBpc2Npbmcgc2FwaWVuLCBzZWQgbWFsZXN1
    YWRhIGRpYW0gbGFjdXMgZWdldCBlcmF0LiBDcmFzIG1vbGxpcyBzY2VsZXJpc3F1ZSBudW5jLiBO
    dWxsYW0gYXJjdS4gQWxpcXVhbSBjb25zZXF1YXQuIEN1cmFiaXR1ciBhdWd1ZSBsb3JlbSwgZGFw
    aWJ1cyBxdWlzLCBsYW9yZWV0IGV0LCBwcmV0aXVtIGFjLCBuaXNpLiBBZW5lYW4gbWFnbmEgbmlz
    bCwgbW9sbGlzIHF1aXMsIG1vbGVzdGllIGV1LCBmZXVnaWF0IGluLCBvcmNpLiBJbiBoYWMgaGFi
    aXRhc3NlIHBsYXRlYSBkaWN0dW1zdC4=
    </data>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>Year Of Birth</key>
    <integer>1965</integer>
    <key>Pets' Names</key>
    <array/>
    <key>Picture</key>
    <data>
        PEKBpYGlmYFCPA==
    </data>
    <key>City of Birth</key>
    <string>Springfield</string>
    <key>Name</key>
    <string>John Doe</string>
    <key>Kids' Names</key>
    <array>
      <string>John</string>
      <string>Kyra</string>
    </array>
    <key>Pangram</key>
    <data>
    VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZy4gIA
    </data>
    <key>FollowingKeyDataLengthIsInsane</key>
    <true/>
    <key>'Lorem Ipsum' Sample Text</key>
    <data>
    TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdCwg
    c2VkIGRvIGVpdXNtb2QgdGVtcG9yIGluY2lkaWR1bnQgdXQgbGFib3JlIGV0IGRvbG9yZSBtYWdu
    YSBhbGlxdWEuIFV0IGVuaW0gYWQgbWluaW0gdmVuaWFtLCBxdWlzIG5vc3RydWQgZXhlcmNpdGF0
    aW9uIHVsbGFtY28gbGFib3JpcyBuaXNpIHV0IGFsaXF1aXAgZXggZWEgY29tbW9kbyBjb25zZXF1
    YXQuIER1aXMgYXV0ZSBpcnVyZSBkb2xvciBpbiByZXByZWhlbmRlcml0IGluIHZvbHVwdGF0ZSB2
    ZWxpdCBlc3NlIGNpbGx1bSBkb2xvcmUgZXUgZnVnaWF0IG51bGxhIHBhcmlhdHVyLiBFeGNlcHRl
    dXIgc2ludCBvY2NhZWNhdCBjdXBpZGF0YXQgbm9uIHByb2lkZW50LCBzdW50IGluIGN1bHBhIHF1
    aSBvZmZpY2lhIGRlc2VydW50IG1vbGxpdCBhbmltIGlkIGVzdCBsYWJvcnVtLgpDdXJhYml0dXIg
    cHJldGl1bSB0aW5jaWR1bnQgbGFjdXMuIE51bGxhIGdyYXZpZGEgb3JjaSBhIG9kaW8uIE51bGxh
    bSB2YXJpdXMsIHR1cnBpcyBldCBjb21tb2RvIHBoYXJldHJhLCBlc3QgZXJvcyBiaWJlbmR1bSBl
    bGl0LCBuZWMgbHVjdHVzIG1hZ25hIGZlbGlzIHNvbGxpY2l0dWRpbiBtYXVyaXMuIEludGVnZXIg
    aW4gbWF1cmlzIGV1IG5pYmggZXVpc21vZCBncmF2aWRhLiBEdWlzIGFjIHRlbGx1cyBldCByaXN1
    cyB2dWxwdXRhdGUgdmVoaWN1bGEuIERvbmVjIGxvYm9ydGlzIHJpc3VzIGEgZWxpdC4gRXRpYW0g
    dGVtcG9yLiBVdCB1bGxhbWNvcnBlciwgbGlndWxhIGV1IHRlbXBvciBjb25ndWUsIGVyb3MgZXN0
    IGV1aXNtb2QgdHVycGlzLCBpZCB0aW5jaWR1bnQgc2FwaWVuIHJpc3VzIGEgcXVhbS4gTWFlY2Vu
    YXMgZmVybWVudHVtIGNvbnNlcXVhdCBtaS4gRG9uZWMgZmVybWVudHVtLiBQZWxsZW50ZXNxdWUg
    bWFsZXN1YWRhIG51bGxhIGEgbWkuIER1aXMgc2FwaWVuIHNlbSwgYWxpcXVldCBuZWMsIGNvbW1v
    ZG8gZWdldCwgY29uc2VxdWF0IHF1aXMsIG5lcXVlLiBBbGlxdWFtIGZhdWNpYnVzLCBlbGl0IHV0
    IGRpY3R1bSBhbGlxdWV0LCBmZWxpcyBuaXNsIGFkaXBpc2Npbmcgc2FwaWVuLCBzZWQgbWFsZXN1
    YWRhIGRpYW0gbGFjdXMgZWdldCBlcmF0LiBDcmFzIG1vbGxpcyBzY2VsZXJpc3F1ZSBudW5jLiBO
    dWxsYW0gYXJjdS4gQWxpcXVhbSBjb25zZXF1YXQuIEN1cmFiaXR1ciBhdWd1ZSBsb3JlbSwgZGFw
    aWJ1cyBxdWlzLCBsYW9yZWV0IGV0LCBwcmV0aXVtIGFjLCBuaXNpLiBBZW5lYW4gbWFnbmEgbmlz
    bCwgbW9sbGlzIHF1aXMsIG1vbGVzdGllIGV1LCBmZXVnaWF0IGluLCBvcmNpLiBJbiBoYWMgaGFi
    aXRhc3NlIHBsYXRlYSBkaWN0dW1zdC4=
    </data>
  </dict>
</plist>

这不是我想要的,但为了完整起见我添加了它。)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Year Of Birth</key>
    <integer>1965</integer>
  <key>Pets&apos; Names</key>
    <array />
  <key>Picture</key>
    <data>
      PEKBpYGlmYFCPA==
    </data>
  <key>City of Birth</key>
    <string>Springfield</string>
  <key>Name</key>
    <string>John Doe</string>
  <key>Kids&apos; Names</key>
    <array>
      <string>John</string>
      <string>Kyra</string>
    </array>
  <key>Pangram</key>
    <data>
      VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZy4gIA
    </data>
  <key>FollowingKeyDataLengthIsInsane</key>
    <true />
  <key>&apos;Lorem Ipsum&apos; Sample Text</key>
    <data>
      TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdCwg
      c2VkIGRvIGVpdXNtb2QgdGVtcG9yIGluY2lkaWR1bnQgdXQgbGFib3JlIGV0IGRvbG9yZSBtYWdu
      YSBhbGlxdWEuIFV0IGVuaW0gYWQgbWluaW0gdmVuaWFtLCBxdWlzIG5vc3RydWQgZXhlcmNpdGF0
      aW9uIHVsbGFtY28gbGFib3JpcyBuaXNpIHV0IGFsaXF1aXAgZXggZWEgY29tbW9kbyBjb25zZXF1
      YXQuIER1aXMgYXV0ZSBpcnVyZSBkb2xvciBpbiByZXByZWhlbmRlcml0IGluIHZvbHVwdGF0ZSB2
      ZWxpdCBlc3NlIGNpbGx1bSBkb2xvcmUgZXUgZnVnaWF0IG51bGxhIHBhcmlhdHVyLiBFeGNlcHRl
      dXIgc2ludCBvY2NhZWNhdCBjdXBpZGF0YXQgbm9uIHByb2lkZW50LCBzdW50IGluIGN1bHBhIHF1
      aSBvZmZpY2lhIGRlc2VydW50IG1vbGxpdCBhbmltIGlkIGVzdCBsYWJvcnVtLgpDdXJhYml0dXIg
      cHJldGl1bSB0aW5jaWR1bnQgbGFjdXMuIE51bGxhIGdyYXZpZGEgb3JjaSBhIG9kaW8uIE51bGxh
      bSB2YXJpdXMsIHR1cnBpcyBldCBjb21tb2RvIHBoYXJldHJhLCBlc3QgZXJvcyBiaWJlbmR1bSBl
      bGl0LCBuZWMgbHVjdHVzIG1hZ25hIGZlbGlzIHNvbGxpY2l0dWRpbiBtYXVyaXMuIEludGVnZXIg
      aW4gbWF1cmlzIGV1IG5pYmggZXVpc21vZCBncmF2aWRhLiBEdWlzIGFjIHRlbGx1cyBldCByaXN1
      cyB2dWxwdXRhdGUgdmVoaWN1bGEuIERvbmVjIGxvYm9ydGlzIHJpc3VzIGEgZWxpdC4gRXRpYW0g
      dGVtcG9yLiBVdCB1bGxhbWNvcnBlciwgbGlndWxhIGV1IHRlbXBvciBjb25ndWUsIGVyb3MgZXN0
      IGV1aXNtb2QgdHVycGlzLCBpZCB0aW5jaWR1bnQgc2FwaWVuIHJpc3VzIGEgcXVhbS4gTWFlY2Vu
      YXMgZmVybWVudHVtIGNvbnNlcXVhdCBtaS4gRG9uZWMgZmVybWVudHVtLiBQZWxsZW50ZXNxdWUg
      bWFsZXN1YWRhIG51bGxhIGEgbWkuIER1aXMgc2FwaWVuIHNlbSwgYWxpcXVldCBuZWMsIGNvbW1v
      ZG8gZWdldCwgY29uc2VxdWF0IHF1aXMsIG5lcXVlLiBBbGlxdWFtIGZhdWNpYnVzLCBlbGl0IHV0
      IGRpY3R1bSBhbGlxdWV0LCBmZWxpcyBuaXNsIGFkaXBpc2Npbmcgc2FwaWVuLCBzZWQgbWFsZXN1
      YWRhIGRpYW0gbGFjdXMgZWdldCBlcmF0LiBDcmFzIG1vbGxpcyBzY2VsZXJpc3F1ZSBudW5jLiBO
      dWxsYW0gYXJjdS4gQWxpcXVhbSBjb25zZXF1YXQuIEN1cmFiaXR1ciBhdWd1ZSBsb3JlbSwgZGFw
      aWJ1cyBxdWlzLCBsYW9yZWV0IGV0LCBwcmV0aXVtIGFjLCBuaXNpLiBBZW5lYW4gbWFnbmEgbmlz
      bCwgbW9sbGlzIHF1aXMsIG1vbGVzdGllIGV1LCBmZXVnaWF0IGluLCBvcmNpLiBJbiBoYWMgaGFi
      aXRhc3NlIHBsYXRlYSBkaWN0dW1zdC4=
    </data>
</dict>
</plist>

FWIW,以下是您可以自己缩进输出的方法:

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="no"/>
<xsl:strip-space elements="*"/>

<xsl:param name="indent-unit" select="'&#9;'"/>

<xsl:template match="*">
    <xsl:param name="indent" select="'&#10;'"/>
    <xsl:value-of select="$indent"/>
    <xsl:copy>
        <xsl:copy-of select="@*"/>
        <xsl:apply-templates>
            <xsl:with-param name="indent" select="concat($indent, $indent-unit)"/>
        </xsl:apply-templates>
    </xsl:copy>
    <xsl:if test="not(following-sibling::*)">
        <xsl:value-of select="substring($indent, 1, string-length($indent) - string-length($indent-unit))"/>
    </xsl:if>
</xsl:template>

<xsl:template match="*[preceding-sibling::*[1][self::key]]">
    <xsl:param name="indent"/>
    <xsl:value-of select="concat($indent, $indent-unit)"/>
    <xsl:copy>
        <xsl:copy-of select="@*"/>
        <xsl:apply-templates>
            <xsl:with-param name="indent" select="concat($indent, $indent-unit, $indent-unit)"/>
        </xsl:apply-templates>
    </xsl:copy>
    <xsl:if test="not(following-sibling::*)">
        <xsl:value-of select="substring($indent, 1, string-length($indent) - string-length($indent-unit))"/>
    </xsl:if>
</xsl:template>

</xsl:stylesheet>

演示https://xsltfiddle.liberty-development.net/pNmC4J6