class Nokogiri::XML::Document def reqt_iter(&block) xpath("//xmlns:requirement | //xmlns:recommendation | //xmlns:permission") .each_with_object({}, &block) end end module Metanorma class Requirements class Modspec < Default def recommendation_label(elem, type, xrefs) @xrefs ||= xrefs.dup init_lookups(elem.document) label = elem.at(ns("./identifier"))&.text if inject_crossreference_reqt?(elem, label) recommendation_label_xref(elem, label, xrefs, type) else type = recommendation_class_label(elem) super end end def recommendation_label_xref(elem, label, xrefs, type) id = @reqtlabels[label] number = xrefs.anchor(id, :modspec, false) number.nil? and return type elem.ancestors("requirement, recommendation, permission").empty? and return number "#{number}" end def init_lookups(doc) return if @init_lookups @init_lookups = true @reqtlabels = reqtlabels(doc) @reqt_ids = reqt_ids(doc) @reqt_links_class = reqt_links_class(doc) @reqt_links_test = reqt_links_test(doc) @reqt_id_base = reqt_id_base(doc) truncate_id_base_outside_reqts(doc) end def reqtlabels(doc) doc.reqt_iter do |r, m| l = r.at(ns("./identifier"))&.text and m[l] = r["id"] end end # embedded reqts xref to reqts via label lookup def inject_crossreference_reqt?(node, label) !node.ancestors("requirement, recommendation, permission").empty? and @reqtlabels[label] end def recommendation_class_label(node) case node["type"] when "verification" then @labels["modspec"]["conformancetest"] when "class" then @labels["modspec"]["#{node.name}class"] when "abstracttest" then @labels["modspec"]["abstracttest"] when "conformanceclass" then @labels["modspec"]["conformanceclass"] else case node.name when "recommendation" then @labels["default"]["recommendation"] when "requirement" then @labels["default"]["requirement"] when "permission" then @labels["default"]["permission"] end end end def reqt_ids(docxml) docxml.reqt_iter do |r, m| id = r.at(ns("./identifier")) or next m[id.text] = { id: r["id"], lbl: @xrefs.anchor(r["id"], :modspec, false) } end end def reqt_links_test(docxml) docxml.reqt_iter { |r, m| reqt_links_test1(r, m) } end def reqt_links_test1(reqt, acc) %w(conformanceclass verification).include?(reqt["type"]) or return subj = reqt_extract_target(reqt) id = reqt.at(ns("./identifier")) or return lbl = @xrefs.anchor(@reqt_ids[id.text.strip][:id], :modspec, false) (subj && lbl) or return acc[subj.text] = { lbl: lbl, id: reqt["id"] } end def reqt_extract_target(reqt) reqt.xpath(ns("./classification[tag][value]")).detect do |x| x.at(ns("./tag")).text.casecmp("target").zero? end&.at(ns("./value")) end def reqt_extract_id_base(reqt) reqt.xpath(ns("./classification[tag][value]")).detect do |x| x.at(ns("./tag")).text.casecmp("identifier-base").zero? end&.at(ns("./value")) end def recommendation_link_test(ident) test = @reqt_links_test[ident&.strip] or return nil "#{test[:lbl]}" end # we have not implemented multiple levels of nesting of classes def reqt_links_class(docxml) docxml.reqt_iter do |r, m| %w(class conformanceclass).include?(r["type"]) or next id = r.at(ns("./identifier")) or next r.xpath(ns("./requirement | ./recommendation | ./permission")) .each do |r1| m = reqt_links_class1(id, r, r1, m) end end end def reqt_links_class1(id, parent_reqt, reqt, acc) id1 = reqt.at(ns("./identifier")) or return acc lbl = @xrefs.anchor(@reqt_ids[id.text.strip][:id], :modspec, false) lbl or return acc acc[id1.text] = { lbl: lbl, id: parent_reqt["id"] } acc end def reqt_hierarchy_extract @reqt_links_class.each_with_object({}) do |(k, v), m| m[v[:id]] ||= [] m[v[:id]] << @reqt_ids[k][:id] end end def reqt_id_base_init(docxml) docxml.reqt_iter { |r, m| m[r["id"]] = reqt_extract_id_base(r)&.text } end def reqt_id_base_inherit(ret, class2reqt) ret.each_key do |k| class2reqt[k]&.each do |k1| ret[k1] ||= ret[k] end end ret end def reqt_id_base(docxml) ret = reqt_id_base_init(docxml) ret = reqt_id_base_inherit(ret, reqt_hierarchy_extract) @modspecidentifierbase or return ret ret.each_key { |k| ret[k] ||= @modspecidentifierbase } end def recommendation_link_class(ident) test = @reqt_links_class[ident&.strip] or return nil "#{test[:lbl]}" end def recommendation_id(ident) test = @reqt_ids[ident&.strip] or return ident&.strip "#{test[:lbl]}" end def recommendation_backlinks_test(node, id, ret) (%w(general class).include?(node["type"]) && xref = recommendation_link_test(id.text)) or return ret lbl = node["type"] == "general" ? "conformancetest" : "conformanceclass" ret << [@labels["modspec"][lbl], xref] ret end def recommendation_backlinks_class(node, id, ret) (node["type"].nil? || node["type"].empty? || node["type"] == "verification") and xref = recommendation_link_class(id.text) and ret << [@labels["modspec"]["included_in"], xref] ret end def truncate_id_base_outside_reqts(docxml) @modspecidentifierbase or return (docxml.xpath(ns("//xref[@style = 'id']")) - docxml .xpath(ns("//requirement//xref | //permission//xref | " \ "//recommendation//xref"))).each do |x| @reqt_id_base[x["target"]] or next # is a modspec requirement x.children = x.children.to_xml.delete_prefix(@modspecidentifierbase) end end def rec_subj(node) case node["type"] when "class" then @labels["modspec"]["targettype"] else @labels["default"]["subject"] end end def rec_target(node) case node["type"] when "class" then @labels["modspec"]["targettype"] when "conformanceclass" then @labels["modspec"]["requirementclass"] when "verification", "abstracttest" then @labels["default"]["requirement"] else @labels["modspec"]["target"] end end def recommend_class(node) case node["type"] when "verification", "abstracttest" then "recommendtest" when "class", "conformanceclass" then "recommendclass" else "recommend" end end def recommend_name_class(node) if %w(verification abstracttest).include?(node["type"]) "RecommendationTestTitle" else "RecommendationTitle" end end def recommend_component_label(node) c = case node["class"] when "test-purpose" then "Test purpose" when "test-method" then "Test method" else node["class"] end @labels["default"][c] || @labels["modspec"][c] || Metanorma::Utils.strict_capitalize_first(c) end end end end