123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171(*
* SPDX-FileCopyrightText: 2024 The Forester Project Contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*)openForester_coreopenstructmoduleT=Typesend(* The idea is to render a search result with surrounding context, say 5 words
on each side. *)typepath=intlistletshow_leaf_node:T.contentT.content_node->string=funnode->matchnodewith|T.Texts->s|T.CDATAs->s|T.Urii|T.Route_of_urii->Format.asprintf"%a"URI.ppi|T.Xml_elt_|T.Transclude_|T.Contextual_number_|T.Section_|T.KaTeX(_,_)|T.Link_|T.Artefact_|T.Datalog_script_|T.Results_of_datalog_query_->raise@@Invalid_argument(Format.asprintf"%a is not a leaf node"T.(pp_content_nodepp_content)node)letget_nth_wordistring=Str.(split@@regexp"[^a-zA-Z0-9]+")string|>List.filter_map(funs->letlower=String.lowercase_asciisinifnot@@Tokenizer.(Set.memlowercommon_words)thenSomelowerelseNone)|>(funl->List.nthli)letrender_context_list:(path->'a->string)->path->'alist->string=funfpathl->matchpathwith|[]->String.concat""@@List.map(f[])l|i::path'->letn=List.nthliinfpath'nletrecrender_context_frontmatter:path->T.contentT.frontmatter->string=funpathfrontmatter->matchpathwith|[]->raise(Invalid_argument"stopped on non-leaf node")|0::_path->Format.(asprintf"%a"(pp_print_optionURI.pp)frontmatter.uri)|1::path'->beginmatchpath'with|[]->Option.value~default:""@@Option.mapT.show_contentfrontmatter.title|path->Option.value~default:""@@Option.map(render_context_contentpath)frontmatter.titleend|2::path'->beginmatchpath'with|[]->assertfalse|_path->assertfalseend(*frontmatter.dates*)|3::path'->beginmatchpath'with|[]->assertfalse|_path->assertfalseend(*frontmatter.attributions*)|4::path'->beginmatchpath'with|[]->assertfalse|path->Option.value~default:""@@Option.map(render_context_contentpath)frontmatter.taxonend(*frontmatter.taxon*)|5::path'->beginmatchpath'with|[]->assertfalse|_path->assertfalseend(*frontmatter.number*)|6::path'->beginmatchpath'with|[]->assertfalse|_path->assertfalseend(*frontmatter.designated_parent*)|7::path'->beginmatchpath'with|[]->assertfalse|_path->assertfalseend(*frontmatter.source_path*)|8::path'->beginmatchpath'with|[]->assertfalse|_path->assertfalseend(*frontmatter.tags*)|9::path'->beginmatchpath'with|[]->assertfalse|_path->assertfalseend(*frontmatter.metas*)|_->raise(Invalid_argument"out of bound index")andrender_context_node:path->T.contentT.content_node->string=funpathnode->matchpathwith|[]->show_leaf_nodenode|i::path'->matchnodewith|T.Texts->get_nth_wordis|T.CDATA_->raise@@Invalid_argument"can't descend into CDATA node"|T.Xml_eltelt->render_context_contentpathelt.content|T.Transclude_->assertfalse|T.Contextual_number_->assertfalse|T.Section_->assertfalse|T.KaTeX(_,_)->assertfalse|T.Linklink->render_context_contentpath'link.content|T.Artefact_->assertfalse|T.Uri_->assertfalse|T.Route_of_uri_->assertfalse|T.Datalog_script_->assertfalse|T.Results_of_datalog_query_->assertfalseandrender_context_content:path->T.content->string=funpathcontent->letT.Contentc=contentinmatchpathwith|[]->T.show_contentcontent|i::path'->letnode=List.nthciin(* render_context_node in *)render_context_nodepath'nodeandrender_context_article:path->T.contentT.article->string=funpatharticle->matchpathwith|[]->""|0::path'->render_context_frontmatterpath'article.frontmatter|1::path'->render_context_contentpath'article.mainmatter|_->raise(Invalid_argument"out of bound index")anddebug_context_article:path->string=function|[]->"empty path"|0::_path'->"frontmatter ->"|1::_path'->"mainmatter ->"|_->raise(Invalid_argument"out of bound index")