1/*
2 * Copyright (c) 1999-2000, Eric Moon.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions, and the following disclaimer.
11 *
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions, and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * 3. The name of the author may not be used to endorse or promote products
17 *    derived from this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
20 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21 * OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
23 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
27 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31
32// ExportContext.cpp
33// e.moon 30jun99
34
35#include "ExportContext.h"
36#include "IPersistent.h"
37
38#include <DataIO.h>
39
40#include <algorithm>
41#include <cstdio>
42
43__USE_CORTEX_NAMESPACE
44
45
46// -------------------------------------------------------- //
47// ctor/dtor
48// -------------------------------------------------------- //
49
50ExportContext::~ExportContext() {}
51
52ExportContext::ExportContext() :
53
54	stream(0),
55	m_indentLevel(0),
56	m_indentIncrement(4),
57	m_attrColumn(30),
58	m_state(INIT) {}
59
60ExportContext::ExportContext(
61	BDataIO*										_stream) :
62
63	stream(_stream),
64	m_indentLevel(0),
65	m_indentIncrement(2),
66	m_attrColumn(30),
67	m_state(INIT) {
68
69	ASSERT(_stream);
70}
71
72
73
74// -------------------------------------------------------- //
75// *** XML formatting helpers
76// -------------------------------------------------------- //
77
78// writes a start tag.  should only be called from
79// IPersistent::xmlExportBegin().
80void ExportContext::beginElement(
81	const char* 								name) {
82
83	ASSERT(name);
84
85	if(!m_objectStack.size()) {
86		reportError("beginElement(): no object being written.\n");
87		return;
88	}
89	if(m_state != WRITE_BEGIN && m_state != WRITE_CONTENT) {
90		reportError("beginElement(): not allowed.\n");
91		return;
92	}
93
94	// push tag onto element stack, and link to entry for the current object
95	m_elementStack.push_back(element_entry());
96	m_elementStack.back().name = name;
97	m_objectStack.back().element = m_elementStack.back().name.String();
98
99	// write tag
100	BString out;
101	out << "\n" << indentString() << '<' << name;
102	writeString(out);
103	indentMore();
104}
105
106// writes an end tag corresponding to the current element.
107// should only be called from IPersistent::xmlExportEnd() or
108// xmlExportContent().
109
110void ExportContext::endElement() {
111
112	if(!m_objectStack.size()) {
113		reportError("endElement(): no object being written.\n");
114		return;
115	}
116	ASSERT(m_elementStack.size());
117	element_entry& entry = m_elementStack.back();
118
119	if(m_state != WRITE_END && m_state != WRITE_CONTENT) {
120		reportError("endElement(): not allowed.\n");
121		return;
122	}
123
124	indentLess();
125
126	BString out;
127
128	// write closing tag
129	if(!entry.hasContent)
130		out << "/>";
131	else
132		out << "\n" << indentString() << "</" << entry.name.String() << ">";
133
134	writeString(out);
135
136	// pop element off stack
137	m_elementStack.pop_back();
138}
139
140// indicates that content follows (writes the end of the
141// current element's start tag.)
142void ExportContext::beginContent() {
143
144	if(!m_objectStack.size()) {
145		reportError("beginContent(): no object being written.\n");
146		return;
147	}
148	ASSERT(m_elementStack.size());
149	element_entry& entry = m_elementStack.back();
150
151	if(m_state != WRITE_CONTENT) {
152		reportError("beginContent(): not allowed.\n");
153		return;
154	}
155
156	BString out = ">";
157	writeString(out);
158
159	entry.hasContent = true;
160}
161
162#define _WRITE_ATTR_BODY(VAL_SPEC) \
163	if(!m_objectStack.size()) {\
164		reportError("writeAttr(): no object being written.\n");\
165		return;\
166	}\
167	ASSERT(m_elementStack.size());\
168	if(m_state != WRITE_ATTRIBUTES &&\
169		m_state != WRITE_CONTENT) {\
170		reportError("writeAttr(): not allowed (state mismatch).\n");\
171		return;\
172	}\
173\
174	m_elementStack.back().hasAttributes = true;\
175\
176	BString out;\
177	out << "\n" << indentString() << key;\
178	_pad_with_spaces(out, key, *this, m_attrColumn) << " = '" << VAL_SPEC << '\'';\
179\
180	writeString(out);
181
182void ExportContext::writeAttr(
183	const char*									key,
184	int8												value) {_WRITE_ATTR_BODY(value)}
185
186void ExportContext::writeAttr(
187	const char*									key,
188	uint8												value) {_WRITE_ATTR_BODY(uint32(value))}
189
190void ExportContext::writeAttr(
191	const char*									key,
192	int16												value) {_WRITE_ATTR_BODY(value)}
193
194void ExportContext::writeAttr(
195	const char*									key,
196	uint16											value) {_WRITE_ATTR_BODY(uint32(value))}
197
198void ExportContext::writeAttr(
199	const char*									key,
200	int32												value) {_WRITE_ATTR_BODY(value)}
201
202void ExportContext::writeAttr(
203	const char*									key,
204	uint32											value) {_WRITE_ATTR_BODY(value)}
205
206void ExportContext::writeAttr(
207	const char*									key,
208	int64												value) {_WRITE_ATTR_BODY(value)}
209
210void ExportContext::writeAttr(
211	const char*									key,
212	uint64											value) {_WRITE_ATTR_BODY(value)}
213
214void ExportContext::writeAttr(
215	const char*									key,
216	const char*									value) {_WRITE_ATTR_BODY(value)}
217
218void ExportContext::writeAttr(
219	const char*									key,
220	const BString&							value) {_WRITE_ATTR_BODY(value)}
221
222void ExportContext::writeAttr(
223	const char*									key,
224	float												value) {_WRITE_ATTR_BODY(value)}
225
226// writes a child object.
227// should only be called from IPersistent::xmlExportContent().
228// returns B_OK on success, or B_ERROR if an error occurred.
229status_t ExportContext::writeObject(
230	IPersistent* object) {
231
232	// * SETUP
233	ASSERT(object);
234	if(m_state == ABORT)
235		return B_ERROR;
236	state_t origState = m_state;
237
238	//   write entry to object stack
239	m_objectStack.push_back(object_entry());
240	object_entry& entry = m_objectStack.back();
241	entry.object = object;
242
243	// * START TAG
244	int elements = m_elementStack.size();
245	m_state = WRITE_BEGIN;
246	object->xmlExportBegin(*this);
247
248	if(m_state == ABORT)
249		return B_ERROR;
250
251	if(!entry.element)
252		reportError("writeObject(): no start tag for object.\n");
253	else if(m_elementStack.size() - elements > 1)
254		reportError("writeObject(): object wrote more than one start tag.\n");
255
256	if(m_state == ABORT)
257		return B_ERROR;
258
259	// * ATTRIBUTES
260	m_state = WRITE_ATTRIBUTES;
261	object->xmlExportAttributes(*this);
262
263	if(m_state == ABORT)
264		return B_ERROR;
265
266	// * CONTENT
267	m_state = WRITE_CONTENT;
268	object->xmlExportContent(*this);
269
270	if(m_state == ABORT)
271		return B_ERROR;
272
273	// * END
274	m_state = WRITE_END;
275	object->xmlExportEnd(*this);
276
277	m_state = origState;
278
279	//   pop object entry
280	m_objectStack.pop_back();
281
282	return (m_state == ABORT) ? B_ERROR : B_OK;
283}
284
285// writes an arbitrary string to the stream (calls reportError()
286// on failure.)
287
288status_t ExportContext::writeString(
289	const BString&							string) {
290
291	return writeString(string.String(), string.Length());
292}
293
294status_t ExportContext::writeString(
295	const char*									data,
296	ssize_t											length) {
297
298	ssize_t written = stream->Write(data, length);
299	if(written < 0) {
300		BString err = "Write error: '";
301		err << strerror(written) << "'.\n";
302		reportError(err.String());
303		return written;
304	}
305	else if(written < length) {
306		BString err = "Write incomplete: '";
307		err << written << " of " << length << " bytes written.\n";
308		reportError(err.String());
309		return B_IO_ERROR;
310	}
311	return B_OK;
312}
313
314
315// -------------------------------------------------------- //
316// *** indentation helpers
317// -------------------------------------------------------- //
318
319const char* ExportContext::indentString() const {
320	return m_indentString.String();
321}
322
323uint16 ExportContext::indentLevel() const {
324	return m_indentLevel;
325}
326
327void ExportContext::indentLess() {
328	m_indentLevel = (m_indentLevel > m_indentIncrement) ?
329		m_indentLevel - m_indentIncrement : 0;
330	m_indentString.SetTo(' ', m_indentLevel);
331}
332
333void ExportContext::indentMore() {
334	m_indentLevel += m_indentIncrement;
335	m_indentString.SetTo(' ', m_indentLevel);
336}
337
338// -------------------------------------------------------- //
339// *** error-reporting operations
340// -------------------------------------------------------- //
341
342class dump_element { public:
343	BString& _s;
344
345	dump_element(BString& s) : _s(s) {}
346	void operator()(const ExportContext::element_entry& entry) {
347		_s << "  " << entry.name << '\n';
348	}
349};
350
351// register a fatal error; halts the write process
352// as soon as possible.
353void ExportContext::reportError(
354	const char*			text) {
355
356	m_error << "FATAL ERROR: ";
357	m_error << text << "\n";
358	if(m_elementStack.size()) {
359		_dumpElementStack(m_error);
360	}
361
362	m_state = ABORT;
363}
364
365void ExportContext::_dumpElementStack(
366	BString&										out) {
367	out << "Element stack:\n";
368		for_each(m_elementStack.begin(), m_elementStack.end(), dump_element(out));
369}
370
371// END -- ExportContext.cpp --
372
373