+ $supp_id = '';
+ if (strpos($atts, 'class=') === false) {
+ $atts .= ' class="footnote"';
+ }
+
+ if (strpos($atts, ' id=') === false) {
+ $atts .= ' id="fn' . $fnid . '"';
+ } else {
+ $supp_id = ' id="fn' . $fnid . '"';
+ }
+
+ if (strpos($att, '^') === false) {
+ $sup = $this->formatFootnote($fns['fnid'], $supp_id);
+ } else {
+ $sup = $this->formatFootnote(''.$fns['fnid'] .' ', $supp_id);
+ }
+
+ $content = $sup . ' ' . $content;
+ }
+
+ if ($tag == "bq") {
+ $cite = $this->shelveURL($cite);
+ $cite = ($cite != '') ? ' cite="' . $cite . '"' : '';
+ $o1 = "\n";
+ $o2 = "\tparseAttribs($att, '', 0).">";
+ $c2 = "
";
+ $c1 = "\n ";
+ } elseif ($tag == 'bc') {
+ $o1 = "";
+ $c1 = "
";
+ $content = $this->shelve($this->rEncodeHTML($content));
+ } elseif ($tag == 'notextile') {
+ $content = $this->shelve($content);
+ $o1 = '';
+ $o2 = '';
+ $c1 = '';
+ $c2 = '';
+ } elseif ($tag == 'pre') {
+ $content = $this->shelve($this->rEncodeHTML($content));
+ $o1 = "";
+ $o2 = '';
+ $c2 = '';
+ $c1 = " ";
+ } elseif ($tag == '###') {
+ $eat = true;
+ } else {
+ $o2 = "<$tag$atts>";
+ $c2 = "$tag>";
+ }
+
+ $content = (!$eat) ? $this->graf($content) : '';
+
+ return array($o1, $o2, $content, $c2, $c1, $eat);
+ }
+
+ /**
+ * Formats a footnote.
+ *
+ * @param string $marker The marker
+ * @param string $atts Attributes
+ * @param bool $anchor TRUE, if its a reference link
+ * @return string Processed footnote
+ */
+
+ protected function formatFootnote($marker, $atts = '', $anchor = true)
+ {
+ $pattern = ($anchor) ? $this->symbols['fn_foot_pattern'] : $this->symbols['fn_ref_pattern'];
+ return $this->replaceMarkers($pattern, array('atts' => $atts, 'marker' => $marker));
+ }
+
+ /**
+ * Replaces markers with replacements in the given input.
+ *
+ * @param string $text The input
+ * @param array $replacements Marker replacement pairs
+ * @return string
+ */
+
+ protected function replaceMarkers($text, $replacements)
+ {
+ if (!empty($replacements)) {
+ foreach ($replacements as $k => $r) {
+ $text = str_replace('{'.$k.'}', $r, $text);
+ }
+ }
+ return $text;
+ }
+
+ /**
+ * Parses HTML comments in the given input.
+ *
+ * This method finds HTML comments in the given input
+ * and replaces them with reference tokens.
+ *
+ * @param string $text Textile input
+ * @return string $text Processed input
+ */
+
+ protected function getHTMLComments($text)
+ {
+ $text = preg_replace_callback(
+ "/\/sx",
+ array(&$this, "fParseHTMLComments"),
+ $text
+ );
+ return $text;
+ }
+
+ /**
+ * Formats a HTML comment.
+ *
+ * Stores the comment on the shelf and returns
+ * a reference token wrapped in to a HTML comment.
+ *
+ * @param array $m Options
+ * @return string Reference token wrapped to a HTML comment tags
+ */
+
+ protected function fParseHTMLComments($m)
+ {
+ return '';
+ }
+
+ /**
+ * Parses paragraphs in the given input.
+ *
+ * @param string $text Textile input
+ * @return string Processed input
+ */
+
+ protected function graf($text)
+ {
+ // Handle normal paragraph text
+ if (!$this->lite) {
+ // Notextile blocks and inlines
+ $text = $this->noTextile($text);
+ // Handle code
+ $text = $this->code($text);
+ }
+
+ // HTML comments --
+ $text = $this->getHTMLComments($text);
+ // Consume link aliases
+ $text = $this->getRefs($text);
+ // Treat quoted quote as a special glyph.
+ $text = $this->glyphQuotedQuote($text);
+ // Generate links
+ $text = $this->links($text);
+
+ // Handle images (if permitted)
+ if (!$this->noimage) {
+ $text = $this->images($text);
+ }
+
+ if (!$this->lite) {
+ // Handle tables
+ $text = $this->tables($text);
+ // Handle redcloth-style definition lists
+ $text = $this->redclothLists($text);
+ // Handle ordered & unordered lists plus txp-style definition lists
+ $text = $this->textileLists($text);
+ }
+
+ // Inline markup (em, strong, sup, sub, del etc)
+ $text = $this->spans($text);
+
+ if (!$this->lite) {
+ // Turn footnote references into supers or links.
+ // As footnote blocks are banned in lite mode there is no point
+ // generating links for them.
+ $text = $this->footnoteRefs($text);
+
+ // Turn note references into links
+ $text = $this->noteRefs($text);
+ }
+
+ // Glyph level substitutions (mainly typographic -- " & ' => curly quotes, -- => em-dash etc.
+ $text = $this->glyphs($text);
+
+ return rtrim($text, "\n");
+ }
+
+ /**
+ * Replaces Textile span tags with their equivalent HTML inline tags.
+ *
+ * @param string $text The Textile document to perform the replacements in
+ * @return string The Textile document with spans replaced by their HTML inline equivalents
+ */
+
+ protected function spans($text)
+ {
+ $span_tags = array_keys($this->span_tags);
+ $pnct = ".,\"'?!;:‹›«»„“”‚‘’";
+ $this->span_depth++;
+
+ if ($this->span_depth <= $this->max_span_depth) {
+ foreach ($span_tags as $tag) {
+ $tag = preg_quote($tag);
+ $text = preg_replace_callback(
+ "/
+ (?P^|(?<=[\s>$pnct\(])|[{[])
+ (?P$tag)(?!$tag)
+ (?P{$this->cls})
+ (?!$tag)
+ (?::(?P\S+[^$tag]{$this->regex_snippets['space']}))?
+ (?P[^{$this->regex_snippets['space']}$tag]+|\S.*?[^\s$tag\n])
+ (?P[$pnct]*)
+ $tag
+ (?P$|[\[\]}<]|(?=[$pnct]{1,2}[^0-9]|\s|\)))
+ /x".$this->regex_snippets['mod'],
+ array(&$this, "fSpan"),
+ $text
+ );
+ }
+ }
+ $this->span_depth--;
+ return $text;
+ }
+
+ /**
+ * Formats a span tag and stores it on the shelf.
+ *
+ * @param array $m Options
+ * @return string Content wrapped to reference tokens
+ * @see Parser::spans()
+ */
+
+ protected function fSpan($m)
+ {
+ $tag = $this->span_tags[$m['tag']];
+ $atts = $this->parseAttribsToArray($m['atts']);
+
+ if ($m['cite'] != '') {
+ $atts['cite'] = trim($m['cite']);
+ ksort($atts);
+ }
+
+ $atts = $this->formatAttributeString($atts);
+ $content = $this->spans($m['content']);
+ $opentag = '<'.$tag.$atts.'>';
+ $closetag = ''.$tag.'>';
+ $tags = $this->storeTags($opentag, $closetag);
+ $out = "{$tags['open']}{$content}{$m['end']}{$tags['close']}";
+
+ if (($m['pre'] && !$m['tail']) || ($m['tail'] && !$m['pre'])) {
+ $out = $m['pre'].$out.$m['tail'];
+ }
+
+ return $out;
+ }
+
+ /**
+ * Stores a tag pair in the tag cache.
+ *
+ * @param string $opentag Opening tag
+ * @param string $closetag Closing tag
+ * @return array Reference tokens for both opening and closing tag
+ */
+
+ protected function storeTags($opentag, $closetag = '')
+ {
+ $tags = array();
+
+ $this->refCache[$this->refIndex] = $opentag;
+ $tags['open'] = $this->uid.$this->refIndex.':ospan ';
+ $this->refIndex++;
+
+ $this->refCache[$this->refIndex] = $closetag;
+ $tags['close'] = ' '.$this->uid.$this->refIndex.':cspan';
+ $this->refIndex++;
+
+ return $tags;
+ }
+
+ /**
+ * Replaces reference tokens with corresponding shelved span tags.
+ *
+ * This method puts all shelved span tags back to the final,
+ * parsed input.
+ *
+ * @param string $text The input
+ * @return string Processed text
+ * @see Parser::storeTags()
+ */
+
+ protected function retrieveTags($text)
+ {
+ $text = preg_replace_callback(
+ '/'.$this->uid.'(?P[0-9]+):ospan /',
+ array(&$this, 'fRetrieveTags'),
+ $text
+ );
+
+ $text = preg_replace_callback(
+ '/ '.$this->uid.'(?P[0-9]+):cspan/',
+ array(&$this, 'fRetrieveTags'),
+ $text
+ );
+
+ return $text;
+ }
+
+ /**
+ * Retrieves a tag from the tag cache.
+ *
+ * @param array $m Options
+ * @return string
+ * @see Parser::retrieveTags()
+ */
+
+ protected function fRetrieveTags($m)
+ {
+ return $this->refCache[$m['token']];
+ }
+
+ /**
+ * Parses note lists in the given input.
+ *
+ * This method should be ran after other blocks
+ * have been processed, but before reference tokens
+ * have been replaced with their replacements.
+ *
+ * @param string $text Textile input
+ * @return string Processed input
+ */
+
+ protected function placeNoteLists($text)
+ {
+ extract($this->regex_snippets);
+
+ // Sequence all referenced definitions...
+ if (!empty($this->notes)) {
+ $o = array();
+ foreach ($this->notes as $label => $info) {
+ if (!empty($info['seq'])) {
+ $o[$info['seq']] = $info;
+ $info['seq'] = $label;
+ } else {
+ $this->unreferencedNotes[] = $info; // Unreferenced definitions go here for possible future use.
+ }
+ }
+
+ if (!empty($o)) {
+ ksort($o);
+ }
+
+ $this->notes = $o;
+ }
+
+ // Replace list markers.
+ $text = preg_replace_callback(
+ "@notelist(?P{$this->c})".
+ "(?:\:(?P[$wrd|{$this->syms}]))?".
+ "(?P[\^!]?)(?P\+?)\.?$space*
@U$mod",
+ array(&$this, "fNoteLists"),
+ $text
+ );
+
+ return $text;
+ }
+
+ /**
+ * Formats a note list.
+ *
+ * @param array $m Options
+ * @return string Processed note list
+ */
+
+ protected function fNoteLists($m)
+ {
+ if (!$m['startchar']) {
+ $m['startchar'] = 'a';
+ }
+
+ $index = $m['links'].$m['extras'].$m['startchar'];
+
+ if (empty($this->notelist_cache[$index])) {
+ // If not in cache, build the entry...
+ $out = array();
+
+ if (!empty($this->notes)) {
+ foreach ($this->notes as $seq => $info) {
+ $links = $this->makeBackrefLink($info, $m['links'], $m['startchar']);
+ $atts = '';
+ if (!empty($info['def'])) {
+ $id = $info['id'];
+ extract($info['def']);
+ $out[] = "\t".''.$links.' '.$content.' ';
+ } else {
+ $out[] = "\t".''.$links.' Undefined Note [#'.$info['seq'].']. ';
+ }
+ }
+ }
+
+ if ('+' == $m['extras'] && !empty($this->unreferencedNotes)) {
+ foreach ($this->unreferencedNotes as $seq => $info) {
+ if (!empty($info['def'])) {
+ extract($info['def']);
+ $out[] = "\t".''.$content.' ';
+ }
+ }
+ }
+
+ $this->notelist_cache[$index] = join("\n", $out);
+ }
+
+ if ($this->notelist_cache[$index]) {
+ $atts = $this->parseAttribs($m['atts']);
+ return "\n{$this->notelist_cache[$index]}\n ";
+ }
+
+ return '';
+ }
+
+ /**
+ * Renders a note back reference link.
+ *
+ * This method renders an array of back reference
+ * links for notes.
+ *
+ * @param array $info Options
+ * @param string $g_links Reference type
+ * @param int $i Instance count
+ * @return string Processed input
+ */
+
+ protected function makeBackrefLink(&$info, $g_links, $i)
+ {
+ $link = '';
+ $atts = '';
+ $content = '';
+ $id = '';
+
+ if (!empty($info['def'])) {
+ extract($info['def']);
+ }
+
+ $backlink_type = ($link) ? $link : $g_links;
+ $allow_inc = (false === strpos($this->syms, $i));
+
+ $i_ = str_replace(array('&', ';', '#'), '', $this->encodeHigh($i));
+ $decode = (strlen($i) !== strlen($i_));
+
+ if ($backlink_type === '!') {
+ return '';
+ } elseif ($backlink_type === '^') {
+ return ''.$i.' ';
+ } else {
+ $out = array();
+
+ foreach ($info['refids'] as $id) {
+ $out[] = ''. (($decode) ? $this->decodeHigh($i_) : $i_) .' ';
+ if ($allow_inc) {
+ $i_++;
+ }
+ }
+
+ return join(' ', $out);
+ }
+ }
+
+ /**
+ * Formats note definitions.
+ *
+ * This method formats notes and stores them in
+ * note cache for later use and to build reference
+ * links.
+ *
+ * @param array $m Options
+ * @return string Empty string
+ */
+
+ protected function fParseNoteDefs($m)
+ {
+ $label = $m['label'];
+ $link = $m['link'];
+ $att = $m['att'];
+ $content = $m['content'];
+
+ // Assign an id if the note reference parse hasn't found the label yet.
+ if (empty($this->notes[$label]['id'])) {
+ $this->notes[$label]['id'] = $this->linkPrefix . ($this->linkIndex++);
+ }
+
+ // Ignores subsequent defs using the same label
+ if (empty($this->notes[$label]['def'])) {
+ $this->notes[$label]['def'] = array(
+ 'atts' => $this->parseAttribs($att),
+ 'content' => $this->graf($content),
+ 'link' => $link,
+ );
+ }
+ return '';
+ }
+
+ /**
+ * Parses note references in the given input.
+ *
+ * This method replaces note reference tags with
+ * links.
+ *
+ * @param string $text Textile input
+ * @return string
+ */
+
+ protected function noteRefs($text)
+ {
+ $text = preg_replace_callback(
+ "/\[(?P{$this->c})\#(?P[^\]!]+?)(?P[!]?)\]/Ux",
+ array(&$this, "fParseNoteRefs"),
+ $text
+ );
+ return $text;
+ }
+
+ /**
+ * Formats note reference links.
+ *
+ * By the time this function is called, all note lists will have been
+ * processed into the notes array, and we can resolve the link numbers in
+ * the order we process the references.
+ *
+ * @param array $m Options
+ * @return string Note reference
+ */
+
+ protected function fParseNoteRefs($m)
+ {
+ $atts = $this->parseAttribs($m['atts']);
+ $nolink = ($m['nolink'] === '!');
+
+ // Assign a sequence number to this reference if there isn't one already.
+
+ if (empty($this->notes[$m['label']]['seq'])) {
+ $num = $this->notes[$m['label']]['seq'] = ($this->note_index++);
+ } else {
+ $num = $this->notes[$m['label']]['seq'];
+ }
+
+ // Make our anchor point & stash it for possible use in backlinks when the
+ // note list is generated later.
+ $refid = $this->linkPrefix . ($this->linkIndex++);
+ $this->notes[$m['label']]['refids'][] = $refid;
+
+ // If we are referencing a note that hasn't had the definition parsed yet, then assign it an ID.
+
+ if (empty($this->notes[$m['label']]['id'])) {
+ $id = $this->notes[$m['label']]['id'] = $this->linkPrefix . ($this->linkIndex++);
+ } else {
+ $id = $this->notes[$m['label']]['id'];
+ }
+
+ // Build the link (if any).
+ $out = ''.$num.' ';
+
+ if (!$nolink) {
+ $out = ''.$out.' ';
+ }
+
+ // Build the reference.
+ return $this->replaceMarkers($this->symbols['nl_ref_pattern'], array('atts' => $atts, 'marker' => $out));
+ }
+
+ /**
+ * Parses URI into component parts.
+ *
+ * This method splits a URI-like string apart into component parts, while
+ * also providing validation.
+ *
+ * @param string $uri The string to pick apart (if possible)
+ * @param array $m Reference to an array the URI component parts are assigned to
+ * @return bool TRUE if the string validates as a URI
+ * @link http://tools.ietf.org/html/rfc3986#appendix-B
+ */
+
+ protected function parseURI($uri, &$m)
+ {
+ $r = "@^((?P[^:/?#]+):)?".
+ "(//(?P[^/?#]*))?".
+ "(?P[^?#]*)".
+ "(\?(?P[^#]*))?".
+ "(#(?P.*))?@";
+
+ $ok = preg_match($r, $uri, $m);
+ return $ok;
+ }
+
+ /**
+ * Checks whether a component part can be added to a URI.
+ *
+ * @param array $mask An array of allowed component parts
+ * @param string $name The component to add
+ * @param array $parts An array of existing components to modify
+ * @return bool TRUE if the component can be added
+ */
+
+ protected function addPart(&$mask, $name, &$parts)
+ {
+ return (in_array($name, $mask) && isset($parts[$name]) && '' !== $parts[$name]);
+ }
+
+ /**
+ * Rebuild a URI from parsed parts and a mask.
+ *
+ * @param array $parts Full array of URI parts
+ * @param string $mask Comma separated list of URI parts to include in the rebuilt URI
+ * @param bool $encode Flag to control encoding of the path part of the rebuilt URI
+ * @return string The rebuilt URI
+ * @link http://tools.ietf.org/html/rfc3986#section-5.3
+ */
+
+ protected function rebuildURI($parts, $mask = 'scheme,authority,path,query,fragment', $encode = true)
+ {
+ $mask = explode(',', $mask);
+ $out = '';
+
+ if ($this->addPart($mask, 'scheme', $parts)) {
+ $out .= $parts['scheme'] . ':';
+ }
+
+ if ($this->addPart($mask, 'authority', $parts)) {
+ $out .= '//' . $parts['authority'];
+ }
+
+ if ($this->addPart($mask, 'path', $parts)) {
+ if (!$encode) {
+ $out .= $parts['path'];
+ } else {
+ $pp = explode('/', $parts['path']);
+ foreach ($pp as &$p) {
+ $p = str_replace(array('%25', '%40'), array('%', '@'), rawurlencode($p));
+ if (!in_array($parts['scheme'], array('tel','mailto'))) {
+ $p = str_replace('%2B', '+', $p);
+ }
+ }
+
+ $pp = implode('/', $pp);
+ $out .= $pp;
+ }
+ }
+
+ if ($this->addPart($mask, 'query', $parts)) {
+ $out .= '?' . $parts['query'];
+ }
+
+ if ($this->addPart($mask, 'fragment', $parts)) {
+ $out .= '#' . $parts['fragment'];
+ }
+
+ return $out;
+ }
+
+ /**
+ * Parses and shelves links in the given input.
+ *
+ * This method parses the input Textile document for links.
+ * Formats and encodes them, and stores the created link
+ * elements in cache.
+ *
+ * @param string $text Textile input
+ * @return string The input document with link pulled out and replaced with tokens
+ */
+
+ protected function links($text)
+ {
+ $text = $this->markStartOfLinks($text);
+ return $this->replaceLinks($text);
+ }
+
+ /**
+ * Finds and marks the start of well formed links in the input text.
+ *
+ * @param string $text String to search for link starting positions
+ * @return string Text with links marked
+ * @see Parser::links()
+ */
+
+ protected function markStartOfLinks($text)
+ {
+ // Slice text on '":' boundaries. These always occur in inline
+ // links between the link text and the url part and are much more
+ // infrequent than '"' characters so we have less possible links
+ // to process.
+ $mod = $this->regex_snippets['mod'];
+ $slices = preg_split('/":(?='.$this->regex_snippets['char'].')/'.$mod, $text);
+
+ if (count($slices) > 1) {
+
+ // There are never any start of links in the last slice, so pop it
+ // off (we'll glue it back later).
+ $last_slice = array_pop($slices);
+
+ foreach ($slices as &$slice) {
+
+ // If there is no possible start quote then this slice is not a link
+ if (false === strpos($slice, '"')) {
+ continue;
+ }
+
+ // Cut this slice into possible starting points wherever we
+ // find a '"' character. Any of these parts could represent
+ // the start of the link text - we have to find which one.
+ $possible_start_quotes = explode('"', $slice);
+
+ // Start our search for the start of the link with the closest prior
+ // quote mark.
+ $possibility = rtrim(array_pop($possible_start_quotes));
+
+ // Init the balanced count. If this is still zero at the end
+ // of our do loop we'll mark the " that caused it to balance
+ // as the start of the link and move on to the next slice.
+ $balanced = 0;
+ $linkparts = array();
+ $iter = 0;
+
+ while (null !== $possibility) {
+ // Starting at the end, pop off the previous part of the
+ // slice's fragments.
+
+ // Add this part to those parts that make up the link text.
+ $linkparts[] = $possibility;
+ $len = strlen($possibility) > 0;
+
+ if ($len) {
+ // did this part inc or dec the balanced count?
+ if (preg_match('/^\S|=$/'.$mod, $possibility)) {
+ $balanced--;
+ }
+
+ if (preg_match('/\S$/'.$mod, $possibility)) {
+ $balanced++;
+ }
+
+ $possibility = array_pop($possible_start_quotes);
+ } else {
+ // If quotes occur next to each other, we get zero length strings.
+ // eg. ...""Open the door, HAL!"":url...
+ // In this case we count a zero length in the last position as a
+ // closing quote and others as opening quotes.
+ $balanced = (!$iter++) ? $balanced+1 : $balanced-1;
+
+ $possibility = array_pop($possible_start_quotes);
+
+ // If out of possible starting segments we back the last one
+ // from the linkparts array
+ if (null === $possibility) {
+ array_pop($linkparts);
+ break;
+ }
+
+ // If the next possibility is empty or ends in a space we have a
+ // closing ".
+ if (0 === strlen($possibility) ||
+ preg_match("~{$this->regex_snippets['space']}$~".$mod, $possibility)) {
+ $balanced = 0; // force search exit
+ }
+ }
+
+ if ($balanced <= 0) {
+ array_push($possible_start_quotes, $possibility);
+ break;
+ }
+
+ }
+
+ // Rebuild the link's text by reversing the parts and sticking them back
+ // together with quotes.
+ $link_content = implode('"', array_reverse($linkparts));
+
+ // Rebuild the remaining stuff that goes before the link but that's
+ // already in order.
+ $pre_link = implode('"', $possible_start_quotes);
+
+ // Re-assemble the link starts with a specific marker for the next regex.
+ $slice = $pre_link . $this->uid.'linkStartMarker:"' . $link_content;
+ }
+
+ // Add the last part back
+ $slices[] = $last_slice;
+ }
+
+ // Re-assemble the full text with the start and end markers
+ $text = implode('":', $slices);
+
+ return $text;
+ }
+
+ /**
+ * Replaces links with tokens and stores them on the shelf.
+ *
+ * @param string $text The input
+ * @return string Processed input
+ * @see Parser::links()
+ */
+
+ protected function replaceLinks($text)
+ {
+ $stopchars = "\s|^'\"*";
+
+ return preg_replace_callback(
+ '/
+ (?P\[)? # Optionally open with a square bracket eg. Look ["here":url]
+ '.$this->uid.'linkStartMarker:" # marks start of the link
+ (?P.*?) # grab the content of the inner "..." part of the link, can be anything but
+ # do not worry about matching class, id, lang or title yet
+ ": # literal ": marks end of atts + text + title block
+ (?P[^'.$stopchars.']*) # url upto a stopchar
+ /x'.$this->regex_snippets['mod'],
+ array(&$this, "fLink"),
+ $text
+ );
+ }
+
+ /**
+ * Formats a link and stores it on the shelf.
+ *
+ * @param array $m Options
+ * @return string Reference token for the shelved content
+ * @see Parser::replaceLinks()
+ */
+
+ protected function fLink($m)
+ {
+ $in = $m[0];
+ $pre = $m['pre'];
+ $inner = $m['inner'];
+ $url = $m['urlx'];
+ $m = array();
+
+ // Treat empty inner part as an invalid link.
+ $trimmed = trim($inner);
+ if (empty($trimmed)) {
+ return $pre.'"'.$inner.'":'.$url;
+ }
+
+ // Split inner into $atts, $text and $title..
+ preg_match(
+ '/
+ ^
+ (?P' . $this->cls . ') # $atts (if any)
+ ' . $this->regex_snippets['space'] . '* # any optional spaces
+ (?P # $text is...
+ (!.+!) # an image
+ | # else...
+ .+? # link text
+ ) # end of $text
+ (?:\((?P[^)]+?)\))? # $title (if any)
+ $
+ /x'.$this->regex_snippets['mod'],
+ $inner,
+ $m
+ );
+ $atts = isset($m['atts']) ? $m['atts'] : '';
+ $text = isset($m['text']) ? trim($m['text']) : $inner;
+ $title = isset($m['title']) ? $m['title'] : '';
+ $m = array();
+
+ $pop = $tight = '';
+ $url_chars = array();
+ $counts = array(
+ '[' => null,
+ ']' => substr_count($url, ']'), # We need to know how many closing square brackets we have
+ '(' => null,
+ ')' => null,
+ );
+
+ // Look for footnotes or other square-bracket delimieted stuff at the end of the url...
+ // eg. "text":url][otherstuff... will have "[otherstuff" popped back out.
+ // "text":url?q[]=x][123] will have "[123]" popped off the back, the remaining closing square brackets
+ // will later be tested for balance
+ if ($counts[']']) {
+ if (1 === preg_match('@(?P^.*\])(?P\[.*?)$@' . $this->regex_snippets['mod'], $url, $m)) {
+ $url = $m['url'];
+ $tight = $m['tight'];
+ $m = array();
+ }
+ }
+
+ // Split off any trailing text that isn't part of an array assignment.
+ // eg. "text":...?q[]=value1&q[]=value2 ... is ok
+ // "text":...?q[]=value1]following ... would have "following"
+ // popped back out and the remaining square bracket
+ // will later be tested for balance
+ if ($counts[']']) {
+ if (1 === preg_match('@(?P^.*\])(?!=)(?P.*?)$@' . $this->regex_snippets['mod'], $url, $m)) {
+ $url = $m['url'];
+ $tight = $m['end'] . $tight;
+ $m = array();
+ }
+ }
+
+ // Does this need to be mb_ enabled? We are only searching for text in the ASCII charset anyway
+ // Create an array of (possibly) multi-byte characters.
+ // This is going to allow us to pop off any non-matched or nonsense chars from the url
+ $len = strlen($url);
+ $url_chars = str_split($url);
+
+ // Now we have the array of all the multi-byte chars in the url we will parse the
+ // uri backwards and pop off
+ // any chars that don't belong there (like . or , or unmatched brackets of various kinds).
+ $first = true;
+ do {
+ $c = array_pop($url_chars);
+ $popped = false;
+ switch ($c) {
+
+ // Textile URL shouldn't end in these characters, we pop
+ // them off the end and push them out the back of the url again.
+ case '!':
+ case '?':
+ case ':':
+ case ';':
+ case '.':
+ case ',':
+ $pop = $c . $pop;
+ $popped = true;
+ break;
+
+ case '>':
+ $urlLeft = implode('', $url_chars);
+
+ if (preg_match('@(?P<\/[a-z]+)$@', $urlLeft, $m)) {
+ $url_chars = str_split(substr($urlLeft, 0, strlen($m['tag']) * -1));
+ $pop = $m['tag'] . $c . $pop;
+ $popped = true;
+ }
+
+ break;
+
+ case ']':
+ // If we find a closing square bracket we are going to see if it is balanced.
+ // If it is balanced with matching opening bracket then it is part of the URL
+ // else we spit it back out of the URL.
+ if (null === $counts['[']) {
+ $counts['['] = substr_count($url, '[');
+ }
+
+ if ($counts['['] === $counts[']']) {
+ // It is balanced, so keep it
+ $url_chars[] = $c;
+ } else {
+ // In the case of un-matched closing square brackets we just eat it
+ $popped = true;
+ $counts[']'] -= 1;
+ if ($first) {
+ $pre = '';
+ }
+ }
+ break;
+
+ case ')':
+ if (null === $counts[')']) {
+ $counts['('] = substr_count($url, '(');
+ $counts[')'] = substr_count($url, ')');
+ }
+
+ if ($counts['('] === $counts[')']) {
+ // It is balanced, so keep it
+ $url_chars[] = $c;
+ } else {
+ // Unbalanced so spit it out the back end
+ $pop = $c . $pop;
+ $counts[')'] -= 1;
+ $popped = true;
+ }
+ break;
+
+ default:
+ // We have an acceptable character for the end of the url so put it back and
+ // exit the character popping loop
+ $url_chars[] = $c;
+ break;
+ }
+ $first = false;
+ } while ($popped);
+
+ $url = implode('', $url_chars);
+ $uri_parts = array();
+ $this->parseURI($url, $uri_parts);
+
+ $scheme = $uri_parts['scheme'];
+ $scheme_in_list = in_array($scheme, $this->url_schemes);
+ $scheme_ok = ('' === $scheme) || $scheme_in_list;
+
+ if (!$scheme_ok) {
+ return str_replace($this->uid.'linkStartMarker:', '', $in);
+ }
+
+ if ('$' === $text) {
+ if ($scheme_in_list) {
+ $text = ltrim($this->rebuildURI($uri_parts, 'authority,path,query,fragment', false), '/');
+ } else {
+ if (isset($this->urlrefs[$url])) {
+ $url = urldecode($this->urlrefs[$url]);
+ }
+
+ $text = $url;
+ }
+ }
+
+ $text = trim($text);
+ $title = $this->encodeHTML($title);
+
+ if (!$this->noimage) {
+ $text = $this->images($text);
+ }
+
+ $text = $this->spans($text);
+ $text = $this->glyphs($text);
+ $url = $this->shelveURL($this->rebuildURI($uri_parts));
+ $a = $this->newTag(
+ 'a',
+ $this->parseAttribsToArray($atts),
+ false
+ )->title($title)->href($url, true)->rel($this->rel);
+ $tags = $this->storeTags((string) $a, '');
+ $out = $this->shelve($tags['open'].trim($text).$tags['close']);
+
+ return $pre . $out . $pop . $tight;
+ }
+
+ /**
+ * Finds URI aliases within the given input.
+ *
+ * This method finds URI aliases in the Textile input. Links are stored
+ * in an internal cache, so that they can be referenced from any link
+ * in the document.
+ *
+ * This operation happens before the actual link parsing takes place.
+ *
+ * @param string $text Textile input
+ * @return string The Textile document with any URI aliases removed
+ */
+
+ protected function getRefs($text)
+ {
+ $pattern = array();
+
+ foreach ($this->url_schemes as $scheme) {
+ $pattern[] = preg_quote($scheme.':', '/');
+ }
+
+ $pattern =
+ '/^\[(?P.+)\]'.
+ '(?P(?:'.join('|', $pattern).'|\/)\S+)'.
+ '(?='.$this->regex_snippets['space'].'|$)/Um';
+
+ return preg_replace_callback($pattern.$this->regex_snippets['mod'], array(&$this, "refs"), $text);
+ }
+
+ /**
+ * Parses, encodes and shelves the current URI alias.
+ *
+ * @param array $m Options
+ * @return string Empty string
+ * @see Parser::getRefs()
+ */
+
+ protected function refs($m)
+ {
+ $uri_parts = array();
+ $this->parseURI($m['url'], $uri_parts);
+ // Encodes URL if needed.
+ $this->urlrefs[$m['alias']] = ltrim($this->rebuildURI($uri_parts));
+ return '';
+ }
+
+ /**
+ * Shelves parsed URLs.
+ *
+ * Stores away a URL fragments that have been parsed
+ * and requires no more processing.
+ *
+ * @param string $text The URL
+ * @return string The fragment's unique reference ID
+ * @see Parser::retrieveURLs()
+ */
+
+ protected function shelveURL($text)
+ {
+ if ('' === $text) {
+ return '';
+ }
+
+ $this->refCache[$this->refIndex] = $text;
+ return $this->uid.($this->refIndex++).':url';
+ }
+
+ /**
+ * Replaces reference tokens with corresponding shelved URL.
+ *
+ * This method puts all shelved URLs back to the final,
+ * parsed input.
+ *
+ * @param string $text The input
+ * @return string Processed text
+ * @see Parser::shelveURL()
+ */
+
+ protected function retrieveURLs($text)
+ {
+ return preg_replace_callback('/'.$this->uid.'(?P[0-9]+):url/', array(&$this, 'retrieveURL'), $text);
+ }
+
+ /**
+ * Retrieves an URL from the shelve.
+ *
+ * @param array $m Options
+ * @return string The URL
+ */
+
+ protected function retrieveURL($m)
+ {
+ if (!isset($this->refCache[$m['token']])) {
+ return '';
+ }
+
+ $url = $this->refCache[$m['token']];
+ if (isset($this->urlrefs[$url])) {
+ $url = $this->urlrefs[$url];
+ }
+
+ return $this->rEncodeHTML($this->relURL($url));
+ }
+
+ /**
+ * Completes and formats a URL.
+ *
+ * @param string $url The URL
+ * @return string
+ */
+
+ protected function relURL($url)
+ {
+ $parts = @parse_url(urldecode($url));
+
+ if (empty($parts['scheme']) || $parts['scheme'] == 'http') {
+ if (empty($parts['host']) && (isset($parts['path']) && preg_match('/^\w/', $parts['path']))) {
+ $url = $this->relativeImagePrefix.$url;
+ }
+ }
+
+ return $url;
+ }
+
+ /**
+ * Checks if an URL is relative.
+ *
+ * The given URL is considered relative if it doesn't
+ * contain scheme and hostname.
+ *
+ * @param string $url The URL
+ * @return bool TRUE if relative, FALSE otherwise
+ */
+
+ protected function isRelURL($url)
+ {
+ $parts = @parse_url($url);
+ return (empty($parts['scheme']) && empty($parts['host']));
+ }
+
+ /**
+ * Parses and shelves images in the given input.
+ *
+ * This method parses the input Textile document for images and
+ * generates img HTML tags for each one found, caching the
+ * generated img tag internally and replacing the Textile image with a
+ * token to the cached tag.
+ *
+ * @param string $text Textile input
+ * @return string The input document with images pulled out and replaced with tokens
+ */
+
+ protected function images($text)
+ {
+ return preg_replace_callback(
+ '/
+ (?:[[{])? # pre
+ \! # opening !
+ (?P\<|\=|\>|<|>)? # optional alignment $algn
+ (?P'.$this->cls.') # optional style,class atts $atts
+ (?:\.\s)? # optional dot-space
+ (?P[^\s(!]+) # presume this is the src $url
+ \s? # optional space
+ (?:\((?P[^\)]+)\))? # optional title $title
+ \! # closing
+ (?::(?P\S+)(?regex_snippets['mod'],
+ array(&$this, "fImage"),
+ $text
+ );
+ }
+
+ /**
+ * Formats an image and stores it on the shelf.
+ *
+ * @param array $m Options
+ * @return string Reference token for the shelved content
+ * @see Parser::images()
+ */
+
+ protected function fImage($m)
+ {
+ $extras = '';
+
+ $align = (isset($m['align'])) ? $m['align'] : '';
+ $atts = $m['atts'];
+ $url = $m['url'];
+ $title = (isset($m['title'])) ? $m['title'] : '';
+ $href = (isset($m['href'])) ? $m['href'] : '';
+
+ $alignments = array(
+ '<' => 'left',
+ '=' => 'center',
+ '>' => 'right',
+ '<' => 'left',
+ '>' => 'right',
+ );
+
+ if (isset($alignments[$align])) {
+ if ('html5' === $this->doctype) {
+ $extras = 'align-'.$alignments[$align];
+ $align = '';
+ } else {
+ $align = $alignments[$align];
+ }
+ } else {
+ $align = '';
+ }
+
+ if ($title) {
+ $title = $this->encodeHTML($title);
+ }
+
+ $img = $this->newTag('img', $this->parseAttribsToArray($atts, '', 1, $extras))
+ ->align($align)
+ ->alt($title, true)
+ ->src($this->shelveURL($url), true)
+ ->title($title);
+
+ if (!$this->dimensionless_images && $this->isRelUrl($url)) {
+ $real_location = realpath($this->doc_root.ltrim($url, $this->ds));
+
+ if ($real_location) {
+ if ($size = getimagesize($real_location)) {
+ $img->height($size[1])->width($size[0]);
+ }
+ }
+ }
+
+ $out = (string) $img;
+
+ if ($href) {
+ $href = $this->shelveURL($href);
+ $link = $this->newTag('a', array(), false)->href($href)->rel($this->rel);
+ $out = (string) $link . "$img";
+ }
+
+ return $this->shelve($out);
+ }
+
+ /**
+ * Parses code blocks in the given input.
+ *
+ * @param string $text The input
+ * @return string Processed text
+ */
+
+ protected function code($text)
+ {
+ $text = $this->doSpecial($text, '', '
', 'fCode');
+ $text = $this->doSpecial($text, '@', '@', 'fCode');
+ $text = $this->doSpecial($text, '', ' ', 'fPre');
+ return $text;
+ }
+
+ /**
+ * Formats inline code tags.
+ *
+ * @param array $m
+ * @return string
+ */
+
+ protected function fCode($m)
+ {
+ return $m['before'].$this->shelve(''.$this->rEncodeHTML($m['content']).'
');
+ }
+
+ /**
+ * Formats pre tags.
+ *
+ * @param array $m Options
+ * @return string
+ */
+
+ protected function fPre($m)
+ {
+ return $m['before'].''.$this->shelve($this->rEncodeHTML($m['content'])).' ';
+ }
+
+ /**
+ * Shelves parsed content.
+ *
+ * Stores away a fragment of the source text that have been parsed
+ * and requires no more processing.
+ *
+ * @param string $val The content
+ * @return string The fragment's unique reference ID
+ * @see Parser::retrieve()
+ */
+
+ protected function shelve($val)
+ {
+ $i = $this->uid.($this->refIndex++).':shelve';
+ $this->shelf[$i] = $val;
+ return $i;
+ }
+
+ /**
+ * Replaces reference tokens with corresponding shelved content.
+ *
+ * This method puts all shelved content back to the final,
+ * parsed input.
+ *
+ * @param string $text The input
+ * @return string Processed text
+ * @see Parser::shelve()
+ */
+
+ protected function retrieve($text)
+ {
+ if ($this->shelf) {
+ do {
+ $old = $text;
+ $text = str_replace(array_keys($this->shelf), $this->shelf, $text);
+ } while ($text != $old);
+ }
+
+ return $text;
+ }
+
+ /**
+ * Removes BOM and unifies line ending in the given input.
+ *
+ * @param string $text Input Textile
+ * @return string Cleaned version of the input
+ */
+
+ protected function cleanWhiteSpace($text)
+ {
+ // Removes byte order mark.
+ $out = preg_replace("/^\xEF\xBB\xBF|\x1A/", '', $text);
+ // Replaces CRLF and CR with single LF.
+ $out = preg_replace("/\r\n?/", "\n", $out);
+ // Removes leading tabs and spaces, if the line is otherwise empty.
+ $out = preg_replace("/^[ \t]*\n/m", "\n", $out);
+ // Removes leading and ending blank lines.
+ $out = trim($out, "\n");
+ return $out;
+ }
+
+ /**
+ * Removes any unique tokens from the input.
+ *
+ * @param string $text The input to clean
+ * @return string Cleaned input
+ * @since 3.5.5
+ */
+
+ protected function cleanUniqueTokens($text)
+ {
+ return str_replace($this->uid, '', $text);
+ }
+
+ /**
+ * Uses the specified callback method to format the content between end and start nodes.
+ *
+ * @param string $text The input to format
+ * @param string $start The start node to look for
+ * @param string $end The end node to look for
+ * @param string $method The callback method
+ * @return string Processed input
+ */
+
+ protected function doSpecial($text, $start, $end, $method)
+ {
+ return preg_replace_callback(
+ '/(?P^|\s|[|[({>])'.preg_quote($start, '/').'(?P.*?)'.preg_quote($end, '/').'/ms',
+ array(&$this, $method),
+ $text
+ );
+ }
+
+ /**
+ * Parses notextile tags in the given input.
+ *
+ * @param string $text The input
+ * @return string Processed input
+ */
+
+ protected function noTextile($text)
+ {
+ $text = $this->doSpecial($text, '', ' ', 'fTextile');
+ return $this->doSpecial($text, '==', '==', 'fTextile');
+ }
+
+ /**
+ * Format notextile blocks.
+ *
+ * @param array $m Options
+ * @return string
+ */
+
+ protected function fTextile($m)
+ {
+ return $m['before'].$this->shelve($m['content']);
+ }
+
+ /**
+ * Parses footnote reference links in the given input.
+ *
+ * This method replaces [n] instances with links.
+ *
+ * @param string $text The input
+ * @return string $text Processed input
+ * @see Parser::footnoteID()
+ */
+
+ protected function footnoteRefs($text)
+ {
+ return preg_replace_callback(
+ '/(?<=\S)\[(?P'.$this->regex_snippets['digit'].'+)'.
+ '(?P!?)\]'.$this->regex_snippets['space'].'?/U'.$this->regex_snippets['mod'],
+ array(&$this, 'footnoteID'),
+ $text
+ );
+ }
+
+ /**
+ * Renders a footnote reference link or ID.
+ *
+ * @param array $m Options
+ * @return string Footnote link, or ID
+ */
+
+ protected function footnoteID($m)
+ {
+ $backref = ' class="footnote"';
+
+ if (empty($this->fn[$m['id']])) {
+ $this->fn[$m['id']] = $id = $this->linkPrefix . ($this->linkIndex++);
+ $backref .= " id=\"fnrev$id\"";
+ }
+
+ $fnid = $this->fn[$m['id']];
+ $footref = ('!' == $m['nolink']) ? $m['id'] : ''.$m['id'].' ';
+ $footref = $this->formatFootnote($footref, $backref, false);
+
+ return $footref;
+ }
+
+ /**
+ * Parses and shelves quoted quotes in the given input.
+ *
+ * @param string $text The text to search for quoted quotes
+ * @return string
+ */
+
+ protected function glyphQuotedQuote($text, $find = '"?|"[^"]+"')
+ {
+ return preg_replace_callback(
+ "/ (?P{$this->quote_starts})(?P$find)(?P.) /".$this->regex_snippets['mod'],
+ array(&$this, "fGlyphQuotedQuote"),
+ $text
+ );
+ }
+
+ /**
+ * Formats quoted quotes and stores it on the shelf.
+ *
+ * @param array $m Named regular expression parts
+ * @return string Input with quoted quotes removed and replaced with tokens
+ * @see Parser::glyphQuotedQuote()
+ */
+
+ protected function fGlyphQuotedQuote($m)
+ {
+ // Check the correct closing character was found.
+ if (!isset($this->quotes[$m['pre']]) || $m['post'] !== $this->quotes[$m['pre']]) {
+ return $m[0];
+ }
+
+ $pre = strtr($m['pre'], array(
+ '"' => '“',
+ "'" => '‘',
+ ' ' => ' ',
+ ));
+
+ $post = strtr($m['post'], array(
+ '"' => '”',
+ "'" => '’',
+ ' ' => ' ',
+ ));
+
+ $found = $m['quoted'];
+ if (strlen($found) > 1) {
+ $found = rtrim($this->glyphs($m['quoted']));
+ } elseif ('"' === $found) {
+ $found = """;
+ }
+
+ $glyph = ' '.$pre.$found.$post.' ';
+ return $this->shelve($glyph);
+ }
+
+ /**
+ * Replaces glyphs in the given input.
+ *
+ * This method performs typographical glyph replacements. The input is split
+ * across HTML-like tags in order to avoid attempting glyph
+ * replacements within tags.
+ *
+ * @param string $text Input Textile
+ * @return string
+ */
+
+ protected function glyphs($text)
+ {
+ // Fix: hackish -- adds a space if final char of text is a double quote.
+ $text = preg_replace('/"\z/', "\" ", $text);
+
+ $text = preg_split("@(<[\w/!?].*>)@Us".$this->regex_snippets['mod'], $text, -1, PREG_SPLIT_DELIM_CAPTURE);
+ $i = 0;
+ foreach ($text as $line) {
+ // Text tag text tag text ...
+ if (++$i % 2) {
+ // Raw < > & chars are already entity encoded in restricted mode
+ if (!$this->restricted) {
+ $line = preg_replace('/&(?!#?[a-z0-9]+;)/i', '&', $line);
+ $line = str_replace(array('<', '>'), array('<', '>'), $line);
+ }
+ $line = preg_replace($this->glyph_search, $this->glyph_replace, $line);
+ }
+ $glyph_out[] = $line;
+ }
+ return join('', $glyph_out);
+ }
+
+ /**
+ * Replaces glyph references in the given input.
+ *
+ * This method removes temporary glyph: instances
+ * from the input.
+ *
+ * @param string $text The input
+ * @return string Processed input
+ */
+
+ protected function replaceGlyphs($text)
+ {
+ return str_replace($this->uid.':glyph:', '', $text);
+ }
+
+ /**
+ * Translates alignment tag into corresponding CSS text-align property value.
+ *
+ * @param string $in The Textile alignment tag
+ * @return string CSS text-align value
+ */
+
+ protected function hAlign($in)
+ {
+ $vals = array(
+ '<' => 'left',
+ '>' => 'right',
+ '<>' => 'justify',
+ '<' => 'left',
+ '=' => 'center',
+ '>' => 'right',
+ '<>' => 'justify',
+ );
+
+ return (isset($vals[$in])) ? $vals[$in] : '';
+ }
+
+ /**
+ * Translates vertical alignment tag into corresponding CSS vertical-align property value.
+ *
+ * @param string $in The Textile alignment tag
+ * @return string CSS vertical-align value
+ */
+
+ protected function vAlign($in)
+ {
+ $vals = array(
+ '^' => 'top',
+ '-' => 'middle',
+ '~' => 'bottom',
+ );
+
+ return (isset($vals[$in])) ? $vals[$in] : '';
+ }
+
+ /**
+ * Converts character codes in the given input from HTML numeric character reference to character code.
+ *
+ * Conversion is done according to Textile's multi-byte conversion map.
+ *
+ * @param string $text The input
+ * @param string $charset The character set
+ * @return string Processed input
+ */
+
+ protected function encodeHigh($text, $charset = 'UTF-8')
+ {
+ if ($this->isMultiByteStringSupported()) {
+ return mb_encode_numericentity($text, $this->cmap, $charset);
+ }
+
+ return htmlentities($text, ENT_NOQUOTES, $charset);
+ }
+
+ /**
+ * Converts numeric HTML character references to character code.
+ *
+ * @param string $text The input
+ * @param string $charset The character set
+ * @return string Processed input
+ */
+
+ protected function decodeHigh($text, $charset = 'UTF-8')
+ {
+ $text = (string) intval($text) === (string) $text ? "$text;" : "&$text;";
+
+ if ($this->isMultiByteStringSupported()) {
+ return mb_decode_numericentity($text, $this->cmap, $charset);
+ }
+
+ return html_entity_decode($text, ENT_NOQUOTES, $charset);
+ }
+
+ /**
+ * Convert special characters to HTML entities.
+ *
+ * This method's functionality is identical to PHP's own
+ * htmlspecialchars(). In Textile this is used for sanitising
+ * the input.
+ *
+ * @param string $str The string to encode
+ * @param bool $quotes Encode quotes
+ * @return string Encoded string
+ * @see htmlspecialchars()
+ */
+
+ protected function encodeHTML($str, $quotes = true)
+ {
+ $a = array(
+ '&' => '&',
+ '<' => '<',
+ '>' => '>',
+ );
+
+ if ($quotes) {
+ $a = $a + array(
+ "'" => ''', // Numeric, as in htmlspecialchars
+ '"' => '"',
+ );
+ }
+
+ return str_replace(array_keys($a), $a, $str);
+ }
+
+ /**
+ * Convert special characters to HTML entities.
+ *
+ * This is identical to encodeHTML(), but takes restricted
+ * mode into account. When in restricted mode, only escapes
+ * quotes.
+ *
+ * @param string $str The string to encode
+ * @param bool $quotes Encode quotes
+ * @return string Encoded string
+ * @see Parser::encodeHTML()
+ */
+
+ protected function rEncodeHTML($str, $quotes = true)
+ {
+ // In restricted mode, all input but quotes has already been escaped
+ if ($this->restricted) {
+ return str_replace('"', '"', $str);
+ }
+
+ return $this->encodeHTML($str, $quotes);
+ }
+
+ /**
+ * Whether multiple mbstring extensions is loaded.
+ *
+ * @return bool
+ * @since 3.5.5
+ */
+
+ protected function isMultiByteStringSupported()
+ {
+ if ($this->mb === null) {
+ $this->mb = is_callable('mb_strlen');
+ }
+
+ return $this->mb;
+ }
+
+ /**
+ * Whether PCRE supports UTF-8.
+ *
+ * @return bool
+ * @since 3.5.5
+ */
+
+ protected function isUnicodePcreSupported()
+ {
+ return (bool) @preg_match('/\pL/u', 'a');
+ }
+}
diff --git a/app/lib/Netcarver/Textile/Tag.php b/app/lib/Netcarver/Textile/Tag.php
new file mode 100644
index 00000000..3a35f88d
--- /dev/null
+++ b/app/lib/Netcarver/Textile/Tag.php
@@ -0,0 +1,118 @@
+
+ * use Netcarver\Textile\Tag;
+ * $img = new Tag('img');
+ * echo (string) $img->class('big blue')->src('images/elephant.jpg');
+ *
+ */
+
+class Tag extends DataBag
+{
+ /**
+ * The name of the tag.
+ *
+ * @var string
+ */
+
+ protected $tag;
+
+ /**
+ * Whether the tag is self-closing.
+ *
+ * @var bool
+ */
+
+ protected $selfclose;
+
+ /**
+ * Constructor.
+ *
+ * @param string $name The tag name
+ * @param array $attributes An array of attributes
+ * @param bool $selfclosing Whether the tag is self-closing
+ */
+
+ public function __construct($name, array $attributes = null, $selfclosing = true)
+ {
+ parent::__construct($attributes);
+ $this->tag = $name;
+ $this->selfclose = $selfclosing;
+ }
+
+ /**
+ * Returns the tag as HTML.
+ *
+ *
+ * $img = new Tag('img');
+ * $img->src('images/example.jpg')->alt('Example image');
+ * echo (string) $img;
+ *
+ *
+ * @return string A HTML element
+ */
+
+ public function __toString()
+ {
+ $attributes = '';
+
+ if ($this->data) {
+ ksort($this->data);
+ foreach ($this->data as $name => $value) {
+ $attributes .= " $name=\"$value\"";
+ }
+ }
+
+ if ($this->tag) {
+ return '<' . $this->tag . $attributes . (($this->selfclose) ? " />" : '>');
+ }
+
+ return $attributes;
+ }
+}
diff --git a/app/lib/Textile.inc b/app/lib/Textile.inc
new file mode 100644
index 00000000..b2033048
--- /dev/null
+++ b/app/lib/Textile.inc
@@ -0,0 +1,36 @@
+
+ * @copyright 2014 Heinrich-Heine-Universität Düsseldorf
+ * @license http://www.gnu.org/licenses/gpl.html
+ * @link https://bitbucket.org/coderkun/the-legend-of-z
+ */
+
+ namespace hhu\z\lib;
+
+
+ /**
+ * Class to ensure that Compatibility library below is loaded.
+ *
+ * @author Oliver Hanraths
+ */
+ class Textile
+ {
+
+ /**
+ * Call this function to load necessary files.
+ */
+ public static function load()
+ {
+ $path = implode(DS, array('Netcarver', 'Textile'));
+ require_once($path.DS.'DataBag.php');
+ require_once($path.DS.'Parser.php');
+ require_once($path.DS.'Tag.php');
+ }
+
+ }
+
+?>