[m-rev.] for review: add xmlable typeclass to term_to_xml module
Julien Fischer
juliensf at cs.mu.OZ.AU
Mon Jul 25 12:53:22 AEST 2005
On Sat, 23 Jul 2005, Ian MacLarty wrote:
> For review by anyone.
>
> Estimated hours taken: 6
> Branches: main
>
> Add a new method to convert terms to XML using a typeclass.
> The new method is more flexible than the previous method, but DTDs cannot
> be automatically generated using the new method.
>
The log message should include a little more information on
what is more flexible about the new method.
> library/term_to_xml.m:
> Update the documentation to describe the new method.
> Divide the interface into two parts -- one for the new method and
> one for the old method.
> Rename some function symbols and variables of the old method to
> distinguish them from names used in the new method.
> Implement predicates for writing terms that are members of the xmlable
> typeclass.
>
> tests/hard_coded/Mmakefile:
> tests/hard_coded/xmlable_test.exp:
> tests/hard_coded/xmlable_test.m:
> Test the new method.
>
> tests/hard_coded/write_xml.m:
> Use attr_from_source instead of attribute.
>
> Index: library/term_to_xml.m
> ===================================================================
> RCS file: /home/mercury1/repository/mercury/library/term_to_xml.m,v
> retrieving revision 1.7
> diff -u -r1.7 term_to_xml.m
> --- library/term_to_xml.m 16 Jun 2005 04:08:06 -0000 1.7
> +++ library/term_to_xml.m 23 Jul 2005 02:54:14 -0000
> @@ -1,4 +1,4 @@
> -%-----------------------------------------------------------------------------r
> +%-----------------------------------------------------------------------------%
> % Copyright (C) 1993-2005 The University of Melbourne.
> % This file may only be copied under the terms of the GNU Library General
> % Public License - see the file COPYING.LIB in the Mercury distribution.
> @@ -8,54 +8,42 @@
> % Main author: maclarty.
> % Stability: low.
> %
> -% A Mercury term to XML converter.
> -%
> -% This module contains predicates that write arbitrary Mercury terms to
> -% an output stream as XML.
> -%
> -% Each functor in a term is given a corresponding well-formed element name
> -% in the XML document according to a mapping. Some predefined mappings are
> -% provided, but user defined mappings may also be used.
> -%
> -% The following attributes can be set for each XML element:
> -%
> -% functor - the original functor name as returned by
> -% deconstruct.deconstruct/5.
> -%
> -% arity - the arity of the functor as returned by deconstruct.deconstruct/5.
> -%
> -% type - the type name of the Mercury type the element represents.
> -%
> -% field - the field name of a discriminated union functor argument if it has
> -% one.
> -%
> -% The names of the above attributes can also be customized.
> +% This module provides two mechanisms whereby Mercury terms can be converted to
> +% XML documents.
I suggest:
This module provides two mechanisms for converting Mercury terms
to XML documents.
...
> +% Method 1
> +% --------
> +% The first method requires a type to be an instance of the xmlable typeclass
> +% before values of the type can be written as XML.
> +% Members of the xmlable typeclass must implement a to_xml method which
> +% maps values of the type to XML elements.
That seems redundant. Members of a typeclass must implement the methods
by definition.
> +% The XML elements may contain arbitrary children, comments and data.
> +%
> +% Method 2
> +% --------
> +% The second method is less flexible than the first, but it allows for the
> +% automatic generation of a DTD.
> +% In the second method each functor in a term is given a corresponding
Delete 'In the second method'.
> +% well-formed element name in the XML document according to a mapping. Some
> +% predefined mappings are provided, but user defined mappings may also be used.
> +%
> +% Method 1 vs. Method 2
> +% ---------------------
> +%
> +% Method 1 allows values of a specific type to be mapped to arbitrary XML
> +% elements with arbitrary children and arbitrary attributes.
> +% In method 2 each functor in a term can be mapped to only one XML element.
> +% Method 2 also only allows a selected set of attributes.
> +% In method 2 a DTD can be automatically generated. In method 1 DTDs cannot
> +% be automatically generated.
> +%
> +% Method 1 is useful for mapping a specific type to XML,
> +% for example mapping terms which represent mathematical expressions to
> +% MathML.
> +% Method 2 is useful for mapping arbitrary terms of any type to XML.
> +%
> +% In both methods the XML document can be annotated with a stylesheet
> +% reference.
Presumably once we have typeclass reflection you could use both methods,
using the non-typeclass one as a fallback method.
> %
> %-----------------------------------------------------------------------------%
> %-----------------------------------------------------------------------------%
> @@ -67,9 +55,68 @@
> :- import_module int.
> :- import_module io.
> :- import_module list.
> +:- import_module std_util.
> :- import_module type_desc.
>
> %-----------------------------------------------------------------------------%
> +% Method 1 interface
> +%
Please use the section heading format in the coding standard.
%---------------------------
%
% Method 1 interface
%
> +
> + % Instances of this typeclass can be converted to XML.
> + %
> +:- typeclass xmlable(T) where [
> + func to_xml(T::in) = (xml::out(xml_doc)) is det
> +].
Is there a reason the method delcaration just isn't:
func to_xml(T) = xml
I prefer either 'to_xml' or just 'xml' as names for the typeclass.
> + % Values of this type represent an XML document or a portion of
> + % an XML document.
> + %
> +:- type xml
> + %
> + % An XML element with a name, list of attributes
> + % and a list of children.
> + %
> + ---> elem(
> + element_name :: string,
> + attributes :: list(attr),
> + children :: list(xml)
> + )
> +
> + % Textual data. `<', `>', `&', `'' and `"' characters
> + % will be replaced by `<', `>', `&', `''
> + % and `"' respectively.
> + ; data(string)
> +
> + % Data to be enclosed in `<![CDATA[' and `]]>' tags.
> + % Any occurances of `]]>' in the data will be
> + % converted to `]]>'.
s/occurances/occurrences/
> + ; cdata(string)
> +
> + % An XML comment. The comment should not
> + % include the `<!--' and `-->'. Any occurances of
s/occurances/occurrences/
> + % `--' will be replaced by ` - '.
> + ; comment(string)
> +
> + % An entity reference. The string will
> + % have `&' prepended and `;' appended before being
> + % output.
> + ; entity(string)
> +
> + % Raw XML data. The data will be written out verbatim.
> + ; raw(string).
> +
> + % An XML document must have an element at the top-level.
> + %
> +:- inst xml_doc
> + ---> elem(
> + ground, % element_name
> + ground, % attributes
> + ground % children
> + ).
This inst seems redundant.
> +
> + % An element attribute, mapping a name to a value.
> + %
> +:- type attr ---> attr(string, string).
>
> % Values of this type specify the DOCTYPE of an XML document when
> % the DOCTYPE is defined by an external DTD.
> @@ -83,15 +130,21 @@
> % a generated XML document and if so how.
> %
> :- type maybe_dtd
> - % Generate and embed the entire DTD in the document.
> + % Generate and embed the entire DTD in the document
> + % (only available for method 2).
> ---> embed
> % Included a reference to an external DTD.
> ; external(doctype)
> % Do not include any DOCTYPE information.
> ; no_dtd.
>
> +:- inst non_embedded_dtd
> + ---> external(ground)
> + ; no_dtd.
> +
> % Values of this type indicate whether a stylesheet reference should be
> % included in a generated XML document.
> + %
> :- type maybe_stylesheet
> ---> with_stylesheet(
> stylesheet_type :: string, % For example "text/xsl"
> @@ -99,50 +152,68 @@
> )
> ; no_stylesheet.
>
...
> + % write_xml_doc(Stream, Term, !IO).
> + % Same as write_xml_doc/3, but use the given output stream.
> + %
> +:- pred write_xml_doc(io.output_stream::in, T::in, io::di, io::uo) is det
> + <= xmlable(T).
> +
> + % write_xml_doc(Term, MaybeStyleSheet, MaybeDTD, !IO).
> + % Write Term to the current output stream as an XML document.
> + % Term should be an instance of the xmlable typeclass.
s/should/must/ (although that sentence is redundant; the predicate
declaration says as much.)
> + % MaybeStyleSheet and MaybeDTD specify whether or not a stylesheet
> + % reference and/or a DTD should be included.
> + % Using this predicate only external DTDs can be included, i.e.
> + % a DTD cannot be automatically generated and embedded
> + % (that fearure is available only for method 2 -- see below).
> + %
/fearure/feature/
> +:- pred write_xml_doc(T::in, maybe_stylesheet::in,
> + maybe_dtd::in(non_embedded_dtd), io::di, io::uo) is det <= xmlable(T).
> +
> + % write_xml_doc(Stream, Term, MaybeStyleSheet, MaybeDTD, !IO).
> + % Same as write_xml_doc/5, but write output to the given output
> + % stream.
> + %
> +:- pred write_xml_doc(io.output_stream::in, T::in, maybe_stylesheet::in,
> + maybe_dtd::in(non_embedded_dtd), io::di, io::uo) is det <= xmlable(T).
> +
> + % write_xml_element(Indent, Term, !IO).
> + % Write Term out as XML to the current output stream,
> + % using indentation level Indent (each indentation level is one
> + % tab character).
> + % No `<?xml ... ?>' header will be written.
> + % This is useful for generating large XML documents in pieces.
> + %
> +:- pred write_xml_element(int::in, T::in, io::di, io::uo) is det <= xmlable(T).
> +
> + % write_xml_element(Stream, Indent, Term, !IO).
> + % Same as write_xml_element/4, but use the given output stream.
> + %
> +:- pred write_xml_element(io.output_stream::in, int::in, T::in, io::di, io::uo)
> + is det <= xmlable(T).
> +
> + % write_xml_header(MaybeEncoding, !IO).
> + % Write an XML header (i.e. `<?xml version="1.0"?>) to the
> + % current output stream.
> + % If MaybeEncoding is yes(Encoding), then include `encoding="Encoding"'
> + % in the header.
> + %
> +:- pred write_xml_header(maybe(string)::in, io::di, io::uo) is det.
> +
> + % Same as write_xml_header/3, but use the given output stream.
> + %
> +:- pred write_xml_header(io.output_stream::in, maybe(string)::in, io::di,
> + io::uo) is det.
> +
> +%-----------------------------------------------------------------------------%
> +% Method 2 interface
> +%
Fix the section heading here.
>
> % Values of this type specify which mapping from functors to elements
> % to use when generating XML. The role of a mapping is twofold:
> @@ -194,6 +265,122 @@
> ; unique
> ; custom(element_pred).
>
> + % Deterministic procedures with the following signature can be used as
> + % custom functor to element mappings. The inputs to the procedure are
> + % a type and some information about a functor for that type
> + % if the type is a discriminated union. The output should be a well
> + % formed XML element name and a list of attributes that should be set
> + % for that element. See the types `maybe_functor_info' and
> + % `attr_from_source' below.
> + %
> +:- type element_pred == (pred(type_desc.type_desc, maybe_functor_info, string,
> + list(attr_from_source))).
> +
> +:- inst element_pred == (pred(in, in, out, out) is det).
> +
> + % Values of this type are passed to custom functor-to-element
> + % mapping predicates to tell the predicate which functor to generate
> + % an element name for if the type is a discriminated union. If the
> + % type is not a discriminated union, then none_du is passed to
> + % the predicate when requesting an element for the type.
> + %
> +:- type maybe_functor_info
> + % The functor's name and arity.
> + ---> du_functor(
> + functor_name :: string,
> + functor_arity :: int
> + )
> + % The type is not a discriminated union.
> + ; none_du.
> +
> + % Values of this type specify attributes that should be set from
> + % a particular source. The attribute_name field specifies the name
> + % of the attribute in the generated XML and the attribute_source
> + % field indicates where the attribute's value should come from.
> + %
> +:- type attr_from_source
> + ---> attr_from_source(
> + attr_name :: string,
> + attr_source :: attr_source
> + ).
> +
> + % Possible attribute sources.
> + %
> +:- type attr_source
> + % The original functor name as returned by
> + % deconstruct.deconstruct/5.
> + ---> functor
> + % The field name if the functor appears in a
> + % named field (If the field is not named then this
> + % attribute is omitted.
You've left out the closing parenthesis there.
...
> %-----------------------------------------------------------------------------%
> @@ -378,23 +510,65 @@
> :- import_module exception.
> :- import_module map.
> :- import_module require.
> -:- import_module std_util.
> :- import_module string.
>
> %-----------------------------------------------------------------------------%
>
> +write_xml_doc(X, !IO) :-
> + write_xml_doc(X, no_stylesheet, no_dtd, !IO).
> +
I suggest s/X/XML/ or s/X/Doc/ there and below.
> +write_xml_doc(Stream, X, !IO) :-
> + write_xml_doc(Stream, X, no_stylesheet, no_dtd, !IO).
> +
> +write_xml_doc(X, MaybeStyleSheet, MaybeDTD, !IO) :-
> + write_xml_header(no, !IO),
> + write_stylesheet_ref(MaybeStyleSheet, !IO),
> + Root = to_xml(X),
> + Root = elem(RootName, _, Children),
> + (
> + MaybeDTD = no_dtd
> + ;
> + MaybeDTD = external(DocType),
> + write_external_doctype(RootName, DocType, !IO)
> + ),
> + ( if contains_noformat_xml(Children) then
> + ChildrenFormat = no_format
> + else
> + ChildrenFormat = format
> + ),
> + write_xml_element_format(ChildrenFormat, 0, Root, !IO).
> +
> +write_xml_doc(Stream, X, MaybeStyleSheet, MaybeDTD, !IO) :-
> + io.set_output_stream(Stream, OrigStream, !IO),
> + write_xml_doc(X, MaybeStyleSheet, MaybeDTD, !IO),
> + io.set_output_stream(OrigStream, _, !IO).
> +
> +write_xml_element(Indent, X, !IO) :-
> + Root = to_xml(X),
> + Root = elem(_, _, Children),
> + ( if contains_noformat_xml(Children) then
> + ChildrenFormat = no_format
> + else
> + ChildrenFormat = format
> + ),
> + write_xml_element_format(ChildrenFormat, Indent, Root, !IO).
> +
> +write_xml_element(Stream, Indent, X, !IO) :-
> + io.set_output_stream(Stream, OrigStream, !IO),
> + write_xml_element(Indent, X, !IO),
> + io.set_output_stream(OrigStream, _, !IO).
> +
> write_xml_doc(X, ElementMapping, MaybeStyleSheet, MaybeDTD, DTDResult, !IO) :-
> DTDResult = can_generate_dtd(MaybeDTD, ElementMapping,
> type_desc.type_of(X)),
> (
> DTDResult = ok
> ->
> - get_element_pred(ElementMapping, MakeElement),
> write_xml_header(no, !IO),
> write_stylesheet_ref(MaybeStyleSheet, !IO),
> write_doctype(canonicalize, X, ElementMapping, MaybeDTD, _,
> !IO),
> - write_xml_element(canonicalize, MakeElement, 0, X, !IO)
> + write_xml_element(canonicalize, ElementMapping, 0, X, !IO)
> ;
> true
> ).
> @@ -413,12 +587,12 @@
> (
> DTDResult = ok
> ->
> - get_element_pred(ElementMapping, MakeElement),
> write_xml_header(no, !IO),
> write_stylesheet_ref(MaybeStyleSheet, !IO),
> write_doctype(include_details_cc, X, ElementMapping, MaybeDTD,
> _, !IO),
> - write_xml_element(include_details_cc, MakeElement, 0, X, !IO)
> + write_xml_element(include_details_cc, ElementMapping,
> + 0, X, !IO)
> ;
> true
> ).
> @@ -430,8 +604,9 @@
> DTDResult, !IO),
> io.set_output_stream(OrigStream, _, !IO).
>
> -write_xml_element(NonCanon, MakeElement, IndentLevel, X, !IO) :-
> +write_xml_element(NonCanon, ElementMapping, IndentLevel, X, !IO) :-
> type_to_univ(X, Univ),
> + get_element_pred(ElementMapping, MakeElement),
> write_xml_element_univ(NonCanon, MakeElement, IndentLevel, Univ, [], _,
> !IO).
>
> @@ -449,8 +624,6 @@
> write_dtd_from_type(TypeDesc, ElementMapping, DTDResult, !IO),
> io.set_output_stream(OrigStream, _, !IO).
>
> -:- pred write_xml_header(maybe(string)::in, io::di, io::uo) is det.
> -
> write_xml_header(MaybeEncoding, !IO) :-
> io.write_string("<?xml version=""1.0""", !IO),
> (
> @@ -463,6 +636,11 @@
> io.write_string("?>\n", !IO)
> ).
>
> +write_xml_header(Stream, MaybeEncoding, !IO) :-
> + io.set_output_stream(Stream, OrigStream, !IO),
> + write_xml_header(MaybeEncoding, !IO),
> + io.set_output_stream(OrigStream, _, !IO).
> +
> :- pred write_stylesheet_ref(maybe_stylesheet::in, io::di, io::uo) is det.
>
> write_stylesheet_ref(no_stylesheet, !IO).
...
> @@ -784,6 +968,104 @@
>
> %-----------------------------------------------------------------------------%
>
> + % The following type is used to decide if an entity should be
> + % formated (i.e. be indented and have a newline at the end).
s/formated/formatted/
> + % We do not format sibling entities if there is anything besides
> + % elements, Cdata or comments in the siblings, since then whitespaces
> + % are more likely to be significant.
I suggest:
We do not format sibiling entities if they contain anything
besides ....
> + % (Although technically spaces are always significant, they are
> + % usually interpreted as only formatting when they are between
> + % markup).
> + %
> +:- type maybe_format
> + ---> format
> + ; no_format.
> +
Julien.
--------------------------------------------------------------------------
mercury-reviews mailing list
post: mercury-reviews at cs.mu.oz.au
administrative address: owner-mercury-reviews at cs.mu.oz.au
unsubscribe: Address: mercury-reviews-request at cs.mu.oz.au Message: unsubscribe
subscribe: Address: mercury-reviews-request at cs.mu.oz.au Message: subscribe
--------------------------------------------------------------------------
More information about the reviews
mailing list