Orchard CMS: добавляем поддержку shortcodes

Программирование

Tagged Under : , ,

Shortcodes должны быть известны пользователям блогов на WordPress: с помощью шорткодов можно добавить свой функционал прямо в текст статьи. Это, на мой взгляд, необходимость.

Например, плагин подсветки CodeColorer, который использую я. Аналогичный функционал понадобился бы при реализации спойлер-блоков, как, например, в статье на сайте kanobu.ru. Применений можно найти множество.

Данной возможности в Orchard CMS по умолчанию не наблюдается: там всё по заранее настроенному порядку, т.е. один тип контента нельзя вставить внутрь другого, нельзя добавить несколько одинаковых типов контента к статье (допустим несколько галерей). Это, естественно, ограничивает разработчиков и, как следствие, конечных пользователей.

Итак, задача, которую нужно прежде всего решить, это программно переопределить Parts.Common.Body.cshtml таким образом, чтобы в ней обрабатывались шорткоды.

Пошагово процесс выглядит следующим образом:

  • Добавить класс, описывающий шорткод
  • Реализовать класс, через который можно будет добавлять шорткоды и методы их обрабатывающие. Реализовать в рамках класса простейший парсер шорткодов и отрисовку
  • Переопределить Parts.Common.Body.cshtml

Создадим модуль и откроем его проект в Visual Studio или другой среде разработки. В папку Modules добавляем класс ShortCodeItem со следующим содержимым:

using System;
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;
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 проекта копию оригинального файла и меняем его содержимое на следующее:

@using kosfiz.Shortcodes.Models;
@{
    var body = ShortCodeService.Render(Model.Html.ToString());
}
@body

Далее добавляем в папку Models класс ShortCodeShapeProvider

using System;
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");
                }
            });
        }
    }
}

Вот собственно и всё. Пример создания модуля использующего шорткоды описан в статье «Ссылка на модуль




Оставить комментарий