/**************************************************************************** ** CopyRight(c) 2023, XXXX ** All rights reserved ** ** 文件名称: calccrc.hpp ** 摘要: SCD CRC 计算算法 ** ** 当前版本: 1.0.0.0 ** 作者: ** ** 完成日期: 2023-3-18 ** ** 历史修改记录:  ** 作者 描述 修改时间 ****************************************************************************/ #pragma once #include "pugixml.hpp" #include "sformat.h" #include #include #include #include #include #include #include #include #include using namespace std; using namespace pugi; class CCalCRC { public: enum eType { eGoose = 0, eSV }; /** * @brief 计算IED CRC * @param pszSCDFile SCD文件路径 * @return IEDCRC Map */ static map getIedCrc(const char* pszSCDFile) { map mapCRC; xml_document m_scddoc; xml_parse_result parse_xml = m_scddoc.load_file(pszSCDFile, parse_full); if (parse_xml.status != status_ok) { //打开失败 //cout << "打开SCD失败!" << endl; return mapCRC; } //SCD根节点 xml_node root_scd = m_scddoc.document_element(); vector vecIED = getChildElements(root_scd, "IED"); for (xml_node node_ied : vecIED) { xml_document ccddoc; xml_node xml_pre_node = ccddoc.prepend_child(pugi::node_declaration); xml_pre_node.append_attribute("version") = "1.0"; xml_pre_node.append_attribute("encoding") = "UTF-8"; xml_node root_ccd = ccddoc.append_child("IED"); //CCD中IED原始,设置name属性 string iedName = node_ied.attribute("name").as_string(); root_ccd.append_attribute("configVersion") = node_ied.attribute("configVersion").as_string(); root_ccd.append_attribute("desc") = node_ied.attribute("desc").as_string(); root_ccd.append_attribute("manufacturer") = node_ied.attribute("manufacturer").as_string(); root_ccd.append_attribute("name") = node_ied.attribute("name").as_string(); root_ccd.append_attribute("type") = node_ied.attribute("type").as_string(); bool bIsEmpty = createPubAndSubNode(root_ccd, root_scd, node_ied); if (bIsEmpty) continue; //删除私有属性 cutNodeDesc(root_ccd); //按格式保存临时文件 string strTempFile = string(pszSCDFile) + ".ccdtmp.txt"; ccddoc.save_file(strTempFile.data()); string serial; //剔除冗余空字符 ifstream in(strTempFile); string strline; while (getline(in, strline)) { trim(strline); strline = remove_spaces_except_in_quotes(strline); serial += strline; } in.close(); remove(strTempFile.data()); int crc = 0xffffffff; crc = crcbyTable(crc, serial); string crcValue = dec2hex(crc); for (size_t i = crcValue.size(); i < 8; i++) crcValue.insert(0, "0"); //输出IED CRC //cout << iedName << " : " << crcValue << endl; mapCRC[iedName] = crcValue; } return mapCRC; } /** * @brief 计算单IED CRC * @param pszSCDFile SCD文件路径 * @param pszIEDName IED名称 * @return CRC值 */ static string getIedCrc(const char* pszSCDFile, const char* pszIEDName) { xml_document m_scddoc; xml_parse_result parse_xml = m_scddoc.load_file(pszSCDFile, parse_full); if (parse_xml.status != status_ok) { //打开失败 //cout << "打开SCD失败!" << endl; return ""; } //SCD根节点 xml_node root_scd = m_scddoc.document_element(); xpath_node_set set_IED = root_scd.select_nodes(SFormat("/SCL/IED[@name='{0}']", pszIEDName).data()); //GOOSE或SV控制块存在时,则认为发布内容存在 if (set_IED.empty()) return ""; xml_node node_ied = set_IED[0].node(); xml_document ccddoc; xml_node xml_pre_node = ccddoc.prepend_child(pugi::node_declaration); xml_pre_node.append_attribute("version") = "1.0"; xml_pre_node.append_attribute("encoding") = "UTF-8"; xml_node root_ccd = ccddoc.append_child("IED"); //CCD中IED原始,设置name属性 root_ccd.append_attribute("configVersion") = node_ied.attribute("configVersion").as_string(); root_ccd.append_attribute("desc") = node_ied.attribute("desc").as_string(); root_ccd.append_attribute("manufacturer") = node_ied.attribute("manufacturer").as_string(); root_ccd.append_attribute("name") = node_ied.attribute("name").as_string(); root_ccd.append_attribute("type") = node_ied.attribute("type").as_string(); bool bIsEmpty = createPubAndSubNode(root_ccd, root_scd, node_ied); if (bIsEmpty) return ""; //删除私有属性 cutNodeDesc(root_ccd); //按格式保存临时文件 string strTempFile = string(pszSCDFile) + ".ccdtmp.txt"; ccddoc.save_file(strTempFile.data()); string serial; //剔除冗余空字符 ifstream in(strTempFile); string strline; while (getline(in, strline)) { trim(strline); strline = remove_spaces_except_in_quotes(strline); serial += strline; } in.close(); remove(strTempFile.data()); int crc = 0xffffffff; crc = crcbyTable(crc, serial); string crcValue = dec2hex(crc); for (size_t i = crcValue.size(); i < 8; i++) crcValue.insert(0, "0"); return crcValue; } /** * @brief 导出CCD * @param pszSCDFile SCD文件路径 * @param pszIEDName IED名称 * @param pszExportDir 导出CCD目录 * @return CRC值 */ static void exportIedCCD(const char* pszSCDFile, const char* pszIEDName, const char* pszExportDir) { xml_document m_scddoc; xml_parse_result parse_xml = m_scddoc.load_file(pszSCDFile, parse_full); if (parse_xml.status != status_ok) { //打开失败 //cout << "打开SCD失败!" << endl; return; } //SCD根节点 xml_node root_scd = m_scddoc.document_element(); xpath_node_set set_IED = root_scd.select_nodes(SFormat("/SCL/IED[@name='{0}']", pszIEDName).data()); //GOOSE或SV控制块存在时,则认为发布内容存在 if (set_IED.empty()) return; xml_node node_ied = set_IED[0].node(); xml_document ccddoc; xml_node xml_pre_node = ccddoc.prepend_child(pugi::node_declaration); xml_pre_node.append_attribute("version") = "1.0"; xml_pre_node.append_attribute("encoding") = "UTF-8"; xml_node root_ccd = ccddoc.append_child("IED"); //CCD中IED原始,设置name属性 string iedName = node_ied.attribute("name").as_string(); root_ccd.append_attribute("configVersion") = node_ied.attribute("configVersion").as_string(); root_ccd.append_attribute("desc") = node_ied.attribute("desc").as_string(); root_ccd.append_attribute("manufacturer") = node_ied.attribute("manufacturer").as_string(); root_ccd.append_attribute("name") = node_ied.attribute("name").as_string(); root_ccd.append_attribute("type") = node_ied.attribute("type").as_string(); bool bIsEmpty = createPubAndSubNode(root_ccd, root_scd, node_ied); if (bIsEmpty) return; xml_document ccddoctmp; xml_node xml_pre_nodetmp = ccddoctmp.prepend_child(pugi::node_declaration); xml_pre_nodetmp.append_attribute("version") = "1.0"; xml_pre_nodetmp.append_attribute("encoding") = "UTF-8"; xml_node root_ccdtmp = ccddoctmp.append_copy(root_ccd); //删除私有属性 cutNodeDesc(root_ccdtmp); //按格式保存临时文件 string strTempFile = string(pszSCDFile) + ".ccdtmp.txt"; ccddoctmp.save_file(strTempFile.data()); string serial; //剔除冗余空字符 ifstream in(strTempFile); string strline; while (getline(in, strline)) { trim(strline); strline = remove_spaces_except_in_quotes(strline); serial += strline; } in.close(); remove(strTempFile.data()); int crc = 0xffffffff; crc = crcbyTable(crc, serial); string crcValue = dec2hex(crc); for (size_t i = crcValue.size(); i < 8; i++) crcValue.insert(0, "0"); xml_node node_crc = root_ccd.append_child("CRC"); node_crc.append_attribute("id") = crcValue.data(); //获取当前时间 std::time_t t = std::time(nullptr); char buffer[100]; std::strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", std::localtime(&t)); string currenttime(buffer); node_crc.append_attribute("timestamp") = currenttime.data(); //std::chrono::system_clock::time_point now = std::chrono::system_clock::now(); //std::time_t now_time = std::chrono::system_clock::to_time_t(now); //std::tm* now_tm = std::localtime(&now_time); //std::stringstream ss; //ss << std::put_time(now_tm, "%Y-%m-%d %H:%M:%S"); //node_crc.append_attribute("timestamp") = ss.str().data(); string strExportFile = pszExportDir; strExportFile = strExportFile + "/" + iedName + ".ccd"; ccddoc.save_file(strExportFile.data()); } /** * @brief 计算SCD CRC * @param mapCRC 各个IED的CRC值列表 * @return CRC值 */ static string getScdCRC(map mapCRC) { string SCDBuffer; for (auto it : mapCRC) SCDBuffer += it.second; int crc = 0xffffffff; crc = crcbyTable(crc, SCDBuffer); string crcValue = dec2hex(crc); for (size_t i = crcValue.size(); i < 8; i++) crcValue.insert(0, "0"); return crcValue; } /** * @brief 计算字符串的CRC值 * @param crc 初始值 * @param data 字符串 * @return CRC值 */ static unsigned int crcbyTable(unsigned int crc, string data) { unsigned int table[256] = { 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d }; size_t length = data.length(); unsigned char ch; for (int i = 0; i < length; i++) { ch = data.at(i); crc = (crc >> 8) ^ table[(crc ^ ch) & 0xff]; } crc = crc ^ 0xffffffff; return crc; } //将int转成16进制字符串 static string dec2hex(int i) { stringstream ioss; //定义字符串流 string s_temp; //存放转化后字符 ioss << setiosflags(ios::uppercase) << hex << i; //以十六制(大写)形式输出 ioss >> s_temp; return s_temp; } private: /** * @brief 创建 PubAndSubNode * @param root_ccd CCD根节点 * @param root_scd SCD根节点 * @param node_IED IED根节点 * @return 是否存在内容 */ static bool createPubAndSubNode(xml_node root_ccd, xml_node root_scd, xml_node node_ied) { xml_node nodeGoosePub = root_ccd.append_child("GOOSEPUB"); xml_node nodeGooseSub = root_ccd.append_child("GOOSESUB"); xml_node nodeSvPub = root_ccd.append_child("SVPUB"); xml_node nodeSvSub = root_ccd.append_child("SVSUB"); bool emptyflag = createPublishNode(nodeGoosePub, nodeSvPub, root_scd, node_ied); emptyflag &= createSubscriptionNode(nodeGooseSub, nodeSvSub, root_scd, node_ied); //内容为空时删除 if (nodeGoosePub.first_child().empty()) root_ccd.remove_child(nodeGoosePub); if (nodeSvPub.first_child().empty()) root_ccd.remove_child(nodeSvPub); if (nodeGooseSub.first_child().empty()) root_ccd.remove_child(nodeGooseSub); if (nodeSvSub.first_child().empty()) root_ccd.remove_child(nodeSvSub); return emptyflag; } /** * @brief 创建 GOOSEPUB和SVPUB * @param nodeGoosePub GOOSEPUB节点 * @param nodeSvPub SVPUB节点 * @param node_scd SCD根节点 * @param node_ied IED根节点 * @return 是否存在内容 */ static bool createPublishNode(xml_node nodeGoosePub, xml_node nodeSvPub, xml_node node_scd, xml_node node_ied) { bool emptyFlag = true; string iedName = node_ied.attribute("name").as_string(); vector vecAcceptPoint = getChildElements(node_ied, "AccessPoint"); //变量IED下访问的 for (xml_node node_AcceptPoint : vecAcceptPoint) { xpath_node_set setControl_goose = node_AcceptPoint.select_nodes("./Server/LDevice/LN0/GSEControl"); xpath_node_set setControl_sample = node_AcceptPoint.select_nodes("./Server/LDevice/LN0/SampledValueControl"); //GOOSE或SV控制块存在时,则认为发布内容存在 if (!setControl_goose.empty() || !setControl_sample.empty()) emptyFlag = false; addControlForPub(nodeGoosePub, setControl_goose, eGoose, node_scd); addControlForPub(nodeSvPub, setControl_sample, eSV, node_scd); } return emptyFlag; } /** * @brief 创建 GOOSEPUB/SVPUB * @param nodePub_CCD GOOSEPUB或SVPUB节点 * @param setControl GSE/SMV访问点 * @param pubType GSE/SMV * @param node_scd SCD根节点 * @return void */ static void addControlForPub(xml_node nodePub_CCD, xpath_node_set setControl, eType pubType, xml_node node_scd) { string refnameAttr, instAttr, nameAttr, datSetAttr, iedName, apName; for (int jj = 0; jj < setControl.size(); jj++) { xml_node node_Control = setControl[jj].node(); xml_node node_LN0 = node_Control.parent(); xml_node node_LDvice = node_LN0.parent(); xml_node node_Server = node_LDvice.parent(); xml_node node_AccessPoint = node_Server.parent(); xml_node node_IED = node_AccessPoint.parent(); string nameAttr = node_Control.attribute("name").as_string(); string datSetAttr = node_Control.attribute("datSet").as_string(); string instAttr = node_LDvice.attribute("inst").as_string(); string apName = node_AccessPoint.attribute("name").as_string(); string iedName = node_IED.attribute("name").as_string(); string strCBName; //计算控制块refernece if (pubType == eGoose) { strCBName = "GOCBref"; refnameAttr = iedName + instAttr + "/LLN0$GO$" + nameAttr; } else if (pubType == eSV) { strCBName = "SMVCBref"; refnameAttr = iedName + instAttr + "/LLN0$MS$" + nameAttr; } xml_node nodeCBref = nodePub_CCD.append_child(strCBName.data()); nodeCBref.append_attribute("name") = refnameAttr.data(); //添加GSEControl/SampledValueControl insertNodeSorted(nodeCBref, node_Control); //添加ConnectedAP addConnectedAPForPub(node_scd, nodeCBref, iedName, apName, nameAttr, instAttr, pubType); //添加数据集 addFCDAForPub(node_scd, node_LN0, nodeCBref, datSetAttr, pubType); } } /** * @brief 创建 GOOSEPUB/SVPUB的ConnectedAP * @param node_scd scd根节点 * @param nodeCBref GOCBref或SMVCBref * @param iedName apName nameAttrldinst 索引属性 * @param pubType GOOSE/SV */ static void addConnectedAPForPub(xml_node node_scd, xml_node nodeCBref, string iedName, string apName, string nameAttr, string ldinst, eType pubType) { string pathCAP; if (pubType == eGoose) { pathCAP = SFormat("/SCL/Communication/SubNetwork/ConnectedAP[@iedName='{0}' and @apName='{1}']/GSE[@cbName='{2}' and @ldInst='{3}']", iedName, apName, nameAttr, ldinst); } else if (pubType == eSV) { pathCAP = SFormat("/SCL/Communication/SubNetwork/ConnectedAP[@iedName='{0}' and @apName='{1}']/SMV[@cbName='{2}' and @ldInst='{3}']", iedName, apName, nameAttr, ldinst); } xpath_node_set setConnectedAP = node_scd.select_nodes(pathCAP.data()); if (!setConnectedAP.empty()) { //添加ConnectedAP xml_node node_ConnectedAP = setConnectedAP[0].parent(); insertNodeSorted(nodeCBref, node_ConnectedAP); xml_node ConnectedAP = nodeCBref.child("ConnectedAP"); ConnectedAP.remove_attribute("desc"); //删除ConnectedAP中其他的控制块 xpath_node_set GSE_SMVSet; if (pubType == eGoose) GSE_SMVSet = ConnectedAP.select_nodes(SFormat("GSE[@cbName!='{0}' or @ldInst!='{1}']", nameAttr, ldinst).data()); else if (pubType == eSV) GSE_SMVSet = ConnectedAP.select_nodes(SFormat("SMV[@cbName!='{0}' or @ldInst!='{1}']", nameAttr, ldinst).data()); for (int i = 0; i < GSE_SMVSet.size(); i++) removeChildNode(GSE_SMVSet[i].node()); xpath_node_set AddressSet = ConnectedAP.select_nodes("./Address"); for (int i = 0; i < AddressSet.size(); i++) removeChildNode(AddressSet[i].node()); } } /** * @brief GOOSEPUB/SVPUB的dataset和FCDA * @param node_scd scd根节点 * @param node_LN0 数据集归属LN0 * @param nodeCBref GOCBref或SMVCBref * @param datSetAttr 数据集名称 * @param pubType GOOSE/SV */ static void addFCDAForPub(xml_node node_scd, xml_node node_LN0, xml_node nodeCBref, string datSetAttr, eType pubType) { string pathDataSet = SFormat("./DataSet[@name='{0}']", datSetAttr); xpath_node_set setDataSet = node_LN0.select_nodes(pathDataSet.data()); if (setDataSet.empty()) return; xml_node nodeDataTypeTemplate = node_scd.child("DataTypeTemplates"); xml_node node_LDvice = node_LN0.parent(); xml_node node_DataSet = setDataSet[0].node(); vector vec_FCDA = getChildElements(node_DataSet, "FCDA"); xml_node nodeDataSet = nodeCBref.append_child("DataSet"); insertNodeAttr(node_DataSet, nodeDataSet); string prefix, lnInst, lnClass, doname, daName; for (xml_node node_FCDA : vec_FCDA) { prefix = node_FCDA.attribute("prefix").as_string(); lnInst = node_FCDA.attribute("lnInst").as_string(); lnClass = node_FCDA.attribute("lnClass").as_string(); doname = node_FCDA.attribute("doName").as_string(); daName = node_FCDA.attribute("daName").as_string(); xml_node nodeFCDA = nodeDataSet.append_child("FCDA"); vector donamelist = stringSplit(doname, '.'); if (donamelist.size() == 0) continue; //FCDA属性 map mapAttrs = getNodeAttributes(node_FCDA); if (daName != "") mapAttrs["bType"] = calc_bType(daName, doname, prefix, lnInst, lnClass, node_LDvice, nodeDataTypeTemplate); string pathDOI = SFormat("./LN0[not(@prefix) or @prefix='{0}'][@inst='{1}'][@lnClass='{2}']/DOI[@name='{3}']|./LN[not(@prefix) or @prefix='{0}'][@inst='{1}'][@lnClass='{2}']/DOI[@name='{3}']", prefix, lnInst, lnClass, donamelist.at(0)); if (!prefix.empty()) replace_all(pathDOI, "not(@prefix) or ", ""); if ("" == lnInst) replace_all(pathDOI, "[@lnInst='']", ""); if (daName.empty()) //正常SV { xpath_node_set set_DOI = node_LDvice.select_nodes(pathDOI.data()); if (set_DOI.size() > 0) { mapAttrs["desc"] = set_DOI[0].node().attribute("desc").as_string(); insertNodeSorted(nodeFCDA, set_DOI[0].node()); } } else//正常GOOSE { xpath_node_set set_DOI = node_LDvice.select_nodes(pathDOI.data()); if (set_DOI.size() > 0) { xml_node node_DOI = set_DOI[0].node(); mapAttrs["desc"] = node_DOI.attribute("desc").as_string(); //考虑doname中有.的情况 string pathDAI = string("."); for (int i = 1; i < donamelist.size(); i++) { string xpath = SFormat("/SDI[@name='{0}']", donamelist.at(i)); pathDAI += xpath; } vector danamelist = stringSplit(daName, '.'); for (int i = 0; i < danamelist.size(); i++) { if (i == danamelist.size() - 1) pathDAI += SFormat("/DAI[@name='{0}']", danamelist.at(i)); else pathDAI += SFormat("/SDI[@name='{0}']", danamelist.at(i)); } xpath_node_set set_DAI = node_DOI.select_nodes(pathDAI.data()); if (set_DAI.size() > 0) { xml_node node_DAI = set_DAI[0].node(); string daidesc = node_DAI.attribute("desc").as_string(); if (!daidesc.empty()) mapAttrs["desc"] = daidesc; insertNodeSorted(nodeFCDA, node_DAI); } } } for (auto& it : mapAttrs) { if (it.first.data() == "dU" || it.first.data() == "desc") continue; nodeFCDA.append_attribute(it.first.data()) = it.second.data(); } } } /** * @brief 创建 GOOSESUB和SVSUB * @param nodeGooseSub GOOSESUB节点 * @param nodeSvSub SVSUB节点 * @param node_scd SCD根节点 * @param node_ied IED根节点 * @return 是否存在内容 */ static bool createSubscriptionNode(xml_node nodeGooseSub, xml_node nodeSvSub, xml_node node_scd, xml_node node_ied) { xml_node nodeDataTypeTemplate = node_scd.child("DataTypeTemplates"); //遍历虚回路 string path_ExtRef = "./AccessPoint/Server/LDevice/*[self::LN0 or self::LN]/Inputs/ExtRef[@intAddr!='']"; xpath_node_set set_ExtRef = node_ied.select_nodes(path_ExtRef.data()); if (set_ExtRef.empty()) return true; multimap mapInputs; //记录已添加控制块标识 vector GOCBrefName_GOOSE, GOCBrefName_SV; string refnameAttr; for (int d = 0; d < set_ExtRef.size(); d++) { xml_node node_ExtRef = set_ExtRef[d].node(); string key = string(node_ExtRef.attribute("prefix").as_string()) + node_ExtRef.attribute("ldInst").as_string() + node_ExtRef.attribute("iedName").as_string() + node_ExtRef.attribute("lnClass").as_string() + node_ExtRef.attribute("lnInst").as_string() + node_ExtRef.attribute("doName").as_string() + node_ExtRef.attribute("daName").as_string(); mapInputs.insert(make_pair(key, node_ExtRef)); } //遍历关联记录 for (int d = 0; d < set_ExtRef.size(); d++) { xml_node node_ExtRef = set_ExtRef[d].node(); string iedNameAttr = node_ExtRef.attribute("iedName").as_string(); string ldInstAttr = node_ExtRef.attribute("ldInst").as_string(); string prefixAttr = node_ExtRef.attribute("prefix").as_string(); string lnInstAttr = node_ExtRef.attribute("lnInst").as_string(); string lnClassAttr = node_ExtRef.attribute("lnClass").as_string(); string doNameAttr = node_ExtRef.attribute("doName").as_string(); string daNameAttr = node_ExtRef.attribute("daName").as_string(); string key = iedNameAttr + ldInstAttr; //查找关联的IED string path_PubLDevice = SFormat("/SCL/IED[@name='{0}']/AccessPoint/Server/LDevice[@inst='{1}']", iedNameAttr, ldInstAttr); xpath_node_set set_PubLDevice = node_scd.select_nodes(path_PubLDevice.data()); if (set_PubLDevice.empty()) continue; //查找该关联IED下的FCDA信息 xml_node node_PubLDevice = set_PubLDevice[0].node(); string path_dataset; path_dataset = SFormat("../LDevice/LN0/DataSet/FCDA[@ldInst='{0}'][@prefix='{1}'][@lnClass='{2}'][@lnInst='{3}'][@doName='{4}'][@daName='{5}']", ldInstAttr, prefixAttr, lnClassAttr, lnInstAttr, doNameAttr, daNameAttr); if ("" == prefixAttr) replace_all(path_dataset, "[@prefix='']", "[not(@prefix) or @prefix='']"); if ("" == lnInstAttr) replace_all(path_dataset, "[@lnInst='']", "[not(@lnInst) or @lnInst='']"); if ("" == daNameAttr) replace_all(path_dataset, "[@daName='']", ""); xpath_node_set set_Pubfcda = node_PubLDevice.select_nodes(path_dataset.data()); //添加control addControlForSub(set_Pubfcda, nodeGooseSub, node_ied, eGoose, node_PubLDevice, GOCBrefName_GOOSE, mapInputs); addControlForSub(set_Pubfcda, nodeSvSub, node_ied, eSV, node_PubLDevice, GOCBrefName_SV, mapInputs); } return false; } /** * @brief 创建 GOOSESUB/SVSUB * @param set_Pubfcda 发布端数据集成员列表 * @param nodeSub GOOSESUB或SVSUB * @param node_SubIED 订阅IED,即需要导出的IED * @param pubType GOOSE/SV * @param node_PubLDevice 发布端逻辑设备 * @param GOCBrefName 已添加控制块标识 * @param mapInputs ExtRef记录Map */ static void addControlForSub(xpath_node_set set_Pubfcda, xml_node nodeSub, xml_node node_SubIED, eType pubType, xml_node node_PubLDevice, vector& GOCBrefName, const multimap& mapInputs) { if (set_Pubfcda.empty()) return; //根据该记录的数据块的名称,查找对应的控制块 vector set_PubControlALL; vector datasetNameAttrList; vector datasetList; string strCtrlName = pubType == eGoose ? "GSEControl" : "SampledValueControl"; //解析控制块信息 for (int d = 0; d < set_Pubfcda.size(); d++) { xml_node node_Pubfcda = set_Pubfcda[d].node(); string datasetNameAttr = node_Pubfcda.parent().attribute("name").as_string(); if (count(datasetList.begin(), datasetList.end(), datasetNameAttr)) continue; datasetList.push_back(datasetNameAttr); string pathControl = SFormat("./LN0/{0}[@datSet='{1}']", strCtrlName, datasetNameAttr); xpath_node_set set_PubControl = node_PubLDevice.select_nodes(pathControl.data()); if (set_PubControl.empty()) { pathControl = SFormat("../LDevice/{0}", pathControl); set_PubControl = node_PubLDevice.select_nodes(pathControl.data()); } if (set_PubControl.empty()) continue; xml_node node_PubControl = set_PubControl[0].node(); if (!count(set_PubControlALL.begin(), set_PubControlALL.end(), node_PubControl)) { set_PubControlALL.push_back(node_PubControl); datasetNameAttrList.push_back(datasetNameAttr); } } if (set_PubControlALL.empty()) return; for (int i = 0; i < set_PubControlALL.size(); i++) { xml_node node_PubControl = set_PubControlALL.at(i); string datasetNameAttr = datasetNameAttrList.at(i); xml_node node_LN0 = node_PubControl.parent(); node_PubLDevice = node_LN0.parent(); xml_node node_Server = node_PubLDevice.parent(); xml_node node_AccessPoint = node_Server.parent(); xml_node node_IED = node_AccessPoint.parent(); xml_node nodeRoot = node_IED.parent(); string nameAttr = node_PubControl.attribute("name").as_string(); string ldInstAttr = node_PubLDevice.attribute("inst").as_string(); string iedNameAttr = node_IED.attribute("name").as_string(); string refnameAttr = ""; if (pubType == eSV) refnameAttr = iedNameAttr + ldInstAttr + "/LLN0$MS$" + nameAttr; else refnameAttr = iedNameAttr + ldInstAttr + "/LLN0$GO$" + nameAttr; //添加过则不再添加 if (count(GOCBrefName.begin(), GOCBrefName.end(), refnameAttr)) return; GOCBrefName.push_back(refnameAttr); //添加节点 string strBrefName = pubType == eGoose ? "GOCBref" : "SMVCBref"; xml_node nodeCBref = nodeSub.append_child(strBrefName.data()); nodeCBref.append_attribute("name") = refnameAttr.data(); insertNodeSorted(nodeCBref, node_PubControl); //去除Private属性 xml_node GSEControl = nodeCBref.child(strCtrlName.data()); xml_node privatenode = nodeCBref.child("Private"); if (!privatenode.empty()) GSEControl.remove_child(privatenode); //添加ConnectedAP addConnectedAPForSub(nodeRoot, nodeCBref, iedNameAttr, nameAttr, ldInstAttr, pubType); //添加DataSet和对应的每一条FCDA记录 addFCDAForSub(nodeRoot, node_PubLDevice, node_SubIED, nodeCBref, datasetNameAttr, mapInputs, pubType); } } /** * @brief 创建 GOOSESUB/SVSUB的ConnectedAP * @param node_scd scd根节点 * @param nodeCBref GOCBref或SMVCBref * @param iedName apName nameAttrldinst 索引属性 * @param pubType GOOSE/SV */ static void addConnectedAPForSub(xml_node node_scd, xml_node nodeCBref, string iedName, string cbName, string ldInstAttr, eType pubType) { //与发布相比,仅保留GSE/SMV子元素 string strCtrlName = pubType == eGoose ? "GSE" : "SMV"; string path_PubGS = SFormat("/SCL/Communication/SubNetwork/ConnectedAP[@iedName='{0}']/{1}[@cbName='{2}' and @ldInst='{3}']", iedName, strCtrlName, cbName, ldInstAttr); xpath_node_set set_PubGS = node_scd.select_nodes(path_PubGS.data()); if (!set_PubGS.empty()) { xml_node PubConnectedAP = nodeCBref.append_child("ConnectedAP"); xml_node node_PubConnectedCtl = set_PubGS[0].node(); insertNodeAttr(set_PubGS[0].node().parent(), PubConnectedAP); insertNodeSorted(PubConnectedAP, node_PubConnectedCtl); } } /** * @brief GOOSESUB/SVSUB的FCDA * @param node_scd scd根节点 * @param node_PubLDevice 发布的LDevice节点 * @param node_SubIED 订阅的IED节点 * @param nodeCBref GOCBref或SMVCBref * @param datasetNameAttr 数据集名称 * @param mapInputs ExtRef记录Map * @param pubType GOOSE/SV */ static void addFCDAForSub(xml_node node_scd, xml_node node_PubLDevice, xml_node node_SubIED, xml_node nodeCBref, string datasetNameAttr, const multimap& hashInputs, eType pubType) { //添加DataSet string path_fcda = SFormat("./LN0/DataSet[@name='{0}']", datasetNameAttr); xpath_node_set node_PubDataSets = node_PubLDevice.select_nodes(path_fcda.data()); if (node_PubDataSets.empty()) return; xml_node node_PubDataSet = node_PubDataSets[0].node(); xml_node nodeDataSet = nodeCBref.append_child("DataSet"); insertNodeAttr(node_PubDataSet, nodeDataSet); //记录IED信息 xml_node nodeDataTypeTemplate = node_scd.child("DataTypeTemplates"); xml_node node_Server = node_PubLDevice.parent(); xml_node node_AccessPoint = node_Server.parent(); xml_node node_IED = node_AccessPoint.parent(); string iedNameAttr = node_IED.attribute("name").as_string(); //添加每一条FCDA vector vec_PubFCDA = getChildElements(node_PubDataSet, "FCDA"); for (xml_node node_PubFCDA : vec_PubFCDA) { string prefix = node_PubFCDA.attribute("prefix").as_string(); string ldInst = node_PubFCDA.attribute("ldInst").as_string(); string lnInst = node_PubFCDA.attribute("lnInst").as_string(); string lnClass = node_PubFCDA.attribute("lnClass").as_string(); string doname = node_PubFCDA.attribute("doName").as_string(); string daName = node_PubFCDA.attribute("daName").as_string(); xml_node nodeFCDA = nodeDataSet.append_child("FCDA"); //计算FCDA的bType string stype; map mapAttrs = getNodeAttributes(node_PubFCDA); if (!daName.empty()) mapAttrs["bType"] = calc_bType(daName, doname, prefix, lnInst, lnClass, node_PubLDevice, nodeDataTypeTemplate); vector donamelist = stringSplit(doname, '.'); if (!donamelist.empty()) { string pathDOI = SFormat("./LN0[not(@prefix) or @prefix='{0}'][@inst='{1}'][@lnClass='{2}']/DOI[@name='{3}']|./LN[not(@prefix) or @prefix='{0}'][@inst='{1}'][@lnClass='{2}']/DOI[@name='{3}']", prefix, lnInst, lnClass, donamelist.at(0)); if (!prefix.empty()) replace_all(pathDOI, "not(@prefix) or ", ""); if ("" == lnInst) replace_all(pathDOI, "[@lnInst='']", ""); xpath_node_set set_DOI = node_PubLDevice.select_nodes(pathDOI.data()); if (set_DOI.size() > 0) { xml_node node_DOI = set_DOI[0].node(); mapAttrs["desc"] = node_DOI.attribute("desc").as_string(); string pathDAI = string("."); for (int i = 1; i < donamelist.size(); i++) { string xpath = SFormat("/SDI[@name='{0}']", donamelist.at(i)); pathDAI += xpath; } vector danamelist = stringSplit(daName, '.'); for (int i = 0; i < danamelist.size(); i++) { if (i == danamelist.size() - 1) pathDAI += SFormat("/DAI[@name='{0}']", danamelist.at(i)); else pathDAI += SFormat("/SDI[@name='{0}']", danamelist.at(i)); } xpath_node_set set_DAI = node_DOI.select_nodes(pathDAI.data()); if (set_DAI.size() > 0) { xml_node node_DAI = set_DAI[0].node(); string daidesc = node_DAI.attribute("desc").as_string(); if (!daidesc.empty()) mapAttrs["desc"] = daidesc; } } } for (auto& it : mapAttrs) { if (it.first.data() == "dU" || it.first.data() == "desc") continue; nodeFCDA.append_attribute(it.first.data()) = it.second.data(); } //每一条FCDA记录在订阅装置中 string mapKey = prefix + ldInst + iedNameAttr + lnClass + lnInst + doname + daName; auto node_hash_ExtRefSet = hashInputs.equal_range(mapKey); for (auto k = node_hash_ExtRefSet.first; k != node_hash_ExtRefSet.second; k++) { xml_node node_hash_ExtRef = k->second; string intAddrAttr = node_hash_ExtRef.attribute("intAddr").as_string(); vector lstTemp = stringSplit(intAddrAttr, '.'); if (lstTemp.size() < 2) { if (!nodeFCDA.child("intAddr").empty()) { xml_node node_intAddr = nodeFCDA.append_child("intAddr"); node_intAddr.append_attribute("desc") = ""; node_intAddr.append_attribute("name") = ""; } continue; } string strLD_LN = lstTemp.at(0); if (strLD_LN.find(':') != string::npos) { strLD_LN = stringSplit(strLD_LN, ':').at(1); } string LDevice_name = stringSplit(strLD_LN, '/').at(0); string LN_prefix_lnClass_inst = stringSplit(strLD_LN, '/').at(1);; string DOI_name = lstTemp.at(1); vector daList; for (int j = 2; j < lstTemp.size(); j++) daList.push_back(lstTemp.at(j)); xml_node node_intAddr = nodeFCDA.append_child("intAddr"); string pathLDevice = SFormat("./AccessPoint/Server/LDevice[@inst='{0}']", LDevice_name); xpath_node_set set_LDevice = node_SubIED.select_nodes(pathLDevice.data()); if (set_LDevice.empty()) continue; vector vec_LN = getChildElements(set_LDevice[0].node(), "LN"); xml_node vec_LN0 = set_LDevice[0].node().child("LN0"); vec_LN.push_back(vec_LN0); //ln0合并 for (xml_node node_LN : vec_LN) { string LN_pll = string(node_LN.attribute("prefix").as_string()) + node_LN.attribute("lnClass").as_string() + node_LN.attribute("inst").as_string(); if (LN_pll == LN_prefix_lnClass_inst) { string pathDOI = SFormat("./DOI[@name='{0}']", DOI_name); xpath_node_set set_DOI = node_LN.select_nodes(pathDOI.data()); if (set_DOI.size() > 0) { xml_node node_DOI = set_DOI[0].node(); if (daList.empty()) //SV { node_intAddr.append_attribute("desc") = node_DOI.attribute("desc").as_string(); node_intAddr.append_attribute("name") = intAddrAttr.data(); insertNodeSorted(node_intAddr, set_DOI[0].node()); } else //GOOSE { string pathDAI = string("."); for (int i = 0; i < daList.size(); i++) { string xpath = SFormat("/SDI[@name='{0}']", daList.at(i)); if (i == daList.size() - 1) xpath = SFormat("/DAI[@name='{0}']", daList.at(i)); pathDAI += xpath; } xpath_node_set set_DAI = node_DOI.select_nodes(pathDAI.data()); if (set_DAI.size() > 0) { xml_node node_DAI = set_DAI[0].node(); string strDaiDesc = node_DAI.attribute("desc").as_string(); if(!strDaiDesc.empty()) node_intAddr.append_attribute("desc") = strDaiDesc.data(); else node_intAddr.append_attribute("desc") = node_DOI.attribute("desc").as_string(); node_intAddr.append_attribute("name") = intAddrAttr.data(); insertNodeSorted(node_intAddr, node_DAI); } else { node_intAddr.append_attribute("desc") = node_DOI.attribute("desc").as_string(); node_intAddr.append_attribute("name") = intAddrAttr.data(); } } } } } } if (hashInputs.count(mapKey) == 0) { xml_node node_intAddr = nodeFCDA.append_child("intAddr"); node_intAddr.append_attribute("desc") = ""; node_intAddr.append_attribute("name") = "NULL"; continue; } } } //计算FCDA节点的 bType 属性 static string calc_bType(string daName, string doname, string prefix, string lnInst, string lnClass, xml_node nodeLDevice, xml_node nodeDataTypeTemplate) { string bType = "BOOLEAN"; string typeAttr = ""; /*if (daName == "q") { bType = "Quality"; } else if (daName == "t") { bType = "Timestamp"; } else if (endWith(daName, ".f")) { bType = "FLOAT32"; } else */ { if (!doname.empty()) { string lnType = ""; string pathLN; pathLN = SFormat("./LN0[not(@prefix) or @prefix='{0}'][@inst='{1}'][@lnClass='{2}']|./LN[not(@prefix) or @prefix='{0}'][@inst='{1}'][@lnClass='{2}']", prefix, lnInst, lnClass); if (!prefix.empty()) replace_all(pathLN, "not(@prefix) or ", ""); if ("" == lnInst) replace_all(pathLN, "[@lnInst='']", ""); xpath_node_set set_LN = nodeLDevice.select_nodes(pathLN.data()); if (set_LN.size() > 0) { xml_node nodeLN = set_LN[0].node(); lnType = nodeLN.attribute("lnType").as_string(); if (doname.find('.') == string::npos) { string pathDTTDO = SFormat("./LNodeType[@id='{0}']/DO[@name='{1}']", lnType, doname); xpath_node_set setDTTDO = nodeDataTypeTemplate.select_nodes(pathDTTDO.data()); if (setDTTDO.size() > 0) { typeAttr = setDTTDO[0].node().attribute("type").as_string(); } } else { vector DOList = stringSplit(doname, '.'); string pathDTTDO = SFormat("./LNodeType[@id='{0}']/DO[@name='{1}']", lnType, DOList.at(0)); xpath_node_set setDTTDO = nodeDataTypeTemplate.select_nodes(pathDTTDO.data()); if (setDTTDO.size() > 0) { typeAttr = setDTTDO[0].node().attribute("type").as_string(); for (int kk = 1; kk < DOList.size(); kk++) { string pathDTTSDO = SFormat("./DOType[@id='{0}']/SDO[@name='{1}']", typeAttr, DOList.at(kk)); xpath_node_set setDTTSDO = nodeDataTypeTemplate.select_nodes(pathDTTSDO.data()); if (setDTTSDO.empty()) { break; } typeAttr = setDTTSDO[0].node().attribute("type").as_string(); } } } if (daName.find('.') == string::npos) { string pathDTTDA = SFormat("./DOType[@id='{0}']/DA[@name='{1}']", typeAttr, daName); xpath_node_set setDTTDA = nodeDataTypeTemplate.select_nodes(pathDTTDA.data()); if (setDTTDA.size() > 0) { bType = setDTTDA[0].node().attribute("bType").as_string(); } } else { vector DAList = stringSplit(daName, '.'); string pathDTTDA = SFormat("./DOType[@id='{0}']/DA[@name='{1}']", typeAttr, DAList.at(0)); xpath_node_set setDTTDA = nodeDataTypeTemplate.select_nodes(pathDTTDA.data()); if (setDTTDA.size() > 0) { typeAttr = setDTTDA[0].node().attribute("type").as_string(); for (int kk = 1; kk < DAList.size(); kk++) { string pathDTTBDA = SFormat("./DAType[@id='{0}']/BDA[@name='{1}']", typeAttr, DAList.at(kk)); xpath_node_set setDTTBDA = nodeDataTypeTemplate.select_nodes(pathDTTBDA.data()); if (setDTTBDA.empty()) { break; } if (kk == DAList.size() - 1) { bType = setDTTBDA[0].node().attribute("bType").as_string(); } else { typeAttr = setDTTBDA[0].node().attribute("type").as_string(); } } } } } } } return bType; } static void cutNodeDesc(xml_node node_ied) { if (node_ied.empty()) return; string strIedName = node_ied.attribute("name").as_string(); node_ied.remove_attributes(); node_ied.append_attribute("name") = strIedName.data(); xpath_node_set pubfcdanodeset = node_ied.select_nodes("./GOOSEPUB/GOCBref/DataSet/FCDA|./SVPUB/SMVCBref/DataSet/FCDA"); xpath_node_set subfcdanodeset = node_ied.select_nodes("./GOOSESUB/GOCBref/DataSet/FCDA|./SVSUB/SMVCBref/DataSet/FCDA"); for (int i = 0; i < pubfcdanodeset.size(); i++) pubfcdanodeset[i].node().remove_attribute("desc"); for (int i = 0; i < subfcdanodeset.size(); i++) { xml_node node_fcda = subfcdanodeset[i].node(); string strBtype = node_fcda.attribute("bType").as_string(); subfcdanodeset[i].node().remove_attributes(); if (!strBtype.empty()) node_fcda.append_attribute("bType") = strBtype.data(); xpath_node_set intaddrset = node_fcda.select_nodes("./intAddr"); for (int j = 0; j < intaddrset.size(); j++) intaddrset[j].node().remove_attribute("desc"); } } ///后续函数为部分基础处理函数,可忽略 private: //按顺序插入节点 static void insertNodeSorted(xml_node parent, xml_node current) { if (parent.empty() || current.empty()) { return; } if (node_element != current.type()) { return; } //跳过dU string nameAttr = current.attribute("name").as_string(); if (nameAttr == "dU") { return; } xml_node nodeNew = parent.append_child(current.name()); insertNodeAttr(current, nodeNew); string textCurr = current.text().as_string(); if (!textCurr.empty()) { nodeNew.text().set(textCurr.data()); } xml_node nodeChild = current.first_child(); xml_node nodeChildL = current.last_child(); while (!nodeChild.empty()) { if (nodeChild.type() == pugi::node_element) { insertNodeSorted(nodeNew, nodeChild); } if (nodeChild == nodeChildL) { break; } else { nodeChild = nodeChild.next_sibling(); } } } //插入节点属性 static void insertNodeAttr(xml_node current, xml_node nodeNew) { map mapAttrs = getNodeAttributes(current); //map 会根据key值自动排序 for (auto& attr : mapAttrs) { if (attr.first == "desc" || attr.first == "dU") { continue; } nodeNew.append_attribute(attr.first.data()) = attr.second.data(); } } //获取子元素列表 static vector getChildElements(xml_node node, string childName) { int i = 0; vector vector; for (pugi::xml_node_iterator it2 = node.children().begin(); it2 != node.children().end(); it2++) { xml_node n = *it2; if (n.type() != pugi::node_element) continue; if (n.name() == childName) { vector.push_back(n); } i++; } return vector; } static bool removeChildNode(xml_node node) { xml_node pa_node = node.parent(); if (pa_node.empty()) return false; return pa_node.remove_child(node); } //获取所有属性 static map getNodeAttributes(xml_node node) { map attrMap; for (xml_attribute_iterator it1 = node.attributes().begin(); it1 != node.attributes().end(); ++it1) { xml_attribute attr = *it1; attrMap[attr.name()] = attr.value(); } return attrMap; } static vector stringSplit(string str, char split) { vector res; istringstream iss(str); // 输入流 string token; // 接收缓冲区 while (getline(iss, token, split)) // 以split为分隔符 res.push_back(token); return res; } static string& replace_all(string& src, const string& old_value, const string& new_value) { // 每次重新定位起始位置,防止上轮替换后的字符串形成新的old_value for (string::size_type pos(0); pos != string::npos; pos += new_value.length()) { if ((pos = src.find(old_value, pos)) != string::npos) { src.replace(pos, old_value.length(), new_value); } else break; } return src; } static bool endWith(const string& str, const string& tail) { return str.compare(str.size() - tail.size(), tail.size(), tail) == 0; } static bool startWith(const string& str, const string& head) { return str.compare(0, head.size(), head) == 0; } static string trim(string& text) { if (!text.empty()) { text.erase(0, text.find_first_not_of(" \n\r\t")); text.erase(text.find_last_not_of(" \n\r\t") + 1); } return text; } static std::string remove_spaces_except_in_quotes(const std::string& input) { std::string output; bool quote_flag = false; // 是否在引号内的标志 bool attr_flag = false; // 是否在<>内的标志 for (char c : input) // 遍历每个字符 { if (c == '"') // 如果是引号 { quote_flag = !quote_flag; // 切换引号标志 } if (c == '<') { attr_flag = true; } if (c == '>') { attr_flag = false; // 切换引号标志 } if (c != ' ' || quote_flag || !attr_flag) // 如果不是空格或者在引号内 { output += c; // 保留字符 } } return output; } };