From 2b3f10b6b049d7181f0ca0669e53ea08ced7fdb2 Mon Sep 17 00:00:00 2001 From: Daniel Barsotti Date: Mon, 10 Sep 2012 14:40:43 +0200 Subject: [PATCH 01/12] Initial import of the CND parser (NEEDS REVIEW AND CLEANUP) --- .../Util/CND/Exception/ParserException.php | 28 + .../Util/CND/Exception/ScannerException.php | 21 + .../Util/CND/Helper/AbstractDebuggable.php | 46 + .../CND/Helper/CndSyntaxTreeNodeVisitor.php | 68 ++ .../Util/CND/Helper/NodeTypeGenerator.php | 23 + src/PHPCR/Util/CND/Parser/AbstractParser.php | 103 ++ src/PHPCR/Util/CND/Parser/CndParser.php | 932 ++++++++++++++++++ src/PHPCR/Util/CND/Parser/ParserInterface.php | 11 + src/PHPCR/Util/CND/Parser/SyntaxTreeNode.php | 203 ++++ .../CND/Parser/SyntaxTreeVisitorInterface.php | 8 + src/PHPCR/Util/CND/Reader/BufferReader.php | 144 +++ src/PHPCR/Util/CND/Reader/FileReader.php | 24 + src/PHPCR/Util/CND/Reader/ReaderInterface.php | 57 ++ .../Util/CND/Scanner/AbstractScanner.php | 65 ++ .../Scanner/Context/DefaultScannerContext.php | 29 + ...ScannerContextWithoutSpacesAndComments.php | 18 + .../CND/Scanner/Context/ScannerContext.php | 157 +++ src/PHPCR/Util/CND/Scanner/GenericScanner.php | 314 ++++++ src/PHPCR/Util/CND/Scanner/GenericToken.php | 37 + src/PHPCR/Util/CND/Scanner/PhpScanner.php | 45 + .../Util/CND/Scanner/ScannerInterface.php | 23 + src/PHPCR/Util/CND/Scanner/Token.php | 110 +++ .../Scanner/TokenFilter/NoCommentsFilter.php | 13 + .../Scanner/TokenFilter/NoNewlinesFilter.php | 13 + .../TokenFilter/NoWhitespacesFilter.php | 13 + .../Scanner/TokenFilter/TokenFilterChain.php | 31 + .../TokenFilter/TokenFilterInterface.php | 15 + .../Scanner/TokenFilter/TokenTypeFilter.php | 32 + src/PHPCR/Util/CND/Scanner/TokenQueue.php | 74 ++ .../Tests/Util/CND/Fixtures/cnd/example.cnd | 12 + .../Util/CND/Fixtures/cnd/example.compact.cnd | 6 + .../Util/CND/Fixtures/cnd/example.verbose.cnd | 48 + .../Tests/Util/CND/Fixtures/cnd/example1.cnd | 11 + .../cnd/jackrabbit-builtin-nodetypes.cnd | 629 ++++++++++++ .../Util/CND/Fixtures/cnd/no-stop-at-eof.cnd | 5 + .../Util/CND/Fixtures/files/TestFile.php | 18 + .../Util/CND/Fixtures/files/TestFile.txt | 3 + .../Util/CND/Helper/NodeTypeGeneratorTest.php | 27 + .../Tests/Util/CND/Parser/CndParserTest.php | 146 +++ .../Util/CND/Parser/SyntaxTreeNodeTest.php | 31 + .../Util/CND/Reader/BufferReaderTest.php | 105 ++ .../Tests/Util/CND/Reader/FileReaderTest.php | 96 ++ .../Util/CND/Scanner/GenericScannerTest.php | 172 ++++ .../Tests/Util/CND/Scanner/PhpScannerTest.php | 25 + .../Tests/Util/CND/Scanner/TokenQueueTest.php | 89 ++ .../Tests/Util/CND/Scanner/TokenTest.php | 35 + 46 files changed, 4115 insertions(+) create mode 100644 src/PHPCR/Util/CND/Exception/ParserException.php create mode 100644 src/PHPCR/Util/CND/Exception/ScannerException.php create mode 100644 src/PHPCR/Util/CND/Helper/AbstractDebuggable.php create mode 100644 src/PHPCR/Util/CND/Helper/CndSyntaxTreeNodeVisitor.php create mode 100644 src/PHPCR/Util/CND/Helper/NodeTypeGenerator.php create mode 100644 src/PHPCR/Util/CND/Parser/AbstractParser.php create mode 100644 src/PHPCR/Util/CND/Parser/CndParser.php create mode 100644 src/PHPCR/Util/CND/Parser/ParserInterface.php create mode 100644 src/PHPCR/Util/CND/Parser/SyntaxTreeNode.php create mode 100644 src/PHPCR/Util/CND/Parser/SyntaxTreeVisitorInterface.php create mode 100644 src/PHPCR/Util/CND/Reader/BufferReader.php create mode 100644 src/PHPCR/Util/CND/Reader/FileReader.php create mode 100644 src/PHPCR/Util/CND/Reader/ReaderInterface.php create mode 100644 src/PHPCR/Util/CND/Scanner/AbstractScanner.php create mode 100644 src/PHPCR/Util/CND/Scanner/Context/DefaultScannerContext.php create mode 100644 src/PHPCR/Util/CND/Scanner/Context/DefaultScannerContextWithoutSpacesAndComments.php create mode 100644 src/PHPCR/Util/CND/Scanner/Context/ScannerContext.php create mode 100644 src/PHPCR/Util/CND/Scanner/GenericScanner.php create mode 100644 src/PHPCR/Util/CND/Scanner/GenericToken.php create mode 100644 src/PHPCR/Util/CND/Scanner/PhpScanner.php create mode 100644 src/PHPCR/Util/CND/Scanner/ScannerInterface.php create mode 100644 src/PHPCR/Util/CND/Scanner/Token.php create mode 100644 src/PHPCR/Util/CND/Scanner/TokenFilter/NoCommentsFilter.php create mode 100644 src/PHPCR/Util/CND/Scanner/TokenFilter/NoNewlinesFilter.php create mode 100644 src/PHPCR/Util/CND/Scanner/TokenFilter/NoWhitespacesFilter.php create mode 100644 src/PHPCR/Util/CND/Scanner/TokenFilter/TokenFilterChain.php create mode 100644 src/PHPCR/Util/CND/Scanner/TokenFilter/TokenFilterInterface.php create mode 100644 src/PHPCR/Util/CND/Scanner/TokenFilter/TokenTypeFilter.php create mode 100644 src/PHPCR/Util/CND/Scanner/TokenQueue.php create mode 100644 tests/PHPCR/Tests/Util/CND/Fixtures/cnd/example.cnd create mode 100644 tests/PHPCR/Tests/Util/CND/Fixtures/cnd/example.compact.cnd create mode 100644 tests/PHPCR/Tests/Util/CND/Fixtures/cnd/example.verbose.cnd create mode 100644 tests/PHPCR/Tests/Util/CND/Fixtures/cnd/example1.cnd create mode 100644 tests/PHPCR/Tests/Util/CND/Fixtures/cnd/jackrabbit-builtin-nodetypes.cnd create mode 100644 tests/PHPCR/Tests/Util/CND/Fixtures/cnd/no-stop-at-eof.cnd create mode 100644 tests/PHPCR/Tests/Util/CND/Fixtures/files/TestFile.php create mode 100644 tests/PHPCR/Tests/Util/CND/Fixtures/files/TestFile.txt create mode 100644 tests/PHPCR/Tests/Util/CND/Helper/NodeTypeGeneratorTest.php create mode 100644 tests/PHPCR/Tests/Util/CND/Parser/CndParserTest.php create mode 100644 tests/PHPCR/Tests/Util/CND/Parser/SyntaxTreeNodeTest.php create mode 100644 tests/PHPCR/Tests/Util/CND/Reader/BufferReaderTest.php create mode 100644 tests/PHPCR/Tests/Util/CND/Reader/FileReaderTest.php create mode 100644 tests/PHPCR/Tests/Util/CND/Scanner/GenericScannerTest.php create mode 100644 tests/PHPCR/Tests/Util/CND/Scanner/PhpScannerTest.php create mode 100644 tests/PHPCR/Tests/Util/CND/Scanner/TokenQueueTest.php create mode 100644 tests/PHPCR/Tests/Util/CND/Scanner/TokenTest.php diff --git a/src/PHPCR/Util/CND/Exception/ParserException.php b/src/PHPCR/Util/CND/Exception/ParserException.php new file mode 100644 index 00000000..fdff25f8 --- /dev/null +++ b/src/PHPCR/Util/CND/Exception/ParserException.php @@ -0,0 +1,28 @@ +peek(); + $msg = sprintf("PARSER ERROR: %s. Current token is [%s, '%s'] at line %s, column %s", $msg, GenericToken::getTypeName($token->getType()), $token->getData(), $token->getLine(), $token->getRow()); + + // construct a lookup of the next tokens + $lookup = ''; + for($i = 1; $i <= 5; $i++) { + if ($queue->isEof()) { + break; + } + $token = $queue->get(); + $lookup .= $token->getData() . ' '; + } + $msg .= "\nBuffer lookup: \"$lookup\""; + + parent::__construct($msg); + } +} diff --git a/src/PHPCR/Util/CND/Exception/ScannerException.php b/src/PHPCR/Util/CND/Exception/ScannerException.php new file mode 100644 index 00000000..ae6970b0 --- /dev/null +++ b/src/PHPCR/Util/CND/Exception/ScannerException.php @@ -0,0 +1,21 @@ +getCurrentLine(), + $reader->getCurrentColumn(), + $reader->consume() + ); + + parent::__construct($msg); + } +} diff --git a/src/PHPCR/Util/CND/Helper/AbstractDebuggable.php b/src/PHPCR/Util/CND/Helper/AbstractDebuggable.php new file mode 100644 index 00000000..3c769712 --- /dev/null +++ b/src/PHPCR/Util/CND/Helper/AbstractDebuggable.php @@ -0,0 +1,46 @@ +' + * @param string $msg The message to display + * @return void + */ + protected function debugRes($msg) + { + $this->debug("=> " . $msg, 1); + } + + /** + * Display the message as a section header + * @param string $msg The message to display + * @return void + */ + protected function debugSection($msg) + { + $this->debug(sprintf("\n\n----- %s %s\n", $msg, str_repeat('-', 80 - strlen($msg)))); + } + +} diff --git a/src/PHPCR/Util/CND/Helper/CndSyntaxTreeNodeVisitor.php b/src/PHPCR/Util/CND/Helper/CndSyntaxTreeNodeVisitor.php new file mode 100644 index 00000000..acf8af7f --- /dev/null +++ b/src/PHPCR/Util/CND/Helper/CndSyntaxTreeNodeVisitor.php @@ -0,0 +1,68 @@ +generator = $generator; + } + + public function getNodeTypeDefs() + { + return $this->nodeTypeDefs; + } + + public function visit(SyntaxTreeNode $node) + { + //var_dump($node->getType()); + +// switch ($node->getType()) { +// +// case 'nodeTypeDef': +// $this->curNodeTypeDef = new NodeTypeDefinition(); +// $this->nodeTypeDefs[] = $this->curNodeTypeDef; +// break; +// +// case 'nodeTypeName': +// if ($node->hasProperty('value')) { +// $this->curNodeTypeDef->setName($node->getProperty('value')); +// } +// break; +// +// case 'supertypes': +// if ($node->hasProperty('value')) { +// $this->curNodeTypeDef->addDeclaredSupertypeName($node->getProperty('value')); +// } +// break; +// +// case 'nodeTypeAttributes': +// if ($node->hasChild('orderable')) $this->curNodeTypeDef->setHasOrderableChildNodes(true); +// if ($node->hasChild('mixin')) $this->curNodeTypeDef->setIsMixin(true); +// if ($node->hasChild('abstract')) $this->curNodeTypeDef->setIsAbstract(true); +// if ($node->hasChild('query')) $this->curNodeTypeDef->setIsQueryable(true); +// break; +// +// // TODO: write the rest +// } + } + +} diff --git a/src/PHPCR/Util/CND/Helper/NodeTypeGenerator.php b/src/PHPCR/Util/CND/Helper/NodeTypeGenerator.php new file mode 100644 index 00000000..0453cbdb --- /dev/null +++ b/src/PHPCR/Util/CND/Helper/NodeTypeGenerator.php @@ -0,0 +1,23 @@ +root = $root; + } + + public function generate() + { + $visitor = new CndSyntaxTreeNodeVisitor($this); + $this->root->accept($visitor); + return $visitor->getNodeTypeDefs(); + } + +} diff --git a/src/PHPCR/Util/CND/Parser/AbstractParser.php b/src/PHPCR/Util/CND/Parser/AbstractParser.php new file mode 100644 index 00000000..37b92da8 --- /dev/null +++ b/src/PHPCR/Util/CND/Parser/AbstractParser.php @@ -0,0 +1,103 @@ +tokenQueue = $tokenQueue; + } + + /** + * Check the next token without consuming it and return true if it matches the given type and data. + * If the data is not provided (equal to null) then only the token type is checked. + * Return false otherwise. + * + * @param int $type The expected token type + * @param null|string $data The expected data or null + * @return bool + */ + protected function checkToken($type, $data = null) + { + if ($this->tokenQueue->isEof()) { + return false; + } + + $token = $this->tokenQueue->peek(); + + if ($token->getType() !== $type) { + return false; + } + + if ($data && $token->getData() !== $data) { + return false; + } + + return true; + } + + /** + * Check if the next token matches the expected type and data. If it does, then consume and return it, + * otherwise throw an exception. + * + * @throws \LazyGuy\PhpParse\Exception\ParserException + * @param int $type The expected token type + * @param null|string $data The expected token data or null + * @return \LazyGuy\PhpParse\Scanner\Token + */ + protected function expectToken($type, $data = null) + { + $token = $this->tokenQueue->peek(); + + if (!$this->checkToken($type, $data)) { + throw new ParserException($this->tokenQueue, sprintf("Expected token [%s, '%s']", Token::getTypeName($type), $data)); + } + + $this->tokenQueue->next(); + + return $token; + } + + /** + * Check if the next token matches the expected type and data. If it does, then consume it, otherwise + * return false. + * + * @param int $type The expected token type + * @param null|string $data The expected token data or null + * @return bool|\LazyGuy\PhpParse\Scanner\Token + */ + protected function checkAndExpectToken($type, $data = null) + { + if ($this->checkToken($type, $data)) { + $token = $this->tokenQueue->peek(); + $this->tokenQueue->next(); + return $token; + } + + return false; + } +} diff --git a/src/PHPCR/Util/CND/Parser/CndParser.php b/src/PHPCR/Util/CND/Parser/CndParser.php new file mode 100644 index 00000000..b6edb5a1 --- /dev/null +++ b/src/PHPCR/Util/CND/Parser/CndParser.php @@ -0,0 +1,932 @@ +addChild($nsMapping); + $root->addChild($nodeTypes); + + while (!$this->tokenQueue->isEof()) { + + $this->debugSection('PARSER CYCLE'); + + while ($this->checkToken(Token::TK_SYMBOL, '<')) { + $nsMapping->addChild($this->parseNamespaceMapping()); + } + + if (!$this->tokenQueue->isEof()) { + $nodeTypes->addChild($this->parseNodeTypeDef()); + } + + } + + return $root; + } + + /** + * A namespace declaration consists of prefix/URI pair. The prefix must be + * a valid JCR namespace prefix, which is the same as a valid XML namespace + * prefix. The URI can in fact be any string. Just as in XML, it need not + * actually be a URI, though adhering to that convention is recommended. + * + * NamespaceMapping ::= '<' Prefix '=' Uri '>' + * Prefix ::= String + * Uri ::= String + * + * @return SyntaxTreeNode + */ + protected function parseNamespaceMapping() + { + $this->debug('parseNamespaceMapping'); + + $this->expectToken(Token::TK_SYMBOL, '<'); + $prefix = $this->parseCndString(); + $this->expectToken(Token::TK_SYMBOL, '='); + $uri = substr($this->expectToken(Token::TK_STRING)->getData(), 1, -1); + $this->expectToken(Token::TK_SYMBOL, '>'); + + $this->debugRes("nsmapping: $prefix => $uri"); + + return new SyntaxTreeNode('nsMapping', array('prefix' => $prefix, 'uri' => $uri)); + } + + /** + * A node type definition consists of a node type name followed by an optional + * supertypes block, an optional node type attributes block and zero or more + * blocks, each of which is either a property or child node definition. + * + * NodeTypeDef ::= NodeTypeName [Supertypes] + * [NodeTypeAttribute {NodeTypeAttribute}] + * {PropertyDef | ChildNodeDef} + * + * @return SyntaxTreeNode + */ + protected function parseNodeTypeDef() + { + $this->debug('parseNodeTypeDef'); + + $node = new SyntaxTreeNode('nodeTypeDef'); + $node->addChild($this->parseNodeTypeName()); + + if ($this->checkToken(Token::TK_SYMBOL, '>')) { + $node->addChild($this->parseSupertypes()); + } + + if ($attrNode = $this->parseNodeTypeAttribues()){ + $node->addChild($attrNode); + } + + if ($children = $this->parseChildDefs()) { + foreach($children as $child) { + $node->addChild($child); + } + } + + return $node; + } + + /** + * The node type name is delimited by square brackets and must be a valid JCR name. + * + * NodeTypeName ::= '[' String ']' + * + * @return SyntaxTreeNode + */ + protected function parseNodeTypeName() + { + $this->debug('parseNodeTypeName'); + + $this->expectToken(Token::TK_SYMBOL, '['); + $name = $this->parseCndString(); + $this->expectToken(Token::TK_SYMBOL, ']'); + + $this->debugRes("nodeTypeName: $name"); + + return new SyntaxTreeNode('nodeTypeName', array('value' => $name)); + } + + /** + * The list of supertypes is prefixed by a '>'. If the node type is not a + * mixin then it implicitly has nt:base as a supertype even if neither + * nt:base nor a subtype of nt:base appears in the list or if this element + * is absent. A question mark indicates that the supertypes list is a variant. + * + * Supertypes ::= '>' (StringList | '?') + * + * @return SyntaxTreeNode + */ + protected function parseSupertypes() + { + $this->debug('parseSupertypes'); + + $this->expectToken(Token::TK_SYMBOL, '>'); + + $supertypes = new SyntaxTreeNode('supertypes'); + + if ($this->checkAndExpectToken(Token::TK_SYMBOL, '?')) { + $supertypes->setProperty('value', '?'); + $display = '?'; + } else { + $list = $this->parseCndStringList(); + $supertypes->setProperty('value', $list); + $display = join(', ', $list); + } + + $this->debugRes(sprintf('supertypes: (%s)', $display)); + + return $supertypes; + } + + /** + * The node type attributes are indicated by the presence or absence of keywords. + * + * If 'orderable' is present without a '?' then orderable child nodes is supported. + * If 'orderable' is present with a '?' then orderable child nodes is a variant. + * If 'orderable' is absent then orderable child nodes * is not supported. + * + * If 'mixin' is present without a '?' then the node type is a mixin. + * If 'mixin' is present with a '?' then the mixin status is a variant. + * If 'mixin' is absent then the node type is primary. + * + * If 'abstract' is present without a '?' then the node type is abstract. + * If 'abstract' is present with a '?' then the abstract status is a variant. + * If 'abstract' is absent then the node type is concrete. + * + * If 'query' is present then the node type is queryable. + * If 'noquery' is present then the node type is not queryable. + * If neither query nor noquery are present then the queryable setting of the + * node type is a variant. + * + * If 'primaryitem' is present without a '?' then the string following it is + * the name of the primary item of the node type. + * If 'primaryitem' is present with a '?' then the primary item is a variant. + * If 'primaryitem' is absent then the node type has no primary item. + * + * NodeTypeAttribute ::= Orderable | Mixin | Abstract | Query | PrimaryItem + * Orderable ::= ('orderable' | 'ord' | 'o') ['?'] + * Mixin ::= ('mixin' | 'mix' | 'm') ['?'] + * Abstract ::= ('abstract' | 'abs' | 'a') ['?'] + * Query ::= ('noquery' | 'nq') | ('query' | 'q' ) + * PrimaryItem ::= ('primaryitem'| '!')(String | '?') + * + * @return SyntaxTreeNode + */ + protected function parseNodeTypeAttribues() + { + $this->debug('parseNodeTypeAttributes'); + return $this->parseAttributes('nodeTypeAttributes', $this->getNodeTypeAttributes()); + } + + /** + * Parse both the children propery and nodes definitions + * + * {PropertyDef | ChildNodeDef} + * + * @return SyntaxTreeNode + */ + protected function parseChildDefs() + { + $this->debug('parseChildDefs'); + + $propDefs = new SyntaxTreeNode('propertyDefs'); + $childNodeDef = new SyntaxTreeNode('childNodeDefs'); + + while (true) { + + if ($this->checkToken(Token::TK_SYMBOL, '-')) { + $propDefs->addChild($this->parsePropDef()); + } elseif ($this->checkToken(Token::TK_SYMBOL, '+')) { + $childNodeDef->addChild($this->parseChildNodeDef()); + } else { + break; + } + } + + // Only return the nodes that actually have children + $children = array(); + + if ($propDefs->hasChildren()) { + $children[] = $propDefs; + } + if ($childNodeDef->hasChildren()) { + $children[] = $childNodeDef; + } + + return $children; + } + + /** + * A property definition consists of a property name element followed by + * optional property type, default values, property attributes and value + * constraints elements. + * + * The property name, or '*' to indicate a residual property definition, + * is prefixed with a '-'. + * + * PropertyDef ::= PropertyName [PropertyType] [DefaultValues] + * [PropertyAttribute {PropertyAttribute}] + * [ValueConstraints] + * PropertyName ::= '-' String + * + * @return SyntaxTreeNode + */ + protected function parsePropDef() + { + $this->debug('parsePropDef'); + + $node = new SyntaxTreeNode('propertyDef'); + + // Parse the property name + $this->expectToken(Token::TK_SYMBOL, '-'); + if ($this->checkAndExpectToken(Token::TK_SYMBOL, '*')) { + $name = '*'; + } else { + $name = $this->parseCndString(); + } + $node->addChild(new SyntaxTreeNode('propertyName', array('value' => $name))); + + // Parse the property type + if ($this->checkToken(Token::TK_SYMBOL, '(')) { + $node->addChild($this->parsePropertyType()); + } + + // Parse default value + if ($this->checkToken(Token::TK_SYMBOL, '=')) { + $node->addChild($this->parseDefaultValue()); + } + + if ($attrNode = $this->parsePropertyAttributes()) { + $node->addChild($attrNode); + } + // Check if there is a constraint (and not another namespace def) + // Next token is '<' and two token later it's not '=', i.e. not 'tokenQueue->peek(); + $next2 = $this->tokenQueue->peek(2); + if ($next1 && $next1->getData() === '<' && (!$next2 || $next2->getData() !== '=')) { + $node->addChild($this->parseValueConstraints()); + } + + $this->debugRes('propertyName: ' . $name); + + return $node; + } + + /** + * The property type is delimited by parentheses ('*' is a synonym for UNDEFINED). + * If this element is absent, STRING is assumed. A '?' indicates that this + * attribute is a variant. + * + * PropertyType ::= '(' ('STRING' | 'BINARY' | 'LONG' | 'DOUBLE' | + * 'BOOLEAN' | 'DATE' | 'NAME' | 'PATH' | + * 'REFERENCE' | 'WEAKREFERENCE' | + * 'DECIMAL' | 'URI' | 'UNDEFINED' | '*' | + * '?') ')' + * + * @return SyntaxTreeNode + */ + protected function parsePropertyType() + { + $this->debug('parsePropertyType'); + + // TODO: can the property be lowercase or camelcase as in old spec? + $types = array("STRING", "BINARY", "LONG", "DOUBLE", "BOOLEAN", "DATE", "NAME", "PATH", + "REFERENCE", "WEAKREFERENCE", "DECIMAL", "URI", "UNDEFINED", "*", "?"); + + $this->expectToken(Token::TK_SYMBOL, '('); + + $data = $this->tokenQueue->get()->getData(); + if (!in_array($data, $types)) { + throw new ParserException($this->tokenQueue, sprintf("Invalid property type: %s", $data)); + } + + $this->expectToken(Token::TK_SYMBOL, ')'); + + $this->debugRes('propertyType: ' . $data); + + return new SyntaxTreeNode('propertyType', array('value' => $data)); + } + + /** + * The default values, if any, are listed after a '='. The attribute is a + * list in order to accommodate multi-value properties. The absence of this + * element indicates that there is no static default value reportable. A '?' + * indicates that this attribute is a variant + * + * DefaultValues ::= '=' (StringList | '?') + * + * @return SyntaxTreeNode + */ + protected function parseDefaultValue() + { + $this->debug('parseDefaultValues'); + + // TODO: parse ? + $this->expectToken(Token::TK_SYMBOL, '='); + + if ($this->checkAndExpectToken(Token::TK_SYMBOL, '?')) { + $list = array('?'); + } else { + $list = $this->parseCndStringList(); + } + + $this->debugRes(sprintf('defaultValues: (%s)', join(', ', $list))); + + return new SyntaxTreeNode('defaultValues', array('value' => $list)); + } + + /** + * The value constraints, if any, are listed after a '<'. The absence of + * this element indicates that no value constraints reportable within the + * value constraint syntax. A '?' indicates that this attribute is a variant + * + * ValueConstraints ::= '<' (StringList | '?') + * + * @return SyntaxTreeNode + */ + protected function parseValueConstraints() + { + $this->debug('parseValueConstraints'); + + $this->expectToken(Token::TK_SYMBOL, '<'); + + if ($this->checkAndExpectToken(Token::TK_SYMBOL, '?')) { + $list = array('?'); + } else { + $list = $this->parseCndStringList(); + } + + $this->debugRes(sprintf('valueConstraints: (%s)', join(', ', $list))); + + return new SyntaxTreeNode('valueConstraints', array('value' => $list)); + } + + /** + * The property attributes are indicated by the presence or absence of keywords. + * + * If 'autocreated' is present without a '?' then the item is autocreated. + * If 'autocreated' is present with a '?' then the autocreated status is a variant. + * If 'autocreated' is absent then the item is not autocreated. + * + * If 'mandatory' is present without a '?' then the item is mandatory. + * If 'mandatory' is present with a '?' then the mandatory status is a variant. + * If 'mandatory' is absent then the item is not mandatory. + * + * If 'protected' is present without a '?' then the item is protected. + * If 'protected' is present with a '?' then the protected status is a variant. + * If 'protected' is absent then the item is not protected. + * + * The OPV status of an item is indicated by the presence of that corresponding + * keyword. + * If no OPV keyword is present then an OPV status of COPY is assumed. + * If the keyword 'OPV' followed by a '?' is present then the OPV status of the + * item is a variant. + * + * If 'multiple' is present without a '?' then the property is multi-valued. + * If 'multiple' is present with a '?' then the multi-value status is a variant. + * If 'multiple' is absent then the property is single-valued. + * + * The available query comparison operators are listed after the keyword 'queryops'. + * If 'queryops' is followed by a '?' then this attribute is a variant. + * If this element is absent then the full set of operators is available. + * + * If 'nofulltext' is present without a '?' then the property does not support full + * text search. + * If 'nofulltext' is present with a '?' then this attribute is a variant. + * If 'nofulltext' is absent then the property does support full text search. + * + * If 'noqueryorder' is present without a '?' then query results cannot be ordered + * by this property. + * If 'noqueryorder' is present with a '?' then this attribute is a variant. + * If 'noqueryorder' is absent then query results can be ordered by this property. + * + * PropertyAttribute ::= Autocreated | Mandatory | Protected | + * Opv | Multiple | QueryOps | NoFullText | + * NoQueryOrder + * Autocreated ::= ('autocreated' | 'aut' | 'a' )['?'] + * Mandatory ::= ('mandatory' | 'man' | 'm') ['?'] + * Protected ::= ('protected' | 'pro' | 'p') ['?'] + * Opv ::= 'COPY' | 'VERSION' | 'INITIALIZE' | 'COMPUTE' | + * 'IGNORE' | 'ABORT' | ('OPV' '?') + * Multiple ::= ('multiple' | 'mul' | '*') ['?'] + * QueryOps ::= ('queryops' | 'qop') + * (('''Operator {','Operator}''') | '?') + * Operator ::= '=' | '<>' | '<' | '<=' | '>' | '>=' | 'LIKE' + * NoFullText ::= ('nofulltext' | 'nof') ['?'] + * NoQueryOrder ::= ('noqueryorder' | 'nqord') ['?'] + * + * @return SyntaxTreeNode + */ + protected function parsePropertyAttributes() + { + $this->debug('parsePropertyAttributes'); + return $this->parseAttributes('propertyTypeAttributes', $this->getPropertyTypeAttributes()); + } + + /** + * The node attributes are indicated by the presence or absence of keywords. + * + * If 'autocreated' is present without a '?' then the item is autocreated. + * If 'autocreated' is present with a '?' then the autocreated status is a variant. + * If 'autocreated' is absent then the item is not autocreated. + * + * If 'mandatory' is present without a '?' then the item is mandatory. + * If 'mandatory' is present with a '?' then the mandatory status is a variant. + * If 'mandatory' is absent then the item is not mandatory. + * + * If 'protected' is present without a '?' then the item is protected. + * If 'protected' is present with a '?' then the protected status is a variant. + * If 'protected' is absent then the item is not protected. + * + * The OPV status of an item is indicated by the presence of that corresponding + * keyword. + * If no OPV keyword is present then an OPV status of COPY is assumed. + * If the keyword 'OPV' followed by a '?' is present then the OPV status of the + * item is a variant. + * + * If 'sns' is present without a '?' then the child node supports same-name siblings. + * If 'sns' is present with a '?' then this attribute is a variant. + * If 'sns' is absent then the child node does support same-name siblings. + * + * NodeAttribute ::= Autocreated | Mandatory | Protected | + * Opv | Sns + * Autocreated ::= ('autocreated' | 'aut' | 'a' )['?'] + * Mandatory ::= ('mandatory' | 'man' | 'm') ['?'] + * Protected ::= ('protected' | 'pro' | 'p') ['?'] + * Opv ::= 'COPY' | 'VERSION' | 'INITIALIZE' | 'COMPUTE' | + * 'IGNORE' | 'ABORT' | ('OPV' '?') + * Sns ::= ('sns' | '*') ['?'] + * + * @return SyntaxTreeNode + */ + protected function parseNodeAttributes() + { + /** + * TODO: Clarify this problem + * + * Either there is a bug in the Jackrabbit builtin nodetypes CND file here: + * + * [rep:Group] > rep:Authorizable + * + rep:members (rep:Members) = rep:Members multiple protected VERSION + * - rep:members (WEAKREFERENCE) protected multiple < 'rep:Authorizable' + * + * or there is an error in the spec that says that a node attribute cannot be + * "multiple". + */ + $this->debug('parseNodeAttributes'); + return $this->parseAttributes('nodeAttributes', $this->getNodeAttributes()); + } + + /** + * A child node definition consists of a node name element followed by optional + * required node types, default node types and node attributes elements. + * + * The node name, or '*' to indicate a residual property definition, is prefixed + * with a '+'. + * + * The required primary node type list is delimited by parentheses. If this + * element is missing then a required primary node type of nt:base is assumed. + * A '?' indicates that the this attribute is a variant. + * + * ChildNodeDef ::= NodeName [RequiredTypes] [DefaultType] + * [NodeAttribute {NodeAttribute}] + * NodeName ::= '+' String + * RequiredTypes ::= '(' (StringList | '?') ')' + * DefaultType ::= '=' (String | '?') + * + * @return SyntaxTreeNode + */ + protected function parseChildNodeDef() + { + $this->debug('parseChildNodeDef'); + + $node = new SyntaxTreeNode('childNodeDef'); + + $this->expectToken(Token::TK_SYMBOL, '+'); + + // Parse the property name + if ($this->checkAndExpectToken(Token::TK_SYMBOL, '*')) { + $name = '*'; + } else { + $name = $this->parseCndString(); + } + $node->addChild(new SyntaxTreeNode('nodeName', array('value' => $name))); + + // Parse the required types + if ($this->checkAndExpectToken(Token::TK_SYMBOL, '(')) { + if ($this->checkAndExpectToken(Token::TK_SYMBOL, '?')) { + $list = '?'; + } else { + $list = $this->parseCndStringList(); + } + $this->expectToken(Token::TK_SYMBOL, ')'); + + $node->addChild(new SyntaxTreeNode('requiredTypes', array('value' => $list))); + } + + // Parse the default type + if ($this->checkAndExpectToken(Token::TK_SYMBOL, '=')) { + $node->addChild(new SyntaxTreeNode('defaultType', array('value' => $this->parseCndString()))); + } + + if ($attrNode = $this->parseNodeAttributes()) { + $node->addChild($attrNode); + } + + $this->debugRes('childNodeName: ' . $name); + + return $node; + } + + /** + * Parse a string list + * + * StringList ::= String {',' String} + * + * @return array + */ + protected function parseCndStringList() + { + $this->debug('parseCndStringList'); + + $strings = array(); + + $strings[] = $this->parseCndString(); + while ($this->checkAndExpectToken(Token::TK_SYMBOL, ',')) { + $strings[] = $this->parseCndString(); + } + + $this->debugRes(sprintf('string-list: (%s)', join(', ', $strings))); + + return $strings; + } + + /** + * Parse a string + * + * String ::= QuotedString | UnquotedString + * QuotedString ::= SingleQuotedString | DoubleQuotedString + * SingleQuotedString ::= ''' UnquotedString ''' + * DoubleQuotedString ::= '"' UnquotedString '"' + * UnquotedString ::= LocalName + * LocalName ::= ValidString – SelfOrParent + * SelfOrParent ::= '.' | '..' + * ValidString ::= ValidChar {ValidChar} + * ValidChar ::= XmlChar – InvalidChar + * InvalidChar ::= '/' | ':' | '[' | ']' | '|' | '*' + * XmlChar ::= Any character that matches the Char production + * at http://www.w3.org/TR/xml/#NT-Char + * Char ::= "\t" | "\r" | "\n" | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] + * + * TODO: check \n, \r, \t are valid in CND strings! + * + * @return string + */ + protected function parseCndString() + { + $this->debug('parseCndString'); + + // TODO: adapt + + $string = ''; + $lastType = null; + + while (true) { + + $token = $this->tokenQueue->peek(); + $type = $token->getType(); + $data = $token->getData(); + + IF ($type === Token::TK_STRING) { + $string = substr($data, 1, -1); + $this->tokenQueue->next(); + return $string; + } + + // If it's not an identifier or a symbol allowed in a string, break + if ($type !== Token::TK_IDENTIFIER && $type !== Token::TK_SYMBOL + || ($type === Token::TK_SYMBOL && $data !== '_' && $data !== ':')) { + break; + } + + // Detect spaces (an identifier cannot be followed by an identifier as it would have been read as a single token) + if ($type === Token::TK_IDENTIFIER && $lastType === Token::TK_IDENTIFIER) { + break; + } + + $string .= $token->getData(); + + $this->tokenQueue->next(); + $lastType = $type; + } + + if ($string === '') { + throw new ParserException($this->tokenQueue, sprintf("Expected CND string, found '%s': ", $this->tokenQueue->peek()->getData())); + } + + $this->debugRes(sprintf('string: %s', $string)); + + return $string; + } + + /** + * Return an array representing the allowed node type attributes. + * The values are the aliases for the attribute. + * Variant indicates if the attribute can be variant (followed by a ?) + * Some attributes will need special handling in the parsing function. + * + * @return array + */ + protected function getNodeTypeAttributes() + { + return array( + 'orderable' => array('values' => array('o', 'ord', 'orderable'), 'variant' => true), + 'mixin' => array('values' => array('m', 'mix', 'mixin'), 'variant' => true), + 'abstract' => array('values' => array('a', 'abs', 'abstract'), 'variant' => true), + 'noquery' => array('values' => array('noquery', 'nq'), 'variant' => false), + 'query' => array('values' => array('query', 'q'), 'variant' => false), + 'primaryitem' => array('values' => array('primaryitem', '!'), 'variant' => false), // Needs special handling ! + ); + } + + /** + * Return an array representing the commonly allowed attributes for nodes and property types. + * The values are the aliases for the attribute. + * Variant indicates if the attribute can be variant (followed by a ?) + * Some attributes will need special handling in the parsing function. + * + * @return array + */ + protected function getCommonAttributes() + { + return array( + 'autocreated' => array('values' => array('a', 'aut', 'autocreated'), 'variant' => true), + 'mandatory' => array('values' => array('m', 'man', 'mandatory'), 'variant' => true), + 'protected' => array('values' => array('p', 'pro', 'protected'), 'variant' => true), + 'COPY' => array('values' => array('COPY'), 'variant' => false), + 'VERSION' => array('values' => array('VERSION'), 'variant' => false), + 'INITIALIZE' => array('values' => array('INITIALIZE'), 'variant' => false), + 'COMPUTE' => array('values' => array('COMPUTE'), 'variant' => false), + 'IGNORE' => array('values' => array('IGNORE'), 'variant' => false), + 'ABORT' => array('values' => array('ABORT'), 'variant' => false), + 'OPV' => array('values' => array('OPV'), 'variant' => true), + ); + } + + /** + * Return an array representing the allowed property type attributes. + * The values are the aliases for the attribute. + * Variant indicates if the attribute can be variant (followed by a ?) + * Some attributes will need special handling in the parsing function. + * + * @return array + */ + protected function getPropertyTypeAttributes() + { + return array_merge( + $this->getCommonAttributes(), + array( + 'multiple' => array('values' => array('*', 'mul', 'multiple'), 'variant' => true), + 'queryops' => array('values' => array('qop', 'queryops'), 'variant' => true), // Needs special handling ! + 'nofulltext' => array('values' => array('nof', 'nofulltext'), 'variant' => true), + 'noqueryorder' => array('values' => array('nqord', 'noqueryorder'), 'variant' => true), + ) + ); + } + + /** + * Return an array representing the allowed node attributes. + * The values are the aliases for the attribute. + * Variant indicates if the attribute can be variant (followed by a ?) + * Some attributes will need special handling in the parsing function. + * + * @return array + */ + protected function getNodeAttributes() + { + return array_merge( + $this->getCommonAttributes(), + array( + 'sns' => array('values' => array('*', 'sns'), 'variant' => true), + ) + ); + } + + /** + * Parse a list of attributes. + * The allowed attributes must be specified in $attributes. + * The type of the list must be given (node type attributes, property type attributes or node attributes). + * + * @param string $type + * @param array $attributes + * @return SyntaxTreeNode + */ + protected function parseAttributes($type, $attributes) + { + $this->debug('parseAttributes'); + + $node = new SyntaxTreeNode($type); + + $options = array(); + + while ($attrNode = $this->parseAttribute($attributes)) { + $node->addChild($attrNode); + $options[] = $attrNode->getType(); + } + + $this->debugRes(sprintf('%s: (%s)', $type, join(', ', $options))); + + if (empty($options)) { + return false; + } + + return $node; + } + + /** + * Parse a single attribute. + * The allowed attributes must be given in $attributes. + * Return the attribute if any was found or false. + * + * @param array $attributes + * @return bool|SyntaxTreeNode + */ + protected function parseAttribute($attributes) + { + $this->debug('parseAttribute'); + + $token = $this->tokenQueue->peek(); + if (!$token) { + return false; + } + $data = $token->getData(); + + foreach ($attributes as $name => $def) { + + if (in_array($data, $def['values'])) { + + // Node type attribute found + $this->tokenQueue->next(); + + // Handle special cases + if ($attribute = $this->parseSpecialCaseAttribute($name)) { + return $attribute; + } + + // If this attribute can ba variant + if ($def['variant']) { + if ($this->checkAndExpectToken(Token::TK_SYMBOL, '?')) { + $variant = true; + } + } + + $node = new SyntaxTreeNode($name); + if (isset($variant)) { + $node->setProperty('variant', true); + } + return $node; + } + + } + + return false; + } + + /** + * Some attributes need special handling to be parsed, they are managed in + * this function. Return the attribute if any was found or false. + * + * At this point the attribute token has already been removed from the queue. + * + * @param string $attributeName + * @return bool|SyntaxTreeNode + */ + protected function parseSpecialCaseAttribute($attributeName) + { + $this->debug('parseSpecialCaseAttribute'); + + if ($attributeName === 'primaryitem') { + + /** + * If 'primaryitem' is present without a '?' then the string following it is + * the name of the primary item of the node type. + * If 'primaryitem' is present with a '?' then the primary item is a variant. + * If 'primaryitem' is absent then the node type has no primary item. + * + * PrimaryItem ::= ('primaryitem'| '!')(String | '?') + */ + + if ($this->checkAndExpectToken(Token::TK_SYMBOL, '?')) { + return new SyntaxTreeNode('primaryitem', array('value' => '?')); + } + return new SyntaxTreeNode('primaryitem', array('value' => $this->parseCndString())); + } + + if ($attributeName === 'queryops') { + + return $this->parseQueryOpsAttribute(); + } + + return false; + } + + /** + * The available query comparison operators are listed after the keyword 'queryops'. + * If 'queryops' is followed by a '?' then this attribute is a variant. + * If this element is absent then the full set of operators is available. + * + * QueryOps ::= ('queryops' | 'qop') + * (('''Operator {','Operator}''') | '?') + * Operator ::= '=' | '<>' | '<' | '<=' | '>' | '>=' | 'LIKE' + * + * @return SyntaxTreeNode + */ + protected function parseQueryOpsAttribute() + { + if ($this->checkAndExpectToken(Token::TK_SYMBOL, '?')) { + return new SyntaxTreeNode('queryops', array('variant' => true)); + } + + $ops = array(); + do { + + $op = $this->parseQueryOperator(); + $ops[] = $op; + + } while ($op && $this->checkAndExpectToken(Token::TK_SYMBOL, ',')); + + if (empty($ops)) { + // There must be at least an operator if this attribute is not variant + throw new ParserException($this->tokenQueue, 'Operator expected'); + } + + return new SyntaxTreeNode('queryops', array('value' => $ops)); + } + + /** + * Parse a query operator. + * + * This is quite complicated for not so much... Idealy this should be implemented + * in a specialized Scanner. + * + * @return bool|string + */ + protected function parseQueryOperator() + { + $token = $this->tokenQueue->peek(); + $data = $token->getData(); + + $nextToken = $this->tokenQueue->peek(1); + $nextData = $nextToken->getData(); + $op = false; + + switch ($data) { + case '<': + $op = ($nextData === '>' ? '>=' : ($nextData === '=' ? '<=' : '<')); + break; + case '>': + $op = ($nextData === '=' ? '>=' : '>'); + break; + case '=': + $op = '='; + break; + case 'LIKE': + $op = 'LIKE'; + break; + } + + // Consume the correct number of tokens + if ($op === 'LIKE' || strlen($op) === 1) { + $this->tokenQueue->next(); + } elseif (strlen($op) === 2) { + $this->tokenQueue->next(); + $this->tokenQueue->next(); + } + + return $op; + } +} diff --git a/src/PHPCR/Util/CND/Parser/ParserInterface.php b/src/PHPCR/Util/CND/Parser/ParserInterface.php new file mode 100644 index 00000000..71418c50 --- /dev/null +++ b/src/PHPCR/Util/CND/Parser/ParserInterface.php @@ -0,0 +1,11 @@ +type = $type; + $this->properties = $properties; + $this->children = array(); + } + + /** + * @param string $name + * @param string $value + * @return void + */ + public function setProperty($name, $value) + { + $this->properties[$name] = $value; + } + + /** + * @param SyntaxTreeNode $child + * @return void + */ + public function addChild(SyntaxTreeNode $child) + { + $this->children[] = $child; + } + + /** + * @param SyntaxTreeVisitorInterface $visitor + * @return void + */ + public function accept(SyntaxTreeVisitorInterface $visitor) + { + $visitor->visit($this); + foreach($this->children as $child) { + $child->accept($visitor); + } + } + + /** + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * @return array + */ + public function getProperties() + { + return $this->properties; + } + + /** + * @param string $name + * @return bool + */ + public function hasProperty($name) + { + return array_key_exists($name, $this->properties); + } + + /** + * @throws \InvalidArgumentException + * @param string $name + * @return mixed + */ + public function getProperty($name) + { + if (!array_key_exists($name, $this->properties)) { + throw new \InvalidArgumentException("No property '$name' found."); + } + + return $this->properties[$name]; + } + + /** + * Get all the children + * @return array + */ + public function getChildren() + { + return $this->children; + } + + /** + * Return all the children of a given type + * @param $type + * @return array + */ + public function getChildrenByType($type) + { + $children = array(); + foreach ($this->children as $child) { + if ($child->getType() === $type) { + $children[] = $child; + } + } + return $children; + } + + /** + * Return the first child with the given type or false if none + * @param $type + * @return array + */ + public function getFirstChildByType($type) + { + foreach ($this->children as $child) { + if ($child->getType() === $type) { + return $child; + } + } + return false; + } + + /** + * Return true if the node has child nodes and false otherwise + * @return bool + */ + public function hasChildren() + { + return !empty($this->children); + } + + /** + * Return true if the node has at least one child of the given type + * @param string $type + * @return bool + */ + public function hasChild($type) + { + foreach ($this->children as $child) { + if ($child->getType() === $type) { + return true; + } + } + return false; + } + + /** + * Return a dumped version of the tree + * + * @param bool $compact If true, less spacing is added to the dump + * @param int $level Internal parameter used to indicate the current indentation level + * @return string + */ + public function dump($compact = false, $level = 0) + { + $dump = ''; + $indent = str_repeat(' ', $level); + $dump .= sprintf("%sNODE[%s]%s\n", $indent, $this->type, !empty($this->properties) || !empty($this->children) ? ':' : ''); + + foreach ($this->properties as $key => $value) { + if (is_array($value)) { + $value = sprintf('(%s)', join(', ', $value)); + } + $dump .= sprintf("%s - %s = %s\n", $indent, $key, $value); + } + + if (!$compact && $this->hasChildren()) { + $dump .= "\n"; + } + + foreach ($this->children as $child) { + $dump .= $child->dump($compact, $level + 1); + } + + if (!$compact && !$this->hasChildren()) { + $dump .= "\n"; + } + + return $dump; + } +} diff --git a/src/PHPCR/Util/CND/Parser/SyntaxTreeVisitorInterface.php b/src/PHPCR/Util/CND/Parser/SyntaxTreeVisitorInterface.php new file mode 100644 index 00000000..53209264 --- /dev/null +++ b/src/PHPCR/Util/CND/Parser/SyntaxTreeVisitorInterface.php @@ -0,0 +1,8 @@ +eofMarker = chr(1); + $this->buffer = $buffer . $this->eofMarker; + + $this->reset(); + } + + public function reset() + { + $this->startPos = 0; + + $this->forwardPos = 0; + $this->curLine = $this->curCol = 1; + $this->nextCurLine = $this->nextCurCol = 1; + } + + public function getEofMarker() + { + return $this->eofMarker; + } + + /** + * @return int + */ + function getCurrentLine() + { + return $this->curLine; + } + + /** + * @return int + */ + function getCurrentColumn() + { + return $this->curCol; + } + + /** + * @return string + */ + function getBuffer() + { + return $this->buffer; + } + + /** + * Return the literal delimited by start and end position + * @return string + */ + public function current() + { + return substr($this->buffer, $this->startPos, $this->forwardPos - $this->startPos); + } + + public function currentChar() + { + return substr($this->buffer, $this->forwardPos, 1); + } + + public function isEof() + { + return $this->currentChar() === $this->getEofMarker() + || $this->startPos > strlen($this->buffer) + || $this->forwardPos > strlen($this->buffer); + } + + /** + * Advance the forward position and return the literal delimited by start and end position + * @return string + */ + public function forward() + { + if ($this->forwardPos < strlen($this->buffer)) { + $this->forwardPos++; + $this->nextCurCol++; + } + + if ($this->current() === PHP_EOL) { + $this->nextCurLine++; + $this->nextCurCol = 1; + } + + return $this->current(); + } + + public function forwardChar() + { + $this->forward(); + return $this->currentChar(); + } + + public function rewind() + { + $this->forwardPos = $this->startPos; + $this->nextCurLine = $this->curLine; + $this->nextCurCol = $this->curCol; + } + + public function unget() + { + if ($this->forwardPos > $this->startPos) { + $this->forwardPos--; + $this->nextCurCol--; + } + } + + public function consume() + { + $current = $this->current(); + + if ($current !== $this->getEofMarker()) { + $this->startPos = $this->forwardPos; + } + + $this->curLine = $this->nextCurLine; + $this->curCol = $this->nextCurCol; + + return $current; + } + +} diff --git a/src/PHPCR/Util/CND/Reader/FileReader.php b/src/PHPCR/Util/CND/Reader/FileReader.php new file mode 100644 index 00000000..bed0d236 --- /dev/null +++ b/src/PHPCR/Util/CND/Reader/FileReader.php @@ -0,0 +1,24 @@ +fileName = $fileName; + + parent::__construct(file_get_contents($fileName)); + } + + public function getFileName() + { + return $this->fileName; + } +} diff --git a/src/PHPCR/Util/CND/Reader/ReaderInterface.php b/src/PHPCR/Util/CND/Reader/ReaderInterface.php new file mode 100644 index 00000000..511bd3d3 --- /dev/null +++ b/src/PHPCR/Util/CND/Reader/ReaderInterface.php @@ -0,0 +1,57 @@ +resetQueue(); + $this->context = $context; + } + + public function resetQueue() + { + $this->queue = new TokenQueue(); + } + + /** + * @param Token $token + * @return Token | void + */ + public function applyFilters(Token $token) + { + foreach ($this->context->getTokenFilters() as $filter) { + + $token = $filter->filter($token); + + if (is_null($token)) { + break; + } + } + + return $token; + } + + protected function getQueue() + { + return $this->queue; + } + + protected function addToken(ReaderInterface $reader, Token $token) + { + $token->setLine($reader->getCurrentLine()); + $token->setRow($reader->getCurrentColumn()); + + $this->debugToken($token); + if ($token = $this->applyFilters($token)) { + $this->queue->add($token); + } else { + $this->debug(" -- Token rejected"); + } + } + + protected function debugToken(Token $token) + { + $this->debugRes($token); + } +} diff --git a/src/PHPCR/Util/CND/Scanner/Context/DefaultScannerContext.php b/src/PHPCR/Util/CND/Scanner/Context/DefaultScannerContext.php new file mode 100644 index 00000000..3459d110 --- /dev/null +++ b/src/PHPCR/Util/CND/Scanner/Context/DefaultScannerContext.php @@ -0,0 +1,29 @@ +addWhitespace(" "); + $this->addWhitespace("\t"); + + $this->addStringDelimiter('\''); + $this->addStringDelimiter('"'); + + $this->addLineCommentDelimiter('//'); + + $this->addBlockCommentDelimiter('/*', '*/'); + + $symbols = array( + '<', '>', '+', '*', '%', '&', '/', '(', ')', '=', '?', '#', '|', '!', '~', + '[', ']', '{', '}', '$', ',', ';', ':', '.', '-', '_', '\\', + ); + foreach($symbols as $symbol) { + $this->addSymbol($symbol); + } + } +} diff --git a/src/PHPCR/Util/CND/Scanner/Context/DefaultScannerContextWithoutSpacesAndComments.php b/src/PHPCR/Util/CND/Scanner/Context/DefaultScannerContextWithoutSpacesAndComments.php new file mode 100644 index 00000000..0ca5d59f --- /dev/null +++ b/src/PHPCR/Util/CND/Scanner/Context/DefaultScannerContextWithoutSpacesAndComments.php @@ -0,0 +1,18 @@ +addTokenFilter(new TokenFilter\NoNewlinesFilter()); + $this->addTokenFilter(new TokenFilter\NoWhitespacesFilter()); + $this->addTokenFilter(new TokenFilter\NoCommentsFilter()); + } + +} diff --git a/src/PHPCR/Util/CND/Scanner/Context/ScannerContext.php b/src/PHPCR/Util/CND/Scanner/Context/ScannerContext.php new file mode 100644 index 00000000..8b3d3512 --- /dev/null +++ b/src/PHPCR/Util/CND/Scanner/Context/ScannerContext.php @@ -0,0 +1,157 @@ +blockCommentDelimiters[$startDelim] = $endDelim; + } + + /** + * @return array + */ + public function getBlockCommentDelimiters() + { + return $this->blockCommentDelimiters; + } + + /** + * @param string $delim + */ + public function addLineCommentDelimiter($delim) + { + $this->lineCommentDelimiters[] = $delim; + } + + /** + * @return array + */ + public function getLineCommentDelimiters() + { + return $this->lineCommentDelimiters; + } + + /** + * @param string $delim + */ + public function addStringDelimiter($delim) + { + if (!in_array($delim, $this->stringDelimiters)) { + $this->stringDelimiters[] = $delim; + } + } + + /** + * @return array + */ + public function getStringDelimiters() + { + return $this->stringDelimiters; + } + + /** + * @param string $symbol + */ + public function addSymbol($symbol) + { + if (!in_array($symbol, $this->symbols)) { + $this->symbols[] = $symbol; + } + } + + /** + * @return array + */ + public function getSymbols() + { + return $this->symbols; + } + + /** + * @param array $whitespace + */ + public function addWhitespace($whitespace) + { + if (!in_array($whitespace, $this->whitespaces)) { + $this->whitespaces[] = $whitespace; + } + } + + /** + * @return array + */ + public function getWhitespaces() + { + return $this->whitespaces; + } + + /** + * @param \LazyGuy\PhpParse\Scanner\TokenFilter\TokenFilterInterface $filter + * @return void + */ + public function addTokenFilter(TokenFilterInterface $filter) + { + $this->tokenFilters[] = $filter; + } + + /** + * @return array + */ + public function getTokenFilters() + { + return $this->tokenFilters; + } +} diff --git a/src/PHPCR/Util/CND/Scanner/GenericScanner.php b/src/PHPCR/Util/CND/Scanner/GenericScanner.php new file mode 100644 index 00000000..7da790c6 --- /dev/null +++ b/src/PHPCR/Util/CND/Scanner/GenericScanner.php @@ -0,0 +1,314 @@ +debugSection("SCANNER CYCLE"); + + $this->resetQueue(); + + while (!$reader->isEof()) { + + $this->debug(sprintf('Loop on: [%s]', str_replace("\n", '', $reader->currentChar()))); + + $tokenFound = false; + + $tokenFound = $tokenFound || $this->consumeComments($reader); + $tokenFound = $tokenFound || $this->consumeNewLine($reader); + $tokenFound = $tokenFound || $this->consumeSpaces($reader); + $tokenFound = $tokenFound || $this->consumeString($reader); + $tokenFound = $tokenFound || $this->consumeIdentifiers($reader); + $tokenFound = $tokenFound || $this->consumeSymbols($reader); + + if (!$tokenFound) { + $char = $reader->forwardChar(); + $reader->consume(); + + if ($char !== $reader->getEofMarker()) { + $token = new GenericToken(GenericToken::TK_UNKNOWN, $char); + $this->addToken($reader, $token); + } + } + + } + + $this->debug('== EOF =='); + + return $this->getQueue(); + } + + /** + * Detect and consume whitespaces + * + * @param \LazyGuy\PhpParse\Reader\ReaderInterface $reader + * @return bool + */ + protected function consumeSpaces(ReaderInterface $reader) + { +// $this->debug('consumeSpaces'); + + if (in_array($reader->currentChar(), $this->context->getWhitespaces())) { + + $char = $reader->forwardChar(); + while (in_array($char, $this->context->getWhitespaces())) { + $char = $reader->forwardChar(); + } + + $buffer = $reader->consume(); + + $token = new GenericToken(GenericToken::TK_WHITESPACE, $buffer); + $this->addToken($reader, $token); + + return true; + } + } + + /** + * Detect and consume newlines + * + * @param \LazyGuy\PhpParse\Reader\ReaderInterface $reader + * @return bool + */ + protected function consumeNewLine(ReaderInterface $reader) + { +// $this->debug('consumeNewline'); + + if ($reader->currentChar() === PHP_EOL) { + + $token = new GenericToken(GenericToken::TK_NEWLINE, PHP_EOL); + $this->addToken($reader, $token); + + + while ($reader->forward() === PHP_EOL) { + $reader->consume(); + $reader->forward(); + } + $reader->rewind(); + + return true; + } + + return false; + } + + /** + * Detect and consume strings + * + * @throws \LazyGuy\PhpParse\Exception\ScannerException + * @param \LazyGuy\PhpParse\Reader\ReaderInterface $reader + * @return bool + */ + protected function consumeString(ReaderInterface $reader) + { +// $this->debug('consumeStrings'); + + $curDelimiter = $reader->currentChar(); + if (in_array($curDelimiter, $this->context->getStringDelimiters())) { + + $char = $reader->forwardChar(); + while ($char !== $curDelimiter) { + + if ($char === PHP_EOL) { + throw new ScannerException($reader, "Newline detected in string"); + } + + $char = $reader->forwardChar(); + } + $reader->forward(); + + $token = new GenericToken(GenericToken::TK_STRING, $reader->consume()); + $this->addToken($reader, $token); + return true; + } + } + + /** + * Detect and consume comments + * + * @param \LazyGuy\PhpParse\Reader\ReaderInterface $reader + * @return bool + */ + protected function consumeComments(ReaderInterface $reader) + { + if ($this->consumeBlockComments($reader)) { + return true; + } + + return $this->consumeLineComments($reader); + } + + /** + * Detect and consume block comments + * + * @throws \LazyGuy\PhpParse\Exception\ScannerException + * @param \LazyGuy\PhpParse\Reader\ReaderInterface $reader + * @return bool + */ + protected function consumeBlockComments(ReaderInterface $reader) + { +// $this->debug('consumeBlockComments'); + + $nextChar = $reader->currentChar(); + foreach($this->context->getBlockCommentDelimiters() as $beginDelim => $endDelim) { + + if (!$beginDelim || !$endDelim) { + continue; + } + + if ($nextChar === $beginDelim[0]) { + + // Lookup the start delimiter + for ($i = 1; $i <= strlen($beginDelim); $i++) { + $reader->forward(); + } + + if ($reader->current() === $beginDelim) { + + // Start delimiter found, let's try to find the end delimiter + $nextChar = $reader->forwardChar(); + while ($nextChar !== $reader->getEofMarker()) { + + if ($nextChar === $endDelim[0]) { + + for ($i = 1; $i <= strlen($endDelim); $i++) { + $reader->forward(); + } + + if (substr($reader->current(), -2) === $endDelim) { + $token = new GenericToken(GenericToken::TK_COMMENT, $reader->consume()); + $this->addToken($reader, $token); + + return true; + } + } + + $nextChar = $reader->forwardChar(); + } + + // End of file reached and no end delimiter found, error + throw new ScannerException($reader, "Unterminated block comment"); + + } else { + + // Start delimiter not found, rewind the looked up characters + $reader->rewind(); + return false; + } + + } + + } + + } + + /** + * Detect and consume line comments + * + * @param \LazyGuy\PhpParse\Reader\ReaderInterface $reader + * @return bool + */ + protected function consumeLineComments(ReaderInterface $reader) + { +// $this->debug('consumeLineComments'); + + $nextChar = $reader->currentChar(); + foreach($this->context->getLineCommentDelimiters() as $delimiter) { + + if ($delimiter && $nextChar === $delimiter[0]) { + + for ($i = 1; $i <= strlen($delimiter); $i++) { + $reader->forward(); + } + + if ($reader->current() === $delimiter) { + + // consume to end of line + $char = $reader->currentChar(); + while (!$reader->isEof() && $char !== PHP_EOL) { + $char = $reader->forwardChar(); + } + $token = new GenericToken(GenericToken::TK_COMMENT, $reader->consume()); + $this->addToken($reader, $token); + + return true; + + } else { + + // Rewind the looked up characters + $reader->rewind(); + return false; + } + + } + } + } + + /** + * Detect and consume identifiers + * + * @param \LazyGuy\PhpParse\Reader\ReaderInterface $reader + * @return bool + */ + protected function consumeIdentifiers(ReaderInterface $reader) + { +// $this->debug('consumeIdentifiers'); + + $nextChar = $reader->currentChar(); + + if (preg_match('/[a-zA-Z]/', $nextChar)) { + $nextChar = $reader->forwardChar(); + while (preg_match('/[a-zA-Z0-9_]/', $nextChar)) { + $nextChar = $reader->forwardChar(); + } + $token = new GenericToken(GenericToken::TK_IDENTIFIER, $reader->consume()); + $this->addToken($reader, $token); + return true; + } + } + + /** + * Detect and consume symbols + * + * @param \LazyGuy\PhpParse\Reader\ReaderInterface $reader + * @return bool + */ + protected function consumeSymbols(ReaderInterface $reader) + { +// $this->debug('consumeSymbols'); + + $found = false; + $nextChar = $reader->currentChar(); + while (in_array($nextChar, $this->context->getSymbols())) { + $found = true; + $token = new GenericToken(GenericToken::TK_SYMBOL, $nextChar); + $this->addToken($reader, $token); + + $reader->consume(); + $nextChar = $reader->forwardChar(); + } + + $reader->consume(); + + return $found; + } + +} diff --git a/src/PHPCR/Util/CND/Scanner/GenericToken.php b/src/PHPCR/Util/CND/Scanner/GenericToken.php new file mode 100644 index 00000000..ace5816b --- /dev/null +++ b/src/PHPCR/Util/CND/Scanner/GenericToken.php @@ -0,0 +1,37 @@ +getType()), trim($this->data), $this->line, $this->row); + } + + +} diff --git a/src/PHPCR/Util/CND/Scanner/PhpScanner.php b/src/PHPCR/Util/CND/Scanner/PhpScanner.php new file mode 100644 index 00000000..9469f1eb --- /dev/null +++ b/src/PHPCR/Util/CND/Scanner/PhpScanner.php @@ -0,0 +1,45 @@ +getBuffer(), 0, -1)) as $phpToken) { + + if (is_string($phpToken)) { + + $token = new Token(0, trim($phpToken), $curLine); + + } elseif ($phpToken[0] !== T_WHITESPACE) { + + $line = isset($phpToken[2]) ? $phpToken[2] : 0; + $token = new Token($phpToken[0], trim($phpToken[1]), $line); + $curLine = $line; + + } else { + // Strip whitespaces + continue; + } + + $token = $this->applyFilters($token); + + if ($token) { + $tokens[] = $token; + } + } + + return new TokenQueue($tokens); + } + +} diff --git a/src/PHPCR/Util/CND/Scanner/ScannerInterface.php b/src/PHPCR/Util/CND/Scanner/ScannerInterface.php new file mode 100644 index 00000000..41c065f1 --- /dev/null +++ b/src/PHPCR/Util/CND/Scanner/ScannerInterface.php @@ -0,0 +1,23 @@ +type = $type; + $this->data = $data; + $this->line = $line; + $this->row = $row; + } + + /** + * @return string + */ + public function getData() + { + return $this->data; + } + + /** + * @return int + */ + public function getType() + { + return $this->type; + } + + public function __toString() + { + return sprintf("TOKEN(%s, '%s', %s, %s)", $this->type, trim($this->data), $this->line, $this->row); + } + + /** + * @param int $line + */ + public function setLine($line) + { + $this->line = $line; + } + + /** + * @return int + */ + public function getLine() + { + return $this->line; + } + + /** + * @param int $row + */ + public function setRow($row) + { + $this->row = $row; + } + + /** + * @return int + */ + public function getRow() + { + return $this->row; + } + +} diff --git a/src/PHPCR/Util/CND/Scanner/TokenFilter/NoCommentsFilter.php b/src/PHPCR/Util/CND/Scanner/TokenFilter/NoCommentsFilter.php new file mode 100644 index 00000000..d1ff7346 --- /dev/null +++ b/src/PHPCR/Util/CND/Scanner/TokenFilter/NoCommentsFilter.php @@ -0,0 +1,13 @@ +filters[] = $filter; + } + + /** + * @param Token $token + * @return Token | null + */ + function filter(Token $token) + { + foreach ($this->filters as $filter) { + + $token = $filter->filter($token); + + if (!$token) { + return null; + } + } + + return $token; + } +} diff --git a/src/PHPCR/Util/CND/Scanner/TokenFilter/TokenFilterInterface.php b/src/PHPCR/Util/CND/Scanner/TokenFilter/TokenFilterInterface.php new file mode 100644 index 00000000..dbe100ba --- /dev/null +++ b/src/PHPCR/Util/CND/Scanner/TokenFilter/TokenFilterInterface.php @@ -0,0 +1,15 @@ +type = $tokenType; + } + + /** + * @param Token $token + * @return Token | null + */ + function filter(Token $token) + { + if ($token->getType() === $this->type) { + return null; + } + + return $token; + } +} diff --git a/src/PHPCR/Util/CND/Scanner/TokenQueue.php b/src/PHPCR/Util/CND/Scanner/TokenQueue.php new file mode 100644 index 00000000..a8e70be1 --- /dev/null +++ b/src/PHPCR/Util/CND/Scanner/TokenQueue.php @@ -0,0 +1,74 @@ +tokens = $tokens; + } + + public function add(Token $token) + { + $this->tokens[] = $token; + } + + public function reset() + { + return reset($this->tokens); + } + + public function isEof() + { + return current($this->tokens) === false; + } + + public function peek($offset = 0) + { + if (!$offset) { + return current($this->tokens); + } + + $lookup = key($this->tokens) + $offset; + + if ($lookup >= count($this->tokens)) { + return false; + } + + return $this->tokens[key($this->tokens) + $offset]; + } + + public function get($count = 1) + { + for ($i = 1; $i <= $count; $i++) { + $item = $this->peek(); + $this->next(); + } + + return $item; + } + + public function next() + { + return next($this->tokens); + } + + public function getIterator() + { + return new \ArrayIterator($this->tokens); + } + + public function debug() + { + foreach($this->tokens as $token) + { + echo $token . "\n"; + } + } +} \ No newline at end of file diff --git a/tests/PHPCR/Tests/Util/CND/Fixtures/cnd/example.cnd b/tests/PHPCR/Tests/Util/CND/Fixtures/cnd/example.cnd new file mode 100644 index 00000000..7ac57aed --- /dev/null +++ b/tests/PHPCR/Tests/Util/CND/Fixtures/cnd/example.cnd @@ -0,0 +1,12 @@ +/* An example node type definition */ + +[ns:NodeType] > ns:ParentType1, ns:ParentType2 + orderable mixin + - ex:property (STRING) + = 'default1' , 'default2' + mandatory autocreated protected multiple + VERSION + < 'constraint1', 'constraint2' + + ns:node (ns:reqType1, ns:reqType2) + = ns:defaultType + mandatory autocreated protected VERSION \ No newline at end of file diff --git a/tests/PHPCR/Tests/Util/CND/Fixtures/cnd/example.compact.cnd b/tests/PHPCR/Tests/Util/CND/Fixtures/cnd/example.compact.cnd new file mode 100644 index 00000000..d3af8391 --- /dev/null +++ b/tests/PHPCR/Tests/Util/CND/Fixtures/cnd/example.compact.cnd @@ -0,0 +1,6 @@ + +[ns:NodeType]>ns:ParentType1, ns:ParentType2 o m +-ex:property(STRING)='default1','default2' m a p * VERSION + <'constraint1', 'constraint2' ++ns:node(ns:reqType1,ns:reqType2)=ns:defaultType + m a p VERSION \ No newline at end of file diff --git a/tests/PHPCR/Tests/Util/CND/Fixtures/cnd/example.verbose.cnd b/tests/PHPCR/Tests/Util/CND/Fixtures/cnd/example.verbose.cnd new file mode 100644 index 00000000..834a5a93 --- /dev/null +++ b/tests/PHPCR/Tests/Util/CND/Fixtures/cnd/example.verbose.cnd @@ -0,0 +1,48 @@ +/* An example node type definition */ + +// The namespace declaration + + +// Node type name +[ns:NodeType] + +// Supertypes +> ns:ParentType1, ns:ParentType2 + +// This node type supports orderable child nodes +orderable + +// This is a mixin node type +mixin + +// Nodes of this node type have a property called 'ex:property' of type STRING +- ex:property (STRING) + +// The default values for this +// (multi-value) property are... += 'default1', 'default2' + +// and it is... +mandatory autocreated protected + +// and multi-valued +multiple + +// and has an on-parent-version setting of ... +VERSION + +// The constraint settings are... +< 'constraint1', 'constraint2' + +// Nodes of this node type have a child node called ns:node which must be of +// at least the node types ns:reqType1 and ns:reqType2 ++ ns:node (ns:reqType1, ns:reqType2) + +// and the default primary node type of the child node is... += ns:defaultType + +// This child node is... +mandatory autocreated protected + +// and has an on-parent-version setting of ... +VERSION diff --git a/tests/PHPCR/Tests/Util/CND/Fixtures/cnd/example1.cnd b/tests/PHPCR/Tests/Util/CND/Fixtures/cnd/example1.cnd new file mode 100644 index 00000000..bbb7d497 --- /dev/null +++ b/tests/PHPCR/Tests/Util/CND/Fixtures/cnd/example1.cnd @@ -0,0 +1,11 @@ +<'phpcr'='http://www.doctrine-project.org/projects/phpcr_odm'> + [phpcr:apitest] + mixin + - phpcr:class (STRING) + [phpcr:test] + mixin + - phpcr:prop (STRING) + +<'phpcr'='http://www.doctrine-project.org/projects/phpcr_odm'> +[phpcr:primary_item_test] > ? +- phpcr:content (STRING) diff --git a/tests/PHPCR/Tests/Util/CND/Fixtures/cnd/jackrabbit-builtin-nodetypes.cnd b/tests/PHPCR/Tests/Util/CND/Fixtures/cnd/jackrabbit-builtin-nodetypes.cnd new file mode 100644 index 00000000..628e912a --- /dev/null +++ b/tests/PHPCR/Tests/Util/CND/Fixtures/cnd/jackrabbit-builtin-nodetypes.cnd @@ -0,0 +1,629 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + + + + + +//------------------------------------------------------------------------------ +// B A S E T Y P E +//------------------------------------------------------------------------------ + +/** + * nt:base is an abstract primary node type that is the base type for all other + * primary node types. It is the only primary node type without supertypes. + * + * @since 1.0 + */ +[nt:base] + abstract + - jcr:primaryType (NAME) mandatory autocreated protected COMPUTE + - jcr:mixinTypes (NAME) protected multiple COMPUTE + +//------------------------------------------------------------------------------ +// S T A N D A R D A P P L I C A T I O N N O D E T Y P E S +//------------------------------------------------------------------------------ + +/** + * This abstract node type serves as the supertype of nt:file and nt:folder. + * @since 1.0 + */ +[nt:hierarchyNode] > mix:created + abstract + +/** + * Nodes of this node type may be used to represent files. This node type inherits + * the item definitions of nt:hierarchyNode and requires a single child node called + * jcr:content. The jcr:content node is used to hold the actual content of the + * file. This child node is mandatory, but not auto-created. Its node type will be + * application-dependent and therefore it must be added by the user. A common + * approach is to make the jcr:content a node of type nt:resource. The + * jcr:content child node is also designated as the primary child item of its parent. + * + * @since 1.0 + */ +[nt:file] > nt:hierarchyNode + primaryitem jcr:content + + jcr:content (nt:base) mandatory + +/** + * The nt:linkedFile node type is similar to nt:file, except that the content + * node is not stored directly as a child node, but rather is specified by a + * REFERENCE property. This allows the content node to reside anywhere in the + * workspace and to be referenced by multiple nt:linkedFile nodes. The content + * node must be referenceable. + * + * @since 1.0 + */ +[nt:linkedFile] > nt:hierarchyNode + primaryitem jcr:content + - jcr:content (REFERENCE) mandatory + +/** + * Nodes of this type may be used to represent folders or directories. This node + * type inherits the item definitions of nt:hierarchyNode and adds the ability + * to have any number of other nt:hierarchyNode child nodes with any names. + * This means, in particular, that it can have child nodes of types nt:folder, + * nt:file or nt:linkedFile. + * + * @since 1.0 + */ +[nt:folder] > nt:hierarchyNode + + * (nt:hierarchyNode) VERSION + +/** + * This node type may be used to represent the content of a file. In particular, + * the jcr:content subnode of an nt:file node will often be an nt:resource. + * + * @since 1.0 + */ +[nt:resource] > mix:mimeType, mix:lastModified, mix:referenceable + primaryitem jcr:data + - jcr:data (BINARY) mandatory + +/** + * This mixin node type can be used to add standardized title and description + * properties to a node. + * + * Note that the protected attributes suggested by JSR283 are omitted in this variant. + * @since 2.0 + */ +[mix:title] + mixin + - jcr:title (STRING) + - jcr:description (STRING) + +/** + * This mixin node type can be used to add standardized creation information + * properties to a node. Since the properties are protected, their values are + * controlled by the repository, which should set them appropriately upon the + * initial persist of a node with this mixin type. In cases where this mixin is + * added to an already existing node the semantics of these properties are + * implementation specific. Note that jackrabbit initializes the properties to + * the current date and user in this case. + * + * + * @since 2.0 + */ +[mix:created] + mixin + - jcr:created (DATE) autocreated protected + - jcr:createdBy (STRING) autocreated protected + +/** + * This mixin node type can be used to provide standardized modification + * information properties to a node. + * + * The following is not yet implemented in Jackrabbit: + * "Since the properties are protected, their values + * are controlled by the repository, which should set them appropriately upon a + * significant modification of the subgraph of a node with this mixin. What + * constitutes a significant modification will depend on the semantics of the various + * parts of a node's subgraph and is implementation-dependent" + * + * Jackrabbit initializes the properties to the current date and user in the + * case they are newly created. + * + * Note that the protected attributes suggested by JSR283 are omitted in this variant. + * @since 2.0 + */ +[mix:lastModified] + mixin + - jcr:lastModified (DATE) autocreated + - jcr:lastModifiedBy (STRING) autocreated + +/** + * This mixin node type can be used to provide a standardized property that + * specifies the natural language in which the content of a node is expressed. + * The value of the jcr:language property should be a language code as defined + * in RFC 46465. Examples include "en" (English), "en-US" (United States English), + * "de" (German) and "de-CH" (Swiss German). + * + * Note that the protected attributes suggested by JSR283 are omitted in this variant. + * @since 2.0 + */ +[mix:language] + mixin + - jcr:language (STRING) + +/** + * This mixin node type can be used to provide standardized mimetype and + * encoding properties to a node. If a node of this type has a primary item + * that is a single-value BINARY property then jcr:mimeType property indicates + * the media type applicable to the contents of that property and, if that + * media type is one to which a text encoding applies, the jcr:encoding property + * indicates the character set used. If a node of this type does not meet the + * above precondition then the interpretation of the jcr:mimeType and + * jcr:encoding properties is implementation-dependent. + * + * Note that the protected attributes suggested by JSR283 are omitted in this variant. + * @since 2.0 + */ +[mix:mimeType] + mixin + - jcr:mimeType (STRING) + - jcr:encoding (STRING) + +/** + * This node type may be used to represent the location of a JCR item not just + * within a particular workspace but within the space of all workspaces in all JCR + * repositories. + * + * @prop jcr:protocol Stores a string holding the protocol through which the + * target repository is to be accessed. + * @prop jcr:host Stores a string holding the host name of the system + * through which the repository is to be accessed. + * @prop jcr:port Stores a string holding the port number through which the + * target repository is to be accessed. + * + * The semantics of these properties above are left undefined but are assumed to be + * known by the application. The names and descriptions of the properties are not + * normative and the repository does not enforce any particular semantic + * interpretation on them. + * + * @prop jcr:repository Stores a string holding the name of the target repository. + * @prop jcr:workspace Stores the name of a workspace. + * @prop jcr:path Stores a path to an item. + * @prop jcr:id Stores a weak reference to a node. + * + * In most cases either the jcr:path or the jcr:id property would be used, but + * not both, since they may point to different nodes. If any of the properties + * other than jcr:path and jcr:id are missing, the address can be interpreted as + * relative to the current container at the same level as the missing specifier. + * For example, if no repository is specified, then the address can be + * interpreted as referring to a workspace and path or id within the current + * repository. + * + * @since 2.0 + */ +[nt:address] + - jcr:protocol (STRING) + - jcr:host (STRING) + - jcr:port (STRING) + - jcr:repository (STRING) + - jcr:workspace (STRING) + - jcr:path (PATH) + - jcr:id (WEAKREFERENCE) + +/** + * The mix:etag mixin type defines a standardized identity validator for BINARY + * properties similar to the entity tags used in HTTP/1.1 + * + * A jcr:etag property is an opaque string whose syntax is identical to that + * defined for entity tags in HTTP/1.1. Semantically, the jcr:etag is comparable + * to the HTTP/1.1 strong entity tag. + * + * On creation of a mix:etag node N, or assignment of mix:etag to N, the + * repository must create a jcr:etag property with an implementation determined + * value. + * + * The value of the jcr:etag property must change immediately on persist of any + * of the following changes to N: + * - A BINARY property is added t o N. + * - A BINARY property is removed from N. + * - The value of an existing BINARY property of N changes. + * + * @since 2.0 + */ +[mix:etag] + mixin + - jcr:etag (STRING) protected autocreated + +//------------------------------------------------------------------------------ +// U N S T R U C T U R E D C O N T E N T +//------------------------------------------------------------------------------ + +/** + * This node type is used to store unstructured content. It allows any number of + * child nodes or properties with any names. It also allows multiple nodes having + * the same name as well as both multi-value and single-value properties with any + * names. This node type also supports client-orderable child nodes. + * + * @since 1.0 + */ +[nt:unstructured] + orderable + - * (UNDEFINED) multiple + - * (UNDEFINED) + + * (nt:base) = nt:unstructured sns VERSION + +//------------------------------------------------------------------------------ +// R E F E R E N C E A B L E +//------------------------------------------------------------------------------ + +/** + * This node type adds an auto-created, mandatory, protected STRING property to + * the node, called jcr:uuid, which exposes the identifier of the node. + * Note that the term "UUID" is used for backward compatibility with JCR 1.0 + * and does not necessarily imply the use of the UUID syntax, or global uniqueness. + * The identifier of a referenceable node must be a referenceable identifier. + * Referenceable identifiers must fulfill a number of constraints beyond the + * minimum required of standard identifiers (see 3.8.3 Referenceable Identifiers). + * A reference property is a property that holds the referenceable identifier of a + * referenceable node and therefore serves as a pointer to that node. The two types + * of reference properties, REFERENCE and WEAKREFERENCE differ in that the former + * enforces referential integrity while the latter does not. + * + * @since 1.0 + */ +[mix:referenceable] + mixin + - jcr:uuid (STRING) mandatory autocreated protected INITIALIZE + +//------------------------------------------------------------------------------ +// L O C K I N G +//------------------------------------------------------------------------------ + +/** + * @since 1.0 + */ +[mix:lockable] + mixin + - jcr:lockOwner (STRING) protected IGNORE + - jcr:lockIsDeep (BOOLEAN) protected IGNORE + +//------------------------------------------------------------------------------ +// S H A R E A B L E N O D E S +//------------------------------------------------------------------------------ + +/** + * @since 2.0 + */ +[mix:shareable] > mix:referenceable + mixin + +//------------------------------------------------------------------------------ +// V E R S I O N I N G +//------------------------------------------------------------------------------ + +/** + * @since 2.0 + */ +[mix:simpleVersionable] + mixin + - jcr:isCheckedOut (BOOLEAN) = 'true' mandatory autocreated protected IGNORE + +/** + * @since 1.0 + */ +[mix:versionable] > mix:simpleVersionable, mix:referenceable + mixin + - jcr:versionHistory (REFERENCE) mandatory protected IGNORE < 'nt:versionHistory' + - jcr:baseVersion (REFERENCE) mandatory protected IGNORE < 'nt:version' + - jcr:predecessors (REFERENCE) mandatory protected multiple IGNORE < 'nt:version' + - jcr:mergeFailed (REFERENCE) protected multiple ABORT < 'nt:version' + /** @since 2.0 */ + - jcr:activity (REFERENCE) protected < 'nt:activity' + /** @since 2.0 */ + - jcr:configuration (REFERENCE) protected IGNORE < 'nt:configuration' + +/** + * @since 1.0 + */ +[nt:versionHistory] > mix:referenceable + - jcr:versionableUuid (STRING) mandatory autocreated protected ABORT + /** @since 2.0 */ + - jcr:copiedFrom (WEAKREFERENCE) protected ABORT < 'nt:version' + + jcr:rootVersion (nt:version) = nt:version mandatory autocreated protected ABORT + + jcr:versionLabels (nt:versionLabels) = nt:versionLabels mandatory autocreated protected ABORT + + * (nt:version) = nt:version protected ABORT + +/** + * @since 1.0 + */ +[nt:versionLabels] + - * (REFERENCE) protected ABORT < 'nt:version' + +/** + * @since 1.0 + */ +[nt:version] > mix:referenceable + - jcr:created (DATE) mandatory autocreated protected ABORT + - jcr:predecessors (REFERENCE) protected multiple ABORT < 'nt:version' + - jcr:successors (REFERENCE) protected multiple ABORT < 'nt:version' + /** @since 2.0 */ + - jcr:activity (REFERENCE) protected ABORT < 'nt:activity' + + jcr:frozenNode (nt:frozenNode) protected ABORT + +/** + * @since 1.0 + */ +[nt:frozenNode] > mix:referenceable + orderable + - jcr:frozenPrimaryType (NAME) mandatory autocreated protected ABORT + - jcr:frozenMixinTypes (NAME) protected multiple ABORT + - jcr:frozenUuid (STRING) mandatory autocreated protected ABORT + - * (UNDEFINED) protected ABORT + - * (UNDEFINED) protected multiple ABORT + + * (nt:base) protected sns ABORT + +/** + * @since 1.0 + */ +[nt:versionedChild] + - jcr:childVersionHistory (REFERENCE) mandatory autocreated protected ABORT < 'nt:versionHistory' + +/** + * @since 2.0 + */ +[nt:activity] > mix:referenceable + - jcr:activityTitle (STRING) mandatory autocreated protected + +/** + * @since 2.0 + */ +[nt:configuration] > mix:versionable + - jcr:root (REFERENCE) mandatory autocreated protected + +//------------------------------------------------------------------------------ +// N O D E T Y P E S +//------------------------------------------------------------------------------ + +/** + * This node type is used to store a node type definition. Property and child node + * definitions within the node type definition are stored as same-name sibling nodes + * of type nt:propertyDefinition and nt:childNodeDefinition. + * + * @since 1.0 + */ +[nt:nodeType] + - jcr:nodeTypeName (NAME) protected mandatory + - jcr:supertypes (NAME) protected multiple + - jcr:isAbstract (BOOLEAN) protected mandatory + - jcr:isQueryable (BOOLEAN) protected mandatory + - jcr:isMixin (BOOLEAN) protected mandatory + - jcr:hasOrderableChildNodes (BOOLEAN) protected mandatory + - jcr:primaryItemName (NAME) protected + + jcr:propertyDefinition (nt:propertyDefinition) = nt:propertyDefinition protected sns + + jcr:childNodeDefinition (nt:childNodeDefinition) = nt:childNodeDefinition protected sns + +/** + * This node type used to store a property definition within a node type definition, + * which itself is stored as an nt:nodeType node. + * + * @since 1.0 + */ +[nt:propertyDefinition] + - jcr:name (NAME) protected + - jcr:autoCreated (BOOLEAN) protected mandatory + - jcr:mandatory (BOOLEAN) protected mandatory + - jcr:onParentVersion (STRING) protected mandatory + < 'COPY', 'VERSION', 'INITIALIZE', 'COMPUTE', 'IGNORE', 'ABORT' + - jcr:protected (BOOLEAN) protected mandatory + - jcr:requiredType (STRING) protected mandatory + < 'STRING', 'URI', 'BINARY', 'LONG', 'DOUBLE', + 'DECIMAL', 'BOOLEAN', 'DATE', 'NAME', 'PATH', + 'REFERENCE', 'WEAKREFERENCE', 'UNDEFINED' + - jcr:valueConstraints (STRING) protected multiple + - jcr:defaultValues (UNDEFINED) protected multiple + - jcr:multiple (BOOLEAN) protected mandatory + - jcr:availableQueryOperators (NAME) protected mandatory multiple + - jcr:isFullTextSearchable (BOOLEAN) protected mandatory + - jcr:isQueryOrderable (BOOLEAN) protected mandatory + +/** + * This node type used to store a child node definition within a node type definition, + * which itself is stored as an nt:nodeType node. + * + * @since 1.0 + */ +[nt:childNodeDefinition] + - jcr:name (NAME) protected + - jcr:autoCreated (BOOLEAN) protected mandatory + - jcr:mandatory (BOOLEAN) protected mandatory + - jcr:onParentVersion (STRING) protected mandatory + < 'COPY', 'VERSION', 'INITIALIZE', 'COMPUTE', 'IGNORE', 'ABORT' + - jcr:protected (BOOLEAN) protected mandatory + - jcr:requiredPrimaryTypes (NAME) = 'nt:base' protected mandatory multiple + - jcr:defaultPrimaryType (NAME) protected + - jcr:sameNameSiblings (BOOLEAN) protected mandatory + +//------------------------------------------------------------------------------ +// Q U E R Y +//------------------------------------------------------------------------------ + +/** + * @since 1.0 + */ +[nt:query] + - jcr:statement (STRING) + - jcr:language (STRING) + +//------------------------------------------------------------------------------ +// L I F E C Y C L E M A N A G E M E N T +//------------------------------------------------------------------------------ + +/** + * Only nodes with mixin node type mix:lifecycle may participate in a lifecycle. + * + * @peop jcr:lifecyclePolicy + * This property is a reference to another node that contains + * lifecycle policy information. The definition of the referenced + * node is not specified. + * @prop jcr:currentLifecycleState + * This property is a string identifying the current lifecycle + * state of this node. The format of this string is not specified. + * + * @since 2.0 + */ +[mix:lifecycle] + mixin + - jcr:lifecyclePolicy (REFERENCE) protected INITIALIZE + - jcr:currentLifecycleState (STRING) protected INITIALIZE + +//------------------------------------------------------------------------------ +// J A C K R A B B I T I N T E R N A L S +//------------------------------------------------------------------------------ + +[rep:root] > nt:unstructured + + jcr:system (rep:system) = rep:system mandatory IGNORE + +[rep:system] + orderable + + jcr:versionStorage (rep:versionStorage) = rep:versionStorage mandatory protected ABORT + + jcr:nodeTypes (rep:nodeTypes) = rep:nodeTypes mandatory protected ABORT + // @since 2.0 + + jcr:activities (rep:Activities) = rep:Activities mandatory protected ABORT + // @since 2.0 + + jcr:configurations (rep:Configurations) = rep:Configurations protected ABORT + + * (nt:base) = nt:base IGNORE + + +/** + * Node Types (virtual) storage + */ +[rep:nodeTypes] + + * (nt:nodeType) = nt:nodeType protected ABORT + +/** + * Version storage + */ +[rep:versionStorage] + + * (nt:versionHistory) = nt:versionHistory protected ABORT + + * (rep:versionStorage) = rep:versionStorage protected ABORT + +/** + * Activities storage + * @since 2.0 + */ +[rep:Activities] + + * (nt:activity) = nt:activity protected ABORT + + * (rep:Activities) = rep:Activities protected ABORT + +/** + * the intermediate nodes for the configurations storage. + * Note: since the versionable node points to the configuration and vice versa, + * a configuration could never be removed because no such API exists. therefore + * the child node definitions are not protected. + * @since 2.0 + */ +[rep:Configurations] + + * (nt:configuration) = nt:configuration ABORT + + * (rep:Configurations) = rep:Configurations ABORT + +/** + * mixin that provides a multi value property for referencing versions. + * This is used for recording the baseline versions in the nt:configuration + * node, and to setup a bidirectional relationship between activities and + * the respective versions + * @since 2.0 + */ +[rep:VersionReference] mix + - rep:versions (REFERENCE) protected multiple + +// ----------------------------------------------------------------------------- +// J A C K R A B B I T S E C U R I T Y +// ----------------------------------------------------------------------------- + +[rep:AccessControllable] + mixin + + rep:policy (rep:Policy) protected IGNORE + +[rep:Policy] + abstract + +[rep:ACL] > rep:Policy + orderable + + * (rep:ACE) = rep:GrantACE protected IGNORE + +[rep:ACE] + - rep:principalName (STRING) protected mandatory + - rep:privileges (NAME) protected mandatory multiple + - rep:nodePath (PATH) protected + - rep:glob (STRING) protected + - * (UNDEFINED) protected + +[rep:GrantACE] > rep:ACE + +[rep:DenyACE] > rep:ACE + +// ----------------------------------------------------------------------------- +// Principal based AC +// ----------------------------------------------------------------------------- + +[rep:AccessControl] + + * (rep:AccessControl) protected IGNORE + + * (rep:PrincipalAccessControl) protected IGNORE + +[rep:PrincipalAccessControl] > rep:AccessControl + + rep:policy (rep:Policy) protected IGNORE + +// ----------------------------------------------------------------------------- +// User Management +// ----------------------------------------------------------------------------- + +[rep:Authorizable] > mix:referenceable, nt:hierarchyNode + abstract + + * (nt:base) = nt:unstructured VERSION + - rep:principalName (STRING) protected mandatory + - * (UNDEFINED) + - * (UNDEFINED) multiple + +[rep:Impersonatable] + mixin + - rep:impersonators (STRING) protected multiple + +[rep:User] > rep:Authorizable, rep:Impersonatable + - rep:password (STRING) protected mandatory + - rep:disabled (STRING) protected + +/* --- A node type cannot be multiple !!! --- +[rep:Group] > rep:Authorizable + + rep:members (rep:Members) = rep:Members multiple protected VERSION + - rep:members (WEAKREFERENCE) protected multiple < 'rep:Authorizable' +*/ + +[rep:AuthorizableFolder] > nt:hierarchyNode + + * (rep:Authorizable) = rep:User VERSION + + * (rep:AuthorizableFolder) = rep:AuthorizableFolder VERSION + +/* --- A node type cannot be multiple !!! --- +[rep:Members] + orderable + + * (rep:Members) = rep:Members protected multiple + - * (WEAKREFERENCE) protected < 'rep:Authorizable' +*/ + +// ----------------------------------------------------------------------------- +// J A C K R A B B I T R E T E N T I O N M A N A G E M E N T +// ----------------------------------------------------------------------------- + +[rep:RetentionManageable] + mixin? ord + - rep:hold (UNDEFINED) protected multiple IGNORE + - rep:retentionPolicy (UNDEFINED) protected IGNORE diff --git a/tests/PHPCR/Tests/Util/CND/Fixtures/cnd/no-stop-at-eof.cnd b/tests/PHPCR/Tests/Util/CND/Fixtures/cnd/no-stop-at-eof.cnd new file mode 100644 index 00000000..ad6cf80d --- /dev/null +++ b/tests/PHPCR/Tests/Util/CND/Fixtures/cnd/no-stop-at-eof.cnd @@ -0,0 +1,5 @@ +<'phpcr'='http://www.doctrine-project.org/projects/phpcr_odm'> +[phpcr:primary_item_test] > ? +- phpcr:content (STRING) += ? +< ? diff --git a/tests/PHPCR/Tests/Util/CND/Fixtures/files/TestFile.php b/tests/PHPCR/Tests/Util/CND/Fixtures/files/TestFile.php new file mode 100644 index 00000000..d83cd493 --- /dev/null +++ b/tests/PHPCR/Tests/Util/CND/Fixtures/files/TestFile.php @@ -0,0 +1,18 @@ +scan($reader); + $parser = new CndParser($queue); + $root = $parser->parse(); + + $generator = new NodeTypeGenerator($root); + $generator->generate(); + + $this->assertTrue(true); // To avoid the test being marked incomplete + // TODO: write some real tests + } +} diff --git a/tests/PHPCR/Tests/Util/CND/Parser/CndParserTest.php b/tests/PHPCR/Tests/Util/CND/Parser/CndParserTest.php new file mode 100644 index 00000000..b799c86b --- /dev/null +++ b/tests/PHPCR/Tests/Util/CND/Parser/CndParserTest.php @@ -0,0 +1,146 @@ +addChild(new SyntaxTreeNode('nsMapping', array('prefix' => 'ns', 'uri' => 'http://namespace.com/ns'))); + $root->addChild($node); + + $types = new SyntaxTreeNode('nodeTypes'); + + $attrs = new SyntaxTreeNode('propertyTypeAttributes'); + $attrs->addChild(new SyntaxTreeNode('mandatory')); + $attrs->addChild(new SyntaxTreeNode('autocreated')); + $attrs->addChild(new SyntaxTreeNode('protected')); + $attrs->addChild(new SyntaxTreeNode('multiple')); + $attrs->addChild(new SyntaxTreeNode('VERSION')); + + $props = new SyntaxTreeNode('propertyDefs'); + $prop = new SyntaxTreeNode('propertyDef'); + $prop->addChild(new SyntaxTreeNode('propertyName', array('value' => 'ex:property'))); + $prop->addChild(new SyntaxTreeNode('propertyType', array('value' => 'STRING'))); + $prop->addChild(new SyntaxTreeNode('defaultValues', array('value' => array('default1', 'default2')))); + $prop->addChild($attrs); + $prop->addChild(new SyntaxTreeNode('valueConstraints', array('value' => array('constraint1', 'constraint2')))); + $props->addChild($prop); + + $attrs = new SyntaxTreeNode('nodeAttributes'); + $attrs->addChild(new SyntaxTreeNode('mandatory')); + $attrs->addChild(new SyntaxTreeNode('autocreated')); + $attrs->addChild(new SyntaxTreeNode('protected')); + $attrs->addChild(new SyntaxTreeNode('VERSION')); + + $nodes = new SyntaxTreeNode('childNodeDefs'); + $node = new SyntaxTreeNode('childNodeDef'); + $node->addChild(new SyntaxTreeNode('nodeName', array('value' => 'ns:node'))); + $node->addChild(new SyntaxTreeNode('requiredTypes', array('value' => array('ns:reqType1', 'ns:reqType2')))); + $node->addChild(new SyntaxTreeNode('defaultType', array('value' => 'ns:defaultType'))); + $node->addChild($attrs); + $nodes->addChild($node); + + $attrs = new SyntaxTreeNode('nodeTypeAttributes'); + $attrs->addChild(new SyntaxTreeNode('orderable')); + $attrs->addChild(new SyntaxTreeNode('mixin')); + + $nodeType = new SyntaxTreeNode('nodeTypeDef'); + $nodeType->addChild(new SyntaxTreeNode('nodeTypeName', array('value' => 'ns:NodeType'))); + $nodeType->addChild(new SyntaxTreeNode('supertypes', array('value' => array('ns:ParentType1', 'ns:ParentType2')))); + $nodeType->addChild($attrs); + $nodeType->addChild($props); + $nodeType->addChild($nodes); + + $types->addChild($nodeType); + $root->addChild($types); + + $this->expectedExampleTree = $root; + } + + public function testParseNormal() + { + $this->assertParsedFile(__DIR__ . '/../Fixtures/cnd/example.cnd', $this->expectedExampleTree); + } + + public function testParseCompact() + { + $this->assertParsedFile(__DIR__ . '/../Fixtures/cnd/example.compact.cnd', $this->expectedExampleTree); + } + + public function testParseVerbose() + { + $this->assertParsedFile(__DIR__ . '/../Fixtures/cnd/example.verbose.cnd', $this->expectedExampleTree); + } + + public function testParseExample1() + { + // Assert there are no exceptions + $this->parseFile(__DIR__ . '/../Fixtures/cnd/example1.cnd'); + + $this->assertTrue(true); // To avoid the test being marked incomplete + // TODO: write some real tests + + } + + public function testParseJackrabbitBuiltin() + { + $this->parseFile(__DIR__ . '/../Fixtures/cnd/jackrabbit-builtin-nodetypes.cnd'); + + $this->assertTrue(true); // To avoid the test being marked incomplete + // TODO: write some real tests + } + + /** + * Test the case where the parser did not parse correctly + * the default values at the end of the parsed file. + * + * Assert no exception is thrown + * + * @return void + */ + public function testNoStopAtEofError() + { + $this->parseFile(__DIR__ . '/../Fixtures/cnd/no-stop-at-eof.cnd'); + + $this->assertTrue(true); // To avoid the test being marked incomplete + // TODO: write some real tests + } + + // TODO: test variant required type for child node def + // TODO: test parseCndString without a string throws a parser error + // TODO: test variant primary type + // TODO: test queryops + + protected function assertParsedFile($file, $expectedCnd) + { + $actualCnd = $this->parseFile($file); + + $this->assertEquals($expectedCnd->dump(), $actualCnd->dump()); + + $this->assertEquals($expectedCnd, $actualCnd); + } + + protected function parseFile($file) + { + $reader = new FileReader($file); + $scanner = new GenericScanner(new Context\DefaultScannerContextWithoutSpacesAndComments()); + $queue = $scanner->scan($reader); + + //define('DEBUG', true); + + $parser = new CndParser($queue); + return $parser->parse(); + } + +} diff --git a/tests/PHPCR/Tests/Util/CND/Parser/SyntaxTreeNodeTest.php b/tests/PHPCR/Tests/Util/CND/Parser/SyntaxTreeNodeTest.php new file mode 100644 index 00000000..9c96bc07 --- /dev/null +++ b/tests/PHPCR/Tests/Util/CND/Parser/SyntaxTreeNodeTest.php @@ -0,0 +1,31 @@ +setProperty('root-prop-1', 'some value'); + $root->setProperty('root-prop-2', 'some other value'); + + $node1 = new SyntaxTreeNode('child'); + $node1->setProperty('child-prop-1', 'foo'); + $node1->setProperty('child-prop-2', 'bar'); + + $node2 = new SyntaxTreeNode('child'); + $node2->setProperty('child-prop-3', 'foo'); + $node2->setProperty('child-prop-4', 'bar'); + + $root->addChild($node1); + $root->addChild($node2); + + //var_dump($root); + + $this->assertTrue(true); // To avoid the test being marked incomplete + // TODO: write some real tests + } +} diff --git a/tests/PHPCR/Tests/Util/CND/Reader/BufferReaderTest.php b/tests/PHPCR/Tests/Util/CND/Reader/BufferReaderTest.php new file mode 100644 index 00000000..3096a0da --- /dev/null +++ b/tests/PHPCR/Tests/Util/CND/Reader/BufferReaderTest.php @@ -0,0 +1,105 @@ +assertInstanceOf('\PHPCR\Util\CND\Reader\BufferReader', $reader); + $this->assertAttributeEquals($buffer . $reader->getEofMarker(), 'buffer', $reader); + $this->assertAttributeEquals(0, 'startPos', $reader); + $this->assertAttributeEquals(0, 'forwardPos', $reader); + + $this->assertEquals(1, $reader->getCurrentLine()); + $this->assertEquals(1, $reader->getCurrentColumn()); + + $this->assertEquals('', $reader->current()); + $this->assertEquals('S', $reader->forward()); + $this->assertEquals('So', $reader->forward()); + + $reader->rewind(); + + $this->assertEquals(1, $reader->getCurrentLine()); + $this->assertEquals(1, $reader->getCurrentColumn()); + + $this->assertEquals('', $reader->current()); + $this->assertEquals('S', $reader->forward()); + $this->assertEquals('So', $reader->forward()); + $this->assertEquals('Som', $reader->forward()); + $this->assertEquals('Some', $reader->forward()); + $this->assertEquals('Some', $reader->consume()); + + $this->assertEquals(5, $reader->getCurrentColumn()); + + $this->assertEquals(' ', $reader->forward()); + $this->assertEquals(' r', $reader->forward()); + $reader->rewind(); + $this->assertEquals(' ', $reader->forward()); + $reader->unget(); + $this->assertEquals(' ', $reader->forward()); + $reader->unget(); + $reader->unget(); + $this->assertEquals(' ', $reader->forward()); + $this->assertEquals(' ', $reader->consume()); + + $this->assertEquals(6, $reader->getCurrentColumn()); + + $this->assertEquals('r', $reader->forward()); + $this->assertEquals('ra', $reader->forward()); + $this->assertEquals('ran', $reader->forward()); + $this->assertEquals('rand', $reader->forward()); + $this->assertEquals('rando', $reader->forward()); + $this->assertEquals('random', $reader->forward()); + $this->assertEquals('random', $reader->consume()); + + $this->assertEquals(12, $reader->getCurrentColumn()); + + $this->assertEquals(PHP_EOL, $reader->forward()); + $this->assertEquals(PHP_EOL, $reader->consume()); + + $this->assertEquals(2, $reader->getCurrentLine()); + $this->assertEquals(1, $reader->getCurrentColumn()); + + $this->assertEquals('s', $reader->forward()); + $this->assertEquals('st', $reader->forward()); + $this->assertEquals('str', $reader->forward()); + $this->assertEquals('stri', $reader->forward()); + $this->assertEquals('strin', $reader->forward()); + $this->assertEquals('string', $reader->forward()); + $this->assertEquals('string', $reader->consume()); + + $this->assertEquals(2, $reader->getCurrentLine()); + $this->assertEquals(7, $reader->getCurrentColumn()); + + $this->assertEquals($reader->getEofMarker(), $reader->forward()); + $this->assertEquals($reader->getEofMarker(), $reader->consume()); + $this->assertEquals($reader->getEofMarker(), $reader->forward()); + } + + public function test__constructEmptyString() + { + $reader = new BufferReader(''); + + $this->assertInstanceOf('\PHPCR\Util\CND\Reader\BufferReader', $reader); + $this->assertAttributeEquals($reader->getEofMarker(), 'buffer', $reader); + $this->assertAttributeEquals(0, 'startPos', $reader); + $this->assertAttributeEquals(0, 'forwardPos', $reader); + + $this->assertEquals(1, $reader->getCurrentLine()); + $this->assertEquals(1, $reader->getCurrentColumn()); + + $this->assertEquals('', $reader->current()); + $this->assertEquals($reader->getEofMarker(), $reader->forward()); + $this->assertEquals($reader->getEofMarker(), $reader->forward()); + $reader->rewind(); + $this->assertEquals($reader->getEofMarker(), $reader->forward()); + $this->assertEquals($reader->getEofMarker(), $reader->consume()); + } + +} diff --git a/tests/PHPCR/Tests/Util/CND/Reader/FileReaderTest.php b/tests/PHPCR/Tests/Util/CND/Reader/FileReaderTest.php new file mode 100644 index 00000000..fbf2102f --- /dev/null +++ b/tests/PHPCR/Tests/Util/CND/Reader/FileReaderTest.php @@ -0,0 +1,96 @@ +filename = __DIR__ . '/../Fixtures/files/TestFile.txt'; + $this->reader = new FileReader($this->filename); + + $this->lines = array( + 'This is a test file...', + '', + '...containing dummy content.', + '' + ); + + $this->content = file_get_contents($this->filename); + $this->chars = array_merge( + preg_split('//', $this->lines[0], -1, PREG_SPLIT_NO_EMPTY), + array("\n", "\n"), + preg_split('//', $this->lines[2], -1, PREG_SPLIT_NO_EMPTY), + array("\n", "\n") + ); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function test__construct_fileNotFound() + { + $reader = new FileReader('unexisting_file'); + } + + public function testGetFileName() + { + $this->assertEquals($this->filename, $this->reader->getFileName()); + } + + public function testGetBuffer() + { + $this->assertEquals(file_get_contents($this->filename) . $this->reader->getEofMarker(), $this->reader->getBuffer()); + } + + public function testGetNextChar() + { + $curLine = 1; + $curCol = 1; + + for ($i = 0; $i < count($this->chars); $i++) { + + $peek = $this->reader->currentChar(); + + if ($peek === $this->reader->getEofMarker()) { + $this->assertEquals(count($this->chars) - 1, $i); + break; + } + + //var_dump('Expected:' . $this->chars[$i] . ', found: ' . $peek); + + $this->assertEquals($curLine, $this->reader->getCurrentLine()); + $this->assertEquals($curCol, $this->reader->getCurrentColumn()); + + // Assert isEof is false before the end of the file + $this->assertFalse($this->reader->isEof()); + + // Assert isEol is true at end of the lines + if ($peek === "\n") { + $curLine++; + $curCol = 1; + } else { + $curCol++; + } + + // Assert the next character is the expected one + $this->assertEquals($peek, $this->chars[$i]); + $this->assertEquals( + $this->chars[$i], + $peek, + sprintf("Character mismatch at position %s, expected '%s', found '%s'", $i, $this->chars[$i], $peek) + ); + + $this->reader->forward(); + $this->reader->consume(); + } + + // Check it's the end of the file + $this->assertEquals($this->reader->getEofMarker(), $this->reader->currentChar()); + $this->assertTrue($this->reader->isEof()); + $this->assertEquals(false, $this->reader->forwardChar()); + } + +} diff --git a/tests/PHPCR/Tests/Util/CND/Scanner/GenericScannerTest.php b/tests/PHPCR/Tests/Util/CND/Scanner/GenericScannerTest.php new file mode 100644 index 00000000..af73faab --- /dev/null +++ b/tests/PHPCR/Tests/Util/CND/Scanner/GenericScannerTest.php @@ -0,0 +1,172 @@ +expectedTokensNoEmptyToken = array(); + foreach($this->expectedTokens as $token) { + if ($token[0] !== Token::TK_NEWLINE && $token[0] !== Token::TK_WHITESPACE) { + $this->expectedTokensNoEmptyToken[] = $token; + } + } + } + + public function testScan() + { + $reader = new FileReader(__DIR__ . '/../Fixtures/files/TestFile.php'); + + // Test the raw file with newlines and whitespaces + $scanner = new GenericScanner(new DefaultScannerContext()); + $queue = $scanner->scan($reader); + $this->assertTokens($this->expectedTokens, $queue); + } + + public function testFilteredScan() + { + $reader = new FileReader(__DIR__ . '/../Fixtures/files/TestFile.php'); + + // Test the raw file with newlines and whitespaces + $context = new DefaultScannerContext(); + $context->addTokenFilter(new TokenFilter\NoNewlinesFilter()); + $context->addTokenFilter(new TokenFilter\NoWhitespacesFilter()); + $scanner = new GenericScanner($context); + + $queue = $scanner->scan($reader); + $this->assertTokens($this->expectedTokensNoEmptyToken, $queue); + } + + protected function assertTokens($tokens, TokenQueue $queue) + { + $queue->reset(); + + $it = new \ArrayIterator($tokens); + + $token = $queue->peek(); + + while ($it->valid()) { + + $expectedToken = $it->current(); + + $this->assertFalse($queue->isEof(), 'There is no more tokens, expected = ' . $expectedToken[1]); + + //var_dump("Expected: {$expectedToken[1]}, Found: {$token->getData()}"); + + $this->assertToken($expectedToken[0], $expectedToken[1], $token); + + $token = $queue->next(); + $it->next(); + } + + $this->assertTrue($queue->isEof(), 'There are more unexpected tokens.'); + } + + protected function assertToken($type, $data, Token $token) + { + //var_dump($token); + $this->assertEquals($type, $token->getType(), + sprintf('Expected token [%s, %s], found [%s, %s]', Token::getTypeName($type), $data, Token::getTypeName($token->getType()), $token->getData())); + + $this->assertEquals($data, trim($token->getData()), + sprintf('Expected token [%s, %s], found [%s, %s]', Token::getTypeName($type), $data, Token::getTypeName($token->getType()), $token->getData())); + } +} diff --git a/tests/PHPCR/Tests/Util/CND/Scanner/PhpScannerTest.php b/tests/PHPCR/Tests/Util/CND/Scanner/PhpScannerTest.php new file mode 100644 index 00000000..72295e15 --- /dev/null +++ b/tests/PHPCR/Tests/Util/CND/Scanner/PhpScannerTest.php @@ -0,0 +1,25 @@ +scan($reader); + + $this->assertEquals(new Token(T_OPEN_TAG, 'get()); + $this->assertEquals(new Token(T_ECHO, 'echo', 1), $queue->get()); + $this->assertEquals(new Token(T_CONSTANT_ENCAPSED_STRING, "'Hello world'", 1), $queue->get()); + $this->assertEquals(new Token(0, ';', 1), $queue->get()); + $this->assertFalse($queue->get()); + } + +} diff --git a/tests/PHPCR/Tests/Util/CND/Scanner/TokenQueueTest.php b/tests/PHPCR/Tests/Util/CND/Scanner/TokenQueueTest.php new file mode 100644 index 00000000..087a8d0b --- /dev/null +++ b/tests/PHPCR/Tests/Util/CND/Scanner/TokenQueueTest.php @@ -0,0 +1,89 @@ +token0 = new Token(0, 'token 0'); + $this->token1 = new Token(1, 'token 1'); + $this->token2 = new Token(2, 'token 2'); + $this->token3 = new Token(3, 'token 3'); + + $this->queue = new TokenQueue(); + $this->queue->add($this->token0); + $this->queue->add($this->token1); + $this->queue->add($this->token2); + $this->queue->add($this->token3); + } + + public function testAdd() + { + $queue = new TokenQueue(); + $this->assertAttributeEquals(array(), 'tokens', $queue); + + $queue->add($this->token0); + $this->assertAttributeEquals(array($this->token0), 'tokens', $queue); + + $queue->add($this->token1); + $this->assertAttributeEquals(array($this->token0, $this->token1), 'tokens', $queue); + } + + public function testResetAndPeek() + { + $this->assertEquals($this->token0, $this->queue->reset()); + $this->assertEquals($this->token0, $this->queue->peek()); + } + + public function testIsEofAndNext() + { + // Token0 + $this->assertFalse($this->queue->isEof()); + + // Token1 + $this->queue->next(); + $this->assertFalse($this->queue->isEof()); + + // Token2 + $this->queue->next(); + $this->assertFalse($this->queue->isEof()); + + // Token3 + $this->queue->next(); + $this->assertFalse($this->queue->isEof()); + + // EOF + $this->queue->next(); + $this->assertTrue($this->queue->isEof()); + } + + public function testIsEofEmptyQueue() + { + $queue = new TokenQueue(); + $this->assertTrue($queue->isEof()); + $queue->add(new Token(0, 'token')); + $this->assertFalse($queue->isEof()); + } + + public function testGet() + { + $this->queue->reset(); + $this->assertEquals($this->token0, $this->queue->get()); + $this->assertEquals($this->token1, $this->queue->get()); + $this->assertEquals($this->token2, $this->queue->get()); + $this->assertEquals($this->token3, $this->queue->get()); + $this->assertEquals(false, $this->queue->get()); + } + + public function testGetIterator() + { + $this->assertEquals( + array($this->token0, $this->token1, $this->token2, $this->token3), + iterator_to_array($this->queue->getIterator()) + ); + } +} diff --git a/tests/PHPCR/Tests/Util/CND/Scanner/TokenTest.php b/tests/PHPCR/Tests/Util/CND/Scanner/TokenTest.php new file mode 100644 index 00000000..e110f5b9 --- /dev/null +++ b/tests/PHPCR/Tests/Util/CND/Scanner/TokenTest.php @@ -0,0 +1,35 @@ +token = new Token(123, 'foobar'); + } + + public function test__construct() + { + $this->assertAttributeEquals(123, 'type', $this->token); + $this->assertAttributeEquals('foobar', 'data', $this->token); + } + + public function testGetData() + { + $this->assertEquals('foobar', $this->token->getData()); + } + + public function testGetType() + { + $this->assertEquals(123, $this->token->getType()); + } + + public function test__toString() + { + $this->assertEquals('TOKEN(123, \'foobar\', 0, 0)', $this->token->__toString()); + } + +} From c4ccca635a9bc3aaaf4e3180d760ea465a75b316 Mon Sep 17 00:00:00 2001 From: Daniel Barsotti Date: Mon, 10 Sep 2012 15:06:11 +0200 Subject: [PATCH 02/12] Added author in phpDoc --- src/PHPCR/Util/CND/Exception/ParserException.php | 3 +++ src/PHPCR/Util/CND/Exception/ScannerException.php | 3 +++ src/PHPCR/Util/CND/Helper/AbstractDebuggable.php | 2 ++ src/PHPCR/Util/CND/Helper/CndSyntaxTreeNodeVisitor.php | 3 +++ src/PHPCR/Util/CND/Helper/NodeTypeGenerator.php | 3 +++ src/PHPCR/Util/CND/Parser/AbstractParser.php | 2 ++ src/PHPCR/Util/CND/Parser/CndParser.php | 2 ++ src/PHPCR/Util/CND/Parser/ParserInterface.php | 3 +++ src/PHPCR/Util/CND/Parser/SyntaxTreeNode.php | 3 +++ src/PHPCR/Util/CND/Parser/SyntaxTreeVisitorInterface.php | 3 +++ src/PHPCR/Util/CND/Reader/BufferReader.php | 3 +++ src/PHPCR/Util/CND/Reader/FileReader.php | 3 +++ src/PHPCR/Util/CND/Reader/ReaderInterface.php | 3 +++ src/PHPCR/Util/CND/Scanner/AbstractScanner.php | 3 +++ src/PHPCR/Util/CND/Scanner/Context/DefaultScannerContext.php | 3 +++ .../Context/DefaultScannerContextWithoutSpacesAndComments.php | 3 +++ src/PHPCR/Util/CND/Scanner/Context/ScannerContext.php | 3 +++ src/PHPCR/Util/CND/Scanner/GenericScanner.php | 2 ++ src/PHPCR/Util/CND/Scanner/GenericToken.php | 3 +++ src/PHPCR/Util/CND/Scanner/PhpScanner.php | 3 +++ src/PHPCR/Util/CND/Scanner/ScannerInterface.php | 3 +++ src/PHPCR/Util/CND/Scanner/Token.php | 2 ++ src/PHPCR/Util/CND/Scanner/TokenFilter/NoCommentsFilter.php | 3 +++ src/PHPCR/Util/CND/Scanner/TokenFilter/NoNewlinesFilter.php | 3 +++ src/PHPCR/Util/CND/Scanner/TokenFilter/NoWhitespacesFilter.php | 3 +++ src/PHPCR/Util/CND/Scanner/TokenFilter/TokenFilterChain.php | 3 +++ .../Util/CND/Scanner/TokenFilter/TokenFilterInterface.php | 3 +++ src/PHPCR/Util/CND/Scanner/TokenFilter/TokenTypeFilter.php | 3 +++ src/PHPCR/Util/CND/Scanner/TokenQueue.php | 3 +++ 29 files changed, 82 insertions(+) diff --git a/src/PHPCR/Util/CND/Exception/ParserException.php b/src/PHPCR/Util/CND/Exception/ParserException.php index fdff25f8..9e3b2205 100644 --- a/src/PHPCR/Util/CND/Exception/ParserException.php +++ b/src/PHPCR/Util/CND/Exception/ParserException.php @@ -5,6 +5,9 @@ use PHPCR\Util\CND\Scanner\TokenQueue, PHPCR\Util\CND\Scanner\GenericToken; +/** + * @author Daniel Barsotti + */ class ParserException extends \Exception { public function __construct(TokenQueue $queue, $msg) diff --git a/src/PHPCR/Util/CND/Exception/ScannerException.php b/src/PHPCR/Util/CND/Exception/ScannerException.php index ae6970b0..40794763 100644 --- a/src/PHPCR/Util/CND/Exception/ScannerException.php +++ b/src/PHPCR/Util/CND/Exception/ScannerException.php @@ -4,6 +4,9 @@ use PHPCR\Util\CND\Reader\ReaderInterface; +/** + * @author Daniel Barsotti + */ class ScannerException extends \Exception { public function __construct(ReaderInterface $reader, $msg) diff --git a/src/PHPCR/Util/CND/Helper/AbstractDebuggable.php b/src/PHPCR/Util/CND/Helper/AbstractDebuggable.php index 3c769712..04fcf8e0 100644 --- a/src/PHPCR/Util/CND/Helper/AbstractDebuggable.php +++ b/src/PHPCR/Util/CND/Helper/AbstractDebuggable.php @@ -5,6 +5,8 @@ /** * This abstract class provides few debugging function that will only * have an effect if the named constant DEBUG exists and is set to true. + * + * @author Daniel Barsotti */ abstract class AbstractDebuggable { diff --git a/src/PHPCR/Util/CND/Helper/CndSyntaxTreeNodeVisitor.php b/src/PHPCR/Util/CND/Helper/CndSyntaxTreeNodeVisitor.php index acf8af7f..829d7266 100644 --- a/src/PHPCR/Util/CND/Helper/CndSyntaxTreeNodeVisitor.php +++ b/src/PHPCR/Util/CND/Helper/CndSyntaxTreeNodeVisitor.php @@ -10,6 +10,9 @@ // TODO cleanup this dependency // PHPCR\Util\CND\PHPCR\NodeType\NodeTypeDefinition; +/** + * @author Daniel Barsotti + */ class CndSyntaxTreeNodeVisitor implements SyntaxTreeVisitorInterface { protected $generator; diff --git a/src/PHPCR/Util/CND/Helper/NodeTypeGenerator.php b/src/PHPCR/Util/CND/Helper/NodeTypeGenerator.php index 0453cbdb..5376a81a 100644 --- a/src/PHPCR/Util/CND/Helper/NodeTypeGenerator.php +++ b/src/PHPCR/Util/CND/Helper/NodeTypeGenerator.php @@ -4,6 +4,9 @@ use PHPCR\Util\CND\Parser\SyntaxTreeNode; +/** + * @author Daniel Barsotti + */ class NodeTypeGenerator { protected $root; diff --git a/src/PHPCR/Util/CND/Parser/AbstractParser.php b/src/PHPCR/Util/CND/Parser/AbstractParser.php index 37b92da8..4f098b18 100644 --- a/src/PHPCR/Util/CND/Parser/AbstractParser.php +++ b/src/PHPCR/Util/CND/Parser/AbstractParser.php @@ -15,6 +15,8 @@ * - checkToken - check if the next token matches * - expectToken - expect the next token to match * - checkAndExpectToken - check and then expect the next token to match + * + * @author Daniel Barsotti */ abstract class AbstractParser extends AbstractDebuggable implements ParserInterface { diff --git a/src/PHPCR/Util/CND/Parser/CndParser.php b/src/PHPCR/Util/CND/Parser/CndParser.php index b6edb5a1..adad733e 100644 --- a/src/PHPCR/Util/CND/Parser/CndParser.php +++ b/src/PHPCR/Util/CND/Parser/CndParser.php @@ -11,6 +11,8 @@ * * @see http://www.day.com/specs/jcr/2.0/25_Appendix.html#25.2.3 CND Grammar * @see http://jackrabbit.apache.org/node-type-notation.html + * + * @author Daniel Barsotti */ class CndParser extends AbstractParser { diff --git a/src/PHPCR/Util/CND/Parser/ParserInterface.php b/src/PHPCR/Util/CND/Parser/ParserInterface.php index 71418c50..496984a5 100644 --- a/src/PHPCR/Util/CND/Parser/ParserInterface.php +++ b/src/PHPCR/Util/CND/Parser/ParserInterface.php @@ -2,6 +2,9 @@ namespace PHPCR\Util\CND\Parser; +/** + * @author Daniel Barsotti + */ interface ParserInterface { /** diff --git a/src/PHPCR/Util/CND/Parser/SyntaxTreeNode.php b/src/PHPCR/Util/CND/Parser/SyntaxTreeNode.php index a54e7300..aab60ba6 100644 --- a/src/PHPCR/Util/CND/Parser/SyntaxTreeNode.php +++ b/src/PHPCR/Util/CND/Parser/SyntaxTreeNode.php @@ -2,6 +2,9 @@ namespace PHPCR\Util\CND\Parser; +/** + * @author Daniel Barsotti + */ class SyntaxTreeNode { /** diff --git a/src/PHPCR/Util/CND/Parser/SyntaxTreeVisitorInterface.php b/src/PHPCR/Util/CND/Parser/SyntaxTreeVisitorInterface.php index 53209264..a304e5e3 100644 --- a/src/PHPCR/Util/CND/Parser/SyntaxTreeVisitorInterface.php +++ b/src/PHPCR/Util/CND/Parser/SyntaxTreeVisitorInterface.php @@ -2,6 +2,9 @@ namespace PHPCR\Util\CND\Parser; +/** + * @author Daniel Barsotti + */ interface SyntaxTreeVisitorInterface { function visit(SyntaxTreeNode $node); diff --git a/src/PHPCR/Util/CND/Reader/BufferReader.php b/src/PHPCR/Util/CND/Reader/BufferReader.php index 59519793..fbbdfc0f 100644 --- a/src/PHPCR/Util/CND/Reader/BufferReader.php +++ b/src/PHPCR/Util/CND/Reader/BufferReader.php @@ -2,6 +2,9 @@ namespace PHPCR\Util\CND\Reader; +/** + * @author Daniel Barsotti + */ class BufferReader implements ReaderInterface { protected $eofMarker; diff --git a/src/PHPCR/Util/CND/Reader/FileReader.php b/src/PHPCR/Util/CND/Reader/FileReader.php index bed0d236..0926b55f 100644 --- a/src/PHPCR/Util/CND/Reader/FileReader.php +++ b/src/PHPCR/Util/CND/Reader/FileReader.php @@ -2,6 +2,9 @@ namespace PHPCR\Util\CND\Reader; +/** + * @author Daniel Barsotti + */ class FileReader extends BufferReader { protected $fileName; diff --git a/src/PHPCR/Util/CND/Reader/ReaderInterface.php b/src/PHPCR/Util/CND/Reader/ReaderInterface.php index 511bd3d3..1578da26 100644 --- a/src/PHPCR/Util/CND/Reader/ReaderInterface.php +++ b/src/PHPCR/Util/CND/Reader/ReaderInterface.php @@ -2,6 +2,9 @@ namespace PHPCR\Util\CND\Reader; +/** + * @author Daniel Barsotti + */ interface ReaderInterface { /** diff --git a/src/PHPCR/Util/CND/Scanner/AbstractScanner.php b/src/PHPCR/Util/CND/Scanner/AbstractScanner.php index bee57ecf..d9577685 100644 --- a/src/PHPCR/Util/CND/Scanner/AbstractScanner.php +++ b/src/PHPCR/Util/CND/Scanner/AbstractScanner.php @@ -5,6 +5,9 @@ use PHPCR\Util\CND\Helper\AbstractDebuggable, PHPCR\Util\CND\Reader\ReaderInterface; +/** + * @author Daniel Barsotti + */ abstract class AbstractScanner extends AbstractDebuggable implements ScannerInterface { private $queue; diff --git a/src/PHPCR/Util/CND/Scanner/Context/DefaultScannerContext.php b/src/PHPCR/Util/CND/Scanner/Context/DefaultScannerContext.php index 3459d110..1fb0d8a2 100644 --- a/src/PHPCR/Util/CND/Scanner/Context/DefaultScannerContext.php +++ b/src/PHPCR/Util/CND/Scanner/Context/DefaultScannerContext.php @@ -4,6 +4,9 @@ use PHPCR\Util\CND\Scanner\TokenFilter\TokenFilterInterface; +/** + * @author Daniel Barsotti + */ class DefaultScannerContext extends ScannerContext { public function __construct() diff --git a/src/PHPCR/Util/CND/Scanner/Context/DefaultScannerContextWithoutSpacesAndComments.php b/src/PHPCR/Util/CND/Scanner/Context/DefaultScannerContextWithoutSpacesAndComments.php index 0ca5d59f..606b2448 100644 --- a/src/PHPCR/Util/CND/Scanner/Context/DefaultScannerContextWithoutSpacesAndComments.php +++ b/src/PHPCR/Util/CND/Scanner/Context/DefaultScannerContextWithoutSpacesAndComments.php @@ -4,6 +4,9 @@ use PHPCR\Util\CND\Scanner\TokenFilter; +/** + * @author Daniel Barsotti + */ class DefaultScannerContextWithoutSpacesAndComments extends DefaultScannerContext { public function __construct() diff --git a/src/PHPCR/Util/CND/Scanner/Context/ScannerContext.php b/src/PHPCR/Util/CND/Scanner/Context/ScannerContext.php index 8b3d3512..db7c84fe 100644 --- a/src/PHPCR/Util/CND/Scanner/Context/ScannerContext.php +++ b/src/PHPCR/Util/CND/Scanner/Context/ScannerContext.php @@ -4,6 +4,9 @@ use PHPCR\Util\CND\Scanner\TokenFilter\TokenFilterInterface; +/** + * @author Daniel Barsotti + */ class ScannerContext { /** diff --git a/src/PHPCR/Util/CND/Scanner/GenericScanner.php b/src/PHPCR/Util/CND/Scanner/GenericScanner.php index 7da790c6..9b6f2de0 100644 --- a/src/PHPCR/Util/CND/Scanner/GenericScanner.php +++ b/src/PHPCR/Util/CND/Scanner/GenericScanner.php @@ -11,6 +11,8 @@ * * This class can be extended and the class properties redefined in order to adapt * the token generation to your needs. + * + * @author Daniel Barsotti */ class GenericScanner extends AbstractScanner { diff --git a/src/PHPCR/Util/CND/Scanner/GenericToken.php b/src/PHPCR/Util/CND/Scanner/GenericToken.php index ace5816b..f620e5ba 100644 --- a/src/PHPCR/Util/CND/Scanner/GenericToken.php +++ b/src/PHPCR/Util/CND/Scanner/GenericToken.php @@ -2,6 +2,9 @@ namespace PHPCR\Util\CND\Scanner; +/** + * @author Daniel Barsotti + */ class GenericToken extends Token { const TK_WHITESPACE = 0; diff --git a/src/PHPCR/Util/CND/Scanner/PhpScanner.php b/src/PHPCR/Util/CND/Scanner/PhpScanner.php index 9469f1eb..79920f9a 100644 --- a/src/PHPCR/Util/CND/Scanner/PhpScanner.php +++ b/src/PHPCR/Util/CND/Scanner/PhpScanner.php @@ -4,6 +4,9 @@ use PHPCR\Util\CND\Reader\ReaderInterface; +/** + * @author Daniel Barsotti + */ class PhpScanner extends AbstractScanner { /** diff --git a/src/PHPCR/Util/CND/Scanner/ScannerInterface.php b/src/PHPCR/Util/CND/Scanner/ScannerInterface.php index 41c065f1..93a0fce0 100644 --- a/src/PHPCR/Util/CND/Scanner/ScannerInterface.php +++ b/src/PHPCR/Util/CND/Scanner/ScannerInterface.php @@ -4,6 +4,9 @@ use PHPCR\Util\CND\Reader\ReaderInterface; +/** + * @author Daniel Barsotti + */ interface ScannerInterface { /** diff --git a/src/PHPCR/Util/CND/Scanner/Token.php b/src/PHPCR/Util/CND/Scanner/Token.php index 50ea62d1..b9609e02 100644 --- a/src/PHPCR/Util/CND/Scanner/Token.php +++ b/src/PHPCR/Util/CND/Scanner/Token.php @@ -6,6 +6,8 @@ * Base Token class. * * Unless you want to redefine the token type constants, you should rather use GenericToken. + * + * @author Daniel Barsotti */ class Token { diff --git a/src/PHPCR/Util/CND/Scanner/TokenFilter/NoCommentsFilter.php b/src/PHPCR/Util/CND/Scanner/TokenFilter/NoCommentsFilter.php index d1ff7346..93e69eff 100644 --- a/src/PHPCR/Util/CND/Scanner/TokenFilter/NoCommentsFilter.php +++ b/src/PHPCR/Util/CND/Scanner/TokenFilter/NoCommentsFilter.php @@ -4,6 +4,9 @@ use PHPCR\Util\CND\Scanner\GenericToken; +/** + * @author Daniel Barsotti + */ class NoCommentsFilter extends TokenTypeFilter { function __construct() diff --git a/src/PHPCR/Util/CND/Scanner/TokenFilter/NoNewlinesFilter.php b/src/PHPCR/Util/CND/Scanner/TokenFilter/NoNewlinesFilter.php index 07138449..f4a203c2 100644 --- a/src/PHPCR/Util/CND/Scanner/TokenFilter/NoNewlinesFilter.php +++ b/src/PHPCR/Util/CND/Scanner/TokenFilter/NoNewlinesFilter.php @@ -4,6 +4,9 @@ use PHPCR\Util\CND\Scanner\GenericToken; +/** + * @author Daniel Barsotti + */ class NoNewlinesFilter extends TokenTypeFilter { function __construct() diff --git a/src/PHPCR/Util/CND/Scanner/TokenFilter/NoWhitespacesFilter.php b/src/PHPCR/Util/CND/Scanner/TokenFilter/NoWhitespacesFilter.php index 0a2d434b..90009032 100644 --- a/src/PHPCR/Util/CND/Scanner/TokenFilter/NoWhitespacesFilter.php +++ b/src/PHPCR/Util/CND/Scanner/TokenFilter/NoWhitespacesFilter.php @@ -4,6 +4,9 @@ use PHPCR\Util\CND\Scanner\GenericToken; +/** + * @author Daniel Barsotti + */ class NoWhitespacesFilter extends TokenTypeFilter { function __construct() diff --git a/src/PHPCR/Util/CND/Scanner/TokenFilter/TokenFilterChain.php b/src/PHPCR/Util/CND/Scanner/TokenFilter/TokenFilterChain.php index 3b1c8a9c..fa07ef56 100644 --- a/src/PHPCR/Util/CND/Scanner/TokenFilter/TokenFilterChain.php +++ b/src/PHPCR/Util/CND/Scanner/TokenFilter/TokenFilterChain.php @@ -2,6 +2,9 @@ namespace PHPCR\Util\CND\Scanner\TokenFilter; +/** + * @author Daniel Barsotti + */ class TokenFilterChain implements TokenFilterInterface { protected $filters; diff --git a/src/PHPCR/Util/CND/Scanner/TokenFilter/TokenFilterInterface.php b/src/PHPCR/Util/CND/Scanner/TokenFilter/TokenFilterInterface.php index dbe100ba..865e2a43 100644 --- a/src/PHPCR/Util/CND/Scanner/TokenFilter/TokenFilterInterface.php +++ b/src/PHPCR/Util/CND/Scanner/TokenFilter/TokenFilterInterface.php @@ -4,6 +4,9 @@ use PHPCR\Util\CND\Scanner\Token; +/** + * @author Daniel Barsotti + */ interface TokenFilterInterface { /** diff --git a/src/PHPCR/Util/CND/Scanner/TokenFilter/TokenTypeFilter.php b/src/PHPCR/Util/CND/Scanner/TokenFilter/TokenTypeFilter.php index af287964..96589223 100644 --- a/src/PHPCR/Util/CND/Scanner/TokenFilter/TokenTypeFilter.php +++ b/src/PHPCR/Util/CND/Scanner/TokenFilter/TokenTypeFilter.php @@ -4,6 +4,9 @@ use PHPCR\Util\CND\Scanner\Token; +/** + * @author Daniel Barsotti + */ class TokenTypeFilter implements TokenFilterInterface { /** diff --git a/src/PHPCR/Util/CND/Scanner/TokenQueue.php b/src/PHPCR/Util/CND/Scanner/TokenQueue.php index a8e70be1..4541a427 100644 --- a/src/PHPCR/Util/CND/Scanner/TokenQueue.php +++ b/src/PHPCR/Util/CND/Scanner/TokenQueue.php @@ -2,6 +2,9 @@ namespace PHPCR\Util\CND\Scanner; +/** + * @author Daniel Barsotti + */ class TokenQueue implements \IteratorAggregate { /** From c0086c15203d34e855879b1e6ac92470cd15b0ff Mon Sep 17 00:00:00 2001 From: Daniel Barsotti Date: Mon, 10 Sep 2012 15:36:29 +0200 Subject: [PATCH 03/12] Start of tests cleanup to integrate with PHPCR --- .../CND/Helper/CndSyntaxTreeNodeVisitor.php | 40 ++++++++++++------- .../Util/CND/Helper/NodeTypeGenerator.php | 12 +++++- .../Util/CND/Helper/NodeTypeGeneratorTest.php | 9 +++-- 3 files changed, 42 insertions(+), 19 deletions(-) diff --git a/src/PHPCR/Util/CND/Helper/CndSyntaxTreeNodeVisitor.php b/src/PHPCR/Util/CND/Helper/CndSyntaxTreeNodeVisitor.php index 829d7266..912c9769 100644 --- a/src/PHPCR/Util/CND/Helper/CndSyntaxTreeNodeVisitor.php +++ b/src/PHPCR/Util/CND/Helper/CndSyntaxTreeNodeVisitor.php @@ -6,27 +6,39 @@ // TODO: as it is now, this will not work !!! use PHPCR\Util\CND\Parser\SyntaxTreeNode, - PHPCR\Util\CND\Parser\SyntaxTreeVisitorInterface; -// TODO cleanup this dependency -// PHPCR\Util\CND\PHPCR\NodeType\NodeTypeDefinition; + PHPCR\Util\CND\Parser\SyntaxTreeVisitorInterface, + PHPCR\SessionInterface, + PHPCR\NodeType\NodeTypeDefinitionInterface; /** * @author Daniel Barsotti */ class CndSyntaxTreeNodeVisitor implements SyntaxTreeVisitorInterface { - protected $generator; + /** + * @var \PHPCR\NodeTypeManagerInterface + */ + protected $nodeTypeManager; + + /** + * @var \PHPCR\NamespaceRegistryInterface + */ + protected $namespaceRegistry; + /** + * @var array + */ protected $nodeTypeDefs = array(); /** - * @var \LazyGuy\PhpParse\PHPCR\NodeType\NodeTypeDefinition + * @var \PHPCR\NodeTypeDefinitionInterface */ protected $curNodeTypeDef; - public function __construct(NodeTypeGenerator $generator) + public function __construct(SessionInterface $session) { - $this->generator = $generator; + $this->namespaceRegistry = $session->getWorkspace()->getNamespaceRegistry(); + $this->nodeTypeManager = $session->getWorkspace()->getNodeTypeManager(); } public function getNodeTypeDefs() @@ -38,12 +50,12 @@ public function visit(SyntaxTreeNode $node) { //var_dump($node->getType()); -// switch ($node->getType()) { -// -// case 'nodeTypeDef': -// $this->curNodeTypeDef = new NodeTypeDefinition(); -// $this->nodeTypeDefs[] = $this->curNodeTypeDef; -// break; + switch ($node->getType()) { + + case 'nodeTypeDef': + $this->curNodeTypeDef = new NodeTypeDefinition(); + $this->nodeTypeDefs[] = $this->curNodeTypeDef; + break; // // case 'nodeTypeName': // if ($node->hasProperty('value')) { @@ -65,7 +77,7 @@ public function visit(SyntaxTreeNode $node) // break; // // // TODO: write the rest -// } + } } } diff --git a/src/PHPCR/Util/CND/Helper/NodeTypeGenerator.php b/src/PHPCR/Util/CND/Helper/NodeTypeGenerator.php index 5376a81a..af897226 100644 --- a/src/PHPCR/Util/CND/Helper/NodeTypeGenerator.php +++ b/src/PHPCR/Util/CND/Helper/NodeTypeGenerator.php @@ -2,7 +2,8 @@ namespace PHPCR\Util\CND\Helper; -use PHPCR\Util\CND\Parser\SyntaxTreeNode; +use PHPCR\Util\CND\Parser\SyntaxTreeNode, + PHPCR\SessionInterface; /** * @author Daniel Barsotti @@ -11,8 +12,15 @@ class NodeTypeGenerator { protected $root; - public function __construct(SyntaxTreeNode $root) + protected $session; + + /** + * @param \PHPCR\SessionInterface $session + * @param \PHPCR\Util\CND\Parser\SyntaxTreeNode $root + */ + public function __construct(SessionInterface $session, SyntaxTreeNode $root) { + $this->session = $session; $this->root = $root; } diff --git a/tests/PHPCR/Tests/Util/CND/Helper/NodeTypeGeneratorTest.php b/tests/PHPCR/Tests/Util/CND/Helper/NodeTypeGeneratorTest.php index 787ea806..9481155e 100644 --- a/tests/PHPCR/Tests/Util/CND/Helper/NodeTypeGeneratorTest.php +++ b/tests/PHPCR/Tests/Util/CND/Helper/NodeTypeGeneratorTest.php @@ -8,6 +8,7 @@ PHPCR\Util\CND\Scanner\GenericScanner, PHPCR\Util\CND\Scanner\Context; +// TODO: this belongs to functional testing, move it to phpcr-api-test repo (so that we have an implementation for the session) class NodeTypeGeneratorTest extends \PHPUnit_Framework_TestCase { public function testGenerator() @@ -17,9 +18,11 @@ public function testGenerator() $queue = $scanner->scan($reader); $parser = new CndParser($queue); $root = $parser->parse(); - - $generator = new NodeTypeGenerator($root); - $generator->generate(); + + // TODO: get a session, somehow... + //$session = ... +// $generator = new NodeTypeGenerator($sesion, $root); +// $generator->generate(); $this->assertTrue(true); // To avoid the test being marked incomplete // TODO: write some real tests From f07f246954a77179925085a6623c214cc38fea45 Mon Sep 17 00:00:00 2001 From: Daniel Barsotti Date: Thu, 13 Sep 2012 22:57:18 +0200 Subject: [PATCH 04/12] Some more node type generation + refactoring --- .../CND/Helper/CndSyntaxTreeNodeVisitor.php | 73 +++++++++++-------- .../Util/CND/Helper/NodeTypeGenerator.php | 23 ++++-- 2 files changed, 60 insertions(+), 36 deletions(-) diff --git a/src/PHPCR/Util/CND/Helper/CndSyntaxTreeNodeVisitor.php b/src/PHPCR/Util/CND/Helper/CndSyntaxTreeNodeVisitor.php index 912c9769..cbef55b9 100644 --- a/src/PHPCR/Util/CND/Helper/CndSyntaxTreeNodeVisitor.php +++ b/src/PHPCR/Util/CND/Helper/CndSyntaxTreeNodeVisitor.php @@ -7,7 +7,8 @@ use PHPCR\Util\CND\Parser\SyntaxTreeNode, PHPCR\Util\CND\Parser\SyntaxTreeVisitorInterface, - PHPCR\SessionInterface, + PHPCR\NamespaceRegistryInterface, + PHPCR\NodeType\NodeTypeManagerInterface, PHPCR\NodeType\NodeTypeDefinitionInterface; /** @@ -31,14 +32,19 @@ class CndSyntaxTreeNodeVisitor implements SyntaxTreeVisitorInterface protected $nodeTypeDefs = array(); /** - * @var \PHPCR\NodeTypeDefinitionInterface + * @var array + */ + protected $namespaces = array(); + + /** + * @var \PHPCR\NodeTypeTemplateInterface */ protected $curNodeTypeDef; - public function __construct(SessionInterface $session) + public function __construct(NamespaceRegistryInterface $nsRegistry, NodeTypeManagerInterface $ntManager) { - $this->namespaceRegistry = $session->getWorkspace()->getNamespaceRegistry(); - $this->nodeTypeManager = $session->getWorkspace()->getNodeTypeManager(); + $this->namespaceRegistry = $nsRegistry; + $this->nodeTypeManager = $ntManager; } public function getNodeTypeDefs() @@ -46,37 +52,46 @@ public function getNodeTypeDefs() return $this->nodeTypeDefs; } + public function getNamespaces() + { + return $this->namespaces; + } + public function visit(SyntaxTreeNode $node) { - //var_dump($node->getType()); - + var_dump($node->getType()); + switch ($node->getType()) { + case 'nsMapping': + $this->namespaces[$node->getProperty('prefix')] = $node->getProperty('uri'); + break; + case 'nodeTypeDef': - $this->curNodeTypeDef = new NodeTypeDefinition(); + $this->curNodeTypeDef = $this->nodeTypeManager->createNodeTypeTemplate(); $this->nodeTypeDefs[] = $this->curNodeTypeDef; break; -// -// case 'nodeTypeName': -// if ($node->hasProperty('value')) { -// $this->curNodeTypeDef->setName($node->getProperty('value')); -// } -// break; -// -// case 'supertypes': -// if ($node->hasProperty('value')) { -// $this->curNodeTypeDef->addDeclaredSupertypeName($node->getProperty('value')); -// } -// break; -// -// case 'nodeTypeAttributes': -// if ($node->hasChild('orderable')) $this->curNodeTypeDef->setHasOrderableChildNodes(true); -// if ($node->hasChild('mixin')) $this->curNodeTypeDef->setIsMixin(true); -// if ($node->hasChild('abstract')) $this->curNodeTypeDef->setIsAbstract(true); -// if ($node->hasChild('query')) $this->curNodeTypeDef->setIsQueryable(true); -// break; -// -// // TODO: write the rest + + case 'nodeTypeName': + if ($node->hasProperty('value')) { + $this->curNodeTypeDef->setName($node->getProperty('value')); + } + break; + + case 'supertypes': + if ($node->hasProperty('value')) { + $this->curNodeTypeDef->setDeclaredSuperTypeNames($node->getProperty('value')); + } + break; + + case 'nodeTypeAttributes': + if ($node->hasChild('orderable')) $this->curNodeTypeDef->setOrderableChildNodes(true); + if ($node->hasChild('mixin')) $this->curNodeTypeDef->setMixin(true); + if ($node->hasChild('abstract')) $this->curNodeTypeDef->setAbstract(true); + if ($node->hasChild('query')) $this->curNodeTypeDef->setQueryable(true); + break; + + // TODO: write the rest } } diff --git a/src/PHPCR/Util/CND/Helper/NodeTypeGenerator.php b/src/PHPCR/Util/CND/Helper/NodeTypeGenerator.php index af897226..f5baaf53 100644 --- a/src/PHPCR/Util/CND/Helper/NodeTypeGenerator.php +++ b/src/PHPCR/Util/CND/Helper/NodeTypeGenerator.php @@ -3,32 +3,41 @@ namespace PHPCR\Util\CND\Helper; use PHPCR\Util\CND\Parser\SyntaxTreeNode, - PHPCR\SessionInterface; + PHPCR\WorkspaceInterface; /** * @author Daniel Barsotti */ class NodeTypeGenerator { + /** + * @var \PHPCR\Util\CND\Parser\SyntaxTreeNode + */ protected $root; - protected $session; + /** + * @var \PHPCR\WorkspaceInterface + */ + protected $workspace; /** - * @param \PHPCR\SessionInterface $session + * @param \PHPCR\WorkspaceInterface $session * @param \PHPCR\Util\CND\Parser\SyntaxTreeNode $root */ - public function __construct(SessionInterface $session, SyntaxTreeNode $root) + public function __construct(WorkspaceInterface $workspace, SyntaxTreeNode $root) { - $this->session = $session; + $this->workspace = $workspace; $this->root = $root; } public function generate() { - $visitor = new CndSyntaxTreeNodeVisitor($this); + $visitor = new CndSyntaxTreeNodeVisitor($this->workspace->getNamespaceRegistry(), $this->workspace->getNodeTypeManager()); $this->root->accept($visitor); - return $visitor->getNodeTypeDefs(); + return array( + 'namespaces' => $visitor->getNamespaces(), + 'nodeTypes' => $visitor->getNodeTypeDefs(), + ); } } From 9a42a211a00b3f11acc7f9a794801e4af435474a Mon Sep 17 00:00:00 2001 From: Daniel Barsotti Date: Thu, 13 Sep 2012 22:57:33 +0200 Subject: [PATCH 05/12] Cleanup --- src/PHPCR/Util/CND/Scanner/PhpScanner.php | 48 ------------------- .../Tests/Util/CND/Scanner/PhpScannerTest.php | 25 ---------- 2 files changed, 73 deletions(-) delete mode 100644 src/PHPCR/Util/CND/Scanner/PhpScanner.php delete mode 100644 tests/PHPCR/Tests/Util/CND/Scanner/PhpScannerTest.php diff --git a/src/PHPCR/Util/CND/Scanner/PhpScanner.php b/src/PHPCR/Util/CND/Scanner/PhpScanner.php deleted file mode 100644 index 79920f9a..00000000 --- a/src/PHPCR/Util/CND/Scanner/PhpScanner.php +++ /dev/null @@ -1,48 +0,0 @@ - - */ -class PhpScanner extends AbstractScanner -{ - /** - * @param \PHPCR\Util\CND\Reader\ReaderInterface $reader - * @return \PHPCR\Util\CND\Scanner\TokenQueue - */ - public function scan(ReaderInterface $reader) - { - $tokens = array(); - $curLine = 0; - - foreach (token_get_all(substr($reader->getBuffer(), 0, -1)) as $phpToken) { - - if (is_string($phpToken)) { - - $token = new Token(0, trim($phpToken), $curLine); - - } elseif ($phpToken[0] !== T_WHITESPACE) { - - $line = isset($phpToken[2]) ? $phpToken[2] : 0; - $token = new Token($phpToken[0], trim($phpToken[1]), $line); - $curLine = $line; - - } else { - // Strip whitespaces - continue; - } - - $token = $this->applyFilters($token); - - if ($token) { - $tokens[] = $token; - } - } - - return new TokenQueue($tokens); - } - -} diff --git a/tests/PHPCR/Tests/Util/CND/Scanner/PhpScannerTest.php b/tests/PHPCR/Tests/Util/CND/Scanner/PhpScannerTest.php deleted file mode 100644 index 72295e15..00000000 --- a/tests/PHPCR/Tests/Util/CND/Scanner/PhpScannerTest.php +++ /dev/null @@ -1,25 +0,0 @@ -scan($reader); - - $this->assertEquals(new Token(T_OPEN_TAG, 'get()); - $this->assertEquals(new Token(T_ECHO, 'echo', 1), $queue->get()); - $this->assertEquals(new Token(T_CONSTANT_ENCAPSED_STRING, "'Hello world'", 1), $queue->get()); - $this->assertEquals(new Token(0, ';', 1), $queue->get()); - $this->assertFalse($queue->get()); - } - -} From 6ab38f138cb582c054f147659b75046ca6360637 Mon Sep 17 00:00:00 2001 From: Daniel Barsotti Date: Wed, 19 Sep 2012 13:42:49 +0200 Subject: [PATCH 06/12] Work in progress: CND parser --- .../CND/Helper/CndSyntaxTreeNodeVisitor.php | 96 ++++++++++++++++--- 1 file changed, 83 insertions(+), 13 deletions(-) diff --git a/src/PHPCR/Util/CND/Helper/CndSyntaxTreeNodeVisitor.php b/src/PHPCR/Util/CND/Helper/CndSyntaxTreeNodeVisitor.php index cbef55b9..e1b33a9a 100644 --- a/src/PHPCR/Util/CND/Helper/CndSyntaxTreeNodeVisitor.php +++ b/src/PHPCR/Util/CND/Helper/CndSyntaxTreeNodeVisitor.php @@ -2,14 +2,13 @@ namespace PHPCR\Util\CND\Helper; -// TODO: this class needs to be rewritten using PHPCR\NodeType\NodeTypeManagerInterface to generate the node types -// TODO: as it is now, this will not work !!! - use PHPCR\Util\CND\Parser\SyntaxTreeNode, PHPCR\Util\CND\Parser\SyntaxTreeVisitorInterface, PHPCR\NamespaceRegistryInterface, PHPCR\NodeType\NodeTypeManagerInterface, - PHPCR\NodeType\NodeTypeDefinitionInterface; + PHPCR\NodeType\NodeTypeDefinitionInterface, + PHPCR\PropertyType, + PHPCR\Version\OnParentVersionAction; /** * @author Daniel Barsotti @@ -17,7 +16,7 @@ class CndSyntaxTreeNodeVisitor implements SyntaxTreeVisitorInterface { /** - * @var \PHPCR\NodeTypeManagerInterface + * @var \PHPCR\NodeType\NodeTypeManagerInterface */ protected $nodeTypeManager; @@ -37,10 +36,15 @@ class CndSyntaxTreeNodeVisitor implements SyntaxTreeVisitorInterface protected $namespaces = array(); /** - * @var \PHPCR\NodeTypeTemplateInterface + * @var \PHPCR\NodeType\NodeTypeTemplateInterface */ protected $curNodeTypeDef; + /** + * @var \PHPCR\NodeType\PropertyDefinitionTemplateInterface + */ + protected $curPropTypeDef; + public function __construct(NamespaceRegistryInterface $nsRegistry, NodeTypeManagerInterface $ntManager) { $this->namespaceRegistry = $nsRegistry; @@ -59,8 +63,6 @@ public function getNamespaces() public function visit(SyntaxTreeNode $node) { - var_dump($node->getType()); - switch ($node->getType()) { case 'nsMapping': @@ -85,14 +87,82 @@ public function visit(SyntaxTreeNode $node) break; case 'nodeTypeAttributes': - if ($node->hasChild('orderable')) $this->curNodeTypeDef->setOrderableChildNodes(true); - if ($node->hasChild('mixin')) $this->curNodeTypeDef->setMixin(true); - if ($node->hasChild('abstract')) $this->curNodeTypeDef->setAbstract(true); - if ($node->hasChild('query')) $this->curNodeTypeDef->setQueryable(true); + $this->setNodeTypeAttributes($node); + // OPTIMIZATION: Here we could "ask" the tree not to send the children as they just have been taken in account + break; + + case 'propertyDef': + $this->curPropTypeDef = $this->nodeTypeManager->createPropertyDefinitionTemplate(); + $this->curNodeTypeDef->getPropertyDefinitionTemplates()->append($this->curPropTypeDef); + break; + + case 'propertyName': + $this->curPropTypeDef->setName($node->getProperty('value')); + break; + + case 'propertyType': + $this->curPropTypeDef->setRequiredType(PropertyType::valueFromName($node->getProperty('value'))); + break; + + case 'defaultValues': + $this->curPropTypeDef->setDefaultValues($node->getProperty('value')); + break; + + case 'valueConstraints': + $this->curPropTypeDef->setValueConstraints($node->getProperty('value')); + break; + + case 'propertyTypeAttributes': + $this->setPropertyTypeAttributes($node); + // OPTIMIZATION: Here we could "ask" the tree not to send the children as they just have been taken in account break; - // TODO: write the rest + default: +// var_dump(sprintf('Unhandled node [%s]', $node->getType())); + + } + } + + protected function setNodeTypeAttributes(SyntaxTreeNode $node) + { + if ($node->hasChild('orderable')) $this->curNodeTypeDef->setOrderableChildNodes(true); + if ($node->hasChild('mixin')) $this->curNodeTypeDef->setMixin(true); + if ($node->hasChild('abstract')) $this->curNodeTypeDef->setAbstract(true); + if ($node->hasChild('query')) $this->curNodeTypeDef->setQueryable(true); + } + + protected function setPropertyTypeAttributes(SyntaxTreeNode $node) + { + if ($node->hasChild('autocreated')) $this->curPropTypeDef->setAutoCreated(true); + if ($node->hasChild('mandatory')) $this->curPropTypeDef->setMandatory(true); + if ($node->hasChild('protected')) $this->curPropTypeDef->setProtected(true); + if ($node->hasChild('multiple')) $this->curPropTypeDef->setMultiple(true); + if ($node->hasChild('queryops')) $this->curPropTypeDef->setAvailableQueryOperators($node->getPRoperty('value')); + + if ($node->hasChild('nofulltext')) { + $this->curPropTypeDef->setFullTextSearchable(false); + } else { + // TODO: Check the property should be set to true when there is no nofulltext node + $this->curPropTypeDef->setFullTextSearchable(true); } + + if ($node->hasChild('noqueryorder')) { + $this->curPropTypeDef->setQueryOrderable(false); + } else { + // TODO: Check the property should be set to true when there is no noqueryorder node + $this->curPropTypeDef->setQueryOrderable(true); + } + + // WARNING: + // Potentially the syntax tree could have more than one OPV node, which is wrong in a CND. + // If this happens the order of the "if" below determine which one will be used. + if ($node->hasChild('COPY')) $this->curPropTypeDef->setOnParentVersion(OnParentVersionAction::COPY); + if ($node->hasChild('VERSION')) $this->curPropTypeDef->setOnParentVersion(OnParentVersionAction::VERSION); + if ($node->hasChild('INITIALIZE')) $this->curPropTypeDef->setOnParentVersion(OnParentVersionAction::INITIALIZE); + if ($node->hasChild('COMPUTE')) $this->curPropTypeDef->setOnParentVersion(OnParentVersionAction::COMPUTE); + if ($node->hasChild('IGNORE')) $this->curPropTypeDef->setOnParentVersion(OnParentVersionAction::IGNORE); + if ($node->hasChild('ABORT')) $this->curPropTypeDef->setOnParentVersion(OnParentVersionAction::ABORT); + // TODO: What to do with an "OPV" node? } } From 97963e915f6580f0a13cd34eb10a807725d40780 Mon Sep 17 00:00:00 2001 From: Daniel Barsotti Date: Wed, 19 Sep 2012 16:29:06 +0200 Subject: [PATCH 07/12] Allow the SyntaxTreeNode visitor to indicate he does not want to visit the child nodes --- src/PHPCR/Util/CND/Helper/CndSyntaxTreeNodeVisitor.php | 10 +++++----- src/PHPCR/Util/CND/Parser/SyntaxTreeNode.php | 9 ++++++--- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/PHPCR/Util/CND/Helper/CndSyntaxTreeNodeVisitor.php b/src/PHPCR/Util/CND/Helper/CndSyntaxTreeNodeVisitor.php index e1b33a9a..0f7adbf1 100644 --- a/src/PHPCR/Util/CND/Helper/CndSyntaxTreeNodeVisitor.php +++ b/src/PHPCR/Util/CND/Helper/CndSyntaxTreeNodeVisitor.php @@ -88,8 +88,8 @@ public function visit(SyntaxTreeNode $node) case 'nodeTypeAttributes': $this->setNodeTypeAttributes($node); - // OPTIMIZATION: Here we could "ask" the tree not to send the children as they just have been taken in account - break; + // return false to indicate we don't want to visit the children + return false; case 'propertyDef': $this->curPropTypeDef = $this->nodeTypeManager->createPropertyDefinitionTemplate(); @@ -114,11 +114,11 @@ public function visit(SyntaxTreeNode $node) case 'propertyTypeAttributes': $this->setPropertyTypeAttributes($node); - // OPTIMIZATION: Here we could "ask" the tree not to send the children as they just have been taken in account - break; + // return false to indicate we don't want to visit the children + return false; default: -// var_dump(sprintf('Unhandled node [%s]', $node->getType())); + var_dump(sprintf('Unhandled node [%s]', $node->getType())); } } diff --git a/src/PHPCR/Util/CND/Parser/SyntaxTreeNode.php b/src/PHPCR/Util/CND/Parser/SyntaxTreeNode.php index aab60ba6..dd3a274f 100644 --- a/src/PHPCR/Util/CND/Parser/SyntaxTreeNode.php +++ b/src/PHPCR/Util/CND/Parser/SyntaxTreeNode.php @@ -55,14 +55,17 @@ public function addChild(SyntaxTreeNode $child) } /** + * Make the $visitor visit this node and it's children. + * The visitor may return the value boolean false (strict equality) to indicate to not visit the children. * @param SyntaxTreeVisitorInterface $visitor * @return void */ public function accept(SyntaxTreeVisitorInterface $visitor) { - $visitor->visit($this); - foreach($this->children as $child) { - $child->accept($visitor); + if (false !== $visitor->visit($this)) { + foreach($this->children as $child) { + $child->accept($visitor); + } } } From 95741b53709fe2472096372e78b509eab02d9ac1 Mon Sep 17 00:00:00 2001 From: Daniel Barsotti Date: Wed, 19 Sep 2012 16:37:13 +0200 Subject: [PATCH 08/12] Deactivated debug code --- src/PHPCR/Util/CND/Helper/CndSyntaxTreeNodeVisitor.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/PHPCR/Util/CND/Helper/CndSyntaxTreeNodeVisitor.php b/src/PHPCR/Util/CND/Helper/CndSyntaxTreeNodeVisitor.php index 0f7adbf1..18eb4cef 100644 --- a/src/PHPCR/Util/CND/Helper/CndSyntaxTreeNodeVisitor.php +++ b/src/PHPCR/Util/CND/Helper/CndSyntaxTreeNodeVisitor.php @@ -110,6 +110,7 @@ public function visit(SyntaxTreeNode $node) case 'valueConstraints': $this->curPropTypeDef->setValueConstraints($node->getProperty('value')); + var_dump($this->curNodeTypeDef->getDeclaredChildNodeDefinitions());die; break; case 'propertyTypeAttributes': @@ -118,7 +119,7 @@ public function visit(SyntaxTreeNode $node) return false; default: - var_dump(sprintf('Unhandled node [%s]', $node->getType())); +// var_dump(sprintf('Unhandled node [%s]', $node->getType())); } } From 2013dcc8394a7bf988d96f425a35ec6b5619fb8e Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Wed, 27 Mar 2013 02:42:43 +0100 Subject: [PATCH 09/12] cleanup the cnd parser --- .../Util/CND/Helper/AbstractDebuggable.php | 48 -- .../CND/Helper/CndSyntaxTreeNodeVisitor.php | 169 ----- .../Util/CND/Helper/NodeTypeGenerator.php | 43 -- src/PHPCR/Util/CND/Parser/AbstractParser.php | 51 +- src/PHPCR/Util/CND/Parser/CndParser.php | 709 +++++++----------- src/PHPCR/Util/CND/Parser/ParserInterface.php | 14 - src/PHPCR/Util/CND/Parser/SyntaxTreeNode.php | 209 ------ .../CND/Parser/SyntaxTreeVisitorInterface.php | 11 - src/PHPCR/Util/CND/Reader/BufferReader.php | 16 - src/PHPCR/Util/CND/Reader/ReaderInterface.php | 27 +- .../Util/CND/Scanner/AbstractScanner.php | 18 +- .../CND/Scanner/Context/ScannerContext.php | 7 +- src/PHPCR/Util/CND/Scanner/GenericScanner.php | 63 +- .../Util/CND/Scanner/ScannerInterface.php | 26 - .../Scanner/TokenFilter/TokenFilterChain.php | 5 + src/PHPCR/Util/CND/Scanner/TokenQueue.php | 9 +- 16 files changed, 352 insertions(+), 1073 deletions(-) delete mode 100644 src/PHPCR/Util/CND/Helper/AbstractDebuggable.php delete mode 100644 src/PHPCR/Util/CND/Helper/CndSyntaxTreeNodeVisitor.php delete mode 100644 src/PHPCR/Util/CND/Helper/NodeTypeGenerator.php delete mode 100644 src/PHPCR/Util/CND/Parser/ParserInterface.php delete mode 100644 src/PHPCR/Util/CND/Parser/SyntaxTreeNode.php delete mode 100644 src/PHPCR/Util/CND/Parser/SyntaxTreeVisitorInterface.php delete mode 100644 src/PHPCR/Util/CND/Scanner/ScannerInterface.php diff --git a/src/PHPCR/Util/CND/Helper/AbstractDebuggable.php b/src/PHPCR/Util/CND/Helper/AbstractDebuggable.php deleted file mode 100644 index 04fcf8e0..00000000 --- a/src/PHPCR/Util/CND/Helper/AbstractDebuggable.php +++ /dev/null @@ -1,48 +0,0 @@ - - */ -abstract class AbstractDebuggable -{ - /** - * Display the message on the console. - * @param string $msg The message to display - * @param int $indent The indentation to use (size = 2 char) - * @return void - */ - protected function debug($msg, $indent = 0) - { - //@codeCoverageIgnoreStart - if (defined('DEBUG') && DEBUG) { - echo sprintf("%s%s\n", str_repeat(' ', $indent), $msg); - } - //@codeCoverageIgnoreEnd - } - - /** - * Display the message as a result, indented by 1 and prefixed with '=>' - * @param string $msg The message to display - * @return void - */ - protected function debugRes($msg) - { - $this->debug("=> " . $msg, 1); - } - - /** - * Display the message as a section header - * @param string $msg The message to display - * @return void - */ - protected function debugSection($msg) - { - $this->debug(sprintf("\n\n----- %s %s\n", $msg, str_repeat('-', 80 - strlen($msg)))); - } - -} diff --git a/src/PHPCR/Util/CND/Helper/CndSyntaxTreeNodeVisitor.php b/src/PHPCR/Util/CND/Helper/CndSyntaxTreeNodeVisitor.php deleted file mode 100644 index 18eb4cef..00000000 --- a/src/PHPCR/Util/CND/Helper/CndSyntaxTreeNodeVisitor.php +++ /dev/null @@ -1,169 +0,0 @@ - - */ -class CndSyntaxTreeNodeVisitor implements SyntaxTreeVisitorInterface -{ - /** - * @var \PHPCR\NodeType\NodeTypeManagerInterface - */ - protected $nodeTypeManager; - - /** - * @var \PHPCR\NamespaceRegistryInterface - */ - protected $namespaceRegistry; - - /** - * @var array - */ - protected $nodeTypeDefs = array(); - - /** - * @var array - */ - protected $namespaces = array(); - - /** - * @var \PHPCR\NodeType\NodeTypeTemplateInterface - */ - protected $curNodeTypeDef; - - /** - * @var \PHPCR\NodeType\PropertyDefinitionTemplateInterface - */ - protected $curPropTypeDef; - - public function __construct(NamespaceRegistryInterface $nsRegistry, NodeTypeManagerInterface $ntManager) - { - $this->namespaceRegistry = $nsRegistry; - $this->nodeTypeManager = $ntManager; - } - - public function getNodeTypeDefs() - { - return $this->nodeTypeDefs; - } - - public function getNamespaces() - { - return $this->namespaces; - } - - public function visit(SyntaxTreeNode $node) - { - switch ($node->getType()) { - - case 'nsMapping': - $this->namespaces[$node->getProperty('prefix')] = $node->getProperty('uri'); - break; - - case 'nodeTypeDef': - $this->curNodeTypeDef = $this->nodeTypeManager->createNodeTypeTemplate(); - $this->nodeTypeDefs[] = $this->curNodeTypeDef; - break; - - case 'nodeTypeName': - if ($node->hasProperty('value')) { - $this->curNodeTypeDef->setName($node->getProperty('value')); - } - break; - - case 'supertypes': - if ($node->hasProperty('value')) { - $this->curNodeTypeDef->setDeclaredSuperTypeNames($node->getProperty('value')); - } - break; - - case 'nodeTypeAttributes': - $this->setNodeTypeAttributes($node); - // return false to indicate we don't want to visit the children - return false; - - case 'propertyDef': - $this->curPropTypeDef = $this->nodeTypeManager->createPropertyDefinitionTemplate(); - $this->curNodeTypeDef->getPropertyDefinitionTemplates()->append($this->curPropTypeDef); - break; - - case 'propertyName': - $this->curPropTypeDef->setName($node->getProperty('value')); - break; - - case 'propertyType': - $this->curPropTypeDef->setRequiredType(PropertyType::valueFromName($node->getProperty('value'))); - break; - - case 'defaultValues': - $this->curPropTypeDef->setDefaultValues($node->getProperty('value')); - break; - - case 'valueConstraints': - $this->curPropTypeDef->setValueConstraints($node->getProperty('value')); - var_dump($this->curNodeTypeDef->getDeclaredChildNodeDefinitions());die; - break; - - case 'propertyTypeAttributes': - $this->setPropertyTypeAttributes($node); - // return false to indicate we don't want to visit the children - return false; - - default: -// var_dump(sprintf('Unhandled node [%s]', $node->getType())); - - } - } - - protected function setNodeTypeAttributes(SyntaxTreeNode $node) - { - if ($node->hasChild('orderable')) $this->curNodeTypeDef->setOrderableChildNodes(true); - if ($node->hasChild('mixin')) $this->curNodeTypeDef->setMixin(true); - if ($node->hasChild('abstract')) $this->curNodeTypeDef->setAbstract(true); - if ($node->hasChild('query')) $this->curNodeTypeDef->setQueryable(true); - } - - protected function setPropertyTypeAttributes(SyntaxTreeNode $node) - { - if ($node->hasChild('autocreated')) $this->curPropTypeDef->setAutoCreated(true); - if ($node->hasChild('mandatory')) $this->curPropTypeDef->setMandatory(true); - if ($node->hasChild('protected')) $this->curPropTypeDef->setProtected(true); - if ($node->hasChild('multiple')) $this->curPropTypeDef->setMultiple(true); - if ($node->hasChild('queryops')) $this->curPropTypeDef->setAvailableQueryOperators($node->getPRoperty('value')); - - if ($node->hasChild('nofulltext')) { - $this->curPropTypeDef->setFullTextSearchable(false); - } else { - // TODO: Check the property should be set to true when there is no nofulltext node - $this->curPropTypeDef->setFullTextSearchable(true); - } - - if ($node->hasChild('noqueryorder')) { - $this->curPropTypeDef->setQueryOrderable(false); - } else { - // TODO: Check the property should be set to true when there is no noqueryorder node - $this->curPropTypeDef->setQueryOrderable(true); - } - - // WARNING: - // Potentially the syntax tree could have more than one OPV node, which is wrong in a CND. - // If this happens the order of the "if" below determine which one will be used. - if ($node->hasChild('COPY')) $this->curPropTypeDef->setOnParentVersion(OnParentVersionAction::COPY); - if ($node->hasChild('VERSION')) $this->curPropTypeDef->setOnParentVersion(OnParentVersionAction::VERSION); - if ($node->hasChild('INITIALIZE')) $this->curPropTypeDef->setOnParentVersion(OnParentVersionAction::INITIALIZE); - if ($node->hasChild('COMPUTE')) $this->curPropTypeDef->setOnParentVersion(OnParentVersionAction::COMPUTE); - if ($node->hasChild('IGNORE')) $this->curPropTypeDef->setOnParentVersion(OnParentVersionAction::IGNORE); - if ($node->hasChild('ABORT')) $this->curPropTypeDef->setOnParentVersion(OnParentVersionAction::ABORT); - // TODO: What to do with an "OPV" node? - } - -} diff --git a/src/PHPCR/Util/CND/Helper/NodeTypeGenerator.php b/src/PHPCR/Util/CND/Helper/NodeTypeGenerator.php deleted file mode 100644 index f5baaf53..00000000 --- a/src/PHPCR/Util/CND/Helper/NodeTypeGenerator.php +++ /dev/null @@ -1,43 +0,0 @@ - - */ -class NodeTypeGenerator -{ - /** - * @var \PHPCR\Util\CND\Parser\SyntaxTreeNode - */ - protected $root; - - /** - * @var \PHPCR\WorkspaceInterface - */ - protected $workspace; - - /** - * @param \PHPCR\WorkspaceInterface $session - * @param \PHPCR\Util\CND\Parser\SyntaxTreeNode $root - */ - public function __construct(WorkspaceInterface $workspace, SyntaxTreeNode $root) - { - $this->workspace = $workspace; - $this->root = $root; - } - - public function generate() - { - $visitor = new CndSyntaxTreeNodeVisitor($this->workspace->getNamespaceRegistry(), $this->workspace->getNodeTypeManager()); - $this->root->accept($visitor); - return array( - 'namespaces' => $visitor->getNamespaces(), - 'nodeTypes' => $visitor->getNodeTypeDefs(), - ); - } - -} diff --git a/src/PHPCR/Util/CND/Parser/AbstractParser.php b/src/PHPCR/Util/CND/Parser/AbstractParser.php index 4f098b18..bad7128a 100644 --- a/src/PHPCR/Util/CND/Parser/AbstractParser.php +++ b/src/PHPCR/Util/CND/Parser/AbstractParser.php @@ -2,13 +2,12 @@ namespace PHPCR\Util\CND\Parser; -use PHPCR\Util\CND\Scanner\GenericToken as Token, - PHPCR\Util\CND\Scanner\TokenQueue, - PHPCR\Util\CND\Exception\ParserException, - PHPCR\Util\CND\Helper\AbstractDebuggable; +use PHPCR\Util\CND\Scanner\GenericToken as Token; +use PHPCR\Util\CND\Scanner\TokenQueue; +use PHPCR\Util\CND\Exception\ParserException; /** - * Abstract base class for parsers with debugging capabilities. + * Abstract base class for parsers * * It implements helper functions for parsers: * @@ -18,29 +17,22 @@ * * @author Daniel Barsotti */ -abstract class AbstractParser extends AbstractDebuggable implements ParserInterface +abstract class AbstractParser { /** * The token queue - * @var \LazyGuy\PhpParse\Scanner\TokenQueue + * @var TokenQueue */ protected $tokenQueue; - /** - * @param \LazyGuy\PhpParse\Scanner\TokenQueue $tokenQueue - */ - public function __construct(TokenQueue $tokenQueue) - { - $this->tokenQueue = $tokenQueue; - } - /** * Check the next token without consuming it and return true if it matches the given type and data. * If the data is not provided (equal to null) then only the token type is checked. * Return false otherwise. - * + * * @param int $type The expected token type * @param null|string $data The expected data or null + * * @return bool */ protected function checkToken($type, $data = null) @@ -62,14 +54,35 @@ protected function checkToken($type, $data = null) return true; } + /** + * Check if the token data is one of the elements of the data array. + * + * @param int $type + * @param array $data + * + * @return bool + */ + protected function checkTokenIn($type, array $data) + { + foreach ($data as $d) { + if ($this->checkToken($type, $d)) { + return true; + } + } + + return false; + } + /** * Check if the next token matches the expected type and data. If it does, then consume and return it, * otherwise throw an exception. * - * @throws \LazyGuy\PhpParse\Exception\ParserException * @param int $type The expected token type * @param null|string $data The expected token data or null - * @return \LazyGuy\PhpParse\Scanner\Token + * + * @return Token + * + * @throws ParserException */ protected function expectToken($type, $data = null) { @@ -90,7 +103,7 @@ protected function expectToken($type, $data = null) * * @param int $type The expected token type * @param null|string $data The expected token data or null - * @return bool|\LazyGuy\PhpParse\Scanner\Token + * @return bool|Token */ protected function checkAndExpectToken($type, $data = null) { diff --git a/src/PHPCR/Util/CND/Parser/CndParser.php b/src/PHPCR/Util/CND/Parser/CndParser.php index adad733e..b898d771 100644 --- a/src/PHPCR/Util/CND/Parser/CndParser.php +++ b/src/PHPCR/Util/CND/Parser/CndParser.php @@ -2,52 +2,113 @@ namespace PHPCR\Util\CND\Parser; -use PHPCR\Util\CND\Scanner\GenericToken as Token, - PHPCR\Util\CND\Scanner\TokenQueue, - PHPCR\Util\CND\Exception\ParserException; +use PHPCR\NodeType\NodeDefinitionTemplateInterface; +use PHPCR\NodeType\NodeTypeManagerInterface; +use PHPCR\NodeType\NodeTypeTemplateInterface; +use PHPCR\NodeType\PropertyDefinitionTemplateInterface; +use PHPCR\PropertyType; +use PHPCR\Util\CND\Reader\BufferReader; +use PHPCR\Util\CND\Scanner\Context\DefaultScannerContextWithoutSpacesAndComments; +use PHPCR\Util\CND\Scanner\GenericScanner; +use PHPCR\Util\CND\Scanner\GenericToken as Token; +use PHPCR\Util\CND\Exception\ParserException; +use PHPCR\Version\OnParentVersionAction; /** * Parser for JCR-2.0 CND files. * + * Implementation: + * Builds a TokenQueue containing CND statements. The parser does not expect + * any whitespaces, new lines or comments in the queue. It uses the CndScanner + * to be sure to generate a valid TokenQueue. + * * @see http://www.day.com/specs/jcr/2.0/25_Appendix.html#25.2.3 CND Grammar * @see http://jackrabbit.apache.org/node-type-notation.html * * @author Daniel Barsotti + * @author David Buchmann */ class CndParser extends AbstractParser { + // node type attributes + private $ORDERABLE = array('o', 'ord', 'orderable');//, 'variant' => true); + private $MIXIN = array('m', 'mix', 'mixin');//, 'variant' => true); + private $ABSTRACT = array('a', 'abs', 'abstract');//, 'variant' => true); + private $NOQUERY = array('noquery', 'nq');//, 'variant' => false); + private $QUERY = array('query', 'q');//, 'variant' => false); + private $PRIMARYITEM = array('primaryitem', '!');//, 'variant' => false); + + // common for properties and child definitions + private $AUTOCREATED = array('a', 'aut', 'autocreated'); //, 'variant' => true), + private $MANDATORY = array('m', 'man', 'mandatory'); //, 'variant' => true), + private $PROTECTED = array('p', 'pro', 'protected'); //, 'variant' => true), + private $OPV = array('COPY', 'VERSION', 'INITIALIZE', 'COMPUTE', 'IGNORE', 'ABORT'); + + // property type attributes + private $MULTIPLE = array('*', 'mul', 'multiple'); //, 'variant' => true), + private $QUERYOPS = array('qop', 'queryops'); //, 'variant' => true), // Needs special handling ! + private $NOFULLTEXT = array('nof', 'nofulltext'); //, 'variant' => true), + private $NOQUERYORDER = array('nqord', 'noqueryorder'); //, 'variant' => true), + + // child node attributes + private $SNS = array('*', 'sns'); //, 'variant' => true), + /** - * Parse a TokenQueue containing CND statements. This parser does not expect any whitespaces, new lines - * or comments in the queue. Please use the CndScanner to be sure to generate a valid TokenQueue. - * - * This function returns an array of node type definition in array representation. - * It does not actually return and array of PHPCR\NodeType ! + * @var NodeTypeManagerInterface + */ + private $ntm; + + /** + * @var array + */ + protected $namespaces = array(); + + /** + * @var array + */ + protected $nodeTypes = array(); + + /** + * @param NodeTypeManagerInterface $ntm + */ + public function __construct(NodeTypeManagerInterface $ntm) + { + $this->ntm = $ntm; + } + + /** + * Parse a string of CND statements. * - * @return SyntaxTreeNode + * @return array with the namespaces map and the nodeTypes which is a list + * of NodeTypeDefinitionInterface */ - public function parse() + public function parseString($cnd) { - $root = new SyntaxTreeNode('root'); - $nsMapping = new SyntaxTreeNode('nsMappings'); - $nodeTypes = new SyntaxTreeNode('nodeTypes'); - $root->addChild($nsMapping); - $root->addChild($nodeTypes); + $reader = new BufferReader($cnd); + $scanner = new GenericScanner(new DefaultScannerContextWithoutSpacesAndComments()); + $this->tokenQueue = $scanner->scan($reader); - while (!$this->tokenQueue->isEof()) { + return $this->parse(); + } - $this->debugSection('PARSER CYCLE'); + private function parse() + { + while (!$this->tokenQueue->isEof()) { while ($this->checkToken(Token::TK_SYMBOL, '<')) { - $nsMapping->addChild($this->parseNamespaceMapping()); + $this->parseNamespaceMapping(); } if (!$this->tokenQueue->isEof()) { - $nodeTypes->addChild($this->parseNodeTypeDef()); + $this->parseNodeType(); } } - return $root; + return array( + 'namespaces' => $this->namespaces, + 'nodeTypes' => $this->nodeTypes, + ); } /** @@ -59,77 +120,55 @@ public function parse() * NamespaceMapping ::= '<' Prefix '=' Uri '>' * Prefix ::= String * Uri ::= String - * - * @return SyntaxTreeNode */ protected function parseNamespaceMapping() { - $this->debug('parseNamespaceMapping'); - $this->expectToken(Token::TK_SYMBOL, '<'); $prefix = $this->parseCndString(); $this->expectToken(Token::TK_SYMBOL, '='); $uri = substr($this->expectToken(Token::TK_STRING)->getData(), 1, -1); $this->expectToken(Token::TK_SYMBOL, '>'); - $this->debugRes("nsmapping: $prefix => $uri"); - - return new SyntaxTreeNode('nsMapping', array('prefix' => $prefix, 'uri' => $uri)); + $this->namespaces[$prefix] = $uri; } /** * A node type definition consists of a node type name followed by an optional * supertypes block, an optional node type attributes block and zero or more * blocks, each of which is either a property or child node definition. - * + * * NodeTypeDef ::= NodeTypeName [Supertypes] * [NodeTypeAttribute {NodeTypeAttribute}] * {PropertyDef | ChildNodeDef} - * - * @return SyntaxTreeNode */ - protected function parseNodeTypeDef() + protected function parseNodeType() { - $this->debug('parseNodeTypeDef'); - - $node = new SyntaxTreeNode('nodeTypeDef'); - $node->addChild($this->parseNodeTypeName()); + $nodeType = $this->ntm->createNodeTypeTemplate(); + $this->parseNodeTypeName($nodeType); if ($this->checkToken(Token::TK_SYMBOL, '>')) { - $node->addChild($this->parseSupertypes()); + $this->parseSupertypes($nodeType); } - if ($attrNode = $this->parseNodeTypeAttribues()){ - $node->addChild($attrNode); - } + $this->parseNodeTypeAttributes($nodeType); - if ($children = $this->parseChildDefs()) { - foreach($children as $child) { - $node->addChild($child); - } - } + $this->parseChildrenAndAttributes($nodeType); - return $node; + $this->nodeTypes[] = $nodeType; } /** * The node type name is delimited by square brackets and must be a valid JCR name. - * + * * NodeTypeName ::= '[' String ']' - * - * @return SyntaxTreeNode */ - protected function parseNodeTypeName() + protected function parseNodeTypeName(NodeTypeTemplateInterface $nodeType) { - $this->debug('parseNodeTypeName'); - $this->expectToken(Token::TK_SYMBOL, '['); $name = $this->parseCndString(); $this->expectToken(Token::TK_SYMBOL, ']'); - $this->debugRes("nodeTypeName: $name"); - - return new SyntaxTreeNode('nodeTypeName', array('value' => $name)); + $nodeType->setName($name); } /** @@ -139,29 +178,16 @@ protected function parseNodeTypeName() * is absent. A question mark indicates that the supertypes list is a variant. * * Supertypes ::= '>' (StringList | '?') - * - * @return SyntaxTreeNode */ - protected function parseSupertypes() + protected function parseSupertypes(NodeTypeTemplateInterface $nodeType) { - $this->debug('parseSupertypes'); - $this->expectToken(Token::TK_SYMBOL, '>'); - $supertypes = new SyntaxTreeNode('supertypes'); - if ($this->checkAndExpectToken(Token::TK_SYMBOL, '?')) { - $supertypes->setProperty('value', '?'); - $display = '?'; + $nodeType->setDeclaredSuperTypeNames('?'); } else { - $list = $this->parseCndStringList(); - $supertypes->setProperty('value', $list); - $display = join(', ', $list); + $nodeType->setDeclaredSuperTypeNames($this->parseCndStringList()); } - - $this->debugRes(sprintf('supertypes: (%s)', $display)); - - return $supertypes; } /** @@ -195,51 +221,59 @@ protected function parseSupertypes() * Abstract ::= ('abstract' | 'abs' | 'a') ['?'] * Query ::= ('noquery' | 'nq') | ('query' | 'q' ) * PrimaryItem ::= ('primaryitem'| '!')(String | '?') - * - * @return SyntaxTreeNode */ - protected function parseNodeTypeAttribues() + protected function parseNodeTypeAttributes(NodeTypeTemplateInterface $nodeType) { - $this->debug('parseNodeTypeAttributes'); - return $this->parseAttributes('nodeTypeAttributes', $this->getNodeTypeAttributes()); + while (true) { + if ($this->checkTokenIn(Token::TK_IDENTIFIER, $this->ORDERABLE)) { + $nodeType->setOrderableChildNodes(true); + } else if ($this->checkTokenIn(Token::TK_IDENTIFIER, $this->MIXIN)) { + $nodeType->setMixin(true); + } else if ($this->checkTokenIn(Token::TK_IDENTIFIER, $this->ABSTRACT)) { + $nodeType->setAbstract(true); + } else if ($this->checkTokenIn(Token::TK_IDENTIFIER, $this->NOQUERY)) { + $nodeType->setQueryable(false); + } else if ($this->checkTokenIn(Token::TK_IDENTIFIER, $this->QUERY)) { + $nodeType->setQueryable(true); + } else if ($this->checkTokenIn(Token::TK_IDENTIFIER, $this->PRIMARYITEM)) { + /* + * If 'primaryitem' is present without a '?' then the string following it is + * the name of the primary item of the node type. + * If 'primaryitem' is present with a '?' then the primary item is a variant. + * If 'primaryitem' is absent then the node type has no primary item. + * + * PrimaryItem ::= ('primaryitem'| '!')(String | '?') + */ + if ($this->checkAndExpectToken(Token::TK_SYMBOL, '?')) { + $nodeType->setPrimaryItemName('?'); + } else { + $this->tokenQueue->next(); + $nodeType->setPrimaryItemName($this->parseCndString()); + continue; + } + } else { + return; + } + $this->tokenQueue->next(); + } } /** * Parse both the children propery and nodes definitions * * {PropertyDef | ChildNodeDef} - * - * @return SyntaxTreeNode */ - protected function parseChildDefs() + protected function parseChildrenAndAttributes(NodeTypeTemplateInterface $nodeType) { - $this->debug('parseChildDefs'); - - $propDefs = new SyntaxTreeNode('propertyDefs'); - $childNodeDef = new SyntaxTreeNode('childNodeDefs'); - while (true) { - if ($this->checkToken(Token::TK_SYMBOL, '-')) { - $propDefs->addChild($this->parsePropDef()); + $this->parsePropDef($nodeType); } elseif ($this->checkToken(Token::TK_SYMBOL, '+')) { - $childNodeDef->addChild($this->parseChildNodeDef()); + $this->parseChildNodeDef($nodeType); } else { - break; + return; } } - - // Only return the nodes that actually have children - $children = array(); - - if ($propDefs->hasChildren()) { - $children[] = $propDefs; - } - if ($childNodeDef->hasChildren()) { - $children[] = $childNodeDef; - } - - return $children; } /** @@ -254,48 +288,48 @@ protected function parseChildDefs() * [PropertyAttribute {PropertyAttribute}] * [ValueConstraints] * PropertyName ::= '-' String - * - * @return SyntaxTreeNode */ - protected function parsePropDef() + protected function parsePropDef(NodeTypeTemplateInterface $nodeType) { - $this->debug('parsePropDef'); + $this->expectToken(Token::TK_SYMBOL, '-'); - $node = new SyntaxTreeNode('propertyDef'); + $property = $this->ntm->createPropertyDefinitionTemplate(); + $property->setAutoCreated(false); + $property->setMandatory(false); + $property->setMultiple(false); + $property->setOnParentVersion(OnParentVersionAction::COPY); + $property->setProtected(false); + $property->setRequiredType(PropertyType::STRING); + $property->setFullTextSearchable(true); + $property->setQueryOrderable(true); + $nodeType->getPropertyDefinitionTemplates()->append($property); // Parse the property name - $this->expectToken(Token::TK_SYMBOL, '-'); if ($this->checkAndExpectToken(Token::TK_SYMBOL, '*')) { - $name = '*'; + $property->setName('*'); } else { - $name = $this->parseCndString(); + $property->setName($this->parseCndString()); } - $node->addChild(new SyntaxTreeNode('propertyName', array('value' => $name))); // Parse the property type - if ($this->checkToken(Token::TK_SYMBOL, '(')) { - $node->addChild($this->parsePropertyType()); + if ($this->checkAndExpectToken(Token::TK_SYMBOL, '(')) { + $this->parsePropertyType($property); } // Parse default value - if ($this->checkToken(Token::TK_SYMBOL, '=')) { - $node->addChild($this->parseDefaultValue()); + if ($this->checkAndExpectToken(Token::TK_SYMBOL, '=')) { + $this->parseDefaultValue($property); } - if ($attrNode = $this->parsePropertyAttributes()) { - $node->addChild($attrNode); - } + $this->parsePropertyAttributes($property); + // Check if there is a constraint (and not another namespace def) // Next token is '<' and two token later it's not '=', i.e. not 'tokenQueue->peek(); $next2 = $this->tokenQueue->peek(2); if ($next1 && $next1->getData() === '<' && (!$next2 || $next2->getData() !== '=')) { - $node->addChild($this->parseValueConstraints()); + $this->parseValueConstraints($property); } - - $this->debugRes('propertyName: ' . $name); - - return $node; } /** @@ -308,29 +342,21 @@ protected function parsePropDef() * 'REFERENCE' | 'WEAKREFERENCE' | * 'DECIMAL' | 'URI' | 'UNDEFINED' | '*' | * '?') ')' - * - * @return SyntaxTreeNode */ - protected function parsePropertyType() + protected function parsePropertyType(PropertyDefinitionTemplateInterface $property) { - $this->debug('parsePropertyType'); - - // TODO: can the property be lowercase or camelcase as in old spec? $types = array("STRING", "BINARY", "LONG", "DOUBLE", "BOOLEAN", "DATE", "NAME", "PATH", "REFERENCE", "WEAKREFERENCE", "DECIMAL", "URI", "UNDEFINED", "*", "?"); - $this->expectToken(Token::TK_SYMBOL, '('); + if (! $this->checkTokenIn(Token::TK_IDENTIFIER, $types)) { + throw new ParserException($this->tokenQueue, sprintf("Invalid property type: %s", $this->tokenQueue->get()->getData())); + } $data = $this->tokenQueue->get()->getData(); - if (!in_array($data, $types)) { - throw new ParserException($this->tokenQueue, sprintf("Invalid property type: %s", $data)); - } $this->expectToken(Token::TK_SYMBOL, ')'); - $this->debugRes('propertyType: ' . $data); - - return new SyntaxTreeNode('propertyType', array('value' => $data)); + $property->setRequiredType(PropertyType::valueFromName($data)); } /** @@ -340,25 +366,16 @@ protected function parsePropertyType() * indicates that this attribute is a variant * * DefaultValues ::= '=' (StringList | '?') - * - * @return SyntaxTreeNode */ - protected function parseDefaultValue() + protected function parseDefaultValue(PropertyDefinitionTemplateInterface $property) { - $this->debug('parseDefaultValues'); - - // TODO: parse ? - $this->expectToken(Token::TK_SYMBOL, '='); - if ($this->checkAndExpectToken(Token::TK_SYMBOL, '?')) { $list = array('?'); } else { $list = $this->parseCndStringList(); } - $this->debugRes(sprintf('defaultValues: (%s)', join(', ', $list))); - - return new SyntaxTreeNode('defaultValues', array('value' => $list)); + $property->setDefaultValues($list); } /** @@ -367,13 +384,9 @@ protected function parseDefaultValue() * value constraint syntax. A '?' indicates that this attribute is a variant * * ValueConstraints ::= '<' (StringList | '?') - * - * @return SyntaxTreeNode */ - protected function parseValueConstraints() + protected function parseValueConstraints(PropertyDefinitionTemplateInterface $property) { - $this->debug('parseValueConstraints'); - $this->expectToken(Token::TK_SYMBOL, '<'); if ($this->checkAndExpectToken(Token::TK_SYMBOL, '?')) { @@ -382,9 +395,7 @@ protected function parseValueConstraints() $list = $this->parseCndStringList(); } - $this->debugRes(sprintf('valueConstraints: (%s)', join(', ', $list))); - - return new SyntaxTreeNode('valueConstraints', array('value' => $list)); + $property->setValueConstraints($list); } /** @@ -425,7 +436,7 @@ protected function parseValueConstraints() * by this property. * If 'noqueryorder' is present with a '?' then this attribute is a variant. * If 'noqueryorder' is absent then query results can be ordered by this property. - * + * * PropertyAttribute ::= Autocreated | Mandatory | Protected | * Opv | Multiple | QueryOps | NoFullText | * NoQueryOrder @@ -440,18 +451,92 @@ protected function parseValueConstraints() * Operator ::= '=' | '<>' | '<' | '<=' | '>' | '>=' | 'LIKE' * NoFullText ::= ('nofulltext' | 'nof') ['?'] * NoQueryOrder ::= ('noqueryorder' | 'nqord') ['?'] + */ + protected function parsePropertyAttributes(PropertyDefinitionTemplateInterface $property) + { + $opvSeen = false; + while (true) { + if ($this->checkTokenIn(Token::TK_IDENTIFIER, $this->AUTOCREATED)) { + $property->setAutoCreated(true); + } else if ($this->checkTokenIn(Token::TK_IDENTIFIER, $this->MANDATORY)) { + $property->setMandatory(true); + } else if ($this->checkTokenIn(Token::TK_IDENTIFIER, $this->PROTECTED)) { + $property->setProtected(true); + } else if ($this->checkTokenIn(Token::TK_IDENTIFIER, $this->MULTIPLE)) { + $property->setMultiple(true); + } else if ($this->checkTokenIn(Token::TK_IDENTIFIER, $this->QUERYOPS)) { + $property->setAvailableQueryOperators($this->parseQueryOpsAttribute()); + } else if ($this->checkTokenIn(Token::TK_IDENTIFIER, $this->NOFULLTEXT)) { + $property->setFullTextSearchable(false); + } else if ($this->checkTokenIn(Token::TK_IDENTIFIER, $this->NOQUERYORDER)) { + $property->setQueryOrderable(false); + } else if ($this->checkTokenIn(Token::TK_IDENTIFIER, $this->OPV)) { + if ($opvSeen) { + throw new ParserException($this->tokenQueue, 'More than one on parent version action specified on property ' . $property->getName()); + } + $token = $this->tokenQueue->get(); + $property->setOnParentVersion(OnParentVersionAction::valueFromName($token->getData())); + $opvSeen = true; + continue; + } else { + return; + } + $this->tokenQueue->next(); + } + } + + /** + * A child node definition consists of a node name element followed by optional + * required node types, default node types and node attributes elements. + * + * The node name, or '*' to indicate a residual property definition, is prefixed + * with a '+'. + * + * The required primary node type list is delimited by parentheses. If this + * element is missing then a required primary node type of nt:base is assumed. + * A '?' indicates that the this attribute is a variant. * - * @return SyntaxTreeNode + * ChildNodeDef ::= NodeName [RequiredTypes] [DefaultType] + * [NodeAttribute {NodeAttribute}] + * NodeName ::= '+' String + * RequiredTypes ::= '(' (StringList | '?') ')' + * DefaultType ::= '=' (String | '?') */ - protected function parsePropertyAttributes() + protected function parseChildNodeDef(NodeTypeTemplateInterface $nodeType) { - $this->debug('parsePropertyAttributes'); - return $this->parseAttributes('propertyTypeAttributes', $this->getPropertyTypeAttributes()); + $this->expectToken(Token::TK_SYMBOL, '+'); + $childType = $this->ntm->createNodeDefinitionTemplate(); + $nodeType->getNodeDefinitionTemplates()->append($childType); + + // Parse the property name + if ($this->checkAndExpectToken(Token::TK_SYMBOL, '*')) { + $childType->setName('*'); + } else { + $childType->setName($this->parseCndString()); + } + + // Parse the required types + if ($this->checkAndExpectToken(Token::TK_SYMBOL, '(')) { + if ($this->checkAndExpectToken(Token::TK_SYMBOL, '?')) { + $list = '?'; + } else { + $list = $this->parseCndStringList(); + } + $this->expectToken(Token::TK_SYMBOL, ')'); + $childType->setRequiredPrimaryTypeNames($list); + } + + // Parse the default type + if ($this->checkAndExpectToken(Token::TK_SYMBOL, '=')) { + $childType->setDefaultPrimaryTypeName($this->parseCndString()); + } + + $this->parseChildNodeAttributes($nodeType, $childType); } /** * The node attributes are indicated by the presence or absence of keywords. - * + * * If 'autocreated' is present without a '?' then the item is autocreated. * If 'autocreated' is present with a '?' then the autocreated status is a variant. * If 'autocreated' is absent then the item is not autocreated. @@ -482,11 +567,11 @@ protected function parsePropertyAttributes() * Opv ::= 'COPY' | 'VERSION' | 'INITIALIZE' | 'COMPUTE' | * 'IGNORE' | 'ABORT' | ('OPV' '?') * Sns ::= ('sns' | '*') ['?'] - * - * @return SyntaxTreeNode */ - protected function parseNodeAttributes() - { + protected function parseChildNodeAttributes( + NodeTypeTemplateInterface $parentType, + NodeDefinitionTemplateInterface $childType + ) { /** * TODO: Clarify this problem * @@ -499,69 +584,27 @@ protected function parseNodeAttributes() * or there is an error in the spec that says that a node attribute cannot be * "multiple". */ - $this->debug('parseNodeAttributes'); - return $this->parseAttributes('nodeAttributes', $this->getNodeAttributes()); - } - - /** - * A child node definition consists of a node name element followed by optional - * required node types, default node types and node attributes elements. - * - * The node name, or '*' to indicate a residual property definition, is prefixed - * with a '+'. - * - * The required primary node type list is delimited by parentheses. If this - * element is missing then a required primary node type of nt:base is assumed. - * A '?' indicates that the this attribute is a variant. - * - * ChildNodeDef ::= NodeName [RequiredTypes] [DefaultType] - * [NodeAttribute {NodeAttribute}] - * NodeName ::= '+' String - * RequiredTypes ::= '(' (StringList | '?') ')' - * DefaultType ::= '=' (String | '?') - * - * @return SyntaxTreeNode - */ - protected function parseChildNodeDef() - { - $this->debug('parseChildNodeDef'); - - $node = new SyntaxTreeNode('childNodeDef'); - - $this->expectToken(Token::TK_SYMBOL, '+'); - - // Parse the property name - if ($this->checkAndExpectToken(Token::TK_SYMBOL, '*')) { - $name = '*'; - } else { - $name = $this->parseCndString(); - } - $node->addChild(new SyntaxTreeNode('nodeName', array('value' => $name))); - - // Parse the required types - if ($this->checkAndExpectToken(Token::TK_SYMBOL, '(')) { - if ($this->checkAndExpectToken(Token::TK_SYMBOL, '?')) { - $list = '?'; + while(true) { + if ($this->checkTokenIn(Token::TK_IDENTIFIER, $this->PRIMARYITEM)) { + $parentType->setPrimaryItemName($childType->getName()); + } else if ($this->checkTokenIn(Token::TK_IDENTIFIER, $this->AUTOCREATED)) { + $childType->setAutoCreated(true); + } else if ($this->checkTokenIn(Token::TK_IDENTIFIER, $this->MANDATORY)) { + $childType->setMandatory(true); + } else if ($this->checkTokenIn(Token::TK_IDENTIFIER, $this->PROTECTED)) { + $childType->setProtected(true); + } else if ($this->checkTokenIn(Token::TK_IDENTIFIER, $this->SNS)) { + $childType->setSameNameSiblings(true); + } else if ($this->checkTokenIn(Token::TK_IDENTIFIER, $this->OPV)) { + $token = $this->tokenQueue->get(); + $childType->setOnParentVersion(OnParentVersionAction::valueFromName($token->getData())); + continue; } else { - $list = $this->parseCndStringList(); + return; } - $this->expectToken(Token::TK_SYMBOL, ')'); - - $node->addChild(new SyntaxTreeNode('requiredTypes', array('value' => $list))); - } - // Parse the default type - if ($this->checkAndExpectToken(Token::TK_SYMBOL, '=')) { - $node->addChild(new SyntaxTreeNode('defaultType', array('value' => $this->parseCndString()))); - } - - if ($attrNode = $this->parseNodeAttributes()) { - $node->addChild($attrNode); + $this->tokenQueue->next(); } - - $this->debugRes('childNodeName: ' . $name); - - return $node; } /** @@ -573,8 +616,6 @@ protected function parseChildNodeDef() */ protected function parseCndStringList() { - $this->debug('parseCndStringList'); - $strings = array(); $strings[] = $this->parseCndString(); @@ -582,8 +623,6 @@ protected function parseCndStringList() $strings[] = $this->parseCndString(); } - $this->debugRes(sprintf('string-list: (%s)', join(', ', $strings))); - return $strings; } @@ -610,20 +649,15 @@ protected function parseCndStringList() */ protected function parseCndString() { - $this->debug('parseCndString'); - - // TODO: adapt - $string = ''; $lastType = null; while (true) { - $token = $this->tokenQueue->peek(); $type = $token->getType(); $data = $token->getData(); - IF ($type === Token::TK_STRING) { + if ($type === Token::TK_STRING) { $string = substr($data, 1, -1); $this->tokenQueue->next(); return $string; @@ -650,212 +684,9 @@ protected function parseCndString() throw new ParserException($this->tokenQueue, sprintf("Expected CND string, found '%s': ", $this->tokenQueue->peek()->getData())); } - $this->debugRes(sprintf('string: %s', $string)); - return $string; } - /** - * Return an array representing the allowed node type attributes. - * The values are the aliases for the attribute. - * Variant indicates if the attribute can be variant (followed by a ?) - * Some attributes will need special handling in the parsing function. - * - * @return array - */ - protected function getNodeTypeAttributes() - { - return array( - 'orderable' => array('values' => array('o', 'ord', 'orderable'), 'variant' => true), - 'mixin' => array('values' => array('m', 'mix', 'mixin'), 'variant' => true), - 'abstract' => array('values' => array('a', 'abs', 'abstract'), 'variant' => true), - 'noquery' => array('values' => array('noquery', 'nq'), 'variant' => false), - 'query' => array('values' => array('query', 'q'), 'variant' => false), - 'primaryitem' => array('values' => array('primaryitem', '!'), 'variant' => false), // Needs special handling ! - ); - } - - /** - * Return an array representing the commonly allowed attributes for nodes and property types. - * The values are the aliases for the attribute. - * Variant indicates if the attribute can be variant (followed by a ?) - * Some attributes will need special handling in the parsing function. - * - * @return array - */ - protected function getCommonAttributes() - { - return array( - 'autocreated' => array('values' => array('a', 'aut', 'autocreated'), 'variant' => true), - 'mandatory' => array('values' => array('m', 'man', 'mandatory'), 'variant' => true), - 'protected' => array('values' => array('p', 'pro', 'protected'), 'variant' => true), - 'COPY' => array('values' => array('COPY'), 'variant' => false), - 'VERSION' => array('values' => array('VERSION'), 'variant' => false), - 'INITIALIZE' => array('values' => array('INITIALIZE'), 'variant' => false), - 'COMPUTE' => array('values' => array('COMPUTE'), 'variant' => false), - 'IGNORE' => array('values' => array('IGNORE'), 'variant' => false), - 'ABORT' => array('values' => array('ABORT'), 'variant' => false), - 'OPV' => array('values' => array('OPV'), 'variant' => true), - ); - } - - /** - * Return an array representing the allowed property type attributes. - * The values are the aliases for the attribute. - * Variant indicates if the attribute can be variant (followed by a ?) - * Some attributes will need special handling in the parsing function. - * - * @return array - */ - protected function getPropertyTypeAttributes() - { - return array_merge( - $this->getCommonAttributes(), - array( - 'multiple' => array('values' => array('*', 'mul', 'multiple'), 'variant' => true), - 'queryops' => array('values' => array('qop', 'queryops'), 'variant' => true), // Needs special handling ! - 'nofulltext' => array('values' => array('nof', 'nofulltext'), 'variant' => true), - 'noqueryorder' => array('values' => array('nqord', 'noqueryorder'), 'variant' => true), - ) - ); - } - - /** - * Return an array representing the allowed node attributes. - * The values are the aliases for the attribute. - * Variant indicates if the attribute can be variant (followed by a ?) - * Some attributes will need special handling in the parsing function. - * - * @return array - */ - protected function getNodeAttributes() - { - return array_merge( - $this->getCommonAttributes(), - array( - 'sns' => array('values' => array('*', 'sns'), 'variant' => true), - ) - ); - } - - /** - * Parse a list of attributes. - * The allowed attributes must be specified in $attributes. - * The type of the list must be given (node type attributes, property type attributes or node attributes). - * - * @param string $type - * @param array $attributes - * @return SyntaxTreeNode - */ - protected function parseAttributes($type, $attributes) - { - $this->debug('parseAttributes'); - - $node = new SyntaxTreeNode($type); - - $options = array(); - - while ($attrNode = $this->parseAttribute($attributes)) { - $node->addChild($attrNode); - $options[] = $attrNode->getType(); - } - - $this->debugRes(sprintf('%s: (%s)', $type, join(', ', $options))); - - if (empty($options)) { - return false; - } - - return $node; - } - - /** - * Parse a single attribute. - * The allowed attributes must be given in $attributes. - * Return the attribute if any was found or false. - * - * @param array $attributes - * @return bool|SyntaxTreeNode - */ - protected function parseAttribute($attributes) - { - $this->debug('parseAttribute'); - - $token = $this->tokenQueue->peek(); - if (!$token) { - return false; - } - $data = $token->getData(); - - foreach ($attributes as $name => $def) { - - if (in_array($data, $def['values'])) { - - // Node type attribute found - $this->tokenQueue->next(); - - // Handle special cases - if ($attribute = $this->parseSpecialCaseAttribute($name)) { - return $attribute; - } - - // If this attribute can ba variant - if ($def['variant']) { - if ($this->checkAndExpectToken(Token::TK_SYMBOL, '?')) { - $variant = true; - } - } - - $node = new SyntaxTreeNode($name); - if (isset($variant)) { - $node->setProperty('variant', true); - } - return $node; - } - - } - - return false; - } - - /** - * Some attributes need special handling to be parsed, they are managed in - * this function. Return the attribute if any was found or false. - * - * At this point the attribute token has already been removed from the queue. - * - * @param string $attributeName - * @return bool|SyntaxTreeNode - */ - protected function parseSpecialCaseAttribute($attributeName) - { - $this->debug('parseSpecialCaseAttribute'); - - if ($attributeName === 'primaryitem') { - - /** - * If 'primaryitem' is present without a '?' then the string following it is - * the name of the primary item of the node type. - * If 'primaryitem' is present with a '?' then the primary item is a variant. - * If 'primaryitem' is absent then the node type has no primary item. - * - * PrimaryItem ::= ('primaryitem'| '!')(String | '?') - */ - - if ($this->checkAndExpectToken(Token::TK_SYMBOL, '?')) { - return new SyntaxTreeNode('primaryitem', array('value' => '?')); - } - return new SyntaxTreeNode('primaryitem', array('value' => $this->parseCndString())); - } - - if ($attributeName === 'queryops') { - - return $this->parseQueryOpsAttribute(); - } - - return false; - } - /** * The available query comparison operators are listed after the keyword 'queryops'. * If 'queryops' is followed by a '?' then this attribute is a variant. @@ -865,12 +696,13 @@ protected function parseSpecialCaseAttribute($attributeName) * (('''Operator {','Operator}''') | '?') * Operator ::= '=' | '<>' | '<' | '<=' | '>' | '>=' | 'LIKE' * - * @return SyntaxTreeNode + * @return array */ protected function parseQueryOpsAttribute() { if ($this->checkAndExpectToken(Token::TK_SYMBOL, '?')) { - return new SyntaxTreeNode('queryops', array('variant' => true)); + // this denotes a variant, whatever that is + throw new ParserException($this->tokenQueue, 'TODO: understand what "variant" means'); } $ops = array(); @@ -886,15 +718,12 @@ protected function parseQueryOpsAttribute() throw new ParserException($this->tokenQueue, 'Operator expected'); } - return new SyntaxTreeNode('queryops', array('value' => $ops)); + return $ops; } /** * Parse a query operator. * - * This is quite complicated for not so much... Idealy this should be implemented - * in a specialized Scanner. - * * @return bool|string */ protected function parseQueryOperator() diff --git a/src/PHPCR/Util/CND/Parser/ParserInterface.php b/src/PHPCR/Util/CND/Parser/ParserInterface.php deleted file mode 100644 index 496984a5..00000000 --- a/src/PHPCR/Util/CND/Parser/ParserInterface.php +++ /dev/null @@ -1,14 +0,0 @@ - - */ -interface ParserInterface -{ - /** - * @return SyntaxTreeNode The root node of the syntax tree - */ - function parse(); -} diff --git a/src/PHPCR/Util/CND/Parser/SyntaxTreeNode.php b/src/PHPCR/Util/CND/Parser/SyntaxTreeNode.php deleted file mode 100644 index dd3a274f..00000000 --- a/src/PHPCR/Util/CND/Parser/SyntaxTreeNode.php +++ /dev/null @@ -1,209 +0,0 @@ - - */ -class SyntaxTreeNode -{ - /** - * The type of the node - * @var string - */ - protected $type; - - /** - * The properties of the node - * @var array - */ - protected $properties; - - /** - * The child nodes - * @var array - */ - protected $children; - - /** - * @param string $type - */ - public function __construct($type, $properties = array()) - { - $this->type = $type; - $this->properties = $properties; - $this->children = array(); - } - - /** - * @param string $name - * @param string $value - * @return void - */ - public function setProperty($name, $value) - { - $this->properties[$name] = $value; - } - - /** - * @param SyntaxTreeNode $child - * @return void - */ - public function addChild(SyntaxTreeNode $child) - { - $this->children[] = $child; - } - - /** - * Make the $visitor visit this node and it's children. - * The visitor may return the value boolean false (strict equality) to indicate to not visit the children. - * @param SyntaxTreeVisitorInterface $visitor - * @return void - */ - public function accept(SyntaxTreeVisitorInterface $visitor) - { - if (false !== $visitor->visit($this)) { - foreach($this->children as $child) { - $child->accept($visitor); - } - } - } - - /** - * @return string - */ - public function getType() - { - return $this->type; - } - - /** - * @return array - */ - public function getProperties() - { - return $this->properties; - } - - /** - * @param string $name - * @return bool - */ - public function hasProperty($name) - { - return array_key_exists($name, $this->properties); - } - - /** - * @throws \InvalidArgumentException - * @param string $name - * @return mixed - */ - public function getProperty($name) - { - if (!array_key_exists($name, $this->properties)) { - throw new \InvalidArgumentException("No property '$name' found."); - } - - return $this->properties[$name]; - } - - /** - * Get all the children - * @return array - */ - public function getChildren() - { - return $this->children; - } - - /** - * Return all the children of a given type - * @param $type - * @return array - */ - public function getChildrenByType($type) - { - $children = array(); - foreach ($this->children as $child) { - if ($child->getType() === $type) { - $children[] = $child; - } - } - return $children; - } - - /** - * Return the first child with the given type or false if none - * @param $type - * @return array - */ - public function getFirstChildByType($type) - { - foreach ($this->children as $child) { - if ($child->getType() === $type) { - return $child; - } - } - return false; - } - - /** - * Return true if the node has child nodes and false otherwise - * @return bool - */ - public function hasChildren() - { - return !empty($this->children); - } - - /** - * Return true if the node has at least one child of the given type - * @param string $type - * @return bool - */ - public function hasChild($type) - { - foreach ($this->children as $child) { - if ($child->getType() === $type) { - return true; - } - } - return false; - } - - /** - * Return a dumped version of the tree - * - * @param bool $compact If true, less spacing is added to the dump - * @param int $level Internal parameter used to indicate the current indentation level - * @return string - */ - public function dump($compact = false, $level = 0) - { - $dump = ''; - $indent = str_repeat(' ', $level); - $dump .= sprintf("%sNODE[%s]%s\n", $indent, $this->type, !empty($this->properties) || !empty($this->children) ? ':' : ''); - - foreach ($this->properties as $key => $value) { - if (is_array($value)) { - $value = sprintf('(%s)', join(', ', $value)); - } - $dump .= sprintf("%s - %s = %s\n", $indent, $key, $value); - } - - if (!$compact && $this->hasChildren()) { - $dump .= "\n"; - } - - foreach ($this->children as $child) { - $dump .= $child->dump($compact, $level + 1); - } - - if (!$compact && !$this->hasChildren()) { - $dump .= "\n"; - } - - return $dump; - } -} diff --git a/src/PHPCR/Util/CND/Parser/SyntaxTreeVisitorInterface.php b/src/PHPCR/Util/CND/Parser/SyntaxTreeVisitorInterface.php deleted file mode 100644 index a304e5e3..00000000 --- a/src/PHPCR/Util/CND/Parser/SyntaxTreeVisitorInterface.php +++ /dev/null @@ -1,11 +0,0 @@ - - */ -interface SyntaxTreeVisitorInterface -{ - function visit(SyntaxTreeNode $node); -} \ No newline at end of file diff --git a/src/PHPCR/Util/CND/Reader/BufferReader.php b/src/PHPCR/Util/CND/Reader/BufferReader.php index fbbdfc0f..558372f0 100644 --- a/src/PHPCR/Util/CND/Reader/BufferReader.php +++ b/src/PHPCR/Util/CND/Reader/BufferReader.php @@ -61,14 +61,6 @@ function getCurrentColumn() return $this->curCol; } - /** - * @return string - */ - function getBuffer() - { - return $this->buffer; - } - /** * Return the literal delimited by start and end position * @return string @@ -122,14 +114,6 @@ public function rewind() $this->nextCurCol = $this->curCol; } - public function unget() - { - if ($this->forwardPos > $this->startPos) { - $this->forwardPos--; - $this->nextCurCol--; - } - } - public function consume() { $current = $this->current(); diff --git a/src/PHPCR/Util/CND/Reader/ReaderInterface.php b/src/PHPCR/Util/CND/Reader/ReaderInterface.php index 1578da26..79784ea8 100644 --- a/src/PHPCR/Util/CND/Reader/ReaderInterface.php +++ b/src/PHPCR/Util/CND/Reader/ReaderInterface.php @@ -13,19 +13,21 @@ interface ReaderInterface public function getEofMarker(); /** - * @return int + * @return string with just one character */ - function getCurrentLine(); + public function currentChar(); + + public function isEof(); /** * @return int */ - function getCurrentColumn(); + function getCurrentLine(); /** - * @return string + * @return int */ - function getBuffer(); + function getCurrentColumn(); /** * Return the literal delimited by start and end position @@ -39,6 +41,8 @@ public function current(); */ public function forward(); + public function forwardChar(); + /** * Rewind the forward position to the start position * @return void @@ -46,15 +50,10 @@ public function forward(); public function rewind(); /** - * Rewind the forward position to its previous position - * @return void - */ - public function unget(); - - /** - * Return the literal delimited by start and end position, then set the start position to the end position - * @return void + * Return the literal delimited by start and end position, then set the + * start position to the end position + * + * @return string */ public function consume(); - } diff --git a/src/PHPCR/Util/CND/Scanner/AbstractScanner.php b/src/PHPCR/Util/CND/Scanner/AbstractScanner.php index d9577685..ba67fc42 100644 --- a/src/PHPCR/Util/CND/Scanner/AbstractScanner.php +++ b/src/PHPCR/Util/CND/Scanner/AbstractScanner.php @@ -2,14 +2,16 @@ namespace PHPCR\Util\CND\Scanner; -use PHPCR\Util\CND\Helper\AbstractDebuggable, - PHPCR\Util\CND\Reader\ReaderInterface; +use PHPCR\Util\CND\Reader\ReaderInterface; /** * @author Daniel Barsotti */ -abstract class AbstractScanner extends AbstractDebuggable implements ScannerInterface +abstract class AbstractScanner { + /** + * @var TokenQueue + */ private $queue; protected $context; @@ -52,17 +54,9 @@ protected function addToken(ReaderInterface $reader, Token $token) { $token->setLine($reader->getCurrentLine()); $token->setRow($reader->getCurrentColumn()); - - $this->debugToken($token); + if ($token = $this->applyFilters($token)) { $this->queue->add($token); - } else { - $this->debug(" -- Token rejected"); } } - - protected function debugToken(Token $token) - { - $this->debugRes($token); - } } diff --git a/src/PHPCR/Util/CND/Scanner/Context/ScannerContext.php b/src/PHPCR/Util/CND/Scanner/Context/ScannerContext.php index db7c84fe..75ea053a 100644 --- a/src/PHPCR/Util/CND/Scanner/Context/ScannerContext.php +++ b/src/PHPCR/Util/CND/Scanner/Context/ScannerContext.php @@ -49,7 +49,7 @@ class ScannerContext protected $symbols = array(); /** - * @var array + * @var TokenFilterInterface[] */ protected $tokenFilters = array(); @@ -142,8 +142,7 @@ public function getWhitespaces() } /** - * @param \LazyGuy\PhpParse\Scanner\TokenFilter\TokenFilterInterface $filter - * @return void + * @param TokenFilterInterface $filter */ public function addTokenFilter(TokenFilterInterface $filter) { @@ -151,7 +150,7 @@ public function addTokenFilter(TokenFilterInterface $filter) } /** - * @return array + * @return TokenFilterInterface[] */ public function getTokenFilters() { diff --git a/src/PHPCR/Util/CND/Scanner/GenericScanner.php b/src/PHPCR/Util/CND/Scanner/GenericScanner.php index 9b6f2de0..896394ce 100644 --- a/src/PHPCR/Util/CND/Scanner/GenericScanner.php +++ b/src/PHPCR/Util/CND/Scanner/GenericScanner.php @@ -2,9 +2,8 @@ namespace PHPCR\Util\CND\Scanner; -use PHPCR\Util\CND\Reader\ReaderInterface, - PHPCR\Util\CND\Scanner\Token, - PHPCR\Util\CND\Exception\ScannerException; +use PHPCR\Util\CND\Reader\ReaderInterface; +use PHPCR\Util\CND\Exception\ScannerException; /** * Generic scanner detecting GenericTokens. @@ -19,19 +18,14 @@ class GenericScanner extends AbstractScanner /** * Scan the given reader and construct a TokenQueue composed of GenericToken. * - * @param \LazyGuy\PhpParse\Reader\ReaderInterface $reader + * @param ReaderInterface $reader * @return TokenQueue */ public function scan(ReaderInterface $reader) { - $this->debugSection("SCANNER CYCLE"); - $this->resetQueue(); while (!$reader->isEof()) { - - $this->debug(sprintf('Loop on: [%s]', str_replace("\n", '', $reader->currentChar()))); - $tokenFound = false; $tokenFound = $tokenFound || $this->consumeComments($reader); @@ -53,21 +47,17 @@ public function scan(ReaderInterface $reader) } - $this->debug('== EOF =='); - return $this->getQueue(); } /** * Detect and consume whitespaces * - * @param \LazyGuy\PhpParse\Reader\ReaderInterface $reader + * @param ReaderInterface $reader * @return bool */ protected function consumeSpaces(ReaderInterface $reader) { -// $this->debug('consumeSpaces'); - if (in_array($reader->currentChar(), $this->context->getWhitespaces())) { $char = $reader->forwardChar(); @@ -82,18 +72,18 @@ protected function consumeSpaces(ReaderInterface $reader) return true; } + + return false; } /** * Detect and consume newlines * - * @param \LazyGuy\PhpParse\Reader\ReaderInterface $reader + * @param ReaderInterface $reader * @return bool */ protected function consumeNewLine(ReaderInterface $reader) { -// $this->debug('consumeNewline'); - if ($reader->currentChar() === PHP_EOL) { $token = new GenericToken(GenericToken::TK_NEWLINE, PHP_EOL); @@ -115,14 +105,12 @@ protected function consumeNewLine(ReaderInterface $reader) /** * Detect and consume strings * - * @throws \LazyGuy\PhpParse\Exception\ScannerException - * @param \LazyGuy\PhpParse\Reader\ReaderInterface $reader + * @throws ScannerException + * @param ReaderInterface $reader * @return bool */ protected function consumeString(ReaderInterface $reader) { -// $this->debug('consumeStrings'); - $curDelimiter = $reader->currentChar(); if (in_array($curDelimiter, $this->context->getStringDelimiters())) { @@ -141,12 +129,14 @@ protected function consumeString(ReaderInterface $reader) $this->addToken($reader, $token); return true; } + + return false; } /** * Detect and consume comments * - * @param \LazyGuy\PhpParse\Reader\ReaderInterface $reader + * @param ReaderInterface $reader * @return bool */ protected function consumeComments(ReaderInterface $reader) @@ -161,21 +151,15 @@ protected function consumeComments(ReaderInterface $reader) /** * Detect and consume block comments * - * @throws \LazyGuy\PhpParse\Exception\ScannerException - * @param \LazyGuy\PhpParse\Reader\ReaderInterface $reader + * @throws ScannerException + * @param ReaderInterface $reader * @return bool */ protected function consumeBlockComments(ReaderInterface $reader) { -// $this->debug('consumeBlockComments'); - $nextChar = $reader->currentChar(); foreach($this->context->getBlockCommentDelimiters() as $beginDelim => $endDelim) { - if (!$beginDelim || !$endDelim) { - continue; - } - if ($nextChar === $beginDelim[0]) { // Lookup the start delimiter @@ -220,18 +204,18 @@ protected function consumeBlockComments(ReaderInterface $reader) } + return false; + } /** * Detect and consume line comments * - * @param \LazyGuy\PhpParse\Reader\ReaderInterface $reader + * @param ReaderInterface $reader * @return bool */ protected function consumeLineComments(ReaderInterface $reader) { -// $this->debug('consumeLineComments'); - $nextChar = $reader->currentChar(); foreach($this->context->getLineCommentDelimiters() as $delimiter) { @@ -262,18 +246,18 @@ protected function consumeLineComments(ReaderInterface $reader) } } + + return false; } /** * Detect and consume identifiers * - * @param \LazyGuy\PhpParse\Reader\ReaderInterface $reader + * @param ReaderInterface $reader * @return bool */ protected function consumeIdentifiers(ReaderInterface $reader) { -// $this->debug('consumeIdentifiers'); - $nextChar = $reader->currentChar(); if (preg_match('/[a-zA-Z]/', $nextChar)) { @@ -285,18 +269,18 @@ protected function consumeIdentifiers(ReaderInterface $reader) $this->addToken($reader, $token); return true; } + + return false; } /** * Detect and consume symbols * - * @param \LazyGuy\PhpParse\Reader\ReaderInterface $reader + * @param ReaderInterface $reader * @return bool */ protected function consumeSymbols(ReaderInterface $reader) { -// $this->debug('consumeSymbols'); - $found = false; $nextChar = $reader->currentChar(); while (in_array($nextChar, $this->context->getSymbols())) { @@ -312,5 +296,4 @@ protected function consumeSymbols(ReaderInterface $reader) return $found; } - } diff --git a/src/PHPCR/Util/CND/Scanner/ScannerInterface.php b/src/PHPCR/Util/CND/Scanner/ScannerInterface.php deleted file mode 100644 index 93a0fce0..00000000 --- a/src/PHPCR/Util/CND/Scanner/ScannerInterface.php +++ /dev/null @@ -1,26 +0,0 @@ - - */ -interface ScannerInterface -{ - /** - * @abstract - * @param \LazyGuy\PhpParse\Reader\ReaderInterface $reader - * @return TokenQueue - */ - function scan(ReaderInterface $reader); - - /** - * @abstract - * @param Token $token - * @return Token | void - */ - function applyFilters(Token $token); - -} diff --git a/src/PHPCR/Util/CND/Scanner/TokenFilter/TokenFilterChain.php b/src/PHPCR/Util/CND/Scanner/TokenFilter/TokenFilterChain.php index fa07ef56..7b004dbf 100644 --- a/src/PHPCR/Util/CND/Scanner/TokenFilter/TokenFilterChain.php +++ b/src/PHPCR/Util/CND/Scanner/TokenFilter/TokenFilterChain.php @@ -2,11 +2,16 @@ namespace PHPCR\Util\CND\Scanner\TokenFilter; +use PHPCR\Util\CND\Scanner\Token; + /** * @author Daniel Barsotti */ class TokenFilterChain implements TokenFilterInterface { + /** + * @var TokenFilterInterface[] + */ protected $filters; public function addFilter(TokenFilterInterface $filter) diff --git a/src/PHPCR/Util/CND/Scanner/TokenQueue.php b/src/PHPCR/Util/CND/Scanner/TokenQueue.php index 4541a427..0c4b3fb1 100644 --- a/src/PHPCR/Util/CND/Scanner/TokenQueue.php +++ b/src/PHPCR/Util/CND/Scanner/TokenQueue.php @@ -49,6 +49,7 @@ public function peek($offset = 0) public function get($count = 1) { + $item = null; for ($i = 1; $i <= $count; $i++) { $item = $this->peek(); $this->next(); @@ -66,12 +67,4 @@ public function getIterator() { return new \ArrayIterator($this->tokens); } - - public function debug() - { - foreach($this->tokens as $token) - { - echo $token . "\n"; - } - } } \ No newline at end of file From 0ef1871171b7e1c3e6b19f7cbe8848a2f3c62319 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Wed, 27 Mar 2013 02:51:07 +0100 Subject: [PATCH 10/12] fixing some of the unit tests too. CndParserTest still broken. --- .../Util/CND/Helper/NodeTypeGeneratorTest.php | 30 -------- .../Tests/Util/CND/Parser/CndParserTest.php | 72 ------------------- .../Util/CND/Parser/SyntaxTreeNodeTest.php | 31 -------- .../Util/CND/Reader/BufferReaderTest.php | 5 -- .../Tests/Util/CND/Reader/FileReaderTest.php | 5 -- 5 files changed, 143 deletions(-) delete mode 100644 tests/PHPCR/Tests/Util/CND/Helper/NodeTypeGeneratorTest.php delete mode 100644 tests/PHPCR/Tests/Util/CND/Parser/SyntaxTreeNodeTest.php diff --git a/tests/PHPCR/Tests/Util/CND/Helper/NodeTypeGeneratorTest.php b/tests/PHPCR/Tests/Util/CND/Helper/NodeTypeGeneratorTest.php deleted file mode 100644 index 9481155e..00000000 --- a/tests/PHPCR/Tests/Util/CND/Helper/NodeTypeGeneratorTest.php +++ /dev/null @@ -1,30 +0,0 @@ -scan($reader); - $parser = new CndParser($queue); - $root = $parser->parse(); - - // TODO: get a session, somehow... - //$session = ... -// $generator = new NodeTypeGenerator($sesion, $root); -// $generator->generate(); - - $this->assertTrue(true); // To avoid the test being marked incomplete - // TODO: write some real tests - } -} diff --git a/tests/PHPCR/Tests/Util/CND/Parser/CndParserTest.php b/tests/PHPCR/Tests/Util/CND/Parser/CndParserTest.php index b799c86b..df03e76a 100644 --- a/tests/PHPCR/Tests/Util/CND/Parser/CndParserTest.php +++ b/tests/PHPCR/Tests/Util/CND/Parser/CndParserTest.php @@ -10,64 +10,6 @@ class CndParserTest extends \PHPUnit_Framework_TestCase { - - public function setUp() - { - $root = new SyntaxTreeNode('root'); - - $node = new SyntaxTreeNode('nsMappings'); - $node->addChild(new SyntaxTreeNode('nsMapping', array('prefix' => 'ns', 'uri' => 'http://namespace.com/ns'))); - $root->addChild($node); - - $types = new SyntaxTreeNode('nodeTypes'); - - $attrs = new SyntaxTreeNode('propertyTypeAttributes'); - $attrs->addChild(new SyntaxTreeNode('mandatory')); - $attrs->addChild(new SyntaxTreeNode('autocreated')); - $attrs->addChild(new SyntaxTreeNode('protected')); - $attrs->addChild(new SyntaxTreeNode('multiple')); - $attrs->addChild(new SyntaxTreeNode('VERSION')); - - $props = new SyntaxTreeNode('propertyDefs'); - $prop = new SyntaxTreeNode('propertyDef'); - $prop->addChild(new SyntaxTreeNode('propertyName', array('value' => 'ex:property'))); - $prop->addChild(new SyntaxTreeNode('propertyType', array('value' => 'STRING'))); - $prop->addChild(new SyntaxTreeNode('defaultValues', array('value' => array('default1', 'default2')))); - $prop->addChild($attrs); - $prop->addChild(new SyntaxTreeNode('valueConstraints', array('value' => array('constraint1', 'constraint2')))); - $props->addChild($prop); - - $attrs = new SyntaxTreeNode('nodeAttributes'); - $attrs->addChild(new SyntaxTreeNode('mandatory')); - $attrs->addChild(new SyntaxTreeNode('autocreated')); - $attrs->addChild(new SyntaxTreeNode('protected')); - $attrs->addChild(new SyntaxTreeNode('VERSION')); - - $nodes = new SyntaxTreeNode('childNodeDefs'); - $node = new SyntaxTreeNode('childNodeDef'); - $node->addChild(new SyntaxTreeNode('nodeName', array('value' => 'ns:node'))); - $node->addChild(new SyntaxTreeNode('requiredTypes', array('value' => array('ns:reqType1', 'ns:reqType2')))); - $node->addChild(new SyntaxTreeNode('defaultType', array('value' => 'ns:defaultType'))); - $node->addChild($attrs); - $nodes->addChild($node); - - $attrs = new SyntaxTreeNode('nodeTypeAttributes'); - $attrs->addChild(new SyntaxTreeNode('orderable')); - $attrs->addChild(new SyntaxTreeNode('mixin')); - - $nodeType = new SyntaxTreeNode('nodeTypeDef'); - $nodeType->addChild(new SyntaxTreeNode('nodeTypeName', array('value' => 'ns:NodeType'))); - $nodeType->addChild(new SyntaxTreeNode('supertypes', array('value' => array('ns:ParentType1', 'ns:ParentType2')))); - $nodeType->addChild($attrs); - $nodeType->addChild($props); - $nodeType->addChild($nodes); - - $types->addChild($nodeType); - $root->addChild($types); - - $this->expectedExampleTree = $root; - } - public function testParseNormal() { $this->assertParsedFile(__DIR__ . '/../Fixtures/cnd/example.cnd', $this->expectedExampleTree); @@ -117,20 +59,6 @@ public function testNoStopAtEofError() // TODO: write some real tests } - // TODO: test variant required type for child node def - // TODO: test parseCndString without a string throws a parser error - // TODO: test variant primary type - // TODO: test queryops - - protected function assertParsedFile($file, $expectedCnd) - { - $actualCnd = $this->parseFile($file); - - $this->assertEquals($expectedCnd->dump(), $actualCnd->dump()); - - $this->assertEquals($expectedCnd, $actualCnd); - } - protected function parseFile($file) { $reader = new FileReader($file); diff --git a/tests/PHPCR/Tests/Util/CND/Parser/SyntaxTreeNodeTest.php b/tests/PHPCR/Tests/Util/CND/Parser/SyntaxTreeNodeTest.php deleted file mode 100644 index 9c96bc07..00000000 --- a/tests/PHPCR/Tests/Util/CND/Parser/SyntaxTreeNodeTest.php +++ /dev/null @@ -1,31 +0,0 @@ -setProperty('root-prop-1', 'some value'); - $root->setProperty('root-prop-2', 'some other value'); - - $node1 = new SyntaxTreeNode('child'); - $node1->setProperty('child-prop-1', 'foo'); - $node1->setProperty('child-prop-2', 'bar'); - - $node2 = new SyntaxTreeNode('child'); - $node2->setProperty('child-prop-3', 'foo'); - $node2->setProperty('child-prop-4', 'bar'); - - $root->addChild($node1); - $root->addChild($node2); - - //var_dump($root); - - $this->assertTrue(true); // To avoid the test being marked incomplete - // TODO: write some real tests - } -} diff --git a/tests/PHPCR/Tests/Util/CND/Reader/BufferReaderTest.php b/tests/PHPCR/Tests/Util/CND/Reader/BufferReaderTest.php index 3096a0da..33838750 100644 --- a/tests/PHPCR/Tests/Util/CND/Reader/BufferReaderTest.php +++ b/tests/PHPCR/Tests/Util/CND/Reader/BufferReaderTest.php @@ -41,11 +41,6 @@ public function test__construct() $this->assertEquals(' r', $reader->forward()); $reader->rewind(); $this->assertEquals(' ', $reader->forward()); - $reader->unget(); - $this->assertEquals(' ', $reader->forward()); - $reader->unget(); - $reader->unget(); - $this->assertEquals(' ', $reader->forward()); $this->assertEquals(' ', $reader->consume()); $this->assertEquals(6, $reader->getCurrentColumn()); diff --git a/tests/PHPCR/Tests/Util/CND/Reader/FileReaderTest.php b/tests/PHPCR/Tests/Util/CND/Reader/FileReaderTest.php index fbf2102f..ad2d1dab 100644 --- a/tests/PHPCR/Tests/Util/CND/Reader/FileReaderTest.php +++ b/tests/PHPCR/Tests/Util/CND/Reader/FileReaderTest.php @@ -40,11 +40,6 @@ public function testGetFileName() $this->assertEquals($this->filename, $this->reader->getFileName()); } - public function testGetBuffer() - { - $this->assertEquals(file_get_contents($this->filename) . $this->reader->getEofMarker(), $this->reader->getBuffer()); - } - public function testGetNextChar() { $curLine = 1; From 0b69c7102ea38a03b4b920ed878d3afd5d9fb5f8 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Thu, 28 Mar 2013 14:11:30 +0100 Subject: [PATCH 11/12] fix a couple of bugs in cnd parsers and move functional tests to phpcr-api-test --- src/PHPCR/Util/CND/Parser/CndParser.php | 38 +- src/PHPCR/Util/CND/Reader/BufferReader.php | 1 + src/PHPCR/Util/CND/Scanner/GenericScanner.php | 5 +- .../Tests/Util/CND/Fixtures/cnd/example.cnd | 12 - .../Util/CND/Fixtures/cnd/example.compact.cnd | 6 - .../Util/CND/Fixtures/cnd/example.verbose.cnd | 48 -- .../Tests/Util/CND/Fixtures/cnd/example1.cnd | 11 - .../cnd/jackrabbit-builtin-nodetypes.cnd | 629 ------------------ .../Util/CND/Fixtures/cnd/no-stop-at-eof.cnd | 5 - .../Tests/Util/CND/Parser/CndParserTest.php | 74 --- 10 files changed, 33 insertions(+), 796 deletions(-) delete mode 100644 tests/PHPCR/Tests/Util/CND/Fixtures/cnd/example.cnd delete mode 100644 tests/PHPCR/Tests/Util/CND/Fixtures/cnd/example.compact.cnd delete mode 100644 tests/PHPCR/Tests/Util/CND/Fixtures/cnd/example.verbose.cnd delete mode 100644 tests/PHPCR/Tests/Util/CND/Fixtures/cnd/example1.cnd delete mode 100644 tests/PHPCR/Tests/Util/CND/Fixtures/cnd/jackrabbit-builtin-nodetypes.cnd delete mode 100644 tests/PHPCR/Tests/Util/CND/Fixtures/cnd/no-stop-at-eof.cnd delete mode 100644 tests/PHPCR/Tests/Util/CND/Parser/CndParserTest.php diff --git a/src/PHPCR/Util/CND/Parser/CndParser.php b/src/PHPCR/Util/CND/Parser/CndParser.php index b898d771..44e7b6b8 100644 --- a/src/PHPCR/Util/CND/Parser/CndParser.php +++ b/src/PHPCR/Util/CND/Parser/CndParser.php @@ -8,6 +8,8 @@ use PHPCR\NodeType\PropertyDefinitionTemplateInterface; use PHPCR\PropertyType; use PHPCR\Util\CND\Reader\BufferReader; +use PHPCR\Util\CND\Reader\FileReader; +use PHPCR\Util\CND\Reader\ReaderInterface; use PHPCR\Util\CND\Scanner\Context\DefaultScannerContextWithoutSpacesAndComments; use PHPCR\Util\CND\Scanner\GenericScanner; use PHPCR\Util\CND\Scanner\GenericToken as Token; @@ -76,23 +78,41 @@ public function __construct(NodeTypeManagerInterface $ntm) $this->ntm = $ntm; } + /** + * Parse a file with CND statements. + * + * @param string $filename absolute path to the CND file to read + * + * @return array with the namespaces map and the nodeTypes which is a + * hashmap of typename = > NodeTypeDefinitionInterface + */ + public function parseFile($filename) + { + $reader = new FileReader($filename); + + return $this->parse($reader); + } + /** * Parse a string of CND statements. * - * @return array with the namespaces map and the nodeTypes which is a list - * of NodeTypeDefinitionInterface + * @param string $cnd string with CND content + * + * @return array with the namespaces map and the nodeTypes which is a + * hashmap of typename = > NodeTypeDefinitionInterface */ public function parseString($cnd) { $reader = new BufferReader($cnd); - $scanner = new GenericScanner(new DefaultScannerContextWithoutSpacesAndComments()); - $this->tokenQueue = $scanner->scan($reader); - return $this->parse(); + return $this->parse($reader); } - private function parse() + private function parse(ReaderInterface $reader) { + $scanner = new GenericScanner(new DefaultScannerContextWithoutSpacesAndComments()); + $this->tokenQueue = $scanner->scan($reader); + while (!$this->tokenQueue->isEof()) { while ($this->checkToken(Token::TK_SYMBOL, '<')) { @@ -154,7 +174,7 @@ protected function parseNodeType() $this->parseChildrenAndAttributes($nodeType); - $this->nodeTypes[] = $nodeType; + $this->nodeTypes[$nodeType->getName()] = $nodeType; } /** @@ -184,7 +204,7 @@ protected function parseSupertypes(NodeTypeTemplateInterface $nodeType) $this->expectToken(Token::TK_SYMBOL, '>'); if ($this->checkAndExpectToken(Token::TK_SYMBOL, '?')) { - $nodeType->setDeclaredSuperTypeNames('?'); + $nodeType->setDeclaredSuperTypeNames(array('?')); } else { $nodeType->setDeclaredSuperTypeNames($this->parseCndStringList()); } @@ -464,6 +484,8 @@ protected function parsePropertyAttributes(PropertyDefinitionTemplateInterface $ $property->setProtected(true); } else if ($this->checkTokenIn(Token::TK_IDENTIFIER, $this->MULTIPLE)) { $property->setMultiple(true); + } else if ($this->checkTokenIn(Token::TK_SYMBOL, $this->MULTIPLE)) { + $property->setMultiple(true); } else if ($this->checkTokenIn(Token::TK_IDENTIFIER, $this->QUERYOPS)) { $property->setAvailableQueryOperators($this->parseQueryOpsAttribute()); } else if ($this->checkTokenIn(Token::TK_IDENTIFIER, $this->NOFULLTEXT)) { diff --git a/src/PHPCR/Util/CND/Reader/BufferReader.php b/src/PHPCR/Util/CND/Reader/BufferReader.php index 558372f0..05931d68 100644 --- a/src/PHPCR/Util/CND/Reader/BufferReader.php +++ b/src/PHPCR/Util/CND/Reader/BufferReader.php @@ -78,6 +78,7 @@ public function currentChar() public function isEof() { return $this->currentChar() === $this->getEofMarker() + || $this->currentChar() === false || $this->startPos > strlen($this->buffer) || $this->forwardPos > strlen($this->buffer); } diff --git a/src/PHPCR/Util/CND/Scanner/GenericScanner.php b/src/PHPCR/Util/CND/Scanner/GenericScanner.php index 896394ce..7497590b 100644 --- a/src/PHPCR/Util/CND/Scanner/GenericScanner.php +++ b/src/PHPCR/Util/CND/Scanner/GenericScanner.php @@ -27,7 +27,6 @@ public function scan(ReaderInterface $reader) while (!$reader->isEof()) { $tokenFound = false; - $tokenFound = $tokenFound || $this->consumeComments($reader); $tokenFound = $tokenFound || $this->consumeNewLine($reader); $tokenFound = $tokenFound || $this->consumeSpaces($reader); @@ -166,12 +165,12 @@ protected function consumeBlockComments(ReaderInterface $reader) for ($i = 1; $i <= strlen($beginDelim); $i++) { $reader->forward(); } - if ($reader->current() === $beginDelim) { // Start delimiter found, let's try to find the end delimiter $nextChar = $reader->forwardChar(); - while ($nextChar !== $reader->getEofMarker()) { + + while (! $reader->isEof()) { if ($nextChar === $endDelim[0]) { diff --git a/tests/PHPCR/Tests/Util/CND/Fixtures/cnd/example.cnd b/tests/PHPCR/Tests/Util/CND/Fixtures/cnd/example.cnd deleted file mode 100644 index 7ac57aed..00000000 --- a/tests/PHPCR/Tests/Util/CND/Fixtures/cnd/example.cnd +++ /dev/null @@ -1,12 +0,0 @@ -/* An example node type definition */ - -[ns:NodeType] > ns:ParentType1, ns:ParentType2 - orderable mixin - - ex:property (STRING) - = 'default1' , 'default2' - mandatory autocreated protected multiple - VERSION - < 'constraint1', 'constraint2' - + ns:node (ns:reqType1, ns:reqType2) - = ns:defaultType - mandatory autocreated protected VERSION \ No newline at end of file diff --git a/tests/PHPCR/Tests/Util/CND/Fixtures/cnd/example.compact.cnd b/tests/PHPCR/Tests/Util/CND/Fixtures/cnd/example.compact.cnd deleted file mode 100644 index d3af8391..00000000 --- a/tests/PHPCR/Tests/Util/CND/Fixtures/cnd/example.compact.cnd +++ /dev/null @@ -1,6 +0,0 @@ - -[ns:NodeType]>ns:ParentType1, ns:ParentType2 o m --ex:property(STRING)='default1','default2' m a p * VERSION - <'constraint1', 'constraint2' -+ns:node(ns:reqType1,ns:reqType2)=ns:defaultType - m a p VERSION \ No newline at end of file diff --git a/tests/PHPCR/Tests/Util/CND/Fixtures/cnd/example.verbose.cnd b/tests/PHPCR/Tests/Util/CND/Fixtures/cnd/example.verbose.cnd deleted file mode 100644 index 834a5a93..00000000 --- a/tests/PHPCR/Tests/Util/CND/Fixtures/cnd/example.verbose.cnd +++ /dev/null @@ -1,48 +0,0 @@ -/* An example node type definition */ - -// The namespace declaration - - -// Node type name -[ns:NodeType] - -// Supertypes -> ns:ParentType1, ns:ParentType2 - -// This node type supports orderable child nodes -orderable - -// This is a mixin node type -mixin - -// Nodes of this node type have a property called 'ex:property' of type STRING -- ex:property (STRING) - -// The default values for this -// (multi-value) property are... -= 'default1', 'default2' - -// and it is... -mandatory autocreated protected - -// and multi-valued -multiple - -// and has an on-parent-version setting of ... -VERSION - -// The constraint settings are... -< 'constraint1', 'constraint2' - -// Nodes of this node type have a child node called ns:node which must be of -// at least the node types ns:reqType1 and ns:reqType2 -+ ns:node (ns:reqType1, ns:reqType2) - -// and the default primary node type of the child node is... -= ns:defaultType - -// This child node is... -mandatory autocreated protected - -// and has an on-parent-version setting of ... -VERSION diff --git a/tests/PHPCR/Tests/Util/CND/Fixtures/cnd/example1.cnd b/tests/PHPCR/Tests/Util/CND/Fixtures/cnd/example1.cnd deleted file mode 100644 index bbb7d497..00000000 --- a/tests/PHPCR/Tests/Util/CND/Fixtures/cnd/example1.cnd +++ /dev/null @@ -1,11 +0,0 @@ -<'phpcr'='http://www.doctrine-project.org/projects/phpcr_odm'> - [phpcr:apitest] - mixin - - phpcr:class (STRING) - [phpcr:test] - mixin - - phpcr:prop (STRING) - -<'phpcr'='http://www.doctrine-project.org/projects/phpcr_odm'> -[phpcr:primary_item_test] > ? -- phpcr:content (STRING) diff --git a/tests/PHPCR/Tests/Util/CND/Fixtures/cnd/jackrabbit-builtin-nodetypes.cnd b/tests/PHPCR/Tests/Util/CND/Fixtures/cnd/jackrabbit-builtin-nodetypes.cnd deleted file mode 100644 index 628e912a..00000000 --- a/tests/PHPCR/Tests/Util/CND/Fixtures/cnd/jackrabbit-builtin-nodetypes.cnd +++ /dev/null @@ -1,629 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - - - - - -//------------------------------------------------------------------------------ -// B A S E T Y P E -//------------------------------------------------------------------------------ - -/** - * nt:base is an abstract primary node type that is the base type for all other - * primary node types. It is the only primary node type without supertypes. - * - * @since 1.0 - */ -[nt:base] - abstract - - jcr:primaryType (NAME) mandatory autocreated protected COMPUTE - - jcr:mixinTypes (NAME) protected multiple COMPUTE - -//------------------------------------------------------------------------------ -// S T A N D A R D A P P L I C A T I O N N O D E T Y P E S -//------------------------------------------------------------------------------ - -/** - * This abstract node type serves as the supertype of nt:file and nt:folder. - * @since 1.0 - */ -[nt:hierarchyNode] > mix:created - abstract - -/** - * Nodes of this node type may be used to represent files. This node type inherits - * the item definitions of nt:hierarchyNode and requires a single child node called - * jcr:content. The jcr:content node is used to hold the actual content of the - * file. This child node is mandatory, but not auto-created. Its node type will be - * application-dependent and therefore it must be added by the user. A common - * approach is to make the jcr:content a node of type nt:resource. The - * jcr:content child node is also designated as the primary child item of its parent. - * - * @since 1.0 - */ -[nt:file] > nt:hierarchyNode - primaryitem jcr:content - + jcr:content (nt:base) mandatory - -/** - * The nt:linkedFile node type is similar to nt:file, except that the content - * node is not stored directly as a child node, but rather is specified by a - * REFERENCE property. This allows the content node to reside anywhere in the - * workspace and to be referenced by multiple nt:linkedFile nodes. The content - * node must be referenceable. - * - * @since 1.0 - */ -[nt:linkedFile] > nt:hierarchyNode - primaryitem jcr:content - - jcr:content (REFERENCE) mandatory - -/** - * Nodes of this type may be used to represent folders or directories. This node - * type inherits the item definitions of nt:hierarchyNode and adds the ability - * to have any number of other nt:hierarchyNode child nodes with any names. - * This means, in particular, that it can have child nodes of types nt:folder, - * nt:file or nt:linkedFile. - * - * @since 1.0 - */ -[nt:folder] > nt:hierarchyNode - + * (nt:hierarchyNode) VERSION - -/** - * This node type may be used to represent the content of a file. In particular, - * the jcr:content subnode of an nt:file node will often be an nt:resource. - * - * @since 1.0 - */ -[nt:resource] > mix:mimeType, mix:lastModified, mix:referenceable - primaryitem jcr:data - - jcr:data (BINARY) mandatory - -/** - * This mixin node type can be used to add standardized title and description - * properties to a node. - * - * Note that the protected attributes suggested by JSR283 are omitted in this variant. - * @since 2.0 - */ -[mix:title] - mixin - - jcr:title (STRING) - - jcr:description (STRING) - -/** - * This mixin node type can be used to add standardized creation information - * properties to a node. Since the properties are protected, their values are - * controlled by the repository, which should set them appropriately upon the - * initial persist of a node with this mixin type. In cases where this mixin is - * added to an already existing node the semantics of these properties are - * implementation specific. Note that jackrabbit initializes the properties to - * the current date and user in this case. - * - * - * @since 2.0 - */ -[mix:created] - mixin - - jcr:created (DATE) autocreated protected - - jcr:createdBy (STRING) autocreated protected - -/** - * This mixin node type can be used to provide standardized modification - * information properties to a node. - * - * The following is not yet implemented in Jackrabbit: - * "Since the properties are protected, their values - * are controlled by the repository, which should set them appropriately upon a - * significant modification of the subgraph of a node with this mixin. What - * constitutes a significant modification will depend on the semantics of the various - * parts of a node's subgraph and is implementation-dependent" - * - * Jackrabbit initializes the properties to the current date and user in the - * case they are newly created. - * - * Note that the protected attributes suggested by JSR283 are omitted in this variant. - * @since 2.0 - */ -[mix:lastModified] - mixin - - jcr:lastModified (DATE) autocreated - - jcr:lastModifiedBy (STRING) autocreated - -/** - * This mixin node type can be used to provide a standardized property that - * specifies the natural language in which the content of a node is expressed. - * The value of the jcr:language property should be a language code as defined - * in RFC 46465. Examples include "en" (English), "en-US" (United States English), - * "de" (German) and "de-CH" (Swiss German). - * - * Note that the protected attributes suggested by JSR283 are omitted in this variant. - * @since 2.0 - */ -[mix:language] - mixin - - jcr:language (STRING) - -/** - * This mixin node type can be used to provide standardized mimetype and - * encoding properties to a node. If a node of this type has a primary item - * that is a single-value BINARY property then jcr:mimeType property indicates - * the media type applicable to the contents of that property and, if that - * media type is one to which a text encoding applies, the jcr:encoding property - * indicates the character set used. If a node of this type does not meet the - * above precondition then the interpretation of the jcr:mimeType and - * jcr:encoding properties is implementation-dependent. - * - * Note that the protected attributes suggested by JSR283 are omitted in this variant. - * @since 2.0 - */ -[mix:mimeType] - mixin - - jcr:mimeType (STRING) - - jcr:encoding (STRING) - -/** - * This node type may be used to represent the location of a JCR item not just - * within a particular workspace but within the space of all workspaces in all JCR - * repositories. - * - * @prop jcr:protocol Stores a string holding the protocol through which the - * target repository is to be accessed. - * @prop jcr:host Stores a string holding the host name of the system - * through which the repository is to be accessed. - * @prop jcr:port Stores a string holding the port number through which the - * target repository is to be accessed. - * - * The semantics of these properties above are left undefined but are assumed to be - * known by the application. The names and descriptions of the properties are not - * normative and the repository does not enforce any particular semantic - * interpretation on them. - * - * @prop jcr:repository Stores a string holding the name of the target repository. - * @prop jcr:workspace Stores the name of a workspace. - * @prop jcr:path Stores a path to an item. - * @prop jcr:id Stores a weak reference to a node. - * - * In most cases either the jcr:path or the jcr:id property would be used, but - * not both, since they may point to different nodes. If any of the properties - * other than jcr:path and jcr:id are missing, the address can be interpreted as - * relative to the current container at the same level as the missing specifier. - * For example, if no repository is specified, then the address can be - * interpreted as referring to a workspace and path or id within the current - * repository. - * - * @since 2.0 - */ -[nt:address] - - jcr:protocol (STRING) - - jcr:host (STRING) - - jcr:port (STRING) - - jcr:repository (STRING) - - jcr:workspace (STRING) - - jcr:path (PATH) - - jcr:id (WEAKREFERENCE) - -/** - * The mix:etag mixin type defines a standardized identity validator for BINARY - * properties similar to the entity tags used in HTTP/1.1 - * - * A jcr:etag property is an opaque string whose syntax is identical to that - * defined for entity tags in HTTP/1.1. Semantically, the jcr:etag is comparable - * to the HTTP/1.1 strong entity tag. - * - * On creation of a mix:etag node N, or assignment of mix:etag to N, the - * repository must create a jcr:etag property with an implementation determined - * value. - * - * The value of the jcr:etag property must change immediately on persist of any - * of the following changes to N: - * - A BINARY property is added t o N. - * - A BINARY property is removed from N. - * - The value of an existing BINARY property of N changes. - * - * @since 2.0 - */ -[mix:etag] - mixin - - jcr:etag (STRING) protected autocreated - -//------------------------------------------------------------------------------ -// U N S T R U C T U R E D C O N T E N T -//------------------------------------------------------------------------------ - -/** - * This node type is used to store unstructured content. It allows any number of - * child nodes or properties with any names. It also allows multiple nodes having - * the same name as well as both multi-value and single-value properties with any - * names. This node type also supports client-orderable child nodes. - * - * @since 1.0 - */ -[nt:unstructured] - orderable - - * (UNDEFINED) multiple - - * (UNDEFINED) - + * (nt:base) = nt:unstructured sns VERSION - -//------------------------------------------------------------------------------ -// R E F E R E N C E A B L E -//------------------------------------------------------------------------------ - -/** - * This node type adds an auto-created, mandatory, protected STRING property to - * the node, called jcr:uuid, which exposes the identifier of the node. - * Note that the term "UUID" is used for backward compatibility with JCR 1.0 - * and does not necessarily imply the use of the UUID syntax, or global uniqueness. - * The identifier of a referenceable node must be a referenceable identifier. - * Referenceable identifiers must fulfill a number of constraints beyond the - * minimum required of standard identifiers (see 3.8.3 Referenceable Identifiers). - * A reference property is a property that holds the referenceable identifier of a - * referenceable node and therefore serves as a pointer to that node. The two types - * of reference properties, REFERENCE and WEAKREFERENCE differ in that the former - * enforces referential integrity while the latter does not. - * - * @since 1.0 - */ -[mix:referenceable] - mixin - - jcr:uuid (STRING) mandatory autocreated protected INITIALIZE - -//------------------------------------------------------------------------------ -// L O C K I N G -//------------------------------------------------------------------------------ - -/** - * @since 1.0 - */ -[mix:lockable] - mixin - - jcr:lockOwner (STRING) protected IGNORE - - jcr:lockIsDeep (BOOLEAN) protected IGNORE - -//------------------------------------------------------------------------------ -// S H A R E A B L E N O D E S -//------------------------------------------------------------------------------ - -/** - * @since 2.0 - */ -[mix:shareable] > mix:referenceable - mixin - -//------------------------------------------------------------------------------ -// V E R S I O N I N G -//------------------------------------------------------------------------------ - -/** - * @since 2.0 - */ -[mix:simpleVersionable] - mixin - - jcr:isCheckedOut (BOOLEAN) = 'true' mandatory autocreated protected IGNORE - -/** - * @since 1.0 - */ -[mix:versionable] > mix:simpleVersionable, mix:referenceable - mixin - - jcr:versionHistory (REFERENCE) mandatory protected IGNORE < 'nt:versionHistory' - - jcr:baseVersion (REFERENCE) mandatory protected IGNORE < 'nt:version' - - jcr:predecessors (REFERENCE) mandatory protected multiple IGNORE < 'nt:version' - - jcr:mergeFailed (REFERENCE) protected multiple ABORT < 'nt:version' - /** @since 2.0 */ - - jcr:activity (REFERENCE) protected < 'nt:activity' - /** @since 2.0 */ - - jcr:configuration (REFERENCE) protected IGNORE < 'nt:configuration' - -/** - * @since 1.0 - */ -[nt:versionHistory] > mix:referenceable - - jcr:versionableUuid (STRING) mandatory autocreated protected ABORT - /** @since 2.0 */ - - jcr:copiedFrom (WEAKREFERENCE) protected ABORT < 'nt:version' - + jcr:rootVersion (nt:version) = nt:version mandatory autocreated protected ABORT - + jcr:versionLabels (nt:versionLabels) = nt:versionLabels mandatory autocreated protected ABORT - + * (nt:version) = nt:version protected ABORT - -/** - * @since 1.0 - */ -[nt:versionLabels] - - * (REFERENCE) protected ABORT < 'nt:version' - -/** - * @since 1.0 - */ -[nt:version] > mix:referenceable - - jcr:created (DATE) mandatory autocreated protected ABORT - - jcr:predecessors (REFERENCE) protected multiple ABORT < 'nt:version' - - jcr:successors (REFERENCE) protected multiple ABORT < 'nt:version' - /** @since 2.0 */ - - jcr:activity (REFERENCE) protected ABORT < 'nt:activity' - + jcr:frozenNode (nt:frozenNode) protected ABORT - -/** - * @since 1.0 - */ -[nt:frozenNode] > mix:referenceable - orderable - - jcr:frozenPrimaryType (NAME) mandatory autocreated protected ABORT - - jcr:frozenMixinTypes (NAME) protected multiple ABORT - - jcr:frozenUuid (STRING) mandatory autocreated protected ABORT - - * (UNDEFINED) protected ABORT - - * (UNDEFINED) protected multiple ABORT - + * (nt:base) protected sns ABORT - -/** - * @since 1.0 - */ -[nt:versionedChild] - - jcr:childVersionHistory (REFERENCE) mandatory autocreated protected ABORT < 'nt:versionHistory' - -/** - * @since 2.0 - */ -[nt:activity] > mix:referenceable - - jcr:activityTitle (STRING) mandatory autocreated protected - -/** - * @since 2.0 - */ -[nt:configuration] > mix:versionable - - jcr:root (REFERENCE) mandatory autocreated protected - -//------------------------------------------------------------------------------ -// N O D E T Y P E S -//------------------------------------------------------------------------------ - -/** - * This node type is used to store a node type definition. Property and child node - * definitions within the node type definition are stored as same-name sibling nodes - * of type nt:propertyDefinition and nt:childNodeDefinition. - * - * @since 1.0 - */ -[nt:nodeType] - - jcr:nodeTypeName (NAME) protected mandatory - - jcr:supertypes (NAME) protected multiple - - jcr:isAbstract (BOOLEAN) protected mandatory - - jcr:isQueryable (BOOLEAN) protected mandatory - - jcr:isMixin (BOOLEAN) protected mandatory - - jcr:hasOrderableChildNodes (BOOLEAN) protected mandatory - - jcr:primaryItemName (NAME) protected - + jcr:propertyDefinition (nt:propertyDefinition) = nt:propertyDefinition protected sns - + jcr:childNodeDefinition (nt:childNodeDefinition) = nt:childNodeDefinition protected sns - -/** - * This node type used to store a property definition within a node type definition, - * which itself is stored as an nt:nodeType node. - * - * @since 1.0 - */ -[nt:propertyDefinition] - - jcr:name (NAME) protected - - jcr:autoCreated (BOOLEAN) protected mandatory - - jcr:mandatory (BOOLEAN) protected mandatory - - jcr:onParentVersion (STRING) protected mandatory - < 'COPY', 'VERSION', 'INITIALIZE', 'COMPUTE', 'IGNORE', 'ABORT' - - jcr:protected (BOOLEAN) protected mandatory - - jcr:requiredType (STRING) protected mandatory - < 'STRING', 'URI', 'BINARY', 'LONG', 'DOUBLE', - 'DECIMAL', 'BOOLEAN', 'DATE', 'NAME', 'PATH', - 'REFERENCE', 'WEAKREFERENCE', 'UNDEFINED' - - jcr:valueConstraints (STRING) protected multiple - - jcr:defaultValues (UNDEFINED) protected multiple - - jcr:multiple (BOOLEAN) protected mandatory - - jcr:availableQueryOperators (NAME) protected mandatory multiple - - jcr:isFullTextSearchable (BOOLEAN) protected mandatory - - jcr:isQueryOrderable (BOOLEAN) protected mandatory - -/** - * This node type used to store a child node definition within a node type definition, - * which itself is stored as an nt:nodeType node. - * - * @since 1.0 - */ -[nt:childNodeDefinition] - - jcr:name (NAME) protected - - jcr:autoCreated (BOOLEAN) protected mandatory - - jcr:mandatory (BOOLEAN) protected mandatory - - jcr:onParentVersion (STRING) protected mandatory - < 'COPY', 'VERSION', 'INITIALIZE', 'COMPUTE', 'IGNORE', 'ABORT' - - jcr:protected (BOOLEAN) protected mandatory - - jcr:requiredPrimaryTypes (NAME) = 'nt:base' protected mandatory multiple - - jcr:defaultPrimaryType (NAME) protected - - jcr:sameNameSiblings (BOOLEAN) protected mandatory - -//------------------------------------------------------------------------------ -// Q U E R Y -//------------------------------------------------------------------------------ - -/** - * @since 1.0 - */ -[nt:query] - - jcr:statement (STRING) - - jcr:language (STRING) - -//------------------------------------------------------------------------------ -// L I F E C Y C L E M A N A G E M E N T -//------------------------------------------------------------------------------ - -/** - * Only nodes with mixin node type mix:lifecycle may participate in a lifecycle. - * - * @peop jcr:lifecyclePolicy - * This property is a reference to another node that contains - * lifecycle policy information. The definition of the referenced - * node is not specified. - * @prop jcr:currentLifecycleState - * This property is a string identifying the current lifecycle - * state of this node. The format of this string is not specified. - * - * @since 2.0 - */ -[mix:lifecycle] - mixin - - jcr:lifecyclePolicy (REFERENCE) protected INITIALIZE - - jcr:currentLifecycleState (STRING) protected INITIALIZE - -//------------------------------------------------------------------------------ -// J A C K R A B B I T I N T E R N A L S -//------------------------------------------------------------------------------ - -[rep:root] > nt:unstructured - + jcr:system (rep:system) = rep:system mandatory IGNORE - -[rep:system] - orderable - + jcr:versionStorage (rep:versionStorage) = rep:versionStorage mandatory protected ABORT - + jcr:nodeTypes (rep:nodeTypes) = rep:nodeTypes mandatory protected ABORT - // @since 2.0 - + jcr:activities (rep:Activities) = rep:Activities mandatory protected ABORT - // @since 2.0 - + jcr:configurations (rep:Configurations) = rep:Configurations protected ABORT - + * (nt:base) = nt:base IGNORE - - -/** - * Node Types (virtual) storage - */ -[rep:nodeTypes] - + * (nt:nodeType) = nt:nodeType protected ABORT - -/** - * Version storage - */ -[rep:versionStorage] - + * (nt:versionHistory) = nt:versionHistory protected ABORT - + * (rep:versionStorage) = rep:versionStorage protected ABORT - -/** - * Activities storage - * @since 2.0 - */ -[rep:Activities] - + * (nt:activity) = nt:activity protected ABORT - + * (rep:Activities) = rep:Activities protected ABORT - -/** - * the intermediate nodes for the configurations storage. - * Note: since the versionable node points to the configuration and vice versa, - * a configuration could never be removed because no such API exists. therefore - * the child node definitions are not protected. - * @since 2.0 - */ -[rep:Configurations] - + * (nt:configuration) = nt:configuration ABORT - + * (rep:Configurations) = rep:Configurations ABORT - -/** - * mixin that provides a multi value property for referencing versions. - * This is used for recording the baseline versions in the nt:configuration - * node, and to setup a bidirectional relationship between activities and - * the respective versions - * @since 2.0 - */ -[rep:VersionReference] mix - - rep:versions (REFERENCE) protected multiple - -// ----------------------------------------------------------------------------- -// J A C K R A B B I T S E C U R I T Y -// ----------------------------------------------------------------------------- - -[rep:AccessControllable] - mixin - + rep:policy (rep:Policy) protected IGNORE - -[rep:Policy] - abstract - -[rep:ACL] > rep:Policy - orderable - + * (rep:ACE) = rep:GrantACE protected IGNORE - -[rep:ACE] - - rep:principalName (STRING) protected mandatory - - rep:privileges (NAME) protected mandatory multiple - - rep:nodePath (PATH) protected - - rep:glob (STRING) protected - - * (UNDEFINED) protected - -[rep:GrantACE] > rep:ACE - -[rep:DenyACE] > rep:ACE - -// ----------------------------------------------------------------------------- -// Principal based AC -// ----------------------------------------------------------------------------- - -[rep:AccessControl] - + * (rep:AccessControl) protected IGNORE - + * (rep:PrincipalAccessControl) protected IGNORE - -[rep:PrincipalAccessControl] > rep:AccessControl - + rep:policy (rep:Policy) protected IGNORE - -// ----------------------------------------------------------------------------- -// User Management -// ----------------------------------------------------------------------------- - -[rep:Authorizable] > mix:referenceable, nt:hierarchyNode - abstract - + * (nt:base) = nt:unstructured VERSION - - rep:principalName (STRING) protected mandatory - - * (UNDEFINED) - - * (UNDEFINED) multiple - -[rep:Impersonatable] - mixin - - rep:impersonators (STRING) protected multiple - -[rep:User] > rep:Authorizable, rep:Impersonatable - - rep:password (STRING) protected mandatory - - rep:disabled (STRING) protected - -/* --- A node type cannot be multiple !!! --- -[rep:Group] > rep:Authorizable - + rep:members (rep:Members) = rep:Members multiple protected VERSION - - rep:members (WEAKREFERENCE) protected multiple < 'rep:Authorizable' -*/ - -[rep:AuthorizableFolder] > nt:hierarchyNode - + * (rep:Authorizable) = rep:User VERSION - + * (rep:AuthorizableFolder) = rep:AuthorizableFolder VERSION - -/* --- A node type cannot be multiple !!! --- -[rep:Members] - orderable - + * (rep:Members) = rep:Members protected multiple - - * (WEAKREFERENCE) protected < 'rep:Authorizable' -*/ - -// ----------------------------------------------------------------------------- -// J A C K R A B B I T R E T E N T I O N M A N A G E M E N T -// ----------------------------------------------------------------------------- - -[rep:RetentionManageable] - mixin? ord - - rep:hold (UNDEFINED) protected multiple IGNORE - - rep:retentionPolicy (UNDEFINED) protected IGNORE diff --git a/tests/PHPCR/Tests/Util/CND/Fixtures/cnd/no-stop-at-eof.cnd b/tests/PHPCR/Tests/Util/CND/Fixtures/cnd/no-stop-at-eof.cnd deleted file mode 100644 index ad6cf80d..00000000 --- a/tests/PHPCR/Tests/Util/CND/Fixtures/cnd/no-stop-at-eof.cnd +++ /dev/null @@ -1,5 +0,0 @@ -<'phpcr'='http://www.doctrine-project.org/projects/phpcr_odm'> -[phpcr:primary_item_test] > ? -- phpcr:content (STRING) -= ? -< ? diff --git a/tests/PHPCR/Tests/Util/CND/Parser/CndParserTest.php b/tests/PHPCR/Tests/Util/CND/Parser/CndParserTest.php deleted file mode 100644 index df03e76a..00000000 --- a/tests/PHPCR/Tests/Util/CND/Parser/CndParserTest.php +++ /dev/null @@ -1,74 +0,0 @@ -assertParsedFile(__DIR__ . '/../Fixtures/cnd/example.cnd', $this->expectedExampleTree); - } - - public function testParseCompact() - { - $this->assertParsedFile(__DIR__ . '/../Fixtures/cnd/example.compact.cnd', $this->expectedExampleTree); - } - - public function testParseVerbose() - { - $this->assertParsedFile(__DIR__ . '/../Fixtures/cnd/example.verbose.cnd', $this->expectedExampleTree); - } - - public function testParseExample1() - { - // Assert there are no exceptions - $this->parseFile(__DIR__ . '/../Fixtures/cnd/example1.cnd'); - - $this->assertTrue(true); // To avoid the test being marked incomplete - // TODO: write some real tests - - } - - public function testParseJackrabbitBuiltin() - { - $this->parseFile(__DIR__ . '/../Fixtures/cnd/jackrabbit-builtin-nodetypes.cnd'); - - $this->assertTrue(true); // To avoid the test being marked incomplete - // TODO: write some real tests - } - - /** - * Test the case where the parser did not parse correctly - * the default values at the end of the parsed file. - * - * Assert no exception is thrown - * - * @return void - */ - public function testNoStopAtEofError() - { - $this->parseFile(__DIR__ . '/../Fixtures/cnd/no-stop-at-eof.cnd'); - - $this->assertTrue(true); // To avoid the test being marked incomplete - // TODO: write some real tests - } - - protected function parseFile($file) - { - $reader = new FileReader($file); - $scanner = new GenericScanner(new Context\DefaultScannerContextWithoutSpacesAndComments()); - $queue = $scanner->scan($reader); - - //define('DEBUG', true); - - $parser = new CndParser($queue); - return $parser->parse(); - } - -} From f666c3647bcdd633b58052cacd715653f0107858 Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Thu, 28 Mar 2013 14:22:43 +0100 Subject: [PATCH 12/12] solved the multiple child node mystery --- src/PHPCR/Util/CND/Parser/CndParser.php | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/src/PHPCR/Util/CND/Parser/CndParser.php b/src/PHPCR/Util/CND/Parser/CndParser.php index 44e7b6b8..3e7a2bf8 100644 --- a/src/PHPCR/Util/CND/Parser/CndParser.php +++ b/src/PHPCR/Util/CND/Parser/CndParser.php @@ -53,7 +53,9 @@ class CndParser extends AbstractParser private $NOQUERYORDER = array('nqord', 'noqueryorder'); //, 'variant' => true), // child node attributes - private $SNS = array('*', 'sns'); //, 'variant' => true), + // multiple is actually a jackrabbit specific synonym for sns + // http://www.mail-archive.com/users@jackrabbit.apache.org/msg19268.html + private $SNS = array('*', 'sns', 'multiple'); //, 'variant' => true), /** * @var NodeTypeManagerInterface @@ -594,18 +596,6 @@ protected function parseChildNodeAttributes( NodeTypeTemplateInterface $parentType, NodeDefinitionTemplateInterface $childType ) { - /** - * TODO: Clarify this problem - * - * Either there is a bug in the Jackrabbit builtin nodetypes CND file here: - * - * [rep:Group] > rep:Authorizable - * + rep:members (rep:Members) = rep:Members multiple protected VERSION - * - rep:members (WEAKREFERENCE) protected multiple < 'rep:Authorizable' - * - * or there is an error in the spec that says that a node attribute cannot be - * "multiple". - */ while(true) { if ($this->checkTokenIn(Token::TK_IDENTIFIER, $this->PRIMARYITEM)) { $parentType->setPrimaryItemName($childType->getName());