使用新标记更新大量xml文件

时间:2016-12-02 23:52:24

标签: java xml perl replace sed

我有大约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”命令。

有关实现此目标的最佳方法或工具的任何建议吗?

4 个答案:

答案 0 :(得分: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的新元素,最后重命名了它。确保输入文件中不存在此类元素。

答案 1 :(得分:1)

您已将其标记为perl,因此我将提供perl解决方案。我能提供的最佳建议通常是使用解析器,因为XML是一种可解析的语言,而且存在很好的语言。对于这类工作,我特别喜欢#include "libavutil/samplefmt.h" #include "libavutil/avutil.h" #include "libavutil/cpu.h" #include "libavutil/dict.h" #include "libavutil/log.h" #include "libavutil/pixfmt.h" #include "libavutil/rational.h" #include "libavutil/version.h" XML::Twig也很不错,但不进行现场编辑)。

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

这里有一堆完全有效的XML更改,比如一元标记,缩进和行拆分,它们在语义上保持一致,但会破坏正则表达式。因此,有人做出的未来变化 - 就他们所关注的而言,重新格式化XML是有效/无关的 - 将会破坏下游的变化。因为你的脚本没有正确处理它。此外 - XML::LibXML与正则表达式非常相似,但上下文,因此非常适合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::Twig方法:

parsefile_inplace

答案 2 :(得分:0)

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

它假设您在单独的行上有标记,并且应该在每个条目键=“mergeTemplates”之后插入新标记(如果不是,根据具体情况,可以稍微修改代码以使用带有分块读取的匹配而不是行或由两行读取以检测第二个标记。)

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 ++)和find and replace in files命令。只需将行<entry key="mergeTemplates" value="false"/>替换为<entry key="mergeTemplates" value="false"/>\n..new entry

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

对“你怎么知道它不会成为通用系统”的问题进行评论,我非常有信心在开发通用生产系统时,没有人会要求“java,perl,unix sed或者其他工具”。

答案 3 :(得分:0)

使用sed这些东西相对容易:

您可以将地址与正则表达式匹配:

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

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

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

/^<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(免费BSD)将创建一个文件名加.bak的备份

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

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