XPagesのソースの読み方

XPagesのサンプルアプリの仕組みを理解するには、ソースコードを読めるようになるのが早道。

基本はXML

XPagesのソースコードはXMLで記述されている。

編集ボックスが一個だけ貼り付けられたXPageのソースコードはこんな感じ。

  01: <?xml version="1.0" encoding="UTF-8"?>
  02: <xp:view xmlns:xp="http://www.ibm.com/xsp/core">
  03:   <xp:inputText id="inputText1"></xp:inputText>
  04: </xp:view>
  

ボタンのソースコード

ボタンを押したら、ページを遷移するという処理のソースコード。

  01: <xp:button value="ラベル" id="button1">
  02:   <xp:eventHandler event="onclick" submit="true" refreshMode="complete">
  03:     <xp:this.action>
  04:       <xp:openPage name="/Test01.xsp" target="openDocument"></xp:openPage>
  05:     </xp:this.action>
  06:   </xp:eventHandler>
  07: </xp:button>
  

JavaScriptが組み込まれたソース

以下は、ラベルにDBのタイトルを表示するXPageのソース。

  01: <?xml version="1.0" encoding="UTF-8"?>
  02: <xp:view xmlns:xp="http://www.ibm.com/xsp/core">
  03:   <xp:label value="#{javascript:database.getTitle()}" id="label1"></xp:label>
  04: </xp:view>
  

value="#{...}"の場合、組み込むコードには"が使えない。使うには\"のようにエスケープする必要がある。 2番目の方法は、<![CDATA[...]]>を使っているが、こちらは"が普通に使える。これはXMLで決められている記述方法で、「CDATAセクション」とよぶものらしい。

あと、複数行にわたるコードを組み込む場合は、<![CDATA[...]]>を使うほうが読みやすい。どちらも適当に改行などを入れて読みやすくすることはできる。

データソース関係のソースコード

データソースを使っているページもやや特殊なので、一例をあげて説明してみる。

  01: <?xml version="1.0" encoding="UTF-8"?>
  02: <xp:view xmlns:xp="http://www.ibm.com/xsp/core">
  03:   <xp:this.data>
  04:     <xp:dominoDocument var="dominoDocument1" formName="MainForm" />
  05:   </xp:this.data>  
  06:   <xp:table>
  07:     <xp:tr>
  08:       <xp:td>
  09:         <xp:label value="Hostname:" id="hostname_Label1" for="hostname1" />
  10:       </xp:td>
  11:       <xp:td>
  12:         <xp:inputText value="#{dominoDocument1.hostname}" id="hostname1" />
  13:       </xp:td>
  14:     </xp:tr>
  15:     <xp:tr>
  16:       <xp:td>
  17:         <xp:label value="Pcid:" id="pcid_Label1" for="pcid1" />
  18:       </xp:td>
  19:       <xp:td>
  20:         <xp:inputText value="#{dominoDocument1.pcid}" id="pcid1" />
  21:       </xp:td>
  22:     </xp:tr>
  23:   </xp:table>
  24: </xp:view>
  

ソースコードを整形するツール

XPageのソースは適当なところで改行されてたり、JavaScriptのコードのインデントがずれてたりしてて、読みやすいとはいえない。

そこで、ソースコードを整形して、自分好みの形に整形してみる。 こんな感じに整形できる。

  # 整形前
  <xp:panel themeId="Panel.header">
          <xp:text escape="true" themeId="ComputedField.header.subtitle"
                  id="cfAuthorProfileTitle">
                  <xp:this.value><![CDATA[#{javascript:var cc             = getComponent("ccProfileHeader").getPropertyMap();
  var name        = cc.getProperty("name");
  var format      = res.getString("authorprofile.title");
  
  if (name == null) {
      name = cc.getProperty("nameAbbreviated");
  }
  
  I18n.format(format, name);}]]></xp:this.value>
          </xp:text>
  </xp:panel>
  

 

  # 整形後
  <xp:panel themeId="Panel.header">
    <xp:text escape="true" id="cfAuthorProfileTitle" themeId="ComputedField.header.subtitle">
      <xp:this.value>
        <![CDATA[
          #{javascript:
            var cc             = getComponent("ccProfileHeader").getPropertyMap();
            var name        = cc.getProperty("name");
            var format      = res.getString("authorprofile.title");
  
            if (name == null) {
                    name = cc.getProperty("nameAbbreviated");
            }
  
            I18n.format(format, name);
          }
        ]]>
      </xp:this.value>
    </xp:text>
  </xp:panel>
  

整形するツールはRubyで書いた。xmlをsample.xmlとかにコピーし、ruby analize.rb sample.txt のように使う。 エラー処理は省いているので、開始タグがぬけているような不完全なXMLではエラーになる。文字コードも合わせておかないとうまくいかないかも。 JavaScriptのコードは整形できてない。

  require "rexml/document"
  include REXML
  
  def analize_cdatas(cdatas)
    lines = []
    cdatas.each {|cdata|
      lines.push "<![CDATA["
  
      code_str = cdata.to_s
      if code_str =~ /^\#\{javascript\:(.+)\}$/m
        js_code_str = $1
  
        lines.push "  #\{javascript:"
        js_code_str.split(/\n/).each {|oneline|
          oneline.gsub!(/\t/, "  ")
          lines.push "    #{oneline}"
        }
        lines.push "  }"
      else
        lines.push "  #{cdata.to_s}"
      end
      lines.push "]]>"
    }
    lines
  end
  
  def show_element(element, level)
    sp = '  ' * level
  
    if element.has_attributes?
      attr_list = []
      element.attributes.each {|name, value|
        attr_list.push "#{name}=\"#{value}\""
      }
      open_tag = "#{sp}<#{element.expanded_name} #{attr_list.join(' ')}>"
  
    else
      open_tag = "#{sp}<#{element.expanded_name}>"
    end
    if (element.text) and (element.text.gsub(/\s/, "") != "") and (element.cdatas.size == 0)
      open_tag += element.text
    end
    @output_list.push open_tag
    current_output_lineno = @output_list.size
    # この要素がCDATAセクションを持っているか
    if element.cdatas.size > 0
      cdata_lines = analize_cdatas(element.cdatas)
      cdata_lines.each {|oneline|
        @output_list.push "#{sp}  #{oneline}"
      }
    end
  
    element.each_element { |subelem|
      show_element(subelem, level + 1)
    }
  
    # 出力行数が増えてないなら、開始タグと終了タグの間に挟むべきものがないということ
    if current_output_lineno == @output_list.size
      last_output = @output_list.pop
      if last_output =~ /\>$/
        last_output.gsub!(/\>$/, " />")   # <xp:tag....> → <xp:tag.... />
        @output_list.push last_output
      else
        @output_list.push "#{last_output}</#{element.expanded_name}>"
      end
    else
      @output_list.push "#{sp}</#{element.expanded_name}>"
    end
  end
  
  @output_list = []
  filename = ARGV.shift
  doc = Document.new File.new(filename)
  xml_decl = doc.xml_decl   # XML宣言
  if xml_decl.to_s != ""
    @output_list.push xml_decl.to_s
  end
  show_element(doc.root, 0)
  index = 1
  @output_list.each {|oneline|
    printf("%03d: %s\n", index, oneline)
    index += 1
  }