Je réponds tardivement à ce post, alors je ne sais pas si c'est encore pertinent, mais au cas où cela puisse servir à quelqu'un je vais quand même poser ma proposition.

Je me disais en l'occurrence qu'il pouvait s'agir d'un cas d'usage pour la library Sprache, qui sert à créer dans parsers. Une des points intéressants est l'aspect déclaratif du code servant à définir ces parsers et qui le rend facile à lire.

N'ayant pas les détails des règles appliquées pour les données, je me suis permis d'extrapoler. Voici les données que j'ai utilisées en référence :

Code txt : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
03/04/2023
Désérialisation un grand classique
243
Rue de LAMBREYAGE
59450
Sin-le-Noble
Liste des Artistes
Art Contemporain
Emplacement
Nom
Age
Pays
Goodies
Expos
Sell
1
Matthieu dethé
45
France
23
18
6
2
Benoit Breton
34
France
12
5
2
3
Jean Flou
65
Belgique
53
4
2
Nombre
3
Street Art
Emplacement
Nom
Age
Pays
Expos
1
Sabart Deco
32
France
140
2
Laurent Pez
23
Suisse
4
Nombre
2
Art Grotesque
Emplacement
Nom
Age
Pays
Goodies
Expos
Sell
1
Grouchy Deco
32
France
9999
71
4
2
Masset Pez
23
Suisse
4
7
6
Nombre
2
Nombre Total d'exposants
7

Les classes de modèle de données C# (exposition, en-tête et pied de fichier, catégories, artistes) :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
namespace Expositions
{
    public class Exhibition
    {
        public Exhibition(Headers headers, IEnumerable<Category> categories, Footer footer)
        {
            Headers = headers;
            Categories = categories.ToArray();
            Footer = footer;
            _artists = new Lazy<Artist[]>(() => Categories.SelectMany(c => c.Artists).ToArray());
        }
 
        public Headers Headers { get; }
        public IEnumerable<Category> Categories { get; }
        public Footer Footer { get; }
 
        public IEnumerable<Artist> Artists => _artists.Value;
        private Lazy<Artist[]> _artists;
    }
 
    public class Headers
    {
        public Headers(DateTime date, string title, int number, string street, string zipCode, string city)
        {
            Date = date;
            Title = title;
            Number = number;
            Street = street;
            ZipCode = zipCode;
            City = city;
        }
 
        public DateTime Date { get; }
        public string Title { get; }
        public int Number { get; }
        public string Street { get; }
        public string ZipCode { get; }
        public string City { get; }
    }
 
    public class Category
    {
        public Category(string title, IEnumerable<Artist> artists, int count)
        {
            Name = title;
            Artists = artists.ToArray();
            Count = count;
        }
 
        public string Name { get; }
        public IEnumerable<Artist> Artists { get; }
        public int Count { get; }
    }
 
    public class Artist
    {
        public int Slot { get; internal set; }
        public string Name { get; internal set; } = "";
        public int Age { get; internal set; }
        public string Country { get; internal set; } = "";
        public int Goodies { get; internal set; }
        public int Exhibits { get; internal set; }
        public int Sell { get; internal set; }
    }
 
    public class Footer
    {
        public Footer(int count) { Count = count; }
        public int Count { get; internal set; }
    }
}
La classe de grammaire servant à définir les parsers :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
using Sprache;
 
namespace Expositions
{
    public static class Grammar
    {
        #region Primitives
        public static readonly Parser<string> Date =
            Parse.Digit.Or(Parse.Char('/')).Except(Parse.LineEnd).AtLeastOnce().Text().Token();
 
        public static readonly Parser<string> SimpleText =
            Parse.AnyChar.Except(Parse.LineEnd).Many().Text().Token();
 
        public static readonly Parser<string> Integer =
            Parse.Digit.AtLeastOnce().Text().Token();
 
        public static readonly Parser<string> ZipCode =
            Parse.LetterOrDigit.Except(Parse.LineEnd).Many().Text().Token();
 
        public static Parser<string> Label(string lookup) =>
            from label in Parse.String(lookup).Text().Token() select label;
        #endregion
 
        #region Headers
        public static readonly Parser<Headers> Headers =
          from date in Date
          from title in SimpleText
          from number in Integer
          from street in SimpleText
          from zipCode in ZipCode
          from city in SimpleText
 
          select new Headers
          (
              DateTime.Parse(date),
              title,
              int.Parse(number),
              street,
              zipCode,
              city
          );
        #endregion
 
