实现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 class ColumnView
{
public string Name { get; set; }
public int Width { get; set; }
}
public class MainTreeData
{
public List<string> Columns { get; set; }
public List<ColumnView> Columns { get; set; }
public Dictionary<string, Item> Items { get; set; }
}
private readonly List<string> _columns = new();
private readonly List<ColumnView> _columns = new();
private readonly HashSet<TreeNode> _selectingNodes = new();
private readonly Dictionary<TreeItem, TreeNode> _mapper = new();
private readonly Dictionary<string, Item> _items = new();
@ -41,22 +46,28 @@ public partial class MainTreePanel : Tree
UpdateColumns();
}
public void AddColumn(string columnName, int index)
public void AddColumn(string columnName, int index, int width)
{
if (string.IsNullOrEmpty(columnName)) return;
if (_columns.Contains(columnName)) return;
if (index < 0)
var targets = _columns.Where(column => column.Name == columnName).ToList();
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();
}
public void RemoveColumn(string columnName)
{
if (string.IsNullOrEmpty(columnName)) return;
_columns.Remove(columnName);
_columns.RemoveAll(column => column.Name == columnName);
UpdateColumns();
}
@ -72,8 +83,8 @@ public partial class MainTreePanel : Tree
int index = 1;
foreach (var column in _columns)
{
SetColumnTitle(index, column);
SetColumnCustomMinimumWidth(index, 400);
SetColumnTitle(index, column.Name);
SetColumnCustomMinimumWidth(index, column.Width);
SetColumnExpand(index, true);
index += 1;
}
@ -83,14 +94,12 @@ public partial class MainTreePanel : Tree
index = 1;
foreach (var column in _columns)
{
if (kv.Value.TryGetValue(column, out var value, out _))
if (!kv.Value.TryGetValue(column.Name, out var value, out _))
{
kv.Key.SetText(index, value);
}
else
{
kv.Key.SetText(index, "");
value = "";
}
kv.Key.SetText(index, value);
kv.Key.SetTextAlignment(index, HorizontalAlignment.Center);
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.Threading.Tasks;
using Learn.Component;
using Learn.Config;
using Learn.Parsers;
using Learn.Utils;
@ -32,6 +33,7 @@ public partial class Main : Node
[ExportGroup("主展示面板")]
[Export] private MainTreePanel _mainTreePanel;
[Export] private LineEdit _columnIndexText;
[Export] private LineEdit _columnWidthText;
[Export] private LineEdit _columnText;
[Export] private Button _addColumnButton;
[Export] private Button _removeColumnButton;
@ -50,7 +52,12 @@ public partial class Main : Node
_removeKeyButton.Pressed += _nodeInfoEditPanel.RemoveKeyValue;
_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);
_clearColumnButton.Pressed += () => _mainTreePanel.ClearColumns();
@ -64,17 +71,15 @@ public partial class Main : Node
_resetButton.Pressed += DoReset;
_loadButton.Pressed += LoadData;
_doParseButton.Text = "开始解析";
LoadData();
_mainTreePanel.Scan("C:/Users/15401/Desktop/2025-up7"); // todo: 弄好后删掉
FoldAll();
_refreshPanels = true;
_configs = new Configs();
_configs.ConfigPath = "config.json";
_configs.Load(true);
LoadData();
ScanDir();
FoldAll();
_refreshPanels = true;
}
private void LoadData()
@ -101,17 +106,9 @@ public partial class Main : Node
{
var root = _mainTreePanel.GetRoot();
if (root == null) return;
var queue = new Queue<TreeItem>();
queue.Enqueue(root);
while (queue.Count > 0)
foreach (var child in root.GetChildren())
{
var treeItem = queue.Dequeue();
foreach (var child in treeItem.GetChildren())
{
if(child == null) continue;
child.Collapsed = collapsed;
queue.Enqueue(child);
}
child.SetCollapsedRecursive(collapsed);
}
}
@ -130,7 +127,8 @@ public partial class Main : Node
private async void ScanDir()
{
var path = await _dirSelector.SelectFolderAsync();
// var path = await _dirSelector.SelectFolderAsync();
var path = _configs.Get<AppConfig>().ScanPath;
if (string.IsNullOrEmpty(path))
{
return;
@ -148,40 +146,11 @@ public partial class Main : Node
var originName = _doParseButton.Text;
_doParseButton.Disabled = true;
ItemParser parser = new TMDBParser(_configs);
ItemParser parser = new RawParser();
var stopwatch = Stopwatch.StartNew();
var stack = new Stack<TreeItem>();
stack.Push(root);
while (stack.Count > 0)
if (_mainTreePanel.Query(root, out var node))
{
_doParseButton.Text = "解析中";
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);
}
await parser.Parse(node);
}
_doParseButton.Text = originName;

View File

@ -17,7 +17,7 @@ grow_vertical = 2
[node name="FileDirDialog" type="FileDialog" parent="."]
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")
_dirSelector = NodePath("../FileDirDialog")
_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")
_mainTreePanel = NodePath("../MarginContainer/HSplitContainer/MainTreePanel")
_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")
_addColumnButton = NodePath("../MarginContainer/HSplitContainer/ScrollContainer/VBoxContainer2/FoldableContainer4/VBoxContainer2/TabContainer/树表操作/HBoxContainer/AddColumn")
_removeColumnButton = NodePath("../MarginContainer/HSplitContainer/ScrollContainer/VBoxContainer2/FoldableContainer4/VBoxContainer2/TabContainer/树表操作/HBoxContainer/RemoveColumn")
@ -137,6 +138,17 @@ layout_mode = 2
layout_mode = 2
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/树表操作"]
layout_mode = 2

View File

@ -16,5 +16,5 @@ namespace Learn.Parsers;
/// </summary>
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;
}
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)
{
var item = node.Info;
var name = item.Name();
var matches = Regex.Matches(name, @"[^\[\]_]+").Select(match => match.Value).ToArray();
if (matches.Length == 0) return false;
if (matches.Length == 1)
{
node.Info.SetRawTitleIfNotExist(matches[0]);
return true;
}
if (matches.Length >= 2)
{
node.Info.SetGroupIfNotExist(matches[0]);
var title = matches[1].Trim();
var match = Regex.Match(title, "(.+)第(.+)季");
if (match.Success)
{
node.Info.SetSeasonIfNotExist(ParseSeason(match.Groups[2].Value.Trim()));
node.Info.SetRawTitleIfNotExist(match.Groups[1].Value.Trim());
}
else
{
node.Info.SetRawTitleIfNotExist(title);
}
}
return true;
}
public async Task<bool> DoParse(TreeNode node)
public async Task Parse(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;
// 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;
}
}