type factory based kindle 2 latex converter initial commit.
This commit is contained in:
74
typefactory/.gitignore
vendored
Normal file
74
typefactory/.gitignore
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
# This file is used to ignore files which are generated
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
*~
|
||||
*.autosave
|
||||
*.a
|
||||
*.core
|
||||
*.moc
|
||||
*.o
|
||||
*.obj
|
||||
*.orig
|
||||
*.rej
|
||||
*.so
|
||||
*.so.*
|
||||
*_pch.h.cpp
|
||||
*_resource.rc
|
||||
*.qm
|
||||
.#*
|
||||
*.*#
|
||||
core
|
||||
!core/
|
||||
tags
|
||||
.DS_Store
|
||||
.directory
|
||||
*.debug
|
||||
Makefile*
|
||||
*.prl
|
||||
*.app
|
||||
moc_*.cpp
|
||||
ui_*.h
|
||||
qrc_*.cpp
|
||||
Thumbs.db
|
||||
*.res
|
||||
*.rc
|
||||
/.qmake.cache
|
||||
/.qmake.stash
|
||||
|
||||
# qtcreator generated files
|
||||
*.pro.user*
|
||||
CMakeLists.txt.user*
|
||||
|
||||
# xemacs temporary files
|
||||
*.flc
|
||||
|
||||
# Vim temporary files
|
||||
.*.swp
|
||||
|
||||
# Visual Studio generated files
|
||||
*.ib_pdb_index
|
||||
*.idb
|
||||
*.ilk
|
||||
*.pdb
|
||||
*.sln
|
||||
*.suo
|
||||
*.vcproj
|
||||
*vcproj.*.*.user
|
||||
*.ncb
|
||||
*.sdf
|
||||
*.opensdf
|
||||
*.vcxproj
|
||||
*vcxproj.*
|
||||
|
||||
# MinGW generated files
|
||||
*.Debug
|
||||
*.Release
|
||||
|
||||
# Python byte code
|
||||
*.pyc
|
||||
|
||||
# Binaries
|
||||
# --------
|
||||
*.dll
|
||||
*.exe
|
||||
|
||||
7
typefactory/kindle2latex.pro
Normal file
7
typefactory/kindle2latex.pro
Normal file
@@ -0,0 +1,7 @@
|
||||
TEMPLATE = app
|
||||
CONFIG += console c++17
|
||||
CONFIG -= app_bundle
|
||||
CONFIG -= qt
|
||||
|
||||
SOURCES += \
|
||||
main.cpp
|
||||
325
typefactory/main.cpp
Normal file
325
typefactory/main.cpp
Normal file
@@ -0,0 +1,325 @@
|
||||
#include <array>
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <getopt.h>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "typefactory.h" // <-- твой хедер
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Helpers
|
||||
// ------------------------------------------------------------
|
||||
|
||||
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<char> (ch));
|
||||
break;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Model
|
||||
// ------------------------------------------------------------
|
||||
|
||||
struct Quote {
|
||||
std::string meta;
|
||||
std::vector<std::string> text_lines;
|
||||
};
|
||||
|
||||
struct ParseContext {
|
||||
std::vector<std::string> order;
|
||||
std::unordered_map<std::string, std::vector<Quote>> by_title;
|
||||
std::unordered_map<std::string, std::size_t> seen;
|
||||
|
||||
std::string cur_title;
|
||||
std::string cur_meta;
|
||||
std::vector<std::string> cur_text;
|
||||
|
||||
void start_title (std::string t) {
|
||||
cur_title = std::move (t);
|
||||
cur_meta.clear();
|
||||
cur_text.clear();
|
||||
}
|
||||
|
||||
void set_meta (std::string m) {
|
||||
cur_meta = std::move (m);
|
||||
}
|
||||
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();
|
||||
}
|
||||
};
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Handlers
|
||||
// ------------------------------------------------------------
|
||||
|
||||
enum class Stage { Title, Meta, Body };
|
||||
|
||||
static constexpr std::array<Stage, 3> 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;
|
||||
}
|
||||
};
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Factory wiring (using your typefactory.h) + caching handlers
|
||||
// ------------------------------------------------------------
|
||||
|
||||
using HandlerPtr = std::shared_ptr<ILineHandler>;
|
||||
using HandlerMap = std::unordered_map<Stage, HandlerPtr>;
|
||||
|
||||
static TypeFactory<Stage, ILineHandler> build_factory() {
|
||||
TypeFactory<Stage, ILineHandler> f;
|
||||
f.registerType<TitleHandler> (Stage::Title);
|
||||
f.registerType<MetaHandler> (Stage::Meta);
|
||||
f.registerType<BodyHandler> (Stage::Body);
|
||||
return f;
|
||||
}
|
||||
|
||||
static HandlerMap build_handlers_cache (const TypeFactory<Stage, ILineHandler> &factory) {
|
||||
HandlerMap handlers;
|
||||
handlers.reserve (kAllStages.size());
|
||||
|
||||
// Рантайм-валидация: для каждого Stage обязаны уметь создать handler
|
||||
for (Stage st : kAllStages) {
|
||||
try {
|
||||
auto h = factory.create (st); // shared_ptr<ILineHandler>
|
||||
if (!h)
|
||||
throw std::runtime_error ("Factory returned null handler");
|
||||
handlers.emplace (st, std::move (h));
|
||||
} catch (const std::out_of_range &) {
|
||||
throw std::runtime_error ("Missing handler registration for some Stage");
|
||||
}
|
||||
}
|
||||
|
||||
// Доп.проверка: ensure all are present (на случай коллизий/ошибок emplace)
|
||||
if (handlers.size() != kAllStages.size())
|
||||
throw std::runtime_error ("Handler cache size mismatch");
|
||||
|
||||
return handlers;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// CLI
|
||||
// ------------------------------------------------------------
|
||||
|
||||
struct CliArgs {
|
||||
std::string input;
|
||||
std::string output;
|
||||
};
|
||||
|
||||
static void print_usage (const char *argv0) {
|
||||
std::cerr << "Usage: " << argv0 << " --input <file> --output <file>\n";
|
||||
}
|
||||
|
||||
static bool parse_args (int argc, char **argv, CliArgs &out) {
|
||||
static option long_opts[] = {
|
||||
{"input", required_argument, nullptr, 'i'},
|
||||
{"output", required_argument, nullptr, 'o'},
|
||||
{"help", no_argument, nullptr, 'h'},
|
||||
{nullptr, 0, nullptr, 0 }
|
||||
};
|
||||
|
||||
int c = 0;
|
||||
while ((c = ::getopt_long (argc, argv, "i:o:h", long_opts, nullptr)) != -1) {
|
||||
switch (c) {
|
||||
case 'i':
|
||||
out.input = optarg;
|
||||
break;
|
||||
case 'o':
|
||||
out.output = optarg;
|
||||
break;
|
||||
case 'h':
|
||||
print_usage (argv[0]);
|
||||
return false;
|
||||
default:
|
||||
print_usage (argv[0]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (out.input.empty() || out.output.empty()) {
|
||||
print_usage (argv[0]);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Convert
|
||||
// ------------------------------------------------------------
|
||||
|
||||
static int convert (const std::string &in_path, const std::string &out_path) {
|
||||
std::ifstream in (in_path);
|
||||
if (!in.is_open()) {
|
||||
std::cerr << "Failed to open input file: " << in_path
|
||||
<< " (" << std::strerror (errno) << ")\n";
|
||||
return 2;
|
||||
}
|
||||
|
||||
// Build factory + cache handlers once
|
||||
TypeFactory<Stage, ILineHandler> 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";
|
||||
return 4;
|
||||
}
|
||||
|
||||
ParseContext ctx{};
|
||||
Stage stage = Stage::Title;
|
||||
|
||||
std::string line;
|
||||
while (std::getline (in, line)) {
|
||||
line = rstrip_cr (std::move (line));
|
||||
|
||||
auto it = handlers.find (stage);
|
||||
if (it == handlers.end() || !it->second) {
|
||||
std::cerr << "Internal error: handler missing at runtime\n";
|
||||
return 4;
|
||||
}
|
||||
|
||||
Stage next = stage;
|
||||
it->second->handle (line, ctx, next);
|
||||
stage = next;
|
||||
}
|
||||
|
||||
// EOF: если файл не закончился "==========", всё равно зафиксируем последний блок
|
||||
ctx.finalize_quote();
|
||||
|
||||
std::ofstream out (out_path, std::ios::trunc);
|
||||
if (!out.is_open()) {
|
||||
std::cerr << "Failed to open output file: " << out_path
|
||||
<< " (" << std::strerror (errno) << ")\n";
|
||||
return 3;
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i < ctx.order.size(); ++i) {
|
||||
const auto &title = ctx.order[i];
|
||||
out << "\\section {" << latex_escape (title) << "}\n";
|
||||
|
||||
const auto it = ctx.by_title.find (title);
|
||||
if (it == ctx.by_title.end())
|
||||
continue;
|
||||
|
||||
for (const auto &q : it->second) {
|
||||
out << " \\subsection {" << latex_escape (q.meta) << "}\n";
|
||||
for (const auto &tl : q.text_lines)
|
||||
out << " " << latex_escape (tl) << "\n";
|
||||
out << " \\subsubsection{notes}\n\n";
|
||||
}
|
||||
|
||||
if (i + 1 < ctx.order.size())
|
||||
out << "\n";
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main (int argc, char **argv) {
|
||||
CliArgs args;
|
||||
if (!parse_args (argc, argv, args))
|
||||
return 1;
|
||||
return convert (args.input, args.output);
|
||||
}
|
||||
118
typefactory/typefactory.h
Normal file
118
typefactory/typefactory.h
Normal file
@@ -0,0 +1,118 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2026 Dim Himro
|
||||
|
||||
#ifndef TYPEFACTORY_H
|
||||
#define TYPEFACTORY_H
|
||||
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <utility> // std::move
|
||||
|
||||
/**
|
||||
* @brief Registry-based factory for creating objects by runtime identifier.
|
||||
*
|
||||
* Allows registering derived types and instantiating them using a ClassId key.
|
||||
*
|
||||
* Notes:
|
||||
* - Not thread-safe. Register types during initialization phase only.
|
||||
* - Re-registering the same id overwrites the previous entry ("last wins").
|
||||
* - ClassId must be hashable (std::hash<ClassId> specialization must exist).
|
||||
*
|
||||
* @tparam ClassId -Unique class identifier type (e.g. std::string, int, enum).
|
||||
* @tparam BaseClass -Base type for all created objects.
|
||||
* @tparam Arg - Constructor arguments forwarded to registered derived types (by value).
|
||||
*
|
||||
* Usage example:
|
||||
* @code
|
||||
* #include <string>
|
||||
*
|
||||
* struct Base {
|
||||
* virtual ~Base() = default;
|
||||
* virtual int get() = 0;
|
||||
* };
|
||||
*
|
||||
* struct Derived1 : Base {
|
||||
* explicit Derived1(int start) : m_start(start) {}
|
||||
* int get() override { return m_start + 1; }
|
||||
* int m_start = 0;
|
||||
* };
|
||||
*
|
||||
* struct Derived2 : Base {
|
||||
* explicit Derived2(int start) : m_start(start) {}
|
||||
* int get() override { return m_start + 2; }
|
||||
* int m_start = 0;
|
||||
* };
|
||||
*
|
||||
* TypeFactory<std::string, Base, int> factory;
|
||||
* factory.registerType<Derived1>("one");
|
||||
* factory.registerType<Derived2>("two");
|
||||
*
|
||||
* auto a = factory.create("one", 10);
|
||||
* auto b = factory.creator("two")(10); // advanced API: get creator function
|
||||
*
|
||||
* (void)a->get();
|
||||
* (void)b->get();
|
||||
* @endcode
|
||||
*/
|
||||
template <class ClassId, class BaseClass, class... Args>
|
||||
class TypeFactory {
|
||||
public:
|
||||
using BasePtr = std::shared_ptr<BaseClass>;
|
||||
using CreatorFn = BasePtr (*)(Args...);
|
||||
|
||||
TypeFactory() = default;
|
||||
|
||||
/**
|
||||
* @brief Registers a Derived type under the given id.
|
||||
*
|
||||
* Requirements (checked at compile-time):
|
||||
* - Derived must inherit from BaseClass
|
||||
* - Derived must be constructible from Args...
|
||||
*
|
||||
* Re-registering the same id overwrites the previous entry ("last wins").
|
||||
*/
|
||||
template <class Derived>
|
||||
void registerType(const ClassId& id) {
|
||||
static_assert(std::is_base_of_v<BaseClass, Derived>,
|
||||
"Derived must inherit from BaseClass");
|
||||
static_assert(std::is_constructible_v<Derived, Args...>,
|
||||
"Derived must be constructible from Args...");
|
||||
|
||||
classes.insert_or_assign(id, &instantiate<Derived>);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns creator function for the given id.
|
||||
* @throws std::out_of_range when id is not registered.
|
||||
*/
|
||||
[[nodiscard]] CreatorFn creator(const ClassId& id) const {
|
||||
return classes.at(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Creates an instance for the given id using provided arguments.
|
||||
* @throws std::out_of_range when id is not registered.
|
||||
*/
|
||||
[[nodiscard]] BasePtr create(const ClassId& id, Args... args) const {
|
||||
return creator(id)(std::move(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks whether an id is registered.
|
||||
*/
|
||||
[[nodiscard]] bool contains(const ClassId& id) const {
|
||||
return classes.find(id) != classes.end();
|
||||
}
|
||||
|
||||
protected:
|
||||
// Protected by design: derived factories may extend/inspect the registry.
|
||||
std::unordered_map<ClassId, CreatorFn> classes;
|
||||
|
||||
private:
|
||||
template <class Derived>
|
||||
static BasePtr instantiate(Args... args) {
|
||||
return std::make_shared<Derived>(std::move(args)...);
|
||||
}
|
||||
};
|
||||
#endif // TYPEFACTORY_H
|
||||
Reference in New Issue
Block a user