Skip to content

Commit bdd2383

Browse files
SWeinishpaass
authored andcommitted
fix: rewrite the parsing of LocalisedString to support "?" key
1 parent 7031cfd commit bdd2383

File tree

4 files changed

+343
-172
lines changed

4 files changed

+343
-172
lines changed
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
using Xunit;
2+
using Yafc.Parser;
3+
4+
namespace Yafc.Model.Data.Tests;
5+
6+
public class LocalisedStringParserTests {
7+
public LocalisedStringParserTests() {
8+
FactorioLocalization.Initialize(new System.Collections.Generic.Dictionary<string, string>() {
9+
["hours"] = "__1__ __plural_for_parameter__1__{1=hour|rest=hours}__",
10+
["si-unit-kilometer-per-hour"] = "__1__ km/h",
11+
["not-enough-ingredients"] = "Not enough ingredients.",
12+
["item-name.iron-plate"] = "Iron plate",
13+
["item-name.big-iron-plate"] = "Big __ITEM__iron-plate__",
14+
["connecting"] = "__plural_for_parameter__1__{1=__1__ player is|rest=__1__ players are}__ connecting",
15+
["ends.in"] = "__plural_for_parameter__1__{ends in 12=option 1|ends in 2=option 2|rest=option 3}__"
16+
});
17+
}
18+
19+
[Fact]
20+
public void Parse_JustString() {
21+
var localised = LocalisedStringParser.Parse("test");
22+
Assert.Equal("test", localised);
23+
}
24+
25+
[Fact]
26+
public void Parse_RemoveRichText() {
27+
var localised = LocalisedStringParser.Parse("[color=#ffffff]iron[/color] [color=1,0,0]plate[.color] [item=iron-plate]");
28+
Assert.Equal("iron plate ", localised);
29+
}
30+
31+
[Fact]
32+
public void Parse_NoParameters() {
33+
var localised = LocalisedStringParser.Parse("not-enough-ingredients", []);
34+
Assert.Equal("Not enough ingredients.", localised);
35+
}
36+
37+
[Fact]
38+
public void Parse_Parameter() {
39+
var localised = LocalisedStringParser.Parse("si-unit-kilometer-per-hour", ["100"]);
40+
Assert.Equal("100 km/h", localised);
41+
}
42+
43+
[Fact]
44+
public void Parse_LinkItem() {
45+
var localised = LocalisedStringParser.Parse("item-name.big-iron-plate", []);
46+
Assert.Equal("Big Iron plate", localised);
47+
}
48+
49+
[Fact]
50+
public void Parse_PluralSpecial() {
51+
var localised = LocalisedStringParser.Parse("hours", ["1"]);
52+
Assert.Equal("1 hour", localised);
53+
}
54+
55+
[Fact]
56+
public void Parse_PluralRest() {
57+
var localised = LocalisedStringParser.Parse("hours", ["2"]);
58+
Assert.Equal("2 hours", localised);
59+
}
60+
61+
[Fact]
62+
public void Parse_PluralWithParameter() {
63+
var localised = LocalisedStringParser.Parse("connecting", ["1"]);
64+
Assert.Equal("1 player is connecting", localised);
65+
}
66+
67+
[Theory]
68+
[InlineData(12, "option 1")]
69+
[InlineData(22, "option 2")]
70+
[InlineData(5, "option 3")]
71+
public void Parse_PluralEndsIn(int n, string expectedResult) {
72+
var localised = LocalisedStringParser.Parse("ends.in", [n.ToString()]);
73+
Assert.Equal(expectedResult, localised);
74+
}
75+
}

Yafc.Parser/Data/FactorioDataDeserializer.cs

