JavaScript Examples
第31回 ExcelのWebビューアをJavaScriptで作る

 前回に引き続き今回は、業務にJavaScriptを利用する という視点から、業務の定番ソフトExcelと連携する方法 を紹介したい。今回紹介する方法は、ExcelをWebページ として保存してViewerに利用するというものだが、 最近のExcelはXMLスキーマを無料で公開するなど、踏み 込んだ開発も可能になってきている。




 Excelは実はHTML作成ツールでもある。ただ、Excel関連
の書籍を見てもこのことはあまり大きくは書かれていない
ものが多い。まるでほんの刺身のつま程度にしか扱われて
いないのだが、あなどってはいけない。表組みやグラフな
どを組み込んだページを作ろうと思ったらこれほど使いや
すいツールはちょっと無い。やはり餅は餅屋というものだ。

 現在ではExcelは実務系ソフトの定番であり、ほとん
どどこの事務所にも常備されていると言っても過言では
ない状態だ。この使い慣れたツールを使ってそのまま
HTML作成できるメリットは小さくない。

 Excelの保存形式に「Webページ」というのがあるのを
ご存知だろうか?メニューの[ファイル]-[Webページとして
保存]を選ぶか、保存のダイアログの下の方にある「ファ
イルの種類」から選択することができる。

 これだけで、Excelで見たままのレイアウトのWebページ
が出力されるのだ。しかも、複数シートに渡るブックな
ページはフレーム処理のためのJavaScriptまで自動生成
してくれるというつくりだ。

 この機能は、筆者の記憶ではExcel97の頃からすでに実装
されており、Win版の2000,2002,2003は当然ながらMac版の
v.xでも使えるようになっている。

 実は、当初筆者は、ExcelのXMLページとしての保存機能
を使ってDOM処理する話を書きたいと思っていたのだが、XML
は残念ながらMac版の出力形式としてサポートされていなか
ったため、どのExcelでも利用できる今回のWebページ保存
を取り上げることにした。ただ、2003.12のExcelのXMLスキ
ーマ無料公開のインパクトは小さくないと思われるので、
今後のExcelとXMLの関連には注意が必要かもしれない。


今回のテーマのポイント
 1. Excelを表&グラフ用HTML作成ツールとして使ってみる  2. Excelが保存するWebページはHTML/JavaScriptで書かれている  3. Excel Webページの情報取得と読み込み方法
ExcelのWebページデータをブラウザで閲覧するビューア ブック別のシートリストもメニューに自動展開してくれるExcelのWebページビューアを作ってみよう。 ■まず使ってみよう  このビューアはJavaScriptでできている。 JavaScriptだからいろいろな改変をソースを 読みつつ追加していくこともできるが、まず、 どういう機能を持っているのか、実際に使っ て確かめてみよう。    まず、Web Designingのサイト (http://book.mycom.co.jp/wd/currentissue/) へ行ってJavaScript Lab.のデータをダウンロ ードして欲しい。この際に必要な、パスワード はWeb Designingの目次に書いてある。  ダウンロードしたファイルを解凍し、ホルダ を開くとsampleというホルダの中にexceleview というホルダがありその中のindex.htmを開くと ビューアが開くので試してみて欲しい。  もちろん、この時あなたのマシンにExcelが 入っている必要はない。これがExcelをWebペー ジ化するメリットのひとつだ。  ちなみに、excelviewホルダのファイル ツリーは次のようになっている。Book1や Book2はExcelから出力したものだから、 excelviewホルダの基本構成はindex.htmと filelist.jsという2つのファイルとtools という1つのホルダだ。

 /excelview-+- index.htm (トップページ)             |             +- filelist.js (ページのリスト)             |          (  +- Book1.htm (Excelから出力) )              |          (  +- Book1.files/ (Excelから自動出力) )              |          (  +- Book2.htm (Excelから出力) )              |          (  +- Book2.files/ (Excelから自動出力) )              |             +- /tools                  |                  +-menu.htm (スクリプト本体)                  |                  +-howto.htm (使い方のページ)                  |                  +-/imgs (各種画像)
 index.htmを開いたメニューにはexceleviewホ ルダにあるBook1とBook2の中のシートがそれぞ れ展開されてリンクになっている。つまり、 メニューのシート名をクリックすれば右側の フレームへそのシートが読み込まれるというわけだ。  一応、メニューをクリックするとメニュー アイテムが開閉したりホルダ画像も開閉する 仕掛けも少しつけてある。 ■利用方法  さて、どんな動作をするのかがわかったら次 は自分でExcelからWebページを出力してビューア でのぞいてみよう(今度はExcelが必要だ)。  手順は次のようなものだ。  1. Excelを「Webページとして保存」で excelviewホルダへ保存すると...

****.htm と 複数シートがある場合は***.filesという ホルダーができる。
 ↓  2. excelviewホルダ内の「filelist.js」を エディタで開き保存したファイル名を追記する。
 これだけだ。ポイントは、2の filelist.js作成部分だが、ようは、作 成したファイル名を次のように exFile[1] = "総務部.htm" exFile[2] = "営業部.htm" exFile[3] = "宴会部.htm" : : といった具合に[]の中の連番だけ変えて書き 足すだけでよい。あとは、スクリプト側で 読み込んで処理してくれる。 これができたら、excelviewホルダ内の 「index.htm」をブラウザで開けばOKだ。  その後、Excelを修正したら再度の要領で excelviewホルダへ保存すればよいし、 追加/削除するなら「filelist.js」内のリス トを修正すれば 自動的にメニューが変更さ れて展開されるというわけだ。  また、このビューアはindex.htmをトップペ ージとするフレームでできており、 次のようなフレーム構成となっている。

  index.htm-+- menu (メニューフレーム)             |             +- excelFrame                (エクセルWebページを読み込むフレーム)
【index.htm】

<html> <head> <title>Excel WebViewer</title> </head> <frameset             cols  = "200,*"             frameborder   = 0             framespacing  = 0             border        = 0>   <frame               src = "./tools/menu.htm"              name          = "menu"               frameborder   = 0               framespacing  = 0               border        = 0               marginwidth   = 0              marginheight  = 0>   <frame               src = "./tools/howto.htm"              name          = "excelFrame"                frameborder   = 0               framespacing  = 0               border        = 0               marginwidth   = 0              marginheight  = 0> <noframes> <body onload="self.focus()"> このページを見るのにはフレームの表示が できるブラウザが必要です。 </body> </noframes> </frameset> </html>
         次のページからこのスクリプト本体が書かれている menu.htm を解説していこう。 Excelファイル名の取得から起動、そしてメニューを生成する .js外部ファイルでリストを取得し、bodyタグのonloadで起動する。  とりあえず、スクリプトの前半部分だ。 まず、最初にexFileという名前の配列を 作っている。 // Excelファイル名用配列 var exFile = new Array() この配列は次のscriptタグのsrcで読み込まれ るfilelist.jsのなかで exFile[0] = "tools/howto.htm" exFile[1] = "総務部.htm" exFile[2] = "宴会部.htm" といったファイル名をセットするためのも のだ。これで、ファイル名データを取得した らいよいよスクリプトの本番になる。  「変数の設定1」「変数の設定2」はコメン トを参照していただくとして「起動」をみて 欲しい。startLoadExcels()がこのスクリプト で最初に動作する関数で次のページのbodyタ グ内のonloadイベントによって起動する。  この関数から起動するloadExcel()はエクセ ルファイルをフレームへ読み込む関数だが引 数に前述のexFile[]配列を使っている。最初は、 exFile[0]、つまりtools/howto.htmが読み込ま れるというわけだ。  その後、読み込まれたファイルの情報を利 用して、mkMenu()関数がメニューを生成して いくのだが、読み込みに時間がかかったり、 失敗する場合も考慮している。

【./sample/tools/memu.htm】 <script language="JavaScript"> <!-- ////////////////////////////////////////////////////////////////////// // Excel WebViewer 2003.11.30 // // Webページとして保存したExcelデータをブラウザで閲覧するための // エクスプローラー風メニューを自動生成するスクリプトです。  //-------------------------------------------------------------------- // Support : http://game.gr.jp/js/       useFree * 使用/改変も自由です //====================================================================
  // Excelファイル名用配列   var exFile = new Array()
//--> </script> <!--  filelistの読み込み  --> <script language="JavaScript" src="../filelist.js"></script> <!--  スクリプト本体  --> <script language="JavaScript"> <!--
/*========================================================*/ // 変数の設定1 //
  //読み込みが遅れた場合の再読み込みトライ回数   var reloadMax = 5
/*========================================================*/ // 変数の設定2 (あまり変更しない変数) //
  // memu.htmからツールホルダへのパス   var toolsdir    = "./"   // memu.htmからExcelデータホルダへのパス   var exceldir    = "../"   //ファイル読込みカウンター   var exfi = 0   // メニューアイテム数   var menulength = 0   //読み込みが遅れた場合の再読み込みカウンター   var reloadcounter  = 0
/*========================================================*/ // 起動 //
  // ファイル読込み起動(onloadで起動します)   function startLoadExcels()   {     loadExcel(exFile[exfi])   //最初のファイル(使い方)読み込み   }
  // ファイル読み込み & メニュー生成起動   function loadExcel(url)   {      //urlで指定されたファイルを右フレームへ読込む      parent.frames.excelFrame.location.href=exceldir+url
     //mkMenu()を実行(読み込みタイミング調整)      if(exFile.length > 0)         setTimeout("mkMenu('"+url+"')",loadinterval())
  }
  //読込みが遅れた場合の再読込みタイミング調整   function loadinterval()   {     //再読込み回数が増える毎にインターバルを長くする     switch (reloadcounter)     {       case 0  : return 500   ; break ;       case 1  : return 1000  ; break ;       case 2  : return 1500  ; break ;       case 3  : return 2000  ; break ;       case 4  : return 5000  ; break ;       case 5  : return 8000  ; break ;       default : return 10000 ;     }   }
