Avatar billede montago Praktikant
26. september 2007 - 15:50 Der er 16 kommentarer og
1 løsning

Selektiv replace af bestemte links

Jeg er igang med en portal hvor jeg dynamisk henter HTML-sider fra externe services - nu vil jeg så igang med at konvertere relative links sådan at de peger på Portalen selv.

Min lille brainstorm har givet mig følgende mulige links

//Relative links
<a href='?'>?</a>
<a href='?test=123'>?test=123</a>
<a href='link.asp?test=abc'>link.asp?test=abc</a>
<a href='link.asp'>link.asp</a>
<a href='mappe/link.asp'>mappe/link.asp</a>
<a href='/mappe/link.asp'>/mappe/link.asp</a>


//Absolutte links
<a href='www.link.asp'>Ignore Absolute Link</a><br>
<a href='http://www.link.asp'>Ignore Absolute Link</a><br>
<a href='http://www.link.asp?'>Ignore Absolute Link</a><br>
<a href='http://www.link.asp?asd=123'>Ignore Absolute Link</a><br>


Spørgsmåler er så: hvordan konstruere jeg en regex som:
1. Ikke matcher links som er absolutte

Klacificeres:
2. Links med  Querystring
3. Links uden Querystring

Følgende RegEx finder links MED querystring, men matcher også en Absolut
(<a.+?href=['\"])(.*?[?].+?)(['\"]>)

dvs. jeg mangler at matche en "relativ" href="?" og href="link.asp" (samt dem med mapper ??)


spørgsmålet er om jeg bør snuppe HELE URL'en og parse den, eller om jeg kan grabbe dem jeg skal konvertere med RegExp



NB. jeg arbejder i C# ...
Avatar billede barklund Nybegynder
26. september 2007 - 16:06 #1
Det førstnævnte "absolutte" link er ikke absolut. Laver du et link som <a href='www.link.asp'>Ignore Absolute Link</a><br>, så bliver det et almindeligt relativt link til en side i samme mappe som nuværende, der hedder "www.link.asp". Du skal have http:// foran for at det bliver til et absolut link.

Men når det kommer til stykket, har C# garanteret en "URI" klasse, som kan dette. Derfor bør du bare lave et regexp, der fanger alle url'er, så smider du dem én af gangen igennem URI-klassen, og dem der har et scheme (http:// eller noget andet forrest) er absolutte og skal ikke håndteres - resten håndterer du.

Vil du lave det i regexp kan det skam også lade sig gøre, hvis C#'s regexp-engine understøtter (variabel-længde) look-aheads:

... href="((?!\w+://).*?)" ...

Så vil du kun fange href's, der ikke starter med "abc://" - altså http://, https://, mailto:// og så videre.
Avatar billede montago Praktikant
26. september 2007 - 17:45 #2
Link1 er jo absolut i den henseende at man med target=_blank kan bruge den, men ellers ikke virke specielt prof...

men jeg tror lige jeg kigger lidt på hvad URI kan og ikke kan...
Avatar billede nielle Nybegynder
26. september 2007 - 19:10 #3
> "hvis C#'s regexp-engine understøtter (variabel-længde) look-aheads:"

Det gør den skam.
Avatar billede nielle Nybegynder
26. september 2007 - 19:28 #4
En løsning baseret på negative lookahead:

            string portalUrl = "http://www.portal.dk/";

            Regex re = new Regex(@"(href=[""'])(?!(https?|ftp)://)");
            text = re.Replace(text, "$1" + portalUrl);
Avatar billede barklund Nybegynder
26. september 2007 - 19:38 #5
Man kan (vel) helt undlade at matche noget :)

    Regex re = new Regex(@"(?<!href=[""'])(?!(https?|ftp)://)");
    text = re.Replace(text, portalUrl);

Og så eventuelt efter behov tilføje flere schemes som mailto og andet godt :)

--
Morten Barklund
Avatar billede nielle Nybegynder
26. september 2007 - 19:49 #6
Den gaår bare lidt amok ;^)

        static void Main(string[] args)
        {
            Test(@"<a href='www.link.asp'>");
            Test(@"<a href='http://www.link.asp'>Ignore Absolute Link</a><br>");
            Test(@"<a href='https://www.link.asp'>Ignore Absolute Link</a><br>");
            Test(@"<a href='ftp://www.link.asp'>Ignore Absolute Link</a><br>");
        }

        private static void Test(string text)
        {
            string portalUrl = "http://www.portal.dk/";

            // Regex re = new Regex(@"(href=[""'])(?!(https?|ftp)://)");
            // text = re.Replace(text, "$1" + portalUrl);

            Regex re = new Regex(@"(?<!href=[""'])(?!(https?|ftp)://)");
            text = re.Replace(text, portalUrl);

            Console.WriteLine(text);
        }
Avatar billede barklund Nybegynder
26. september 2007 - 20:09 #7
Nu har jeg slet ikke C#, så jeg snakker rent hypotetisk - det burde kunne lade sig gøre for en pæn implementation :)
Avatar billede barklund Nybegynder
26. september 2007 - 20:09 #8
Hov vent, det er jo et negativt lookbehind - det skal være positivt:

    Regex re = new Regex(@"(?<=href=[""'])(?!(https?|ftp)://)");
    text = re.Replace(text, portalUrl);

Sådan :)
Avatar billede nielle Nybegynder
26. september 2007 - 20:12 #9
Funker :^)
Avatar billede montago Praktikant
26. september 2007 - 22:06 #10
okay i to :D

dét jeg skal, er at extende den eventuelle querystring med "&GID=" + GadgetID
sådan at "<a href='?test=123'>?test=123</a>" bliver til "<a href='?test=123&GID=GA1'>?test=123</a>"

