Replaces all %...% placeholders in a page's contents

@param string $markdown Markdown contents of a page
@param array  $meta     meta data to use for %meta.*% replacement

@return string substituted Markdown contents
182411
public function substituteFileContent($markdown, array $meta = array())
    {
        $variables = array();

        // replace %version%
        $variables['%version%'] = static::VERSION;

        // replace %site_title%
        $variables['%site_title%'] = $this->getConfig('site_title');

        // replace %base_url%
        if ($this->isUrlRewritingEnabled()) {
            // always use `%base_url%?sub/page` syntax for internal links
            // we'll replace the links accordingly, depending on enabled rewriting
            $variables['%base_url%?'] = $this->getBaseUrl();
        } else {
            // actually not necessary, but makes the URL look a little nicer
            $variables['%base_url%?'] = $this->getBaseUrl() . '?';
        }
        $variables['%base_url%'] = rtrim($this->getBaseUrl(), '/');

        // replace %theme_url%
        $variables['%theme_url%'] = $this->getBaseThemeUrl() . $this->getConfig('theme');

        // replace %meta.*%
        if ($meta) {
            foreach ($meta as $metaKey => $metaValue) {
                if (is_scalar($metaValue) || ($metaValue === null)) {
                    $variables['%meta.' . $metaKey . '%'] = (string) $metaValue;
                }
            }
        }

        return str_replace(array_keys($variables), $variables, $markdown);
    }
1033157
public function transform(StreamMetaData $metadata): string
    {
        $this->replaceMagicDirFileConstants($metadata);
        $this->wrapReflectionGetFileName($metadata);

        // We should always vote abstain, because if there is only changes for magic constants, we can drop them
        return self::RESULT_ABSTAIN;
    }
1139805
public void addMeta(char meta, String replacement) {
        metaCharacterSet.set(meta);
        replacementMap.put(new String(new char[] { meta }), replacement);
    }
1277049
function _renderMarkdown( htmlDoc ) {
    const replacements = {};

    // First turn all outputs into text so *<span class="or-output></span>* can be detected
    htmlDoc.find( '/root/form//span[contains(@class, "or-output")]' ).forEach( ( el, index ) => {
        const key = `---output-${index}`;
        const textNode = el.childNodes()[ 0 ].clone();
        replacements[ key ] = el.toString();
        textNode.text( key );
        el.replace( textNode );
        // Note that we end up in a situation where we likely have sibling text nodes...
    } );

    // Now render markdown
    htmlDoc.find( '/root/form//span[contains(@class, "question-label") or contains(@class, "or-hint")]' ).forEach( ( el, index ) => {
        let key;
        /**
         * Using text() is done because:
         * a) We are certain that these <span>s do not contain other elements, other than formatting/markdown <span>s.
         * b) This avoids the need to merge any sibling text nodes that could have been created in the previous step.
         *
         * Note that text() will convert &gt; to >
         */
        const original = el.text().replace( '<', '&lt;' ).replace( '>', '&gt;' );
        const rendered = markdown.toHtml( original );
        if ( original !== rendered ) {
            key = `$$$${index}`;
            replacements[ key ] = rendered;
            el.text( key );
        }
    } );

    // TODO: does this result in self-closing tags?
    let htmlStr = _docToString( htmlDoc );

    // Now replace the placeholders with the rendered HTML
    // in reverse order so outputs are done last
    Object.keys( replacements ).reverse().forEach( key => {
        const replacement = replacements[ key ];
        if ( replacement ) {
            htmlStr = htmlStr.replace( key, replacement );
        }
    } );

    return htmlStr;
}
1013534
protected function regexSearchReplace( $html ) {

		/* cherry pick likely content areas */
		// HTML5, ungreedy
		preg_match( '/(?:<main[^>]*>)(.*)<\/main>/isU', $html, $matches );
		$html = ( ! empty( $matches[1] ) ) ? $matches[1] : $html;

		// WP content area, greedy
		preg_match( '/(?:<div id="main"[^>]*>)(.*)<\/div>/is', $html, $matches );
		$html = ( ! empty( $matches[1] ) ) ? $matches[1] : $html;

		// general content area, greedy
		preg_match( '/(?:<div id="content"[^>]*>)(.*)<\/div>/is', $html, $matches );
		$html = ( ! empty( $matches[1] ) ) ? $matches[1] : $html;

		// specific PB content area, greedy
		preg_match( '/(?:<div class="entry-content"[^>]*>)(.*)<\/div>/is', $html, $matches );
		$html = ( ! empty( $matches[1] ) ) ? $matches[1] : $html;

		/* cull */
		// get rid of script tags, ungreedy
		$result = preg_replace( '/(?:<script[^>]*>)(.*)<\/script>/isU', '', $html );
		// get rid of forms, ungreedy
		$result = preg_replace( '/(?:<form[^>]*>)(.*)<\/form>/isU', '', $result );
		// get rid of html5 nav content, ungreedy
		$result = preg_replace( '/(?:<nav[^>]*>)(.*)<\/nav>/isU', '', $result );
		// get rid of PB nav, next/previous
		$result = preg_replace( '/(?:<div class="nav"[^>]*>)(.*)<\/div>/isU', '', $result );
		// get rid of PB share buttons
		$result = preg_replace( '/(?:<div class="share-wrap-single"[^>]*>)(.*)<\/div>/isU', '', $result );
		// get rid of html5 footer content, ungreedy
		$result = preg_replace( '/(?:<footer[^>]*>)(.*)<\/footer>/isU', '', $result );
		// get rid of sidebar content, greedy
		$result = preg_replace( '/(?:<div id="sidebar\d{0,}"[^>]*>)(.*)<\/div>/is', '', $result );
		// get rid of comments, greedy
		$result = preg_replace( '/(?:<div id="comments"[^>]*>)(.*)<\/div>/is', '', $result );

		return $result;
	}