        #region Artists
        public static Parser<Artist> Artist(CategoryParseOptions options)
        {
            var return0 = Parse.Preview(Parse.AnyChar).Except(Parse.String("Nombre")).Return("0");
            var returnEmpty = Parse.Preview(Parse.AnyChar).Except(Parse.String("Nombre")).Return("");
 
            var parseSlot = options.Slot ?
                (from slot in Integer select slot) :
                Parse.Return("0");
            var parseName = options.Name ?
                (from name in SimpleText select name) :
                Parse.Return("");
            var parseAge = options.Age ?
                (from age in Integer select age) :
                Parse.Return("0");
            var parseCountry = options.Country ?
                (from country in SimpleText select country) :
                Parse.Return("");
            var parseGoodies = options.Goodies ?
                (from goodies in Integer select goodies) :
                Parse.Return("0");
            var parseExhibits = options.Exhibits ?
                (from exhibits in Integer select exhibits) :
                Parse.Return("0");
            var parseSell = options.Sell ?
                (from sell in Integer select sell) :
                Parse.Return("0");
 
            return
                from slot in parseSlot
                from name in parseName
                from age in parseAge
                from country in parseCountry
                from goodies in parseGoodies
                from exhibits in parseExhibits
                from sell in parseSell
                select new Artist
                {
                    Slot = int.Parse(slot),
                    Name = name,
                    Age = int.Parse(age),
                    Country = country,
                    Goodies = int.Parse(goodies),
                    Exhibits = int.Parse(exhibits),
                    Sell = int.Parse(sell),
                };
        }
 
        public class CategoryParseOptions
        {
            public bool Slot { get; set; }
            public bool Name { get; set; }
            public bool Age { get; set; }
            public bool Country { get; set; }
            public bool Goodies { get; set; }
            public bool Exhibits { get; set; }
            public bool Sell { get; set; }
        }
 
        public static readonly Parser<Category> Category =
            from title in SimpleText
            from slot in Parse.Optional(Label("Emplacement"))
            from name in Parse.Optional(Label("Nom"))
            from age in Parse.Optional(Label("Age"))
            from country in Parse.Optional(Label("Pays"))
            from goodies in Parse.Optional(Label("Goodies"))
            from exhibits in Parse.Optional(Label("Expos"))
            from sell in Parse.Optional(Label("Sell"))
 
            let options = new CategoryParseOptions
            {
                Slot = slot.GetOrDefault() != null,
                Name = name.GetOrDefault() != null,
                Age = age.GetOrDefault() != null,
                Country = country.GetOrDefault() != null,
                Goodies = goodies.GetOrDefault() != null,
                Exhibits = exhibits.GetOrDefault() != null,
                Sell = sell.GetOrDefault() != null
            }
 
            from artists in Artist(options).Except(Parse.String("Nombre")).Many()
 
            from label in Parse.String("Nombre").Token()
            from count in Integer
 
            select new Category(title, artists, int.Parse(count));
 
        public static readonly Parser<IEnumerable<Category>> Categories =
            from label in Parse.String("Liste des Artistes").Token()
            from categories in Category.Many()
            select categories;
        #endregion
 
        #region Footer
        public static readonly Parser<Footer> Footer =
            from label in Parse.String("Nombre Total d'exposants").Token()
            from count in Integer
            select new Footer(int.Parse(count));
        #endregion
 
        #region Exhibition
        public static readonly Parser<Exhibition> Exhibition =
           from headers in Headers
           from label in Parse.String("Liste des Artistes").Token()
           from categories in Category.Many()
           from footer in Footer
           select new Exhibition(headers, categories, footer);
        #endregion
    }
}
Un programme console tout simple pour utiliser le parser :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
using Expositions;
using Sprache;
 
Console.WriteLine("Start");
 
var source = await File.ReadAllTextAsync("source.txt");
 
var exhibition = Grammar.Exhibition.Parse(source);
 
var headers = exhibition.Headers;
Console.WriteLine($"Date : {headers.Date}");
Console.WriteLine($"Titre : {headers.Title}");
Console.WriteLine($"Nombre : {headers.Number}");
Console.WriteLine($"Rue : {headers.Street}");
Console.WriteLine($"Code postal : {headers.ZipCode}");
Console.WriteLine($"Ville : {headers.City}");
 
foreach (var category in exhibition.Categories)
{
    Console.WriteLine();
    Console.WriteLine(category.Name);
 
    foreach (var artist in category.Artists)
    {
        Console.WriteLine(artist.Country);
    }
}
 
Console.WriteLine();
Console.WriteLine($"Count: {exhibition.Footer.Count}");
 
Console.WriteLine("End");