Обработайте содержимое Jekyll, чтобы заменить первое вхождение любого заголовка поста гиперссылкой поста с этим заголовком. - PullRequest
0 голосов
/ 01 июля 2018

Что я пытаюсь сделать

Я создаю плагин Jekyll ruby, который заменит первое вхождение любого слова в текстовом содержимом пост-копии гиперссылкой, ссылающейся на URL-адрес поста с тем же именем.

Проблемы у меня

Я получил это на работу, но я не могу понять две проблемы в методе process_words:

  1. Как искать только заголовок сообщения в основном тексте копии сообщения, а не метатеги перед сообщением или оглавлением (которое также генерируется перед текстом копии основного сообщения)? Я не могу заставить это работать с Нокигири, хотя это, кажется, инструмент выбора здесь.
  2. Если URL-адрес поста не равен post.data['url'], где он находится?
  3. Кроме того, есть ли более эффективный и чистый способ сделать это?

Текущий код работает, но заменит первое вхождение, даже если это значение атрибута HTML, такого как якорь или метатег.

Пример результата

У нас есть блог с 3 сообщениями:

  • Хобби
  • Еда
  • Велосипеды

А в основном тексте поста "Хобби" у нас есть предложение, в котором каждое слово впервые появляется в посте, например:

I love mountain biking and bicycles in general. 

Плагин обработает это предложение и выведет его как:

I love mountain biking and <a href="https://example.com/link/to/bicycles/">bicycles</a> in general. 

Мой текущий код (ОБНОВЛЕНО 1)

# _plugins/hyperlink_first_word_occurance.rb
require "jekyll"
require 'uri'


module Jekyll

    # Replace the first occurance of each post title in the content with the post's title hyperlink
    module HyperlinkFirstWordOccurance
        POST_CONTENT_CLASS = "page__content"
        BODY_START_TAG = "<body"
        ASIDE_START_TAG = "<aside"
        OPENING_BODY_TAG_REGEX = %r!<body(.*)>\s*!
        CLOSING_ASIDE_TAG_REGEX = %r!</aside(.*)>\s*!

        class << self
            # Public: Processes the content and updates the 
            # first occurance of each word that also has a post
            # of the same title, into a hyperlink.
            #
            # content - the document or page to be processes.
            def process(content)
                @title = content.data['title']
                @posts = content.site.posts

                content.output = if content.output.include? BODY_START_TAG
                                    process_html(content)
                                else
                                    process_words(content.output)
                                end
            end


            # Public: Determines if the content should be processed.
            #
            # doc - the document being processes.
            def processable?(doc)
                (doc.is_a?(Jekyll::Page) || doc.write?) &&
                    doc.output_ext == ".html" || (doc.permalink&.end_with?("/"))
            end


            private

            # Private: Processes html content which has a body opening tag.
            #
            # content - html to be processes.
            def process_html(content)
            content.output = if content.output.include? ASIDE_START_TAG
                    head, opener, tail = content.output.partition(CLOSING_ASIDE_TAG_REGEX)
                            else
                    head, opener, tail = content.output.partition(POST_CONTENT_CLASS)
                            end
                body_content, *rest = tail.partition("</body>")

                processed_markup = process_words(body_content)

                content.output = String.new(head) << opener << processed_markup << rest.join
            end

            # Private: Processes each word of the content and makes
            # the first occurance of each word that also has a post
            # of the same title, into a hyperlink.
            #
            # html = the html which includes all the content.
            def process_words(html)
                page_content = html
                @posts.docs.each do |post|
                    post_title = post.data['title'] || post.name
                    post_title_lowercase = post_title.downcase
                    if post_title != @title
                        if page_content.include?(" " + post_title_lowercase + " ") ||
                            page_content.include?(post_title_lowercase + " ") ||
                            page_content.include?(post_title_lowercase + ",") ||
                            page_content.include?(post_title_lowercase + ".")
                            page_content = page_content.sub(post_title_lowercase, "<a href=\"#{ post.url }\">#{ post_title.downcase }</a>")
                        elsif page_content.include?(" " + post_title + " ") ||
                            page_content.include?(post_title + " ") ||
                            page_content.include?(post_title + ",") ||
                            page_content.include?(post_title + ".")
                            page_content = page_content.sub(post_title, "<a href=\"#{ post.data['url'] }\">#{ post_title }</a>")
                        end
                    end
                end
                page_content
            end
        end
    end
end


Jekyll::Hooks.register %i[posts pages], :post_render do |doc|
  # code to call after Jekyll renders a post
  Jekyll::HyperlinkFirstWordOccurance.process(doc) if Jekyll::HyperlinkFirstWordOccurance.processable?(doc)
end

Обновление 1

Обновил мой код по совету @Keith Mifsud. Теперь с помощью элемента aside боковой панели или класса page__content выберите основной текст для работы.

Также улучшена проверка и замена правильного термина.

PS: Пример базового кода, с которого я начал работать над своим плагином, был @ Keith Mifsud jekyll-target-blank plugin

1 Ответ

0 голосов
/ 01 июля 2018

этот код выглядит очень знакомым :) Я предлагаю вам посмотреть тестовый файл Rspecs для проверки ваших проблем: https://github.com/keithmifsud/jekyll-target-blank

Я постараюсь ответить на ваши вопросы, извините, я сам не смог проверить их на момент написания.

Как искать только заголовок сообщения в основном тексте копии сообщения, а не метатеги перед сообщением или оглавлением (которое также создается перед текстом основного сообщения)? Я не могу заставить это работать с Нокигири, хотя это, кажется, инструмент выбора здесь.

Ваши требования здесь:

1) Игнорировать содержимое вне тегов <body></body>.

Кажется, это уже реализовано в методе process_html(). Этот метод устанавливает единственный процесс body_content, и он должен работать как есть. У вас есть тесты для этого? Как вы отлаживаете это? Такое же разбиение строк работает в моем плагине. То есть обрабатывается только содержимое внутри тела.

2) Игнорировать содержимое в оглавлении (TOC). Я предлагаю вам расширить метод process_html(), разделив переменную body_content. Найдите содержимое между открывающим и закрывающим тегами вашего оглавления (по идентификатору, классу CSS и т. Д.) И исключите его, затем добавьте обратно на свою позицию до или после строки process_words.

3) Использовать ли плагин Nokigiri? Этот плагин отлично подходит для разбора HTML. Я думаю, что вы разбираете строки, а затем создаете HTML. Так что ванильного Ruby и URI-плагина должно хватить. Вы все еще можете использовать его, если хотите, но это не будет быстрее, чем расщепление строк в ruby.

Если URL-адрес поста не указан в post.data ['url'], где он находится?

Я думаю, вы должны иметь метод, чтобы получить все заголовки всех сообщений, а затем сопоставить слова со списком. Вы можете получить всю коллекцию постов из самого документа doc.site.posts и каждый пост вернуть название. Метод process_words() может проверять каждую работу, чтобы увидеть, соответствует ли она элементу из массива. Но что, если заголовок состоит из более чем одного слова?

Кроме того, есть ли более эффективный и чистый способ сделать это?

Пока все хорошо. Я начну с исправления проблем, а затем рефакторинг для стандартов скорости и кодирования.

Опять же, я предлагаю вам использовать тестирование, чтобы помочь вам в этом.

Дайте мне знать, если я могу помочь больше:)

...