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(" 'string', 'value' => $token, ]; return [ 'type' => token_name($token[0]), 'value' => $token[1] ]; }, $tokens); array_shift($parsedTokens); // remove first token: ` '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()"); } }