Kindleの本をPower BIとPower Appsで可視化する

# はじめに
先日、[下記の記事](https://qiita.com/tamazoo/items/aee45a8fced512014581)を拝読し、
**Kindleってデスクトップアプリを入れると、xmlで書籍データが蓄積されるんだ！**
と衝撃を受けました。

https://qiita.com/tamazoo/items/aee45a8fced512014581

記事では
1. `Python`で`XML`をパース
1. `Power BI`で可視化

というアプローチをとっています。

私は`Power Apps Lover`として、`Power Apps`で表現する方法を考えてみました。

# 1. 書籍データを取得する（XMLのパース）
詳しくは[紹介した記事](https://qiita.com/tamazoo/items/aee45a8fced512014581)を是非読んでいただきたいです。
Kindleのデスクトップアプリをインストールすると、`\AppData\Local\Amazon\Kindle\Cache\`に`XML`ファイルが自動保存されます。

```plaintext:XMLパス
C:\Users\user_name\AppData\Local\Amazon\Kindle\Cache\KindleSyncMetadataCache.xml
```

こちらを`Power BI Desktop`でパースします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/cde0d41a-6cbc-2a04-817a-04e94fd46599.png)

標準で`XML`はデータとして読み込むことができます。

- 本のタイトル
- 著者
- 出版社
- 購入日
- 出版日
- データのタイプ

読み込むと上記のようなデータが取れるようです。
Power Query式で下記のように読み込みます。

```plaintext:Power Query式
let
    // XMLファイルをテーブルに読み込む
    Source = Xml.Tables(File.Contents("C:\Users\{user_name}\AppData\Local\Amazon\Kindle\Cache\KindleSyncMetadataCache.xml")),
    
    // "add_update_list"列を展開して"meta_data"を含める
    ExpandedAddUpdateList = Table.ExpandTableColumn(Source, "add_update_list", {"meta_data"}, {"meta_data"}),
    
    // "meta_data"列を展開して各種の詳細を含める
    ExpandedMetaData = Table.ExpandTableColumn(ExpandedAddUpdateList, "meta_data", {"ASIN", "title", "authors", "publishers", "publication_date", "purchase_date", "textbook_type", "cde_contenttype", "content_type", "origins"}, {"ASIN", "title", "authors", "publishers", "publication_date", "purchase_date", "textbook_type", "cde_contenttype", "content_type", "origins"}),
    
    // "title", "authors", "publication_date"などのネストされた列をさらに展開
    ExpandedTitle = Table.ExpandTableColumn(ExpandedMetaData, "title", {"Element:Text", "Attribute:pronunciation"}, {"Title_Text", "Title_Pronunciation"}),
    ExpandedAuthors = Table.ExpandTableColumn(ExpandedTitle, "authors", {"author"}, {"Author"}),
    ExpandedPublicationDate = Table.ExpandTableColumn(ExpandedAuthors, "publication_date", {"Element:Text"}, {"PublicationDate"}),
    ExpandedPublishers = Table.ExpandTableColumn(ExpandedPublicationDate, "publishers", {"publisher"}, {"Publisher"}),
    ExpandedPurchaseDate = Table.ExpandTableColumn(ExpandedPublishers, "purchase_date", {"Element:Text"}, {"PurchaseDate"}),
    ExpandedTextbookType = Table.ExpandTableColumn(ExpandedPurchaseDate, "textbook_type", {"Element:Text"}, {"TextbookType"}),
    ExpandedOrigins = Table.ExpandTableColumn(ExpandedTextbookType, "origins", {"origin"}, {"Origin"}),
    ExpandedOriginType = Table.ExpandTableColumn(ExpandedOrigins, "Origin", {"type"}, {"OriginType"}),
    
    // 最終出力用に必要な列だけを選択
    SelectedColumns = Table.SelectColumns(ExpandedOriginType,{"ASIN", "Title_Text", "Title_Pronunciation", "Author", "Publisher", "PublicationDate", "PurchaseDate", "content_type", "OriginType"}),
    SelectRows = Table.SelectRows(SelectedColumns, each ([Title_Text] <> "" and [Title_Text] <> "---------------" and [ASIN] <> "B003ODIZL6")),
    
    // 日付列のデータ型を正確な処理のためにdatetimeに変更
    ChangedType = Table.TransformColumnTypes(SelectRows,{{"PublicationDate", type datetime}, {"PurchaseDate", type datetime}, {"ASIN", type text}, {"Title_Text", type text}, {"Title_Pronunciation", type text}, {"Publisher", type text}, {"content_type", type text}, {"OriginType", type text}}),
    ReplaceErrorValues = Table.ReplaceErrorValues(ChangedType, {{"OriginType", null}})
in
    ReplaceErrorValues

```

その他、データを整形するとこのようにできます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/5a176480-3882-0444-6990-7c923ba91571.png)

:::note warn
ビューの作成であれば、Power BIで全て完結します。
スキーマの設定やビューの作成は、この記事では取り扱いません。
:::

一旦ここまでで、`発行`を実行します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/d40cc425-8252-5140-048b-c4b4f927d9ca.png)

# 2. Power Appsで書籍データを取得する
Power BI（SaaS）に発行されたデータセットは、**Power Appsで直接取得することができます。**

`データの接続`から`PowerBI`を選択します。
PowerBIコネクタから直接テーブルを取得し、`テーブル`や`ギャラリー`のデータソースとすることはできないので、一度ボタンクリックを通じて、コレクションとして評価します。

活用するアクションは`PowerBI.ExecuteDatasetQuery`です。


https://powerusers.microsoft.com/t5/Building-Power-Apps/Get-data-from-PowerBI-dataset/td-p/706140


:::note info
面白い
:::


## Power BIのデータセットを読み込む

```plaintext:OnSelect
// 1. Set Context
UpdateContext(
    {
        GroupID: "---",
        DatasetID: "---",
        Query: "EVALUATE 'Main'" // 先は全行抽出するDAXクエリ式
    }
);

// 2. Evalute query
ClearCollect(queryResults,
    ForAll(PowerBI.ExecuteDatasetQuery(GroupID,DatasetID,Query).firstTableRows,
        {
            ASIN: Text(ThisRecord.Value.'Main[ASIN]'),
            Title: Text(ThisRecord.Value.'Main[Title_Text]'),
            Publisher: Text(ThisRecord.Value.'Main[Publisher]'),
            PublicationDate: DateTimeValue(ThisRecord.Value.'Main[PublicationDate]'),
            PurchaseDate: DateTimeValue(ThisRecord.Value.'Main[PurchaseDate]'),
            OriginType: Text(ThisRecord.Value.'Main[OriginType]'),
            Authors: Text(ThisRecord.Value.'Main[Authors]')
        }
    );
);

```

:::note info
とても面白い事象ですが、**Power BIのデータセットを読み込むだけでは、データ型を含めてコレクションに格納することはできないようです。**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/00bcd2b1-6e5e-cc8d-5022-0b455805ff4f.png)

[ParseJSON](https://learn.microsoft.com/ja-jp/power-platform/power-fx/reference/function-parsejson)関数で文字列を評価した時のように、`UntypedObject`型と評価されてしまいます。

上記の現象の対策として、[ForAll](https://learn.microsoft.com/ja-jp/power-platform/power-fx/reference/function-forall)関数でデータ型を変換しています。
:::


https://learn.microsoft.com/ja-jp/power-platform/power-fx/reference/function-parsejson

https://koruneko.hatenablog.com/entry/2022/09/07/195521

https://learn.microsoft.com/ja-jp/power-platform/power-fx/reference/function-forall

## アプリを作り上げる
こんな感じでレイアウトを整えます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/4cbb80a3-d5c4-92f1-71d8-53ed1fb6e0f4.png)

[モダンコントロールのテーブル](https://youseibubu.com/apps/modern_table/)を採用していますが、そちらについては下記の記事が参考になります。

https://youseibubu.com/apps/modern_table/

Power Appsのスクリーンの`OnVisible`でデータの取得を実施

```plaintext:OnVisible

UpdateContext(
    {
        varSortOrder: SortOrder.Ascending
    }
);
// 1. Set Context
UpdateContext(
    {
        GroupID: "---",
        DatasetID: "---",
        Query: "EVALUATE 'Main'"
    }
);

// 2. Evalute query
ClearCollect(queryResults,
    ForAll(PowerBI.ExecuteDatasetQuery(GroupID,DatasetID,Query).firstTableRows,
        {
            ASIN: Text(ThisRecord.Value.'Main[ASIN]'),
            Title: Text(ThisRecord.Value.'Main[Title_Text]'),
            Publisher: Text(ThisRecord.Value.'Main[Publisher]'),
            PublicationDate: DateTimeValue(ThisRecord.Value.'Main[PublicationDate]'),
            PurchaseDate: DateTimeValue(ThisRecord.Value.'Main[PurchaseDate]'),
            OriginType: Text(ThisRecord.Value.'Main[OriginType]'),
            Authors: Text(ThisRecord.Value.'Main[Authors]')
        }
    );
);

```

モダンコントロールのテーブルの`Items`プロパティは、条件に応じて値の表示を変化させます。

```plaintext:Items
Sort(
    Search(
        If(
            And(!IsBlank(cmbPublisher.Selected.Value),!IsBlank(cmbOriginType.Selected.Value)),
            Filter(queryResults,
                Publisher=cmbPublisher.Selected.Value,
                OriginType=cmbOriginType.Selected.Value
            ),
            !IsBlank(cmbPublisher.Selected.Value),
            Filter(queryResults,Publisher=cmbPublisher.Selected.Value),
            !IsBlank(cmbOriginType.Selected.Value),
            Filter(queryResults,OriginType=cmbOriginType.Selected.Value),
            queryResults
        ),
        inpSearch.Value,"Title"
    ),
    PurchaseDate,
    varSortOrder
)
```

これで、**それらしいビューはできます。**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/04666851-704b-e460-63d7-b49584811d07.png)

:::note info
Power BIでできることをわざわざAppsでやっているだけで本当に意味はないです。
:::

# 【応用】Power Appsならでは！を加える
`Kindle for desktop`で作成されるXMLでは、読み込めるデータに限りがあります。

- ASINコード
- 本のタイトル
- 著者
- 出版社
- 購入日
- 出版日
- データのタイプ

そして`XML`のデータを読み込んで、Power Appsで表示するだけでは芸がない。

**せっかくなので画像や詳細をAPI経由で集めたい！**
その方法を模索していこうと思います。

## ASINコードを使うハードルが高い…
XMLで`ASINコード`を取得できています。

**ASINコード（Amazon Standard Identification Number）** は、Amazonが商品識別に使用するコードとのこと。

Amazonのコードなので、[Amazon Product Advertising API 5.0](https://webservices.amazon.com/paapi5/documentation/)が使えそう！

と思いましたが、何やら利用のハードルが高く、私は今、使用する条件が満たせませんでした(´;ω;｀)

## 本の検索で対処してみる
今回はKindle本なので、本を検索するAPIであれば対処できるのでは！？と思い、[Google Books API](https://developers.google.com/books?hl=ja)を使ってみることにしました。

https://developers.google.com/books?hl=ja


**本の情報検索のみであれば認証も不要のAPIです！**

過去にもトライしていました。

https://qiita.com/DEmodoriGatsuO/items/4262fe54a163baf807e6

https://qiita.com/DEmodoriGatsuO/items/29f7b3e145b1215e5bb8

`カスタムコネクタ`を作って、`Power Apps`から`API`を使えるようにします。

https://learn.microsoft.com/ja-jp/connectors/custom-connectors/

## カスタムコネクタの作成
カスタムコネクタの概要は下記をご覧ください。

https://learn.microsoft.com/ja-jp/connectors/custom-connectors/

:::note info
今回は本の検索機能に絞って作成します。
:::

1. Power Automate > カスタムコネクタ
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/8c20f90a-36d5-cf82-d6b1-5a14d4336c3b.png)
1. 「一から作成」を選択
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/ea9a0c79-e27d-6c38-2722-925f2a723c46.png)
1. コネクタ名を設定（好きな名前をつけてください）
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/494d0a19-53b2-1bed-f9e5-2542747d51c1.png)
1. ホスト名とベースURLを設定
    1. ホスト`www.googleapis.com`、ベースURL「/」
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/876af6ea-d8eb-1b97-c813-dd101d94f387.png)
1. セキュリティは認証なし
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/fe3f02e4-7a09-a983-ac41-e5fbded0ba36.png)
1. 定義はアクション名とイコールです。`GetBooksData`とつけました
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/713fa53b-2c7f-65d0-b516-ba811f9aa601.png)

### 7.「要求」の設定
要求は`サンプルからのインポート`で実施します。

動詞は`GET`
URLに下記を設定します。

```
https://www.googleapis.com/books/v1/volumes?q=夏目漱石&maxResults=10&startIndex=0
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/716e6e4e-1b6d-97cb-d34e-ef3d37dc1e3c.png)

**この画面になっていればOK!!**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/4beef342-3785-eaf8-dc26-9d040d39d8df.png)

