diff --git a/samples/SAXPrint/SAXPrint.cpp b/samples/SAXPrint/SAXPrint.cpp index a018570b15820e7fe521d335484216fe2f5f3801..dbb2eb9f903740238d2179856a75f2dcf021c4c0 100644 --- a/samples/SAXPrint/SAXPrint.cpp +++ b/samples/SAXPrint/SAXPrint.cpp @@ -56,6 +56,9 @@ /* * $Log$ + * Revision 1.8 2000/04/05 00:20:32 roddey + * More updates for the low level formatted output support + * * Revision 1.7 2000/03/28 19:43:11 roddey * Fixes for signed/unsigned warnings. New work for two way transcoding * stuff. @@ -142,7 +145,8 @@ static void usage() << " -v Invoke the Validating SAX Parser.\n" << " -n Enable namespace processing.\n" << " -x=XXX Use a particular transcoder for output.\n" - << " -? Show this help\n" + << " -? Show this help\n\n" + << " If no encoding is provided, it defaults to UTF-8\n" << endl; } @@ -239,10 +243,9 @@ int main(int argC, char* argV[]) // try { - SAXPrintHandlers handler(encodingName, doEscapes); + SAXPrintHandlers handler(encodingName); parser.setDocumentHandler(&handler); parser.setErrorHandler(&handler); - parser.parse(xmlFile); } diff --git a/samples/SAXPrint/SAXPrintHandlers.cpp b/samples/SAXPrint/SAXPrintHandlers.cpp index cf60a50d3cd8105a55c0f1e3821a0a11760c2356..757f748ad08d9775203d6b20379861d494537759 100644 --- a/samples/SAXPrint/SAXPrintHandlers.cpp +++ b/samples/SAXPrint/SAXPrintHandlers.cpp @@ -56,6 +56,9 @@ /* * $Log$ + * Revision 1.6 2000/04/05 00:20:32 roddey + * More updates for the low level formatted output support + * * Revision 1.5 2000/03/28 19:43:11 roddey * Fixes for signed/unsigned warnings. New work for two way transcoding * stuff. @@ -89,18 +92,54 @@ #include "SAXPrint.hpp" +// --------------------------------------------------------------------------- +// Local const data +// +// Note: This is the 'safe' way to do these strings. If you compiler supports +// L"" style strings, and portability is not a concern, you can use +// those types constants directly. +// --------------------------------------------------------------------------- +static const XMLCh gEndElement[] = { chOpenAngle, chForwardSlash, chNull }; +static const XMLCh gEndPI[] = { chQuestion, chCloseAngle, chSpace }; +static const XMLCh gStartPI[] = { chOpenAngle, chQuestion, chSpace }; +static const XMLCh gXMLDecl1[] = +{ + chOpenAngle, chQuestion, chLatin_x, chLatin_m, chLatin_l + , chSpace, chLatin_v, chLatin_e, chLatin_r, chLatin_s, chLatin_i + , chLatin_o, chLatin_n, chEqual, chDoubleQuote, chDigit_1, chPeriod + , chDigit_0, chDoubleQuote, chSpace, chLatin_e, chLatin_n, chLatin_c + , chLatin_o, chLatin_d, chLatin_i, chLatin_n, chLatin_g, chEqual + , chDoubleQuote, chNull +}; + +static const XMLCh gXMLDecl2[] = +{ + chDoubleQuote, chSpace, chQuestion, chCloseAngle + , chCR, chLF, chNull +}; + + + + // --------------------------------------------------------------------------- // SAXPrintHandlers: Constructors and Destructor // --------------------------------------------------------------------------- -SAXPrintHandlers::SAXPrintHandlers( const char* const encodingName - , const bool doEscapes) : +SAXPrintHandlers::SAXPrintHandlers(const char* const encodingName) : + fFormatter ( encodingName - , (doEscapes ? XMLFormatter::StdEscapes : XMLFormatter::NoEscapes) , this + , XMLFormatter::NoEscapes + , XMLFormatter::UnRep_Fail ) { + // + // Go ahead and output an XML Decl with our known encoding. This + // is not the best answer, but its the best we can do until we + // have SAX2 support. + // + fFormatter << gXMLDecl1 << fFormatter.getEncodingName() << gXMLDecl2; } SAXPrintHandlers::~SAXPrintHandlers() @@ -158,7 +197,7 @@ void SAXPrintHandlers::unparsedEntityDecl(const XMLCh* const name } -void SAXPrintHandlers::notationDecl( const XMLCh* const name +void SAXPrintHandlers::notationDecl(const XMLCh* const name , const XMLCh* const publicId , const XMLCh* const systemId) { @@ -172,7 +211,7 @@ void SAXPrintHandlers::notationDecl( void SAXPrintHandlers::characters(const XMLCh* const chars , const unsigned int length) { - fFormatter.formatBuf(chars, length); + fFormatter.formatBuf(chars, length, XMLFormatter::CharEscapes); } @@ -182,7 +221,7 @@ void SAXPrintHandlers::endDocument() void SAXPrintHandlers::endElement(const XMLCh* const name) { - fFormatter << "</" << name << ">"; + fFormatter << gEndElement << name << chCloseAngle; } void SAXPrintHandlers::ignorableWhitespace( const XMLCh* const chars @@ -194,10 +233,10 @@ void SAXPrintHandlers::ignorableWhitespace( const XMLCh* const chars void SAXPrintHandlers::processingInstruction(const XMLCh* const target , const XMLCh* const data) { - fFormatter << "<?" << target; + fFormatter << gStartPI << target; if (data) - fFormatter << " " << data; - fFormatter << "?>\n"; + fFormatter << chSpace << data; + fFormatter << gEndPI; } void SAXPrintHandlers::startDocument() @@ -207,13 +246,26 @@ void SAXPrintHandlers::startDocument() void SAXPrintHandlers::startElement(const XMLCh* const name , AttributeList& attributes) { - fFormatter << "<" << name; - unsigned int len = attributes.getLength(); + // The name has to be representable without any escapes + fFormatter << XMLFormatter::NoEscapes + << XMLFormatter::UnRep_Fail + << chOpenAngle << name; + unsigned int len = attributes.getLength(); for (unsigned int index = 0; index < len; index++) { - fFormatter << " " << attributes.getName(index) << "=\"" - << attributes.getValue(index) << "\""; + // + // Again the name has to be completely representable. But the + // attribute does require the attribute style escaping. + // + fFormatter << XMLFormatter::NoEscapes + << XMLFormatter::UnRep_Fail + << chSpace << attributes.getName(index) + << chEqual << chDoubleQuote + << XMLFormatter::AttrEscapes + << attributes.getValue(index) + << XMLFormatter::NoEscapes + << chDoubleQuote; } - fFormatter << ">"; + fFormatter << XMLFormatter::NoEscapes << chCloseAngle; } diff --git a/samples/SAXPrint/SAXPrintHandlers.hpp b/samples/SAXPrint/SAXPrintHandlers.hpp index 98321fbd231fd1048ee821e138a980d72bfcd25b..3156fe6248e19dde457f467944fd38daa8149c5f 100644 --- a/samples/SAXPrint/SAXPrintHandlers.hpp +++ b/samples/SAXPrint/SAXPrintHandlers.hpp @@ -56,6 +56,9 @@ /* * $Log$ + * Revision 1.5 2000/04/05 00:20:32 roddey + * More updates for the low level formatted output support + * * Revision 1.4 2000/03/28 19:43:12 roddey * Fixes for signed/unsigned warnings. New work for two way transcoding * stuff. @@ -89,7 +92,6 @@ public: SAXPrintHandlers ( const char* const encodingName - , const bool doEscapes ); ~SAXPrintHandlers(); diff --git a/src/framework/XMLFormatter.cpp b/src/framework/XMLFormatter.cpp index 3a74236cef4af7d0f79566156c97787c3d0e7f02..5aa32e942ec83c5c902eac96c2f2ff5b84cf2369 100644 --- a/src/framework/XMLFormatter.cpp +++ b/src/framework/XMLFormatter.cpp @@ -56,6 +56,9 @@ /** * $Log$ + * Revision 1.2 2000/04/05 00:20:16 roddey + * More updates for the low level formatted output support + * * Revision 1.1 2000/03/28 19:43:17 roddey * Fixes for signed/unsigned warnings. New work for two way transcoding * stuff. @@ -72,18 +75,92 @@ #include <util/TranscodingException.hpp> #include <util/XMLExceptMsgs.hpp> #include <framework/XMLFormatter.hpp> +#include <memory.h> + + +// --------------------------------------------------------------------------- +// Local data +// +// gXXXRef +// These are hard coded versions of the char refs we put out for the +// standard char refs. +// +// gEscapeChars +// For each style of escape, we have a list of the chars that must +// be escaped for that style. The first null hit in each list indicates +// no more valid entries in that list. The first entry is a dummy for +// the NoEscapes style. +// --------------------------------------------------------------------------- +static const XMLCh gAmpRef[] = +{ + chAmpersand, chLatin_a, chLatin_m, chLatin_p, chSemiColon, chNull +}; + +static const XMLCh gAposRef[] = +{ + chAmpersand, chLatin_a, chLatin_p, chLatin_o, chLatin_s, chSemiColon, chNull +}; + +static const XMLCh gGTRef[] = +{ + chAmpersand, chLatin_g, chLatin_t, chSemiColon, chNull +}; + +static const XMLCh gLTRef[] = +{ + chAmpersand, chLatin_l, chLatin_t, chSemiColon, chNull +}; + +static const XMLCh gQuoteRef[] = +{ + chAmpersand, chLatin_q, chLatin_u, chLatin_o, chLatin_t, chSemiColon, chNull +}; + +static const unsigned int kEscapeCount = 6; +static const XMLCh gEscapeChars[XMLFormatter::EscapeFlags_Count][kEscapeCount] = +{ + { chNull , chNull , chNull , chNull , chNull , chNull } + , { chAmpersand , chCloseAngle , chDoubleQuote , chOpenAngle , chSingleQuote , chNull } + , { chAmpersand , chOpenAngle , chDoubleQuote , chNull , chNull , chNull } + , { chAmpersand , chOpenAngle , chNull , chNull , chNull , chNull } +}; + + +// --------------------------------------------------------------------------- +// Local methods +// --------------------------------------------------------------------------- +static inline bool inEscapeList(const XMLFormatter::EscapeFlags escStyle + , const XMLCh toCheck) +{ + const XMLCh* escList = gEscapeChars[escStyle]; + while (*escList) + { + if (*escList++ == toCheck) + return true; + } + return false; +} // --------------------------------------------------------------------------- // XMLFormatter: Constructors and Destructor // --------------------------------------------------------------------------- XMLFormatter::XMLFormatter( const char* const outEncoding + , XMLFormatTarget* const target , const EscapeFlags escapeFlags - , XMLFormatTarget* const target) : + , const UnRepFlags unrepFlags) : + fEscapeFlags(escapeFlags) , fOutEncoding(0) , fTarget(target) + , fUnRepFlags(unrepFlags) , fXCoder(0) + + , fAposRef(0) + , fAmpRef(0) + , fGTRef(0) + , fLTRef(0) + , fQuoteRef(0) { // Transcode the encoding string fOutEncoding = XMLString::transcode(outEncoding); @@ -94,7 +171,7 @@ XMLFormatter::XMLFormatter( const char* const outEncoding ( fOutEncoding , resCode - , 64 * 1024 + , kTmpBufSize ); if (!fXCoder) @@ -112,12 +189,20 @@ XMLFormatter::XMLFormatter( const char* const outEncoding XMLFormatter::XMLFormatter( const XMLCh* const outEncoding + , XMLFormatTarget* const target , const EscapeFlags escapeFlags - , XMLFormatTarget* const target) : + , const UnRepFlags unrepFlags) : fEscapeFlags(escapeFlags) , fOutEncoding(0) , fTarget(target) + , fUnRepFlags(unrepFlags) , fXCoder(0) + + , fAposRef(0) + , fAmpRef(0) + , fGTRef(0) + , fLTRef(0) + , fQuoteRef(0) { // Copy the encoding string fOutEncoding = XMLString::replicate(outEncoding); @@ -146,6 +231,12 @@ XMLFormatter::XMLFormatter( const XMLCh* const outEncoding XMLFormatter::~XMLFormatter() { + delete fAposRef; + delete fAmpRef; + delete fGTRef; + delete fLTRef; + delete fQuoteRef; + delete [] fOutEncoding; delete fXCoder; @@ -157,24 +248,34 @@ XMLFormatter::~XMLFormatter() // XMLFormatter: Formatting methods // --------------------------------------------------------------------------- void -XMLFormatter::formatBuf(const XMLCh* const toFormat, const unsigned int count) +XMLFormatter::formatBuf(const XMLCh* const toFormat + , const unsigned int count + , const EscapeFlags escapeFlags + , const UnRepFlags unrepFlags) { + // + // Figure out the actual escape flag value. If the parameter is not + // the default, then take it. Else take the current default. + // + const EscapeFlags actualEsc = (escapeFlags == DefaultEscape) + ? fEscapeFlags : escapeFlags; + // // If we don't have any escape flags set, then we can do the most // efficient loop, else we have to do it the hard way. // - unsigned int srcCount = count; const XMLCh* srcPtr = toFormat; - if (fEscapeFlags == NoEscapes) + const XMLCh* endPtr = toFormat + count; + unsigned int charsEaten; + if (actualEsc == NoEscapes) { // // Just do a whole buffer at a time into the temp buffer, cap it // off, and send it to the target. // - while (srcCount) + while (srcPtr < endPtr) { - unsigned int charsEaten; - + const unsigned int srcCount = endPtr - srcPtr; const unsigned srcChars = srcCount > kTmpBufSize ? kTmpBufSize : srcCount; @@ -196,17 +297,118 @@ XMLFormatter::formatBuf(const XMLCh* const toFormat, const unsigned int count) } #endif + // If we get any bytes out, then write them if (outBytes) { fTmpBuf[outBytes] = 0; fTarget->writeChars(fTmpBuf); } - srcCount -= charsEaten; + // And bump up our pointer + srcPtr += charsEaten; } } else { + // + // This one just escapes the standard set of XML defined character + // refs: apos, amp, lt, gt, and quot. + // + // For now, just whimp out and do it the simple but slow way in + // order to get this concept out for evaluation. Come back later + // and spiff it up. + // + while (srcPtr < endPtr) + { + // + // Run a temp pointer up until we hit a character that we have + // to escape. Then we can convert all the chars between our + // current source pointer and here all at once. + // + const XMLCh* tmpPtr = srcPtr; + while (!inEscapeList(actualEsc, *tmpPtr) && (tmpPtr < endPtr)) + tmpPtr++; + + // + // If we got any chars, then lets convert them and write them + // out. + // + if (tmpPtr > srcPtr) + { + const unsigned int srcCount = tmpPtr - srcPtr; + const unsigned srcChars = srcCount > kTmpBufSize ? + kTmpBufSize : srcCount; + + const unsigned int outBytes = fXCoder->transcodeTo + ( + srcPtr + , srcChars + , fTmpBuf + , kTmpBufSize + , charsEaten + , XMLTranscoder::UnRep_Throw + ); + + #if XML_DEBUG + if ((outBytes > kTmpBufSize) + || (charsEaten > srcCount)) + { + // <TBD> The transcoder is freakin out maaaannn + } + #endif + + // If we get any bytes out, then write them + if (outBytes) + { + fTmpBuf[outBytes] = 0; + fTarget->writeChars(fTmpBuf); + } + + // And bump up our pointer + srcPtr += charsEaten; + } + else if (tmpPtr < endPtr) + { + // + // Ok, so we've hit a char that must be escaped. So loop + // until we hit the end or a non-escaped char and put out + // char refs for these. + // + bool done = false; + while ((srcPtr < endPtr) && !done) + { + switch(*srcPtr) + { + case chAmpersand : + fTarget->writeChars(getAmpRef()); + break; + + case chSingleQuote : + fTarget->writeChars(getAposRef()); + break; + + case chDoubleQuote : + fTarget->writeChars(getQuoteRef()); + break; + + case chCloseAngle : + fTarget->writeChars(getGTRef()); + break; + + case chOpenAngle : + fTarget->writeChars(getLTRef()); + break; + + default: + done = true; + break; + } + + if (!done) + srcPtr++; + } + } + } } } @@ -217,8 +419,128 @@ XMLFormatter& XMLFormatter::operator<<(const XMLCh* const toFormat) return *this; } -XMLFormatter& XMLFormatter::operator<<(const char* const toFormat) +XMLFormatter& XMLFormatter::operator<<(const XMLCh toFormat) { - fTarget->writeChars((const XMLByte*)toFormat); + // Make a temp string format that + XMLCh szTmp[2]; + szTmp[0] = toFormat; + szTmp[1] = 0; + + formatBuf(szTmp, 1); return *this; } + + + +// --------------------------------------------------------------------------- +// XMLFormatter: Private helper methosd +// --------------------------------------------------------------------------- +const XMLByte* XMLFormatter::getAposRef() +{ + if (fAposRef) + return fAposRef; + + unsigned int charsEaten; + const unsigned int outBytes = fXCoder->transcodeTo + ( + gAposRef + , XMLString::stringLen(gAposRef) + , fTmpBuf + , kTmpBufSize + , charsEaten + , XMLTranscoder::UnRep_Throw + ); + fTmpBuf[outBytes] = 0; + + ((XMLFormatter*)this)->fAposRef = new XMLByte[outBytes + 1]; + memcpy(fAposRef, fTmpBuf, outBytes + 1); + return fAposRef; +} + +const XMLByte* XMLFormatter::getAmpRef() +{ + if (fAmpRef) + return fAmpRef; + + unsigned int charsEaten; + const unsigned int outBytes = fXCoder->transcodeTo + ( + gAmpRef + , XMLString::stringLen(gAmpRef) + , fTmpBuf + , kTmpBufSize + , charsEaten + , XMLTranscoder::UnRep_Throw + ); + fTmpBuf[outBytes] = 0; + + ((XMLFormatter*)this)->fAmpRef = new XMLByte[outBytes + 1]; + memcpy(fAmpRef, fTmpBuf, outBytes + 1); + return fAmpRef; +} + +const XMLByte* XMLFormatter::getGTRef() +{ + if (fGTRef) + return fGTRef; + + unsigned int charsEaten; + const unsigned int outBytes = fXCoder->transcodeTo + ( + gGTRef + , XMLString::stringLen(gGTRef) + , fTmpBuf + , kTmpBufSize + , charsEaten + , XMLTranscoder::UnRep_Throw + ); + fTmpBuf[outBytes] = 0; + + ((XMLFormatter*)this)->fGTRef = new XMLByte[outBytes + 1]; + memcpy(fGTRef, fTmpBuf, outBytes + 1); + return fGTRef; +} + +const XMLByte* XMLFormatter::getLTRef() +{ + if (fLTRef) + return fLTRef; + + unsigned int charsEaten; + const unsigned int outBytes = fXCoder->transcodeTo + ( + gLTRef + , XMLString::stringLen(gLTRef) + , fTmpBuf + , kTmpBufSize + , charsEaten + , XMLTranscoder::UnRep_Throw + ); + fTmpBuf[outBytes] = 0; + + ((XMLFormatter*)this)->fLTRef = new XMLByte[outBytes + 1]; + memcpy(fLTRef, fTmpBuf, outBytes + 1); + return fLTRef; +} + +const XMLByte* XMLFormatter::getQuoteRef() +{ + if (fQuoteRef) + return fQuoteRef; + + unsigned int charsEaten; + const unsigned int outBytes = fXCoder->transcodeTo + ( + gQuoteRef + , XMLString::stringLen(gQuoteRef) + , fTmpBuf + , kTmpBufSize + , charsEaten + , XMLTranscoder::UnRep_Throw + ); + fTmpBuf[outBytes] = 0; + + ((XMLFormatter*)this)->fQuoteRef = new XMLByte[outBytes + 1]; + memcpy(fQuoteRef, fTmpBuf, outBytes + 1); + return fQuoteRef; +} diff --git a/src/framework/XMLFormatter.hpp b/src/framework/XMLFormatter.hpp index 3a1f271e30d7728a5495ea59f756810d50a8abc5..18c75e186bcc6ead168dcda4f30bd0bd7b616d51 100644 --- a/src/framework/XMLFormatter.hpp +++ b/src/framework/XMLFormatter.hpp @@ -56,6 +56,9 @@ /* * $Log$ + * Revision 1.2 2000/04/05 00:20:16 roddey + * More updates for the low level formatted output support + * * Revision 1.1 2000/03/28 19:43:17 roddey * Fixes for signed/unsigned warnings. New work for two way transcoding * stuff. @@ -89,8 +92,20 @@ public: { NoEscapes , StdEscapes - , FailEscapes - , AllEscapes + , AttrEscapes + , CharEscapes + + // Special values, don't use directly + , EscapeFlags_Count + , DefaultEscape = 999 + }; + + enum UnRepFlags + { + UnRep_Fail + , UnRep_CharRef + + , DefaultUnRep = 999 }; @@ -100,15 +115,17 @@ public: XMLFormatter ( const XMLCh* const outEncoding - , const EscapeFlags escapeFlags , XMLFormatTarget* const target + , const EscapeFlags escapeFlags = NoEscapes + , const UnRepFlags unrepFlags = UnRep_Fail ); XMLFormatter ( const char* const outEncoding - , const EscapeFlags escapeFlags , XMLFormatTarget* const target + , const EscapeFlags escapeFlags = NoEscapes + , const UnRepFlags unrepFlags = UnRep_Fail ); ~XMLFormatter(); @@ -121,6 +138,8 @@ public: ( const XMLCh* const toFormat , const unsigned int count + , const EscapeFlags escapeFlags = DefaultEscape + , const UnRepFlags unrepFlags = DefaultUnRep ); XMLFormatter& operator<< @@ -130,7 +149,37 @@ public: XMLFormatter& operator<< ( - const char* const toFormat + const XMLCh toFormat + ); + + + // ----------------------------------------------------------------------- + // Getter methods + // ----------------------------------------------------------------------- + const XMLCh* getEncodingName() const; + + + // ----------------------------------------------------------------------- + // Setter methods + // ----------------------------------------------------------------------- + void setEscapeFlags + ( + const EscapeFlags newFlags + ); + + void setUnRepFlags + ( + const UnRepFlags newFlags + ); + + XMLFormatter& operator<< + ( + const EscapeFlags newFlags + ); + + XMLFormatter& operator<< + ( + const UnRepFlags newFlags ); @@ -152,6 +201,17 @@ private : }; + // ----------------------------------------------------------------------- + // Private helper methods + // ----------------------------------------------------------------------- + const XMLByte* getAposRef(); + const XMLByte* getAmpRef(); + const XMLByte* getGTRef(); + const XMLByte* getLTRef(); + const XMLByte* getQuoteRef(); + + + // ----------------------------------------------------------------------- // Private, non-virtual methods // @@ -164,18 +224,39 @@ private : // This the name of the output encoding. Saved mainly for meaningful // error messages. // + // fRef // fTarget // This is the target object for the formatting operation. // + // fUnRepFlags + // The unrepresentable flags that indicate how to react when a + // character cannot be represented in the target encoding. + // // fXCoder // This the transcoder that we will use. It is created using the // encoding name we were told to use. + // + // fAposRef + // fAmpRef + // fGTRef + // fLTRef + // fQuoteRef + // These are character refs for the standard char refs, in the + // output encoding. They are faulted in as required, by transcoding + // them from fixed Unicode versions. // ----------------------------------------------------------------------- EscapeFlags fEscapeFlags; XMLCh* fOutEncoding; XMLFormatTarget* fTarget; + UnRepFlags fUnRepFlags; XMLTranscoder* fXCoder; XMLByte fTmpBuf[kTmpBufSize + 1]; + + XMLByte* fAposRef; + XMLByte* fAmpRef; + XMLByte* fGTRef; + XMLByte* fLTRef; + XMLByte* fQuoteRef; }; @@ -206,4 +287,41 @@ protected : void operator=(const XMLFormatTarget&) {} }; + +// --------------------------------------------------------------------------- +// XMLFormatter: Getter methods +// --------------------------------------------------------------------------- +inline const XMLCh* XMLFormatter::getEncodingName() const +{ + return fOutEncoding; +} + + +// --------------------------------------------------------------------------- +// XMLFormatter: Setter methods +// --------------------------------------------------------------------------- +inline void XMLFormatter::setEscapeFlags(const EscapeFlags newFlags) +{ + fEscapeFlags = newFlags; +} + +inline void XMLFormatter::setUnRepFlags(const UnRepFlags newFlags) +{ + fUnRepFlags = newFlags; +} + + +inline XMLFormatter& XMLFormatter::operator<<(const EscapeFlags newFlags) +{ + fEscapeFlags = newFlags; + return *this; +} + +inline XMLFormatter& XMLFormatter::operator<<(const UnRepFlags newFlags) +{ + fUnRepFlags = newFlags; + return *this; +} + + #endif