|
a/libupnpp/expatmm.hxx |
|
b/libupnpp/expatmm.hxx |
|
... |
|
... |
25 |
|
25 |
|
26 |
#include <string.h>
|
26 |
#include <string.h>
|
27 |
#include <expat.h>
|
27 |
#include <expat.h>
|
28 |
|
28 |
|
29 |
namespace expatmm {
|
29 |
namespace expatmm {
|
30 |
class ExpatXMLParser {
|
30 |
class ExpatXMLParser {
|
31 |
public:
|
31 |
public:
|
32 |
|
32 |
|
33 |
/* Create a new parser, using the default Chunk Size */
|
33 |
/* Create a new parser, using the default Chunk Size */
|
34 |
ExpatXMLParser(void)
|
34 |
ExpatXMLParser(void)
|
35 |
{
|
35 |
{
|
36 |
init();
|
36 |
init();
|
37 |
}
|
37 |
}
|
38 |
|
38 |
|
39 |
/* Create a new parser, using a user-supplied chunk size */
|
39 |
/* Create a new parser, using a user-supplied chunk size */
|
40 |
ExpatXMLParser(size_t chunk_size)
|
40 |
ExpatXMLParser(size_t chunk_size)
|
41 |
{
|
41 |
{
|
42 |
init(chunk_size);
|
42 |
init(chunk_size);
|
43 |
}
|
43 |
}
|
44 |
|
44 |
|
45 |
/* Destructor that cleans up xml_buffer and parser */
|
45 |
/* Destructor that cleans up xml_buffer and parser */
|
46 |
virtual ~ExpatXMLParser(void)
|
46 |
virtual ~ExpatXMLParser(void)
|
47 |
{
|
47 |
{
|
48 |
valid_parser = false;
|
48 |
valid_parser = false;
|
49 |
if(expat_parser != NULL) {
|
49 |
if(expat_parser != NULL) {
|
50 |
XML_ParserFree(expat_parser);
|
50 |
XML_ParserFree(expat_parser);
|
51 |
expat_parser = NULL;
|
51 |
expat_parser = NULL;
|
52 |
}
|
52 |
}
|
53 |
if(xml_buffer != NULL) {
|
53 |
if(xml_buffer != NULL) {
|
54 |
delete xml_buffer;
|
54 |
delete xml_buffer;
|
55 |
xml_buffer = NULL;
|
55 |
xml_buffer = NULL;
|
56 |
}
|
56 |
}
|
57 |
}
|
57 |
}
|
58 |
|
58 |
|
59 |
/*
|
59 |
/*
|
60 |
Generic Parser. Most derivations will simply call this, rather
|
60 |
Generic Parser. Most derivations will simply call this, rather
|
61 |
than implement their own. This will loop, processing XML data
|
61 |
than implement their own. This will loop, processing XML data
|
62 |
and calling the necessary handler code until an error is encountered.
|
62 |
and calling the necessary handler code until an error is encountered.
|
63 |
*/
|
63 |
*/
|
64 |
virtual bool Parse(void)
|
64 |
virtual bool Parse(void)
|
65 |
{
|
65 |
{
|
66 |
/* Ensure that the parser is ready */
|
66 |
/* Ensure that the parser is ready */
|
67 |
if(!Ready())
|
67 |
if(!Ready())
|
68 |
return false;
|
68 |
return false;
|
69 |
|
69 |
|
70 |
ssize_t bytes_read;
|
70 |
ssize_t bytes_read;
|
71 |
/* Loop, reading the XML source block by block */
|
71 |
/* Loop, reading the XML source block by block */
|
72 |
while((bytes_read = read_block()) >= 0) {
|
72 |
while((bytes_read = read_block()) >= 0) {
|
73 |
if(bytes_read > 0) {
|
73 |
if(bytes_read > 0) {
|
74 |
XML_Status local_status =
|
74 |
XML_Status local_status =
|
75 |
XML_Parse(expat_parser, getReadBuffer(), bytes_read,
|
75 |
XML_Parse(expat_parser, getReadBuffer(), bytes_read,
|
76 |
XML_FALSE);
|
76 |
XML_FALSE);
|
77 |
|
77 |
|
78 |
if(local_status != XML_STATUS_OK) {
|
78 |
if(local_status != XML_STATUS_OK) {
|
79 |
status = local_status;
|
79 |
status = local_status;
|
80 |
last_error = XML_GetErrorCode(expat_parser);
|
80 |
last_error = XML_GetErrorCode(expat_parser);
|
81 |
break;
|
81 |
break;
|
82 |
}
|
82 |
}
|
83 |
|
83 |
|
84 |
/* Break on successful "short read", in event of EOF */
|
84 |
/* Break on successful "short read", in event of EOF */
|
85 |
if(getLastError() == XML_ERROR_FINISHED)
|
85 |
if(getLastError() == XML_ERROR_FINISHED)
|
86 |
break;
|
86 |
break;
|
87 |
}
|
87 |
}
|
88 |
}
|
88 |
}
|
89 |
|
89 |
|
90 |
/* Finalize the parser */
|
90 |
/* Finalize the parser */
|
91 |
if((getStatus() == XML_STATUS_OK) ||
|
91 |
if((getStatus() == XML_STATUS_OK) ||
|
92 |
(getLastError() == XML_ERROR_FINISHED)) {
|
92 |
(getLastError() == XML_ERROR_FINISHED)) {
|
93 |
XML_Parse(expat_parser, getBuffer(), 0, XML_TRUE);
|
93 |
XML_Parse(expat_parser, getBuffer(), 0, XML_TRUE);
|
94 |
return true;
|
94 |
return true;
|
95 |
}
|
95 |
}
|
96 |
|
96 |
|
97 |
/* Return false in the event of an error. The parser is
|
97 |
/* Return false in the event of an error. The parser is
|
98 |
not finalized on error. */
|
98 |
not finalized on error. */
|
99 |
return false;
|
99 |
return false;
|
100 |
}
|
100 |
}
|
101 |
|
101 |
|
102 |
/* Expose status, error, and control codes to users */
|
102 |
/* Expose status, error, and control codes to users */
|
103 |
virtual bool Ready(void) { return valid_parser; };
|
103 |
virtual bool Ready(void) { return valid_parser; };
|
104 |
virtual XML_Error getLastError(void) { return last_error; };
|
104 |
virtual XML_Error getLastError(void) { return last_error; };
|
105 |
virtual XML_Status getStatus(void) { return status; };
|
105 |
virtual XML_Status getStatus(void) { return status; };
|
106 |
|
106 |
|
107 |
protected:
|
107 |
protected:
|
108 |
virtual XML_Char *getBuffer(void) { return xml_buffer; };
|
108 |
virtual XML_Char *getBuffer(void) { return xml_buffer; };
|
109 |
virtual const char *getReadBuffer(void) { return xml_buffer; };
|
109 |
virtual const char *getReadBuffer(void) { return xml_buffer; };
|
110 |
virtual size_t getBlockSize(void) { return xml_buffer_size; };
|
110 |
virtual size_t getBlockSize(void) { return xml_buffer_size; };
|
111 |
|
111 |
|
112 |
/* Read XML data.
|
112 |
/* Read XML data.
|
113 |
*
|
113 |
*
|
114 |
* Override this to implement your container-specific parser.
|
114 |
* Override this to implement your container-specific parser.
|
115 |
*
|
115 |
*
|
116 |
* You must:
|
116 |
* You must:
|
117 |
* put new XML data into xml_buffer
|
117 |
* put new XML data into xml_buffer
|
118 |
* set status
|
118 |
* set status
|
119 |
* set last_error
|
119 |
* set last_error
|
120 |
* return the amount of XML_Char's written to xml_buffer
|
120 |
* return the amount of XML_Char's written to xml_buffer
|
121 |
*
|
121 |
*
|
122 |
* on error, return < 0. The contents of xml_buffer will be
|
122 |
* on error, return < 0. The contents of xml_buffer will be
|
123 |
* thrown away in this event, so it is the derived class's
|
123 |
* thrown away in this event, so it is the derived class's
|
124 |
* responsibility to reseek the "data cursor" to re-get any
|
124 |
* responsibility to reseek the "data cursor" to re-get any
|
125 |
* data in the buffer on an error condition.
|
125 |
* data in the buffer on an error condition.
|
126 |
*
|
126 |
*
|
127 |
* Use setReadiness, setStatus, and setLastError to handle
|
127 |
* Use setReadiness, setStatus, and setLastError to handle
|
128 |
* error, status, and control events and codes.
|
128 |
* error, status, and control events and codes.
|
129 |
*
|
129 |
*
|
130 |
* The default implementation returns "no elements" if it is
|
130 |
* The default implementation returns "no elements" if it is
|
131 |
* ever called. and should be overridden by the derived class.
|
131 |
* ever called. and should be overridden by the derived class.
|
132 |
*
|
132 |
*
|
133 |
* Note that, as the actual parser only uses
|
133 |
* Note that, as the actual parser only uses
|
134 |
* getBuffer()/getBlockSize()/read_block() (no direct access
|
134 |
* getBuffer()/getBlockSize()/read_block() (no direct access
|
135 |
* to the buffer), you are free to use an entirely different
|
135 |
* to the buffer), you are free to use an entirely different
|
136 |
* I/O mechanism, like what does the inputRefXMLParser below.
|
136 |
* I/O mechanism, like what does the inputRefXMLParser below.
|
137 |
*/
|
137 |
*/
|
138 |
virtual ssize_t read_block(void)
|
138 |
virtual ssize_t read_block(void)
|
139 |
{
|
139 |
{
|
140 |
last_error = XML_ERROR_NO_ELEMENTS;
|
140 |
last_error = XML_ERROR_NO_ELEMENTS;
|
141 |
status = XML_STATUS_ERROR;
|
141 |
status = XML_STATUS_ERROR;
|
142 |
return -1;
|
142 |
return -1;
|
143 |
}
|
143 |
}
|
144 |
|
144 |
|
145 |
virtual void setReadiness(bool ready)
|
145 |
virtual void setReadiness(bool ready)
|
146 |
{
|
146 |
{
|
147 |
valid_parser = ready;
|
147 |
valid_parser = ready;
|
148 |
}
|
148 |
}
|
149 |
virtual void setStatus(XML_Status new_status)
|
149 |
virtual void setStatus(XML_Status new_status)
|
150 |
{
|
150 |
{
|
151 |
status = new_status;
|
151 |
status = new_status;
|
152 |
}
|
152 |
}
|
153 |
virtual void setLastError(XML_Error new_last_error)
|
153 |
virtual void setLastError(XML_Error new_last_error)
|
154 |
{
|
154 |
{
|
155 |
last_error = new_last_error;
|
155 |
last_error = new_last_error;
|
156 |
};
|
156 |
};
|
157 |
|
157 |
|
158 |
/* Methods to be overriden */
|
158 |
/* Methods to be overriden */
|
159 |
virtual void StartElement(const XML_Char *,
|
159 |
virtual void StartElement(const XML_Char *,
|
160 |
const XML_Char **) {}
|
160 |
const XML_Char **) {}
|
161 |
virtual void EndElement(const XML_Char *) {}
|
161 |
virtual void EndElement(const XML_Char *) {}
|
162 |
virtual void CharacterData(const XML_Char *, int) {}
|
162 |
virtual void CharacterData(const XML_Char *, int) {}
|
163 |
virtual void ProcessingInstruction(const XML_Char *,
|
163 |
virtual void ProcessingInstruction(const XML_Char *,
|
164 |
const XML_Char *) {}
|
164 |
const XML_Char *) {}
|
165 |
virtual void CommentData(const XML_Char *) {}
|
165 |
virtual void CommentData(const XML_Char *) {}
|
166 |
virtual void DefaultHandler(const XML_Char *, int) {}
|
166 |
virtual void DefaultHandler(const XML_Char *, int) {}
|
167 |
virtual void CDataStart(void) {}
|
167 |
virtual void CDataStart(void) {}
|
168 |
virtual void CDataEnd(void) {}
|
168 |
virtual void CDataEnd(void) {}
|
169 |
|
169 |
|
|
|
170 |
/* The handle for the parser (expat) */
|
|
|
171 |
XML_Parser expat_parser;
|
|
|
172 |
|
170 |
private:
|
173 |
private:
|
171 |
/* The handle for the parser (expat) */
|
|
|
172 |
XML_Parser expat_parser;
|
|
|
173 |
|
174 |
|
174 |
/* Temporary buffer where data is streamed in */
|
175 |
/* Temporary buffer where data is streamed in */
|
175 |
XML_Char *xml_buffer;
|
176 |
XML_Char *xml_buffer;
|
176 |
size_t xml_buffer_size;
|
177 |
size_t xml_buffer_size;
|
177 |
|
178 |
|
178 |
/* Tells if the parser is ready to accept data */
|
179 |
/* Tells if the parser is ready to accept data */
|
179 |
bool valid_parser;
|
180 |
bool valid_parser;
|
180 |
|
181 |
|
181 |
/* Status and Error codes in the event of unforseen events */
|
182 |
/* Status and Error codes in the event of unforseen events */
|
182 |
XML_Status status;
|
183 |
XML_Status status;
|
183 |
XML_Error last_error;
|
184 |
XML_Error last_error;
|
184 |
|
185 |
|
185 |
/* Expat callbacks.
|
186 |
/* Expat callbacks.
|
186 |
* The expatmm protocol is to pass (this) as the userData argument
|
187 |
* The expatmm protocol is to pass (this) as the userData argument
|
187 |
* in the XML_Parser structure. These static methods will convert
|
188 |
* in the XML_Parser structure. These static methods will convert
|
188 |
* handlers into upcalls to the instantiated class's virtual members
|
189 |
* handlers into upcalls to the instantiated class's virtual members
|
189 |
* to do the actual handling work. */
|
190 |
* to do the actual handling work. */
|
190 |
static void _element_start_handler(void *userData, const XML_Char *name,
|
191 |
static void _element_start_handler(void *userData, const XML_Char *name,
|
191 |
const XML_Char **atts)
|
192 |
const XML_Char **atts)
|
192 |
{
|
193 |
{
|
193 |
ExpatXMLParser *me = (ExpatXMLParser*)userData;
|
194 |
ExpatXMLParser *me = (ExpatXMLParser*)userData;
|
194 |
if(me != NULL) me->StartElement(name, atts);
|
195 |
if(me != NULL) me->StartElement(name, atts);
|
195 |
}
|
196 |
}
|
196 |
static void _element_end_handler(void *userData, const XML_Char *name)
|
197 |
static void _element_end_handler(void *userData, const XML_Char *name)
|
197 |
{
|
198 |
{
|
198 |
ExpatXMLParser *me = (ExpatXMLParser*)userData;
|
199 |
ExpatXMLParser *me = (ExpatXMLParser*)userData;
|
199 |
if(me != NULL) me->EndElement(name);
|
200 |
if(me != NULL) me->EndElement(name);
|
200 |
}
|
201 |
}
|
201 |
static void _character_data_handler(void *userData,
|
202 |
static void _character_data_handler(void *userData,
|
202 |
const XML_Char *s, int len)
|
203 |
const XML_Char *s, int len)
|
203 |
{
|
204 |
{
|
204 |
ExpatXMLParser *me = (ExpatXMLParser*)userData;
|
205 |
ExpatXMLParser *me = (ExpatXMLParser*)userData;
|
205 |
if(me != NULL) me->CharacterData(s, len);
|
206 |
if(me != NULL) me->CharacterData(s, len);
|
206 |
}
|
207 |
}
|
207 |
static void _processing_instr_handler(void *userData,
|
208 |
static void _processing_instr_handler(void *userData,
|
208 |
const XML_Char *target,
|
209 |
const XML_Char *target,
|
209 |
const XML_Char *data)
|
210 |
const XML_Char *data)
|
210 |
{
|
211 |
{
|
211 |
ExpatXMLParser *me = (ExpatXMLParser*)userData;
|
212 |
ExpatXMLParser *me = (ExpatXMLParser*)userData;
|
212 |
if(me != NULL) me->ProcessingInstruction(target, data);
|
213 |
if(me != NULL) me->ProcessingInstruction(target, data);
|
213 |
}
|
214 |
}
|
214 |
static void _comment_handler(void *userData, const XML_Char *data)
|
215 |
static void _comment_handler(void *userData, const XML_Char *data)
|
215 |
{
|
216 |
{
|
216 |
ExpatXMLParser *me = (ExpatXMLParser*)userData;
|
217 |
ExpatXMLParser *me = (ExpatXMLParser*)userData;
|
217 |
if(me != NULL) me->CommentData(data);
|
218 |
if(me != NULL) me->CommentData(data);
|
218 |
}
|
219 |
}
|
219 |
static void _default_handler(void *userData, const XML_Char *s, int len)
|
220 |
static void _default_handler(void *userData, const XML_Char *s, int len)
|
220 |
{
|
221 |
{
|
221 |
ExpatXMLParser *me = (ExpatXMLParser*)userData;
|
222 |
ExpatXMLParser *me = (ExpatXMLParser*)userData;
|
222 |
if(me != NULL) me->DefaultHandler(s, len);
|
223 |
if(me != NULL) me->DefaultHandler(s, len);
|
223 |
}
|
224 |
}
|
224 |
static void _cdata_start_handler(void *userData)
|
225 |
static void _cdata_start_handler(void *userData)
|
225 |
{
|
226 |
{
|
226 |
ExpatXMLParser *me = (ExpatXMLParser*)userData;
|
227 |
ExpatXMLParser *me = (ExpatXMLParser*)userData;
|
227 |
if(me != NULL) me->CDataStart();
|
228 |
if(me != NULL) me->CDataStart();
|
228 |
}
|
229 |
}
|
229 |
static void _cdata_end_handler(void *userData)
|
230 |
static void _cdata_end_handler(void *userData)
|
230 |
{
|
231 |
{
|
231 |
ExpatXMLParser *me = (ExpatXMLParser*)userData;
|
232 |
ExpatXMLParser *me = (ExpatXMLParser*)userData;
|
232 |
if(me != NULL) me->CDataEnd();
|
233 |
if(me != NULL) me->CDataEnd();
|
233 |
}
|
234 |
}
|
234 |
/* Register our static handlers with the Expat events. */
|
235 |
/* Register our static handlers with the Expat events. */
|
235 |
void register_default_handlers()
|
236 |
void register_default_handlers()
|
236 |
{
|
237 |
{
|
237 |
XML_SetElementHandler(expat_parser, &_element_start_handler,
|
238 |
XML_SetElementHandler(expat_parser, &_element_start_handler,
|
238 |
&_element_end_handler);
|
239 |
&_element_end_handler);
|
239 |
XML_SetCharacterDataHandler(expat_parser, &_character_data_handler);
|
240 |
XML_SetCharacterDataHandler(expat_parser, &_character_data_handler);
|
240 |
XML_SetProcessingInstructionHandler(expat_parser,
|
241 |
XML_SetProcessingInstructionHandler(expat_parser,
|
241 |
&_processing_instr_handler);
|
242 |
&_processing_instr_handler);
|
242 |
XML_SetCommentHandler(expat_parser, &_comment_handler);
|
243 |
XML_SetCommentHandler(expat_parser, &_comment_handler);
|
243 |
XML_SetCdataSectionHandler(expat_parser, &_cdata_start_handler,
|
244 |
XML_SetCdataSectionHandler(expat_parser, &_cdata_start_handler,
|
244 |
&_cdata_end_handler);
|
245 |
&_cdata_end_handler);
|
245 |
XML_SetDefaultHandler(expat_parser, &_default_handler);
|
246 |
XML_SetDefaultHandler(expat_parser, &_default_handler);
|
246 |
}
|
247 |
}
|
247 |
/* Constructor common code */
|
248 |
/* Constructor common code */
|
248 |
void init(size_t chunk_size = 0)
|
249 |
void init(size_t chunk_size = 0)
|
249 |
{
|
250 |
{
|
250 |
valid_parser = false;
|
251 |
valid_parser = false;
|
251 |
expat_parser = NULL;
|
252 |
expat_parser = NULL;
|
252 |
xml_buffer_size = chunk_size ? chunk_size : 10240;
|
253 |
xml_buffer_size = chunk_size ? chunk_size : 10240;
|
253 |
xml_buffer = new XML_Char[xml_buffer_size];
|
254 |
xml_buffer = new XML_Char[xml_buffer_size];
|
254 |
if(xml_buffer == NULL)
|
255 |
if(xml_buffer == NULL)
|
255 |
return;
|
256 |
return;
|
256 |
expat_parser = XML_ParserCreate(NULL);
|
257 |
expat_parser = XML_ParserCreate(NULL);
|
257 |
|
258 |
|
258 |
if(expat_parser == NULL) {
|
259 |
if(expat_parser == NULL) {
|
259 |
delete xml_buffer;
|
260 |
delete xml_buffer;
|
260 |
xml_buffer = NULL;
|
261 |
xml_buffer = NULL;
|
261 |
return;
|
262 |
return;
|
262 |
}
|
263 |
}
|
263 |
status = XML_STATUS_OK;
|
264 |
status = XML_STATUS_OK;
|
264 |
last_error = XML_ERROR_NONE;
|
265 |
last_error = XML_ERROR_NONE;
|
265 |
|
266 |
|
266 |
memset(xml_buffer, 0, chunk_size * sizeof(XML_Char));
|
267 |
memset(xml_buffer, 0, chunk_size * sizeof(XML_Char));
|
267 |
|
268 |
|
268 |
/* Set the "ready" flag on this parser */
|
269 |
/* Set the "ready" flag on this parser */
|
269 |
valid_parser = true;
|
270 |
valid_parser = true;
|
270 |
XML_SetUserData(expat_parser, (void*)this);
|
271 |
XML_SetUserData(expat_parser, (void*)this);
|
271 |
register_default_handlers();
|
272 |
register_default_handlers();
|
272 |
}
|
273 |
}
|
273 |
};
|
274 |
};
|
274 |
|
275 |
|
275 |
/** A specialization of ExpatXMLParser that does not copy its input */
|
276 |
/** A specialization of ExpatXMLParser that does not copy its input */
|
276 |
class inputRefXMLParser : public ExpatXMLParser {
|
277 |
class inputRefXMLParser : public ExpatXMLParser {
|
277 |
public:
|
278 |
public:
|
278 |
// Beware: we only use a ref to input to minimize copying. This means
|
279 |
// Beware: we only use a ref to input to minimize copying. This means
|
279 |
// that storage for the input parameter must persist until you are done
|
280 |
// that storage for the input parameter must persist until you are done
|
280 |
// with the parser object !
|
281 |
// with the parser object !
|
281 |
inputRefXMLParser(const std::string& input)
|
282 |
inputRefXMLParser(const std::string& input)
|
282 |
: ExpatXMLParser(1), // Have to allocate a small buf even if not used.
|
283 |
: ExpatXMLParser(1), // Have to allocate a small buf even if not used.
|
283 |
m_input(input)
|
284 |
m_input(input)
|
284 |
{}
|
285 |
{}
|
285 |
|
286 |
|
286 |
protected:
|
287 |
protected:
|
287 |
ssize_t read_block(void)
|
288 |
ssize_t read_block(void)
|
288 |
{
|
289 |
{
|
289 |
if (getLastError() == XML_ERROR_FINISHED) {
|
290 |
if (getLastError() == XML_ERROR_FINISHED) {
|
290 |
setStatus(XML_STATUS_OK);
|
291 |
setStatus(XML_STATUS_OK);
|
291 |
return -1;
|
292 |
return -1;
|
292 |
}
|
293 |
}
|
293 |
setLastError(XML_ERROR_FINISHED);
|
294 |
setLastError(XML_ERROR_FINISHED);
|
294 |
return m_input.size();
|
295 |
return m_input.size();
|
295 |
}
|
296 |
}
|
296 |
const char *getReadBuffer()
|
297 |
const char *getReadBuffer()
|
297 |
{
|
298 |
{
|
298 |
return m_input.c_str();
|
299 |
return m_input.c_str();
|
299 |
}
|
300 |
}
|
300 |
virtual size_t getBlockSize(void)
|
301 |
virtual size_t getBlockSize(void)
|
301 |
{
|
302 |
{
|
302 |
return m_input.size();
|
303 |
return m_input.size();
|
303 |
}
|
304 |
}
|
304 |
private:
|
305 |
protected:
|
305 |
const std::string& m_input;
|
306 |
const std::string& m_input;
|
306 |
};
|
307 |
};
|
307 |
|
308 |
|
308 |
}; // End namespace expatmm
|
309 |
}; // End namespace expatmm
|
309 |
|
310 |
|
310 |
#endif /* _EXPATMM_EXPATXMLPARSER_H */
|
311 |
#endif /* _EXPATMM_EXPATXMLPARSER_H */
|
311 |
/* Local Variables: */
|
|
|
312 |
/* mode: c++ */
|
|
|
313 |
/* c-basic-offset: 4 */
|
|
|
314 |
/* tab-width: 4 */
|
|
|
315 |
/* indent-tabs-mode: t */
|
|
|
316 |
/* End: */
|
|
|