[英]Fast Natural Sort for Large XML File in Browser?
我現在遇到一個問題,這是我們團隊無法控制的服務器上當前限制的結果。
我們有一項工作應該由數據庫完成,但是我們被迫使用XML文件並使用Javascript / jQuery對其進行解析。 我們甚至沒有腳本的寫訪問權限(僅通過FTP帳戶)...我們不想談論它,但這就是我們所得到的。
由於這些限制,問題在於我們需要解析一個大約500kb的大型XML文件,其中包含文件名/編號/ URL的1700余條記錄。
該數字非常復雜,例如“ 31-2b-1029E”,並混入“ T2315342”之類的內容。
因此,我認為我需要使用一種稱為“自然排序”的東西(感謝stackoverflow)。
無論如何,我在這里嘗試使用此腳本:
/*
* Reference: http://www.overset.com/2008/09/01/javascript-natural-sort-algorithm/
* Natural Sort algorithm for Javascript - Version 0.6 - Released under MIT license
* Author: Jim Palmer (based on chunking idea from Dave Koelle)
* Contributors: Mike Grier (mgrier.com), Clint Priest, Kyle Adams, guillermo
*/
function naturalSort (a, b) {
var re = /(^-?[0-9]+(\.?[0-9]*)[df]?e?[0-9]?$|^0x[0-9a-f]+$|[0-9]+)/gi,
sre = /(^[ ]*|[ ]*$)/g,
dre = /(^([\w ]+,?[\w ]+)?[\w ]+,?[\w ]+\d+:\d+(:\d+)?[\w ]?|^\d{1,4}[\/\-]\d{1,4}[\/\-]\d{1,4}|^\w+, \w+ \d+, \d{4})/,
hre = /^0x[0-9a-f]+$/i,
ore = /^0/,
// convert all to strings and trim()
x = a.toString().replace(sre, '') || '',
y = b.toString().replace(sre, '') || '',
// chunk/tokenize
xN = x.replace(re, '\0$1\0').replace(/\0$/,'').replace(/^\0/,'').split('\0'),
yN = y.replace(re, '\0$1\0').replace(/\0$/,'').replace(/^\0/,'').split('\0'),
// numeric, hex or date detection
xD = parseInt(x.match(hre)) || (xN.length != 1 && x.match(dre) && Date.parse(x)),
yD = parseInt(y.match(hre)) || xD && y.match(dre) && Date.parse(y) || null;
// first try and sort Hex codes or Dates
if (yD)
if ( xD < yD ) return -1;
else if ( xD > yD ) return 1;
// natural sorting through split numeric strings and default strings
for(var cLoc=0, numS=Math.max(xN.length, yN.length); cLoc < numS; cLoc++) {
// find floats not starting with '0', string or 0 if not defined (Clint Priest)
oFxNcL = !(xN[cLoc] || '').match(ore) && parseFloat(xN[cLoc]) || xN[cLoc] || 0;
oFyNcL = !(yN[cLoc] || '').match(ore) && parseFloat(yN[cLoc]) || yN[cLoc] || 0;
// handle numeric vs string comparison - number < string - (Kyle Adams)
if (isNaN(oFxNcL) !== isNaN(oFyNcL)) return (isNaN(oFxNcL)) ? 1 : -1;
// rely on string comparison if different types - i.e. '02' < 2 != '02' < '2'
else if (typeof oFxNcL !== typeof oFyNcL) {
oFxNcL += '';
oFyNcL += '';
}
if (oFxNcL < oFyNcL) return -1;
if (oFxNcL > oFyNcL) return 1;
}
return 0;
}
並使用:
// Natural Sort (disabled because it is super freaking slow.... need xsl transform sorting instead)
var sortedSet = $(data).children("documents").children("document").sort(function(a, b) {
return naturalSort($(a).children('index').text(), $(b).children('index').text());
});
這在我們其他較小的XML文件上也能正常工作,但對於500kb的巨型文件Safari(v4)來說,它掛了最多幾分鍾的時間就可以將其整理出來,而Firefox(最新的)則需要大約10秒鍾來處理(仍然不好,但至少是理智的)。
我還發現了另一個較小/較輕的腳本Alphanum :
function alphanum(a, b) {
function chunkify(t) {
var tz = [], x = 0, y = -1, n = 0, i, j;
while (i = (j = t.charAt(x++)).charCodeAt(0)) {
var m = (i == 46 || (i >=48 && i <= 57));
if (m !== n) {
tz[++y] = "";
n = m;
}
tz[y] += j;
}
return tz;
}
var aa = chunkify(a);
var bb = chunkify(b);
for (x = 0; aa[x] && bb[x]; x++) {
if (aa[x] !== bb[x]) {
var c = Number(aa[x]), d = Number(bb[x]);
if (c == aa[x] && d == bb[x]) {
return c - d;
} else return (aa[x] > bb[x]) ? 1 : -1;
}
}
return aa.length - bb.length;
}
這對於Safari而言運行速度更快,但仍會鎖定瀏覽器一分鍾左右。
我做了一些研究,似乎有人建議使用XSL對XML條目進行排序,由於將其內置到瀏覽器中而不是在JavaScript之上運行,因此顯然更快。
顯然有幾種不同的實現方式, Sarissa被多次提及,sourceforge頁面似乎表明最后一次更新發生在2011-06-22。
還有其他選擇,例如xslt.js
我的問題是:
感謝您關注我的問題。
好問題,+ 1。
這是一個XSLT 1.0解決方案 (存在一個XSLT 2.0解決方案,它更簡單,更容易編寫,並且可能更高效,但是5種主流瀏覽器都沒有配備XSLT 2.0處理器):
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common" exclude-result-prefixes="xml">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="vDigits" select="'0123456789'"/>
<xsl:variable name="vPadding" select=
"' '"/>
<xsl:variable name="vMaxNumLength"
select="string-length($vPadding)"/>
<xsl:template match="/">
<xsl:variable name="vrtfPass1">
<t>
<xsl:apply-templates/>
</t>
</xsl:variable>
<xsl:variable name="vPass1" select="ext:node-set($vrtfPass1)"/>
<t>
<xsl:for-each select="$vPass1/*/*">
<xsl:sort select="@sortMe"/>
<xsl:copy>
<xsl:value-of select="."/>
</xsl:copy>
</xsl:for-each>
</t>
</xsl:template>
<xsl:template match="str">
<str>
<xsl:apply-templates select="text()" mode="normalize"/>
<xsl:copy-of select="text()"/>
</str>
</xsl:template>
<xsl:template match="text()" mode="normalize" name="normalize">
<xsl:param name="pText" select="."/>
<xsl:param name="pAccum" select="''"/>
<xsl:choose>
<xsl:when test="not(string-length($pText) >0)">
<xsl:attribute name="sortMe">
<xsl:value-of select="$pAccum"/>
</xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="vChar1" select="substring($pText,1,1)"/>
<xsl:choose>
<xsl:when test="not(contains($vDigits,$vChar1))">
<xsl:variable name="vDig1" select=
"substring(translate($pText,
translate($pText, $vDigits, ''),
''
),
1,1)"/>
<xsl:variable name="vDig">
<xsl:choose>
<xsl:when test="string-length($vDig1)">
<xsl:value-of select="$vDig1"/>
</xsl:when>
<xsl:otherwise>0</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="vNewText" select=
"substring-before(concat($pText,$vDig), $vDig)"/>
<xsl:call-template name="normalize">
<xsl:with-param name="pText" select=
"substring($pText, string-length($vNewText)+1)"/>
<xsl:with-param name="pAccum" select=
"concat($pAccum, $vNewText)"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="vNonDig1" select=
"substring(translate($pText, $vDigits, ''),1,1)"/>
<xsl:variable name="vNonDig">
<xsl:choose>
<xsl:when test="string-length($vNonDig1)">
<xsl:value-of select="$vNonDig1"/>
</xsl:when>
<xsl:otherwise>Z</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="vNum" select=
"substring-before(concat($pText,'Z'),$vNonDig)"/>
<xsl:variable name="vNumLength" select=
"string-length($vNum)"/>
<xsl:variable name="vNewText" select=
"concat(substring($vPadding,
1,
$vMaxNumLength -$vNumLength),
$vNum
)"/>
<xsl:call-template name="normalize">
<xsl:with-param name="pText" select=
"substring($pText, $vNumLength +1)"/>
<xsl:with-param name="pAccum" select=
"concat($pAccum, $vNewText)"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
在以下XML文檔上應用此轉換時 :
<t>
<str>Allegia 6R Clasteron</str>
<str>200X Radonius</str>
<str>Xiph Xlater 10000</str>
<str>1000X Radonius Maximus</str>
<str>Callisto Morphamax 6000 SE</str>
<str>10X Radonius</str>
<str>20X Radonius</str>
<str>30X Radonius</str>
<str>20X Radonius Prime</str>
<str>40X Radonius</str>
<str>Allegia 50 Clasteron</str>
<str>Allegia 500 Clasteron</str>
<str>Allegia 50B Clasteron</str>
<str>Allegia 51 Clasteron</str>
<str>Alpha 100</str>
<str>Alpha 2</str>
<str>Alpha 200</str>
<str>Alpha 2A</str>
<str>Alpha 2A-8000</str>
<str>Alpha 2A-900</str>
<str>Callisto Morphamax</str>
<str>Callisto Morphamax 500</str>
<str>Callisto Morphamax 5000</str>
<str>Callisto Morphamax 600</str>
<str>Callisto Morphamax 6000 SE2</str>
<str>Callisto Morphamax 700</str>
<str>Callisto Morphamax 7000</str>
<str>Xiph Xlater 2000</str>
<str>Xiph Xlater 300</str>
<str>Xiph Xlater 40</str>
<str>Xiph Xlater 5</str>
<str>Xiph Xlater 50</str>
<str>Xiph Xlater 500</str>
<str>Xiph Xlater 5000</str>
<str>Xiph Xlater 58</str>
</t>
產生想要的,正確的“自然排序”結果 :
<t>
<str>10X Radonius</str>
<str>20X Radonius</str>
<str>20X Radonius Prime</str>
<str>30X Radonius</str>
<str>40X Radonius</str>
<str>200X Radonius</str>
<str>1000X Radonius Maximus</str>
<str>Allegia 6R Clasteron</str>
<str>Allegia 50 Clasteron</str>
<str>Allegia 50B Clasteron</str>
<str>Allegia 51 Clasteron</str>
<str>Allegia 500 Clasteron</str>
<str>Alpha 2</str>
<str>Alpha 2A</str>
<str>Alpha 2A-900</str>
<str>Alpha 2A-8000</str>
<str>Alpha 100</str>
<str>Alpha 200</str>
<str>Callisto Morphamax</str>
<str>Callisto Morphamax 500</str>
<str>Callisto Morphamax 600</str>
<str>Callisto Morphamax 700</str>
<str>Callisto Morphamax 5000</str>
<str>Callisto Morphamax 6000 SE</str>
<str>Callisto Morphamax 6000 SE2</str>
<str>Callisto Morphamax 7000</str>
<str>Xiph Xlater 5</str>
<str>Xiph Xlater 40</str>
<str>Xiph Xlater 50</str>
<str>Xiph Xlater 58</str>
<str>Xiph Xlater 300</str>
<str>Xiph Xlater 500</str>
<str>Xiph Xlater 2000</str>
<str>Xiph Xlater 5000</str>
<str>Xiph Xlater 10000</str>
</t>
重要假設 :此解決方案假定數字不超過40位。 盡管在大多數實際情況中都是如此,但如果出現這種限制不足的情況,則很容易修改此解決方案以將限制值接受為外部/全局參數。
最后,性能 :
處理與上面類似的XML文檔,但是具有1700個str
元素需要0.659秒。 在我8歲的奔騰單核3GHz CPU和2GB RAM計算機上。
說明 :
這是兩遍解決方案。
在第一遍中,所有節點均按“原樣”復制,但sortMe
屬性已添加到每個str
元素。 此屬性包含str
的唯一文本節點子級的字符串值-其中任何數字都用空格補齊,固定總長度為40。
在Pass 2中,我們使用單個sort鍵( sortMe
屬性)按字母順序對所有str
元素進行排序。
現在,回答所有4個原始問題 :
我的問題是:
XSL是針對此特定問題的最佳排序選項嗎?
如果是這樣,如何使用XSL進行自然排序? (訪問資源?)
如果對兩個問題都同意,我應該使用哪個庫以獲得最佳兼容性和最快速度?
如果XSL不是最佳選擇,那么哪個是?
答案 :
最佳排序算法的任何實現(與語言無關)都應足夠。 在這方面,XSLT是一個不錯的選擇。
上面的代碼提供了“自然”排序的完整且准確的XSLT實現。
無需庫-只需按原樣使用上面的代碼即可。 如果需要幫助如何從PL調用轉換,請查閱相應的文檔。
包括任何PL,XSLT以及最佳排序算法的實現都是合適的選擇。
輔助問題的幾個答案:
(a)Sarissa不是XSLT處理器,它是Javascript包裝器層,可為作為瀏覽器一部分提供的XSLT處理器提供通用的Javascript API。
(b)xslt.js是一個無效項目,試圖在Javascript中實現XSLT處理器。 算了,那是歷史。
朝此方向進行的最新工作是Saxon-CE,它目前處於alpha發行版(這是用Java編寫的,並使用GWT交叉編譯為Javascript)。 完成后,這將在瀏覽器中提供XSLT 2.0。 服務器端Saxon的排序規則可為您提供“自然排序”( <xsl:sort collation='http://saxon.sf.net/collation?alphanumeric=yes'/>
),但是在當前版本中不可用撒克遜公元-CE。
(PS以前我沒有遇到過“自然排序”這個名字。謝謝。)
排序函數的調用次數比數組中要排序的元素的次數更多,實際上要調用的次數更多。 對於您的1700個元素進行排序,根據瀏覽器的不同,比較函子可能會被調用10,000至750,000次。由於排序比較功能很慢,因此每個元素執行一次繁重的操作並存儲結果將使您受益匪淺,然后對存儲的結果進行排序。
我敢打賭,主要的問題是您在排序函數中使用了jquery。 那一定很貴。 實際的自然排序比較可能相對較快。 我不知道您的xml結構,但是如果您可以在排序函數中拋棄jquery,請嘗試將元素引用復制到新數組中,這是線性時間。 然后,您對數組進行排序。 然后,循環遍歷現在已排序的數組,並使用元素引用在xml文檔中設置順序。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.