Нужна помощь в анализе HTML-разметки с помощью CSVHelper через мой .NET API

Я получаю CSV от третьей стороны в следующем формате:

Job Requisition Id,Brand,Is Deleted,Internal Status,Job Code,Job Title-JobRequisitionLocale,Posting Start Date-JobRequisitionPosting,Posting End Date-JobRequisitionPosting,Job Posting Status-JobRequisitionPosting,Minimum Salary,Maximum Salary,Job Board Id-JobRequisitionPosting,Postal Code,Job Description-JobRequisitionLocale
1,TEST,TEST,TEST,TEST,TEST,2024-07-16T11:41:50Z,2024-07-31T00:59:59Z,TEST,00.00,00,00,TEST,TEST,"<p class = "MsoNoSpacing"><span style = "font-size:11pt"><span style = "font-family:Calibri,sans-serif"><b><span lang = "EN-US" style = "font-size:12.0pt">Role: </span></b></p"

Я сократил разметку HTML, она стала намного длиннее. Однако я пытаюсь прочитать ВСЮ HTML-разметку, которая находится в конце каждой строки, чтобы сохранить ее в столбце «Описание вакансии» (последний столбец) как обычный текст для хранения в БД.

Но, похоже, строка продолжает заканчиваться первой запятой в HTML-разметке в кавычках после «Calibri»: <span style="font-family:Calibri,

Это мой код контроллера:

var jobPositions = new List<JobAdvertModel>();

await foreach (var blobItem in containerClient.GetBlobsAsync())
{
    var blobClient = containerClient.GetBlobClient(blobItem.Name);
    var blobDownloadInfo = await blobClient.DownloadAsync();

    using (var streamReader = new StreamReader(blobDownloadInfo.Value.Content))
    using (var csvReader = new CsvReader(streamReader, new CsvConfiguration(CultureInfo.InvariantCulture)
    {
        Delimiter = ",",
        BadDataFound = context =>
        {
            // Handle or log bad data
            Console.WriteLine($"Bad data found: {context.RawRecord}");
        }
    }))
    {
        csvReader.Context.RegisterClassMap<JobAdvertMap>();
        var records = csvReader.GetRecords<JobAdvertModel>().ToList();
        jobPositions.AddRange(records);
    }
}

return Ok(jobPositions);

(Удалены фрагменты Azure, приведенные выше, но они считываются из лазурного объекта, который принимается еженедельно.)

Я пытаюсь поместить всю HTML-разметку в 1 столбец в виде обычного текста для хранения в БД.

🤔 А знаете ли вы, что...
C# поддерживает LINQ (Language Integrated Query) для удобного запроса и обработки данных.


56
2

Ответы:

Решено

Как уже отмечалось в комментариях, ваш CSV неверен, поскольку кавычки не экранируются должным образом с помощью "". Кроме того, HTML-код кажется неверным, а тег </p не закрыт должным образом (должен быть </p>). Однако лучшим вариантом, безусловно, было бы исправить это на стороне производителя и, следовательно, фактически получить правильные данные.

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

using System.Globalization;
using System.Text.Json;
using CsvHelper;
using CsvHelper.Configuration;

namespace MyProgram;

public class Program
{
    record Test(int LineIndex, string A, string B, string C, string D, string E, string F, string G, string H, string I, string J, string K, string L, string M,
                string Html);
    public static async Task Main()
    {
        var toBeParsed = """
        0,Job Requisition Id,Brand,Is Deleted,Internal Status,Job Code,Job Title-JobRequisitionLocale,Posting Start Date-JobRequisitionPosting,Posting End Date-JobRequisitionPosting,Job Posting Status-JobRequisitionPosting,Minimum Salary,Maximum Salary,Job Board Id-JobRequisitionPosting,Postal Code,Job Description-JobRequisitionLocale
        1,TEST,TEST,TEST,TEST,TEST,2024-07-16T11:41:50Z,2024-07-31T00:59:59Z,TEST,00.00,00,00,TEST,TEST,"<p class = "MsoNoSpacing"><span style = "font-size:11pt"><span style = "font-family:Calibri,sans-serif"><b><span lang = "EN-US" style = "font-size:12.0pt">Role: </span></b></p"
        """;
        var correctCsv = CorrectCsv(toBeParsed);
        var config = new CsvConfiguration(CultureInfo.InvariantCulture)
        {
            HasHeaderRecord = false, // just to make it work with the provided "Test" record
            NewLine = Environment.NewLine,
        };
        using var reader = new StringReader(correctCsv);
        using var csvReader = new CsvReader(reader, config);
        var parsedCsv = csvReader.GetRecords<Test>().ToArray();
        Console.WriteLine(JsonSerializer.Serialize(parsedCsv, new JsonSerializerOptions { WriteIndented = true }));
        var lastLineLastRow = parsedCsv[^1].Html;
        Console.WriteLine(lastLineLastRow);
    }

