plan_execute/src/app.cpp

384 lines
11 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "app.hpp"
#include <fstream>
#include <sstream>
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <stdexcept>
// ============================================================
// 匿名命名空间:简易 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<TodoItem>& 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<TodoItem>& 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<unsigned char>(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<unsigned char>(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<TodoItem>& 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<unsigned char>(s_[pos_])) &&
s_[pos_] != ',' && s_[pos_] != '}' && s_[pos_] != ']') {
++pos_;
}
}
}
};
} // anonymous namespace
// ============================================================
// TodoService 实现
// ============================================================
TodoService::TodoService(const std::string& filepath)
: filepath_(filepath) {
load();
}
const std::vector<TodoItem>& TodoService::get_all() const {
return items_;
}
std::optional<TodoItem> 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<TodoItem> TodoService::update(
int id,
const std::optional<std::string>& title,
const std::optional<std::string>& description,
const std::optional<bool>& 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_);
}