xmllint学習 #2 namespaceを持ったXMLのxpathでのパース方法

2019年7月5日

xmllint テクノロジー プログラミング 特集

まったくnamespaceでコントロールするXMLほど扱いにくいものはありません。 ネットでも、みんなパースするのに苦労しているようです。 でも、SVGもOfficeドキュメントも、東証資料のXBRLも、どれも複数のnamespaceで構成されていて、ここから逃げることはできなさそうです。 それなら、ちゃんと向き合ってnamespaceパースをxmllintでできるようにしておきたいと思います。

Officeドキュメントのパース

<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"><p:cSld><p:spTree><p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr><p:grpSpPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="0" cy="0"/><a:chOff x="0" y="0"/><a:chExt cx="0" cy="0"/></a:xfrm></p:grpSpPr><p:sp><p:nvSpPr><p:cNvPr id="2" name="Title 1"/><p:cNvSpPr><a:spLocks noGrp="1"/></p:cNvSpPr><p:nvPr><p:ph type="title"/></p:nvPr></p:nvSpPr><p:spPr/><p:txBody><a:bodyPr/><a:lstStyle/><a:p><a:r><a:rPr lang="en-US" dirty="0" smtClean="0"/><a:t>{page-2:title}</a:t></a:r><a:endParaRPr lang="en-US" dirty="0"/></a:p></p:txBody></p:sp><p:sp><p:nvSpPr><p:cNvPr id="3" name="Content Placeholder 2"/><p:cNvSpPr><a:spLocks noGrp="1"/></p:cNvSpPr><p:nvPr><p:ph idx="1"/></p:nvPr></p:nvSpPr><p:spPr/><p:txBody><a:bodyPr/><a:lstStyle/><a:p><a:pPr marL="0" indent="0"><a:buNone/></a:pPr><a:r><a:rPr lang="en-US" dirty="0" smtClean="0"/><a:t>{page-2:txt}</a:t></a:r><a:endParaRPr lang="en-US" dirty="0"/></a:p></p:txBody></p:sp></p:spTree><p:extLst><p:ext uri="{BB962C8B-B14F-4D97-AF65-F5344CB8AC3E}"><p14:creationId xmlns:p14="http://schemas.microsoft.com/office/powerpoint/2010/main" val="2873090306"/></p:ext></p:extLst></p:cSld><p:clrMapOvr><a:masterClrMapping/></p:clrMapOvr></p:sld> pptxをzip解凍した時になかのスライド情報が入っているデータファイルですが、この中身の<a:t>部分にアタッチしてみたいと思います。 まず、ベースのnamespaceにアタッチするには・・・ $ xmllint --xpath "/*[local-name()='sld' and namespace-uri()='http://schemas.openxmlformats.org/presentationml/2006/main']" slide.xml <p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"><p:cSld><p:spTree><p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr><p:grpSpPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="0" cy="0"/><a:chOff x="0" y="0"/><a:chExt cx="0" cy="0"/></a:xfrm></p:grpSpPr><p:sp><p:nvSpPr><p:cNvPr id="2" name="Title 1"/><p:cNvSpPr><a:spLocks noGrp="1"/></p:cNvSpPr><p:nvPr><p:ph type="title"/></p:nvPr></p:nvSpPr><p:spPr/><p:txBody><a:bodyPr/><a:lstStyle/><a:p><a:r><a:rPr lang="en-US" dirty="0" smtClean="0"/><a:t>{page-2:title}</a:t></a:r><a:endParaRPr lang="en-US" dirty="0"/></a:p></p:txBody></p:sp><p:sp><p:nvSpPr><p:cNvPr id="3" name="Content Placeholder 2"/><p:cNvSpPr><a:spLocks noGrp="1"/></p:cNvSpPr><p:nvPr><p:ph idx="1"/></p:nvPr></p:nvSpPr><p:spPr/><p:txBody><a:bodyPr/><a:lstStyle/><a:p><a:pPr marL="0" indent="0"><a:buNone/></a:pPr><a:r><a:rPr lang="en-US" dirty="0" smtClean="0"/><a:t>{page-2:txt}</a:t></a:r><a:endParaRPr lang="en-US" dirty="0"/></a:p></p:txBody></p:sp></p:spTree><p:extLst><p:ext uri="{BB962C8B-B14F-4D97-AF65-F5344CB8AC3E}"><p14:creationId xmlns:p14="http://schemas.microsoft.com/office/powerpoint/2010/main" val="2873090306"/></p:ext></p:extLst></p:cSld><p:clrMapOvr><a:masterClrMapping/></p:clrMapOvr></p:sld> root階層からのデータが全て拾えました。 さらに、その個階層にあたる"a:t"へのアタッチは・・・ $ xmllint --xpath "//*[local-name()='t' and namespace-uri()='http://schemas.openxmlformats.org/drawingml/2006/main']" slide.xml <a:t>{page-2:title}</a:t><a:t>{page-2:txt}</a:t> 非常に長いコマンドですが、それぞれのnamespaceにアタッチするたびに"local-name()"と"namespace-uri()"をセットしなければいけません。

shellオプション

xmllintはshellオプションという強力なxmlパーサー機能を持っています。 簡単に説明すると、xml内の階層を、shellコマンドのように、cd(chenge-directory)したり、内容をcatしたり、することができる言わば対話式にパースすることが出来る機能です。 通常の使い方は以下のとおりです。 $ xmllint --shell %xmlファイル > du 内容の階層を全て表示... > ls 同階層のリスト表示 > cat 内容テキストの表示 .. などなど 以下のようにパイプつなぎで書くこともできます。 ※ヘルプの表示 $ echo "help" |xmllint --shell sample.xml / > base display XML base of the node setbase URI change the XML base of the node bye leave shell cat [node] display node or current node cd [path] change directory to path or to root dir [path] dumps informations about the node (namespace, attributes, content) du [path] show the structure of the subtree under path or the current node exit leave shell help display this help free display memory usage load [name] load a new document with name ls [path] list contents of path or the current directory set xml_fragment replace the current node content with the fragment parsed in context xpath expr evaluate the XPath expression in that context and print the result setns nsreg register a namespace to a prefix in the XPath evaluation context format for nsreg is: prefix=[nsuri] (i.e. prefix= unsets a prefix) setrootns register all namespace found on the root element the default namespace if any uses 'defaultns' prefix pwd display current working directory whereis display absolute path of [path] or current working directory quit leave shell save [name] save this document to name or the original name write [name] write the current node to the filename validate check the document for errors relaxng rng validate the document agaisnt the Relax-NG schemas grep string search for a string in the subtree ファイル名は意味はないのですが、こうすることで何ができるかがよく分かります。 そして、namepaceの対応は以下のようにすると取得できます。 $ xmllint --shell slide2.xml / > setns a=http://schemas.openxmlformats.org/drawingml/2006/main / > cat //a:t/text() ------- {page-2:title} ------- {page-2:txt} でも、残念ながら、パイプで繋いでいく方式は、1つのコマンドしかechoで送ることができないので、1ライナーで行う場合は、--xpath方式で行うのが良さそうです。 とりあえず、xmlの各要素にアタッチすることができたので、ある程度の作業はできそうですね。

このブログを検索

ごあいさつ

このWebサイトは、独自思考で我が道を行くユゲタの少し尖った思考のTechブログです。 毎日興味がどんどん切り替わるので、テーマはマルチになっています。 もしかしたらアイデアに困っている人の助けになるかもしれません。