<commit_before>/*
 * SlideRenderer.cpp
 *
 * Copyright (C) 2009-12 by RStudio, Inc.
 *
 * Unless you have received this program directly from RStudio pursuant
 * to the terms of a commercial license agreement with RStudio, then
 * this program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */


#include "SlideRenderer.hpp"

#include <boost/foreach.hpp>
#include <boost/format.hpp>
#include <boost/regex.hpp>
#include <boost/algorithm/string.hpp>

#include <core/Error.hpp>
#include <core/FilePath.hpp>
#include <core/StringUtils.hpp>
#include <core/json/Json.hpp>

#include <core/markdown/Markdown.hpp>

#include <session/SessionModuleContext.hpp>

#include "SlideParser.hpp"
#include "SlideMediaRenderer.hpp"
#include "SlideNavigationList.hpp"

using namespace core;

namespace session {
namespace modules { 
namespace presentation {

namespace {

std::string commandsAsJsonArray(const Slide& slide)
{
   json::Array commandsJsonArray;

   std::vector<Command> commands = slide.commands();
   BOOST_FOREACH(const Command& command, commands)
   {
      commandsJsonArray.push_back(command.asJson());
   }

   std::ostringstream ostr;
   json::write(commandsJsonArray, ostr);
   return ostr.str();
}

Error renderMarkdown(const std::string& content, std::string* pHTML)
{
   markdown::Extensions extensions;
   markdown::HTMLOptions htmlOptions;
   return markdown::markdownToHTML(content,
                                   extensions,
                                   htmlOptions,
                                   pHTML);
}


std::string divWrap(const std::string& classNames, const std::string& contents)
{
   boost::format fmt("\n<div class=\"%1%\">\n%2%\n</div>\n");
   return boost::str(fmt % classNames % contents);
}

std::string mediaClass(const std::string& html)
{
   boost::regex imageRegex(
     "\\s*<p>\\s*<img src=\"([^\"]+)\"(?: [\\w]+=\"[^\"]*\")*/>\\s*</p>\\s*");

   boost::smatch match;
   if (boost::regex_search(html, match, imageRegex))
   {
      std::string::const_iterator begin = match[0].first;
      std::string::const_iterator end = match[0].second;

      if (begin == html.begin() && end == html.end())
         return "mediaOnly";
      else
         return "mediaInline";
   }
   // look for videos
   else
   {
      // trim for comparison
      std::string trimmedHtml = boost::algorithm::trim_copy(html);
      if (boost::algorithm::contains(trimmedHtml, "<video id="))
      {
         bool starts = boost::algorithm::starts_with(trimmedHtml, "<video id=");
         bool ends = boost::algorithm::ends_with(trimmedHtml, "</video>");
         if (starts && ends)
            return "mediaOnly";
         else
            return "mediaInline";
      }
   }

   return std::string();
}

void addFragmentClass(const std::string& fragmentClass,
                      std::string* pHTML)
{
   // add fragment class to elements eligible for incremental build
   *pHTML = boost::regex_replace(*pHTML,
                                 boost::regex("<(h[1-6]|p|blockquote|pre|li)>"),
                                 "<$1 class=\"" + fragmentClass + "\">");

   // remove class from first paragraph element
   std::string pWithClass = "<p class=\"" + fragmentClass + "\">";
   boost::algorithm::replace_first(*pHTML, pWithClass, "<p>");

   // remove class from paragraph inside blockquote
   std::string bqWithClass = "<blockquote class=\"" + fragmentClass + "\">";
   boost::algorithm::replace_first(*pHTML, bqWithClass + "\n" + pWithClass,
                                   bqWithClass + "\n<p>");
}

Error slideMarkdownToHtml(const Slide& slide,
                          const std::string& extraContent,
                          const std::string& incremental,
                          std::string* pHTML)
{
   // render the markdown
   Error error = renderMarkdown(slide.content(), pHTML);
   if (error)
      return error;

   // add the extra content
   pHTML->append(extraContent);

   // slide content classes
   std::string slideClasses = "slideContent";
   if (!slide.showTitle())
      slideClasses += " noTitle";

   // look for an <hr/> splitting the html into columns
   const std::string kHRTag = "<hr/>";
   std::size_t hrLoc = pHTML->find(kHRTag);
   if (hrLoc != std::string::npos)
   {
      // get the columns
      std::string column1 = pHTML->substr(0, hrLoc);
      std::string column2;
      if (pHTML->length() > (column1.length() + kHRTag.length()))
         column2 = pHTML->substr(hrLoc + kHRTag.length());

      // now render two divs with the columns
      pHTML->clear();
      std::ostringstream ostr;
      ostr << divWrap("column column1 " + slideClasses, column1);
      ostr << divWrap("column column2 " + slideClasses, column2);
      *pHTML = ostr.str();
   }

   // apply standard (and optional media) classes
   else
   {
      std::string extraMediaClass = mediaClass(*pHTML);
      if (!extraMediaClass.empty())
         slideClasses = extraMediaClass + " " + slideClasses;

      *pHTML = divWrap(slideClasses, *pHTML);
   }

   // check whether we need to apply the fragment style (create
   // fragmentClass string so we can support other fragment
   // reveal visual styles in the future if we want)
   std::string fragmentClass;
   if (incremental == "true")
      fragmentClass = "fragment";

   // apply fragmentClass if necessary
   if (!fragmentClass.empty())
      addFragmentClass(fragmentClass, pHTML);

   return Success();
}

void validateTransitionType(const std::string& type)
{
   bool isValid = boost::iequals(type, "none") ||
                  boost::iequals(type, "rotate") ||
                  boost::iequals(type, "linear") ||
                  boost::iequals(type, "fade") ||
                  boost::iequals(type, "zoom") ||<commit_msg>eliminate spurious error for rotate transition<commit_after>/*
 * SlideRenderer.cpp
 *
 * Copyright (C) 2009-12 by RStudio, Inc.
 *
 * Unless you have received this program directly from RStudio pursuant
 * to the terms of a commercial license agreement with RStudio, then
 * this program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */


#include "SlideRenderer.hpp"

#include <boost/foreach.hpp>
#include <boost/format.hpp>
#include <boost/regex.hpp>
#include <boost/algorithm/string.hpp>

#include <core/Error.hpp>
#include <core/FilePath.hpp>
#include <core/StringUtils.hpp>
#include <core/json/Json.hpp>

#include <core/markdown/Markdown.hpp>

#include <session/SessionModuleContext.hpp>

#include "SlideParser.hpp"
#include "SlideMediaRenderer.hpp"
#include "SlideNavigationList.hpp"

using namespace core;

namespace session {
namespace modules { 
namespace presentation {

namespace {

std::string commandsAsJsonArray(const Slide& slide)
{
   json::Array commandsJsonArray;

   std::vector<Command> commands = slide.commands();
   BOOST_FOREACH(const Command& command, commands)
   {
      commandsJsonArray.push_back(command.asJson());
   }

   std::ostringstream ostr;
   json::write(commandsJsonArray, ostr);
   return ostr.str();
}

Error renderMarkdown(const std::string& content, std::string* pHTML)
{
   markdown::Extensions extensions;
   markdown::HTMLOptions htmlOptions;
   return markdown::markdownToHTML(content,
                                   extensions,
                                   htmlOptions,
                                   pHTML);
}


std::string divWrap(const std::string& classNames, const std::string& contents)
{
   boost::format fmt("\n<div class=\"%1%\">\n%2%\n</div>\n");
   return boost::str(fmt % classNames % contents);
}

std::string mediaClass(const std::string& html)
{
   boost::regex imageRegex(
     "\\s*<p>\\s*<img src=\"([^\"]+)\"(?: [\\w]+=\"[^\"]*\")*/>\\s*</p>\\s*");

   boost::smatch match;
   if (boost::regex_search(html, match, imageRegex))
   {
      std::string::const_iterator begin = match[0].first;
      std::string::const_iterator end = match[0].second;

      if (begin == html.begin() && end == html.end())
         return "mediaOnly";
      else
         return "mediaInline";
   }
   // look for videos
   else
   {
      // trim for comparison
      std::string trimmedHtml = boost::algorithm::trim_copy(html);
      if (boost::algorithm::contains(trimmedHtml, "<video id="))
      {
         bool starts = boost::algorithm::starts_with(trimmedHtml, "<video id=");
         bool ends = boost::algorithm::ends_with(trimmedHtml, "</video>");
         if (starts && ends)
            return "mediaOnly";
         else
            return "mediaInline";
      }
   }

   return std::string();
}

void addFragmentClass(const std::string& fragmentClass,
                      std::string* pHTML)
{
   // add fragment class to elements eligible for incremental build
   *pHTML = boost::regex_replace(*pHTML,
                                 boost::regex("<(h[1-6]|p|blockquote|pre|li)>"),
                                 "<$1 class=\"" + fragmentClass + "\">");

   // remove class from first paragraph element
   std::string pWithClass = "<p class=\"" + fragmentClass + "\">";
   boost::algorithm::replace_first(*pHTML, pWithClass, "<p>");

   // remove class from paragraph inside blockquote
   std::string bqWithClass = "<blockquote class=\"" + fragmentClass + "\">";
   boost::algorithm::replace_first(*pHTML, bqWithClass + "\n" + pWithClass,
                                   bqWithClass + "\n<p>");
}

Error slideMarkdownToHtml(const Slide& slide,
                          const std::string& extraContent,
                          const std::string& incremental,
                          std::string* pHTML)
{
   // render the markdown
   Error error = renderMarkdown(slide.content(), pHTML);
   if (error)
      return error;

   // add the extra content
   pHTML->append(extraContent);

   // slide content classes
   std::string slideClasses = "slideContent";
   if (!slide.showTitle())
      slideClasses += " noTitle";

   // look for an <hr/> splitting the html into columns
   const std::string kHRTag = "<hr/>";
   std::size_t hrLoc = pHTML->find(kHRTag);
   if (hrLoc != std::string::npos)
   {
      // get the columns
      std::string column1 = pHTML->substr(0, hrLoc);
      std::string column2;
      if (pHTML->length() > (column1.length() + kHRTag.length()))
         column2 = pHTML->substr(hrLoc + kHRTag.length());

      // now render two divs with the columns
      pHTML->clear();
      std::ostringstream ostr;
      ostr << divWrap("column column1 " + slideClasses, column1);
      ostr << divWrap("column column2 " + slideClasses, column2);
      *pHTML = ostr.str();
   }

   // apply standard (and optional media) classes
   else
   {
      std::string extraMediaClass = mediaClass(*pHTML);
      if (!extraMediaClass.empty())
         slideClasses = extraMediaClass + " " + slideClasses;

      *pHTML = divWrap(slideClasses, *pHTML);
   }

   // check whether we need to apply the fragment style (create
   // fragmentClass string so we can support other fragment
   // reveal visual styles in the future if we want)
   std::string fragmentClass;
   if (incremental == "true")
      fragmentClass = "fragment";

   // apply fragmentClass if necessary
   if (!fragmentClass.empty())
      addFragmentClass(fragmentClass, pHTML);

   return Success();
}

void validateTransitionType(const std::string& type)
{
   bool isValid = boost::iequals(type, "none") ||
                  boost::iequals(type, "default") ||
                  boost::iequals(type, "linear") ||
                  boost::iequals(type, "fade") ||
                  boost::iequals(type, "zoom") ||
{'fraction_non_alphanumeric': 0.13788553521359062, 'fraction_numerical': 0.0041233712683490025, 'mean_word_length': 2.852287166454892, 'pattern_counts': {'":': 0, '<': 65, '<?xml version=': 0, '>': 67, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 13, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3310765', 'n_tokens_mistral': 3596, 'n_tokens_neox': 3499, 'n_words': 913}