Alter html server-side in Orchard before rendering

Topics: Customizing Orchard
Jan 30, 2015 at 11:49 PM
I need to alter the href attribute of all <a> tags on the site that would lead off the domain. It has been mentioned that I could do this in two ways:
  1. Implement an IHtmlFilter and use the ProcessContent function to search for and alter <a> tags
  2. Implement an ActionFilter and capture the html that is being generated and alter it there
Would one method be preferred over the other? I have tried both methods but ran into problems with each.

Method 1: When and how will this filter be called and have the right parameters passed into it? The ProcessContent method expects two strings one for the text and one for the flavor. Will this be the html for the current page being rendered?

Method 2: I tried using the Orchard.OutputCache module as a basis from which to look at but ran into some problems
_originalWriter = filterContext.HttpContext.Response.Output;
            _speedbumpWriter = new StringWriterWithEncoding(_originalWriter.Encoding, _originalWriter.FormatProvider);    
            filterContext.HttpContext.Response.Output = _speedbumpWriter;
where do I start manipulating the html? Would I use the ToString() method on _speedbumpWriter and go from there?

Both Methods: Once I have the string of html how can I search it for all <a> tags and then replace the href attribute only when it's domain doesn't match that of the site? When it doesn't match (is an external link) I need to replace the href with href="speedbump?url=[original href value goes here]" Thank you for your help.
Jan 31, 2015 at 12:47 AM
I think I have the IHtmlFilter method started as I was able to successfully manipulate the link "<a href="http://www.google.com">" to "<a href="speedbump?url=http://www.google.com">" but I have run into a couple problems:

Current Code:
    public class SpeedbumpFilter : IHtmlFilter
    {
        public SpeedbumpFilter()
        {

        }

       string IHtmlFilter.ProcessContent(string text, string flavor)
        {
            var num = text.IndexOf("<a href");
            var numTwo = text.IndexOf("<a href=");
            var numThree = text.IndexOf("<a href=\"");
            var newText = text.Replace("<a href=\"", "<a href=\"speedbump?url=");
            return newText;
        }
    }
  1. The filter appears to only be called on the bodypart instead of on the entire page's content - is there a way to force this filter to be applied to all content?
  2. I have done little to nothing with string manipulation and will need a way to ensure only <a> tags with an href that leads off the domain get replaced and I will also need to search against specific urls that lead off the domain but shouldn't be speed bumped. Any ideas how to go about this? I have thought of running regular expressions but I have only just started to touch on those in my education and would need a good tutorial/site to help with creating the regular expressions in c# that will work.
Thank you.
Jan 31, 2015 at 9:27 PM
For 2, one solution is to use the HtmlAgilityPack utility. Then, with an html string, you can create an HtmlDocument where you can select, filter... html tags as nodes, this by using the "Path" syntax, and then, you can access their attributes. As an example, download the "Vandelay Industries" module by Bertrand Le Roy from the Orchard Gallery, and take a look in the " Filters/RelativeUrlHtmlFilter.cs" file where you will see how to do that

For 1, your right, we can see in "BodyPartDriver.cs" how the html filters are invoked. You can do the same in your own part drivers. For other Orchard parts, you have to use other extensibility methods: IShapeTableProvider, ShapeDisplayEvents, alternate views... Personally, I use HtmlAgilityPack in some *.cshtml razor views, e.g to implement my fast menus. Based on a simple html list and the current request, with HtmlAgilityPack I can create a main menu, an aside menu (starting at the right level) and a bredcrumbs, update their items classes (active, selected...), and items href attributes (convert relative to application absolute path)...

Regards