11 changed files with 349381 additions and 403 deletions
@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
|
||||
outline_name_black_list: |
||||
- 的,了,和,是,在 |
||||
- 历史,日志 |
File diff suppressed because it is too large
Load Diff
@ -1,128 +1,151 @@
@@ -1,128 +1,151 @@
|
||||
// outline_service.dart |
||||
import 'dart:io'; |
||||
import 'package:win_text_editor/framework/controllers/logger.dart'; |
||||
import 'package:win_text_editor/framework/services/fast_xml_parser.dart'; |
||||
import 'package:flutter/services.dart'; |
||||
import 'package:jieba_flutter/analysis/jieba_segmenter.dart'; |
||||
import 'package:jieba_flutter/analysis/seg_token.dart'; |
||||
import 'package:win_text_editor/modules/outline/models/outline_node.dart'; |
||||
import 'package:xml/xml.dart'; |
||||
import 'package:yaml/yaml.dart'; |
||||
import 'package:win_text_editor/framework/controllers/logger.dart'; |
||||
|
||||
class OutlineService { |
||||
static const _specialExtensions = [ |
||||
'.uftfunction', |
||||
'.uftservice', |
||||
'.uftatomfunction', |
||||
'.uftatomservice', |
||||
'.uftfactorfunction', |
||||
'.uftfactorservice', |
||||
]; |
||||
static const Map<String, String> _uftFloders = { |
||||
'.settings': '项目设置', |
||||
'metadata': '元数据', |
||||
'tools': '工具资源', |
||||
'uftatom': 'UFT原子', |
||||
'uftbusiness': 'UFT业务逻辑', |
||||
'uftfactor': 'UFT因子', |
||||
'uftstructure': 'UFT对象', |
||||
}; |
||||
static const _hiddenFiles = ['.classpath', '.project', '.respath', 'project.xml', 'module.xml']; |
||||
|
||||
static Future<String?> getSpecialFileName(String filePath) async { |
||||
final extension = filePath.substring(filePath.lastIndexOf('.')); |
||||
if (!_specialExtensions.contains(extension)) { |
||||
return null; |
||||
static List<String> _blackList = []; |
||||
static bool _isJiebaInitialized = false; // 标记分词器是否初始化 |
||||
static JiebaSegmenter? _segmenter; // 分词器实例 |
||||
|
||||
// 初始化分词器 |
||||
static Future<void> _initJieba() async { |
||||
if (!_isJiebaInitialized) { |
||||
await JiebaSegmenter.init(); // 使用正确的初始化方法 |
||||
_segmenter = JiebaSegmenter(); // 创建分词器实例 |
||||
_isJiebaInitialized = true; |
||||
} |
||||
} |
||||
|
||||
// 初始化黑名单 |
||||
// 初始化黑名单 - 修正版本 |
||||
static Future<void> _initBlackList() async { |
||||
try { |
||||
final result = await FastXmlParser.parse(filePath); |
||||
return ('[${result['objectId']}]${result['chineseName']}'); |
||||
} catch (e) { |
||||
Logger().debug('Error reading special file: $e'); |
||||
} |
||||
return null; |
||||
} |
||||
final yamlString = await rootBundle.loadString('assets/config/words_classes.yaml'); |
||||
final yamlMap = loadYaml(yamlString); |
||||
|
||||
/// 延时加载目录内容(不递归) |
||||
static Future<List<OutlineNode>> listDirectory(String path, {int parentDepth = 0}) async { |
||||
final dir = Directory(path); |
||||
final List<FileSystemEntity> entities = await dir.list().toList(); |
||||
final List<OutlineNode> nodes = []; |
||||
// final stopwatch = Stopwatch()..start(); |
||||
|
||||
for (final entity in entities) { |
||||
final pathName = entity.path.split(Platform.pathSeparator).last; |
||||
if (_hiddenFiles.contains(pathName)) continue; |
||||
|
||||
final isDirectory = await FileSystemEntity.isDirectory(entity.path); |
||||
final displayName = |
||||
isDirectory |
||||
? await getModuleDisplayName(entity.path) |
||||
: await getSpecialFileName(entity.path); |
||||
|
||||
nodes.add( |
||||
OutlineNode( |
||||
name: displayName ?? pathName, |
||||
path: entity.path, |
||||
isDirectory: isDirectory, |
||||
depth: parentDepth + 1, |
||||
), |
||||
); |
||||
} |
||||
// 安全获取黑名单列表 |
||||
final blackListItems = (yamlMap['outline_name_black_list'] as List?)?.cast<String>() ?? []; |
||||
|
||||
// stopwatch.stop(); |
||||
// Logger().debug('执行耗时: ${stopwatch.elapsedMilliseconds} 毫秒 (ms)'); |
||||
// 处理逗号分隔的黑名单项 |
||||
_blackList = |
||||
blackListItems.expand<String>((item) { |
||||
return item.split(',').map((word) => word.trim()).where((word) => word.isNotEmpty); |
||||
}).toList(); |
||||
|
||||
return nodes; |
||||
Logger().info('加载黑名单成功: ${_blackList.length}个词'); |
||||
} catch (e) { |
||||
Logger().error('加载黑名单失败: $e'); |
||||
_blackList = []; |
||||
} |
||||
} |
||||
|
||||
static Future<String?> getModuleDisplayName(String dirPath) async { |
||||
// 解析stdfield文件获取中文名称 |
||||
static Future<List<String>> _parseChineseNames(String filePath) async { |
||||
try { |
||||
final floderName = dirPath.split(Platform.pathSeparator).last; |
||||
if (_uftFloders.containsKey(floderName)) return _uftFloders[floderName]; |
||||
|
||||
final moduleFile = File('$dirPath${Platform.pathSeparator}module.xml'); |
||||
if (await moduleFile.exists()) { |
||||
final content = await moduleFile.readAsString(); |
||||
final xmlDoc = XmlDocument.parse(content); |
||||
final infoNode = xmlDoc.findAllElements('info').firstOrNull; |
||||
return infoNode?.getAttribute('cname'); |
||||
} |
||||
final file = File(filePath); |
||||
final content = await file.readAsString(); |
||||
final document = XmlDocument.parse(content); |
||||
|
||||
return document |
||||
.findAllElements('items') |
||||
.map((e) => e.getAttribute('chineseName') ?? '') |
||||
.where((name) => name.isNotEmpty) |
||||
.toList(); |
||||
} catch (e) { |
||||
Logger().debug('Error reading module.xml: $e'); |
||||
Logger().error('解析stdfield文件失败: $e'); |
||||
return []; |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
/// 递归构建完整文件树(原方法保留备用) |
||||
static Future<List<OutlineNode>> buildOutlineTree(String rootPath) async { |
||||
final rootDirectory = Directory(rootPath); |
||||
final List<OutlineNode> nodes = []; |
||||
|
||||
if (await rootDirectory.exists()) { |
||||
final entities = rootDirectory.listSync(); |
||||
|
||||
for (final entity in entities) { |
||||
final pathName = entity.path.split(Platform.pathSeparator).last; |
||||
if (_hiddenFiles.contains(pathName)) continue; |
||||
final node = OutlineNode( |
||||
name: pathName, |
||||
path: entity.path, |
||||
isDirectory: entity is Directory, |
||||
); |
||||
|
||||
if (entity is Directory) { |
||||
node.children.addAll(await buildOutlineTree(entity.path)); |
||||
// 分词并统计词频 |
||||
static Future<Map<String, int>> _analyzeWords(List<String> chineseNames) async { |
||||
await _initJieba(); // 确保分词器已初始化 |
||||
final wordFrequency = <String, int>{}; |
||||
Logger().info('开始分词,共有 ${chineseNames.length} 个中文名称'); |
||||
for (final name in chineseNames) { |
||||
List<SegToken> tokens = _segmenter!.process(name, SegMode.SEARCH); // 使用分词器实例 |
||||
for (final token in tokens) { |
||||
final word = token.word.trim(); |
||||
if (word.length > 1 && !_blackList.contains(word)) { |
||||
wordFrequency[word] = (wordFrequency[word] ?? 0) + 1; |
||||
} |
||||
|
||||
nodes.add(node); |
||||
} |
||||
} |
||||
|
||||
return nodes; |
||||
Logger().info('分词完成,共找到 ${wordFrequency.length} 个有效词语'); |
||||
return wordFrequency; |
||||
} |
||||
|
||||
static Future<String> readFile(String filePath) async { |
||||
return await File(filePath).readAsString(); |
||||
// 获取分词结果 |
||||
static Future<List<OutlineNode>> getWordNodes(String rootPath) async { |
||||
await _initBlackList(); |
||||
|
||||
final stdfieldPath = '$rootPath/metadata/stdfield.stdfield'; |
||||
if (!await File(stdfieldPath).exists()) { |
||||
Logger().error('stdfield文件不存在'); |
||||
return []; |
||||
} |
||||
|
||||
final chineseNames = await _parseChineseNames(stdfieldPath); |
||||
if (chineseNames.isEmpty) { |
||||
Logger().error('未找到有效的中文名称'); |
||||
return []; |
||||
} |
||||
Logger().info('找到 ${chineseNames.length} 个标准字段'); |
||||
|
||||
final wordFrequency = await _analyzeWords(chineseNames); |
||||
final sortedWords = wordFrequency.entries.toList()..sort((a, b) => b.value.compareTo(a.value)); |
||||
|
||||
return sortedWords |
||||
.map( |
||||
(entry) => OutlineNode( |
||||
name: '${entry.key}(${entry.value})', |
||||
frequency: entry.value, |
||||
isDirectory: true, |
||||
depth: 2, |
||||
), |
||||
) |
||||
.toList(); |
||||
} |
||||
|
||||
static Future<void> writeFile(String filePath, String content) async { |
||||
await File(filePath).writeAsString(content); |
||||
// 检查目录结构是否完整 |
||||
static Future<bool> validateDirectoryStructure(String rootPath) async { |
||||
try { |
||||
// 检查必须的文件 |
||||
final requiredFiles = [ |
||||
'metadata/stdfield.stdfield', |
||||
'metadata/stdobj.xml', |
||||
'metadata/component.xml', |
||||
]; |
||||
|
||||
for (var filePath in requiredFiles) { |
||||
final file = File('$rootPath/$filePath'); |
||||
if (!await file.exists()) { |
||||
Logger().error('缺少必要文件: $filePath'); |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
// 检查必须的目录 |
||||
final requiredDirs = ['uftstructure', 'uftatom', 'uftbusiness', 'uftfactor']; |
||||
|
||||
for (var dirPath in requiredDirs) { |
||||
final dir = Directory('$rootPath/$dirPath'); |
||||
if (!await dir.exists()) { |
||||
Logger().error('缺少必要目录: $dirPath'); |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
return true; |
||||
} catch (e) { |
||||
Logger().error('验证目录结构时出错: $e'); |
||||
return false; |
||||
} |
||||
} |
||||
} |
||||
|
@ -1,74 +0,0 @@
@@ -1,74 +0,0 @@
|
||||
import 'dart:io'; |
||||
import 'package:xml/xml.dart'; |
||||
|
||||
void main2() { |
||||
const dictFilePath = "D:/PBHK/pbhk_trade/Sources/ServerUFT/UFT/metadata/dict.dict"; |
||||
const fieldFilePath = "D:/PBHK/pbhk_trade/Sources/ServerUFT/UFT/metadata/stdfield.stdfield"; |
||||
final dictFile = File(dictFilePath); |
||||
final fieldFile = File(fieldFilePath); |
||||
|
||||
if (!dictFile.existsSync()) { |
||||
print('字典文件不存在: $dictFilePath'); |
||||
return; |
||||
} |
||||
if (!fieldFile.existsSync()) { |
||||
print('字段文件不存在: $fieldFilePath'); |
||||
return; |
||||
} |
||||
|
||||
try { |
||||
// 1. 读取字典文件 |
||||
final dictDocument = XmlDocument.parse(dictFile.readAsStringSync()); |
||||
|
||||
// 2. 读取字段文件 |
||||
final fieldDocument = XmlDocument.parse(fieldFile.readAsStringSync()); |
||||
|
||||
// 3. 找出字典文件中所有 xsi:type="metadata:DictionaryType" 的items节点 |
||||
final dictionaryItems = |
||||
dictDocument.findAllElements('items').where((element) { |
||||
return element.getAttribute('xsi:type') == 'metadata:DictionaryType'; |
||||
}).toList(); |
||||
|
||||
// 4. 处理这些节点 |
||||
for (final item in dictionaryItems) { |
||||
bool hasConstantName = false; |
||||
String? dictionaryName; |
||||
|
||||
// 检查所有子节点 |
||||
for (final child in item.childElements) { |
||||
final constantName = child.getAttribute('constantName'); |
||||
if (constantName != null && constantName.isNotEmpty) { |
||||
hasConstantName = true; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
// 如果没有子节点包含constantName属性 |
||||
if (!hasConstantName) { |
||||
dictionaryName = item.getAttribute('name'); |
||||
|
||||
if (dictionaryName != null && dictionaryName.isNotEmpty) { |
||||
// 在字段文件中查找是否有items节点的dictionaryType等于该name值 |
||||
final matchingFieldItems = fieldDocument.findAllElements('items').where((element) { |
||||
return element.getAttribute('dictionaryType') == dictionaryName; |
||||
}); |
||||
|
||||
// 如果没有找到匹配的字段,则删除字典中的当前节点 |
||||
if (matchingFieldItems.isEmpty) { |
||||
item.parent?.children.remove(item); |
||||
} |
||||
} else { |
||||
// 如果没有name属性,也删除该节点 |
||||
item.parent?.children.remove(item); |
||||
} |
||||
} |
||||
} |
||||
|
||||
// 5. 保存修改后的字典文件 |
||||
final outputPath = dictFilePath.replaceAll('.dict', '_modified.xml'); |
||||
File(outputPath).writeAsStringSync(dictDocument.toXmlString(pretty: true)); |
||||
print('处理完成,结果已保存到: $outputPath'); |
||||
} catch (e) { |
||||
print('处理XML文件时出错: $e'); |
||||
} |
||||
} |
Loading…
Reference in new issue