Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 148 additions & 0 deletions src/PHPCR/Util/CndParser/CndParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
<?php

namespace PHPCR\Util\CndParser;

class CndParser
{
protected $lineNo = 0;
protected $lines = array();
protected $scnr;

protected $validPropTypes = array(
'string',
'binary',
'long',
'double',
'boolean',
'date',
'name',
'path',
'reference',
'undefined',
'*'
);

public function parse($lines)
{
$this->lines = $lines;
$this->scnr = new Scanner($lines);
$this->check('Cnd');
}

public function getScanner()
{
return $this->scnr;
}


protected function check($symbol, $optional = false)
{
$method = 'do'.$symbol;
try {
$this->$method();
return true;
} catch (ParseError $e) {
if (false === $optional) {
throw $e;
}

$this->scnr->reweindToLastCommit();
return false;
}
}

protected function checkMany($symbols)
{
foreach ($symbols as $symbol) {
if (false === $this->check($symbol, true)) {
break;
}
}
}

protected function doCnd()
{
$this->check('NsMapping', false);
$this->check('NodeTypeDef', false);
}

protected function doNsMapping()
{
$this->scnr->expect('symbol', '<');
$this->scnr->expect('string', 'ns');
$this->scnr->expect('symbol', '=');
$ns = $this->scnr->getValue('string');
$this->scnr->expect('symbol', '>');
$this->scnr->commit();
}

protected function doNodeTypeDef()
{
$this->check('NodeTypeName');
$this->check('Supertypes', true);
$this->checkUnordered(array(
'Orderable',
'Mixin'
));
$this->checkMany('PropertyDef');
}

protected function doNodeTypeName()
{
$this->scnr->expect('symbol', '[');
$fullName = $this->scnr->getValue('string');
$this->scnr->expect('symbol', ']');
$this->scnr->commit();
}

protected function doSupertypes()
{
$supertypes = array();
$this->scnr->expect('symbol', '>');
while ($string = $this->scnr->getValue('string')) {
$supertypes[] = $string;
if (null === $this->scnr->expect('symbol', ',', true)) {
break;
}
}
$this->scnr->commit();
}

protected function doOrderable()
{
$this->scnr->expect('string', array('orderable', 'ord', 'o'));
$this->commit();
$orderable = true;
}

protected function doMixin()
{
$this->scnr->expect('string', array('mixin', 'mix', 'm'));
$this->commit();
$mixin = true;
}

protected function doPropertyDef()
{
$this->scnr->expect('symbol', '-');
$propName = $this->scnr->getValue('string');
$this->scnr->expect('symbol', '(');
$propType = strtolower($this->scnr->getValue('string')); // just make them all lowercase

// validate property type
if (!in_array($propType, $this->validPropTypes)) {
$t = $this->scnr->getCurrentToken();
throw new ParseError(sprintf('Property "%s" has invalid type "%s", valid types are: "%s"',
$propName, $propType, implode(',', $this->validPropTypes)), $t['lineNo']
);
}

$this->scnr->expect('symbol', ')');
$this->scnr->commit();
}

public function __toString()
{
return "Dump:\n".implode("\n", $this->lines);
}
}
12 changes: 12 additions & 0 deletions src/PHPCR/Util/CndParser/ParseError.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace PHPCR\Util\CndParser;

class ParseError extends \Exception
{
public function __construct($message, $lineNo)
{
$message = '[line: '.$lineNo.'] '.$message;
parent::__construct($message);
}
}
7 changes: 7 additions & 0 deletions src/PHPCR/Util/CndParser/ParseExpectError.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace PHPCR\Util\CndParser;

class ParseError extends \Exception
{
}
162 changes: 162 additions & 0 deletions src/PHPCR/Util/CndParser/Scanner.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
<?php

namespace PHPCR\Util\CndParser;