/*========================================================*/ // メニュー生成 // // mkMenu(url)でファイルを読み込み、createMenu(html)で // 生成したdivへ出力します。
// filelist.js内のリストにもとづいてすべてのブックを // 順番に読込み、各ブック内のシートを取得し展開します。 // 読込みに失敗すると(または遅いと)リロードしますが、 // このリトライの回数は変数reloadMaxで決めます。また // リトライ回数が増えると関数loadinterval()により、 // 次に読み込むまでのインターバルが長くなります。
  function mkMenu(url)   {     //メニューtop 用divのID     var memuId = 'menu' + menulength     //メニューItem 用divのID     var memuItemId = 'menu' + menulength + '_0'
    if(parent.frames.excelFrame)     {       //Excelブック読込用フレーム       ex = parent.frames.excelFrame
      // 読込み失敗時の処理        //       //現在のExcelブック読込用フレームのファイル名を取得       var exFileName = getFileName(ex.location.href)
      //引数urlとExcelブック読込用フレームのファイル名が違えば再帰       if( exFileName != url )       {          if( reloadcounter <= reloadMax )          {             reloadcounter++  //再読込み回数をインクリメント            loadExcel(url) //再帰            return          } else {            var msg = "読み込みを失敗したファイルがあるかも知れません。\\n"                     + "リロードするか、filelist.js を調べてください。"            alert(msg)            return          }
      } else {
        //Excelブック内のシートのurlを配列を取得         var hrefs = getLinks(ex,menulength)
        //メニューに書込むシート名の設定         if(hrefs.length==1) {           if( exFile[menulength].split('.')[0] =="tools/howto")           {             // 使い方ページの場合 "使い方" というシート名にする             var menuItem = [ "使い方" ]           } else {             // 単体シートページの場合はブック名をシート名にする             var menuItem = [ exFile[menulength].split('.')[0] ]           }
        } else {
          // 複数シートページの場合はブックのHTML内よりシート名を取得する           var menuItem = ex.c_rgszSh         }
        // メニュー用HTMLを作成         html=""         html+="<div class='menutop' style = 'height:20px' "         html+="     id='"+memuId+"' onclick='openMenu(this)'>"         html+="<img src = '" + toolsdir + "imgs/minus.gif'> "         html+=  exFile[menulength].split('.')[0]         html+="</div>"
        html+="<div class='menuitems' id='"+memuItemId+"'"         html+="     style='display:block;font-size:12px'>"         for( i = 0 ; i < menuItem.length ; i++ )         {           var txt  = menuItem[i]           html = html + "<img src='" +toolsdir + "imgs/holder0.gif' "           html+="             name='" +memuItemId+"_"+i+"'>"           html+=" <a href='java"           html+="script:loadToExcelFrame("           html+= hrefs.length        +   ",\""           html+= memuItemId+"_"+i    + "\",\""           html+= hrefs[i].getAttribute("href")       + "\",\""           html+= exFile[menulength]  + "\")'>"+txt+"</a><br>"         }         html = html + "</div>"
        // メニュー用HTMLの書出し         createMenu(html)         //ID連番のためのインクリメント         menulength ++ 
      }     }    }
