JavaScript로 Excel 파일 읽기

서버측의 기술없이 클라이언트 기술만으로 로컬에 저장된 엑셀 파일을 다룰 수 있는 라이브러리인 sheetjs에 대한 다양한 기능 중 엑셀 파일을 읽는 기능에 대한 JavaScript 코드를 정리해 봅니다.

이 라이브러리를 별도로 다운로드 받지 않고도 CDN을 통해 이용할 수 있습니다. 아래는 제가 이용한 CDN 접근입니다.


읽고자 하는 엑셀 파일은 다음과 같습니다.

위의 엑셀 파일을 선택하고, 선택된 엑셀 파일을 JSON 형식으로 변환해 콘솔에 출력하는 DOM 요소는 다음과 같습니다.


위의 input DOM 요소를 통해 파일을 선택할 때 발생하는 이벤트 코드는 다음과 같습니다.

function readExcel() {
    let input = event.target;
    let reader = new FileReader();

    reader.onload = function () {
        let data = reader.result;
        let workBook = XLSX.read(data, { type: 'binary' });

        workBook.SheetNames.forEach(function (sheetName) {
            console.log('SheetName: ' + sheetName);

            let rows = XLSX.utils.sheet_to_json(workBook.Sheets[sheetName]);
            console.log(JSON.stringify(rows));
        })
    };

    reader.readAsBinaryString(input.files[0]);
}

실행하고, 엑셀 파일을 선택해 보면 다음과 같은 내용이 콘솔에 표시되는 것을 확인할 수 있습니다.

SheetName: #인구수
[
   {
      "지역명":"무안군 무안읍",
      "남자":8599,
      "여자":7900,
      "가구수":6968,
      "노인수":732,
      "아저씨":1359,
      "어린이":4627
   },
   {
      "지역명":"무안군 일로읍",
      "남자":3659,
      "여자":3243,
      "가구수":3526,
      "노인수":8711,
      "아저씨":8758,
      "어린이":4099
   },

   ....

   {
      "지역명":"무안군 운남면",
      "남자":7357,
      "여자":7667,
      "가구수":3624,
      "노인수":8110,
      "아저씨":8042,
      "어린이":9618
   }
]

sheetjs 라이브러리는 엑셀을 읽는 기능 뿐만 아니라 생성도 가능하며 정교한 처리가 가능합니다.

Javascript 기반의 압축 라이브러리, jszip

웹에서는 로컬에 저장된 여러 개의 파일을 사용하기 위해서는 사용자가 해당 파일들을 모두 마우스를 통한 직접적인 선택 행위가 적용되어야 보안상에 문제가 발생하지 않습니다. 이에 대한 대안은 여러개의 파일을 하나의 파일로 묶아 압축하고 압축된 파일 하나에만 이러한 사용자의 선택 행위가 적용되도록 하는 것입니다. 물론 네트워크를 통한 파일의 접근에는 이러한 문제가 발생하지 않습니다.

여튼, 여러 개의 파일이 하나로 묶인, 하나의 압축 파일로써 zip을 처리할 수 있는 자바스크립트 기반의 라이브러리가 몇가지 되는데.. 그중 제가 사용한 라이브러리를 소개합니다. 바로 jszip인데요. 아래의 해맑고 환하게 웃는 인상좋은 개발자의 github에 방문해 다운로드 받을 수 있습니다.

jszip 라이브러리 이외에도 이를 좀더 쉽게 사용하기 위해서 jszip-utils 라이브러리도 함께 받았는데, 위의 그림에서 이 2개의 라이브러리에 대한 다운로드는 파랑색 외곽선 박스로 언급해 두었습니다.

사용코드는 아래와 같습니다. 만약 자바스크립트의 promise라는 개념을 이해하고 있다면 매우 심플하고 직관적이라는 것을 알 수 있습니다. IO 처리이므로 당연이 비동기방식으로 처리됩니다. 과거 버전에서는 동기 방식도 지원했던거 같은데, 제거된 것 같습니다.





위의 코드에서는 압축 파일 안에 저장된 하나 하나의 파일에 순차적으로 접근해서 압축을 풀고, 풀어진 데이터를 ArrayBuffer 타입으로 전달받도록 21번 째 코드가 file.async(“arraybuffer”)라고 지정되어 있으나, arraybuffer 대신 string, blob로도 지정하여 각각 텍스트, BLOB 타입으로도 전달받을 수 있습니다.

현대의 웹은 무한한 접근성이라는 장점은 있으나 여전이 데스크탑 환경에 비해 처리 속도가 느리지만, 이런 문제점 조차도 웹어셈블리 등의 다양한 기술을 통해 극복해 나가고 있습니다. 향후 몇년안에 모든 프로그램은 서비스화될 것이라고 생각되고, 프로그램의 서비스화에는 JavaScript라는 핵심적인 언어가 중추적인 역할을 할 것입니다.

개발성 향상을 위한 JavaScript의 주석

JavaScript는 type이 불명확하므로 문서화를 통해 그 상세정보를 남겨야 합니다. 그러나 소스코드와 분리된 문서는 사실상 그 활용가치가 매우 떨어집니다. 소스코드와 일원화된 방식으로 문서화가 되어야 하는데, 바로 주석입니다. 아래는 어떤 클래스의 특정함수에 대한 주석을 통한 문서화에 대한 간단한 예시입니다.

