|
|
@@ -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()");
|
|
|
+ }
|
|
|
+}
|