Reputation: 2773
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);
}
}
Upvotes: 5
Views: 16225
Reputation: 3698
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;
}
Upvotes: 0
Reputation: 5197
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. :)
Upvotes: 3
Reputation: 32323
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.
Upvotes: 5