Markup

Builder を自作する方法

Author : trashtoy

Table of Contents

概要

新しい Builder クラスを自作するには BuilderContext のそれぞれのサブクラスを作る必要があります.

Context とはノードを変換する際に使われる中間生成物を表すクラスです. 以下の機能を持ちます.

  • 各種 handle メソッド: 各ノードを文字列などに変換するためのロジックを定義します.
  • getResult(): ノードの変換結果を取り出すメソッドです. このメソッドの返り値がそのまま Peach_Markup_Builder::build() の結果として返されます.

引数の Component のクラス名をそのまま返すだけの簡単な Builder を作るチュートリアルを以下に掲載します.

新しい Context クラスの作成

まずは Context クラスを継承して独自の Context を定義します. Context クラスで定義されている抽象メソッドすべてを実装する必要があります.

  1. class MyContext extends Peach_Markup_Context
  2. {
  3.     private $result "";
  4.  
  5.     public function getResult()
  6.     {
  7.         return $this->result;
  8.     }
  9.  
  10.     public function handleCode(Peach_Markup_Code $node)
  11.     {
  12.         $this->result .= get_class($node);
  13.     }
  14.  
  15.     public function handleComment(Peach_Markup_Comment $node)
  16.     {
  17.         $this->result .= get_class($node);
  18.     }
  19.  
  20.     public function handleContainerElement(Peach_Markup_ContainerElement $node)
  21.     {
  22.         $this->result .= get_class($node);
  23.     }
  24.  
  25.     public function handleEmptyElement(Peach_Markup_EmptyElement $node)
  26.     {
  27.         $this->result .= get_class($node);
  28.     }
  29.  
  30.     public function handleNodeList(Peach_Markup_NodeList $node)
  31.     {
  32.         $this->result .= get_class($node);
  33.     }
  34.  
  35.     public function handleNone(Peach_Markup_None $none)
  36.     {
  37.         $this->result .= get_class($none);
  38.     }
  39.  
  40.     public function handleText(Peach_Markup_Text $node)
  41.     {
  42.         $this->result .= get_class($node);
  43.     }
  44. }

新しい Builder クラスの作成

次に先ほど作成した MyContext に処理を渡すための新しい Builder クラスを作成します. 以下のように createContext() メソッドの中で新しい MyContext オブジェクトを生成して返すようにします.

  1. class MyBuilder extends Peach_Markup_Builder
  2. {
  3.     protected function createContext()
  4.     {
  5.         return new MyContext();
  6.     }
  7. }

それではこの MyBuilder クラスを使ってノードを変換してみましょう. 以下のような結果が得られます.

  1. $builder new MyBuilder();
  2. echo $builder->build(new Peach_Markup_Text("sample text")) PHP_EOL;
  3. echo $builder->build(new Peach_Markup_ContainerElement("element1")) PHP_EOL;
  4. echo $builder->build(new Peach_Markup_EmptyElement("element2")) PHP_EOL;
  5.  
  6. /*
  7. Output:
  8. Peach_Markup_Text
  9. Peach_Markup_ContainerElement
  10. Peach_Markup_EmptyElement
  11.  */

再帰的にノードを処理する方法

先ほど作った MyContext は, 内部に子ノードを含むノード (Container と呼びます) を再帰的に処理することが出来ません. 子ノードを再帰的に処理するには以下のようにしてください.

  1. getChildNodes() で子ノードの一覧を取得します
  2. 取得した各子ノードについて handle() メソッドを適用します

handle() は引数のノードの種類に応じて handleText() や handleContainerElement() など適切なメソッドに処理を割り振るメソッドです. (Visitor パターンにおける visit() メソッドに相当します)

以下にサンプルコードを掲載します.

  1. class MyContext2 extends Peach_Markup_Context
  2. {
  3.     private $result;
  4.     private $indent;
  5.  
  6.     public function __construct()
  7.     {
  8.         $this->result "";
  9.         $this->indent new Peach_Markup_Indent();
  10.     }
  11.  
  12.     public function getResult()
  13.     {
  14.         return $this->result;
  15.     }
  16.  
  17.     private function handleCommon(Peach_Markup_Component $c)
  18.     {
  19.         $this->result .= $this->indent->indent(get_class($cPHP_EOL;
  20.         if ($c instanceof Peach_Markup_Container{
  21.             $this->indent->stepUp();
  22.             $childNodes $c->getChildNodes();
  23.             array_walk($childNodesarray($this"handle"));
  24.             $this->indent->stepDown();
  25.         }}
  26.  
  27.     public function handleCode(Peach_Markup_Code $node)
  28.     {
  29.         $this->handleCommon($node);
  30.     }
  31.  
  32.     public function handleComment(Peach_Markup_Comment $node)
  33.     {
  34.         $this->handleCommon($node);
  35.     }
  36.  
  37.     public function handleContainerElement(Peach_Markup_ContainerElement $node)
  38.     {
  39.         $this->handleCommon($node);
  40.     }
  41.  
  42.     public function handleEmptyElement(Peach_Markup_EmptyElement $node)
  43.     {
  44.         $this->handleCommon($node);
  45.     }
  46.  
  47.     public function handleNodeList(Peach_Markup_NodeList $node)
  48.     {
  49.         $this->handleCommon($node);
  50.     }
  51.  
  52.     public function handleNone(Peach_Markup_None $none)
  53.     {
  54.         $this->handleCommon($none);
  55.     }
  56.  
  57.     public function handleText(Peach_Markup_Text $node)
  58.     {
  59.         $this->handleCommon($node);
  60.     }
  61. }
  62.  
  63. class MyBuilder2 extends Peach_Markup_Builder
  64. {
  65.     protected function createContext()
  66.     {
  67.         return new MyContext2();
  68.     }
  69. }

MyBuilder2 を実際に使用したサンプルコードを以下に掲載します. はじめに作成した MyBuilder とは違い, 子ノードについても再帰的に処理出来ていることが確認できます.

  1. $root new Peach_Markup_ContainerElement("root");
  2. $root->append("Text 1");
  3. $root->append("Text 2");
  4. $sub  new Peach_Markup_ContainerElement("sub");
  5. $sub->append("Text 3");
  6. $sub->append("Text 4");
  7. $root->append($sub);
  8. $root->append("Text 5");
  9.  
  10. echo "Test: MyBuilder" PHP_EOL;
  11. $b1 new MyBuilder();
  12. echo $b1->build($rootPHP_EOL;
  13.  
  14. echo "Test: MyBuilder2" PHP_EOL;
  15. $b2 new MyBuilder2();
  16. echo $b2->build($rootPHP_EOL;
  17.  
  18. /*
  19. Output:
  20. Test: MyBuilder
  21. Peach_Markup_ContainerElement
  22. Test: MyBuilder2
  23. Peach_Markup_ContainerElement
  24.     Peach_Markup_Text
  25.     Peach_Markup_Text
  26.     Peach_Markup_ContainerElement
  27.         Peach_Markup_Text
  28.         Peach_Markup_Text
  29.     Peach_Markup_Text
  30.  */