实现RawParser

This commit is contained in:
limil 2025-12-29 22:26:27 +08:00
parent cf128159d1
commit 9cf948ec9c
8 changed files with 272 additions and 153 deletions

View File

@ -13,14 +13,19 @@ namespace Learn.Component;
public partial class MainTreePanel : Tree public partial class MainTreePanel : Tree
{ {
public class ColumnView
{
public string Name { get; set; }
public int Width { get; set; }
}
public class MainTreeData public class MainTreeData
{ {
public List<string> Columns { get; set; } public List<ColumnView> Columns { get; set; }
public Dictionary<string, Item> Items { get; set; } public Dictionary<string, Item> Items { get; set; }
} }
private readonly List<ColumnView> _columns = new();
private readonly List<string> _columns = new();
private readonly HashSet<TreeNode> _selectingNodes = new(); private readonly HashSet<TreeNode> _selectingNodes = new();
private readonly Dictionary<TreeItem, TreeNode> _mapper = new(); private readonly Dictionary<TreeItem, TreeNode> _mapper = new();
private readonly Dictionary<string, Item> _items = new(); private readonly Dictionary<string, Item> _items = new();
@ -41,22 +46,28 @@ public partial class MainTreePanel : Tree
UpdateColumns(); UpdateColumns();
} }
public void AddColumn(string columnName, int index) public void AddColumn(string columnName, int index, int width)
{ {
if (string.IsNullOrEmpty(columnName)) return; if (string.IsNullOrEmpty(columnName)) return;
if (_columns.Contains(columnName)) return; var targets = _columns.Where(column => column.Name == columnName).ToList();
if (index < 0)
if (index < 0) index = _columns.Count;
if (width < 0) width = 400;
if (targets.Any())
{ {
_columns.Add(columnName); targets.First().Width = width;
}
else
{
_columns.Insert(index, new ColumnView { Name = columnName, Width = width });
} }
_columns.Insert(index, columnName);
UpdateColumns(); UpdateColumns();
} }
public void RemoveColumn(string columnName) public void RemoveColumn(string columnName)
{ {
if (string.IsNullOrEmpty(columnName)) return; if (string.IsNullOrEmpty(columnName)) return;
_columns.Remove(columnName); _columns.RemoveAll(column => column.Name == columnName);
UpdateColumns(); UpdateColumns();
} }
@ -72,8 +83,8 @@ public partial class MainTreePanel : Tree
int index = 1; int index = 1;
foreach (var column in _columns) foreach (var column in _columns)
{ {
SetColumnTitle(index, column); SetColumnTitle(index, column.Name);
SetColumnCustomMinimumWidth(index, 400); SetColumnCustomMinimumWidth(index, column.Width);
SetColumnExpand(index, true); SetColumnExpand(index, true);
index += 1; index += 1;
} }
@ -83,14 +94,12 @@ public partial class MainTreePanel : Tree
index = 1; index = 1;
foreach (var column in _columns) foreach (var column in _columns)
{ {
if (kv.Value.TryGetValue(column, out var value, out _)) if (!kv.Value.TryGetValue(column.Name, out var value, out _))
{ {
value = "";
}
kv.Key.SetText(index, value); kv.Key.SetText(index, value);
} kv.Key.SetTextAlignment(index, HorizontalAlignment.Center);
else
{
kv.Key.SetText(index, "");
}
index += 1; index += 1;
} }
} }

9
Config/AppConfig.cs Normal file
View File

@ -0,0 +1,9 @@
using Learn.Utils;
namespace Learn.Config;
[ConfigItem("APP")]
public class AppConfig : IConfigItem
{
public string ScanPath = "";
}

71
Main.cs
View File

