Power Automate DesktopでDTDと名前空間があるXMLファイルを読む

PowerShellで作ってあったXMLファイルを処理するプログラムをPower Automate Desktopに変換しようとしたところ、なかなか手こずりました。


PowerShellでは簡単

PowerShellのコードは次の通りです。

    foreach ( $sXmlFile in Get-ChildItem -Path ( Join-Path $uDateFolder.fullname "*NFM.XML" ) -File )
    {
        $uXml = [xml](Get-Content $sXmlFile)

        $uDocumentName = $uXml.procedure.'procedure-infomation'.'document-name'.'#text' #書類名

        if ( $uDocumentName -ne "発送目録" )
        {
            $uApplicationNumber = $uXml.procedure.'procedure-infomation'.'application-reference'.'application-number' #事件番号
            $uReferenceId = $uXml.procedure.'procedure-infomation'.'application-reference'.'reference-id' #整理番号
            $uLaw = $uXml.procedure.'procedure-infomation'.law #四法
            $uDivision = $uXml.procedure.'procedure-infomation'.'time-for-responce'.division #応答期間 区分
            $uPeriod = $uXml.procedure.'procedure-infomation'.'time-for-responce'.period #応答期間 期間
            $uDispatchNumber = $uXml.procedure.'procedure-infomation'.'dispatch-number' #発送番号
            $uTime = $uXml.procedure.'procedure-infomation'.'dispatch-date'.time #受信時刻

            uAddMessage (uFormat $uLaw $uDocumentName $uReferenceId $uApplicationNumber $uDivision $uPeriod $uDispatchNumber $uTime)
        }
    }

これで問題なくデータを取得できていました。

(テキスト要素を指定してみたり指定しなかったり、procedure-infomationまで共通だから省略すればいいのにしていなかったりと、今見るとお恥ずかしい)


DTDを含んだXMLを開けない

そのため、早速「ファイルから XML を読み取ります」を実行してみたところ、次のエラーが発生しました。

Microsoft.Flow.RPA.Desktop.Modules.SDK.ActionException: ファイル C:\NKTemp\Sample.XML に有効な XML ドキュメントが含まれていません ---> System.Exception: セキュリティ上の理由から、DTD はこの XML ドキュメントでは使用できません。DTD 処理を有効にするには、XmlReaderSettings の DtdProcessing プロパティを Parse に設定し、XmlReader.Create メソッドにその設定を渡してください。 ---> System.Exception

   --- 内部例外スタック トレースの終わり ---

   --- 内部例外スタック トレースの終わり ---

   場所 Microsoft.Flow.RPA.Desktop.Modules.XML.Actions.ReadXmlFromFile.Execute(ActionContext context)

   場所 Microsoft.Flow.RPA.Desktop.Robin.Engine.Execution.ActionRunner.Run(IActionStatement statement, Dictionary`2 inputArguments, Dictionary`2 outputArguments)

DtdProcessing を検索してみるとどうやらPower Automate DesktopのXMLは.NETのXML機能を使っているようでした。

XmlReaderSettings.DtdProcessing プロパティ

Power Automate DesktopのアクションにはParseを有効にする方法は見当たらないので、これはお手上げです。

仕方がないので、やりたくはないけれど、XMLファイルからDTD部分を削除して処理することにしました。

XMLファイルを普通にテキストファイルとして読み取り、読み取った内容を次のアクションのように置換し、再度別の一時ファイルに保存してからXMLのアクションで読み込みました。

DTDを無視するオプションを用意してもらいたいです。

なお、このファイルはExcelで開いても同様のエラーが発生します。


名前空間が付いた要素を読む

ファイルが読めるようになったので、後は要素の値を読み込むだけと思ったらこれが難航しました。

そのままでは読めない

XMLデータから特定の要素の値を取得するには、XPathクエリで指定します。ファイルやディレクトリをパスを使って指定するのと同様です。

そのため、PowerShellと同じように、/procedure/procedure-infomation/document-nameと指定してみましたが、エラーが発生して読み込めません。

「XPath 式は要素を返しませんでした。」と言われてしまいます。

今回処理するXMLファイルの要素には次のように jpopc: という文字が付加されています。

<?xml version="1.0" encoding="Shift_JIS"?><jpopc:procedure xmlns:jpopc="http://www.jpo.go.jp">
<jpopc:document-type jpopc:unconfirmed-state="1">I21020</jpopc:document-type>
<jpopc:computer-name>PC0001</jpopc:computer-name>
<jpopc:user-name>online-tm</jpopc:user-name>
<jpopc:distinction-number>111111111</jpopc:distinction-number>
<jpopc:relation-file>
<jpopc:input-check-result></jpopc:input-check-result>
<jpopc:application-receipt-list></jpopc:application-receipt-list>
</jpopc:relation-file>
<jpopc:procedure-infomation>
<jpopc:result>
<jpopc:software-message></jpopc:software-message>
<jpopc:communication-result>正常</jpopc:communication-result>
<jpopc:fd-and-cdr></jpopc:fd-and-cdr>
</jpopc:result>
<jpopc:law>1</jpopc:law>
<jpopc:document-name jpopc:document-code="B999">登録査定</jpopc:document-name>

試しに、XPathにjpopc:もつけてみましたがだめでした。

名前空間

これは一体?と思い、調べてみると、この jpopc: のようなものはXML名前空間(NameSpace)というものでした。

