背景:实际开发过程中,使用kanzi studio工程,遇到一个很大的问题,就是在多工程,多预设件,复杂绑定算式情况下,很难梳理接口绑定情况,而且kanzi自带的查找很不好用。尤其是拿到一个新工程,光理解工程逻辑就需要花很大精力。
为此,写一个脚本工具,直接解析.kzproj文件,获取有效信息,帮助快速上手新项目。
如果关心kanzi实际运行时节点情况,请参考帖子kanzi运行时节点状态展示
1. 需求分析(kanzi 3.6.10)
kanzi studio工程文件文件,后缀名kzproj
,本质是一个巨大的xml文件。
经过仔细分析,我们提取重要的字段
-
- 工程引用,一个主工程一般会引用多个子工程,
ProjectReferenceItem
,如果没有子工程,没有字段ProjectReferenceItem
- 工程引用,一个主工程一般会引用多个子工程,
-
- 场景节点,主工程的场景节点是这样的
Screens-Screen-RootPage
,在ScreenLibrary
这个类型下,通过children|ProjectItem|SerializedName
能够提取树状结构
- 场景节点,主工程的场景节点是这样的
-
- 预设件,除了普通节点,就是预设件几点,一般通过占位符引用子工程预设件。类型
PrefabLibrary
,同2取树状结构
- 预设件,除了普通节点,就是预设件几点,一般通过占位符引用子工程预设件。类型
-
- 绑定表达式,
MultiExpressionPropertyBinding
字段含有表达式内容
- 绑定表达式,
-
- 导出文件,将节点信息,预设件路径,绑定表达式导出,方便搜索接口
2. 脚本流程
2.1 读取主工程.kzproj
2.2 通过tinyxml2
转为xml
文档
2.3 查找ProjectReferenceItem
,找到后递归读取i:type=d9p1:string
,获取子工程列表
2.4 查找i:type=d3p1:ScreenLibrary
,找到后递归读取子节点SerializedName
获取树状节点名称。同时在properties
里读取MultiExpressionPropertyBinding
2.5 查找i:type=d3p1:PrefabLibrary
,找到后递归读取子节点SerializedName
获取树状节点名称。同时在properties
里读取MultiExpressionPropertyBinding
。同时在PrefabViewConcept.Prefab
里读取预设件URL路径
2.6 遍历子工程,重复步骤2.5
3. 导出文件说明
============================主工程.kzproj/ScreenItems 【主工程节点】
树状节点名【工程名】
是预设件输出Prefabs路径
有绑定输出绑定内容bindgs
============================主工程.kzproj/PrefabItems 【主工程预设件】
树状节点名【工程名】
有绑定输出绑定内容bindgs
============================..\子工程\子工程.kzproj/PrefabItems【子工程预设件】
树状节点名【工程名】
有绑定输出绑定内容bindgs
4. 使用说明
比如要查看Datasource里ShowHMI接口相关节点逻辑
可以在导出文件中搜索ShowHMI
,可以看到绑定该接口的节点信息,如果是绑定到私有属性,可以同时搜属性名,这样能在kanzi工程中快速定位,上手业务,不至于晕头转向。
5.代码
// 解析GWCluster.kzproj,获取节点名称,路径,绑定信息
// 解析子工程.kzproj
// 解析xml,获取xml节点名称
// 按xml查找工程节点信息,遇到绑定,增加查找列表,最后匹配生成表格
// 导出excel
#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <fstream>
#include <iostream>
#include <sstream>
#include "tinyxml2.h"
#define TEST (1)
static std::string Number2Stri(int value)
{
std::string str;
std::stringstream ss;
ss << value;
ss >> str;
return str;
}
static tinyxml2::XMLElement* gRootElement = NULL;
static std::vector<std::string> gReferenceItems;
struct KanziBinding
{
std::string propertyTypeName;
std::string expression;
};
struct KanziItem
{
std::string name;
std::string referredItemPath;
std::vector<KanziBinding> bindgs;
KanziItem* prefabItem=NULL;
std::vector<KanziItem*> childs;
KanziItem() :name(""), referredItemPath("") {
}
void print(int level = 0) {
std::cout << name << ": " << childs.size() << " ";
if (referredItemPath.size() != 0) {
std::cout << "referredItemPath=" << referredItemPath << std::endl;
}
else {
std::cout << std::endl;
}
if (bindgs.size() != 0) {
for (int i = 0; i < bindgs.size(); i++) {
std::cout << "bindgs/" << i << ":" << bindgs[i].propertyTypeName << "=" << bindgs[i].expression << std::endl;
}
}
for (int i = 0; i < childs.size(); i++) {
std::string str;
str.resize(level, '-');
std::cout << str << i << "-";
childs[i]->print(level + 1);
}
}
void generateOutput(std::string& output, std::string& projectname, int level = 0) {
std::string str;
str.resize(level, '-');
output += str + name + "," + referredItemPath + ",[" + projectname + "]\n";
for (int i = 0; i < bindgs.size(); i++) {
output += "bindgs/" + Number2Stri(i + 1) + "," + bindgs[i].propertyTypeName + "=" + bindgs[i].expression + ",";
if (i == bindgs.size() - 1) {
output += "\n";
}
}
for (int i = 0; i < childs.size(); i++) {
childs[i]->generateOutput(output, projectname, level+1);
}
}
};
static KanziItem gScreenItems;
static KanziItem gScreenPrefabItems;
static void ReadFileContentsByte(std::string filename, std::vector<char>& data);
static void WriteFileContentsByte(std::string filename, std::string& data);
static tinyxml2::XMLError ParseFile(std::vector<char>& fileData);
static tinyxml2::XMLElement* FindLabel(tinyxml2::XMLElement* root, std::string name);
static tinyxml2::XMLElement* FindLabelFromChildByName(tinyxml2::XMLElement* root, std::string name);
static tinyxml2::XMLElement* FindLabelFromChildRecurseByName(tinyxml2::XMLElement* root, std::string name);
static tinyxml2::XMLElement* FindLabelFromChildRecurseByNameValue(tinyxml2::XMLElement* root, std::string name, std::string value);
static tinyxml2::XMLElement* FindElementByAttr(tinyxml2::XMLElement* root, std::string type, std::string name);
static void FindProjectReferenceItem(tinyxml2::XMLElement* root, std::string type, std::string name);
static bool FindProjectReferenceItemValue(tinyxml2::XMLElement* root, std::string type, std::string name);
static void FindProjectScreenItem(tinyxml2::XMLElement* root, KanziItem& item);
static bool FindProjectScreenItemValue(tinyxml2::XMLElement* root, std::string name);
static void FindPrefabsItem(tinyxml2::XMLElement* root, KanziItem& item);
int main(int argc, char *argv[])
{
std::cout << "Hello World!\n";
std::string kzproj = "";
std::string kzprojHead = "";
std::string kzprojName = "";
std::string xmlpath = "";
for (int i = 0; i < argc; i++) {
std::cout << i << ":" << argv[i] << std::endl;
if (1 == i) {
kzproj = std::string(argv[i]);
}
// else if (2 == i) {
// xmlpath = std::string(argv[i]);
// }
}
if(kzproj.size() == 0)
kzproj = "D:\\chenchao\\hmi\\v4\\cluster_es15_code\\cluster_es15_code\\GWCluster\\1Main\\GWCluster.kzproj";
std::cout << "kzproj:" << kzproj << std::endl;
int lastIndex = kzproj.find_last_of("\\");
kzprojHead = kzproj.substr(0, lastIndex+1);
kzprojName = kzproj.substr(lastIndex+1, kzproj.size());
std::cout << "kzprojHead:" << kzprojHead << std::endl;
std::cout << "kzprojName:" << kzprojName << std::endl;
//if (xmlpath.size() == 0)
// xmlpath = "D:\\chenchao\\hmi\\v4\\cluster_es15_code\\cluster_es15_code\\GWCluster\\Application\\bin\\ClusterData.xml";
//std::cout << "xmlpath:" << xmlpath << std::endl;
std::cout << "start parse:" << kzproj << std::endl;
std::vector<char> data;
ReadFileContentsByte(kzproj, data);
if (data.size() == 0) {
std::cout << "error: " << kzproj << " size=0" << std::endl;
exit(0);
}
tinyxml2::XMLDocument doc;
tinyxml2::XMLError error = doc.Parse(data.data(), data.size());
if (error == tinyxml2::XML_SUCCESS)
{
}
else {
std::cout << "error: " << kzproj << " ParseFile" << std::endl;
exit(0);
}
gRootElement = doc.RootElement();
if (gRootElement == NULL) {
std::cout << "error: " << kzproj << " gRootElement=0" << std::endl;
exit(0);
}
std::cout << " parse: Project References" << std::endl;
gReferenceItems.clear();
FindProjectReferenceItem(gRootElement, "i:type", "d3p1:ProjectReferenceItem");
std::cout << " parse: Screen Items" << std::endl;
tinyxml2::XMLElement* ScreenLibrary = FindElementByAttr(gRootElement, "i:type", "d3p1:ScreenLibrary");
if (ScreenLibrary == NULL) {
std::cout << "error: " << kzproj << " ScreenLibrary=0" << std::endl;
exit(0);
}
FindProjectScreenItem(ScreenLibrary, gScreenItems);
//log
gScreenItems.print();
std::cout << " parse: Prefabs Items" << std::endl;
tinyxml2::XMLElement* PrefabItems = FindElementByAttr(gRootElement, "i:type", "d3p1:PrefabLibrary");
if (PrefabItems == NULL) {
std::cout << "error: " << kzproj << " PrefabItems=0" << std::endl;
// exit(0);
}
else {
FindPrefabsItem(PrefabItems, gScreenPrefabItems);
}
//log
gScreenPrefabItems.print();
//std::cout << " match item prefab->prefab lib" << std::endl;
//GWCluster/Prefabs/DarkBgForShowHMI/ -> DarkBgForShowHMI
std::vector<KanziItem*> gSubProjectPrefabItems;
for (int a = 0; a < gReferenceItems.size(); a++) {
std::cout << "start parse: SubProject Prefabs Items" << a << gReferenceItems[a] << std::endl;
std::string subpath = kzprojHead + gReferenceItems[a];
std::vector<char> data;
ReadFileContentsByte(subpath, data);
if (data.size() == 0) {
std::cout << "error: " << subpath << " size=0" << std::endl;
exit(0);
}
tinyxml2::XMLDocument doc;
tinyxml2::XMLError error = doc.Parse(data.data(), data.size());
if (error == tinyxml2::XML_SUCCESS)
{
}
else {
std::cout << "error: " << subpath << " ParseFile" << std::endl;
exit(0);
}
tinyxml2::XMLElement* root = doc.RootElement();
if (root == NULL) {
std::cout << "error: " << subpath << " root=0" << std::endl;
exit(0);
}
std::cout << " parse: SubProject Prefabs Items" << gReferenceItems[a] << std::endl;
tinyxml2::XMLElement* PrefabItems = FindElementByAttr(root, "i:type", "d3p1:PrefabLibrary");
if (PrefabItems == NULL) {
std::cout << "error: " << subpath << " PrefabItems=0" << std::endl;
// exit(0);
}
else {
KanziItem* one = new KanziItem;
FindPrefabsItem(PrefabItems, *one);
gSubProjectPrefabItems.push_back(one);
//log
one->print();
}
}
std::cout << " create output file" << std::endl;
std::string output = "";
output += "============================" + kzprojName + "/ScreenItems\n";
gScreenItems.generateOutput(output, kzprojName);
output += "============================" + kzprojName + "/PrefabItems\n";
gScreenPrefabItems.generateOutput(output, kzprojName);
if (gSubProjectPrefabItems.size() != gReferenceItems.size()) {
std::cout << "error: gSubProjectPrefabItems.size() != gReferenceItems.size()" << gSubProjectPrefabItems.size() << "," << gReferenceItems.size() << std::endl;
exit(0);
}
for (int i = 0; i < gSubProjectPrefabItems.size(); i++) {
output += "============================" + gReferenceItems[i] + "/PrefabItems\n";
gSubProjectPrefabItems[i]->generateOutput(output, gReferenceItems[i]);
}
WriteFileContentsByte("nodexml.txt", output);
std::cout << "Finish!" << std::endl;
}
static void ReadFileContentsByte(std::string filename, std::vector<char>& data)
{
std::fstream fin;
fin.open(filename, std::ios::in | std::ios::binary);
if (!fin.is_open())
{
return;
}
//const int LENGTH = 1000;
fin.seekg(0, std::ios::end);
long int size = fin.tellg();
fin.seekg(0, std::ios::beg);
data.resize(size, 0);
char temp;
long i = 0;
while ((temp = fin.get()) != EOF)
{
//str.push_back((char)temp);
if (i >= size) {
std::cout << i << "/" << size << std::endl;
exit(0);
}
data[i] = temp;
i++;
//std::cout << (byte)temp;
}
fin.close();
}
static void WriteFileContentsByte(std