[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