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 InvalidArgumentException;
30: use Peach\Util\ArrayMap;
31: use Peach\Util\Map;
32: use Peach\Util\Values;
33: use Peach\DF\Utf8Codec;
34:
35: /**
36: * マークアップ言語の要素を表現するクラスです.
37: */
38: abstract class Element implements Node
39: {
40: /**
41: * この要素の名前です.
42: * @var string
43: */
44: private $name;
45:
46: /**
47: * この要素に含まれる属性です.
48: * @var Map
49: */
50: private $attributes;
51:
52: /**
53: * 指定された要素名を持つ Element を構築します.
54: *
55: * @param string $name 要素名
56: * @throws InvalidArgumentException 要素名が空文字列だった場合
57: */
58: public function __construct($name)
59: {
60: $this->name = self::cleanNameString($name);
61: $this->attributes = new ArrayMap();
62: }
63:
64: /**
65: * 指定された文字列について, 不正な UTF-8 のバイト列をあらかじめ除去した上で,
66: * 要素名または属性名として適切かどうかを調べます.
67: *
68: * @param string $name 要素名
69: * @return string 不正なシーケンスを除去した結果の文字列
70: * @throws InvalidArgumentException 引数が要素名または属性名として不適切な場合
71: */
72: private static function cleanNameString($name)
73: {
74: if (!strlen($name)) {
75: throw new InvalidArgumentException("Empty string specified");
76: }
77: $cleanName = self::cleanString($name);
78: if (!NameValidator::validate($cleanName)) {
79: throw new InvalidArgumentException("'{$cleanName}' is not a valid name");
80: }
81: return $cleanName;
82: }
83:
84: /**
85: * 引数の文字列から UTF-8 として不適切なシーケンスを除去します.
86: *
87: * @param string $var 文字列
88: * @return string 不正なシーケンスを除去した結果
89: */
90: private static function cleanString($var)
91: {
92: // @codeCoverageIgnoreStart
93: static $utf8Codec = null;
94: if ($utf8Codec === null) {
95: $utf8Codec = new Utf8Codec();
96: }
97: // @codeCoverageIgnoreEnd
98:
99: $str = Values::stringValue($var);
100: return $utf8Codec->encode($utf8Codec->decode($str));
101: }
102:
103: /**
104: * この要素の名前を返します.
105: * @return string この要素の名前
106: */
107: public function getName()
108: {
109: return $this->name;
110: }
111:
112: /**
113: * 指定された属性の値を返します.
114: * 属性が存在しないか, 値の省略された属性の場合は NULL を返します.
115: * 属性が存在しているかどうかを調べる場合は
116: * {@link Element::hasAttribute()}
117: * を使用してください.
118: *
119: * @param string $name 属性名
120: * @return string 属性の値. 存在しないか, 値の省略された属性の場合は NULL
121: */
122: public function getAttribute($name)
123: {
124: return $this->attributes->get($name);
125: }
126:
127: /**
128: * 指定された属性が存在するかどうかを調べます.
129: * @param string $name 属性名
130: * @return bool 属性が存在する場合は TRUE, それ以外は FALSE
131: */
132: public function hasAttribute($name)
133: {
134: return $this->attributes->containsKey($name);
135: }
136:
137: /**
138: * この要素に属性を設定します.
139: * $value が設定されていない場合は, 値が省略された属性を追加します.
140: *
141: * @param string $name 属性名
142: * @param string $value 属性値
143: */
144: public function setAttribute($name, $value = null)
145: {
146: $cleanName = self::cleanNameString($name);
147: $cleanValue = isset($value) ? self::cleanString($value) : null;
148: $this->attributes->put($cleanName, $cleanValue);
149: }
150:
151: /**
152: * この要素に複数の属性を一括して設定します.
153: * <code>
154: * $element->setAttributes(array("id" => "foo", "class" => "bar"));
155: * </code>
156: * のように, キーに属性名, 値に属性の値を指定してください.
157: *
158: * キーが省略された場合 (具体的にはキーに整数が指定された場合) は,
159: * その値を属性名とする Boolean 属性を設定します.
160: *
161: * @param array|ArrayMap $attr 属性の一覧
162: */
163: public function setAttributes($attr)
164: {
165: if ($attr instanceof ArrayMap) {
166: $this->setAttributes($attr->asArray());
167: return;
168: }
169: if (!is_array($attr)) {
170: throw new InvalidArgumentException("Array required.");
171: }
172: foreach ($attr as $key => $value) {
173: if (is_numeric($key)) {
174: $attrName = $value;
175: $attrValue = null;
176: } else {
177: $attrName = $key;
178: $attrValue = $value;
179: }
180: $this->setAttribute($attrName, $attrValue);
181: }
182: }
183:
184: /**
185: * 指定された属性を削除します.
186: * @param string $name 属性名
187: */
188: public function removeAttribute($name)
189: {
190: $this->attributes->remove($name);
191: }
192:
193: /**
194: * この要素が持つすべての属性を配列で返します.
195: * この要素の返り値を, そのまま {Element::setAttributes()}
196: * の引数として使用することで属性のコピーをすることも出来ます.
197: *
198: * @return array すべての属性の配列. キーが属性名, 値が属性値.
199: * ただし値の省略された属性の場合は属性値が NULL となる.
200: */
201: public function getAttributes()
202: {
203: return $this->attributes->asArray();
204: }
205:
206: /**
207: * このオブジェクトを {@link Container::appendNode()} に指定した場合,
208: * このオブジェクト自身が追加されます.
209: *
210: * @return NodeList このオブジェクトを 1 つだけ含んだ NodeList
211: */
212: public function getAppendee()
213: {
214: return new NodeList($this);
215: }
216: }
217: