...

понедельник, 4 ноября 2013 г.

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

}


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers. Five Filters recommends:



Комментариев нет:

Отправить комментарий