浏览器中的大型XML文件快速自然排序?

时间:2011-12-01 22:52:02

标签: javascript jquery xml xslt

我现在遇到问题,这是我们团队无法控制的服务器当前限制的结果。

我们有一份应该由数据库完成的工作但是我们被迫使用XML文件并使用Javascript / jQuery解析它。我们甚至没有脚本的写入权限(只能通过我们的FTP帐户)...我们不喜欢谈论它,但这就是我们得到的。

由于这些限制,问题是我们需要解析一个大约500kb的大型XML文件,其中包含1700个文档名称/数字/ URL的记录。

这个数字非常复杂,例如“31-2b-1029E”,与“T2315342”之类的东西混合在一起。

所以,我认为我需要使用一种名为“Natural Sort”的东西(谢谢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-ish文件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. XSL是解决此特定问题的最佳选择吗?
  2. 如果是这样,我如何使用XSL进行自然排序? (url to resources?)
  3. 如果对两个问题都同意,我应该使用哪个库以获得最佳兼容性和速度?
  4. 如果XSL不是最佳选择,那么哪一个是?
  5. 感谢您查看我的问题。

3 个答案:

答案 0 :(得分:5)

好问题,+ 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岁的Pentium单核3GHz CPU,2GB RAM计算机上。

<强>解释

  1. 这是一个两遍解决方案。

  2. 在第一次传递中,所有节点都“按原样”复制,但每个sortMe元素都添加了str属性。此属性包含唯一的文本节点子节点str的字符串值 - 其中任何数字都用空格填充,总固定长度为40。

  3. 在第2阶段,我们使用单个排序键 - str属性按字母顺序对所有sortMe元素进行排序。

  4. 现在,回答所有4个原始问题

      

    我的问题是:

         

    XSL是解决此特定问题的最佳选择吗?   如果是这样的话   我可以使用XSL进行自然排序吗? (url to resources?)
      如果两者都是   问题,我应该使用哪个库以获得最佳兼容性   速度?
      如果XSL不是最佳选择,那么哪一个是?

    <强>答案

    1. 任何最佳排序算法的实现(无论语言如何)都应该足够。在这方面,XSLT是一个不错的选择。

    2. 上面的代码提供了一个完整而精确的“自然”排序的XSLT实现。

    3. 不需要库 - 只需按原样使用上述代码即可。如果您需要有关如何从PL调用转换的帮助,请参阅相应的文档。

    4. 任何PL,包含XSLT,以及最佳排序算法的实现都是合适的选择。

答案 1 :(得分:1)

辅助问题的几个答案:

(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'/>)但是在当前版本的Saxon-CE中不可用。

(P.S。之前我没有遇到过“自然分类”这个名字。谢谢。)

答案 2 :(得分:0)

sort函数被调用的次数多于被排序的数组中的元素,实际上要多出许多倍。对于要排序的1700个元素,比较函数可能会在10,000到 750,000 次之间调用,具体取决于浏览器...由于您的排序比较功能很慢,因此您可以通过繁重的工作获益良多每个元素一次并存储结果,然后对存储的结果进行排序。

我打赌主要的问题是你在sort函数中使用jquery。那要贵。实际的自然排序比较可能相对较快。我不知道你的xml结构,但如果你可以抛弃sort函数中的jquery,尝试将元素引用复制到一个新的数组,这是线性时间。然后你对数组进行排序。然后,遍历现在排序的数组并使用元素引用在xml doc中设置顺序。