.from: http://mbed.org/users/rolf/programs/fastxml/gpdz45
XxXxXxXxXxXxXxXxXxXxXxXxXxXxXx fastxml.cpp XxXxXxXxXxXxXxXxXxXxXxXxXxXxXx
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "fastxml.h"
/*!
**
** Copyright (c) 2009 by John W. Ratcliff mailto:jratcliffscarab@gmail.com
**
** The MIT license:
**
** Permission is hereby granted, MEMALLOC_FREE of charge, to any person obtaining a copy
** of this software and associated documentation files (the "Software"), to deal
** in the Software without restriction, including without limitation the rights
** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
** copies of the Software, and to permit persons to whom the Software is furnished
** to do so, subject to the following conditions:
**
** The above copyright notice and this permission notice shall be included in all
** copies or substantial portions of the Software.
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
** WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
** CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
class MyFastXml : public FastXml {
public:
enum CharType {
CT_DATA,
CT_EOF,
CT_SOFT,
CT_END_OF_ELEMENT, // either a forward slash or a greater than symbol
CT_END_OF_LINE,
};
MyFastXml(void) {
mInputData = 0;
memset(mTypes,CT_DATA,256);
mTypes[0] = CT_EOF;
mTypes[32] = CT_SOFT;
mTypes[9] = CT_SOFT;
mTypes['/'] = CT_END_OF_ELEMENT;
mTypes['>'] = CT_END_OF_ELEMENT;
mTypes['?'] = CT_END_OF_ELEMENT;
mTypes[10] = CT_END_OF_LINE;
mTypes[13] = CT_END_OF_LINE;
mError = 0;
}
~MyFastXml(void) {
release();
}
void release(void) {
if(mInputData) {
free(mInputData);
mInputData = 0;
}
mError = 0;
}
inline char *nextSoft(char *scan) {
while ( *scan && mTypes[*scan] != CT_SOFT ) scan++;
return scan;
}
inline char *nextSoftOrClose(char *scan,bool &close) {
while ( *scan && mTypes[*scan] != CT_SOFT && *scan != '>' ) scan++;
close = *scan == '>';
return scan;
}
inline char *nextSep(char *scan) {
while ( *scan && mTypes[*scan] != CT_SOFT && *scan != '=' ) scan++;
return scan;
}
inline char * skipNextData(char *scan) {
// while we have data, and we encounter soft seperators or line feeds...
while ( *scan && mTypes[*scan] == CT_SOFT || mTypes[*scan] == CT_END_OF_LINE ) {
if ( *scan == 13 ) mLineNo++;
scan++;
}
return scan;
}
char * processClose(char c,const char *element,char *scan,int argc,const char **argv,FastXmlInterface *iface) {
if ( c == '/' || c == '?' ) {
if ( *scan != '>' ) { // unexepected character!
mError = "Expected an element close character immediately after the '/' or '?' character.";
return 0;
}
scan++;
bool ok = iface->processElement(element,argc,argv,0,mLineNo);
if ( !ok )
{
mError = "User aborted the parsing process";
return 0;
}
}
else
{
scan = skipNextData(scan);
char *data = scan; // this is the data portion of the element, only copies memory if we encounter line feeds
char *dest_data = 0;
while ( *scan && *scan != '<' )
{
if ( mTypes[*scan] == CT_END_OF_LINE )
{
if ( *scan == 13 ) mLineNo++;
dest_data = scan;
*dest_data++ = 32; // replace the linefeed with a space...
scan = skipNextData(scan);
while ( *scan && *scan != '<' )
{
if ( mTypes[*scan] == CT_END_OF_LINE )
{
if ( *scan == 13 ) mLineNo++;
*dest_data++ = 32; // replace the linefeed with a space...
scan = skipNextData(scan);
}
else
{
*dest_data++ = *scan++;
}
}
break;
}
else
scan++;
}
if ( *scan == '<' )
{
if ( dest_data )
{
*dest_data = 0;
}
else
{
*scan = 0;
}
scan++; // skip it..
if ( *data == 0 ) data = 0;
bool ok = iface->processElement(element,argc,argv,data,mLineNo);
if ( !ok )
{
mError = "User aborted the parsing process";
return 0;
}
if ( *scan == '/' )
{
while ( *scan && *scan != '>' ) scan++;
scan++;
}
}
else
{
mError = "Data portion of an element wasn't terminated properly";
return 0;
}
}
return scan;
}
virtual bool processXml(const char *inputData,unsigned int dataLen,FastXmlInterface *iface)
{
bool ret = true;
#define MAX_ATTRIBUTE 2048 // can't imagine having more than 2,048 attributes in a single element right?
release();
mInputData = (char *)malloc(dataLen+1);
memcpy(mInputData,inputData,dataLen);
mInputData[dataLen] = 0;
mLineNo = 1;
char *element;
char *scan = mInputData;
if ( *scan == '<' )
{
scan++;
while ( *scan )
{
scan = skipNextData(scan);
if ( *scan == 0 ) return ret;
if ( *scan == '<' )
{
scan++;
}
if ( *scan == '/' || *scan == '?' )
{
while ( *scan && *scan != '>' ) scan++;
scan++;
}
else
{
element = scan;
int argc = 0;
const char *argv[MAX_ATTRIBUTE];
bool close;
scan = nextSoftOrClose(scan,close);
if ( close )
{
char c = *(scan-1);
if ( c != '?' && c != '/' )
{
c = '>';
}
*scan = 0;
scan++;
scan = processClose(c,element,scan,argc,argv,iface);
if ( !scan ) return false;
}
else
{
if ( *scan == 0 ) return ret;
*scan = 0; // place a zero byte to indicate the end of the element name...
scan++;
while ( *scan )
{
scan = skipNextData(scan); // advance past any soft seperators (tab or space)
if ( mTypes[*scan] == CT_END_OF_ELEMENT )
{
char c = *scan++;
scan = processClose(c,element,scan,argc,argv,iface);
if ( !scan ) return false;
break;
}
else
{
if ( argc >= MAX_ATTRIBUTE )
{
mError = "encountered too many attributes";
return false;
}
argv[argc] = scan;
scan = nextSep(scan); // scan up to a space, or an equal
if ( *scan )
{
if ( *scan != '=' )
{
*scan = 0;
scan++;
while ( *scan && *scan != '=' ) scan++;
if ( *scan == '=' ) scan++;
}
else
{
*scan=0;
scan++;
}
if ( *scan ) // if not eof...
{
scan = skipNextData(scan);
if ( *scan == 34 )
{
scan++;
argc++;
argv[argc] = scan;
argc++;
while ( *scan && *scan != 34 ) scan++;
if ( *scan == 34 )
{
*scan = 0;
scan++;
}
else
{
mError = "Failed to find closing quote for attribute";
return false;
}
}
else
{
mError = "Expected quote to begin attribute";
return false;
}
}
}
}
}
}
}
}
}
else
{
mError = "Expected the start of an element '<' at this location.";
ret = false; // unexpected character!?
}
return ret;
}
virtual const char *getError(int &lineno) {
const char *ret = mError;
lineno = mLineNo;
mError = 0;
return ret;
}
private:
char mTypes[256];
char *mInputData;
int mLineNo;
const char *mError;
};
FastXml *createFastXml(void) {
MyFastXml *f = new MyFastXml;
return static_cast< FastXml *>(f);
}
void releaseFastXml(FastXml *f) {
MyFastXml *m = static_cast< MyFastXml *>(f);
delete m;
}
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "fastxml.h"
/*!
**
** Copyright (c) 2009 by John W. Ratcliff mailto:jratcliffscarab@gmail.com
**
** The MIT license:
**
** Permission is hereby granted, MEMALLOC_FREE of charge, to any person obtaining a copy
** of this software and associated documentation files (the "Software"), to deal
** in the Software without restriction, including without limitation the rights
** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
** copies of the Software, and to permit persons to whom the Software is furnished
** to do so, subject to the following conditions:
**
** The above copyright notice and this permission notice shall be included in all
** copies or substantial portions of the Software.
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
** WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
** CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
class MyFastXml : public FastXml {
public:
enum CharType {
CT_DATA,
CT_EOF,
CT_SOFT,
CT_END_OF_ELEMENT, // either a forward slash or a greater than symbol
CT_END_OF_LINE,
};
MyFastXml(void) {
mInputData = 0;
memset(mTypes,CT_DATA,256);
mTypes[0] = CT_EOF;
mTypes[32] = CT_SOFT;
mTypes[9] = CT_SOFT;
mTypes['/'] = CT_END_OF_ELEMENT;
mTypes['>'] = CT_END_OF_ELEMENT;
mTypes['?'] = CT_END_OF_ELEMENT;
mTypes[10] = CT_END_OF_LINE;
mTypes[13] = CT_END_OF_LINE;
mError = 0;
}
~MyFastXml(void) {
release();
}
void release(void) {
if(mInputData) {
free(mInputData);
mInputData = 0;
}
mError = 0;
}
inline char *nextSoft(char *scan) {
while ( *scan && mTypes[*scan] != CT_SOFT ) scan++;
return scan;
}
inline char *nextSoftOrClose(char *scan,bool &close) {
while ( *scan && mTypes[*scan] != CT_SOFT && *scan != '>' ) scan++;
close = *scan == '>';
return scan;
}
inline char *nextSep(char *scan) {
while ( *scan && mTypes[*scan] != CT_SOFT && *scan != '=' ) scan++;
return scan;
}
inline char * skipNextData(char *scan) {
// while we have data, and we encounter soft seperators or line feeds...
while ( *scan && mTypes[*scan] == CT_SOFT || mTypes[*scan] == CT_END_OF_LINE ) {
if ( *scan == 13 ) mLineNo++;
scan++;
}
return scan;
}
char * processClose(char c,const char *element,char *scan,int argc,const char **argv,FastXmlInterface *iface) {
if ( c == '/' || c == '?' ) {
if ( *scan != '>' ) { // unexepected character!
mError = "Expected an element close character immediately after the '/' or '?' character.";
return 0;
}
scan++;
bool ok = iface->processElement(element,argc,argv,0,mLineNo);
if ( !ok )
{
mError = "User aborted the parsing process";
return 0;
}
}
else
{
scan = skipNextData(scan);
char *data = scan; // this is the data portion of the element, only copies memory if we encounter line feeds
char *dest_data = 0;
while ( *scan && *scan != '<' )
{
if ( mTypes[*scan] == CT_END_OF_LINE )
{
if ( *scan == 13 ) mLineNo++;
dest_data = scan;
*dest_data++ = 32; // replace the linefeed with a space...
scan = skipNextData(scan);
while ( *scan && *scan != '<' )
{
if ( mTypes[*scan] == CT_END_OF_LINE )
{
if ( *scan == 13 ) mLineNo++;
*dest_data++ = 32; // replace the linefeed with a space...
scan = skipNextData(scan);
}
else
{
*dest_data++ = *scan++;
}
}
break;
}
else
scan++;
}
if ( *scan == '<' )
{
if ( dest_data )
{
*dest_data = 0;
}
else
{
*scan = 0;
}
scan++; // skip it..
if ( *data == 0 ) data = 0;
bool ok = iface->processElement(element,argc,argv,data,mLineNo);
if ( !ok )
{
mError = "User aborted the parsing process";
return 0;
}
if ( *scan == '/' )
{
while ( *scan && *scan != '>' ) scan++;
scan++;
}
}
else
{
mError = "Data portion of an element wasn't terminated properly";
return 0;
}
}
return scan;
}
virtual bool processXml(const char *inputData,unsigned int dataLen,FastXmlInterface *iface)
{
bool ret = true;
#define MAX_ATTRIBUTE 2048 // can't imagine having more than 2,048 attributes in a single element right?
release();
mInputData = (char *)malloc(dataLen+1);
memcpy(mInputData,inputData,dataLen);
mInputData[dataLen] = 0;
mLineNo = 1;
char *element;
char *scan = mInputData;
if ( *scan == '<' )
{
scan++;
while ( *scan )
{
scan = skipNextData(scan);
if ( *scan == 0 ) return ret;
if ( *scan == '<' )
{
scan++;
}
if ( *scan == '/' || *scan == '?' )
{
while ( *scan && *scan != '>' ) scan++;
scan++;
}
else
{
element = scan;
int argc = 0;
const char *argv[MAX_ATTRIBUTE];
bool close;
scan = nextSoftOrClose(scan,close);
if ( close )
{
char c = *(scan-1);
if ( c != '?' && c != '/' )
{
c = '>';
}
*scan = 0;
scan++;
scan = processClose(c,element,scan,argc,argv,iface);
if ( !scan ) return false;
}
else
{
if ( *scan == 0 ) return ret;
*scan = 0; // place a zero byte to indicate the end of the element name...
scan++;
while ( *scan )
{
scan = skipNextData(scan); // advance past any soft seperators (tab or space)
if ( mTypes[*scan] == CT_END_OF_ELEMENT )
{
char c = *scan++;
scan = processClose(c,element,scan,argc,argv,iface);
if ( !scan ) return false;
break;
}
else
{
if ( argc >= MAX_ATTRIBUTE )
{
mError = "encountered too many attributes";
return false;
}
argv[argc] = scan;
scan = nextSep(scan); // scan up to a space, or an equal
if ( *scan )
{
if ( *scan != '=' )
{
*scan = 0;
scan++;
while ( *scan && *scan != '=' ) scan++;
if ( *scan == '=' ) scan++;
}
else
{
*scan=0;
scan++;
}
if ( *scan ) // if not eof...
{
scan = skipNextData(scan);
if ( *scan == 34 )
{
scan++;
argc++;
argv[argc] = scan;
argc++;
while ( *scan && *scan != 34 ) scan++;
if ( *scan == 34 )
{
*scan = 0;
scan++;
}
else
{
mError = "Failed to find closing quote for attribute";
return false;
}
}
else
{
mError = "Expected quote to begin attribute";
return false;
}
}
}
}
}
}
}
}
}
else
{
mError = "Expected the start of an element '<' at this location.";
ret = false; // unexpected character!?
}
return ret;
}
virtual const char *getError(int &lineno) {
const char *ret = mError;
lineno = mLineNo;
mError = 0;
return ret;
}
private:
char mTypes[256];
char *mInputData;
int mLineNo;
const char *mError;
};
FastXml *createFastXml(void) {
MyFastXml *f = new MyFastXml;
return static_cast< FastXml *>(f);
}
void releaseFastXml(FastXml *f) {
MyFastXml *m = static_cast< MyFastXml *>(f);
delete m;
}
XxXxXxXxXxXxXxXxXxXxXxXxXxXxXx fastxml.h XxXxXxXxXxXxXxXxXxXxXxXxXxXxXx
#ifndef FAST_XML_H
#define FAST_XML_H
/*!
**
** Copyright (c) 2009 by John W. Ratcliff mailto:jratcliff@infiniplex.net
**
** The MIT license:
**
** Permission is hereby granted, MEMALLOC_FREE of charge, to any person obtaining a copy
** of this software and associated documentation files (the "Software"), to deal
** in the Software without restriction, including without limitation the rights
** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
** copies of the Software, and to permit persons to whom the Software is furnished
** to do so, subject to the following conditions:
**
** The above copyright notice and this permission notice shall be included in all
** copies or substantial portions of the Software.
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
** WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
** CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// This code snippet provides an extremely lightweight and fast XML parser.
// This parser only handles data elements as if they were streamed data.
// It is important to note that all pointers returned by this parser are
// persistent for the lifetime of the FastXml class. This means you can cache
// copies of the pointers (rather than copying any data) if this matches your
// needs.
// Simpy call createFastXml to get a copy of the FastXml parsing interface
// To parse an XML file, have your application inherit the pure virtual
// interface called 'FastXmlInterface' and implement the single method 'processElement'
//
// For each element in the XML file you will get a callback with the following
// data.
//
// 'elementName' the name of the element (this pointer is persistent)
// 'argc' The total number of attributes and values for this element.
// The number of attribute/value pairs is equal to argc/2
// 'argv' The attribute/value pairs in the form of attribute/value, attribute/value..
// These pointers are persistent and can be cached if needed (until FastXml is released)
// 'elementData' optional data (i.e. text) associated with the element. If this is a null pointer
// then the element had no data. This pointer is persistent.
// 'lineno' The line number in the source XML file.
//
// After calling your routine 'processElement' you must return 'true' to continue parsing
// If you want to stop parsing early, return false.
//
// If the call to process an XML file fails, it will return false.
// You can then call the method 'getError' to get a description of why it failed
// and on what line number of the source XML file it occurred.
class FastXmlInterface {
public:
// return true to continue processing the XML document, false to skip.
virtual bool processElement(const char *elementName, // name of the element
int argc, // number of attributes
const char **argv, // list of attributes.
const char *elementData, // element data, null if none
int lineno) = 0; // line number in the source XML file
};
class FastXml {
public:
virtual bool processXml(const char *inputData,unsigned int dataLen,FastXmlInterface *iface) = 0;
virtual const char * getError(int &lineno) = 0; // report the reason for a parsing error, and the line number where it occurred.
};
FastXml *createFastXml(void);
void releaseFastXml(FastXml *f);
#endif
#define FAST_XML_H
/*!
**
** Copyright (c) 2009 by John W. Ratcliff mailto:jratcliff@infiniplex.net
**
** The MIT license:
**
** Permission is hereby granted, MEMALLOC_FREE of charge, to any person obtaining a copy
** of this software and associated documentation files (the "Software"), to deal
** in the Software without restriction, including without limitation the rights
** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
** copies of the Software, and to permit persons to whom the Software is furnished
** to do so, subject to the following conditions:
**
** The above copyright notice and this permission notice shall be included in all
** copies or substantial portions of the Software.
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
** WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
** CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// This code snippet provides an extremely lightweight and fast XML parser.
// This parser only handles data elements as if they were streamed data.
// It is important to note that all pointers returned by this parser are
// persistent for the lifetime of the FastXml class. This means you can cache
// copies of the pointers (rather than copying any data) if this matches your
// needs.
// Simpy call createFastXml to get a copy of the FastXml parsing interface
// To parse an XML file, have your application inherit the pure virtual
// interface called 'FastXmlInterface' and implement the single method 'processElement'
//
// For each element in the XML file you will get a callback with the following
// data.
//
// 'elementName' the name of the element (this pointer is persistent)
// 'argc' The total number of attributes and values for this element.
// The number of attribute/value pairs is equal to argc/2
// 'argv' The attribute/value pairs in the form of attribute/value, attribute/value..
// These pointers are persistent and can be cached if needed (until FastXml is released)
// 'elementData' optional data (i.e. text) associated with the element. If this is a null pointer
// then the element had no data. This pointer is persistent.
// 'lineno' The line number in the source XML file.
//
// After calling your routine 'processElement' you must return 'true' to continue parsing
// If you want to stop parsing early, return false.
//
// If the call to process an XML file fails, it will return false.
// You can then call the method 'getError' to get a description of why it failed
// and on what line number of the source XML file it occurred.
class FastXmlInterface {
public:
// return true to continue processing the XML document, false to skip.
virtual bool processElement(const char *elementName, // name of the element
int argc, // number of attributes
const char **argv, // list of attributes.
const char *elementData, // element data, null if none
int lineno) = 0; // line number in the source XML file
};
class FastXml {
public:
virtual bool processXml(const char *inputData,unsigned int dataLen,FastXmlInterface *iface) = 0;
virtual const char * getError(int &lineno) = 0; // report the reason for a parsing error, and the line number where it occurred.
};
FastXml *createFastXml(void);
void releaseFastXml(FastXml *f);
#endif
XxXxXxXxXxXxXxXxXxXxXxXxXxXxXx main.cpp XxXxXxXxXxXxXxXxXxXxXxXxXxXxXx
#include "mbed.h"
#include "fastxml.h"
DigitalOut myled(LED1);
class tmpr : public FastXmlInterface {
public:
virtual bool processElement(const char *name, int argc, const char **argv, const char *data, int lineno) {
printf("::%s\n", name);
if(strncmp(name, "tmpr", 4)==0) {
printf("tmpr: %s\n", data);
}
return true;
}
};
const char *code = {
"<root>\r\n"
" <chan1>\r\n"
" <tmpr>23.7</tmpr>\r\n"
" </chan1>\r\n"
" <chan2>\r\n"
" <tmpr>23.7</tmpr>\r\n"
" </chan2>\r\n"
"</root>\r\n"
};
int main() {
FastXml *xml = createFastXml();
FastXmlInterface *tmp = new tmpr();
xml->processXml(code, strlen(code), tmp);
while(1) {
myled = 1;
wait(0.2);
myled = 0;
wait(0.2);
}
}
#include "fastxml.h"
DigitalOut myled(LED1);
class tmpr : public FastXmlInterface {
public:
virtual bool processElement(const char *name, int argc, const char **argv, const char *data, int lineno) {
printf("::%s\n", name);
if(strncmp(name, "tmpr", 4)==0) {
printf("tmpr: %s\n", data);
}
return true;
}
};
const char *code = {
"<root>\r\n"
" <chan1>\r\n"
" <tmpr>23.7</tmpr>\r\n"
" </chan1>\r\n"
" <chan2>\r\n"
" <tmpr>23.7</tmpr>\r\n"
" </chan2>\r\n"
"</root>\r\n"
};
int main() {
FastXml *xml = createFastXml();
FastXmlInterface *tmp = new tmpr();
xml->processXml(code, strlen(code), tmp);
while(1) {
myled = 1;
wait(0.2);
myled = 0;
wait(0.2);
}
}
XxXxXxXxXxXxXxXxXxXxXxXxXxXxXx EOF XxXxXxXxXxXxXxXxXxXxXxXxXxXxXx
No comments:
Post a Comment