Index: shortcodes.php
===================================================================
--- shortcodes.php	(revision 15483)
+++ shortcodes.php	(working copy)
@@ -1,298 +1,844 @@
 <?php
-/**
- * WordPress API for creating bbcode like tags or what WordPress calls
- * "shortcodes." The tag and attribute parsing or regular expression code is
- * based on the Textpattern tag parser.
- *
- * A few examples are below:
- *
- * [shortcode /]
- * [shortcode foo="bar" baz="bing" /]
- * [shortcode foo="bar"]content[/shortcode]
- *
- * Shortcode tags support attributes and enclosed content, but does not entirely
- * support inline shortcodes in other shortcodes. You will have to call the
- * shortcode parser in your function to account for that.
- *
- * {@internal
- * Please be aware that the above note was made during the beta of WordPress 2.6
- * and in the future may not be accurate. Please update the note when it is no
- * longer the case.}}
- *
- * To apply shortcode tags to content:
- *
- * <code>
- * $out = do_shortcode($content);
- * </code>
- *
- * @link http://codex.wordpress.org/Shortcode_API
- *
- * @package WordPress
- * @subpackage Shortcodes
- * @since 2.5
- */
+/*
+Author: Jacob Beauregard (jrbeaure at uvm dot edu)
+*/
 
