Открываем файлы формата 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, но это тема для отдельной статьи…

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 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

      Only users with full accounts can post comments. Log in, please.