들어가며 웹 개발을 하다보면 <table>
의 내용물을 모두 csv 파일로 받게 해달라는 요구사항이 종종 생깁니다.
가장 일반적인 방법은 csv 형태로 파일을 받을 수 있는 API를 서버가 제공해주는 방법입니다. (백엔드 개발자에게 일을 시킵시다.)
하지만 csv를 던져주는 API 서버가 없다면 프론트에서 보여지는 <table>
만이라도 csv로 만들어줘야 합니다.
이번 글은 이럴때 쓰는 방법입니다.
소스코드 우선 이렇게 생긴 HTML이 있다고 생각해 봅시다.
본문이라고는 #
, title
, content
가 들어있는 자그마한 <table>
하나가 있습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <!DOCTYPE html > <html > <head lang ="ko" > <meta charset ="utf-8" > <title > 빈 HTML</title > </head > <body > <table id ="mytable" > <thead > <tr > <th > #</th > <th > title</th > <th > content</th > </tr > </thead > <tbody > <tr > <td > 1</td > <td > Lorem Ipsum</td > <td > 로렘 입섬은 빈칸을 채우기 위한 문구입니다.</td > </tr > <tr > <td > 2</td > <td > Hello World</td > <td > 헬로 월드는 언어를 배우기 시작할때 화면에 표준 출력을 할때 주로 사용하는 문구입니다.</td > </tr > </tbody > </table > <button id ="csvDownloadButton" > CSV 다운로드 받기</button > </body > </html >
테이블 엘리먼트의 id는 mytable
이고, CSV 다운로드 버튼의 id는 csvDownloadButton
입니다.
이제 JS를 조금 추가해봅시다. ES6/ES5에 따라 선택해 사용해주세요.
아래 코드를 <script></script>
태그 사이에 넣어 </body>
바로 앞에 넣어주세요.
ES6을 사용할 경우 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 class ToCSV { constructor () { document .querySelector('#csvDownloadButton' ).addEventListener('click' , e => { e.preventDefault() this .getCSV('mycsv.csv' ) }) } downloadCSV(csv, filename) { let csvFile; let downloadLink; csvFile = new Blob([csv], {type : "text/csv" }) downloadLink = document .createElement("a" ) downloadLink.download = filename; downloadLink.href = window .URL.createObjectURL(csvFile) downloadLink.style.display = "none" document .body.appendChild(downloadLink) downloadLink.click() } getCSV(filename) { const csv = [] const rows = document .querySelectorAll("#mytable table tr" ) for (let i = 0 ; i < rows.length; i++) { const row = [], cols = rows[i].querySelectorAll("td, th" ) for (let j = 0 ; j < cols.length; j++) row.push(cols[j].innerText) csv.push(row.join("," )) } this .downloadCSV(csv.join("\n" ), filename) } } document .addEventListener('DOMContentLoaded' , e => { new ToCSV() })
ES5를 사용하실 경우 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 function downloadCSV (csv, filename ) { var csvFile; var downloadLink; csvFile = new Blob([csv], {type : "text/csv" }) downloadLink = document .createElement("a" ) downloadLink.download = filename; downloadLink.href = window .URL.createObjectURL(csvFile) downloadLink.style.display = "none" document .body.appendChild(downloadLink) downloadLink.click() } function getCSV (filename ) { var csv = [] var rows = document .querySelectorAll("#mytable table tr" ) for (var i = 0 ; i < rows.length; i++) { var row = [], cols = rows[i].querySelectorAll("td, th" ) for (var j = 0 ; j < cols.length; j++) row.push(cols[j].innerText) csv.push(row.join("," )) } downloadCSV(csv.join("\n" ), filename) } document .addEventListener('DOMContentLoaded' , e => { document .querySelector('#csvDownloadButton' ).addEventListener('click' , e => { e.preventDefault() getCSV('mycsv.csv' ) }) })
전체 예시
예제 html_to_csv.html 에서 직접 동작하는 것을 확인해 보세요!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 <!DOCTYPE html > <html > <head lang ="ko" > <meta charset ="utf-8" > <title > 빈 HTML</title > </head > <body > <table id ="mytable" > <thead > <tr > <th > #</th > <th > title</th > <th > content</th > </tr > </thead > <tbody > <tr > <td > 1</td > <td > Lorem Ipsum</td > <td > 로렘 입섬은 빈칸을 채우기 위한 문구입니다.</td > </tr > <tr > <td > 2</td > <td > Hello World</td > <td > 헬로 월드는 언어를 배우기 시작할때 화면에 표준 출력을 할때 주로 사용하는 문구입니다.</td > </tr > </tbody > </table > <button id ="csvDownloadButton" > CSV 다운로드 받기</button > </body > <script type ="text/javascript" > class ToCSV { constructor () { document .querySelector('#csvDownloadButton' ).addEventListener('click' , e => { e.preventDefault() this .getCSV('mycsv.csv' ) }) } downloadCSV(csv, filename) { let csvFile; let downloadLink; csvFile = new Blob([csv], {type: "text/csv" }) downloadLink = document .createElement("a" ) downloadLink.download = filename; downloadLink.href = window .URL.createObjectURL(csvFile) downloadLink.style.display = "none" document .body.appendChild(downloadLink) downloadLink.click() } getCSV(filename) { const csv = [] const rows = document .querySelectorAll("#mytable tr" ) for (let i = 0 ; i < rows.length; i++) { const row = [], cols = rows[i].querySelectorAll("td, th" ) for (let j = 0 ; j < cols.length; j++) row.push(cols[j].innerText) csv.push(row.join("," )) } this .downloadCSV(csv.join("\n" ), filename) } } document .addEventListener('DOMContentLoaded' , e => { new ToCSV() }) </script > </html >
하지만 이렇게하면 한글이 깨지는 문제가 있습니다.
한글 깨지는 문제 해결하기 앞서 한글이 깨지는 이유는 기본적으로 엑셀이 인코딩을 UTF-8로 인식하지 않기 때문에 문제가 발생합니다.
이때 아래 코드를 csv blob을 만들기 전 추가해주면 됩니다.
1 2 3 const BOM = "\uFEFF" ;csv = BOM + csv
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 downloadCSV(csv, filename) { let csvFile; let downloadLink; const BOM = "\uFEFF" ; csv = BOM + csv csvFile = new Blob([csv], {type : "text/csv" }) downloadLink = document .createElement("a" ) downloadLink.download = filename; downloadLink.href = window .URL.createObjectURL(csvFile) downloadLink.style.display = "none" document .body.appendChild(downloadLink) downloadLink.click() }