de tilfælde hvor URL'indeholder en sti eller fil, skal disse pilles ud
"<a href='link.asp?test=abc'>link.asp?test=abc</a>" --> til -->
<a href='?PAGE=link.asp?test=abc&GID=GA1'>link.asp?test=abc</a>

har på fornemmelsen at det nemmeste er at lave en parser

URL = /urlen som blev fundet/
if( URL.match("http://") )
  return url
else{
  if( url.match(...
  ... //osv
}


eller mener i at man kan lave en RegExp ONCE AND FOR ALL :D - THE Ultimate REGEXP !
Avatar billede nielle Nybegynder
26. september 2007 - 22:11 #11
Det er sjældent en god idet at forsøge at lave en regexp som skal klare aaaalt - den bliver ugennemskuelig, og svær at vedligeholde. Hellere løse opgaven a enkelte relativt simple skridt.
Avatar billede montago Praktikant
26. september 2007 - 22:21 #12
BTW... har kigget på URI... synes ikke objectet giver mulighed for meget andet end validering af et link... men har nok ikke kigget nok...
Avatar billede barklund Nybegynder
27. september 2007 - 01:22 #13
Jeg vil nu sige, at ovenstående er ret tæt på at være komplet, men da du skal håndtere url'er med søgestreng forskelligt fra url'er uden (og faktisk skal huske at sætte parameteren ind inden et eventuel fragment og samtidig skal håndtere url'er til en anden side forskelligt fra url'er med udelukkende søgestreng, så skal du nok have tre regexp's i gang der håndterer de tre situationer forskelligt. Men det er nu stadig ret simpelt og overkommeligt :)

Altså først et udtryk til at fange url'er med kun søgestreng:

    Regex re = new Regex(@"(href=['""]\?[^'""#]+)([^'""]*['""])");
    text = re.Replace(text, "$1&GID=GA1$2);

Derefter url'er til andre sider uden scheme forrest og uden søgestreng (men med eventuelt fragment):

    Regex re = new Regex(@"(href=['""](?!(https?|ftp)://)[^'""?#]+)([^'""?]*['""])");
    text = re.Replace(text, "?PAGE=$1?GID=GA1$2);

Og til sidst links til andre sider uden scheme forrest med søgestreng (og med eventuelt fragment) - og her vil vi så heller ikke fange dem, der allerede er omskrevet til "?PAGE=...":

    Regex re = new Regex(@"(href=['""](?!(https?|ftp)://|\?PAGE=)[^'""#]+)([^'""]*['""])");
    text = re.Replace(text, "?PAGE=$1?GID=GA1$2);

Det er bestemt ikke testet, men det burde være i den rigtige retning :)

--
Morten Barklund
Avatar billede montago Praktikant
27. september 2007 - 09:26 #14
Hey Barklund !

umidlbart virker din løsning med 3 regex' -- dog med et mindre problem... Regex'en æder href=" og smadre linksn'e til --> <a ?page=....">

problemet løses vel ved at indsætte ')(' et sted og justere $ i replace... ser lige om jeg selv kan... men post endelig et svar, hvor problemet er løst :) så kan du også få point.
Avatar billede montago Praktikant
27. september 2007 - 09:34 #15
Jeg kom lige til at tænke på...

man kan jo bruge lookahead til at se om linket allerede ER blevet fixet...
Avatar billede barklund Nybegynder
27. september 2007 - 09:40 #16
montago, det gør jeg jo også i den sidste - lookahead til at se om linket starter med ?PAGE=

Derudover troede jeg nu, at jeg havde fået styr på parenteserne - kan se det gik galt i den sidste:

Regex re = new Regex(@"(href=['""])((?!(https?|ftp)://|\?PAGE=)[^'""#]+)([^'""]*['""])");
text = re.Replace(text, "$1?PAGE=$2?GID=GA1$3);

Sådan går det, når man bare skriver lige ud i luften og ikke lige kan teste det :)
Avatar billede montago Praktikant
27. september 2007 - 10:17 #17
Løste faktisk det hele selv... på en mere simpel facon... syntes ikke rigtigt jeg kunne få RegEx til at spille ordenligt

    private static string FixGadgetURLs(string HTML, string GadgetID)
    {
        Regex URLPattern = new Regex("(<a.+?href=['\"])(.+?)(['\"].*?>)");
        HTML = URLPattern.Replace(HTML, new MatchEvaluator(Gadget.UrlFormatter));
       
        Regex GIDPattern = new Regex(@"(\[%GID%\d\])");
        HTML = GIDPattern.Replace(HTML,"GID="+GadgetID);

        return HTML;
    }
    static string UrlFormatter(Match m)
    {
        string URL = m.Groups[2].ToString();

        if (Regex.Match(URL, "^[?]$").Success)
        {// -- ?
            return m.Groups[1] + URL + "[%GID%1]" + m.Groups[3];
        }
        else if (Regex.Match(URL, "^[?].+?").Success)
        {// -- ?query=value
            return m.Groups[1] + URL + "&[%GID%2]" + m.Groups[3];
        }
        else if (Regex.Match(URL, "(https?|ftp|mailto)(://)(.+?)").Success)
        {// -- Http://domain.com/path/file.ext?query=value
            return m.ToString();
        }
        else if (Regex.Match(URL, ".+?[?].+?").Success)
        {// -- file.ext?query.value
            return m.Groups[1] + "?PATH=" + URL.Replace("?", "&") + "&[%GID%3]" + m.Groups[3];
        }
        else
        {// -- /path/file.ext
            return m.Groups[1] + "?PATH=" + URL + "&[%GID%4]" + m.Groups[3];
        }
    }
Avatar billede Ny bruger Nybegynder

Din løsning...

Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.

Loading billede Opret Preview
Kategori
Kurser inden for grundlæggende programmering

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester