| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301 |
- <?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()");
- }
- }
|