1: <?php
2: /*
3: * Copyright (c) 2014 @trashtoy
4: * https://github.com/trashtoy/
5: *
6: * Permission is hereby granted, free of charge, to any person obtaining a copy of
7: * this software and associated documentation files (the "Software"), to deal in
8: * the Software without restriction, including without limitation the rights to use,
9: * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
10: * Software, and to permit persons to whom the Software is furnished to do so,
11: * subject to the following conditions:
12: *
13: * The above copyright notice and this permission notice shall be included in all
14: * copies or substantial portions of the Software.
15: *
16: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17: * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18: * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19: * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20: * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21: * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22: */
23: /**
24: * PHP class file.
25: * @auhtor trashtoy
26: * @since 2.0.0
27: */
28: namespace Peach\Markup;
29: use Peach\Util\ArrayMap;
30: use Peach\Util\Values;
31:
32: /**
33: * 既存の Component をラップして, ノードツリーの構築を簡略化・省力化するための糖衣構文を備えたクラスです.
34: * 主に (MVC フレームワークで言うところの) View の範囲で使用されることを想定しています.
35: */
36: class HelperObject implements Container
37: {
38: /**
39: * このオブジェクトを生成した Helper オブジェクトです.
40: * このオブジェクトの prototype を生成する場合などに使用されます.
41: * @var Helper
42: */
43: private $helper;
44:
45: /**
46: * このオブジェクトがラップしている Component です.
47: * @var Component
48: */
49: private $node;
50:
51: /**
52: * 指定された Helper オブジェクトに紐付けられた新しいインスタンスを構築します.
53: * このコンストラクタは {@link Helper::tag()} から呼び出されます.
54: * 通常は, エンドユーザーがコンストラクタを直接呼び出す機会はありません.
55: *
56: * @param Helper $helper
57: * @param mixed $var このオブジェクトがラップする値 (テキスト, Component など)
58: */
59: public function __construct(Helper $helper, $var)
60: {
61: $this->helper = $helper;
62: $this->node = $this->createNode($var, $helper);
63: }
64:
65: /**
66: * 引数の値をノードに変換します.
67: * 返り値は, 引数によって以下のようになります.
68: *
69: * - {@link Node} 型オブジェクトの場合: 引数自身
70: * - {@link NodeList} 型オブジェクトの場合: 引数自身
71: * - {@link HelperObject} 型オブジェクトの場合: 引数のオブジェクトがラップしているノード
72: * - 文字列の場合: 引数の文字列を要素名に持つ新しい {@link Element}
73: * - null または空文字列の場合: 空の {@link NodeList}
74: * - 上記に当てはまらない場合: 引数の文字列表現をあらわす {@link Text} ノード
75: *
76: * @param mixed $var 変換対象の値
77: * @param Helper $helper ノードの生成に利用する Helper オブジェクト
78: * @return Component 変換後のノード
79: */
80: private function createNode($var, Helper $helper)
81: {
82: if ($var instanceof Node) {
83: return $var;
84: }
85: if ($var instanceof NodeList) {
86: return $var;
87: }
88: if ($var instanceof HelperObject) {
89: return $var->getNode();
90: }
91: if (is_string($var) && strlen($var)) {
92: return $helper->createElement($var);
93: }
94: $nodeName = Values::stringValue($var);
95: return strlen($nodeName) ? new Text($nodeName) : new NodeList();
96: }
97:
98: /**
99: * このオブジェクトがラップしているノードを返します.
100: * @return Component
101: */
102: public function getNode()
103: {
104: return $this->node;
105: }
106:
107: /**
108: * このオブジェクトの子ノードとして, 指定された値を追加します.
109: * このオブジェクトがラップしているオブジェクトが Container でない場合は何もしません.
110: *
111: * @param mixed $var 追加される値
112: */
113: public function appendNode($var)
114: {
115: $node = $this->node;
116: if ($node instanceof Container) {
117: $appendee = ($var instanceof HelperObject) ? $var->getNode() : $var;
118: $node->appendNode($appendee);
119: }
120: }
121:
122: /**
123: * このオブジェクトの子ノードとして, 指定された値を追加して, 最後に自分自身を返します.
124: * メソッドチェーンを可能にするための appendNode() のシンタックスシュガーです.
125: *
126: * @param mixed $var 追加される値
127: * @return HelperObject 自分自身
128: */
129: public function append($var)
130: {
131: $this->appendNode($var);
132: return $this;
133: }
134:
135: /**
136: * 指定された Container にこのオブジェクトを追加します.
137: * 以下の 2 つのコードは, どちらも $obj2 の中に $obj1 を追加しています.
138: * <code>
139: * $obj1->appendTo($obj2);
140: * $obj2->append($obj1);
141: * </code>
142: * {@link HelperObject::append()}
143: * との違いは, 返り値が $obj1 になるか $obj2 になるかという点にあります.
144: *
145: * @param Container $container 追加先の Container
146: * @return HelperObject 自分自身
147: */
148: public function appendTo(Container $container)
149: {
150: $container->appendNode($this->getNode());
151: return $this;
152: }
153:
154: /**
155: * 指定された文字列を整形済コードとして追加します.
156: *
157: * @param string|Code $code 追加対象の整形済文字列
158: * @return HelperObject このオブジェクト自身
159: */
160: public function appendCode($code)
161: {
162: if (!($code instanceof Code)) {
163: return $this->appendCode(new Code($code));
164: }
165:
166: return $this->append($code);
167: }
168:
169: /**
170: * {@link Element::setAttribute()}
171: * および
172: * {@link Element::setAttributes()}
173: * の糖衣構文です.
174: * 引数が配列の場合は setAttributes() を実行し,
175: * 引数が 1 つ以上の文字列の場合は setAttribute() を実行します.
176: * もしもこのオブジェクトがラップしているノードが Element ではなかった場合,
177: * このメソッドは何も行いません.
178: *
179: * jQuery のようなメソッドチェインを実現するため, このオブジェクト自身を返します.
180: *
181: * @param string|array|ArrayMap $var セットする属性
182: * @return HelperObject このオブジェクト自身
183: */
184: public function attr()
185: {
186: $node = $this->node;
187: if (!($node instanceof Element)) {
188: return $this;
189: }
190:
191: $count = func_num_args();
192: if (!$count) {
193: return $this;
194: }
195:
196: $args = func_get_args();
197: $first = $args[0];
198: if (($first instanceof ArrayMap) || is_array($first)) {
199: $node->setAttributes($first);
200: } else {
201: $second = (1 < $count) ? $args[1] : null;
202: $node->setAttribute($first, $second);
203: }
204: return $this;
205: }
206:
207: /**
208: * このオブジェクトの子ノード一覧をあらわす HelperObject を返します.
209: * @return HelperObject
210: */
211: public function children()
212: {
213: if ($this->node instanceof NodeList) {
214: return $this;
215: }
216:
217: $result = $this->helper->tag(null);
218: if ($this->node instanceof Container) {
219: $result->append($this->node->getChildNodes());
220: }
221: return $result;
222: }
223:
224: /**
225: * この HelperObject をレンダリングします.
226: *
227: * @return mixed 出力結果. デフォルトではマークアップされた結果の文字列
228: */
229: public function write()
230: {
231: return $this->helper->write($this);
232: }
233:
234: /**
235: * この HelperObject をデバッグ出力します.
236: * @return string
237: */
238: public function debug()
239: {
240: static $debug = null;
241: if ($debug === null) {
242: $debug = new DebugBuilder();
243: }
244: return $debug->build($this);
245: }
246:
247: /**
248: * この HelperObject がラップしている要素の属性をコピーして, 新しい要素を生成します.
249: * もしもラップしているオブジェクトが Element ではなかった場合は
250: * 空の NodeList をラップする HelperObject を返します.
251: *
252: * @return HelperObject コピーされた要素をラップする HelperObject
253: */
254: public function prototype()
255: {
256: return $this->helper->tag($this->createPrototype());
257: }
258:
259: /**
260: * このオブジェクトをプロトタイプとして, 新しい HelperObject を生成します.
261: *
262: * @return Element
263: */
264: private function createPrototype()
265: {
266: $original = $this->node;
267: if ($original instanceof ContainerElement) {
268: $node = new ContainerElement($original->getName());
269: $node->setAttributes($original->getAttributes());
270: return $node;
271: }
272: if ($original instanceof EmptyElement) {
273: $node = new EmptyElement($original->getName());
274: $node->setAttributes($original->getAttributes());
275: return $node;
276: }
277:
278: return null;
279: }
280:
281: /**
282: * このオブジェクトがラップしているノードの accept() を呼び出します.
283: * @param Context $context
284: */
285: public function accept(Context $context)
286: {
287: $this->node->accept($context);
288: }
289:
290: /**
291: * このオブジェクトの子ノードの一覧を取得します.
292: * もしもこのオブジェクトがラップしているノードが {@link Container Container}
293: * だった場合は, そのオブジェクトの子ノードの一覧を返します.
294: * それ以外は空の配列を返します.
295: * @return array
296: */
297: public function getChildNodes()
298: {
299: $node = $this->node;
300: return ($node instanceof Container) ? $node->getChildNodes() : array();
301: }
302:
303: /**
304: * このオブジェクトがラップしているノードの getApendee() の結果を返します.
305: *
306: * @return NodeList
307: */
308: public function getAppendee()
309: {
310: return $this->node->getAppendee();
311: }
312: }
313: