/**
  * If there's an EasyRecipe in the content, load the HTML and pre-process, else just return
  *
  * @param      $content
  * @param bool $load
  */
 public function __construct($content, $load = true)
 {
     /**
      * If there's no EasyRecipe, just return
      */
     if (!@preg_match(self::regexEasyRecipe, $content)) {
         return;
     }
     /**
      * Load the html - make sure we could parse it
      */
     parent::__construct($content, $load);
     if (!$this->isValid()) {
         return;
     }
     /**
      * Find the easyrecipe(s)
      */
     $this->easyrecipes = $this->getElementsByClassName('easyrecipe');
     /**
      * Sanity check - make sure we could actually find at least one
      */
     if (count($this->easyrecipes) == 0) {
         // echo "<!-- ER COUNT = 0 -->\n";
         return;
     }
     /**
      * This is a valid easyrecipe post
      * Find a version number - the version will be the same for every recipe in a multi recipe post so just get the first
      */
     $this->isEasyRecipe = true;
     /* @var $node DOMElement */
     $node = $this->getElementByClassName("endeasyrecipe", "div", $this->easyrecipes[0], false);
     $this->recipeVersion = $node->nodeValue;
     /*
      * See if this post has already been formatted.
      * Wordpress replaces the parent post_content with the autosave post content (as already formatted by us) on a preview.
      * so we need to know if this post has already been formatted. This is a pretty icky way of doing it since it relies
      * on the style template having a specific title attribute on the endeasyrecipe div - need to make this more robust
      */
     $this->isFormatted = $node !== null && $node->hasAttribute('title');
 }
 /**
  * Process the template text
  *
  * @param null $data
  * @param int $options
  * @return mixed|string
  */
 function replace($data = null, $options = 0)
 {
     /**
      * If we have replacements, pre-process the template and remove conditionally INCLUDEd stuff
      */
     if ($data === null) {
         $data = new stdClass();
     }
     $currentPosition = 0;
     $this->opText = '';
     $inText = $this->inText;
     $firstType = null;
     /**
      * We return from within this loop when we have nothing left to process
      */
     while (true) {
         /**
          * Look for stuff to replace and find the first of them
          */
         $firstPosition = strlen($inText);
         $varPosition = strpos($inText, $this->delimiter, $currentPosition);
         if ($varPosition !== false) {
             $firstPosition = $varPosition;
             $firstType = self::VARIABLEREPLACE;
         }
         $repeatPosition = strpos($inText, '<!-- START REPEAT ', $currentPosition);
         if ($repeatPosition !== false && $repeatPosition < $firstPosition) {
             $firstPosition = $repeatPosition;
             $firstType = self::REPEATREPLACE;
         }
         $includeifPosition = strpos($inText, '<!-- START INCLUDEIF ', $currentPosition);
         if ($includeifPosition !== false && $includeifPosition < $firstPosition) {
             $firstPosition = $includeifPosition;
             $firstType = self::INCLUDEIF;
         }
         $startStripPosition = strpos($inText, '<!-- START STRIP WHITESPACE ', $currentPosition);
         if ($startStripPosition !== false && $startStripPosition < $firstPosition) {
             $firstPosition = $startStripPosition;
             $firstType = self::STARTSTRIP;
         }
         $endStripPosition = strpos($inText, '<!-- END STRIP WHITESPACE ', $currentPosition);
         if ($endStripPosition !== false && $endStripPosition < $firstPosition) {
             $firstPosition = $endStripPosition;
             $firstType = self::ENDSTRIP;
         }
         /**
          * If there's nothing to do, just return what we've got
          */
         if ($firstPosition == strlen($inText)) {
             /**
              * Copy any remaining input over to the output
              */
             $this->opText .= substr($inText, $currentPosition);
             /*
              * If there's any block to whitespace strip, do it
              * Tidy up the start/stop positions first to make it easy to process
              * This allows overlapping and unterminated stip blocks
              */
             if (count($this->stripWhitespace) > 0) {
                 $stripBlocks = array();
                 $startPosition = -1;
                 foreach ($this->stripWhitespace as $position) {
                     /**
                      * End strip?
                      */
                     if ($position < 0) {
                         /**
                          * If there's no previous unterminated START STRIP, ignore this END
                          */
                         if ($startPosition == -1) {
                             continue;
                         }
                         $stripBlocks[] = array($startPosition, abs($position));
                         $startPosition = -1;
                     } else {
                         /**
                          * If there's a previous unterminated START STRIP, ignore this one
                          */
                         if ($startPosition != -1) {
                             continue;
                         }
                         $startPosition = $position;
                     }
                 }
                 /**
                  * Allow for a missing END STRIP
                  */
                 if ($startPosition != -1) {
                     $stripBlocks[] = array($startPosition, strlen($this->opText));
                 }
                 /**
                  * Actually strip out whitespace in the strip blocks
                  */
                 $currentPosition = 0;
                 $strippedText = '';
                 foreach ($stripBlocks as $stripBlock) {
                     $strippedText .= substr($this->opText, $currentPosition, $stripBlock[0] - $currentPosition);
                     $text = substr($this->opText, $stripBlock[0], $stripBlock[1] - $stripBlock[0]);
                     $text = preg_replace('/>\\s+</', '><', $text);
                     $strippedText .= trim($text);
                     $currentPosition = $stripBlock[1];
                 }
                 $strippedText .= substr($this->opText, $currentPosition, strlen($this->opText) - $currentPosition);
                 $this->opText = $strippedText;
             }
             /**
              * If we aren't translating, then just (optionally) clean up whitespace and return
              */
             if (!self::$translate || !class_exists('EasyRecipeDOMDocument')) {
                 return $this->cleanWhitespace($this->opText, $options);
             }
             $doc = new EasyRecipeDOMDocument($this->opText, true);
             if (!$doc) {
                 return $this->opText;
             }
             $xlates = $doc->getElementsByClassName('xlate');
             if (count($xlates) == 0) {
                 return $this->cleanWhitespace($this->opText, $options);
             }
             // FIXME - use gettext if no __
             foreach ($xlates as $xlate) {
                 $original = $doc->innerHTML($xlate);
                 $translation = __($original, self::$textDomain);
                 if ($translation != $original) {
                     $xlate->nodeValue = $translation;
                 }
             }
             $html = $doc->getHTML(true);
             return $this->cleanWhitespace($html, $options);
         }
         /**
          * Copy over everything up to the first thing we need to process
          */
         $length = $firstPosition - $currentPosition;
         $this->opText .= substr($inText, $currentPosition, $length);
         $currentPosition = $firstPosition;
         /**
          * Get the thing to be replaced
          */
         switch ($firstType) {
             /**
              * INCLUDEIF includes the code up to the matching END INCLUDEIF:
              *  IF the condition variable exists and it's not false or null
              */
             case self::INCLUDEIF:
                 /**
                  * Get the conditional.
                  * Only check a smallish substring for efficiency
                  * This limits include condition names to 20 characters
                  */
                 $subString = substr($inText, $currentPosition, 60);
                 if (preg_match('/<!-- START INCLUDEIF (!?)([_a-z][_0-9a-z]{0,31}) -->/i', $subString, $regs)) {
                     $negate = $regs[1];
                     $trueFalse = $negate != '!';
                     $includeCondition = $regs[2];
                 } else {
                     trigger_error("Malformed START INCLUDEIF at {$currentPosition} ({$subString})", E_USER_NOTICE);
                     $this->opText .= "<";
                     $currentPosition++;
                     continue;
                 }
                 $endInclude = "<!-- END INCLUDEIF {$negate}{$includeCondition} -->";
                 $endIncludeLength = strlen($endInclude);
                 $endPosition = strpos($inText, $endInclude);
                 if ($endPosition == false) {
                     trigger_error("'{$endInclude}' not found", E_USER_NOTICE);
                     $this->opText .= "<";
                     $currentPosition++;
                     break;
                 }
                 /**
                  * If the condition is met, just remove the INCLUDEIF comments
                  * If the condition isn't met, remove everything up to the END INCLUDEIF
                  * The condition must be present, and NOT false or NULL
                  */
                 $condition = isset($data->{$includeCondition}) && $data->{$includeCondition} !== false && $data->{$includeCondition} !== null;
                 if ($condition === $trueFalse) {
                     $startInclude = "<!-- START INCLUDEIF {$negate}{$includeCondition} -->";
                     $startIncludeLength = strlen($startInclude);
                     $inText = substr($inText, 0, $currentPosition) . substr($inText, $currentPosition + $startIncludeLength, $endPosition - $currentPosition - $startIncludeLength) . substr($inText, $endPosition + $endIncludeLength);
                 } else {
                     $inText = substr($inText, 0, $currentPosition) . substr($inText, $endPosition + $endIncludeLength);
                 }
                 break;
                 /**
                  * Remove whitespace between tags.
                  * Useful to remove unwanted significant HTML whitespace that may have been introduced by auto formatting the source template
                  */
             /**
              * Remove whitespace between tags.
              * Useful to remove unwanted significant HTML whitespace that may have been introduced by auto formatting the source template
              */
             case self::STARTSTRIP:
                 $currentPosition += 31;
                 $this->stripWhitespace[] = strlen($this->opText);
                 break;
                 /**
                  * Save the output position at which to stop stripping. -ve indicates that it's an end position
                  */
             /**
              * Save the output position at which to stop stripping. -ve indicates that it's an end position
              */
             case self::ENDSTRIP:
                 $currentPosition += 29;
                 $this->stripWhitespace[] = -strlen($this->opText);
                 break;
                 /**
                  * A variable is a valid PHP variable name (limited to 20 chars) between delimiters
                  * If we don't find a valid name, copy over the delimiter and continue
                  * FIXME - fall back to caller's vars if it doesn't exist
                  */
             /**
              * A variable is a valid PHP variable name (limited to 20 chars) between delimiters
              * If we don't find a valid name, copy over the delimiter and continue
              * FIXME - fall back to caller's vars if it doesn't exist
              */
             case self::VARIABLEREPLACE:
                 $s = substr($inText, $currentPosition, 34);
                 if (!preg_match("/^{$this->delimiter}([_a-z][_0-9a-z]{0,31}){$this->delimiter}/si", $s, $regs)) {
                     $this->opText .= $this->delimiter;
                     $currentPosition++;
                     continue;
                 }
                 /**
                  * If we don't have a match for the variable, just assume it's not what we wanted to do
                  * so put the string we matched back into the output and continue from the trailing delimiter
                  */
                 $varName = $regs[1];
                 if (!isset($data->{$varName})) {
                     $this->opText .= $this->delimiter . $varName;
                     $currentPosition += strlen($varName) + 1;
                     continue;
                 }
                 /**
                  * Got a match - replace the <delimiter>...<delimiter> with the vars stuff
                  * We *could* pass this on for recursive processing, but it's not something we would normally want to do
                  * Maybe have a special naming convention for vars we want to do recursively?
                  */
                 $this->opText .= $data->{$varName};
                 $currentPosition += strlen($varName) + 2;
                 break;
                 /**
                  * We've seen a start repeat.
                  * Find the name of the repeat (limited to 20 chars)
                  * If we can't find the name, assume it's not what we want and continue
                  *
                  * Look for a valid START REPEAT in the next 45 characters
                  */
             /**
              * We've seen a start repeat.
              * Find the name of the repeat (limited to 20 chars)
              * If we can't find the name, assume it's not what we want and continue
              *
              * Look for a valid START REPEAT in the next 45 characters
              */
             case self::REPEATREPLACE:
                 $s = substr($inText, $currentPosition, 45);
                 if (!preg_match('/<!-- START REPEAT ([_a-zA-Z][_0-9a-zA-Z]{0,19}) -->/m', $s, $regs)) {
                     $this->opText .= '<';
                     $currentPosition++;
                     continue;
                 }
                 $rptName = $regs[1];
                 /**
                  * Make sure we have a matching key and it's an array
                  */
                 if (!isset($data->{$rptName}) || !is_array($data->{$rptName})) {
                     $this->opText .= '<';
                     $currentPosition++;
                     continue;
                 }
                 /**
                  * Now try to find the end of this repeat
                  */
                 $currentPosition += strlen($rptName) + 22;
                 $rptEnd = strpos($inText, "<!-- END REPEAT {$rptName} -->", $currentPosition);
                 if ($rptEnd === false) {
                     $this->opText .= '<!-- START REPEAT $rptName -->';
                     trigger_error("END REPEAT not found for {$rptName}", E_USER_NOTICE);
                     continue;
                 }
                 /**
                  * Do the repeat processing.
                  * For each item in the repeated array, process as a new template
                  */
                 $rptLength = $rptEnd - $currentPosition;
                 $rptString = substr($inText, $currentPosition, $rptLength);
                 $rptVars = $data->{$rptName};
                 for ($i = 0; $i < count($rptVars); $i++) {
                     $saveTranslate = self::$translate;
                     self::$translate = false;
                     $rpt = new EasyRecipeTemplate($rptString, self::TEXT, $this->delimiter);
                     $this->opText .= $rpt->replace($rptVars[$i], $options);
                     self::$translate = $saveTranslate;
                 }
                 /**
                  * Step over the end repeat
                  */
                 $currentPosition += strlen($rptName) + $rptLength + 20;
                 break;
         }
     }
     return '';
 }
 /**
  * Remove non display stuff
  *
  * @param $content
  *
  * @return mixed
  */
 function filterExcerpt($content)
 {
     $dom = new EasyRecipeDOMDocument($content);
     $dom->removeElementsByClassName('ERSSavePrint', 'div');
     $dom->removeElementsByClassName('ERSRating', 'div');
     $dom->removeElementsByClassName('ERSRatings', 'div');
     $dom->removeElementsByClassName('ERSClear', 'div');
     $dom->removeElementsByClassName('endeasyrecipe', 'div');
     $dom->removeElementsByClassName('ERSLinkback', 'div');
     $content = $dom->getHTML(true);
     /**
      * Remove empty lines left over from the deletions
      */
     return preg_replace('/(\\r\\n|\\n)(?:\\r\\n|\\n)+/', '$1', $content);
 }