### 8. テスト
1. `コネクタの更新` - この操作を実施しないとテストが実施できません
1. `+ 新しい接続`
1. **テスト操作を実施**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/a9505e83-d4c8-4fab-7759-630ca27e2712.png)

**テストが完了したら成功です！**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/101ca1cb-dc07-c3e3-f8d4-95a4685b7930.png)

# Power Appsにカスタムコネクタ機能を加える
準備ができたので、`モダンコントロールのテーブル`で`OnSelect`を実施したレコードで、Google Books APIを実施し、検索結果を表示する画面を作ります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/4a698239-4fca-8137-dec7-31f0d95bc0b4.png)

👆こんなイメージ

:::note warn
紹介した[ようせいさんのブログ](https://youseibubu.com/apps/modern_table/#%e3%83%86%e3%83%bc%e3%83%96%e3%83%ab%e3%81%aeonselect%e3%81%ae%e6%b3%a8%e6%84%8f%e7%82%b9%ef%bc%81)にも記載がありますが、モダンコントロールのテーブルのOnSelectが不安定です

安定的に動かしたいときは、ギャラリーコントロールが無難かもしれません
:::

https://youseibubu.com/apps/modern_table/#%e3%83%86%e3%83%bc%e3%83%96%e3%83%ab%e3%81%aeonselect%e3%81%ae%e6%b3%a8%e6%84%8f%e7%82%b9%ef%bc%81

一旦うまくいくレコードで実行した例を紹介します。

## カスタムコネクタを追加・実行
[データ] > [+ データの追加] > カスタムコネクタを追加します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/f2e13c43-6083-6c74-9859-5346278fc31c.png)

