开发者

Iteration Problem - appending beginning and ending html

I'm trying to figure out how to only append the opening and close div and ul here. I don't know how to compare the next string to the current for the ParentName:

    foreach (SList subList in parentList)
    {
        if (subList.SubList.Count < 1)
            return string.Empty;

        for(int i = 0; i < subList.SubList.Count; i++)
        {
            if (string.Compare(subList.PName, lastPName) != 0)
            {
                subListItemsToHtml.AppendFormat(@"<div id='开发者_StackOverflow{0}-test' class='dropdown'>", subList.SubList[i].PName);
                subListItemsToHtml.Append("<ul>");
            }

            subListItemsToHtml.AppendFormat(@"    <li><a href='{0}'>{1}</a></li>", subList.SubList[i].URL, subList.SubList[i].DisplayName);
            lastPName = subList.SubList[i].PName;

            if (i + 1 < subList.SubList.Count)
                if(string.Compare(subList.SubList[i].PName, subList.SubList[i+1].PName) != 0)
                    subListItemsToHtml.Append("</ul></div>");
        }
    }


    return subListItemsToHtml.ToString();
}


I don't know if the structure of your data matches the structure of the markup, but changing to this seems logical if it does match:

foreach (SList subList in parentList)
{
    if (subList.SubList.Count < 1)
        return string.Empty;

    subListItemsToHtml.AppendFormat(@"<div id='{0}-test' class='dropdown'>", subList.SubList[i].PName);
    subListItemsToHtml.Append("<ul>");

    for(int i = 0; i < subList.SubList.Count; i++)
    {
        subListItemsToHtml.AppendFormat(@"    <li><a href='{0}'>{1}</a></li>", subList.SubList[i].URL, subList.SubList[i].DisplayName);
        lastPName = subList.SubList[i].PName;
    }

    subListItemsToHtml.Append("</ul></div>");
}

return subListItemsToHtml.ToString();

Personally, I'd approach this more like a projection of your data using LINQ and LINQ to XML instead. This will also avoid potential mal-formed HTML (you're build HTML through string concatenation without HTML-escaping your output):

var xhtml = new XDocument(
    new XElement("div",
        from subList in parentList
        where subList.SubList.Count > 0
        select new XElement("div",
            new XAttribute("id", subList.SubList[0].PName + "-test"),
            new XAttribute("class", "dropdown"),
            new XElement("ul",
                from child in subList
                select new XElement("li",
                    new XElement("a",
                        new XAttribute("href", child.URL),
                        new XText(child.DisplayName)))))));
return xhtml.ToString();


Try to refacor the code as follows:

foreach (SList subList in parentList)
{
    if (subList.SubList.Count < 1)
        return string.Empty;

    for(int i = 0; i < subList.SubList.Count; i++)
    {
        if (string.Compare(subList.PName, lastPName) != 0)
        {
            subListItemsToHtml.AppendFormat(@"<div id='{0}-test' class='dropdown'>", subList.SubList[i].PName);
            subListItemsToHtml.Append("<ul>");

            subListItemsToHtml.AppendFormat(@"    <li><a href='{0}'>{1}</a></li>", subList.SubList[i].URL, subList.SubList[i].DisplayName);
            subListItemsToHtml.Append("</ul></div>");
        }
        else
        {
            subListItemsToHtml.AppendFormat(@"    <li><a href='{0}'>{1}</a></li>", subList.SubList[i].URL, subList.SubList[i].DisplayName);
        }
        lastPName = subList.SubList[i].PName;
    }
}


One thing I'm not sure about with your question if it is right that there is a sublist of sublists?

It seems like your structure is this:

public class Parent : List<SList>
{ }

public class SList
{
    public List<SList> SubList = new List<SList>();
    public string PName;
    public string URL;
    public string DisplayName;
}

In your code you compare the top sublist's PName with each child's sublist's PName and that doesn't seem right to me. Perhaps you could explain your structure some more?

