Tags Ostatnio ulepszając swojego bloga miałem proste zadanie. Chciałem przy pomocy swoich własnych znaczników modyfikować tekst w postach.

Nie jest to nic odkrywczego. Blog WordPress ma dużo swoich własny znaczników i tagów, które maja ułatwić ci wstawanie n.p filmików z Youtube albo TED-a.

Ten blog jest napisany w C# przez mnie, a to znaczy, że jeśli czegoś w nim nie ma to znaczy, że muszę to napisać.

Rezultat moich działań jest następujący. Pisząc

[cloud-fblue:Lampa]Test<p>Test</p>[cg]

…po stronie strony internetowej wyświetli się to:

Lampa
Test

Test

Tworzenie swoich własnych znaczników na swoim blogu potrafi bardzo ujednolicić wygląd strony. W końcu aby zmienić coś będzie musiał tylko zmienić kod generujący i zmieniający tekst. Zamist szukać w 500 pisach danego html diva i go zmieniać ręcznie.

Przygotowanie

Po pierwsze musiałem określić wszystkie kombinacje kolorowych obramowań jakie występują na blogu. Zdefiniowałem 6 kolorów, które wcześniej utworzyłem. Tak nawet kolor Blue 2 :)

public enum CloudImage
{
    Green,
    Orange,
    Blue,
    Black,
    Gray,
    Blue2
}

Każdy z tych kolorów będzie potrzebował swojego znacznika.

public static string GetTagName(this CloudImage en)
{
    switch (en)
    {
        case CloudImage.Green:
            return "cloud-green";
        case CloudImage.Orange:
            return "cloud-orange";
        case CloudImage.Blue:
            return "cloud-blue";
        case CloudImage.Blue2:
            return "cloud-blue2";
        case CloudImage.Black:
            return "cloud-black";
        case CloudImage.Gray:
            return "cloud-gray";
        default:
            return "cloud-green";
    }
}

Na bazie danego typu obramowanie będę potrzebował także określonego stylu CSS, który dodam później. Jak widać 4 lata temu nie miałem pomysły na nazwy stylów: “myfun” xD

public static string GetCssName(this CloudImage en)
{
    switch (en)
    {
        case CloudImage.Green:
            return "myfun";
        case CloudImage.Orange:
            return "mywarring";
        case CloudImage.Blue:
            return "myinfo";
        case CloudImage.Blue2:
            return "myt";
        case CloudImage.Black:
            return "myexam";
        case CloudImage.Gray:
            return "myde";
        default:
            return "cloud-green";
    }
}

Przejdźmy teraz do modyfikacji tekstu.

Jak to zrobiłem : bez regex

Najpierw chciałem spróbować czy jestem wstanie osiągnąć taki rezultat bez użycia regex-a. Odpowiedź brzmi tak, ale rozwiązanie wydało się bardzo tępe.

W pętli foreach przechodzą po wszystkich opcjach obramowań.

foreach (CloudImage suit in Enum.GetValues(typeof(CloudImage)))
{
    result = ReplaceIfCloudExist(result, suit);
}

W pętli while szukam odpowiedniego tagu jak i jego zamknięcia. Szukając właściwych indeksów ze znacznika wyciągam później tytuł obramowania jak i jego zawartość.

public static string ReplaceIfCloudExist(string result, CloudImage cloudName)
{
    while (true)
    {
        string find1 = "[" + cloudName.GetTagName();
        string find2 = "[/"+ cloudName.GetTagName() +"]";

        var rr = result.IndexOf(find1);

        var rr2 = result.IndexOf(find2);

        if (rr == -1 || rr2 == -1)
        {
            break;
        }

        string cont = result.Substring(find1.Count() + rr
            , rr2 + find2.Count() - (find1.Count() + rr));

        int t = cont.IndexOf("]");

        int t2 = cont.IndexOf(find2) + find2.Count();

        string titleC = cont.Substring(1, t - 1);

        string content = cont.Substring(t + 1, cont.Length - (t + 1 + find2.Length));

        string html = CreateCloud(titleC, content, cloudName);

        result = result.ReplaceAt(rr, rr2 + find2.Count() - (rr), html);


    }

    return result;
}

Mała metoda rozszerzająca :

public static string ReplaceAt(this string str, int index,
int length, string replace)
{
    return str.Remove(index, Math.Min(length, str.Length - index))
            .Insert(index, replace);
}

A oto kod tworzący obramowanie.

public static string CreateCloud(string title, string content, CloudImage ci)
{
    string contentTitle = "<div id=\"stitle\">"+title+"</div>";

    string mainTag = "<div class=\"cloud "+ ci.GetCssName() + " \">";

    string img = "<img class=\"cloudimage\" " +
        "style=\"margin: 5px 10px 0 5px\" alt=\"Alternate Text\" src=\"/my/" + ci.GetImageName() +"\">";

    string img2 = "<span class=\"cloudimg \" ></span>";

    string cont = "<span class=\"cloudcontent\" >";

    if (ci == CloudImage.Gray)
        img = "";

    string aa = mainTag + contentTitle + img2 + cont + content + "</span>" + "</div>";

    return aa;

}

Kod ten jednak nie był taki doskonały. Fakt to rozwiązanie działa, ale wymaga aby znaczniki były napisane idealnie.

Ostatecznie wyrzuciłem ten kod do kosza i pomyślałem, ze zapewne dużo ładnie można to napisać używając wyrażeń regularnych czyli Regex.

Wyrażenia regularne górą

Ponownie przechodzę do wszystkich możliwych kombinacji.

foreach (CloudImage item in Enum.GetValues(typeof(CloudImage))) 
{
    result = ChangeCloudImage(result, item);
}

Używając wyrażenia regularnego \[cloud-bluee:(.*?)\](.*?)\[cg]

image

jestem wstanie teraz chwycisz wszystkie wystąpienia tego znacznika w bardzo łatwy sposób.

image

Pierwsza grupa symbolizująca tytuł obramowania znajduję po dwukropku. Druga grupa znajduje się pomiędzy tagami.

image

Tym razem tag “cg” jest tagiem domykającym.

public string ChangeCloudImage(string result,CloudImage cloudImage)
{
    string contentTitle = "<div id=\"stitle\">" + "{0}" + "</div>";

    string mainTag = "<div class=\"cloud " + cloudImage.GetCssName() + " \">";

    string img2 = "<span class=\"cloudimg \" ></span>";

    string cont = "<span class=\"cloudcontent\" >";

    string aa = mainTag + contentTitle + img2 + cont + "{1}" + "</span>" + "</div>";

    result = Regex.Replace(result, @"\[" + cloudImage.GetTagName() + @":(.*?)\](.*?)\[cg]",
        (Match m) => string.Format(aa, m.Groups[1].Value, m.Groups[2].Value));

    return result;
}

Kod jest oczywiście dużo prostszy. W miejscu wykrycia składni muszę po prostu wstawić nowy tekst HTML.

W podobny sposób można stworzyć swój znacznik do wystawania filmów YT.

var video = 
"<div class=\"video\"><iframe src=\"https://www.youtube.com/embed/{0}\"  frameborder=\"0\" allowfullscreen></iframe></div>";

result = Regex.Replace(result, @"\[youtubeyy:(.*?)\]", 
(Match m) => string.Format(video, m.Groups[1].Value));

Wyrażenie regularne mogą więc być bardzo pomocne.