これによって、`Power Apps`からカスタムコネクタで定義したアクションが実行できます。

カスタムコネクタの呼び出しは、`OnSelect`で検索結果画面に行った後、検索結果画面の`OnVisible`で下記を実行します。※ここら辺は好きな形で書いてください。

```
If(!IsBlank(ResultTable.Selected),
    ClearCollect(colGoogleBooks,
    Table(
        GoogleBooksSearch.GetBooksData({q:ResultTable.Selected.Title,maxResults:10,startIndex:0}).items)
    )
);
```

結果は赤枠のギャラリーに格納します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/e32692b8-5136-f54c-9ae6-cbdf4c439fa5.png)

### ギャラリーに配置するコントロールのプロパティ
カスタムコネクタで取得したデータを配置するために、`タイトル`、`サブタイトル`、`イメージコントロール`は下記の通り設定しています。

- タイトル
```
Text(ThisItem.Value.volumeInfo.infoLink)
```

- サブタイトル
```
Text(ThisItem.Value.volumeInfo.publisher)
```

- イメージコントロール
```
Text(ThisItem.Value.volumeInfo.imageLinks.thumbnail)
```

型の評価が曖昧になっているからですね…。
カスタムコネクタによって、Google Booksの検索結果の`URL`や`ISBNコード`も取得できるので、私が使っている読書管理アプリに登録するにも一役買いそうです。


https://qiita.com/DEmodoriGatsuO/items/4262fe54a163baf807e6

**APIを縦横無尽に楽しむうえではPower Platformはやはり最高のツール。検証が止まりません。**

# おわりに
今回は`Power Apps`で無理やり表現しましたが、Power BIでできることはPower BIでやったほうがスマートです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/340543b1-aa7e-48cd-2ff5-6d0efdb57c56.png)

こんな感じで映えます。
**あくまでPower Appsでも表現できるんだなーくらいに見てもらえれば幸いです。**