我可以使用 xmlstarlet 将 XML 中的字段转换为标签吗?

我可以使用 xmlstarlet 将 XML 中的字段转换为标签吗?

例如,我想将标签中的字段转换为该标签内的标签

<book name="Data Structure" price="250" pages="350"/>

<book name="Data Structure"> 
<price>250</price>
<pages>350</pages>
</book>

我想使用xmlstarlet或在 Linux 命令行中执行此操作sed

答案1

process.xsl:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" indent="yes"/>

  <xsl:template match="//book">
    <xsl:element name="book">
      <xsl:apply-templates select="./@*"/>
    </xsl:element>
  </xsl:template>

  <xsl:template match="book/@*">
      <xsl:if test="name() = 'name'">
    <xsl:attribute name="{name()}">
      <xsl:value-of select="."/>
    </xsl:attribute>
      </xsl:if>
      <xsl:if test="name() != 'name'">
    <xsl:element name="{name()}">
      <xsl:value-of select="."/>
    </xsl:element>
      </xsl:if>
  </xsl:template>
</xsl:stylesheet>

input.xml:

<book name="Data Structure" price="250" pages="350"/>

命令:

xsltproc process.xsl input.xml

输出:

<?xml version="1.0"?>
<book name="Data Structure">
  <price>250</price>
  <pages>350</pages>
</book>

答案2

我知道这个问题说的是“使用xmlstarletsed”,但是这些工具中的任何一个都需要大量的打字(并使用sed来修改任何不建议使用结构化文档格式)。还有其他支持 XML 的工具可以更轻松地完成这项工作。

假设一个 XML 文档如

<root>
<book name="Data Structure 1" price="250" pages="350"/>
<book name="Data Structure 2" price="350" pages="250"/>
<book name="Data Structure 3" price="450" pages="150"/>
</root>

然后你可以使用xqyqYAML解析器包装器的jq一部分)https://kislyuk.github.io/yq/) 使用表达式做您想做的事情jq

xq工具将示例 XML 文档解析为等效的 JSON 文档:

{
  "root": {
    "book": [
      {
        "@name": "Data Structure 1",
        "@price": "250",
        "@pages": "350"
      },
      {
        "@name": "Data Structure 2",
        "@price": "350",
        "@pages": "250"
      },
      {
        "@name": "Data Structure 3",
        "@price": "450",
        "@pages": "150"
      }
    ]
  }
}

应用以下表达式对数组进行迭代,通过从不是 的键中.root.book[]删除首字母来修改每个 JSON 元素的键。名称中带有首字母缩写的键对应于 XML 中的属性,因此删除会将键变成 XML 节点而不是节点的属性。@@name@@

xq -x '.root.book[] |= (with_entries(select(.key != "@name").key |= ltrimstr("@")))' file.xml

使用上面我自己的示例文件,这会产生

<root>
  <book name="Data Structure 1">
    <price>250</price>
    <pages>350</pages>
  </book>
  <book name="Data Structure 2">
    <price>350</price>
    <pages>250</pages>
  </book>
  <book name="Data Structure 3">
    <price>450</price>
    <pages>150</pages>
  </book>
</root>

如果您的 XML 文档实际上是单个节点

<book name="Data Structure" price="250" pages="350"/>

然后使用

xq -x '.book |= (with_entries(select(.key != "@name").key|=ltrimstr("@")))' file.xml

这与上面的表达式相同,但仅应用于顶级.book部分而不是数组的元素.root.book[]

答案3

请 - 不要使用sed- 它不是适合这项工作的工具。

我自己会使用 Perl:

#!/usr/bin/env perl
use strict;
use warnings;

use XML::Twig;

my $twig = XML::Twig->new( 'pretty_print' => 'indented_a' );
$twig->parsefile ( 'your_file.xml' );

foreach my $thing ( $twig -> root -> children ) {

    my $newthing = $twig -> root -> insert_new_elt($thing->tag);
    foreach my $key ( keys %{$thing -> atts()} ) {
        $newthing -> insert_new_elt($key, $thing -> att($key));
    }
    $thing -> delete;
}

