protected function parseTokens($definition)
	{
		$tokenTypes = [
			'choice' => 'CHOICE[0-9]*=(?<choices>.+?)',
			'map'    => '(?:HASH)?MAP[0-9]*=(?<map>.+?)',
			'parse'  => 'PARSE=(?<regexps>' . self::REGEXP . '(?:,' . self::REGEXP . ')*)',
			'range'  => 'RANGE[0-9]*=(?<min>-?[0-9]+),(?<max>-?[0-9]+)',
			'regexp' => 'REGEXP[0-9]*=(?<regexp>' . self::REGEXP . ')',
			'other'  => '(?<other>[A-Z_]+[0-9]*)'
		];

		// Capture the content of every token in that attribute's definition. Usually there will
		// only be one, as in "foo={URL}" but some older BBCodes use a form of composite
		// attributes such as [FLASH={NUMBER},{NUMBER}]
		preg_match_all(
			'#\\{(' . implode('|', $tokenTypes) . ')(?<options>\\??(?:;[^;]*)*)\\}#',
			$definition,
			$matches,
			PREG_SET_ORDER | PREG_OFFSET_CAPTURE
		);

		$tokens = [];
		foreach ($matches as $m)
		{
			if (isset($m['other'][0])
			 && preg_match('#^(?:CHOICE|HASHMAP|MAP|REGEXP|PARSE|RANGE)#', $m['other'][0]))
			{
				throw new RuntimeException("Malformed token '" . $m['other'][0] . "'");
			}

			$token = [
				'pos'     => $m[0][1],
				'content' => $m[0][0],
				'options' => (isset($m['options'][0])) ? $this->parseOptionString($m['options'][0]) : []
			];

			// Get this token's type by looking at the start of the match
			$head = $m[1][0];
			$pos  = strpos($head, '=');

			if ($pos === false)
			{
				// {FOO}
				$token['id'] = $head;
			}
			else
			{
				// {FOO=...}
				$token['id'] = substr($head, 0, $pos);

				// Copy the content of named subpatterns into the token's config
				foreach ($m as $k => $v)
				{
					if (!is_numeric($k) && $k !== 'options' && $v[1] !== -1)
					{
						$token[$k] = $v[0];
					}
				}
			}

			// The token's type is its id minus the number, e.g. NUMBER1 => NUMBER
			$token['type'] = rtrim($token['id'], '0123456789');

			// {PARSE} tokens can have several regexps separated with commas, we split them up here
			if ($token['type'] === 'PARSE')
			{
				// Match all occurences of a would-be regexp followed by a comma or the end of the
				// string
				preg_match_all('#' . self::REGEXP . '(?:,|$)#', $token['regexps'], $m);

				$regexps = [];
				foreach ($m[0] as $regexp)
				{
					// remove the potential comma at the end
					$regexps[] = rtrim($regexp, ',');
				}

				$token['regexps'] = $regexps;
			}

			$tokens[] = $token;
		}

		return $tokens;
	}
protected function parseTokens($definition)
	{
		$tokenTypes = [
			'choice' => 'CHOICE[0-9]*=(?<choices>.+?)',
			'map'    => '(?:HASH)?MAP[0-9]*=(?<map>.+?)',
			'parse'  => 'PARSE=(?<regexps>' . self::REGEXP . '(?:,' . self::REGEXP . ')*)',
			'range'  => 'RANGE[0-9]*=(?<min>-?[0-9]+),(?<max>-?[0-9]+)',
			'regexp' => 'REGEXP[0-9]*=(?<regexp>' . self::REGEXP . ')',
			'other'  => '(?<other>[A-Z_]+[0-9]*)'
		];

		// Capture the content of every token in that attribute's definition. Usually there will
		// only be one, as in "foo={URL}" but some older BBCodes use a form of composite
		// attributes such as [FLASH={NUMBER},{NUMBER}]
		preg_match_all(
			'#\\{(' . implode('|', $tokenTypes) . ')(?<options>\\??(?:;[^;]*)*)\\}#',
			$definition,
			$matches,
			PREG_SET_ORDER | PREG_OFFSET_CAPTURE
		);

		$tokens = [];
		foreach ($matches as $m)
		{
			if (isset($m['other'][0])
			 && preg_match('#^(?:CHOICE|HASHMAP|MAP|REGEXP|PARSE|RANGE)#', $m['other'][0]))
			{
				throw new RuntimeException("Malformed token '" . $m['other'][0] . "'");
			}

			$token = [
				'pos'     => $m[0][1],
				'content' => $m[0][0],
				'options' => (isset($m['options'][0])) ? $this->parseOptionString($m['options'][0]) : []
			];

			// Get this token's type by looking at the start of the match
			$head = $m[1][0];
			$pos  = strpos($head, '=');

			if ($pos === false)
			{
				// {FOO}
				$token['id'] = $head;
			}
			else
			{
				// {FOO=...}
				$token['id'] = substr($head, 0, $pos);

				// Copy the content of named subpatterns into the token's config
				foreach ($m as $k => $v)
				{
					if (!is_numeric($k) && $k !== 'options' && $v[1] !== -1)
					{
						$token[$k] = $v[0];
					}
				}
			}

			// The token's type is its id minus the number, e.g. NUMBER1 => NUMBER
			$token['type'] = rtrim($token['id'], '0123456789');

			// {PARSE} tokens can have several regexps separated with commas, we split them up here
			if ($token['type'] === 'PARSE')
			{
				// Match all occurences of a would-be regexp followed by a comma or the end of the
				// string
				preg_match_all('#' . self::REGEXP . '(?:,|$)#', $token['regexps'], $m);

				$regexps = [];
				foreach ($m[0] as $regexp)
				{
					// remove the potential comma at the end
					$regexps[] = rtrim($regexp, ',');
				}

				$token['regexps'] = $regexps;
			}

			$tokens[] = $token;
		}

		return $tokens;
	}
