Skip to content

Instantly share code, notes, and snippets.

@kgilden
Created September 3, 2011 20:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kgilden/1191703 to your computer and use it in GitHub Desktop.
Save kgilden/1191703 to your computer and use it in GitHub Desktop.
<?php
// /lib/Gilden/DataProvider/DataProvider.php
/**
* Copyright (C) 2011 by K. Gilden
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
namespace Gilden\DataProvider;
/**
* An abstract implementation of the DataProviderInterface. It is not necessary
* to extend this class, just implement the DataProviderInterface.
*/
abstract class DataProvider implements DataProviderInterface
{
/**
* This is just a stub method. You probably want to
* write your own method for more complex data retrieval
* (such as getting a row by id from a database).
*
* {@inheritDoc}
*/
public function getById($id)
{
return array('id' => $id);
}
}
<?php
// /lib/Gilden/DataProvider/DataProviderInterface.php
/**
* Copyright (C) 2011 by K. Gilden
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
namespace Gilden\DataProvider;
/**
* A data provider interface.
*/
interface DataProviderInterface
{
/**
* Get some data by id. The implementation is totally up to you, where
* the data is retrieved from.
*
* @param integer $id Resource id
* @param string $type Resource type (e.g "link", "media" etc.)
* @return array An associative array consisting the data.
* For example a link resource should return:
* array
* (
* 'id' => $id,
* 'href' => 'http://example.com'
* 'text' => 'Link title'
* )
*/
function findById($id, $type);
}
<?php
// /example.php
/**
* Copyright (C) 2011 by K. Gilden
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* This is just an example of how it all comes together.
*/
use Gilden\Parser\TagParser;
use Gilden\DataProvider\DataProviderInterface;
function __autoload($name)
{
$name = str_replace('\\', '/', $name);
require_once __DIR__.'/lib/'.$name.'.php';
}
/**
* We need to implement the DataProviderInterface for the sake of this
* example. This is the place, where the TagProvider gets the data
* for the tags.
*/
class DataProvider implements DataProviderInterface
{
public function findById($id, $type)
{
if($type === TagParser::TYPE_LINK)
{
return array('href' => 'google.com', 'content' => 'Google');
}
elseif($type === TagParser::TYPE_MEDIA)
{
return array('src' => 'example.swf');
}
}
}
$dp = new DataProvider();
$parser = new TagParser($dp);
$parser->setText('This is [link id=1]');
echo $parser->getHtml();
<?php
// /lib/Gilden/Parser/InvalidTypeException.php
/**
* Copyright (C) 2011 by K. Gilden
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
namespace Gilden\Parser;
/**
* This exception will be thrown, if a tag is of unkown type.
*/
class InvalidTypeException extends \Exception
{
/**
* @param string $type
*/
public function __construct($type)
{
parent::__construct("'$type' is not a valid tag type");
}
}
<?php
// /lib/Gilden/Parser/TagParser.php
/**
* Copyright (C) 2011 by K. Gilden
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
namespace Gilden\Parser;
use Gilden\DataProvider\DataProviderInterface;
/**
* Tag parser class.
*/
class TagParser implements TagParserInterface
{
const TAG_PATTERN = '/\[.*?\]/';
// Tag types
const TYPE_MEDIA = 'media';
const TYPE_LINK = 'link';
/**
* Data provider
*/
protected $dp;
/**
* Rendered html version
*
* @var string $html
*/
protected $html;
/**
* @var boolean $rendered
*/
protected $rendered;
/**
* Raw input text to be parsed
*
* @var string $text
*/
protected $text;
/**
* Constructor.
*/
public function __construct(DataProviderInterface $dp)
{
$this->rendered = false;
$this->dp = $dp;
}
/**
* {@inheritDoc}
*/
public function getHtml()
{
if(!$this->rendered)
{
$this->render();
}
return $this->html;
}
/**
* {@inheritDoc}
*/
public function setText($text)
{
$this->text = $text;
$this->rendered = false;
return $this;
}
/**
* Gets parameters from a tag. Right now this method is rather
* naive. It assumes the tag no syntax errors. Valid tags include:
*
* - [type attr1=val1 attr2='val 2']
*
* These examples are not valid tags and might make this method
* return unexpected results
*
* - [] - no params at all
* - [attr1=val1] - missing type
* - [type attr1='val1] - wrong use of quotes
* - [type attr1=val 1] - missing quotes
*
* @param string $tag
* @return array $params An array of all the parameters
*/
protected function getParameters($tag)
{
// remove "[" and "]"
$tag = substr($tag, 1, -1);
$params = array();
$bits = explode(' ', $tag);
$params['type'] = array_shift($bits);
$processed = array();
$j = 0;
foreach($bits as $i => $bit)
{
$bit = explode('=', $bit);
// Remove quotes from the value.
if(isset($bit[1])
&& $bit[1][0] === "'"
&& $bit[1][strlen($bit[1]) - 1] === "'")
{
$bit[1] = substr($bit[1], 1, -1);
}
if($bit[0][strlen($bit[0]) - 1] === "'")
{
// The bit is the end of a quoted value.
$processed[$j-1]['value'] .= ' '.substr($bit[0], 0, -1);
}
elseif($bit[0][0] === "'")
{
// The bit is the beginning of a quoted value.
echo 'beginning';
$processed[$j-1]['value'] = substr($bit[0], 1);
}
elseif(!isset($bit[1]))
{
// The bit is a continuation of the previous bit.
$processed[$j-1]['value'] .= ' '.$bit[0];
}
else
{
if($bit[1][0] === "'")
{
$bit[1] = substr($bit[1], 1);
}
$processed[$j++] = array('name' => $bit[0], 'value' => $bit[1]);
}
}
foreach($processed as $param)
{
$params[$param['name']] = $param['value'];
}
return $params;
}
/**
* Render the raw text template into html
*/
protected function render()
{
$this->rendered = true;
$this->html = preg_replace_callback(self::TAG_PATTERN, array($this, 'replace'), $this->text);
}
/**
* Replace a tag with a html element
*
* @param string $tag
*/
protected function replace($tag)
{
$params = $this->getParameters($tag[0]);
if(isset($params['id']))
{
$id = $params['id'];
unset($params['id']);
$params_external = $this->dp->findById($id, $params['type']);
if(!is_array($params_external))
{
throw new \Exception(sprintf('Expected array, got %s instead', gettype($params_external)));
}
$params = array_merge($params, $params_external);
}
switch($params['type'])
{
case self::TYPE_MEDIA:
return $this->renderMedia($params);
case self::TYPE_LINK:
return $this->renderLink($params);
default:
throw new InvalidTypeException($params['type']);
}
}
/**
* Render a HTML link tag
*
* @param array $params
* @return string $media
*/
protected function renderLink(array $params)
{
$specials = array('content', 'type');
if(!isset($params['href']))
{
return '[Error! Link tag is missing a ´href´ parameter!]';
}
$link = '<a';
$attr = $this->renderAttributes($params, $specials);
if(isset($attr[0]))
{
$link .= $attr;
}
$link .= '>'.(isset($params['content']) ? $params['content'] : $params['href']).'</a>';
return $link;
}
/**
* Render a flash object tag. This does not work in IE. You must modify
* this function to have it output html similar to
* http://kb2.adobe.com/cps/415/tn_4150.html#main_Add_the_OBJECT_tag_manually
*
* @param array $params
* @return string $media
*/
protected function renderMedia(array $params)
{
$specials = array('src', 'type');
if(!isset($params['src']))
{
return '[Error! Media tag is missing a ´src´ parameter!]';
}
$media = '<object';
$attr = $this->renderAttributes($params, $specials);
if(isset($attr[0]))
{
$media .= $attr;
}
$media .= '><param name="movie" value="'.$params['src'].'"></param></object>';
return $media;
}
/**
* Render attributes.
*
* @param array $params Attributes to render
* @param array $ignore All attributes in this array will be ignored
* @return string $ret Attributes (' foo="bar" bar="foo" ...')
*/
protected function renderAttributes(array $params, array $ignore = array())
{
$ret = '';
foreach($params as $key => $param)
{
if(in_array($key, $ignore))
{
continue;
}
$ret .= ' '.$key.sprintf('="%s"', $param);
}
return $ret;
}
}
<?php
// /lib/Gilden/Parser/TagParserInterface.php
/**
* Copyright (C) 2011 by K. Gilden
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
namespace Gilden\Parser;
interface TagParserInterface
{
/**
* Set the text for rendering.
*
* @param string $text
* @return TagParserInterface
*/
function setText($text);
/**
* Get the rendered html.
*
* @param string Rendered html
*/
function getHtml();
}
<?php
// /lib/Gilden/Tests/TagParserTest.php
/**
* Copyright (C) 2011 by K. Gilden
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
namespace Gilden\Tests\Parser\TagParser;
use Gilden\DataProvider\DataProvider;
use Gilden\Parser\TagParser;
/**
* This is the basic unit test to test the tag parser.
*/
class TagParserText extends \PHPUnit_Framework_TestCase
{
public function testGetHtmlContainingLink()
{
$parser = new TagParser($this->getLinkMock());
$text = "This is [link id=1], [link id=1 class='foo bar']";
$expected = 'This is <a href="http://example.com">example</a>, <a class="foo bar" href="http://example.com">example</a>';
$parser->setText($text);
$this->assertEquals($expected, $parser->getHtml());
}
public function testGetHtmlContainingObject()
{
$parser = new TagParser($this->getMediaMock());
$text = 'Check this swf: [media id=1 width=660 height=440]';
$expected = 'Check this swf: <object width="660" height="440"><param name="movie" value="movie_name.swf"></param></object>';
$parser->setText($text);
$this->assertEquals($expected, $parser->getHtml());
}
/**
* @expectedException \Gilden\Parser\InvalidTypeException
*/
public function testGetHtmlContainingInvalid()
{
$parser = new TagParser($this->getLinkMock());
$text = "Invalid type [invalid id=1 foo='bar foo']";
$parser->setText($text)->getHtml();
}
private function getLinkMock()
{
$return_data = array
(
'href' => 'http://example.com',
'content' => 'example'
);
$mock = $this->getMock('\Gilden\DataProvider\DataProvider', array('getById'));
$mock->expects($this->any())
->method('getById')
->will($this->returnValue($return_data));
return $mock;
}
private function getMediaMock()
{
$return_data = array
(
'src' => 'movie_name.swf'
);
$mock = $this->getMock('\Gilden\DataProvider\DataProvider', array('getById'));
$mock->expects($this->any())
->method('getById')
->will($this->returnValue($return_data));
return $mock;
}
}
function autoload($name)
{
$name = str_replace('\\', '/', $name);
require_once __DIR__.'/../../../'.$name.'.php';
}
spl_autoload_register(__NAMESPACE__.'\autoload');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment