基于DateTime属性对XML节点进行排序 C#,XPath

8
我有一个XML结构,看起来像这样。
<sales>
  <item name="Games" sku="MIC28306200" iCat="28" 
     sTime="11/26/2008 8:41:12 AM" 
     price="1.00" desc="Item Name" />
  <item name="Games" sku="MIC28307100" iCat="28" 
     sTime="11/26/2008 8:42:12 AM" 
     price="1.00" desc="Item Name" />
...
</sales>

我正在寻找一种根据 sTime 属性进行排序的方法,该属性是 DateTime.ToString() 的值。问题在于我需要保留节点,并且出于某种原因,我找不到实现这一点的方法。我相当确定 LINQ 和 XPath 有一种方法可以做到这一点,但我卡住了,因为似乎无法根据 DateTime.ToString() 的值进行排序。

XPathDocument saleResults = new XPathDocument(@"temp/salesData.xml");
XPathNavigator navigator = saleResults.CreateNavigator();

XPathExpression selectExpression = navigator.Compile("sales/item/@sTime");
selectExpression.AddSort("@sTime", 
    XmlSortOrder.Descending, 
    XmlCaseOrder.None, 
    "", 
    XmlDataType.Number);

XPathNodeIterator nodeIterator = navigator.Select(selectExpression);

while( nodeIterator.MoveNext() )
    {
         string checkMe = nodeIterator.Current.Value;
    } 

我还需要维护一个指向节点的指针,以检索其他属性的值。

也许这不像我想象的那么简单。

谢谢。

解决方案:这是我最终使用的方法。结合所选择的答案和IComparable类,这是如何根据sTime属性对XML节点进行排序,并将所有属性放入适当的数组中以供后续使用。

    XPathDocument saleResults = new XPathDocument(@"temp/salesData.xml");
    XPathNavigator navigator = saleResults.CreateNavigator();
    XPathExpression selectExpression = navigator.Compile("sales/item");
    XPathExpression sortExpr = navigator.Compile("@sTime");
    selectExpression.AddSort(sortExpr, new DateTimeComparer());
    XPathNodeIterator nodeIterator = navigator.Select(selectExpression);
    int i = 0;
    while (nodeIterator.MoveNext())
       {
          if (nodeIterator.Current.MoveToFirstAttribute())
          {
              _iNameList.SetValue(nodeIterator.Current.Value, i);
          }
          if (nodeIterator.Current.MoveToNextAttribute())
          {
              _iSkuList.SetValue(nodeIterator.Current.Value, i);
          }
          ...
          nodeIterator.Current.MoveToParent();
          i++;

      }

你能添加另一个属性吗?这个属性是时间的可排序版本,可以使用刻度或YYYYMMDD的形式。 - Ryan Cook
我正在调用一个Web服务,并且已经联系管理员将排序添加到存储过程中,但如果我无法联系到他们,我必须尝试使用现有的XML格式找到解决方案。不过这是个好建议。 - discorax
所有这些答案以不同的方式工作。谢谢各位。所有这些都很有用。 - discorax
除了简单的无知之外,为什么会有人编写不遵循模式的XML格式的Web服务呢?这就像编写API,然后用便笺来记录它的文档一样。 - Robert Rossney
6个回答

5

给您:

XmlDocument myDoc = new XmlDocument();

