이번에 소개할 라이브러리는 SheetJS (xlsx)이다. xlsx는 npm명이고, 프로젝트 자체는 SheetJS이다. 나의 경우에, 두 개가 다른 라이브러리로 인식 했던 적이 있다.
일단, 프로버전은 사용 해보지 않았다. 웬만한 프로젝트가 기본버전으로 이용가능했고, 간단하게 일반적인 테이블의 내용을 다운로드 할 수 있는 수준이라고 보면 된다. 프로버전의 경우 차트 혹은 이미지 cell의 상세 스타일등을 지원을 해준다.
뭐 물론 csv만으로도 데이터를 다운로드 받는데에는 무리가 없을 수도 있지만, SheetJS를 사용하게 되면, 기본적으로 셀병합들은 지원 하기에, 필요하다면 SheetJS를 고려하는 것이 좋다.
준비
간단하게, 데이터를 다운로드 해보도록 하자.
준비는 아래처럼 하면 되겠다.
<html>
<head>
<script src="https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js"></script>
<script src="./util.js"></script>
<script type="text/javascript">
const header = ['상품', '단가', '갯수', '금액']
const items = [
['책', 100, 3, 300],
['연필', 200, 3, 600],
['공책', 100, 1, 100],
['노트북', 150, 3, 450]
]
</script>
</head>
<body>
</body>
</html>
참고로 util.js에는 https://sejiwork.blogspot.com/2017/08/javascript.html의 일부를 발취하여 만들었다. 파일을 다운로드 할 수 있도록 처리 하였다. 참고로 csv다운로드를 원하면, 해당 링크에 존재하니, 사용하길 바란다.
util.js
const util = {}
function fixBinary (bin) { // binary to arrayBuffer
const length = bin.length
const buf = new ArrayBuffer(length)
const arr = new Uint8Array(buf)
for (let i = 0; i < length; i++) {
arr[i] = bin.charCodeAt(i)
}
return buf
}
window.onload = function () {
/**
* Save As function
*
* @request String data, String file name, String file type (default : text/plain;charset=UTF-8)
*
* support browser : chrome, firefox, opera , ie edge, ie 10+
* not support browser : safari
*
*/
util.saveAs = (function () {
let a
if (!document.getElementById('fileDownload')) {
a = document.createElement('a')
a.id = 'fileDownload'
document.head.appendChild(a) // avoid no body.
a.style = 'display: none'
}
return function (data, fileName, fileType) {
let binary = data
try {
binary = fixBinary(window.atob(data)) // base64 to binary to arrayBuffer
} catch (e) {
console.log(e)
}
// eslint-disable-next-line no-undef
const blob = new Blob([binary], {
type: fileType || 'text/plain;charset=UTF-8'
})
const url = window.URL.createObjectURL(blob)
if (window.navigator.msSaveBlob) { // for ie series
window.navigator.msSaveBlob(blob, fileName)
} else {
a.href = url
a.download = fileName
a.click()
}
window.URL.revokeObjectURL(url)
}
}())
}
엑셀 다운로드
진짜 사용 방법은 간단하다.
<html>
<head>
<script src="https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js"></script>
<script src="./util.js"></script>
<script type="text/javascript">
const header = ['상품', '단가', '갯수', '금액']
const items = [
['책', 100, 3, 300],
['연필', 200, 3, 600],
['공책', 100, 1, 100],
['노트북', 150, 3, 450]
]
function xlsxDownload() {
let ws = XLSX.utils.aoa_to_sheet( [header, ...items] )
/* generate workbook */
let wb = XLSX.utils.book_new()
XLSX.utils.book_append_sheet( wb, ws, 'sheet1' )
/* generate file and download */
const wbOut = XLSX.write( wb, { type: 'array', bookType: 'xlsx' } )
util.saveAs( new Blob( [wbOut], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' } ), 'test.xlsx' )
}
</script>
</head>
<body>
<button onclick="xlsxDownload()">다운로드</button>
</body>
</html>
위처럼 2차원 배열로 한번에 묶어서 진행만 해주면 엑셀을 다운로드 할 수 있게 된다. 조금만 자세히 설명하자면,
let ws = XLSX.utils.aoa_to_sheet( [header, ...items] )
위의 코드는 엑셀의 시트를 만들 데이터를 정의 하는 코드이다.
let wb = XLSX.utils.book_new()
XLSX.utils.book_append_sheet( wb, ws, 'sheet1' )
위의 코드는 엑셀을 생성하고 엑셀 시트를 연결해주는 코드라고 보면 된다.
const wbOut = XLSX.write( wb, { type: 'array', bookType: 'xlsx' } )
이 코드가 정의한 데이터와 시트, 엑셀을 파일을 만드는 작업이라고 볼 수 있겠다.
셀 병합
뭐 위처럼 엑셀 기능을 안 쓰면, csv만으로 되니, 추가적으로 footer에 colspan 정보를 추가해서 엑셀 다운로드를 구현하면 아래와 같다.
const header = ['상품', '단가', '갯수', '금액']
const items = [
['책', 100, 3, 300],
['연필', 200, 3, 600],
['공책', 100, 1, 100],
['노트북', 150, 3, 450]
]
const footer = [
{value: '함계', colspan: 3},
{value: 1450}
]
function xlsxDownload() {
const excelData = [header, ...items]
const merges = []
// footer 추가.
const row = excelData.length
let offset = 0
let footerData = []
for( let idx = 0; idx < footer.length; idx++ ) {
footerData.push( footer[idx].value )
const mergeCol = footer[idx].colspan - 1
if( mergeCol > 0 ) {
const s = { r: row, c: idx + offset }
offset += mergeCol
const e = { r: row, c: idx + offset }
merges.push( { s, e } )
for( let i = 0; i < mergeCol; i++ ) {
footerData.push( '' )
}
}
}
excelData.push( footerData )
let ws = XLSX.utils.aoa_to_sheet( excelData )
if( !ws['!merges'] ) { ws['!merges'] = merges }
/* generate workbook */
let wb = XLSX.utils.book_new()
XLSX.utils.book_append_sheet( wb, ws, 'sheet1' )
/* generate file and download */
const wbOut = XLSX.write( wb, { type: 'array', bookType: 'xlsx' } )
util.saveAs( new Blob( [wbOut], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' } ), 'test.xlsx' )
}
사실 조금 노가다 작업이다. merges라는 배열을 생성하여야 하며, { s: { r, c }, e: { r, c } } 이 구조대로 데이터를 생성해주어야 한다. s는 시작 e는 끝 r은 열, c는 행을 뜻하며, 병합될 영역을 뜻한다.
for( let i = 0; i < mergeCol; i++ ) {
footerData.push( '' )
}
코드 중에 위와 같은 코드를 볼 수 있을 것이다. 이건 다른것이 아니라, excelData를 만들 때, 행병합이 이루어 졌을때, 데이터가 유실 되지 않도록, 패딩을 주는 역할이다. 실제로 데이터가
[ '합계', '', '', 1450 ] 이런 형태로 데이터를 만들기 위해서이다.
if( !ws['!merges'] ) { ws['!merges'] = merges }
이 부분을 볼 수 있을 텐데, 셀병합은 엑셀 시트에 적용 되는 내용이다.
다행히 셀병합의 부분은 무료 버전에서도 사용 할 수 있고, 열병합도 사용이 가능하다
이 라이브러리는 신입 때부터 사용하기도 하였고, 엑셀을 다룰수 있는 유틸 하나 만들어두면, 사골곰탕 우려먹듯이 반복적으로 사용 할 수 있다. 뭐 데이터의 타입 지정등 무료기능이 조금 더 있지만... 그건 필요하면 찾아보길 바란다.
한계로는, 컬럼량에 따라 다르겠지만.... 10만 단위로 넘어가면, 예전에는 브라우저가 뻗어서, csv다운로드로 대체 되게끔 했던적이 있다. 참고 바란다.
댓글
댓글 쓰기