Открываем файлы формата Open XML Excel в JavaScript

    Для загрузки информации о торговых точках в наш логистический SaaS-сервис «Муравьиная логистика» из Excel я решил использовать web-браузер. Обычно проще загрузить файл на сервер и с помощью любой библиотеки залить в БД. Но мне было интересно загрузить его построчно для контроля целостности каждой строки на клиенте, ну и, конечно, опробовать так всеми рекламируемое HTML5 FileAPI и Drag and Drop.

    Книга Exсel – это ZIP архив с каталогами и файлами XML в формате Open XML. Парсить XML отлично умеет jQuery, а вот зиппить нет. Для этого на просторах сети была найдена библиотека zip.js, которая прекрасно справилась с поставленной задачей.

    Итак, попробуем посмотреть, что же находится внутри архива:
    <div class="main">
        <progress id="progress"></progress>
        <div class="filedrag" id="comps">Перетащите файл <span class="red">сюда</span></div>
        <div class="result"></div>
    </div>
    

    var c = document.getElementById("comps"),
        FileDragHover = function (e) {
         e.stopPropagation();
         e.preventDefault();
         if(e.target.id==='comps')
           e.target.className = (e.type == "dragover" ? "filedrag hover" : "filedrag");
         else
           c.className = (e.type == "dragover" ? "filedrag hover" : "filedrag");
        }
        c.addEventListener("drop", function(e){
        e.preventDefault();
    
        c.className = "filedrag";
        var files = e.target.files || e.dataTransfer.files;
        for (var i = 0, f; f = files[i]; i++) {
            if(f.name.toLowerCase().indexOf('xlsx')<=0) {
                alert('Это не файл Excel');
            } else {
                zip.createReader(new zip.BlobReader(f), function(reader) {
                    // Получаем все файлы архива
                    reader.getEntries(function(entries) {
                       // В консоли появятся все внутренности архива Excel
                       console.info(entries)
                       return false;
                   });
                }, function(error) {
                    alert("Ошибка: " + error)
                });
            }
        }
    
        return false;
    }, false);
    
    c.addEventListener("dragover", FileDragHover, false);
    c.addEventListener("dragleave", FileDragHover, false);
    


    Результат можно посмотреть тут. Скачайте пример файла и перетащите его на форму.
    В консоли появится список всех файлов архива книги Excel. Среди свойств объектов, появившихся в консоли, есть filename, по нему-то мы и будем искать необходимые нам файлы XML.

    Нам понадобятся два файла из архива:
    • import.xlsx\xl\worksheets\sheet[N].xlsx
    • import.xlsx\xl\sharedStrings.xml

    где:
    sheet[N].xlsx — собственно лист Excel, N — его внутренний номер в книге.
    sharedStrings.xml — ассоциативный массив строк, словарь листа.

    Отфильтруем только нужные для нас файлы:
    // Получаем все файлы архива
    reader.getEntries(function(entries) {
            var a=[],st;
            for(var i in entries){
                    var e=entries[i];
                    var fn=e.filename.toLowerCase();
                    if(fn.indexOf("sheet")>0){
                            a.push(e);
                    }
                    else if(fn.indexOf("sharedstring")>0){
                            st=e;
                    }
            }
        // Массив всех листов книги Excel
        console.info(a)
        // Ассоциативный массив строк
        console.info(st)
        return false;
    });
    


    Результат можно посмотреть тут, закинув файл и посмотрев в консоль.

    Далее нам необходимо извлечь данные простыми селекторами, для словаря строк это — st t, для записей таблицы с данными на листе это — sheetdata row.

    Добавим функцию для вывода данных из листа Excel:
    printExcelData = function(sheets, strings) {
            var unzipProgress = document.getElementById("progress");
        unzipProgress.style.display='block';
    
         strings.getData(new zip.TextWriter(), function(text) {
             // Получаем все строки листа для ассоциации с их кодами
             var i,st=$($.parseXML(decodeURIComponent(escape(text)))).find('si t');
             for(i=0;i<st.length;++i)
                 st[i]=$(st[i]).text();
    
             // Перебираем листы в поисках нужного
             var parseSheet=function(sheet){
                 var j,i,h,sh,d=[],s;
                 sheet.getData(new zip.TextWriter(), function(text) {
                     // а вот и наши записи
                     sh=$($.parseXML(decodeURIComponent(escape(text)))).find('sheetdata row');
    
                     // делаем из строки объект
                     sh.each(function(e){
                         var c=$(this).find('c'),ci,v,o={};
                         for(i=0;i<c.length;++i){
                             ci=$(c[i]);
                             v=ci.find('v').text();
                             if(ci.attr('t'))
                                 v=st[v];
                             j=ci.attr('r').charCodeAt(0)-65;
                             if(h)
                                 o[h[j]]=v;
                             else
                                 o[j]=v;
                         }
                         if(h){
                             d.push(o)
                         } else
                             h=o;
                     });
    
                     var id_name="";
                     for(i in h)
                         if(h[i]=='Comp_Id'){
                             id_name=h[i];
                             break;
                         }
    
                     // Если поле Comp_Id есть в записи, значит лист наш
                     if(id_name=='Comp_Id') {
                         unzipProgress.style.display='none';
    
                         // Это заголовок таблицы данных
                         s="";
                         for(i=0;i<Object.keys(h).length;i++)
                             s+='<th>'+h[i]+'</th>';
                         $('.result thead tr').append(s)
    
                         // Это данные
                         s="";
                         for(j=0; j<d.length; j++){
                             s+='<tr>';
                             for(i=0; i<Object.keys(h).length; i++){
                                 s+='<td>'+d[j][h[i]].toString()+'</td>';
                             }
                             s+='</tr>';
                         }
                         $('.result tbody').append(s)
                         sheets=[];
                         return;
                     }
    
                     if(sheets.length>0)
                         parseSheet(sheets.pop());
                 }, function(current, total) {
                     unzipProgress.value = current;
                     unzipProgress.max = total;
                 });
             }
             parseSheet(sheets.pop());
         }, function(current, total) {
             unzipProgress.value = current;
             unzipProgress.max = total;
         });
    
     }
    


    Так как Chrome считает преступлением использование HTML File API в кросс-домене (Uncaught SecurityError: An attempt was made to break through the security policy of the user agent.), последний пример выложил на Web-сервер.
    Перетаскиваем файл и получаем стандартную таблицу HTML.

    P.S.
    Да, сейчас, как оказалось, есть Open XML SDK for JavaScript, но это тема для отдельной статьи…
    • +4
    • 12,7k
    • 2
    Поделиться публикацией
    Комментарии 2
      0
      а Для nodejs есть вот такая штука
      на одном из проектов у себя пользовался, правда все же его вариант очень «бета», к тому же очень «грязный»

      в своем форке пробовал реализовать hiperlinks, но не удачно — почему-то при упаковке и открытии xls файл ломается.
      Решил пока отложить, и позже попробовать реальзовать ссылки через формулы.

      Кроме того так еще есть два разных формата, кажется XML Spreadsheet и Office Open XML (могу ошибаться)
      по этой теме есть несколько ссылок еще в копилку:
      officeopenxml
      XML Spreadsheet Reference
      ooxml что-то очень тяжелое для понимания

      в свое время когда этим начал заниматься, не мог найти спецификаций и литературы на эту тему
        0
        XML Spreadsheet — это старый формат я его даже не планирую, а вот Office Open XML обязательно реализую,
        и конечно же Google Docs SpreadSheet, но на этот случай у Google есть API

      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

      Самое читаемое