2023年7月11日火曜日

rinnaのjapanese-cloob-vit-b-16でベクトル埋め込みを生成しPineconeに保存して問い合わせを行なう

 rinna株式会社が公開しているjapanese-cloob-vit-b-16のモデル(rinna株式会社によるプレスリリース)とベクトル・データベースPineconeを使って、テキストによる画像検索を行なうアプリケーションを作成します。APEXではユーザーインタフェースを作成します。

以下の処理を実装します。

  1. オブジェクト・ストレージに保存した画像のベクトル埋め込み(embedding)をrinna社のモデルをより生成し、Pineconeに保存する。
  2. 問い合わせテキストのベクトル埋め込みをrinna社のモデルをより生成し、Pinconeのインデックスを検索する。
  3. 検索された画像をアプリケーションに表示する。
作成したアプリケーションのGIF動画です。動物の名前で画像を検索しています。


上記のアプリケーションのエクスポートを、以下に置いています。
https://github.com/ujnak/apexapps/blob/master/exports/multimodal-search.zip

これより、アプリケーションの実装について説明します。


Pineconeのインデックス



rinna社のjapanese-cloob-vit-b-16は512次元のベクトル埋め込みを生成するため、作成するPineconeのインデックスのDimensions512とします。ベクトル埋め込みを生成する際に、Pinecone社が公開している記事Multi-modal ML with OpenAI's CLIPに従ってベクトル埋め込みを正規化します。記事に従ってMetricdotproductを指定しますが、cosineでも結果は変わりません。


PinconeのインデックスのURLは、APEXアプリケーションの置換文字列G_INDEXの置換値として設定します。


PineconeのAPI呼び出しに使うWeb資格証明として、PINECONE_APIが作成済みとします。作成手順は、こちらの記事に記載されています。



オブジェクト・ストレージへの画像アップロード




オブジェクト・ストレージにバケットを作成し、検索対象とする画像をアップロードします。

以下の例ではバケットsourcesを作成しています。


検索対象とする画像を、作成したバケットにアップロードします。


バケット名ネームスペースは、APEXアプリケーションの置換文字列G_BUCKETG_NAMESPACEの置換値として設定します。

検索の対象とする画像を、バケットにアップロードします。

オブジェクト・ストレージに関する設定は、こちらの記事で紹介しています。APEXアプリケーションから操作するために、あらかじめAPIユーザーの作成Web資格証明を作成しておきます。また、事前承認済リクエストを生成するため、ポリシーを追加します。それぞれの準備については、こちらの記事が参考になります。

これからの説明では、Web資格証明としてOCI API Access静的IDOCI_API_ACCESS)が作成済みとします。


ベクトル埋め込みを生成するRESTサービス



無料で使えるOracle CloudのAmpere A1のインスタンスを4OCPU24GBメモリのシェイプで作成します。その上でPythonのコードを実行してベクトル埋め込みを生成します。

インスタンスの生成手順はこちらの記事に記載しています。Oracle Cloud MarketplaceにあるPyTorch - Ampere Optimized Frameworks - Ubuntu 20.04からインスタンスを作成します。

APIサーバーとして実行するためにFlaskを使います。準備作業については、こちらの記事を参照してください。rinna株式会社のjapanese-cloob-vit-b-16を利用する手順は、Huggingfaceのrinna/japanese-cloob-vit-b-16の記載通りです。パッケージのインストールを実施(1. Install package)し、テスト用のコードを実行(2. Run)して、動作を確認します。

画像のURLまたはテキストを受け付けて、rinna/japanese-cloob-vit-b-16のモデルを使ってベクトル埋め込みを生成するサーバーのコードです。generate-embedding.pyとして作成します。


Ubuntuのサーバー上で実行します。

python generate-embedding.py

ubuntu@mywhisper2:~$ python generate-embedding.py 

