11 changed files with 349381 additions and 403 deletions
@ -0,0 +1,3 @@ |
|||||||
|
outline_name_black_list: |
||||||
|
- 的,了,和,是,在 |
||||||
|
- 历史,日志 |
File diff suppressed because it is too large
Load Diff
@ -1,128 +1,151 @@ |
|||||||
|
// outline_service.dart |
||||||
import 'dart:io'; |
import 'dart:io'; |
||||||
import 'package:win_text_editor/framework/controllers/logger.dart'; |
import 'package:flutter/services.dart'; |
||||||
import 'package:win_text_editor/framework/services/fast_xml_parser.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:win_text_editor/modules/outline/models/outline_node.dart'; |
||||||
import 'package:xml/xml.dart'; |
import 'package:xml/xml.dart'; |
||||||
|
import 'package:yaml/yaml.dart'; |
||||||
|
import 'package:win_text_editor/framework/controllers/logger.dart'; |
||||||
|
|
||||||
class OutlineService { |
class OutlineService { |
||||||
static const _specialExtensions = [ |
static List<String> _blackList = []; |
||||||
'.uftfunction', |
static bool _isJiebaInitialized = false; // 标记分词器是否初始化 |
||||||
'.uftservice', |
static JiebaSegmenter? _segmenter; // 分词器实例 |
||||||
'.uftatomfunction', |
|
||||||
'.uftatomservice', |
// 初始化分词器 |
||||||
'.uftfactorfunction', |
static Future<void> _initJieba() async { |
||||||
'.uftfactorservice', |
if (!_isJiebaInitialized) { |
||||||
]; |
await JiebaSegmenter.init(); // 使用正确的初始化方法 |
||||||
static const Map<String, String> _uftFloders = { |
_segmenter = JiebaSegmenter(); // 创建分词器实例 |
||||||
'.settings': '项目设置', |
_isJiebaInitialized = true; |
||||||
'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 Future<void> _initBlackList() async { |
||||||
try { |
try { |
||||||
final result = await FastXmlParser.parse(filePath); |
final yamlString = await rootBundle.loadString('assets/config/words_classes.yaml'); |
||||||
return ('[${result['objectId']}]${result['chineseName']}'); |
final yamlMap = loadYaml(yamlString); |
||||||
} catch (e) { |
|
||||||
Logger().debug('Error reading special file: $e'); |
|
||||||
} |
|
||||||
return null; |
|
||||||
} |
|
||||||
|
|
||||||
/// 延时加载目录内容(不递归) |
// 安全获取黑名单列表 |
||||||
static Future<List<OutlineNode>> listDirectory(String path, {int parentDepth = 0}) async { |
final blackListItems = (yamlMap['outline_name_black_list'] as List?)?.cast<String>() ?? []; |
||||||
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, |
|
||||||
), |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
// 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 { |
try { |
||||||
final floderName = dirPath.split(Platform.pathSeparator).last; |
final file = File(filePath); |
||||||
if (_uftFloders.containsKey(floderName)) return _uftFloders[floderName]; |
final content = await file.readAsString(); |
||||||
|
final document = XmlDocument.parse(content); |
||||||
final moduleFile = File('$dirPath${Platform.pathSeparator}module.xml'); |
|
||||||
if (await moduleFile.exists()) { |
return document |
||||||
final content = await moduleFile.readAsString(); |
.findAllElements('items') |
||||||
final xmlDoc = XmlDocument.parse(content); |
.map((e) => e.getAttribute('chineseName') ?? '') |
||||||
final infoNode = xmlDoc.findAllElements('info').firstOrNull; |
.where((name) => name.isNotEmpty) |
||||||
return infoNode?.getAttribute('cname'); |
.toList(); |
||||||
} |
|
||||||
} catch (e) { |
} catch (e) { |
||||||
Logger().debug('Error reading module.xml: $e'); |
Logger().error('解析stdfield文件失败: $e'); |
||||||
|
return []; |
||||||
} |
} |
||||||
return null; |
|
||||||
} |
} |
||||||
|
|
||||||
/// 递归构建完整文件树(原方法保留备用) |
// 分词并统计词频 |
||||||
static Future<List<OutlineNode>> buildOutlineTree(String rootPath) async { |
static Future<Map<String, int>> _analyzeWords(List<String> chineseNames) async { |
||||||
final rootDirectory = Directory(rootPath); |
await _initJieba(); // 确保分词器已初始化 |
||||||
final List<OutlineNode> nodes = []; |
final wordFrequency = <String, int>{}; |
||||||
|
Logger().info('开始分词,共有 ${chineseNames.length} 个中文名称'); |
||||||
if (await rootDirectory.exists()) { |
for (final name in chineseNames) { |
||||||
final entities = rootDirectory.listSync(); |
List<SegToken> tokens = _segmenter!.process(name, SegMode.SEARCH); // 使用分词器实例 |
||||||
|
for (final token in tokens) { |
||||||
for (final entity in entities) { |
final word = token.word.trim(); |
||||||
final pathName = entity.path.split(Platform.pathSeparator).last; |
if (word.length > 1 && !_blackList.contains(word)) { |
||||||
if (_hiddenFiles.contains(pathName)) continue; |
wordFrequency[word] = (wordFrequency[word] ?? 0) + 1; |
||||||
final node = OutlineNode( |
|
||||||
name: pathName, |
|
||||||
path: entity.path, |
|
||||||
isDirectory: entity is Directory, |
|
||||||
); |
|
||||||
|
|
||||||
if (entity is Directory) { |
|
||||||
node.children.addAll(await buildOutlineTree(entity.path)); |
|
||||||
} |
} |
||||||
|
|
||||||
nodes.add(node); |
|
||||||
} |
} |
||||||
} |
} |
||||||
|
Logger().info('分词完成,共找到 ${wordFrequency.length} 个有效词语'); |
||||||
return nodes; |
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 @@ |
|||||||
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