module Metanorma
class Collection
class Renderer
# Resolves references to other files in the collection. Three routines:
# 1. Eref to a document that has been split into multiple documents
# (sectionsplit) are resolved to direct eref to the split document
# 2. Indirect erefs to a file anchor in an unknwon file in the collection
# (bibitem[@type = 'internal'] ) are resolved to direct eref to the
# containing document
# 3. Direct erefs to other files in collection
# (repo(current-metanorma-collection/x) are resolved to hyperlinks
# @param file [String] XML content
# @param identifier [String] docid
# @param internal_refs [Hash{String=>Hash{String=>String}] schema name to
# anchor to filename
# @return [String] XML content
def update_xrefs(file, docid, internal_refs)
xml, sso = update_xrefs_prep(file, docid)
@nested || sso or
Metanorma::Collection::XrefProcess::xref_process(xml, xml, nil, docid,
@isodoc)
@ncnames = {}
@nested or update_indirect_refs_to_docs(xml, docid, internal_refs)
@files.add_document_suffix(docid, xml)
@nested or update_sectionsplit_refs_to_docs(xml, internal_refs)
update_direct_refs_to_docs(xml, docid)
hide_refs(xml)
sso and eref2link(xml)
@nested or svgmap_resolve(xml, docid)
xml.to_xml
end
def update_xrefs_prep(file, docid)
docxml = file.is_a?(String) ? Nokogiri::XML(file, &:huge) : file
supply_repo_ids(docxml)
sso = @files.get(docid, :sectionsplit_output)
[docxml, sso]
end
def update_sectionsplit_refs_to_docs(docxml, internal_refs)
Util::gather_citeases(docxml).each do |k, v|
(@files.get(k) && @files.get(k, :sectionsplit)) or next
opts = { key: @files.get(k, :indirect_key),
source_suffix: docxml.root["document_suffix"],
target_suffix: @files.get(k, :document_suffix) }
refs = v.each_with_object({}) do |eref, m|
update_sectionsplit_eref_to_doc(eref, internal_refs, m, opts)
end
add_hidden_bibliography(docxml, refs)
end
end
ANCHOR_XPATH = "xmlns:locality[@type = 'anchor']/xmlns:referenceFrom"
.freeze
def update_sectionsplit_eref_to_doc(eref, internal_refs, doclist, opts)
a = eref.at("./xmlns:localityStack/#{ANCHOR_XPATH}") or return
doc = internal_refs[opts[:key]]["#{a.text}_#{opts[:target_suffix]}"]
bibitemid = Metanorma::Utils::to_ncname("#{doc}_#{opts[:source_suffix]}")
eref["bibitemid"] = bibitemid
doclist[bibitemid] ||= doc
doclist
end
BIBITEM_NOT_REPO_XPATH = "//bibitem[not(ancestor::bibitem)]" \
"[not(./docidentifier[@type = 'repository'])]".freeze
def supply_repo_ids(doc)
doc.xpath(ns(BIBITEM_NOT_REPO_XPATH)).each do |b|
b.xpath(ns("./docidentifier")).each do |docid|
id = @isodoc.docid_prefix(docid["type"], docid.children.to_xml)
@files.get(id) or next
@files.get(id, :indirect_key) and next # will resolve as indirect key
docid.next = docid_xml(id)
end
end
end
# repo(current-metanorma-collection/ISO 17301-1:2016)
# replaced by bibdata of "ISO 17301-1:2016" in situ as bibitem.
# Any erefs to that bibitem id are replaced with relative URL
# Preferably with anchor, and is a job to realise dynamic lookup
# of localities.
def update_direct_refs_to_docs(docxml, identifier)
erefs, erefs_no_anchor, anchors, erefs1 =
update_direct_refs_to_docs_prep(docxml)
docxml.xpath(ns("//bibitem")).each do |b|
docid = b.at(ns("./docidentifier[@type = 'repository']")) or next
strip_unresolved_repo_erefs(identifier, docid, erefs1, b) or next
update_bibitem(b, identifier)
docid = docid_to_citeas(b) or next
erefs[docid] and
update_anchors(b, docid, erefs[docid], erefs_no_anchor[docid],
anchors[docid])
end
end
# Hash(docid) of arrays
def update_direct_refs_to_docs_prep(docxml)
erefs = Util::gather_citeases(docxml)
no_anchor = erefs.keys.each_with_object({}) { |k, m| m[k] = [] }
anchors = erefs.keys.each_with_object({}) { |k, m| m[k] = [] }
erefs.each do |k, v|
v.each do |e|
if loc = e.at(".//#{ANCHOR_XPATH}") then anchors[k] << loc
else no_anchor[k] << e end
end
end
[erefs, no_anchor, anchors, Util::gather_bibitemids(docxml)]
end
# strip erefs if they are repository erefs, but do not point to a document
# within the current collection. This can happen if a collection consists
# of many documents, but not all are included in the current collection.
# Do not do this if this is a sectionsplit collection or a nested manifest.
# Return false if bibitem is not to be further processed
def strip_unresolved_repo_erefs(_document_id, bib_docid, erefs, bibitem)
%r{^current-metanorma-collection/(?!Missing:)}.match?(bib_docid.text) and
return true
@nested and return false
erefs[bibitem["id"]]&.each { |x| x.parent and strip_eref(x) }
false
end
# Resolve erefs to a container of ids in another doc,
# to an anchor eref (direct link)
def update_indirect_refs_to_docs(docxml, _docidentifier, internal_refs)
bib, erefs, doc_suffix, doc_type, f = update_indirect_refs_prep(docxml)
internal_refs.each do |schema, ids|
add_suffix = doc_suffix && doc_type && doc_type != schema
ids.each do |id, file|
f[file] ||= { url: @files.url?(file),
parentid: @files.get(file) && @files.get(file,
:parentid) }
k = indirect_ref_key(schema, id, doc_suffix, add_suffix)
update_indirect_refs_to_docs1(f[file], k, file, bib, erefs)
end
end
end
def update_indirect_refs_prep(docxml)
@updated_anchors = {}
@indirect_keys = {}
[Util::gather_bibitems(docxml), Util::gather_bibitemids(docxml),
docxml.root["document_suffix"], docxml.root["type"], {}]
end
def indirect_ref_key(schema, id, doc_suffix, doc_type)
/^#{schema}_/.match?(id) and return id
key = [schema, id, doc_suffix, doc_type].join("::")
x = @indirect_keys[key] and return x
ret = "#{schema}_#{id}"
doc_suffix && doc_type && doc_type != schema and
ret = "#{ret}_#{doc_suffix}"
@indirect_keys[key] = ret
ret
end
def indirect_ref_key(schema, id, doc_suffix, add_suffix)
/^#{schema}_/.match?(id) and return id
#key = "#{schema}_#{id}"
x = @indirect_keys.dig(schema, id) and return x
@indirect_keys[schema] ||= {}
@indirect_keys[schema][id] = if add_suffix
"#{schema}_#{id}_#{doc_suffix}"
else
"#{schema}_#{id}"
end
end
def indirect_ref_keyx(schema, id, doc_suffix, doc_type)
/^#{schema}_/.match?(id) and return id
ret = "#{schema}_#{id}"
doc_suffix && doc_type && doc_type != schema and
ret = "#{ret}_#{doc_suffix}"
ret
end
def update_indirect_refs_to_docs1(filec, key, file, bibitems, erefs)
erefs[key]&.each do |e|
e["citeas"] = file
update_indirect_refs_to_docs_anchor(e, file, filec[:url],
filec[:parentid])
end
update_indirect_refs_to_docs_docid(bibitems[key], file)
end
def update_indirect_refs_to_docs_anchor(eref, file, url, parentid)
a = eref.at(".//#{ANCHOR_XPATH}") or return
parentid and file = "#{parentid}_#{file}"
existing = a.text
anchor = if url then existing
else
#suffix_anchor_indirect(existing, suffix)
#k = "#{existing}_#{file}"
#@ncnames[k] ||= Metanorma::Utils::to_ncname(k)
@indirect_keys[existing] ||= {}
@indirect_keys[existing][file] ||= Metanorma::Utils::to_ncname("#{existing}_#{file}")
end
@updated_anchors[existing] or a.children = anchor
@updated_anchors[anchor] = true
end
def update_indirect_refs_to_docs_docid(bib, file)
docid = bib&.at(ns("./docidentifier[@type = 'repository']")) or return
docid.children = "current-metanorma-collection/#{file}"
docid.previous =
"#{file}"
end
# bottleneck
def update_anchors(bib, docid, erefs, erefs_no_anchor, erefs_anchors)
@files.get(docid) or error_anchor(erefs, docid)
has_anchors, url, ncn_docid = update_anchors_prep(docid)
erefs_no_anchor.each do |e|
update_anchor_create_loc(bib, e, docid)
end
!url && has_anchors or return
erefs_anchors.each do |e|
update_anchors1(docid, ncn_docid, e)
end
end
def update_anchors1(docid, ncn_docid, anchor)
@concat_anchors[anchor.text] ||= "#{ncn_docid}_#{anchor.text}"
if @files.get(docid).dig(:anchors_lookup, @concat_anchors[anchor.text])
anchor.content = @concat_anchors[anchor.text]
end
end
def update_anchors_prep(docid)
@concat_anchors = {}
[@files.get(docid)&.key?(:anchors_lookup), @files.url?(docid),
Metanorma::Utils::to_ncname(docid)]
end
# encode both prefix and suffix to NCName
def suffix_anchor_indirect(prefix, suffix)
k = "#{prefix}_#{suffix}"
@ncnames[k] ||= Metanorma::Utils::to_ncname(k)
end
# if there is a crossref to another document, with no anchor, retrieve the
# anchor given the locality, and insert it into the crossref
def update_anchor_create_loc(_bib, eref, docid)
ins = eref.at(ns("./localityStack")) or return
type = ins.at(ns("./locality/@type"))&.text
type = "clause" if type == "annex"
ref = ins.at(ns("./locality/referenceFrom"))&.text
a = @files.get(docid, :anchors).dig(type, ref) or return
ins << "#{a.sub(/^_/, '')}" \
""
end
end
end
end