Я получаю 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 столбец в виде обычного текста для хранения в БД.
Спасибо @David, я не думаю, что стороннее решение осуществимо. Его исходит от кого-то из рекрутера, который не очень разбирается в технологиях, они просто экспортируют и пересылают. HTML ВСЕГДА начинается с «<p> и заканчивается </p». Есть ли обходной путь, который я мог бы сделать с этими знаниями, чтобы он просто считывал все между этими тегами в кавычках в 1 столбец?
Если данные всегда соответствуют известному формату, вы можете попробовать написать своего рода предварительный анализатор, который исправляет данные, чтобы они были действительными в формате CSV. Учитывая пример, это может включать в себя поиск первой <p>
в каждой строке и добавление второй двойной кавычки для каждой найденной двойной кавычки до последней </p>
. После исправления стандартные инструменты синтаксического анализа смогут читать данные.
Я предлагаю вам прочитать файл в виде обычного текста (не CSV), а затем использовать регулярное выражение для поиска и сопоставления html.
Предположительно в полях типа «Бренд» могли быть неудачные символы, например, We "heart" apples, and carrots
. Не могли бы вы написать стороннюю программу, которая создает для вас действительные файлы CSV и немного упрощает их работу?
Как уже отмечалось в комментариях, ваш 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).
Третья сторона предоставляет неверные данные CSV. В идеале они должны это исправить. В противном случае у вас есть неверные данные CSV, и стандартные библиотеки CSV не смогут их успешно проанализировать.