/usr/lib/python3/dist-packages/requests/__init__.py:89: RequestsDependencyWarning: urllib3 (1.26.13) or chardet (3.0.4) doesn't match a supported version!

  warnings.warn("urllib3 ({}) or chardet ({}) doesn't match a supported "

 * Serving Flask app 'generate-embedding'

 * Debug mode: on

INFO:werkzeug:WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.

 * Running on all addresses (0.0.0.0)

 * Running on https://127.0.0.1:8443

 * Running on https://10.0.0.131:8443

WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.

 * Running on all addresses (0.0.0.0)

 * Running on https://127.0.0.1:8443

 * Running on https://10.0.0.131:8443

INFO:werkzeug:Press CTRL+C to quit

Press CTRL+C to quit

INFO:werkzeug: * Restarting with stat

 * Restarting with stat

/usr/lib/python3/dist-packages/requests/__init__.py:89: RequestsDependencyWarning: urllib3 (1.26.13) or chardet (3.0.4) doesn't match a supported version!

  warnings.warn("urllib3 ({}) or chardet ({}) doesn't match a supported "

WARNING:werkzeug: * Debugger is active!

 * Debugger is active!

INFO:werkzeug: * Debugger PIN: 849-624-968

 * Debugger PIN: 849-624-968



テキストのベクトル埋め込みを生成するには、https://ホスト名/embed-text{ text: "問い合わせ文字列" }というJSONドキュメントをPOSTします。レスポンスとして{ text: "問い合わせ文字列", embedding: [ベクトル埋め込み] }が返されます。

画像のベクトル埋め込みを生成するには、https://ホスト名/embed-image{ url: "画像のURL" }というJSONドキュメントをPOSTします。レスポンスとして{ url: "画像のURL", embedding: [ベクトル埋め込み] }が返されます。

ベクトル埋め込みを生成するRESTサービスを実装したサーバーは、APEXアプリケーションの置換文字列G_EMBEDの置換値であるベースURLとして設定します。


APEXアプリケーションの作成



アプリケーション作成ウィザードを起動し、空のアプリケーションを作成します。名前Multimodal Searchとします。

アプリケーションの作成を実行します。


アプリケーションが作成されたら、アプリケーション定義置換を開きます。

置換文字列G_INDEXG_EMBEDG_NAMESPACEG_BUCKET置換値を設定します。


オブジェクト・ストレージに認証なしでアクセスするため(ベクトル埋め込みを生成するPythonのコードは、認証なしでオブジェクト・ストレージにアクセスする必要があります)、事前承認済リクエストのURLを生成します。

事前承認済リクエストのURLを保持するアプリケーション・アイテムG_PREAUTH_URLを作成します。


アプリケーションの計算を作成し、G_PREAUTH_URLに事前承認済リクエストのURLを設定します。

計算ポイント認証後計算タイプファンクション本体を選択します。計算として以下のコードを記述します。



北米AshburnリージョンがURLに直書きされているため、リージョンが異なる場合はコードの変更が必要です。

オブジェクト・ストレージのバケットに保存されている、画像の一覧を取得するRESTデータ・ソースを作成します。

あらかじめオブジェクト・ストレージのエンドポイントを、リモート・サーバーとして作成します。今回の例では北米Ashburnリージョンのエンドポイントを使用するため、以下のエンドポイントをベースURLとしています。

https://objectstorage.us-ashburn-1.oraclecloud.com/


共有コンポーネントRESTデータ・ソースを開き、作成を開始します。


RESTデータ・ソースの作成として最初からを選択します。

へ進みます。


RESTデータ・ソース・タイプとしてOracle Cloud Infrastructure(OCI)を選択し、名前List Imagesとします。URLエンドポイントは以下の形式で指定します。

https://objectstorage.リージョン.oraclecloud.com/n/ネームスペース/b/バケット/o/

へ進みます。


リモート・サーバーとして、APIのエンドポイントとなるリモート・サーバーを選択します。サービスURLパスは以下の形式で指定します。

n/ネームスペース/b/バケット/o/

へ進みます。


認証が必要ですオンにし、資格証明としてOCI API Access、またはオブジェクト・ストレージにアクセスできる他のWeb資格証明を指定します。

検出をクリックします。


ファイルの一覧が検出されます。

RESTデータ・ソースの作成をクリックします。


RESTデータ・ソースが作成されます。同期化の設定を行うために、作成されたRESTデータ・ソースList Imagesを開きます。


静的IDlist_imagesであることを確認します。

画面右の同期化の管理を開きます。


 同期先として新規表を選択し、表名としてVEC_IMAGESを設定します。同期化を設定することにより、毎回REST APIを呼び出す代わりに表VEC_IMAGESから画像の一覧を取り出せるようになります。

保存をクリックします。


同期化は作成されましたが、表VEC_IMAGESはまだ作成されていません。

表の作成をクリックします。


表VEC_IMAGESが作成されました。詳細同期タイプ置換にします。

保存して実行をクリックし、最初の同期を実行します。


同期が完了すると、ログに実行結果が表示されます。ステータス成功であれば、表VEC_IMAGESにオブジェクト・ストレージのバケットに含まれるファイルの一覧が保存されています。


SQLワークショップSQLコマンドを開いて、表VEC_IMAGESの内容を確認します。

select name from vec_images;


表VEC_IMAGESの画像(のベクトル埋め込み)がPineconeのインデックスのベクトルとして保存されているかどうか、フラグを保持する表VEC_IMAGE_INDEXESを作成します。

以下のDDLを実行します。
create table vec_image_indexes(
    file_name varchar2(80) not null,
    is_indexed varchar2(1)
);

ページ・デザイナでホーム・ページを開き、ユーザー・インターフェースを実装します。

画面上にボタンを4つ作成します。

それぞれボタン名ラベル名として、LOAD_FROM_BUCKET(Load From Bucket)、UPSERT_VECTORS(Upsert Vectors)、DELETE_VECTORS(Delete Vectors)、FIND_IMAGES(Find Images)とします。動作アクションはすべてページの送信です。

レイアウトは適当に調整します。


問合せ文字列を指定するページ・アイテムP1_QUESTIONを作成します。タイプテキスト・フィールドラベルQuestionとします。


検索結果の数を指定するページ・アイテムP1_TOP_Kを作成します。タイプテキスト・フィールドラベルTop Kとします。


オブジェクト・ストレージ上にある画像の一覧と、Pineconeのインデックスにベクトル埋め込みが含まれているかどうかを示すフラグを表示する対話モード・レポートを作成します。

リージョンのタイトルImage IndexesソースSQL問合わせとして以下を記述します。
select 'image' as image, i.name, x.is_indexed 
from vec_images i left outer join vec_image_indexes x on i.name = x.file_name

IMAGEを選択し、オブジェクト・ストレージ上の画像が表示されるように、列の書式HTML式として以下を記述します。

<img src="&G_PREAUTH_URL.#NAME#" width="20"></img>


ページ・アイテムP1_QUESTIONに動物の名前を入力し、問い合わせを行った結果を表示する対話モード・レポートを作成します。

リージョンのタイトルResultsソースSQL問合わせとして以下を記述します。
select c001, n001 from apex_collections where collection_name = 'IMAGES'
レイアウト新規行の開始オフにし、リージョンImage Indexesの右横に配置します。


C001を選択し、検索結果の画像が表示されるように、列の書式HTML式に以下を記述します。

<img src="&G_PREAUTH_URL.#C001#" width="200"></img>


プロセス・ビューを開き、4つのボタンを押したときに実行されるプロセスを作成します。

ボタンLOAD_FROM_BUCKETを押したときに実行されるプロセスを、Load From Bucketとして作成します。ソースPL/SQLコードは以下になります。



ボタンUPSERT_VECTORSを押したときに実行されるプロセスを、Upsert Vectorsとして作成します。ソースPL/SQLコードは以下になります。



ボタンFIND_IMAGESを押したときに実行されるプロセスを、Find Imagesとして作成します。ソースPL/SQLコードは以下になります。



ボタンDELETE_VECTORSを押したときに実行されるプロセスを、Delete Vectorsとして作成します。ソースPL/SQLコードは以下になります。



必ずしも必要な作業でありませんが、作成したRESTデータ・ソースのURLパス接頭辞を以下のように変更し、パラメータを定義するとRESTデータ・ソースの再利用が容易になります。

n/:namespace/b/:bucket/o/


パラメータタイプURLパターンデフォルト値はそれぞれ&G_NAMESPACE.&G_BUCKET.になります。必須はいです。

今回作成したアプリケーションの説明は以上になります。

Oracle APEXのアプリケーション作成の参考になれば幸いです。

2023年7月7日金曜日

Cohereでembeddingを生成しPineconeに保存して問い合わせてみる

 ベクトル・データベースのPineconeは、無料でインデックスをひとつ作成することができます。その無料枠で作成したインデックスにCohereのEmbed APIで生成したembeddingを保存し、問い合わせを実行するアプリケーションを作ってみました。

以下の処理を実装しています。

  1. オラクル・データベースの表に検索対象となる文書を保存します。
  2. 上記の保存された文書を引数にしてCohere Embed APIを呼び出し、embeddingを生成します。生成したembeddingは、文書に紐づけてオラクル・データベースに保存します。
  3. オラクル・データベースに保存されている文書のidとembeddingを、PineconeのインデックスにUpsertします。
  4. 画面から問い合わせとなる文字列を入力します。問い合わせ文字列を引数にしてCohere Embed APIを呼び出し、embeddingを生成します。生成したembeddingでPineconeのインデックスにQueryを発行します。
  5. Pineconeより類似した文書のIDが返されるので、オラクル・データベースに保存された文書をIDを指定して取り出し、画面に印刷します。
以下のような動作になります。



上記のアプリケーションのエクスポートは以下に置いてあります。
https://github.com/ujnak/apexapps/blob/master/exports/pinecone-and-cohere-sample.zip

上記のアプリケーションを一からつくるということもないので、実装のポイントのみを解説します。

Pineconeのアカウントの作成方法については、他の説明を参照してください。 Google、GitHubまたはMicrosoftのアカウントと連携するため、それらのアカウントがあればすぐに作成できます。また、有料の機能を使用するにはアカウントをアップグレードする必要があります。


Pineconeの準備作業


PineconeのAPIを呼び出す際に使用するAPIキーを確認します。このAPIキーより、Oracle APEXのワークスペースにWeb資格証明を作成します。

Pineconeのコンソールを開き、ナビゲーション・メニューよりAPI Keysを開きます。作成済みのAPIキーをコピーします。


ワークスペース・ユーティリティよりWeb資格証明を開き、PineconeのAPI呼び出し時に指定するWeb資格証明を作成します。

Web資格証明名前Pinecone API Access静的IDPINECONE_APIとしています。静的IDは、これから紹介するPL/SQLコードに含まれています。

資格証明タイプとしてHTTPヘッダーを選び、資格証明名としてApi-Keyを指定します。資格証明シークレットとして、PineconeのコンソールでコピーしたAPI Keyを設定します。

PineconeのAPIはHTTPヘッダーApi-Keyの値として、APIキーを渡すことで認証されます。


Pineconeのインデックスを作成します。

Pinecone ConsoleのIndexesを開き、Create Indexを実行します。


作成するインデックスのDimensions768を指定します。今回はCohereのEmbed APIを呼び出してembeddingを生成します。その際に(日本語を扱うため)modelとしてembed-multilingual-v2.0を指定します。このembed-multilingual-v2.0のサイズが768なので、PineconeのインデックスのDimensionsを768にします。CohereのモデルのEmbedding SizeはCo.EmbedのAPIのリファレンスに記載されています。

Metricとしてcosine(コサイン類似度)を選択しています。こちらは他にdotproduct(ドット積)、euclidean(ユークリッド距離)を選択することができます。


作成したインデックスのURLを確認し、コピーします。


このインデックスはAPEXアプリケーションのアプリケーション定義置換に、置換文字列G_INDEX置換値として設定します。

アプリケーションのコード中ではG_INDEXとして、PineconeのAPIのエンドポイントを参照します。


Cohereの準備については、こちらの記事を参照してください。

以上でPineconeを使用する準備ができました。


表VEC_EMBEDDINGSの作成



以下のDDLを実行し、表VEC_EMBEDDINGSを作成します。


アプリケーションのユーザーが入力するのは、列TEXTの値だけです。



Cohereによるembeddingの生成



ボタンGENERATE_EMBEDDINGSをクリックしたときに、列TEXTの値をまとめてCohere Embed APIを呼び出し、embeddingを生成します。

ボタンのクリックにより実行されるプロセスは、Generate Embeddingsとして作成されています。


ソースPL/SQLコードは以下です。


EMBEDDINGが未設定の行をAPEXコレクションにコピーし、それを元にEmbed APIのリクエストを作成しています。Embed APIのリクエストには複数のテキストを含めることができます。

APIのリファレンスによると、1つのリクエストに含めることができるテキストの数は96が上限で、それぞれのテキストのトークンは512を超えないことが推奨されています。

要確認なのですが、英語のモデルであればトークン数は単語数と同じと思うのですが、多言語のモデルembed-multilingual-v2.0で特に日本語の場合は、トークン数は文字数と同じではないかと思います。それもあって表VEC_EMBEDDINGSの列TEXTをVARCHAR2(512 CHAR)としています。

CohereのEmbed APIのレスポンスにid属性が含まれています。ただし、リクエストに複数のテキストが含まれ、それぞれのembeddingがレスポンスに含まれています。idだけではレスポンスに含まれる個々のテキストとembeddingの組み合わせを特定できないため、テキスト(およびembedding)の出現順序を列EMBEDDING_SEQとして保存しています。列EMBEDDING_ID(Cohere Embed APIのレスポンスのid)と組み合わせて、テキストとembeddingを一意に特定します。

この列EMBEDDING_IDとEMBEDDING_SEQは、PineconeのUpsertを呼び出す際のベクトルのIDになります。


PineconeへのUpsertの実行



ボタンUPSERT_VECTORSをクリックしたときに、列EMBEDDINGにデータが記載され、列STATUSがCである行をまとめて、PineconeのUpsertリクエストを発行します。

ボタンのクリックにより実行されるプロセスは、Upsert Vectorsとして作成されています。


ソースPL/SQLコードは以下です。


列EMBEDDING_IDとEMBEDDING_SEQよりidの値を作り、valuesにはCohereのEmbed APIが返してきたembeddingをそのまま渡します。vectors属性に、このオブジェクトの配列を指定し、PineconeのUpsertのリクエストとしています。

ここで指定したidの値を、表VEC_EMBEDDINGSの列VECTOR_IDとして保存します。

Pineconeのインデックスに対する問い合わせは、このidの値を検索結果として返します。表VEC_EMBEDDINGSの列VECTOR_IDから、元のテキストを見つけることができます。



Pineconeのインデックスへの問い合わせ



ボタンQUERYをクリックすると、Pineconeのインデックスへの問い合わせが発行されます。ボタン自体は、動的アクションによって動的コンテンツのリージョンであるResponseをリフレッシュしています。

PineconeのインデックスへのQuery APIの発行および結果の表示は、動的コンテンツに実装されています。


ソースCLOBを返すPL/SQLファンクション本体は以下です。


問い合わせ文字列を引数として、CohereのEmbed APIを呼び出しembeddingを生成しています。

そのembeddingを、PineconeのQuery APIのリクエストのvectorとして渡しています。

結果としてインデックスに保存されているベクトルのidとscoreが返されます。idに一致するテキストを、表VEC_EMBEDDINGSの列VECTOR_IDから検索し表示しています。

今回作成したアプリケーションの説明は以上になります。

ベクトル類似性検索を試してみたい、ということでCohereのEmbed APIとPineconeを使ってみました。とりあえず動かすことはできたので目的は達成したのですが、インデックスのメンテナンスや検索結果の評価方法など、色々と考えないといけないことは多そうです。

Oracle APEXのアプリケーション作成の参考になれば幸いです。

2023年7月6日木曜日

対話グリッドの選択リストの列に動的アクションで値を設定する

 対話グリッドの列に設定した値より、別の列の選択リストに値を設定する方法を紹介します。

以下の動画のように、列Product Name製品名を入力したときに、列Shop販売会社を設定します。列Shopに設定された販売会社は初期値で、選択リストを開いて変更できます。


以下のスクリプトを実行し、上記のサンプル・アプリケーションで使用するスキーマを作成します。


アプリケーション作成ウィザードを起動します。アプリケーションの名前選択リストの初期化とします。

ホーム・ページを削除し、代わりに対話グリッドのページを追加します。


対話グリッドのページは、以下の設定で作成します。

ページ名Orders表またはビュー編集を許可を選択します。

表またはビューとしてSHP_ORDERSを選択します。

詳細ホームページとして設定にチェックを入れます。


以上でアプリケーションの作成を実行します。

アプリケーションが作成されたら、ページ・デザイナで対話グリッドのページを開きます。

ページ・プロパティJavaScriptファンクションおよびグローバル変数の宣言に以下を記述します。

var defaultShops;

この変数defaultShopsは、製品名と販売会社の組み合わせをキャッシュするために使用します。


対話グリッドShp Ordersを選択し、静的IDとしてordersを設定します。


プロセス・ビューを開きます。

製品名より販売会社の初期値を求めるプロセスを、Ajaxコールバックとして作成します。

識別名前GET_DEFAULT_SHOPとします。この名前は、JavaScript側から呼び出すファンクションapex.server.processの引数として与えるため、変更はできません(変更する場合は、apex.server.processの引数も変更する)。

タイプコードを実行を選び、ソースPL/SQLコードとして以下を記述します。



レンダリング・ビューを開きます。

PRODUCT_NAME動的アクションを作成します。

識別名前onChange ProductNameタイミングはデフォルトでイベント変更選択タイプ対話グリッドShp OrdersPRODUCT_NAMEになります。


TRUEアクションとしてJavaScriptコードの実行を選択し、設定コードとして以下を記述します。

ページ・アイテムのAPIのsetValueは、引数として表示値(pDisplayValue)、値(pValue)を指定できますが、対話グリッドのsetValueは値(pValue)のみです。そのため、対話グリッドの選択リストやポップアップLOVへ値を設定するには、JSON形式{ d: "表示値", v: "値" } で渡します。


この状態でアプリケーションを実行すると、記事の先頭のGIF動画のように動作します。

とはいえ、対話グリッドに製品名を入力するたびに、ブラウザからデータベースへの問合せが発生するため、今ひとつ反応が良くありません。

ページが開いた時点で、製品名と販売会社の組み合わせを変数defaultShopsにキャッシュする機能を追加します。

プロセス・ビューを開き、AjaxコールバックとしてGET_DEFAULT_SHOPSを作成します。PL/SQLコードとして以下を記述します。



レンダリング・ビューに戻り、ページ・プロパティJavaScriptページ・ロード時に実行に以下を記述します。



以上でアプリケーションは完成です。

今回作成したアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/ig-set-selectlist-default.zip

Oracle APEXのアプリケーション作成の参考になれば幸いです。

2023年7月5日水曜日

TinyMCEでのイメージ・アップロード

 Oracle APEX 23.1よりリッチ・テキスト・エディタの実装がCKEditorからTinyMCEに置き換えられました。今のところCKEditorも使用できますが、TinyMCEへ移行した方が良いことは間違いありません。

CKEditorをカスタマイズせずに使用している範囲であれば、設定ライブラリCKEditorからTinyMCEに問題なく切り替えられるはずです。プラグインを使用してカスタマイズしている場合は、TinyMCEに同等のカスタマイズを行う必要があります。

CKEditorのイメージ・アップロードについては、以下の記事を書いています。

CKEditor5による画像アップロードを実装する(1) - 準備
CKEditor5による画像アップロードを実装する(2) - REST APIの作成
CKEditor5による画像アップロードを実装する(3) - 完成
CKEditor5による画像アップロードを実装する(4) - 画像をオブジェクト・ストレージに保存する
CKEditor5による画像アップロードを実装する(5) - 動的コンテンツを使う

TinyMCEを使用するにあたって、上記の実装の変更点について記述します。

CKEditorのイメージ・アップロードと同様に、Louis Moreauxさんのブログ記事 -
Oracle APEX 23.1 - TinyMCE and image upload - を参考にしています。


APEXアプリケーションの変更



ページ番号2のフォームのページを変更します。

ページ・プロパティJavaScriptファンクションおよびグローバル変数の宣言で、images_upload_handlerを定義します。

images_upload_handlerについては、TinyMCE DocumentationのImage Uploads - images_upload_handlerで説明されています。



マニュアルに記載されているimages_upload_handlerの例に、APEXセッションで認証するためのコードを数行追加しています。また、TinyMCEのimages_upload_handlerの例では、画像アップロードのレスポンスが{ location: "画像のURL" }という形式で返されることが想定されていますが、今回の実装では{ url: "画像のURL" }なので、その点も修正しています。

TinyMCEのimageプラグインを有効にし、images_upload_handlerを設定します。

ページ・アイテムP2_CONTENT設定ライブラリTinyMCEに変更し、詳細初期化JavaScriptファンクションに以下を記述します。


ここで、urlconverter_callbackとして定義したファンクションで、列CONTENTに含まれるイメージURLに、APEXセッションで認証する引数_apex_sessionを追加しています。

CKEditorとは異なり、urlconverter_callbackによるイメージURLの置き換えは、ページ・アイテムP2_CONTENTが保持しているデータを置き換えます。そのまま、作成変更の適用を実行すると、イメージURLに?_apex_session=....の認証子が付加されてデータベースに保存されます。

ワークアラウンドとして、データを保存する前に?_apex_session=...の認証子をP2_CONTENTの内容から取り除くプロセスを作成します。

作成したプロセスはプロセスプロセス・フォームDocumentの上に配置し、先に実行されるようにします。

識別名前apex_sessionの削除タイプコードを実行ソースPL/SQLコードとして以下を記述します。

サーバー側の条件タイプリクエストは値に含まれるを選び、CREATE,SAVEを設定します。


CKEditorが必要としていた、ページ・ロード時の動的アクションP2_CONTENTの再ロードは、TinyMCEでは不要なので削除します。


以上でTinyMCEへの変更は完了です。アプリケーションはCKEditorのときと同様に動作します。

TinyMCEに変更したアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/tinymce-image-sample.zip


オブジェクト・ストレージを使うアプリの変更



オブジェクト・ストレージを使うAPEXアプリは、?_apex_session=の認証子をイメージURLに追加する代わりに、オブジェクト・ストレージの事前承認リクエストに置き換えるように変更します。

ページ番号2のページ・プロパティJavaScriptファンクションおよびグローバル変数の宣言の記述は以下になります。

TinyMCEの初期化JavaScriptファンクションの記述は以下になります。

ページ・アイテムP2_CONTENTを保存する前に、事前承認リクエストを取り除くプロセスのPL/SQLコードは以下になります。名前事前承認URLの削除とします。



ページ・ロード時の動的アクションP2_CONTENTの再ロードを削除すると、TinyMCEの対応は完了です。

アプリケーションのエクスポートは以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/tinymce-image-sample-obs.zip

こちらの記事で紹介している動的コンテンツの追加手順に変更はありません。

動的コンテンツのページを追加したアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/tinymce-image-sample-dyn.zip

以上になります。

Oracle APEXのアプリケーション作成の参考になれば幸いです。

2023年7月3日月曜日

Excelをアップロードしてフォームの穴埋めを行う

 以下のような簡単なExcelシートをAPEXアプリケーションにアップロードし、その内容でフォームの穴埋めをします。


以下のように動作するアプリケーションを作成します。


クラシック・レポートを使ってブラウザで穴埋めする実装と、APEXコレクションを使ってサーバー側で穴埋めする実装の2種類を試してみます。

アプリケーション作成ウィザードを起動し、空のアプリケーションを作成します。名前Excel Formとします。




クラシック・レポートを使う



空白ページを作成します。ページ定義名前Classic Reportとします。


フォームの穴埋めに使うExcelファイルを指定するページ・アイテムを作成します。

識別名前P2_FILEタイプファイル参照...を選びます。設定記憶域のタイプとしてTable APEX_APPLICATION_TEMP_FILESファイルをパージするタイミングとしてEnd of Sessionを選びます。表示形式は任意ですが、ここではInline File Browseを選択しています。

セッション・ステートストレージセッションごと(永続)を選択します。


ファイルをアップロードするボタンを作成します。

識別ボタン名SUBMITラベルSubmitとします。動作アクションページの送信です。


Excelシートから取り出した値を保持するページ・アイテムを作成します。

ページ・アイテムP2_NAME氏名P2_DEPT所属P2_PURPOSE目的P2_AMOUNT金額を保持します。P2_NAMEP2_DEPTP2_PURPOSEタイプテキスト・フィールドP2_AMOUNT数値フィールドとします。

セッション・ステートストレージリクエストごと(メモリーのみ)とします。


クラシック・レポートのリージョンを作成し、アップロードされたExcelを表形式にします。

リージョンを作成し、識別タイトルとしてDocumentタイプとしてクラシック・レポートを選択します。ソースタイプとしてSQL問合せを選択し、SQL問合せとして以下を記述します。

詳細静的IDとしてdocumentを設定します。


属性遅延ロードオフにします。


クラシック・レポートから値を取り出し、ページ・アイテムに設定します。

ページ・プロパティJavaScriptページ・ロード時に実行に、以下を記述します。



ページの表示時に一瞬レポートが表示されます。それを抑止するために、ページのテンプレート・オプションを開き、Deferred Page Renderingチェックを入れます。


以上で実装は完了です。


遅延ロードをオンにする



クラシック・レポートの遅延ロードオンの場合は、クラシック・レポートの表示はページのロードとは非同期で実行されます。そのため、JavaScriptによる穴埋め処理は動的アクションのイベントリフレッシュ後に実行する必要があります。

ページ番号2のコピーをページ番号3として作成し、クラシック・レポートの遅延ロードオンにします。

クラシック・レポートに動的アクションを作成します。

タイミングイベントとしてリフレッシュ後を選択します。


TRUEアクションとしてJavaScriptコードの実行を選択し、設定コードとして以下を記述します。



ページ・プロパティJavaScriptページ・ロード時に実行に記載されたコードは削除します。


以上で実装は完了です。

遅延ロードをオンにしている場合、レポートのリフレッシュ後に一瞬レポートが表示される状況を抑止するのは難しそうです。


APEXコレクションを使う



ページ番号3のコピーをページ番号4として作成し、クラシック・レポートのリージョンを削除します。

アップロードされたExcelからAPEXコレクションを作成し、それからフォームの穴埋めを行います。

プロセス・ビューを表示させ、新規にプロセスを作成します。

識別名前Fill Formタイプとしてコードを実行を選択します。ソースPL/SQLコードに以下を記述します。


サーバー側の条件ボタン押下時SUBMITを指定します。


サーバー側で実行されるプロセスでページ・アイテムに値を設定しているため、穴埋めに使用されるページ・アイテムのセッション・ステートストレージセッションごと(永続)にする必要があります。


反対に、アップロードするファイルを参照するページ・アイテムの設定ファイルをパージするタイミングEnd of Requestセッション・ステートストレージリクエストごと(メモリーのみ)に変更できます。


以上で実装は完了です。

クラシック・レポートを使った実装を紹介していますが、APEXコレクションを使った実装が一番扱いやすいと思います。

今回作成したAPEXアプリケーションのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/fill-form-excel.zip

Oracle APEXのアプリケーション作成の参考になれば幸いです。