開発部の柳井です。今回はJavaScriptの記事です。
アプリケーションはそれ自体の機能のみでなく、拡張機能(アドイン・アドオンとも)をインストールすると好みの機能を追加出来るものがあります。例えばブラウザだとChromeやFirefoxなどでアドインが提供されており、自社製品のGrpMailもChrome向けにGrpMail@Watcherという拡張機能があります。ブラウザだけでなく、Microsoft Officeの製品でもAppSourceというストアでアドインが提供されています。今回はWordのアドインについて紹介します。
概要
Office製品のアドインはHTML + JavaScript + CSSで構成されます。WordなどのOfficeアプリケーションの中で小さいブラウザが動作しているという感覚です。どこのWebサイトを見に行くかという情報は、XML形式のマニフェストファイルを参照します。Wordなどを開きながらWebサイトを見るだけではなく、Office.js(API)を使用することでWordなどのコンテンツそのものも操作出来ます。
チュートリアル
公式のチュートリアルを使いながら、Wordのテキストを抽出してみましょう。Visual Studioでもプロジェクトを作れますがここではnpmを使います。Node.jsとnpmをインストール後、YeomanとOfficeアドイン用のYeomanジェネレーターをインストールします。最新版ではありませんが、例えば以下の様にインストールされます。ちなみにMicrosoft公式ドキュメントの日本語版はinstallが「取り付ける」などたまに変な翻訳になっているところもあるので注意してください。
1 2 3 4 5 6 7 8 |
$ npm install -g yo generator-office $ node -v v14.15.1 $ npm -v 6.14.8 $ yo --version 4.0.0 |
次に、Yeomanジェネレーターを使用してアドインのプロジェクトを作成します。質問項目が表示されるので、矢印キーで選択したり、プロジェクト名を入力します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
$ yo office _-----_ ╭──────────────────────────╮ | | │ Welcome to the Office │ |--(o)--| │ Add-in generator, by │ `---------´ │ @OfficeDev! Let's create │ ( _´U`_ ) │ a project together! │ /___A___\ /╰──────────────────────────╯ | ~ | __'.___.'__ ´ ` |° ´ Y ` ? Choose a project type: Office Add-in Task Pane project ? Choose a script type: JavaScript ? What do you want to name your add-in? My Office Add-in ? Which Office client application would you like to support? Word ---------------------------------------------------------------------------------- Creating My Office Add-in add-in for Word using JavaScript and Taskpane at /指定のパス/My Office Add-in ---------------------------------------------------------------------------------- I'm all done. Running npm install for you to install the required dependencies. If this fails, try running the command yourself. (略) ---------------------------------------------------------------------------------------------------------- Congratulations! Your add-in has been created! Your next steps: 1. Go the directory where your project was created: cd /指定のパス/My Office Add-in 2. Start the local web server and sideload the add-in: npm start 3. Open the project in VS Code: code . For more information, visit http://code.visualstudio.com. Please visit https://docs.microsoft.com/office/dev/add-ins for more information about Office Add-ins. ---------------------------------------------------------------------------------------------------------- |
最初の質問で「Office Add-in Task Pane project using Angular framework」などを選ぶとAngular、React、Vue.jsなどのフレームワークを使えますが、ここではバニラJavaScriptで行きます。
プロジェクト名のディレクトリに移動し、package.jsonを見ると、npmのコマンドが書かれています。以下のコマンドを実行するとWebサーバが起動し、ファイルが変更された時も随時ビルドします。
1 2 3 4 5 6 7 |
$ cd My\ Office\ Add-in $ ls -a . assets package-lock.json webpack.config.js .. babel.config.json package.json .eslintrc.json manifest.xml src .vscode node_modules tsconfig.json $ npm run dev-server |
https://localhost:3000というURLでこのWebサーバにアクセス出来る様になりますが、普通にブラウザでアクセスしても何も起きません。
以下を実行し、Word上からアドインを起動します。
1 |
$ npm start |
Wordがうまく起動しなかったり、アドインのアイコンが[ホーム]の右端に表示されない場合は、[挿入]→[アドイン]→[個人用アドイン](下向き三角)→[開発者アドイン]から「Consoto〜」を選んでください。「Consoto」とはMicrosoftがよく使う「サンプル」とか「ほげほげ」の意味です。
アドインが右側に表示されたら、下部の「Run」をクリックしてみましょう。「Hello World」という文字が青色でWordに挿入されます。
こちらにあるサンプルコードでは、テキストを変更したり画像を挿入したりしています。JavaScriptからWordのコンテンツにアクセスするので非同期処理としてasync/awaitが書かれています。今回はこちらを参考に、Wordのテキスト全体を取得します。サンプルコードによってはOffice.initializeとOffice.onReady()の違いがありますが、最初は気にしなくて良いです。$('#submit')の様なセレクターを使いたい場合は、適宜jQueryを追加してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<body class="ms-font-m ms-welcome ms-Fabric"> <header class="ms-welcome__header ms-bgColor-neutralLighter"> <img width="90" height="90" src="../../assets/logo-filled.png" alt="Contoso" title="Contoso" /> <h1 class="ms-font-su">Welcome</h1> </header> <section id="sideload-msg" class="ms-welcome__main"> <h2 class="ms-font-xl">Please sideload your add-in to see app body.</h2> </section> <main id="app-body" class="ms-welcome__main" style="display: none;"> <h2 class="ms-font-xl">テキストを抽出する</h2> <div role="button" id="run" class="ms-welcome__action ms-Button ms-Button--hero ms-font-xl"> <span class="ms-Button-label">Run</span> </div> <div> <textarea id="text" cols="50" rows="10"></textarea> </div> </main> </body> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
Office.onReady((info) => { if (info.host === Office.HostType.Word) { document.getElementById("sideload-msg").style.display = "none"; document.getElementById("app-body").style.display = "flex"; document.getElementById("run").onclick = run; } }); export async function run() { return Word.run(async (context) => { getText() .then(function (text) { display(text); }) .catch(function (message) { console.log("message", message); }) .finally(function (message) { }); await context.sync(); }); } // 編集中文書のテキストを抽出する function getText() { return new Promise(function (resolve, reject) { Office.context.document.getFileAsync(Office.FileType.Text, { sliceSize: 100 }, function (result) { if (result.status == Office.AsyncResultStatus.Succeeded) { const myFile = result.value; const state = { file: myFile, counter: 0, sliceCount: myFile.sliceCount }; let ps = []; for (; state.counter < state.sliceCount; state.counter++) { ps.push(getSlice(state)); } console.log('state.sliceCount'); console.log(state.sliceCount); Promise.all(ps) .then(function (paramTextArray) { // テキスト抽出成功 let paramText = paramTextArray.join('');// 1つに連結 let paramTextSize = bytes(paramText); console.log('paramTextSize'); console.log(paramTextSize); resolve(paramText); }) .catch(function (message) { console.log("message", message); reject("テキスト抽出に失敗しました。"); }) .finally(function (message) { myFile.closeAsync(); return; }); } else { console.log("message", message); reject("テキスト抽出に失敗しました。"); } } ); }); } // 編集中文書のテキストのうち、指定したsliceを抽出する function getSlice(state) { console.log('state.counter'); console.log(state.counter); return new Promise(function (resolve, reject) { // 1スライスずつテキストを抽出する state.file.getSliceAsync(state.counter, function (result2) { if (result2.status == Office.AsyncResultStatus.Succeeded) { // 成功 return resolve(result2.value.data); } else { // 失敗 console.log("result2.status", result2.status); return reject("テキスト抽出に失敗しました。"); } }); }); } // テキストのバイト数を取得する function bytes(s) { return (new Blob([s])).size; } function display(message) { document.getElementById("text").innerHTML = message; } |
サンプルのsendFile()内では、抽出したテキストをWebサーバにPOSTしていますが、今回のgetText()ではテキストボックスに表示するだけです。Office.jsの仕様上、document.getFileAsync()は一気にテキストを抽出出来ず、file.getSliceAsync()で少しずつ抽出します(slice)。抽出したテキストをまとめるためにPromise.all()を使っています。細切れなのが分かる様にsliceSizeを100バイトにしていますが、サンプルにある様に100,000バイトなら余裕で動きます。
開発と公開
console.log()を使っていますが、macのSafariのWebインスペクタを使うにはこちらのコマンドで設定を変更する必要があります。なお、開発者ツールはmacではSafari、WindowsではOSやOfficeのバージョンによってEdge・IE11など内部で動作するブラウザのものになります。
1 |
$ defaults write com.microsoft.Word OfficeWebAddinDeveloperExtras -bool true |
npmで起動するWebサーバではなくApacheなどを使う場合は、ドキュメントルートにビルドしたファイルを配置し、Word側でサイドロード(ストアで公開しない場合)の設定をします。macでは特定のフォルダに、Windowsの場合は任意の共有フォルダーにマニフェストファイルを置きます。またはAppSourceで公開したり、Office365の管理センターで組織内ユーザに展開したりも出来ます。
デスクトップ(OSのネイティブ)のアプリケーションは、アドインだとしてもCやSwiftなどを使わないといけないとイメージしていましたが、JavaScriptで手軽に開発が出来る様になっています。Officeを使いながら何かを自動化したい時などにいかがでしょうか?