【UiPath】StudioWeb × Document Understanding 実装上のポイント2

# はじめに

- 本記事は、StudioWeb で Document Understanding（モダンプロジェクト） を扱いたい方向けの内容です。
- 本記事は以下の記事の続きで、細かいトピックベースの実装ポイント解説がメインです。
- 記事の内容は、個人の見解または確認結果であり、UiPath の公式見解ではありません。
- 製品仕様や参考画像は 24.10 バージョンのもので構成しています。

https://qiita.com/Kajiita/items/a939211fe24ec25e9bff

# トピック一覧

```
# 1.パスワード付きのPDFの読み取り
# 2.ログに出力した読み取り結果の取得
# 3.特定領域の値の取得
```

## 1.パスワード付きのPDFの読み取り

「ドキュメントデータを抽出」アクティビティのプロパティにはパスワード入力欄はありません。
このため、パスワード付きのPDFのデータを抽出したい場合、先に***パスワードをはずしてあげる必要があります。***

（実装例）
![パスワード解除.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3301444/90e5884b-3e31-b012-b400-d46be02a9b84.jpeg)

::: note
- 「PDFのパスワードを設定」アクティビティを配置し、「新しい文書を開くパスワード」欄をNullにすることでパスワードを削除できます。
- 出力のエクスポートPDFはILocalResource型なのでそのまま「ドキュメントデータを抽出」アクティビティの入力に利用できます。
:::

## 2.ログに出力した読み取り結果の取得

StudioWeb（自動生成WF） で実行した際のログ出力（読み取り結果）をローカルに取得したいケースありますよね。

![ログから読み取り結果を取得.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3301444/c8291762-4687-2890-a7b6-3ba1c3434478.jpeg)

以下にStudioで実行ログから任意の情報を取得する際の実装例を記載します。

![ログから読み取り結果を取得2.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3301444/932806b0-3432-3868-2571-4ffe2a152e4d.jpeg)

まず、エンドポイントのクエリをつくります。

```
# 2024/12/18以上、ログのメッセージに「The following data has been extracted」を含む　のクエリ例
"\odata\RobotLogs?filter=TimeStamp ge 2024-12-18T00:00:00Z and contains(Message, 'The following data has been extracted')"
```

「OrchestratorへのHTTP要求」アクティビティを配置し、作成したエンドポイントを実行しましょう。

![ログから読み取り結果を取得3.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3301444/2aaedacf-6ea7-3bf5-0e3d-05b46382bb37.jpeg)

HTTP要求の応答レスポンスを一度JSONオブジェクト化し、valueフィールドについて「JSON配列を逆シリアル化」でJSON配列を作成します。

![ログから読み取り結果を取得4.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3301444/997da3a3-62c8-9095-f5e9-4e295e69e10b.jpeg)

繰り返し（コレクションの各要素）でJSON配列の明細レコードを取得します。
値は次の様に記述して出力できます。

```
# TimeStamp と Message フィールドを書き出す例
currentJToken("TimeStamp").ToString()+" "+currentJToken("Message").ToString()
```

::: note
以下が実行ログ（\odata\RobotLogs）で取得できるフィールドです。
- Level
- WindowsIdentity
- ProcessName
- TimeStamp
- Message
- JobKey
- RawMessage
- RobotName
- HostMachineName
- MachineId
- MachineKey
- RuntimeType
- Id
:::
::: note
よく使う比較演算子：
- eq 等しい
- ne 等しくない
- gt より大きい
- ge 以上
- lt より小さい
- le 以下
:::
::: note warn
Orchestrator のWebAPIは一度に取得できるレコードは1000件までに制限されています。
このため、抽出対象レコードが多い場合は、***繰り返し処理でエンドポイントのSkipオプションをインクリメントしながら実行しましょう。***

![ログから読み取り結果を取得5.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3301444/6d22e4b0-4f44-1b7a-ac5b-9635b6e0f85c.jpeg)

```
# エンドポイント記述例
"\odata\RobotLogs?$filter=TimeStamp ge 2024-12-18T00:00:00Z&$Top=1000&$Skip="+カウンタ.ToString
```
:::

## 3.特定領域の値の取得

先の記事でも触れましたが、StudioWeb×DUでは定型帳票用のアクティビティがありません。（IntelligentOCRパッケージが利用できないため）
ですが、次の様に座標情報をもちいた任意領域の抽出は可能です。

