From 488858317e4cc9940bf358b5839e1b31fadd33ab Mon Sep 17 00:00:00 2001 From: ypc <15051963820@163.com> Date: Mon, 11 May 2026 15:43:52 +0800 Subject: [PATCH] =?UTF-8?q?<=E4=BF=AE=E6=94=B9>=201=E3=80=81=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0git=E7=AD=9B=E9=80=89=EF=BC=9B2=E3=80=81=E8=B0=83?= =?UTF-8?q?=E6=95=B4=E8=A7=A3=E6=9E=90ftu=E6=96=87=E4=BB=B6=E7=9A=84?= =?UTF-8?q?=E5=B7=A5=E5=85=B7=E7=A8=8B=E5=BA=8F=EF=BC=9B3=E3=80=81?= =?UTF-8?q?=E7=BC=96=E5=86=99=E8=A7=A3=E6=9E=90=E7=A7=81=E6=9C=89=E8=A7=84?= =?UTF-8?q?=E7=BA=A6=E9=85=8D=E7=BD=AE=E6=96=87=E4=BB=B6=E7=9A=84=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 24 + release/inc/.gitkeep | 0 release/src/makefile | 2 +- release/src/protocol/makefile | 2 +- release/src/system/makefile | 4 +- src/system/FTU_cfg_parse/inc/ftu_cfg_parse.h | 5 + .../FTU_cfg_parse/src/ftu_cfg_parse.cpp | 150 +- src/system/FTU_cfg_parse/src/make_out_cfg.cpp | 58 +- src/system/RTU/inc/self_ptl_cfg.h | 39 + src/system/RTU/src/ftu_cfg.cpp | 3 - src/system/RTU/src/self_ptl_cfg.cpp | 140 + test/file/SYSCONFIG/self_ptl.xml | 5948 ++++++++--------- 12 files changed, 3346 insertions(+), 3029 deletions(-) create mode 100644 .gitignore delete mode 100644 release/inc/.gitkeep create mode 100644 src/system/RTU/inc/self_ptl_cfg.h delete mode 100644 src/system/RTU/src/ftu_cfg.cpp create mode 100644 src/system/RTU/src/self_ptl_cfg.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..476e779 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# 只忽略编译出来的二进制、目标文件 +*.o +*.a +*.obj +*.lib +*.exe +*.out +*.elf +*.hex +*.map +*.d +*.su +*.pdb +*.idb +*.gch + +# 编译临时目录 +obj/ +build/ +lib/ +tmp/ + +# vscode 缓存 +.vscode/ diff --git a/release/inc/.gitkeep b/release/inc/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/release/src/makefile b/release/src/makefile index 7d36375..2347c9a 100644 --- a/release/src/makefile +++ b/release/src/makefile @@ -1,6 +1,6 @@ SUBDIRS := -SUBDIRS += ./public +# SUBDIRS += ./public SUBDIRS += ./protocol SUBDIRS += ./system diff --git a/release/src/protocol/makefile b/release/src/protocol/makefile index 84ed952..623f5d7 100644 --- a/release/src/protocol/makefile +++ b/release/src/protocol/makefile @@ -5,7 +5,7 @@ SUBDIRS += ./libicp67 SUBDIRS += ./libmms_m -SUBDIRS += ./libmoongoose +# SUBDIRS += ./libmoongoose diff --git a/release/src/system/makefile b/release/src/system/makefile index 2ac8766..d0d85f9 100644 --- a/release/src/system/makefile +++ b/release/src/system/makefile @@ -13,9 +13,9 @@ SUBDIRS += ./libweb_server SUBDIRS += ./RTU -SUBDIRS += ./uart_trans +# SUBDIRS += ./uart_trans -SUBDIRS += ./FTU_cfg_parse +# SUBDIRS += ./FTU_cfg_parse define make_subdir @for subdir in $(SUBDIRS); do \ diff --git a/src/system/FTU_cfg_parse/inc/ftu_cfg_parse.h b/src/system/FTU_cfg_parse/inc/ftu_cfg_parse.h index bc7f2b5..99f588d 100644 --- a/src/system/FTU_cfg_parse/inc/ftu_cfg_parse.h +++ b/src/system/FTU_cfg_parse/inc/ftu_cfg_parse.h @@ -12,6 +12,11 @@ typedef struct std::string desc; std::string type_str; std::string default_value; + std::string min; + std::string max; + std::string step; + std::string unit; + uint8_t type; uint16_t inf; }stru_param_cfg_data; diff --git a/src/system/FTU_cfg_parse/src/ftu_cfg_parse.cpp b/src/system/FTU_cfg_parse/src/ftu_cfg_parse.cpp index 46b004e..8335c8a 100644 --- a/src/system/FTU_cfg_parse/src/ftu_cfg_parse.cpp +++ b/src/system/FTU_cfg_parse/src/ftu_cfg_parse.cpp @@ -17,12 +17,16 @@ typedef struct int capacity; }stru_csv_data; -typedef struct +typedef struct { std::string saddr; std::string desc; std::string type_str; std::string default_value; + std::string min; + std::string max; + std::string step; + std::string unit; }stru_param_csv_data; @@ -41,12 +45,6 @@ typedef struct std::vector *p_info; }stru_cfg_file_info; -// LOCAL std::map *> g_gen_file_list = { -// {"st.csv", &g_ftu_cfg.st_info_vec}, -// {"mx.csv", &g_ftu_cfg.mx_info_vec}, -// {"co.csv", &g_ftu_cfg.co_info_vec}, -// {"dd.csv", &g_ftu_cfg.dd_info_vec} -// }; LOCAL std::vector g_gen_file_list = { {"st", "st.csv", &g_ftu_cfg.st_info_vec}, @@ -311,6 +309,133 @@ LOCAL void show_st_mx_co_dd_info_vec(const std::vector &i } } +LOCAL void parse_remark(const std::string &remark, std::string &min, std::string &max, std::string &step, std::string &unit) +{ + if (remark.empty()) + { + return; + } + + // 找到"范围"部分 + std::string range_part; + size_t range_pos = remark.find("\xe8\x8c\x83\xe5\x9b\xb4"); // "范围" UTF-8 + if (range_pos == std::string::npos) + { + return; + } + + std::string after_range = remark.substr(range_pos + 6); // skip "范围" + // 跳过冒号(中文:或英文:) + if (!after_range.empty() && (after_range[0] == '\xef' && after_range[1] == '\xbc' && after_range[2] == '\x9a')) + { + after_range = after_range.substr(3); // skip ":" + } + else if (!after_range.empty() && after_range[0] == ':') + { + after_range = after_range.substr(1); // skip ":" + } + + // 按";"分割,第一部分是范围,后面可能有单位和描述 + size_t semicolon = after_range.find("\xef\xbc\x9b"); // ";" UTF-8 + range_part = (semicolon != std::string::npos) ? after_range.substr(0, semicolon) : after_range; + std::string rest = (semicolon != std::string::npos) ? after_range.substr(semicolon + 3) : ""; + + // 解析范围部分 + range_part = trim(range_part); + + if (!range_part.empty()) + { + if (range_part[0] == '<') + { + // 范围:< MAX + std::string val = range_part.substr(1); + max = trim(val); + } + else if (range_part[0] == '>') + { + // 范围:> MIN + std::string val = range_part.substr(1); + min = trim(val); + } + else + { + // 范围:MIN-MAX + size_t dash = range_part.find('-'); + if (dash == std::string::npos) + { + // 尝试找 "/" 分隔的枚举值,取第一个值作为min,最后一个作为max + // 如 "电磁式100/220" + size_t slash = range_part.find('/'); + if (slash != std::string::npos) + { + // 复杂枚举,不做解析 + } + return; + } + min = trim(range_part.substr(0, dash)); + max = trim(range_part.substr(dash + 1)); + } + } + + // 从剩余部分提取单位 + if (!rest.empty()) + { + size_t unit_pos = rest.find("\xe5\x8d\x95\xe4\xbd\x8d"); // "单位" UTF-8 + if (unit_pos != std::string::npos) + { + std::string unit_str = rest.substr(unit_pos + 6); // skip "单位" + // 跳过冒号(中文:或英文:) + if (!unit_str.empty() && (unit_str[0] == '\xef' && unit_str[1] == '\xbc' && unit_str[2] == '\x9a')) + { + unit_str = unit_str.substr(3); // skip ":" + } + else if (!unit_str.empty() && unit_str[0] == ':') + { + unit_str = unit_str.substr(1); + } + // 只取到下一个";"或结尾 + size_t next_semi = unit_str.find("\xef\xbc\x9b"); + if (next_semi != std::string::npos) + { + unit_str = unit_str.substr(0, next_semi); + } + // 去掉括号中的补充说明,如 "ms(等于0时...)" -> "ms" + size_t paren = unit_str.find('('); + if (paren != std::string::npos) + { + unit_str = unit_str.substr(0, paren); + } + // 去掉"/"后的补充说明,如 "s/21600s=6h" -> "s" + size_t slash = unit_str.find('/'); + if (slash != std::string::npos) + { + unit_str = unit_str.substr(0, slash); + } + unit = trim(unit_str); + } + } + + // 如果备注中没有分号而是空格分隔,尝试匹配 "单位XXX" 直接在范围后面 + // 如 "范围: 0-60000 单位5ms" + if (unit.empty() && semicolon == std::string::npos) + { + size_t unit_pos = after_range.find("\xe5\x8d\x95\xe4\xbd\x8d"); // "单位" + if (unit_pos != std::string::npos) + { + std::string unit_str = after_range.substr(unit_pos + 6); + if (!unit_str.empty() && (unit_str[0] == '\xef' && unit_str[1] == '\xbc' && unit_str[2] == '\x9a')) + { + unit_str = unit_str.substr(3); + } + else if (!unit_str.empty() && unit_str[0] == ':') + { + unit_str = unit_str.substr(1); + } + unit = trim(unit_str); + } + } +} + LOCAL int parse_param_csv(const std::string &path) { stru_csv_data *p_csv = nullptr; @@ -331,9 +456,13 @@ LOCAL int parse_param_csv(const std::string &path) std::string saddr = row.field_vec[1]; std::string type_str = row.field_vec[2]; std::string desc = row.field_vec[3]; + std::string remark = row.field_vec[4]; std::string default_value = row.field_vec[5]; - g_param_csv_data_vec.push_back({saddr, desc, type_str, default_value}); + std::string min, max, step, unit; + parse_remark(remark, min, max, step, unit); + + g_param_csv_data_vec.push_back({saddr, desc, type_str, default_value, min, max, step, unit}); } return 0; @@ -375,7 +504,10 @@ LOCAL void parse_param_cfg_info(std::string temp, stru_param_cfg ¶m_cfg) desc = param_csv_data.desc; default_value = param_csv_data.default_value; std::string saddr_str = "self_ptl.param." + saddr; - vec_param.push_back({saddr_str, desc, type, default_value, type_info.type_id, 0}); + vec_param.push_back({saddr_str, desc, type, default_value, + param_csv_data.min, param_csv_data.max, + param_csv_data.step, param_csv_data.unit, + type_info.type_id, 0}); break; } } diff --git a/src/system/FTU_cfg_parse/src/make_out_cfg.cpp b/src/system/FTU_cfg_parse/src/make_out_cfg.cpp index 4168e9c..ba4fb06 100644 --- a/src/system/FTU_cfg_parse/src/make_out_cfg.cpp +++ b/src/system/FTU_cfg_parse/src/make_out_cfg.cpp @@ -37,9 +37,9 @@ LOCAL tinyxml2::XMLElement *add_info_element(tinyxml2::XMLDocument &doc, tinyxml // 设置desc属性 item->SetAttribute("desc", info.desc.c_str()); // 设置inf属性 - std::ostringstream oss; - oss << "0x" << std::hex << std::uppercase << std::setw(4) << std::setfill('0') << info.inf; - item->SetAttribute("inf_hex", oss.str().c_str()); + // std::ostringstream oss; + // oss << "0x" << std::hex << std::uppercase << std::setw(4) << std::setfill('0') << info.inf; + // item->SetAttribute("inf_hex", oss.str().c_str()); item->SetAttribute("inf", (int)info.inf); @@ -49,36 +49,6 @@ LOCAL tinyxml2::XMLElement *add_info_element(tinyxml2::XMLDocument &doc, tinyxml LOCAL void add_param_element(tinyxml2::XMLDocument &doc, tinyxml2::XMLElement *parent, const stru_param_cfg_data ¶m) { - // tinyxml2::XMLElement *item = doc.NewElement("Param"); - - // tinyxml2::XMLElement *saddr = doc.NewElement("saddr"); - // saddr->SetText(param.saddr.c_str()); - // item->InsertEndChild(saddr); - - // tinyxml2::XMLElement *desc = doc.NewElement("desc"); - // desc->SetText(param.desc.c_str()); - // item->InsertEndChild(desc); - - // tinyxml2::XMLElement *type_str = doc.NewElement("type_str"); - // type_str->SetText(param.type_str.c_str()); - // item->InsertEndChild(type_str); - - // tinyxml2::XMLElement *default_value = doc.NewElement("default_value"); - // default_value->SetText(param.default_value.c_str()); - // item->InsertEndChild(default_value); - - // tinyxml2::XMLElement *type_id = doc.NewElement("type_id"); - // type_id->SetText(type_id_to_str(param.type)); - // item->InsertEndChild(type_id); - - // std::ostringstream oss; - // oss << "0x" << std::hex << std::uppercase << std::setw(4) << std::setfill('0') << param.inf; - // tinyxml2::XMLElement *inf = doc.NewElement("inf"); - // inf->SetText(oss.str().c_str()); - // item->InsertEndChild(inf); - - // parent->InsertEndChild(item); - tinyxml2::XMLElement *item = doc.NewElement("Item"); // 设置 saddr 属性 @@ -87,21 +57,31 @@ LOCAL void add_param_element(tinyxml2::XMLDocument &doc, tinyxml2::XMLElement *p // 设置desc属性 item->SetAttribute("desc", param.desc.c_str()); // 设置inf属性 - std::ostringstream oss; - oss << "0x" << std::hex << std::uppercase << std::setw(4) << std::setfill('0') << param.inf; - item->SetAttribute("inf_hex", oss.str().c_str()); + // std::ostringstream oss; + // oss << "0x" << std::hex << std::uppercase << std::setw(4) << std::setfill('0') << param.inf; + // item->SetAttribute("inf_hex", oss.str().c_str()); item->SetAttribute("inf", (int)param.inf); // 设置type_str属性 - item->SetAttribute("type_str", param.type_str.c_str()); + item->SetAttribute("type", param.type_str.c_str()); // type_id属性 - item->SetAttribute("type_id", (int)param.type); + // item->SetAttribute("type_id", (int)param.type); // 设置default_value属性 - item->SetAttribute("default_value", param.default_value.c_str()); + item->SetAttribute("default", param.default_value.c_str()); + // 设置min、max、step、unit属性 + // item->SetAttribute("min", param.min.c_str()); + // item->SetAttribute("max", param.max.c_str()); + // item->SetAttribute("step", param.step.c_str()); + // item->SetAttribute("unit", param.unit.c_str()); + + item->SetAttribute("min", ""); + item->SetAttribute("max", ""); + item->SetAttribute("step", ""); + item->SetAttribute("unit", ""); parent->InsertEndChild(item); diff --git a/src/system/RTU/inc/self_ptl_cfg.h b/src/system/RTU/inc/self_ptl_cfg.h new file mode 100644 index 0000000..f5f9643 --- /dev/null +++ b/src/system/RTU/inc/self_ptl_cfg.h @@ -0,0 +1,39 @@ +#pragma once + +#include "myBase.h" +#include "mySystem.h" + +typedef struct +{ + std::string saddr; + std::string desc; + uint16_t inf; +}stru_self_ptl_cfg_base; + + +typedef struct +{ + stru_self_ptl_cfg_base base; + uint8_t type; + std::string default_value; + std::string unit; + std::string min; + std::string max; + std::string step; +}stru_self_ptl_cfg_param; + + +typedef struct +{ + std::vector st_vec; + std::vector mx_vec; + std::vector co_vec; + std::vector dd_vec; + + std::vector param_vec; +}stru_self_ptl_cfg; + + +int self_ptl_cfg_parse(const std::string &path); +stru_self_ptl_cfg* self_ptl_cfg_get(); + diff --git a/src/system/RTU/src/ftu_cfg.cpp b/src/system/RTU/src/ftu_cfg.cpp deleted file mode 100644 index d2a0bbd..0000000 --- a/src/system/RTU/src/ftu_cfg.cpp +++ /dev/null @@ -1,3 +0,0 @@ -#include "myBase.h" -#include "mySystem.h" - diff --git a/src/system/RTU/src/self_ptl_cfg.cpp b/src/system/RTU/src/self_ptl_cfg.cpp new file mode 100644 index 0000000..a4c3c41 --- /dev/null +++ b/src/system/RTU/src/self_ptl_cfg.cpp @@ -0,0 +1,140 @@ +#include "self_ptl_cfg.h" +#include "tinyxml2.h" + +LOCAL const char *ele_Root = "Root"; + +LOCAL const char *ele_St = "St"; +LOCAL const char *ele_Mx = "Mx"; +LOCAL const char *ele_Co = "Co"; +LOCAL const char *ele_Dd = "Dd"; +LOCAL const char *ele_Param = "Param"; + +LOCAL const char *ele_Item = "Item"; + +LOCAL const char *attr_count = "count"; +LOCAL const char *attr_saddr = "saddr"; +LOCAL const char *attr_desc = "desc"; +LOCAL const char *attr_inf = "inf"; +LOCAL const char *attr_type = "type"; +LOCAL const char *attr_default = "default"; +LOCAL const char *attr_unit = "unit"; +LOCAL const char *attr_min = "min"; +LOCAL const char *attr_max = "max"; +LOCAL const char *attr_step = "step"; + + + + +LOCAL stru_self_ptl_cfg g_self_ptl_cfg; + + +typedef struct +{ + std::string type; + std::vector *p_vec; +}stru_local_parse_base; + +std::vector g_local_parse_vec = +{ + {ele_St, &g_self_ptl_cfg.st_vec}, + {ele_Mx, &g_self_ptl_cfg.mx_vec}, + {ele_Co, &g_self_ptl_cfg.co_vec}, + {ele_Dd, &g_self_ptl_cfg.dd_vec}, +}; + +stru_self_ptl_cfg* self_ptl_cfg_get() +{ + return &g_self_ptl_cfg; +} + +LOCAL int parse_base(tinyxml2::XMLElement *root) +{ + if (root == nullptr) + { + MY_LOG_E("parse failed, root element is null"); + return -1; + } + + for (const auto &local_parse : g_local_parse_vec) + { + tinyxml2::XMLElement *parent_ele = root->FirstChildElement(local_parse.type.c_str()); + if (parent_ele == nullptr) + { + MY_LOG_E("not found element %s", local_parse.type.c_str()); + continue; + } + + const char *count_str = parent_ele->Attribute(attr_count); + int count = 0; + if (count_str != nullptr) + { + count = atoi(count_str); + } + else + { + MY_LOG_E("not found attribute %s in element %s", attr_count, local_parse.type.c_str()); + continue; + } + + tinyxml2::XMLElement *item_ele = parent_ele->FirstChildElement(ele_Item); + while (item_ele != nullptr) + { + stru_self_ptl_cfg_base cfg_base; + const char *saddr = item_ele->Attribute(attr_saddr); + const char *desc = item_ele->Attribute(attr_desc); + int inf = 0; + item_ele->QueryIntAttribute(attr_inf, &inf); + + if (saddr != nullptr) + { + cfg_base.saddr = saddr; + } + if (desc != nullptr) + { + cfg_base.desc = desc; + } + cfg_base.inf = inf; + + local_parse.p_vec->push_back(cfg_base); + + item_ele = item_ele->NextSiblingElement(ele_Item); + } + + if(local_parse.p_vec->size() != count) + { + MY_LOG_E("count mismatch for element %s, expected %d, actual %d", + local_parse.type.c_str(), count, (int)local_parse.p_vec->size()); + return -1; + } + } + + return 0; +} + +int self_ptl_cfg_parse(const std::string &path) +{ + // 解析XML文件 + + tinyxml2::XMLDocument doc; + if (doc.LoadFile(path.c_str()) != tinyxml2::XML_SUCCESS) + { + MY_LOG_E("Failed to load XML file: %s", path.c_str()); + return -1; + } + + tinyxml2::XMLElement *root = doc.RootElement(); + if (root == nullptr) + { + MY_LOG_E("Failed to get root element"); + return -1; + } + + if(parse_base(root) != 0) + { + MY_LOG_E("Failed to parse base elements"); + return -1; + } + + + return 0; +} diff --git a/test/file/SYSCONFIG/self_ptl.xml b/test/file/SYSCONFIG/self_ptl.xml index 0f78c74..29cb095 100644 --- a/test/file/SYSCONFIG/self_ptl.xml +++ b/test/file/SYSCONFIG/self_ptl.xml @@ -1,2987 +1,2987 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +