#include "app.hpp" #include #include #include #include #include #include // ============================================================ // 匿名命名空间:简易 JSON 序列化 / 反序列化 // 仅支持当前 TodoItem 数据结构的 JSON 子集,无外部依赖。 // ============================================================ namespace { /** * @brief 将字符串中的特殊字符转义为 JSON 安全形式。 */ static std::string json_escape(const std::string& s) { std::string r; r.reserve(s.size() + 4); for (char c : s) { switch (c) { case '"': r += "\\\""; break; case '\\': r += "\\\\"; break; case '\n': r += "\\n"; break; case '\t': r += "\\t"; break; case '\r': r += "\\r"; break; default: r += c; break; } } return r; } /** * @brief 将 TodoItem 列表和 next_id 序列化为 JSON 字符串。 */ static std::string items_to_json(const std::vector& items, int next_id) { std::ostringstream oss; oss << "{\n \"items\": [\n"; for (size_t i = 0; i < items.size(); ++i) { const auto& it = items[i]; oss << " {\n"; oss << " \"id\": " << it.id << ",\n"; oss << " \"title\": \"" << json_escape(it.title) << "\",\n"; oss << " \"description\": \"" << json_escape(it.description) << "\",\n"; oss << " \"completed\": " << (it.completed ? "true" : "false") << "\n"; oss << " }"; if (i + 1 < items.size()) oss << ","; oss << "\n"; } oss << " ],\n \"next_id\": " << next_id << "\n}\n"; return oss.str(); } /** * @brief 简易递归下降 JSON 解析器,专用于解析 TodoItem 数据格式。 * * 支持 JSON 子集:对象、数组、字符串、整数、布尔值。 * 不依赖任何外部库。 */ class JsonReader { public: explicit JsonReader(const std::string& input) : s_(input), pos_(0) {} /** * @brief 解析顶层 JSON 对象,提取 items 和 next_id。 * @param[out] items 解析出的任务列表 * @param[out] next_id 解析出的下一个可用 ID * @return 解析成功返回 true */ bool parse(std::vector& items, int& next_id) { items.clear(); skip_ws(); if (peek() != '{') return false; advance(); // 跳过 '{' while (pos_ < s_.size() && peek() != '}') { skip_ws(); if (peek() == '}') break; if (peek() == ',') { advance(); continue; } std::string key = parse_string(); skip_ws(); if (peek() != ':') return false; advance(); skip_ws(); if (key == "items") { if (!parse_array(items)) return false; } else if (key == "next_id") { next_id = parse_int(); } else { skip_value(); } } if (peek() == '}') advance(); return true; } private: const std::string& s_; size_t pos_ = 0; /// @brief 查看当前字符,不移动指针。 char peek() const { return pos_ < s_.size() ? s_[pos_] : '\0'; } /// @brief 读取当前字符并前进一位。 char advance() { return pos_ < s_.size() ? s_[pos_++] : '\0'; } /// @brief 跳过空白字符。 void skip_ws() { while (pos_ < s_.size() && std::isspace(static_cast(s_[pos_]))) { ++pos_; } } /// @brief 解析 JSON 字符串(含引号和转义处理)。 std::string parse_string() { skip_ws(); if (peek() != '"') return {}; advance(); // 跳过开头的 " std::string result; while (pos_ < s_.size() && s_[pos_] != '"') { if (s_[pos_] == '\\') { advance(); if (pos_ < s_.size()) { switch (s_[pos_]) { case '"': result += '"'; break; case '\\': result += '\\'; break; case '/': result += '/'; break; case 'n': result += '\n'; break; case 't': result += '\t'; break; case 'r': result += '\r'; break; default: result += s_[pos_]; break; } } } else { result += s_[pos_]; } ++pos_; } if (peek() == '"') advance(); // 跳过结尾的 " return result; } /// @brief 解析整数值(可选负号)。 int parse_int() { skip_ws(); int sign = 1; if (peek() == '-') { sign = -1; advance(); } int val = 0; while (pos_ < s_.size() && std::isdigit(static_cast(s_[pos_]))) { val = val * 10 + (s_[pos_] - '0'); ++pos_; } return sign * val; } /** * @brief 解析布尔值 true / false。 * @param[out] value 解析结果 * @return 解析成功返回 true */ bool parse_bool(bool& value) { skip_ws(); if (s_.substr(pos_, 4) == "true") { pos_ += 4; value = true; return true; } if (s_.substr(pos_, 5) == "false") { pos_ += 5; value = false; return true; } return false; } /** * @brief 解析 JSON 数组,元素为 TodoItem 对象。 */ bool parse_array(std::vector& items) { skip_ws(); if (peek() != '[') return false; advance(); // 跳过 '[' while (pos_ < s_.size() && peek() != ']') { skip_ws(); if (peek() == ']') break; if (peek() == ',') { advance(); continue; } TodoItem item; if (!parse_object(item)) return false; items.push_back(std::move(item)); } if (peek() == ']') advance(); return true; } /** * @brief 解析单个 TodoItem JSON 对象。 */ bool parse_object(TodoItem& item) { skip_ws(); if (peek() != '{') return false; advance(); // 跳过 '{' while (pos_ < s_.size() && peek() != '}') { skip_ws(); if (peek() == '}') break; if (peek() == ',') { advance(); continue; } std::string key = parse_string(); skip_ws(); if (peek() != ':') return false; advance(); skip_ws(); if (key == "id") { item.id = parse_int(); } else if (key == "title") { item.title = parse_string(); } else if (key == "description") { item.description = parse_string(); } else if (key == "completed") { bool val = false; if (parse_bool(val)) item.completed = val; } else { skip_value(); } } if (peek() == '}') advance(); return true; } /// @brief 跳过任意 JSON 值(用于忽略不关心的字段)。 void skip_value() { skip_ws(); char c = peek(); if (c == '"') { parse_string(); } else if (c == '{') { int depth = 1; advance(); while (pos_ < s_.size() && depth > 0) { if (s_[pos_] == '{') ++depth; else if (s_[pos_] == '}') --depth; else if (s_[pos_] == '"') { ++pos_; while (pos_ < s_.size() && s_[pos_] != '"') { if (s_[pos_] == '\\') ++pos_; ++pos_; } } ++pos_; } } else if (c == '[') { int depth = 1; advance(); while (pos_ < s_.size() && depth > 0) { if (s_[pos_] == '[') ++depth; else if (s_[pos_] == ']') --depth; else if (s_[pos_] == '"') { ++pos_; while (pos_ < s_.size() && s_[pos_] != '"') { if (s_[pos_] == '\\') ++pos_; ++pos_; } } ++pos_; } } else { // 数字、布尔值、null 等 while (pos_ < s_.size() && !std::isspace(static_cast(s_[pos_])) && s_[pos_] != ',' && s_[pos_] != '}' && s_[pos_] != ']') { ++pos_; } } } }; } // anonymous namespace // ============================================================ // TodoService 实现 // ============================================================ TodoService::TodoService(const std::string& filepath) : filepath_(filepath) { load(); } const std::vector& TodoService::get_all() const { return items_; } std::optional TodoService::get_by_id(int id) const { for (const auto& item : items_) { if (item.id == id) return item; } return std::nullopt; } TodoItem TodoService::create(const std::string& title, const std::string& description) { TodoItem item; item.id = next_id_++; item.title = title; item.description = description; item.completed = false; items_.push_back(item); save(); return item; } std::optional TodoService::update( int id, const std::optional& title, const std::optional& description, const std::optional& completed) { for (auto& item : items_) { if (item.id == id) { if (title.has_value()) item.title = title.value(); if (description.has_value()) item.description = description.value(); if (completed.has_value()) item.completed = completed.value(); save(); return item; } } return std::nullopt; } bool TodoService::delete_item(int id) { auto it = std::find_if(items_.begin(), items_.end(), [id](const TodoItem& item) { return item.id == id; }); if (it == items_.end()) return false; items_.erase(it); save(); return true; } void TodoService::load() { std::ifstream ifs(filepath_); if (!ifs.is_open()) { items_.clear(); next_id_ = 1; return; } std::stringstream buffer; buffer << ifs.rdbuf(); std::string content = buffer.str(); if (content.empty()) { items_.clear(); next_id_ = 1; return; } items_.clear(); int parsed_next_id = 1; JsonReader reader(content); if (reader.parse(items_, parsed_next_id)) { next_id_ = parsed_next_id; // 确保 next_id 大于所有现有 ID,避免冲突 for (const auto& item : items_) { if (item.id >= next_id_) next_id_ = item.id + 1; } } else { // 解析失败时回退到空状态 items_.clear(); next_id_ = 1; } } void TodoService::save() { std::ofstream ofs(filepath_); if (!ofs.is_open()) return; ofs << items_to_json(items_, next_id_); }