|
|
@ -3,6 +3,10 @@ import 'dart:io'; |
|
|
|
import 'package:flutter/services.dart'; |
|
|
|
import 'package:flutter/services.dart'; |
|
|
|
import 'package:jieba_flutter/analysis/jieba_segmenter.dart'; |
|
|
|
import 'package:jieba_flutter/analysis/jieba_segmenter.dart'; |
|
|
|
import 'package:win_text_editor/modules/outline/models/outline_node.dart'; |
|
|
|
import 'package:win_text_editor/modules/outline/models/outline_node.dart'; |
|
|
|
|
|
|
|
import 'package:win_text_editor/modules/outline/services/component_service.dart'; |
|
|
|
|
|
|
|
import 'package:win_text_editor/modules/outline/services/functions_service.dart'; |
|
|
|
|
|
|
|
import 'package:win_text_editor/modules/outline/services/std_field_service.dart'; |
|
|
|
|
|
|
|
import 'package:win_text_editor/modules/outline/services/uft_object_service.dart'; |
|
|
|
import 'package:xml/xml.dart'; |
|
|
|
import 'package:xml/xml.dart'; |
|
|
|
import 'package:yaml/yaml.dart'; |
|
|
|
import 'package:yaml/yaml.dart'; |
|
|
|
import 'package:win_text_editor/framework/controllers/logger.dart'; |
|
|
|
import 'package:win_text_editor/framework/controllers/logger.dart'; |
|
|
@ -19,23 +23,16 @@ class OutlineService { |
|
|
|
// ignore: constant_identifier_names |
|
|
|
// ignore: constant_identifier_names |
|
|
|
static const List<String> REQUIRED_DIRS = ['uftstructure', 'uftatom', 'uftbusiness', 'uftfactor']; |
|
|
|
static const List<String> REQUIRED_DIRS = ['uftstructure', 'uftatom', 'uftbusiness', 'uftfactor']; |
|
|
|
|
|
|
|
|
|
|
|
// ignore: constant_identifier_names |
|
|
|
|
|
|
|
static const Map<String, String> FIELD_ACTIONS = { |
|
|
|
|
|
|
|
"UFTTable": "UFT对象", |
|
|
|
|
|
|
|
"Component": "标准组件", |
|
|
|
|
|
|
|
"Business": "业务层", |
|
|
|
|
|
|
|
"Atom": "原子层", |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ignore: constant_identifier_names |
|
|
|
// ignore: constant_identifier_names |
|
|
|
static const List<String> FILTERED_WORD_CLASSES = ['v', 'a', 'ad', 'f', 'd', 't', 'r']; |
|
|
|
static const List<String> FILTERED_WORD_CLASSES = ['v', 'a', 'ad', 'f', 'd', 't', 'r']; |
|
|
|
|
|
|
|
|
|
|
|
// 静态变量 |
|
|
|
//黑名单 |
|
|
|
static List<String> _blackList = []; |
|
|
|
static List<String> _blackList = []; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static OutlineNode? _searchNode; |
|
|
|
|
|
|
|
//结巴初始化标记 |
|
|
|
static bool _isJiebaInitialized = false; |
|
|
|
static bool _isJiebaInitialized = false; |
|
|
|
static JiebaSegmenter? _segmenter; |
|
|
|
static JiebaSegmenter? _segmenter; |
|
|
|
static Map<String, String> _wordClassDict = {}; |
|
|
|
|
|
|
|
static bool _isWordClassDictInitialized = false; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 私有方法:初始化分词器 |
|
|
|
// 私有方法:初始化分词器 |
|
|
|
static Future<void> _initJieba() async { |
|
|
|
static Future<void> _initJieba() async { |
|
|
@ -76,26 +73,22 @@ class OutlineService { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 私有方法:初始化词性字典(优化版) |
|
|
|
// 私有方法:初始化词性字典(优化版) |
|
|
|
static Future<void> _initWordClassDict() async { |
|
|
|
static Future<Map<String, String>> _initWordClassDict() async { |
|
|
|
if (_isWordClassDictInitialized) return; |
|
|
|
Map<String, String> wordClassDict = {}; |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
try { |
|
|
|
Logger().info('开始加载词性字典...'); |
|
|
|
Logger().info('开始加载词性字典...'); |
|
|
|
final dictContent = await rootBundle.loadString('assets/dict.txt'); |
|
|
|
final dictContent = await rootBundle.loadString('assets/dict.txt'); |
|
|
|
|
|
|
|
|
|
|
|
_wordClassDict = Map.fromEntries( |
|
|
|
wordClassDict = Map.fromEntries( |
|
|
|
dictContent.split('\n').where((line) => line.trim().isNotEmpty).map((line) { |
|
|
|
dictContent.split('\n').where((line) => line.trim().isNotEmpty).map((line) { |
|
|
|
final parts = line.trim().split(RegExp(r'\s+')); |
|
|
|
final parts = line.trim().split(RegExp(r'\s+')); |
|
|
|
return (parts.length >= 3) ? MapEntry(parts[0], parts[2]) : null; |
|
|
|
return (parts.length >= 3) ? MapEntry(parts[0], parts[2]) : null; |
|
|
|
}).whereType<MapEntry<String, String>>(), |
|
|
|
}).whereType<MapEntry<String, String>>(), |
|
|
|
); |
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
_isWordClassDictInitialized = true; |
|
|
|
|
|
|
|
Logger().info('加载词性字典成功: ${_wordClassDict.length}个词'); |
|
|
|
|
|
|
|
} catch (e) { |
|
|
|
} catch (e) { |
|
|
|
Logger().error('加载词性字典失败: $e'); |
|
|
|
Logger().error('加载词性字典失败: $e'); |
|
|
|
_wordClassDict = {}; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return wordClassDict; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 私有方法:解析stdfield文件 |
|
|
|
// 私有方法:解析stdfield文件 |
|
|
@ -137,14 +130,9 @@ class OutlineService { |
|
|
|
// 公开方法:加载子节点 |
|
|
|
// 公开方法:加载子节点 |
|
|
|
static Future<void> loadChildren(String rootPath, OutlineNode dirNode) async { |
|
|
|
static Future<void> loadChildren(String rootPath, OutlineNode dirNode) async { |
|
|
|
try { |
|
|
|
try { |
|
|
|
switch (dirNode.depth) { |
|
|
|
if (dirNode.depth == 1) { |
|
|
|
case 1: // 关键词(字段分类) |
|
|
|
await StdFieldService.loadStdfields(rootPath, dirNode); |
|
|
|
await _loadStdfields(rootPath, dirNode); |
|
|
|
} else { |
|
|
|
break; |
|
|
|
|
|
|
|
case 3: // 标准字段的操作 |
|
|
|
|
|
|
|
await _loadFieldActions(rootPath, dirNode); |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
default: |
|
|
|
|
|
|
|
Logger().error("节点层次不支持: ${dirNode.depth}"); |
|
|
|
Logger().error("节点层次不支持: ${dirNode.depth}"); |
|
|
|
} |
|
|
|
} |
|
|
|
} catch (e) { |
|
|
|
} catch (e) { |
|
|
@ -153,410 +141,22 @@ class OutlineService { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 私有方法:加载字段操作 |
|
|
|
// 公开方法:获取分词结果节点 |
|
|
|
static Future<void> _loadFieldActions(String rootPath, OutlineNode dirNode) async { |
|
|
|
static Future<List<OutlineNode>> getWordNodes(String rootPath) async { |
|
|
|
switch (dirNode.name) { |
|
|
|
if (rootPath.isEmpty) { |
|
|
|
case 'UFTTable': |
|
|
|
throw Exception('根路径不能为空'); |
|
|
|
await _loadUftObject(rootPath, dirNode.value, dirNode); |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
case 'Component': |
|
|
|
|
|
|
|
await _loadComponent(rootPath, dirNode.value, dirNode); |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
case 'Business': |
|
|
|
|
|
|
|
await _loadBusiness(rootPath, dirNode.value, dirNode); |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
case 'Atom': |
|
|
|
|
|
|
|
await _loadAtom(rootPath, dirNode.value, dirNode); |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
default: |
|
|
|
|
|
|
|
Logger().error("操作节点类型不支持: ${dirNode.value}"); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static Future<void> _loadComponent( |
|
|
|
|
|
|
|
String rootPath, |
|
|
|
|
|
|
|
String? fieldName, |
|
|
|
|
|
|
|
OutlineNode parentNode, |
|
|
|
|
|
|
|
) async { |
|
|
|
|
|
|
|
if (fieldName == null || fieldName.isEmpty) return; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
final matches = await _matchComponent(rootPath, fieldName); |
|
|
|
|
|
|
|
matches.forEach( |
|
|
|
|
|
|
|
(key, value) => parentNode.children.add( |
|
|
|
|
|
|
|
OutlineNode( |
|
|
|
|
|
|
|
name: key, |
|
|
|
|
|
|
|
title: value, |
|
|
|
|
|
|
|
value: 'Component', |
|
|
|
|
|
|
|
frequency: 0, |
|
|
|
|
|
|
|
isDirectory: false, |
|
|
|
|
|
|
|
depth: 4, |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//组合匹配 |
|
|
|
|
|
|
|
static Future<Map<String, String>> _matchComponent(String rootPath, String fieldName) async { |
|
|
|
|
|
|
|
Map<String, String> matchComponents = {}; |
|
|
|
|
|
|
|
final componentFile = File('$rootPath/metadata/component.xml'); |
|
|
|
|
|
|
|
if (!await componentFile.exists()) { |
|
|
|
|
|
|
|
Logger().error('component.xml文件不存在'); |
|
|
|
|
|
|
|
return matchComponents; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
final content = await componentFile.readAsString(); |
|
|
|
|
|
|
|
final document = XmlDocument.parse(content); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 查找所有items节点 |
|
|
|
|
|
|
|
for (final item in document.findAllElements('items')) { |
|
|
|
|
|
|
|
// 检查name属性是否匹配fieldName |
|
|
|
|
|
|
|
if (item.getAttribute('name') == fieldName) { |
|
|
|
|
|
|
|
// 获取父节点 |
|
|
|
|
|
|
|
final parentElement = item.parent; |
|
|
|
|
|
|
|
if (parentElement != null) { |
|
|
|
|
|
|
|
// 获取父节点的name和chineseName属性 |
|
|
|
|
|
|
|
final parentName = parentElement.getAttribute('name'); |
|
|
|
|
|
|
|
final parentChineseName = parentElement.getAttribute('chineseName'); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (parentName != null && parentChineseName != null) { |
|
|
|
|
|
|
|
matchComponents[parentName] = parentChineseName; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} catch (e) { |
|
|
|
|
|
|
|
Logger().error('加载Component失败: $e'); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return matchComponents; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//加载原子逻辑层 |
|
|
|
|
|
|
|
static Future<void> _loadAtom(String rootPath, String? fieldName, OutlineNode parentNode) async { |
|
|
|
|
|
|
|
if (fieldName == null || fieldName.isEmpty) return; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
final uftatomDir = Directory('$rootPath\\uftatom'); |
|
|
|
|
|
|
|
if (!await uftatomDir.exists()) { |
|
|
|
|
|
|
|
Logger().error('uftatom目录不存在'); |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
//获取对应组合 |
|
|
|
|
|
|
|
final matchComponents = await _matchComponent(rootPath, fieldName); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 遍历所有.uftstructure文件 |
|
|
|
|
|
|
|
final uftatomFiles = |
|
|
|
|
|
|
|
await uftatomDir |
|
|
|
|
|
|
|
.list(recursive: true) |
|
|
|
|
|
|
|
.where( |
|
|
|
|
|
|
|
(entity) => |
|
|
|
|
|
|
|
entity.path.endsWith('.uftatomfunction') || |
|
|
|
|
|
|
|
entity.path.endsWith('.uftatomservice'), |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
.cast<File>() |
|
|
|
|
|
|
|
.toList(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (final file in uftatomFiles) { |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
final content = await file.readAsString(); |
|
|
|
|
|
|
|
final document = XmlDocument.parse(content); |
|
|
|
|
|
|
|
String matchType = "I"; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 查找匹配的入参 |
|
|
|
|
|
|
|
final List<XmlElement> matchingProperties = |
|
|
|
|
|
|
|
document |
|
|
|
|
|
|
|
.findAllElements('inputParameters') |
|
|
|
|
|
|
|
.where( |
|
|
|
|
|
|
|
(element) => |
|
|
|
|
|
|
|
element.getAttribute('id') == fieldName || |
|
|
|
|
|
|
|
element.getAttribute("paramType") == "COMPONENT" && |
|
|
|
|
|
|
|
matchComponents.containsKey(element.getAttribute('id')), |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
.toList(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//匹配出参 |
|
|
|
|
|
|
|
if (matchingProperties.isEmpty) { |
|
|
|
|
|
|
|
matchingProperties.addAll( |
|
|
|
|
|
|
|
document |
|
|
|
|
|
|
|
.findAllElements('outputParameters') |
|
|
|
|
|
|
|
.where( |
|
|
|
|
|
|
|
(element) => |
|
|
|
|
|
|
|
element.getAttribute('id') == fieldName || |
|
|
|
|
|
|
|
element.getAttribute("paramType") == "COMPONENT" && |
|
|
|
|
|
|
|
matchComponents.containsKey(element.getAttribute('id')), |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
matchType = "O"; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//匹配内部变量 |
|
|
|
|
|
|
|
if (matchingProperties.isEmpty) { |
|
|
|
|
|
|
|
matchingProperties.addAll( |
|
|
|
|
|
|
|
document |
|
|
|
|
|
|
|
.findAllElements('internalParams') |
|
|
|
|
|
|
|
.where( |
|
|
|
|
|
|
|
(element) => |
|
|
|
|
|
|
|
element.getAttribute('id') == fieldName || |
|
|
|
|
|
|
|
element.getAttribute("paramType") == "COMPONENT" && |
|
|
|
|
|
|
|
matchComponents.containsKey(element.getAttribute('id')), |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
matchType = "X"; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//匹配组合 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (matchingProperties.isNotEmpty) { |
|
|
|
|
|
|
|
// 获取structure:Structure节点的chineseName |
|
|
|
|
|
|
|
final businessNode = document.findAllElements('business:Function').firstOrNull; |
|
|
|
|
|
|
|
final chineseName = businessNode?.getAttribute('chineseName') ?? '未命名'; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 获取文件名(不带路径) |
|
|
|
|
|
|
|
final fileName = file.path; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 创建并添加子节点 |
|
|
|
|
|
|
|
parentNode.children.add( |
|
|
|
|
|
|
|
OutlineNode( |
|
|
|
|
|
|
|
name: fileName, |
|
|
|
|
|
|
|
title: '$chineseName($matchType)', |
|
|
|
|
|
|
|
value: 'Atom', |
|
|
|
|
|
|
|
frequency: 0, |
|
|
|
|
|
|
|
isDirectory: false, // 这些是叶子节点 |
|
|
|
|
|
|
|
depth: 4, |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} catch (e) { |
|
|
|
|
|
|
|
Logger().error('解析文件 ${file.path} 失败: $e'); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Logger().info('为 $fieldName 找到 ${parentNode.children.length} 个匹配项'); |
|
|
|
|
|
|
|
} catch (e) { |
|
|
|
|
|
|
|
Logger().error('加载UFT对象失败: $e'); |
|
|
|
|
|
|
|
rethrow; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//加载业务逻辑层 |
|
|
|
|
|
|
|
static Future<void> _loadBusiness( |
|
|
|
|
|
|
|
String rootPath, |
|
|
|
|
|
|
|
String? fieldName, |
|
|
|
|
|
|
|
OutlineNode parentNode, |
|
|
|
|
|
|
|
) async { |
|
|
|
|
|
|
|
if (fieldName == null || fieldName.isEmpty) return; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
final uftbusinessDir = Directory('$rootPath\\uftbusiness'); |
|
|
|
|
|
|
|
if (!await uftbusinessDir.exists()) { |
|
|
|
|
|
|
|
Logger().error('uftbusiness目录不存在'); |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
final matchComponents = await _matchComponent(rootPath, fieldName); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 遍历所有.uftstructure文件 |
|
|
|
|
|
|
|
final uftbusinessFiles = |
|
|
|
|
|
|
|
await uftbusinessDir |
|
|
|
|
|
|
|
.list(recursive: true) |
|
|
|
|
|
|
|
.where((entity) => entity.path.endsWith('.uftfunction')) |
|
|
|
|
|
|
|
.cast<File>() |
|
|
|
|
|
|
|
.toList(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (final file in uftbusinessFiles) { |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
final content = await file.readAsString(); |
|
|
|
|
|
|
|
final document = XmlDocument.parse(content); |
|
|
|
|
|
|
|
String matchType = "I"; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 查找匹配的入参 |
|
|
|
|
|
|
|
final List<XmlElement> matchingProperties = |
|
|
|
|
|
|
|
document |
|
|
|
|
|
|
|
.findAllElements('inputParameters') |
|
|
|
|
|
|
|
.where( |
|
|
|
|
|
|
|
(element) => |
|
|
|
|
|
|
|
element.getAttribute('id') == fieldName || |
|
|
|
|
|
|
|
element.getAttribute("paramType") == "COMPONENT" && |
|
|
|
|
|
|
|
matchComponents.containsKey(element.getAttribute('id')), |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
.toList(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (matchingProperties.isEmpty) { |
|
|
|
|
|
|
|
matchingProperties.addAll( |
|
|
|
|
|
|
|
document |
|
|
|
|
|
|
|
.findAllElements('outputParameters') |
|
|
|
|
|
|
|
.where( |
|
|
|
|
|
|
|
(element) => |
|
|
|
|
|
|
|
element.getAttribute('id') == fieldName || |
|
|
|
|
|
|
|
element.getAttribute("paramType") == "COMPONENT" && |
|
|
|
|
|
|
|
matchComponents.containsKey(element.getAttribute('id')), |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
matchType = "O"; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (matchingProperties.isEmpty) { |
|
|
|
|
|
|
|
matchingProperties.addAll( |
|
|
|
|
|
|
|
document |
|
|
|
|
|
|
|
.findAllElements('internalParams') |
|
|
|
|
|
|
|
.where( |
|
|
|
|
|
|
|
(element) => |
|
|
|
|
|
|
|
element.getAttribute('id') == fieldName || |
|
|
|
|
|
|
|
element.getAttribute("paramType") == "COMPONENT" && |
|
|
|
|
|
|
|
matchComponents.containsKey(element.getAttribute('id')), |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
matchType = "X"; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (matchingProperties.isNotEmpty) { |
|
|
|
|
|
|
|
// 获取structure:Structure节点的chineseName |
|
|
|
|
|
|
|
final businessNode = document.findAllElements('business:Function').firstOrNull; |
|
|
|
|
|
|
|
final chineseName = businessNode?.getAttribute('chineseName') ?? '未命名'; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 获取文件名(不带路径) |
|
|
|
|
|
|
|
final fileName = file.path; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 创建并添加子节点 |
|
|
|
|
|
|
|
parentNode.children.add( |
|
|
|
|
|
|
|
OutlineNode( |
|
|
|
|
|
|
|
name: fileName, |
|
|
|
|
|
|
|
title: '$chineseName($matchType)', |
|
|
|
|
|
|
|
value: 'Business', |
|
|
|
|
|
|
|
frequency: 0, |
|
|
|
|
|
|
|
isDirectory: false, // 这些是叶子节点 |
|
|
|
|
|
|
|
depth: 4, |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} catch (e) { |
|
|
|
|
|
|
|
Logger().error('解析文件 ${file.path} 失败: $e'); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Logger().info('为 $fieldName 找到 ${parentNode.children.length} 个匹配项'); |
|
|
|
|
|
|
|
} catch (e) { |
|
|
|
|
|
|
|
Logger().error('加载UFT对象失败: $e'); |
|
|
|
|
|
|
|
rethrow; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 私有方法:加载UFT对象 |
|
|
|
|
|
|
|
static Future<void> _loadUftObject( |
|
|
|
|
|
|
|
String rootPath, |
|
|
|
|
|
|
|
String? fieldName, |
|
|
|
|
|
|
|
OutlineNode parentNode, |
|
|
|
|
|
|
|
) async { |
|
|
|
|
|
|
|
if (fieldName == null || fieldName.isEmpty) return; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
final uftStructureDir = Directory('$rootPath/uftstructure'); |
|
|
|
|
|
|
|
if (!await uftStructureDir.exists()) { |
|
|
|
|
|
|
|
Logger().error('uftstructure目录不存在'); |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
// 遍历所有.uftstructure文件 |
|
|
|
|
|
|
|
final uftStructureFiles = |
|
|
|
|
|
|
|
await uftStructureDir |
|
|
|
|
|
|
|
.list(recursive: true) |
|
|
|
|
|
|
|
.where((entity) => entity.path.endsWith('.uftstructure')) |
|
|
|
|
|
|
|
.cast<File>() |
|
|
|
|
|
|
|
.toList(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (final file in uftStructureFiles) { |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
final content = await file.readAsString(); |
|
|
|
|
|
|
|
final document = XmlDocument.parse(content); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 查找匹配的properties节点 |
|
|
|
|
|
|
|
final matchingProperties = document |
|
|
|
|
|
|
|
.findAllElements('properties') |
|
|
|
|
|
|
|
.where((element) => element.getAttribute('id') == fieldName); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (matchingProperties.isNotEmpty) { |
|
|
|
|
|
|
|
// 获取structure:Structure节点的chineseName |
|
|
|
|
|
|
|
final structureNode = document.findAllElements('structure:Structure').firstOrNull; |
|
|
|
|
|
|
|
final chineseName = structureNode?.getAttribute('chineseName') ?? '未命名'; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 获取文件名(不带路径) |
|
|
|
|
|
|
|
final fileName = file.path; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 创建并添加子节点 |
|
|
|
|
|
|
|
parentNode.children.add( |
|
|
|
|
|
|
|
OutlineNode( |
|
|
|
|
|
|
|
name: fileName, |
|
|
|
|
|
|
|
title: chineseName, |
|
|
|
|
|
|
|
value: 'UFTTable', |
|
|
|
|
|
|
|
frequency: 0, |
|
|
|
|
|
|
|
isDirectory: false, // 这些是叶子节点 |
|
|
|
|
|
|
|
depth: 4, |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} catch (e) { |
|
|
|
|
|
|
|
Logger().error('解析文件 ${file.path} 失败: $e'); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Logger().info('为 $fieldName 找到 ${parentNode.children.length} 个匹配项'); |
|
|
|
|
|
|
|
} catch (e) { |
|
|
|
|
|
|
|
Logger().error('加载UFT对象失败: $e'); |
|
|
|
|
|
|
|
rethrow; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 私有方法:加载标准字段 |
|
|
|
|
|
|
|
static Future<void> _loadStdfields(String rootPath, OutlineNode dirNode) async { |
|
|
|
|
|
|
|
final stdfieldFile = File('$rootPath/metadata/stdfield.stdfield'); |
|
|
|
|
|
|
|
if (!await stdfieldFile.exists()) { |
|
|
|
|
|
|
|
throw Exception('stdfield文件不存在'); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
final content = await stdfieldFile.readAsString(); |
|
|
|
|
|
|
|
final document = XmlDocument.parse(content); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
dirNode.children.clear(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (final item in document.findAllElements('items')) { |
|
|
|
|
|
|
|
final chineseName = item.getAttribute('chineseName'); |
|
|
|
|
|
|
|
final name = item.getAttribute('name'); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (chineseName != null && name != null && chineseName.contains(dirNode.name)) { |
|
|
|
|
|
|
|
final fieldNode = OutlineNode( |
|
|
|
|
|
|
|
name: chineseName, |
|
|
|
|
|
|
|
title: '$chineseName($name)', |
|
|
|
|
|
|
|
value: name, |
|
|
|
|
|
|
|
frequency: 0, |
|
|
|
|
|
|
|
isDirectory: true, |
|
|
|
|
|
|
|
depth: 2, |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
_createActionNodes(fieldNode); |
|
|
|
|
|
|
|
dirNode.children.add(fieldNode); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 私有方法:创建操作节点 |
|
|
|
final wordClassDict = await _initWordClassDict(); |
|
|
|
static void _createActionNodes(OutlineNode parentNode) { |
|
|
|
|
|
|
|
parentNode.children.addAll( |
|
|
|
|
|
|
|
FIELD_ACTIONS.entries.map( |
|
|
|
|
|
|
|
(entry) => OutlineNode( |
|
|
|
|
|
|
|
name: entry.key, |
|
|
|
|
|
|
|
value: parentNode.value, |
|
|
|
|
|
|
|
title: entry.value, |
|
|
|
|
|
|
|
frequency: 0, |
|
|
|
|
|
|
|
isDirectory: true, |
|
|
|
|
|
|
|
depth: 3, |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 公开方法:获取分词结果节点 |
|
|
|
//初始化数据 |
|
|
|
static Future<List<OutlineNode>> getWordNodes(String rootPath) async { |
|
|
|
await Future.wait([ |
|
|
|
await Future.wait([_initBlackList(), _initWordClassDict()]); |
|
|
|
_initBlackList(), |
|
|
|
|
|
|
|
ComponentService.initComponentFieldMap(rootPath), |
|
|
|
|
|
|
|
StdFieldService.initStdFieldMap(rootPath), |
|
|
|
|
|
|
|
UftObjectService.initUftObjectMap(rootPath), |
|
|
|
|
|
|
|
FunctionsService.initFunctionsMap(rootPath), |
|
|
|
|
|
|
|
]); |
|
|
|
|
|
|
|
|
|
|
|
final stdfieldPath = '$rootPath/metadata/stdfield.stdfield'; |
|
|
|
final stdfieldPath = '$rootPath/metadata/stdfield.stdfield'; |
|
|
|
if (!await File(stdfieldPath).exists()) { |
|
|
|
if (!await File(stdfieldPath).exists()) { |
|
|
@ -580,8 +180,7 @@ class OutlineService { |
|
|
|
frequency: entry.value, |
|
|
|
frequency: entry.value, |
|
|
|
isDirectory: true, |
|
|
|
isDirectory: true, |
|
|
|
depth: 1, |
|
|
|
depth: 1, |
|
|
|
isRoot: true, |
|
|
|
wordClass: wordClassDict[entry.key], |
|
|
|
wordClass: _wordClassDict[entry.key], |
|
|
|
|
|
|
|
), |
|
|
|
), |
|
|
|
) |
|
|
|
) |
|
|
|
.where((node) => !FILTERED_WORD_CLASSES.contains(node.wordClass)) |
|
|
|
.where((node) => !FILTERED_WORD_CLASSES.contains(node.wordClass)) |
|
|
@ -617,4 +216,108 @@ class OutlineService { |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static String _oldSearchQuery = ""; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void applySearchFilter(OutlineNode root, String searchQuery) { |
|
|
|
|
|
|
|
if (_searchNode != null && _oldSearchQuery == searchQuery) { |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_oldSearchQuery = searchQuery; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//创建搜索词第一层节点 |
|
|
|
|
|
|
|
_searchNode = OutlineNode( |
|
|
|
|
|
|
|
name: searchQuery, |
|
|
|
|
|
|
|
title: searchQuery, |
|
|
|
|
|
|
|
value: 'Search', |
|
|
|
|
|
|
|
isDirectory: true, |
|
|
|
|
|
|
|
isExpanded: true, |
|
|
|
|
|
|
|
depth: 1, |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//搜索标准字段 |
|
|
|
|
|
|
|
final List<OutlineNode> stdFieldNodes = StdFieldService.searchStdFields(searchQuery); |
|
|
|
|
|
|
|
_searchNode?.children.addAll(stdFieldNodes); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
final virtualNode = OutlineNode( |
|
|
|
|
|
|
|
name: "virtualNode", |
|
|
|
|
|
|
|
title: "【非字段匹配】", |
|
|
|
|
|
|
|
value: 'virtualNode', |
|
|
|
|
|
|
|
isDirectory: true, |
|
|
|
|
|
|
|
depth: 2, |
|
|
|
|
|
|
|
isExpanded: true, |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//搜索UFT对象 |
|
|
|
|
|
|
|
final List<OutlineNode> uftObjectNodes = UftObjectService.searchUftObjects(searchQuery); |
|
|
|
|
|
|
|
if (uftObjectNodes.isNotEmpty) { |
|
|
|
|
|
|
|
final uftObjectActionNode = OutlineNode( |
|
|
|
|
|
|
|
name: 'UFTTable', |
|
|
|
|
|
|
|
title: 'UFT对象(${uftObjectNodes.length})', |
|
|
|
|
|
|
|
value: 'virtualNode', |
|
|
|
|
|
|
|
isDirectory: true, |
|
|
|
|
|
|
|
depth: 3, |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
uftObjectActionNode.children.addAll(uftObjectNodes); |
|
|
|
|
|
|
|
virtualNode.children.add(uftObjectActionNode); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//搜索组件 |
|
|
|
|
|
|
|
final List<OutlineNode> componentNodes = ComponentService.searchComponents(searchQuery); |
|
|
|
|
|
|
|
if (componentNodes.isNotEmpty) { |
|
|
|
|
|
|
|
final componentActionNode = OutlineNode( |
|
|
|
|
|
|
|
name: 'Component', |
|
|
|
|
|
|
|
title: '标准组件(${componentNodes.length})', |
|
|
|
|
|
|
|
value: 'virtualNode', |
|
|
|
|
|
|
|
isDirectory: true, |
|
|
|
|
|
|
|
depth: 3, |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
componentActionNode.children.addAll(componentNodes); |
|
|
|
|
|
|
|
virtualNode.children.add(componentActionNode); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//搜索函数 |
|
|
|
|
|
|
|
final List<OutlineNode> businessNodes = FunctionsService.searchBusiness(searchQuery); |
|
|
|
|
|
|
|
if (businessNodes.isNotEmpty) { |
|
|
|
|
|
|
|
final businessActionNode = OutlineNode( |
|
|
|
|
|
|
|
name: 'Business', |
|
|
|
|
|
|
|
title: "业务层(${businessNodes.length})", |
|
|
|
|
|
|
|
value: 'virtualNode', |
|
|
|
|
|
|
|
isDirectory: true, |
|
|
|
|
|
|
|
depth: 3, |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
businessActionNode.children.addAll(businessNodes); |
|
|
|
|
|
|
|
virtualNode.children.add(businessActionNode); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//搜索函数 |
|
|
|
|
|
|
|
final List<OutlineNode> atomNodes = FunctionsService.searchAtoms(searchQuery); |
|
|
|
|
|
|
|
if (atomNodes.isNotEmpty) { |
|
|
|
|
|
|
|
final atomActionNode = OutlineNode( |
|
|
|
|
|
|
|
name: 'Atom', |
|
|
|
|
|
|
|
title: '原子层(${atomNodes.length})', |
|
|
|
|
|
|
|
value: 'virtualNode', |
|
|
|
|
|
|
|
isDirectory: true, |
|
|
|
|
|
|
|
depth: 3, |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
atomActionNode.children.addAll(atomNodes); |
|
|
|
|
|
|
|
virtualNode.children.add(atomActionNode); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (virtualNode.children.isNotEmpty) { |
|
|
|
|
|
|
|
_searchNode?.children.add(virtualNode); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//如果没有子节点,返回空列表 |
|
|
|
|
|
|
|
if (_searchNode!.children.isNotEmpty) { |
|
|
|
|
|
|
|
root.title = "搜索结果"; |
|
|
|
|
|
|
|
root.children.clear(); |
|
|
|
|
|
|
|
root.isExpanded = false; |
|
|
|
|
|
|
|
root.children.add(_searchNode!); |
|
|
|
|
|
|
|
root.isExpanded = true; |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
root.title = "无结果"; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|