Canalblog
Editer l'article Suivre ce blog Administration + Créer mon blog
Publicité
Fanfois et ses papillons
1 décembre 2011

Aspirateur (5 - expressions régulières, le retour)

Avant de penser à récupérer les pages référencées dans la page courante, nous allons tenter de compléter les ressources associées à cette première page.
Si vous double cliquez sur la version locale de la page HTML récupérée, vous verez quelque chose comme :

Aspirateur 05 01

Comparé à ce que l'on peut voir en allant sur canalblog, il doit y avoir un problème quelque part.
En ce qui concerne l'aspect visuel, le principal problème réside dans l'absence locale des feuilles de style.
Par soucis de sécurité, les butineurs ne vont pas chercher les feuilles de style distantes associées à une page locale.
Nous allons donc commencer par récupérer les feuilles de style indiquées dans la page.

Ceux qui connaissent HTML savent qu'il faut s'intéresser aux tags <link> pour trouver les liens vers les feuilles de style.
Se pose alors un problème que nous n'avions pas avec le tag <img>. Le tag <link> peut servir à plusieurs choses. Nous ne pouvons pas, en tout cas nous n'allons pas, nous contenter de récupérer l'attribut href.

Pourquoi ce besoin soudain d'analyser la signification exact du tag et ne pas traiter systématiquement l'URL trouvée dans href ?

Pour apprendre jeune Padawan, pour APPRENDRE.

Bon, un peu également, pour s'éviter un mal au crâne potentiel avec des choses comme <link rel="canonical" href="http://www.canalblog.com" />.
Qu'allons-nous regarder au niveau du tag <link>?
Nous allons récupérer les attributs rel, type et bien sûr href. Si nous désirons traiter que le cas où <link> précise une feuille de style, nous chercherons à trouver un rel à stylesheet associé à un type valant text/css. Mais rien ne nous empêchera de faire plus si nous le souhaitons.
Nous allons également accepter que les valeurs associées à nos attributs soient entourées de ", de ' ou d'espaces.

Pour commencer nous ajoutons une nouvelle classe à notre projet AspirateurUtil, à savoir la classe LinkProcessor. A ce stade, son code est directement pompé sur celui de ImageProcessor :

using System;
using System.Text;
using System.Text.RegularExpressions;
using Aspirateur.Model;
 
namespace AspirateurUtil
{
    /// <summary>
    /// Classe de traitement des tags HTML link.
    /// </summary>
    public class LinkProcessor
    {
        #region Membres
        // L'expression régulière permettant le traitement des tags link.
        private static Regex _regex = new Regex("<\\s*link\\b[^>]*\\bhref\\s*=\\s*[\"']?([^\"'\\s]+)[\"']?[^>]*>", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled);
 
        // La classe à utiliser pour le traitement de l'URL.
        private UrlFileMatcher _urlFileMatcher;
        #endregion
 
        #region Constructeurs
        public LinkProcessor(UrlFileMatcher urlFileMatcher)
        {
            _urlFileMatcher = urlFileMatcher;
        }
        #endregion
 
        #region Méthodes publiques
        public string ProcessPage(string pageContent)
        {
            return _regex.Replace(pageContent, new MatchEvaluator(LinkEvaluator));
        }
        #endregion
 
        #region Méthodes privées
        /// <summary>
        /// Fonction appelée lorsqu'un tag LINK est trouvé.
        /// </summary>
        /// <param name="m">La définition trouvée.</param>
        /// <returns>La définition avec l'URL locale.</returns>
        private string LinkEvaluator(Match m)
        {
            Uri absUrl, relUrl;
            string path;
 
            // Génération des noms nécessaires au traitement.
            if (!_urlFileMatcher.ProcessUrl(m.Groups[1].Value, false, out absUrl, out relUrl, out path))
                // On ne sait pas traiter.
                return m.ToString();
 
            // Récupération de la feuille de style.
            if (!DownloadHelper.GetFile(absUrl, path))
            {
                return m.ToString();
            }
 
            // On substitue l'URL locale à l'URL distante.
            StringBuilder strURL = new StringBuilder();
            int iDebut = m.Groups[1].Index - m.Groups[0].Index;
            strURL.Append(m.Groups[0].Value.Substring(0, iDebut));
            strURL.Append(relUrl.ToString());
            strURL.Append(m.Groups[0].Value.Substring(iDebut + m.Groups[1].Length));
            return strURL.ToString();
        }
        #endregion
    }
}

