Create a new csv file with new columns

У меня есть локальный CSV-файл со списком сотрудников и их временя потраченое на работу в течение нескольких дней. Нужно считать текстовый файл и написать алгоритм на любом языке программирования, который после отработки формирует новый CSV-файл со сводной таблицей по всем сотрудники из исходного файла CSV.
Будет понятнее на примере
Input:
A, B, C
1a,1b,1c
2a,1b,2c
3a,1b,3c
1a,2b,4c
2a,2b,5c
3a,2b,6c
1a,3b,7c
2a,3b,8c
3a,3b,9c
a = EmployeeName, b = Date, c = WorkHours - this is my headers
Мне нужно получить новый файл с массивом:
Output:
A,1b,2b,3b
1a,1c,4c,7c
2a,2c,5c,8c
3a,3c,6c,9c
Это мой код, который читает файл и открывает его на странице.

<html>
<head>
<meta charset="utf-8" />
<title>Demo - CSV-to-Table</title>
</head>
You can use the CSVToArray() function mentioned in this blog entry.

<script type="text/javascript">
    function Upload() {
        var fileUpload = document.getElementById("fileUpload");
        var regex = /^([a-zA-Z0-9\s_\\.\-:])+(.csv|.txt)$/;
        if (regex.test(fileUpload.value.toLowerCase())) {
            if (typeof (FileReader) != "undefined") {
                var reader = new FileReader();
                reader.onload = function (e) {
                    var table = document.createElement("table");
                    var rows = e.target.result.split("\n");
                    for (var i = 0; i < rows.length; i++) {
                        var cells = rows[i].split(",");
                        if (cells.length > 1) {
                            var row = table.insertRow(-1);
                            for (var j = 0; j < cells.length; j++) {
                                var cell = row.insertCell(-1);
                                cell.innerHTML = cells[j];
                            }
                        }
                    }
                    var dvCSV = document.getElementById("dvCSV");
                    dvCSV.innerHTML = "";
                    dvCSV.appendChild(table);
                }
                reader.readAsText(fileUpload.files[0]);
            } else {
                alert("This browser does not support HTML5.");
            }
        } else {
            alert("Please upload a valid CSV file.");
        }
    }
</script>
<input type="file" id="fileUpload" />
<input type="button" id="upload" value="Upload" onclick="Upload()" />
<hr />
<div id="dvCSV">
</div>
</html>

Помогите мне изменить его так, чтобы он принимал файл и создавал повторно обработанный файл со сведённой таблицей. Может ли кто-нибудь пролить свет на то, как лучше всего это сделать?
Can use Javascript, JQuerry, libraries Node.js(paraparse,read.csv,csvtojson,csv-parser e.t.c.)?


Ответы (1 шт):

Автор решения: Alexander Lonberg

На чистом JS.

Сортировку 1b,2b,3b не включал. Если она необходима. Сперва надо собрать все b, отсортировать и только после этого пройтись по таблице.

const CSV = `
A, B, C
1a,1b,1c
2a,1b,2c
3a,1b,3c
1a,2b,4c
2a,2b,5c
3a,2b,6c
1a,3b,7c
2a,3b,8c
3a,3b,9c
`

const table = CSV.trim().split(/\n/g).map((row) => row.split(/[\s,]+/g))
// Отделяем типы столбцов
const columns = table.splice(0, 1)[0]
// Определяем столбцы
const columnName = columns.findIndex((v) => /a/i.test(v))
const columnDate = columns.findIndex((v) => /b/i.test(v))
const columnWork = columns.findIndex((v) => /c/i.test(v))
// Раз нужно свернуть сотрудников, можно сразу собрать их в кучу
// ... заодно запомнить все варианты number:b
const A = []
const employees = table.reduce((a, v) => {
  const name = v[columnName]
  const date = v[columnDate]
  const work = v[columnWork]
  // Индекс столбца даты
  let i = A.indexOf(date)
  if (i === -1) {
    i = A.push(date) - 1
  }
  if (!a[name]) {
    a[name] = []
  }
  // Именно в этот индекс и вставляем, чтоб не промахнуться :)
  a[name][i] = work
  return a
}, {})
// Теперь просто сворачиваем каждую строку -> Сотрудник:Время
const result = [...Object.entries(employees)].map(([e, w]) => [e, w].flat().join(','))
// Добавляем первую строку даты
result.unshift(['A', ...A].join(','))
// Склеиваем строки
const newCSV = result.join(`\n`)

console.log(newCSV)

Разумеется:

  • если столбцы именуются как-то подругому, необходимо изменить
columns.findIndex((v) => /EmployeeName/i.test(v))
  • если дата пишется в каком либо формате, придется привести ее к реальной дате new Date() и сравнивать метку времени
  • если имена сотрудников пишуться как "Ваня Иванов", создать регулярку new RegExp("Ваня Иванов".replace(/\s+/g, ' '), 'i')

const CSV = `
Name,               Date,        Work 
Jerrod Tate,        Jun 29 2020, 5.2
Bianca Wright,      Jun 29 2020, 5.4
Wayne Payne,        Jun 29 2020, 6.5
Jerrod Tate,        Jun 30 2020, 5
Heath Mcdaniel,     Jun 30 2020, 6
Ezekiel Harrington, Jun 30 2020, 6.4
`
// Полноценная таблица
const table = CSV.trim().split(/[\n\r]+/g).map((row) => row.trim().split(/,+/g))
// Отделяем типы столбцов
const columns = table.shift()
// Определяем столбцы
const columnName = columns.findIndex((v) => /Name/i.test(v))
const columnDate = columns.findIndex((v) => /Date/i.test(v))
const columnWork = columns.findIndex((v) => /Work/i.test(v))

// Сортируем дату
const A = table.reduce((a, v) => {
  const date = (new Date(v[columnDate])).valueOf()
  a.includes(date) || a.push(date)
  return a
}, []).sort()

const employees = table.reduce((a, v) => {
  const name = v[columnName].trim().replace(/\s+/g, ' ')
  const date = (new Date(v[columnDate])).valueOf()
  const work = v[columnWork].trim()
  let nw = [...a.entries()].find(([re]) => re.test(name))
  if (!nw) {
    // Здесь можно чем-то заполнить если не работали
    nw = [null, { name, work: (new Array(A.length)).fill('0') }]
    a.set(new RegExp(name, 'i'), nw[1])
  }
  nw[1].work[A.indexOf(date)] = work
  return a
}, new Map())

// Далее делаем что угодно
const result = [...employees.entries()].map(([_, { name, work }]) => [name, ...work])
// Наши заголовки с датой
result.unshift(['Employee', ...A.map((d) => (new Date(d).toDateString()))])

result.map((v)=>console.log(v.join(', ')))  

→ Ссылка