1: <?php
2: /*
3: * Copyright (c) 2016 Geraldo B. Landre
4: *
5: * See the file LICENSE for copying permission.
6: */
7: namespace pgn\tags;
8:
9: use pgn\exceptions\InvalidDataException;
10: use pgn\exceptions\InvalidGameFormatException;
11:
12: /**
13: * Abstract class representing a Tag in a PGN game
14: *
15: * @author Geraldo
16: */
17: abstract class Tag {
18:
19: /**
20: * @return the name of the tag
21: */
22: abstract public function getName();
23:
24: protected $data;
25: protected $errorMsg;
26:
27: /**
28: * Returns the tag as a string. The default string patter is:
29: * [TagName "content"]
30: * @return string
31: */
32: public function __toString() {
33: return '[' . $this->getName() . ' "' . $this->get() . '"]';
34: }
35:
36: /**
37: * Sets the tag with the data if it's a valid data
38: * @param string $data
39: * @throws InvalidDataException throws an exception if the data is not in the correct format
40: * @see Tag::validate
41: */
42: public function set($data) {
43: if ($this->validate($data)) {
44: $this->data = $data;
45: } else {
46: throw new InvalidDataException("[" . __CLASS__ . "]" . $this->errorMsg);
47: }
48: }
49:
50: /**
51: * Returns the data of the tag as a string
52: * @see Tag::formatted
53: * @return string formatted data
54: */
55: public function get() {
56: return $this->formatted($this->data);
57: }
58:
59: /**
60: *
61: * @param mixed $data
62: * @return boolean returns if a data is valid
63: */
64: public function validate($data) {
65:
66: if ($data === "0") {
67: return true;
68: }
69:
70: if ($data === \NULL) {
71: return false;
72: }
73:
74: if (is_array($data)) {
75: return false;
76: }
77:
78: if (empty($data)) {
79: return false;
80: }
81:
82: return true;
83: }
84:
85: /**
86: * Returns the default value of the Tag
87: * @return string
88: */
89: public function getDefaultValue() {
90: return '?';
91: }
92:
93: /**
94: * Returns its data as a formatted string
95: * It's recommended to be overwritten
96: *
97: * @see http://php.net/manual/pt_BR/function.strval.php
98: * @return string
99: */
100: protected function formatted($data) {
101: if (isset($data)) {
102: return strval($data);
103: }
104: return $this->getDefaultValue();
105: }
106:
107: /**
108: *
109: * @param string $unparsedTag
110: * @param string [] $errors array of strings
111: * @return Tag The tag created
112: */
113: static public function parse($unparsedTag, &$errors) {
114:
115: if (!(boolean) preg_match("/^" . self::validPattern() . "$/", $unparsedTag)) {
116: $errors[] = "[" . __CLASS__ . "] error trying to parse '$unparsedTag':[tag does not match with the pattern]";
117: return NULL;
118: }
119:
120: if (!is_array($errors)) {
121: $errors = array();
122: }
123:
124: $tagName = self::parseTagName($unparsedTag);
125: $tagValue = self::parseTagValue($unparsedTag);
126:
127: return self::createTag($tagName, $tagValue);
128: }
129:
130: /**
131: * Parses a string in correct format of a tag
132: * and returns the value of the tag
133: * @throws \pgn\exceptions\InvalidGameFormatException if tag was not on correct format
134: * @assert('[TestName "Test Value"]') === "TestName"
135: * @assert('[TestName "Test Value"') === "TestName"
136: * @assert('asdf') throws InvalidGameFormatException
137: * @param string $unparsedTag
138: */
139: static public function parseTagName($unparsedTag) {
140: return self::parseTagAndGetMatch($unparsedTag, 1);
141: }
142:
143: /**
144: * Parses a string in correct format of a tag
145: * and returns the value of the tag
146: * if the string is not in the correct format throws an exception
147: * @throws \pgn\exceptions\InvalidGameFormatException if tag was not on correct format
148: * @assert('[TestName "Test Value"]') === "Test Value"
149: * @assert('[TestName "Test Value"') === "Test Value"
150: * @assert('asdf') throws InvalidGameFormatException
151: * @param string $unparsedTag
152: */
153: static public function parseTagValue($unparsedTag) {
154: return self::parseTagAndGetMatch($unparsedTag, 2);
155: }
156:
157: /**
158: * Get the regex pattern of a valid tag string
159: * @return string regex pattern of a valid tag string
160: */
161: static public function validPattern() {
162: return "\[.*\s+\".*\"\]";
163: }
164:
165: /**
166: * Creates a tag, based on tags classes (@see \pgn\tags)
167: * @param string $tagName the name of the tag
168: * @param string $tagValue the value of the tag
169: * @return \pgn\tags\Tag
170: */
171: static public function createTag($tagName, $tagValue) {
172: try {
173: $namespace = "\\pgn\\tags\\";
174: $className = $namespace . $tagName;
175: $tag = new $className ();
176: $tag->set($tagValue);
177: return $tag;
178: }
179: catch(InvalidDataException $ex) {
180: $errors = "[" . __CLASS__ . "] bad value [$tagValue] for tag $tagName [" . $ex->getMessage() . "]";
181: }
182: catch (\Exception $ex) {
183: $errors = "[" . __CLASS__ . "] urecognized Tag: $tagName [" . $ex->getMessage() . "]";
184: return self::createUknownTag($tagName, $tagValue, $errors);
185: }
186: }
187:
188: /**
189: * Creates an Uknown Tag (@see \pgn\tags\UknownTag)
190: * @param string $tagName name of the tag
191: * @param string $tagValue value of the tag
192: * @param string $errors output parameter to store errors and warnings
193: * @return \pgn\tags\UknownTag or NULL if it was not possible to create a tag
194: */
195: static private function createUknownTag($tagName, $tagValue, &$errors) {
196: try {
197: $tag = new UknownTag();
198: $tag->setName($tagName);
199: $tag->set($tagValue);
200: return $tag;
201: } catch (\Exception $ex) {
202: $errors[] = "[" . __CLASS__ . "] error trying to parse '$unparsedTag' [" . $ex->getMessage() . "]";
203: return NULL;
204: }
205: }
206:
207: /**
208: * Parses a tag according to the correct format: [TagName "TagValue"]
209: * and returns an array with those values. If the string is not in the
210: * correct format, returns null.
211: *
212: * @param string $unparsedTag string to be parsed
213: * @return array array containing the matches or null if the string does not match
214: */
215: static private function parseTag($unparsedTag) {
216: $matches = array();
217: if (preg_match("/^" . self::validPattern() . "$/", $unparsedTag)) {
218: $pattern = "/^\[(.*)\s+\"(.*)\"\]$/";
219: preg_match($pattern, $unparsedTag, $matches);
220: }
221: return (count($matches == 3)) ? $matches : NULL;
222: }
223:
224: /**
225: * Parses a string in correct format of a tag
226: * put the hole tag at index 0, the tag name at index 1 and the tag value
227: * at index 2. Returns the value in the index passed
228: * if the string is not in the correct format throws an exception
229: * @param string $unparsedTag The tag to be parsed
230: * @param int $index 0 to return the hole tag;
231: * 1 to return the tag name;
232: * 2 to return the tag value.
233: * @return string the tag information located in the matches array (indexes 0, 1 or 2)
234: * @throws \pgn\exceptions\InvalidGameFormatException if tag was not on correct format
235: */
236: static private function parseTagAndGetMatch($unparsedTag, $index) {
237: $matches = self::parseTag($unparsedTag);
238: if ($matches != NULL) {
239: return $matches[$index];
240: }
241: throw new InvalidGameFormatException("[" . __CLASS__ . "]: bad tag format: " . $unparsedTag);
242: }
243:
244: }