From 5d1ba0388af2f66d1d83db755361d3b2eb5f68ad Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Mon, 10 Aug 2015 18:30:25 +0200 Subject: Add streaming API to serializer Show what can be done with it in the xhtml example. --- examples/makefile | 3 +- examples/xhtml/README | 2 + examples/xhtml/driver.cxx | 129 ++++++++++++++++++++++++++++++++++++++++++++++ examples/xhtml/makefile | 88 +++++++++++++++++++++++++++++++ xml/serializer | 16 ++++++ xml/serializer.ixx | 55 ++++++++++++++++++++ 6 files changed, 292 insertions(+), 1 deletion(-) create mode 100644 examples/xhtml/README create mode 100644 examples/xhtml/driver.cxx create mode 100644 examples/xhtml/makefile diff --git a/examples/makefile b/examples/makefile index e2f1330..d6966fa 100644 --- a/examples/makefile +++ b/examples/makefile @@ -4,7 +4,8 @@ include $(dir $(lastword $(MAKEFILE_LIST)))../build/bootstrap.make -examples := roundtrip processing persistence inheritance hybrid performance +examples := roundtrip processing persistence inheritance hybrid performance \ +xhtml default := $(out_base)/ test := $(out_base)/.test diff --git a/examples/xhtml/README b/examples/xhtml/README new file mode 100644 index 0000000..dcc25c1 --- /dev/null +++ b/examples/xhtml/README @@ -0,0 +1,2 @@ +This example shows how to define a simple C++-embedded domain-specific +language (EDSL) for a particular XML vocabulary, XHTML5 in this case. diff --git a/examples/xhtml/driver.cxx b/examples/xhtml/driver.cxx new file mode 100644 index 0000000..c298983 --- /dev/null +++ b/examples/xhtml/driver.cxx @@ -0,0 +1,129 @@ +// file : examples/xhtml/driver.cxx +// copyright : not copyrighted - public domain + +#include + +#include + +using namespace std; +using namespace xml; + +namespace xhtml +{ + // "Canonical" XHTML5 vocabulary. + // + const char* xmlns = "http://www.w3.org/1999/xhtml"; + + inline void _html (serializer& s) + { + s.doctype_decl ("html"); + s.start_element (xmlns, "html"); + s.namespace_decl (xmlns, ""); + } + inline void html_ (serializer& s) {s.end_element ();} + + inline void _head (serializer& s) + { + s.start_element (xmlns, "head"); + s.start_element (xmlns, "meta"); + s.attribute ("charset", "UTF-8"); + s.end_element (); + } + inline void head_ (serializer& s) {s.end_element ();} + + inline void _title (serializer& s) {s.start_element (xmlns, "title");} + inline void title_ (serializer& s) {s.end_element ();} + + inline void _body (serializer& s) {s.start_element (xmlns, "body");} + inline void body_ (serializer& s) {s.end_element ();} + + inline void _p (serializer& s) {s.start_element (xmlns, "p");} + inline void p_ (serializer& s) {s.end_element ();} + + // "Inline" elements, i.e., those that are written without + // indentation. + // + inline void _em (serializer& s) + { + s.suspend_indentation (); + s.start_element (xmlns, "em"); + } + inline void em_ (serializer& s) + { + s.end_element (); + s.resume_indentation (); + } + + inline void _br_ (serializer& s) + { + s.suspend_indentation (); + s.start_element (xmlns, "br"); + s.end_element (); + s.resume_indentation (); + } + + // Attributes. + // + template + struct attr_value + { + attr_value (const char* n, const T& v): name (n), value (v) {} + + void operator() (serializer& s) const {s.attribute (name, value);} + const char* name; + const T& value; + }; + + struct attr + { + const char* name; + + explicit + attr (const char* n): name (n) {} + + // s << (attr = 123); + // + template + attr_value operator= (const T& v) {return attr_value (name, v);} + + // s << attr (123); + // + template + attr_value operator() (const T& v) {return attr_value (name, v);} + + // s << attr << 123 << ~attr; + // + void operator() (serializer& s) const {s.start_attribute (name);} + void (*operator~ ())(serializer& s) const {return &end;} + + static void end (serializer& s) {s.end_attribute ();} + }; + + static attr id ("id"); +} + +int +main () +{ + try + { + using namespace xhtml; + + serializer s (cout, "output"); + + s << _html + << _head + << _title << "Example XHTML5 document" << title_ + << head_ + << _body << (id = 123) + << _p << "Here be " << _em << "Dragons!" << em_ << _br_ + << "And " << 123 << p_ + << body_ + << html_; + } + catch (const xml::serialization& e) + { + cerr << e.what () << endl; + return 1; + } +} diff --git a/examples/xhtml/makefile b/examples/xhtml/makefile new file mode 100644 index 0000000..cfd4dd3 --- /dev/null +++ b/examples/xhtml/makefile @@ -0,0 +1,88 @@ +# file : examples/xhtml/makefile +# copyright : Copyright (c) 2013-2014 Code Synthesis Tools CC +# license : MIT; see accompanying LICENSE file + +include $(dir $(lastword $(MAKEFILE_LIST)))../../build/bootstrap.make + +cxx_tun := driver.cxx + +cxx_obj := $(addprefix $(out_base)/,$(cxx_tun:.cxx=.o)) +cxx_od := $(cxx_obj:.o=.o.d) + +studxml.l := $(out_root)/xml/studxml.l +studxml.l.cpp-options := $(out_root)/xml/studxml.l.cpp-options + +driver := $(out_base)/driver +test := $(out_base)/.test +dist := $(out_base)/.dist +clean := $(out_base)/.clean + +# Build. +# +$(driver): $(cxx_obj) $(studxml.l) +$(cxx_obj) $(cxx_od): $(studxml.l.cpp-options) + +$(call include-dep,$(cxx_od)) + +# Alias for default target. +# +$(out_base)/: $(driver) + +# Dist +# +$(dist): name := $(subst $(src_root)/examples/,,$(src_base)) +$(dist): sources := $(cxx_tun) +$(dist): extras := README +$(dist): export extra_dist := $(extras) $(name)-vc9.vcproj \ +$(name)-vc10.vcxproj $(name)-vc10.vcxproj.filters \ +$(name)-vc11.vcxproj $(name)-vc11.vcxproj.filters \ +$(name)-vc12.vcxproj $(name)-vc12.vcxproj.filters +$(dist): + $(call dist-data,$(sources) $(extras)) + $(call meta-automake,../template/Makefile.am) + $(call meta-vc9proj,../template/template-vc9.vcproj,$(name)-vc9.vcproj) + $(call meta-vc10proj,../template/template-vc10.vcxproj,$(name)-vc10.vcxproj) + $(call meta-vc11proj,../template/template-vc11.vcxproj,$(name)-vc11.vcxproj) + $(call meta-vc12proj,../template/template-vc12.vcxproj,$(name)-vc12.vcxproj) + +# Test. +# +$(test): $(driver) + $(call message,test $<,$<) + +# Clean. +# +$(clean): \ + $(driver).o.clean \ + $(addsuffix .cxx.clean,$(cxx_obj)) \ + $(addsuffix .cxx.clean,$(cxx_od)) + + +# Generated .gitignore. +# +ifeq ($(out_base),$(src_base)) +$(driver): | $(out_base)/.gitignore + +$(out_base)/.gitignore: files := driver +$(clean): $(out_base)/.gitignore.clean + +$(call include,$(bld_root)/git/gitignore.make) +endif + + +# How to. +# +$(call include,$(bld_root)/dist.make) +$(call include,$(bld_root)/meta/vc9proj.make) +$(call include,$(bld_root)/meta/vc10proj.make) +$(call include,$(bld_root)/meta/vc11proj.make) +$(call include,$(bld_root)/meta/vc12proj.make) +$(call include,$(bld_root)/meta/automake.make) + +$(call include,$(bld_root)/cxx/o-e.make) +$(call include,$(bld_root)/cxx/cxx-o.make) +$(call include,$(bld_root)/cxx/cxx-d.make) + +# Dependencies. +# +$(call import,$(src_root)/xml/makefile) diff --git a/xml/serializer b/xml/serializer index 316a501..eb592df 100644 --- a/xml/serializer +++ b/xml/serializer @@ -185,6 +185,8 @@ namespace xml // namespace is declared. If both prefix and namespace are empty, // then the default namespace declaration is cleared (xmlns=""). // + // This function should be called after start_element(). + // void namespace_decl (const std::string& ns, const std::string& prefix); @@ -251,6 +253,20 @@ namespace xml genxSender sender_; std::size_t depth_; }; + + // Stream-like interface for serializer. If the passed argument + // is a function with the void f(serializer&) signature or is a + // function object with the void operator() (serializer&) const + // operator, then this function (object) is called with the passed + // serializer. Otherwise, the argument is passed to the serializer's + // characters() function. + // + serializer& + operator<< (serializer&, void (*func) (serializer&)); + + template + serializer& + operator<< (serializer&, const T& value); } #include diff --git a/xml/serializer.ixx b/xml/serializer.ixx index ea3dcf0..5cff976 100644 --- a/xml/serializer.ixx +++ b/xml/serializer.ixx @@ -118,4 +118,59 @@ namespace xml { characters (value_traits::serialize (value, *this)); } + + // operator<< + // + + inline serializer& + operator<< (serializer& s, void (*func) (serializer&)) + { + func (s); + return s; + } + + namespace details + { + // Detect whether T defines void operator(A) const. + // + template + struct is_functor + { + typedef char no[1]; + typedef char yes[2]; + + template struct check; + + template + static no& test (...); + + template + static yes& test (check*); + + static const bool value = sizeof (test (0)) == sizeof (yes); + }; + + template ::value> + struct inserter; + + template + struct inserter + { + static void insert (serializer& s, const T& f) {f (s);} + }; + + template + struct inserter + { + static void insert (serializer& s, const T& v) {s.characters (v);} + }; + } + + template + inline serializer& + operator<< (serializer& s, const T& value) + { + details::inserter::insert (s, value); + return s; + } } -- cgit v1.1