Nevertheless, if I can assume you can flatten your parentList data to just an IEnumerable<SList> then I have a solution for you.

First, I flatten the parentList data like so:

var sublists =
    from subList in parentList
    from subList2 in subList.SubList
    select subList2;

Then I do the following:

var html = String.Join(Environment.NewLine,
    sublists
        .AdjacentBy(
            sl => sl.PName,
            sls => String.Format(@"<div id='{0}-test' class='dropdown'><ul>",
                sls.First().PName),
            sl => String.Format(@"<li><a href='{0}'>{1}</a></li>",
                sl.URL,
                sl.DisplayName),
            sls => @"</ul></div>")
        .SelectMany(x => x));

And that produces your html, which should is like the equivalent of this:

var html = @"<div id='foo1a-test' class='dropdown'><ul>
<li><a href='url1a'>bar1a</a></li>
</ul></div>
<div id='goo1b-test' class='dropdown'><ul>
<li><a href='url1b'>bar1b</a></li>
<li><a href='url2a'>bar2a</a></li>
</ul></div>
<div id='foo2b-test' class='dropdown'><ul>
<li><a href='url2b'>bar2b</a></li>
</ul></div>";

Now, I'm sure that you spotted the use of a new extension method AdjacentBy. This is where all the magic takes place.

I very much like to abstract away the kind of operation you are doing into a nice re-usable LINQ operator. Doing so means that your code that does the work of generating your HTML is tight and concise and separated away from the grunt work of iterating the list and grouping the results.

The signature for AdjacentBy looks like this:

IEnumerable<IEnumerable<V>> AdjacentBy<T, K, V>(
    this IEnumerable<T> source,
    Func<T, K> keySelector,
    Func<IEnumerable<T>, V> headerSelector,
    Func<T, V> valueSelector,
    Func<IEnumerable<T>, V> footerSelector)

Its job is to take a list and make list of lists the whenever the value produced by the keySelector changes. The values in the list of lists comes from the valueSelector.

The headerSelector & footerSelector lambdas can be used to create a header value and footer value based on the items in the current list.

So, as an example, If I run this query:

var query = "CBBCCA"
    .AdjacentBy(
        c => c,
        cs => cs.Count().ToString() + "x",
        c => c.ToString(),
        cs => ".")
    .ToArray();

It will be the equivalent of:

var query = new []
{
    new [] { "1x", "C", "." },
    new [] { "2x", "B", "B", "." },
    new [] { "2x", "C", "C", "." },
    new [] { "1x", "A", "." },
};

Here's the full definition of AdjacentBy:

public static IEnumerable<IEnumerable<V>> AdjacentBy<T, K, V>(
    this IEnumerable<T> source,
    Func<T, K> keySelector,
    Func<IEnumerable<T>, V> headerSelector,
    Func<T, V> valueSelector,
    Func<IEnumerable<T>, V> footerSelector)
{
    var first = true;
    var last = default(K);
    var list = new List<T>();
    var values = (IEnumerable<V>)null;
    Func<List<T>, IEnumerable<V>> getValues = ts =>
    {
        var vs = (IEnumerable<V>)null;
        if (ts.Count > 0)
        {
            IEnumerable<V> hs = headerSelector == null
                ? Enumerable.Empty<V>()
                : new [] { headerSelector(ts) };
            IEnumerable<V> fs = footerSelector == null
                ? Enumerable.Empty<V>()
                : new [] { footerSelector(ts) };
            vs = hs
                .Concat(ts.Select(t => valueSelector(t)))
                .Concat(fs)
                .ToArray();
        }
        return vs;
    };
    foreach (var t in source)
    {
        var current = keySelector(t);
        if (first || !current.Equals(last))
        {
            first = false;
            values = getValues(list);
            if (values != null)
            {
                yield return values;
            }
            list.Clear();
            last = current;
        }
        list.Add(t);
    }
    values = getValues(list);
    if (values != null)
    {
        yield return values;
    }
}

I hope this helps.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