![特定領域の値を抽出.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3301444/f947781c-a4e3-0d0c-df3c-271314556210.jpeg)
![特定領域の値を抽出2.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3301444/dafa811b-d1cc-f5f7-f75e-12844530ce22.jpeg)

「繰り返し（コレクションの各要素）」アクティビティを配置し、extractedResults.DocumentMetadata.DocumentObjectModel.Pages　＞　.Sections　＞　.WordGroups　の順で currentWord オブジェクトを操作できるようにします。
currentWord は「.Text」で抽出されたワードの値を「.Box.Left」「.Box.Top」「.Box.Right」「.Box.Bottom」で座標情報を確認・出力できます。

このため、次の様な記述を条件分岐を入れておくことで、任意領域内の文字列のみ取得することが可能です。

```
currentWord.Box.Left >= 領域の左端のX座標 and 
currentWord.Box.Top >= 領域の上端のY座標 and 
currentWord.Box.Right <= 領域の右端のX座標 and 
currentWord.Box.Bottom <= 領域の下端のY座標
```

PDFの場合は、一度ワード毎の座標情報を出力し、適当に余白分を追加して決めます。
画像の場合は、次の様なHTMLをつかって手元で調べることも可能です。
（使い方：「ファイルの選択」ボタン押下＝＞画像選択＝＞任意領域をマウスで指定＝＞座標情報が画面下部に表示されるのでご確認ください）

（画像の任意範囲の座標を取得.html）
```
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Canvas Image ROI Selector</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            text-align: center;
            margin-top: 20px;
        }
        canvas {
            border: 1px solid black;
            cursor: crosshair;
        }
        #coordinates {
            margin-top: 10px;
        }
    </style>
</head>
<body>
    <h1>画像から選択範囲を取得</h1>
    <input type="file" id="imageLoader" accept="image/*">
    <canvas id="canvas"></canvas>
    <div id="coordinates">
        <p><strong>選択範囲：</strong></p>
        <p>Left: <span id="left">-</span>, Top: <span id="top">-</span>, Right: <span id="right">-</span>, Bottom: <span id="bottom">-</span></p>
    </div>
    <script>
        const imageLoader = document.getElementById('imageLoader');
        const canvas = document.getElementById('canvas');
        const ctx = canvas.getContext('2d');
        let image = new Image();
        let isDragging = false;
        let startX, startY, endX, endY;

        // 画像が選択されたら読む
        imageLoader.addEventListener('change', (e) => {
            const file = e.target.files[0];
            const reader = new FileReader();
            reader.onload = function(event) {
                image.src = event.target.result;
            };
            reader.readAsDataURL(file);
        });

        // 画像のロード完了時にキャンバスへ描画
        image.onload = () => {
            canvas.width = image.width;
            canvas.height = image.height;
            ctx.drawImage(image, 0, 0);
        };

        // マウスイベントで選択範囲の処理
        canvas.addEventListener('mousedown', (e) => {
            const rect = canvas.getBoundingClientRect();
            startX = e.clientX - rect.left;
            startY = e.clientY - rect.top;
            isDragging = true;
        });

        canvas.addEventListener('mousemove', (e) => {
            if (isDragging) {
                const rect = canvas.getBoundingClientRect();
                endX = e.clientX - rect.left;
                endY = e.clientY - rect.top;

                // キャンバスをリセットして画像を再描画
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                ctx.drawImage(image, 0, 0);

                // 選択範囲を描画
                ctx.strokeStyle = 'red';
                ctx.lineWidth = 2;
                ctx.strokeRect(startX, startY, endX - startX, endY - startY);
            }
        });

        canvas.addEventListener('mouseup', () => {
            isDragging = false;

            // 出力座標の計算
            const left = Math.min(startX, endX);
            const top = Math.min(startY, endY);
            const right = Math.max(startX, endX);
            const bottom = Math.max(startY, endY);
            const Height = bottom - top

            // 座標を表示
            document.getElementById('left').textContent = Math.round(left);
            document.getElementById('top').textContent = Math.round(top);
            document.getElementById('right').textContent = Math.round(right);
            document.getElementById('bottom').textContent = Math.round(bottom);
        });
    </script>
</body>
</html>
```

# さいごに
いかがでしたでしょうか。
今後も役に立ちそうな開発Tipsをみつけたらシェアしていきたいとおもいます。
最後までお読みいただきありがとうございます(・ω・)ノ