更改标签中的文本,但只有标签包含在某个 XML 块中

更改标签中的文本,但只有标签包含在某个 XML 块中

使用 Git Bash,我尝试有条件地替换数百个文件中 yrot 标签中的内容,但前提是它属于与 wheel 相关的部件名称标签。

// YES, change

<part name="D_wheel1" seqNumber="1" >
  <yrot min="0.000000" max="0.000000" cur="0.000000" />
</part>

// YES, change 

<part name="D_wheel2" seqNumber="1" >
  <yrot min="0.000000" max="0.000000" cur="0.000000" />
</part>

// NO, don't change
<part name="door" seqNumber="1" >
  <yrot min="0.000000" max="0.000000" cur="0.000000" />
</part>

// Example Line Change
// From: <yrot min="0.000000" max="0.000000" cur="0.000000" />
// To:   <yrot min="INF" max="INF"/>

使用 awk 之类的工具是否可以实现这一点?或者我需要使用某种特殊的 XML 解析器吗?

编辑:需要明确的是,大约有十几个属于 的标签,其中一个是 .仅出现在标签内。我只想在名称包含“wheel”时替换该行。本身是嵌套的。

对于那些声称我需要 XML 解析器的人来说,如果满足条件(yrot 标签在轮子中),为什么简单的文本查找/替换不起作用?检查有那么难吗?

答案1

将您的 XML 提供data.xml为:

 $ cat data.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <root>
       <part name="D_wheel1" seqNumber="1">
          <yrot min="0.000000" max="0.000000" cur="0.000000" />
       </part>
       <part name="D_wheel2" seqNumber="1">
          <yrot min="0.000000" max="0.000000" cur="0.000000" />
       </part>
       <part name="door" seqNumber="1">
          <yrot min="0.000000" max="0.000000" cur="0.000000" />
       </part>
    </root>

xmlstarlet与使用X路径:

$ xmlstarlet ed \
    --var target '//part[contains(@name, "wheel")]/yrot' \
    -u '$target/@*[name()="min" or name()="max"]' -v 'INF' \
    -d '$target/@cur' data.xml

<?xml version="1.0" encoding="UTF-8"?>
<root>
  <part name="D_wheel1" seqNumber="1">
    <yrot min="INF" max="INF"/>
  </part>
  <part name="D_wheel2" seqNumber="1">
    <yrot min="INF" max="INF"/>
  </part>
  <part name="door" seqNumber="1">
    <yrot min="0.000000" max="0.000000" cur="0.000000"/>
  </part>
</root>

或者使用经典方法XSLT: 和xsltprocxmlstarlet

$ cat data.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" version="1.0" encoding="UTF-8" indent="yes"/>
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="*[contains(@name, 'wheel')]/yrot">
        <xsl:copy>
            <xsl:attribute name="min">INF</xsl:attribute>
            <xsl:attribute name="max">INF</xsl:attribute>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

$ xsltproc data.xsl data.xml #or: xmlstarlet tr data.xsl data.xml
<?xml version="1.0" encoding="UTF-8"?>
<root>
   <part name="D_wheel1" seqNumber="1">
      <yrot min="INF" max="INF"/>
   </part>
   <part name="D_wheel2" seqNumber="1">
      <yrot min="INF" max="INF"/>
   </part>
   <part name="door" seqNumber="1">
      <yrot min="0.000000" max="0.000000" cur="0.000000"/>
   </part>
</root>

答案2

使用python的ElementTree标准库:

#! /usr/bin/env python

import sys
import xml.etree.ElementTree as ET

def do_one(file_name):
    tree = ET.parse(file_name)

    for part in tree.findall("part"):
        if not 'wheel' in part.attrib['name']:
            continue
        for yrot in part.findall('yrot'):
            names = []
            for x in yrot.attrib:
                names.append(x)
            for x in names:
                del yrot.attrib[x]
            yrot.attrib['min'] = 'INF'
            yrot.attrib['max'] = 'INF'

    tree.write(file_name)

