InfoPath フォームのデータを Web ページに表示するには、XSL を記述してフォームを HTML に変換できます。XSL について少し知識があれば、この作業は簡単です。リッチ テキスト フィールドの内容を表示するには、xsl:value-of の代わりに xsl:copy-of を使用する必要があります。
しかし、リッチ テキスト フィールド内のピクチャだけは、この方法ではうまくいかないことがすぐにわかります。InfoPath は、ピクチャを base64 エンコーディングを使用して保存し、その結果の文字列を直接 XML コンテンツ (具体的には img 要素の xd:inline 属性) に埋め込みます。リッチ テキスト フィールド内のピクチャの XHTML は次のようになります。
<img style='WIDTH: 100px; HEIGHT: 122px' tabIndex='-1' src='msoinline/a0d1eb893d744127' xmlns:xd='http://schemas.microsoft.com/office/infopath/2003' xd:inline='/9j/4AAQSkZJRgABAQEASABIAAD...'/> |
src 属性は、InfoPath に対して、エンコードされたピクチャが xd:inline 属性のインラインに保存されていることを通知します。しかし、Internet Explorer は src 属性のコンテンツを理解せず、xd:inline 属性を無視するので、ピクチャの表示に失敗します。xsl:copy-of は、ノードのコンテンツのコピー操作を 1 回実行するので、ピクチャを単独で処理する方法がありません。このため、独自の "copy-of" コードを記述する必要があります。Web ページにインライン ピクチャを表示するには追加の作業が必要ですが、その労力に値する成果が得られます。
このタスクでは、インライン ピクチャを含むリッチ テキスト フィールドを持つシンプルな InfoPath フォームを作成し、デコードされたピクチャを Web ページに表示します。XML フォームから HTML への変換には XSL を使用し、base64 エンコードされたピクチャを Web ページで表示可能にするためには ASPX ページを使用します。あらかじめインターネット インフォメーション サービス (IIS) 6.0 以降をインストールして利用可能にしておく必要があります。また、それをローカルマシンにインストールしていることが前提です。まず、新しい空白のフォームを作成しておきます。この後、タスクに必要なサポートファイルを複数作成します。
フォーム テンプレート
テスト フォーム テンプレートの作成
- [Controls] 作業ウィンドウを開きます。
- ビューにリッチ テキスト ボックスを挿入します。
- リッチ テキスト フィールドをダブルクリックします。
- [Rich Text Box Properties] ダイアログ ボックスで、フィールド名を「RichTextField」に変更し、[OK] をクリックします。
- フォーム テンプレートを「InlinePictures2Html.xsn」という名前で保存し、InfoPath を終了します。
MYFORM.XML ファイル
テスト フォームの作成
- InlinePictures2Html.xsn フォームを開きます。
- リッチ テキスト フィールドに複数のピクチャを挿入します。
- フォームを「C:\Inetpub\wwwroot\form\myForm.xml」という名前で保存し、InfoPath を終了します。
DEFAULT.ASPX ファイル
default.aspx ファイルは Web ユーザに最初に表示されるページです。このページで、InfoPath フォームから HTML Web ページへの XSL 変換が開始されます。これは、リッチ テキスト フィールドのインライン ピクチャを Web ページに表示するために必要な 3 つの主要ファイルの 1 つ目です。次のコードをテキスト エディタにコピーし、「C:\Inetpub\wwwroot\form\default.aspx」という名前でファイルを保存します。これは、myForm.xml ファイルの保存先と同じフォルダです。
<%@ Page Language="C#" %> <%@ Import Namespace="System.Xml" %> <%@ Import Namespace="System.Xml.Xsl" %> <!-- ソース コード作成 Greg Collins --> <script language="C#" runat="server"> void Page_Load(Object sender, EventArgs e) { // 変換対象の XML ファイルに絶対 URL を提供するための XSL 引数を作成 // この引数は URL という名前の xsl:param として利用可能 XsltArgumentList oArgs = new XsltArgumentList(); string sPath = Request.Path.ToString(); sPath = sPath.Substring(0, sPath.LastIndexOf("/")+1) + "myForm.xml"; oArgs.AddParam("URL", "", sPath); xmlFile.TransformArgumentList = oArgs; } </script> <asp:Xml id="xmlFile" runat="server" DocumentSource="myForm.xml" TransformSource="/transform.xsl" /> |
default.aspx ファイルの実際の動作を調べてみましょう。リッチ テキスト フィールドのインラインピクチャをデコードしない場合は、スクリプト ブロック全体が不要になります。スクリプト ブロックの目的は、ルート フォルダから変換対象の XML ファイルまでのパスを取得し、それをパラメータとして XSL に渡すことです。このパスの識別にはコードを使用しますが、単にハードコードすることもできます。
Request.Path は実際は default.aspx ファイルへのパスを返すので、パスのファイル名の部分を削除し、変換対象のXML ファイルの名前と置き換える必要があります。ここで Server.MapPath("myForm.xml") を使用することもできますが、この場合は物理ディレクトリ構造を明らかにすることになるのでセキュリティ上の問題が発生する可能性があります。そこで、代わりにそれを decodePicture.aspx ファイルとして保存します。修正したパスは、URL という名前のパラメータの値になります。これが渡されて transform.xsl ファイルで使用されます。
asp:Xml 要素の 2 つの属性 (id 属性および DocumentSource 属性) の値は、スクリプトブロックで参照される必要があります。id 属性の値は、オブジェクトの名前であり、その TransformArgumentList プロパティがスクリプト ブロックの最後で設定されます。これらの 2 つが一致しない場合、エラーが発生します。DocumentSource 属性の値は、変換対象の XML ファイルの名前であり、この ASPX ファイルの名前を削除した後、パスの最後に追加されます。これらの 2 つが一致しない場合、Web ページで望ましくない結果になることがあります。
TRANSFORM.XSL ファイル
transform.xsl ファイルは、InfoPath フォームを、Web ページに表示される HTML に変換するファイルです。これは、Web ページにリッチ テキスト フィールドのインライン ピクチャを表示するために必要な 3 つの主要ファイルの 2 つ目です。このファイルは、カスタムの "copy-of" 機能を使用して、リッチ テキスト フィールド内の他の XHTML 要素とは別に img 要素を処理します。この分離は、ピクチャのソースが ASPX ページによって提供されることを指定するために重要です。
フォームで使用される任意の名前空間が xsl:stylesheet 要素の名前空間宣言にも含まれていることを確認する必要があります。名前空間を含めていない場合、XSL は処理されません。
次のコードをテキスト エディタにコピーし、「C:\Inetpub\wwwroot\transform.xsl」という名前でファイルを保存します。このファイルは、Web サイト内のどこからでも簡単にアクセスできるように、ルート フォルダに保存されます。
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:cs="C#" xmlns:my="http://schemas.microsoft.com/office/infopath/2003/myXSD/2004-11-03T18:01:22"> <xsl:strip-space elements="*" /> <xsl:output method="html" indent="no" /> <!-- ソース コード作成 Greg Collins (InfoPathDev.com) -->> <!-- 変換対象の XML ファイルの絶対 URL を指定するパラメータ --> <xsl:param name="URL"/> <msxsl:script language="C#" implements-prefix="cs"> <![CDATA[ int nCount = 0; int getCount() { // XML 内のピクチャの順序番号を返す。 // この番号はピクチャをデコードする .xml ファイルへのインデックスとして使用される。 return nCount++; } ]]> </msxsl:script> <xsl:template match="/"> <h1>Rich Text Pictures Displayed in a Web Page:</h1> <xsl:apply-templates /> </xsl:template> <xsl:template match="my:RichTextField"> <xsl:apply-templates /> </xsl:template> <!-- ピクチャ以外の要素用のテンプレート --> <xsl:template match="my:RichTextField//*[name(.) != 'img']"> <xsl:element name="{name(.)}"> <xsl:for-each select="attribute::*"> <xsl:attribute name="{name(.)}"><xsl:value-of select="."/></xsl:attribute> </xsl:for-each> <xsl:apply-templates /> </xsl:element> </xsl:template> <!-- ピクチャ専用のテンプレート --> <xsl:template match="my:RichTextField//*[name(.) = 'img']"> <xsl:element name="img"> <!-- 'src' および 'xd:inline' 以外のすべての属性をコピー --> <xsl:for-each select="attribute::*[name(.) != 'src' and name(.) != 'xd:inline']"> <xsl:attribute name="{name(.)}"><xsl:value-of select="."/></xsl:attribute> </xsl:for-each> <!-- 'src' 属性を生成して base64 ピクチャをデコード --> <xsl:attribute name="src">/decodePicture.aspx?u=<xsl:value-of select="$URL"/>&n=<xsl:value-of select="count(preceding::*[name(.) = 'img'])"/></xsl:attribute> <xsl:apply-templates /> </xsl:element> </xsl:template> </xsl:stylesheet> |
transform.xsl ファイルの実際の動作について調べてみましょう。xsl:param 要素は、その値を、変換を開始した default.aspx ページによって設定されたパラメータから受け取ります。この値は、ルート フォルダから変換対象の XML ファイルへのパスです。このパラメータを使用して、decodePicture.aspx 内のコードが、XML ファイルを読み込んで、その base64 エンコードされた各ピクチャをデコードできるようになるので、このパラメータは必須です。
スクリプト ブロックは、インライン ピクチャのインデックスの作成に使用されます。各ピクチャには 0 から始まり増加する番号が割り当てられます。decodePicture.aspx 内のコードはこの番号を使用して、要求されているピクチャがどれかを判断します。
作成したサンプル フォームは非常に単純なので、メイン テンプレートはページのヘッダーを配置するだけです。実際の処理は、リッチ テキストフィールドのテンプレートが処理されるときに行われます。ピクチャをインラインに保存していない場合は、ここで単純に xsl:copy-of 要素を使用すれば済みますが、このサンプルではインライン ピクチャを扱うので、カスタムの "copy-of" 機能を記述する必要があります。これには、img 要素用とそれ以外の要素用の 2 つのテンプレートが必要です。
1 つ目のカスタム "copy-of" テンプレートは、XPath my:RichTextField//*[name(.) != 'img'] (リッチ テキスト フィールド内で img と指定されていないすべての要素) と一致する必要があります。このテンプレートでは、要素名を同じ名前でコピーし、その各属性をコピーし、テンプレートの適用を続けます。
2 つ目のカスタム "copy-of" テンプレートは、XPath my:RichTextField//*[name(.) = 'img'] (リッチ テキスト フィールド内の img という名前のすべての要素) と一致する必要があります。このテンプレートは、最初のテンプレートと非常によく似ており、同じ名前の要素を作成し、その属性をコピーします。しかし、今回は src および xd:inline の 2 つの属性はコピーしません。src 属性は後で構築されます。また、xd:inline 属性は、decodePicture.aspx ページで処理されるので残します。base64 でエンコードされたピクチャを実際に含むのはこの後者の属性です。
カスタムの "copy-of" 機能を記述したのは、decodePicture.aspx ページを呼び出してデコード済みのピクチャを要求するように img 要素の src 属性を設定するためでした。このために、変換中の XML ファイルへの URL、および処理対象のピクチャへのインデックスを表す 2 つのパラメータを提供します。HTML Web ページ用に変換された img 要素は次のようになります。
<img style='WIDTH: 100px; HEIGHT: 122px' tabIndex='-1' src='/decodePicture.aspx?u=/myForm.xml&n=0'> |
この transform.xsl ファイルのコンテンツは、リッチ テキスト ピクチャを確実に表示するための最低要件です。これ以上の内容については、XSL を記述して、InfoPath フォームの残りの部分を Web ページ用の HTML に変換します。
DECODEPICTURE.ASPX ファイル
decodePicture.aspx ファイルは、base64 でエンコードされたインラインピクチャをデコードする場所です。これは、Web ページにリッチ テキスト フィールドのインライン ピクチャを表示するために必要な 3 つの主要ファイルの 3 つ目です。XSL 変換の一環として作成した img 要素の src 属性が Web ブラウザによって検出されたときに、ブラウザからこのファイルが呼び出されます。
次のコードをテキスト エディタにコピーし、「C:\Inetpub\wwwroot\decodePicture.aspx」という名前でファイルを保存します。このファイルは、Web サイト内のどこからでも簡単にアクセスできるように、ルート フォルダに保存されます。
<%@ Page Language="C#" Debug="true" %> <%@ Import Namespace="System.IO" %> <%@ Import Namespace="System.Xml" %> <%@ Import Namespace="System.Collections" %> <!-- ソース コード作成 Greg Collins (InfoPathDev.com) --> <script language="C#" runat="server"> void Page_Load(Object sender, EventArgs e) { try { // パラメータを取得する string sXmlFile = Request.QueryString["u"]; int nPictureNumber = Convert.ToInt32(Request.QueryString["n"]) * 2; if(null == sXmlFile || 0 > nPictureNumber) return; // XML ファイルへのサーバー パスを取得する sXmlFile = Server.MapPath(sXmlFile); // デコードされたピクチャのキャッシュからの取得を試みる ArrayList arrPicture = (ArrayList)Cache[sXmlFile]; System.Drawing.Image oPicture; // キャッシュ内にない場合はピクチャをデコードし、それをキャッシュに追加する if(null == arrPicture) { // 変換された XML ファイルを読み込む XmlDocument oXml = new XmlDocument(); oXml.Load(sXmlFile); // セクションが機能するように適切な名前空間を追加する XmlNamespaceManager oNsm = new XmlNamespaceManager(oXml.NameTable); oNsm.AddNamespace("rt", "http://www.w3.org/1999/xhtml"); oNsm.AddNamespace("xd", "http://schemas.microsoft.com/office/infopath/2003"); // img 要素の xd:inline 属性全体のリストを取得する XmlNodeList oPictures = oXml.SelectNodes("//rt:img/@xd:inline", oNsm); arrPicture = new ArrayList(); for(int i = 0; i < oPictures.Count; i++) { // base64 でエンコードされた各文字列を実際のピクチャに変換する string sBase64Picture = oPictures.Item(i).Value; byte[] bPicture = Convert.FromBase64String(sBase64Picture); MemoryStream oMemStr = new MemoryStream(); oMemStr.Write(bPicture, 0, bPicture.Length); oPicture = System.Drawing.Image.FromStream(oMemStr); // デコードされた各ピクチャとその形式を配列に追加する arrPicture.Add(oPicture); arrPicture.Add(sBase64Picture.Substring(0,4)); } // ピクチャ配列をアプリケーション キャッシュに格納する Cache.Insert(sXmlFile, arrPicture, new CacheDependency(sXmlFile)); } // ピクチャ番号が配列内の番号より大きい場合は要求を無視する if(arrPicture.Count <= nPictureNumber) return; // 指定されたピクチャおよび形式を取得する oPicture = (System.Drawing.Image)arrPicture[nPictureNumber]; string sPictureFormat = (string)arrPicture[nPictureNumber+1]; // デコードされたピクチャを表示先の Web ページに戻す // R0lG = 大文字の R + 数字のゼロ + 小文字の L + 大文字の G if("R0lG" == sPictureFormat) oPicture.Save(Response.OutputStream, System.Drawing.Imaging.ImageFormat.Gif); else oPicture.Save(Response.OutputStream, System.Drawing.Imaging.ImageFormat.Jpeg); } catch(Exception ex) { // 問題が発生した場合はエラーを表示せず、ピクチャなしで戻る return; } } </script> |
decodePicture.aspx ファイルの実際の動作について調べてみましょう。2 つのクエリ文字列パラメータ (ルートフォルダからインライン ピクチャを含む XML ファイルへのパス、およびデコード対象のピクチャのインデックス) が、カスタムの XSL 変換によって作成された img 要素の src 属性から取得されます。ここで取得したインデックスを 2 倍にします。これは、デコード済みのピクチャを格納する配列には、ピクチャ形式を識別する兄弟情報も格納されているからです。いずれかのパラメータが存在しない場合は、そのまま戻って Web ページに壊れたピクチャのアイコンを表示します。
sXmlFile 変数は、Server.MapPath(sXmlFile) を使用してすぐに絶対サーバー パスに変換されます。この変数は、XML ファイルの読み込みに使用されます。また、キャッシュに格納しているオブジェクトの名前としても使用されます。
最初に行うことは、この特定の XML ファイルが既に解析されキャッシュに格納されているかどうかの検出です。格納されている場合は、キャッシュ内にあるバージョンを使用します。ピクチャをキャッシュすると、デコードされたピクチャが要求されるたびに XML ファイルを読み込む必要がなくなるので、パフォーマンスが大きく向上します。キャッシュ内にない場合は、ピクチャをデコードし、キャッシュに追加する必要があります。
このためには、XML ファイルを読み込み、リッチ テキストフィールド内のピクチャに到達するために要求される名前空間を追加する必要があります。特に追加が必要なのは、リッチ テキストフィールド用と img 要素の xd:inline 属性用の 2 つの名前空間です。名前空間を追加した後、base64 でエンコードされたピクチャのリストを取得し、1 つずつデコードします。
デコードされた各ピクチャは、base64 でエンコードされたピクチャの最初の 4 文字とともに配列に格納されます。これらの 4 文字は、ピクチャの形式が GIF か JPEG かを識別します。JPEG ピクチャは GIF 形式ではあまりきれいに表示されず、また GIF ピクチャの JPEG 形式での表示は動画では許可されないので、ピクチャ形式の識別が必要になります。GIF ピクチャを表す 4 文字は R0lG です。一方、JPEG ピクチャを表す 4 文字は /9j/ です。
InfoPath では GIF と JPEG 以外のピクチャ形式もサポートされていますが、このタスクの目的で使用されるのはこの 2 つの形式だけです。他のすべてのピクチャ形式は、この 2 つの形式のいずれかで正しく表示されるからです。
以上のファイルをすべて用意できたら、Web ページにリッチ テキスト フィールドのインライン ピクチャを正しく表示できます。テストのために、Web ブラウザを起動し、http://localhost/form/default.aspx に移動します。
ここで 1 つ注意しておきたいことがあります。デコードされたインライン ピクチャの表示は、サーバーに保存されている実際のピクチャファイルから読み込まれる場合ほど高速ではありません。しかし、これはリッチ テキスト フィールドの完全なコンテンツをインライン ピクチャも含めて Web ページに表示できる便利さに比べれば些細な問題です。
©2005 Greg Collins. All rights reserved. Licensed to Autonomy Systems, LLC for display on InfoPathDev.com.