

下記の(例1.変換前)のようなXML形式のファイルから、  
testを含む要素を丸っと削除したいと考えています。

testを含む要素が2000個ほどまばらに存在し、  
繰り返し行う作業のため、絶対自動化したいのですが、  
sedを使ってどう表現すればいいのかずっと頭を抱えています。

(例1.変換前) → testを含む要素を削除 → (例2.変換後)

このtestを含む要素の削除方法について、  
アドバイスをいただけないでしょうか。

実装言語としては、シェルスクリプトを想定していますが、ツール、言語等問いません。

恐らく実装された経験がある方、類似の方法を検索する際に必要なキーワードがピンとくる方からすれば楽勝案件なのでは・・・と思っています。そのため、ピンとくるキーワードだけの解答でも大変助かります。

(例1.変換前)

[code]

    <dependency>
          <groupId>com.fasterxml.jackson.core</groupId>
          <artifactId>jackson-annotations</artifactId>
          <version>${jackson.version}</version>
          <type>jar</type>
          <scope>compile</scope>
    </dependency>
    <dependency>
          <groupId>com.fasterxml.jackson.core</groupId>
          <artifactId>jackson-annotations</artifactId>
          <version>${jackson.version}</version>
          <type>test</type>
          <scope>compile</scope>
    </dependency>
    
[/code]

(例2.変換後)

[code]

    <dependency>
          <groupId>com.fasterxml.jackson.core</groupId>
          <artifactId>jackson-annotations</artifactId>
          <version>${jackson.version}</version>
          <type>jar</type>
          <scope>compile</scope>
    </dependency>
    
[/code]


CC BY-SA 3.0
2017-08-31T06:11:04.857
37636
2018-01-10T00:40:07.517
2017-08-31T06:31:52.450
21092
25128
question
shellscript xml sed
[言語不問]XML中の、特定文字列を持つ子要素を持つ親要素を消す方法


XMLパーサーのライブラリを使ったプログラムをつくった方がいいかもしれませんね。javaで **最低限動くもの** だったら、こんな感じでいいと思います。