class Scanner
{
protected $lineNo = 0;
protected $lines;
protected $buffer = '';

protected $tokens = array();
protected $tokenPos = 0;
protected $tokenCommitPos = 0;

const RGX_UNQUOTED_STRING = '([a-zA-Z0-9:_/\.])';
const RGX_SYMBOL = '([\(\)<>\[\-=\+\],])';
const RGX_QUOTE = '(["\'])';
const RGX_COMMENT_LINE = '&^\s*(//|/\*.*\*/).*$&';

public function __construct($lines)
{
$this->lines = $lines;
$this->tokens = $this->init();
}

public function init()
{
foreach ($this->lines as $i => $line) {
$this->lineNo = $i;
if (preg_match(self::RGX_COMMENT_LINE, $line)) {
continue;
}
if (trim($line) == '') {
continue;
}

$this->tokenize($line);
$this->flushBuffer();
}

return $this->tokens;
}

protected function addToken($type, $value)
{
$this->tokens[] = array(
'type' => $type,
'value' => $value,
'lineNo' => $this->lineNo
);
}

protected function flushBuffer()
{
if ($this->buffer) {
$this->addToken('string', $this->buffer);
$this->buffer = '';
}
}

protected function tokenize($line)
{
$stringQuoteOpen = false;

for ($i = 0; $i < strlen($line); $i++) {
$char = substr($line, $i, 1);

if (preg_match(self::RGX_QUOTE, $char)) {
if ($stringQuoteOpen == $char) {
$this->flushBuffer();
$stringQuoteOpen = false;
} elseif (false === $stringQuoteOpen) {
$stringQuoteOpen = $char;
}

continue;
}

if (preg_match(self::RGX_UNQUOTED_STRING, $char)) {
$this->buffer .= $char;
}

if (false === $stringQuoteOpen && $char == ' ') {
$this->flushBuffer();
}

if (false === $stringQuoteOpen && preg_match(self::RGX_SYMBOL, $char)) {
$this->flushBuffer();
$this->addToken('symbol', $char);
}
}
}

protected function getCurrentToken()
{
return $this->tokens[$this->tokenPos];
}

public function getTokens()
{
return $this->tokens;
}

public function expect($type, $values = null, $optional = false)
{
if (!is_array($values)) {
$values = array($values);
}

$ret = null;

$t = $this->getCurrentToken();

if ($type != $t['type'] && false === $optional) {
if ($value) {
$value = implode(',', $value);
throw new ParseError(sprintf(
'Expected token of type "%s" with value "%s", got "%s" with value "%s"',
$type, $value, $t['type'], $t['value']), $t['lineNo']
);
}
throw new ParseError(sprintf('Expected token of type "%s", got "%s": %s', $type, $t['type'], $t['value']), $t['lineNo']);
}

if (null === $value && $type == $t['type']) {
$ret = $t['value'];
}

foreach ($values as $value) {
if ($t['type'] == $type && $t['value'] == $value) {
$ret = $value;
}
}

$this->inc();

return $ret;
}

public function inc()
{
$this->tokenPos++;
}

public function commit()
{
$this->tokenCommitPos = $this->tokenPos;
}

public function reweindToLastCommit()
{
$this->tokenPos = $this->tokenCommitPos;
}

public function getValue()
{
$t = $this->getCurrentToken();
$this->inc();
return $t['value'];

}
}
22 changes: 22 additions & 0 deletions tests/PHPCR/Tests/Util/CndParser/CndParserTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace PHPCR\Tests\Util\CndParser;

use PHPCR\Util\CndParser\CndParser;

class CndParserTest extends \PHPUnit_Framework_TestCase
{
public function setUp()
{
$this->parser = new CndParser;
}

public function testExample()
{
$example = file_get_contents(__DIR__.'/cnd/verbose.cnd');
$lines = explode("\n", $example);
$this->parser->parse($lines);
die(print_r($this->parser->getScanner()->getTokens()));
$this->assertTrue(true);
}
}
12 changes: 12 additions & 0 deletions tests/PHPCR/Tests/Util/CndParser/cnd/example.cnd
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
asdsad /* An example node type definition */ asdasd
<ns = 'http://namespace.com/ns'>
[ns:NodeType] > ns:ParentType1, ns:ParentType2
orderable mixin
- ex:property (string)
= 'default1', 'default2'
primary mandatory autocreated protected multiple
version
< 'constraint1', 'constraint2'
+ ns:node (ns:reqType1, ns:reqType2)
= ns:defaultType
mandatory autocreated protected multiple versionT
Loading