Browse Source

added ParseXsdAttributeAssert

Piotr Labudda 8 years ago
parent
commit
6de1e4984a
1 changed files with 301 additions and 0 deletions
  1. 301 0
      SE/se-lib/ParserXsdAttributeAssert.php

+ 301 - 0
SE/se-lib/ParserXsdAttributeAssert.php

@@ -0,0 +1,301 @@
+<?php
+
+class ParserXsdAttributeAssert {
+
+	public static function buildFromAssertCode($assertCode, $xsdType) { // @param string $assertCode
+		$fieldNamesUpperCase = array_map('strtoupper', (!empty($xsdType['struct'])) ? array_keys($xsdType['struct']) : []);
+		$instanceName = $xsdType['name'];
+
+		$xsdAssertParser = new ParserXsdAttributeAssert();
+		$xsdAssertParser->parseAssertCode($assertCode, [
+			'instanceName' => $instanceName,
+			'fields' => $fieldNamesUpperCase,
+		]);
+		return $xsdAssertParser;
+	}
+	public static function buildFromString($str, $xsdType) { // @param string $str
+		$xsdAssertParser = new ParserXsdAttributeAssert();
+		return $xsdAssertParser;
+	}
+
+	public function __construct() {
+		$this->_state = [];
+	}
+
+	public function parseAssertCode($assertCode, $params = []) {
+		if (empty($params['instanceName'])) throw new Exception("Missing instanceName");
+		$instanceName = $params['instanceName'];
+		$fieldNamesUpperCase = V::get('fields', [], $params, 'array');
+
+		$this->_state = [
+			'instanceName' => $instanceName,
+			'fieldNamesUpperCase' => $fieldNamesUpperCase,
+			'token_tree' => [],
+		];
+
+		$tokens = token_get_all("<?php " . $assertCode);
+		$parsedTokens = array_map(function ($token) {
+			if (is_string($token)) return [
+				'type' => 'string',
+				'value' => $token,
+			];
+			return [
+				'type' => token_name($token[0]),
+				'value' => $token[1]
+			];
+		}, $tokens);
+		array_shift($parsedTokens); // remove first token: `<?php`
+		$parsedTokens = array_map(function ($token) {
+			if ('string' === $token['type'] && '(' === $token['value']) return array_merge($token, [ 'type' => 'opening parenthesis' ]);
+			if ('string' === $token['type'] && ')' === $token['value']) return array_merge($token, [ 'type' => 'closing parenthesis' ]);
+			if ('string' === $token['type'] && ',' === $token['value']) return array_merge($token, [ 'type' => 'comma' ]);
+			return $token;
+		}, $parsedTokens);
+		$parsedTokens = array_filter($parsedTokens, function ($token) {
+			return ('T_WHITESPACE' !== $token['type']);
+		});
+		// opening parenthesis
+		// closing parenthesis
+		// function array_reduce ( array $array , callable $callback [, mixed $initial = NULL ] ) // return mixed
+		UI::table(['rows'=>$parsedTokens, 'caption'=>"parseAttributeWithAssertCode({$attrName})"]);
+		$parser = array_reduce($parsedTokens, function ($parser, $token) use ($fieldNamesUpperCase, $instanceName) {
+			if (!$parser) $parser = [ 'deep' => 0, 'cur' => '', 'tokens' => [] ];
+			// 1. `concat(ID, ' ', DESC, ' ', DESC_PL)`
+			// 1.1 $parser = { deep: 0, cur: 'concat', tokens: []}
+			// 1.2 $parser = { deep: 1, cur: '', tokens: [ {'function', 'concat'}, {'('} ]}
+			if (!empty($parser['cur'])) {
+				switch ($parser['cur']) {
+					case 'substring':
+					case 'concat':
+					{
+						if ('opening parenthesis' === $token['type']) {
+							$parser['deep'] = $parser['deep'] + 1;
+							$parser['tokens'][] = [ 'type' => "function", 'value' => $parser['cur'] ];
+							$parser['tokens'][] = [ 'type' => "opening parenthesis", 'value' => '(' ];
+							$parser['cur'] = '';
+							return $parser;
+						}
+					} break;
+					case 'name': return array_merge($parser, [ 'cur' => 'name(' ]);
+					case 'name(': {
+						if ('closing parenthesis' === $token['type']) {
+							$parser['tokens'][] = [ 'type' => "instanceName", 'value' => $instanceName ];
+							$parser['cur'] = '';
+							return $parser;
+						}
+					} break;
+				}
+				throw new Exception("Parse attribute assert code failed. Not implemented function '{$parser['cur']}'.");
+			}
+			if ('string' === $token['type']) throw new Exception("Parse attribute assert code failed. Not implemented string token '{$token['value']}'.");
+			if ('comma' === $token['type']) {
+				$parser['tokens'][] = $token;
+				return $parser;
+			}
+			if ('T_CONSTANT_ENCAPSED_STRING' === $token['type']) {
+				$parser['tokens'][] = array_merge($token, [ 'type' => 'encapsed string' ]);
+				return $parser;
+			}
+			if ('T_LNUMBER' === $token['type']) {
+				$parser['tokens'][] = array_merge($token, [ 'type' => 'number' ]);
+				return $parser;
+			}
+			if ('closing parenthesis' === $token['type']) {
+				if ($parser['deep'] <= 0) throw new Exception("Parse attribute assert code failed. Parenthesis not match.");
+				$parser['tokens'][] = $token;
+				$parser['deep'] = $parser['deep'] - 1;
+				return $parser;
+			}
+			if ('T_STRING' === $token['type']) {
+				if ('concat' === $token['value']) return array_merge($parser, [ 'cur' => 'concat' ]);
+				if ('substring' === $token['value']) return array_merge($parser, [ 'cur' => 'substring' ]);
+				if ('name' === $token['value']) return array_merge($parser, [ 'cur' => 'name' ]);
+				if (in_array(strtoupper($token['value']), $fieldNamesUpperCase)) {
+					$parser['tokens'][] = [ 'type' => 'field', 'value' => strtoupper($token['value']) ];
+					return $parser;
+				}
+				throw new Exception("Parse attribute assert code failed. Not implemented T_STRING token '{$token['value']}'.");
+			}
+			throw new Exception("Parse attribute assert code failed. Not implemented {$token['type']} token '{$token['value']}'.");
+			// return $parser;
+		}, []);
+		DBG::nicePrint($parser, "parseAttributeWithAssertCode({$attrName}) parsed tokens");
+		$checkParenthesisDepth = array_reduce($parser['tokens'], function ($ret, $token) {
+			switch ($token['type']) {
+				case 'opening parenthesis': $ret['deep'] += 1; break;
+				case 'closing parenthesis': $ret['deep'] -= 1; break;
+			}
+			switch ($token['type']) {
+				case 'opening parenthesis': break;
+				case 'closing parenthesis': break;
+				default: $ret['tokens'][] = array_merge($token, [ 'deep' => $ret['deep'] ]);
+			}
+			return $ret;
+		}, [ 'deep' => 1, 'tokens' => [] ]);
+		if (1 !== $checkParenthesisDepth['deep']) throw new Exception("Parse attribute assert code failed. Parentheses do not match.");
+		$parser['tokens'] = $checkParenthesisDepth['tokens'];
+		UI::table(['rows'=>$parser['tokens'], 'caption'=>"parseAttributeWithAssertCode({$attrName}) parsed tokens"]);
+
+		$tokenTree = array_reduce($parser['tokens'], function ($ret, $token) {
+			static $counter = 0;
+			$counter += 1;
+			DBG::log($ret, 'array', "token_tree loop({$counter}) >>> \$ret");
+			DBG::log($token, 'array', "token_tree loop({$counter}) >>> \$token");
+			$funcIdx = $ret['funcIdx'];
+			if ($ret['lastDeep'] < $token['deep']) { // 'starting parenthesis' was before => func arg
+				$ret['lastDeep'] = $token['deep'];
+			}
+			else if ($ret['lastDeep'] > $token['deep']) { // 'closing parenthesis' was before =>
+				$ret['lastDeep'] = $token['deep'];
+				for ($idx = $funcIdx; $idx >= 0; $idx--) {
+					if ($token['deep'] - 1 === $ret['token_tree'][$idx]['deep']) {
+						$funcIdx = $idx;
+						$ret['funcIdx'] = $funcIdx;
+						break;
+					}
+				}
+			}
+			if ('function' === $token['type']) {
+				$ret['token_tree'][] = array_merge($token, [ 'funcIdx' => $funcIdx, 'args' => [] ]);
+				$ret['funcIdx'] = count($ret['token_tree']) - 1;
+				if ($funcIdx >= 0) $ret['token_tree'][$funcIdx]['args'][] = [ 'type' => 'funcIdx', 'value' => $ret['funcIdx'] ];
+				return $ret;
+			} else {
+				$ret['token_tree'][$funcIdx]['args'][] = $token;
+			}
+			return $ret;
+		}, [ 'funcIdx' => -1, 'lastDeep' => 1, 'token_tree' => [] ]); // $token_tree = { 'type', 'value', 'deep', 'args' }
+		DBG::nicePrint($parser['token_tree'], "parseAttributeWithAssertCode({$attrName}) token_tree");
+		// TODO: validate $tokenTree
+		$parser['token_tree'] = $tokenTree['token_tree'];
+
+		$this->_state['token_tree'] = $parser['token_tree'];
+	}
+
+	public function toMysqlSelect($rootTableName, $primaryKeyField) {
+		$sqlAssertCode = '';
+		$sqlPrefix = 't';
+		$tokenTree = array_map(function ($val) { return $val; }, $this->_state['token_tree']);
+		for ($idx = count($tokenTree) - 1; $idx >= 0; $idx--) {
+			$func = $tokenTree[$idx];
+			switch ($func['value']) {
+				case 'concat': $value = $this->_toMysqlConcat($func, $sqlPrefix); break;
+				case 'substring': $value = $this->_toMysqlSubstring($func, $sqlPrefix); break;
+				default: throw new Exception("Not implemented asser code function '{$func['value']}'");
+			}
+			for ($subIdx = $idx - 1; $subIdx >= 0; $subIdx--) {
+				$tokenTree[$subIdx]['args'] = array_map(function ($arg) use ($value, $idx) {
+					if ('funcIdx' === $arg['type'] && $arg['value'] == $idx) {
+						return [
+							'type' => 'raw string',
+							'value' => $value,
+						];
+					}
+					return $arg;
+				}, $tokenTree[$subIdx]['args']);
+			}
+		}
+
+		$func = $tokenTree[0];
+		$sqlAssertCode .= "{$func['value']}(";
+		$sqlAssertCode .= implode("", array_map(function ($arg) use ($sqlPrefix) {
+			switch ($arg['type']) {
+				case 'field': return "{$sqlPrefix}.{$arg['value']}";
+				case 'number': return $arg['value'];
+			}
+			return $arg['value'];
+		}, $func['args']));
+		$sqlAssertCode .= ")";
+
+		$sql = "
+			select {$sqlPrefix}.{$primaryKeyField} as primary_key
+				, {$sqlAssertCode} as label
+			from `{$rootTableName}` {$sqlPrefix}
+		";
+		return $sql;
+	}
+	function _toMysqlConcat($func, $sqlPrefix) {
+		return "concat(" . implode("", array_map(function ($arg) use ($sqlPrefix) {
+			if ('field' == $arg['type']) return "{$sqlPrefix}.{$arg['value']}";
+			return $arg['value'];
+		}, $func['args'])) . ")";
+	}
+	function _toMysqlSubstring($func, $sqlPrefix) {
+		return "substring(" . implode("", array_map(function ($arg) use ($sqlPrefix) {
+			if ('field' == $arg['type']) return "{$sqlPrefix}.{$arg['value']}";
+			return $arg['value'];
+		}, $func['args'])) . ")";
+	}
+
+	public function evaluate($item) {
+		$value = '';
+		$uppserCaseItem = array_combine(
+			array_map('strtoupper', array_keys($item)),
+			array_values($item)
+		);
+		$tokenTree = array_map(function ($val) { return $val; }, $this->_state['token_tree']);
+		for ($idx = count($tokenTree) - 1; $idx >= 0; $idx--) {
+			$func = $tokenTree[$idx];
+			switch ($func['value']) {
+				case 'concat': $value = $this->_evaluateConcat($func, $uppserCaseItem); break;
+				case 'substring': $value = $this->_evaluateSubstring($func, $uppserCaseItem); break;
+				default: throw new Exception("Not implemented asser code function '{$func['value']}'");
+			}
+			for ($subIdx = $idx - 1; $subIdx >= 0; $subIdx--) {
+				$tokenTree[$subIdx]['args'] = array_map(function ($arg) use ($value, $idx) {
+					if ('funcIdx' === $arg['type'] && $arg['value'] == $idx) {
+						return [
+							'type' => 'raw string',
+							'value' => $value,
+						];
+					}
+					return $arg;
+				}, $tokenTree[$subIdx]['args']);
+			}
+		}
+		return $value;
+	}
+	public function _evaluateConcat($func, $item) {
+		return implode('', array_map(function ($arg) use ($item) {
+			switch ($arg['type']) {
+				case 'field': return V::get($arg['value'], '', $item);
+				case 'comma': return '';
+				case 'encapsed string': return substr($arg['value'], 1, -1);
+				case 'raw string': return $arg['value'];
+			}
+		}, $func['args']));
+	}
+	public function _evaluateSubstring($func, $item) {
+		// PHP: substr( $str, $start [ , $length ] ) // NOTE $start with 0
+		// XPath: substr( $str, $start [ , $length ] ) // NOTE $start with 1
+		// SQL: substr( $str, $start [ , $length ] ) // NOTE $start with 1
+		$str = '';
+		$start = 0;
+		$length = null;
+		// $func ['args'] = [ string, comma, number, comma, number ]
+		// or             = [ string, comma, number ]
+		if (!in_array(count($func['args']), [3, 5])) throw new Exception("Sytax error for substring function in assert code");
+		$arg = $func['args'][0];
+		switch ($arg['type']) {
+			case 'field': $str = V::get($arg['value'], '', $item); break;
+			case 'encapsed string': $str = substr($arg['value'], 1, -1); break;
+			case 'number': $str = (string)$arg['value']; break;
+			case 'raw string': $str = $arg['value']; break;
+		}
+		if ('number' !== $func['args'][2]['type']) throw new Exception("Sytax error for substring function in assert code - expected second arguemnt type as number but '{$func['args'][2]['type']}' given");
+		$start = (int)$func['args'][2]['value'] - 1;
+		$start = ($start < 0) ? 0 : $start; // BUG in assert code
+		if (5 === count($func['args'])) {
+			if ('number' !== $func['args'][4]['type']) throw new Exception("Sytax error for substring function in assert code - expected third arguemnt type as number but '{$func['args'][2]['type']}' given");
+			$length = (int)$func['args'][4]['value'];
+		}
+		return (null === $length)
+			? substr($str, $start)
+			: substr($str, $start, $length)
+		;
+	}
+
+	public function toString() {
+		throw new Exception("TODO: ParserXsdAttributeAssert->toString()");
+	}
+}