[code]

    import java.io.File;
    import java.io.FileOutputStream;
    
    import javax.xml.parsers.DocumentBuilder;
    import javax.xml.parsers.DocumentBuilderFactory;
    import javax.xml.transform.Transformer;
    import javax.xml.transform.TransformerFactory;
    import javax.xml.transform.dom.DOMSource;
    import javax.xml.transform.stream.StreamResult;
    
    import org.w3c.dom.Document;
    import org.w3c.dom.Element;
    import org.w3c.dom.Node;
    import org.w3c.dom.NodeList;
    
    public class RemoveTestDependencies {
        public static void main(String[] args) {
            try {
                DocumentBuilderFactory dbfactory = DocumentBuilderFactory.newInstance();
                DocumentBuilder builder = dbfactory.newDocumentBuilder();
                Document doc = builder.parse(new File(args[0]));
                Element root = doc.getDocumentElement();
                NodeList childNodes = root.getChildNodes();
                for (int i = 0; i < childNodes.getLength(); i++) {
                    Node item = childNodes.item(i);
                    if ("dependencies".equals(item.getNodeName())) {
                        NodeList childNodes2 = item.getChildNodes();
                        int length = childNodes2.getLength();
                        for (int j = 0; j < childNodes2.getLength(); j++) {
                            Node item2 = childNodes2.item(j);
                            if ("dependency".equals(item2.getNodeName())) {
                                NodeList childNodes3 = item2.getChildNodes();
                                for (int k = 0; k < childNodes3.getLength(); k++) {
                                    Node item3 = childNodes3.item(k);
                                    if (item3 != null && "type".equals(item3.getNodeName())
                                            && item3.getTextContent() != null
                                            && item3.getTextContent().indexOf("test") >= 0) {
                                        item.removeChild(item2);
                                    }
                                }
                            }
                        }
                        System.out.println("Removed "+ (length - childNodes2.getLength()) + " dependencies");
                    }
                }
                TransformerFactory transFactory = TransformerFactory.newInstance();
                Transformer transformer = transFactory.newTransformer();
                DOMSource source = new DOMSource(doc);
                File newXML = new File(args[1]);
                FileOutputStream os = new FileOutputStream(newXML);
                StreamResult result = new StreamResult(os);
                transformer.transform(source, result);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
[/code]

引数に修正前後のpom.xmlのパスを渡します。

[code]

    java RemoveTestDependencies C:\test\pom.xml C:\test\pom_new.xml
    
[/code]

※適当に書いたので、例外処理などはちゃんと実装する必要があります。


CC BY-SA 3.0
2017-08-31T09:47:48.923
37640
2017-08-31T09:47:48.923
21092
37636
answer


`sed` は行指向のコマンドですので、XML のようなフォーマットを扱うのは非常に難しく、殆ど不可能です。  
シェルスクリプトで XML を扱うには、

  * XML 専用のコマンドを使用する
  * XML を行指向のフォーマットに変換してから処理する
  * 該当部分だけ、XML を扱うライブラリが用意されているプログラミング言語でコマンドとして作成する

といった事が必要です。  
XML 専用のコマンドをいくつか紹介すると、`xsltproc`(XSLT
を覚える必要がある)、`xmllint`(今回の例には力不足か)、`xmlstarlet`(xpath で簡単な編集までならできる)などです。

`xmlstarlet` を使用する例:

[code]

    # この xpath が質問の要件を満たしているかは、十分に検討していません
    xmlstarlet ed -d '//dependency[type[text()="test"]]' in.xml
    
[/code]


CC BY-SA 3.0
2017-08-31T09:57:40.003
37642
2017-08-31T09:57:40.003
3054
37636
answer

[code]

    ## GNU sed の場合
    $ sed --version
    sed (GNU sed) 4.4
    $ sed -nr '
        /<dependency>/,/<\/dependency>/{
          H
          /<\/dependency>/{
            x
            s/^\n//
            /<type>test<\/type>/!p
            s/.*//
            h
          }
        }' dependency.xml
    
    ## Awk の場合
    $ awk -v RS='</?dependency>' '
        /<type>/ && !/<type>test<\/type>/{
          print "<dependency>" $0 "</dependency>"
        }' dependency.xml
    
    ## GNU grep の場合
    $ grep --version
    grep (GNU grep) 3.1
    $ grep -Pzo '<dependency>((?!</dependency>)(.|\n))*?<type>((?!test<).)*?/type>(.|\n)*?</dependency>\n?' dependency.xml 
    
[/code]

GNU sed 版の場合、`<dependency>` タグは同一行になくて `<type>`
タグは同一行にあることが前提なので、かなり無理がありそうです(XML 文字列のフォーマットを強制しているので)。

Awk 版は `RS` 変数を使って `<dependency> 〜 </dependency>` ブロックを 1 論理行とみなして処理していて、GNU
grep 版では `negative lookahead((?!...))` を使っています。

全体的に「やればできないことはない」といった所でしょうかね。お勧めできませんけれども。


CC BY-SA 3.0
2017-08-31T12:43:01.940
37647
2017-08-31T15:05:47.867
2017-08-31T15:05:47.867
37636
answer


このようなフィルタリングはXSLTでやれば簡単ですね．御要望の条件を

「子要素のテキストに"test"を含むdependency要素を削除する．」

と解釈させていただきますと、次のようなXSLTスタイルシートで一発でできます．

[code]

    <?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        version="1.0">
    
        <!--子要素のテキストに"test"を含むdependency要素は読み飛ばす-->
        <xsl:template match="dependency[*[contains(.,'test')]]"/>
    
        <!-- 他は単純にコピーする -->
        <xsl:template match="*">
            <xsl:copy>
                <xsl:copy-of select="@*"/>
                <xsl:apply-templates/>
            </xsl:copy>
        </xsl:template>
    
    </xsl:stylesheet>
    
[/code]

XSLT 1.0の機能で十分です．私はSaxonでやりましたがWindowsに入っているMSXMLでもいけるはずです．


CC BY-SA 3.0
2017-09-01T00:31:47.423
37650
2017-09-01T00:31:47.423
9503
37636
answer