#include "markdown_translator.h" #include #include #include MarkdownTranslator::MarkdownTranslator(){ } MarkdownTranslator::~MarkdownTranslator() { } std::string MarkdownTranslator::translate(const std::string& markdownContent, const std::string& cssPath, const std::string& title) { std::stringstream htmlOutput; std::stringstream markdownStream(markdownContent); std::string line; std::vector headers; std::string currentLine; while (std::getline(markdownStream, currentLine)) { std::regex headerRegex("^(#{1,3})\\s+(.*)$"); std::smatch matches; if (std::regex_match(currentLine, matches, headerRegex)) { int level = matches[1].length(); std::string content = matches[2]; if (level <= 3) { headers.push_back(std::to_string(level) + ":" + content); } } } // Reset the stream to start over markdownStream.clear(); markdownStream.str(markdownContent); // Start with basic HTML structure htmlOutput << "\n"; htmlOutput << "\n"; htmlOutput << "\n"; htmlOutput << " \n"; htmlOutput << " \n"; htmlOutput << " " + title + "\n"; htmlOutput << " \n"; htmlOutput << "\n"; htmlOutput << "\n"; // Add navigation sidebar generateSideBar(htmlOutput, headers, title); // Main content container htmlOutput << "
\n"; htmlOutput << "
\n"; // Process each line of markdown // State of current parse bool inFigureBlock{false}; std::vector figureLines; while (std::getline(markdownStream, line)) { if(line.find(":::figure") == 0){ inFigureBlock = true; figureLines.clear(); continue; } if(inFigureBlock && line == ":::"){ inFigureBlock = false; htmlOutput << " " << processFigureBlock(figureLines) << "\n"; continue; } else if(inFigureBlock){ figureLines.push_back(line); continue; } htmlOutput << " " << processLine(line); } // Add article meta information section htmlOutput << "
\n"; htmlOutput << "

Last updated: " << getCurrentDateTime() << "

\n"; htmlOutput << "
\n"; htmlOutput << "
\n"; htmlOutput << "
\n"; htmlOutput << "\n"; htmlOutput << "\n"; return htmlOutput.str(); } void MarkdownTranslator::generateSideBar(std::stringstream& output, const std::vector& headers, const std::string& title) { output << "
\n"; output << "
\n"; output << "

" + title + "

\n"; output << "
\n"; output << "
    \n"; output << "

    Table of Contents

    \n"; output << "
  • Home
  • \n"; output << "
  • Recent Changes
  • \n"; output << "
  • Random Page
  • \n"; output << "
      \n"; // Generate navigation from headers for (const auto& header : headers) { size_t separatorPos = header.find(':'); if (separatorPos != std::string::npos) { int level = std::stoi(header.substr(0, separatorPos)); std::string content = header.substr(separatorPos + 1); // Create anchor ID from header content std::string anchorId = createAnchorId(content); // Add indentation based on header level std::string indentation = ""; for (int i = 1; i < level; ++i) { indentation += " "; } output << " " << indentation << "
    • " << content << "
    • \n"; } } output << "
    \n"; output << "
\n"; output << "
\n"; } std::string MarkdownTranslator::createAnchorId(const std::string& text) { std::string id = text; // Convert to lowercase std::transform(id.begin(), id.end(), id.begin(), ::tolower); // Replace spaces with hyphens std::replace(id.begin(), id.end(), ' ', '-'); // Remove any non-alphanumeric characters except hyphens id.erase( std::remove_if( id.begin(), id.end(), [](char c) { return !(std::isalnum(c) || c == '-'); } ), id.end() ); return id; } std::string MarkdownTranslator::getCurrentDateTime() { std::time_t now = std::time(nullptr); std::tm* localTime = std::localtime(&now); char buffer[80]; std::strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", localTime); return std::string(buffer); } std::string MarkdownTranslator::processLine(const std::string& line) { std::string processed = line; // Check for headers first (they start at beginning of line) processed = processHeaders(processed); // If it wasn't a header, process inline elements if (processed == line) { processed = processBold(processed); processed = processItalic(processed); processed = processLinks(processed); // Wrap in paragraph tags if it's regular text if (!processed.empty() && processed[0] != '<') { processed = processParagraph(processed); } } return processed + "\n"; } std::string MarkdownTranslator::processHeaders(const std::string& line) { // Check for H1-H6 std::regex headerRegex("^(#{1,6})\\s+(.*)$"); std::smatch matches; if (std::regex_match(line, matches, headerRegex)) { int level = matches[1].length(); std::string content = matches[2]; std::string anchorId = createAnchorId(content); return "" + content + ""; } return line; } std::string MarkdownTranslator::processBold(const std::string& text) { // Replace **text** or __text__ with text std::string result = text; std::regex boldRegex("\\*\\*([^\\*]+)\\*\\*|__([^_]+)__"); result = std::regex_replace(result, boldRegex, "$1$2"); return result; } std::string MarkdownTranslator::processItalic(const std::string& text) { // Replace *text* or _text_ with text std::string result = text; std::regex italicRegex("\\*([^\\*]+)\\*|_([^_]+)_"); result = std::regex_replace(result, italicRegex, "$1$2"); return result; } std::string MarkdownTranslator::processLinks(const std::string& text) { // Replace [text](url) with text std::string result = text; std::regex linkRegex("\\[([^\\]]+)\\]\\(([^\\)]+)\\)"); result = std::regex_replace(result, linkRegex, "$1"); return result; } std::string MarkdownTranslator::processParagraph(const std::string& text) { if (text.empty()) return ""; return "

" + text + "

"; } std::string MarkdownTranslator::processSingleFigure(const std::string& text) { // Extract image details using regex std::regex imageRegex("!\\[(.*?)\\]\\(([^\\s\"]+)(\\s+\"(.*?)\")?\\)"); std::smatch matches; if (std::regex_search(text, matches, imageRegex)) { std::string alt = matches[1].str(); std::string src = matches[2].str(); std::string title = matches.size() > 4 && matches[4].matched ? " title=\"" + matches[4].str() + "\"" : ""; // Create the HTML for the image return "\"""; } return text; } std::string MarkdownTranslator::processFigureBlock(const std::vector& lines){ std::stringstream html; std::string imageHtml; std::string caption; html << "
\n"; // Process each line in the figure block for (const auto& line : lines) { if (line.find("![") == 0) { // Process the image imageHtml = processSingleFigure(line); html << " " << imageHtml << "\n"; } else if (line.find("Caption:") == 0) { // Extract caption caption = line.substr(8); html << "
" << caption << "
\n"; } else if (!line.empty()) { // Process any other content html << "

" << line << "

\n"; } } html << "
"; return html.str(); }