protected function parseTokens ( $ definition ) { $ tokenTypes = [ 'choice' => 'CHOICE[0-9]*=(?<choices>.+?)' , 'map' => '(?:HASH)?MAP[0-9]*=(?<map>.+?)' , 'parse' => 'PARSE=(?<regexps>' . self :: REGEXP . '(?:,' . self :: REGEXP . ')*)' , 'range' => 'RANGE[0-9]*=(?<min>-?[0-9]+),(?<max>-?[0-9]+)' , 'regexp' => 'REGEXP[0-9]*=(?<regexp>' . self :: REGEXP . ')' , 'other' => '(?<other>[A-Z_]+[0-9]*)' ] ; // Capture the content of every token in that attribute's definition. Usually there will // only be one, as in "foo={URL}" but some older BBCodes use a form of composite // attributes such as [FLASH={NUMBER},{NUMBER}] preg_match_all ( '#\\{(' . implode ( '|' , $ tokenTypes ) . ')(?<options>\\??(?:;[^;]*)*)\\}#' , $ definition , $ matches , PREG_SET_ORDER | PREG_OFFSET_CAPTURE ) ; $ tokens = [ ] ; foreach ( $ matches as $ m ) { if ( isset ( $ m [ 'other' ] [ 0 ] ) && preg_match ( '#^(?:CHOICE|HASHMAP|MAP|REGEXP|PARSE|RANGE)#' , $ m [ 'other' ] [ 0 ] ) ) { throw new RuntimeException ( "Malformed token '" . $ m [ 'other' ] [ 0 ] . "'" ) ; } $ token = [ 'pos' => $ m [ 0 ] [ 1 ] , 'content' => $ m [ 0 ] [ 0 ] , 'options' => ( isset ( $ m [ 'options' ] [ 0 ] ) ) ? $ this -> parseOptionString ( $ m [ 'options' ] [ 0 ] ) : [ ] ] ; // Get this token's type by looking at the start of the match $ head = $ m [ 1 ] [ 0 ] ; $ pos = strpos ( $ head , '=' ) ; if ( $ pos === false ) { // {FOO} $ token [ 'id' ] = $ head ; } else { // {FOO=...} $ token [ 'id' ] = substr ( $ head , 0 , $ pos ) ; // Copy the content of named subpatterns into the token's config foreach ( $ m as $ k => $ v ) { if ( ! is_numeric ( $ k ) && $ k !== 'options' && $ v [ 1 ] !== - 1 ) { $ token [ $ k ] = $ v [ 0 ] ; } } } // The token's type is its id minus the number, e.g. NUMBER1 => NUMBER $ token [ 'type' ] = rtrim ( $ token [ 'id' ] , '0123456789' ) ; // {PARSE} tokens can have several regexps separated with commas, we split them up here if ( $ token [ 'type' ] === 'PARSE' ) { // Match all occurences of a would-be regexp followed by a comma or the end of the // string preg_match_all ( '#' . self :: REGEXP . '(?:,|$)#' , $ token [ 'regexps' ] , $ m ) ; $ regexps = [ ] ; foreach ( $ m [ 0 ] as $ regexp ) { // remove the potential comma at the end $ regexps [ ] = rtrim ( $ regexp , ',' ) ; } $ token [ 'regexps' ] = $ regexps ; } $ tokens [ ] = $ token ; } return $ tokens ; }
Parse and return all the tokens contained in a definition

@param  string $definition
@return array
Parse and return all the tokens contained in a definition