Lines changed: 5 additions & 172 deletions
Original file line numberDiff line numberDiff line change
@@ -391,11 +391,7 @@ private void DeserializeItem(LuaTable table, ErrorCollector _) {
391391
item.stackSize = table.Get("stack_size", 1);
392392

393393
if (item.locName == null && table.Get("placed_as_equipment_result", out string? result)) {
394-
Localize("equipment-name." + result, null);
395-
396-
if (localeBuilder.Length > 0) {
397-
item.locName = FinishLocalize();
398-
}
394+
item.locName = LocalisedStringParser.Parse("equipment-name." + result, [])!;
399395
}
400396
if (table.Get("fuel_value", out string? fuelValue)) {
401397
item.fuelValue = ParseEnergy(fuelValue);
@@ -505,166 +501,6 @@ private void DeserializeFluid(LuaTable table, ErrorCollector _) {
505501
return null;
506502
}
507503

508-
private readonly StringBuilder localeBuilder = new StringBuilder();
509-
510-
private void Localize(object obj) {
511-
if (obj is LuaTable table) {
512-
if (!table.Get(1, out string? key)) {
513-
return;
514-
}
515-
516-
Localize(key, table);
517-
}
518-
else {
519-
_ = localeBuilder.Append(obj);
520-
}
521-
}
522-
523-
private string FinishLocalize() {
524-
_ = localeBuilder.Replace("\\n", "\n");
525-
526-
// Cleaning up tags using simple state machine
527-
// 0 = outside of tag, 1 = first potential tag char, 2 = inside possible tag, 3 = inside definite tag
528-
// tag is definite when it contains '=' or starts with '/' or '.'
529-
int state = 0, tagStart = 0;
530-
for (int i = 0; i < localeBuilder.Length; i++) {
531-
char chr = localeBuilder[i];
532-
533-
switch (state) {
534-
case 0:
535-
if (chr == '[') {
536-
state = 1;
537-
tagStart = i;
538-
}
539-
break;
540-
case 1:
541-
if (chr == ']') {
542-
state = 0;
543-
}
544-
else {
545-
state = (chr is '/' or '.') ? 3 : 2;
546-
}
547-
548-
break;
549-
case 2:
550-
if (chr == '=') {
551-
state = 3;
552-
}
553-
else if (chr == ']') {
554-
state = 0;
555-
}
556-
557-
break;
558-
case 3:
559-
if (chr == ']') {
560-
_ = localeBuilder.Remove(tagStart, i - tagStart + 1);
561-
i = tagStart - 1;
562-
state = 0;
563-
}
564-
break;
565-
}
566-
}
567-
568-
string s = localeBuilder.ToString();
569-
_ = localeBuilder.Clear();
570-
571-
return s;
572-
}
573-
574-
private void Localize(string? key, LuaTable? table) {
575-
if (string.IsNullOrEmpty(key)) {
576-
if (table == null) {
577-
return;
578-
}
579-
580-
foreach (object? elem in table.ArrayElements) {
581-
if (elem is LuaTable sub) {
582-
Localize(sub);
583-
}
584-
else {
585-
_ = localeBuilder.Append(elem);
586-
}
587-
}
588-
return;
589-
}
590-
591-
key = FactorioLocalization.Localize(key);
592-
593-
if (key == null) {
594-
if (table != null) {
595-
_ = localeBuilder.Append(string.Join(" ", table.ArrayElements<string>()));
596-
}
597-
598-
return;
599-
}
600-
601-
if (!key.Contains("__")) {
602-
_ = localeBuilder.Append(key);
603-
return;
604-
}
605-
606-
using var parts = ((IEnumerable<string>)key.Split("__")).GetEnumerator();
607-
608-
while (parts.MoveNext()) {
609-
_ = localeBuilder.Append(parts.Current);
610-
611-
if (!parts.MoveNext()) {
612-
break;
613-
}
614-
615-
string control = parts.Current;
616-
617-
if (control is "ITEM" or "FLUID" or "RECIPE" or "ENTITY") {
618-
if (!parts.MoveNext()) {
619-
break;
620-
}
621-
622-
string subKey = control.ToLowerInvariant() + "-name." + parts.Current;
623-
Localize(subKey, null);
624-
}
625-
else if (control == "CONTROL") {
626-
if (!parts.MoveNext()) {
627-
break;
628-
}
629-
630-
_ = localeBuilder.Append(parts.Current);
631-
}
632-
else if (control == "ALT_CONTROL") {
633-
if (!parts.MoveNext() || !parts.MoveNext()) {
634-
break;
635-
}
636-
637-
_ = localeBuilder.Append(parts.Current);
638-
}
639-
else if (table != null && int.TryParse(control, out int i)) {
640-
if (table.Get(i + 1, out string? s)) {
641-
Localize(s, null);
642-
}
643-
else if (table.Get(i + 1, out LuaTable? t)) {
644-
Localize(t);
645-
}
646-
else if (table.Get(i + 1, out float f)) {
647-
_ = localeBuilder.Append(f);
648-
}
649-
}
650-
else if (control.StartsWith("plural")) {
651-
_ = localeBuilder.Append("(???)");
652-
653-
if (!parts.MoveNext()) {
654-
break;
655-
}
656-
}
657-
else {
658-
// Not supported token... Append everything else as-is
659-
while (parts.MoveNext()) {
660-
_ = localeBuilder.Append(parts.Current);
661-
}
662-
663-
break;
664-
}
665-
}
666-
}
667-
668504
private T DeserializeCommon<T>(LuaTable table, string prototypeType) where T : FactorioObject, new() {
669505
if (!table.Get("name", out string? name)) {
670506
throw new NotSupportedException($"Read a definition of a {prototypeType} that does not have a name.");
@@ -674,22 +510,19 @@ private void Localize(string? key, LuaTable? table) {
674510
target.factorioType = table.Get("type", "");
675511

676512
if (table.Get("localised_name", out object? loc)) { // Keep UK spelling for Factorio/LUA data objects
677-
Localize(loc);
513+
target.locName = LocalisedStringParser.Parse(loc)!;
678514
}
679515
else {
680-
Localize(prototypeType + "-name." + target.name, null);
516+
target.locName = LocalisedStringParser.Parse(prototypeType + "-name." + target.name, [])!;
681517
}
682518

683-
target.locName = localeBuilder.Length == 0 ? null! : FinishLocalize(); // null-forgiving: We have another chance at the end of CalculateMaps.
684-
685519
if (table.Get("localised_description", out loc)) { // Keep UK spelling for Factorio/LUA data objects
686-
Localize(loc);
520+
target.locDescr = LocalisedStringParser.Parse(loc);
687521
}
688522
else {
689-
Localize(prototypeType + "-description." + target.name, null);
523+
target.locDescr = LocalisedStringParser.Parse(prototypeType + "-description." + target.name, []);
690524
}
691525

692-
target.locDescr = localeBuilder.Length == 0 ? null : FinishLocalize();
693526
_ = table.Get("icon_size", out float defaultIconSize);
694527

695528
if (table.Get("icon", out string? s)) {

0 commit comments

Comments
 (0)