如何对同名的XML节点进行分组?

时间:2021-12-18 16:52:32

I am trying to figure out how to group XML nodes with the same name but different values.

我试图弄清楚如何分组具有相同名称但不同值的XML节点。

The web service I'm using returns an XmlElement that looks like this:

我正在使用的Web服务返回一个如下所示的XmlElement:

<Items>
    <Item>
        <Name name="Name">Item 1</Name>
        <Description name="Description">
            Lorem ipsum dolor sit amet, consectetur adipiscing elit.
        </Description>
        <AssociatedItems name="Associated Items">Item 2</AssociatedItems>
        <AssociatedItems name="Associated Items">Item 3</AssociatedItems>
        <AssociatedItems name="Associated Items">Item 4</AssociatedItems>
        <AssociatedItems name="Associated Items">Item 5</AssociatedItems>
    </Item>
</Items>

I am transforming each node into an HTML tag

我正在将每个节点转换为HTML标记

protected void lnkItem_Click(object sender, EventArgs e)
{
    LinkButton link = (LinkButton)sender;
    GridViewRow row = (GridViewRow)link.Parent.Parent;

    string id = gv.DataKeys[row.RowIndex]["id"].ToString();

    PublicApiAsmxServiceSoapClient service = new PublicApiAsmxServiceSoapClient("PublicApiAsmxServiceSoap", WEB_SERVICE_URL);
    XmlElement xml = service.ItemGetAsXml(id);
    XElement nodes = XElement.Parse(xml.InnerXml);

    foreach (var node in nodes.Elements())
    {
        InsertHTML(node);
    }
}

private void InsertHTML(XElement node)
{
    if (node.Value == string.Empty)
        return;

    pnl.ContentTemplateContainer.Controls.Add(new LiteralControl(String.Format("<h3>{0}</h3>", node.Attribute("name").Value)));
    pnl.ContentTemplateContainer.Controls.Add(new LiteralControl(String.Format("<p>{0}</p>", node.Value)));
}

With my code right now, the HTML output would be:

现在使用我的代码,HTML输出将是:

<h3>Name</h3>
<p>Item 1</p>
<h3>Description</h3>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
<h3>Associated Items</h3>
<p>Item 2</p>
<h3>Associated Items</h3>
<p>Item 3</p>
<h3>Associated Items</h3>
<p>Item 4</p>
<h3>Associated Items</h3>
<p>Item 5</p>

Is there a way to group the same nodes as an unordered list? Something like this, perhaps:

有没有办法将相同的节点组合为无序列表?也许是这样的事情:

<h3>Name</h3>
<p>Item 1</p>
<h3>Description</h3>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
<h3>Associated Items</h3>
<ul>
    <li>Item 2</li>
    <li>Item 3</li>
    <li>Item 4</li>
    <li>Item 5</li>
</ul>

Thank you! (Please be nice.)

谢谢! (请友好一点。)

PS The child nodes that repeat aren't limited to AssociatedTerms only. There are possibly more child nodes of the same name that repeat.

PS重复的子节点不仅限于AssociatedTerms。可能有更多同名的子节点重复。

3 个解决方案

#1


1  

It would be easiest using a generator to generate the elements based on the groupings since what gets generated depends on how many duplicates there are.

使用生成器根据分组生成元素是最容易的,因为生成的内容取决于有多少重复项。

As I understand it, you're grouping by element name and the value of the name attributes. Then generating an h3 followed by an element depending on if there are multiple elements in the group. p if there's a single element or a ul if there are multiple elements.

据我了解,您按元素名称和名称属性的值进行分组。然后生成一个h3,后跟一个元素,具体取决于组中是否有多个元素。 p如果有单个元素,或者如果有多个元素则为ul。

IEnumerable<XElement> GroupedElements(XElement root)
{
    var groupedItems =
        from element in root.Elements()
        group element
        by new
        {
            Element = element.Name,
            Name = (string)element.Attribute("name"),
        };
    foreach (var g in groupedItems)
    {
        yield return new XElement("h3", g.Key.Name);
        var isMultiple = g.Skip(1).Any();
        if (isMultiple)
            yield return new XElement("ul",
                from item in g
                select new XElement("li", item.Value.Trim())
            );
        else
            yield return new XElement("p", g.Single().Value.Trim());
    }
}
var xmlStr = @"<Items>
    <Item>
        <Name name=""Name"">Item 1</Name>
        <Description name=""Description"">
            Lorem ipsum dolor sit amet, consectetur adipiscing elit.
        </Description>
        <AssociatedItems name=""Associated Items"">Item 2</AssociatedItems>
        <AssociatedItems name=""Associated Items"">Item 3</AssociatedItems>
        <AssociatedItems name=""Associated Items"">Item 4</AssociatedItems>
        <AssociatedItems name=""Associated Items"">Item 5</AssociatedItems>
    </Item>
</Items>";
var doc = XDocument.Parse(xmlStr);
var transformed = new XElement("div", 
    from item in doc.XPathSelectElements("/Items/Item")
    select GroupedElements(item)
);

Yields the following:

产量如下:

<div>
  <h3>Name</h3>
  <p>Item 1</p>
  <h3>Description</h3>
  <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
  <h3>Associated Items</h3>
  <ul>
    <li>Item 2</li>
    <li>Item 3</li>
    <li>Item 4</li>
    <li>Item 5</li>
  </ul>
</div>

#2


0  

I am not sure if it the best solution but you can try something like this

我不确定它是否是最佳解决方案,但您可以尝试这样的事情

       List<string> items = new List<string>();
          XmlReader rdr = XmlReader.Create(new System.IO.StringReader(xml));
          while (rdr.Read())
          {
            if (rdr.NodeType == XmlNodeType.Element)
            {
                InsertHTML(rdr.LocalName.ToString(), rdr.Value.ToString());
            }
          }
        private void InsertHTML(string name, string value)
        {
            if (value == string.Empty)
                return;
            if(name=="AssociatedItems")
             {

               items.add(value);
             }
             else
             {
            pnl.ContentTemplateContainer.Controls.Add(new LiteralControl(String.Format("<h3>{0}</h3>", name)));
            pnl.ContentTemplateContainer.Controls.Add(new LiteralControl(String.Format("<p>{0}</p>", value)));
             }
        }
           pnl.ContentTemplateContainer.Controls.Add(new LiteralControl(String.Format("<h3>{0}</h3>", "Associated Items")));
           pnl.ContentTemplateContainer.Controls.Add(new LiteralControl(String.Format("<ul>")));
        foreach(var item in items)
        {

          pnl.ContentTemplateContainer.Controls.Add(new LiteralControl(String.Format("<li>{0}</li>", item)));

        }
         pnl.ContentTemplateContainer.Controls.Add(new LiteralControl(String.Format("</ul>")));

#3


0  

If you wanted to use XSLT, you could use this:

如果您想使用XSLT,可以使用:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="html" indent="yes"/>

  <xsl:key name="grouping" match="Item/*" use="@name"/>

  <xsl:template match="Item">
    <xsl:apply-templates
         select="*[generate-id(.)=generate-id(key('grouping', @name)[1])]"/>
  </xsl:template>

  <xsl:template match="Item/*">
    <h3><xsl:value-of select="@name"/></h3>
    <xsl:choose>
      <xsl:when test="count(key('grouping', @name))=1">
        <p><xsl:value-of select="normalize-space(.)"/></p>
      </xsl:when>
      <xsl:otherwise>
        <li>
          <xsl:for-each select="key('grouping', @name)">
            <ul><xsl:value-of select="normalize-space(.)"/></ul>
          </xsl:for-each>
        </li>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>

#1


1  

It would be easiest using a generator to generate the elements based on the groupings since what gets generated depends on how many duplicates there are.

使用生成器根据分组生成元素是最容易的,因为生成的内容取决于有多少重复项。

As I understand it, you're grouping by element name and the value of the name attributes. Then generating an h3 followed by an element depending on if there are multiple elements in the group. p if there's a single element or a ul if there are multiple elements.

据我了解,您按元素名称和名称属性的值进行分组。然后生成一个h3,后跟一个元素,具体取决于组中是否有多个元素。 p如果有单个元素,或者如果有多个元素则为ul。

IEnumerable<XElement> GroupedElements(XElement root)
{
    var groupedItems =
        from element in root.Elements()
        group element
        by new
        {
            Element = element.Name,
            Name = (string)element.Attribute("name"),
        };
    foreach (var g in groupedItems)
    {
        yield return new XElement("h3", g.Key.Name);
        var isMultiple = g.Skip(1).Any();
        if (isMultiple)
            yield return new XElement("ul",
                from item in g
                select new XElement("li", item.Value.Trim())
            );
        else
            yield return new XElement("p", g.Single().Value.Trim());
    }
}
var xmlStr = @"<Items>
    <Item>
        <Name name=""Name"">Item 1</Name>
        <Description name=""Description"">
            Lorem ipsum dolor sit amet, consectetur adipiscing elit.
        </Description>
        <AssociatedItems name=""Associated Items"">Item 2</AssociatedItems>
        <AssociatedItems name=""Associated Items"">Item 3</AssociatedItems>
        <AssociatedItems name=""Associated Items"">Item 4</AssociatedItems>
        <AssociatedItems name=""Associated Items"">Item 5</AssociatedItems>
    </Item>
</Items>";
var doc = XDocument.Parse(xmlStr);
var transformed = new XElement("div", 
    from item in doc.XPathSelectElements("/Items/Item")
    select GroupedElements(item)
);

Yields the following:

产量如下:

<div>
  <h3>Name</h3>
  <p>Item 1</p>
  <h3>Description</h3>
  <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
  <h3>Associated Items</h3>
  <ul>
    <li>Item 2</li>
    <li>Item 3</li>
    <li>Item 4</li>
    <li>Item 5</li>
  </ul>
</div>

#2


0  

I am not sure if it the best solution but you can try something like this

我不确定它是否是最佳解决方案,但您可以尝试这样的事情

       List<string> items = new List<string>();
          XmlReader rdr = XmlReader.Create(new System.IO.StringReader(xml));
          while (rdr.Read())
          {
            if (rdr.NodeType == XmlNodeType.Element)
            {
                InsertHTML(rdr.LocalName.ToString(), rdr.Value.ToString());
            }
          }
        private void InsertHTML(string name, string value)
        {
            if (value == string.Empty)
                return;
            if(name=="AssociatedItems")
             {

               items.add(value);
             }
             else
             {
            pnl.ContentTemplateContainer.Controls.Add(new LiteralControl(String.Format("<h3>{0}</h3>", name)));
            pnl.ContentTemplateContainer.Controls.Add(new LiteralControl(String.Format("<p>{0}</p>", value)));
             }
        }
           pnl.ContentTemplateContainer.Controls.Add(new LiteralControl(String.Format("<h3>{0}</h3>", "Associated Items")));
           pnl.ContentTemplateContainer.Controls.Add(new LiteralControl(String.Format("<ul>")));
        foreach(var item in items)
        {

          pnl.ContentTemplateContainer.Controls.Add(new LiteralControl(String.Format("<li>{0}</li>", item)));

        }
         pnl.ContentTemplateContainer.Controls.Add(new LiteralControl(String.Format("</ul>")));

#3


0  

If you wanted to use XSLT, you could use this:

如果您想使用XSLT,可以使用:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="html" indent="yes"/>

  <xsl:key name="grouping" match="Item/*" use="@name"/>

  <xsl:template match="Item">
    <xsl:apply-templates
         select="*[generate-id(.)=generate-id(key('grouping', @name)[1])]"/>
  </xsl:template>

  <xsl:template match="Item/*">
    <h3><xsl:value-of select="@name"/></h3>
    <xsl:choose>
      <xsl:when test="count(key('grouping', @name))=1">
        <p><xsl:value-of select="normalize-space(.)"/></p>
      </xsl:when>
      <xsl:otherwise>
        <li>
          <xsl:for-each select="key('grouping', @name)">
            <ul><xsl:value-of select="normalize-space(.)"/></ul>
          </xsl:for-each>
        </li>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>