View source with formatted comments or as raw
    1:- module(pwp,
    2          [ pwp_files/2,                % +FileIn, +FileOut
    3            pwp_stream/3,               % +StreamIn, +StreamOut, +Context
    4            pwp_xml/3                   % +DomIn, -DOMOut, +Context
    5          ]).    6:- autoload(library(lists),[append/3]).    7:- autoload(library(readutil),[read_file_to_codes/3]).    8:- autoload(library(sgml),[load_xml_file/2]).    9:- autoload(library(sgml_write),[xml_write/3]).   10
   11/** <module> Prolog Well-formed Pages
   12
   13PWP is an approach to server-side scripting using Prolog
   14which is based on a simple key principle:
   15
   16    - The source form of a PWP should be WELL-FORMED XML
   17
   18Especially when generating XML rather than HTML, this is such an
   19obvious thing to do.  We have many kinds of XML checking tools.
   20
   21    - We can tell whether an XML document is WELL FORMED (all the
   22      punctuation is right, all tags balance) using practically
   23      any decent parser, including SWI Prolog's 'sgml'.
   24
   25    - If we can write a Document Type Description then we can check
   26      that a document is VALID using tools like Open SP (formerly
   27      James Clark's SP) or SWI Prolog's 'sgml'.  This does not
   28      guarantee that the output will be valid, but it does catch
   29      a lot of errors very early.
   30
   31    - If we can write an XML Schema then we can check that a
   32      document is schema-valid.  (SWI Prolog's 'sgml' does not
   33      yet come with a schema validator, but who knows what the
   34      future holds?).
   35
   36    - Since an XML document is just a data structure, we can use
   37      any checking tool that we can write in Prolog, IF the input
   38      is well-formed so that we can load a template as a Prolog
   39      data structure.
   40
   41Having decided that the input should be well formed, that means
   42*|NO NEW SYNTAX|*
   43
   44None of the weird and horrible <% ... %> or whatever not-quite-XML
   45stuff you see in other template systems, making checking so very hard
   46(and therefore, making errors so distressingly common).
   47
   48That in turns means that PWP "markup" must be based on special
   49elements or special attributes.  The fact that an XML parser must
   50allow undeclared attributes on any element even when validating,
   51but must not allow undeclared elements, suggests doing this through
   52attributes.  In particular, one should be able to take an existing
   53DTD, such as an XHTML DTD, and just use that without modification.
   54So the design reduces to
   55
   56    - Allow dynamic data selection, insertion, and transformation
   57    just using a small number of extra attributes.
   58
   59This description uses the following name space:
   60
   61    ==
   62    xmlns:pwp='http://www.cs.otago.ac.nz/staffpriv/ok/pwp.pl'
   63    ==
   64
   65The attributes are
   66
   67    - pwp:ask = Query
   68    - pwp:use = Term
   69    - pwp:how = text | xml
   70    - pwp:tag = QName or '-'
   71    - pwp:att = '' | 'one non-alphanumeric character'
   72
   73Here's what they mean.  Each element is expanded in the context
   74of a set of variable bindings.  After expansion, if the tag is
   75not mapped to '-', all attributes in the pwp: namespace are removed
   76and the children elements are recursively expanded.
   77
   78    * pwp:ask = Query
   79
   80        * Query is a Prolog goal.  For each solution of Query, the element
   81        is further processed with the new variables of Query added to
   82        the context.
   83
   84        * If Query is not a well formed Prolog goal, or if execution of
   85        Query throws an exception, page transformation comes to a complete
   86        halt and no page is generated.
   87
   88    * pwp:use = Term
   89    * pwp:how = text | xml | text-file | xml-file | pwp-file
   90
   91        Term is a Prolog term; variables in Term are bound by the context.
   92        An empty Term is regarded as a missing value for this attribute.
   93        The Prolog variable CONTEXT refers to the entire context, a list
   94        of Name = Value, where Name is a Prolog atom holding the name of        the context variable and Value is an arbitrary Prolog term.
   95
   96        - If pwp:how is text,
   97            The value of Term is used to define a sequence of characters.
   98
   99            - A number produces the same characters that write/1 would.
  100            - An atom produces the same characters that write/1 would.
  101            - A string produces the same characters that write/1 would.
  102            - A list of character codes produces those characters.
  103
  104            - The following terms produce the same sequence of characters
  105              that the corresponding goal would have sent to the current
  106              output stream:
  107
  108              - write(Datum)
  109              - writeq(Datum)
  110              - write_canonical(Datum)
  111              - print(Datum)
  112              - print(Datum)
  113              - format(Format)
  114              - format(Format, Arguments)
  115
  116            - A singleton list [X] defines the characters that X defines.
  117            - Any other term F(T1,...,Tn) defines the characters that T1
  118              defines, followed by the characters that T2 defines, ...,
  119              followed by the characters that Tn defines.
  120
  121        - If pwp:how is xml,
  122            The value of Term must be an XML term as defined in the
  123            SGML2PL documentation or a list of such terms.  A single
  124            term is taken as if it had been [Term].  The resulting
  125            list of terms replaces the children of the current element
  126            and will not be further processed.
  127
  128        - If pwp:how is text-file,
  129            The value of Term is used to define a sequence of characters.
  130            That sequence of characters is used as a file name.
  131            The file is read as a sequence of characters, and that
  132            sequence used as character data.
  133
  134        - If pwp:how is xml-file,
  135            The value of Term is used to define a sequence of characters.
  136            That sequence of characters is used as a file name.
  137            The file is loaded as XML, and the sequence of XML items thus
  138            obtained used.  This means that PWP provides XML inclusion
  139            without depending on the parser to support XInclude.
  140
  141        - If pwp:how is pwp-file,
  142            Like xml-file, but PWP attributes are evaluated and processed.
  143            The current context variables are passed to the PWP processor.
  144
  145        The default value for pwp:how is text.
  146
  147    * pwp:tag = QName or '-'
  148
  149        If pwp:tag is missing or the value is empty, the current element
  150        appears in the output (after further processing) with its present
  151        tag.  If pwp:tag is a QName, the current element appears (...)
  152        with that as its tag.  That option is most useful in DTDs, where
  153        an "authoring" DTD may use one tag and have it automatically mapped
  154        to another tag in the output, e.g., <item> -> <li>.  Finally, if
  155        pwp:tag is '-', the children of the current element (either the
  156        result of pwp:use or the transformed original children, whichever
  157        applies) appear in the output but there is no element around them.
  158
  159        A missing or empty pwp:ask is just like pwp:ask = 'true'.
  160
  161    * pwp:att = '' | 'one non-alphanumeric character'.
  162
  163        Attributes in the pwp namespace are not affected by this attribute.
  164        Such attributes are always stripped out and never substituted into.
  165
  166        If pwp:att is missing or empty, attributes of the current
  167        element are copied over to the output unchanged.
  168
  169        If pwp:att = 'c' for some non-alphanumeric character c,
  170        each attribute is examined for occurrences of c(...)c and c[...]c
  171        which are as short as possible.
  172        There is no one character which could be used every time, so you
  173        have to explicitly choose a substitution marker which is safe
  174        for the data you do not want to be altered.  None of the pwp
  175        attributes are inherited, least of all this one.
  176
  177        Text outside c(...)c groups is copied unchanged; text inside
  178        a c(...)c group is parsed as a Prolog term and treated as if by
  179        pwp:how = text. Text inside a c[...]c group is evaluated (in the
  180        current context), and if it fails, the entire attribute will be
  181        removed from the element.
  182
  183
  184Examples:
  185
  186    1. *|A "Hello World" like example|*
  187
  188    ==
  189    <html
  190      xmlns:pwp="http://www.cs.otago.ac.nz/staffpriv/ok/pwp.pl"
  191      pwp:ask = "ensure_loaded(msg), once(msg(Greeting))">
  192      <head>
  193        <title pwp:use="Greeting"/>
  194      </head>
  195      <body>
  196        <p><span pwp:use="Greeting" pwp:tag='-'/></p>
  197      </body>
  198    </html>
  199    ==
  200
  201    where msg.pl contains
  202
  203    ==
  204    msg('Hello, World!').
  205    ==
  206
  207    This example illustrates an important point.  Prolog Well-Formed
  208    Pages provide *NO* way to physically incorporate Prolog *clauses*
  209    into a page template.   Prolog clauses must be put in separate
  210    files which can be checked by a Prolog syntax checker, compiler,
  211    cross-referencer, &c WITHOUT the Prolog tool in question needing
  212    to know anything whatsoever about PWP.  You load the files using
  213    pwp:ask on the root element.
  214
  215    2. *|Binding some variables and using them|*
  216
  217    ==
  218    <html
  219      xmlns:pwp="http://www.cs.otago.ac.nz/staffpriv/ok/pwp.pl">
  220      <head><title>Example 2</title></head>
  221      <body pwp:ask="Hello = 'Hello world', A = 20, B = 22">
  222        <h1 pwp:use="Hello"/>
  223        <p>The answer is <span pwp:use="C" pwp:ask="C is A+B"/>.</p>
  224      </body>
  225    </html>
  226    ==
  227
  228    3. *|Making a table|*
  229    We are given a Prolog database staff.pl defining
  230    staff(NickName, FullName, Office, Phone, E_Mail_Address).
  231    status(NickName, full_time | part_time).
  232    We want to make a phone list of full time staff.
  233
  234    ==
  235    <html
  236      xmlns:pwp="http://www.cs.otago.ac.nz/staffpriv/ok/pwp.pl"
  237      pwp:ask='ensure_loaded(staff)'>
  238      <head>
  239        <title>Phone list for Full-Time staff.</title>
  240      </head>
  241      <body>
  242        <h1>Phone list for Full-Time staff.</h1>
  243        <table
  244          pwp:ask = "setof(FullName-Phone,
  245                           N^O^E^(
  246                             status(N, full_time),
  247                             staff(N, FullName, O, Phone, E)
  248                           ),
  249                           Staff_List)">
  250          <tr><th>Name</th><th>Phone</th></tr>
  251          <tr pwp:ask="member(FullName-Phone, Staff_List)">
  252            <td pwp:use="FullName"/>
  253            <td pwp:use="Phone"/>
  254          </tr>
  255        </table>
  256      </body>
  257    </html>
  258    ==
  259
  260    4. *|Substituting into an attribute|*
  261    Same data base as before, but now we want to make a mailing list
  262    page.
  263
  264    ==
  265    <html
  266      xmlns:pwp="http://www.cs.otago.ac.nz/staffpriv/ok/pwp.pl"
  267      pwp:ask='ensure_loaded(staff)'>
  268      <head>
  269        <title>Phone list for Full-Time staff.</title>
  270      </head>
  271      <body>
  272        <h1>Phone list for Full-Time staff.</h1>
  273        <table
  274          pwp:ask = "setof(FullName-E_Mail,
  275                           N^O^P^staff(N, FullName, O, P, E_Mail),
  276                           Staff_List)">
  277          <tr><th>Name</th><th>Address</th></tr>
  278          <tr pwp:ask="member(FullName-E_Mail, Staff_List)">
  279            <td pwp:use="FullName"/>
  280            <td><a pwp:use="E_Mail"
  281                   pwp:att='$' href="mailto:$(E_Mail)$"/></td>
  282          </tr>
  283        </table>
  284      </body>
  285    </html>
  286    ==
  287
  288    5. *|If-then-else effect|*
  289    A page that displays the value of the 'SHELL' environment
  290    variable if it has one, otherwise displays 'There is no
  291    default shell.'
  292
  293    ==
  294    <html
  295      xmlns:pwp="http://www.cs.otago.ac.nz/staffpriv/ok/pwp.pl">
  296      <head><title>$SHELL</title></head>
  297      <body>
  298        <p pwp:ask="getenv('SHELL', Shell)"
  299        >The default shell is <span pwp:tag="-" pwp:use="Shell"/>.</p>
  300        <p pwp:ask="\+getenv('SHELL',_)">There is no default shell.</p>
  301      </body>
  302    </html>
  303    ==
  304
  305    There is one other criterion for a good server-side template
  306    language:
  307
  308    It should be possible to compile templates so as to eliminate
  309    most if not all interpretation overhead.
  310
  311    This particular notation satisfies that criterion with the
  312    limitation that the conversion of a term to character data requires
  313    run-time traversal of terms (because the terms are not known until
  314    run time).
  315
  316@author Richard O'Keefe
  317@tbd    Support compilation of PWP input files
  318*/
  319
  320:- meta_predicate
  321    pwp_files(:, +),
  322    pwp_stream(:, +, +),
  323    pwp_xml(:, -, +).  324
  325
  326%%  pwp_files(:In:atom, +Out:atom) is det.
  327%
  328%   loads an Xml document from the file named In,
  329%   transforms it using the PWP attributes, and
  330%   writes the transformed version to the new file named Out.
  331
  332pwp_files(M:In, Out) :-
  333    load_xml_file(In, Contents),
  334    pwp_xml(M:Contents, Transformed, []),
  335    !,
  336    setup_call_cleanup(open(Out, write, Output),
  337                       xml_write(Output, Transformed, []),
  338                       close(Output)).
  339
  340
  341%!  pwp_stream(:Input:input_stream, +Output:output_stream,
  342%!             +Context:list) is det.
  343%
  344%   Loads an Xml document from the given Input stream, transforms it
  345%   using the PWP attributes, and writes  the transformed version to
  346%   the given Output stream.  Context   provides  initial contextual
  347%   variables and is a list of Name=Value.
  348
  349pwp_stream(M:Input, Output, Context) :-
  350    load_xml_file(stream(Input), Contents),
  351    pwp_xml(M:Contents, Transformed, Context),
  352    !,
  353    xml_write(Output, Transformed, []).
  354
  355
  356/*  Recall that an XML term is one of
  357
  358        <atom>                  Character Data
  359        sdata(...)              SDATA (SGML only)
  360        ndata(...)              NDATA
  361        pi(...)                 Processing instruction
  362
  363        element(Name, [Att...], [Child...])
  364
  365            where Att is Attribute=Value and Child is an XML term.
  366
  367    We are only concerned with elements; all other XML terms are
  368    left alone.  I have given some thought to recognising
  369
  370        <?pwp ...Command...?>
  371
  372    processing instructions, executing the Command, and removing
  373    the processing instructions, as a debugging tool.  But this
  374    is a proof-of-concept implementation; debugging features can
  375    wait for The Real Thing.
  376*/
  377
  378
  379
  380%%  pwp_xml(:In:list(xml), -Out:list(xml), +Context)
  381%
  382%   maps down a list of XML items, acting specially on elements and
  383%   copying everything else unchanged, including white space.
  384%   The Context is a list of 'VariableName'=CurrentValue bindings.
  385
  386pwp_xml(M:In, Out, Context) :-
  387    pwp_list(In, Out, M, Context).
  388
  389pwp_list([], [], _, _).
  390pwp_list([element(Tag0,Atts0,Kids0)|Xs], Ys0, M, Context) :-
  391    !,
  392    pwp_attributes(Atts0, Ask, Use, How, Att, Tag1, Atts1),
  393    (   nonvar(Tag1), Tag1 \== '' -> Tag2 = Tag1
  394    ;   Tag2 = Tag0
  395    ),
  396    (   nonvar(Ask), Ask \== '', Ask \== 'true'
  397    ->  atom_to_term(Ask, Query, Bindings),
  398        pwp_unite(Bindings, Context, Context1),
  399        findall(Xml,
  400                ( M:Query,
  401                  pwp_element(Tag2, Atts1, Kids0, Use, How, Att,
  402                              M, Context1, Xml)),
  403                NewContent)
  404    ;   /* Ask is missing, empty, or true */
  405            pwp_element(Tag2, Atts1, Kids0, Use, How, Att,
  406                    M, Context, NewContent)
  407    ),
  408    pwp_attach(NewContent, Ys0, Ys1),
  409    pwp_list(Xs, Ys1, M, Context).
  410pwp_list([X|Xs], [X|Ys], M, Context) :-
  411    pwp_list(Xs, Ys, M, Context).
  412
  413
  414%!  pwp_attributes(+Atts0:list(=(atom,atom)),
  415%!                 -Ask:atom, -Use:atom, -How:atom, -Att:atom,
  416%!                 -Tag:atom, -Atts1:list(=(atom,atom)))
  417%
  418%   Walks down a list of AttributeName=ItsValue pairs, stripping out
  419%   those whose AttributeName begins  with   the  'pwp:' prefix, and
  420%   copying the rest to Atts1.   Along  the way, Ask/Use/How/Att/Tag
  421%   are      bound      to       the        values       of      the
  422%   pwp:ask/pwp:use/pwp:how/pwp:att/pwp:tag attributes, if   any. At
  423%   the end, any of these variables   that  are still unbound REMAIN
  424%   unbound; they are not bound to default values.
  425
  426pwp_attributes([], _, _, _, _, _, []).
  427pwp_attributes([AV|AVs], Ask, Use, How, Att, Tag, New_Atts1) :-
  428    AV = (Name=Value),
  429    (   pwp_attr(Name, PWPName)
  430    ->  (   pwp_attr(PWPName, Value, Ask, Use, How, Att, Tag)
  431        ->  New_Atts1 = New_Atts2
  432        ;   New_Atts1 = New_Atts2
  433        )
  434    ;   New_Atts1 = [AV|New_Atts2]
  435    ),
  436    pwp_attributes(AVs, Ask, Use, How, Att, Tag, New_Atts2).
  437
  438
  439pwp_attr(ask, Value, Value, _Use, _How, _Att, _Tag).
  440pwp_attr(use, Value, _Ask, Value, _How, _Att, _Tag).
  441pwp_attr(how, Value, _Ask, _Use, Value, _Att, _Tag).
  442pwp_attr(att, Value, _Ask, _Use, _How, Value, _Tag).
  443pwp_attr(tag, Value, _Ask, _Use, _How, _Att, Value).
  444
  445%!  pwp_attr(+XMLAttr, -PWPLocal) is semidet.
  446%
  447%   True if PWPLocal is the local name  of a pwp:Local expression in
  448%   XML.  This  predicate  deals  with    the  three  different  XML
  449%   representations:  the  form  is  returned    of   XML  namespace
  450%   processing is not enabled. The second if   it is enabled and the
  451%   namespace is properly defined and the   last if the namespace is
  452%   not defined.
  453
  454pwp_attr(Atom, PWP) :-
  455    atom(Atom),
  456    atom_concat('pwp:', PWP, Atom),
  457    !.
  458pwp_attr('http://www.cs.otago.ac.nz/staffpriv/ok/pwp.pl':PWP, PWP) :- !.
  459pwp_attr('pwp':PWP, PWP) :- !.
  460pwp_attr('xmlns:pwp', -).
  461
  462%%  pwp_unite(+Bindings, +Context0, -Context:list(=(atom,any)))
  463%
  464%   merges the new Bindings with the bindings in the outer Context0,
  465%   constructing a new list of VariableName=CurrentValue bindings in
  466%   Context1.  This is only used when the CurrentValue parts of the
  467%   new Bindings are known to be distinct new variables, so the
  468%   Bindings cannot possibly conflict with any existing binding in
  469%   Context0.  This is O(|Bindings|.|Context0|), which is not that
  470%   efficient, but since we do not expect there to be very many
  471%   variables it doesn't matter much.
  472
  473pwp_unite(Bindings, Context0, Context) :-
  474    pwp_unite(Bindings, Context0, Context0, Context).
  475
  476
  477pwp_unite([], _, Context, Context).
  478pwp_unite([Binding|Bindings], Context0, Context1, Context) :-
  479    memberchk(Binding, Context0),
  480    !,
  481    pwp_unite(Bindings, Context0, Context1, Context).
  482pwp_unite(['CONTEXT'=Context0|Bindings], Context0, Context1, Context) :-
  483    !,
  484    pwp_unite(Bindings, Context0, Context1, Context).
  485pwp_unite([Binding|Bindings], Context0, Context1, Context) :-
  486    pwp_unite(Bindings, Context0, [Binding|Context1], Context).
  487
  488
  489
  490%%  pwp_unite(+Bindings, +Context0: list(=(atom,any)))
  491%
  492%   looks up the bindings in Bindings in the outer Context0.
  493%   This is only used for 'pwp:use' terms (and the related terms
  494%   in $(...)$ attribute value substitutions), so that we have
  495%   no interest in forming a new context.  (If we did, we'd use
  496%   pwp_unite/3 instead.)  This is only used when the CurrentValue
  497%   parts of the new Bindings are known to be distinct new variables,
  498%   so the Bindings cannot possibly conflict with any existing
  499%   binding in Context0.  However, there _could_ be new variables
  500%   in Bindings, and that would cause problems.  An XML term may
  501%   not contain variables, and a term we want to convert to a list
  502%   of character codes had better not contain variables either.
  503%   One approach would be to just bind such variables to something,
  504%   another is to throw some kind of exception.  For the moment we
  505%   call functor/3 so as to get an instantiation error.
  506
  507pwp_unite([], _).
  508pwp_unite([Binding|Bindings], Context) :-
  509    memberchk(Binding, Context),
  510    !,
  511    pwp_unite(Bindings, Context).
  512pwp_unite([_=Value|_], _) :-
  513    functor(Value, _, _).
  514
  515%%  pwp_attach(+Tree, ?Ys0: list(xml), ?Ys: list(xml))
  516%
  517%   is a combination of "flatten" and "append".
  518%   It unifies Ys0\Ys with the result of flattening Tree.
  519
  520pwp_attach([], Ys, Ys) :- !.
  521pwp_attach([X|Xs], Ys0, Ys) :-
  522    !,
  523    pwp_attach(X, Ys0, Ys1),
  524    pwp_attach(Xs, Ys1, Ys).
  525pwp_attach(X, [X|Ys], Ys).
  526
  527
  528
  529pwp_element('-', _, Kids, Use, How, _, M, Context, Xml) :-
  530    !,
  531    pwp_use(Use, How, Kids, M, Context, Xml).
  532pwp_element(Tag, Atts, [Value], Use, How, Magic, M, Context,
  533            element(Tag,Atts1,Kids1)) :-
  534    verbatim_element(Tag), nonvar(Magic), atomic(Value),
  535    !,
  536    %% Apply substition of c(..)c variables also to content of
  537        %% <script> and <style> tags:
  538        pwp_substitute([cdata=Value|Atts], Magic, Context,
  539                   [cdata=Value1|Atts1]),
  540    pwp_use(Use, How, [Value1], M, Context, Kids1).
  541pwp_element(Tag, Atts, Kids, Use, How, Magic, M, Context,
  542            element(Tag,Atts1,Kids1)) :-
  543    (   nonvar(Magic)
  544    ->  pwp_substitute(Atts, Magic, Context, Atts1)
  545    ;   Atts1 = Atts
  546    ),
  547    pwp_use(Use, How, Kids, M, Context, Kids1).
  548
  549pwp_use('', _, Kids, M, Context, Kids1) :-
  550    !,
  551    pwp_list(Kids, Kids1, M, Context).
  552pwp_use(Use, How, _, M, Context, Kids1) :-
  553    atom_to_term(Use, Term, Bindings),
  554    pwp_unite(Bindings, Context),
  555    pwp_how(How, Term, M, Context, Kids1).
  556
  557pwp_how('text', Term, _,_, [CData]) :-
  558    !,
  559    pwp_use_codes(Term, Codes, []),
  560    atom_codes(CData, Codes).
  561pwp_how('xml', Term, _,_, Kids1) :-
  562    (   Term == []   -> Kids1 = Term
  563    ;   Term = [_|_] -> Kids1 = Term
  564    ;                   Kids1 = [Term]
  565    ).
  566pwp_how('text-file', Term, _,_, [CData]) :-
  567    pwp_use_codes(Term, Codes, []),
  568    atom_codes(FileName, Codes),
  569    read_file_to_codes(FileName, FileCodes, []),
  570    atom_codes(CData, FileCodes).
  571pwp_how('xml-file', Term, _,_, Kids1) :-
  572    pwp_use_codes(Term, Codes, []),
  573    atom_codes(FileName, Codes),
  574    load_xml_file(FileName, Kids1).
  575pwp_how('pwp-file', Term, M,Context, Kids1) :-
  576    pwp_use_codes(Term, Codes, []),
  577    atom_codes(FileName, Codes),
  578    ( memberchk('SCRIPT_DIRECTORY'=ScriptDir,Context) -> true
  579    ; ScriptDir='.'
  580    ),
  581    absolute_file_name(FileName, PathName, [relative_to(ScriptDir)]),
  582    load_xml_file(PathName, Kids0),
  583    pwp_xml(M:Kids0, Kids1, Context),
  584    !.
  585
  586
  587pwp_substitute([], _, _, []).
  588pwp_substitute([AV|AVs], Magic, Context, NewAvs) :-
  589    AV = (Name = Value),
  590    (   sub_atom(Value, _, _, _, Magic)
  591    ->  char_code(Magic, C),
  592        atom_codes(Value, Codes),
  593        pwp_split(Codes, C, B0, T0, A0, Type),
  594        !,
  595        (   pwp_substitute(B0, T0, A0, C, Context, V, Type)
  596        ->  NewAvs = [AV1|Atts1],
  597            atom_codes(New_Value, V),
  598            AV1 = (Name = New_Value)
  599        ;   Type == existence->
  600            NewAvs = Atts1
  601        ),
  602        pwp_substitute(AVs, Magic, Context, Atts1)
  603    ).
  604pwp_substitute([AV|AVs], Magic, Context, [AV|Atts1]) :-
  605    pwp_substitute(AVs, Magic, Context, Atts1).
  606
  607
  608pwp_substitute(B0, T0, A0, C, Context, V0, Type) :-
  609    append(B0, V1, V0),
  610    atom_codes(Atom, T0),
  611    atom_to_term(Atom, Term, Bindings),
  612    pwp_unite(Bindings, Context, _),
  613    (   Type == value
  614    ->  pwp_use_codes(Term, V1, V2)
  615    ;   catch(Term, _, fail),
  616        V2 = V1
  617    ),
  618    (   pwp_split(A0, C, B1, T1, A1, T2)
  619    ->  pwp_substitute(B1, T1, A1, C, Context, V2, T2)
  620    ;   V2 = A0
  621    ).
  622
  623
  624pwp_split(Codes, C, Before, Text, After, Type) :-
  625    append(Before, [C,C1|Rest], Codes),
  626    (   C1 == 0'(
  627    ->  Type = value,
  628        C2 = 0')
  629    ;   C1 == 0'[,
  630        Type = existence,
  631        C2 = 0']
  632    ),
  633    append(Text,   [C2,C|After], Rest),
  634    !.
  635
  636pwp_use_codes(format(Format), S0, S) :-
  637    !,
  638    pwp_format(Format, [], S0, S).
  639pwp_use_codes(format(Format,Args), S0, S) :-
  640    !,
  641    pwp_format(Format, Args, S0, S).
  642pwp_use_codes(write_canonical(Datum), S0, S) :-
  643    !,
  644    pwp_format('~k', [Datum], S0, S).
  645pwp_use_codes(print(Datum), S0, S) :-
  646    !,
  647    pwp_format('~p', [Datum], S0, S).
  648pwp_use_codes(writeq(Datum), S0, S) :-
  649    !,
  650    pwp_format('~q', [Datum], S0, S).
  651pwp_use_codes(write(Datum), S0, S) :-
  652    !,
  653    pwp_format('~w', [Datum], S0, S).
  654pwp_use_codes(Atomic, S0, S) :-
  655    atomic(Atomic),
  656    !,
  657    (   number(Atomic) -> number_codes(Atomic, Codes)
  658    ;   atom(Atomic)   -> atom_codes(Atomic, Codes)
  659    ;   string(Atomic) -> string_codes(Atomic, Codes)
  660    ;   pwp_format('~w', [Atomic], S0, S)
  661    ),
  662    append(Codes, S, S0).
  663pwp_use_codes([X|Xs], S0, S) :-
  664    pwp_is_codes([X|Xs]),
  665    !,
  666    append([X|Xs], S, S0).
  667pwp_use_codes([X|Xs], S0, S) :-
  668    !,
  669    pwp_use_codes(Xs, X, S0, S).
  670pwp_use_codes(Compound, S0, S) :-
  671    Compound =.. [_,X|Xs],
  672    pwp_use_codes(Xs, X, S0, S).
  673
  674
  675
  676pwp_use_codes([], X, S0, S) :-
  677    !,
  678    pwp_use_codes(X, S0, S).
  679pwp_use_codes([Y|Ys], X, S0, S) :-
  680    pwp_use_codes(X, S0, S1),
  681    pwp_use_codes(Ys, Y, S1, S).
  682
  683
  684
  685%%  pwp_is_codes(+String: any)
  686%
  687%   is true when String is a list of integers and each of those
  688%   integers is a possible Unicode value (in the range U+0000..U+10FFFF).
  689%   Back in the days of ISO Latin 1 we would have checked for 0..255,
  690%   and way back in the days of ASCII for 0..127.  Yes, there are more
  691%   than a million possible characters in Unicode; currently about
  692%   100 000 of them are in use.
  693
  694pwp_is_codes([]).
  695pwp_is_codes([C|Cs]) :-
  696    integer(C), C >= 0, C =< 0x10FFFF,
  697    pwp_is_codes(Cs).
  698
  699pwp_format(Format, Arguments, S0, S) :-
  700    format(codes(S0, S), Format, Arguments).
  701
  702
  703verbatim_element(script).
  704verbatim_element(style)