Browse Source

准备关联标准字段

master
hejl 2 months ago
parent
commit
045eab3632
  1. 2
      .gitignore
  2. 1
      CppServerProject
  3. BIN
      documents/PB UFT模块迁移方案.docx
  4. 10
      uft_dev_server/CMakeLists.txt
  5. 10
      uft_dev_server/cmd.txt
  6. 8
      uft_dev_server/rccpp_config.h
  7. 49
      uft_dev_server/src/main.cpp
  8. 65
      uft_dev_server/src/swagger/swagger.json
  9. 200
      win_text_editor/lib/modules/memory_table/widgets/memory_table_left_side.dart
  10. 218
      win_text_editor/lib/modules/template_parser/controllers/template_parser_controller.dart
  11. 51
      win_text_editor/lib/modules/template_parser/widgets/template_parser_view.dart
  12. 2
      win_text_editor/lib/modules/template_parser/widgets/tree_view.dart
  13. 25
      win_text_editor/lib/shared/models/template_node.dart

2
.gitignore vendored

@ -7,3 +7,5 @@
/win_text_editor/web /win_text_editor/web
/win_text_editor/windows/runner /win_text_editor/windows/runner
/cpp_server/swagger /cpp_server/swagger
/uft_dev_server/build
/uft_dev_server/third_party

1
CppServerProject

@ -1 +0,0 @@
Subproject commit 73cb76986ce1748eaee5ee6aee9c34a835d52fd5

BIN
documents/PB UFT模块迁移方案.docx

Binary file not shown.

10
uft_dev_server/CMakeLists.txt

