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\Values;
30:
31: /**
32: * ノードの配列をあらわすクラスです.
33: *
34: * このクラスは Component を実装しているため,
35: * {@link Context::handle()} の引数に渡すことが出来ます.
36: * (実際の処理は {@link Context::handleNodeList()} で行われます)
37: *
38: * ある要素に対して NodeList を追加した場合, このオブジェクト自体ではなく,
39: * このオブジェクトに含まれる各ノードが追加されます.
40: *
41: * 例えるならば DOM の NodeList と NodeFragment を兼任するクラスです.
42: */
43: class NodeList implements Container
44: {
45: /**
46: * Node の配列です.
47: * @var array
48: */
49: private $nodeList;
50:
51: /**
52: * この NodeList を持つノードです. 存在しない場合は null となります.
53: * @var Node
54: */
55: private $owner;
56:
57: /**
58: * 新しい NodeList を生成します.
59: * 引数に値を設定した場合, その値をリストに追加した状態で初期化します.
60: *
61: * @param Component|array|string $var 追加するノード
62: * @param Node $owner この NodeList を内部に持つ Node オブジェクト (エンドユーザーが直接使用することはありません)
63: */
64: public function __construct($var = null, Node $owner = null)
65: {
66: $this->nodeList = array();
67: $this->owner = $owner;
68: $this->appendNode($var);
69: }
70:
71: /**
72: * この NodeList に実際に追加される値を返します.
73: *
74: * @param mixed $var
75: * @return Node[] 追加されるノードの配列
76: */
77: private function prepareAppendee($var)
78: {
79: if ($var instanceof Node) {
80: return $var;
81: }
82: if ($var instanceof Component) {
83: return $var->getAppendee()->getChildNodes();
84: }
85:
86: if (is_array($var)) {
87: $result = array();
88: foreach ($var as $i) {
89: $appendee = $this->prepareAppendee($i);
90: if (is_array($appendee)) {
91: array_splice($result, count($result), 0, $appendee);
92: } else {
93: $result[] = $appendee;
94: }
95: }
96: return $result;
97: }
98: if (!isset($var)) {
99: return array();
100: }
101:
102: return array(new Text(Values::stringValue($var)));
103: }
104:
105: /**
106: * この NodeList の末尾に引数の値を追加します.
107: *
108: * 引数がノードの場合は, 引数をそのまま NodeList の末尾に追加します.
109: *
110: * 引数が Container の場合は, その Container に含まれる各ノードを追加します.
111: * (Container 自身は追加されません)
112: *
113: * 引数が配列の場合は, 配列に含まれる各ノードをこの NodeList に追加します.
114: *
115: * 引数がノードではない場合は, 引数の文字列表現をテキストノードとして追加します.
116: *
117: * @param Node|Container|array|string $var
118: */
119: public function appendNode($var)
120: {
121: $appendee = $this->prepareAppendee($var);
122: if (isset($this->owner)) {
123: $this->checkOwner($appendee);
124: }
125: if (is_array($appendee)) {
126: $this->nodeList = array_merge($this->nodeList, $appendee);
127: } else {
128: $this->nodeList[] = $appendee;
129: }
130: }
131:
132: /**
133: * 指定された Context にこのノードを処理させます.
134: * {@link Context::handleNodeList()} を呼び出します.
135: * @param Context $context
136: */
137: public function accept(Context $context)
138: {
139: $context->handleNodeList($this);
140: }
141:
142: /**
143: * この NodeList に子ノードを追加する際に,
144: * 親子関係が無限ループしないかどうか検査します.
145: * 引数がこの NodeList のオーナーだった場合に InvalidArgumentException をスローします.
146: * 引数が配列もしくは {@link Container} だった場合は,
147: * その子ノードの一覧について再帰的に検査します.
148: *
149: * @param mixed $var 検査対象
150: * @throws \InvalidArgumentException 検査対象にこの NodeList のオーナーが含まれていた場合
151: */
152: private function checkOwner($var)
153: {
154: if (is_array($var)) {
155: foreach ($var as $i) {
156: $this->checkOwner($i);
157: }
158: return;
159: }
160:
161: if ($var instanceof Container) {
162: $this->checkOwner($var->getChildNodes());
163: }
164: if ($var === $this->owner) {
165: throw new \InvalidArgumentException("Tree-loop detected.");
166: }
167: }
168:
169: /**
170: * この NodeList に含まれるノードの個数を返します.
171: * @return int ノードの個数
172: */
173: public function size()
174: {
175: return count($this->nodeList);
176: }
177:
178: /**
179: * この NodeList に含まれるノードの一覧を配列で返します.
180: * @return array
181: */
182: public function getChildNodes()
183: {
184: return $this->nodeList;
185: }
186:
187: /**
188: * この NodeList 自身を返します.
189: * この NodeList が Container に追加される場合,
190: * このオブジェクトの代わりに NodeList に含まれる各ノードが追加されます.
191: *
192: * @return Component このオブジェクト
193: */
194: public function getAppendee()
195: {
196: return $this;
197: }
198: }
199: