new fortune
[feisty_meow.git] / nucleus / library / textual / xml_generator.cpp
1 /*****************************************************************************\
2 *                                                                             *
3 *  Name   : xml_generator                                                     *
4 *  Author : Chris Koeritz                                                     *
5 *                                                                             *
6 *******************************************************************************
7 * Copyright (c) 2007-$now By Author.  This program is free software; you can  *
8 * redistribute it and/or modify it under the terms of the GNU General Public  *
9 * License as published by the Free Software Foundation; either version 2 of   *
10 * the License or (at your option) any later version.  This is online at:      *
11 *     http://www.fsf.org/copyleft/gpl.html                                    *
12 * Please send any updates to: fred@gruntose.com                               *
13 \*****************************************************************************/
14
15 #include "parser_bits.h"
16 #include "string_manipulation.h"
17 #include "xml_generator.h"
18
19 #include <basis/astring.h>
20 #include <structures/stack.h>
21 #include <structures/string_table.h>
22
23 using namespace basis;
24 using namespace structures;
25
26 namespace textual {
27
28 #undef LOG
29 #define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s);
30
31 //////////////
32
33 class tag_info
34 {
35 public:
36   astring _tag_name;
37   string_table _attribs;
38
39   tag_info() {}
40
41   tag_info(const astring &tag_name, const string_table &attribs)
42       : _tag_name(tag_name), _attribs(attribs) {}
43 };
44
45 //////////////
46
47 class tag_stack : public stack<tag_info>
48 {
49 public:
50   tag_stack() : stack<tag_info>(0) {}
51 };
52
53 //////////////
54
55 xml_generator::xml_generator(int mods)
56 : _tags(new tag_stack),
57   _accumulator(new astring),
58   _human_read(mods & HUMAN_READABLE),
59   _clean_chars(mods & CLEAN_ILLEGAL_CHARS),
60   _indentation(2)
61 {
62 }
63
64 xml_generator::~xml_generator()
65 {
66   WHACK(_tags);
67   WHACK(_accumulator);
68 }
69
70 const char *xml_generator::outcome_name(const outcome &to_name)
71 {
72   switch (to_name.value()) {
73     case ERRONEOUS_TAG: return "ERRONEOUS_TAG";
74     default: return common::outcome_name(to_name);
75   }
76 }
77
78 void xml_generator::set_indentation(int to_indent)
79 {
80   if (to_indent <= 0) to_indent = 1;
81   _indentation = to_indent;
82 }
83
84 void xml_generator::reset()
85 {
86   _accumulator->reset();
87 // we need a reset on stack.
88   while (_tags->pop() == common::OKAY) {}
89 }
90
91 astring xml_generator::generate()
92 {
93   astring to_return;
94   generate(to_return);
95   return to_return;
96 }
97
98 void xml_generator::generate(astring &generated)
99 {
100   close_all_tags();
101   generated = "<?xml version=\"1.0\"?>";  // first string is the version.
102   if (_human_read) generated += parser_bits::platform_eol_to_chars();
103   generated += *_accumulator;
104 }
105
106 outcome xml_generator::open_tag(const astring &tag_name)
107 {
108   string_table junk;
109   return open_tag(tag_name, junk);
110 }
111
112 outcome xml_generator::add_header(const astring &tag_name,
113     const string_table &attributes)
114 {
115   tag_info new_item(tag_name, attributes);
116   print_open_tag(new_item, HEADER_TAG);
117   return OKAY;
118 }
119
120 outcome xml_generator::open_tag(const astring &tag_name, const string_table &attributes)
121 {
122   tag_info new_item(tag_name, attributes);
123   print_open_tag(new_item);
124   _tags->push(new_item);
125   return OKAY;
126 }
127
128 outcome xml_generator::close_tag(const astring &tag_name)
129 {
130   if (_tags->elements() < 1) return NOT_FOUND;
131   // check to see that it's the right one to close.
132   if (_tags->top()._tag_name != tag_name) return ERRONEOUS_TAG;
133   print_close_tag(tag_name);
134   _tags->pop();
135   return OKAY;
136 }
137
138 void xml_generator::close_all_tags()
139 {
140   while (_tags->elements()) {
141     close_tag(_tags->top()._tag_name);
142   }
143 }
144
145 outcome xml_generator::add_content(const astring &content)
146 {
147   if (_human_read) {
148     astring indentata = string_manipulation::indentation(_indentation);
149     int num_indents = _tags->elements();
150     for (int i = 0; i < num_indents; i++) 
151      *_accumulator += indentata;
152   }
153   if (_clean_chars) 
154     *_accumulator += clean_reserved(content);
155   else
156     *_accumulator += content;
157   if (_human_read) *_accumulator += parser_bits::platform_eol_to_chars();
158   return OKAY;
159 }
160
161 void xml_generator::print_open_tag(const tag_info &to_print, int type)
162 {
163   bool is_header = false;
164   if (type == HEADER_TAG) is_header = true;
165
166   if (_human_read) {
167 //hmmm: starting to look like a nice macro for this stuff, param is num levs.
168     astring indentata = string_manipulation::indentation(_indentation);
169     int num_indents = _tags->elements();
170     for (int i = 0; i < num_indents; i++) 
171      *_accumulator += indentata;
172   }
173
174   if (is_header)
175     *_accumulator += "<?";
176   else
177     *_accumulator += "<";
178   if (_clean_chars)
179     *_accumulator += clean_reserved(to_print._tag_name);
180   else
181     *_accumulator += to_print._tag_name;
182   for (int i = 0; i < to_print._attribs.symbols(); i++) {
183     astring name, content;
184     to_print._attribs.retrieve(i, name, content);
185     if (_clean_chars) {
186       // flush out badness if we were told to.
187       clean_reserved_mod(name, true);  // clean spaces.
188       clean_reserved_mod(content);
189     }
190     *_accumulator += " ";
191     *_accumulator += name;
192     *_accumulator += "=\"";
193     *_accumulator += content;
194     *_accumulator += "\"";
195   }
196   if (is_header)
197     *_accumulator += "?>";
198   else
199     *_accumulator += ">";
200   if (_human_read) *_accumulator += parser_bits::platform_eol_to_chars();
201 }
202
203 void xml_generator::print_close_tag(const astring &tag_name)
204 {
205   if (_human_read) {
206     astring indentata = string_manipulation::indentation(_indentation);
207     int num_indents = _tags->elements() - 1;
208     for (int i = 0; i < num_indents; i++) 
209      *_accumulator += indentata;
210   }
211   *_accumulator += "</";
212   if (_clean_chars)
213     *_accumulator += clean_reserved(tag_name);
214   else
215     *_accumulator += tag_name;
216   *_accumulator += ">";
217   if (_human_read) *_accumulator += parser_bits::platform_eol_to_chars();
218 }
219
220 #define PLUGIN_REPLACEMENT(posn, repl_string) { \
221   to_modify.zap(posn, posn); \
222   to_modify.insert(posn, repl_string); \
223   posn += int(strlen(repl_string)) - 1; \
224 }
225
226 void xml_generator::clean_reserved_mod(astring &to_modify,
227     bool replace_spaces)
228 {
229 //could this set live somewhere?
230   const char *quot = "&quot;";
231   const char *amp = "&amp;";
232   const char *lt = "&lt;";
233   const char *gt = "&gt;";
234   const char *apos = "&apos;";
235   const char *space = "_";
236     // was going to use %20 but that still won't parse in an attribute name.
237
238   for (int i = 0; i < to_modify.length(); i++) {
239     switch (to_modify[i]) {
240       case '"': PLUGIN_REPLACEMENT(i, quot); break;
241       case '&': PLUGIN_REPLACEMENT(i, amp); break;
242       case '<': PLUGIN_REPLACEMENT(i, lt); break;
243       case '>': PLUGIN_REPLACEMENT(i, gt); break;
244       case '\'': PLUGIN_REPLACEMENT(i, apos); break;
245       case ' ': if (replace_spaces) PLUGIN_REPLACEMENT(i, space); break;
246       default: continue;
247     }
248   }
249 }
250
251 astring xml_generator::clean_reserved(const astring &to_modify,
252     bool replace_spaces)
253 {
254   astring to_return = to_modify;
255   clean_reserved_mod(to_return, replace_spaces);
256   return to_return;
257 }
258
259 } //namespace.
260
261