for file_name in sys.argv[1:]:
    do_one(file_name)

这将解析命令行上传递给脚本的所有文件:

python convert_xml.py *.xml

答案3

尝试使用“标准”unix 工具解析 XML 存在一个巨大的问题。 XML 是一种数据结构,它支持多种语义相同但不具有相同行和缩进的布局。

这意味着解析基于行/正则表达式确实是一个坏主意,因为您将创建一些根本上脆弱的代码。有人可能会在某个时候重组他们的 XML,而您的代码会无缘无故地崩溃。这种事情会给维护程序员和未来的系统管理员带来一些真正的痛苦。

所以,是的,请使用 XML 解析器。有多种选择 - 有人给了你一个 python 选项,所以我在这里也包括了 perl。

#!/usr/bin/perl

use strict;
use warnings;

use XML::Twig;

sub process_part {
    my ( $twig, $part ) = @_;
    if ( $part->att('name') =~ m/wheel/ ) {
        $part->first_child('yrot')->set_att( 'min', 'INF' );
        $part->first_child('yrot')->set_att( 'max', 'INF' );
    }
}

my $twig = XML::Twig->new(
    'pretty_print'  => 'indented_a',
    'twig_handlers' => { 'part' => \&process_part }
);
$twig->parsefile('your_file.xml');
$twig->print;

现在,至于“检查”文本很困难的原因 - 这些都是相同的:

<root>
  <part
      name="D_wheel1"
      seqNumber="1">
    <yrot
        cur="0.000000"
        max="0.000000"
        min="0.000000"
    />
  </part>
  <part
      name="D_wheel2"
      seqNumber="1">
    <yrot
        cur="0.000000"
        max="0.000000"
        min="0.000000"
    />
  </part>
  <part
      name="door"
      seqNumber="1">
    <yrot
        cur="0.000000"
        max="0.000000"
        min="0.000000"
    />
  </part>
</root>

和:

<root><part name="D_wheel1" seqNumber="1"><yrot cur="0.000000" max="0.000000" min="0.000000"/></part><part name="D_wheel2" seqNumber="1"><yrot cur="0.000000" max="0.000000" min="0.000000"/></part><part name="door" seqNumber="1"><yrot cur="0.000000" max="0.000000" min="0.000000"/></part></root>

和:

<root
><part
name="D_wheel1"
seqNumber="1"
><yrot
cur="0.000000"
max="0.000000"
min="0.000000"
/></part><part
name="D_wheel2"
seqNumber="1"
><yrot
cur="0.000000"
max="0.000000"
min="0.000000"
/></part><part
name="door"
seqNumber="1"
><yrot
cur="0.000000"
max="0.000000"
min="0.000000"
/></part></root>

它们在语义上都是相同的,但希望如您所见 - 不会解析相同的内容。像一元标签之类的东西 - 比如>

    <yrot
        cur="0.000000"
        max="0.000000"
        min="0.000000"
    />

对比:

        <yrot cur="0.000000" max="0.000000" min="0.000000" ></yrot>

而且 - 语义相同。那么你摆脱行和正则表达式的束缚,但这是一场赌博并构建了脆弱的代码。

答案4

使用 awk。请注意,这假设了一个非常简单的文件结构,如您所展示的那样。我不能保证它适用于任意 XLM 文件。事实上,我可以断然保证不会。

awk '{if(/<\/part>/){p=0}if($1~/<part/ && $2~/wheel/){p=1}
      if(p==1 && /<yrot/){
        print "<yrot min=\"INF\" max=\"INF\"/>"
      } else{print}}' file

但说真的,这非常脆弱。它假设name=始终是行上的第二个空格分隔字段,它会在嵌套标签和各种其他可能的复杂情况下中断。它会在您给出的示例中提供您想要的输出,但它会因您对文件所做的最微小的更改而中断。 Anthon 使用适当解析器的方法要安全得多。

相关内容