Power Automate Desktop PDF テーブル

Power Automate DesktopでPDFのページ数を取得するのは容易ではないという話です。

PDFをページごとに処理するにはページ数が必要

銀行のサイトから計算書を取得しました。1ページ1件で複数ページあります。このファイルから1件ごとにデータを取得したいと思います。

「PDFからテキストを抽出」アクションを使ってループ内で1ページごとデータを取得して処理しようと思いました。

ループを使うにはファイルのページ数が必要です。簡単にPower Automate Desktopで取れるのだろうと思ったらなんとそのような方法が見つかりませんでした。

ありえないページ数を最大値に指定して、見つからなかったらエラー処理という方法も考えられますが好きではありません。

ExtractedPDFTables.Countを使用

丁度今月リリースされたPower Automate Desktop 2.17 には「PDFからテーブルを抽出する」というアクションが追加されています。

このアクションで取得されたデータの Count プロパティを使うとファイルに含まれるテーブル数を取得できます。

今回のPDFファイルは1ページに1つテーブルが含まれていたため、これを使って取得することができました。

例:ExtractedPDFTables.Count

アクションの実行に時間はかかるものの、今回の件では確実にページ数を取得でき、正しく処理できました。

PDFから直接ページ数を取得するのは容易ではない

PDFファイルを直接読み込んでページ数を取得できるのではと思いました。

見て作って学ぶ、PDFファイルの基本構造

しかし、実際にはバージョン違い等があって、容易ではないようです。

EXCEL VBA AcrobatReaderやAdobeを使わずにPDFのページ数を取得する方法

自社で作ったPDFならバージョンやフォーマットがある程度特定できるのでこういった方法も一つだと思います。

しかし、いろんなところからくる請求書を処理するような場合、いつどんなデータがくるのかわかりません。

Power Automate DesktopでPDFのプロパティを取得できるアクションが欲しいと思いました。

試してみると

次のページにVBAでページ数を取得する方法が紹介されていました。

ExcelVBAのみでPDFファイルのページ数を取得する Qiita

PDFファイルの構造上、「/Count 」はソーステキストに1回しか出現しないこと、通常表示される文字列はStreamオブジェクト内に圧縮されているため正規表現にマッチしないことからページ数の抜き出しが可能となっています。

と書かれています。

そこでPower Automate Desktopに移植してみました。

Power Automate Desktop PDF テーブル

コピーしたコードは次の通りです。Power Automate Desktopの編集画面に貼付すれば復元できます。

Display.SelectFileDialog.SelectFile FileFilter: $'''*.pdf''' IsTopMost: True CheckIfFileExists: False SelectedFile=> SelectedFile ButtonPressed=> ButtonPressed

File.ReadTextFromFile.ReadText File: SelectedFile Encoding: File.TextFileEncoding.UTF8 Content=> FileContents

Text.ParseText.RegexParseForFirstOccurrence Text: FileContents TextToFind: $'''/Count \\d*''' StartingPosition: 0 IgnoreCase: False OccurrencePosition=> Position Match=> Match

Text.GetSubtext.GetSubtextFrom Text: Match CharacterPosition: 7 Subtext=> Subtext

ページ数はSubtext変数に入ります。

実行してみたところExcelから出力したファイルは取得できました。

しかし、家電製品等の取説などは軒並みだめでした。

まずソニーの取説はCountが見つかりませんでした。Encryptという文字があったので暗号化されているようです。もしかしたらそのせいかもしれません。

Chromeで保存したと思われるPDFは次のように複数のカウントがありました。

223 0 obj

<</Type /Pages

/Count 8

/Kids [2 0 R 55 0 R 104 0 R 120 0 R 131 0 R 145 0 R 178 0 R 192 0 R]

/Parent 225 0 R>>

endobj

224 0 obj

<</Type /Pages

/Count 2

/Kids [203 0 R 213 0 R]

/Parent 225 0 R>>

endobj

225 0 obj

<</Type /Pages

/Count 10

/Kids [223 0 R 224 0 R]>>

endobj

これは、ページカウントオブジェクトが階層化されているようです。

この場合 /Parentがないオブジェクトを探し出して処理する必要があります。

少しトライしてみましたが容易ではありませんでした。

/Type /Pagesと/Countの順番が入れ替わっている場合もあるようです。