; ; //以下次ページ
Excel Webページを読み込む Excel Webページのフレーム構成に合わせてシート情報を取得しロードする  mkMenu()をsetTimeout()で起動しているの は、読込みのタイミングを調整だ。読み込み 失敗時の再読込み回数が増える毎に loadinterval()関数によってインターバルを 少しずつ長くするという小ワザも使ってみた。 たとえば、失敗1回目の読み込みは0.5秒後だ が、5回目なら5秒待ってみるという具合だ。  createMenu()はmkMenu()で生成したメニュ ーのHTMLをページ内へ出力するための関数だ。 実は、メニュー出力後、ファイル読込みカウ ンター変数exfiを1進めることで、filelist.js の順番にひとつずつメニューを書き出すよう に制御している。  loadToExcelFrame()関数はメニュー完成後、 メニューのリンクをクリックした時にフレー ムへ読み込む処理だ。単体シートページと 複数シートページではExcelが出力するHTMLが 異なるため分岐して対応している。ポイントは 複数シートの場合、シートタブがつくため タブとシートの2フレームが出力されることだ。  これに対して単体シートにはフレームが無い から階層を修正して読み込む必要があるわけだ。  あと、補助処理のopenMenu()関数はメニュー クリック時の表示/非表示をdisplay属性をで 処理しており、getLinks()、getFileName()は リンク属性やファイル名を取り出す命令だ。

  // メニュー出力用div生成   function createMenu(html)   {     // divを生成     divTag = document.createElement('DIV')
    // ボディにdivを追加     menudiv = document.body.appendChild(divTag)     menudiv.innerHTML = html
    // メニュー生成後まだ未生成メニューがあれば続ける     exfi++     if(exfi < exFile.length)   loadExcel(exFile[exfi])     else   parent.frames.excelFrame.location.href = toolsdir + "howto.htm"   }
