code:
<?php

namespace JBBCode;

require_once 'ElementNode.php';
require_once 'TextNode.php';
require_once 'DefaultCodeDefinitionSet.php';
require_once 'DocumentElement.php';
require_once 'CodeDefinition.php';
require_once 'CodeDefinitionBuilder.php';
require_once 'CodeDefinitionSet.php';
require_once 'NodeVisitor.php';
require_once 'ParserException.php';
require_once 'Tokenizer.php';
require_once 'visitors/NestLimitVisitor.php';
require_once 'InputValidator.php';

use JBBCode\CodeDefinition;

/**
 * BBCodeParser is the main parser class that constructs and stores the parse tree. Through this class
 * new bbcode definitions can be added, and documents may be parsed and converted to html/bbcode/plaintext, etc.
 *
 * @author jbowens
 */
class Parser
{

    const OPTION_STATE_DEFAULT = 0;
    const OPTION_STATE_TAGNAME = 1;
    const OPTION_STATE_KEY = 2;
    const OPTION_STATE_VALUE = 3;
    const OPTION_STATE_QUOTED_VALUE = 4;
    const OPTION_STATE_JAVASCRIPT = 5;

    /** @var DocumentElement The root element of the parse tree */
    protected $treeRoot;

    /** @var CodeDefinition[] The list of bbcodes to be used by the parser. */
    protected $bbcodes = array();

    /** @var integer The next node id to use. This is used while parsing. */
    protected $nextNodeid;

    /**
     * Constructs an instance of the BBCode parser
     */
    public function __construct()
    {
        $this->treeRoot = new DocumentElement();
    }

    /**
     * Adds a simple (text-replacement only) bbcode definition
     *
     * @param string  $tagName      the tag name of the code (for example the b in [b])
     * @param string  $replace      the html to use, with {param} and optionally {option} for replacements
     * @param boolean $useOption    whether or not this bbcode uses the secondary {option} replacement
     * @param boolean $parseContent whether or not to parse the content within these elements
     * @param integer $nestLimit    an optional limit of the number of elements of this kind that can be nested within
     *                              each other before the parser stops parsing them.
     * @param InputValidator $optionValidator   the validator to run {option} through
     * @param InputValidator  $bodyValidator     the validator to run {param} through (only used if $parseContent == false)
     *
     * @return Parser
     */
    public function addBBCode($tagName, $replace, $useOption = false, $parseContent = true, $nestLimit = -1,
                              InputValidator $optionValidator = null, InputValidator $bodyValidator = null)
    {
        $builder = new CodeDefinitionBuilder($tagName, $replace);

        $builder->setUseOption($useOption);
        $builder->setParseContent($parseContent);
        $builder->setNestLimit($nestLimit);

        if ($optionValidator) {
            $builder->setOptionValidator($optionValidator);
        }

        if ($bodyValidator) {
            $builder->setBodyValidator($bodyValidator);
        }

        $this->addCodeDefinition($builder->build());

        return $this;
    }

    /**
     * Adds a complex bbcode definition. You may subclass the CodeDefinition class, instantiate a definition of your new
     * class and add it to the parser through this method.
     *
     * @param CodeDefinition $definition the bbcode definition to add
     *
     * @return Parser
     */
    public function addCodeDefinition(CodeDefinition $definition)
    {
        $this->bbcodes[$definition->getTagName()][$definition->usesOption()] = $definition;
        return $this;
    }

    /**
     * Adds a set of CodeDefinitions.
     *
     * @param CodeDefinitionSet $set  the set of definitions to add
     *
     * @return Parser
     */
    public function addCodeDefinitionSet(CodeDefinitionSet $set) {
        foreach ($set->getCodeDefinitions() as $def) {
            $this->addCodeDefinition($def);
        }

        return $this;
    }

    /**
     * Returns the entire parse tree as text. Only {param} content is returned. BBCode markup will be ignored.
     *
     * @return string a text representation of the parse tree
     */
    public function getAsText()
    {
        return $this->treeRoot->getAsText();
    }

    /**
     * Returns the entire parse tree as bbcode. This will be identical to the inputted string, except unclosed tags
     * will be closed.
     *
     * @return string a bbcode representation of the parse tree
     */
    public function getAsBBCode()
    {
        return $this->treeRoot->getAsBBCode();
    }

    /**
     * Returns the entire parse tree as HTML. All BBCode replacements will be made. This is generally the method
     * you will want to use to retrieve the parsed bbcode.
     *
     * @return string a parsed html string
     */
    public function getAsHTML()
    {
        return $this->treeRoot->getAsHTML();
    }

    /**
     * Accepts the given NodeVisitor at the root.
     *
     * @param NodeVisitor $nodeVisitor a NodeVisitor
     *
     * @return Parser
     */
    public function accept(NodeVisitor $nodeVisitor)
    {
        $this->treeRoot->accept($nodeVisitor);

        return $this;
    }
    /**
     * Constructs the parse tree from a string of bbcode markup.
     *
     * @param string $str the bbcode markup to parse
     *
     * @return Parser
     */
    public function parse($str)
    {
        /* Set the tree root back to a fresh DocumentElement. */
        $this->reset();

        $parent = $this->treeRoot;
        $tokenizer = new Tokenizer($str);

        while ($tokenizer->hasNext()) {
            $parent = $this->parseStartState($parent, $tokenizer);
            if ($parent->getCodeDefinition() && false ===
                $parent->getCodeDefinition()->parseContent()) {
                /* We're inside an element that does not allow its contents to be parseable. */
                $this->parseAsTextUntilClose($parent, $tokenizer);
                $parent = $parent->getParent();
            }
        }

        /* We parsed ignoring nest limits. Do an O(n) traversal to remove any elements that
         * are nested beyond their CodeDefinition's nest limit. */
        $this->removeOverNestedElements();

        return $this;
    }

    /**
     * Removes any elements that are nested beyond their nest limit from the parse tree. This
     * method is now deprecated. In a future release its access privileges will be made
     * protected.
     *
     * @deprecated
     */
    public function removeOverNestedElements()
    {
        $nestLimitVisitor = new \JBBCode\visitors\NestLimitVisitor();
        $this->accept($nestLimitVisitor);
    }

    /**
     * Removes the old parse tree if one exists.
     */
    protected function reset()
    {
        // remove any old tree information
        $this->treeRoot = new DocumentElement();
        /* The document element is created with nodeid 0. */
        $this->nextNodeid = 1;
    }

    /**
     * Determines whether a bbcode exists based on its tag name and whether or not it uses an option
     *
     * @param string  $tagName    the bbcode tag name to check
     * @param boolean $usesOption whether or not the bbcode accepts an option
     *
     * @return bool true if the code exists, false otherwise
     */
    public function codeExists($tagName, $usesOption = false)
    {
        return isset($this->bbcodes[strtolower($tagName)][$usesOption]);
    }

    /**
     * Returns the CodeDefinition of a bbcode with the matching tag name and usesOption parameter
     *
     * @param string  $tagName    the tag name of the bbcode being searched for
     * @param boolean $usesOption whether or not the bbcode accepts an option
     *
     * @return CodeDefinition if the bbcode exists, null otherwise
     */
    public function getCode($tagName, $usesOption = false)
    {
        if($this->codeExists($tagName, $usesOption)) {
            return $this->bbcodes[strtolower($tagName)][$usesOption];
        }

        return null;
    }

    /**
     * Adds a set of default, standard bbcode definitions commonly used across the web.
     *
     * This method is now deprecated. Please use DefaultCodeDefinitionSet and
     * addCodeDefinitionSet() instead.
     *
     * @deprecated
     */
    public function loadDefaultCodes()
    {
        $defaultSet = new DefaultCodeDefinitionSet();
        $this->addCodeDefinitionSet($defaultSet);
    }

    /**
     * Creates a new text node with the given parent and text string.
     *
     * @param ElementNode $parent  the parent of the text node
     * @param string $string  the text of the text node
     *
     * @return TextNode the newly created TextNode
     */
    protected function createTextNode(ElementNode $parent, $string)
    {
        $children = $parent->getChildren();
        if (!empty($children)) {
            $lastElement = end($children);
            reset($children);

            if ($lastElement->isTextNode()) {
                $lastElement->setValue($lastElement->getValue() . $string);
                return $lastElement;
            }
        }

        $textNode = new TextNode($string);
        $textNode->setNodeId(++$this->nextNodeid);
        $parent->addChild($textNode);
        return $textNode;
    }

    /**
     * jBBCode parsing logic is loosely modelled after a FSM. While not every function maps
     * to a unique DFSM state, each function handles the logic of one or more FSM states.
     * This function handles the beginning parse state when we're not currently in a tag
     * name.
     *
     * @param ElementNode  $parent the current parent node we're under
     * @param Tokenizer    $tokenizer the tokenizer we're using
     *
     * @return ElementNode the new parent we should use for the next iteration.
     */
    protected function parseStartState(ElementNode $parent, Tokenizer $tokenizer)
    {
        $next = $tokenizer->next();

        if ('[' == $next) {
            return $this->parseTagOpen($parent, $tokenizer);
        }
        else {
            $this->createTextNode($parent, $next);
            /* Drop back into the main parse loop which will call this
             * same method again. */
            return $parent;
        }
    }

    /**
     * This function handles parsing the beginnings of an open tag. When we see a [
     * at an appropriate time, this function is entered.
     *
     * @param ElementNode $parent  the current parent node
     * @param Tokenizer   $tokenizer  the tokenizer we're using
     *
     * @return ElementNode the new parent node
     */
    protected function parseTagOpen(ElementNode $parent, Tokenizer $tokenizer)
    {

        if (!$tokenizer->hasNext()) {
            /* The [ that sent us to this state was just a trailing [, not the
             * opening for a new tag. Treat it as such. */
            $this->createTextNode($parent, '[');
            return $parent;
        }

        $next = $tokenizer->next();

        /* This while loop could be replaced by a recursive call to this same method,
         * which would likely be a lot clearer but I decided to use a while loop to
         * prevent stack overflow with a string like [[[[[[[[[...[[[.
         */
        while ('[' == $next) {
            /* The previous [ was just a random bracket that should be treated as text.
             * Continue until we get a non open bracket. */
            $this->createTextNode($parent, '[');
            if (!$tokenizer->hasNext()) {
                $this->createTextNode($parent, '[');
                return $parent;
            }
            $next = $tokenizer->next();
        }

        if (!$tokenizer->hasNext()) {
            $this->createTextNode($parent, '['.$next);
            return $parent;
        }

        $after_next = $tokenizer->next();
        $tokenizer->stepBack();

        if ($after_next != ']')
        {
            $this->createTextNode($parent, '['.$next);
            return $parent;
        }

        /* At this point $next is either ']' or plain text. */
        if (']' == $next) {
            $this->createTextNode($parent, '[');
            $this->createTextNode($parent, ']');
            return $parent;
        } else {
            /* $next is plain text... likely a tag name. */
            return $this->parseTag($parent, $tokenizer, $next);
        }
    }

    protected function parseOptions($tagContent)
    {
        $buffer = "";
        $tagName = "";
        $state = static::OPTION_STATE_TAGNAME;
        $keys = array();
        $values = array();
        $options = array();

        $len = strlen($tagContent);
        $done = false;
        $idx = 0;

        try{
            while(!$done){
                $char = $idx < $len ? $tagContent[$idx]:null;
                switch($state){
                    case static::OPTION_STATE_TAGNAME:
                        switch($char){
                            case '=':
                                $state = static::OPTION_STATE_VALUE;
                                $tagName = $buffer;
                                $keys[] = $tagName;
                                $buffer = "";
                                break;
                            case ' ':
                                if($buffer) {
                                    $state = static::OPTION_STATE_DEFAULT;
                                    $tagName = $buffer;
                                    $buffer = '';
                                    $keys[] = $tagName;
                                }
                                break;
                            case "\n":
                            case "\r":
                                break;

                            case null:
                                $tagName = $buffer;
                                $buffer = '';
                                $keys[] = $tagName;
                                break;
                            default:
                                $buffer .= $char;
                        }
                        break;

                    case static::OPTION_STATE_DEFAULT:
                        switch($char){
                            case ' ':
                                // do nothing
                            default:
                                $state = static::OPTION_STATE_KEY;
                                $buffer .= $char;
                        }
                        break;

                    case static::OPTION_STATE_VALUE:
                        switch($char){
                            case '"':
                                $state = static::OPTION_STATE_QUOTED_VALUE;
                                break;
                            case null: // intentional fall-through
                            case ' ': // key=value<space> delimits to next key
                                $values[] = trim($buffer);
                                $buffer = "";
                                $state = static::OPTION_STATE_KEY;
                                break;
                            case ":":
                                if($buffer=="javascript"){
                                    $state = static::OPTION_STATE_JAVASCRIPT;
                                }
                                $buffer .= $char;
                                break;
                            default:
                                $buffer .= $char;

                        }
                        break;

                    case static::OPTION_STATE_JAVASCRIPT:
                        switch($char){
                            case ";":
                                $buffer .= $char;
                                $values[] = $buffer;
                                $buffer = "";
                                $state = static::OPTION_STATE_KEY;

                                break;
                            default:
                                $buffer .= $char;
                        }
                        break;

                    case static::OPTION_STATE_KEY:
                        switch($char){
                            case '=':
                                $state = static::OPTION_STATE_VALUE;
                                $keys[] = trim($buffer);
                                $buffer = '';
                                break;
                            case ' ': // ignore <space>key=value
                                break;
                            default:
                                $buffer .= $char;
                                break;
                        }
                        break;

                    case static::OPTION_STATE_QUOTED_VALUE:
                        switch($char){
                            case null:
                            case '"':
                                $state = static::OPTION_STATE_KEY;
                                $values[] = $buffer;
                                $buffer = '';

                                // peek ahead. If the next character is not a space or a closing brace, we have a bad tag and need to abort
                                if(isset($tagContent[$idx+1]) && $tagContent[$idx+1]!=" " && $tagContent[$idx+1]!="]" ){
                                    throw new ParserException("Badly formed attribute: $tagContent");
                                }
                                break;
                            default:
                                $buffer .= $char;
                                break;
                        }
                        break;
                    default:
                        if(!empty($char)){
                            $state = static::OPTION_STATE_KEY;
                        }

                }
                if($idx >= $len){
                    $done = true;
                }
                $idx++;
            }

            if(!empty($keys) && !empty($values)){
                if(count($keys)==(count($values)+1)){
                    array_unshift($values, "");
                }

                $options = array_combine($keys, $values);
            }
        }
        catch(ParserException $e){
            // if we're in this state, then something evidently went wrong. We'll consider everything that came after the tagname to be the attribute for that keyname
            $options[$tagName]= substr($tagContent, strpos($tagContent, "=")+1);
        }
        return array($tagName, $options);
    }

    /**
     * This is the next step in parsing a tag. It's possible for it to still be invalid at this
     * point but many of the basic invalid tag name conditions have already been handled.
     *
     * @param ElementNode $parent  the current parent element
     * @param Tokenizer   $tokenizer  the tokenizer we're using
     * @param string      $tagContent  the text between the [ and the ], assuming there is actually a ]
     *
     * @return ElementNode the new parent element
     */
    protected function parseTag(ElementNode $parent, Tokenizer $tokenizer, $tagContent)
    {
        if (!$tokenizer->hasNext() || ($next = $tokenizer->next()) != ']') {
            /* This is a malformed tag. Both the previous [ and the tagContent
             * is really just plain text. */
            $this->createTextNode($parent, '[');
            $this->createTextNode($parent, $tagContent);
            return $parent;
        }

        /* This is a well-formed tag consisting of [something] or [/something], but
         * we still need to ensure that 'something' is a valid tag name. Additionally,
         * if it's a closing tag, we need to ensure that there was a previous matching
         * opening tag.
         */
        /* There could be attributes. */
        list($tmpTagName, $options) = $this->parseOptions($tagContent);

        // $tagPieces = explode('=', $tagContent);
        // $tmpTagName = $tagPieces[0];

        $actualTagName = $tmpTagName;
        if ('' != $tmpTagName && '/' == $tmpTagName[0]) {
            /* This is a closing tag name. */
            $actualTagName = substr($tmpTagName, 1);
        }

        if ('' != $tmpTagName && '/' == $tmpTagName[0]) {
            /* This is attempting to close an open tag. We must verify that there exists an
             * open tag of the same type and that there is no option (options on closing
             * tags don't make any sense). */
            $elToClose = $parent->closestParentOfType($actualTagName);
            if (null == $elToClose || count($options) > 1) {
                /* Closing an unopened tag or has an option. Treat everything as plain text. */
                $this->createTextNode($parent, '[');
                $this->createTextNode($parent, $tagContent);
                $this->createTextNode($parent, ']');
                return $parent;
            } else {
                /* We're closing $elToClose. In order to do that, we just need to return
                 * $elToClose's parent, since that will change our effective parent to be
                 * elToClose's parent. */
                return $elToClose->getParent();
            }
        }

        /* Verify that this is a known bbcode tag name. */
        if ('' == $actualTagName || !$this->codeExists($actualTagName, !empty($options))) {
            /* This is an invalid tag name! Treat everything we've seen as plain text. */
            $this->createTextNode($parent, '[');
            $this->createTextNode($parent, $tagContent);
            $this->createTextNode($parent, ']');
            return $parent;
        }

        /* If we're here, this is a valid opening tag. Let's make a new node for it. */
        $el = new ElementNode();
        $el->setNodeId(++$this->nextNodeid);
        $code = $this->getCode($actualTagName, !empty($options));
        $el->setCodeDefinition($code);
        if (!empty($options)) {
            /* We have an attribute we should save. */
            $el->setAttribute($options);
        }
        $parent->addChild($el);
        return $el;
    }

    /**
     * Handles parsing elements whose CodeDefinitions disable parsing of element
     * contents. This function uses a rolling window of 3 tokens until it finds the
     * appropriate closing tag or reaches the end of the token stream.
     *
     * @param ElementNode $parent  the current parent element
     * @param Tokenizer   $tokenizer  the tokenizer we're using
     *
     * @return ElementNode the new parent element
     */
    protected function parseAsTextUntilClose(ElementNode $parent, Tokenizer $tokenizer)
    {
        /* $parent's code definition doesn't allow its contents to be parsed. Here we use
         * a sliding window of three tokens until we find [ /tagname ], signifying the
         * end of the parent. */
        if (!$tokenizer->hasNext()) {
            return $parent;
        }
        $prevPrev = $tokenizer->next();
        if (!$tokenizer->hasNext()) {
            $this->createTextNode($parent, $prevPrev);
            return $parent;
        }
        $prev = $tokenizer->next();
        if (!$tokenizer->hasNext()) {
            $this->createTextNode($parent, $prevPrev);
            $this->createTextNode($parent, $prev);
            return $parent;
        }
        $curr = $tokenizer->next();
        while ('[' != $prevPrev || '/'.$parent->getTagName() != strtolower($prev) ||
            ']' != $curr) {
            $this->createTextNode($parent, $prevPrev);
            $prevPrev = $prev;
            $prev = $curr;
            if (!$tokenizer->hasNext()) {
                $this->createTextNode($parent, $prevPrev);
                $this->createTextNode($parent, $prev);
                return $parent;
            }
            $curr = $tokenizer->next();
        }
    }

}


code:
<?php

namespace JBBCode;
use Exception;

class ParserException extends Exception{
}

repo_name: DaSourcerer/jBBCode

license: mit


code:
<?php

namespace JBBCode;

require_once 'Node.php';

/**
 * Represents a piece of text data. TextNodes never have children.
 *
 * @author jbowens
 */
class TextNode extends Node
{
    /** @var string The value of this text node */
    protected $value;

    /**
     * Constructs a text node from its text string
     *
     * @param string $val
     */
    public function __construct($val)
    {
        $this->value = $val;
    }

    public function accept(NodeVisitor $visitor)
    {
        $visitor->visitTextNode($this);
    }

    /**
     * (non-PHPdoc)
     * @see JBBCode.Node::isTextNode()
     *
     * @returns boolean true
     */
    public function isTextNode()
    {
        return true;
    }

    /**
     * Returns the text string value of this text node.
     *
     * @return string
     */
    public function getValue()
    {
        return $this->value;
    }

    /**
     * (non-PHPdoc)
     * @see JBBCode.Node::getAsText()
     *
     * Returns the text representation of this node.
     *
     * @return string this node represented as text
     */
    public function getAsText()
    {
        return $this->getValue();
    }

    /**
     * (non-PHPdoc)
     * @see JBBCode.Node::getAsBBCode()
     *
     * Returns the bbcode representation of this node. (Just its value)
     *
     * @return string this node represented as bbcode
     */
    public function getAsBBCode()
    {
        return $this->getValue();
    }

    /**
     * (non-PHPdoc)
     * @see JBBCode.Node::getAsHTML()
     *
     * Returns the html representation of this node. (Just its value)
     *
     * @return string this node represented as HTML
     */
    public function getAsHTML()
    {
        return $this->getValue();
    }

    /**
     * Edits the text value contained within this text node.
     *
     * @param string $newValue  the new text value of the text node
     */
    public function setValue($newValue)
    {
        $this->value = $newValue;
    }

}


code:
<?php

namespace JBBCode;

/**
 * This Tokenizer is used while constructing the parse tree. The tokenizer
 * handles splitting the input into brackets and miscellaneous text. The
 * parser is then built as a FSM ontop of these possible inputs.
 *
 * @author jbowens
 */
class Tokenizer
{

    /** @var integer[] the positions of tokens found during parsing */
    protected $tokens = array();

    /** @var integer the number of the current token */
    protected $i = -1;

    /**
     * Constructs a tokenizer from the given string. The string will be tokenized
     * upon construction.
     *
     * @param string $str the string to tokenize
     */
    public function __construct($str)
    {
        $strLen = strlen($str);
        $position = 0;

        while ($position < $strLen) {
            $offset = strcspn($str, '[]', $position);
            //Have we hit a single ']' or '['?
            if ($offset == 0) {
                $this->tokens[] = $str[$position];
                $position++;
            } else {
                $this->tokens[] = substr($str, $position, $offset);
                $position += $offset;
            }
        }
    }

    /**
     * Returns true if there is another token in the token stream.
     * @return boolean
     */
    public function hasNext()
    {
        return isset($this->tokens[$this->i + 1]);
    }

    /**
     * Advances the token stream to the next token and returns the new token.
     * @return null|string
     */
    public function next()
    {
        if (!$this->hasNext()) {
            return null;
        } else {
            return $this->tokens[++$this->i];
        }
    }

    /**
     * Retrieves the current token.
     * @return null|string
     */
    public function current()
    {
        if ($this->i < 0) {
            return null;
        } else {
            return $this->tokens[$this->i];
        }
    }

    /**
     * Moves the token stream back a token.
     */
    public function stepBack()
    {
        if ($this->i > -1) {
            $this->i--;
        }
    }

    /**
     * Restarts the tokenizer, returning to the beginning of the token stream.
     */
    public function restart()
    {
        $this->i = -1;
    }

    /**
     * toString method that returns the entire string from the current index on.
     * @return string
     */
    public function toString()
    {
        return implode('', array_slice($this->tokens, $this->i + 1));
    }

}


code:
<?php
require_once "/path/to/jbbcode/Parser.php";

$parser = new JBBCode\Parser();
$parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());

$text = "The default codes include: [b]bold[/b], [i]italics[/i], [u]underlining[/u], ";
$text .= "[url=http://jbbcode.com]links[/url], [color=red]color![/color] and more.";

$parser->parse($text);

print $parser->getAsHtml();


code:
<?php
require_once "/path/to/jbbcode/Parser.php";

$parser = new JBBCode\Parser();
$parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());

$text = "The bbcode in here [b]is never closed!";
$parser->parse($text);

print $parser->getAsBBCode();


code:
<?php
require_once "/path/to/jbbcode/Parser.php";

$parser = new JBBCode\Parser();
$parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());

$text = "[b][u]There is [i]a lot[/i] of [url=http://en.wikipedia.org/wiki/Markup_language]markup[/url] in this";
$text .= "[color=#333333]text[/color]![/u][/b]";
$parser->parse($text);

print $parser->getAsText();


code:
<?php
require_once "/path/to/jbbcode/Parser.php";

$parser = new JBBCode\Parser();

$parser->addBBCode("quote", '
{param}
');
$parser->addBBCode("code", '
{param}
', false, false, 1);


code:
<?php

require_once("../Parser.php");
require_once("../visitors/SmileyVisitor.php");

error_reporting(E_ALL);

$parser = new JBBCode\Parser();
$parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());

if (count($argv) < 2) {
    die("Usage: " . $argv[0] . " \"bbcode string\"\n");
}

$inputText = $argv[1];

$parser->parse($inputText);

$smileyVisitor = new \JBBCode\visitors\SmileyVisitor();
$parser->accept($smileyVisitor);

echo $parser->getAsHTML() . "\n";


code:
<?php

require_once("../Parser.php");
require_once("../visitors/TagCountingVisitor.php");

error_reporting(E_ALL);

$parser = new JBBCode\Parser();
$parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());

if (count($argv) < 3) {
    die("Usage: " . $argv[0] . " \"bbcode string\" <tag name to check>\n");
}

$inputText = $argv[1];
$tagName = $argv[2];

$parser->parse($inputText);

$tagCountingVisitor = new \JBBCode\visitors\TagCountingVisitor();
$parser->accept($tagCountingVisitor);

echo $tagCountingVisitor->getFrequency($tagName) . "\n";


code:
<?php

require_once(dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'Parser.php');

/**
 * Test cases testing the functionality of parsing bbcode and
 * retrieving a bbcode well-formed bbcode representation.
 *
 * @author jbowens
 */
class BBCodeToBBCodeTest extends PHPUnit_Framework_TestCase
{
    /**
     * @var JBBCode\Parser
     */
    private $_parser;

    protected function setUp()
    {
        $this->_parser = new JBBCode\Parser();
        $this->_parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
    }

    /**
     * A utility method for these tests that will evaluate its arguments as bbcode with
     * a fresh parser loaded with only the default bbcodes. It returns the
     * bbcode output, which in most cases should be in the input itself.
     */
    private function defaultBBCodeParse($bbcode)
    {
        $parser = new JBBCode\Parser();
        $parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
        $parser->parse($bbcode);
        return $parser->getAsBBCode();
    }

    public function testEmptyString()
    {
        $this->assertEmpty($this->_parser->parse('')->getAsBBCode());
    }

    public function testContentCleared()
    {
        $this->assertEquals('foo', $this->_parser->parse('foo')->getAsBBCode());
        $this->assertEquals('bar', $this->_parser->parse('bar')->getAsBBCode());
    }

    /**
     * @param string $code
     * @param string $expected
     * @dataProvider codeProvider
     */
    public function testParse($code, $expected)
    {
        $this->assertEquals($expected, $this->_parser->parse($code)->getAsBBCode());
    }

    public function codeProvider()
    {
        return array(
            array('foo', 'foo'),
            array('[b]this is bold[/b]', '[b]this is bold[/b]'),
            array('[b]bold', '[b]bold[/b]'),
            array(
                'buffer text [b]this is bold[/b] buffer text',
                'buffer text [b]this is bold[/b] buffer text'
            ),
            array(
                'this is some text with [b]bold tags[/b] and [i]italics[/i] and things like [u]that[/u].',
                'this is some text with [b]bold tags[/b] and [i]italics[/i] and things like [u]that[/u].',
            ),
            array(
                'This contains a [url=http://jbbcode.com]url[/url] which uses an option.',
                'This contains a [url=http://jbbcode.com]url[/url] which uses an option.'
            ),
            array(
                'This doesn\'t use the url option [url]http://jbbcode.com[/url].',
                'This doesn\'t use the url option [url]http://jbbcode.com[/url].'
            ),
        );
    }
}


code:
<?php

require_once(dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'Parser.php');

/**
 * Test cases testing the ability to parse bbcode and retrieve a
 * plain text representation without any markup.
 *
 * @author jbowens
 */
class BBCodeToTextTest extends PHPUnit_Framework_TestCase
{
    /**
     * @var JBBCode\Parser
     */
    private $_parser;

    protected function setUp()
    {
        $this->_parser = new JBBCode\Parser();
        $this->_parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
    }

    public function testEmptyString()
    {
        $this->assertEmpty($this->_parser->parse('')->getAsText());
    }

    public function testContentCleared()
    {
        $this->assertEquals('foo', $this->_parser->parse('foo')->getAsText());
        $this->assertEquals('bar', $this->_parser->parse('bar')->getAsText());
    }

    /**
     * @param string $code
     * @param string $expected
     * @dataProvider codeProvider
     */
    public function testParse($code, $expected)
    {
        $this->assertEquals($expected, $this->_parser->parse($code)->getAsText());
    }

    public function codeProvider()
    {
        return array(
            array('foo', 'foo'),
            array('[b]this is bold[/b]', 'this is bold'),
            array('[b]this is bold', 'this is bold'),
            array(
                'buffer text [b]this is bold[/b] buffer text',
                'buffer text this is bold buffer text'
            ),
            array(
                'this is some text with [b]bold tags[/b] and [i]italics[/i] and things like [u]that[/u].',
                'this is some text with bold tags and italics and things like that.'
            ),
            array(
                'This contains a [url=http://jbbcode.com]url[/url] which uses an option.',
                'This contains a url which uses an option.'
            ),
            array(
                'This doesn\'t use the url option [url]http://jbbcode.com[/url].',
                'This doesn\'t use the url option http://jbbcode.com.'
            ),
        );
    }
}


code:
<?php

require_once dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'Parser.php';

/**
 * Test cases for the default bbcode set.
 *
 * @author jbowens
 * @since May 2013
 */
class DefaultCodesTest extends PHPUnit_Framework_TestCase
{

    /**
     * Asserts that the given bbcode string produces the given html string
     * when parsed with the default bbcodes.
     */
    public function assertProduces($bbcode, $html)
    {
        $parser = new \JBBCode\Parser();
        $parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
        $parser->parse($bbcode);
        $this->assertEquals($html, $parser->getAsHtml());
    }

    /**
     * Tests the [b] bbcode.
     */
    public function testBold()
    {
        $this->assertProduces('[b]this should be bold[/b]', 'this should be bold');
    }

    /**
     * Tests the [color] bbcode.
     */
    public function testColor()
    {
        $this->assertProduces('[color=red]red[/color]', 'red');
    }

    /**
     * Tests the example from the documentation.
     */
    public function testExample()
    {
        $text = "The default codes include: [b]bold[/b], [i]italics[/i], [u]underlining[/u], ";
        $text .= "[url=http://jbbcode.com]links[/url], [color=red]color![/color] and more.";
        $html = 'The default codes include: bold, italics, underlining, ';
        $html .= 'links, color! and more.';
        $this->assertProduces($text, $html);
    }

}


code:
<?php

require_once(dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'Parser.php');
require_once(dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'visitors' . DIRECTORY_SEPARATOR . 'HTMLSafeVisitor.php');

/**
 * Test cases testing the HTMLSafe visitor, which escapes all html characters in the source text
 *
 * @author astax-t
 */
class HTMLSafeTest extends PHPUnit_Framework_TestCase
{
    /**
     * Asserts that the given bbcode string produces the given html string
     * when parsed with the default bbcodes.
     */
    public function assertProduces($bbcode, $html)
    {
        $parser = new \JBBCode\Parser();
        $parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
        $parser->parse($bbcode);

		$htmlsafer = new JBBCode\visitors\HTMLSafeVisitor();
		$parser->accept($htmlsafer);

        $this->assertEquals($html, $parser->getAsHtml());
    }

    /**
     * Tests escaping quotes and ampersands in simple text
     */
    public function testQuoteAndAmp()
    {
        $this->assertProduces('te"xt te&xt', 'te"xt te&xt');
    }

    /**
     * Tests escaping quotes and ampersands inside a BBCode tag
     */
    public function testQuoteAndAmpInTag()
    {
        $this->assertProduces('[b]te"xt te&xt[/b]', 'te"xt te&xt');
    }

    /**
     * Tests escaping HTML tags
     */
    public function testHtmlTag()
    {
        $this->assertProduces('not bold', '<b>not bold</b>');
        $this->assertProduces('[b]bold[/b]
', '<b>bold</b> <hr>');
    }

    /**
     * Tests escaping ampersands in URL using [url]...[/url]
     */
    public function testUrlParam()
    {
        $this->assertProduces('text [url]http://example.com/?a=b&c=d[/url] more text', 'text http://example.com/?a=b&c=d more text');
    }

    /**
     * Tests escaping ampersands in URL using [url=...] tag
     */
    public function testUrlOption()
    {
        $this->assertProduces('text [url=http://example.com/?a=b&c=d]this is a "link"[/url]', 'text this is a "link"');
    }

    /**
     * Tests escaping ampersands in URL using [url=...] tag when URL is in quotes
     */
    public function testUrlOptionQuotes()
    {
        $this->assertProduces('text [url="http://example.com/?a=b&c=d"]this is a "link"[/url]', 'text this is a "link"');
    }

}


code:
<?php

require_once dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'Parser.php';

/**
 * Test cases for CodeDefinition nest limits. If an element is nested beyond
 * its CodeDefinition's nest limit, it should be removed from the parse tree.
 *
 * @author jbowens
 * @since May 2013
 */
class NestLimitTest extends PHPUnit_Framework_TestCase
{

    /**
     * Tests that when elements have no nest limits they may be
     * nested indefinitely.
     */
    public function testIndefiniteNesting()
    {
        $parser = new JBBCode\Parser();
        $parser->addBBCode('b', '{param}', false, true, -1);
        $parser->parse('[b][b][b][b][b][b][b][b]bold text[/b][/b][/b][/b][/b][/b][/b][/b]');
        $this->assertEquals('' .
                'bold text' .
                '',
                $parser->getAsHtml());
    }

    /**
     * Test over nesting.
     */
    public function testOverNesting()
    {
        $parser = new JBBCode\Parser();
        $parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
        $parser->addBBCode('quote', '
{param}
', false, true, 2);
        $bbcode = '[quote][quote][quote]wut[/quote] huh?[/quote] i don\'t know[/quote]';
        $parser->parse($bbcode);
        $expectedBbcode = '[quote][quote] huh?[/quote] i don\'t know[/quote]';
        $expectedHtml = '

 huh?
 i don\'t know
';
        $this->assertEquals($expectedBbcode, $parser->getAsBBCode());
        $this->assertEquals($expectedHtml, $parser->getAsHtml());
    }

}


code:
<?php

require_once dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'Parser.php';

/**
 * Test cases for the code definition parameter that disallows parsing
 * of an element's content.
 *
 * @author jbowens
 */
class ParseContentTest extends PHPUnit_Framework_TestCase
{

    /**
     * Tests that when a bbcode is created with parseContent = false,
     * its contents actually are not parsed.
     */
    public function testSimpleNoParsing()
    {

        $parser = new JBBCode\Parser();
        $parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
        $parser->addBBCode('verbatim', '{param}', false, false);

        $parser->parse('[verbatim]plain text[/verbatim]');
        $this->assertEquals('plain text', $parser->getAsHtml());

        $parser->parse('[verbatim][b]bold[/b][/verbatim]');
        $this->assertEquals('[b]bold[/b]', $parser->getAsHtml());

    }

    public function testNoParsingWithBufferText()
    {

        $parser = new JBBCode\Parser();
        $parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
        $parser->addBBCode('verbatim', '{param}', false, false);

        $parser->parse('buffer text[verbatim]buffer text[b]bold[/b]buffer text[/verbatim]buffer text');
        $this->assertEquals('buffer textbuffer text[b]bold[/b]buffer textbuffer text', $parser->getAsHtml());
    }

    /**
     * Tests that when a tag is not closed within an unparseable tag,
     * the BBCode output does not automatically close that tag (because
     * the contents were not parsed).
     */
    public function testUnclosedTag()
    {

        $parser = new JBBCode\Parser();
        $parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
        $parser->addBBCode('verbatim', '{param}', false, false);

        $parser->parse('[verbatim]i wonder [b]what will happen[/verbatim]');
        $this->assertEquals('i wonder [b]what will happen', $parser->getAsHtml());
        $this->assertEquals('[verbatim]i wonder [b]what will happen[/verbatim]', $parser->getAsBBCode());
    }

    /**
     * Tests that an unclosed tag with parseContent = false ends cleanly.
     */
    public function testUnclosedVerbatimTag()
    {
        $parser = new JBBCode\Parser();
        $parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
        $parser->addBBCode('verbatim', '{param}', false, false);

        $parser->parse('[verbatim]yo this [b]text should not be bold[/b]');
        $this->assertEquals('yo this [b]text should not be bold[/b]', $parser->getAsHtml());
    }

    /**
     * Tests a malformed closing tag for a verbatim block.
     */
    public function testMalformedVerbatimClosingTag()
    {
        $parser = new JBBCode\Parser();
        $parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
        $parser->addBBCode('verbatim', '{param}', false, false);
        $parser->parse('[verbatim]yo this [b]text should not be bold[/b][/verbatim');
        $this->assertEquals('yo this [b]text should not be bold[/b][/verbatim', $parser->getAsHtml());
    }

    /**
     * Tests an immediate end after a verbatim.
     */
    public function testVerbatimThenEof()
    {
        $parser = new JBBCode\Parser();
        $parser->addBBCode('verbatim', '{param}', false, false);
        $parser->parse('[verbatim]');
        $this->assertEquals('', $parser->getAsHtml());
    }

}


code:
<?php

require_once(dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'Parser.php');

/**
 * A series of test cases for various potential parsing edge cases. This
 * includes a lot of tests using brackets for things besides genuine tag
 * names.
 *
 * @author jbowens
 *
 */
class ParsingEdgeCaseTest extends PHPUnit_Framework_TestCase
{

    /**
     * A utility method for these tests that will evaluate
     * its arguments as bbcode with a fresh parser loaded
     * with only the default bbcodes. It returns the
     * html output.
     */
    private function defaultParse($bbcode)
    {
        $parser = new JBBCode\Parser();
        $parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
        $parser->parse($bbcode);
        return $parser->getAsHtml();
    }

    /**
     * Asserts that the given bbcode matches the given html when
     * the bbcode is run through defaultParse.
     */
    private function assertProduces($bbcode, $html)
    {
        $this->assertEquals($html, $this->defaultParse($bbcode));
    }

    /**
     * Tests attempting to use a code that doesn't exist.
     */
    public function testNonexistentCodeMalformed()
    {
        $this->assertProduces('[wat]', '[wat]');
    }

    /**
     * Tests attempting to use a code that doesn't exist, but this
     * time in a well-formed fashion.
     *
     * @depends testNonexistentCodeMalformed
     */
    public function testNonexistentCodeWellformed()
    {
        $this->assertProduces('[wat]something[/wat]', '[wat]something[/wat]');
    }

    /**
     * Tests a whole bunch of meaningless left brackets.
     */
    public function testAllLeftBrackets()
    {
        $this->assertProduces('[[[[[[[[', '[[[[[[[[');
    }

    /**
     * Tests a whole bunch of meaningless right brackets.
     */
    public function testAllRightBrackets()
    {
        $this->assertProduces(']]]]]', ']]]]]');
    }

    /**
     * Intermixes well-formed, meaningful tags with meaningless brackets.
     */
    public function testRandomBracketsInWellformedCode()
    {
        $this->assertProduces('[b][[][[i]heh[/i][/b]',
                              '[[][heh');
    }

    /**
     * Tests an unclosed tag within a closed tag.
     */
    public function testUnclosedWithinClosed()
    {
        $this->assertProduces('[url=http://jbbcode.com][b]oh yeah[/url]',
                              'oh yeah');
    }

    /**
     * Tests half completed opening tag.
     */
    public function testHalfOpenTag()
    {
        $this->assertProduces('[b', '[b');
        $this->assertProduces('wut [url=http://jbbcode.com',
                              'wut [url=http://jbbcode.com');
    }

    /**
     * Tests half completed closing tag.
     */
    public function testHalfClosingTag()
    {
        $this->assertProduces('[b]this should be bold[/b',
                              'this should be bold[/b');
    }

    /**
     * Tests lots of left brackets before the actual tag. For example:
     * [[[[[[[[b]bold![/b]
     */
    public function testLeftBracketsThenTag()
    {
        $this->assertProduces('[[[[[b]bold![/b]',
                              '[[[[bold!');
    }

    /**
     * Tests a whitespace after left bracket.
     */
    public function testWhitespaceAfterLeftBracketWhithoutTag()
    {
        $this->assertProduces('[ ABC ] ',
                              '[ ABC ] ');
    }

}


code:
<?php

require_once(dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'Parser.php');

class SimpleEvaluationTest extends PHPUnit_Framework_TestCase
{

    /**
     * A utility method for these tests that will evaluate
     * its arguments as bbcode with a fresh parser loaded
     * with only the default bbcodes. It returns the
     * html output.
     */
    private function defaultParse($bbcode)
    {
        $parser = new JBBCode\Parser();
        $parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
        $parser->parse($bbcode);
        return $parser->getAsHtml();
    }

    /**
     * Asserts that the given bbcode matches the given html when
     * the bbcode is run through defaultParse.
     */
    private function assertProduces($bbcode, $html)
    {
        $this->assertEquals($html, $this->defaultParse($bbcode));
    }

    public function testEmptyString()
    {
        $this->assertProduces('', '');
    }

    public function testOneTag()
    {
        $this->assertProduces('[b]this is bold[/b]', 'this is bold');
    }

    public function testOneTagWithSurroundingText()
    {
        $this->assertProduces('buffer text [b]this is bold[/b] buffer text',
                              'buffer text this is bold buffer text');
    }

    public function testMultipleTags()
    {
        $bbcode = <<<EOD
this is some text with [b]bold tags[/b] and [i]italics[/i] and
things like [u]that[/u].
EOD;
        $html = <<<EOD
this is some text with bold tags and italics and
things like that.
EOD;
        $this->assertProduces($bbcode, $html);
    }

    public function testCodeOptions()
    {
        $code = 'This contains a [url=http://jbbcode.com/?b=2]url[/url] which uses an option.';
        $html = 'This contains a url which uses an option.';
        $this->assertProduces($code, $html);
    }

    public function testAttributes()
    {
        $parser = new JBBCode\Parser();
        $builder = new JBBCode\CodeDefinitionBuilder('img', '');
        $parser->addCodeDefinition($builder->setUseOption(true)->setParseContent(false)->build());

        $expected = 'Multiple  options.';

        $code = 'Multiple [img height="50" alt="alt text"]http://jbbcode.com/img.png[/img] options.';
        $parser->parse($code);
        $result = $parser->getAsHTML();
        $this->assertEquals($expected, $result);

        $code = 'Multiple [img height=50 alt="alt text"]http://jbbcode.com/img.png[/img] options.';
        $parser->parse($code);
        $result = $parser->getAsHTML();
        $this->assertEquals($expected, $result);
    }

    /**
     * @depends testCodeOptions
     */
    public function testOmittedOption()
    {
        $code = 'This doesn\'t use the url option [url]http://jbbcode.com[/url].';
        $html = 'This doesn\'t use the url option http://jbbcode.com.';
        $this->assertProduces($code, $html);
    }

    public function testUnclosedTag()
    {
        $code = 'hello [b]world';
        $html = 'hello world';
        $this->assertProduces($code, $html);
    }

    public function testNestingTags()
    {
        $code = '[url=http://jbbcode.com][b]hello [u]world[/u][/b][/url]';
        $html = 'hello world';
        $this->assertProduces($code, $html);
    }

    public function testBracketInTag()
    {
        $this->assertProduces('[b]:-[[/b]', ':-[');
    }

    public function testBracketWithSpaceInTag()
    {
        $this->assertProduces('[b]:-[ [/b]', ':-[ ');
    }

    public function testBracketWithTextInTag()
    {
        $this->assertProduces('[b]:-[ foobar[/b]', ':-[ foobar');
    }

    public function testMultibleBracketsWithTextInTag()
    {
        $this->assertProduces('[b]:-[ [fo[o[bar[/b]', ':-[ [fo[o[bar');
    }

}


code:
<?php

require_once(dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'Tokenizer.php');

/**
 * Test cases testing the functionality of the Tokenizer. The tokenizer
 * is used by the parser to make parsing simpler.
 *
 * @author jbowens
 */
class TokenizerTest extends PHPUnit_Framework_TestCase
{

    public function testEmptyString()
    {
        $tokenizer = new JBBCode\Tokenizer('');
        $this->assertFalse($tokenizer->hasNext());
    }

    public function testPlainTextOnly()
    {
        $tokenizer = new JBBCode\Tokenizer('this is some plain text.');
        $this->assertEquals('this is some plain text.', $tokenizer->next());
        $this->assertEquals('this is some plain text.', $tokenizer->current());
        $this->assertFalse($tokenizer->hasNext());
    }

    public function testStartingBracket()
    {
        $tokenizer = new JBBCode\Tokenizer('[this has a starting bracket.');
        $this->assertEquals('[', $tokenizer->next());
        $this->assertEquals('[', $tokenizer->current());
        $this->assertEquals('this has a starting bracket.', $tokenizer->next());
        $this->assertEquals('this has a starting bracket.', $tokenizer->current());
        $this->assertFalse($tokenizer->hasNext());
        $this->assertEquals(null, $tokenizer->next());
    }

    public function testOneTag()
    {
        $tokenizer = new JBBCode\Tokenizer('[b]');
        $this->assertEquals('[', $tokenizer->next());
        $this->assertEquals('b', $tokenizer->next());
        $this->assertEquals(']', $tokenizer->next());
        $this->assertFalse($tokenizer->hasNext());
    }

    public function testMatchingTags()
    {
        $tokenizer = new JBBCode\Tokenizer('[url]http://jbbcode.com[/url]');
        $this->assertEquals('[', $tokenizer->next());
        $this->assertEquals('url', $tokenizer->next());
        $this->assertEquals(']', $tokenizer->next());
        $this->assertEquals('http://jbbcode.com', $tokenizer->next());
        $this->assertEquals('[', $tokenizer->next());
        $this->assertEquals('/url', $tokenizer->next());
        $this->assertEquals(']', $tokenizer->next());
        $this->assertFalse($tokenizer->hasNext());
    }

    public function testLotsOfBrackets()
    {
        $tokenizer = new JBBCode\Tokenizer('[[][]][');
        $this->assertEquals('[', $tokenizer->next());
        $this->assertEquals('[', $tokenizer->next());
        $this->assertEquals(']', $tokenizer->next());
        $this->assertEquals('[', $tokenizer->next());
        $this->assertEquals(']', $tokenizer->next());
        $this->assertEquals(']', $tokenizer->next());
        $this->assertEquals('[', $tokenizer->next());
        $this->assertFalse($tokenizer->hasNext());
    }

}


code:
<?php

require_once dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'Parser.php';
require_once dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'validators' . DIRECTORY_SEPARATOR . 'UrlValidator.php';
require_once dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'validators' . DIRECTORY_SEPARATOR . 'CssColorValidator.php';

/**
 * Test cases for InputValidators.
 *
 * @author jbowens
 * @since May 2013
 */
class ValidatorTest extends PHPUnit_Framework_TestCase
{

    /**
     * Tests an invalid url directly on the UrlValidator.
     */
    public function testInvalidUrl()
    {
        $urlValidator = new \JBBCode\validators\UrlValidator();
        $this->assertFalse($urlValidator->validate('#yolo#swag'));
        $this->assertFalse($urlValidator->validate('giehtiehwtaw352353%3'));
    }

    /**
     * Tests a valid url directly on the UrlValidator.
     */
    public function testValidUrl()
    {
        $urlValidator = new \JBBCode\validators\UrlValidator();
        $this->assertTrue($urlValidator->validate('http://google.com'));
        $this->assertTrue($urlValidator->validate('http://jbbcode.com/docs'));
        $this->assertTrue($urlValidator->validate('https://www.maps.google.com'));
    }

    /**
     * Tests an invalid url as an option to a url bbcode.
     *
     * @depends testInvalidUrl
     */
    public function testInvalidOptionUrlBBCode()
    {
        $parser = new JBBCode\Parser();
        $parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
        $parser->parse('[url=javascript:alert("HACKED!");]click me[/url]');
        $this->assertEquals('[url=javascript:alert("HACKED!");]click me[/url]',
                $parser->getAsHtml());
    }

    /**
     * Tests an invalid url as the body to a url bbcode.
     *
     * @depends testInvalidUrl
     */
    public function testInvalidBodyUrlBBCode()
    {
        $parser = new JBBCode\Parser();
        $parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
        $parser->parse('[url]javascript:alert("HACKED!");[/url]');
        $this->assertEquals('[url]javascript:alert("HACKED!");[/url]', $parser->getAsHtml());
    }

    /**
     * Tests a valid url as the body to a url bbcode.
     *
     * @depends testValidUrl
     */
    public function testValidUrlBBCode()
    {
        $parser = new JBBCode\Parser();
        $parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
        $parser->parse('[url]http://jbbcode.com[/url]');
        $this->assertEquals('http://jbbcode.com',
                $parser->getAsHtml());
    }

    /**
     * Tests valid english CSS color descriptions on the CssColorValidator.
     */
    public function testCssColorEnglish()
    {
        $colorValidator = new JBBCode\validators\CssColorValidator();
        $this->assertTrue($colorValidator->validate('red'));
        $this->assertTrue($colorValidator->validate('yellow'));
        $this->assertTrue($colorValidator->validate('LightGoldenRodYellow'));
    }

    /**
     * Tests valid hexadecimal CSS color values on the CssColorValidator.
     */
    public function testCssColorHex()
    {
        $colorValidator = new JBBCode\validators\CssColorValidator();
        $this->assertTrue($colorValidator->validate('#000'));
        $this->assertTrue($colorValidator->validate('#ff0000'));
        $this->assertTrue($colorValidator->validate('#aaaaaa'));
    }

    /**
     * Tests valid rgba CSS color values on the CssColorValidator.
     */
    public function testCssColorRgba()
    {
        $colorValidator = new JBBCode\validators\CssColorValidator();
        $this->assertTrue($colorValidator->validate('rgba(255, 0, 0, 0.5)'));
        $this->assertTrue($colorValidator->validate('rgba(50, 50, 50, 0.0)'));
    }

    /**
     * Tests invalid CSS color values on the CssColorValidator.
     */
    public function testInvalidCssColor()
    {
        $colorValidator = new JBBCode\validators\CssColorValidator();
        $this->assertFalse($colorValidator->validate('" onclick="javascript: alert(\"gotcha!\");'));
        $this->assertFalse($colorValidator->validate('"><marquee scrollamount="100'));
    }

    /**
     * Tests valid css colors in a color bbcode.
     *
     * @depends testCssColorEnglish
     * @depends testCssColorHex
     */
    public function testValidColorBBCode()
    {
        $parser = new JBBCode\Parser();
        $parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
        $parser->parse('[color=red]colorful text[/color]');
        $this->assertEquals('colorful text',
                $parser->getAsHtml());
        $parser->parse('[color=#00ff00]green[/color]');
        $this->assertEquals('green', $parser->getAsHtml());
    }

    /**
     * Tests invalid css colors in a color bbcode.
     *
     * @depends testInvalidCssColor
     */
    public function testInvalidColorBBCode()
    {
        $parser = new JBBCode\Parser();
        $parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());
        $parser->parse('[color=" onclick="alert(\'hey ya!\');]click me[/color]');
        $this->assertEquals('[color=" onclick="alert(\'hey ya!\');]click me[/color]',
                $parser->getAsHtml());
    }

}


code:
<?php

namespace JBBCode\validators;

require_once dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'InputValidator.php';

/**
 * An InputValidator for CSS color values. This is a very rudimentary
 * validator. It will allow a lot of color values that are invalid. However,
 * it shouldn't allow any invalid color values that are also a security
 * concern.
 *
 * @author jbowens
 * @since May 2013
 */
class CssColorValidator implements \JBBCode\InputValidator
{

    /**
     * Returns true if $input uses only valid CSS color value
     * characters.
     *
     * @param string $input  the string to validate
     * @return boolean
     */
    public function validate($input)
    {
        return (bool) preg_match('/^[A-z0-9\-#., ()%]+$/', $input);
    }

}


code:
<?php

namespace JBBCode\validators;

require_once dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'InputValidator.php';

/**
 * An InputValidator for urls. This can be used to make [url] bbcodes secure.
 *
 * @author jbowens
 * @since May 2013
 */
class UrlValidator implements \JBBCode\InputValidator
{

    /**
     * Returns true iff $input is a valid url.
     *
     * @param string $input  the string to validate
     * @return boolean
     */
    public function validate($input)
    {
        $valid = filter_var($input, FILTER_VALIDATE_URL);
        return !!$valid;
    }

}


code:
<?php

namespace JBBCode\visitors;

/**
 * This visitor escapes html content of all strings and attributes
 *
 * @author Alexander Polyanskikh
 */
class HTMLSafeVisitor implements \JBBCode\NodeVisitor
{
	public function visitDocumentElement(\JBBCode\DocumentElement $documentElement)
	{
		foreach ($documentElement->getChildren() as $child) {
			$child->accept($this);
		}
	}

	public function visitTextNode(\JBBCode\TextNode $textNode)
	{
		$textNode->setValue($this->htmlSafe($textNode->getValue()));
	}

	public function visitElementNode(\JBBCode\ElementNode $elementNode)
	{
		$attrs = $elementNode->getAttribute();
		if (is_array($attrs))
		{
			foreach ($attrs as &$el)
				$el = $this->htmlSafe($el);

			$elementNode->setAttribute($attrs);
		}

		foreach ($elementNode->getChildren() as $child) {
			$child->accept($this);
		}
	}

	protected function htmlSafe($str, $options = null)
	{
		if (is_null($options))
		{
			if (defined('ENT_DISALLOWED'))
				$options = ENT_QUOTES | ENT_DISALLOWED | ENT_HTML401; // PHP 5.4+
			else
				$options = ENT_QUOTES;  // PHP 5.3
		}

		return htmlspecialchars($str, $options, 'UTF-8');
	}
}


code:
<?php

namespace JBBCode\visitors;

require_once dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'CodeDefinition.php';
require_once dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'DocumentElement.php';
require_once dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'ElementNode.php';
require_once dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'NodeVisitor.php';
require_once dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'TextNode.php';

/**
 * This visitor is used by the jBBCode core to enforce nest limits after
 * parsing. It traverses the parse graph depth first, removing any subtrees
 * that are nested deeper than an element's code definition allows.
 *
 * @author jbowens
 * @since May 2013
 */
class NestLimitVisitor implements \JBBCode\NodeVisitor
{

    /* A map from tag name to current depth. */
    protected $depth = array();

    public function visitDocumentElement(\JBBCode\DocumentElement $documentElement)
    {
        foreach($documentElement->getChildren() as $child) {
            $child->accept($this);
        }
    }

    public function visitTextNode(\JBBCode\TextNode $textNode)
    {
        /* Nothing to do. Text nodes don't have tag names or children. */
    }

    public function visitElementNode(\JBBCode\ElementNode $elementNode)
    {
        $tagName = strtolower($elementNode->getTagName());

        /* Update the current depth for this tag name. */
        if (isset($this->depth[$tagName])) {
            $this->depth[$tagName]++;
        } else {
            $this->depth[$tagName] = 1;
        }

        /* Check if $elementNode is nested too deeply. */
        if ($elementNode->getCodeDefinition()->getNestLimit() != -1 &&
                $elementNode->getCodeDefinition()->getNestLimit() < $this->depth[$tagName]) {
            /* This element is nested too deeply. We need to remove it and not visit any
             * of its children. */
            $elementNode->getParent()->removeChild($elementNode);
        } else {
            /* This element is not nested too deeply. Visit all of its children. */
            foreach ($elementNode->getChildren() as $child) {
                $child->accept($this);
            }
        }

        /* Now that we're done visiting this node, decrement the depth. */
        $this->depth[$tagName]--;
    }

}


code:
<?php

namespace JBBCode\visitors;

/**
 * This visitor is an example of how to implement smiley parsing on the JBBCode
 * parse graph. It converts :) into image tags pointing to /smiley.png.
 *
 * @author jbowens
 * @since April 2013
 */
class SmileyVisitor implements \JBBCode\NodeVisitor
{

    function visitDocumentElement(\JBBCode\DocumentElement $documentElement)
    {
        foreach($documentElement->getChildren() as $child) {
            $child->accept($this);
        }
    }

    function visitTextNode(\JBBCode\TextNode $textNode)
    {
        /* Convert :) into an image tag. */
        $textNode->setValue(str_replace(':)',
                                        '<img src="/smiley.png" alt=":)" />',
                                        $textNode->getValue()));
    }

    function visitElementNode(\JBBCode\ElementNode $elementNode)
    {
        /* We only want to visit text nodes within elements if the element's
         * code definition allows for its content to be parsed.
         */
        if ($elementNode->getCodeDefinition()->parseContent()) {
            foreach ($elementNode->getChildren() as $child) {
                $child->accept($this);
            }
        }
    }

}


code:
<?php

namespace JBBCode\visitors;

/**
 * This visitor traverses parse graph, counting the number of times each
 * tag name occurs.
 *
 * @author jbowens
 * @since January 2013
 */
class TagCountingVisitor implements \JBBcode\NodeVisitor
{
    protected $frequencies = array();

    public function visitDocumentElement(\JBBCode\DocumentElement $documentElement)
    {
        foreach ($documentElement->getChildren() as $child) {
            $child->accept($this);
        }
    }

    public function visitTextNode(\JBBCode\TextNode $textNode)
    {
        // Nothing to do here, text nodes do not have tag names or children
    }

    public function visitElementNode(\JBBCode\ElementNode $elementNode)
    {
        $tagName = strtolower($elementNode->getTagName());

        // Update this tag name's frequency
        if (isset($this->frequencies[$tagName])) {
            $this->frequencies[$tagName]++;
        } else {
            $this->frequencies[$tagName] = 1;
        }

        // Visit all the node's childrens
        foreach ($elementNode->getChildren() as $child) {
            $child->accept($this);
        }

    }

