#include "converter.h" #include #include #include #include #include #include #include #include #include #include "typefactory.h" static std::string rstrip_cr (std::string s) { if (!s.empty() && s.back() == '\r') s.pop_back(); return s; } static std::string latex_escape (const std::string &s) { std::string out; out.reserve (s.size() + s.size() / 8); for (unsigned char ch : s) { switch (ch) { case '\\': out += "\\textbackslash{}"; break; case '{': out += "\\{"; break; case '}': out += "\\}"; break; case '%': out += "\\%"; break; case '$': out += "\\$"; break; case '#': out += "\\#"; break; case '&': out += "\\&"; break; case '_': out += "\\_"; break; case '^': out += "\\textasciicircum{}"; break; case '~': out += "\\textasciitilde{}"; break; default: out.push_back (static_cast (ch)); break; } } return out; } struct Quote { std::string meta; std::vector text_lines; }; struct ParseContext { std::vector order; std::unordered_map> by_title; std::unordered_map seen; std::string cur_title; std::string cur_meta; std::vector cur_text; void start_title (std::string title) { cur_title = std::move (title); cur_meta.clear(); cur_text.clear(); } void set_meta (std::string meta) { cur_meta = std::move (meta); } void add_text (std::string line) { cur_text.push_back (std::move (line)); } void finalize_quote() { if (cur_title.empty() || cur_meta.empty()) { cur_meta.clear(); cur_text.clear(); return; } if (seen.find (cur_title) == seen.end()) { seen.emplace (cur_title, order.size()); order.push_back (cur_title); } by_title[cur_title].push_back (Quote{cur_meta, cur_text}); cur_meta.clear(); cur_text.clear(); } }; enum class Stage { Title, Meta, Body }; static constexpr std::array kAllStages = { Stage::Title, Stage::Meta, Stage::Body }; struct ILineHandler { virtual ~ILineHandler() = default; virtual void handle (const std::string &line, ParseContext &ctx, Stage &next) = 0; }; struct TitleHandler final : ILineHandler { void handle (const std::string &line, ParseContext &ctx, Stage &next) override { if (line.empty()) { next = Stage::Title; return; } ctx.start_title (line); next = Stage::Meta; } }; struct MetaHandler final : ILineHandler { void handle (const std::string &line, ParseContext &ctx, Stage &next) override { ctx.set_meta (line); next = Stage::Body; } }; struct BodyHandler final : ILineHandler { void handle (const std::string &line, ParseContext &ctx, Stage &next) override { if (line == "==========") { ctx.finalize_quote(); next = Stage::Title; return; } ctx.add_text (line); next = Stage::Body; } }; using HandlerPtr = std::shared_ptr; using HandlerMap = std::unordered_map; static TypeFactory build_factory() { TypeFactory factory; factory.registerType (Stage::Title); factory.registerType (Stage::Meta); factory.registerType (Stage::Body); return factory; } static HandlerMap build_handlers_cache ( const TypeFactory &factory) { HandlerMap handlers; handlers.reserve (kAllStages.size()); for (Stage stage : kAllStages) { try { auto handler = factory.create (stage); if (!handler) throw std::runtime_error ("Factory returned null handler"); handlers.emplace (stage, std::move (handler)); } catch (const std::out_of_range &) { throw std::runtime_error ("Missing handler registration for Stage"); } } if (handlers.size() != kAllStages.size()) throw std::runtime_error ("Handler cache size mismatch"); return handlers; } struct Converter::Impl { ParseContext context; }; Converter::Converter (std::string input_file_name) : input_file_name_ (std::move (input_file_name)) , impl_ (std::make_unique()) { } Converter::~Converter() = default; int Converter::error_code() const noexcept { return error_code_; } bool Converter::parse() { std::ifstream input (input_file_name_); if (!input.is_open()) { std::cerr << "Failed to open input file: " << input_file_name_ << " (" << std::strerror (errno) << ")\n"; error_code_ = 2; return false; } TypeFactory factory = build_factory(); HandlerMap handlers; try { handlers = build_handlers_cache (factory); } catch (const std::exception &e) { std::cerr << "Internal error while building handler cache: " << e.what() << "\n"; error_code_ = 4; return false; } impl_->context = ParseContext{}; Stage stage = Stage::Title; std::string line; while (std::getline (input, line)) { line = rstrip_cr (std::move (line)); auto it = handlers.find (stage); if (it == handlers.end() || !it->second) { std::cerr << "Internal error: missing handler at runtime\n"; error_code_ = 4; return false; } Stage next = stage; it->second->handle (line, impl_->context, next); stage = next; } impl_->context.finalize_quote(); error_code_ = 0; return true; } bool Converter::save (const std::string &output_file_name) { if (!parse()) return false; std::ofstream output (output_file_name, std::ios::trunc); if (!output.is_open()) { std::cerr << "Failed to open output file: " << output_file_name << " (" << std::strerror (errno) << ")\n"; error_code_ = 3; return false; } const ParseContext &ctx = impl_->context; for (std::size_t i = 0; i < ctx.order.size(); ++i) { const auto &title = ctx.order[i]; output << "\\section {" << latex_escape (title) << "}\n"; const auto it = ctx.by_title.find (title); if (it == ctx.by_title.end()) continue; for (const auto "e : it->second) { output << " \\subsection {" << latex_escape (quote.meta) << "}\n"; for (const auto &text_line : quote.text_lines) output << " " << latex_escape (text_line) << "\n"; output << " \\subsubsection{notes}\n\n"; } if (i + 1 < ctx.order.size()) output << "\n"; } error_code_ = 0; return true; }