@ -6,6 +6,7 @@ using Godot;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Learn.Component; using Learn.Component;
using Learn.Config;
using Learn.Parsers; using Learn.Parsers;
using Learn.Utils; using Learn.Utils;
@ -32,6 +33,7 @@ public partial class Main : Node
[ExportGroup("主展示面板")] [ExportGroup("主展示面板")]
[Export] private MainTreePanel _mainTreePanel; [Export] private MainTreePanel _mainTreePanel;
[Export] private LineEdit _columnIndexText; [Export] private LineEdit _columnIndexText;
[Export] private LineEdit _columnWidthText;
[Export] private LineEdit _columnText; [Export] private LineEdit _columnText;
[Export] private Button _addColumnButton; [Export] private Button _addColumnButton;
[Export] private Button _removeColumnButton; [Export] private Button _removeColumnButton;
@ -50,7 +52,12 @@ public partial class Main : Node
_removeKeyButton.Pressed += _nodeInfoEditPanel.RemoveKeyValue; _removeKeyButton.Pressed += _nodeInfoEditPanel.RemoveKeyValue;
_mainTreePanel.MultiSelected += (_, _, _) => { _refreshPanels = true; }; _mainTreePanel.MultiSelected += (_, _, _) => { _refreshPanels = true; };
_addColumnButton.Pressed += () => _mainTreePanel.AddColumn(_columnText.Text, int.TryParse(_columnIndexText.Text, out var index) ? index : -1); _addColumnButton.Pressed += () =>
{
_mainTreePanel.AddColumn(_columnText.Text,
int.TryParse(_columnIndexText.Text, out var index) ? index : -1,
int.TryParse(_columnWidthText.Text, out var width) ? width : -1);
};
_removeColumnButton.Pressed += () => _mainTreePanel.RemoveColumn(_columnText.Text); _removeColumnButton.Pressed += () => _mainTreePanel.RemoveColumn(_columnText.Text);
_clearColumnButton.Pressed += () => _mainTreePanel.ClearColumns(); _clearColumnButton.Pressed += () => _mainTreePanel.ClearColumns();
@ -65,16 +72,14 @@ public partial class Main : Node
_loadButton.Pressed += LoadData; _loadButton.Pressed += LoadData;
_doParseButton.Text = "开始解析"; _doParseButton.Text = "开始解析";
LoadData();
_mainTreePanel.Scan("C:/Users/15401/Desktop/2025-up7"); // todo: 弄好后删掉
FoldAll();
_refreshPanels = true;
_configs = new Configs(); _configs = new Configs();
_configs.ConfigPath = "config.json"; _configs.ConfigPath = "config.json";
_configs.Load(true); _configs.Load(true);
LoadData();
ScanDir();
FoldAll();
_refreshPanels = true;
} }
private void LoadData() private void LoadData()
@ -101,17 +106,9 @@ public partial class Main : Node
{ {
var root = _mainTreePanel.GetRoot(); var root = _mainTreePanel.GetRoot();
if (root == null) return; if (root == null) return;
var queue = new Queue<TreeItem>(); foreach (var child in root.GetChildren())
queue.Enqueue(root);
while (queue.Count > 0)
{ {
var treeItem = queue.Dequeue(); child.SetCollapsedRecursive(collapsed);
foreach (var child in treeItem.GetChildren())
{
if(child == null) continue;
child.Collapsed = collapsed;
queue.Enqueue(child);
}
} }
} }
@ -130,7 +127,8 @@ public partial class Main : Node
private async void ScanDir() private async void ScanDir()
{ {
var path = await _dirSelector.SelectFolderAsync(); // var path = await _dirSelector.SelectFolderAsync();
var path = _configs.Get<AppConfig>().ScanPath;
if (string.IsNullOrEmpty(path)) if (string.IsNullOrEmpty(path))
{ {
return; return;
@ -148,40 +146,11 @@ public partial class Main : Node
var originName = _doParseButton.Text; var originName = _doParseButton.Text;
_doParseButton.Disabled = true; _doParseButton.Disabled = true;
ItemParser parser = new TMDBParser(_configs); ItemParser parser = new RawParser();
var stopwatch = Stopwatch.StartNew(); if (_mainTreePanel.Query(root, out var node))
var stack = new Stack<TreeItem>();
stack.Push(root);
while (stack.Count > 0)
{ {
_doParseButton.Text = "解析中"; await parser.Parse(node);
if (stopwatch.ElapsedMilliseconds > 2000)
{
_mainTreePanel.UpdateColumns();
stopwatch.Restart();
}
var treeItem = stack.Pop();
foreach (var child in treeItem.GetChildren())
{
if(child == null) continue;
if (_mainTreePanel.Query(child, out var node))
{
try
{
if (!await parser.DoParse(node))
{
GD.PrintErr($"解析{child.GetText(0)}失败");
}
}
catch (Exception ex)
{
GD.PrintErr($"解析{child.GetText(0)}出现异常:" + ex);
}
}
stack.Push(child);
}
} }
_doParseButton.Text = originName; _doParseButton.Text = originName;

View File

@ -17,7 +17,7 @@ grow_vertical = 2
[node name="FileDirDialog" type="FileDialog" parent="."] [node name="FileDirDialog" type="FileDialog" parent="."]
script = ExtResource("1_d2g23") script = ExtResource("1_d2g23")
[node name="Main" type="Node" parent="." node_paths=PackedStringArray("_dirSelector", "_openDirButton", "_doParseButton", "_saveButton", "_resetButton", "_loadButton", "_nodeInfoEditPanel", "_addKeyButton", "_removeKeyButton", "_inspectorPanel", "_mainTreePanel", "_columnIndexText", "_columnText", "_addColumnButton", "_removeColumnButton", "_clearColumnButton", "_expandAllButton", "_foldAllButton")] [node name="Main" type="Node" parent="." node_paths=PackedStringArray("_dirSelector", "_openDirButton", "_doParseButton", "_saveButton", "_resetButton", "_loadButton", "_nodeInfoEditPanel", "_addKeyButton", "_removeKeyButton", "_inspectorPanel", "_mainTreePanel", "_columnIndexText", "_columnWidthText", "_columnText", "_addColumnButton", "_removeColumnButton", "_clearColumnButton", "_expandAllButton", "_foldAllButton")]
script = ExtResource("2_0727o") script = ExtResource("2_0727o")
_dirSelector = NodePath("../FileDirDialog") _dirSelector = NodePath("../FileDirDialog")
_openDirButton = NodePath("../MarginContainer/HSplitContainer/ScrollContainer/VBoxContainer2/FoldableContainer4/VBoxContainer2/TabContainer/文件夹操作/ScanDir") _openDirButton = NodePath("../MarginContainer/HSplitContainer/ScrollContainer/VBoxContainer2/FoldableContainer4/VBoxContainer2/TabContainer/文件夹操作/ScanDir")
@ -31,6 +31,7 @@ _removeKeyButton = NodePath("../MarginContainer/HSplitContainer/ScrollContainer/
_inspectorPanel = NodePath("../MarginContainer/HSplitContainer/ScrollContainer/VBoxContainer2/FoldableContainer2/InspectorPanel") _inspectorPanel = NodePath("../MarginContainer/HSplitContainer/ScrollContainer/VBoxContainer2/FoldableContainer2/InspectorPanel")
_mainTreePanel = NodePath("../MarginContainer/HSplitContainer/MainTreePanel") _mainTreePanel = NodePath("../MarginContainer/HSplitContainer/MainTreePanel")
_columnIndexText = NodePath("../MarginContainer/HSplitContainer/ScrollContainer/VBoxContainer2/FoldableContainer4/VBoxContainer2/TabContainer/树表操作/HBoxContainer2/ColumnIndex") _columnIndexText = NodePath("../MarginContainer/HSplitContainer/ScrollContainer/VBoxContainer2/FoldableContainer4/VBoxContainer2/TabContainer/树表操作/HBoxContainer2/ColumnIndex")
_columnWidthText = NodePath("../MarginContainer/HSplitContainer/ScrollContainer/VBoxContainer2/FoldableContainer4/VBoxContainer2/TabContainer/树表操作/HBoxContainer3/ColumnWidth")
_columnText = NodePath("../MarginContainer/HSplitContainer/ScrollContainer/VBoxContainer2/FoldableContainer4/VBoxContainer2/TabContainer/树表操作/HBoxContainer/ColunmText") _columnText = NodePath("../MarginContainer/HSplitContainer/ScrollContainer/VBoxContainer2/FoldableContainer4/VBoxContainer2/TabContainer/树表操作/HBoxContainer/ColunmText")
_addColumnButton = NodePath("../MarginContainer/HSplitContainer/ScrollContainer/VBoxContainer2/FoldableContainer4/VBoxContainer2/TabContainer/树表操作/HBoxContainer/AddColumn") _addColumnButton = NodePath("../MarginContainer/HSplitContainer/ScrollContainer/VBoxContainer2/FoldableContainer4/VBoxContainer2/TabContainer/树表操作/HBoxContainer/AddColumn")
_removeColumnButton = NodePath("../MarginContainer/HSplitContainer/ScrollContainer/VBoxContainer2/FoldableContainer4/VBoxContainer2/TabContainer/树表操作/HBoxContainer/RemoveColumn") _removeColumnButton = NodePath("../MarginContainer/HSplitContainer/ScrollContainer/VBoxContainer2/FoldableContainer4/VBoxContainer2/TabContainer/树表操作/HBoxContainer/RemoveColumn")
@ -137,6 +138,17 @@ layout_mode = 2
layout_mode = 2 layout_mode = 2
text = "行" text = "行"
[node name="HBoxContainer3" type="HBoxContainer" parent="MarginContainer/HSplitContainer/ScrollContainer/VBoxContainer2/FoldableContainer4/VBoxContainer2/TabContainer/树表操作"]
layout_mode = 2
[node name="Label2" type="Label" parent="MarginContainer/HSplitContainer/ScrollContainer/VBoxContainer2/FoldableContainer4/VBoxContainer2/TabContainer/树表操作/HBoxContainer3"]
layout_mode = 2
text = "宽度:"
[node name="ColumnWidth" type="LineEdit" parent="MarginContainer/HSplitContainer/ScrollContainer/VBoxContainer2/FoldableContainer4/VBoxContainer2/TabContainer/树表操作/HBoxContainer3"]
custom_minimum_size = Vector2(50, 0)
layout_mode = 2
[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/HSplitContainer/ScrollContainer/VBoxContainer2/FoldableContainer4/VBoxContainer2/TabContainer/树表操作"] [node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/HSplitContainer/ScrollContainer/VBoxContainer2/FoldableContainer4/VBoxContainer2/TabContainer/树表操作"]
layout_mode = 2 layout_mode = 2

View File

@ -16,5 +16,5 @@ namespace Learn.Parsers;
/// </summary> /// </summary>
public interface ItemParser public interface ItemParser
{ {
public Task<bool> DoParse(TreeNode node); public Task Parse(TreeNode node);
} }

171
Parsers/RawParser.cs Normal file
View File

@ -0,0 +1,171 @@
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Learn.Models;
namespace Learn.Parsers;
public class RawParser : ItemParser
{
private string ParseSeasonFromCN(string season)
{
if (int.TryParse(season, out _)) return season;
switch (season)
{
case "零": return "0";
case "一": return "1";
case "二": return "2";
case "三": return "3";
case "四": return "4";
case "五": return "5";
case "六": return "6";
case "七": return "7";
case "八": return "8";
case "九": return "9";
}
return season;
}
private (string, string) SplitTitleAndSeason(string rawTitle)
{
var match1 = Regex.Match(rawTitle, @"第(.+)季");
if (match1.Success)
{
var seasonStr = match1.Groups[0].Value.Trim();
var season = ParseSeasonFromCN(match1.Groups[1].Value.Trim());
var title = rawTitle.Replace(seasonStr, "").Trim();
return (title, season);
}
var match2 = Regex.Match(rawTitle, @"[Ss]eason *(\d+)");
if (match2.Success)
{
var seasonStr = match1.Groups[0].Value.Trim();
var season = int.Parse(match1.Groups[1].Value.Trim()).ToString();
var title = rawTitle.Replace(seasonStr, "").Trim();
return (title, season);
}
return (rawTitle, null);
}
private bool TryParseRawTitle(Item item, out string rawTitle)
{
rawTitle = null;
var name = item.Name();
var matches = Regex.Matches(name, @"[^\[\]_]+").Select(match => match.Value).ToList();
matches.RemoveAll(match => string.IsNullOrEmpty(match.Trim()));
if (matches.Count == 0) return false;
if (matches.Count == 1)
{
rawTitle = matches[0];
}
else
{
rawTitle = matches[1];
}
(rawTitle, _) = SplitTitleAndSeason(rawTitle);
return true;
}
private bool TryParseSeason(Item item, out string season)
{
season = null;
var name = item.Name();
var matches = Regex.Matches(name, @"[^\[\]_]+").Select(match => match.Value).ToArray();
if (matches.Length == 0) return false;
if (matches.Length == 1)
{
(_, season) = SplitTitleAndSeason(matches[0]);
if (!string.IsNullOrEmpty(season)) return true;
}
else
{
(_, season) = SplitTitleAndSeason(matches[1]);
if (!string.IsNullOrEmpty(season)) return true;
}
return false;
}
private bool TryParseGroup(Item item, out string group)
{
group = null;
var name = item.Name();
var matches = Regex.Matches(name, @"[^\[\]_《》【】]+").Select(match => match.Value).ToArray();
if (matches.Length <= 1) return false;
group = matches[0];
return true;
}
private delegate bool FieldParser(Item item, out string result);
private bool TryParseField(TreeNode node, FieldParser fieldParser, out string result)
{
result = null;
if (node.Info == null) return false;
if (!fieldParser(node.Info, out var fieldValue)) return false;
var parsed = new List<string>();
foreach (var child in node.Children)
{
if (fieldParser(child.Info, out var childFieldValue))
{
parsed.Add(childFieldValue);
}
}
var totalCount = parsed.Count;
if (totalCount == 0)
{
result = fieldValue;
return true;
}
var maxGroupCount = parsed.GroupBy(value => value)
.Select(group => group.Count())
.Max();
if (maxGroupCount > totalCount / 2)
{
result = fieldValue;
return true;
}
return false;
}
private void DoParse(TreeNode node, FieldParser fieldParser, string fieldName)
{
var queue = new Queue<TreeNode>();
queue.Enqueue(node);
while (queue.Count > 0)
{
var current = queue.Dequeue();
if (TryParseField(current, fieldParser, out var fieldValue))
{
current.Info.Info.TryAdd(fieldName, fieldValue);
}
else
{
foreach (var child in current.Children)
{
queue.Enqueue(child);
}
}
}
}
public async Task Parse(TreeNode node)
{
DoParse(node, TryParseRawTitle, ItemFields.Key_RawTitle);
DoParse(node, TryParseSeason, ItemFields.Key_Season);
DoParse(node, TryParseGroup, ItemFields.Key_Group);
}
}

1
Parsers/RawParser.cs.uid Normal file
View File

@ -0,0 +1 @@
uid://w0q201bv7vj8

View File

@ -52,90 +52,38 @@ public class TMDBParser(Configs configs) : ItemParser
return _client; return _client;
} }
private string ParseSeason(string season)
{
if (int.TryParse(season, out _)) return season;
switch (season)
{
case "零": return "0";
case "一": return "1";
case "二": return "2";
case "三": return "3";
case "四": return "4";
case "五": return "5";
case "六": return "6";
case "七": return "7";
case "八": return "8";
case "九": return "9";
}
return season;
}
private bool DoRawParse(TreeNode node) public async Task Parse(TreeNode node)
{ {
var item = node.Info; // if (!node.TryGetValue(ItemFields.Key_RawTitle, out var rawTitle, out _))
var name = item.Name(); // {
var matches = Regex.Matches(name, @"[^\[\]_]+").Select(match => match.Value).ToArray(); // if (!DoRawParse(node))
// {
if (matches.Length == 0) return false; // return false;
// }
if (matches.Length == 1) // node.TryGetValue(ItemFields.Key_RawTitle, out rawTitle, out _);
{ // }
node.Info.SetRawTitleIfNotExist(matches[0]); //
return true; // if (string.IsNullOrEmpty(rawTitle))
} // {
// return false;
if (matches.Length >= 2) // }
{ //
node.Info.SetGroupIfNotExist(matches[0]); // var result = await QueryTMDB(rawTitle);
// if (result == null)
var title = matches[1].Trim(); // {
var match = Regex.Match(title, "(.+)第(.+)季"); // GD.PrintErr($"找不到对应的TV{rawTitle}");
if (match.Success) // return false;
{ // }
node.Info.SetSeasonIfNotExist(ParseSeason(match.Groups[2].Value.Trim())); //
node.Info.SetRawTitleIfNotExist(match.Groups[1].Value.Trim()); // if (result.FirstAirDate != null)
} // {
else // node.Info.SetYear(result.FirstAirDate.Value.Year);
{ // }
node.Info.SetRawTitleIfNotExist(title); //
} // node.Info.SetTitleIfNotExist(result.Name);
} //
// return true;
return true;
}
public async Task<bool> DoParse(TreeNode node)
{
if (!node.TryGetValue(ItemFields.Key_RawTitle, out var rawTitle, out _))
{
if (!DoRawParse(node))
{
return false;
}
node.TryGetValue(ItemFields.Key_RawTitle, out rawTitle, out _);
}
if (string.IsNullOrEmpty(rawTitle))
{
return false;
}
var result = await QueryTMDB(rawTitle);
if (result == null)
{
GD.PrintErr($"找不到对应的TV{rawTitle}");
return false;
}
if (result.FirstAirDate != null)
{
node.Info.SetYear(result.FirstAirDate.Value.Year);
}
node.Info.SetTitleIfNotExist(result.Name);
return true;
} }
} }