5

I am using the HtmlAgilityPack. I am searching through all P tags and adding a "margin-top: 0px" to the style within the P tag.

As you can see it is kinda "brute forcing" the margin-top attribute. It seems there has to be a better way to do this using the HtmlAgilityPack but I could not find it, and the HtmlAgilityPack documentation is non-existent.

Anybody know a better way?

HtmlNodeCollection pTagNodes = node.SelectNodes("//p[not(contains(@style,'margin-top'))]");

if (pTagNodes != null && pTagNodes.Any())
{
    foreach (HtmlNode pTagNode in pTagNodes)
    {
        if (pTagNode.Attributes.Contains("style"))
        {
            string styles = pTagNode.Attributes["style"].Value;
            pTagNode.SetAttributeValue("style", styles + "; margin-top: 0px");
        }
        else
        {
            pTagNode.Attributes.Add("style", "margin-top: 0px");
        }
    }
}


UPDATE: I have modified the code based on Alex's suggestions. Would still like to know if there is a some built-in functionality in HtmlAgilityPack that will handle the style attributes in a more "DOM" manner.

const string margin = "; margin-top: 0px";

HtmlNodeCollection pTagNodes = node.SelectNodes("//p[not(contains(@style,'margin-top'))]");

if (pTagNodes != null && pTagNodes.Any())
{
    foreach (var pTagNode in pTagNodes)
    {
        string styles = pTagNode.GetAttributeValue("style", "");
        pTagNode.SetAttributeValue("style", styles + margin);
    }
}

3 Answers 3

5

You could simplify your code a little bit by using HtmlNode.GetAttributeValue method, and making your "margin-top" magic string as constant:

const string margin = "margin-top: 0";
foreach (var pTagNode in pTagNodes)
{
    var styles = pTagNode.GetAttributeValue("style", null);
    var separator = (styles == null ? null : "; ");
    pTagNode.SetAttributeValue("style", styles + separator + margin);
}

Not a very significant improvement, but this code is simpler as for me.

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks Alex, I like these changes and am going to make them. I have upvoted you but want to keep the question open for now. I am hoping someone knows of some feature within HtmlAgilityPack which will parse the style attributes and allow me to add the margin through some collection type structure. I looked but could not find one but that does not mean it is not there.
@Gene S, I really doubt AgilityPack can parse the content of the style attribute. But you can try to split the attribute value by a semicolon (;) char by using string.Split method, process those values, and pack the values then by using string.Join.
I thought about that. Maybe I will create some extension methods so it looks baked into the AgilityPack. Thanks for your feedback.
There's no reason you can't add a CSS parser for the style tag. This might do the trick
3

First of all, are you sure you need more than what you asked for? Alex solution should just work fine for your current problem, if it's always that "simple" why bother and add more complexity to it?

Anway, the AgilityPack doesn't have that kind of function, but surely the .Net Framework has. Note this is all for .Net 4, if you're using an earlier version things might be a bit different. First of, System.Web.dll comes with the CssStyleCollection Class, this class already has everything build in that you could want for parsing inline css, there's just one catch, it's constructor is internal so the solution is a bit "hacky". First off, for construction an instance of the class all you need is a bit of reflection, the code for that has already been done here. Just keep in mind that this works now, but could break in a future version of .Net. All that's left is really easy

CssStyleCollection css = CssStyleTools.Create();
css.Value = "border-top:1px dotted #BBB;margin-top: 0px;font-size:12px";
Console.WriteLine(css["margin-top"]); //prints "0px"

IF you can't for some reason add a reference to System.Web (would be the case if you're using .Net 4 Client Profile) there's always the possibility to use Reflector.

Personally i'd go with Alex's solution, but it's up to you to decide. :)

1 Comment

My gut tells me to go with Alex's solution as well but I appreciate your input. I'm glad to know that I was just not overlooking something in the Agility Pack.
0

Just use the following extension method of the HtmlNode:

public static void AddOrUpdateCssValue(this HtmlNode htmlNode, string cssKey, string cssValue)
{
    string style = htmlNode.GetAttributeValue("style", "");
    string newStyle = addOrUpdateCssStyleKeyValue(style: style, newKey: cssKey, newValue: cssValue);

    htmlNode.SetAttributeValue(name: "style", value: newStyle);
}
private static string addOrUpdateCssStyleKeyValue(string style, string newKey, string newValue)
{
    if (String.IsNullOrEmpty(style)) return style;
    if (String.IsNullOrEmpty(newKey)) return style;
    if (String.IsNullOrEmpty(newValue)) return style;

    style = style.Clone() as string;
    List<string> keyValue = style.Split(';').Where(x => String.IsNullOrEmpty(x) == false).Select(x => x.Trim()).ToList();

    bool found = false;
    List<string> updatedStyles = new List<string>();
    foreach (string keyValuePair in keyValue)
    {
        if (String.IsNullOrEmpty(keyValuePair) == true) continue;
        if (keyValuePair.Contains(':') == false) continue;

        List<string> splitted = keyValuePair.Split(':').Where(x => String.IsNullOrEmpty(x) == false).Select(x => x.Trim()).ToList();
        if (splitted == null) continue;
        if (splitted.Count < 2) continue;

        string key = splitted[0];
        string value = splitted[1];

        if (key == newKey)
        {
            value = newValue;
            found = true;
        }

        updatedStyles.Add(String.Format("{0}: {1}", key, value));
    }

    if (found == false)
    {
        updatedStyles.Add(String.Format("{0}: {1}", newKey, newValue));
    }

    string result = String.Join("; ", updatedStyles);
    return result;
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.