26
Shortcodes должны быть известны пользователям блогов на WordPress: с помощью шорткодов можно добавить свой функционал прямо в текст статьи. Это, на мой взгляд, необходимость.
Например, плагин подсветки CodeColorer, который использую я. Аналогичный функционал понадобился бы при реализации спойлер-блоков, как, например, в статье на сайте kanobu.ru. Применений можно найти множество.
Данной возможности в Orchard CMS по умолчанию не наблюдается: там всё по заранее настроенному порядку, т.е. один тип контента нельзя вставить внутрь другого, нельзя добавить несколько одинаковых типов контента к статье (допустим несколько галерей). Это, естественно, ограничивает разработчиков и, как следствие, конечных пользователей.
Итак, задача, которую нужно прежде всего решить, это программно переопределить Parts.Common.Body.cshtml таким образом, чтобы в ней обрабатывались шорткоды.
Пошагово процесс выглядит следующим образом:
- Добавить класс, описывающий шорткод
- Реализовать класс, через который можно будет добавлять шорткоды и методы их обрабатывающие. Реализовать в рамках класса простейший парсер шорткодов и отрисовку
- Переопределить Parts.Common.Body.cshtml
Создадим модуль и откроем его проект в Visual Studio или другой среде разработки. В папку Modules добавляем класс ShortCodeItem со следующим содержимым:
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace kosfiz.Shortcodes.Models
{
public class ShortCodeItem
{
private string codeName = string.Empty;
public string CodeName
{
get
{
return codeName;
}
set
{
codeName = value;
}
}
private int startIndex;
public int StartIndex
{
get
{
return startIndex;
}
set
{
startIndex = value;
}
}
private int endIndex;
public int EndIndex
{
get
{
return endIndex;
}
set
{
endIndex = value;
}
}
public string TargetText
{
get;
set;
}
public string SourceText
{
get;
set;
}
}
}
На следующем шаге добавляем ShortCodeService, который и будет выполнять основные функции.
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
namespace kosfiz.Shortcodes.Models
{
public class ShortCodeService
{
//здесь будем хранить все шорткоды и методы, их обрабатывающие
static Dictionary<string, Func<Dictionary<string, object>, string>> Methods = new Dictionary<string, Func<Dictionary<string, object>, string>>();
//метод добавления шорткода
public static void AddShortCode(string ShortCodeName, Func<Dictionary<string, object>, string> method)
{
if (!Methods.ContainsKey(ShortCodeName)) //если шорткод уже есть, то игнорируем его
Methods.Add(ShortCodeName, method);
}
//выполняем метод соответсвующий имени шорткода и передаём ему параметры, сторонний метод должен принимать на выход словарь строка = объект
private static string DoMethod(string ShortCodeName, Dictionary<string, object> atts)
{
return Methods[ShortCodeName].Invoke(atts);
}
//из текста выбирает шорткоды
private static List<ShortCodeItem> GetShortCodes(string Text)
{
List<ShortCodeItem> codes = new List<ShortCodeItem>();
foreach (var item in Methods)
{
try
{
string startTag = string.Format("[{0}", item.Key);
string endTag = string.Format("[/{0}]", item.Key);
int startIndex = Text.IndexOf(startTag);
while (startIndex != -1)
{
int endIndex = Text.IndexOf(endTag, startIndex);
if (endIndex != -1)
{
int paramsEndIndex = Text.IndexOf("]", startIndex);
if (paramsEndIndex != -1)
{
string source = Text.Substring(startIndex + startTag.Length, paramsEndIndex - startIndex - startTag.Length);
Dictionary<string, object> atts = GetValues(source, paramsEndIndex + 1, endIndex - paramsEndIndex - 1, Text);
codes.Add(new ShortCodeItem { CodeName = item.Key, StartIndex = startIndex, EndIndex = endIndex, TargetText = DoMethod(item.Key, atts), SourceText = Text.Substring(startIndex, endIndex + endTag.Length - startIndex) });
}
}
startIndex = Text.IndexOf(startTag, startIndex + 1);
}
}
catch (Exception) { }
}
return codes;
}
//формируем словарь параметров шорткода
private static Dictionary<string, object> GetValues(string source, int InnerStart, int InnerEnd, string InnerText)
{
Dictionary<string, object> values = new Dictionary<string, object>();
int paramNameIndex = source.IndexOf("=");
int lastParamValueIndex = 0;
while (paramNameIndex != -1)
{
string attrName = source.Substring(0, paramNameIndex);
string attrValue = string.Empty;
if (source[paramNameIndex + 1] != '"')
{
lastParamValueIndex = source.IndexOf(" ", paramNameIndex + 1);
if (lastParamValueIndex == -1)
lastParamValueIndex = source.Length - 1;
attrValue = source.Substring(paramNameIndex + 1, lastParamValueIndex - paramNameIndex);
}
else
{
lastParamValueIndex = source.IndexOf("\"", paramNameIndex + 2);
if (lastParamValueIndex == -1)
lastParamValueIndex = source.Length - 1;
attrValue = source.Substring(paramNameIndex + 2, lastParamValueIndex - paramNameIndex - 2);
}
values.Add(attrName.Trim(), attrValue.Trim());
source = source.Remove(0, lastParamValueIndex);
paramNameIndex = source.IndexOf("=");
}
//InnerHtml предопределённый параметр, содержит содержимое тегов шорткода [shortcodeName]InnerHtml[/shortcodeName]
values.Add("InnerHtml", InnerText.Substring(InnerStart, InnerEnd).Trim());
return values;
}
//заменяем теги шорткодов на html сгенерированный модулями
public static MvcHtmlString Render(string Text)
{
List<ShortCodeItem> codes = GetShortCodes(Text);
foreach (var item in codes)
Text = Text.Replace(item.SourceText, item.TargetText);
return MvcHtmlString.Create(Text);
}
}
}
Собственно, парсер обрабатывает шорткод теги вида:
[shortcodeName attr1=value attr2="value with whitespace or =" attr3=value2]different text[/shortcodeName]
Т.е. необходим закрывающий шорткод тег, значения с пробелом или = заключается в двойные кавычки. Содержимое между открывающим и закрывающим шорткодами передаётся во внешний метод по ключу InnerHtml.
Осталось переопределить Parts.Common.Body.cshtml. Для этого добавляем в папку Views проекта копию оригинального файла и меняем его содержимое на следующее:
@{
var body = ShortCodeService.Render(Model.Html.ToString());
}
@body
Далее добавляем в папку Models класс ShortCodeShapeProvider
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Orchard.DisplayManagement.Descriptors;
using Orchard;
using Orchard.ContentManagement;
namespace kosfiz.Shortcodes.Models
{
public class ShortCodeShapeProvider : IShapeTableProvider
{
private readonly IWorkContextAccessor _workContextAccessor;
public ShortCodeShapeProvider(IWorkContextAccessor workContextAccessor)
{
_workContextAccessor = workContextAccessor;
}
public void Discover(ShapeTableBuilder builder)
{
builder.Describe("Parts_Common_Body").OnDisplaying(displaying =>
{
ContentItem item = displaying.Shape.ContentItem;
if (displaying.ShapeMetadata.DisplayType == "Detail")
{
displaying.ShapeMetadata.Alternates.Add("Parts_Common_Body");
}
});
}
}
}
Вот собственно и всё. Пример создания модуля использующего шорткоды описан в статье «Ссылка на модуль






