-- recipe-microdata Soupault Plugin -- SPDX-FileCopyrightText: 2026 toastal -- SPDX-License-Identifier: MPL-2.0 -- -- Applies Schema.org TechArticle markup to cookbook recipes. Scoped to the -- cookbook section via widget config; values come from the page itself -- (head metas + h1), so no Nickel table is needed: -- article#Article → mainEntity TechArticle -- h1 → headline (kept: valid on TechArticle via CreativeWork) -- head metas → description / keywords / author stub / datePublished -- site default → license link (custom_options.metadata_default) -- Plugin.require_version("5.0.0") function head_meta_content(name) local meta = HTML.select_one(page, format("head meta[name=\"%s\"]", name)) if HTML.is_element(meta) then return HTML.get_attribute(meta, "content") end return nil end function hidden_meta(itemprop, content, parent) local elem = HTML.create_element("meta") HTML.set_attribute(elem, "itemprop", itemprop) HTML.set_attribute(elem, "content", content) HTML.append_child(parent, elem) return elem end function hidden_link(itemprop, href, parent) local elem = HTML.create_element("link") HTML.set_attribute(elem, "itemprop", itemprop) HTML.set_attribute(elem, "href", href) HTML.append_child(parent, elem) return elem end function author_stub(parent, name) local span = HTML.create_element("span") HTML.set_attribute(span, "itemprop", "author") HTML.set_attribute(span, "itemscope", "") HTML.set_attribute(span, "itemtype", "https://schema.org/Person") hidden_meta("name", name, span) HTML.append_child(parent, span) return span end article = HTML.select_one(page, "article#Article") if not HTML.is_element(article) then -- The cookbook section index has no article: nothing to do there. feed = HTML.select_one(page, "ul.Feed-items") if HTML.is_element(feed) then Plugin.exit("Cookbook index page, no recipe to mark up") end Plugin.fail(format("No article#Article found in “%s”, cannot apply TechArticle scope", page_file)) end main = HTML.select_one(page, "main#Main") if not HTML.is_element(main) then Plugin.fail(format("No main#Main found in “%s”, skipping TechArticle scope", page_file)) end HTML.set_attribute(article, "itemscope", "") HTML.set_attribute(article, "itemprop", "mainEntity") HTML.set_attribute(article, "itemtype", "https://schema.org/TechArticle") h1 = HTML.select_one(main, "h1[itemprop=\"headline\"]") if not HTML.is_element(h1) then h1 = HTML.select_one(main, "h1") end if HTML.is_element(h1) then headline = HTML.create_element("meta") HTML.set_attribute(headline, "itemprop", "headline") HTML.set_attribute(headline, "content", String.trim(HTML.strip_tags(h1))) HTML.prepend_child(main, headline) else Log.warning(format("No h1 found in “%s”, skipping WebPage.headline compensation", page_file)) end description = head_meta_content("description") if Value.is_string(description) and description ~= "" then hidden_meta("description", description, article) end keywords = head_meta_content("keywords") if Value.is_string(keywords) and keywords ~= "" then hidden_meta("keywords", keywords, article) end author = head_meta_content("author") if Value.is_string(author) and author ~= "" then author_stub(article, author) end published = head_meta_content("dcterms.date") if Value.is_string(published) and published ~= "" then hidden_meta("datePublished", published, article) end license_url = nil metadata_default = soupault_config["custom_options"]["metadata_default"] if Value.is_table(metadata_default) then license = metadata_default["license"] if Value.is_table(license) then license_url = license["url"] end end if Value.is_string(license_url) and license_url ~= "" then hidden_link("license", license_url, article) else Log.warning("custom_options.metadata_default.license.url is missing, skipping recipe license") end