$twig->print;

输出:

<root>
  <book>
    <pages>350</pages>
    <name>Data Structure</name>
    <price>250</price>
  </book>
</root>

这非常简单,因为我们正在使用(匿名)哈希att()。要挑选一个属性,我们必须做更多的事情 - 我们需要定义我们想要的属性保持 name并将其作为父元素的属性插入。

这个使用map可能有点令人头疼:

#!/usr/bin/env perl
use strict;
use warnings;
use Data::Dumper;

use XML::Twig;

my %keep_att = ( name => 1 );
my $twig = XML::Twig->new( 'pretty_print' => 'indented_a' );
$twig->parse( \*DATA );

foreach my $thing ( $twig->root->children ) {
    my $newthing = $twig->root->insert_new_elt( $thing->tag,
        { map { $_ => $thing->att($_) } keys %keep_att } );

    foreach my $key ( keys %{ $thing->atts() } ) {
        next if $keep_att{$key};
        $newthing->insert_new_elt( $key, $thing->att($key) );
    }
    $thing->delete;
}

$twig->print;

__DATA__
<root>
<book name="Data Structure" price="250" pages="350"/>
</root>

这会产生:

<root>
  <book name="Data Structure">
    <price>250</price>
    <pages>350</pages>
  </book>
</root>

现在,发生的事情map是我们基本上拆分出我们想要保留的属性 - 并将它们重新插入到我们的新元素中 - 以及我们想要保留的元素想要保留,并把它们变成孩子。

有点像这样:

foreach my $thing ( $twig->root->children ) {

    my %attributes = %{$thing->atts()};
    my %new_children; 
    foreach my $attr ( keys %attributes ) {
       if ( $keep_att{$attr} ) { 
           #leave it in %attributes; 
       }
       else {
           $new_children{$attr} = $attributes{$attr}; 
           delete $attributes{$attr}
       }
    }
    print Dumper \%attributes;
    print Dumper \%new_children;

    my $newthing = $twig->root->insert_new_elt( $thing->tag,
        { %attributes } );

    foreach my $key ( keys %new_children ) {
        $newthing->insert_new_elt( $key, $new_children{$key} );
    }
    $thing->delete;
}

答案4

我想在 Linux 命令行中使用xmlstarletor执行此操作sed

使用xmlstarlet1.6.1 和您的输入 XML 文件,以下命令将生成您想要的输出:

xmlstarlet edit --omit-decl --var T 'book' \
 -s '$T' -t elem -n 'price'  -u '$prev' -x 'string(../@price)'  -d '$T/@price' \
 -s '$T' -t elem -n 'pages'  -u '$prev' -x 'string(../@pages)'  -d '$T/@pages' \
file.xml

在哪里

  • 该变量包含要转换的元素T的节点集;如果输入有s 包含在根元素中,则book使用输入文件中的单个元素book(或*)即可; 选择全部,第一个book*/book//book(//book)[1]
  • -s/创建一个名为--subnode( ) 的元素( ) 子节点作为每个元素的属性-t elem-n$T
  • -u/使用相对 XPath 表达式 ( ) 在每个新创建的元素 ( )中--update插入值$prev-x
  • -d/转换后--delete删除每个元素中的属性$T

xmlstarlet edit代码可以使用便利$prev(又名 $xstar:prev)节点来引用由最近的 -i/ --insert-a/--append-s/--subnode选项创建的节点。的例子$prev文档/xmlstarlet.txt和源代码的 示例/ed-backref*


name要列出所有s的属性名称(除 之外)以book消除重复项,例如对于脚本生成器,您可以说,

xmlstarlet select -t \
  -m '//book/@*[name() != "name"]' -v 'name()' -n \
file.xml | 
awk '!seen[$1]++'

xmlstarlet或者,除了支持 EXSLT 之外不使用任何工具动态:地图 功能:

xmlstarlet select -t \
  --var T='//book/@*[name() != "name"]' \
  -m 'set:distinct(dyn:map($T,"name()"))' -v . -n \
file.xml

相关内容