본문 바로가기
개발/React

React Handsontable로 csv 편집기 만들기 (6)

by 피로물든딸기 2021. 6. 2.
반응형

프로젝트 전체 링크

 

이전 - (5) 선택한 경로의 파일 읽기

현재 - (6) csv 파일 파싱하기

다음 - (7) App.js에서 파싱된 csv 파일 받기

 

깃허브에서 코드 확인하기


아래와 같은 csv 파일이 있다.

 

이 파일을 메모장으로 열면 아래처럼 보인다.

d,e,f,g,h가 5칸이므로 ","가 4개있고, 다른 line에서도 ,가 4개 생기게 된다.

 

csv 파일은 보통 ","로 구분하기 때문에, parsing을 할 경우 ","로 split을 하면 된다.

 

하지만 우리가 parsing할 csv파일 중 "치킨, 맥주"에는 이미 ","가 있다.

 

이런 경우 메모장으로 열어보면 ","가 csv에 포함된 경우 ""으로 구분하고 있는 것을 알 수 있다.

 

실제 console.log를 확인해도 치킨, 맥주를 ""로 구분하고 있다.

 

따라서 이 프로젝트에서 parsing하는 csv에는 ,는 포함하고 있지만, "는 없는 경우만 parsing한다고 가정한다.


library.js에 parsingCsv 함수를 만든다.

먼저 file을 넘겨 받아서, 각 line별로 split한다.

line은 \r\n 또는 \n으로 구분되기 때문에, 두 경우 모두 split 해야 한다.

이 경우 split에 "/\r\n|\n/"로 구분자를 넣어주면 두 경우 모두 split 할 수 있다.

sptLine(배열)은 for of를 이용하여 순회한다.

// library.js

const DELIMITER = ',';
const APOSTROPHE = '"';

...

export const parsingCsv = (file) => {
  let sptLine = file.split(/\r\n|\n/);
  console.log(sptLine);

  for(let line of sptLine)
  {
    if(line === "") continue;

    let spt = mySplit(line, DELIMITER);
    console.log(spt);
  }

  return;
}

 

if(line === "") continue는 csv 파일 가장 아래에 enter가 하나 있는 경우는 parsing할 필요가 없기 때문에 필요하다.

위의 csv 파일처럼 커서가 있는 경우, (csv 파일을 수정하다가 마지막 line이 불필요하게 생긴 경우)

마지막 line은 불필요하기 때문에 continue 처리하였다.


parsingCsv에 mySplit 함수는 특정 구분자로, 여기에서는 ","로 구분한다.

특정 구분자로 split하는 함수를 만드는 방법은 링크를 참고하자. c++ ver을 js ver로 만들었다.

export const mySplit = (line, delimiter) => {
  let spt = [];
  let tmp = "";
  
  for(let i = 0; i < line.length; i++)
  {
    if(line[i] === delimiter) {
      spt.push(tmp);
      tmp = "";

      continue;
    }

    tmp += line[i];
  }

  spt.push(tmp);

  return spt;
}

 

handleOnDrop과 handleUpload에 로그를 주석처리하고 parsingCsvresult를 넘겨줘서 로그를 확인해보자.

fileReader.onload = function () {
  //console.log(fileReader.result); 
  parsingCsv(fileReader.result);
};

 

가장 첫 줄의 로그는 sptLine으로 line별로 구분된 배열이 출력되었고,

다음 세 줄은 각 line별로 "," 구분된 것을 알 수 있다.

 

하지만 "치킨, 맥주" 처럼 ","가 csv파일에 포함된 경우에는 mySplit이 제대로 동작하지 않는다.

split된 length = 4가 나올 것 같지만, "치킨, 맥주"에 의해 5개로 구분된 것을 알 수 있다.

 

따라서 mySplit에 ignore을 포함하여 아래와 같이 수정한다.

 

""(ignore)사이는 delimiter를 무시하도록 flag를 추가한다.

ignore을 만나면 flagfalse로 하고, 다시 ignore을 만나면 flag true로 변경한다.

메모장에서는 ""가 보이지만, excel에서는 보이지 않기 때문에 ignore할 문자는 tmp에 추가하지 않고 continue 한다.

(나중에 csv를 다시 download 할 때, ","가 있다면 양 옆에 "를 추가한다.)

export const mySplit = (line, delimiter, ignore) => {
  let spt = [];
  let tmp = "";
  let flag = false;

  for(let i = 0; i < line.length; i++)
  {
    if(ignore === line[i] && flag === true) 
    {
      flag = false;
      continue;
    }
    else if(ignore === line[i])
    {
      flag = true;
      continue;
    } 
    
    if(line[i] === delimiter && flag === false) {
      spt.push(tmp);
      tmp = "";

      continue;
    }

    tmp += line[i];
  }

  spt.push(tmp);

  return spt;
}

 

그리고 parsingCsv에서 APOSTROPHE를 ignore에 넘긴다.

//let spt = mySplit(line, DELIMITER);
let spt = mySplit(line, DELIMITER, APOSTROPHE);

 

split을 추가한 후에 다시 log를 확인해보면, 제대로 parsing 된 것을 알 수 있다.

 

width가 올바르게 4가 된 것을 확인할 수 있고, "치킨, 맥주"로 parsing 되었다.

 

전체 코드는 아래와 같다.

(handleOnDrop과 handleUpload는 로그를 지우고 parsingCsv로 바꾸었다.)

// library.js

const DELIMITER = ',';
const APOSTROPHE = '"';

...

export const handleOnDrop = (e, setState, csvObject) => {
  e.preventDefault();

  let file = e.dataTransfer.files[0];
  let fileReader = new FileReader();

  fileReader.readAsText(file, "utf-8"); // or euc-kr

  fileReader.onload = function () {
    //console.log(fileReader.result); 
    parsingCsv(fileReader.result, csvObject);
    return;
  };

  setState(false);
  return false; 
};

export const handleUpload = (e, csvObject) => {
  let file = e.target.files[0];
  let fileReader = new FileReader();
  
  if(file === undefined) return; /* 방어 코드 추가 */

  fileReader.readAsText(file, "utf-8"); // or euc-kr

  fileReader.onload = function () {
    //console.log(fileReader.result); 
    parsingCsv(fileReader.result, csvObject);
  };
}

export const mySplit = (line, delimiter, ignore) => {
  let spt = [];
  let tmp = "";
  let flag = false;

  for(let i = 0; i < line.length; i++)
  {
    if(ignore === line[i] && flag === true) 
    {
      flag = false;
      continue;
    }
    else if(ignore === line[i])
    {
      flag = true;
      continue;
    } 
    
    if(line[i] === delimiter && flag === false) {
      spt.push(tmp);
      tmp = "";

      continue;
    }

    tmp += line[i];
  }

  spt.push(tmp);

  return spt;
}

export const parsingCsv = (file) => {
  let sptLine = file.split(/\r\n|\n/);
  console.log(sptLine);

  for(let line of sptLine)
  {
    if(line === "") continue;

    let spt = mySplit(line, DELIMITER, APOSTROPHE);
    console.log(spt);
  }

  return;
}

이전 - (5) 선택한 경로의 파일 읽기

다음 - (7) App.js에서 파싱된 csv 파일 받기

반응형

댓글