Нужна помощь в анализе 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 столбец в виде обычного текста для хранения в БД.

Третья сторона предоставляет неверные данные CSV. В идеале они должны это исправить. В противном случае у вас есть неверные данные CSV, и стандартные библиотеки CSV не смогут их успешно проанализировать.

David 13.08.2024 15:42

Спасибо @David, я не думаю, что стороннее решение осуществимо. Его исходит от кого-то из рекрутера, который не очень разбирается в технологиях, они просто экспортируют и пересылают. HTML ВСЕГДА начинается с «<p> и заканчивается </p». Есть ли обходной путь, который я мог бы сделать с этими знаниями, чтобы он просто считывал все между этими тегами в кавычках в 1 столбец?

wiizimov 13.08.2024 15:48

Если данные всегда соответствуют известному формату, вы можете попробовать написать своего рода предварительный анализатор, который исправляет данные, чтобы они были действительными в формате CSV. Учитывая пример, это может включать в себя поиск первой <p> в каждой строке и добавление второй двойной кавычки для каждой найденной двойной кавычки до последней </p>. После исправления стандартные инструменты синтаксического анализа смогут читать данные.

David 13.08.2024 15:50

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

Poul Bak 13.08.2024 17:32

Предположительно в полях типа «Бренд» могли быть неудачные символы, например, We "heart" apples, and carrots. Не могли бы вы написать стороннюю программу, которая создает для вас действительные файлы CSV и немного упрощает их работу?

Andrew Morton 13.08.2024 20:50
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
5
56
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 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.

Если вы делаете это таким образом, можно использовать функцию Split(Char[], Int32).

Andrew Morton 13.08.2024 20:41

Другие вопросы по теме

Похожие вопросы

После перезапуска игры цели не появляются
Таблица данных JQuery – сортировка столбцов не работает в
Как я могу исправить связь многие-ко-многим между двумя объектами, чтобы третья таблица автоматически заполнялась в структуре сущностей и веб-API (С#)
Как я могу зарегистрировать HTTP-ответ с помощью промежуточного программного обеспечения в функциях Azure, выполняющих изолированный процесс .NET 8?
Контекст Testcontainers .NET 8 db, похоже, не обновляется в [Факт], проблема решена, ищет объяснение
Переписать удаление служебной шины Azure
Невозможно вставить явное значение для столбца идентификаторов в таблице «Обсуждаемые продукты», если для параметра IDENTITY_INSERT установлено значение OFF
Сопоставление и удаление подстрок повторяющихся символов, а затем возврат длины самой длинной подстроки
Как отсортировать массив объектов сам по себе на основе свойства указанного объекта?
Динамическое создание выражений LINQ — ToLower и «Содержит» одновременно