1 | // -------------------------------------------------------------------------------- |
---|
2 | // |
---|
3 | // Copyright (C) 2000 |
---|
4 | // Ralf Westram |
---|
5 | // |
---|
6 | // Permission to use, copy, modify, distribute and sell this software |
---|
7 | // and its documentation for any purpose is hereby granted without fee, |
---|
8 | // provided that the above copyright notice appear in all copies and |
---|
9 | // that both that copyright notice and this permission notice appear |
---|
10 | // in supporting documentation. Ralf Westram makes no |
---|
11 | // representations about the suitability of this software for any |
---|
12 | // purpose. It is provided "as is" without express or implied warranty. |
---|
13 | // |
---|
14 | // This code is part of my library. |
---|
15 | // You may find a more recent version at http://www.reallysoft.de/ |
---|
16 | // |
---|
17 | // -------------------------------------------------------------------------------- |
---|
18 | |
---|
19 | #include "xml.hxx" |
---|
20 | |
---|
21 | using namespace std; |
---|
22 | |
---|
23 | XML_Document *the_XML_Document = NULp; |
---|
24 | |
---|
25 | static const char *entities = |
---|
26 | " <!ENTITY nbsp \" \">\n" |
---|
27 | " <!ENTITY acute \"´\">\n" // Acute accent (forward) |
---|
28 | " <!ENTITY eacute \"é\">\n" // e Acute (forward) |
---|
29 | " <!ENTITY apostr \"'\">\n" // single quote (vertical) |
---|
30 | " <!ENTITY semi \";\">\n" |
---|
31 | ; |
---|
32 | |
---|
33 | static string encodeEntities(const string& str, bool /*quotedText*/) { |
---|
34 | // if quotedText is true the string is encoded for usage in quotes |
---|
35 | // currently it makes no difference, but this might change |
---|
36 | |
---|
37 | string neu; |
---|
38 | neu.reserve(str.length()*4); |
---|
39 | |
---|
40 | |
---|
41 | for (string::const_iterator s = str.begin(); s != str.end(); ++s) { |
---|
42 | char replace = 0; |
---|
43 | const char *entity = NULp; |
---|
44 | |
---|
45 | switch (*s) { |
---|
46 | case '<': { entity = "lt"; break; } |
---|
47 | case '>': { entity = "gt"; break; } |
---|
48 | case '&': { entity = "amp"; break; } |
---|
49 | case '\'': { entity = "apostr"; break; } |
---|
50 | case char(0xb4): { entity = "acute"; break; } // acute (forward) |
---|
51 | case 'é': { entity = "eacute"; break; } |
---|
52 | default: { replace = *s; } |
---|
53 | } |
---|
54 | |
---|
55 | if (replace) { |
---|
56 | neu.append(1, replace); |
---|
57 | } |
---|
58 | else { |
---|
59 | xml_assert(entity); |
---|
60 | neu.append(1, '&'); |
---|
61 | neu.append(entity); |
---|
62 | neu.append(1, ';'); |
---|
63 | } |
---|
64 | } |
---|
65 | |
---|
66 | return neu; |
---|
67 | } |
---|
68 | |
---|
69 | // ---------------------- |
---|
70 | // XML_Attribute |
---|
71 | |
---|
72 | XML_Attribute::XML_Attribute(const string& name_, const string& content_) : |
---|
73 | name(name_), |
---|
74 | content(content_), |
---|
75 | next(NULp) |
---|
76 | {} |
---|
77 | |
---|
78 | XML_Attribute::~XML_Attribute() { |
---|
79 | delete next; |
---|
80 | } |
---|
81 | XML_Attribute *XML_Attribute::append_to(XML_Attribute *queue) { |
---|
82 | if (!queue) return this; |
---|
83 | queue->next = append_to(queue->next); |
---|
84 | return queue; |
---|
85 | } |
---|
86 | void XML_Attribute::print(FILE *out) const { |
---|
87 | fprintf(out, " %s=\"%s\"", name.c_str(), encodeEntities(content, true).c_str()); |
---|
88 | if (next) next->print(out); |
---|
89 | } |
---|
90 | |
---|
91 | // ----------------- |
---|
92 | // XML_Node |
---|
93 | |
---|
94 | XML_Node::XML_Node(bool is_tag) { |
---|
95 | xml_assert(the_XML_Document); |
---|
96 | |
---|
97 | father = the_XML_Document->LatestSon(); |
---|
98 | the_XML_Document->set_LatestSon(this); |
---|
99 | indent = 0; |
---|
100 | |
---|
101 | if (father) { |
---|
102 | father->add_son(this, is_tag); |
---|
103 | indent = father->Indent()+1; |
---|
104 | } |
---|
105 | |
---|
106 | opened = false; |
---|
107 | } |
---|
108 | |
---|
109 | XML_Node::~XML_Node() { |
---|
110 | if (father) father->remove_son(this); |
---|
111 | the_XML_Document->set_LatestSon(father); |
---|
112 | } |
---|
113 | |
---|
114 | |
---|
115 | // ---------------- |
---|
116 | // XML_Tag |
---|
117 | |
---|
118 | XML_Tag::XML_Tag(const string &name_) : |
---|
119 | XML_Node(true), |
---|
120 | name(name_), |
---|
121 | son(NULp), |
---|
122 | attribute(NULp), |
---|
123 | state(0), |
---|
124 | onExtraLine(true) |
---|
125 | {} |
---|
126 | |
---|
127 | XML_Tag::~XML_Tag() { |
---|
128 | FILE *out = the_XML_Document->Out(); |
---|
129 | xml_assert(!son); |
---|
130 | close(out); |
---|
131 | |
---|
132 | delete attribute; |
---|
133 | } |
---|
134 | |
---|
135 | void XML_Tag::add_attribute(const string& name_, const string& content_) { |
---|
136 | XML_Attribute *newAttr = new XML_Attribute(name_, content_); |
---|
137 | attribute = newAttr->append_to(attribute); |
---|
138 | } |
---|
139 | |
---|
140 | void XML_Tag::add_attribute(const string& name_, int value) { |
---|
141 | char buf[30]; |
---|
142 | sprintf(buf, "%i", value); |
---|
143 | add_attribute(name_, buf); |
---|
144 | } |
---|
145 | |
---|
146 | void XML_Tag::add_son(XML_Node *son_, bool son_is_tag) { |
---|
147 | if (son) throw string("Tried to add a second son! Destroy previous son first."); |
---|
148 | son = son_; |
---|
149 | int wanted_state = son_is_tag ? 2 : 1; |
---|
150 | if (state<wanted_state) state = wanted_state; |
---|
151 | } |
---|
152 | |
---|
153 | void XML_Tag::remove_son(XML_Node *son_) { |
---|
154 | if (son != son_) throw string("Tried to remove wrong son!"); |
---|
155 | son = NULp; |
---|
156 | } |
---|
157 | |
---|
158 | inline void to_indent(FILE *out, int indent) { int i = indent*the_XML_Document->indentation_per_level; while (i--) fputc(' ', out); } |
---|
159 | |
---|
160 | void XML_Tag::open(FILE *out) { |
---|
161 | if (father && !father->Opened()) father->open(out); |
---|
162 | if (onExtraLine) { |
---|
163 | fputc('\n', out); |
---|
164 | to_indent(out, Indent()); |
---|
165 | } |
---|
166 | fputc('<', out); fputs(name.c_str(), out); |
---|
167 | if (attribute) attribute->print(out); |
---|
168 | fputc('>', out); |
---|
169 | opened = true; |
---|
170 | } |
---|
171 | |
---|
172 | void XML_Tag::close(FILE *out) { |
---|
173 | if (!opened) { |
---|
174 | if (!the_XML_Document->skip_empty_tags || attribute || !father) { |
---|
175 | if (father && !father->Opened()) father->open(out); |
---|
176 | if (onExtraLine) { |
---|
177 | fputc('\n', out); |
---|
178 | to_indent(out, Indent()); |
---|
179 | } |
---|
180 | fputc('<', out); fputs(name.c_str(), out); |
---|
181 | if (attribute) attribute->print(out); |
---|
182 | fputs("/>", out); |
---|
183 | } |
---|
184 | } |
---|
185 | else { |
---|
186 | if (state >= 2 && onExtraLine) { fputc('\n', out); to_indent(out, Indent()); } |
---|
187 | fprintf(out, "</%s>", name.c_str()); |
---|
188 | } |
---|
189 | } |
---|
190 | |
---|
191 | // ----------------- |
---|
192 | // XML_Text |
---|
193 | |
---|
194 | XML_Text::~XML_Text() { |
---|
195 | FILE *out = the_XML_Document->Out(); |
---|
196 | close(out); |
---|
197 | } |
---|
198 | |
---|
199 | void XML_Text::add_son(XML_Node *, bool) { |
---|
200 | throw string("Can't add son to XML_Text-Node"); |
---|
201 | } |
---|
202 | |
---|
203 | void XML_Text::remove_son(XML_Node * /* son_ */) { |
---|
204 | throw string("Can't remove son from XML_Text-Node"); |
---|
205 | } |
---|
206 | |
---|
207 | void XML_Text::open(FILE *) {} |
---|
208 | |
---|
209 | void XML_Text::close(FILE *out) { |
---|
210 | if (father && !father->Opened()) father->open(out); |
---|
211 | fputs(encodeEntities(content, false).c_str(), out); |
---|
212 | } |
---|
213 | |
---|
214 | // -------------------- |
---|
215 | // XML_Comment |
---|
216 | |
---|
217 | XML_Comment::~XML_Comment() { |
---|
218 | FILE *out = the_XML_Document->Out(); |
---|
219 | close(out); |
---|
220 | } |
---|
221 | |
---|
222 | void XML_Comment::add_son(XML_Node *, bool) { |
---|
223 | throw string("Can't add son to XML_Comment-Node"); |
---|
224 | } |
---|
225 | |
---|
226 | void XML_Comment::remove_son(XML_Node *) { |
---|
227 | throw string("Can't remove son from XML_Comment-Node"); |
---|
228 | } |
---|
229 | |
---|
230 | void XML_Comment::open(FILE *) {} |
---|
231 | |
---|
232 | void XML_Comment::close(FILE *out) { |
---|
233 | fputc('\n', out); to_indent(out, Indent()); |
---|
234 | fputs("<!--", out); |
---|
235 | fputs(encodeEntities(content, false).c_str(), out); |
---|
236 | fputs("-->", out); |
---|
237 | } |
---|
238 | |
---|
239 | // --------------------- |
---|
240 | // XML_Document |
---|
241 | |
---|
242 | XML_Document::XML_Document(const string& name_, const string& dtd_, FILE *out_) |
---|
243 | : dtd(dtd_), |
---|
244 | root(NULp), |
---|
245 | out(out_), |
---|
246 | skip_empty_tags(false), |
---|
247 | indentation_per_level(1) |
---|
248 | { |
---|
249 | xml_assert(out); |
---|
250 | if (the_XML_Document) GBK_terminate("You can only have one XML_Document at a time."); |
---|
251 | the_XML_Document = this; |
---|
252 | latest_son = NULp; |
---|
253 | root = new XML_Tag(name_); |
---|
254 | xml_assert(latest_son == root); |
---|
255 | |
---|
256 | fputs("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n", out); |
---|
257 | fprintf(out, "<!DOCTYPE %s SYSTEM '%s' [\n%s]>\n", name_.c_str(), dtd.c_str(), entities); |
---|
258 | } |
---|
259 | |
---|
260 | XML_Document::~XML_Document() { |
---|
261 | delete root; |
---|
262 | xml_assert(the_XML_Document == this); |
---|
263 | the_XML_Document = NULp; |
---|
264 | } |
---|
265 | |
---|
266 | |
---|