XML名前空間の簡単な説明  KANZAKI

そして、実際にXpathで指定する方法はこちらのページが参考になりました。こちらはRSSのXMLについて解説しているページですが、基本的なところは同じなようです。

XPathにおける名前空間の扱い 東京電機大学

次のように指定すれば名前空間を指定せずに要素を指定することができます。

*[local-name()='procedure']

説明は上のページに書かれていますが、 * ですべての要素を選択して、その中からローカル名が procedure であるものを指定しているということになります。

サンプル

実際にアクションに指定したのは次の通りです。

まずは procedure-information までを uXPathPI 変数に納めます。

uXPathPI 変数を使ってそこから先の部分を指定します。

これは次のようにルートから一気に書いても同じです。長くなってわかりにくくなりますが。

/*[local-name()='procedure']/*[local-name()='procedure-infomation']/*[local-name()='document-name']

XPathに名前空間は指定できない

プログラム中で名前空間を定義して、XPathに指定するやり方が上のページの下の方に説明されていますが、Power Automate Desktopのアクションでこれは処理できないと思います。

次のサイトでは、.NET を使う場合のXPathと名前空間の説明が書かれていて、こちらも同様にプログラム中で名前空間を定義してからXPathに指定しています。

連載 .NETで簡単XML 第5回 DOMとXPath @IT

複数の名前空間を持つXMLに対応できない

私の場合は上の方法で回避できましたが、一つのXMLに複数の名前空間が出てきてなおかつ要素名が重複した場合には対処ができなくなります。例えば次のような場合です。

jpopc:procedure と usopc:procedure が混在している場合。

この場合現在のPower Automate Desktopだとお手上げなので、別の処理系を使ってその結果を読み込むことになるでしょう。

DTDを含めて今後の対応に期待したいところです。


実際のプログラム

実際の処理をするフローは次の通りです。

あるフォルダー中のXMLファイルを順次処理しています。

DTDを削除して、それからXMLを読み込み、要素の値を取得しています。


コードは次の通りです。(フローに張り付ければ普通のアクションとして操作できます)

SET uFolderPath TO $'''C:\\temp\\20220125'''
Folder.GetFiles Folder: uFolderPath FileFilter: $'''*NFM.XML''' IncludeSubfolders: False FailOnAccessDenied: True SortBy1: Folder.SortBy.NoSort SortDescending1: False SortBy2: Folder.SortBy.NoSort SortDescending2: False SortBy3: Folder.SortBy.NoSort SortDescending3: False Files=> Files
LOOP FOREACH uXMLFile IN Files
    File.ReadTextFromFile.ReadText File: uXMLFile Encoding: File.TextFileEncoding.DefaultEncoding Content=> FileContents
    Text.Replace Text: FileContents TextToFind: $'''<!DOCTYPE jpopc:procedure SYSTEM \"v4crc00.dtd\" []>''' IsRegEx: False IgnoreCase: False ReplaceWith: $'''%''%''' ActivateEscapeSequences: False Result=> Replaced
    File.GetTempPath TempFile=> TempFile
    File.WriteText File: TempFile TextToWrite: Replaced AppendNewLine: False IfFileExists: File.IfFileExists.Overwrite Encoding: File.FileEncoding.DefaultEncoding
    XML.ReadFromFile File: TempFile Encoding: XML.FileEncoding.DefaultEncoding XmlDocument=> XmlDocument
    File.Delete Files: TempFile
    SET uXPathPI TO $'''/*[local-name()=\'procedure\']/*[local-name()=\'procedure-infomation\']'''
    XML.GetXmlElementValue.GetElementValue Document: XmlDocument XPathQuery: $'''%uXPathPI%/*[local-name()=\'document-name\']''' TextValue=> uDocumentName
    IF uDocumentName = $'''発送目録''' THEN
    ELSE
        XML.GetXmlElementValue.GetElementValue Document: XmlDocument XPathQuery: $'''%uXPathPI%/*[local-name()=\'application-reference\']/*[local-name()=\'application-number\']''' TextValue=> uApplicationNumber
        XML.GetXmlElementValue.GetElementValue Document: XmlDocument XPathQuery: $'''%uXPathPI%/*[local-name()=\'application-reference\']/*[local-name()=\'reference-id\']''' TextValue=> uReferenceId
    END
END


これを読まれている方がうまくデータを処理できることを祈ります。


コメント

  1. 私も同じ方法で読み込んでいました。

    返信削除
    返信
    1. コメントをありがとうございます。早く、簡単に読み込めるようになってほしいものです。

      削除

コメントを投稿

アクセス数の多い投稿

セキュリティ対策ソフトのノートンが詐欺ソフトまがいになってしまってショック

突然滅茶苦茶遅くなったPCがWindows Updateのキャッシュクリアで復活

Teamsで日本語入力すると左上に変換ウィンドウが出る

Amazon Prime Videoで4K UHD映画を検索する方法

NEC Aterm WX5400HP をセットアップ

Excel 2019 クエリが原因で日本語入力の一文字目が勝手に確定する

Microsoft Flight Simulator (2020)のPS4コントローラー設定

次期主力Wi-Fiルーター NEC Aterm WX5400HP

iPhone 12 Proと iPhone 8 Plusのサイズを比較

Excel VBAからODBCを使ってデータを簡単に取得する