C # Многострочный и многоколонный экстрактор для чтения электронных писем - PullRequest
1 голос
/ 03 октября 2019

Итак, я создал консольное приложение на C #, которое будет читать электронные письма и извлекать из них данные.

С некоторой помощью я перешел на этап, где он может читать столбцы парами, но как толькоЯ попал в нижнюю часть письма (там может быть даже больше строк, чем эти два), оно не может его разбить.

Вот что я попробовал:

using System;
using System.Text.RegularExpressions;
using System.Collections.Generic;

namespace Multiline_Email_Test
{
// <summary>
/// Console app to test the reading of the multiline email.
/// If successful readback is shown we could import to SQL Server.
/// </summary>
public class Program
{
    public static void Main()
    {
        string email = @"NOTIFICATION OF MOVEMENT STARTING IN AUGUST

Consignor Package ID                              Local Reference Number
-------------------                              ----------------------
GRLK123450012                                         123456

Place Of dispatch                                Guarantor type code
-----------------                                -------------------
GR00001234567                                          1

Consignee Package ID                              Guarantor details
-----------------                                -------------------
RR001239E0070

Place Of delivery                                Date of dispatch DD MM YYYY
-----------------                                ---------------------------
FR001379E0570                                    21 03 2019

                                                 Time of dispatch
                                                 ----------------
                                                 08:29

                                                Vehicle registration number
                                               ---------------------------
                                               XXBB12345678

Item number   Package Product CN CodeCode    Quantity       Brand
-----------   -------------------------     --------       -----
Line 1 of 2   B000           22040009       7603.200       Guinness DIC    440ml CAN 06X04 MDCES
Line 2 of 2   B000           22040009       14636.160      Guinness DIC    440ml CAN 06X04 MDCES

";



var dict = new Dictionary<string, string>();
        try
        {
            var lines = email.Split(Environment.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
            int starts = 0, end = 0, length = 0;
            while (!lines[starts + 1].StartsWith("-"))
                starts++;
            for (int i = starts + 1; i < lines.Length; i += 3)
            {
                var mc = Regex.Matches(lines[i], @"(?:^| )-");
                foreach (Match m in mc)
                {
                    int start = m.Value.StartsWith(" ") ? m.Index + 1 : m.Index;
                    end = start;
                    while (lines[i][end++] == '-' && end < lines[i].Length)
                        ;
                    length = Math.Min(end - start, lines[i - 1].Length - start);
                    string key = length > 0 ? lines[i - 1].Substring(start, length).Trim() : "";
                    end = start;
                    while (lines[i][end++] == '-' && end < lines[i].Length)
                        ;
                    length = Math.Min(end - start, lines[i + 1].Length - start);
                    string value = length > 0 ? lines[i + 1].Substring(start, length).Trim() : "";
                    dict.Add(key, value);
                }
            }
        }
        catch (Exception ex)
        {
            throw new Exception(ex.ToString());
        }

        foreach (var x in dict)
            Console.WriteLine("{0} : {1}", x.Key, x.Value);
       }
   }
}

Я создал живую демонстрацию в .net fiddle здесь https://dotnetfiddle.net/6nMO2c

1 Ответ

1 голос
/ 10 ноября 2019

Что касается значений заголовка документа, ваш код кажется функциональным, но просто для забавы я нашел регулярное выражение, которое выполняет эту работу. Затем я также отвечаю на вопрос о данных таблицы.

        int textArrayPosition = 0; // Just to separate the header part and the table part
        var headersDictionary = new Dictionary<string, string>();
        List<string> arrayHeaders;
        List<List<string>> arrayData = new List<List<string>>();
        var headersFinder = new Regex(@"^(.*?) {2,}(.*)\r\n\-*? {2,}\-*\r\n(.*?)( {2,}(.*)|$)", RegexOptions.Multiline);

        foreach (Match match in headersFinder.Matches(inputText))
        {
            if (match.Groups.Count < 4)
                continue;

            var firstHeaderName = match.Groups[1].Value;
            var secondHeaderName = match.Groups[2].Value;

            if (!string.IsNullOrWhiteSpace(firstHeaderName))
                headersDictionary.Add(firstHeaderName, match.Groups[3].Value);

            if (!string.IsNullOrWhiteSpace(secondHeaderName))
            {
                if (match.Groups.Count == 6)
                    headersDictionary.Add(secondHeaderName, match.Groups[5].Value);
                else
                    headersDictionary.Add(secondHeaderName, string.Empty);
            }

            textArrayPosition = match.Index + match.Length;
        }

        Console.WriteLine("*** Document headers :");
        foreach (var entry in headersDictionary)
            Console.WriteLine($"{entry.Key} = {entry.Value}");

Затем мы находим таблицу в вашем тексте в виде списка строк.

 var arrayLines = inputText.Substring(textArrayPosition).Split(new string[] { "\n", "\r" }, StringSplitOptions.RemoveEmptyEntries);

Таким образом, мы обрабатываем таблицуТак как заголовки таблицы не позволяют разделять столбцы, я основывался на том факте, что в первой строке данных было найдено как минимум 2 последовательных пробела, чтобы можно было угадать положение столбцов. Простое регулярное выражение помогает нам сделать это.

        if (arrayLines.Length > 2)
        {
            var arrayColsPositions = new List<int>();

            // Find cols positions
            arrayColsPositions.Add(0);
            var firstDataLine = arrayLines[2];
            var columnsPositionDetector = new Regex(@" {2,}", RegexOptions.Singleline);
            foreach (Match match in columnsPositionDetector.Matches(firstDataLine))
            {
                arrayColsPositions.Add(match.Index + match.Length);
            }

            // Find headers
            arrayHeaders = ReadLineValues(arrayLines[0], arrayColsPositions).ToList();
            // Find data lines
            for (int lineId = 2; lineId < arrayLines.Length; lineId++)
            {
                arrayData.Add(ReadLineValues(arrayLines[lineId], arrayColsPositions).ToList());
            }

            Console.WriteLine("\n*** Array headers :");
            Console.WriteLine(string.Join(", ", arrayHeaders));

            Console.WriteLine("\n*** Array lines data :");
            foreach (var record in arrayData)
            {
                Console.WriteLine(string.Join(", ", record));
            }
        }
        else
            Console.WriteLine("The array is empty.");

Наконец, вот небольшой полезный метод, который я разработал для удобного поиска, без превышения длины определенных строк, данных в нужном месте.

    private static IEnumerable<string> ReadLineValues(string sourceLine, List<int> colsPositions)
    {
        for (int colId = 0; colId < colsPositions.Count; colId++)
        {
            var start = colsPositions[colId];
            int length;
            if (colId < colsPositions.Count - 1)
                length = colsPositions[colId + 1] - start;
            else
                length = sourceLine.Length - start;

            if (start < sourceLine.Length)
            {
                if (start + length > sourceLine.Length)
                    length = sourceLine.Length - start;

                yield return sourceLine.Substring(start, length).Trim();
            }
        }
    }
...