1129752
public function populateMetaDataFromText(&$md, $removeMetaData = false)
    {
        if (!$md) {
            return;
        }

        // See if there is YAML metadata block at the top of the document. e.g.
        // ---
        // property: value
        // another: value
        // ---
        //
        // If we found one, then we'll use a YAML parser to extract the
        // data out and then remove the whole block from the markdown string.
        $parser = new \Mni\FrontYAML\Parser();
        $document = $parser->parse($md, false);
        $yaml = $document->getYAML();
        if ($yaml) {
            foreach ($yaml as $key => $value) {
                if (!property_exists(get_class($this), $key)) {
                    continue;
                }
                $this->$key = $value;
            }
            if ($removeMetaData) {
                $md = $document->getContent();
            }
            return;
        }

        // this is the alternative way of parsing the properties out that don't contain
        // a YAML block declared with ---
        //
        // get the text up to the first empty line
        $extPattern = "/^(.+)\n\r*\n/Uis";
        $matches = preg_match($extPattern, $md, $block);

        if ($matches && $block[1]) {
            $metaDataFound = false;

            // find the key/value pairs
            $lines = preg_split('/\v+/', $block[1]);
            $key = '';
            $value = '';
            foreach ($lines as $line) {
                if (strpos($line, ':') !== false) {
                    list($key, $value) = explode(':', $line, 2);
                    $key = trim($key);
                    $value = trim($value);
                } else {
                    $value .= ' ' . trim($line);
                }
                if (property_exists(get_class(), $key)) {
                    $this->$key = $value;
                    $metaDataFound = true;
                }
            }

            // optionally remove the metadata block (only on the page that
            // is displayed)
            if ($metaDataFound && $removeMetaData) {
                $md = preg_replace($extPattern, '', $md);
            }
        }
    }
612847
public function markdown(string $markdown, array $args = []): string
    {
        if (!$markdown) {
            return $markdown;
        } elseif (!$this->canMarkdown()) {
            return $markdown;
        } elseif (!$this->WPCom_Markdown) {
            return $markdown;
        }
        $default_args = ['unslash' => false];
        $args += $default_args; // Merge defaults.

        return (string) $this->WPCom_Markdown->transform($markdown, $args);
    }
994397
public static function newDocumentXHTML($markup = null, $charset = null) 
    {
        $contentType = $charset
        ? ";charset=$charset"
        : '';
        return self::newDocument($markup, "application/xhtml+xml{$contentType}");
    }
687940
protected function __getMetaString()
    {
        $strOutput = "";

        foreach ($this->__meta as $key => $value) {
            if (! in_array($key, array_merge($this->__reservedmeta, $this->__fieldmeta))) {
                $strOutput .= " {$key}=\"{$value}\"";
            }
        }

        return $strOutput;
    }
1067016
public function getTitle($markdown)
    {
        $html = "<xml>" . self::transform($markdown) . "</xml>";
        $dom = new \DOMDocument;
        $dom->loadXml($html);

        return $dom->getElementsByTagName('h1')->item(0)->nodeValue;
    }
37238
protected function stubReplace($placeholder, $replace, $pregReplace = false)
    {
        if ($pregReplace) {

            $this->data->output['content'] = preg_replace(
                $placeholder,
                $replace,
                $this->data->output['content']
            );

        } else {

            $this->data->output['content'] = str_replace(
                $placeholder,
                $replace,
                $this->data->output['content']
            );
        }

        return $this;
    }
