123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873(*********************************************************************************)(* Xtmpl *)(* *)(* Copyright (C) 2012-2021 Institut National de Recherche en Informatique *)(* et en Automatique. All rights reserved. *)(* *)(* This program is free software; you can redistribute it and/or modify *)(* it under the terms of the GNU Lesser General Public License version *)(* 3 as published by the Free Software Foundation. *)(* *)(* This program is distributed in the hope that it will be useful, *)(* but WITHOUT ANY WARRANTY; without even the implied warranty of *)(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *)(* GNU Library General Public License for more details. *)(* *)(* You should have received a copy of the GNU Lesser General Public *)(* License along with this program; if not, write to the Free Software *)(* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA *)(* 02111-1307 USA *)(* *)(* Contact: Maxence.Guesdon@inria.fr *)(* *)(* *)(*********************************************************************************)(** Templating XML trees.
XML {!type:tree}s which are rewritten using {!type:callback} rules
provided by the {!type:env} environment.
A complete description of the templating rules is available in the
{!section:engine} section below.
*)moduleSMap=Xml.SMapmoduleName_set=Xml.Name_setmoduleStr=Re.Str(** {2 XML documents} *)moduleAttributes=Xml.Name_mapmodulerecP:Types.PwithmoduleAttributes=Xml.Name_mapandtypeattr_value=X.treelist=structmoduleAttributes=Xml.Name_maptypeattr_value=X.treelisttypedata=unitletcompare_name=Xml.Name.compareletcompare_attr_value=List.compareX.compare_treeletcompare_data__=0letdefault_data()=()letversion_name()=("","version")letdefault_version()=[X.cdata"1.0"]letdefault_attr_value()=[X.cdata""]letpp_nameppfname=Format.pp_print_stringppf(Xml.string_of_namename)letpp_attr_value=X.pp_treesletpp_attributes:(Format.formatter->attr_valueAttributes.t->unit)option=Some(funppfatts->!PP.pp_attributes_rppfatts)endandX:Types.Swithtypename=Attributes.keyandtypeattr_value=P.attr_valueandtypeattributes=P.attr_valueAttributes.tandtypedata=unit=Types.Make(P)andPP:sig(**/**)valpp_attributes_r:(Format.formatter->X.attributes->unit)ref(**/**)end=structletpp_attributes_r:(Format.formatter->X.attributes->unit)ref=ref(fun__->assertfalse)endinclude(X)(** Empty map of attributes *)letatts_empty:attributes=Attributes.empty(** {2 Errors} *)(** To catch eventual infinite loops in rewriting, we keep a
stack of the rules called. *)typerewrite_stack=(name*attributes*treelist*Types.locoption)list(** String representation of the given rewrite stack. *)letstring_of_rewrite_stackl=letb=Buffer.create 256inletf((prefix,t),atts,subs,loc)=Buffer.add_stringb"==================\n";Buffer.add_stringb("Apply <"^prefix^":"^t^">\nAttributes:");Attributes.iter(fun(p,s)v->Buffer.add_stringb"\n ";ifp<>""thenBuffer.add_stringb(p^":");Printf.bprintfb"%s=%S "s(to_stringv))atts;Buffer.add_stringb"\nSubs=\n";List.iter(funxml->Buffer.add_stringb(to_string[xml]))subs;Buffer.add_stringb"\n"inList.iterf(List.revl);Buffer.contentsbtypeTypes.error+=|Loopofrewrite_stack(** The [Loop] error is raised when the rewrite stack
is higher than a default value of [100]. This value can be changed
by setting the [XTMPL_REWRITE_DEPTH_LIMIT] environment variable.
*)|Parse_attribute_errorofTypes.locoption*Xml.name*string|Invalid_attribute_valueofstring*treelist|Fixpoint_limitofintletloop_errorstack=Types.error(Loopstack)letparse_attribute_errorlocnamemsg=Types.error(Parse_attribute_error(loc,name,msg))letinvalid_attribute_valueattv=Types.error(Invalid_attribute_value(att,v))letfixpoint_limitn=Types.error(Fixpoint_limitn)letstring_of_error=functionLoopstack->letmsg="Max rewrite depth reached -- possible loop ?\nRewrite stack:\n"^(string_of_rewrite_stackstack)inSomemsg|Parse_attribute_error(loc,name,msg)->Some(Printf.sprintf"%sParse error in attribute %S: %s"(matchlocwithNone->""|Someloc->(Types.string_of_locloc)^": ")(Xml.string_of_namename)msg)|Invalid_attribute_value(att,v)->Some(Printf.sprintf"invalid value of attribute %s: %s"att(to_stringv))|Fixpoint_limitn->Some(Printf.sprintf"Xtmpl fixpoint iteration limit reached (%d)"n)|_->Nonelet()=Types.register_string_of_errorstring_of_error(** {2 Special tag and attributes} *)(** The environment tag, currently ["env_"].
See the template rules in the {!section:engine} section below for more
information about this tag.
*)lettag_env="env_"(** The defer attribute, currently ["defer_"]. See the engine section
for details. *)letatt_defer="defer_"(** The escamp attribute, currently ["escamp_"]. This attribute
is used when converting XML to string or reading XML from a string.
The CDATA associated to this attribute indicates the other attributes
in which the ampersands must be escaped (when parsing XML from an
attribute string) or unescaped (when printing XML to an attribute string).
This is useful for urls with &, for example in [<a href="...">] nodes.
Example: In [<a escamp_="href", href="http://foo.fr?v1=3amp;v2=4">...</a>].
As attributes are parsed as XML, setting the [escamp_] attribute to ["href"]
will make the ampersand escaped. The attribute is kept during rewriting.
When the XML tree will be converted to a string, the [escamp_] attribute
will be removed. Several attribute names can be indicated, using [',']
or [';'] as separator, as in
[ <a escamp_="href, foo, gee:buz" href="..." ...>...</a> ].
*)letatt_escamp="escamp_"(** The protect attribute, currently "protect_". See the engine section
for details. This attribute is removed when a XML tree is converted
to a string, as for {!att_escamp}.
*)letatt_protect="protect_"(** {2 Mapping to {!Xml} documents} *)(**/**)letre_escape=Str.regexp"&\\(\\([a-z]+\\)\\|\\(#[0-9]+\\)\\);"letescape_ampersands=letlen=String.lengthsinletb=Buffer.createleninfori=0tolen-1domatchs.[i]with'&'whenStr.string_matchre_escapesi->Buffer.add_charb'&'|'&'->Buffer.add_stringb"&"|c->Buffer.add_charbcdone;Buffer.contentsbletre_amp=Str.regexp_string"&"letunescape_ampersands=Str.global_replacere_amp"&"sletgen_atts_to_escape=letkey=("",att_escamp)infunget_attto_satts->letspec=get_attattskeyinmatchspecwithNone->Xml.Name_set.empty|Somex->lets=to_sxinletl=Misc.split_strings[',';';']inList.fold_right(funsset->lets=Misc.strip_stringsinletname=matchMisc.split_strings[':']with[]|[_]->("",s)|p::q->(p,String.concat":"q)inXml.Name_set.addnameset)lXml.Name_set.emptyletatts_to_escape=gen_atts_to_escapeXml.get_att(fun(x,_loc)->x)letxml_atts_to_escape=gen_atts_to_escapeget_att(function[Ds]->s.Types.text|_->failwith("Invalid value for attribute "^att_escamp))moduleTo_xml=structmoduleMapper_to_xml=Types.Make_map(X)(Xml)letmap_namename=nameletmap_datad=dletmapper_to_xml~xml_atts()=letmap_xmls=ref(fun_->assertfalse)inletrecmap_attr_valuexmls=(to_stringxmls,None)andto_stringtrees=Xml.to_string(!map_xmlstrees)andmap_attributesatts=letatts_to_escape=xml_atts_to_escapeattsinletescampname=Name_set.memnameatts_to_escapeinAttributes.fold(map_attribute~escamp)attsXml.Name_map.emptyandmap_attribute~escampnamexmls(map:Xml.attributes)=matchnamewith|("",s)whens=att_escamp->map|("",s)whens=att_protect->map|_->lets=to_stringxmlsinlets=ifescampnamethenunescape_ampersandselsesinlets=ifxml_attsthenselseXml.unescapeXml.default_parse_paramsinXml.atts_one~atts:mapname(s,None)andmapper()=letparam=Types.{map_name;map_data;map_attributes=Somemap_attributes;map_attr_value=map_attr_value;}inMapper_to_xml.mapperparaminletmapper=mapper()inmap_xmls:=mapper.Types.map_xmls;mapperletto_xml_atts_true=mapper_to_xml~xml_atts:true()letto_xml_atts_false=mapper_to_xml~xml_atts:false()leton_to_xml_mapperf=fun?(xml_atts=true)x->f(ifxml_attsthento_xml_atts_trueelseto_xml_atts_false)xletto_xml_attributes=on_to_xml_mapper(funto_xmlatts->to_xml.map_attsatts)letto_string?(xml_atts=true)xmls=ifxml_attsthenXml.to_string(to_xml_atts_true.map_xmlsxmls)elseMisc.string_of_ppX.pp_treesxmlsletdoc_to_string?(xml_atts=true)doc=ifxml_attsthenXml.doc_to_string(to_xml_atts_true.map_docdoc)elseMisc.string_of_ppX.pp_docdocletto_xml=on_to_xml_mapper(funto_xmld->to_xml.map_xmld)letto_xmls=on_to_xml_mapper(funto_xmld->to_xml.map_xmlsd)letto_doc=on_to_xml_mapper(funto_xmld->to_xml.map_docd)letto_prolog=on_to_xml_mapper(funto_xmlp->to_xml.map_prologp)endlet()=letf:Format.formatter->attributes->unit=letpp_attr?firstppf~escampnamexmls=matchnamewith|("",s)whens=att_escamp->()|("",s)whens=att_protect->()|_->lets=X.to_stringxmlsinlets=ifescampnamethenunescape_ampersandselsesinlets=Xml.unescapeXml.default_parse_paramsin(* Xml.pp_attr will escape the string *)Xml.pp_attr?firstppfname(s,None)infunppfatts->matchAttributes.bindingsattswith|[]->()|(name,value)::q->letatts_to_escape=xml_atts_to_escapeattsinletescampname=Name_set.memnameatts_to_escapeinpp_attr~first:trueppf~escampnamevalue;List.iter(fun(n,v)->pp_attrppf~escampnv)qinPP.pp_attributes_r:=f(**/**)(** Convert to a {!Xml.tree}.
Optional argument [xml_atts] indicates whether the code in attributes remains valid XML
or not. Default is [true] but it should be set to [false] when outputting
final documents like XHTML pages. *)letto_xml=To_xml.to_xml(** Same as {!to_xml} but for a list of trees. *)letto_xmls=To_xml.to_xmls(** Same as {!to_xml} but for a map of attributes. *)letto_xml_attributes=To_xml.to_xml_attributes(** Same as {!to_xml} but for a doc. *)letto_doc=To_xml.to_doc(** Same as {!to_xml} but for a document prolog. *)letto_prolog=To_xml.to_prolog(** {2 Mapping from a {!Xml} documents} *)(**/**)moduleFrom_xml=structmoduleMapper_to_xml=Types.Make_map(Xml)(X)letmap_namename=nameletmap_datad=dletmapper_from_xml()=letmap_xmls=ref(fun_->assertfalse)inletrecmap_attributes:Xml.attributes->attributes=funatts->letto_escape=atts_to_escapeattsinXml.Name_map.fold(funname(s,loc)acc->letpos_start=Option.mapfstlocinletescamp=Name_set.memnameto_escapeinlets=ifescampthenescape_ampersandselsesintryletxmls=!map_xmls(Xml.from_string?pos_starts)inatts_one~atts:accnamexmlswith|Types.Error(Xml.Parse_error(loc,msg))->Xml.parse_errorlocmsg|e->letmsg=Printf.sprintf"%s\n%s"(Printexc.to_stringe)sinparse_attribute_errorlocnamemsg)attsAttributes.emptyandmap_attr_value:Xml.attr_value->attr_value=fun(str,_)->!map_xmls(Xml.from_stringstr)andmapper()=letparam=Types.{map_name;map_data;map_attributes=Somemap_attributes;map_attr_value=map_attr_value;}inMapper_to_xml.mapperparaminletmapper=mapper()inmap_xmls:=mapper.Types.map_xmls;mapperletmapper=mapper_from_xml()letdoc_from_string?pos_startstr=mapper.map_doc(Xml.doc_from_string?pos_startstr)letfrom_string?pos_startstr=mapper.map_xmls(Xml.from_string?pos_startstr)letfrom_xml=mapper.map_xmlletfrom_xmls=mapper.map_xmlsletfrom_xml_attributes=mapper.map_attsletfrom_doc=mapper.map_docletfrom_prolog=mapper.map_prologend(**/**)(** Convert from a {!Xml.tree} list. Attribute values must
be valid XML.*)letfrom_xml=From_xml.from_xml(** Same as {!from_xml} but for a list of trees. *)letfrom_xmls=From_xml.from_xmls(** Convert {!Xml.type-attributes} to {!attributes}. *)letfrom_xml_attributes=From_xml.from_xml_attributes(** Convert from a {!Xml.type-doc}. Attribute values must be valid XML.*)letfrom_doc=From_xml.from_doc(** Convert from a {!Xml.type-prolog}. *)letfrom_prolog=From_xml.from_prolog(** {2 Input/output} *)(** Output a tree list to a string.
See {!to_xml} about [xml_atts] argument.*)letto_string=To_xml.to_string(** Output an XML document to a string.
See {!to_xml} about [xml_atts] argument.*)letdoc_to_string=To_xml.doc_to_string(** Parses a string as {!tree} list. *)letfrom_string=From_xml.from_string(** Parses a {!type:doc}. *)letdoc_from_string=From_xml.doc_from_string(** Same as {!from_string} but reads from a file. *)letfrom_filefile=from_xmls(Xml.from_filefile)(** Same as {!doc_from_string} but reads from a file. *)letdoc_from_filefile=from_doc(Xml.doc_from_filefile)(** {2 Utils} *)(** Same as {!get_att} but return a string [s] only if [name] is bound to
a single CDATA XML node ([[D s]]).
In particular, if [name] is bound to a list of XML tree, or to
a single tree which is not a CDATA, the function returns [None].
*)letget_att_cdataattsname=matchget_attattsnamewith|Some[Ds]->Somes.Types.text|Somexmls->Some(to_stringxmls)|_->None(** Same as {!opt_att} but looking for CDATA bounded values, as in
{!get_att_cdata}.
*)letopt_att_cdataatts?(def="")name=matchget_att_cdataattsnamewithNone->def|Somes->s(** [upto_first_element trees] returns the list of trees until
the first [E] element, included.
@raise Not_found if there is no element in the list.
*)letupto_first_element=letreciteracc=function|[]->raiseNot_found|(E_)asxml::_->List.rev(xml::acc)|xml::q->iter(xml::acc)qinfunl->iter[]l(** Same as {!merge_cdata} but taking a {!tree} list. *)letmerge_cdata_list=letrecfacc=function[]->List.revacc|(Dd1)::(Dd2)::q->letd=D(Types.merge_cdatad1d2)infacc(d::q)|((D_)asx)::q->f(x::acc)q|Enode::q->letsubs=f[]node.subsinf(E{nodewithsubs}::acc)q|xml::q->f(xml::acc)qinfunl->f[]l(** Recursively merge sibling [D] nodes into one [D] node. *)letmerge_cdatat=matchtwith|Enode->E{nodewithsubs=merge_cdata_listnode.subs}|xml->xml(** {2 Environment}
An {!type:env} is a {!type:name}-to-{!type:callback} associative map. In addition
to basic manipulation functions, the functions {!val:env_add_xml} and
{!val:env_of_list} provide convenient shortcuts for common operations.
The environments are immutable, all mutating operations return new
environments.
*)type'aenv={env_ns:Iri.tSMap.t;env_map:('acallback)Attributes.t;}and'acallback='a->'aenv->?loc:Types.loc->attributes->treelist->'a*treelist(** An environment that contains no binding.*)letenv_empty()={env_ns=SMap.empty;env_map=Attributes.empty}(** This exception can be raised by callbacks to indicate that the
node to be rewritten remains unchanged. *)exceptionNo_change(**/**)letenv_resolveenvname=matchnamewith("",str)->("",str)|(ns,str)->matchSMap.find_optnsenv.env_nswith|None->(ns,str)|Someiri->letstr=Printf.sprintf"%s%s"(Iri.to_stringiri)strin("",str)letprotect_in_envenvatts=matchget_attatts("",att_protect)withNone->env|Some[Ds]->letfenvs=matchMisc.split_strings[':']with[]->env|[s]|["";s]->{envwithenv_map=Attributes.remove("",s)env.env_map}|s1::q->lets2=String.concat":"qinletk=env_resolveenv(s1,s2)in{envwithenv_map=Attributes.removekenv.env_map}inList.fold_leftfenv(Misc.split_strings.Types.text[',';';'])|Somel->invalid_attribute_valueatt_protectl(**/**)(** Add a binding to an environment.
[env_add_cb "double" (fun acc _ _ xml -> (acc, xml @ xml))] binds the key
[("", "double")] to a callback that doubles an XML subtree.
[env_add_cb ~prefix: "foo" "double" (fun acc _ _ xml -> (acc, xml @ xml))] does the same but
for the key [("foo", "double")].
If the same key was already bound, the previous binding is replaced.
Opional argument [prefix] is [""] by default.
*)letenv_add_cb?(prefix="")namecbenv=letk=env_resolveenv(prefix,name)in{envwithenv_map=Attributes.addkcbenv.env_map}(** Bind a callback that returns some XML.
The most frequent operation performed by a callback is to return
constant XML subtrees. This convenience function lets you provide
the XML subtrees.
[env_add_xml "logo" [ E (("","img"), atts_one ("","src") [D "logo.png"], []) ] env]
binds the key [("","logo")] to a callback that returns an XHTML image tag.
Optional argument [prefix] can be used to specify a prefix for the rule name. Default is [""].
*)letenv_add_xml?prefixavenv=env_add_cb?prefixa(fundata_?loc__->data,v)env(** Get a binding from an environment.
If the binding is not found, returns [None].
*)letenv_getkenv=letk=env_resolveenvkinAttributes.find_optkenv.env_map(** Add several bindings at once.
This convenience function saves you the effort of calling
{!val:env_add_cb} several times yourself.
[env_of_list ~env:env [ (ns1, k1), f1 ; (ns2, k2), f2 ]] is equivalent to
[env_add_cb ~prefix: ns1 k1 f1 (env_add_cb ~prefix: ns2 k2 f2 env)]. This means that one key
is present twice in the list, the first association in the list
will hide the second one in the resulting environment.
The [env] optional argument is the environment to which bindings are added. If
not provided, {!val:env_empty}[ ()] is used.
*)letenv_of_list?(env=env_empty())l=List.fold_right(fun((prefix,name),f)env->env_add_cb~prefixnamefenv)lenv(** String representation of all the keys in the environment. *)letstring_of_envenv=letf(prefix,name)_acc=lets=matchprefixwith""->name|s->s^":"^nameins::accinString.concat", "(Attributes.foldfenv.env_map[])(** {2:engine Templating engine}
The [apply_*] functions apply a given environment and data to XML tree(s). These
trees are given as parameter ({!apply_to_xmls}) or can be read from a
file ({!apply_to_file}), or a string ({!apply_to_string}).
The functions return the result of the rewrite as XML trees, or
can write it to a file ({!apply_into_file}). They also return data
as the result of the callbacks called, as in a classic fold function (callbacks
take the data in parameter, as the environment, the attributes and subnodes
of the rewritten node).
The rewrite rules are applied until a fix-point is reached.
If the [XTMPL_FIXPOINT_LIMIT] environment variable contains a valid integer [n],
it is used as a fix-point limit: if no fix-point is reached in [n] iterations,
then a [Failure] exception is raised.
{ol
{- A single iteration descends recursively into the XML tree. If an
element has a callback associated in the environment, then the
callback is applied to the current data and the node's attributes and children.
{i Example}: consider the following XML:
{v <album author="Bertrand Betsch" name="Orange Bleue Amère">
<track>Vivre</track>
<track>Tant mal que mal</track>
</album> v}
This would look for a callback bound to [("","album")] in the environment
and call it using
[callback data env {("","author")->[ D "Bertrand Betsch"]|("","name")->[D "Orange Bleue Amère"]} xml]
where [env] is the current environment and [xml] represents the
two children [ <track>..</track> ] elements.
}
{- The callback returns a pair composed of (maybe new) data
and a new list of elements that is used instead of the old element.
{i Example}: assuming that the environnement was build using
[env_add_cb "x2" (fun data _ _ xml -> (data, xml @ xml)) env],
then [<x2>A</x2>] is rewritten as [AA].
}
{- The engine then recursively descends into those replaced
elements (this means that a poorly conceived rule set may well never
terminate).
{i Example}: [<x2><x2>A</x2></x2>] is first rewritten as
[<x2>A</x2><x2>A</x2>], and then as [AAAA].
}
{- The [env_] element (see {!val:tag_env} is a special case: it is automatically
replaced with its children (as if its callback was [(fun data _ _ xml -> (data, xml))]).
[env_] effectively changes the environment
used when processing its children by adding the bindings defined by
its attributes (using {!val:env_add_xml}).
{i Example}: [<env_ a="<b>A</b>"><a/></env_>] is
replaced by [<a/>], which in turn is replaced by
[<b>A</b>].
}
{- If an element has a [defer_] attribute (that is greater
than zero), then it is not processed and the attribute is decremented
by one, and the process recursively applies to its children.
{i Example}: [<x2 defer_="1"><x2>A</x2></x2>] is rewritten as
[<x2 defer_="0">AA</x2>]. The {b next} iteration will effectively apply the
rule to the node and return [AAAA].
}
{- If an element has a [protect_] attribute, then the value
must be CDATA and contains a list of names to remove from the environment
when descending in the children. The names are separated by [','] or [';'],
for example: [<foo protect_="title,id,foo:bar" ..>...</foo>].
}
}
*)(**/**)letlimit=trySome(int_of_string(Sys.getenv"XTMPL_FIXPOINT_LIMIT"))with_->Noneletmax_rewrite_depth=tryint_of_string(Sys.getenv"XTMPL_REWRITE_DEPTH_LIMIT")with_->100letpushstacktag?locattssubs=letstack=(tag,atts,subs,loc)::stackinifList.lengthstack>max_rewrite_depththenloop_errorstackelsestackletset_namespaces=letfnamevenv=matchnamewith("xmlns",ns)->beginlets=to_string~xml_atts:falsevinletiri=Iri.of_stringsin{envwithenv_ns=SMap.addnsirienv.env_ns}end|_->envinfunenvatts->Attributes.foldfattsenvletreceval_envstackdataenv?locattssubs=(* prerr_endline
(Printf.sprintf "env: subs=%s"
(String.concat "" (List.map string_of_xml subs)));
*)letenv=Attributes.fold(fun(prefix,s)vacc->(* prerr_endline (Printf.sprintf "env: %s=%s" s v);*)env_add_xml~prefixsvacc)attsenvineval_xmlsstackdataenvsubsandeval_xmlsstackdataenvxmls=let(data,l)=List.fold_left(fun(data,acc)xml->let(data,subs)=eval_xmlstackdataenvxmlin(data,subs::acc))(data,[])xmlsin(data,List.flatten(List.revl))andeval_atts=letfstackenvnamexmls(data,map)=let(data,xmls)=eval_xmlsstackdataenvxmlsin(data,Attributes.addnamexmlsmap)infunstackdataenvatts->Attributes.fold(fstackenv)atts(data,Attributes.empty)andeval_xmlstackdataenvxml=matchxmlwith|D_|C_|PI_->(data,[xml])|E{name;atts;subs;loc}->let(data,atts)=eval_attsstackdataenvattsinletenv=set_namespacesenvattsinletenv_protect=protect_in_envenvattsinmatchnamewith("",t)whent=tag_env->letstack=pushstacknameattssubsineval_envstackdataenv_protect?locattssubs|(prefix,tag)->matchenv_get(prefix,tag)envwith|Somef->let(defer,atts)=matchget_att_cdataatts("",att_defer)withNone->(0,atts)|Somes->tryletn=int_of_stringsin(n,Attributes.remove("",att_defer)atts)with_->(0,atts)inifdefer>0then(* defer evaluation, evaluate subs first *)(let(data,subs)=eval_xmlsstackdataenv_protectsubsinletatts=Attributes.add("",att_defer)[cdata(string_of_int(defer-1))]attsin(data,[node?loc(prefix,tag)~attssubs]))else(letxml=tryletstack=pushstack(prefix,tag)?locattssubsinSome(stack,fdataenv_protect?locattssubs)withNo_change->NoneinmatchxmlwithNone->(* no change in node, eval children anyway *)let(data,subs)=eval_xmlsstackdataenv_protectsubsin(data,[node?loc(prefix,tag)~attssubs])|Some(stack,(data,xmls))->(*prerr_endline
(Printf.sprintf "=== Evaluated tag %s -> %s\n"
tag (String.concat "" (List.map string_of_xml xmls)));*)eval_xmlsstackdataenv_protectxmls)(* eval f before subs *)|None->let(data,subs)=eval_xmlsstackdataenv_protectsubsin(data,[node?loc(prefix,tag)~attssubs])and(eval_string:rewrite_stack->'a->'aenv->string->'a*string)=funstackdataenvs->letxmls=from_stringsinlet(data,xmls)=eval_xmlsstackdataenvxmlsin(data,to_stringxmls)letrecfix_point_snd?(n=0)f(data,x)=matchlimitwithSomelwhenn>=l->fixpoint_limitl|_->let(data,y)=f(data,x)inify=xthen(data,x)elsefix_point_snd~n:(n+1)f(data,y)(**/**)(** As {!val:apply_to_string}, but applies to a list of XML trees.*)letapply_to_xmlsdataenvxmls=(*prerr_endline (string_of_env env);*)letf(data,xmls)=eval_xmls[]dataenvxmlsinfix_point_sndf(data,xmls)(** As {!val:apply_to_string}, but applies to a single XML tree.*)letapply_to_xmldataenvxml=apply_to_xmlsdataenv[xml](** As {!val:apply_to_string}, but applies to a doc. *)letapply_to_docdataenvd=let(data,elements)=apply_to_xmlsdataenvd.elementsin(data,docd.prologelements)(** Applies as many iterations as necessary to a piece of XML (represented
as an unparsed string) to reach a fix-point.
See {!section-engine} for how an iteration is applied.
*)let(apply_to_string:'a->'aenv->string->'a*treelist)=fundataenvs->letxmls=from_stringsinapply_to_xmlsdataenvxmls(** As {!val:apply_to_string}, but reads the XML from a file. *)letapply_to_filedataenvfile=letxmls=from_filefileinapply_to_xmlsdataenvxmls(** As {!val:apply_to_file}, but writes the result back to a file.
For instance, [apply_to_file data env ~infile:"source.xml" ~outfile: "dest.xml"].
When provided, optional argument [head] is prepended
to the XML that is output to the file. By default, nothing is prepended.
*)letapply_into_filedata?headenv~infile~outfile=let(data,xmls)=apply_to_filedataenvinfileinlets=to_stringxmlsinlets=matchheadwithNone->s|Someh->h^sinMisc.file_of_string~file:outfiles;data(** As {!val:apply_into_file}, but read the XML from a string instead of a file.
*)letapply_string_into_filedata?headenv~outfiles=let(data,xmls)=apply_to_stringdataenvsinlets=to_stringxmlsinlets=matchheadwithNone->s|Someh->h^sinMisc.file_of_string~file:outfiles;data