Du côté de l'appelant, btnGo_Click, il nous suffit d'ajouter deux lignes de code :

// On cherche les feuilles de style.
LinkProcessor linkProc = new LinkProcessor(urlFileMatcher);
strPage = linkProc.ProcessPage(strPage);

En théorie cela devrait fonctionner, pas forcément très bien, mais nous pouvons toujours essayer.

Si vous avez suivi sous débogueur vous avez dû constater :

  • la réussite de la récupération de l'icône favicon.ico ;
  • la réussite de la récupération de la page canalblog.htm ;
  • l'échec de la récupération de la feuille de style frontend.css ;
  • l'échec de la récupération de la feuille de style jquery.fancybox-1.3.1.css ;
  • la réussite de la récupération de la feuille de style autocomplete.css.

A l'arrivée notre programme n'a pas planté, mais le résultat, c'est-à-dire la page locale, n'est pas visuellement beaucoup plus proche de l'originale.

Que s'est-il passé ?

La récupération de l'icône ne nous intéressait pas trop, mais ne semble pas présenter de problème. Un détail tout de même, mon butineur ne semble pas comprendre qu'elle est définie car il ne l'affiche pas.

La récupération de la page canalblog.htm ne nous intéressait pas et pourrait devenir gênante dans un proche avenir. Ici elle n'a pas posé de problème, car elle n'a fait que provoquer l'écriture sur le disque d'un fichier canalblog.htm qui a été écrasé à la fin par notre version locale. Comme c'est bien la version locale que nous voulons, tout va bien. Cela dit, si la page courante n'était pas justement canalblog.htm, nous aurions perdu notre version locale. La chose est donc inutile et potentiellement dangereuse. Il faut éviter cette récupération.

En ce qui concerne les feuilles de style, les échecs sont liés à la présence d'une question dans l'URL (un caractère ? suivi de paramètres). Nous allons commencer par nous occuper de ce problème.

La solution consiste à apprendre à la méthode ProcessUrl de UrlFileMatcher à traiter un peu mieux l'URL fournie. Nous modifions donc la façon dont absUrl est calculée :

public bool ProcessUrl(string url, bool isPage, out Uri absUrl, out Uri relUrl, out string path)
{
    // Initialisation.
    Uri filePath = null;
    absUrl = relUrl = null;
    path = null;
 
    try
    {
        // Construction de l'adresse absolue.
        Uri absFullUrl = new Uri(_currentUrl, url);
        absUrl = new Uri(absFullUrl.GetLeftPart(UriPartial.Path));
 
        // On vérifie que l'adresse absolue pointe bien vers le site principal.
        if (_leSite.BaseURL.IsBaseOf(absUrl))
        {
            // Construction de l'URL relative.
            relUrl = _currentUrl.MakeRelativeUri(absFullUrl);
 
            // Construction du chemin complet local.
            filePath = new Uri(_leSite.BaseDirectory, _leSite.BaseVirtualDiretory.MakeRelativeUri(absUrl));
        }
        else
        {
            // Pour l'instant on ne fait rien.
            return false;
        }
 
        // On s'assure que le répertoire de destination existe.
        path = filePath.LocalPath;
        Directory.CreateDirectory(Path.GetDirectoryName(path));
 
        // Est-ce bien une URL de fichier ?
        if (string.IsNullOrWhiteSpace(Path.GetFileName(path)))
        {
            // Non, on ajoute le nom complet par défaut.
            path = Path.Combine(path, _leSite.DefaultFileName);
        }
        // Faut-il modifier l'extension ?
        else if (isPage)
        {
            path = Path.ChangeExtension(path, _leSite.DefaultExtensionString);
        }
 
        // Tout va bien.
        return true;
    }
    catch (Exception Ex)
    {
        // Il nous faudrait loguer l'erreur mais nous ne savons pas faire.
        return false;
    }
}

Si nous relançons notre aspirateur et double cliquons sur la page locale obtenue, le résultat est très nettement meilleur, du point de vue visuel s'entend.

Aspirateur 05 02

Cela dit, le problème de la récupération intempestive de la page canalblog.htm persiste. Pour le résoudre, il nous faut nous occuper non seulement de l'attribut href, mais également de l'attribut rel. Pour faire bonne mesure, nous allons également récupérer l'attribut type.

Pour faire cela nous devons utiliser plusieurs groupes de captures. C'est alors que le fait que l'ordre des attributs soit libre dans un tag HTML nous pose un problème. Pour l'instant nous n'avons cherché à récupérer que le href. Notre expression régulière ne comporte donc qu'un seul groupe de capture.

