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 mają 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>
…po stronie strony internetowej wyświetli się to:
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. Zamiast szukać w 500 wpisach 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, że 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:(.*?)\](.*?)\
jestem wstanie teraz chwycisz wszystkie wystąpienia tego znacznika w bardzo łatwy sposób.
Pierwsza grupa symbolizująca tytuł obramowania znajduję po dwukropku. Druga grupa znajduje się pomiędzy tagami.
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() + @":(.*?)\](.*?)\
",
(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.