mirror of
https://github.com/jezhiggins/arabica
synced 2024-12-26 21:58:39 +01:00
504 lines
12 KiB
C++
504 lines
12 KiB
C++
#ifndef ARABICA_XSLT_SINK_HPP
|
|
#define ARABICA_XSLT_SINK_HPP
|
|
|
|
#include <ostream>
|
|
#include <sstream>
|
|
#include <XML/escaper.hpp>
|
|
#include <SAX/ContentHandler.hpp>
|
|
#include <SAX/ext/LexicalHandler.hpp>
|
|
#include <SAX/helpers/AttributesImpl.hpp>
|
|
#include "xslt_output.hpp"
|
|
#include "handler/xslt_constants.hpp"
|
|
|
|
namespace Arabica
|
|
{
|
|
namespace XSLT
|
|
{
|
|
|
|
template<class string_type, class string_adaptor = Arabica::default_string_adaptor<string_type> >
|
|
class Sink
|
|
{
|
|
public:
|
|
virtual ~Sink() { }
|
|
|
|
virtual Output<string_type, string_adaptor>& asOutput() = 0;
|
|
}; // class Sink
|
|
|
|
template<class string_type, class string_adaptor>
|
|
class SinkHolder
|
|
{
|
|
public:
|
|
SinkHolder(Sink<string_type, string_adaptor>* sink) :
|
|
sink_(sink),
|
|
owned_(true)
|
|
{
|
|
} // SinkHolder
|
|
|
|
SinkHolder(Sink<string_type, string_adaptor>& sink) :
|
|
sink_(&sink),
|
|
owned_(false)
|
|
{
|
|
} // SinkHolder
|
|
|
|
~SinkHolder()
|
|
{
|
|
clear();
|
|
} // ~SinkHolder
|
|
|
|
void reset(Sink<string_type, string_adaptor>* sink)
|
|
{
|
|
clear();
|
|
sink_ = sink;
|
|
owned_ = true;
|
|
} // assign
|
|
|
|
void reset(Sink<string_type, string_adaptor>& sink)
|
|
{
|
|
clear();
|
|
sink_ = &sink;
|
|
owned_ = false;
|
|
} // reset
|
|
|
|
Sink<string_type, string_adaptor>& get() const
|
|
{
|
|
return *sink_;
|
|
} // get
|
|
|
|
private:
|
|
void clear()
|
|
{
|
|
if(owned_)
|
|
delete sink_;
|
|
} // clear
|
|
|
|
Sink<string_type, string_adaptor>* sink_;
|
|
bool owned_;
|
|
|
|
SinkHolder(const SinkHolder&);
|
|
bool operator==(const SinkHolder&) const;
|
|
SinkHolder& operator=(const SinkHolder&);
|
|
}; // class SinkHolder
|
|
|
|
template<class string_type, class string_adaptor = Arabica::default_string_adaptor<string_type> >
|
|
class StreamSink : public Sink<string_type, string_adaptor>, private Output<string_type, string_adaptor>
|
|
{
|
|
typedef StylesheetConstant<string_type, string_adaptor> SC;
|
|
|
|
public:
|
|
StreamSink(std::basic_ostream<typename string_adaptor::value_type>& stream) :
|
|
stream_(stream),
|
|
disable_output_escaping_(false),
|
|
in_cdata_(false),
|
|
empty_(false),
|
|
seen_root_(true),
|
|
indent_(-1),
|
|
out_again_(false)
|
|
{
|
|
} // StreamSink
|
|
|
|
StreamSink(const StreamSink& rhs) :
|
|
stream_(rhs.stream_),
|
|
disable_output_escaping_(rhs.disable_output_escaping_),
|
|
in_cdata_(rhs.in_cdata_),
|
|
empty_(false),
|
|
seen_root_(rhs.seen_root_),
|
|
indent_(rhs.indent_),
|
|
out_again_(rhs.out_again_),
|
|
settings_(rhs.settings_)
|
|
{
|
|
} // StreamSink
|
|
|
|
~StreamSink()
|
|
{
|
|
flush();
|
|
} // ~StreamSink
|
|
|
|
virtual Output<string_type, string_adaptor>& asOutput() { return *this; }
|
|
|
|
void flush() {
|
|
stream_.flush();
|
|
}
|
|
|
|
protected:
|
|
typedef typename Output<string_type, string_adaptor>::Settings Settings;
|
|
|
|
void do_start_document(const Settings& settings)
|
|
{
|
|
seen_root_ = false;
|
|
settings_ = settings;
|
|
if(setting(SC::indent) == SC::yes)
|
|
indent_ = 0;
|
|
} // do_start_document
|
|
|
|
void do_end_document()
|
|
{
|
|
} // do_end_document
|
|
|
|
void do_start_element(const string_type& qName,
|
|
const string_type& /* namespaceURI */,
|
|
const SAX::Attributes<string_type, string_adaptor>& atts)
|
|
{
|
|
if(!seen_root_)
|
|
do_decl(qName);
|
|
|
|
if(empty_)
|
|
stream_ << ">";
|
|
|
|
indent();
|
|
stream_ << '<' << qName;
|
|
for(int a = 0; a < atts.getLength(); ++a)
|
|
{
|
|
stream_ << ' ' << atts.getQName(a) << '=' << '\"';
|
|
string_type ch = atts.getValue(a);
|
|
std::for_each(string_adaptor::begin(ch), string_adaptor::end(ch), Arabica::XML::attribute_escaper<typename string_adaptor::value_type>(stream_));
|
|
stream_ << '\"';
|
|
}
|
|
empty_ = true;
|
|
} // do_start_element
|
|
|
|
void do_end_element(const string_type& qName,
|
|
const string_type& /* namespaceURI */)
|
|
{
|
|
if(!seen_root_)
|
|
do_decl(string_adaptor::empty_string());
|
|
|
|
preoutdent(empty_);
|
|
|
|
if(empty_)
|
|
stream_ << "/>";
|
|
else
|
|
stream_ << "</" << qName << ">";
|
|
|
|
outdent(empty_);
|
|
|
|
empty_ = false;
|
|
} // do_end_element
|
|
|
|
void do_characters(const string_type& ch)
|
|
{
|
|
close_element_if_empty();
|
|
|
|
if(!disable_output_escaping_ && !in_cdata_)
|
|
std::for_each(string_adaptor::begin(ch), string_adaptor::end(ch), Arabica::XML::text_escaper<typename string_adaptor::value_type>(stream_));
|
|
else if(in_cdata_)
|
|
{
|
|
size_t breakAt = string_adaptor::find(ch, SC::CDATAEnd);
|
|
if(breakAt == string_adaptor::npos())
|
|
{
|
|
stream_ << ch;
|
|
return;
|
|
}
|
|
size_t start = 0;
|
|
do
|
|
{
|
|
breakAt += 2;
|
|
stream_ << string_adaptor::substr(ch, start, breakAt);
|
|
do_end_CDATA();
|
|
start = breakAt;
|
|
do_start_CDATA();
|
|
breakAt = string_adaptor::find(ch, SC::CDATAEnd, breakAt);
|
|
}
|
|
while(breakAt != string_adaptor::npos());
|
|
stream_ << string_adaptor::substr(ch, start);
|
|
}
|
|
else
|
|
stream_ << ch;
|
|
} // characters
|
|
|
|
void do_start_CDATA()
|
|
{
|
|
close_element_if_empty();
|
|
|
|
in_cdata_ = true;
|
|
stream_ << SC::CDATAStart;
|
|
} // do_start_CDATA
|
|
|
|
void do_end_CDATA()
|
|
{
|
|
in_cdata_ = false;
|
|
stream_ << SC::CDATAEnd;
|
|
} // do_end_CDATA
|
|
|
|
void do_comment(const string_type& ch)
|
|
{
|
|
close_element_if_empty();
|
|
|
|
stream_ << SC::CommentStart
|
|
<< ch
|
|
<< SC::CommentEnd;
|
|
} // do_comment
|
|
|
|
void do_processing_instruction(const string_type& target,
|
|
const string_type& data)
|
|
{
|
|
close_element_if_empty();
|
|
|
|
stream_ << SC::PIStart
|
|
<< target
|
|
<< " "
|
|
<< data
|
|
<< SC::PIEnd;
|
|
} // do_processing_instruction
|
|
|
|
void do_disableOutputEscaping(bool disable) { disable_output_escaping_ = disable; }
|
|
|
|
bool want_namespace_declarations() const { return true; }
|
|
|
|
private:
|
|
void close_element_if_empty()
|
|
{
|
|
if(empty_)
|
|
{
|
|
stream_ << '>';
|
|
empty_ = false;
|
|
}
|
|
|
|
if(!seen_root_)
|
|
do_decl(string_adaptor::empty_string());
|
|
} // close_element_if_empty
|
|
|
|
void indent()
|
|
{
|
|
if(indent_ == -1)
|
|
return;
|
|
|
|
if(indent_ != 0)
|
|
stream_ << "\n";
|
|
|
|
for(int i = 0; i != indent_; ++i)
|
|
stream_ << " ";
|
|
|
|
indent_ += 2;
|
|
|
|
out_again_ = false;
|
|
} // indent
|
|
|
|
void preoutdent(bool empty)
|
|
{
|
|
if(out_again_ && !empty)
|
|
stream_ << "\n";
|
|
} // preoutdent
|
|
|
|
void outdent(bool empty)
|
|
{
|
|
if(indent_ == -1)
|
|
return;
|
|
|
|
if(out_again_ && empty)
|
|
stream_ << "\n";
|
|
|
|
indent_ -= 2;
|
|
out_again_ = true;
|
|
} // outdent
|
|
|
|
void do_decl(const string_type& qName)
|
|
{
|
|
if((setting(SC::method) == SC::text) || (setting(SC::omit_xml_declaration) == SC::yes))
|
|
return;
|
|
|
|
{
|
|
string_type version = setting(SC::version);
|
|
if(string_adaptor::empty(version))
|
|
version = SC::Version;
|
|
stream_ << "<?xml version=\"" << version << "\"";
|
|
}
|
|
{
|
|
string_type s = setting(SC::standalone);
|
|
if(!string_adaptor::empty(s))
|
|
stream_ << " standalone=\"" << s << "\"";
|
|
}
|
|
stream_ << "?>\n";
|
|
|
|
if(!string_adaptor::empty(qName))
|
|
{
|
|
string_type pub = setting(SC::doctype_public);
|
|
string_type sys = setting(SC::doctype_system);
|
|
|
|
if(!string_adaptor::empty(sys))
|
|
{
|
|
stream_ << "<!DOCTYPE " << qName << "\n";
|
|
if(!string_adaptor::empty(pub))
|
|
stream_ << " PUBLIC \"" << pub << "\" \"" << sys << "\">\n";
|
|
else
|
|
stream_ << " SYSTEM \"" << sys << "\">\n";
|
|
} // if(!sys.empty())
|
|
}
|
|
seen_root_ = true;
|
|
} // do_decl
|
|
|
|
string_type setting(const string_type& name)
|
|
{
|
|
typename Settings::const_iterator i = settings_.find(name);
|
|
if(i == settings_.end())
|
|
return string_adaptor::empty_string();
|
|
return i->second;
|
|
} // setting
|
|
|
|
std::basic_ostream<typename string_adaptor::value_type>& stream_;
|
|
bool disable_output_escaping_;
|
|
bool in_cdata_;
|
|
bool empty_;
|
|
bool seen_root_;
|
|
int indent_;
|
|
bool out_again_;
|
|
Settings settings_;
|
|
|
|
bool operator==(const StreamSink&) const;
|
|
StreamSink& operator=(const StreamSink&);
|
|
}; // class StreamSink
|
|
|
|
template<class string_type, class string_adaptor = Arabica::default_string_adaptor<string_type> >
|
|
class DOMSink : public Sink<string_type, string_adaptor>, private Output<string_type, string_adaptor>
|
|
{
|
|
typedef StylesheetConstant<string_type, string_adaptor> SC;
|
|
public:
|
|
DOMSink() :
|
|
indent_(-1),
|
|
out_again_(false)
|
|
{
|
|
} // DOMSink
|
|
|
|
~DOMSink()
|
|
{
|
|
} // ~DOMSink
|
|
|
|
virtual Output<string_type, string_adaptor>& asOutput() { return *this; }
|
|
DOM::Node<string_type, string_adaptor> node() const { return documentFrag_; }
|
|
|
|
protected:
|
|
typedef typename Output<string_type, string_adaptor>::Settings Settings;
|
|
|
|
void do_start_document(const Settings& settings)
|
|
{
|
|
typename Settings::const_iterator i = settings.find(SC::indent);
|
|
if((i != settings.end()) &&
|
|
(i->second == SC::yes))
|
|
indent_ = 0;
|
|
} // do_start_document
|
|
|
|
void do_end_document()
|
|
{
|
|
} // do_end_document
|
|
|
|
void do_characters(const string_type& ch)
|
|
{
|
|
DOM::Node<string_type, string_adaptor> lc = current().getLastChild();
|
|
if(lc == 0 || lc.getNodeType() != DOM::Node_base::TEXT_NODE)
|
|
current().appendChild(document().createTextNode(ch));
|
|
else
|
|
lc.setNodeValue(string_adaptor::concat(lc.getNodeValue(), ch));
|
|
} // do_characters
|
|
|
|
void do_start_CDATA()
|
|
{
|
|
} // do_start_CDATA
|
|
|
|
void do_end_CDATA()
|
|
{
|
|
DOM::Node<string_type, string_adaptor> lc = current().getLastChild();
|
|
if(lc.getNodeType() == DOM::Node_base::TEXT_NODE)
|
|
current().replaceChild(document().createCDATASection(lc.getNodeValue()), lc);
|
|
} // do_end_CDATA
|
|
|
|
void do_comment(const string_type& ch)
|
|
{
|
|
current().appendChild(document().createComment(ch));
|
|
} // do_comment
|
|
|
|
void do_processing_instruction(const string_type& target,
|
|
const string_type& data)
|
|
{
|
|
current().appendChild(document().createProcessingInstruction(target, data));
|
|
} // do_processing_instruction
|
|
|
|
void do_disableOutputEscaping(bool /* disable */) { }
|
|
|
|
bool want_namespace_declarations() const { return false; }
|
|
|
|
void do_start_element(const string_type& qName,
|
|
const string_type& namespaceURI,
|
|
const SAX::Attributes<string_type, string_adaptor>& atts)
|
|
{
|
|
indent();
|
|
DOM::Element<string_type, string_adaptor> elem = document().createElementNS(namespaceURI, qName);
|
|
current().appendChild(elem);
|
|
|
|
// attributes here
|
|
for(int i = 0; i < atts.getLength(); ++i)
|
|
elem.setAttributeNS(atts.getURI(i), atts.getQName(i), atts.getValue(i));
|
|
|
|
current_ = elem;
|
|
} // do_start_element
|
|
|
|
void do_end_element(const string_type& /* qName */,
|
|
const string_type& /* namespaceURI */)
|
|
{
|
|
outdent();
|
|
current_ = current_.getParentNode();
|
|
} // do_end_element
|
|
|
|
private:
|
|
DOM::Document<string_type, string_adaptor>& document()
|
|
{
|
|
if(document_ != 0)
|
|
return document_;
|
|
|
|
DOM::DOMImplementation<string_type, string_adaptor> di = SimpleDOM::DOMImplementation<string_type, string_adaptor>::getDOMImplementation();
|
|
document_ = di.createDocument(string_adaptor::empty_string(), string_adaptor::empty_string(), 0);
|
|
return document_;
|
|
} // document
|
|
|
|
DOM::Node<string_type, string_adaptor>& current()
|
|
{
|
|
if(current_ != 0)
|
|
return current_;
|
|
|
|
documentFrag_ = document().createDocumentFragment();
|
|
current_ = documentFrag_;
|
|
return current_;
|
|
} // current
|
|
|
|
void indent()
|
|
{
|
|
if(indent_ == -1)
|
|
return;
|
|
|
|
string_type sps;
|
|
if(indent_ != 0)
|
|
{
|
|
std::basic_string<typename string_adaptor::value_type> sps;
|
|
sps += SC::CARRIAGE_RETURN;
|
|
std::fill_n(std::back_inserter<std::basic_string<typename string_adaptor::value_type> >(sps), indent_, SC::SPACE);
|
|
do_characters(string_adaptor::construct(sps));
|
|
} // if ...
|
|
|
|
indent_ += 2;
|
|
|
|
out_again_ = false;
|
|
} // indent
|
|
|
|
void outdent()
|
|
{
|
|
if(indent_ == -1)
|
|
return;
|
|
|
|
if(out_again_)
|
|
do_characters(SC::newline);
|
|
|
|
indent_ -= 2;
|
|
out_again_ = true;
|
|
} // outdent
|
|
|
|
DOM::Document<string_type, string_adaptor> document_;
|
|
DOM::DocumentFragment<string_type, string_adaptor> documentFrag_;
|
|
DOM::Node<string_type, string_adaptor> current_;
|
|
|
|
int indent_;
|
|
bool out_again_;
|
|
}; // DOMSink
|
|
|
|
} // namespace XSLT
|
|
} // namespace Arabica
|
|
|
|
#endif // ARABICA_XSLT_SINK_HPP
|
|
|