score:1

Accepted answer

You can split the text by the dots . which gives you the info for each person, then split each by new lines \n, \r, or \r\n depending on what line-ending your files uses. You can then get the name from the second line (with index 1) and the age from the third line (with index 2). Finally, split the name and age by colon and space ": " and get the second string (with index 1). This assumes that your file structure is fixed and never changes, otherwise you'll need find the name and age based on conditions which you said you wanted to avoid:

var persons = new List<Person>();
var personInfo = readText.Split(new char[]{'.'}, StringSplitOptions.RemoveEmptyEntries);
foreach (var i in personInfo)
{
    var person = new Person();
    var lines = i.Split(new char[]{'\n'}, StringSplitOptions.RemoveEmptyEntries);
    person.Name = lines[1].Split(new string[]{": "}, StringSplitOptions.None)[1];
    person.Age = lines[2].Split(new string[]{": "}, StringSplitOptions.None)[1];
    persons.Add(person);
}

Alternatively, you can use LINQ:

var persons = readText.Split(new char[]{'.'}, StringSplitOptions.RemoveEmptyEntries)
                      .Select(i => i.Split(new char[]{'\n'}, StringSplitOptions.RemoveEmptyEntries))
                      .Select(l => new Person{
                          Name = l[1].Split(new string[]{": "}, StringSplitOptions.None)[1],
                          Age = l[2].Split(new string[]{": "}, StringSplitOptions.None)[1]
                      });

score:1

If you use File.ReadAllLines() instead of .ReadAllText(), then you can use a boxing function. I've written one of these myself, so if you want something that works out of the box (no pun intended) then you can install Anaximander.Linq and use it like this:

var readText = File.ReadAllLines(path);

var people = readText
    .BoxWhile((a, b) => b != ".")
    .Select(x => x
            .Where(t => t != "." && t != "Person")
            .Select(t => t.Split(':').Last().Trim())
        .ToList())
    .Where(x => x.Any())
    .Select(x => new Person
    {
        Name = x[0],
        Age = x[1]
    });

If you want to know how this works internally, then the source code is available on GitHub so you can take a look at how BoxedEnumerable is implemented. It uses a sliding window with a comparison operator; it starts a new "box" each time the comparison between the current element and the next returns false. Your question is actually one of several cases I've seen recently where the comparison doesn't need to look at the current and next, only the next, so I'll be adding an additional method for that shortly.

score:0

Easier alternative might be to split by "Person" or "name:"

List<Person> people = File.ReadAllText(@"c:\temp\person.txt")
    .Split(new[] { "name:" }, StringSplitOptions.None)
    .Skip(1)
    .Select(p => p.Split('\n', ':'))
    .Select(a => new Person { 
         Name = a[0].Trim(), 
         Age = a[2].Trim() 
    })
    .ToList();

score:0

Not simple but very powerfull solution using monadic parser Sprache:

You can combine parsers to parse very complex structures.

SplitTestSample.csproj

<PropertyGroup>
  <OutputType>Exe</OutputType>
  <TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>

<ItemGroup>
  <PackageReference Include="Sprache" Version="2.1.2" />
</ItemGroup>

Program.cs

using System;
using System.Collections.Generic;
using Sprache;

namespace SplitTextSample
{
    class Program
    {
        public static string Text =
@"Person
name: abc
age: 40
.
Person
name: xyx
age: 18
.
Person
name: uke
age: 27
.";

        public class Person
        {
            public string Name { get; set; }
            public string Age { get; set; }
        }

        static void Main(string[] args)
        {
            // Parses: Person\r\n
            var personTagParser = Parse.String("Person").Then(_=>Parse.LineEnd);

            // Parses: name: {name}\r\n
            Parser<string> nameParser = from tag in Parse.String("name:").Token()
                from name in Parse.AnyChar.Except(Parse.LineEnd).AtLeastOnce().Text()
                from lineEnd in Parse.LineEnd
                select name;

            // Parses: age: {age}\r\n.[\r\n]
            Parser<string> ageParser = from tag in Parse.String("age:").Token()
                from age in Parse.AnyChar.Except(Parse.LineEnd).AtLeastOnce().Text()
                from delimeter in Parse.LineEnd.Then(_ => Parse.Char('.')).Then(_=> Parse.LineEnd.Optional())
                select age;

            // Parses: Person\r\nname: {name}\r\nage: {age}\r\n.[\r\n]
            Parser<Person> personParser =
                from personTag in personTagParser
                from name in nameParser
                from age in ageParser
                select new Person{Name = name, Age = age};

            // Parses: Many persons
            var personsParser = personParser.Many();

            // Final parse returns IEnumerable<Person>
            var persons = personsParser.Parse(Text);

            foreach (var person in persons)
            {
                Console.WriteLine($"Name: {person.Name}, Age: {person.Age}");
            }

            Console.ReadLine();
        }

}

Result:

Name: abc, Age: 40
Name: xyx, Age: 18
Name: uke, Age: 27

Related Articles