myDoc.LoadXml(@"
<sales>
<item name=""Games""
    sku=""MIC28306200""
    iCat=""28""
    sTime=""11/26/2008 8:41:12 AM""
    price=""1.00""
    desc=""Item Name"" />
<item name=""Games""
    sku=""MIC28307100""
    iCat=""28""
    sTime=""11/26/2008 8:42:12 AM""
    price=""1.00""
    desc=""Item Name"" />
</sales>
");

var sortedItems = myDoc.GetElementsByTagName("item").OfType<XmlElement>()
    .OrderBy(item => DateTime.ParseExact(item.GetAttribute("sTime"), "MM/dd/yyyy h:mm:ss tt", null));

foreach (var item in sortedItems)
{
    Console.WriteLine(item.OuterXml);
}

这是一个完美运作的控制台应用程序。

嗨@Timothy。您的代码似乎可以满足我的需求。您能帮我处理一下我之前在这里发布的帖子吗:http://stackoverflow.com/questions/12943635/sort-xml-nodes-alphabetically-on-attribute-name 此外,我从未使用过Linq,所以我不确定您的代码是否适用于我的VB.NET环境。 谢谢! - Adam

5

在XPathExpression.Addsort中有一个重载,它需要一个IComparer接口。如果你自己实现了比较器并作为IComparer使用,你可以使用这个机制。

 class Program
        {
            static void Main(string[] args)
            {
                XPathDocument saleResults = new XPathDocument( @"salesData.xml" );
                XPathNavigator navigator = saleResults.CreateNavigator( );
                XPathExpression selectExpression = navigator.Compile( "sales/item" );
                XPathExpression sortExpr = navigator.Compile("@sTime");
                selectExpression.AddSort(sortExpr, new DateTimeComparer());
                XPathNodeIterator nodeIterator = navigator.Select( selectExpression );            
                while ( nodeIterator.MoveNext( ) )
                {
                    string checkMe = nodeIterator.Current.Value;
                }
            }
            public class DateTimeComparer : IComparer
            {
                public int Compare(object x, object y)
                {
                    DateTime dt1 = DateTime.Parse( x.ToString( ) );
                    DateTime dt2 = DateTime.Parse( y.ToString( ) );
                    return dt1.CompareTo( dt2 );
                }
            }
        }

我尝试了一下,但是出现错误:无法将类型System.DateTime转换为System.Collections.IComparer。 - discorax
另一个棘手的部分是,我需要对节点进行排序,而不仅仅是属性。 - discorax
请看代码。您需要选择要排序的节点(项目节点,而不是sTime属性),并使用表示排序键表达式(sTime属性)的表达式以及自定义比较器。 - jlew

2

这是一个XSLT解决方案:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="sales"> <sales> <xsl:for-each select="item"> <xsl:sort select="substring(@sTime,7,4)" data-type="number"/> <xsl:sort select="substring(@sTime,1,2)" data-type="number"/> <xsl:sort select="substring(@sTime,4,2)" data-type="number"/> <xsl:sort select="substring-after(substring-after(@sTime,' '),' ')" /> <xsl:sort data-type="number" select= "translate( substring-before(substring-after(@sTime,' '),' '), ':', '' ) " /> <xsl:copy-of select="."/> </xsl:for-each> </sales> </xsl:template> </xsl:stylesheet>

当该转换应用于以下XML文档时

<sales>
    <item name="Games" sku="MIC28306200" iCat="28"
          sTime="11/26/2008 8:41:12 PM"
          price="1.00" desc="Item Name" />
    <item name="Games" sku="MIC28307100" iCat="28"
          sTime="11/26/2008 8:42:12 AM"
                price="1.00" desc="Item Name" />
    <item name="Games" sku="MIC28307100" iCat="28"
          sTime="11/26/2008 11:42:12 AM"
                price="1.00" desc="Item Name" />
    <item name="Games" sku="MIC28306200" iCat="28"
          sTime="12/23/2008 8:41:12 PM"
          price="1.00" desc="Item Name" />
    <item name="Games" sku="MIC28307100" iCat="28"
          sTime="12/23/2008 8:42:12 AM"
                price="1.00" desc="Item Name" />
</sales>

将会产生正确的结果:

<销售>
   <商品 名称="游戏" sku="MIC28307100" iCat="28" 销售时间="11/26/2008 8:42:12 AM" 价格="1.00" 描述="商品名称"/>
   <商品 名称="游戏" sku="MIC28307100" iCat="28" 销售时间="11/26/2008 11:42:12 AM" 价格="1.00" 描述="商品名称"/>
   <商品 名称="游戏" sku="MIC28306200" iCat="28" 销售时间="11/26/2008 8:41:12 PM" 价格="1.00" 描述="商品名称"/>
   <商品 名称="游戏" sku="MIC28307100" iCat="28" 销售时间="12/23/2008 8:42:12 AM" 价格="1.00" 描述="商品名称"/>
   <商品 名称="游戏" sku="MIC28306200" iCat="28" 销售时间="12/23/2008 8:41:12 PM" 价格="1.00" 描述="商品名称"/>
</销售>
这是一个关于IT技术的文本,其中包含了一段XML代码。本段代码描述了销售记录中的商品信息,包括商品名称、sku、销售时间、价格和描述等属性。为了方便理解,我们将其中的英文内容翻译成了中文。注意,我们并没有删除其中的HTML标签,以保持格式的完整性。

1

如果XML正确构建,你想要做的事情会更容易实现。XML Schema建议日期/时间值应以ISO8601格式表示,即CCCC-MM-DD HH:MM:SS。(实际上,XML Schema希望日期和时间之间的分隔符是T,目前我不记得原因了。)

这种方式格式化日期和时间的两个主要优点是:

  • 其他XML用户期望的格式
  • 您可以按其字符串值进行排序。

在XML中以任何其他方式格式化日期,这对将由XSLT处理的XML来说是一种残忍。

很容易使.NET以此格式发出DateTime值(使用“s”格式说明符,它代表-等待它-“可排序”)。


0

我知道这个问题已经很久了,你可能已经有了解决方案,但我还是想分享我的答案:

      private static void  SortElementAttributesBasis(XmlNode rootNode)
    {



        for (int j = 0; j < rootNode.ChildNodes.Count; j++)
        {
            for (int i = 1; i < rootNode.ChildNodes.Count; i++)
            {
                Console.WriteLine(rootNode.OuterXml);
                DateTime dt1 = DateTime.ParseExact(rootNode.ChildNodes[i].Attributes["sTime"].Value, "M/d/yyyy h:mm:ss tt", System.Globalization.CultureInfo.InvariantCulture);
                DateTime dt2 = DateTime.ParseExact(rootNode.ChildNodes[i-1].Attributes["sTime"].Value, "M/d/yyyy h:mm:ss tt", System.Globalization.CultureInfo.InvariantCulture);
                int compare = DateTime.Compare(dt1,dt2);
                if (compare < 0)
                {
                    rootNode.InsertBefore(rootNode.ChildNodes[i], rootNode.ChildNodes[i - 1]);
                    Console.WriteLine(rootNode.OuterXml);
                }

                // Provide the name of Attribute in .Attribute["Name"] based on value you want to sort.

                   //if (String.Compare(rootNode.ChildNodes[i].Attributes["sTime"].Value, rootNode.ChildNodes[1 - 1].Attributes["sTime"].Value) < 0)
                //{
                //    rootNode.InsertBefore(rootNode.ChildNodes[i], rootNode.ChildNodes[i - 1]);

                //}
            }
        }
    }

输入的 XML 是由 @Dimitre Novatchev 提供的示例

<sales>
<item name="Games" sku="MIC28306200" iCat="28"
      sTime="11/26/2008 8:41:12 PM"
      price="1.00" desc="Item Name" />
<item name="Games" sku="MIC28307100" iCat="28"
      sTime="11/26/2008 8:42:12 AM"
            price="1.00" desc="Item Name" />
<item name="Games" sku="MIC28307100" iCat="28"
      sTime="11/26/2008 11:42:12 AM"
            price="1.00" desc="Item Name" />
<item name="Games" sku="MIC28306200" iCat="28"
      sTime="12/23/2008 8:41:12 PM"
      price="1.00" desc="Item Name" />
<item name="Games" sku="MIC28307100" iCat="28"
      sTime="12/23/2008 8:42:12 AM"
            price="1.00" desc="Item Name" />

输出

<item name="Games" sku="MIC28307100" iCat="28" sTime="11/26/2008 8:42:12 AM" price="1.00" desc="Item Name" /><item name="Games" sku="MIC28307100" iCat="28" sTime="11/26/2008 11:42:12 AM" price="1.00" desc="Item Name" /><item name="Games" sku="MIC28306200" iCat="28" sTime="11/26/2008 8:41:12 PM" price="1.00" desc="Item Name" /><item name="Games" sku="MIC28307100" iCat="28" sTime="12/23/2008 8:42:12 AM" price="1.00" desc="Item Name" />


0

假设您的日期时间格式如下:

2010-06-01T15:16:29+05:00

那么最简单的方法是:

< xsl:sort select="translate(XPATH_RETURNING_DATE,'-T:+','')" order="descending" data-type="number" />

在日期时间中,只需替换额外的字符即可。在我的日期时间格式中,我有额外的字符(- T:和+),所以只需替换它,然后您的日期时间将以数字格式呈现,可以轻松排序。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接