Pour ceux qui sont un peu perdu, n’hésitez pas à jeter un œil sur les liens indiqués ici. Pour mémoire, notre groupe de capture est défini par ([^"'\s]+), ce qui donne dans notre C# ([^\"'\\s]+), car nous devons doubler les \ et échapper la ".

Si nous ajoutons bêtement, cela va sans dire, un nouveau groupe de capture à notre expression, il risque d'y avoir des problèmes. Par exemple, quels problèmes présente l'expression suivante :

<\s*link\b[^>]*\bhref\s*=\s*["']?([^"'\s]+)["']?[^>]*\brel\s*=\s*["']?([^"'\s]+)["']?[^>]*>

Il y en a plusieurs, mais celui qui est probablement le plus grave, c'est que nous forçons l'attribut href à être défini avant l'attribut rel.

Nous pourrions tâtonner assez longtemps en ajoutant ici ou là des | dans notre expression, mais il se trouve qu'il y a quelqu'un qui propose une solution fort élégante dans le billet Capturing Multiple, Optional HTML Attribute Values. Il propose de traiter le cas du tag <div>, et la capture de ces attributs id et class, à l'aide de l'expression suivante :

<div\b(?>\s+(?:id="([^"]*)"|class="([^"]*)")|[^\s>]+|\s+?)*>

Comme cela est précisé dans le billet (que je vous invite fortement à lire, car je ne vais pas le traduire), le fait que la valeur des attributs peut également être encadrée par des ' ou rien, n'est pas pris en compte. De même, le fait qu'il peut y avoir des blancs autour du = séparant l'attribut de sa valeur est ignoré pour simplifier l'expression. Cela dit, cette expression nous garantit de toujours trouver dans le premier groupe de capture la valeur associée à l'attribut id, et dans le second celle liée à class.

Transposons cela à notre problème. Nous obtenons facilement :

<link\b(?>\s+(?:rel="([^"]*)"|type="([^"]*)"|href="([^"]*)")|[^\s>]+|\s+?)*>

Si vous ne voyez pas comment passer à cette expression depuis la précédente, la suite du billet risque d'être rude. Pour vous aider un peu, beaucoup si vous maitrisez les éléments de base des expressions régulières (à savoir ce qui est présenté dans la page Regular Expression Language - Quick Reference), je vous conseille vivement d'installer sur votre machine Expresso.
L'expression régulière que nous venons de créer est alors décomposée et commentée dans la fenêtre "Regex Analyzer". Avec un jeu de test, on peut également voir ce qui est reconnu, groupe compris en dépliant les éléments reconnus. Sur la capture d'écran à venir, le jeu de test est le suivant :

  • <link rel="shortcut icon" href="http://www.canalblog.com/favicon.ico" type="image/x-icon" />
  • <link type="text/css" href="/sharedDocs/css/autocomplete.css" rel="stylesheet" media="screen" />

Aspirateur 05 03

Remarque :
Le jeu de test complet (mais qui augmente la taille de la capture d'écran) que j'ai utilisé est en fait :

  • <link rel="shortcut icon" href="http://www.canalblog.com/favicon.ico" type="image/x-icon" />
  • < link rel ="shortcut icon" href= "http://www.canalblog.com/favicon.ico" type = "image/x-icon" />
  • <link rel='shortcut icon' href='http://www.canalblog.com/favicon.ico' type='image/x-icon' />
  • <link rel='shortcut icon' href='http://www.canalblog.com/favicon.ico' type= 'image/x-icon' />
  • <link rel="shortcut icon' href='http://www.canalblog.com/favicon.ico" type='image/x-icon' />
  • <link rel="shortcut icon" href='http://www.canalblog.com/favicon.ico" type='image/x-icon' />
  • <link rel="shortcut icon" href=http://www.canalblog.com/favicon.ico type=image/x-icon />
  • <link rel="shortcut icon" href = http://www.canalblog.com/favicon.ico type= image/x-icon />
  • <link rel="shortcut icon" type=image/x-icon />
  • <link rel="canonical" href="http://www.canalblog.com" />
  • <link rel="canonical' href="http://www.canalblog.com" />
  • <link rel=canonical href="http://www.canalblog.com" />
  • <link rel = canonical href= http://www.canalblog.com />
  • <link rel="stylesheet" type="text/css" href="/sharedDocs/css/frontend.css?1320746400" media="all" />
  • <link rel=stylesheet type="text/css" href="/sharedDocs/css/frontend.css?1320746400" media="all" />
  • <link rel="stylesheet" type="text/css" href="/sharedDocs/css/jquery.fancybox-1.3.1.css?1320746400" media="all" />
  • <link type="text/css" href="/sharedDocs/css/autocomplete.css" rel="stylesheet" media="screen" />
  • <link type="text/css" href="/sharedDocs/css/autocomplete.css' media="screen" rel="stylesheet" />

Comme première amélioration nous aimerions rendre l'attribut href obligatoire. Le billet Capturing Multiple, Optional HTML Attribute Values nous fournit, là encore, une solution élégante. Ajouter un groupe de capture vide après le traitement de l'élément qui nous intéresse, (), et demander sa réapparition plus tard, \4 car il s'agit ici du quatrième groupe de capture défini dans l'expression. Comme il ne contiendra rien, il n'oblige pas à la présence de quoi que ce soit dans le texte traité, par contre il devra être défini. Pour cela, il faudra que l'élément qui nous intéresse soit trouvé. Cela donne :

<link\b(?>\s+(?:rel="([^"]*)"|type="([^"]*)"|href="([^"]*)"())|[^\s>]+|\s+?)*\4>

Si vous faites le test dans Expresso, avec le jeu de test complet, vous constaterez un nombre important d'échec. Problème, certains échecs ne sont pas souhaitables. En effet href n'est pas trouvé lorsque sa valeur est encadrée par des ' ou par rien.

Commençons par nous attaquer au problème " ou '. Nous allons réellement traiter le problème, ce que nous n'avions pas fait dans le deuxième billet de cette série. En effet notre ["']?([^"'\s]+)["']? n'est pas des plus subtile. La règle dit que si la valeur du paramètre est encadrée par des ", elle peut contenir des '. La réciproque est vraie. De plus les espaces sont alors parfaitement valides. Notre expression régulière est bien loin du compte.

Pour arriver au résultat il nous faut donc trouver un " ou un ', et le mémoriser dans un groupe de capture. Dès que nous retrouvons ce caractère mémorisé, nous sommes à la fin. Ce qui est présent entre les deux correspond à ce que nous cherchons. Cela donne quelque chose comme :

(["'])([^\1]*)\1

Sauf que [^\1] n'est absolument pas interprété comme "tout, sauf ce qu'il y a dans le premier groupe de capture". En fait il faut utiliser une autre technique. Personnellement j'ai opté pour "Zero-Width Positive Lookahead Assertions" et j'obtiens l'expression suivante :

(["'])(.*?(?=\1))\1

Attention, (["'])(.*(?=\1))\1 ne fait pas ce que l'on souhaite, il faut impérativement le ? de (["'])(.*?(?=\1))\1 pour s'arrêter au premier élément fermant de la ligne et non le dernier.

Si nous introduisons cela juste pour href cela donne (le groupe de capture (["']) est le troisième présent dans notre expression) :

<link\b(?>\s+(?:rel="([^"]*)"|type="([^"]*)"|href=(["'])(.*?(?=\3))\3())|[^\s>]+|\s+?)*\5>

Si nous souhaitions autoriser une version échappée à la C du délimiteur (en clair \" ou \' suivant le cas) dans la valeur de l'attribut, Regexes in Depth: Advanced Quoted String Matching nous propose :

(["'])((?:\\\1|.)*?)\1

Il se trouve que HTML n'autorise pas cette syntaxe. HTML impose d'utiliser &quot; &apos; ou les versions numériques à la &#34. Cela dit pour d'autres analyses cela peut être très utile.

Il nous faut maintenant considérer le cas suplémentaire où il n'y a pas de délimiteur. Je vous propose la solution suivante :

(?(["'])(["'])(.*?(?=\1))\1|([^>\s]*)\b)

Je sais, la première fois on a mal à la tête. Il faut lire la chose en séparant les blocs de la manière suivante :
(  ?(["'])  (["'])(.*?(?=\1))\1  |  ([^>]*)\b  )
En pensant :
(  ?(expression)  si trouvée  |  si pas trouvée  )
Donc, nous cherchons ["']. Si nous le trouvons nous faisons le traitement que nous avons déjà vu. Si nous ne trouvons pas, nous faisons ([^>\s]*)\b. Nous cherchons donc n'importe quoi sauf > ou un "espace", un nombre indéterminé de fois et à la frontière d'un mot.

Vous l'avez sans doute remarqué, nous avons un problème. Ce que nous cherchons peut potentiellement être trouvé, soit entouré de " ou ', le résultat sera alors dans un groupe de capture, le second dans notre exemple, soit entouré de rien, le résultat sera alors dans un autre groupe de capture, le troisième dans notre exemple.
Cela complique notablement la chose car il nous faut maintenant regarder dans deux groupes de capture. La solution la plus simple pour n'avoir à prendre en compte qu'un groupe de capture consiste à utiliser les groupes nommés qui fonctionnent comme nous le souhaitons en .NET. En effet .NET autorise la présence répétitive d'un groupe nommé, ce qui n'est pas standard.
A supposer que nous voulions nommer le groupe "href", cela donne :

(?(["'])(["'])(?<href>.*?(?=\1))\1|(?<href>[^>\s]*)\b)

Dernier point les espaces entourant le signe =. Si nous appliquons cela à href nous obtenons :

href\s*=\s*(?(["'])(["'])(?<href>.*?(?=\1))\1|(?<href>[^>\s]*)\b)

Notons que la recherche d'espace de chaque côté du = nous autorise à enlever le \b que nous avons à la fin du cas ou il n'y a ni " ni '. Cela donne alors :

href\s*=\s*(?(["'])(["'])(?<href>.*?(?=\1))\1|(?<href>[^>\s]*))

Si nous incluons cela dans l'expression complète, il faut tenir compte du fait que les groupes nommés sont numérotés après les groupes non nommés. Cela donne, en ajoutant la prise en compte des espaces avant le tag HTML :

<\s*link\b(?>\s+(?:rel="([^"]*)"|type="([^"]*)"|href\s*=\s*(?(["'])(["'])(?<href>.*?(?=\3))\3|(?<href>[^>\s]*)\b)())|[^\s>]+|\s+?)*\4>

Remarque :
Si vous testez dans Expresso, vous pourrez trouver des succès non souhaités lorsque les deux cotés n’utilisent pas le même délimiteur. Par exemple sur <link rel="shortcut icon' href='http://www.canalblog.com/favicon.ico" type='image/x-icon' />, vous obtenez pour le groupe "href" :
http://www.canalblog.com/favicon.ico" type=
C'est normal. Les " et les espaces sont autorisés car le délimiteur est '. Le problème est dans le HTML, pas dans notre analyse.

Nous généralisons maintenant le traitement des délimiteurs, et rendons obligatoire rel et type en plus de href :

<\s*link\b(?>\s+(?:rel\s*=\s*(?(["'])(["'])(?<rel>.*?(?=\1))\1|(?<rel>[^>\s]*))()|type\s*=\s*(?(["'])(["'])(?<type>.*?(?=\3))\3|(?<type>[^>\s]*))()|href\s*=\s*(?(["'])(["'])(?<href>.*?(?=\5))\5|(?<href>[^>\s]*))())|[^\s>]+|\s+?)*\2\4\6>

Nous voilà arrivé à un niveau suffisant pour notre expression régulière. Notons que si nous voulons simplement rendre obligatoire rel et href il nous suffit de remplacer le \2\4\6> final par \2\6> et le tour est joué.
Passons au code et à son utilisation. Sa déclaration devient :

#region Membres
// L'expression régulière permettant le traitement des tags link.
private static Regex _regex = new Regex("<\\s*link\\b(?>\\s+(?:" +
                                        "rel\\s*=\\s*(?([\"'])([\"'])(?<rel>.*?(?=\\1))\\1|(?<rel>[^>\\s]*))()|" +
                                        "type\\s*=\\s*(?([\"'])([\"'])(?<type>.*?(?=\\3))\\3|(?<type>[^>\\s]*))()|" +
                                        "href\\s*=\\s*(?([\"'])([\"'])(?<href>.*?(?=\\5))\\5|(?<href>[^>\\s]*))()" +
                                        ")|[^\\s>]+|\\s+?)*\\2\\4\\6>",
                                        RegexOptions.IgnoreCase | RegexOptions.CultureInvariant |
                                        RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled);
#endregion

Dans la fonction LinkEvaluator, nous n'avons qu'à regarder ce qui est présent dans le groupe de capture "rel" pour décider si nous récupérons, ou pas. Notons que nous pourrions très bien ne pas nous occuper de ce qu'il y a dans le groupe "type". Pour l'exemple, nous allons vérifier que les feuilles de style sont du type text/css. Cela dit, comme nous ne traiterons pas leur contenue... Cela donne :

#region Méthodes privées
/// <summary>
/// Fonction appelée lorsqu'un tag LINK est trouvé.
/// </summary>
/// <param name="m">La définition trouvée.</param>
/// <returns>La définition avec l'URL locale.</returns>
private string LinkEvaluator(Match m)
{
    // L'expression régulière nous assure la présence de rel, type et href.
    Group rel = m.Groups["rel"];
    Group type = m.Groups["type"];
    Group href = m.Groups["href"];
 
    switch (rel.Value.ToLower())
    {
        case "shortcut icon":
            break;
        case "stylesheet":
            // On valide type pour la forme.
            if (type.Value.ToLower() != "text/css")
            {
                // on ne traite pas.
                return m.ToString();
            }
            break;
        default:
            // on ne traite pas.
            return m.ToString();
    }
 
    // Génération des noms nécessaires au traitement.
    Uri absUrl, relUrl;
    string path;
    if (!_urlFileMatcher.ProcessUrl(href.Value, false, out absUrl, out relUrl, out path))
        // On ne sait pas traiter.
        return m.ToString();
 
    // Récupération de la feuille de style.
    if (!DownloadHelper.GetFile(absUrl, path))
    {
        return m.ToString();
    }
 
    // On substitue l'URL locale à l'URL distante.
    StringBuilder strURL = new StringBuilder();
    int iDebut = href.Index - m.Groups[0].Index;
    strURL.Append(m.Groups[0].Value.Substring(0, iDebut));
    strURL.Append(relUrl.ToString());
    strURL.Append(m.Groups[0].Value.Substring(iDebut + href.Length));
    return strURL.ToString();
}
#endregion

Nous profitons de nos nouvelles connaissances en matière d'expression régulière pour améliorer celle présente dans ImageProcessor, et modifier le code en conséquence (seul le code modifié est présenté) :

#region Membres
// L'expression régulière permettant le traitement des tags img.
private static Regex _regex = new Regex("<\\s*img\\b(?>\\s+(?:src\\s*=\\s*(?([\"'])([\"'])(?<src>.*?(?=\\1))\\1|(?<src>[^>\\s]*))())|[^\\s>]+|\\s+?)*\\2>",
                                        RegexOptions.IgnoreCase | RegexOptions.CultureInvariant |
                                        RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled);
#endregion
 
#region Méthodes privées
/// <summary>
/// Fonction appelée lorsqu'un tag IMG est trouvé.
/// </summary>
/// <param name="m">La définition trouvée.</param>
/// <returns>La définition avec l'URL locale.</returns>
private string ImageEvaluator(Match m)
{
    // L'expression régulière nous assure la présence de src.
    Group src = m.Groups["src"];
 
    // Génération des noms nécessaires au traitement.
    Uri absUrl, relUrl;
    string path;
    if (!_urlFileMatcher.ProcessUrl(src.Value, false, out absUrl, out relUrl, out path))
    {
        // On ne sait pas traiter.
        return m.ToString();
    }
 
    // Récupération de l'image.
    if (!DownloadHelper.GetFile(absUrl, path))
    {
        // Rien en locale, on garde le tag d'origine.
        return m.ToString();
    }
 
    // On substitue l'URL locale à l'URL distante.
    StringBuilder strURL = new StringBuilder();
    int iDebut = src.Index - m.Groups[0].Index;
    strURL.Append(m.Groups[0].Value.Substring(0, iDebut));
    strURL.Append(relUrl.ToString());
    strURL.Append(m.Groups[0].Value.Substring(iDebut + src.Length));
    return strURL.ToString();
}
#endregion

Si nous lançons cette version du programme, il n'y a aucun progrès visible au niveau de la version locale finale. Nous savons cependant que le rel="canonical" ne peut plus nous poser de problème.

Pour ce billet nous allons nous arrêter là. Il fut relativement "court", mais susceptible de faire un peu mal à la tête via la mise au point de l'expression régulière. Notons que cette dernière est facilement modifiable pour traiter n'importe quel autre tag.

Tient, comme ça, avant de partir, et si nous traitions le tag <script> ?
Facile, rien qu'un malheureux copier/coller de ImageProcessor pour créer ScriptProcessor et le tour est quasiment joué. Je vous laisse le faire.

Les bougons feront remarquer qu'il y a probablement matière à généralisation et/ou re-factorisation.
Ce n'est pas faux, nous y penserons la prochaine fois.
En attendant vous trouverez le code ici avec la classe ScriptProcessor en bonus exclussif.

Publicité
Publicité
Commentaires
Fanfois et ses papillons
Publicité
Archives
Publicité