【问题标题】:Update a lot of xml files with a new tag使用新标签更新大量 xml 文件
【发布时间】:2017-04-17 23:31:07
【问题描述】:

我在一个文件夹中放置了大约 150 个 xml 文件,需要使用新标签进行更新。

当前:

<entry key="mergeTemplates" value="false"/>
<entry key="sysDescriptions"/>

新功能:

  <entry key="mergeTemplates" value="false"/>
  <entry key="requestable">
    <value>
      <Boolean>true</Boolean>
    </value>
  </entry>
  <entry key="sysDescriptions">

我确实尝试过 java 的“替换”方法。但没能完成。 在 Unix 上也尝试了“sed”命令。

对完成此任务的最佳方式或工具有何建议?

【问题讨论】:

    标签: java xml perl replace sed


    【解决方案1】:

    一般来说,您不应尝试使用面向行的工具来处理 XML 数据。改用xmlstarlet 之类的东西:

    xmlstarlet ed -i "//entry[@key='sysDescriptions']" -t elem -n "new_entry" \
        -i "//new_entry" -t attr -n "key" -v "requestable" \
        --subnode "//new_entry" -t elem -n "value" \
        --subnode "//new_entry/value" -t elem -n "Boolean" \
        --subnode "//new_entry/value/Boolean" -t text -n "dummy" -v "true" \
        -r "//new_entry" -v "entry" input.xml
    

    为了可读性,我插入了一个名为new_entry的新元素,最后将其重命名。确保您的输入文件中不存在此类元素。

    【讨论】:

    • 如果一个人只需要处理一堆具有众所周知格式的特定文件,实际上没有理由避免快速简单的纯文本处理。毕竟,XML 文件内容是通用文本的子集。
    • 我不同意。 XML 是上下文相关的,而正则表达式不是。因此,正则表达式解决方案总是脆弱而笨拙,因为XML 可以通过一系列完全有效的方式更改格式,从而破坏正则表达式。
    • 如果您开发库或生产系统,我完全同意。但是,如果您只需要使用特定数据更新您的特定文件,并不总是需要过度复杂化并设计所有的花里胡哨。在这种情况下,没有正则表达式,只需查找并替换文本行。
    • 我扩展了我的答案以解释为什么我认为纯文本处理在这里就足够了。
    【解决方案2】:

    您已将其标记为 perl,因此我将提供 perl 解决方案。我通常能提供的最好建议是使用解析器,因为 XML 是一种可解析的语言,而且存在好的语言。我特别喜欢XML::Twig 做这种工作(XML::LibXML 也不错,但不做就地编辑)。

    我强烈建议避免使用正则表达式 - XML is not well suited to parsing via regex, because it's contextual and regex isn't

    您可以对 XML 进行一系列完全有效的更改,例如一元标记、缩进和行拆分,使其在语义上保持相同,但会混乱地破坏正则表达式。因此,某人未来所做的更改——就他们而言是有效/微不足道的,比如重新格式化 XML——将破坏“下游”,因为您的脚本无法正确处理它。此外 - xpath 很像正则表达式,但 上下文相关的,因此非常适合 XML 解析/处理。

    #!/usr/bin/env perl
    use warnings;
    use strict;
    
    use XML::Twig;
    
    my $twig = XML::Twig -> parse (\*DATA); 
    
    my $to_insert = XML::Twig::Elt -> new (   'entry', {key => "requestable"} );
    $to_insert -> insert_new_elt ( 'value' ) -> insert_new_elt('Boolean', "true" );
    
    print "Generated new XML:\n";
    $to_insert -> print;
    
    my $insert_this = $to_insert -> cut;
    
    my $insert_after = $twig -> findnodes ('//entry[@key="mergeTemplates"]',0);
    $to_insert -> paste ( after => $insert_after );
    
    print "Generated XML:\n";
    $twig -> set_pretty_print('indented'); 
    $twig -> print;
    
    
    __DATA__
    <xml>
    <entry key="mergeTemplates" value="false"/>
    <entry key="sysDescriptions"/>
    </xml>
    

    这可以很方便地适应使用XML::Twigparsefile_inplace方法:

    #!/usr/bin/env perl
    use warnings;
    use strict;
    use XML::Twig;
    
    sub insert_merge {
       my ( $twig, $insert_after ) = @_;
    
       my $to_insert = XML::Twig::Elt->new( 'entry', { key => "requestable" } );
       $to_insert->insert_new_elt('value')->insert_new_elt( 'Boolean', "true" );
    
       $to_insert->paste( after => $insert_after );
       $twig -> flush;
    }
    
    my $twig =
      XML::Twig->new(
       twig_handlers => { '//entry[@key="mergeTemplates"]' => \&insert_merge },
       pretty_print => 'indented' );
    
     #glob finds files, if you want something more extensive then File::Find::Rule
    foreach my $filename ( glob ( "/path/to/dir/*xml" ) ) { 
        $twig->parsefile_inplace($filename); 
    }
    

    【讨论】:

      【解决方案3】:

      使用 sed,这些事情相对容易:

      您可以使用正则表达式匹配地址:

      /^<entry key="mergeTemplates" value="false"\/>$/
      

      看看有几个字符需要转义,因为它们具有特殊含义。还使用^(输入开始)和$(输入结束)。

      当你有一个地址时,你可以在 in 上运行命令,在这种情况下,我们需要 apend 命令:

      /^<entry key="mergeTemplates" value="false"\/>$/a\
      <entry key="requestable">\
        <value>\
          <Boolean>true</Boolean>\
        </value>\
      </entry>
      

      这就是完整的 sed 脚本。要运行它,您可以将其保存在一个文件 (insert_xml.sed) 中,并使用 sed -f:

      sed -f insert_xml.sed input_file.xml
      

      使用-i 标志进行就地编辑,它将是-i(GNU)或-i ''(免费BSD)。使用 -i.bak (GNU) 或 -i .bak (Free BSD) 将创建一个文件名加上 .bak 的备份

      然后为需要更新的文件编写一个for循环:

      for file in *.xml; do
        sed -i.bak -f insert_xml.sed "$file"
      done
      

      【讨论】:

      • 不是我的 DV,但猜测是因为用 regex 解析 XML 是一种非常糟糕的做法,因为您在 不是的语言上使用正则表达式 常规。
      • @Sobrique 是的,但有时可以简单地替换。
      【解决方案4】:

      这绝不是一个有效的解决方案,但它应该适用于 150 个文件。如果您有 SSD,它应该会在眨眼间完成。

      假设您在单独的行中有标签,并且应该在每个条目 key="mergeTemplates" 之后插入新标签(如果不是,根据情况,可以稍微修改代码以使用 Matcher 和分块读取而不是行或读取两行以检测第二个标签)。

      public void addTextAfterLine(String inputFolder, String prefixLine,
              String text) throws IOException {
          // iterate over files in input dir
          try (DirectoryStream<Path> dirStream = Files
                  .newDirectoryStream(new File(inputFolder).toPath())) {
              for (Path inputPath : dirStream) {
                  File inputFile = inputPath.toFile();
                  String inputFileName = inputFile.getName();
                  if (!inputFileName.endsWith(".xml") || inputFile.isDirectory())
                      continue;
                  File outputTmpFile = new File(inputFolder, inputFile.getName()
                          + ".tmp");
                  // read line by line and write to output
                  try (BufferedReader inputReader = new BufferedReader(
                          new InputStreamReader(new FileInputStream(inputFile),
                                  StandardCharsets.UTF_8));
                          BufferedWriter outputWriter = new BufferedWriter(
                                  new OutputStreamWriter(new FileOutputStream(
                                          outputTmpFile), StandardCharsets.UTF_8))) {
                      String line = inputReader.readLine();
                      while (line != null) {
                          outputWriter.write(line);
                          outputWriter.write('\n');
                          if (line.equals(prefixLine)) {
                              // add text after prefix line
                              outputWriter.write(text);
                          }
                          line = inputReader.readLine();
                      }
                  }
                  // delete original file and rename modified to original name
                  Files.delete(inputPath);
                  outputTmpFile.renameTo(inputFile);
              }
          }
      }
      
      public static void main(String[] args) throws IOException {
          final String inputFolder = "/tmp/xml/input";
          final String prefixLine = "<entry key=\"mergeTemplates\" value=\"false\"/>";
          final String newText = 
                  "<entry key=\"requestable\">\n"
                          + "    <value>\n"
                          + "      <Boolean>true</Boolean>\n"
                          + "    </value>\n"
                          + "</entry>\n"              
                  ;
          new TagInsertSample()
                  .addTextAfterLine(inputFolder, prefixLine, newText);
      }
      

      您还可以使用高级编辑器(例如 Windows 上的 Notepad++),以及在文件中查找和替换命令。只需将&lt;entry key="mergeTemplates" value="false"/&gt; 行替换为&lt;entry key="mergeTemplates" value="false"/&gt;\n..new entry

      这里有很多注意事项,您不应该使用文本处理工具来处理 XML。如果您正在开发通用系统或库来处理未知文件,这是正确的。但是,只要在已知格式的文件上完成任务,就不需要复杂的 XML,文本处理就可以了。

      以“你怎么知道它不会成为通用系统”的问题抢占 cmets,我非常有信心在开发通用生产系统时没有人会要求“java、perl、Unix sed 或任何其他工具”。

      【讨论】:

      • 代码就像一个魅力!我用 sed、perl、java 到处跑,试图把它弄好!!谢谢。
      • 我很高兴它有帮助。如果你愿意,你可以接受答案)
      猜你喜欢
      • 2020-05-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-08
      • 2021-12-31
      • 2012-01-02
      • 2010-09-15
      相关资源
      最近更新 更多