各キーワードのデリミターもスペースの場合と改行の場合もあります。改行はCRなのかLFなのか、CRLFなのかも考える必要があります。

存在しないページを指定してもエラーが発生しない

好きではないけれど、エラーが発生したらおしまいという方法も試してみようと思いました。

ところが、ループで「PDFからテキストを抽出」アクションを繰り返して、存在しないページに達してもエラーは発生しませんでした。

そしてExtractedPDFTextには最終ページのテキストが入るようです。直前でExtractedPDFTextをクリアしても挙動は一緒でした。

これは困りました。

ファイル分割ではエラーが発生

仕方がないのでファイル分割を試したところ、さすがにこちらはエラーが発生しました。そのため、「ページが範囲外です」エラーが発生したらループを抜けるというフローを書いてみました。

ループ中で、「新しいPDFファイルへのPDFファイルページの抽出」アクションにページ数(LoopIndex)を指定しながら実行します。存在しないページを指定するとエラーが発生するので、LoopIndexから1引いた数がページ数です。

Power Automate Desktop PDF テーブル

コードは次の通りです。

LOOP LoopIndex FROM 1 TO 1000 STEP 1

    Pdf.ExtractPages PDFFile: $'''C:\\PADTemp\\Sample.pdf''' PageSelection: LoopIndex ExtractedPDFPath: $'''C:\\PADTemp\\OutputFile.pdf''' IfFileExists: Pdf.IfFileExists.Overwrite ExtractedPDFFile=> ExtractedPDF

    ON ERROR PageOutOfBoundsError

        GOTO LastPage

    END

END

LABEL LastPage

Display.ShowMessageDialog.ShowMessage Message: $'''ページ数は %LoopIndex - 1%''' Icon: Display.Icon.None Buttons: Display.Buttons.OK DefaultButton: Display.DefaultButton.Button1 IsTopMost: False ButtonPressed=> ButtonPressed

デスクトップフローとして部品化してもよいかもしれません。

実際に使うとなると、分割ファイルのパスについて考慮が必要です。固定ファイルにするのか、一時ファイルとして処理するのか。分割ファイルの最後は削除するのかどうかも考慮する必要があります。他のエラーが発生したときのTry Catchも必要でしょう。

ページ数が最大値に達したときの処理も必要かもしれません。ありえないページ数にしておけば現実的には問題ありませんが。

2022/11/24 追記

ループで1ページずつカウントアップしていくと時間がかかるので、10ページずつカウントアップして抽出を試みて、エラーが起こったら1ページずつカウントダウンして、エラーが起こらないページを探す、という方法が考えられます。これだと抽出回数を軽減できます。

数百ページがざらなら、100ページずつカウントアップして探すのも一つだと思います。

このあたりは二分探索的なアルゴリズムが使える気がするので、調べてみるのも一つでしょう。

アルゴリズムを勉強するなら二分探索から始めよう! 『なっとく!アルゴリズム』より

Acrobatから取得する

2022/11/24 追記

原始的というかPower Automate for Desktopらしい方法としては、Acrobat でPDFを開き、「ファイル」メニューの「プロパティ」を開き、「ページ数」を取得するという方法が考えられます。調べたいファイルしか開いていないのが確実であればこの方法も一つです。

しかし、実際に Acrobat のウィンドウを待機するには次のような問題が開いrます。

  1. タイトルにファイル名が表示されるとは限らない
  2. MDIとSDI

タイトルにファイル名が表示されるとは限らない

Acrobatのウィンドウのタイトルに表示される文字は、ファイル名とは限りません。PDFファイルのタイトルが表示されている場合があります。常にファイル名を表示するには、「編集」「環境設定」「文書」にある「常にファイル名を文書のタイトルとして使用」にチェックが入っている必要があります。

MDIとSDI

AcrobatはMDIとSDIを選択可能です。デフォルトはMDI(一つのウィンドウで複数のファイルを開く)ですが、「編集」「環境設定」「一般」の「同じウィンドウで新しいタブとして文書を開く」のチェックを外すとSDIにすることができます。

ユーザーによって使い方が異なるのでウィンドウクラスが「AcrobatSDIWindow」かどうかを考慮する必要があると思います。

他にも問題があったような気もしますが、眠いのでここまでとします。