Javascript-форум (https://javascript.ru/forum/)
-   Общие вопросы Javascript (https://javascript.ru/forum/misc/)
-   -   Парсинг локально подключаемого xml файла объемом более 17 мб (https://javascript.ru/forum/misc/21384-parsing-lokalno-podklyuchaemogo-xml-fajjla-obemom-bolee-17-mb.html)

Hag1989 07.09.2011 15:21

Парсинг локально подключаемого xml файла объемом более 17 мб
 
Вложений: 1
Здравствуйте!

Пишу функции для справочника в формате chm. Задача - локально подключить и отпарсить xml файл объёмом 17-25 мб (следовательно выполнить это на движке IE6). В результате получил огромную нагрузку на процессор (если решитесь запустить index.html на слабом компе, то запаситесь терпением на время обработки. Chm работает быстрее и выведет данные, примерно, через 5-10 сек).
Помогите найти более логичное и быстрее работающее решение задачи чем то, что я своял:

Теперь всё по порядку.
xml имеет следующую структуру:

Код:

<Root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <Row>
                <Col>text</Col>
                <Col>text</Col>
                        ...
                <Col>img/name1.jpg</Col>
                <Col>img/name2.jpg</Col>
        </Row>
...
        <Row>
                <Col>text</Col>
                <Col>text</Col>
                <Col>-</Col>

                        ...
                <Col>img/name.jpg</Col>
                <Col>img/name.jpg</Col>
        </Row>
</Root>

*! Опытным путем выяснил, что документ с пустыми тегами типа <Col></Col> не проходит обработку, функция стопарится на firstChild.data, пришлось заменять пустые элементы на <Col>-</Col>.

С подключением xml я справился следующим образом (эксперимента ради реализовал поддержку и для Firefox с помощью объекта XMLHttpRequest):
//см. рекомендации на w3schools.com
// 1. делаем запрос к БД, т.е. загружаем xml
    function createDocument() 
    {
        //Temporary DOM object.
        var xmlDoc;

        //Create the DOM object for IE
        if (window.ActiveXObject) 
        {
            var versions = 
            [
                "Msxml2.DOMDocument.6.0",
                "Msxml2.DOMDocument.3.0"
            ];

            for (var i = 0; i < versions.length; i++) 
            {
                try 
                {
                    xmlDoc = new ActiveXObject(versions[i]);
					    xmlDoc.load("data_01.xml");
                    return xmlDoc;
                } 
                catch (error) 
                {
                    //do nothing here
                }
            }

        }
       //Cтроим DOM для Firefox
        else if (window.XMLHttpRequest)
  {// локально работае везде кроме Chrome и Opera 11
  xmlDoc=new XMLHttpRequest();
xmlDoc.open("GET","data_01.xml",false);
xmlDoc.send();
xmlDoc=xmlDoc.responseXML;
  return xmlDoc;
  }
      return null;
    }


Далее, парсим xml в двухмерный массив arr[i][j]

var xmlDoc = createDocument();

  // 2. парсим тело полученного документа в массив извлекая текстовые данные посредством свойств firstChild.data

  var arr = [],
      xmlRows = xmlDoc.getElementsByTagName('Row');

  // log
  log("Загружено элементов Row: "+xmlRows.length+". ");

  for(var i=0; i<xmlRows.length; i++) {
    var xmlCols = xmlRows[i].getElementsByTagName('Col');
    arr[i] = [];

    for(var j=0; j<xmlCols.length; j++) {

arr[i][j] = xmlCols[j].firstChild.data;
		  // log
 // log("Загружено элементов Col: "+arr[i][j].length+". ");
	
  }
}
  // Возвращаем массив Arr
  return arr;


Потом вывожу данные построчно в динамически формируемый список.
На этом, пока, всё.

В дальнейшем предполагается разбить полученный массив arr на множество двухмерных массивов, данные которых будут выводиться на экран в зависимости от действий пользователей.

Вот весь рабочий код:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1251">
<title>Chm справочник на xml базе данных</title>
<body>

     <div id="logDiv" style="position: relative; clear:both; margin: 0px auto; width: 334px; height: 100px; overflow: auto; white-space: pre-wrap; border: 1px solid black; left: 9px; margin-top: 0px;"></div>  
     <center><input type="button" value="Очистить журнал" onclick="clearLog();"/></center>

<script type="text/javascript">

function log(str) {
  document.getElementById("logDiv").innerHTML += str+"\n";
}

function clearLog() {
  document.getElementById("logDiv").innerHTML = "";
}

// Чтобы не засорять пространство глобальных переменных все делаем в безымянной функции
var DBArray = (function() {
  // 1. делаем запрос к БД, т.е. загружаем xml
    function createDocument() 
    {
        //Temporary DOM object.
        var xmlDoc;

        //Create the DOM object for IE
        if (window.ActiveXObject) 
        {
            var versions = 
            [
                "Msxml2.DOMDocument.6.0",
                "Msxml2.DOMDocument.3.0"
            ];

            for (var i = 0; i < versions.length; i++) 
            {
                try 
                {
                    xmlDoc = new ActiveXObject(versions[i]);
					    xmlDoc.load("data_01.xml");
                    return xmlDoc;
                } 
                catch (error) 
                {
                    //do nothing here
                }
            }

        }
       //Cтроим DOM для Firefox
        else if (window.XMLHttpRequest)
  {// локально работае везде кроме Chrome и Opera 11
  xmlDoc=new XMLHttpRequest();
xmlDoc.open("GET","data_01.xml",false);
xmlDoc.send();
xmlDoc=xmlDoc.responseXML;
  return xmlDoc;
  }
      return null;
    }

    var xmlDoc = createDocument();

  // 2. парсим тело полученного документа в массив
  var arr = [],
      xmlRows = xmlDoc.getElementsByTagName('Row');

  // log
  log("Загружено элементов Row: "+xmlRows.length+". ");

  for(var i=0; i<xmlRows.length; i++) {
    var xmlCols = xmlRows[i].getElementsByTagName('Col');
    arr[i] = [];

    for(var j=0; j<xmlCols.length; j++) {

arr[i][j] = xmlCols[j].firstChild.data;
		  // log
 // log("Загружено элементов Col: "+arr[i][j].length+". ");
	
  }
}
  // Возвращаем массив Arr
  return arr;
})();


// выводим списком текстовое содержимое элементов Col которое отпарcили в массив arr функции DBArray
function createList(data) {
  var list = document.createElement("ul");
  for (var i = 0; i < data.length; i++) {
    var newItem = document.createElement("li");
    var newText = document.createTextNode(data[i]);
    newItem.appendChild(newText);
    list.appendChild(newItem);
  }
  return list;
}

window.onload = function() {
  var list = createList(DBArray);
  document.body.appendChild(list);
}

</script>
</body>
</html>


P. S. К сожалению я специализируюсь, в основном, на верстке и пока плохо владею javascript. Прошу простить если моя задача покажется Вам слишком абсурдной, с радостью приму любые советы по решению изложенной проблемы.

ваый 07.09.2011 16:00

Занятно. А не пробовали делать парсинг с помощью Dom парсера?
ActiveXObject("Microsoft.XMLDOM") в ИЕ, DomParser в остальных?
(http://javascript.ru/forum/misc/2090...tml#post121722)
Может, будет быстрее?

Hag1989 07.09.2011 16:32

Только что обновил сообщение прикрепив ссылку на w3schools.com

DOM parser предполагает наличие тегов в теле документа с функцией, что уже не очень удобно, к тому же все данные обрабатываются в одну текстовую строку, у меня никак не удалось запихнуть их в в духмерный массив:

<script>
txt="<note>";
txt=txt+"<from>Jani</from>";
txt=txt+"<from>Anne</from>";
txt=txt+"<from>Jolie</from>";
txt=txt+"<from>Susanne</from>";
txt=txt+"<from>Marie</from>";
txt=txt+"<to>Tove</to>";
txt=txt+"<from>Marie</from>";
txt=txt+"<from>Anne</from>";
txt=txt+"<from>Jolie</from>";
txt=txt+"<from>Анна</from>";
txt=txt+"<from>Вера</from>";
txt=txt+"</note>";
txt="<note>";
txt=txt+"<from>Jolie</from>";
txt=txt+"<from>Susanne</from>";
txt=txt+"<from>Marie</from>";
txt=txt+"<to>Tove</to>";
txt=txt+"<from>Marie</from>";
txt=txt+"<from>Anne</from>";
txt=txt+"<from>Jolie</from>";
txt=txt+"<from>Marie</from>";
txt=txt+"<from>Инна</from>";
txt=txt+"</note>";
txt="</root>";
var xmlDoc;
if (window.DOMParser)
  {
  parser=new DOMParser();
  xmlDoc=parser.parseFromString(txt,"text/xml");
  }
else // Internet Explorer
  {
  xmlDoc=new ActiveXObject("Microsoft.XMLDOM");
  xmlDoc.async="false";
  xmlDoc.loadXML(txt);
  }

  var arr = [],
      xmlRows = xmlDoc.getElementsByTagName('note');
  for(var i=0; i<xmlRows.length; i++) {
    var xmlCols = xmlRows[i].getElementsByTagName('from');
    arr[i] = [];
    for(var j=0; j<xmlCols.length; j++) {

arr[i][j] = xmlCols[j].firstChild.data;
  }
}

document.getElementById("from").innerHTML=arr;

</script>

Snipe 07.09.2011 16:49

Слышал, но не проверял, что обратные цыклы работают быстрее.
Т.е. i меняется не от 0 к length, а от length к 0 и проверяется при этом во втором условии i.
Т.е. как-то так:
for (var i=xmlCols.length; i; i--)


Но даже если делать с прямым циклом, то length лучше посчитать заранее (у вас в каждой итерации цикла data.length пересчитывается вроде как):
for (var i = 0, l = data.length; i < l; i++)

ваый 07.09.2011 17:10

Цитата:

Сообщение от Snipe (Сообщение 125365)
Слышал, но не проверял, что обратные цыклы работают быстрее.
Т.е. i меняется не от 0 к length, а от length к 0 и проверяется при этом во втором условии i.
Т.е. как-то так:
for (var i=xmlCols.length; i; i--)


Но даже если делать с прямым циклом, то length лучше посчитать заранее (у вас в каждой итерации цикла data.length пересчитывается вроде как):
for (var i = 0, l = data.length; i < l; i++)

Сравнение:
http://jsperf.com/caching-array-length/12

Snipe 07.09.2011 17:33

Цитата:

Сообщение от ваый (Сообщение 125370)

Ну ок, обратные цыклы не особо зажгли (хотя у них в примере опечатка, не знаю как в коде), однако кеширование длины в моем случае увеличило производительность почти в два раза.

Hag1989 07.09.2011 17:44

Спасибо за совет! Значене length конечно можно заранее объявить, но вся вся нагрузка на проц. ложится при разборе arr[i][j] = xmlCols[j].firstChild.data, т. е. когда извлекаются текстовые данные.

Magneto 07.09.2011 17:47

Цитата:

Сообщение от ваый

Интересно, похоже что в современных браузерах обратные цыклы работают медленне обычных.

Kolyaj 07.09.2011 18:58

Да какая разница какие циклы быстрее, сам цикл никогда не будет слабым звеном, если только вы не проверяете, какие циклы быстрее.

DjDiablo 07.09.2011 19:08

А обязательно использовать XML ???
Может получится хранить инфу в json ????

Тогда и преобразовывать из массива в массив не надо, храни сразу так как планируешь использовать.

Hag1989 07.09.2011 19:15

Цитата:

Сравнение:
http://jsperf.com/caching-array-length/12
Спасибо ваый и Snipe ! В моем случае цикл c инкрементом (Counting down) оказался самым производительным; но при открытии из CHM он оказался быстрее чем с итерацией только на 25% :( , а на jsperf.com производительность движка IE6 была выше чем Without caching почти в 4 раза (проверял в IEtester), несмотря на то, что во время работы скрипта Counting down браузер 5 раз ругался алертом "A script on this page is causing Internet Explorer to run slowly"

Hag1989 07.09.2011 19:17

Цитата:

А обязательно использовать XML ???
Может получится хранить инфу в json ????
К сожалению работа привязана к XML, кот импортируется из EXCEL.

Snipe 07.09.2011 19:23

Можно в промежутке XSL трансформацию сделать в JSON )

Hag1989 07.09.2011 19:26

Конечно, если бы написать VBA надстройку к EXCEL, которая импортировала бы готовый файл в формате json ... Но программист, написавший форму для заполнения БД под excel, пока, отказался это делать. Если я смогу четко описать структуру БД в json, то может он и возьмется за работу.

Snipe 07.09.2011 19:27

Есть еще вариант, запускать преобразования пачками с таймаутами. Тогда браузер не замрет на 5 секунд, но и время разбора будет большим, ну и данные будут доступны не сразу, а постепенно...

Hag1989 07.09.2011 19:27

Цитата:

Можно в промежутке XSL трансформацию сделать в JSON )
К сожалению, я, пока, не сумею такое изобразить.

popov654 07.09.2011 20:20

Цитата:

Сообщение от ваый (Сообщение 125370)

Ппц))
На что я обратил для себя внимание:
  1. Opera при всей её замечательности плохо справляется со случаем переменной "снаружи" (бордовая полоска) - результат почти вдвое медленнее наилучшего
  2. Забавно, но без кэширования FF5 работал быстрее FF6 :blink:
  3. Chrome ну просто ппц какой быстрый! А как это так?.. :-?

У меня в Opera 11 результаты подтвердили статистику:
for in
252
±1.10%
99% slower

for check index
2,664
±0.25%
89% slower

using cached length outside test
13,012
±0.29%
44% slower

- самые медленные способы организации цикла в Opera 11

Hag1989 07.09.2011 20:53

Сравнение производительности циклов в разных браузерах это конечно хорошо, но как лучше переписать функции в моём примере для производительной работы в IE6. Проверял убирая функцию вывода списка, выяснил, что тормоза, в первую очередь не в data.length, а в arr[i][j] = xmlCols[j].firstChild.data. Исправьте меня если не прав.

Hag1989 07.09.2011 21:17

Цитата:

Сообщение от Snipe (Сообщение 125417)
Есть еще вариант, запускать преобразования пачками с таймаутами. Тогда браузер не замрет на 5 секунд, но и время разбора будет большим, ну и данные будут доступны не сразу, а постепенно...

А если запускать не с таймаутами, а по событиям, например при onclick на сылку1 будет парситься и выводиться на экран только часть массива? буду крайне благодарен если пришлете пример подобной реализации.

Snipe 08.09.2011 00:08

Цитата:

Сообщение от Hag1989 (Сообщение 125436)
А если запускать не с таймаутами, а по событиям, например при onclick на сылку1 будет парситься и выводиться на экран только часть массива? буду крайне благодарен если пришлете пример подобной реализации.

Эм, так а список ссылок как будут строится?

Hag1989 08.09.2011 00:51

Цитата:

Сообщение от Snipe (Сообщение 125450)
Эм, так а список ссылок как будут строится?

-из только что подгруженной части массива. К примеру первая группа ссылок у меня только в первом элементе Row, его и отпарсить по событию onLoad, далее при onClick на его ссылке (тот же текст к которому будут дописываться тег <a ></a> и массив значений атрибутов href=""), зная, что элементы первого Row связаны только со вторым Row, парсить второй, затем также ещё несколько Row.
Хотя, всё это слишком мудрено логичнее, не умножая сущностей, отпарсить все данные сразу поставив пользователю красивую заставку на время парсинга)))

Hag1989 08.09.2011 00:56

Важнее сейчас следующее: подскажите как заменить конструкцию с firstChild.data ведь документ с пустыми тегами типа <Col></Col> не проходит обработку, или как-нибудь создавать пустую строку чтобы firstChild.data находил объект? Замена <Col></Col> на <Col>-</Col> не выход из положения!

ваый 08.09.2011 01:22

А вот еще что можно оптимизировать. Создание новых нодов и потом appendChild - достаточно тяжелые операции. Гораздо легче создать строку, и потом вставить ее через innerHTML.
// Вместо
function createList(data) {
  var list = document.createElement("ul");
  for (var i = 0; i < data.length; i++) {
    var newItem = document.createElement("li");
    var newText = document.createTextNode(data[i]);
    newItem.appendChild(newText);
    list.appendChild(newItem);
  }
  return list;
}

// Делаем
function createList(data) {
  var list = document.createElement("ul"),
    cont = '', 
    i, l;

  for (i = 0, l = data.length; i < l; i++) {
    cont += '<li>' + data[i] + '</li>';
  }
  list.innerHTML = cont;
 
  return list;
}

Magneto 08.09.2011 01:42

Вам уже советовали использовать JSON вместо XML, я присоединяюсь к этому совету. Приблезительно как будет выглядеть файл:
[ [ "text1",
    "text2",
    "img/img1.png",
    "img/img2.png"],
  [ "text3",
    "text4",
    "img/img3.png",
    "img/img4.png"],
  [ "text5",
    "text6",
    "img/img5.png",
    "img/img6.png"] ]

Замечу что этот массив полностью аналогичен тому массиву который Вы пытаетесь получить с XML.

Есть еще идея, если Вы планируете использовать данный файл только в браузерах то Вы можете формировать изначально правильный JavaScript документ и подсоединять его к документу через теги:
<script type="text/javascript" src="data-table.js"></script>

Вот как примерно будет выглядеть документ:
var dataTable = [ [ 'text1',
                    'text2',
                    'img/img1.png',
                    'img/img2.png'],
                  [ 'text3',
                    'text4',
                    'img/img3.png',
                    'img/img4.png'],
                  [ 'text5',
                    'text6',
                    'img/img5.png',
                    'img/img6.png'] ];
tableLoad();


Замечу что этот метод кросбраузерный и он минимально нагружает процессор. Данный скрипт нужно подключать в самом конце документа и функция tableLoad(); должна уже быть определена где-то выше. Срабатывание этой функции означает что база полностью загружена и можно с ней работать.

Hag1989 08.09.2011 17:56

Спасибо, Magneto!
Главный вопрос: будет ли работать Json в CHM файле, т. е. на движке IE6? Я ведь пишу функции для CHM-справочника.


Часовой пояс GMT +3, время: 22:05.