Skip to content

Commit 19a3901

Browse files
committed
修复 .lang 解析丢失的 edge case
将简化版 line.IndexOf('=') 替换为完整的 DeserializeFromLang 状态机, 补回原版支持的 #PARSE_ESCAPES 续行、/* */ 多行注释、 // / # / < 单行注释、空行跳过等特性。 同步删除 AssetsNamespaceResource 中未使用的 FileProviders 属性 和废弃的 launchSettings.json。
1 parent 60f616f commit 19a3901

4 files changed

Lines changed: 90 additions & 26 deletions

File tree

src/Packer.Core/Model/ModProvider/AssetsNamespaceResource.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,4 @@ string ModVersion
2020
public FloatingConfig LocalConfig => field ??= FloatingConfig.Load(this);
2121

2222
public PackerPolicy PackerPolicies => field ??= PackerPolicy.Load(this);
23-
24-
public IEnumerable<IResourceFileProvider> FileProviders => field ??= PackerPolicies.CreateProviders(this, LocalConfig);
2523
}

src/Packer.Core/Model/PackerPolicy.cs

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,18 @@ public sealed class PackerPolicy : ICollection<PackerPolicyItem>
2222
public static PackerPolicy Load(string namespaceDirPath)
2323
{
2424
var policyFile = Path.Combine(namespaceDirPath, DefaultFileName);
25-
if (!File.Exists(policyFile)) return Shared;
25+
if (!File.Exists(policyFile))
26+
return Shared;
2627
try
2728
{
2829
var json = File.ReadAllText(policyFile);
2930
Log.Debug("策略文件内容: {Json}", json);
3031
var policies = JsonSerializer.Deserialize<PackerPolicy>(
3132
json, SourceGenerationContext.JsonOptions);
32-
if (policies is null) { Log.Warning("策略反序列化结果为 null: {File}", policyFile); return Shared; }
33-
if (policies._items.Count == 0) { Log.Warning("策略列表为空: {File}", policyFile); return Shared; }
33+
if (policies is null)
34+
{ Log.Warning("策略反序列化结果为 null: {File}", policyFile); return Shared; }
35+
if (policies._items.Count == 0)
36+
{ Log.Warning("策略列表为空: {File}", policyFile); return Shared; }
3437
Log.Debug("成功加载 {Count} 条策略: {File}", policies._items.Count, policyFile);
3538
return policies;
3639
}
@@ -71,7 +74,7 @@ protected IResourceFileProvider CreateProvider(
7174
var json = File.ReadAllText(filePath);
7275
var entries = new Dictionary<string, string>();
7376
foreach (var prop in JsonDocument.Parse(json).RootElement.EnumerateObject())
74-
if (prop.Value.ValueKind == System.Text.Json.JsonValueKind.String)
77+
if (prop.Value.ValueKind == JsonValueKind.String)
7578
entries[prop.Name] = prop.Value.GetString()!;
7679
return new JsonFile(entries, relativePath)
7780
{
@@ -81,14 +84,8 @@ protected IResourceFileProvider CreateProvider(
8184

8285
case { parentDir: "lang", ext: ".lang" }:
8386
var content = File.ReadAllText(filePath);
84-
var entries2 = new Dictionary<string, string>();
85-
foreach (var line in content.EnumerateLines())
86-
{
87-
var eq = line.IndexOf('=');
88-
if (eq == -1) continue;
89-
entries2.TryAdd(line[..eq].Trim().ToString(), line[(eq + 1)..].Trim().ToString());
90-
}
91-
return new LangFile(entries2, relativePath)
87+
entries = LangFile.DeserializeFromLang(content);
88+
return new LangFile(entries, relativePath)
9289
{
9390
Namespace = ns,
9491
PolicyItem = this
@@ -112,17 +109,20 @@ public override IEnumerable<IResourceFileProvider> CreateProviders(
112109
foreach (var domainDir in Directory.EnumerateDirectories(namespaceResource.LocalPath))
113110
{
114111
var domainName = Path.GetFileName(domainDir);
115-
if (config.ExclusionDomains.Contains(domainName)) continue;
112+
if (config.ExclusionDomains.Contains(domainName))
113+
continue;
116114
bool domainForceInclude = config.InclusionDomains.Contains(domainName);
117115

118116
foreach (var file in Directory.EnumerateFiles(domainDir, "*", SearchOption.AllDirectories))
119117
{
120118
var relativePath = Path.GetRelativePath(namespaceResource.LocalPath, file).Replace('\\', '/');
121-
if (config.ExclusionPaths.Contains(relativePath)) continue;
119+
if (config.ExclusionPaths.Contains(relativePath))
120+
continue;
122121
if (!domainForceInclude)
123122
{
124123
bool inTargetLang = relativePath.Contains("zh_cn", StringComparison.OrdinalIgnoreCase);
125-
if (!config.InclusionPaths.Contains(relativePath) && !inTargetLang) continue;
124+
if (!config.InclusionPaths.Contains(relativePath) && !inTargetLang)
125+
continue;
126126
}
127127
yield return CreateProvider(file, relativePath, namespaceResource);
128128
}
@@ -136,7 +136,8 @@ public override IEnumerable<IResourceFileProvider> CreateProviders(
136136
INamespaceResource namespaceResource, FloatingConfig config)
137137
{
138138
var targetDir = new DirectoryInfo(Source);
139-
if (!targetDir.Exists) yield break;
139+
if (!targetDir.Exists)
140+
yield break;
140141
var targetNs = new AssetsNamespaceResource(
141142
targetDir.FullName, targetDir.Name, targetDir.Parent?.Name ?? "", namespaceResource.ModVersion);
142143
foreach (var policy in targetNs.PackerPolicies)

src/Packer.Core/Model/ResourceFile/LangFile.cs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,77 @@ public override Stream GetContentStream()
5757
}
5858
return new MemoryStream(Encoding.UTF8.GetBytes(sb.ToString()));
5959
}
60+
internal static Dictionary<string, string> DeserializeFromLang(string content)
61+
{
62+
var result = new Dictionary<string, string>();
63+
var escapeMode = false;
64+
var state = LangParseState.Normal;
65+
string pendingKey = "";
66+
StringBuilder pendingValue = new StringBuilder();
67+
68+
69+
foreach (var line in content.EnumerateLines())
70+
{
71+
switch (state)
72+
{
73+
case LangParseState.LineContinuation when line.EndsWith("\\"):
74+
pendingValue.Append(line.TrimStart()[..^1]);
75+
continue;
76+
77+
case LangParseState.LineContinuation:
78+
pendingValue.Append(line.TrimStart());
79+
result.TryAdd(pendingKey, pendingValue.ToString());
80+
pendingValue.Clear();
81+
state = LangParseState.Normal;
82+
continue;
83+
84+
case LangParseState.MultiLineComment when line.Trim().EndsWith("*/"):
85+
state = LangParseState.Normal;
86+
continue;
87+
88+
case LangParseState.MultiLineComment:
89+
continue;
90+
}
91+
92+
// ── Normal 状态 ──
93+
94+
switch (line)
95+
{
96+
case "#PARSE_ESCAPES":
97+
escapeMode = true;
98+
continue;
99+
case ['/', '*', ..]:
100+
state = LangParseState.MultiLineComment;
101+
continue;
102+
case ['#' or '<', ..] or ['/', '/', ..]:
103+
continue;
104+
}
105+
106+
107+
108+
var eq = line.IndexOf('=');
109+
if (eq == -1)
110+
{
111+
continue;
112+
}
113+
114+
var key = line[..eq];
115+
var value = eq + 1 < line.Length ? line[(eq + 1)..] : "";
116+
117+
if (escapeMode && value.EndsWith("\\"))
118+
{
119+
state = LangParseState.LineContinuation;
120+
pendingKey = key.ToString();
121+
pendingValue.Append(value[..^1]);
122+
}
123+
else
124+
{
125+
result.TryAdd(key.ToString(), value.ToString());
126+
}
127+
}
128+
129+
return result;
130+
}
131+
132+
private enum LangParseState { Normal, MultiLineComment, LineContinuation }
60133
}

src/Packer.Core/Properties/launchSettings.json

Lines changed: 0 additions & 8 deletions
This file was deleted.

0 commit comments

Comments
 (0)