From 539ef6568697c9dc1712fc7a2a379d4e3fb9235c Mon Sep 17 00:00:00 2001 From: Pinapelz Date: Tue, 7 Oct 2025 13:47:34 -0700 Subject: Initial commit --- src/markdown_translator.cpp | 264 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 264 insertions(+) create mode 100644 src/markdown_translator.cpp (limited to 'src/markdown_translator.cpp') diff --git a/src/markdown_translator.cpp b/src/markdown_translator.cpp new file mode 100644 index 0000000..d362f82 --- /dev/null +++ b/src/markdown_translator.cpp @@ -0,0 +1,264 @@ +#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(); +} -- cgit v1.2.3