@ -16,11 +16,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
# #
set(SOURCE_DIR src) set(SOURCE_DIR src)
add_subdirectory(imgui)
set(RCCPP_SOURCE_DIR third_party/RuntimeCompiledCPlusPlus/Aurora)
# D:\aigc\Libs\RCCPP\lib
set(RCCPP_LIB_DIR "D:/aigc/Libs/RCCPP/lib")
# #
@ -45,14 +41,12 @@ add_executable(${PROJECT_NAME}
# #
target_link_libraries(${PROJECT_NAME} PRIVATE target_link_libraries(${PROJECT_NAME} PRIVATE
Drogon::Drogon Drogon::Drogon
imgui
${CMAKE_DL_LIBS} # ${CMAKE_DL_LIBS} #
"${RCCPP_LIB_DIR}/RuntimeCompiler.lib"
"${RCCPP_LIB_DIR}/RuntimeObjectSystem.lib"
) )
# #
target_include_directories(${PROJECT_NAME} PRIVATE target_include_directories(${PROJECT_NAME} PRIVATE
${RCCPP_SOURCE_DIR}
${SOURCE_DIR} ${SOURCE_DIR}
${SOURCE_DIR}/controllers ${SOURCE_DIR}/controllers
${SOURCE_DIR}/filters ${SOURCE_DIR}/filters

10
uft_dev_server/cmd.txt

@ -1,12 +1,4 @@
#增加RCC++依赖包(专为自行编译RCCPP提供,直接使用已有RCCPP目录则无需执行--注意命令执行位置)
mkdir third_party
git clone https://github.com/RuntimeCompiledCPlusPlus/RuntimeCompiledCPlusPlus.git
cd RuntimeCompiledCPlusPlus/Aurora
rm -Path build -Recurse -Force
mkdir build
cd build
cmake .. -DCMAKE_INSTALL_PREFIX="D:/aigc/Libs/RCCPP" -DINSTALL_CMAKE_CONFIG=ON -DBUILD_SHARED_LIBS=ON -G "Visual Studio 17 2022" -A x64 -DCMAKE_POLICY_VERSION_MINIMUM="4.0.2"
cmake --build . --config Debug --target install
#工程编译 #工程编译

8
uft_dev_server/rccpp_config.h

@ -1,8 +0,0 @@
#pragma once
#define RCCPPUSER_USE_PRECOMPILED_HEADER 0
#define RCCPPUSER_USE_EXCEPTIONS 1
#define RCCPPUSER_USE_RTTI 1
#define RCCPPUSER_USE_DEBUG_NEW 0
#define RCCPPUSER_USE_VLD 0
#define RCCPPUSER_USE_IMGUI 0

49
uft_dev_server/src/main.cpp

@ -1,27 +1,38 @@
#include <drogon/drogon.h> #include "imgui.h"
#include <iostream> #include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h"
using namespace drogon;
int main() int main()
{ {
try // 初始化窗口和渲染上下文(例如GLFW+OpenGL)
{ glfwInit();
// 设置日志级别 GLFWwindow *window = glfwCreateWindow(1280, 720, "ImGui Demo", NULL, NULL);
drogon::app().setLogLevel(trantor::Logger::kTrace);
drogon::app().registerBeginningAdvice([]()
{ LOG_INFO << "Drogon application starting..."; });
// 加载配置 // 初始化ImGui
drogon::app().loadConfigFile("./config.json"); ImGui::CreateContext();
ImGui_ImplGlfw_InitForOpenGL(window, true);
ImGui_ImplOpenGL3_Init("#version 130");
// 启动服务 while (!glfwWindowShouldClose(window))
drogon::app().run();
}
catch (const std::exception &e)
{ {
std::cerr << "Error: " << e.what() << std::endl; // 开始新帧
return 1; ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
// 创建UI
ImGui::Begin("Demo Window");
ImGui::Text("Hello, world!");
if (ImGui::Button("Save"))
{
// 按钮点击处理
}
ImGui::End();
// 渲染
ImGui::Render();
glClear(GL_COLOR_BUFFER_BIT);
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
glfwSwapBuffers(window);
} }
return 0;
} }

65
uft_dev_server/src/swagger/swagger.json

@ -1,65 +0,0 @@
{
"openapi": "3.0.0",
"info": {
"title": "CppServerProject API",
"version": "1.0.0",
"description": "A sample C++ backend server with Swagger documentation"
},
"paths": {
"/hello": {
"get": {
"summary": "Hello World",
"description": "Returns a simple greeting",
"responses": {
"200": {
"description": "Successful response",
"content": {
"text/plain": {
"schema": {
"type": "string",
"example": "Hello, World!"
}
}
}
}
}
}
},
"/api/info": {
"get": {
"summary": "Get API info",
"description": "Returns basic API information",
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiInfo"
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"ApiInfo": {
"type": "object",
"properties": {
"status": {
"type": "string"
},
"version": {
"type": "string"
},
"message": {
"type": "string"
}
}
}
}
}
}

200
win_text_editor/lib/modules/memory_table/widgets/memory_table_left_side.dart

@ -42,64 +42,56 @@ class MemoryTableLeftSide extends StatelessWidget {
Expanded( Expanded(
child: LayoutBuilder( child: LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
return SingleChildScrollView( return SizedBox(
scrollDirection: Axis.horizontal, width: constraints.maxWidth,
child: SizedBox( child: SfDataGrid(
width: constraints.maxWidth > 800 ? constraints.maxWidth : 800, source: fieldsSource,
child: SfDataGrid( gridLinesVisibility: GridLinesVisibility.both,
source: fieldsSource, headerGridLinesVisibility: GridLinesVisibility.both,
gridLinesVisibility: GridLinesVisibility.both, columnWidthMode: ColumnWidthMode.fitByCellValue,
headerGridLinesVisibility: GridLinesVisibility.both, selectionMode: SelectionMode.none,
columnWidthMode: ColumnWidthMode.fitByCellValue, columns: [
selectionMode: SelectionMode.none, GridColumn(
columns: [ columnName: 'select',
GridColumn( label: ValueListenableBuilder<bool>(
columnName: 'select', valueListenable: fieldsSource.selectionNotifier,
label: ValueListenableBuilder<bool>( builder: (context, _, __) => _buildCheckboxHeader(context, fieldsSource),
valueListenable: fieldsSource.selectionNotifier,
builder:
(context, _, __) => _buildCheckboxHeader(context, fieldsSource),
),
width: 60,
), ),
GridColumn( width: 60,
columnName: 'id', ),
label: _buildGridHeader('序号'), GridColumn(columnName: 'id', label: _buildGridHeader('序号'), minimumWidth: 80),
minimumWidth: 80, GridColumn(
), columnName: 'name',
GridColumn( label: _buildGridHeader('名称'),
columnName: 'name', minimumWidth: 120,
label: _buildGridHeader('名称'), ),
minimumWidth: 120, GridColumn(
), columnName: 'chineseName',
GridColumn( label: _buildGridHeader('中文名'),
columnName: 'chineseName', minimumWidth: 120,
label: _buildGridHeader('中文名'), ),
minimumWidth: 120, GridColumn(
), columnName: 'type',
GridColumn( label: _buildGridHeader('类型'),
columnName: 'type', minimumWidth: 120,
label: _buildGridHeader('类型'), ),
minimumWidth: 120, GridColumn(
), columnName: 'remark',
GridColumn( label: _buildGridHeader('备注'),
columnName: 'remark', minimumWidth: 200,
label: _buildGridHeader('备注'), ),
minimumWidth: 200, ],
), onCellTap: (details) {
], if (details.column.columnName == 'select') {
onCellTap: (details) { final rowIndex = details.rowColumnIndex.rowIndex - 1;
if (details.column.columnName == 'select') { if (rowIndex >= 0 && rowIndex < fieldsSource.items.length) {
final rowIndex = details.rowColumnIndex.rowIndex - 1; fieldsSource.toggleRowSelection(
if (rowIndex >= 0 && rowIndex < fieldsSource.items.length) { rowIndex,
fieldsSource.toggleRowSelection( !fieldsSource.items[rowIndex].isSelected,
rowIndex, );
!fieldsSource.items[rowIndex].isSelected,
);
}
} }
}, }
), },
), ),
); );
}, },
@ -142,58 +134,54 @@ class MemoryTableLeftSide extends StatelessWidget {
Expanded( Expanded(
child: LayoutBuilder( child: LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
return SingleChildScrollView( return SizedBox(
scrollDirection: Axis.horizontal, width: constraints.maxWidth,
child: SizedBox( child: SfDataGrid(
width: constraints.maxWidth > 800 ? constraints.maxWidth : 800, source: indexesSource,
child: SfDataGrid( gridLinesVisibility: GridLinesVisibility.both,
source: indexesSource, headerGridLinesVisibility: GridLinesVisibility.both,
gridLinesVisibility: GridLinesVisibility.both, columnWidthMode: ColumnWidthMode.fitByCellValue,
headerGridLinesVisibility: GridLinesVisibility.both, columns: [
columnWidthMode: ColumnWidthMode.fitByCellValue, GridColumn(
columns: [ columnName: 'select',
GridColumn( label: ValueListenableBuilder<bool>(
columnName: 'select', valueListenable: indexesSource.selectionNotifier,
label: ValueListenableBuilder<bool>( builder: (context, _, __) => _buildCheckboxHeader(context, indexesSource),
valueListenable: indexesSource.selectionNotifier,
builder:
(context, _, __) => _buildCheckboxHeader(context, indexesSource),
),
width: 60,
),
GridColumn(
columnName: 'indexName',
label: _buildGridHeader('索引名称'),
minimumWidth: 120,
),
GridColumn(
columnName: 'isPrimary',
label: _buildGridHeader('是否主键'),
minimumWidth: 100,
),
GridColumn(
columnName: 'indexFields',
label: _buildGridHeader('索引字段'),
minimumWidth: 150,
),
GridColumn(
columnName: 'rule',
label: _buildGridHeader('规则'),
minimumWidth: 200,
), ),
], width: 60,
onCellTap: (details) { ),
if (details.column.columnName == 'select') { GridColumn(
final rowIndex = details.rowColumnIndex.rowIndex - 1; columnName: 'indexName',
if (rowIndex >= 0 && rowIndex < indexesSource.items.length) { label: _buildGridHeader('索引名称'),
indexesSource.toggleRowSelection( minimumWidth: 120,
rowIndex, ),
!indexesSource.items[rowIndex].isSelected, GridColumn(
); columnName: 'isPrimary',
} label: _buildGridHeader('是否主键'),
minimumWidth: 100,
),
GridColumn(
columnName: 'indexFields',
label: _buildGridHeader('索引字段'),
minimumWidth: 150,
),
GridColumn(
columnName: 'rule',
label: _buildGridHeader('规则'),
minimumWidth: 200,
),
],
onCellTap: (details) {
if (details.column.columnName == 'select') {
final rowIndex = details.rowColumnIndex.rowIndex - 1;
if (rowIndex >= 0 && rowIndex < indexesSource.items.length) {
indexesSource.toggleRowSelection(
rowIndex,
!indexesSource.items[rowIndex].isSelected,
);
} }
}, }
), },
), ),
); );
}, },

218
win_text_editor/lib/modules/template_parser/controllers/template_parser_controller.dart

@ -13,8 +13,12 @@ class TemplateParserController extends BaseContentController {
final FilterController filterController; final FilterController filterController;
final GridViewController gridController; final GridViewController gridController;
static const String modeByPath = "byPath";
static const String modeByStruct = "byStruct";
String _filePath = ''; String _filePath = '';
String? _errorMessage; String? _errorMessage;
String statisticsMode = modeByPath;
String get filePath => _filePath; String get filePath => _filePath;
String? get errorMessage => _errorMessage; String? get errorMessage => _errorMessage;
@ -51,6 +55,12 @@ class TemplateParserController extends BaseContentController {
} }
//--------------------- //---------------------
void setStatisticsMode(String? value) {
statisticsMode = value ?? modeByPath;
notifyListeners();
}
//widget调用入口 //widget调用入口
Future<void> pickFile() async { Future<void> pickFile() async {
final result = await FilePicker.platform.pickFiles( final result = await FilePicker.platform.pickFiles(
@ -71,34 +81,27 @@ class TemplateParserController extends BaseContentController {
await _loadTemplateData(); await _loadTemplateData();
} }
//xml文件 List<TemplateNode> _buildTreeNodes(
Future<void> _loadTemplateData() async { xml.XmlElement element,
try { String path, {
_errorMessage = null; required int depth,
final file = File(_filePath); int repeatCount = 1,
final content = await file.readAsString(); }) {
final document = xml.XmlDocument.parse(content); //
if (statisticsMode == TemplateParserController.modeByStruct) {
// return _buildTreeNodesByStructure(element.document!);
// } else {
treeController.updateTreeNodes( return _buildTreeNodesByPath(element, path, depth: depth, repeatCount: repeatCount);
_buildTreeNodes(document.rootElement, document.rootElement.localName, depth: 0),
);
//
gridController.updateTemplateItems(_parseAllNodeValues(document));
} catch (e) {
_errorMessage = 'Failed to load XML: ${e.toString()}';
Logger().error('XML加载错误$_errorMessage');
} }
} }
//----------------------------- //-----------------------------
// //
List<TemplateNode> _buildTreeNodes( List<TemplateNode> _buildTreeNodesByPath(
xml.XmlElement element, xml.XmlElement element,
String path, { String path, {
required int depth, required int depth,
int repreatCount = 1, int repeatCount = 1,
}) { }) {
final node = TemplateNode( final node = TemplateNode(
path: path, path: path,
@ -106,8 +109,8 @@ class TemplateParserController extends BaseContentController {
children: [], children: [],
depth: depth, depth: depth,
isExpanded: depth < 5, // isExpanded: depth < 5, //
isRepeated: repreatCount > 1, isRepeated: repeatCount > 1,
repreatCount: repreatCount, repeatCount: repeatCount,
); );
// //
@ -143,7 +146,7 @@ class TemplateParserController extends BaseContentController {
} else { } else {
// //
node.children.addAll( node.children.addAll(
_buildTreeNodes(elements.first, path0, depth: depth + 1, repreatCount: elements.length), _buildTreeNodes(elements.first, path0, depth: depth + 1, repeatCount: elements.length),
); );
} }
}); });
@ -151,11 +154,172 @@ class TemplateParserController extends BaseContentController {
return [node]; return [node];
} }
// List<TemplateNode> _buildTreeNodesByStructure(xml.XmlDocument document) {
final structureMap = <String, List<xml.XmlElement>>{};
//
void collectNodes(xml.XmlElement element) {
final structureKey = _getNodeStructureKey(element);
structureMap.putIfAbsent(structureKey, () => []).add(element);
//
for (var child in element.children.whereType<xml.XmlElement>()) {
collectNodes(child);
}
}
collectNodes(document.rootElement);
//
return structureMap.entries.map((entry) {
final elements = entry.value;
final firstElement = elements.first;
final nodeName = firstElement.name.local;
final attributes = firstElement.attributes.map((attr) => attr.name.local).toList()..sort();
//
final parentNode = TemplateNode(
path: entry.key,
name:
attributes.isEmpty
? '$nodeName(${elements.length})'
: '$nodeName(${attributes.join("|")})(${elements.length})',
children: [],
depth: 0,
isExpanded: true,
isRepeated: false,
repeatCount: elements.length,
);
//
parentNode.children.addAll(
firstElement.attributes.map(
(attr) => TemplateNode(
path: '${entry.key}/@${attr.name.local}',
name: '@${attr.qualifiedName}',
children: [],
depth: 1,
isAttribute: true,
),
),
);
//
final hasTextContent = firstElement.children.whereType<xml.XmlText>().isNotEmpty;
if (hasTextContent) {
parentNode.children.add(
TemplateNode(
path: '${entry.key}/#text',
name: '#text',
children: [],
depth: 1,
isTextNode: true,
),
);
}
return parentNode;
}).toList();
}
//
String _getNodeStructureKey(xml.XmlElement element) {
final attrNames = element.attributes.map((attr) => attr.name.local).toList()..sort();
return '${element.name.local}|${attrNames.join(",")}';
}
// _loadTemplateData
Future<void> _loadTemplateData() async {
try {
_errorMessage = null;
final file = File(_filePath);
final content = await file.readAsString();
final document = xml.XmlDocument.parse(content);
//
treeController.updateTreeNodes(
statisticsMode == modeByStruct
? _buildTreeNodesByStructure(document)
: _buildTreeNodesByPath(document.rootElement, document.rootElement.localName, depth: 0),
);
//
gridController.updateTemplateItems(_parseAllNodeValues(document));
} catch (e) {
_errorMessage = 'Failed to load XML: ${e.toString()}';
Logger().error('XML加载错误$_errorMessage');
}
}
//
List<TemplateItem> _parseAllNodeValues(xml.XmlDocument document) { List<TemplateItem> _parseAllNodeValues(xml.XmlDocument document) {
final items = <TemplateItem>[]; final items = <TemplateItem>[];
int id = 0; int id = 0;
//
if (statisticsMode == modeByStruct) {
final structureMap = <String, List<xml.XmlElement>>{};
//
void collectNodes(xml.XmlElement element) {
final structureKey = _getNodeStructureKey(element);
structureMap.putIfAbsent(structureKey, () => []).add(element);
for (var child in element.children.whereType<xml.XmlElement>()) {
collectNodes(child);
}
}
collectNodes(document.rootElement);
//
structureMap.forEach((structureKey, elements) {
final firstElement = elements.first;
final attributes = firstElement.attributes.map((attr) => attr.name.local).toList()..sort();
int index = 0;
//
for (final attrName in attributes) {
index = 0;
for (final element in elements) {
final attr = element.getAttributeNode(attrName);
if (attr != null) {
index++;
items.add(
TemplateItem(
id: id++,
rowId: "$structureKey/@$index",
xPath: '$structureKey/@$attrName',
value: attr.value,
),
);
}
}
}
//
index = 0;
for (final element in elements) {
final textNodes = element.children.whereType<xml.XmlText>();
if (textNodes.isNotEmpty) {
index++;
items.add(
TemplateItem(
id: id++,
rowId: "$structureKey/$index",
xPath: "$structureKey/#text",
value: textNodes.first.text,
),
);
}
}
});
return items;
}
//
void traverse(xml.XmlElement element, String currentPath, int index) { void traverse(xml.XmlElement element, String currentPath, int index) {
// //
for (final attr in element.attributes) { for (final attr in element.attributes) {
@ -163,10 +327,8 @@ class TemplateParserController extends BaseContentController {
TemplateItem( TemplateItem(
id: id++, id: id++,
rowId: "$currentPath/@$index", rowId: "$currentPath/@$index",
content: attr.value,
xPath: '$currentPath/@${attr.name.local}', xPath: '$currentPath/@${attr.name.local}',
value: attr.value, value: attr.value,
nodeType: NodeType.attribute,
), ),
); );
} }
@ -178,15 +340,13 @@ class TemplateParserController extends BaseContentController {
TemplateItem( TemplateItem(
id: id++, id: id++,
rowId: "$currentPath/@$index", rowId: "$currentPath/@$index",
content: textNodes.first.text,
xPath: currentPath, xPath: currentPath,
value: textNodes.first.text, value: textNodes.first.text,
nodeType: NodeType.text,
), ),
); );
} }
// 3. //
final childElements = element.children.whereType<xml.XmlElement>(); final childElements = element.children.whereType<xml.XmlElement>();
final groupedChildren = <String, List<xml.XmlElement>>{}; final groupedChildren = <String, List<xml.XmlElement>>{};

51
win_text_editor/lib/modules/template_parser/widgets/template_parser_view.dart

@ -69,19 +69,46 @@ class _TemplateParserViewState extends State<TemplateParserView> {
Widget _buildFilePathInput() { Widget _buildFilePathInput() {
return Consumer<TemplateParserController>( return Consumer<TemplateParserController>(
builder: (context, controller, _) { builder: (context, controller, _) {
return TextField( return Row(
decoration: InputDecoration( children: [
labelText: 'XML File', //
hintText: 'Select an XML file', SizedBox(
suffixIcon: IconButton( width: 150,
icon: const Icon(Icons.folder_open), child: DropdownButtonFormField<String>(
onPressed: controller.pickFile, value: 'byPath', //
decoration: const InputDecoration(
labelText: '统计模式',
border: OutlineInputBorder(),
contentPadding: EdgeInsets.symmetric(horizontal: 8, vertical: 12),
),
items: const [
DropdownMenuItem(value: 'byPath', child: Text('按节点路径')),
DropdownMenuItem(value: 'byStruct', child: Text('按节点结构')),
],
onChanged: (value) {
controller.setStatisticsMode(value);
},
),
), ),
border: const OutlineInputBorder(), const SizedBox(width: 8),
errorText: controller.errorMessage, //
), Expanded(
controller: TextEditingController(text: controller.filePath), child: TextField(
readOnly: true, decoration: InputDecoration(
labelText: 'XML File',
hintText: 'Select an XML file',
suffixIcon: IconButton(
icon: const Icon(Icons.folder_open),
onPressed: controller.pickFile,
),
border: const OutlineInputBorder(),
errorText: controller.errorMessage,
),
controller: TextEditingController(text: controller.filePath),
readOnly: true,
),
),
],
); );
}, },
); );

2
win_text_editor/lib/modules/template_parser/widgets/tree_view.dart

@ -77,7 +77,7 @@ class TemplateTreeView extends StatelessWidget {
trailing: trailing:
templateNode.isRepeated templateNode.isRepeated
? Text( ? Text(
"(${templateNode.repreatCount.toString()})", "(${templateNode.repeatCount.toString()})",
style: const TextStyle(color: Colors.grey), style: const TextStyle(color: Colors.grey),
) )
: null, : null,

25
win_text_editor/lib/shared/models/template_node.dart

@ -14,8 +14,9 @@ class TemplateNode implements TreeNode {
final String path; final String path;
bool isRepeated; bool isRepeated;
bool isAttribute; bool isAttribute;
int repreatCount; int repeatCount;
bool isChecked; // bool isChecked;
final bool isTextNode;
TemplateNode({ TemplateNode({
required this.name, required this.name,
@ -25,8 +26,9 @@ class TemplateNode implements TreeNode {
this.isExpanded = false, this.isExpanded = false,
this.isRepeated = false, this.isRepeated = false,
this.isAttribute = false, this.isAttribute = false,
this.repreatCount = 1, this.repeatCount = 1,
this.isChecked = false, // this.isChecked = false,
this.isTextNode = false,
}); });
@override @override
@ -44,21 +46,8 @@ enum NodeType { element, attribute, text }
class TemplateItem { class TemplateItem {
final int id; final int id;
final String rowId; final String rowId;
final String content;
final String xPath; final String xPath;
final String value; final String value;
final NodeType nodeType;
TemplateItem({ TemplateItem({required this.id, required this.rowId, required this.xPath, required this.value});
required this.id,
required this.rowId,
required this.content,
required this.xPath,
required this.value,
required this.nodeType,
});
bool matchesPath(String path) {
return xPath == path;
}
} }

Loading…
Cancel
Save