/**
 * 메뉴 항목을 추가한다.
 * @param {string} id 항목에 대한 고유 식별자 
 * @param {string} url 항목 아이콘 
 * @param {string} title 항목 타이틀
 * @param {function} callback 실행에 대한 호출 함수
 * @returns {boolean} 성공 여부
 */
addMenu(id, url, title, callback) {
   ...

IDE는 이 주석을 실시간으로 해석해 개발자에게 매우 직관적인 힌트를 제공합니다. 예를들어서 VSCode에서는 아래와 같은 힌트를 제공합니다.

아울러 이러한 주석은 다양한 문서 형식으로써, 예를들어 pdf, docx, html 등과 같은 별도의 문서를 자동으로 생성해 낼 수 있습니다.

mustache.js를 이용한 HTML Tag에 Data를 Binding

이 글은 Include HTML이라는 글을 기초로 작성된 글입니다. Include HTML은 동적으로 외부의 HTML 리소스를 원하는 위치에 Include 해주는 방법을 설명합니다. 이제 이렇게 Include HTML을 통해 HTML 리소스를 원하는 위치에 포함할 때, 원하는 데이터를 Binding 해주는 방법을 설명합니다. 이를 위해 mustache.js 라이브러리를 이용합니다. mustache.js 라이브러리는 많은 프로그래밍 언어를 지원하는데, 여기서는 JS를 이용하였으며 관련 링크는 https://github.com/janl/mustache.js 입니다.

먼저 메인 페이지는 다음과 같습니다.

위의 id가 containerA인 DOM 요소에 A.html 리소스를 포함하고자 합니다. A.html의 내용은 다음과 같습니다.

조회결과

{{name}}
{{age}}
{{vision}}

위의 {{ .. }}로 묶인 3개의 항목이 보입니다. 이 {{ .. }} 위치에 원하는 데이터를 바인딩하는 것인데요. 바인딩하고자 하는 데이터는 아래와 같습니다.

let data = {
    name: "홍길동",
    age: function () {
        let birthYear = 1576;
        return new Date().getFullYear() - birthYear;
    },
    vision: "모든 세상 사람이 배 부르게 사는 그날까지.."
};

name, age, vision이 {{ .. }}에 지정된 단어와 동일합니다. 이렇게 각 데이터가 1:1로 매칭됩니다. 위의 data는 DBMS 조회를 통해 네트워크를 통해 가져올 수도 있겠죠. 이제 아래의 코드를 통해 외부의 HTML 리소스를 불러오면서 위의 데이터를 바인딩해 화면에 표시해 줍니다.

includeHTML(document.querySelector('#containerA'), 'A.html', data);

실행해보면 결과는 다음과 같습니다.

앞서 작성한 includeHTML 함수는 다음과 같습니다.

function includeHTML(divContainer, urlHTML, data) {
    let xhttp = new XMLHttpRequest();
    xhttp.onreadystatechange = function () {
        if (this.readyState == 4) {
            if (this.status == 200) {
                var result = Mustache.render(this.responseText, data);
                divContainer.innerHTML = result;
            } else if (this.status == 404) { divContainer.innerHTML = "Page not found."; }
        }
    }

    xhttp.open("GET", urlHTML, true);
    xhttp.send();
}

위의 코드 중 Mustache 코드가 보이는데, 이 라이브러리를 사용하기 위해서는 아래의 코드가 필요합니다.


Include HTML

GIS는 하나의 페이지에서 다양한 기능을 구현하는 경우가 대부분이다. 그러다보니 새로운 컨텐츠를 표시하기 위해 현재 표시되는 페이지(html)에 새로운 Tag를 생성해야 한다. 만약 ASP나 JSP와 같은 서버측 페이지 기술을 사용하지 않고 오로지 Javascript만을 사용하는 경우라면 Javascript 코드로 Tag 생성을 위해 매우 긴 문자열을 기술했을 것이다. 늘 이부분이 걸려왔는데.. 이번에 진행하는 프로젝트에서는 Javascript의 문자열을 통한 Tag의 생성이 아닌 HTML 파일을 원하는 곳에 Include ! 하는 방식을 적용해 본다.

그중 한가지 방법으로 W3School에서 제공하는 방식인데, 약간의 커스터마이징을 거친 버전을 정리해 본다. 예를들어 하나의 메인 페이지가 다음의 DOM 요소로 구성되어 있다고 하자.

위의 containerA에는 A.html을, containerB에는 B.html을 Include해 보고자 하는 것인데.. 먼저 A.html 파일의 내용은 다음과 같다.


  1. 안녕하세요!
  2. Hello!

B.html 파일의 내용은 다음과 같다.


  • 사과
  • 포도

이제 위의 2개의 html 리소스(파일)을 원하는 DIV 요소에 Include 하는 코드는 아래와 같다.

includeHTML(document.querySelector('#containerA'), 'A.html');
includeHTML(document.querySelector('#containerB'), 'B.html');

includeHTML라는 함수는 아래와 같다.

function includeHTML(divContainer, urlHTML) {
        let xhttp = new XMLHttpRequest();

        xhttp.onreadystatechange = function () {
        if (this.readyState == 4) {
            if (this.status == 200) { divContainer.innerHTML = this.responseText; }
            if (this.status == 404) { divContainer.innerHTML = "Page not found."; }
        }
    }

    xhttp.open("GET", urlHTML, true);
    xhttp.send();
}

결과는 아래와 같다.