2009-03-31 21:06:06 +02:00
|
|
|
#ifndef ARABICA_XSLT_OUTPUT_HPP
|
|
|
|
#define ARABICA_XSLT_OUTPUT_HPP
|
|
|
|
|
|
|
|
#include <XML/escaper.hpp>
|
|
|
|
#include <SAX/ext/LexicalHandler.hpp>
|
|
|
|
#include "xslt_namespace_stack.hpp"
|
|
|
|
#include "xslt_qname.hpp"
|
|
|
|
#include "handler/xslt_constants.hpp"
|
|
|
|
|
|
|
|
namespace Arabica
|
|
|
|
{
|
|
|
|
namespace XSLT
|
|
|
|
{
|
|
|
|
|
2012-11-06 20:29:22 +01:00
|
|
|
template<class string_type, class string_adaptor>
|
2009-03-31 21:06:06 +02:00
|
|
|
class Output
|
|
|
|
{
|
|
|
|
public:
|
2012-11-06 20:29:22 +01:00
|
|
|
typedef std::map<string_type, string_type> Settings;
|
2012-11-06 21:15:35 +01:00
|
|
|
typedef std::set<QName<string_type, string_adaptor> > CDATAElements;
|
2009-03-31 21:06:06 +02:00
|
|
|
|
|
|
|
protected:
|
|
|
|
Output() :
|
|
|
|
buffering_(0),
|
|
|
|
pending_element_(false),
|
2009-04-03 20:01:26 +02:00
|
|
|
pending_text_(false),
|
2009-03-31 21:06:06 +02:00
|
|
|
pending_attribute_(-1),
|
|
|
|
text_mode_(false),
|
|
|
|
warning_sink_(0)
|
|
|
|
{
|
|
|
|
} // Output
|
|
|
|
~Output()
|
|
|
|
{
|
|
|
|
} // ~Output
|
|
|
|
|
|
|
|
public:
|
2009-04-07 11:29:56 +02:00
|
|
|
void start_document(const Settings& settings, const CDATAElements& cdataElements)
|
2009-03-31 21:06:06 +02:00
|
|
|
{
|
2012-11-06 20:35:28 +01:00
|
|
|
typename Settings::const_iterator method = settings.find("method");
|
2009-03-31 21:06:06 +02:00
|
|
|
text_mode_ = (method != settings.end() && method->second == "text");
|
|
|
|
if(text_mode_)
|
|
|
|
do_disableOutputEscaping(true);
|
|
|
|
|
2009-04-07 11:29:56 +02:00
|
|
|
cdataElements_ = cdataElements;
|
2009-03-31 21:06:06 +02:00
|
|
|
do_start_document(settings);
|
|
|
|
} // start_document
|
|
|
|
|
|
|
|
void end_document()
|
|
|
|
{
|
|
|
|
do_end_document();
|
|
|
|
} // end_document
|
|
|
|
|
2012-11-06 20:29:22 +01:00
|
|
|
bool start_element(const string_type& qName, const string_type& namespaceURI)
|
2009-03-31 21:06:06 +02:00
|
|
|
{
|
2012-11-06 21:15:35 +01:00
|
|
|
QName<string_type, string_adaptor> en = QName<string_type, string_adaptor>::create(qName, namespaceURI);
|
2009-03-31 21:06:06 +02:00
|
|
|
return start_element(en.prefix, en.localName, en.namespaceURI);
|
|
|
|
} // start_element
|
|
|
|
|
2012-11-06 20:29:22 +01:00
|
|
|
bool start_element(const string_type& prefix, const string_type& localName, const string_type& namespaceURI)
|
2009-03-31 21:06:06 +02:00
|
|
|
{
|
|
|
|
if(is_buffering())
|
|
|
|
return false;
|
|
|
|
|
2009-04-03 20:01:26 +02:00
|
|
|
flush_current();
|
2009-03-31 21:06:06 +02:00
|
|
|
|
|
|
|
namespaceStack_.pushScope();
|
|
|
|
if(!namespaceURI.empty())
|
|
|
|
namespaceStack_.declareNamespace(prefix, namespaceURI);
|
|
|
|
|
2012-11-06 20:29:22 +01:00
|
|
|
string_type mapped_prefix = namespaceStack_.findPrefix(namespaceURI);
|
2009-03-31 21:06:06 +02:00
|
|
|
|
2012-11-06 21:15:35 +01:00
|
|
|
element_stack_.push(QName<string_type, string_adaptor>(mapped_prefix, localName, namespaceURI));
|
2009-03-31 21:06:06 +02:00
|
|
|
|
|
|
|
atts_.clear();
|
|
|
|
pending_element_ = true;
|
|
|
|
|
|
|
|
namespaceStack_.pushScope();
|
|
|
|
return true;
|
|
|
|
} // start_element
|
|
|
|
|
2012-11-06 20:29:22 +01:00
|
|
|
void end_element(const string_type& qName, const string_type& namespaceURI)
|
2009-03-31 21:06:06 +02:00
|
|
|
{
|
2012-11-06 21:15:35 +01:00
|
|
|
QName<string_type, string_adaptor> en = QName<string_type, string_adaptor>::create(qName, namespaceURI);
|
2009-03-31 21:06:06 +02:00
|
|
|
end_element(en.prefix, en.localName, en.namespaceURI);
|
|
|
|
} // end_element
|
|
|
|
|
2012-11-06 20:29:22 +01:00
|
|
|
void end_element(const string_type& /* prefix */, const string_type& localName, const string_type& namespaceURI)
|
2009-03-31 21:06:06 +02:00
|
|
|
{
|
|
|
|
if(pop_if_buffering())
|
|
|
|
return;
|
|
|
|
|
2009-04-03 20:01:26 +02:00
|
|
|
flush_current();
|
2009-03-31 21:06:06 +02:00
|
|
|
|
|
|
|
if(!text_mode_)
|
|
|
|
{
|
2012-11-06 20:29:22 +01:00
|
|
|
string_type mapped_prefix = namespaceStack_.findPrefix(namespaceURI);
|
2009-03-31 21:06:06 +02:00
|
|
|
do_end_element((!mapped_prefix.empty()) ? mapped_prefix + ':' + localName : localName, namespaceURI);
|
|
|
|
element_stack_.pop();
|
|
|
|
} // end_element
|
|
|
|
|
|
|
|
namespaceStack_.popScope();
|
|
|
|
} // end_element
|
|
|
|
|
2012-11-06 20:29:22 +01:00
|
|
|
void start_attribute(const string_type& qName, const string_type& namespaceURI)
|
2009-03-31 21:06:06 +02:00
|
|
|
{
|
2012-11-06 21:15:35 +01:00
|
|
|
QName<string_type, string_adaptor> qn = QName<string_type, string_adaptor>::create(qName, namespaceURI);
|
2009-03-31 21:06:06 +02:00
|
|
|
start_attribute(qn.prefix, qn.localName, qn.namespaceURI);
|
|
|
|
} // start_attribute
|
|
|
|
|
2012-11-06 20:29:22 +01:00
|
|
|
void start_attribute(const string_type& prefix, const string_type& localName, const string_type& namespaceURI)
|
2009-03-31 21:06:06 +02:00
|
|
|
{
|
|
|
|
if(push_buffering())
|
|
|
|
return;
|
|
|
|
|
|
|
|
if(!pending_element_)
|
|
|
|
{
|
|
|
|
warning("WARNING: Cannot write attribute, no open element");
|
|
|
|
return;
|
|
|
|
} // if ...
|
|
|
|
|
|
|
|
if(!namespaceURI.empty())
|
|
|
|
namespaceStack_.declareNamespace(prefix, namespaceURI, true);
|
|
|
|
|
2012-11-06 20:29:22 +01:00
|
|
|
string_type mapped_prefix = namespaceStack_.findPrefix(namespaceURI);
|
|
|
|
string_type qName = (!mapped_prefix.empty()) ? mapped_prefix + ':' + localName : localName;
|
2009-03-31 21:06:06 +02:00
|
|
|
|
|
|
|
atts_.addOrReplaceAttribute(namespaceURI,
|
|
|
|
localName,
|
|
|
|
qName,
|
|
|
|
"",
|
|
|
|
"");
|
|
|
|
pending_attribute_ = atts_.getIndex(qName);
|
|
|
|
} // start_attribute
|
|
|
|
|
|
|
|
void end_attribute()
|
|
|
|
{
|
|
|
|
if(pop_buffering())
|
|
|
|
return;
|
|
|
|
|
|
|
|
if(pending_attribute_ == -1)
|
|
|
|
return;
|
|
|
|
|
|
|
|
atts_.setValue(pending_attribute_, buffer_.str());
|
|
|
|
|
|
|
|
pending_attribute_ = -1;
|
|
|
|
} // end_attribute
|
|
|
|
|
2012-11-06 20:29:22 +01:00
|
|
|
void add_attribute(const string_type& uri,
|
|
|
|
const string_type& localName,
|
|
|
|
const string_type& qName,
|
|
|
|
const string_type& value)
|
2009-03-31 21:06:06 +02:00
|
|
|
{
|
|
|
|
if(!pending_element_)
|
|
|
|
{
|
|
|
|
warning("WARNING: Cannot write attribute, no open element");
|
|
|
|
return;
|
|
|
|
} // if ...
|
|
|
|
|
|
|
|
atts_.addAttribute(uri, localName, qName, "", value);
|
|
|
|
} // add_attribute
|
|
|
|
|
2012-11-06 20:29:22 +01:00
|
|
|
void characters(const string_type& ch)
|
2009-03-31 21:06:06 +02:00
|
|
|
{
|
|
|
|
if(buffering_)
|
|
|
|
{
|
|
|
|
if(buffering_ == 1)
|
|
|
|
buffer_ << ch;
|
|
|
|
return;
|
|
|
|
} // if ...
|
|
|
|
|
|
|
|
flush_element();
|
|
|
|
|
|
|
|
if(pending_attribute_ != -1)
|
|
|
|
{
|
|
|
|
atts_.setValue(pending_attribute_, atts_.getValue(pending_attribute_) + ch);
|
|
|
|
return;
|
|
|
|
} // if ...
|
|
|
|
|
2009-04-03 20:01:26 +02:00
|
|
|
if(buffering_)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if(!pending_text_)
|
|
|
|
{
|
|
|
|
pending_text_ = true;
|
|
|
|
if(isCDATA())
|
|
|
|
do_start_CDATA();
|
|
|
|
} // if ...
|
|
|
|
do_characters(ch);
|
2009-03-31 21:06:06 +02:00
|
|
|
} // characters
|
|
|
|
|
|
|
|
void start_comment()
|
|
|
|
{
|
|
|
|
if(push_buffering())
|
|
|
|
return;
|
|
|
|
|
2009-04-03 20:01:26 +02:00
|
|
|
flush_current();
|
2009-03-31 21:06:06 +02:00
|
|
|
} // start_comment
|
|
|
|
|
|
|
|
void end_comment()
|
|
|
|
{
|
|
|
|
if(pop_buffering())
|
|
|
|
return;
|
|
|
|
|
|
|
|
if(!text_mode_)
|
|
|
|
{
|
2012-11-06 20:29:22 +01:00
|
|
|
string_type comment = escape(buffer_.str(), "--", "- -");
|
2009-03-31 21:06:06 +02:00
|
|
|
if(comment.length() && *(comment.rbegin()) == '-')
|
|
|
|
comment.append(" ");
|
|
|
|
do_comment(comment);
|
|
|
|
} // if ...
|
|
|
|
} // end_comment
|
|
|
|
|
2012-11-06 20:29:22 +01:00
|
|
|
void start_processing_instruction(const string_type& target)
|
2009-03-31 21:06:06 +02:00
|
|
|
{
|
|
|
|
if(push_buffering())
|
|
|
|
return;
|
|
|
|
|
2009-04-03 20:01:26 +02:00
|
|
|
flush_current();
|
2009-03-31 21:06:06 +02:00
|
|
|
|
|
|
|
target_ = target;
|
|
|
|
} // start_processing_instruction
|
|
|
|
|
|
|
|
void end_processing_instruction()
|
|
|
|
{
|
|
|
|
if(pop_buffering())
|
|
|
|
return;
|
|
|
|
|
|
|
|
if(!text_mode_)
|
|
|
|
{
|
2012-11-06 20:29:22 +01:00
|
|
|
string_type data = escape(buffer_.str(), "?>", "? >");
|
2009-03-31 21:06:06 +02:00
|
|
|
do_processing_instruction(target_, data);
|
|
|
|
} // if ...
|
|
|
|
} // end_processing_instruction
|
|
|
|
|
|
|
|
void disableOutputEscaping(bool disable)
|
|
|
|
{
|
|
|
|
if(!text_mode_)
|
|
|
|
do_disableOutputEscaping(disable);
|
|
|
|
} // disableOutputEscaping
|
|
|
|
|
|
|
|
void set_warning_sink(Output& warning_sink)
|
|
|
|
{
|
|
|
|
warning_sink_ = &warning_sink;
|
|
|
|
} // set_warning_sink
|
|
|
|
|
|
|
|
protected:
|
|
|
|
virtual void do_start_document(const Settings& settings) = 0;
|
|
|
|
virtual void do_end_document() = 0;
|
2012-11-06 20:29:22 +01:00
|
|
|
virtual void do_start_element(const string_type& qName, const string_type& namespaceURI, const SAX::Attributes<string_type, string_adaptor>& atts) = 0;
|
|
|
|
virtual void do_end_element(const string_type& qName, const string_type& namespaceURI) = 0;
|
|
|
|
virtual void do_characters(const string_type& ch) = 0;
|
2009-04-03 20:01:26 +02:00
|
|
|
virtual void do_start_CDATA() = 0;
|
|
|
|
virtual void do_end_CDATA() = 0;
|
2012-11-06 20:29:22 +01:00
|
|
|
virtual void do_comment(const string_type& ch) = 0;
|
|
|
|
virtual void do_processing_instruction(const string_type& target, const string_type& data) = 0;
|
2009-03-31 21:06:06 +02:00
|
|
|
virtual void do_disableOutputEscaping(bool disable) = 0;
|
|
|
|
virtual bool want_namespace_declarations() const = 0;
|
|
|
|
|
|
|
|
private:
|
2012-11-06 20:29:22 +01:00
|
|
|
string_type escape(string_type str, const string_type& t, const string_type& r) const
|
2009-03-31 21:06:06 +02:00
|
|
|
{
|
2012-11-06 20:35:28 +01:00
|
|
|
typename string_type::size_type naughty = str.find(t);
|
2012-11-06 20:29:22 +01:00
|
|
|
while(naughty != string_type::npos)
|
2009-03-31 21:06:06 +02:00
|
|
|
{
|
|
|
|
str.replace(naughty, t.length(), r, 0, r.length());
|
|
|
|
naughty = str.find(t, naughty);
|
|
|
|
} // while
|
|
|
|
return str;
|
|
|
|
} // escape
|
|
|
|
|
|
|
|
bool push_buffering()
|
|
|
|
{
|
|
|
|
bool is_buf = is_buffering();
|
|
|
|
++buffering_;
|
|
|
|
|
|
|
|
if(is_buf)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
buffer_.str("");
|
|
|
|
return false;
|
|
|
|
} // push_buffering
|
|
|
|
|
|
|
|
bool is_buffering()
|
|
|
|
{
|
|
|
|
if(!buffering_)
|
|
|
|
return false;
|
|
|
|
warning("WARNING: non-text ignored when creating processing instruction, comment or attribute");
|
|
|
|
return true;
|
|
|
|
} // is_buffering
|
|
|
|
|
|
|
|
bool pop_if_buffering()
|
|
|
|
{
|
|
|
|
if(!buffering_)
|
|
|
|
return false;
|
|
|
|
--buffering_;
|
|
|
|
return true;
|
|
|
|
} // pop_if_buffering
|
|
|
|
|
|
|
|
bool pop_buffering()
|
|
|
|
{
|
|
|
|
--buffering_;
|
|
|
|
return (buffering_ != 0); // oh, Visual Studio how I curse you warning C4800
|
|
|
|
} // pop_buffering
|
|
|
|
|
2009-04-03 20:01:26 +02:00
|
|
|
void flush_current()
|
|
|
|
{
|
|
|
|
if(pending_text_)
|
|
|
|
{
|
|
|
|
if(isCDATA())
|
|
|
|
do_end_CDATA();
|
|
|
|
pending_text_ = false;
|
|
|
|
} // if ...
|
|
|
|
|
|
|
|
flush_element();
|
|
|
|
} // flush_current
|
|
|
|
|
2009-03-31 21:06:06 +02:00
|
|
|
void flush_element()
|
|
|
|
{
|
|
|
|
if((!pending_element_) || (pending_attribute_ != -1))
|
|
|
|
return;
|
|
|
|
|
|
|
|
if(text_mode_)
|
|
|
|
return;
|
|
|
|
|
|
|
|
bool want_decs = want_namespace_declarations();
|
|
|
|
if(want_decs)
|
|
|
|
addNamespaceDeclarations();
|
|
|
|
namespaceStack_.popScope();
|
|
|
|
if(want_decs)
|
|
|
|
addNamespaceDeclarations();
|
|
|
|
|
|
|
|
do_start_element(element_stack_.top().qname, element_stack_.top().namespaceURI, atts_);
|
|
|
|
pending_element_ = false;
|
|
|
|
} // flush_element
|
|
|
|
|
2009-04-03 20:01:26 +02:00
|
|
|
bool isCDATA()
|
|
|
|
{
|
2009-04-07 14:39:31 +02:00
|
|
|
if(element_stack_.empty())
|
|
|
|
return false;
|
2012-11-06 21:15:35 +01:00
|
|
|
QName<string_type, string_adaptor> currentElement = element_stack_.top();
|
2009-04-03 20:01:26 +02:00
|
|
|
return cdataElements_.find(currentElement) != cdataElements_.end();
|
|
|
|
} // isCDATA
|
|
|
|
|
2009-03-31 21:06:06 +02:00
|
|
|
void addNamespaceDeclarations()
|
|
|
|
{
|
2012-11-09 20:24:27 +01:00
|
|
|
for(typename NamespaceStack<string_type>::Scope::const_iterator n = namespaceStack_.begin(), ne = namespaceStack_.end(); n != ne; ++n)
|
2009-03-31 21:06:06 +02:00
|
|
|
{
|
|
|
|
if(n->first == "xml")
|
|
|
|
continue;
|
|
|
|
|
2012-11-06 20:29:22 +01:00
|
|
|
string_type qName = (n->first.empty()) ? "xmlns" : "xmlns:" + n->first;
|
2009-03-31 21:06:06 +02:00
|
|
|
atts_.addAttribute("http://www.w3.org/2000/xmlns",
|
|
|
|
n->first,
|
|
|
|
qName,
|
|
|
|
"",
|
|
|
|
n->second);
|
|
|
|
}
|
|
|
|
} // addNamespaceDeclarations
|
|
|
|
|
2012-11-06 20:29:22 +01:00
|
|
|
void warning(const string_type& warning_message)
|
2009-03-31 21:06:06 +02:00
|
|
|
{
|
|
|
|
warning_sink_->characters(warning_message);
|
|
|
|
warning_sink_->characters("\n");
|
|
|
|
} // warning
|
|
|
|
|
|
|
|
int buffering_;
|
|
|
|
bool pending_element_;
|
2009-04-03 20:01:26 +02:00
|
|
|
bool pending_text_;
|
2010-01-10 23:02:43 +01:00
|
|
|
int pending_attribute_;
|
|
|
|
bool text_mode_;
|
|
|
|
Output* warning_sink_;
|
2009-04-03 20:01:26 +02:00
|
|
|
CDATAElements cdataElements_;
|
2012-11-06 21:15:35 +01:00
|
|
|
std::stack<QName<string_type, string_adaptor> > element_stack_;
|
2012-11-06 20:29:22 +01:00
|
|
|
string_type target_;
|
|
|
|
SAX::AttributesImpl<string_type, string_adaptor> atts_;
|
2009-03-31 21:06:06 +02:00
|
|
|
std::stringstream buffer_;
|
2012-11-09 20:24:27 +01:00
|
|
|
NamespaceStack<string_type> namespaceStack_;
|
2009-03-31 21:06:06 +02:00
|
|
|
}; // class Output
|
|
|
|
|
|
|
|
} // namespace XSLT
|
|
|
|
} // namespace Arabica
|
|
|
|
|
|
|
|
#endif // ARABICA_XSLT_OUTPUT_HPP
|
|
|
|
|