    /**
     * Retrieves the frequency of the given tag name.
     *
     * @param string $tagName the tag name to look up
     *
     * @return integer
     */
    public function getFrequency($tagName)
    {
        if (!isset($this->frequencies[$tagName])) {
            return 0;
        } else {
            return $this->frequencies[$tagName];
        }
    }

}


code:
jBBCode
=======
[![Software License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE.md)
[![Build Status](https://img.shields.io/travis/jbowens/jBBCode.svg)](https://travis-ci.org/jbowens/jBBCode)

jBBCode is a bbcode parser written in php 5.3. It's relatively lightweight and parses
bbcodes without resorting to expensive regular expressions.

Documentation
-------------

For complete documentation and examples visit [jbbcode.com](http://jbbcode.com).

###A basic example

jBBCode includes a few optional, default bbcode definitions that may be loaded through the
`DefaultCodeDefinitionSet` class. Below is a simple example of using these codes to convert
a bbcode string to html.

```php
<?php
require_once "/path/to/jbbcode/Parser.php";

$parser = new JBBCode\Parser();
$parser->addCodeDefinitionSet(new JBBCode\DefaultCodeDefinitionSet());

$text = "The default codes include: [b]bold[/b], [i]italics[/i], [u]underlining[/u], ";
$text .= "[url=http://jbbcode.com]links[/url], [color=red]color![/color] and more.";

$parser->parse($text);

print $parser->getAsHtml();
```

### Composer

You may load jBBCode via composer. In your composer.json file:

```json
"require": {
    "jbbcode/jbbcode": "1.3.*"
}
```

In your php file:

```php
require 'vendor/autoloader.php';

$parser = new JBBCode\Parser();
```

Contribute
----------

I would love help maintaining jBBCode. Look at [open issues](http://github.com/jbowens/jBBCode/issues) for ideas on
what needs to be done. Before submitting a pull request, verify that all unit tests still pass.

#### Running unit tests
To run the unit tests,
ensure that [phpunit](http://github.com/sebastianbergmann/phpunit) is installed, or install it through the composer
dev dependencies. Then run `phpunit` from the project directory. If you're adding new functionality, writing
additional unit tests is a great idea.

Author
------

jBBCode was written by Jackson Owens. You may reach him at [jackson_owens@alumni.brown.edu](mailto:jackson_owens@alumni.brown.edu).

Other languages
---------------

This library is in the process of being ported to java. For more information, see [jBBCode-java](https://github.com/jbowens/jBBCode-java).

License
-------

The project is under MIT license. Please see the [license file](LICENSE.md) for details.


jbbcode/jbbcode

type: library

description: A lightweight but extensible BBCode parser written in PHP 5.3.

keywords: BBCode BB

license: MIT

Jackson Owens

role: Developer

minimum-stability: stable


code:
<?xml version="1.0" encoding="utf-8"?>
<phpunit colors="true"
		convertErrorsToExceptions="true"
		convertNoticesToExceptions="true"
		convertWarningsToExceptions="true"
		stopOnFailure="false">
		<testsuites>
			<testsuite name="jBBCode Test Suite">
				<directory>JBBCode/tests</directory>
			</testsuite>
		</testsuites>
</phpunit>


code:
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto

###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs     diff=csharp

###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln       merge=binary
#*.csproj    merge=binary
#*.vbproj    merge=binary
#*.vcxproj   merge=binary
#*.vcproj    merge=binary
#*.dbproj    merge=binary
#*.fsproj    merge=binary
#*.lsproj    merge=binary
#*.wixproj   merge=binary
#*.modelproj merge=binary
#*.sqlproj   merge=binary
#*.wwaproj   merge=binary

###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg   binary
#*.png   binary
#*.gif   binary

###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc   diff=astextplain
#*.DOC   diff=astextplain
#*.docx  diff=astextplain
#*.DOCX  diff=astextplain
#*.dot   diff=astextplain
#*.DOT   diff=astextplain
#*.pdf   diff=astextplain
#*.PDF   diff=astextplain
#*.rtf   diff=astextplain
#*.RTF   diff=astextplain

repo_name: Thissal/RimJobWorld

path: .gitattributes


code:
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.

# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates

# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs

# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/

# Visual Studio 2015 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/

# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*

# NUNIT
*.VisualState.xml
TestResult.xml

# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c

# DNX
project.lock.json
project.fragment.lock.json
artifacts/

*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc

# Chutzpah Test files
_Chutzpah*

# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb

# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap

# TFS 2012 Local Workspace
$tf/

# Guidance Automation Toolkit
*.gpState

# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user

# JustCode is a .NET coding add-in
.JustCode

# TeamCity is a build add-in
_TeamCity*

# DotCover is a Code Coverage Tool
*.dotCover

# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*

# MightyMoose
*.mm.*
AutoTest.Net/

# Web workbench (sass)
.sass-cache/

# Installshield output folder
[Ee]xpress/

# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html

# Click-Once directory
publish/

# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
#*.pubxml
*.publishproj

# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/

# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# NuGet v3's project.json files produces more ignoreable files
*.nuget.props
*.nuget.targets

# Microsoft Azure Build Output
csx/
*.build.csdef

# Microsoft Azure Emulator
ecf/
rcf/

# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt

# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/

# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
node_modules/
orleans.codegen.cs

# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/

# RIA/Silverlight projects
Generated_Code/

# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm

# SQL Server files
*.mdf
*.ldf

# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings

# Microsoft Fakes
FakesAssemblies/

# GhostDoc plugin setting file
*.GhostDoc.xml

# Node.js Tools for Visual Studio
.ntvs_analysis.dat

# Visual Studio 6 build log
*.plg

# Visual Studio 6 workspace options file
*.opt

# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions

# Paket dependency manager
.paket/paket.exe
paket-files/

# FAKE - F# Make
.fake/

# JetBrains Rider
.idea/
*.sln.iml

# CodeRush
.cr/

# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
/Archived
/MyTo-Do.txt
/LL_post.txt
/creditsnew.txt

path: .gitignore


code:
<?xml version="1.0" encoding="utf-8"?>

<ModMetaData>
	<name>Rimjobworld</name>
	<author>Void Main & RimJobWorld Community Project Team</author>
	<targetVersion>0.17.1557</targetVersion>
	<description> </description>
</ModMetaData>


code:
<?xml version="1.0" encoding="utf-8" ?>

<BodyPartDefs>
	<BodyPartDef>
		<defName>InsectGenitals</defName>
		insectGenitals
		<hitPoints>20</hitPoints>
		<frostbiteVulnerability>0.1</frostbiteVulnerability>
		<oldInjuryBaseChance>0.015</oldInjuryBaseChance>
		<skinCovered>true</skinCovered>
		<isSolid>true</isSolid>
		<dontSuggestAmputation>false</dontSuggestAmputation>
		<bleedingRateMultiplier>1.0</bleedingRateMultiplier>
		<tags>

RJW_FertilitySource

		</tags>
	</BodyPartDef>
</BodyPartDefs>


code:
<?xml version="1.0" encoding="utf-8" ?>

<BodyPartDefs>
	<BodyPartDef>
		<defName>MechGenitals</defName>
		mechGenitals
		<hitPoints>20</hitPoints>
		<frostbiteVulnerability>0.1</frostbiteVulnerability>
		<oldInjuryBaseChance>0.015</oldInjuryBaseChance>
		<skinCovered>true</skinCovered>
		<isSolid>true</isSolid>
		<dontSuggestAmputation>false</dontSuggestAmputation>
		<bleedingRateMultiplier>1.0</bleedingRateMultiplier>
		<tags>

RJW_FertilitySource

		</tags>
	</BodyPartDef>
</BodyPartDefs>


code:
<?xml version="1.0" encoding="utf-8" ?>
<BodyPartDefs>

	<BodyPartDef>
		<defName>Genitals</defName>
		genitals
		<hitPoints>12</hitPoints>
		<frostbiteVulnerability>0.1</frostbiteVulnerability>
		<oldInjuryBaseChance>0.015</oldInjuryBaseChance>
		<skinCovered>true</skinCovered>
		<isSolid>false</isSolid>
		<dontSuggestAmputation>false</dontSuggestAmputation>
		<bleedingRateMultiplier>1.0</bleedingRateMultiplier>
		<tags>

RJW_FertilitySource

		</tags>
	</BodyPartDef>

	<BodyPartDef>
		<defName>Chest</defName>
		chest
		<hitPoints>12</hitPoints>
		<frostbiteVulnerability>0.1</frostbiteVulnerability>
		<oldInjuryBaseChance>0.015</oldInjuryBaseChance>
		<skinCovered>true</skinCovered>
		<isSolid>false</isSolid>
		<dontSuggestAmputation>true</dontSuggestAmputation>
		<bleedingRateMultiplier>1.0</bleedingRateMultiplier>
	</BodyPartDef>

	<BodyPartDef>
		<defName>Anus</defName>
		anus
		<hitPoints>8</hitPoints>
		<frostbiteVulnerability>0.1</frostbiteVulnerability>
		<oldInjuryBaseChance>0.015</oldInjuryBaseChance>
		<skinCovered>true</skinCovered>
		<isSolid>false</isSolid>
		<dontSuggestAmputation>false</dontSuggestAmputation>
		<bleedingRateMultiplier>1.0</bleedingRateMultiplier>
	</BodyPartDef>
</BodyPartDefs>


code:
<?xml version="1.0" encoding="utf-8" ?>
<Defs>

	<HediffDef>
		<defName>Armbinder</defName>
		<hediffClass>Hediff</hediffClass>
		<defaultLabelColor>(0.5, 0.5, 0.9)</defaultLabelColor>
		armbinder
		<makesSickThought>false</makesSickThought>
		<tendable>false</tendable>
		<stages>

				<capMods>

						<capacity>Manipulation</capacity>
						<setMax>0.00</setMax>

				</capMods>

		</stages>
	</HediffDef>

	<HediffDef>
		<defName>Gag</defName>
		<hediffClass>Hediff</hediffClass>
		<defaultLabelColor>(0.5, 0.5, 0.9)</defaultLabelColor>
		gag
		<makesSickThought>false</makesSickThought>
		<tendable>false</tendable>
		<stages>

				<capMods>

						<capacity>Talking</capacity>
						<setMax>0.00</setMax>

						<capacity>Eating</capacity>
						<offset>-0.75</offset>

				</capMods>

		</stages>
	</HediffDef>
</Defs>


code:
<?xml version="1.0" encoding="utf-8" ?>
<ThingDefs>

	<ThingDef>
		<defName>Hololock</defName>
		Hololock
		<description>
			A high-tech locking mechanism. Features include:
			- Extremely hardy, plasteel-reinforced build
			- Key and lock paired using a cryptographic UID holographically embedded in a jade substrate, ensuring counterfeit keys are impossible to produce
			- Self-destruct failsafe triggers when the lock is tampered with, ensuring that the protected item is destroyed rather than captured by the enemy

			Originally designed for military use, the hololock has since become popular in the BDSM community, where it is customary to engrave a key and lock pair with matching names so they are easier to identify.
		</description>
		<thingClass>ThingWithComps</thingClass>
		<category>Item</category>
		<thingCategories>

Manufactured

		</thingCategories>
		<useHitPoints>true</useHitPoints>
		<selectable>true</selectable>
		<statBases>
			<MaxHitPoints>500</MaxHitPoints>
			<Flammability>0.0</Flammability>
			<DeteriorationRate>0.1</DeteriorationRate>
			<Beauty>-6</Beauty>
			<MarketValue>400</MarketValue>
			<Mass>0.5</Mass>
		</statBases>
		<altitudeLayer>Item</altitudeLayer>
		<tickerType>Never</tickerType>
		<alwaysHaulable>true</alwaysHaulable>
		<tradeTags>

Exotic

		</tradeTags>
		<comps>

		</comps>
		<pathCost>2</pathCost>
		<graphicData>
			<texPath>hololock</texPath>
			<graphicClass>Graphic_Single</graphicClass>
		</graphicData>
	</ThingDef>

	<ThingDef>
		<defName>Holokey</defName>
		Holokey
		<description>A high-tech key. Releases the lock on an item with a matching cryptographic stamp.</description>
		<thingClass>ThingWithComps</thingClass>
		<category>Item</category>
		<thingCategories>

Manufactured

		</thingCategories>
		<useHitPoints>true</useHitPoints>
		<selectable>true</selectable>
		<statBases>
			<MaxHitPoints>50</MaxHitPoints>
			<Flammability>0.2</Flammability>
			<DeteriorationRate>0.5</DeteriorationRate>
			<Beauty>0</Beauty>
			<MarketValue>100</MarketValue>
			<Mass>0.1</Mass>
		</statBases>
		<altitudeLayer>Item</altitudeLayer>
		<tickerType>Never</tickerType>
		<alwaysHaulable>true</alwaysHaulable>
		<tradeTags>

Exotic

		</tradeTags>
		<tradeability>Sellable</tradeability>
		<comps>

				<compClass>rjw.CompStampedApparelKey</compClass>
				<useJob>UnlockBondageGear</useJob>
				<useLabel>Unlock apparel</useLabel>

				<compClass>rjw.CompUnlockBondageGear</compClass>

		</comps>
		<pathCost>1</pathCost>
		<graphicData>
			<texPath>holokey</texPath>
			<graphicClass>Graphic_Single</graphicClass>
		</graphicData>
	</ThingDef>

	<rjw.bondage_gear_def Name="BondageGearBase" Abstract="True">
		<thingClass>rjw.bondage_gear</thingClass>
		<category>Item</category>
		<thingCategories>

Apparel

		</thingCategories>
		<useHitPoints>true</useHitPoints>
		<selectable>true</selectable>
		<drawGUIOverlay>true</drawGUIOverlay>
		<altitudeLayer>Item</altitudeLayer>
		<tickerType>Never</tickerType>
		<alwaysHaulable>true</alwaysHaulable>
		<canBeSpawningInventory>false</canBeSpawningInventory>
		<tradeTags>

Exotic

		</tradeTags>
		<tradeNeverStack>true</tradeNeverStack>
		<comps>

				<compClass>rjw.CompBondageGear</compClass>
				<useJob>GiveBondageGear</useJob>
				<useLabel>Equip</useLabel>

				<compClass>rjw.CompGetBondageGear</compClass>

		</comps>
		<pathCost>10</pathCost>
	</rjw.bondage_gear_def>

	<rjw.bondage_gear_def ParentName="BondageGearBase">
		<defName>Armbinder</defName>
		armbinder
		<description>A high-tech armbinder. It prevents any pawn it's attached to from using their arms.</description>
		<thingClass>rjw.armbinder</thingClass>
		<graphicData>
			<texPath>armbinder</texPath>
			<graphicClass>Graphic_Single</graphicClass>
		</graphicData>
		<statBases>
			<MaxHitPoints>250</MaxHitPoints>
			<Flammability>0.3</Flammability>
			<DeteriorationRate>1.0</DeteriorationRate>
			<Beauty>-6</Beauty>
			<MarketValue>850</MarketValue>
			<Mass>4</Mass>
			<ArmorRating_Blunt>0.15</ArmorRating_Blunt>
			<ArmorRating_Sharp>0.25</ArmorRating_Sharp>
			<Insulation_Cold>-4</Insulation_Cold>
		</statBases>
		<apparel>
			<bodyPartGroups>

Arms

LeftHand

RightHand

			</bodyPartGroups>
			<layers>

Shell

			</layers>

		</apparel>
		<soul_type>rjw.bondage_gear_soul</soul_type>
		<equipped_hediff>Armbinder</equipped_hediff>
		<gives_bound_moodlet>true</gives_bound_moodlet>
		<gives_gagged_moodlet>false</gives_gagged_moodlet>
	</rjw.bondage_gear_def>

	<rjw.bondage_gear_def ParentName="BondageGearBase">
		<defName>Gag</defName>
		gag
		<description>A high-tech gag. It prevents any pawn it's attached to from talking.</description>
		<thingClass>rjw.bondage_gear</thingClass>
		<graphicData>
			<texPath>gag</texPath>
			<graphicClass>Graphic_Single</graphicClass>
		</graphicData>
		<statBases>
			<MaxHitPoints>150</MaxHitPoints>
			<Flammability>0.2</Flammability>
			<DeteriorationRate>1.0</DeteriorationRate>
			<Beauty>-6</Beauty>
			<MarketValue>550</MarketValue>
			<Mass>1</Mass>
		</statBases>
		<apparel>
			<bodyPartGroups>

FullHead

			</bodyPartGroups>
			<layers>

OnSkin

			</layers>

		</apparel>
		<soul_type>rjw.bondage_gear_soul</soul_type>
		<equipped_hediff>Gag</equipped_hediff>
		<gives_bound_moodlet>false</gives_bound_moodlet>
		<gives_gagged_moodlet>true</gives_gagged_moodlet>
	</rjw.bondage_gear_def>

	<rjw.bondage_gear_def ParentName="BondageGearBase">
		<defName>ChastityBelt</defName>
		chastity belt
		<description>A high-tech chastity belt. It prevents sex and masturbation.</description>
		<thingClass>rjw.bondage_gear</thingClass>
		<graphicData>
			<texPath>chastity_belt</texPath>
			<graphicClass>Graphic_Single</graphicClass>
		</graphicData>
		<statBases>
			<MaxHitPoints>300</MaxHitPoints>
			<Flammability>0.1</Flammability>
			<DeteriorationRate>0.5</DeteriorationRate>
			<Beauty>-6</Beauty>
			<MarketValue>1200</MarketValue>
			<Mass>3</Mass>
		</statBases>
		<apparel>

			<bodyPartGroups>

Legs

			</bodyPartGroups>

			<layers>

Shell

			</layers>

		</apparel>
		<equippedStatOffsets>
			<SexAbility>-9.99</SexAbility>
		</equippedStatOffsets>
		<soul_type>rjw.bondage_gear_soul</soul_type>

		<gives_bound_moodlet>true</gives_bound_moodlet>
		<gives_gagged_moodlet>false</gives_gagged_moodlet>
		<blocks_genitals>true</blocks_genitals>
	</rjw.bondage_gear_def>
</ThingDefs>


code:
<?xml version="1.0" encoding="utf-8" ?>
<RecipeDefs>

	<RecipeDef>
		<defName>MakeHololock</defName>
		make hololock
		<description>Make a hololock.</description>
		<jobString>Making hololock.</jobString>
		<workSpeedStat>SmithingSpeed</workSpeedStat>
		<effectWorking>Cook</effectWorking>
		<soundWorking>Recipe_Machining</soundWorking>
		<workAmount>9000</workAmount>
		<unfinishedThingDef>UnfinishedComponent</unfinishedThingDef>
		<ingredients>

				<filter>
					<thingDefs>

Plasteel

					</thingDefs>
				</filter>
				<count>20</count>

				<filter>
					<thingDefs>

Component

					</thingDefs>
				</filter>
				<count>4</count>

				<filter>
					<thingDefs>

Jade

					</thingDefs>
				</filter>
				<count>2</count>

		</ingredients>
		<fixedIngredientFilter>
			<thingDefs>

Plasteel

Component

Jade

			</thingDefs>
		</fixedIngredientFilter>
		<products>
			<Hololock>1</Hololock>
		</products>
		<skillRequirements>

				<skill>Crafting</skill>
				<minLevel>6</minLevel>

		</skillRequirements>
		<workSkill>Crafting</workSkill>
	</RecipeDef>

	<RecipeDef>
		<defName>MakeArmbinder</defName>
		make armbinder
		<description>Make an armbinder.</description>
		<jobString>Making armbinder.</jobString>
		<workSpeedStat>TailoringSpeed</workSpeedStat>
		<soundWorking>Recipe_Tailor</soundWorking>
		<workAmount>5000</workAmount>
		<unfinishedThingDef>UnfinishedApparel</unfinishedThingDef>
		<ingredients>

				<filter>
					<thingDefs>

DevilstrandCloth

					</thingDefs>
				</filter>
				<count>50</count>

				<filter>
					<thingDefs>

Steel

					</thingDefs>
				</filter>
				<count>30</count>

				<filter>
					<thingDefs>

Hololock

					</thingDefs>
				</filter>
				<count>1</count>

		</ingredients>
		<fixedIngredientFilter>
			<thingDefs>

DevilstrandCloth

Steel

Hololock

			</thingDefs>
		</fixedIngredientFilter>
		<products>
			<Armbinder>1</Armbinder>
		</products>
		<skillRequirements>

				<skill>Crafting</skill>
				<minLevel>6</minLevel>

		</skillRequirements>
		<workSkill>Crafting</workSkill>
	</RecipeDef>

	<RecipeDef>
		<defName>MakeGag</defName>
		make gag
		<description>Make a gag.</description>
		<jobString>Making gag.</jobString>
		<workSpeedStat>TailoringSpeed</workSpeedStat>
		<soundWorking>Recipe_Tailor</soundWorking>
		<workAmount>1500</workAmount>
		<unfinishedThingDef>UnfinishedApparel</unfinishedThingDef>
		<ingredients>

				<filter>
					<thingDefs>

DevilstrandCloth

					</thingDefs>
				</filter>
				<count>15</count>

				<filter>
					<thingDefs>

Steel

					</thingDefs>
				</filter>
				<count>10</count>

				<filter>
					<thingDefs>

Hololock

					</thingDefs>
				</filter>
				<count>1</count>

		</ingredients>
		<fixedIngredientFilter>
			<thingDefs>

DevilstrandCloth

Steel

Hololock

			</thingDefs>
		</fixedIngredientFilter>
		<products>
			<Gag>1</Gag>
		</products>
		<skillRequirements>

				<skill>Crafting</skill>
				<minLevel>4</minLevel>

		</skillRequirements>
		<workSkill>Crafting</workSkill>
	</RecipeDef>

	<RecipeDef>
		<defName>MakeChastityBelt</defName>
		make chastity belt
		<description>Make a chastity belt.</description>
		<jobString>Making chastity belt.</jobString>
		<workSpeedStat>TailoringSpeed</workSpeedStat>
		<soundWorking>Recipe_Tailor</soundWorking>
		<workAmount>4000</workAmount>
		<unfinishedThingDef>UnfinishedApparel</unfinishedThingDef>
		<ingredients>

				<filter>
					<thingDefs>

Plasteel

					</thingDefs>
				</filter>
				<count>50</count>

				<filter>
					<thingDefs>

Hololock

					</thingDefs>
				</filter>
				<count>1</count>

		</ingredients>
		<fixedIngredientFilter>
			<thingDefs>

Plasteel

Hololock

			</thingDefs>
		</fixedIngredientFilter>
		<products>
			<ChastityBelt>1</ChastityBelt>
		</products>
		<skillRequirements>

				<skill>Crafting</skill>
				<minLevel>6</minLevel>

		</skillRequirements>
		<workSkill>Crafting</workSkill>
	</RecipeDef>
</RecipeDefs>


code:
<?xml version="1.0" encoding="utf-8" ?>
<RecipeDefs>

	<rjw.force_off_gear_def Abstract="True" Name="ForceOffGenericGear">
		<workerClass>rjw.Recipe_ForceOffGear</workerClass>
		<effectWorking>Surgery</effectWorking>
		<soundWorking>Recipe_Surgery</soundWorking>
		<workSpeedStat>MedicalOperationSpeed</workSpeedStat>
		<workSkill>Medicine</workSkill>
		<workSkillLearnFactor>8</workSkillLearnFactor>
		<workAmount>1500</workAmount>
		<surgerySuccessChanceFactor>2.0</surgerySuccessChanceFactor>
		<deathOnFailedSurgeryChance>0.0</deathOnFailedSurgeryChance>

		<targetsBodyPart>true</targetsBodyPart>
		<ingredients>

				<filter>
					<categories>

Medicine

					</categories>
				</filter>
				<count>1</count>

		</ingredients>
		<fixedIngredientFilter>
			<categories>

Medicine

			</categories>
		</fixedIngredientFilter>
		<recipeUsers>

Human

		</recipeUsers>
	</rjw.force_off_gear_def>

	<rjw.force_off_gear_def ParentName="ForceOffGenericGear">
		<defName>ForceOffArmbinder</defName>
		force off armbinder
		<description>Forcibly removes an armbinder without the key at the cost of significant damage to the wearer.</description>
		<jobString>Forcibly removing armbinder.</jobString>
		<removes_apparel>Armbinder</removes_apparel>
		<failure_affects>Torso</failure_affects>
		<destroys_one_of>

LeftHand

RightHand

		</destroys_one_of>
		<major_burns_on>

LeftHand

RightHand

LeftArm

RightArm

		</major_burns_on>
		<minor_burns_on>

Torso

		</minor_burns_on>
	</rjw.force_off_gear_def>

	<rjw.force_off_gear_def ParentName="ForceOffGenericGear">
		<defName>ForceOffGag</defName>
		force off gag
		<description>Forcibly removes a gag without the key at the cost of significant damage to the wearer.</description>
		<jobString>Forcibly removing gag.</jobString>
		<removes_apparel>Gag</removes_apparel>
		<failure_affects>Head</failure_affects>
		<destroys_one_of>

Jaw

		</destroys_one_of>
		<major_burns_on>

Head

Neck

		</major_burns_on>
	</rjw.force_off_gear_def>

	<rjw.force_off_gear_def ParentName="ForceOffGenericGear">
		<defName>ForceOffChastityBelt</defName>
		force off chastity belt
		<description>Forcibly removes a chastity belt without the key at the cost of significant damage to the wearer.</description>
		<jobString>Forcibly removing chastity belt.</jobString>
		<removes_apparel>ChastityBelt</removes_apparel>
		<failure_affects>Torso</failure_affects>
		<destroys_one_of>

Genitals

		</destroys_one_of>
		<major_burns_on>

Torso

		</major_burns_on>
		<minor_burns_on>

LeftLeg

RightLeg

		</minor_burns_on>
	</rjw.force_off_gear_def>
</RecipeDefs>


code:
<?xml version="1.0" encoding="utf-8" ?>
<Defs>

	<ThoughtDef>
		<defName>Bound</defName>
		<workerClass>rjw.ThoughtWorker_Bound</workerClass>
		<validWhileDespawned>true</validWhileDespawned>
		<nullifyingTraits>

Masochist

		</nullifyingTraits>
		<stages>

				bound
				<description>This thing is seriously annoying. I wish I could take it off.</description>
				<baseMoodEffect>-5</baseMoodEffect>

				gagged
				<description>Ish shnn ish shrrrrsh uhnnninnn</description>
				<baseMoodEffect>-4</baseMoodEffect>

				bound and gagged
				<description>Ufff ish hhhh shrrrnnn shhhrrr! hhhek shi thfffff!</description>
				<baseMoodEffect>-8</baseMoodEffect>

		</stages>
	</ThoughtDef>

	<ThoughtDef>
		<defName>MasochistBound</defName>
		<workerClass>rjw.ThoughtWorker_Bound</workerClass>
		<validWhileDespawned>true</validWhileDespawned>
		<requiredTraits>

Masochist

		</requiredTraits>
		<stages>

				masochist bound
				<description>Heh... heh heh...</description>
				<baseMoodEffect>5</baseMoodEffect>

				masochist gagged
				<description>Nnnn shsh hhhch uhnnn</description>
				<baseMoodEffect>4</baseMoodEffect>

				masochist bound and gagged
				<description>Shnnn ihhh shrrr fff shnnn... shsh chhhnnn uhfsh~!</description>
				<baseMoodEffect>8</baseMoodEffect>

		</stages>
	</ThoughtDef>
</Defs>


code:
<?xml version="1.0" encoding="utf-8"?>
<Defs>

	<ConceptDef>
		<defName>WhoreBeds</defName>
		Whore beds
		<priority>50</priority>
		<needsOpportunity>True</needsOpportunity>
		<helpText>Whores prefer to work in beds.\n\nThere are specific whore beds you can build for them.</helpText>
		<highlightTags>

MainTab-Architect-Closed

DesignationCategoryButton-Furniture-Closed

Designator-Build-WhoreBed

		</highlightTags>
	</ConceptDef>
</Defs>


code:
<?xml version="1.0" encoding="utf-8" ?>
<Defs>

	<DamageDef>
		<defName>ViralDamage</defName>
		<workerClass>DamageWorker_AddInjury</workerClass>
		<label>viral damage</label>
		<externalViolence>false</externalViolence>
		<deathMessage>{0} has died of disease.</deathMessage>
		<hediff>VirusPerma</hediff>
		<hediffSkin>VirusPerma</hediffSkin>
		<hediffSolid>VirusPerma</hediffSolid>
		<makesBlood>false</makesBlood>
		<harmAllLayersUntilOutside>false</harmAllLayersUntilOutside>
	</DamageDef>
</Defs>


code:
<?xml version="1.0" encoding="utf-8" ?>
<Defs>

	<ArchitectSense.DesignationSubCategoryDef>
		<defName>SubCategory_Whore_Beds</defName>
		Whore Beds
		<description>Beds for whores.</description>
		<defNames>

WhoreSleepingSpot

BedWhore

DoubleBedWhore

RoyalBedWhore

		</defNames>
		<designationCategory>Furniture</designationCategory>
	</ArchitectSense.DesignationSubCategoryDef>
</Defs>


code:
<?xml version="1.0" encoding="utf-8" ?>
<DesignationDefs>

	<DesignationDef>
		<defName>ComfortPrisoner</defName>
		<texturePath>UI/Commands/comfort_prisoner_true</texturePath>
		<targetType>Thing</targetType>
	</DesignationDef>

	<DesignationDef>
		<defName>ComfortPrisonerNoSticky</defName>
		<texturePath>UI/Commands/comfort_prisoner_invisible</texturePath>
		<targetType>Thing</targetType>
	</DesignationDef>
</DesignationDefs>


code:
<?xml version="1.0" encoding="utf-8" ?>

<Defs>

  <HediffDef Name="RJW_ImplantEgg" Abstract="True" Class="rjw.HediffDef_InsectEgg">
    <defName>RJW_InsectEgg</defName>
    <hediffClass>rjw.Hediff_InsectEgg</hediffClass>
    <defaultLabelColor>(0.8, 0.8, 0.35)</defaultLabelColor>
    <initialSeverity>0.1</initialSeverity>
    <isBad>true</isBad>
    <tendable>false</tendable>
  </HediffDef>

  <HediffDef ParentName="RJW_ImplantEgg">
    <defName>RJW_MegascarabEgg</defName>
    Megascarab's Egg
    <parentDef>Megascarab</parentDef>
  </HediffDef>
  <HediffDef ParentName="RJW_ImplantEgg">
    <defName>RJW_MegaspiderEgg</defName>
    Megaspider's Egg
    <parentDef>Megaspider</parentDef>
  </HediffDef>
  <HediffDef ParentName="RJW_ImplantEgg">
    <defName>RJW_SpelopedeEgg</defName>
    Spelopede's Egg
    <parentDef>Spelopede</parentDef>
  </HediffDef>

  <HediffDef Name="RJW_ImplantMech" Abstract="True" Class="rjw.HediffDef_MechImplants">
    <hediffClass>rjw.Hediff_MechImplants</hediffClass>
    <defaultLabelColor>(0.8, 0.8, 0.35)</defaultLabelColor>
    <initialSeverity>0.1</initialSeverity>
    <parentDefs>

Mechanoid_Scyther

Mechanoid_Centipede

    </parentDefs>
    <tendable>false</tendable>
  </HediffDef>

  <HediffDef ParentName="RJW_ImplantMech">
    <hediffClass>rjw.Hediff_MicroComputer</hediffClass>
    <defName>RJW_MicroComputer</defName>
    <tendable>false</tendable>
    <isBad>true</isBad>
    MicroComputer
    <minEventInterval>30000</minEventInterval>
    <maxEventInterval>90000</maxEventInterval>
    <randomHediffDefs>

RJW_Orgasm

TransportCums

    </randomHediffDefs>
  </HediffDef>

  <HediffDef>
    <defName>Parasite</defName>
    <hediffClass>rjw.Hediff_Parasite</hediffClass>
    <defaultLabelColor>(0.7, 1.0, 0.7)</defaultLabelColor>
    <initialSeverity>0.1</initialSeverity>
    pregnant
    <comps>

        <sendLetterWhenDiscovered>true</sendLetterWhenDiscovered>
        <discoverLetterLabel>{0} Parasited</discoverLetterLabel>
        <discoverLetterText>{0} is parasited!</discoverLetterText>

    </comps>
    <stages>

        early-stage-hidden
        <everVisible>false</everVisible>
        <vomitMtbDays>2.5</vomitMtbDays>

        middle-stage
        <minSeverity>0.333</minSeverity>
        <capMods>

            <capacity>Moving</capacity>
            <offset>-0.15</offset>

        </capMods>

        late-stage
        <minSeverity>0.666</minSeverity>
        <vomitMtbDays>2</vomitMtbDays>
        <capMods>

            <capacity>Moving</capacity>
            <offset>-0.30</offset>

        </capMods>

    </stages>
  </HediffDef>
</Defs>