+/*
+
+From Test Cases (http://svn.automattic.com/wordpress-tests/wp-testcase/test_shortcode.php)
+==========================================================================================
+Shortcode Statuses (to implement, or not to implement?)
+	enabled = the shortcode works as normal (default)
+	strip = the shortcode will be parsed and removed.  e.g. '[shortcode foo="bar"]' produces ''.  '[shortcode]foo[/shortcode]' produces 'foo'.
+	faux = the shortcode will be abbreviated.  e.g. '[shortcode foo="bar"]' products '[shortcode]'.  '[shortocde]foo[/shortcode]' produces '[shortcode]'
+	disabled = the shortcode is not parsed at all.  e.g. '[shortcode foo="bar"]' products '[shortcode foo="bar"]'
+
+
+Major Differences/Improvements:
+===============================
+I. Addressing http://codex.wordpress.org/Shortcode_API#Limitations
+
+	1. You can nest any tag at any depth regardless of ancestors (Ticket #10702)
+
+	2. Tag and attribute names may match /[A-Za-z][-A-Za-z0-9_:.]*//* (trialing /* because that comment ends),
+	   with case-insensitive interpretation
+
+	3. Interpretation doesn't get tripped up by things like hyphens
+
+II. Addressing Ticket #12760, 
+
+	1. Changed from fix in #6518
+	   Reasoning: balancing double-square-brackets can have misleading results with nesting
+
+	2. Shortcodes escapable by using [[, ]]
+
+	3. ']]' is escaped "right to left", so '[shortcode]]]' would evaluate to 'result]'
+
+	4. '[[' is escaped left to right '[[[shortcode]]]' => '[result]'
+
+III. Enhancements
+
+	1. Only matches valid shortcode for registered hooks, everything else will appear as text
+
+	2. Can register multiple hooks to single shortcode, uses priority (default: 10)
+
+IV. Conflicting Design Changes
+
+	1. Quoted literals are escaped by entities rather than cslashes
+
+	2. Inline (self-closing) shortcodes get passed content to accomodate multiple callbacks
+
+	3. No equivalent to shortcode_parse_atts function 
+	   (Not marked private in function reference, but not documented in shortcode API page)
+
+	4. Boolean attributes take the place of positional attributes 
+	   [foo bar] gets attributes array('bar' => 'bar') instead of array('0' => 'bar')
+
+	5. Disallows attribute and tag names that don't match /[A-Za-z][-A-Za-z0-9_:.]*/ 
+
+//pardon me /*
+
+	6. Disallows unquoted attribute values, unless they match /[-A-Za-z0-9_:.]+/
+
+
+VI. Addressing Tickets #9264, #10082 (see following, `Basic Interpretation Method`)
+
+
+Basic Interpretation Method:
+============================
+1. If an open tag is encountered, it is added to the stack
+
+2. If a close tag is encountered and there is no matching open tag on the stack
+   the close tag is ignored
+
+3. If a close tag is encountered and there is a matching open tag on the stack
+   all opened tags on the stack before the matched tag will be implicitly self-closed
+
+4. If text or an inline tag is encountered, it will be evaluated to its parent's
+   content immediately
+
+5. If tags are not closed by the end of the interpretation, they will be implicitly
+   self-closed
+
+
+Issues:
+=======
+1. Haven't written new unit tests to reflect new functionality added
+
+2. Documentation is not as good (though I hope most of the code is self-explanatory)
+
+3. Not 100% backwards compatible
+
+*/
+
+/* +++++ PUBLIC FUNCTIONS ++++++ */
+
 /**
- * Container for storing shortcode tags and their hook to call for the shortcode
- *
- * @since 2.5
- * @name $shortcode_tags
- * @var array
- * @global array $shortcode_tags
- */
-$shortcode_tags = array();
+ * add hook for shortcode tag
+*/
+function add_shortcode($name,$handler,$priority=10) {
+	$name = strtolower($name);
+	shortcode_register_set('priority',$name,$handler,$priority);
+	shortcode_register_set('callback',$name,$priority,$handler,true);
+}
 
 /**
- * Add hook for shortcode tag.
- *
- * There can only be one hook for each shortcode. Which means that if another
- * plugin has a similar shortcode, it will override yours or yours will override
- * theirs depending on which order the plugins are included and/or ran.
- *
- * Simplest example of a shortcode tag using the API:
- *
- * <code>
- * // [footag foo="bar"]
- * function footag_func($atts) {
- * 	return "foo = {$atts[foo]}";
- * }
- * add_shortcode('footag', 'footag_func');
- * </code>
- *
- * Example with nice attribute defaults:
- *
- * <code>
- * // [bartag foo="bar"]
- * function bartag_func($atts) {
- * 	extract(shortcode_atts(array(
- * 		'foo' => 'no foo',
- * 		'baz' => 'default baz',
- * 	), $atts));
- *
- * 	return "foo = {$foo}";
- * }
- * add_shortcode('bartag', 'bartag_func');
- * </code>
- *
- * Example with enclosed content:
- *
- * <code>
- * // [baztag]content[/baztag]
- * function baztag_func($atts, $content='') {
- * 	return "content = $content";
- * }
- * add_shortcode('baztag', 'baztag_func');
- * </code>
- *
- * @since 2.5
- * @uses $shortcode_tags
- *
- * @param string $tag Shortcode tag to be searched in post content.
- * @param callable $func Hook to run when shortcode is found.
- */
-function add_shortcode($tag, $func) {
-	global $shortcode_tags;
+ * remove hook for shortcode tag
+*/
+function remove_shortcode($name,$handler) {
+	$name = strtolower($name);
+	$priority = shortcode_register_get('priority',$name,$handler);
 
-	if ( is_callable($func) )
-		$shortcode_tags[$tag] = $func;
+	shortcode_register_unset('priority',$name,$handler);
+	shortcode_register_unset('callback',$name,$priority,$handler);
 }
 
 /**
- * Removes hook for shortcode.
- *
- * @since 2.5
- * @uses $shortcode_tags
- *
- * @param string $tag shortcode tag to remove hook for.
- */
-function remove_shortcode($tag) {
-	global $shortcode_tags;
+ * check if handler is hooked to shortcode tag, returns priority
+*/
+function has_shortcode($name,$handler) {
+	$name = strtolower($name);
+	return shortcode_register_get('priority',$name,$handler);
+}
 
-	unset($shortcode_tags[$tag]);
+/**
+ * removes all shortcode hooks
+*/
+function remove_all_shortcodes($name) {
+	$name = strtolower($name);
+	shortcode_register_unset('priority',$name);
+	shortcode_register_unset('callback',$name);
 }
 
 /**
- * Clear all shortcodes.
- *
- * This function is simple, it clears all of the shortcode tags by replacing the
- * shortcodes global by a empty array. This is actually a very efficient method
- * for removing all shortcodes.
- *
- * @since 2.5
- * @uses $shortcode_tags
- */
-function remove_all_shortcodes() {
-	global $shortcode_tags;
+ * retrieves all shortcode hooks associated with a tag
+ * returns order FCFS by priority
+*/
+function get_shortcodes($name) {
+	$name = strtolower($name);
+	$priority_groups = shortcode_register_get('callback',$name);
+	$callbacks = array();
+	if (is_array($priority_groups)) {
+		foreach ($priority_groups as $priority_group) {
+			$callbacks = array_merge($callbacks,$priority_group);
+		}
+	}
+	return array_keys($callbacks);
+}
 
-	$shortcode_tags = array();
+/**
+ * executes the shortcode hooks for the shortcode tag with specified attributes
+ * initial content is $content, but is actively modified by each handler
+*/
+function apply_shortcodes($attrs,$content,$name) {
+	$name = strtolower($name);
+	$callbacks = get_shortcodes($name);
+
+	if (is_array($callbacks)) {
+		foreach ($callbacks as $callback) {
+			if (function_exists($callback)) {
+				$content = $callback($attrs,$content,$name);
+			}
+		}
+	}
+
+	return $content;
 }
 
 /**
- * Search content for shortcodes and filter shortcodes through their hooks.
- *
- * If there are no shortcode tags defined, then the content will be returned
- * without any filtering. This might cause issues when plugins are disabled but
- * the shortcode will still show up in the post or content.
- *
- * @since 2.5
- * @uses $shortcode_tags
- * @uses get_shortcode_regex() Gets the search pattern for searching shortcodes.
- *
- * @param string $content Content to search for shortcodes
- * @return string Content with shortcodes filtered out.
- */
-function do_shortcode($content) {
-	global $shortcode_tags;
+ * removes shortcodes with registered handlers from page
+*/
+function strip_shortcodes($content) {
+	$sc_re = "/" . shortcode_re_combine(array('tag_inline','tag_open','tag_close')) . "/";
+	$content = preg_replace($sc_re,'',$content);
+	return $content;
+}
 
-	if (empty($shortcode_tags) || !is_array($shortcode_tags))
-		return $content;
+/**
+ * alias of array_merge, unless we want to special case positional attributes
+*/
+function shortcode_atts($defaults, $attrs) {
+	return array_merge($defaults, $attrs);
+}
 
-	$pattern = get_shortcode_regex();
-	return preg_replace_callback('/'.$pattern.'/s', 'do_shortcode_tag', $content);
+/* run main eval function */
+function do_shortcode($expr) {
+	return shortcode_eval($expr);
 }
 
+
+
+/* +++++++++++++ THE UNDERPINNINGS +++++++++++ */
+
 /**
- * Retrieve the shortcode regular expression for searching.
- *
- * The regular expression combines the shortcode tags in the regular expression
- * in a regex class.
- *
- * The regular expresion contains 6 different sub matches to help with parsing.
- *
- * 1/6 - An extra [ or ] to allow for escaping shortcodes with double [[]]
- * 2 - The shortcode name
- * 3 - The shortcode argument list
- * 4 - The self closing /
- * 5 - The content of a shortcode when it wraps some content.
- *
- * @since 2.5
- * @uses $shortcode_tags
- *
- * @return string The shortcode search regular expression
- */
-function get_shortcode_regex() {
-	global $shortcode_tags;
-	$tagnames = array_keys($shortcode_tags);
-	$tagregexp = join( '|', array_map('preg_quote', $tagnames) );
+ * retrieves all shortcode tags that have at least one registered hook
+*/
+function shortcode_tags() {
+	$tags = array_keys(shortcode_register_get('callback'));
+	return $tags;
+}
 
-	// WARNING! Do not change this regex without changing do_shortcode_tag() and strip_shortcodes()
-	return '(.?)\[('.$tagregexp.')\b(.*?)(?:(\/))?\](?:(.+?)\[\/\2\])?(.?)';
+/**
+ * keeps track of information shared between many shortcode functions
+*/
+//I'm at war with global variables, so I used a static variable instead.
+function shortcode_register($assignment=null) {
+	static $register = array();
+	if ($assignment !== null) {
+		$register = $assignment;
+	}
+	return $register;
 }
 
 /**
- * Regular Expression callable for do_shortcode() for calling shortcode hook.
- * @see get_shortcode_regex for details of the match array contents.
- *
- * @since 2.5
- * @access private
- * @uses $shortcode_tags
- *
- * @param array $m Regular expression match array
- * @return mixed False on failure.
- */
-function do_shortcode_tag( $m ) {
-	global $shortcode_tags;
+ * gets a value from the register based on attributes
+*/
+function shortcode_register_get() {
+	$arguments = func_get_args();
+	$register  = shortcode_register();
 
-	// allow [[foo]] syntax for escaping a tag
-	if ( $m[1] == '[' && $m[6] == ']' ) {
-		return substr($m[0], 1, -1);
+	$value = $register;
+	while (count($arguments) && $value != null) {
+		$key = array_shift($arguments);
+		if (isset($value[$key])) {
+			$value = $value[$key];
+		} else {
+			$value = null;
+		}
 	}
+	return $value;
+}
 
-	$tag = $m[2];
-	$attr = shortcode_parse_atts( $m[3] );
+/**
+ * sets a value in the register
+*/
+function shortcode_register_set() {
+	$arguments = func_get_args();
+	$register  = shortcode_register();
+	$value     = array_pop($arguments);
 
-	if ( isset( $m[5] ) ) {
-		// enclosing tag - extra parameter
-		return $m[1] . call_user_func( $shortcode_tags[$tag], $attr, $m[5], $tag ) . $m[6];
-	} else {
-		// self-closing tag
-		return $m[1] . call_user_func( $shortcode_tags[$tag], $attr, NULL,  $tag ) . $m[6];
+	$group =& $register;
+	while (count($arguments)) {
+		$key = array_shift($arguments);
+		if (!is_array($group[$key])) {
+			$group[$key] = array();
+		}
+		$group =& $group[$key];
 	}
+	$group = $value;
+	shortcode_register($register);
 }
 
 /**
- * Retrieve all attributes from the shortcodes tag.
- *
- * The attributes list has the attribute name as the key and the value of the
- * attribute as the value in the key/value pair. This allows for easier
- * retrieval of the attributes, since all attributes have to be known.
- *
- * @since 2.5
- *
- * @param string $text
- * @return array List of attributes and their value.
- */
-function shortcode_parse_atts($text) {
-	$atts = array();
-	$pattern = '/(\w+)\s*=\s*"([^"]*)"(?:\s|$)|(\w+)\s*=\s*\'([^\']*)\'(?:\s|$)|(\w+)\s*=\s*([^\s\'"]+)(?:\s|$)|"([^"]*)"(?:\s|$)|(\S+)(?:\s|$)/';
-	$text = preg_replace("/[\x{00a0}\x{200b}]+/u", " ", $text);
-	if ( preg_match_all($pattern, $text, $match, PREG_SET_ORDER) ) {
-		foreach ($match as $m) {
-			if (!empty($m[1]))
-				$atts[strtolower($m[1])] = stripcslashes($m[2]);
-			elseif (!empty($m[3]))
-				$atts[strtolower($m[3])] = stripcslashes($m[4]);
-			elseif (!empty($m[5]))
-				$atts[strtolower($m[5])] = stripcslashes($m[6]);
-			elseif (isset($m[7]) and strlen($m[7]))
-				$atts[] = stripcslashes($m[7]);
-			elseif (isset($m[8]))
-				$atts[] = stripcslashes($m[8]);
+ * unsets/deletes a value from the register
+*/
+function shortcode_register_unset() {
+	$arguments = func_get_args();
+	$register = short_code_register();
+	$group =& $register;
+
+	$exists = true;
+	while (count($arguments) > 1 && !$exists) {
+		$key = array_shift($arguments);
+		if (!is_array($group[$key])) {
+			$exists = false;
+		} else {
+			$group =& $group[$key];
 		}
-	} else {
-		$atts = ltrim($text);
 	}
-	return $atts;
+	if ($exists) {
+		$key = array_shift($arguments);
+		unset($group[$key]);
+	}
+	shortcode_register($register);
 }
 
+/*
+ * types of shortcodes to be parsed
+*/
+function shortcode_types() {
+	return array(
+		'esc_lsqbr',
+		'esc_rsqbr',
+		'tag_inline',
+		'tag_open',
+		'tag_close',
+		'attr',
+		'literal',
+		'entity_ref',
+		'char_ref',
+		'char_str',
+		'name',
+		'text'
+	);
+}
+
+/*
+ * subtypes of a root (raw) expression
+*/
+function shortcode_sub_root() {
+	return array('esc_rsqbr','esc_lsqbr','tag_inline','tag_open','tag_close','text');
+}
+
+/*
+ * subtypes of a tag_inline expression
+*/
+function shortcode_sub_tag_inline() {
+	return array('attr','name');
+}
+
+/*
+ * i bet you get the point by now
+*/
+function shortcode_sub_tag_open() {
+	return array('attr','name');
+}
+
+function shortcode_sub_tag_close() {
+	return array('name');
+}
+
+function shortcode_sub_text() {
+	return null;
+}
+
+function shortcode_sub_name() {
+	return null;
+}
+
+function shortcode_sub_attr() {
+	return array('literal','name');
+}
+
+function shortcode_sub_literal() {
+	return array('char_ref','entity_ref','char_str');
+}
+
+function shortcode_sub_char_str() {
+	return null;
+}
+
+function shortcode_sub_char_ref() {
+	return null;
+}
+
+function shortcode_sub_entity_ref() {
+	//formally it's a name, but it's easier to interpret it with the & and ;
+	return null; //array('name');
+}
+
+function shortcode_sub_esc_lsqbr() {
+	return null;
+}
+
+function shortcode_sub_esc_rsqbr() {
+	return null;
+}
+
 /**
- * Combine user attributes with known attributes and fill in defaults when needed.
- *
- * The pairs should be considered to be all of the attributes which are
- * supported by the caller and given as a list. The returned attributes will
- * only contain the attributes in the $pairs list.
- *
- * If the $atts list has unsupported attributes, then they will be ignored and
- * removed from the final returned list.
- *
- * @since 2.5
- *
- * @param array $pairs Entire list of supported attributes and their defaults.
- * @param array $atts User defined attributes in shortcode tag.
- * @return array Combined and filtered attribute list.
- */
-function shortcode_atts($pairs, $atts) {
-	$atts = (array)$atts;
-	$out = array();
-	foreach($pairs as $name => $default) {
-		if ( array_key_exists($name, $atts) )
-			$out[$name] = $atts[$name];
-		else
-			$out[$name] = $default;
+ * returns subtypes of type specified by $type
+*/
+function shortcode_sub($type='root') {
+	if (in_array($type,shortcode_types()) || $type == 'root') {
+		$func = "shortcode_sub_{$type}";
+		$subtypes = $func();
 	}
-	return $out;
+	return $subtypes;
 }
 
+
+// started writing backwards compatibility function
+// but would really prefer not to support these
+
+function shortcode_re_compat_name($named=false) {
+	$expr = '\w+';
+	if ($named) {
+		$expr = "(?P<compat_name>{$expr})";
+	}
+	return $expr;
+}
+
+function shortcode_re_compat_literal($named=false) {
+	$nq = shortcode_re_compat_literal_nq();
+	$sq = shortcode_re_compat_literal_sq();
+	$dq = shortcode_re_compat_literal_dq();
+	$expr = "(?:{$nq}|{$sq}|{$dq})";
+	if ($named) {
+		$expr = "(?P<compat_literal>{$expr})";
+	}
+	return $expr;
+}
+
+function shortcode_re_compat_literal_dq() {
+	$expr = '"[^"]*"';
+	return $expr;
+}
+
+function shortcode_re_compat_literal_sq() {
+	$expr = "'[^']*'";
+	return $expr;
+}
+
+function shortcode_re_compat_literal_nq() {
+	$expr = '\S+?(?=\])';
+	return $expr;
+}
+
+function shortcode_re_compat_attr($named=false) {
+	$name = shortcode_re_compat_name();
+	$literal = shortcode_re_compat_literal();
+	$expr = "(?:{$name}\\s*=)?\\s*{$literal}";
+	if ($named) {
+		$expr = "(?P<compat_attr>{$expr})";
+	}
+	return $expr;	
+}
+
 /**
- * Remove all shortcode tags from the given content.
- *
- * @since 2.5
- * @uses $shortcode_tags
- *
- * @param string $content Content to remove shortcode tags.
- * @return string Content without shortcode tags.
- */
-function strip_shortcodes( $content ) {
-	global $shortcode_tags;
+ * regular expression to match a valid name
+*/
+function shortcode_re_name($named=false) {
+	$expr = '[A-Za-z][-A-Za-z0-9_:.]*';
+	if ($named) {
+		$expr = "(?P<name>{$expr})";
+	}
+	return $expr;
+}
 
-	if (empty($shortcode_tags) || !is_array($shortcode_tags))
-		return $content;
+/**
+ * regular expression to match a name that's registered as a shortcode
+*/
+function shortcode_re_registered_name($named=false) {
+	$tags = array_map('preg_quote',shortcode_tags());
+	$expr = join('|',$tags);
+	$expr = "(?i:{$expr})";
+	if ($named) {
+		$expr = "(?P<name>{$expr})";
+	}
+	return $expr;
+}
 
-	$pattern = get_shortcode_regex();
+/**
+ * you probably get the point by now
+*/
+function shortcode_re_char_str($named=false) {
+	$expr =  "[^%&]+";
+	if ($named) {
+		$expr = "(?P<char_str>{$expr})";
+	}
+	return $expr;
+}
 
-	return preg_replace('/'.$pattern.'/s', '$1$6', $content);
+/**
+ * single quote version for shortcode_re_char_str
+*/
+function shortcode_re_char_str_sq($named=false) {
+	$expr =  "[^%&']+";
+	if ($named) {
+		$expr = "(?P<char_str>{$expr})";
+	}
+	return $expr;
 }
 
-add_filter('the_content', 'do_shortcode', 11); // AFTER wpautop()
+/**
+ * double quote version for shortcode_re_char_str
+*/
+function shortcode_re_char_str_dq($named=false) {
+	$expr = '[^%&"]+';
+	if ($named) {
+		$expr = "(?P<char_str>{$expr})";
+	}
+	return $expr;
+}
 
-?>
\ No newline at end of file
+function shortcode_re_char_ref($named=false) {
+	$expr = "(?:[0-9]+|x[0-9a-fA-F]+)";
+	if ($named) {
+		$expr = "(?P<char_ref>{$expr})";
+	}
+	$expr = "&#{$expr};";
+	return $expr;
+}
+
+function shortcode_re_entity_ref($named=false) {
+	$name = shortcode_re_name();
+
+	$expr = "&{$name};";
+	if ($named) {
+		$expr = "(?P<entity_ref>{$expr})";
+	}
+	return $expr;
+}
+
+function shortcode_re_literal($named=false) {
+	$nq = shortcode_re_literal_nq();
+	$sq = shortcode_re_literal_sq();
+	$dq = shortcode_re_literal_dq();
+	$expr = "(?:{$nq}|{$sq}|{$dq})";
+	if ($named) {
+		$expr = "(?P<literal>{$expr})";
+	}
+	return $expr;
+}
+
+function shortcode_re_literal_nq($named=false) {
+	$expr = "[-a-zA-Z0-9_:.]+";
+	return $expr;
+}
+
+function shortcode_re_literal_sq($named=false) {
+	$char_str = shortcode_re_char_str_sq();
+	$char_ref = shortcode_re_char_ref();
+	$entity_ref = shortcode_re_entity_ref();
+
+	$expr = "'(?:{$char_str}|{$char_ref}|{$entity_ref})*'";
+	return $expr;
+}
+
+function shortcode_re_literal_dq($named=false) {
+	$char_str = shortcode_re_char_str_dq();
+	$char_ref = shortcode_re_char_ref();
+	$entity_ref = shortcode_re_entity_ref();
+
+	$expr =  "\"(?:{$char_str}|{$char_ref}|{$entity_ref})*\"";
+	return $expr;
+}
+
+function shortcode_re_attr($named=false) {
+	$name  = shortcode_re_name();
+	$literal = shortcode_re_literal();
+
+	$expr = "{$name}(?:={$literal})?";
+	if ($named) {
+		$expr = "(?P<attr>{$expr})";
+	}
+	return $expr;
+}
+
+function shortcode_re_esc_lsqbr($named=false) {
+	$expr = "\\[\\[";
+	if ($named) {
+		$expr = "(?P<esc_lsqbr>{$expr})";
+	}
+	return $expr;
+}
+
+function shortcode_re_esc_rsqbr($named=false) {
+	//two right square brackets not immediately followed by an odd number of right square brackets
+	$expr = '\]\](?=(?:\]\])*(?!\]))';
+	if ($named) {
+		$expr = "(?P<esc_rsqbr>{$expr})";
+	}
+	return $expr;
+}
+
+function shortcode_re_tag_inline($named=false) {
+	$name = shortcode_re_registered_name();
+	$attr = shortcode_re_attr();
+
+	$expr = "{$name}(?:\\s+{$attr})*";
+	if ($named) {
+		$expr = "(?P<tag_inline>{$expr})";
+	}
+	$expr = "\\[{$expr}\\s*\\/\\]";
+	return $expr;
+}
+
+function shortcode_re_tag_open($named=false) {
+	$name = shortcode_re_registered_name();
+	$attr = shortcode_re_attr();
+
+	$expr = "{$name}(?:\\s+{$attr})*";
+	if ($named) {
+		$expr = "(?P<tag_open>{$expr})";
+	}
+	$expr = "\\[{$expr}\\s*\\]";
+	return $expr;
+}
+
+function shortcode_re_tag_close($named=false) {
+	$name = shortcode_re_registered_name();
+	$expr = $name;
+	if ($named) {
+		$expr = "(?P<tag_close>{$expr})";
+	}
+	$expr = "\\[\\/{$expr}\\s*\\]";
+	return $expr;
+}
+
+function shortcode_re_text($named=false) {
+	$expr = '(?s:.[^\[\]]*)';
+	if ($named) {
+		$expr = "(?P<text>{$expr})";
+	}
+	return $expr;
+}
+
+/**
+ * combines shortcode regex of types specified in parameters, returns delimited regex
+*/
+function shortcode_re_combine() {
+	$types = func_get_args();
+
+	$regexps = array();
+	foreach ($types as $type) {
+		$re_func = "shortcode_re_$type";
+		array_push($regexps,$re_func(1));
+	}
+	$statement = '/' . join('|',$regexps) . '/';
+	return $statement;
+}
+
+/**
+ * returns regular expression to match subtypes of an expression (ex. attr => {$name}={$literal})
+*/
+function shortcode_re_subtypes($type) {
+	$subtypes = shortcode_sub($type);
+	$re = null;
+	if (count($subtypes)) {
+		$re = call_user_func_array('shortcode_re_combine',$subtypes);
+	}
+	return $re;
+}
+
+/**
+ * evaluates a name expression
+*/
+function shortcode_eval_name($name) {
+	return strtolower($name['expression']);
+}
+
+/**
+ * evaluates a char_str expression
+*/
+function shortcode_eval_char_str($char_str) {
+	return $char_str['expression'];
+}
+
+/**
+ * did you get the point yet?
+*/
+function shortcode_eval_char_ref($char_ref) {
+	$expr = $char_ref['expression'];
+	if ($expr[0] == 'x') {
+		$expr = hexdec($expr);
+	}
+	$expr = chr($expr);
+	return $expr;
+}
+
+function shortcode_eval_entity_ref($entity_ref) {
+	$expr = strtolower($entity_ref['expression']);
+	$lookup = array_flip(get_html_translation_table(HTML_ENTITIES));
+	return $lookup[$expr];
+}
+
+function shortcode_eval_literal($literal) {
+	$expr = "";
+	foreach ($literal['children'] as $child) {
+		$eval_func = "shortcode_eval_{$child['type']}";
+		$expr .= $eval_func($child);
+	}
+	if ($expr[0] == "'" || $expr[0] == '"') {
+		$expr = substr($expr,1,strlen($expr)-2);
+	}
+	return $expr;
+}
+
+function shortcode_eval_attr($attr) {
+	$children = $attr['children'];
+	$key = shortcode_eval_name($children[0]);
+	if (isset($children[1])) {
+		$value = shortcode_eval_literal($children[1]);
+	} else {
+		$value = $key;
+	}
+	return array('key' => $key, 'value' => $value);
+}
+
+function shortcode_eval_tag_inline($tag_inline,&$stack) {
+	$children = $tag_inline['children'];
+
+	$name = shortcode_eval_name($children[0]);
+	$attrs = array();
+
+	foreach ($children as $child) {
+		if ($child['type'] == 'attr') {
+			$attr = shortcode_eval_attr($child);
+			$key = $attr['key'];
+			$value = $attr['value'];
+			$attrs[$key] = $value;
+		}
+	}
+
+	$content = apply_shortcodes($attrs,'',$name);
+
+	// adds evaluated content to next on the stack
+	$node = array_pop($stack);
+	$node['content'] .= $content;
+	array_push($stack,$node);
+
+	return $node;
+}
+
+function shortcode_eval_esc_lsqbr($esc_lsqbr,&$stack) {
+	$node = array_pop($stack);
+	$node['content'] .= "[";
+	array_push($stack,$node);
+
+	return $node;
+}
+
+function shortcode_eval_esc_rsqbr($esc_rsqbr,&$stack) {
+	$node = array_pop($stack);
+	$node['content'] .= "]";
+	array_push($stack,$node);
+
+	return $node;	
+}
+
+function shortcode_eval_tag_open($tag_open,&$stack,&$refs) {
+	$children = $tag_open['children'];
+
+	$name = shortcode_eval_name($children[0]);
+	$attrs = array();
+
+	foreach ($children as $child) {
+		if ($child['type'] == 'attr') {
+			$attr = shortcode_eval_attr($child);
+			$key = $attr['key'];
+			$value = $attr['value'];
+			$attrs[$key] = $value;
+		}
+	}
+
+	$data = array('name' => $name, 'attrs' => $attrs);
+	$node = array('node' => $data, 'content' => '');
+
+	//throws itself on the stack with attributes, etc.
+	array_push($stack,$node);
+	$refs[$name] += 1;
+
+	return $node;
+}
+
+function shortcode_eval_tag_close($tag_close,&$stack,&$refs) {
+	$value = null;
+
+	$name = shortcode_eval_name($tag_close['children'][0]);
+
+	if ($refs[$name] > 0) {
+		do {
+			$child = array_pop($stack);
+			$child_node  = $child['node'];
+			$child_name  = $child['node']['name'];
+			$child_attrs = $child['node']['attrs'];
+
+			$parent = array_pop($stack);
+			$parent['content'] .= apply_shortcodes($child_attrs,'',$child_name);
+			$parent['content'] .= $child['content'];
+
+			$refs[$name] -= 1;
+			array_push($stack,$parent);
+		} while ($child_name != $name);
+	}
+
+	return array('name' => $name);
+}
+
+function shortcode_eval_text($text,&$stack) {
+	$node = array_pop($stack);
+	$node['content'] .= $text['expression'];
+	array_push($stack,$node);
+	return $text['expression'];
+}
+
+function shortcode_eval($expr) {
+	$stack = array();
+	$refs  = array();
+
+	//build parse tree
+	$root = shortcode_build_tree($expr);
+
+	//create root content node, throw on stack
+	$root_node = array('node' => 'root', 'content' => '');
+	array_push($stack,$root_node);
+
+	//evaluate immediate children
+	$subs = shortcode_sub();
+	foreach ($root['children'] as $child) {
+		if (in_array($child['type'],$subs)) {
+			$func = "shortcode_eval_{$child['type']}";
+			$func($child,$stack,$refs);
+		}
+	}
+
+	//evaluate children remaining on stack
+	$child = array_pop($stack);
+	while ($child['node'] != 'root') {
+		$parent = array_pop($stack);
+
+		$child_node  = $child['node'];
+		$child_name  = $child['node']['name'];
+		$child_attrs = $child['node']['attrs'];
+
+		$parent['content'] .= apply_shortcodes($child_attrs,'',$child_name);
+		$parent['content'] .= $child['content'];
+		$child = $parent;
+	}
+
+	return $child['content'];
+}
+
+/**
+* builds a tree from a shortcode expression
+*/
+function shortcode_build_tree($parent) {
+	if (is_string($parent)) {
+		$parent = array('type' => 'root', 'expression' => $parent);
+	}
+	$parent_type = $parent['type'];
+	$parent_expression = $parent['expression'];
+
+	$re_subtypes = shortcode_re_subtypes($parent_type);
+	$nodes = array();
+	if ($re_subtypes) {
+		$match_set = array();
+		preg_match_all($re_subtypes,$parent_expression,$match_set,PREG_SET_ORDER);
+
+		foreach ($match_set as $index => $match) {
+			foreach (shortcode_types() as $type) {
+				if (strlen($match[$type]) > 0) {
+					$nodes[$index] = array('type' => $type, 'expression' => $match[$type]);
+				}
+			}
+		}
+	}
+
+
+	$parent['children'] = array_map('shortcode_build_tree',$nodes);
+	return $parent;
+}
+
+
+add_filter('the_content','shortcode_eval',11);
\ No newline at end of file
