여러 XML 파일을 XSLT를 사용하여 내용에 따라 별도의 폴더로 나눕니다.

16031 단어 xpathXMLXSLT

소개



다수 있는 XML 파일을, 내용을 해석한 결과로 개별의 폴더에 배분하는 방법을 생각했습니다.
처리 대상의 XML 파일군은, 미리 임의의 폴더에 저장되어 있습니다.

해결책


  • XML을 구문 분석해야 하기 때문에 XSLT에서 처리합니다.
  • XML 파일을 얻으려면 XPath2.0의 Collection 함수를 사용하여 변수에 저장합니다.
  • 변수에 담은 XML 파일군을 해석해, 새롭게 폴더를 작성해 거기에 새로운 XML을 처리 대상의 XML의 명칭·내용으로 작성합니다.
  • XML을 작성하기 위해 XSLT의 result-document 요소를 사용한다.

  • 환경


  • XPath 2.0
  • XSLT 2.0
  • XSLT 프로세서 SaxonHE 9.9.1.6J
  • OS macOS Mojave

  • XML 파일



    XSLT를 작동하려면 XML(편의상 트리거 XML이라고 함)이 필요합니다.
    그러므로, 분배하고 싶은 XML군(같게 타겟 XML군)과는 별도로 트리거 XML(여기에서는 trigger.xml)을 준비합니다.
    타겟 XML군은 samples 폴더에 들어가 트리거 XML과 같은 디렉토리에 둡니다.


    대상 XML 그룹



    이번에는 문서 구조는 공통된 것으로 합니다.

    cucumber.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <properties>
     <type>vegetable</type>
     <color>green</color>
    </properties>
    

    tomato.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <properties>
     <type>vegetable</type>
     <color>red</color>
    </properties>
    

    strawberry.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <properties>
     <type>fruit</type>
     <color>red</color>
    </properties>
    

    kiwi.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <properties>
     <type>fruit</type>
     <color>green</color>
    </properties>
    

    트리거 XML



    트리거 XML 자체는 처리 대상이 아니므로, 정형식이라도 하면 됩니다. 적당히 루트 요소를 하나 만들어 둡니다.

    trigger.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <root/>
    

    XSLT



    타겟 XML군을 포함한 폴더의 패스를, 파라미터로서 건네줍니다. 트리거 XML의 상대 경로입니다.
    외부 파라미터로서 이 폴더의 패스를 주는 것도 가능합니다.

    XMLFileSorter.xsl
    <xsl:stylesheet XMLns:xsl="http://www.w3.org/1999/XSL/Transform"
        XMLns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs" version="2.0">
        <!--処理したいXMLを収めたフォルダのパスを収めたパラメータ-->
        <xsl:param name="extPath" required="no" as="xs:string">./samples</xsl:param>
    

    트리거 XML의 루트 노드와 일치하여 대상 XML 그룹을 처리하는 템플릿입니다.
        <xsl:template match="/">
            <!--トリガーXMLが配置されたuriを変数varDirnameに格納。後々の処理で使用-->
            <xsl:variable name="varDirname" select="resolve-uri('.',document-uri(/))" as="xs:anyURI"/>
    
            <!--ターゲットXML群の中身を、変数varXMLFilesにリストとして収める-->
            <xsl:variable name="varXMLFiles" as="element()+">
            <!--内容は後述-->
            </xsl:variable>
    
            <!--変数varXMLFiles内の各要素を処理して、XMLファイルを適宜フォルダに振り分けて出力する-->
            <xsl:for-each select="$varXMLFiles">
            <!--内容は後述-->
            </xsl:for-each>
        </xsl:template>
    

    템플릿의 변수 varXMLFiles의 파트입니다.
    타겟 XML군의 하나하나의 XML 파일에 대해서, 그 명칭과 내용을 취득합니다.
    사람 XML 파일을 사람 file 요소로, 그 바로 아래의 name 요소에 파일 이름을, contents 요소에 루트 노드 이하를 저장합니다.
            <!-- 
            collection関数
            第1引数(?の前) フォルダまたはファイルのuri。ここでは$varDirnameと/と$extpathを結合したもの
            第2引数(?の後) オプション。「;」区切りで複数のオプションを指定できる
                select 対象とするファイルを指定。ここではXMLを指定。ワイルドカードが使える
                recurse 再帰の有無。今回は再帰あり。
            -->
            <xsl:variable name="varXMLFiles" as="element()+">
                <!--ターゲットXML群-->
                <xsl:for-each select="collection(concat($varDirname ,'/',$extPath, '?select=*.xml;recurse=yes'))">
                    <!--ひとXMLファイル-->
                    <file>
                        <!--ファイル名を格納-->
                        <name>
                            <!--ターゲットXMLのuriを / でスプリットしたリストの末尾-->
                            <xsl:value-of select="tokenize(document-uri(/), '/')[last()]"/>
                        </name>
                        <!--ルートノード以下を取得-->
                        <contents>
                            <xsl:sequence select="self::node()"/>
                        </contents>
                    </file>
                </xsl:for-each>
            </xsl:variable>
    

    템플릿의 변수 varXMLFiles를 처리하여 XML 파일을 나누는 부분입니다.
    여기에서는 각 타겟 XML이 가지고 있던 type 요소 및 color 요소에 대해 그 값 마다 폴더를 만들고 그 안에 새롭게 XML 파일을 출력합니다.
    폴더 작성 위치는 트리거 XML과 동일한 디렉토리입니다.
    이 출력된 XML 파일은, 명칭·내용 모두 타겟 XML과 동일해, 결과적으로 타겟 XML군을 배분한 것이 됩니다.
    이번에는 단일 구조의 여러 대상 XML을 처리하고 있습니다만, 이 for-each 내부의 기술에 따라 구조가 다른 XML에서도 구분할 수 있습니다.
            <xsl:for-each select="$varXMLFiles">
                <xsl:variable name="varType" select="child::contents/child::properties/child::type/text()" as="text()"/>
                <xsl:variable name="varColor" select="child::contents/child::properties/child::color/text()" as="text()"/>
                <!--typeごとにフォルダを作り、name要素に格納しておいた名称でファイルを出力-->
                <xsl:result-document
                    href="{concat($varDirname ,'/',$varType,'/',child::name)}"
                    encoding="UTF-8" method="XML" indent="yes">
                    <!--contents要素下に置いたノードを出力-->
                    <xsl:sequence select="child::contents/child::node()"/>
                </xsl:result-document>
                <!--colorごとにフォルダを作り、name要素に格納しておいた名称でファイルを出力-->
                <xsl:result-document
                    href="{concat($varDirname ,'/',$varColor,'/',child::name)}"
                    encoding="UTF-8" method="XML" indent="yes">
                    <xsl:sequence select="child::contents/child::node()"/>
                </xsl:result-document>
            </xsl:for-each>
    

    XSLT의 전체는 다음과 같습니다.

    XMLFileSorter.xsl
    <?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet XMLns:xsl="http://www.w3.org/1999/XSL/Transform"
        XMLns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs" version="2.0">
        <xsl:param name="extPath" required="no" as="xs:string">./samples</xsl:param>
        <xsl:template match="/">
            <xsl:variable name="varDirname" select="resolve-uri('.',document-uri(/))" as="xs:anyURI"/>
            <xsl:variable name="varXMLFiles" as="element()+">
                <xsl:for-each select="collection(concat($varDirname ,'/',$extPath, '?select=*.xml;recurse=yes'))">
                    <file>
                        <name>
                            <xsl:value-of select="tokenize(document-uri(/), '/')[last()]"/>
                        </name>
                        <contents>
                            <xsl:sequence select="self::node()"/>
                        </contents>
                    </file>
                </xsl:for-each>
            </xsl:variable>
            <xsl:for-each select="$varXMLFiles">
                <xsl:variable name="varType" select="child::contents/child::properties/child::type/text()" as="text()"/>
                <xsl:variable name="varColor" select="child::contents/child::properties/child::color/text()" as="text()"/>
                <xsl:result-document
                    href="{concat($varDirname ,'/',$varType,'/',child::name)}"
                    encoding="UTF-8" method="XML" indent="yes">
                    <xsl:sequence select="child::contents/child::node()"/>
                </xsl:result-document>
                <xsl:result-document
                    href="{concat($varDirname ,'/',$varColor,'/',child::name)}"
                    encoding="UTF-8" method="XML" indent="yes">
                    <xsl:sequence select="child::contents/child::node()"/>
                </xsl:result-document>
            </xsl:for-each>
        </xsl:template>
    </xsl:stylesheet>
    

    실행



    터미널에서 Saxon을 실행합니다.$ java -jar path/to/saxonhe.jar -s:path/to/trigger.xml -xsl:path/to/XMLFileSorter.xsl처리할 XML이 들어 있는 폴더의 경로를 지정하려면 매개 변수를 사용합니다.$ java -jar path/to/saxonhe.jar -s:path/to/trigger.xml -xsl:path/to/XMLFileSorter.xsl extPath=path/to/folder

    실행 결과



    이와 같이, type로서 fruit·vegetable, color로서 green·red의 폴더가 작성되어 그 안에 적절히 나누어진 타겟 XML이 카피되고 있습니다.

    좋은 웹페이지 즐겨찾기