/*=======================================================*/ // Excel Web Page の読み込み // // メニュー完成後、メニューのリンクをクリックしたときに // 右フレームへシートまたはブックを読み込む処理です。

  // Excelブック内のシート読み込み   function loadToExcelFrame(sheetLength,imgid,itemurl,topurl)   {
    var ex = parent.frames.excelFrame     if( sheetLength == 1 )     {
      //単体シートページの場合excelFrameへtopurlを読込む       ex.location.href = exceldir + topurl
    } else {
      //複数シートページの場合
      if(ex.frSheet)       {
        //現在のExcelブック読込用フレームのファイル名を取得         var fileName = getFileName(ex.location.href)
        if(fileName == topurl)         {           //ブックが同じならシートフレーム(frSheet)だけ書換える           ex.frSheet.location.href = exceldir + itemurl         } else {           //ブックが違えばexcelFrameごと書換えてからシートを読込む           ex.location.href = exceldir + topurl           setTimeout("ex.frSheet.location.href ='"+ exceldir + itemurl+"'",300)         }
      } else {
        //単体ページの場合ブックフレーム(excelFrame)ごと書換える         ex.location.href = exceldir + topurl         setTimeout("ex.frSheet.location.href ='"+ exceldir + itemurl+"'",300)       }     }   }
/*========================================================*/ // 補助処理 //
  // メニューtopクリック時のホルダ画像とmenu表示/非表示切り替え   function openMenu(menuOj)   {     var menuName = menuOj.id + "_0"     var oj = document.getElementById(menuName)
    if( oj.style.display == "none" )     {       menuOj.firstChild.setAttribute("src", toolsdir + "imgs/minus.gif")       oj.style.display = "block"     } else {       menuOj.firstChild.setAttribute("src", toolsdir + "imgs/plus.gif")       oj.style.display = "none"     }   }
  // ブック内のシートのurlを配列.hrefに収めて返す   function getLinks(ex,menunum)   {     var links   = ex.document.getElementsByTagName("Link")
    if( links.length == 1 )     {       //単体ページの場合       shLinks = links       shLinks[0].href = [exFile[menunum]]       return  shLinks
    } else {
      //複数ページの場合       var cnt     = 0       var shLinks = new Array()       for( i = 0 ; i < links.length ; i++ )       {         if(links[i].getAttribute("id") == "shLink" &&            links[i].getAttribute("href") != ""){           shLinks[cnt] = links[i]           cnt ++         }       }       //document.allなら下記でも可       //shLinks = ex.document.getElementsByTagName("Link").item("shLink")       return shLinks     }
  }
  //urlの文字列からファイル名を取り出す   function getFileName( str )   {      var wk = ( "" + str ).split('/')      var res = wk[ wk.length-1 ]       if(res=="howto.htm")          var res =  "tools/howto.htm"      else if(str.indexOf(".files") != -1) //O7対策         var res = wk[ wk.length-2 ] +"/"+wk[ wk.length-1 ]       return res   }
//--> </script>
</head> <body bgcolor = "#cccccc"        onload  = "startLoadExcels()">
<!-- タイトル --> <div id="title"> Excel WebViewer    <a style="text-decoration:none " href="./howto.htm" target="excelFrame"> <font color="#cccccc">[?]</font></a>  </div>
</body> </html>

↑このスクリプトサンプル ./sample/f3/f3.htm
【動作ブラウザ】 win mac linux n4 n6 n7 m1 e5 e6 o7 n4 n6 n7 m1 e5 s1 n4 n6 n7 m1 k3 × ○ ○ ○ ○ ○ ○ × ○ ○ ○ ○ ○ × ○ ○ ○ ×  このスクリプトは、とりあえず読み込んで 表示するだけだが、アイディアしだいでたと えばファイル参照ダイアログを組み込んだり、 JavaScriptでできることは少し書き換えるだ けで実装できるので挑戦してみて欲しい。 注意: *このスクリプトはフレーム間のデータアクセスを含むので同一ドメイン内での処理を想定している。 *ExcelのWebページで保存後のhtmlを再度Excelで読むことも可能だがグラフなどはgif画像に置き換えられているなど元のExcelデータとは多少異なることに注意が必要。再度Excelを修正する際は元のExcelファイルを使うことをお勧めする。 //////////////////////////////////////////////////////////////////// *上記サンプルの動作環境は各サンプルをご確認ください ==================================================================== 凡例 Win n3 -- NetscapeNavogator 3.x n4 -- NetscapeNavogator 4.x n6 -- NetscapeNavogator 6.x n7 -- NetscapeNavogator 7.x m1 -- Mozilla1.x e4 -- Internet Explorer 4.x e5 -- Internet Explorer 5.x e6 -- Internet Explorer 6.x o6 -- Opera 6.0 o7 -- Opera 7.0 Mac n3 -- NetscapeNavogator 3.x n4 -- NetscapeNavogator 4.x n6 -- NetscapeNavogator 6.x n7 -- NetscapeNavogator 7.x m1 -- Mozilla1.x e4.5 -- Internet Explorer 4.5 e5 -- Internet Explorer 5.0 または 5.1 s1 -- Safari Linux n3 -- NetscapeNavogator 3.x n4 -- NetscapeNavogator 4.x n6 -- NetscapeNavogator 6.x n7 -- NetscapeNavogator 7.x m1 -- Mozilla1.x k3 -- Konqueror 3.x --------------------------------------------------------------------




●JavaScript 資料




Toshirou Takahashi tato@fureai.or.jp