    private static string CorrectCsv(string toBeParsed)
    {
        var lastSeparatorIndex = toBeParsed.LastIndexOf(",\"<p"); // search from back for last occurence of ",<p" within the string
        var html = toBeParsed[(lastSeparatorIndex + 2)..].TrimEnd('"'); // extract HTML from string
        var escapedHtml = html.Replace("\"", "\"\""); // escape "
        return toBeParsed[..lastSeparatorIndex] + ",\"" + escapedHtml + "\""; // concatenate new correctly escaped CSV
    }
}

Ожидаемый результат:

[
  {
    "LineIndex": 0,
    "A": "Job Requisition Id",
    "B": "Brand",
    "C": "Is Deleted",
    "D": "Internal Status",
    "E": "Job Code",
    "F": "Job Title-JobRequisitionLocale",
    "G": "Posting Start Date-JobRequisitionPosting",
    "H": "Posting End Date-JobRequisitionPosting",
    "I": "Job Posting Status-JobRequisitionPosting",
    "J": "Minimum Salary",
    "K": "Maximum Salary",
    "L": "Job Board Id-JobRequisitionPosting",
    "M": "Postal Code",
    "Html": "Job Description-JobRequisitionLocale"
  },
  {
    "LineIndex": 1,
    "A": "TEST",
    "B": "TEST",
    "C": "TEST",
    "D": "TEST",
    "E": "TEST",
    "F": "2024-07-16T11:41:50Z",
    "G": "2024-07-31T00:59:59Z",
    "H": "TEST",
    "I": "00.00",
    "J": "00",
    "K": "00",
    "L": "TEST",
    "M": "TEST",
    "Html": "\u003Cp class=\u0022MsoNoSpacing\u0022\u003E\u003Cspan style=\u0022font-size:11pt\u0022\u003E\u003Cspan style=\u0022font-family:Calibri,sans-serif\u0022\u003E\u003Cb\u003E\u003Cspan lang=\u0022EN-US\u0022 style=\u0022font-size:12.0pt\u0022\u003ERole: \u003C/span\u003E\u003C/b\u003E\u003C/p"
  }
]
<p class = "MsoNoSpacing"><span style = "font-size:11pt"><span style = "font-family:Calibri,sans-serif"><b><span lang = "EN-US" style = "font-size:12.0pt">Role: </span></b></p

Обратите внимание: мне пришлось внести простую настройку в ваши данные, то есть добавить 0 в качестве индекса строки для первой строки и чтобы я мог отключить HasHeaderRecord, чтобы использовать мою простую запись Test, иначе библиотека CSV выдаст ошибку ошибка, поскольку не найдено свойств, соответствующих именам заголовков.

Вы можете сделать все это намного более производительным, используя Span<char>, если необходимо.

В качестве альтернативы вы, конечно, также можете написать свой собственный синтаксический анализатор CSV, который может обрабатывать/ожидать искаженные данные.


Если вы знаете, что HTML всегда является последним столбцом (как вы упомянули в своем вопросе), то почему бы не разделить данные на основе этой информации?

Считайте символы за символами и отслеживайте встречающиеся запятые. Как только вы встретите 13 запятых, вы уже знаете, что остальное — это HTML.

public static void Main()
{
    var line = "1,TEST,TEST,TEST,TEST,TEST,2024-07-16T11:41:50Z,2024-07-31T00:59:59Z,TEST,00.00,00,00,TEST,TEST,HTMLSTARTSHEHRE";
    var commaCounter = 0;
    var charCounter = 0;
    var htmlStartPos = 0;
    foreach(char c in line.ToCharArray())
    {
        charCounter++;
        if (c == ',')
            commaCounter++;
        
        if (commaCounter == 13)
            htmlStartPos = charCounter;
    }
    var htmlText = line.Substring(htmlStartPos+1);
    Console.WriteLine("HTML Starts at:" + htmlStartPos+1);
    Console.WriteLine("HTMLTEXT: " + htmlText);
}

Выделите HTML из строки, а затем обработайте ее как обычную строку CSV.


Интересные вопросы для изучения

После перезапуска игры цели не появляютсяТаблица данных JQuery – сортировка столбцов не работает вКак я могу исправить связь многие-ко-многим между двумя объектами, чтобы третья таблица автоматически заполнялась в структуре сущностей и веб-API (С#)Как я могу зарегистрировать HTTP-ответ с помощью промежуточного программного обеспечения в функциях Azure, выполняющих изолированный процесс .NET 8?Контекст Testcontainers .NET 8 db, похоже, не обновляется в [Факт], проблема решена, ищет объяснениеЕсть ли только способ CSS выбрать последний вложенный элемент по имени класса?Проблемы доступности тегов span/mark, вложенных в абзацыКнопка воспроизведения звука запускает фантомный второй щелчок, когда currentTime изменяется программно, предотвращая воспроизведениеCSS и JavaScript не применяются правильно после переноса содержимого HTML в компонент приложения Angular из-за проблем с инкапсуляцией просмотра и порядком загрузки скриптовКак создать текст с эффектом градиента и тиснения в CSS