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
これを読まれている方がうまくデータを処理できることを祈ります。
2023-10-21 追記:
参考リンク
Power Automate for DesktopでテキストをXMLオブジェクトに変換
私も同じ方法で読み込んでいました。
返信削除コメントをありがとうございます。早く、簡単に読み込めるようになってほしいものです。
削除ナレッジありがとうございます!大変助かりました m(__)m
返信削除お役に立てて何よりです。これには手こずりました。
削除