Открываем файлы формата 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;      });   } 

ссылка на оригинал статьи http://habrahabr.ru/post/200770/