197268
private function doReplacements($contents)
	{
		// Replace variables
		if (isset($this->replacements) && count($this->replacements) > 0)
		{
			foreach ($this->replacements as $k => $v)
			{
				if (is_array($v))
				{
					foreach ($v as $replacement)
					{
						foreach ($replacement as $key => $value)
						{
							$contents = preg_replace("/%={$key}=%/", $value, $contents, 1);
						}
					}
				}
				else
				{
					// See if there are any instances of our key with special qualifiers
					if (preg_match_all("/%={$k}\+([[:alpha:]]+)=%/", $contents, $matches) && isset($matches[1]))
					{
						// Remove complete match
						unset($matches[0]);
						foreach ($matches[1] as $match)
						{
							switch ($match)
							{
								// Upper case word
								case 'uc':
									$value = strtoupper($v);
									break;
								// Upper case first character
								case 'ucf':
									$value = ucfirst($v);
									break;
								// Upper case first character and plural
								case 'ucfp':
									$value = ucfirst(Inflector::pluralize($v));
									break;
								// Plural form
								case 'p':
									$value = Inflector::pluralize($v);
									break;
							}

							$contents = str_replace("%={$k}+{$match}=%", $value, $contents);
						}
					}

					// Now do all basic replacements
					$contents = str_replace("%={$k}=%", $v, $contents);

					if ($this->doBlindReplacements)
					{
						$contents = str_replace($k, $v, $contents);
					}
				}
			}
		}

		// Now parse for nested templates
		preg_match_all('/\$\^[[:alpha:]\.]*\^\$/', $contents, $matches, PREG_SET_ORDER);

		if (isset($matches) && count($matches) > 0)
		{
			foreach ($matches as $match)
			{
				foreach ($match as $key)
				{
					// Get template contents
					$keyReal = preg_replace('/\$\^([[:alpha:]\.]*)\^\$/', '$1', $key);
					$subPath = __DIR__ . DS . 'Scaffolding' . DS . 'Templates' . DS . $this->getType() . '.' . $keyReal . '.tmpl';

					if (!is_file($subPath))
					{
						continue;
					}

					$subTmpl = file_get_contents($subPath);

					if (isset($this->replacements[$keyReal]) && is_array($this->replacements[$keyReal]))
					{
						$count    = count($this->replacements[$keyReal]);
						$repeat   = str_repeat($key, $count);
						$contents = str_replace($key, $repeat, $contents);
					}

					$contents = str_replace($key, $subTmpl, $contents);
					$contents = $this->doReplacements($contents);
				}
			}
		}

		return $contents;
	}
441475
protected function metaKeyIsRegex($meta)
    {
        if (!isset($meta['meta_key'])) {
            return false;
        }

        return $this->pregMatchArray($meta['meta_key'], $this->regex);
    }
326685
public function addHtml($html, $meta = array())
    {
        $objString = new StaticText($html, $meta);
        $objString->setMeta("parent", $this, true);
        $this->__fields->addObject($objString);

        return $objString;
    }
1002734
public function replace($code, array $placeholders = [])
    {
        $code = Helper::toType($code);
        if (empty($code) || !is_string($code)) {
            return $code;
        }
        if (!empty($placeholders)) {
            $this->addMultiPlaceholders($placeholders);
        }
        /*
         * Remove tpl-comment
         * ```
         * {* Comment about *}
         * ```
         */
        $code = preg_replace('/\{\*.*?\*\}/is', "", $code);
        $code = preg_replace_callback(
            '/
                (?P<beforeSkip>\{\!\\s*)?\[\[
                (?P<sanitizeDisable>\!)?
                (?P<type>[\#\%\~\$]?|\+{1,2}|@{1,2})					# search type of variable template
                (?P<name>@?[\\w\-\/\\\.\$]+)							# name of variable template [\w, -, \, .]
                (?:[^\[\]]++ | \[(?!\[) | \](?!\]) | (?R))*		# possible recursion
                \]\](?P<afterSkip>\\s*\!\})?
            /iux',
            [$this, 'replaceCallback'],
            $code
        );
        return Helper::toType($code);
    }
493983
private function replaceMagicDirFileConstants(StreamMetaData $metadata): void
    {
        $magicConstFinder = new NodeFinderVisitor([Dir::class, File::class]);
        $traverser        = new NodeTraverser();
        $traverser->addVisitor($magicConstFinder);
        $traverser->traverse($metadata->syntaxTree);

        /** @var MagicConst[] $magicConstants */
        $magicConstants = $magicConstFinder->getFoundNodes();
        $magicFileValue = $metadata->uri;
        $magicDirValue  = dirname($magicFileValue);
        foreach ($magicConstants as $magicConstantNode) {
            $tokenPosition = $magicConstantNode->getAttribute('startTokenPos');
            $replacement   = $magicConstantNode instanceof Dir ? $magicDirValue : $magicFileValue;

            $metadata->tokenStream[$tokenPosition][1] = "'{$replacement}'";
        }
    }
463880
protected function charsetFixHTML($markup) 
    {
        $matches = array();
        // find meta tag
        preg_match(
            '@\s*<meta[^>]+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i',
            $markup, $matches, PREG_OFFSET_CAPTURE
        );
        if (! isset($matches[0])) {
            return;
        }
        $metaContentType = $matches[0][0];
        $markup = substr($markup, 0, $matches[0][1])
        .substr($markup, $matches[0][1]+strlen($metaContentType));
        $headStart = stripos($markup, '<head>');
        $markup = substr($markup, 0, $headStart+6).$metaContentType
        .substr($markup, $headStart+6);
        return $markup;
    }