diff --git a/indra/llcommon/llsys.cpp b/indra/llcommon/llsys.cpp index a61085cc522b530ac197b4ff761203011fe23c70..d89b5dbb922a2cb92f18ab17876022e85b7bc549 100755 --- a/indra/llcommon/llsys.cpp +++ b/indra/llcommon/llsys.cpp @@ -1116,9 +1116,11 @@ class FrameWatcher public: FrameWatcher(): // Hooking onto the "mainloop" event pump gets us one call per frame. +#if 0 mConnection(LLEventPumps::instance() .obtain("mainloop") .listen("FrameWatcher", boost::bind(&FrameWatcher::tick, this, _1))), +#endif // Initializing mSampleStart to an invalid timestamp alerts us to skip // trying to compute framerate on the first call. mSampleStart(-1), diff --git a/indra/llmessage/CMakeLists.txt b/indra/llmessage/CMakeLists.txt index 2795afc61da93a420b4248d3f57fdbf4b2b389f7..43d5232ea87ee3cee7abf04687e454e5d73c47a1 100755 --- a/indra/llmessage/CMakeLists.txt +++ b/indra/llmessage/CMakeLists.txt @@ -65,6 +65,7 @@ set(llmessage_SOURCE_FILES llmail.cpp llmessagebuilder.cpp llmessageconfig.cpp + llmessagelog.cpp llmessagereader.cpp llmessagetemplate.cpp llmessagetemplateparser.cpp @@ -164,6 +165,7 @@ set(llmessage_HEADER_FILES llmail.h llmessagebuilder.h llmessageconfig.h + llmessagelog.h llmessagereader.h llmessagetemplate.h llmessagetemplateparser.h diff --git a/indra/llmessage/llcircuit.cpp b/indra/llmessage/llcircuit.cpp index e6e3145e275892ff27a6e0a0f597ec5c21ff22c5..44fc6e31793ab51c009a916c248f32f43461c2cf 100755 --- a/indra/llmessage/llcircuit.cpp +++ b/indra/llmessage/llcircuit.cpp @@ -1415,3 +1415,12 @@ F32 LLCircuitData::getAgeInSeconds() const { return mExistenceTimer.getElapsedTimeF32(); } + +std::vector<LLCircuitData*> LLCircuit::getCircuitDataList() +{ + std::vector<LLCircuitData*> list; + for (const auto& item : mCircuitData) + list.push_back(item.second); + return list; +} + diff --git a/indra/llmessage/llcircuit.h b/indra/llmessage/llcircuit.h index b8021bc9f053c9dd19c5a87c4bb3afd6b97e3786..e0c055592516b282dac0e423b984e6afdb9a6434 100755 --- a/indra/llmessage/llcircuit.h +++ b/indra/llmessage/llcircuit.h @@ -327,6 +327,8 @@ public: circuit_data_map::iterator& first, circuit_data_map::iterator& end); + std::vector<LLCircuitData*> getCircuitDataList(); + // Lists that optimize how many circuits we need to traverse a frame // HACK - this should become protected eventually, but stupid !@$@# message system/circuit classes are jumbling things up. circuit_data_map mUnackedCircuitMap; // Map of circuits with unacked data diff --git a/indra/llmessage/llhttpclient.cpp b/indra/llmessage/llhttpclient.cpp index 0c9055db3d93059eb9d477829498afa561602bf0..3c512f53c79882f69d729b8274bec72706ad78c9 100755 --- a/indra/llmessage/llhttpclient.cpp +++ b/indra/llmessage/llhttpclient.cpp @@ -40,6 +40,9 @@ #include "message.h" #include <curl/curl.h> +#include "llmessagelog.h" + +#include <boost/algorithm/string.hpp> const F32 HTTP_REQUEST_EXPIRY_SECS = 60.0f; LLURLRequest::SSLCertVerifyCallback LLHTTPClient::mCertVerifyCallback = NULL; @@ -53,9 +56,9 @@ namespace class LLHTTPClientURLAdaptor : public LLURLRequestComplete { public: - LLHTTPClientURLAdaptor(LLCurl::ResponderPtr responder) + LLHTTPClientURLAdaptor(LLCurl::ResponderPtr responder, U64 request_id) : LLURLRequestComplete(), mResponder(responder), mStatus(HTTP_INTERNAL_ERROR), - mReason("LLURLRequest complete w/no status") + mReason("LLURLRequest complete w/no status"), mRequestID(request_id) { } @@ -77,6 +80,10 @@ namespace // *TODO: Re-interpret mRequestStatus codes? // Would like to detect curl errors, such as // connection errors, write erros, etc. + if(LLMessageLog::haveLogger()) + { + LLMessageLog::logHTTPResponse(mStatus, channels, buffer, mLoggedHeaders, mRequestID); + } if (mResponder.get()) { mResponder->setResult(mStatus, mReason); @@ -89,12 +96,19 @@ namespace { mResponder->setResponseHeader(header, value); } + if(LLMessageLog::haveLogger()) + { + mLoggedHeaders[header] = value; + } } private: LLCurl::ResponderPtr mResponder; S32 mStatus; std::string mReason; + + U64 mRequestID; + LLSD mLoggedHeaders; }; class Injector : public LLIOPipe @@ -242,15 +256,17 @@ static void request( { responder->completeResult(HTTP_INTERNAL_CURL_ERROR, "Internal Error - curl failure"); } - delete req ; + delete req; delete body_injector; - return ; + return; } req->setSSLVerifyCallback(LLHTTPClient::getCertVerifyCallback(), (void *)req); LL_DEBUGS("LLHTTPClient") << httpMethodAsVerb(method) << " " << url << " " << headers << LL_ENDL; + LLSD final_headers = headers; + // Insert custom headers if the caller sent any if (headers.isMap()) { @@ -259,8 +275,8 @@ static void request( req->allowCookies(); } - LLSD::map_const_iterator iter = headers.beginMap(); - LLSD::map_const_iterator end = headers.endMap(); + LLSD::map_const_iterator iter = headers.beginMap(); + LLSD::map_const_iterator end = headers.endMap(); for (; iter != end; ++iter) { @@ -287,6 +303,7 @@ static void request( if(!headers.has(HTTP_OUT_HEADER_ACCEPT)) { req->addHeader(HTTP_OUT_HEADER_ACCEPT, HTTP_CONTENT_LLSD_XML); + final_headers[HTTP_OUT_HEADER_ACCEPT] = HTTP_CONTENT_LLSD_XML; } } @@ -296,12 +313,12 @@ static void request( responder->setHTTPMethod(method); } - req->setCallback(new LLHTTPClientURLAdaptor(responder)); - if (method == HTTP_POST && gMessageSystem) { req->addHeader("X-SecondLife-UDP-Listen-Port", llformat("%d", gMessageSystem->mPort)); + final_headers["X-SecondLife-UDP-Listen-Port"] = llformat("%d", + gMessageSystem->mPort); } if (method == HTTP_PUT || method == HTTP_POST || method == HTTP_PATCH) @@ -314,10 +331,29 @@ static void request( // if they did not specify a Content-Type, then ask the // injector. req->addHeader(HTTP_OUT_HEADER_CONTENT_TYPE, body_injector->contentType()); + final_headers[HTTP_OUT_HEADER_CONTENT_TYPE] = body_injector->contentType(); } - chain.push_back(LLIOPipe::ptr_t(body_injector)); + chain.push_back(LLIOPipe::ptr_t(body_injector)); } + //<edit> TODO: Will this log cookies from cURL's cookie jar? + static U64 request_id = 0; + ++request_id; + if(LLMessageLog::haveLogger()) + { + LLIOPipe::buffer_ptr_t body_buffer(new LLBufferArray); + LLChannelDescriptors body_channels = body_buffer->nextChannel(); + LLSD body_context = LLSD(); + bool body_eos = false; + + if(body_injector) + body_injector->process(body_channels, body_buffer, body_eos, body_context, NULL); + + LLMessageLog::logHTTPRequest(url, method, body_channels, body_buffer, final_headers, request_id); + } + + req->setCallback(new LLHTTPClientURLAdaptor(responder, request_id)); + chain.push_back(LLIOPipe::ptr_t(req)); theClientPump->addChain(chain, timeout); @@ -661,6 +697,22 @@ void LLHTTPClient::copy( request(url, HTTP_COPY, NULL, responder, timeout, headers); } +void LLHTTPClient::builderRequest( + EHTTPMethod method, + const std::string& url, + const U8* data, + S32 size, + const LLSD& headers, + ResponderPtr responder, + const F32 timeout) +{ + Injector* body_injector = NULL; + if(data != NULL && size) + { + body_injector = new RawInjector(data, size); + } + request(url, method, body_injector, responder, timeout, headers); +} void LLHTTPClient::setPump(LLPumpIO& pump) { @@ -671,3 +723,31 @@ bool LLHTTPClient::hasPump() { return theClientPump != NULL; } + +std::string get_base_cap_url(std::string url) +{ + std::vector<std::string> url_parts; + boost::algorithm::split(url_parts, url, boost::is_any_of("/")); + + // This is a normal linden-style CAP url. + if(url_parts.size() >= 4 && url_parts[3] == "cap") + { + url_parts.resize(5); + return boost::algorithm::join(url_parts, "/"); + } + // Maybe OpenSim? Just cut off the query string and last /. + else + { + size_t query_pos = url.find_first_of("\?"); + + if(query_pos != std::string::npos) + { + LLStringUtil::truncate(url, query_pos); + } + + static const std::string tokens(" /\?"); + LLStringUtil::trimTail(url, tokens); + + return url; + } +} diff --git a/indra/llmessage/llhttpclient.h b/indra/llmessage/llhttpclient.h index fd48b4a743e3f4523d72488b0f5b91d7e89af326..b9da02cbd7f0cb99a5f4058e003a6c594a35c93c 100755 --- a/indra/llmessage/llhttpclient.h +++ b/indra/llmessage/llhttpclient.h @@ -59,6 +59,14 @@ public: /** @name non-blocking API */ //@{ + static void builderRequest( + EHTTPMethod method, + const std::string& url, + const U8* data, + S32 size, + const LLSD& headers = LLSD(), + ResponderPtr responder = NULL, + const F32 timeout = HTTP_REQUEST_EXPIRY_SECS); static void head( const std::string& url, ResponderPtr, @@ -198,4 +206,6 @@ protected: static LLURLRequest::SSLCertVerifyCallback mCertVerifyCallback; }; +std::string get_base_cap_url(std::string url); + #endif // LL_LLHTTPCLIENT_H diff --git a/indra/llmessage/llhttpconstants.cpp b/indra/llmessage/llhttpconstants.cpp index 32f76f0d7004601a18bd4c4f3638401ac502a413..3dd8773ac77bf3fd38015e11100127c3ee8ecbff 100755 --- a/indra/llmessage/llhttpconstants.cpp +++ b/indra/llmessage/llhttpconstants.cpp @@ -156,6 +156,29 @@ const std::string& httpMethodAsVerb(EHTTPMethod method) return VERBS[method]; } +EHTTPMethod httpVerbAsMethod(const std::string& verb) +{ + static const std::string VERBS[] = { + HTTP_VERB_INVALID, + HTTP_VERB_HEAD, + HTTP_VERB_GET, + HTTP_VERB_PUT, + HTTP_VERB_POST, + HTTP_VERB_DELETE, + HTTP_VERB_MOVE, + HTTP_VERB_OPTIONS, + HTTP_VERB_PATCH, + HTTP_VERB_COPY + }; + + for(int i=0; i<HTTP_METHOD_COUNT; ++i) + { + if(VERBS[i] == verb) + return (EHTTPMethod)i; + } + return HTTP_INVALID; +} + bool isHttpInformationalStatus(S32 status) { // Check for status 1xx. diff --git a/indra/llmessage/llhttpconstants.h b/indra/llmessage/llhttpconstants.h index d6bcbd3c19329ed1a2aa1bd4bcfdd1ed4bbde317..3a89723b62dd0254733a6f25741fe11c8d004444 100755 --- a/indra/llmessage/llhttpconstants.h +++ b/indra/llmessage/llhttpconstants.h @@ -117,6 +117,7 @@ enum EHTTPMethod }; const std::string& httpMethodAsVerb(EHTTPMethod method); +EHTTPMethod httpVerbAsMethod(const std::string&); bool isHttpInformationalStatus(S32 status); bool isHttpGoodStatus(S32 status); bool isHttpRedirectStatus(S32 status); diff --git a/indra/llmessage/llmessageconfig.cpp b/indra/llmessage/llmessageconfig.cpp index 64e79d67675530152a9f8a357b64e0e6e6eaf5a1..01f12d9b947ad6ac2ccbbbb4df619a514a45ed8b 100755 --- a/indra/llmessage/llmessageconfig.cpp +++ b/indra/llmessage/llmessageconfig.cpp @@ -34,48 +34,6 @@ #include "llsdserialize.h" #include "message.h" -static const char messageConfigFileName[] = "message.xml"; -static const F32 messageConfigRefreshRate = 5.0; // seconds - -static std::string sServerName = ""; -static std::string sConfigDir = ""; - -static std::string sServerDefault; -static LLSD sMessages; - - -class LLMessageConfigFile : public LLLiveFile -{ -public: - LLMessageConfigFile() : - LLLiveFile(filename(), messageConfigRefreshRate), - mMaxQueuedEvents(0) - { } - - static std::string filename(); - - LLSD mMessages; - std::string mServerDefault; - - static LLMessageConfigFile& instance(); - // return the singleton configuration file - - /* virtual */ bool loadFile(); - void loadServerDefaults(const LLSD& data); - void loadMaxQueuedEvents(const LLSD& data); - void loadMessages(const LLSD& data); - void loadCapBans(const LLSD& blacklist); - void loadMessageBans(const LLSD& blacklist); - bool isCapBanned(const std::string& cap_name) const; - -public: - LLSD mCapBans; - S32 mMaxQueuedEvents; - -private: - static const S32 DEFAULT_MAX_QUEUED_EVENTS = 100; -}; - std::string LLMessageConfigFile::filename() { std::ostringstream ostr; diff --git a/indra/llmessage/llmessageconfig.h b/indra/llmessage/llmessageconfig.h index 1b39c386ca5f9b1dda21bd2df0af612a9a69fd84..0f6075e172476dc6096861a3839ed80fb755a498 100755 --- a/indra/llmessage/llmessageconfig.h +++ b/indra/llmessage/llmessageconfig.h @@ -29,6 +29,17 @@ #include <string> #include "llsd.h" +#include "llfile.h" +#include "lllivefile.h" + +static const char messageConfigFileName[] = "message.xml"; +static const F32 messageConfigRefreshRate = 5.0; // seconds + +static std::string sServerName = ""; +static std::string sConfigDir = ""; + +static std::string sServerDefault; +static LLSD sMessages; class LLSD; @@ -53,4 +64,36 @@ public: static bool isCapBanned(const std::string& cap_name); static LLSD getConfigForMessage(const std::string& msg_name); }; + +class LLMessageConfigFile : public LLLiveFile +{ +public: + LLMessageConfigFile() : + LLLiveFile(filename(), messageConfigRefreshRate), + mMaxQueuedEvents(0) + { } + + static std::string filename(); + + LLSD mMessages; + std::string mServerDefault; + + static LLMessageConfigFile& instance(); + // return the singleton configuration file + + /* virtual */ bool loadFile(); + void loadServerDefaults(const LLSD& data); + void loadMaxQueuedEvents(const LLSD& data); + void loadMessages(const LLSD& data); + void loadCapBans(const LLSD& blacklist); + void loadMessageBans(const LLSD& blacklist); + bool isCapBanned(const std::string& cap_name) const; + +public: + LLSD mCapBans; + S32 mMaxQueuedEvents; + +private: + static const S32 DEFAULT_MAX_QUEUED_EVENTS = 100; +}; #endif // LL_MESSAGECONFIG_H diff --git a/indra/llmessage/llmessagelog.cpp b/indra/llmessage/llmessagelog.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1082d9888cef8b7de25cade65ce534b615a312cf --- /dev/null +++ b/indra/llmessage/llmessagelog.cpp @@ -0,0 +1,132 @@ +/** + * @file lleasymessagereader.cpp + * + * $LicenseInfo:firstyear=2015&license=viewerlgpl$ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * $/LicenseInfo$ + */ + +#include "llmessagelog.h" + +LLMessageLogEntry::LLMessageLogEntry(EType type, LLHost from_host, LLHost to_host, U8* data, S32 data_size) +: mType(type), + mFromHost(from_host), + mToHost(to_host), + mDataSize(data_size), + mData(NULL) +{ + if(data) + { + mData = new U8[data_size]; + memcpy(mData, data, data_size); + } +} + +LLMessageLogEntry::LLMessageLogEntry() +: mType(NONE), + mFromHost(LLHost()), + mToHost(LLHost()), + mDataSize(0), + mData(NULL) +{ +} + +LLMessageLogEntry::LLMessageLogEntry(EType type, const std::string& url, const LLChannelDescriptors& channels, + const LLIOPipe::buffer_ptr_t& buffer, const LLSD& headers, U64 request_id, + EHTTPMethod method, U32 status_code) + : mType(type), + mURL(url), + mHeaders(headers), + mRequestID(request_id), + mMethod(method), + mStatusCode(status_code), + mDataSize(0), + mData(NULL) +{ + if(buffer.get()) + { + S32 channel = type == HTTP_REQUEST ? channels.out() : channels.in(); + mDataSize = buffer->countAfter(channel, NULL); + if (mDataSize > 0) + { + mData = new U8[mDataSize + 1]; + buffer->readAfter(channel, NULL, mData, mDataSize); + + //make sure this is null terminated, since it's going to be used stringified + mData[mDataSize] = '\0'; + ++mDataSize; + } + } +} + +LLMessageLogEntry::LLMessageLogEntry(const LLMessageLogEntry& entry) + : mDataSize(entry.mDataSize), + mType(entry.mType), + mToHost(entry.mToHost), + mFromHost(entry.mFromHost), + mURL(entry.mURL), + mHeaders(entry.mHeaders), + mRequestID(entry.mRequestID), + mMethod(entry.mMethod), + mStatusCode(entry.mStatusCode) +{ + mData = new U8[mDataSize]; + memcpy(mData, entry.mData, mDataSize); +} + +LLMessageLogEntry::~LLMessageLogEntry() +{ + delete[] mData; +} + +LogCallback LLMessageLog::sCallback = NULL; + +void LLMessageLog::setCallback(LogCallback callback) +{ + sCallback = callback; +} + +void LLMessageLog::log(LLHost from_host, LLHost to_host, U8* data, S32 data_size) +{ + //we don't store anything locally anymore, don't bother creating a + //log entry if it's not even going to be logged. + if(!sCallback || !data_size || data == NULL) return; + + // TODO: We usually filter on message type. We can avoid unnecessary copies + // by first only calling LLTemplateMessageReader::decodeTemplate() and asking + // if we're even interested in that message type. + LogPayload payload = new LLMessageLogEntry(LLMessageLogEntry::TEMPLATE, from_host, to_host, data, data_size); + + sCallback(payload); +} + +void LLMessageLog::logHTTPRequest(const std::string& url, EHTTPMethod method, const LLChannelDescriptors& channels, + const LLIOPipe::buffer_ptr_t& buffer, const LLSD& headers, U64 request_id) +{ + if(!sCallback) return; + + LogPayload payload = new LLMessageLogEntry(LLMessageLogEntry::HTTP_REQUEST, url, channels, buffer, + headers, request_id, method); + + sCallback(payload); +} + +void LLMessageLog::logHTTPResponse(U32 status_code, const LLChannelDescriptors& channels, + const LLIOPipe::buffer_ptr_t& buffer, const LLSD& headers, U64 request_id) +{ + if(!sCallback) return; + + LogPayload payload = new LLMessageLogEntry(LLMessageLogEntry::HTTP_RESPONSE, "", channels, buffer, + headers, request_id, HTTP_INVALID, status_code); + + sCallback(payload); +} diff --git a/indra/llmessage/llmessagelog.h b/indra/llmessage/llmessagelog.h new file mode 100644 index 0000000000000000000000000000000000000000..d6770595a1ba82d52ed95433119b6caef668b120 --- /dev/null +++ b/indra/llmessage/llmessagelog.h @@ -0,0 +1,82 @@ +/** + * @file lleasymessagereader.cpp + * + * $LicenseInfo:firstyear=2015&license=viewerlgpl$ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * $/LicenseInfo$ + */ + +#ifndef LL_LLMESSAGELOG_H +#define LL_LLMESSAGELOG_H + +#include "linden_common.h" +#include "llhost.h" +#include "lliopipe.h" +#include "llurlrequest.h" +#include <queue> +#include <string.h> + +class LLMessageSystem; + +class LLMessageLogEntry; +typedef LLMessageLogEntry* LogPayload; + +class LLMessageLogEntry +{ +public: + enum EType + { + NONE, + TEMPLATE, + HTTP_REQUEST, + HTTP_RESPONSE, + LOG_TYPE_NUM + }; + LLMessageLogEntry(EType type, LLHost from_host, LLHost to_host, U8* data, S32 data_size); + LLMessageLogEntry(EType type, const std::string& url, const LLChannelDescriptors& channels, + const LLIOPipe::buffer_ptr_t& buffer, const LLSD& headers, U64 request_id, + EHTTPMethod method = HTTP_INVALID, U32 status_code = 0); + LLMessageLogEntry(const LLMessageLogEntry& entry); + LLMessageLogEntry(); + ~LLMessageLogEntry(); + EType mType; + LLHost mFromHost; + LLHost mToHost; + S32 mDataSize; + U8* mData; + + //http-related things + std::string mURL; + U32 mStatusCode; + EHTTPMethod mMethod; + LLSD mHeaders; + U64 mRequestID; +}; + +typedef void (*LogCallback) (LogPayload); + +class LLMessageLog +{ +public: + static void setCallback(LogCallback callback); + static void log(LLHost from_host, LLHost to_host, U8* data, S32 data_size); + static void logHTTPRequest(const std::string& url, EHTTPMethod method, const LLChannelDescriptors& channels, + const LLIOPipe::buffer_ptr_t& buffer, const LLSD& headers, U64 request_id); + static void logHTTPResponse(U32 status_code, const LLChannelDescriptors& channels, + const LLIOPipe::buffer_ptr_t& buffer, const LLSD& headers, U64 request_id); + + static bool haveLogger(){return sCallback != NULL;} + +private: + static LogCallback sCallback; +}; +#endif diff --git a/indra/llmessage/llmessagetemplate.h b/indra/llmessage/llmessagetemplate.h index a44e16fc3ae6ee41b2e30841747b9acd79caa3d5..fa5adc28fbcd2a8582ba2bdb5168f6752bff3d70 100755 --- a/indra/llmessage/llmessagetemplate.h +++ b/indra/llmessage/llmessagetemplate.h @@ -31,6 +31,10 @@ #include "llstl.h" #include "llindexedvector.h" +#include <boost/signals2.hpp> +#include <boost/shared_ptr.hpp> +using namespace boost::signals2::keywords; + class LLMsgVarData { public: @@ -286,8 +290,7 @@ public: mMaxDecodeTimePerMsg(0.f), mBanFromTrusted(false), mBanFromUntrusted(false), - mHandlerFunc(NULL), - mUserData(NULL) + mMessageSignal(new message_signal_t()) { mName = LLMessageStringTable::getInstance()->getString(name); } @@ -355,17 +358,33 @@ public: return mDeprecation; } - void setHandlerFunc(void (*handler_func)(LLMessageSystem *msgsystem, void **user_data), void **user_data) + boost::signals2::connection setHandlerFunc(void (*handler_func)(LLMessageSystem *msgsystem, void **user_data), void **user_data) + { + disconnectAllSlots(); + if(handler_func) + return addHandlerFunc(boost::bind(handler_func,_1,user_data)); + else //keep behavior of NULL handler_func clear callbacks + return boost::signals2::connection(); + } + + boost::signals2::connection addHandlerFunc(boost::function<void (LLMessageSystem *msgsystem)> handler_slot) { - mHandlerFunc = handler_func; - mUserData = user_data; + return mMessageSignal->connect(handler_slot); + } + + void disconnectAllSlots() + { + //if mMessageSignal is not empty clear it out. + if(!mMessageSignal->empty()) + mMessageSignal->disconnect_all_slots(); } BOOL callHandlerFunc(LLMessageSystem *msgsystem) const { - if (mHandlerFunc) + if (!mMessageSignal->empty()) { - mHandlerFunc(msgsystem, mUserData); + //fire and forget + (*mMessageSignal)(msgsystem); return TRUE; } return FALSE; @@ -414,8 +433,7 @@ public: private: // message handler function (this is set by each application) - void (*mHandlerFunc)(LLMessageSystem *msgsystem, void **user_data); - void **mUserData; -}; + typedef boost::signals2::signal_type<void (LLMessageSystem*), mutex_type<boost::signals2::dummy_mutex> >::type message_signal_t; + boost::shared_ptr< message_signal_t > mMessageSignal;}; #endif // LL_LLMESSAGETEMPLATE_H diff --git a/indra/llmessage/llpacketring.cpp b/indra/llmessage/llpacketring.cpp index a23d6c9fb6b2d0989a17cf1549e11559ee1e7638..38d10230196dff10fc3bfb4a3758f2f186ad2ee5 100755 --- a/indra/llmessage/llpacketring.cpp +++ b/indra/llmessage/llpacketring.cpp @@ -43,6 +43,8 @@ #include "message.h" #include "u64.h" +#include "llmessagelog.h" + /////////////////////////////////////////////////////////// LLPacketRing::LLPacketRing () : mUseInThrottle(FALSE), @@ -272,6 +274,7 @@ S32 LLPacketRing::receivePacket (S32 socket, char *datap) BOOL LLPacketRing::sendPacket(int h_socket, char * send_buffer, S32 buf_size, LLHost host) { + LLMessageLog::log(LLHost(16777343, gMessageSystem->getListenPort()), host, (U8*)send_buffer, buf_size); BOOL status = TRUE; if (!mUseOutThrottle) { diff --git a/indra/llmessage/lltemplatemessagereader.cpp b/indra/llmessage/lltemplatemessagereader.cpp index 406afadd2f87532e7f110b2de8c36187ee349713..aee09bff3ea48702edb936fe47cc6565ad04acbf 100755 --- a/indra/llmessage/lltemplatemessagereader.cpp +++ b/indra/llmessage/lltemplatemessagereader.cpp @@ -445,7 +445,7 @@ S32 LLTemplateMessageReader::getMessageSize() const // Returns template for the message contained in buffer BOOL LLTemplateMessageReader::decodeTemplate( const U8* buffer, S32 buffer_size, // inputs - LLMessageTemplate** msg_template ) // outputs + LLMessageTemplate** msg_template, BOOL custom) // outputs { const U8* header = buffer + LL_PACKET_ID_SIZE; @@ -487,6 +487,7 @@ BOOL LLTemplateMessageReader::decodeTemplate( } else // bogus packet received (too short) { + if (!custom) LL_WARNS() << "Packet with unusable length received (too short): " << buffer_size << LL_ENDL; return(FALSE); @@ -499,9 +500,12 @@ BOOL LLTemplateMessageReader::decodeTemplate( } else { + if (!custom) + { LL_WARNS() << "Message #" << std::hex << num << std::dec << " received but not registered!" << LL_ENDL; gMessageSystem->callExceptionFunc(MX_UNREGISTERED_MESSAGE); + } return(FALSE); } @@ -530,7 +534,7 @@ void LLTemplateMessageReader::logRanOffEndOfPacket( const LLHost& host, const S3 static LLTrace::BlockTimerStatHandle FTM_PROCESS_MESSAGES("Process Messages"); // decode a given message -BOOL LLTemplateMessageReader::decodeData(const U8* buffer, const LLHost& sender ) +BOOL LLTemplateMessageReader::decodeData(const U8* buffer, const LLHost& sender, BOOL custom) { llassert( mReceiveSize >= 0 ); llassert( mCurrentRMessageTemplate); @@ -588,6 +592,7 @@ BOOL LLTemplateMessageReader::decodeData(const U8* buffer, const LLHost& sender } else { + if (!custom) LL_ERRS() << "Unknown block type" << LL_ENDL; return FALSE; } @@ -634,6 +639,7 @@ BOOL LLTemplateMessageReader::decodeData(const U8* buffer, const LLHost& sender if ((decode_pos + data_size) > mReceiveSize) { + if (!custom) logRanOffEndOfPacket(sender, decode_pos, data_size); // default to 0 length variable blocks @@ -670,6 +676,7 @@ BOOL LLTemplateMessageReader::decodeData(const U8* buffer, const LLHost& sender // so, copy data pointer and set data size to fixed size if ((decode_pos + mvci.getSize()) > mReceiveSize) { + if (!custom) logRanOffEndOfPacket(sender, decode_pos, mvci.getSize()); // default to 0s. @@ -698,6 +705,7 @@ BOOL LLTemplateMessageReader::decodeData(const U8* buffer, const LLHost& sender return FALSE; } + if (!custom) { static LLTimer decode_timer; @@ -753,11 +761,12 @@ BOOL LLTemplateMessageReader::decodeData(const U8* buffer, const LLHost& sender BOOL LLTemplateMessageReader::validateMessage(const U8* buffer, S32 buffer_size, const LLHost& sender, - bool trusted) + bool trusted, + BOOL custom) { mReceiveSize = buffer_size; - BOOL valid = decodeTemplate(buffer, buffer_size, &mCurrentRMessageTemplate ); - if(valid) + BOOL valid = decodeTemplate(buffer, buffer_size, &mCurrentRMessageTemplate, custom ); + if(valid && !custom) { mCurrentRMessageTemplate->mReceiveCount++; //LL_DEBUGS() << "MessageRecvd:" @@ -828,3 +837,9 @@ void LLTemplateMessageReader::copyToBuilder(LLMessageBuilder& builder) const } builder.copyFromMessageData(*mCurrentRMessageData); } + +LLMessageTemplate* LLTemplateMessageReader::getTemplate() +{ + return mCurrentRMessageTemplate; +} + diff --git a/indra/llmessage/lltemplatemessagereader.h b/indra/llmessage/lltemplatemessagereader.h index fcf8f92fa6aac61651d2eb912269c643b2da3fae..59cb4ae437d0e86c58e6da533dd1a369ff07475d 100755 --- a/indra/llmessage/lltemplatemessagereader.h +++ b/indra/llmessage/lltemplatemessagereader.h @@ -99,25 +99,26 @@ public: virtual void copyToBuilder(LLMessageBuilder&) const; BOOL validateMessage(const U8* buffer, S32 buffer_size, - const LLHost& sender, bool trusted = false); + const LLHost& sender, bool trusted = false, BOOL custom = FALSE); BOOL readMessage(const U8* buffer, const LLHost& sender); bool isTrusted() const; bool isBanned(bool trusted_source) const; bool isUdpBanned() const; - + + BOOL decodeData(const U8* buffer, const LLHost& sender, BOOL custom = FALSE); + LLMessageTemplate* getTemplate(); + private: void getData(const char *blockname, const char *varname, void *datap, S32 size = 0, S32 blocknum = 0, S32 max_size = S32_MAX); BOOL decodeTemplate(const U8* buffer, S32 buffer_size, // inputs - LLMessageTemplate** msg_template ); // outputs + LLMessageTemplate** msg_template, BOOL custom = FALSE); // outputs void logRanOffEndOfPacket( const LLHost& host, const S32 where, const S32 wanted ); - BOOL decodeData(const U8* buffer, const LLHost& sender ); - S32 mReceiveSize; LLMessageTemplate* mCurrentRMessageTemplate; LLMsgData* mCurrentRMessageData; diff --git a/indra/llmessage/lltransfermanager.cpp b/indra/llmessage/lltransfermanager.cpp index ec7b21d8b60b6f3dc20d4627e08b56ca4444d9f2..5e6d342f203e043ab8869edbd3832de0828c36b7 100755 --- a/indra/llmessage/lltransfermanager.cpp +++ b/indra/llmessage/lltransfermanager.cpp @@ -396,7 +396,7 @@ void LLTransferManager::processTransferInfo(LLMessageSystem *msgp, void **) } else { - LL_INFOS() << "LLTransferManager::processTransferInfo replay FINISHED for " << transfer_id << LL_ENDL; + LL_DEBUGS() << "LLTransferManager::processTransferInfo replay FINISHED for " << transfer_id << LL_ENDL; } // This transfer is done, either via error or not. ttp->completionCallback(status); diff --git a/indra/llmessage/message.cpp b/indra/llmessage/message.cpp index eaf2cefa4e9977c321550678805a4dc827d6ecd8..fef9902a9e3d3f25576bace1cb883d8ff79d0b3d 100755 --- a/indra/llmessage/message.cpp +++ b/indra/llmessage/message.cpp @@ -77,6 +77,8 @@ #include "v3math.h" #include "v4math.h" #include "lltransfertargetvfile.h" +#include "llrand.h" +#include "llmessagelog.h" // Constants //const char* MESSAGE_LOG_FILENAME = "message.log"; @@ -519,7 +521,7 @@ LLCircuitData* LLMessageSystem::findCircuit(const LLHost& host, } // Returns TRUE if a valid, on-circuit message has been received. -BOOL LLMessageSystem::checkMessages( S64 frame_count ) +BOOL LLMessageSystem::checkMessages( S64 frame_count, bool faked_message, U8 fake_buffer[MAX_BUFFER_SIZE], LLHost fake_host, S32 fake_size ) { // Pump BOOL valid_packet = FALSE; @@ -547,16 +549,34 @@ BOOL LLMessageSystem::checkMessages( S64 frame_count ) S32 acks = 0; S32 true_rcv_size = 0; - U8* buffer = mTrueReceiveBuffer; + U8* buffer = mTrueReceiveBuffer.buffer; + + if(!faked_message) + { + + mTrueReceiveSize = mPacketRing.receivePacket(mSocket, (char *)mTrueReceiveBuffer.buffer); + + receive_size = mTrueReceiveSize; + mLastSender = mPacketRing.getLastSender(); + mLastReceivingIF = mPacketRing.getLastReceivingInterface(); + } else { + buffer = fake_buffer; //true my ass. + mTrueReceiveSize = fake_size; + receive_size = mTrueReceiveSize; + mLastSender = fake_host; + mLastReceivingIF = mPacketRing.getLastReceivingInterface(); //don't really give two tits about the interface, just leave it + } - mTrueReceiveSize = mPacketRing.receivePacket(mSocket, (char *)mTrueReceiveBuffer); // If you want to dump all received packets into SecondLife.log, uncomment this //dumpPacketToLog(); - receive_size = mTrueReceiveSize; - mLastSender = mPacketRing.getLastSender(); - mLastReceivingIF = mPacketRing.getLastReceivingInterface(); - + if(mTrueReceiveSize && receive_size > (S32) LL_MINIMUM_VALID_PACKET_SIZE && !faked_message) + { +#define LOCALHOST_ADDR 16777343 + LLMessageLog::log(mLastSender, LLHost(LOCALHOST_ADDR, mPort), buffer, mTrueReceiveSize); +#undef LOCALHOST_ADDR + } + if (receive_size < (S32) LL_MINIMUM_VALID_PACKET_SIZE) { // A receive size of zero is OK, that means that there are no more packets available. @@ -575,7 +595,7 @@ BOOL LLMessageSystem::checkMessages( S64 frame_count ) LLCircuitData* cdp; // note if packet acks are appended. - if(buffer[0] & LL_ACK_FLAG) + if(buffer[0] & LL_ACK_FLAG && !faked_message) { acks += buffer[--receive_size]; true_rcv_size = receive_size; @@ -607,14 +627,14 @@ BOOL LLMessageSystem::checkMessages( S64 frame_count ) // this message came in on if it's valid, and NULL if the // circuit was bogus. - if(cdp && (acks > 0) && ((S32)(acks * sizeof(TPACKETID)) < (true_rcv_size))) + if(cdp && (acks > 0) && ((S32)(acks * sizeof(TPACKETID)) < (true_rcv_size)) && !faked_message) { TPACKETID packet_id; U32 mem_id=0; for(S32 i = 0; i < acks; ++i) { true_rcv_size -= sizeof(TPACKETID); - memcpy(&mem_id, &mTrueReceiveBuffer[true_rcv_size], /* Flawfinder: ignore*/ + memcpy(&mem_id, &buffer[true_rcv_size], /* Flawfinder: ignore*/ sizeof(TPACKETID)); packet_id = ntohl(mem_id); //LL_INFOS("Messaging") << "got ack: " << packet_id << LL_ENDL; @@ -2969,18 +2989,32 @@ void LLMessageSystem::addTemplate(LLMessageTemplate *templatep) mMessageNumbers[templatep->mMessageNumber] = templatep; } - -void LLMessageSystem::setHandlerFuncFast(const char *name, void (*handler_func)(LLMessageSystem *msgsystem, void **user_data), void **user_data) +boost::signals2::connection LLMessageSystem::setHandlerFuncFast(const char *name, void (*handler_func)(LLMessageSystem *msgsystem, void **user_data), void **user_data) { LLMessageTemplate* msgtemplate = get_ptr_in_map(mMessageTemplates, name); if (msgtemplate) { - msgtemplate->setHandlerFunc(handler_func, user_data); + return msgtemplate->setHandlerFunc(handler_func, user_data); } else { LL_ERRS("Messaging") << name << " is not a known message name!" << LL_ENDL; } + return boost::signals2::connection();//dummy connection. we should never get here. +} + +boost::signals2::connection LLMessageSystem::addHandlerFuncFast(const char *name, boost::function<void (LLMessageSystem *msgsystem)> handler_slot) +{ + LLMessageTemplate* msgtemplate = get_ptr_in_map(mMessageTemplates, name); + if(msgtemplate) + { + return msgtemplate->addHandlerFunc(handler_slot); + } + else + { + LL_ERRS("Messaging") << name << " is not a known message name!" << LL_ENDL; + } + return boost::signals2::connection();//dummy connection. we should never get here. } bool LLMessageSystem::callHandler(const char *name, @@ -3383,7 +3417,7 @@ void LLMessageSystem::dumpPacketToLog() { S32 offset = cur_line_pos * 3; snprintf(line_buffer + offset, sizeof(line_buffer) - offset, - "%02x ", mTrueReceiveBuffer[i]); /* Flawfinder: ignore */ + "%02x ", mTrueReceiveBuffer.buffer[i]); /* Flawfinder: ignore */ cur_line_pos++; if (cur_line_pos >= 16) { @@ -4050,11 +4084,17 @@ void LLMessageSystem::banUdpMessage(const std::string& name) LL_WARNS() << "Attempted to ban an unknown message: " << name << "." << LL_ENDL; } } + const LLHost& LLMessageSystem::getSender() const { return mLastSender; } +LLCircuit* LLMessageSystem::getCircuit() +{ + return &mCircuitInfo; +} + LLHTTPRegistration<LLHTTPNodeAdapter<LLTrustedMessageService> > gHTTPRegistrationTrustedMessageWildcard("/trusted-message/<message-name>"); diff --git a/indra/llmessage/message.h b/indra/llmessage/message.h index 40ac1d2366f5a359cf9e26860561d480f1c7dbbc..9aedfb16b3963d4c2a01726b1a6da8f8ee8130a2 100755 --- a/indra/llmessage/message.h +++ b/indra/llmessage/message.h @@ -57,6 +57,8 @@ #include "llstoredmessage.h" +#include <boost/signals2/connection.hpp> + const U32 MESSAGE_MAX_STRINGS_LENGTH = 64; const U32 MESSAGE_NUMBER_OF_HASH_BUCKETS = 8192; @@ -215,11 +217,9 @@ class LLMessageSystem : public LLMessageSenderInterface typedef std::map<const char *, LLMessageTemplate*> message_template_name_map_t; typedef std::map<U32, LLMessageTemplate*> message_template_number_map_t; -private: message_template_name_map_t mMessageTemplates; message_template_number_map_t mMessageNumbers; -public: S32 mSystemVersionMajor; S32 mSystemVersionMinor; S32 mSystemVersionPatch; @@ -292,10 +292,16 @@ public: // methods for building, sending, receiving, and handling messages - void setHandlerFuncFast(const char *name, void (*handler_func)(LLMessageSystem *msgsystem, void **user_data), void **user_data = NULL); - void setHandlerFunc(const char *name, void (*handler_func)(LLMessageSystem *msgsystem, void **user_data), void **user_data = NULL) + boost::signals2::connection setHandlerFuncFast(const char *name, void (*handler_func)(LLMessageSystem *msgsystem, void **user_data), void **user_data = NULL); + boost::signals2::connection setHandlerFunc(const char *name, void (*handler_func)(LLMessageSystem *msgsystem, void **user_data), void **user_data = NULL) { - setHandlerFuncFast(LLMessageStringTable::getInstance()->getString(name), handler_func, user_data); + return setHandlerFuncFast(LLMessageStringTable::getInstance()->getString(name), handler_func, user_data); + } + + boost::signals2::connection addHandlerFuncFast(const char *name, boost::function<void (LLMessageSystem *msgsystem)> handler_slot); + boost::signals2::connection addHandlerFunc(const char *name, boost::function<void (LLMessageSystem *msgsystem)> handler_slot) + { + return addHandlerFuncFast(LLMessageStringTable::getInstance()->getString(name), handler_slot); } // Set a callback function for a message system exception. @@ -326,7 +332,7 @@ public: bool addCircuitCode(U32 code, const LLUUID& session_id); BOOL poll(F32 seconds); // Number of seconds that we want to block waiting for data, returns if data was received - BOOL checkMessages( S64 frame_count = 0 ); + BOOL checkMessages( S64 frame_count = 0, bool faked_message = false, U8 fake_buffer[MAX_BUFFER_SIZE] = NULL, LLHost fake_host = LLHost(), S32 fake_size = 0 ); void processAcks(F32 collect_time = 0.f); BOOL isMessageFast(const char *msg); @@ -571,6 +577,7 @@ public: void getCircuitInfo(LLSD& info) const; U32 getOurCircuitCode(); + LLCircuit* getCircuit(); void enableCircuit(const LLHost &host, BOOL trusted); void disableCircuit(const LLHost &host); @@ -767,7 +774,18 @@ private: LLMessagePollInfo *mPollInfop; U8 mEncodedRecvBuffer[MAX_BUFFER_SIZE]; - U8 mTrueReceiveBuffer[MAX_BUFFER_SIZE]; + +// Push current alignment to stack and set alignment to 1 byte boundary +#pragma pack(push,1) + + struct ReceiveBuffer_t + { + proxywrap_t header; + U8 buffer[MAX_BUFFER_SIZE]; + } mTrueReceiveBuffer; + +#pragma pack(pop) /* restore original alignment from stack */ + S32 mTrueReceiveSize; // Must be valid during decode @@ -807,6 +825,7 @@ private: S32 mIncomingCompressedSize; // original size of compressed msg (0 if uncomp.) TPACKETID mCurrentRecvPacketID; // packet ID of current receive packet (for reporting) +public: LLMessageBuilder* mMessageBuilder; LLTemplateMessageBuilder* mTemplateMessageBuilder; LLSDMessageBuilder* mLLSDMessageBuilder; @@ -814,6 +833,7 @@ private: LLTemplateMessageReader* mTemplateMessageReader; LLSDMessageReader* mLLSDMessageReader; +private: friend class LLMessageHandlerBridge; bool callHandler(const char *name, bool trustedSource, diff --git a/indra/llmessage/tests/lltemplatemessagedispatcher_test.cpp b/indra/llmessage/tests/lltemplatemessagedispatcher_test.cpp index 3b04530c1ac9e5bd98e596a4e9360c144a9f6d09..52be82247064e44cbe873c68ded956457047454a 100755 --- a/indra/llmessage/tests/lltemplatemessagedispatcher_test.cpp +++ b/indra/llmessage/tests/lltemplatemessagedispatcher_test.cpp @@ -54,7 +54,7 @@ BOOL LLTemplateMessageReader::readMessage(const U8* data,class LLHost const &) } BOOL gValidateMessage = FALSE; -BOOL LLTemplateMessageReader::validateMessage(const U8*, S32 buffer_size, LLHost const &sender, bool trusted) +BOOL LLTemplateMessageReader::validateMessage(const U8*, S32 buffer_size, LLHost const &sender, bool trusted, BOOL faked) { return gValidateMessage; } diff --git a/indra/llprimitive/llvolumemessage.cpp b/indra/llprimitive/llvolumemessage.cpp index 8d47a7147f87e21a92ea57390003bc03c41ae7ea..5e0b7c366c9ea47604b2b4d55e2c78352a214bb6 100755 --- a/indra/llprimitive/llvolumemessage.cpp +++ b/indra/llprimitive/llvolumemessage.cpp @@ -478,7 +478,7 @@ bool LLVolumeMessage::constrainVolumeParams(LLVolumeParams& params) bad |= params.setRevolutions(params.getPathParams().getRevolutions()) ? 0 : 0x200; bad |= params.setRadiusOffset(params.getPathParams().getRadiusOffset()) ? 0 : 0x400; bad |= params.setSkew(params.getPathParams().getSkew()) ? 0 : 0x800; - if(bad) + if(bad && false) { LL_WARNS() << "LLVolumeMessage::constrainVolumeParams() - " << "forced to constrain incoming volume params: " diff --git a/indra/llxml/CMakeLists.txt b/indra/llxml/CMakeLists.txt index fe4b958b2433ff4bcd9d61a5a7074566e7295a05..7f7d242ee1debd22719e24ea7b8a64e4cbf6b52a 100755 --- a/indra/llxml/CMakeLists.txt +++ b/indra/llxml/CMakeLists.txt @@ -24,6 +24,7 @@ set(llxml_SOURCE_FILES llxmlnode.cpp llxmlparser.cpp llxmltree.cpp + pugixml.cpp ) set(llxml_HEADER_FILES @@ -34,6 +35,8 @@ set(llxml_HEADER_FILES llxmlnode.h llxmlparser.h llxmltree.h + pugixml.hpp + pugiconfig.hpp ) set_source_files_properties(${llxml_HEADER_FILES} diff --git a/indra/llxml/pugiconfig.hpp b/indra/llxml/pugiconfig.hpp new file mode 100644 index 0000000000000000000000000000000000000000..56f1d224bdcd8a3a9ce882366e176e83548b156f --- /dev/null +++ b/indra/llxml/pugiconfig.hpp @@ -0,0 +1,72 @@ +/** + * pugixml parser - version 1.4 + * -------------------------------------------------------- + * Copyright (C) 2006-2014, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) + * Report bugs and download new versions at http://pugixml.org/ + * + * This library is distributed under the MIT License. See notice at the end + * of this file. + * + * This work is based on the pugxml parser, which is: + * Copyright (C) 2003, by Kristen Wegner (kristen@tima.net) + */ + +#ifndef HEADER_PUGICONFIG_HPP +#define HEADER_PUGICONFIG_HPP + +// Uncomment this to enable wchar_t mode +// #define PUGIXML_WCHAR_MODE + +// Uncomment this to disable XPath +// #define PUGIXML_NO_XPATH + +// Uncomment this to disable STL +// #define PUGIXML_NO_STL + +// Uncomment this to disable exceptions +// #define PUGIXML_NO_EXCEPTIONS + +// Set this to control attributes for public classes/functions, i.e.: +// #define PUGIXML_API __declspec(dllexport) // to export all public symbols from DLL +// #define PUGIXML_CLASS __declspec(dllimport) // to import all classes from DLL +// #define PUGIXML_FUNCTION __fastcall // to set calling conventions to all public functions to fastcall +// In absence of PUGIXML_CLASS/PUGIXML_FUNCTION definitions PUGIXML_API is used instead + +// Tune these constants to adjust memory-related behavior +// #define PUGIXML_MEMORY_PAGE_SIZE 32768 +// #define PUGIXML_MEMORY_OUTPUT_STACK 10240 +// #define PUGIXML_MEMORY_XPATH_PAGE_SIZE 4096 + +// Uncomment this to switch to header-only version +// #define PUGIXML_HEADER_ONLY +// #include "pugixml.cpp" + +// Uncomment this to enable long long support +// #define PUGIXML_HAS_LONG_LONG + +#endif + +/** + * Copyright (c) 2006-2014 Arseny Kapoulkine + * + * Permission is hereby granted, 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. + */ diff --git a/indra/llxml/pugixml.cpp b/indra/llxml/pugixml.cpp new file mode 100644 index 0000000000000000000000000000000000000000..28f1381f091469a3ba969f4da0d2937729ffefe9 --- /dev/null +++ b/indra/llxml/pugixml.cpp @@ -0,0 +1,10775 @@ +/** + * pugixml parser - version 1.4 + * -------------------------------------------------------- + * Copyright (C) 2006-2014, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) + * Report bugs and download new versions at http://pugixml.org/ + * + * This library is distributed under the MIT License. See notice at the end + * of this file. + * + * This work is based on the pugxml parser, which is: + * Copyright (C) 2003, by Kristen Wegner (kristen@tima.net) + */ + +#ifndef SOURCE_PUGIXML_CPP +#define SOURCE_PUGIXML_CPP + +#include "pugixml.hpp" + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <assert.h> + +#ifdef PUGIXML_WCHAR_MODE +# include <wchar.h> +#endif + +#ifndef PUGIXML_NO_XPATH +# include <math.h> +# include <float.h> +# ifdef PUGIXML_NO_EXCEPTIONS +# include <setjmp.h> +# endif +#endif + +#ifndef PUGIXML_NO_STL +# include <istream> +# include <ostream> +# include <string> +#endif + +// For placement new +#include <new> + +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable: 4127) // conditional expression is constant +# pragma warning(disable: 4324) // structure was padded due to __declspec(align()) +# pragma warning(disable: 4611) // interaction between '_setjmp' and C++ object destruction is non-portable +# pragma warning(disable: 4702) // unreachable code +# pragma warning(disable: 4996) // this function or variable may be unsafe +# pragma warning(disable: 4793) // function compiled as native: presence of '_setjmp' makes a function unmanaged +#endif + +#ifdef __INTEL_COMPILER +# pragma warning(disable: 177) // function was declared but never referenced +# pragma warning(disable: 279) // controlling expression is constant +# pragma warning(disable: 1478 1786) // function was declared "deprecated" +# pragma warning(disable: 1684) // conversion from pointer to same-sized integral type +#endif + +#if defined(__BORLANDC__) && defined(PUGIXML_HEADER_ONLY) +# pragma warn -8080 // symbol is declared but never used; disabling this inside push/pop bracket does not make the warning go away +#endif + +#ifdef __BORLANDC__ +# pragma option push +# pragma warn -8008 // condition is always false +# pragma warn -8066 // unreachable code +#endif + +#ifdef __SNC__ +// Using diag_push/diag_pop does not disable the warnings inside templates due to a compiler bug +# pragma diag_suppress=178 // function was declared but never referenced +# pragma diag_suppress=237 // controlling expression is constant +#endif + +// Inlining controls +#if defined(_MSC_VER) && _MSC_VER >= 1300 +# define PUGI__NO_INLINE __declspec(noinline) +#elif defined(__GNUC__) +# define PUGI__NO_INLINE __attribute__((noinline)) +#else +# define PUGI__NO_INLINE +#endif + +// Simple static assertion +#define PUGI__STATIC_ASSERT(cond) { static const char condition_failed[(cond) ? 1 : -1] = {0}; (void)condition_failed[0]; } + +// Digital Mars C++ bug workaround for passing char loaded from memory via stack +#ifdef __DMC__ +# define PUGI__DMC_VOLATILE volatile +#else +# define PUGI__DMC_VOLATILE +#endif + +// Borland C++ bug workaround for not defining ::memcpy depending on header include order (can't always use std::memcpy because some compilers don't have it at all) +#if defined(__BORLANDC__) && !defined(__MEM_H_USING_LIST) +using std::memcpy; +using std::memmove; +#endif + +// In some environments MSVC is a compiler but the CRT lacks certain MSVC-specific features +#if defined(_MSC_VER) && !defined(__S3E__) +# define PUGI__MSVC_CRT_VERSION _MSC_VER +#endif + +#ifdef PUGIXML_HEADER_ONLY +# define PUGI__NS_BEGIN namespace pugi { namespace impl { +# define PUGI__NS_END } } +# define PUGI__FN inline +# define PUGI__FN_NO_INLINE inline +#else +# if defined(_MSC_VER) && _MSC_VER < 1300 // MSVC6 seems to have an amusing bug with anonymous namespaces inside namespaces +# define PUGI__NS_BEGIN namespace pugi { namespace impl { +# define PUGI__NS_END } } +# else +# define PUGI__NS_BEGIN namespace pugi { namespace impl { namespace { +# define PUGI__NS_END } } } +# endif +# define PUGI__FN +# define PUGI__FN_NO_INLINE PUGI__NO_INLINE +#endif + +// uintptr_t +#if !defined(_MSC_VER) || _MSC_VER >= 1600 +# include <stdint.h> +#else +# ifndef _UINTPTR_T_DEFINED +// No native uintptr_t in MSVC6 and in some WinCE versions +typedef size_t uintptr_t; +#define _UINTPTR_T_DEFINED +# endif +PUGI__NS_BEGIN + typedef unsigned __int8 uint8_t; + typedef unsigned __int16 uint16_t; + typedef unsigned __int32 uint32_t; +PUGI__NS_END +#endif + +// Memory allocation +PUGI__NS_BEGIN + PUGI__FN void* default_allocate(size_t size) + { + return malloc(size); + } + + PUGI__FN void default_deallocate(void* ptr) + { + free(ptr); + } + + template <typename T> + struct xml_memory_management_function_storage + { + static allocation_function allocate; + static deallocation_function deallocate; + }; + + template <typename T> allocation_function xml_memory_management_function_storage<T>::allocate = default_allocate; + template <typename T> deallocation_function xml_memory_management_function_storage<T>::deallocate = default_deallocate; + + typedef xml_memory_management_function_storage<int> xml_memory; +PUGI__NS_END + +// String utilities +PUGI__NS_BEGIN + // Get string length + PUGI__FN size_t strlength(const char_t* s) + { + assert(s); + + #ifdef PUGIXML_WCHAR_MODE + return wcslen(s); + #else + return strlen(s); + #endif + } + + // Compare two strings + PUGI__FN bool strequal(const char_t* src, const char_t* dst) + { + assert(src && dst); + + #ifdef PUGIXML_WCHAR_MODE + return wcscmp(src, dst) == 0; + #else + return strcmp(src, dst) == 0; + #endif + } + + // Compare lhs with [rhs_begin, rhs_end) + PUGI__FN bool strequalrange(const char_t* lhs, const char_t* rhs, size_t count) + { + for (size_t i = 0; i < count; ++i) + if (lhs[i] != rhs[i]) + return false; + + return lhs[count] == 0; + } + + // Get length of wide string, even if CRT lacks wide character support + PUGI__FN size_t strlength_wide(const wchar_t* s) + { + assert(s); + + #ifdef PUGIXML_WCHAR_MODE + return wcslen(s); + #else + const wchar_t* end = s; + while (*end) end++; + return static_cast<size_t>(end - s); + #endif + } + +#ifdef PUGIXML_WCHAR_MODE + // Convert string to wide string, assuming all symbols are ASCII + PUGI__FN void widen_ascii(wchar_t* dest, const char* source) + { + for (const char* i = source; *i; ++i) *dest++ = *i; + *dest = 0; + } +#endif +PUGI__NS_END + +#if !defined(PUGIXML_NO_STL) || !defined(PUGIXML_NO_XPATH) +// auto_ptr-like buffer holder for exception recovery +PUGI__NS_BEGIN + struct buffer_holder + { + void* data; + void (*deleter)(void*); + + buffer_holder(void* data_, void (*deleter_)(void*)): data(data_), deleter(deleter_) + { + } + + ~buffer_holder() + { + if (data) deleter(data); + } + + void* release() + { + void* result = data; + data = 0; + return result; + } + }; +PUGI__NS_END +#endif + +PUGI__NS_BEGIN + static const size_t xml_memory_page_size = + #ifdef PUGIXML_MEMORY_PAGE_SIZE + PUGIXML_MEMORY_PAGE_SIZE + #else + 32768 + #endif + ; + + static const uintptr_t xml_memory_page_alignment = 32; + static const uintptr_t xml_memory_page_pointer_mask = ~(xml_memory_page_alignment - 1); + static const uintptr_t xml_memory_page_name_allocated_mask = 16; + static const uintptr_t xml_memory_page_value_allocated_mask = 8; + static const uintptr_t xml_memory_page_type_mask = 7; + + struct xml_allocator; + + struct xml_memory_page + { + static xml_memory_page* construct(void* memory) + { + if (!memory) return 0; //$ redundant, left for performance + + xml_memory_page* result = static_cast<xml_memory_page*>(memory); + + result->allocator = 0; + result->memory = 0; + result->prev = 0; + result->next = 0; + result->busy_size = 0; + result->freed_size = 0; + + return result; + } + + xml_allocator* allocator; + + void* memory; + + xml_memory_page* prev; + xml_memory_page* next; + + size_t busy_size; + size_t freed_size; + + char data[1]; + }; + + struct xml_memory_string_header + { + uint16_t page_offset; // offset from page->data + uint16_t full_size; // 0 if string occupies whole page + }; + + struct xml_allocator + { + xml_allocator(xml_memory_page* root): _root(root), _busy_size(root->busy_size) + { + } + + xml_memory_page* allocate_page(size_t data_size) + { + size_t size = offsetof(xml_memory_page, data) + data_size; + + // allocate block with some alignment, leaving memory for worst-case padding + void* memory = xml_memory::allocate(size + xml_memory_page_alignment); + if (!memory) return 0; + + // align upwards to page boundary + void* page_memory = reinterpret_cast<void*>((reinterpret_cast<uintptr_t>(memory) + (xml_memory_page_alignment - 1)) & ~(xml_memory_page_alignment - 1)); + + // prepare page structure + xml_memory_page* page = xml_memory_page::construct(page_memory); + assert(page); + + page->memory = memory; + page->allocator = _root->allocator; + + return page; + } + + static void deallocate_page(xml_memory_page* page) + { + xml_memory::deallocate(page->memory); + } + + void* allocate_memory_oob(size_t size, xml_memory_page*& out_page); + + void* allocate_memory(size_t size, xml_memory_page*& out_page) + { + if (_busy_size + size > xml_memory_page_size) return allocate_memory_oob(size, out_page); + + void* buf = _root->data + _busy_size; + + _busy_size += size; + + out_page = _root; + + return buf; + } + + void deallocate_memory(void* ptr, size_t size, xml_memory_page* page) + { + if (page == _root) page->busy_size = _busy_size; + + assert(ptr >= page->data && ptr < page->data + page->busy_size); + (void)!ptr; + + page->freed_size += size; + assert(page->freed_size <= page->busy_size); + + if (page->freed_size == page->busy_size) + { + if (page->next == 0) + { + assert(_root == page); + + // top page freed, just reset sizes + page->busy_size = page->freed_size = 0; + _busy_size = 0; + } + else + { + assert(_root != page); + assert(page->prev); + + // remove from the list + page->prev->next = page->next; + page->next->prev = page->prev; + + // deallocate + deallocate_page(page); + } + } + } + + char_t* allocate_string(size_t length) + { + // allocate memory for string and header block + size_t size = sizeof(xml_memory_string_header) + length * sizeof(char_t); + + // round size up to pointer alignment boundary + size_t full_size = (size + (sizeof(void*) - 1)) & ~(sizeof(void*) - 1); + + xml_memory_page* page; + xml_memory_string_header* header = static_cast<xml_memory_string_header*>(allocate_memory(full_size, page)); + + if (!header) return 0; + + // setup header + ptrdiff_t page_offset = reinterpret_cast<char*>(header) - page->data; + + assert(page_offset >= 0 && page_offset < (1 << 16)); + header->page_offset = static_cast<uint16_t>(page_offset); + + // full_size == 0 for large strings that occupy the whole page + assert(full_size < (1 << 16) || (page->busy_size == full_size && page_offset == 0)); + header->full_size = static_cast<uint16_t>(full_size < (1 << 16) ? full_size : 0); + + // round-trip through void* to avoid 'cast increases required alignment of target type' warning + // header is guaranteed a pointer-sized alignment, which should be enough for char_t + return static_cast<char_t*>(static_cast<void*>(header + 1)); + } + + void deallocate_string(char_t* string) + { + // this function casts pointers through void* to avoid 'cast increases required alignment of target type' warnings + // we're guaranteed the proper (pointer-sized) alignment on the input string if it was allocated via allocate_string + + // get header + xml_memory_string_header* header = static_cast<xml_memory_string_header*>(static_cast<void*>(string)) - 1; + + // deallocate + size_t page_offset = offsetof(xml_memory_page, data) + header->page_offset; + xml_memory_page* page = reinterpret_cast<xml_memory_page*>(static_cast<void*>(reinterpret_cast<char*>(header) - page_offset)); + + // if full_size == 0 then this string occupies the whole page + size_t full_size = header->full_size == 0 ? page->busy_size : header->full_size; + + deallocate_memory(header, full_size, page); + } + + xml_memory_page* _root; + size_t _busy_size; + }; + + PUGI__FN_NO_INLINE void* xml_allocator::allocate_memory_oob(size_t size, xml_memory_page*& out_page) + { + const size_t large_allocation_threshold = xml_memory_page_size / 4; + + xml_memory_page* page = allocate_page(size <= large_allocation_threshold ? xml_memory_page_size : size); + out_page = page; + + if (!page) return 0; + + if (size <= large_allocation_threshold) + { + _root->busy_size = _busy_size; + + // insert page at the end of linked list + page->prev = _root; + _root->next = page; + _root = page; + + _busy_size = size; + } + else + { + // insert page before the end of linked list, so that it is deleted as soon as possible + // the last page is not deleted even if it's empty (see deallocate_memory) + assert(_root->prev); + + page->prev = _root->prev; + page->next = _root; + + _root->prev->next = page; + _root->prev = page; + } + + // allocate inside page + page->busy_size = size; + + return page->data; + } +PUGI__NS_END + +namespace pugi +{ + /// A 'name=value' XML attribute structure. + struct xml_attribute_struct + { + /// Default ctor + xml_attribute_struct(impl::xml_memory_page* page): header(reinterpret_cast<uintptr_t>(page)), name(0), value(0), prev_attribute_c(0), next_attribute(0) + { + } + + uintptr_t header; + + char_t* name; ///< Pointer to attribute name. + char_t* value; ///< Pointer to attribute value. + + xml_attribute_struct* prev_attribute_c; ///< Previous attribute (cyclic list) + xml_attribute_struct* next_attribute; ///< Next attribute + }; + + /// An XML document tree node. + struct xml_node_struct + { + /// Default ctor + /// \param type - node type + xml_node_struct(impl::xml_memory_page* page, xml_node_type type): header(reinterpret_cast<uintptr_t>(page) | (type - 1)), parent(0), name(0), value(0), first_child(0), prev_sibling_c(0), next_sibling(0), first_attribute(0) + { + } + + uintptr_t header; + + xml_node_struct* parent; ///< Pointer to parent + + char_t* name; ///< Pointer to element name. + char_t* value; ///< Pointer to any associated string data. + + xml_node_struct* first_child; ///< First child + + xml_node_struct* prev_sibling_c; ///< Left brother (cyclic list) + xml_node_struct* next_sibling; ///< Right brother + + xml_attribute_struct* first_attribute; ///< First attribute + }; +} + +PUGI__NS_BEGIN + struct xml_extra_buffer + { + char_t* buffer; + xml_extra_buffer* next; + }; + + struct xml_document_struct: public xml_node_struct, public xml_allocator + { + xml_document_struct(xml_memory_page* page): xml_node_struct(page, node_document), xml_allocator(page), buffer(0), extra_buffers(0) + { + } + + const char_t* buffer; + + xml_extra_buffer* extra_buffers; + }; + + inline xml_allocator& get_allocator(const xml_node_struct* node) + { + assert(node); + + return *reinterpret_cast<xml_memory_page*>(node->header & xml_memory_page_pointer_mask)->allocator; + } +PUGI__NS_END + +// Low-level DOM operations +PUGI__NS_BEGIN + inline xml_attribute_struct* allocate_attribute(xml_allocator& alloc) + { + xml_memory_page* page; + void* memory = alloc.allocate_memory(sizeof(xml_attribute_struct), page); + + return new (memory) xml_attribute_struct(page); + } + + inline xml_node_struct* allocate_node(xml_allocator& alloc, xml_node_type type) + { + xml_memory_page* page; + void* memory = alloc.allocate_memory(sizeof(xml_node_struct), page); + + return new (memory) xml_node_struct(page, type); + } + + inline void destroy_attribute(xml_attribute_struct* a, xml_allocator& alloc) + { + uintptr_t header = a->header; + + if (header & impl::xml_memory_page_name_allocated_mask) alloc.deallocate_string(a->name); + if (header & impl::xml_memory_page_value_allocated_mask) alloc.deallocate_string(a->value); + + alloc.deallocate_memory(a, sizeof(xml_attribute_struct), reinterpret_cast<xml_memory_page*>(header & xml_memory_page_pointer_mask)); + } + + inline void destroy_node(xml_node_struct* n, xml_allocator& alloc) + { + uintptr_t header = n->header; + + if (header & impl::xml_memory_page_name_allocated_mask) alloc.deallocate_string(n->name); + if (header & impl::xml_memory_page_value_allocated_mask) alloc.deallocate_string(n->value); + + for (xml_attribute_struct* attr = n->first_attribute; attr; ) + { + xml_attribute_struct* next = attr->next_attribute; + + destroy_attribute(attr, alloc); + + attr = next; + } + + for (xml_node_struct* child = n->first_child; child; ) + { + xml_node_struct* next = child->next_sibling; + + destroy_node(child, alloc); + + child = next; + } + + alloc.deallocate_memory(n, sizeof(xml_node_struct), reinterpret_cast<xml_memory_page*>(header & xml_memory_page_pointer_mask)); + } + + inline void append_node(xml_node_struct* child, xml_node_struct* node) + { + child->parent = node; + + xml_node_struct* head = node->first_child; + + if (head) + { + xml_node_struct* tail = head->prev_sibling_c; + + tail->next_sibling = child; + child->prev_sibling_c = tail; + head->prev_sibling_c = child; + } + else + { + node->first_child = child; + child->prev_sibling_c = child; + } + + child->next_sibling = 0; + } + + inline void prepend_node(xml_node_struct* child, xml_node_struct* node) + { + child->parent = node; + + xml_node_struct* head = node->first_child; + + if (head) + { + child->prev_sibling_c = head->prev_sibling_c; + head->prev_sibling_c = child; + } + else + child->prev_sibling_c = child; + + child->next_sibling = head; + node->first_child = child; + } + + inline void insert_node_after(xml_node_struct* child, xml_node_struct* node) + { + xml_node_struct* parent = node->parent; + + child->parent = parent; + + if (node->next_sibling) + node->next_sibling->prev_sibling_c = child; + else + parent->first_child->prev_sibling_c = child; + + child->next_sibling = node->next_sibling; + child->prev_sibling_c = node; + + node->next_sibling = child; + } + + inline void insert_node_before(xml_node_struct* child, xml_node_struct* node) + { + xml_node_struct* parent = node->parent; + + child->parent = parent; + + if (node->prev_sibling_c->next_sibling) + node->prev_sibling_c->next_sibling = child; + else + parent->first_child = child; + + child->prev_sibling_c = node->prev_sibling_c; + child->next_sibling = node; + + node->prev_sibling_c = child; + } + + inline void remove_node(xml_node_struct* node) + { + xml_node_struct* parent = node->parent; + + if (node->next_sibling) + node->next_sibling->prev_sibling_c = node->prev_sibling_c; + else if (parent->first_child) + parent->first_child->prev_sibling_c = node->prev_sibling_c; + + if (node->prev_sibling_c->next_sibling) + node->prev_sibling_c->next_sibling = node->next_sibling; + else + parent->first_child = node->next_sibling; + } + + PUGI__FN_NO_INLINE xml_node_struct* append_new_node(xml_node_struct* node, xml_allocator& alloc, xml_node_type type = node_element) + { + xml_node_struct* child = allocate_node(alloc, type); + if (!child) return 0; + + append_node(child, node); + + return child; + } + + PUGI__FN_NO_INLINE xml_attribute_struct* append_new_attribute(xml_node_struct* node, xml_allocator& alloc) + { + xml_attribute_struct* a = allocate_attribute(alloc); + if (!a) return 0; + + xml_attribute_struct* first_attribute = node->first_attribute; + + if (first_attribute) + { + xml_attribute_struct* last_attribute = first_attribute->prev_attribute_c; + + last_attribute->next_attribute = a; + a->prev_attribute_c = last_attribute; + first_attribute->prev_attribute_c = a; + } + else + { + node->first_attribute = a; + a->prev_attribute_c = a; + } + + return a; + } +PUGI__NS_END + +// Helper classes for code generation +PUGI__NS_BEGIN + struct opt_false + { + enum { value = 0 }; + }; + + struct opt_true + { + enum { value = 1 }; + }; +PUGI__NS_END + +// Unicode utilities +PUGI__NS_BEGIN + inline uint16_t endian_swap(uint16_t value) + { + return static_cast<uint16_t>(((value & 0xff) << 8) | (value >> 8)); + } + + inline uint32_t endian_swap(uint32_t value) + { + return ((value & 0xff) << 24) | ((value & 0xff00) << 8) | ((value & 0xff0000) >> 8) | (value >> 24); + } + + struct utf8_counter + { + typedef size_t value_type; + + static value_type low(value_type result, uint32_t ch) + { + // U+0000..U+007F + if (ch < 0x80) return result + 1; + // U+0080..U+07FF + else if (ch < 0x800) return result + 2; + // U+0800..U+FFFF + else return result + 3; + } + + static value_type high(value_type result, uint32_t) + { + // U+10000..U+10FFFF + return result + 4; + } + }; + + struct utf8_writer + { + typedef uint8_t* value_type; + + static value_type low(value_type result, uint32_t ch) + { + // U+0000..U+007F + if (ch < 0x80) + { + *result = static_cast<uint8_t>(ch); + return result + 1; + } + // U+0080..U+07FF + else if (ch < 0x800) + { + result[0] = static_cast<uint8_t>(0xC0 | (ch >> 6)); + result[1] = static_cast<uint8_t>(0x80 | (ch & 0x3F)); + return result + 2; + } + // U+0800..U+FFFF + else + { + result[0] = static_cast<uint8_t>(0xE0 | (ch >> 12)); + result[1] = static_cast<uint8_t>(0x80 | ((ch >> 6) & 0x3F)); + result[2] = static_cast<uint8_t>(0x80 | (ch & 0x3F)); + return result + 3; + } + } + + static value_type high(value_type result, uint32_t ch) + { + // U+10000..U+10FFFF + result[0] = static_cast<uint8_t>(0xF0 | (ch >> 18)); + result[1] = static_cast<uint8_t>(0x80 | ((ch >> 12) & 0x3F)); + result[2] = static_cast<uint8_t>(0x80 | ((ch >> 6) & 0x3F)); + result[3] = static_cast<uint8_t>(0x80 | (ch & 0x3F)); + return result + 4; + } + + static value_type any(value_type result, uint32_t ch) + { + return (ch < 0x10000) ? low(result, ch) : high(result, ch); + } + }; + + struct utf16_counter + { + typedef size_t value_type; + + static value_type low(value_type result, uint32_t) + { + return result + 1; + } + + static value_type high(value_type result, uint32_t) + { + return result + 2; + } + }; + + struct utf16_writer + { + typedef uint16_t* value_type; + + static value_type low(value_type result, uint32_t ch) + { + *result = static_cast<uint16_t>(ch); + + return result + 1; + } + + static value_type high(value_type result, uint32_t ch) + { + uint32_t msh = static_cast<uint32_t>(ch - 0x10000) >> 10; + uint32_t lsh = static_cast<uint32_t>(ch - 0x10000) & 0x3ff; + + result[0] = static_cast<uint16_t>(0xD800 + msh); + result[1] = static_cast<uint16_t>(0xDC00 + lsh); + + return result + 2; + } + + static value_type any(value_type result, uint32_t ch) + { + return (ch < 0x10000) ? low(result, ch) : high(result, ch); + } + }; + + struct utf32_counter + { + typedef size_t value_type; + + static value_type low(value_type result, uint32_t) + { + return result + 1; + } + + static value_type high(value_type result, uint32_t) + { + return result + 1; + } + }; + + struct utf32_writer + { + typedef uint32_t* value_type; + + static value_type low(value_type result, uint32_t ch) + { + *result = ch; + + return result + 1; + } + + static value_type high(value_type result, uint32_t ch) + { + *result = ch; + + return result + 1; + } + + static value_type any(value_type result, uint32_t ch) + { + *result = ch; + + return result + 1; + } + }; + + struct latin1_writer + { + typedef uint8_t* value_type; + + static value_type low(value_type result, uint32_t ch) + { + *result = static_cast<uint8_t>(ch > 255 ? '?' : ch); + + return result + 1; + } + + static value_type high(value_type result, uint32_t ch) + { + (void)ch; + + *result = '?'; + + return result + 1; + } + }; + + template <size_t size> struct wchar_selector; + + template <> struct wchar_selector<2> + { + typedef uint16_t type; + typedef utf16_counter counter; + typedef utf16_writer writer; + }; + + template <> struct wchar_selector<4> + { + typedef uint32_t type; + typedef utf32_counter counter; + typedef utf32_writer writer; + }; + + typedef wchar_selector<sizeof(wchar_t)>::counter wchar_counter; + typedef wchar_selector<sizeof(wchar_t)>::writer wchar_writer; + + template <typename Traits, typename opt_swap = opt_false> struct utf_decoder + { + static inline typename Traits::value_type decode_utf8_block(const uint8_t* data, size_t size, typename Traits::value_type result) + { + const uint8_t utf8_byte_mask = 0x3f; + + while (size) + { + uint8_t lead = *data; + + // 0xxxxxxx -> U+0000..U+007F + if (lead < 0x80) + { + result = Traits::low(result, lead); + data += 1; + size -= 1; + + // process aligned single-byte (ascii) blocks + if ((reinterpret_cast<uintptr_t>(data) & 3) == 0) + { + // round-trip through void* to silence 'cast increases required alignment of target type' warnings + while (size >= 4 && (*static_cast<const uint32_t*>(static_cast<const void*>(data)) & 0x80808080) == 0) + { + result = Traits::low(result, data[0]); + result = Traits::low(result, data[1]); + result = Traits::low(result, data[2]); + result = Traits::low(result, data[3]); + data += 4; + size -= 4; + } + } + } + // 110xxxxx -> U+0080..U+07FF + else if (static_cast<unsigned int>(lead - 0xC0) < 0x20 && size >= 2 && (data[1] & 0xc0) == 0x80) + { + result = Traits::low(result, ((lead & ~0xC0) << 6) | (data[1] & utf8_byte_mask)); + data += 2; + size -= 2; + } + // 1110xxxx -> U+0800-U+FFFF + else if (static_cast<unsigned int>(lead - 0xE0) < 0x10 && size >= 3 && (data[1] & 0xc0) == 0x80 && (data[2] & 0xc0) == 0x80) + { + result = Traits::low(result, ((lead & ~0xE0) << 12) | ((data[1] & utf8_byte_mask) << 6) | (data[2] & utf8_byte_mask)); + data += 3; + size -= 3; + } + // 11110xxx -> U+10000..U+10FFFF + else if (static_cast<unsigned int>(lead - 0xF0) < 0x08 && size >= 4 && (data[1] & 0xc0) == 0x80 && (data[2] & 0xc0) == 0x80 && (data[3] & 0xc0) == 0x80) + { + result = Traits::high(result, ((lead & ~0xF0) << 18) | ((data[1] & utf8_byte_mask) << 12) | ((data[2] & utf8_byte_mask) << 6) | (data[3] & utf8_byte_mask)); + data += 4; + size -= 4; + } + // 10xxxxxx or 11111xxx -> invalid + else + { + data += 1; + size -= 1; + } + } + + return result; + } + + static inline typename Traits::value_type decode_utf16_block(const uint16_t* data, size_t size, typename Traits::value_type result) + { + const uint16_t* end = data + size; + + while (data < end) + { + unsigned int lead = opt_swap::value ? endian_swap(*data) : *data; + + // U+0000..U+D7FF + if (lead < 0xD800) + { + result = Traits::low(result, lead); + data += 1; + } + // U+E000..U+FFFF + else if (static_cast<unsigned int>(lead - 0xE000) < 0x2000) + { + result = Traits::low(result, lead); + data += 1; + } + // surrogate pair lead + else if (static_cast<unsigned int>(lead - 0xD800) < 0x400 && data + 1 < end) + { + uint16_t next = opt_swap::value ? endian_swap(data[1]) : data[1]; + + if (static_cast<unsigned int>(next - 0xDC00) < 0x400) + { + result = Traits::high(result, 0x10000 + ((lead & 0x3ff) << 10) + (next & 0x3ff)); + data += 2; + } + else + { + data += 1; + } + } + else + { + data += 1; + } + } + + return result; + } + + static inline typename Traits::value_type decode_utf32_block(const uint32_t* data, size_t size, typename Traits::value_type result) + { + const uint32_t* end = data + size; + + while (data < end) + { + uint32_t lead = opt_swap::value ? endian_swap(*data) : *data; + + // U+0000..U+FFFF + if (lead < 0x10000) + { + result = Traits::low(result, lead); + data += 1; + } + // U+10000..U+10FFFF + else + { + result = Traits::high(result, lead); + data += 1; + } + } + + return result; + } + + static inline typename Traits::value_type decode_latin1_block(const uint8_t* data, size_t size, typename Traits::value_type result) + { + for (size_t i = 0; i < size; ++i) + { + result = Traits::low(result, data[i]); + } + + return result; + } + + static inline typename Traits::value_type decode_wchar_block_impl(const uint16_t* data, size_t size, typename Traits::value_type result) + { + return decode_utf16_block(data, size, result); + } + + static inline typename Traits::value_type decode_wchar_block_impl(const uint32_t* data, size_t size, typename Traits::value_type result) + { + return decode_utf32_block(data, size, result); + } + + static inline typename Traits::value_type decode_wchar_block(const wchar_t* data, size_t size, typename Traits::value_type result) + { + return decode_wchar_block_impl(reinterpret_cast<const wchar_selector<sizeof(wchar_t)>::type*>(data), size, result); + } + }; + + template <typename T> PUGI__FN void convert_utf_endian_swap(T* result, const T* data, size_t length) + { + for (size_t i = 0; i < length; ++i) result[i] = endian_swap(data[i]); + } + +#ifdef PUGIXML_WCHAR_MODE + PUGI__FN void convert_wchar_endian_swap(wchar_t* result, const wchar_t* data, size_t length) + { + for (size_t i = 0; i < length; ++i) result[i] = static_cast<wchar_t>(endian_swap(static_cast<wchar_selector<sizeof(wchar_t)>::type>(data[i]))); + } +#endif +PUGI__NS_END + +PUGI__NS_BEGIN + enum chartype_t + { + ct_parse_pcdata = 1, // \0, &, \r, < + ct_parse_attr = 2, // \0, &, \r, ', " + ct_parse_attr_ws = 4, // \0, &, \r, ', ", \n, tab + ct_space = 8, // \r, \n, space, tab + ct_parse_cdata = 16, // \0, ], >, \r + ct_parse_comment = 32, // \0, -, >, \r + ct_symbol = 64, // Any symbol > 127, a-z, A-Z, 0-9, _, :, -, . + ct_start_symbol = 128 // Any symbol > 127, a-z, A-Z, _, : + }; + + static const unsigned char chartype_table[256] = + { + 55, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 0, 0, 63, 0, 0, // 0-15 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16-31 + 8, 0, 6, 0, 0, 0, 7, 6, 0, 0, 0, 0, 0, 96, 64, 0, // 32-47 + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 192, 0, 1, 0, 48, 0, // 48-63 + 0, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, // 64-79 + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 0, 0, 16, 0, 192, // 80-95 + 0, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, // 96-111 + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 0, 0, 0, 0, 0, // 112-127 + + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, // 128+ + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192 + }; + + enum chartypex_t + { + ctx_special_pcdata = 1, // Any symbol >= 0 and < 32 (except \t, \r, \n), &, <, > + ctx_special_attr = 2, // Any symbol >= 0 and < 32 (except \t), &, <, >, " + ctx_start_symbol = 4, // Any symbol > 127, a-z, A-Z, _ + ctx_digit = 8, // 0-9 + ctx_symbol = 16 // Any symbol > 127, a-z, A-Z, 0-9, _, -, . + }; + + static const unsigned char chartypex_table[256] = + { + 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 2, 3, 3, 2, 3, 3, // 0-15 + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 16-31 + 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 16, 16, 0, // 32-47 + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 0, 0, 3, 0, 3, 0, // 48-63 + + 0, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, // 64-79 + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 0, 0, 0, 0, 20, // 80-95 + 0, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, // 96-111 + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 0, 0, 0, 0, 0, // 112-127 + + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, // 128+ + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20 + }; + +#ifdef PUGIXML_WCHAR_MODE + #define PUGI__IS_CHARTYPE_IMPL(c, ct, table) ((static_cast<unsigned int>(c) < 128 ? table[static_cast<unsigned int>(c)] : table[128]) & (ct)) +#else + #define PUGI__IS_CHARTYPE_IMPL(c, ct, table) (table[static_cast<unsigned char>(c)] & (ct)) +#endif + + #define PUGI__IS_CHARTYPE(c, ct) PUGI__IS_CHARTYPE_IMPL(c, ct, chartype_table) + #define PUGI__IS_CHARTYPEX(c, ct) PUGI__IS_CHARTYPE_IMPL(c, ct, chartypex_table) + + PUGI__FN bool is_little_endian() + { + unsigned int ui = 1; + + return *reinterpret_cast<unsigned char*>(&ui) == 1; + } + + PUGI__FN xml_encoding get_wchar_encoding() + { + PUGI__STATIC_ASSERT(sizeof(wchar_t) == 2 || sizeof(wchar_t) == 4); + + if (sizeof(wchar_t) == 2) + return is_little_endian() ? encoding_utf16_le : encoding_utf16_be; + else + return is_little_endian() ? encoding_utf32_le : encoding_utf32_be; + } + + PUGI__FN xml_encoding guess_buffer_encoding(uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3) + { + // look for BOM in first few bytes + if (d0 == 0 && d1 == 0 && d2 == 0xfe && d3 == 0xff) return encoding_utf32_be; + if (d0 == 0xff && d1 == 0xfe && d2 == 0 && d3 == 0) return encoding_utf32_le; + if (d0 == 0xfe && d1 == 0xff) return encoding_utf16_be; + if (d0 == 0xff && d1 == 0xfe) return encoding_utf16_le; + if (d0 == 0xef && d1 == 0xbb && d2 == 0xbf) return encoding_utf8; + + // look for <, <? or <?xm in various encodings + if (d0 == 0 && d1 == 0 && d2 == 0 && d3 == 0x3c) return encoding_utf32_be; + if (d0 == 0x3c && d1 == 0 && d2 == 0 && d3 == 0) return encoding_utf32_le; + if (d0 == 0 && d1 == 0x3c && d2 == 0 && d3 == 0x3f) return encoding_utf16_be; + if (d0 == 0x3c && d1 == 0 && d2 == 0x3f && d3 == 0) return encoding_utf16_le; + if (d0 == 0x3c && d1 == 0x3f && d2 == 0x78 && d3 == 0x6d) return encoding_utf8; + + // look for utf16 < followed by node name (this may fail, but is better than utf8 since it's zero terminated so early) + if (d0 == 0 && d1 == 0x3c) return encoding_utf16_be; + if (d0 == 0x3c && d1 == 0) return encoding_utf16_le; + + // no known BOM detected, assume utf8 + return encoding_utf8; + } + + PUGI__FN xml_encoding get_buffer_encoding(xml_encoding encoding, const void* contents, size_t size) + { + // replace wchar encoding with utf implementation + if (encoding == encoding_wchar) return get_wchar_encoding(); + + // replace utf16 encoding with utf16 with specific endianness + if (encoding == encoding_utf16) return is_little_endian() ? encoding_utf16_le : encoding_utf16_be; + + // replace utf32 encoding with utf32 with specific endianness + if (encoding == encoding_utf32) return is_little_endian() ? encoding_utf32_le : encoding_utf32_be; + + // only do autodetection if no explicit encoding is requested + if (encoding != encoding_auto) return encoding; + + // skip encoding autodetection if input buffer is too small + if (size < 4) return encoding_utf8; + + // try to guess encoding (based on XML specification, Appendix F.1) + const uint8_t* data = static_cast<const uint8_t*>(contents); + + PUGI__DMC_VOLATILE uint8_t d0 = data[0], d1 = data[1], d2 = data[2], d3 = data[3]; + + return guess_buffer_encoding(d0, d1, d2, d3); + } + + PUGI__FN bool get_mutable_buffer(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable) + { + size_t length = size / sizeof(char_t); + + if (is_mutable) + { + out_buffer = static_cast<char_t*>(const_cast<void*>(contents)); + out_length = length; + } + else + { + char_t* buffer = static_cast<char_t*>(xml_memory::allocate((length + 1) * sizeof(char_t))); + if (!buffer) return false; + + memcpy(buffer, contents, length * sizeof(char_t)); + buffer[length] = 0; + + out_buffer = buffer; + out_length = length + 1; + } + + return true; + } + +#ifdef PUGIXML_WCHAR_MODE + PUGI__FN bool need_endian_swap_utf(xml_encoding le, xml_encoding re) + { + return (le == encoding_utf16_be && re == encoding_utf16_le) || (le == encoding_utf16_le && re == encoding_utf16_be) || + (le == encoding_utf32_be && re == encoding_utf32_le) || (le == encoding_utf32_le && re == encoding_utf32_be); + } + + PUGI__FN bool convert_buffer_endian_swap(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable) + { + const char_t* data = static_cast<const char_t*>(contents); + size_t length = size / sizeof(char_t); + + if (is_mutable) + { + char_t* buffer = const_cast<char_t*>(data); + + convert_wchar_endian_swap(buffer, data, length); + + out_buffer = buffer; + out_length = length; + } + else + { + char_t* buffer = static_cast<char_t*>(xml_memory::allocate((length + 1) * sizeof(char_t))); + if (!buffer) return false; + + convert_wchar_endian_swap(buffer, data, length); + buffer[length] = 0; + + out_buffer = buffer; + out_length = length + 1; + } + + return true; + } + + PUGI__FN bool convert_buffer_utf8(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size) + { + const uint8_t* data = static_cast<const uint8_t*>(contents); + size_t data_length = size; + + // first pass: get length in wchar_t units + size_t length = utf_decoder<wchar_counter>::decode_utf8_block(data, data_length, 0); + + // allocate buffer of suitable length + char_t* buffer = static_cast<char_t*>(xml_memory::allocate((length + 1) * sizeof(char_t))); + if (!buffer) return false; + + // second pass: convert utf8 input to wchar_t + wchar_writer::value_type obegin = reinterpret_cast<wchar_writer::value_type>(buffer); + wchar_writer::value_type oend = utf_decoder<wchar_writer>::decode_utf8_block(data, data_length, obegin); + + assert(oend == obegin + length); + *oend = 0; + + out_buffer = buffer; + out_length = length + 1; + + return true; + } + + template <typename opt_swap> PUGI__FN bool convert_buffer_utf16(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, opt_swap) + { + const uint16_t* data = static_cast<const uint16_t*>(contents); + size_t data_length = size / sizeof(uint16_t); + + // first pass: get length in wchar_t units + size_t length = utf_decoder<wchar_counter, opt_swap>::decode_utf16_block(data, data_length, 0); + + // allocate buffer of suitable length + char_t* buffer = static_cast<char_t*>(xml_memory::allocate((length + 1) * sizeof(char_t))); + if (!buffer) return false; + + // second pass: convert utf16 input to wchar_t + wchar_writer::value_type obegin = reinterpret_cast<wchar_writer::value_type>(buffer); + wchar_writer::value_type oend = utf_decoder<wchar_writer, opt_swap>::decode_utf16_block(data, data_length, obegin); + + assert(oend == obegin + length); + *oend = 0; + + out_buffer = buffer; + out_length = length + 1; + + return true; + } + + template <typename opt_swap> PUGI__FN bool convert_buffer_utf32(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, opt_swap) + { + const uint32_t* data = static_cast<const uint32_t*>(contents); + size_t data_length = size / sizeof(uint32_t); + + // first pass: get length in wchar_t units + size_t length = utf_decoder<wchar_counter, opt_swap>::decode_utf32_block(data, data_length, 0); + + // allocate buffer of suitable length + char_t* buffer = static_cast<char_t*>(xml_memory::allocate((length + 1) * sizeof(char_t))); + if (!buffer) return false; + + // second pass: convert utf32 input to wchar_t + wchar_writer::value_type obegin = reinterpret_cast<wchar_writer::value_type>(buffer); + wchar_writer::value_type oend = utf_decoder<wchar_writer, opt_swap>::decode_utf32_block(data, data_length, obegin); + + assert(oend == obegin + length); + *oend = 0; + + out_buffer = buffer; + out_length = length + 1; + + return true; + } + + PUGI__FN bool convert_buffer_latin1(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size) + { + const uint8_t* data = static_cast<const uint8_t*>(contents); + size_t data_length = size; + + // get length in wchar_t units + size_t length = data_length; + + // allocate buffer of suitable length + char_t* buffer = static_cast<char_t*>(xml_memory::allocate((length + 1) * sizeof(char_t))); + if (!buffer) return false; + + // convert latin1 input to wchar_t + wchar_writer::value_type obegin = reinterpret_cast<wchar_writer::value_type>(buffer); + wchar_writer::value_type oend = utf_decoder<wchar_writer>::decode_latin1_block(data, data_length, obegin); + + assert(oend == obegin + length); + *oend = 0; + + out_buffer = buffer; + out_length = length + 1; + + return true; + } + + PUGI__FN bool convert_buffer(char_t*& out_buffer, size_t& out_length, xml_encoding encoding, const void* contents, size_t size, bool is_mutable) + { + // get native encoding + xml_encoding wchar_encoding = get_wchar_encoding(); + + // fast path: no conversion required + if (encoding == wchar_encoding) return get_mutable_buffer(out_buffer, out_length, contents, size, is_mutable); + + // only endian-swapping is required + if (need_endian_swap_utf(encoding, wchar_encoding)) return convert_buffer_endian_swap(out_buffer, out_length, contents, size, is_mutable); + + // source encoding is utf8 + if (encoding == encoding_utf8) return convert_buffer_utf8(out_buffer, out_length, contents, size); + + // source encoding is utf16 + if (encoding == encoding_utf16_be || encoding == encoding_utf16_le) + { + xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be; + + return (native_encoding == encoding) ? + convert_buffer_utf16(out_buffer, out_length, contents, size, opt_false()) : + convert_buffer_utf16(out_buffer, out_length, contents, size, opt_true()); + } + + // source encoding is utf32 + if (encoding == encoding_utf32_be || encoding == encoding_utf32_le) + { + xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be; + + return (native_encoding == encoding) ? + convert_buffer_utf32(out_buffer, out_length, contents, size, opt_false()) : + convert_buffer_utf32(out_buffer, out_length, contents, size, opt_true()); + } + + // source encoding is latin1 + if (encoding == encoding_latin1) return convert_buffer_latin1(out_buffer, out_length, contents, size); + + assert(!"Invalid encoding"); + return false; + } +#else + template <typename opt_swap> PUGI__FN bool convert_buffer_utf16(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, opt_swap) + { + const uint16_t* data = static_cast<const uint16_t*>(contents); + size_t data_length = size / sizeof(uint16_t); + + // first pass: get length in utf8 units + size_t length = utf_decoder<utf8_counter, opt_swap>::decode_utf16_block(data, data_length, 0); + + // allocate buffer of suitable length + char_t* buffer = static_cast<char_t*>(xml_memory::allocate((length + 1) * sizeof(char_t))); + if (!buffer) return false; + + // second pass: convert utf16 input to utf8 + uint8_t* obegin = reinterpret_cast<uint8_t*>(buffer); + uint8_t* oend = utf_decoder<utf8_writer, opt_swap>::decode_utf16_block(data, data_length, obegin); + + assert(oend == obegin + length); + *oend = 0; + + out_buffer = buffer; + out_length = length + 1; + + return true; + } + + template <typename opt_swap> PUGI__FN bool convert_buffer_utf32(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, opt_swap) + { + const uint32_t* data = static_cast<const uint32_t*>(contents); + size_t data_length = size / sizeof(uint32_t); + + // first pass: get length in utf8 units + size_t length = utf_decoder<utf8_counter, opt_swap>::decode_utf32_block(data, data_length, 0); + + // allocate buffer of suitable length + char_t* buffer = static_cast<char_t*>(xml_memory::allocate((length + 1) * sizeof(char_t))); + if (!buffer) return false; + + // second pass: convert utf32 input to utf8 + uint8_t* obegin = reinterpret_cast<uint8_t*>(buffer); + uint8_t* oend = utf_decoder<utf8_writer, opt_swap>::decode_utf32_block(data, data_length, obegin); + + assert(oend == obegin + length); + *oend = 0; + + out_buffer = buffer; + out_length = length + 1; + + return true; + } + + PUGI__FN size_t get_latin1_7bit_prefix_length(const uint8_t* data, size_t size) + { + for (size_t i = 0; i < size; ++i) + if (data[i] > 127) + return i; + + return size; + } + + PUGI__FN bool convert_buffer_latin1(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable) + { + const uint8_t* data = static_cast<const uint8_t*>(contents); + size_t data_length = size; + + // get size of prefix that does not need utf8 conversion + size_t prefix_length = get_latin1_7bit_prefix_length(data, data_length); + assert(prefix_length <= data_length); + + const uint8_t* postfix = data + prefix_length; + size_t postfix_length = data_length - prefix_length; + + // if no conversion is needed, just return the original buffer + if (postfix_length == 0) return get_mutable_buffer(out_buffer, out_length, contents, size, is_mutable); + + // first pass: get length in utf8 units + size_t length = prefix_length + utf_decoder<utf8_counter>::decode_latin1_block(postfix, postfix_length, 0); + + // allocate buffer of suitable length + char_t* buffer = static_cast<char_t*>(xml_memory::allocate((length + 1) * sizeof(char_t))); + if (!buffer) return false; + + // second pass: convert latin1 input to utf8 + memcpy(buffer, data, prefix_length); + + uint8_t* obegin = reinterpret_cast<uint8_t*>(buffer); + uint8_t* oend = utf_decoder<utf8_writer>::decode_latin1_block(postfix, postfix_length, obegin + prefix_length); + + assert(oend == obegin + length); + *oend = 0; + + out_buffer = buffer; + out_length = length + 1; + + return true; + } + + PUGI__FN bool convert_buffer(char_t*& out_buffer, size_t& out_length, xml_encoding encoding, const void* contents, size_t size, bool is_mutable) + { + // fast path: no conversion required + if (encoding == encoding_utf8) return get_mutable_buffer(out_buffer, out_length, contents, size, is_mutable); + + // source encoding is utf16 + if (encoding == encoding_utf16_be || encoding == encoding_utf16_le) + { + xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be; + + return (native_encoding == encoding) ? + convert_buffer_utf16(out_buffer, out_length, contents, size, opt_false()) : + convert_buffer_utf16(out_buffer, out_length, contents, size, opt_true()); + } + + // source encoding is utf32 + if (encoding == encoding_utf32_be || encoding == encoding_utf32_le) + { + xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be; + + return (native_encoding == encoding) ? + convert_buffer_utf32(out_buffer, out_length, contents, size, opt_false()) : + convert_buffer_utf32(out_buffer, out_length, contents, size, opt_true()); + } + + // source encoding is latin1 + if (encoding == encoding_latin1) return convert_buffer_latin1(out_buffer, out_length, contents, size, is_mutable); + + assert(!"Invalid encoding"); + return false; + } +#endif + + PUGI__FN size_t as_utf8_begin(const wchar_t* str, size_t length) + { + // get length in utf8 characters + return utf_decoder<utf8_counter>::decode_wchar_block(str, length, 0); + } + + PUGI__FN void as_utf8_end(char* buffer, size_t size, const wchar_t* str, size_t length) + { + // convert to utf8 + uint8_t* begin = reinterpret_cast<uint8_t*>(buffer); + uint8_t* end = utf_decoder<utf8_writer>::decode_wchar_block(str, length, begin); + + assert(begin + size == end); + (void)!end; + + // zero-terminate + buffer[size] = 0; + } + +#ifndef PUGIXML_NO_STL + PUGI__FN std::string as_utf8_impl(const wchar_t* str, size_t length) + { + // first pass: get length in utf8 characters + size_t size = as_utf8_begin(str, length); + + // allocate resulting string + std::string result; + result.resize(size); + + // second pass: convert to utf8 + if (size > 0) as_utf8_end(&result[0], size, str, length); + + return result; + } + + PUGI__FN std::basic_string<wchar_t> as_wide_impl(const char* str, size_t size) + { + const uint8_t* data = reinterpret_cast<const uint8_t*>(str); + + // first pass: get length in wchar_t units + size_t length = utf_decoder<wchar_counter>::decode_utf8_block(data, size, 0); + + // allocate resulting string + std::basic_string<wchar_t> result; + result.resize(length); + + // second pass: convert to wchar_t + if (length > 0) + { + wchar_writer::value_type begin = reinterpret_cast<wchar_writer::value_type>(&result[0]); + wchar_writer::value_type end = utf_decoder<wchar_writer>::decode_utf8_block(data, size, begin); + + assert(begin + length == end); + (void)!end; + } + + return result; + } +#endif + + inline bool strcpy_insitu_allow(size_t length, uintptr_t allocated, char_t* target) + { + assert(target); + size_t target_length = strlength(target); + + // always reuse document buffer memory if possible + if (!allocated) return target_length >= length; + + // reuse heap memory if waste is not too great + const size_t reuse_threshold = 32; + + return target_length >= length && (target_length < reuse_threshold || target_length - length < target_length / 2); + } + + PUGI__FN bool strcpy_insitu(char_t*& dest, uintptr_t& header, uintptr_t header_mask, const char_t* source) + { + assert(header); + + size_t source_length = strlength(source); + + if (source_length == 0) + { + // empty string and null pointer are equivalent, so just deallocate old memory + xml_allocator* alloc = reinterpret_cast<xml_memory_page*>(header & xml_memory_page_pointer_mask)->allocator; + + if (header & header_mask) alloc->deallocate_string(dest); + + // mark the string as not allocated + dest = 0; + header &= ~header_mask; + + return true; + } + else if (dest && strcpy_insitu_allow(source_length, header & header_mask, dest)) + { + // we can reuse old buffer, so just copy the new data (including zero terminator) + memcpy(dest, source, (source_length + 1) * sizeof(char_t)); + + return true; + } + else + { + xml_allocator* alloc = reinterpret_cast<xml_memory_page*>(header & xml_memory_page_pointer_mask)->allocator; + + // allocate new buffer + char_t* buf = alloc->allocate_string(source_length + 1); + if (!buf) return false; + + // copy the string (including zero terminator) + memcpy(buf, source, (source_length + 1) * sizeof(char_t)); + + // deallocate old buffer (*after* the above to protect against overlapping memory and/or allocation failures) + if (header & header_mask) alloc->deallocate_string(dest); + + // the string is now allocated, so set the flag + dest = buf; + header |= header_mask; + + return true; + } + } + + struct gap + { + char_t* end; + size_t size; + + gap(): end(0), size(0) + { + } + + // Push new gap, move s count bytes further (skipping the gap). + // Collapse previous gap. + void push(char_t*& s, size_t count) + { + if (end) // there was a gap already; collapse it + { + // Move [old_gap_end, new_gap_start) to [old_gap_start, ...) + assert(s >= end); + memmove(end - size, end, reinterpret_cast<char*>(s) - reinterpret_cast<char*>(end)); + } + + s += count; // end of current gap + + // "merge" two gaps + end = s; + size += count; + } + + // Collapse all gaps, return past-the-end pointer + char_t* flush(char_t* s) + { + if (end) + { + // Move [old_gap_end, current_pos) to [old_gap_start, ...) + assert(s >= end); + memmove(end - size, end, reinterpret_cast<char*>(s) - reinterpret_cast<char*>(end)); + + return s - size; + } + else return s; + } + }; + + PUGI__FN char_t* strconv_escape(char_t* s, gap& g) + { + char_t* stre = s + 1; + + switch (*stre) + { + case '#': // &#... + { + unsigned int ucsc = 0; + + if (stre[1] == 'x') // &#x... (hex code) + { + stre += 2; + + char_t ch = *stre; + + if (ch == ';') return stre; + + for (;;) + { + if (static_cast<unsigned int>(ch - '0') <= 9) + ucsc = 16 * ucsc + (ch - '0'); + else if (static_cast<unsigned int>((ch | ' ') - 'a') <= 5) + ucsc = 16 * ucsc + ((ch | ' ') - 'a' + 10); + else if (ch == ';') + break; + else // cancel + return stre; + + ch = *++stre; + } + + ++stre; + } + else // &#... (dec code) + { + char_t ch = *++stre; + + if (ch == ';') return stre; + + for (;;) + { + if (static_cast<unsigned int>(static_cast<unsigned int>(ch) - '0') <= 9) + ucsc = 10 * ucsc + (ch - '0'); + else if (ch == ';') + break; + else // cancel + return stre; + + ch = *++stre; + } + + ++stre; + } + + #ifdef PUGIXML_WCHAR_MODE + s = reinterpret_cast<char_t*>(wchar_writer::any(reinterpret_cast<wchar_writer::value_type>(s), ucsc)); + #else + s = reinterpret_cast<char_t*>(utf8_writer::any(reinterpret_cast<uint8_t*>(s), ucsc)); + #endif + + g.push(s, stre - s); + return stre; + } + + case 'a': // &a + { + ++stre; + + if (*stre == 'm') // &am + { + if (*++stre == 'p' && *++stre == ';') // & + { + *s++ = '&'; + ++stre; + + g.push(s, stre - s); + return stre; + } + } + else if (*stre == 'p') // &ap + { + if (*++stre == 'o' && *++stre == 's' && *++stre == ';') // ' + { + *s++ = '\''; + ++stre; + + g.push(s, stre - s); + return stre; + } + } + break; + } + + case 'g': // &g + { + if (*++stre == 't' && *++stre == ';') // > + { + *s++ = '>'; + ++stre; + + g.push(s, stre - s); + return stre; + } + break; + } + + case 'l': // &l + { + if (*++stre == 't' && *++stre == ';') // < + { + *s++ = '<'; + ++stre; + + g.push(s, stre - s); + return stre; + } + break; + } + + case 'q': // &q + { + if (*++stre == 'u' && *++stre == 'o' && *++stre == 't' && *++stre == ';') // " + { + *s++ = '"'; + ++stre; + + g.push(s, stre - s); + return stre; + } + break; + } + + default: + break; + } + + return stre; + } + + // Utility macro for last character handling + #define ENDSWITH(c, e) ((c) == (e) || ((c) == 0 && endch == (e))) + + PUGI__FN char_t* strconv_comment(char_t* s, char_t endch) + { + gap g; + + while (true) + { + while (!PUGI__IS_CHARTYPE(*s, ct_parse_comment)) ++s; + + if (*s == '\r') // Either a single 0x0d or 0x0d 0x0a pair + { + *s++ = '\n'; // replace first one with 0x0a + + if (*s == '\n') g.push(s, 1); + } + else if (s[0] == '-' && s[1] == '-' && ENDSWITH(s[2], '>')) // comment ends here + { + *g.flush(s) = 0; + + return s + (s[2] == '>' ? 3 : 2); + } + else if (*s == 0) + { + return 0; + } + else ++s; + } + } + + PUGI__FN char_t* strconv_cdata(char_t* s, char_t endch) + { + gap g; + + while (true) + { + while (!PUGI__IS_CHARTYPE(*s, ct_parse_cdata)) ++s; + + if (*s == '\r') // Either a single 0x0d or 0x0d 0x0a pair + { + *s++ = '\n'; // replace first one with 0x0a + + if (*s == '\n') g.push(s, 1); + } + else if (s[0] == ']' && s[1] == ']' && ENDSWITH(s[2], '>')) // CDATA ends here + { + *g.flush(s) = 0; + + return s + 1; + } + else if (*s == 0) + { + return 0; + } + else ++s; + } + } + + typedef char_t* (*strconv_pcdata_t)(char_t*); + + template <typename opt_trim, typename opt_eol, typename opt_escape> struct strconv_pcdata_impl + { + static char_t* parse(char_t* s) + { + gap g; + + char_t* begin = s; + + while (true) + { + while (!PUGI__IS_CHARTYPE(*s, ct_parse_pcdata)) ++s; + + if (*s == '<') // PCDATA ends here + { + char_t* end = g.flush(s); + + if (opt_trim::value) + while (end > begin && PUGI__IS_CHARTYPE(end[-1], ct_space)) + --end; + + *end = 0; + + return s + 1; + } + else if (opt_eol::value && *s == '\r') // Either a single 0x0d or 0x0d 0x0a pair + { + *s++ = '\n'; // replace first one with 0x0a + + if (*s == '\n') g.push(s, 1); + } + else if (opt_escape::value && *s == '&') + { + s = strconv_escape(s, g); + } + else if (*s == 0) + { + char_t* end = g.flush(s); + + if (opt_trim::value) + while (end > begin && PUGI__IS_CHARTYPE(end[-1], ct_space)) + --end; + + *end = 0; + + return s; + } + else ++s; + } + } + }; + + PUGI__FN strconv_pcdata_t get_strconv_pcdata(unsigned int optmask) + { + PUGI__STATIC_ASSERT(parse_escapes == 0x10 && parse_eol == 0x20 && parse_trim_pcdata == 0x0800); + + switch (((optmask >> 4) & 3) | ((optmask >> 9) & 4)) // get bitmask for flags (eol escapes trim) + { + case 0: return strconv_pcdata_impl<opt_false, opt_false, opt_false>::parse; + case 1: return strconv_pcdata_impl<opt_false, opt_false, opt_true>::parse; + case 2: return strconv_pcdata_impl<opt_false, opt_true, opt_false>::parse; + case 3: return strconv_pcdata_impl<opt_false, opt_true, opt_true>::parse; + case 4: return strconv_pcdata_impl<opt_true, opt_false, opt_false>::parse; + case 5: return strconv_pcdata_impl<opt_true, opt_false, opt_true>::parse; + case 6: return strconv_pcdata_impl<opt_true, opt_true, opt_false>::parse; + case 7: return strconv_pcdata_impl<opt_true, opt_true, opt_true>::parse; + default: assert(false); return 0; // should not get here + } + } + + typedef char_t* (*strconv_attribute_t)(char_t*, char_t); + + template <typename opt_escape> struct strconv_attribute_impl + { + static char_t* parse_wnorm(char_t* s, char_t end_quote) + { + gap g; + + // trim leading whitespaces + if (PUGI__IS_CHARTYPE(*s, ct_space)) + { + char_t* str = s; + + do ++str; + while (PUGI__IS_CHARTYPE(*str, ct_space)); + + g.push(s, str - s); + } + + while (true) + { + while (!PUGI__IS_CHARTYPE(*s, ct_parse_attr_ws | ct_space)) ++s; + + if (*s == end_quote) + { + char_t* str = g.flush(s); + + do *str-- = 0; + while (PUGI__IS_CHARTYPE(*str, ct_space)); + + return s + 1; + } + else if (PUGI__IS_CHARTYPE(*s, ct_space)) + { + *s++ = ' '; + + if (PUGI__IS_CHARTYPE(*s, ct_space)) + { + char_t* str = s + 1; + while (PUGI__IS_CHARTYPE(*str, ct_space)) ++str; + + g.push(s, str - s); + } + } + else if (opt_escape::value && *s == '&') + { + s = strconv_escape(s, g); + } + else if (!*s) + { + return 0; + } + else ++s; + } + } + + static char_t* parse_wconv(char_t* s, char_t end_quote) + { + gap g; + + while (true) + { + while (!PUGI__IS_CHARTYPE(*s, ct_parse_attr_ws)) ++s; + + if (*s == end_quote) + { + *g.flush(s) = 0; + + return s + 1; + } + else if (PUGI__IS_CHARTYPE(*s, ct_space)) + { + if (*s == '\r') + { + *s++ = ' '; + + if (*s == '\n') g.push(s, 1); + } + else *s++ = ' '; + } + else if (opt_escape::value && *s == '&') + { + s = strconv_escape(s, g); + } + else if (!*s) + { + return 0; + } + else ++s; + } + } + + static char_t* parse_eol(char_t* s, char_t end_quote) + { + gap g; + + while (true) + { + while (!PUGI__IS_CHARTYPE(*s, ct_parse_attr)) ++s; + + if (*s == end_quote) + { + *g.flush(s) = 0; + + return s + 1; + } + else if (*s == '\r') + { + *s++ = '\n'; + + if (*s == '\n') g.push(s, 1); + } + else if (opt_escape::value && *s == '&') + { + s = strconv_escape(s, g); + } + else if (!*s) + { + return 0; + } + else ++s; + } + } + + static char_t* parse_simple(char_t* s, char_t end_quote) + { + gap g; + + while (true) + { + while (!PUGI__IS_CHARTYPE(*s, ct_parse_attr)) ++s; + + if (*s == end_quote) + { + *g.flush(s) = 0; + + return s + 1; + } + else if (opt_escape::value && *s == '&') + { + s = strconv_escape(s, g); + } + else if (!*s) + { + return 0; + } + else ++s; + } + } + }; + + PUGI__FN strconv_attribute_t get_strconv_attribute(unsigned int optmask) + { + PUGI__STATIC_ASSERT(parse_escapes == 0x10 && parse_eol == 0x20 && parse_wconv_attribute == 0x40 && parse_wnorm_attribute == 0x80); + + switch ((optmask >> 4) & 15) // get bitmask for flags (wconv wnorm eol escapes) + { + case 0: return strconv_attribute_impl<opt_false>::parse_simple; + case 1: return strconv_attribute_impl<opt_true>::parse_simple; + case 2: return strconv_attribute_impl<opt_false>::parse_eol; + case 3: return strconv_attribute_impl<opt_true>::parse_eol; + case 4: return strconv_attribute_impl<opt_false>::parse_wconv; + case 5: return strconv_attribute_impl<opt_true>::parse_wconv; + case 6: return strconv_attribute_impl<opt_false>::parse_wconv; + case 7: return strconv_attribute_impl<opt_true>::parse_wconv; + case 8: return strconv_attribute_impl<opt_false>::parse_wnorm; + case 9: return strconv_attribute_impl<opt_true>::parse_wnorm; + case 10: return strconv_attribute_impl<opt_false>::parse_wnorm; + case 11: return strconv_attribute_impl<opt_true>::parse_wnorm; + case 12: return strconv_attribute_impl<opt_false>::parse_wnorm; + case 13: return strconv_attribute_impl<opt_true>::parse_wnorm; + case 14: return strconv_attribute_impl<opt_false>::parse_wnorm; + case 15: return strconv_attribute_impl<opt_true>::parse_wnorm; + default: assert(false); return 0; // should not get here + } + } + + inline xml_parse_result make_parse_result(xml_parse_status status, ptrdiff_t offset = 0) + { + xml_parse_result result; + result.status = status; + result.offset = offset; + + return result; + } + + struct xml_parser + { + xml_allocator alloc; + char_t* error_offset; + xml_parse_status error_status; + + // Parser utilities. + #define PUGI__SKIPWS() { while (PUGI__IS_CHARTYPE(*s, ct_space)) ++s; } + #define PUGI__OPTSET(OPT) ( optmsk & (OPT) ) + #define PUGI__PUSHNODE(TYPE) { cursor = append_new_node(cursor, alloc, TYPE); if (!cursor) PUGI__THROW_ERROR(status_out_of_memory, s); } + #define PUGI__POPNODE() { cursor = cursor->parent; } + #define PUGI__SCANFOR(X) { while (*s != 0 && !(X)) ++s; } + #define PUGI__SCANWHILE(X) { while ((X)) ++s; } + #define PUGI__ENDSEG() { ch = *s; *s = 0; ++s; } + #define PUGI__THROW_ERROR(err, m) return error_offset = m, error_status = err, static_cast<char_t*>(0) + #define PUGI__CHECK_ERROR(err, m) { if (*s == 0) PUGI__THROW_ERROR(err, m); } + + xml_parser(const xml_allocator& alloc_): alloc(alloc_), error_offset(0), error_status(status_ok) + { + } + + // DOCTYPE consists of nested sections of the following possible types: + // <!-- ... -->, <? ... ?>, "...", '...' + // <![...]]> + // <!...> + // First group can not contain nested groups + // Second group can contain nested groups of the same type + // Third group can contain all other groups + char_t* parse_doctype_primitive(char_t* s) + { + if (*s == '"' || *s == '\'') + { + // quoted string + char_t ch = *s++; + PUGI__SCANFOR(*s == ch); + if (!*s) PUGI__THROW_ERROR(status_bad_doctype, s); + + s++; + } + else if (s[0] == '<' && s[1] == '?') + { + // <? ... ?> + s += 2; + PUGI__SCANFOR(s[0] == '?' && s[1] == '>'); // no need for ENDSWITH because ?> can't terminate proper doctype + if (!*s) PUGI__THROW_ERROR(status_bad_doctype, s); + + s += 2; + } + else if (s[0] == '<' && s[1] == '!' && s[2] == '-' && s[3] == '-') + { + s += 4; + PUGI__SCANFOR(s[0] == '-' && s[1] == '-' && s[2] == '>'); // no need for ENDSWITH because --> can't terminate proper doctype + if (!*s) PUGI__THROW_ERROR(status_bad_doctype, s); + + s += 4; + } + else PUGI__THROW_ERROR(status_bad_doctype, s); + + return s; + } + + char_t* parse_doctype_ignore(char_t* s) + { + assert(s[0] == '<' && s[1] == '!' && s[2] == '['); + s++; + + while (*s) + { + if (s[0] == '<' && s[1] == '!' && s[2] == '[') + { + // nested ignore section + s = parse_doctype_ignore(s); + if (!s) return s; + } + else if (s[0] == ']' && s[1] == ']' && s[2] == '>') + { + // ignore section end + s += 3; + + return s; + } + else s++; + } + + PUGI__THROW_ERROR(status_bad_doctype, s); + } + + char_t* parse_doctype_group(char_t* s, char_t endch, bool toplevel) + { + assert((s[0] == '<' || s[0] == 0) && s[1] == '!'); + s++; + + while (*s) + { + if (s[0] == '<' && s[1] == '!' && s[2] != '-') + { + if (s[2] == '[') + { + // ignore + s = parse_doctype_ignore(s); + if (!s) return s; + } + else + { + // some control group + s = parse_doctype_group(s, endch, false); + if (!s) return s; + + // skip > + assert(*s == '>'); + s++; + } + } + else if (s[0] == '<' || s[0] == '"' || s[0] == '\'') + { + // unknown tag (forbidden), or some primitive group + s = parse_doctype_primitive(s); + if (!s) return s; + } + else if (*s == '>') + { + return s; + } + else s++; + } + + if (!toplevel || endch != '>') PUGI__THROW_ERROR(status_bad_doctype, s); + + return s; + } + + char_t* parse_exclamation(char_t* s, xml_node_struct* cursor, unsigned int optmsk, char_t endch) + { + // parse node contents, starting with exclamation mark + ++s; + + if (*s == '-') // '<!-...' + { + ++s; + + if (*s == '-') // '<!--...' + { + ++s; + + if (PUGI__OPTSET(parse_comments)) + { + PUGI__PUSHNODE(node_comment); // Append a new node on the tree. + cursor->value = s; // Save the offset. + } + + if (PUGI__OPTSET(parse_eol) && PUGI__OPTSET(parse_comments)) + { + s = strconv_comment(s, endch); + + if (!s) PUGI__THROW_ERROR(status_bad_comment, cursor->value); + } + else + { + // Scan for terminating '-->'. + PUGI__SCANFOR(s[0] == '-' && s[1] == '-' && ENDSWITH(s[2], '>')); + PUGI__CHECK_ERROR(status_bad_comment, s); + + if (PUGI__OPTSET(parse_comments)) + *s = 0; // Zero-terminate this segment at the first terminating '-'. + + s += (s[2] == '>' ? 3 : 2); // Step over the '\0->'. + } + } + else PUGI__THROW_ERROR(status_bad_comment, s); + } + else if (*s == '[') + { + // '<![CDATA[...' + if (*++s=='C' && *++s=='D' && *++s=='A' && *++s=='T' && *++s=='A' && *++s == '[') + { + ++s; + + if (PUGI__OPTSET(parse_cdata)) + { + PUGI__PUSHNODE(node_cdata); // Append a new node on the tree. + cursor->value = s; // Save the offset. + + if (PUGI__OPTSET(parse_eol)) + { + s = strconv_cdata(s, endch); + + if (!s) PUGI__THROW_ERROR(status_bad_cdata, cursor->value); + } + else + { + // Scan for terminating ']]>'. + PUGI__SCANFOR(s[0] == ']' && s[1] == ']' && ENDSWITH(s[2], '>')); + PUGI__CHECK_ERROR(status_bad_cdata, s); + + *s++ = 0; // Zero-terminate this segment. + } + } + else // Flagged for discard, but we still have to scan for the terminator. + { + // Scan for terminating ']]>'. + PUGI__SCANFOR(s[0] == ']' && s[1] == ']' && ENDSWITH(s[2], '>')); + PUGI__CHECK_ERROR(status_bad_cdata, s); + + ++s; + } + + s += (s[1] == '>' ? 2 : 1); // Step over the last ']>'. + } + else PUGI__THROW_ERROR(status_bad_cdata, s); + } + else if (s[0] == 'D' && s[1] == 'O' && s[2] == 'C' && s[3] == 'T' && s[4] == 'Y' && s[5] == 'P' && ENDSWITH(s[6], 'E')) + { + s -= 2; + + if (cursor->parent) PUGI__THROW_ERROR(status_bad_doctype, s); + + char_t* mark = s + 9; + + s = parse_doctype_group(s, endch, true); + if (!s) return s; + + assert((*s == 0 && endch == '>') || *s == '>'); + if (*s) *s++ = 0; + + if (PUGI__OPTSET(parse_doctype)) + { + while (PUGI__IS_CHARTYPE(*mark, ct_space)) ++mark; + + PUGI__PUSHNODE(node_doctype); + + cursor->value = mark; + + PUGI__POPNODE(); + } + } + else if (*s == 0 && endch == '-') PUGI__THROW_ERROR(status_bad_comment, s); + else if (*s == 0 && endch == '[') PUGI__THROW_ERROR(status_bad_cdata, s); + else PUGI__THROW_ERROR(status_unrecognized_tag, s); + + return s; + } + + char_t* parse_question(char_t* s, xml_node_struct*& ref_cursor, unsigned int optmsk, char_t endch) + { + // load into registers + xml_node_struct* cursor = ref_cursor; + char_t ch = 0; + + // parse node contents, starting with question mark + ++s; + + // read PI target + char_t* target = s; + + if (!PUGI__IS_CHARTYPE(*s, ct_start_symbol)) PUGI__THROW_ERROR(status_bad_pi, s); + + PUGI__SCANWHILE(PUGI__IS_CHARTYPE(*s, ct_symbol)); + PUGI__CHECK_ERROR(status_bad_pi, s); + + // determine node type; stricmp / strcasecmp is not portable + bool declaration = (target[0] | ' ') == 'x' && (target[1] | ' ') == 'm' && (target[2] | ' ') == 'l' && target + 3 == s; + + if (declaration ? PUGI__OPTSET(parse_declaration) : PUGI__OPTSET(parse_pi)) + { + if (declaration) + { + // disallow non top-level declarations + if (cursor->parent) PUGI__THROW_ERROR(status_bad_pi, s); + + PUGI__PUSHNODE(node_declaration); + } + else + { + PUGI__PUSHNODE(node_pi); + } + + cursor->name = target; + + PUGI__ENDSEG(); + + // parse value/attributes + if (ch == '?') + { + // empty node + if (!ENDSWITH(*s, '>')) PUGI__THROW_ERROR(status_bad_pi, s); + s += (*s == '>'); + + PUGI__POPNODE(); + } + else if (PUGI__IS_CHARTYPE(ch, ct_space)) + { + PUGI__SKIPWS(); + + // scan for tag end + char_t* value = s; + + PUGI__SCANFOR(s[0] == '?' && ENDSWITH(s[1], '>')); + PUGI__CHECK_ERROR(status_bad_pi, s); + + if (declaration) + { + // replace ending ? with / so that 'element' terminates properly + *s = '/'; + + // we exit from this function with cursor at node_declaration, which is a signal to parse() to go to LOC_ATTRIBUTES + s = value; + } + else + { + // store value and step over > + cursor->value = value; + PUGI__POPNODE(); + + PUGI__ENDSEG(); + + s += (*s == '>'); + } + } + else PUGI__THROW_ERROR(status_bad_pi, s); + } + else + { + // scan for tag end + PUGI__SCANFOR(s[0] == '?' && ENDSWITH(s[1], '>')); + PUGI__CHECK_ERROR(status_bad_pi, s); + + s += (s[1] == '>' ? 2 : 1); + } + + // store from registers + ref_cursor = cursor; + + return s; + } + + char_t* parse_tree(char_t* s, xml_node_struct* root, unsigned int optmsk, char_t endch) + { + strconv_attribute_t strconv_attribute = get_strconv_attribute(optmsk); + strconv_pcdata_t strconv_pcdata = get_strconv_pcdata(optmsk); + + char_t ch = 0; + xml_node_struct* cursor = root; + char_t* mark = s; + + while (*s != 0) + { + if (*s == '<') + { + ++s; + + LOC_TAG: + if (PUGI__IS_CHARTYPE(*s, ct_start_symbol)) // '<#...' + { + PUGI__PUSHNODE(node_element); // Append a new node to the tree. + + cursor->name = s; + + PUGI__SCANWHILE(PUGI__IS_CHARTYPE(*s, ct_symbol)); // Scan for a terminator. + PUGI__ENDSEG(); // Save char in 'ch', terminate & step over. + + if (ch == '>') + { + // end of tag + } + else if (PUGI__IS_CHARTYPE(ch, ct_space)) + { + LOC_ATTRIBUTES: + while (true) + { + PUGI__SKIPWS(); // Eat any whitespace. + + if (PUGI__IS_CHARTYPE(*s, ct_start_symbol)) // <... #... + { + xml_attribute_struct* a = append_new_attribute(cursor, alloc); // Make space for this attribute. + if (!a) PUGI__THROW_ERROR(status_out_of_memory, s); + + a->name = s; // Save the offset. + + PUGI__SCANWHILE(PUGI__IS_CHARTYPE(*s, ct_symbol)); // Scan for a terminator. + PUGI__CHECK_ERROR(status_bad_attribute, s); //$ redundant, left for performance + + PUGI__ENDSEG(); // Save char in 'ch', terminate & step over. + PUGI__CHECK_ERROR(status_bad_attribute, s); //$ redundant, left for performance + + if (PUGI__IS_CHARTYPE(ch, ct_space)) + { + PUGI__SKIPWS(); // Eat any whitespace. + PUGI__CHECK_ERROR(status_bad_attribute, s); //$ redundant, left for performance + + ch = *s; + ++s; + } + + if (ch == '=') // '<... #=...' + { + PUGI__SKIPWS(); // Eat any whitespace. + + if (*s == '"' || *s == '\'') // '<... #="...' + { + ch = *s; // Save quote char to avoid breaking on "''" -or- '""'. + ++s; // Step over the quote. + a->value = s; // Save the offset. + + s = strconv_attribute(s, ch); + + if (!s) PUGI__THROW_ERROR(status_bad_attribute, a->value); + + // After this line the loop continues from the start; + // Whitespaces, / and > are ok, symbols and EOF are wrong, + // everything else will be detected + if (PUGI__IS_CHARTYPE(*s, ct_start_symbol)) PUGI__THROW_ERROR(status_bad_attribute, s); + } + else PUGI__THROW_ERROR(status_bad_attribute, s); + } + else PUGI__THROW_ERROR(status_bad_attribute, s); + } + else if (*s == '/') + { + ++s; + + if (*s == '>') + { + PUGI__POPNODE(); + s++; + break; + } + else if (*s == 0 && endch == '>') + { + PUGI__POPNODE(); + break; + } + else PUGI__THROW_ERROR(status_bad_start_element, s); + } + else if (*s == '>') + { + ++s; + + break; + } + else if (*s == 0 && endch == '>') + { + break; + } + else PUGI__THROW_ERROR(status_bad_start_element, s); + } + + // !!! + } + else if (ch == '/') // '<#.../' + { + if (!ENDSWITH(*s, '>')) PUGI__THROW_ERROR(status_bad_start_element, s); + + PUGI__POPNODE(); // Pop. + + s += (*s == '>'); + } + else if (ch == 0) + { + // we stepped over null terminator, backtrack & handle closing tag + --s; + + if (endch != '>') PUGI__THROW_ERROR(status_bad_start_element, s); + } + else PUGI__THROW_ERROR(status_bad_start_element, s); + } + else if (*s == '/') + { + ++s; + + char_t* name = cursor->name; + if (!name) PUGI__THROW_ERROR(status_end_element_mismatch, s); + + while (PUGI__IS_CHARTYPE(*s, ct_symbol)) + { + if (*s++ != *name++) PUGI__THROW_ERROR(status_end_element_mismatch, s); + } + + if (*name) + { + if (*s == 0 && name[0] == endch && name[1] == 0) PUGI__THROW_ERROR(status_bad_end_element, s); + else PUGI__THROW_ERROR(status_end_element_mismatch, s); + } + + PUGI__POPNODE(); // Pop. + + PUGI__SKIPWS(); + + if (*s == 0) + { + if (endch != '>') PUGI__THROW_ERROR(status_bad_end_element, s); + } + else + { + if (*s != '>') PUGI__THROW_ERROR(status_bad_end_element, s); + ++s; + } + } + else if (*s == '?') // '<?...' + { + s = parse_question(s, cursor, optmsk, endch); + if (!s) return s; + + assert(cursor); + if ((cursor->header & xml_memory_page_type_mask) + 1 == node_declaration) goto LOC_ATTRIBUTES; + } + else if (*s == '!') // '<!...' + { + s = parse_exclamation(s, cursor, optmsk, endch); + if (!s) return s; + } + else if (*s == 0 && endch == '?') PUGI__THROW_ERROR(status_bad_pi, s); + else PUGI__THROW_ERROR(status_unrecognized_tag, s); + } + else + { + mark = s; // Save this offset while searching for a terminator. + + PUGI__SKIPWS(); // Eat whitespace if no genuine PCDATA here. + + if (*s == '<' || !*s) + { + // We skipped some whitespace characters because otherwise we would take the tag branch instead of PCDATA one + assert(mark != s); + + if (!PUGI__OPTSET(parse_ws_pcdata | parse_ws_pcdata_single) || PUGI__OPTSET(parse_trim_pcdata)) + { + continue; + } + else if (PUGI__OPTSET(parse_ws_pcdata_single)) + { + if (s[0] != '<' || s[1] != '/' || cursor->first_child) continue; + } + } + + if (!PUGI__OPTSET(parse_trim_pcdata)) + s = mark; + + if (cursor->parent || PUGI__OPTSET(parse_fragment)) + { + PUGI__PUSHNODE(node_pcdata); // Append a new node on the tree. + cursor->value = s; // Save the offset. + + s = strconv_pcdata(s); + + PUGI__POPNODE(); // Pop since this is a standalone. + + if (!*s) break; + } + else + { + PUGI__SCANFOR(*s == '<'); // '...<' + if (!*s) break; + + ++s; + } + + // We're after '<' + goto LOC_TAG; + } + } + + // check that last tag is closed + if (cursor != root) PUGI__THROW_ERROR(status_end_element_mismatch, s); + + return s; + } + + #ifdef PUGIXML_WCHAR_MODE + static char_t* parse_skip_bom(char_t* s) + { + unsigned int bom = 0xfeff; + return (s[0] == static_cast<wchar_t>(bom)) ? s + 1 : s; + } + #else + static char_t* parse_skip_bom(char_t* s) + { + return (s[0] == '\xef' && s[1] == '\xbb' && s[2] == '\xbf') ? s + 3 : s; + } + #endif + + static bool has_element_node_siblings(xml_node_struct* node) + { + while (node) + { + xml_node_type type = static_cast<xml_node_type>((node->header & impl::xml_memory_page_type_mask) + 1); + if (type == node_element) return true; + + node = node->next_sibling; + } + + return false; + } + + static xml_parse_result parse(char_t* buffer, size_t length, xml_document_struct* xmldoc, xml_node_struct* root, unsigned int optmsk) + { + // allocator object is a part of document object + xml_allocator& alloc = *static_cast<xml_allocator*>(xmldoc); + + // early-out for empty documents + if (length == 0) + return make_parse_result(PUGI__OPTSET(parse_fragment) ? status_ok : status_no_document_element); + + // get last child of the root before parsing + xml_node_struct* last_root_child = root->first_child ? root->first_child->prev_sibling_c : 0; + + // create parser on stack + xml_parser parser(alloc); + + // save last character and make buffer zero-terminated (speeds up parsing) + char_t endch = buffer[length - 1]; + buffer[length - 1] = 0; + + // skip BOM to make sure it does not end up as part of parse output + char_t* buffer_data = parse_skip_bom(buffer); + + // perform actual parsing + parser.parse_tree(buffer_data, root, optmsk, endch); + + // update allocator state + alloc = parser.alloc; + + xml_parse_result result = make_parse_result(parser.error_status, parser.error_offset ? parser.error_offset - buffer : 0); + assert(result.offset >= 0 && static_cast<size_t>(result.offset) <= length); + + if (result) + { + // since we removed last character, we have to handle the only possible false positive (stray <) + if (endch == '<') + return make_parse_result(status_unrecognized_tag, length - 1); + + // check if there are any element nodes parsed + xml_node_struct* first_root_child_parsed = last_root_child ? last_root_child->next_sibling : root->first_child; + + if (!PUGI__OPTSET(parse_fragment) && !has_element_node_siblings(first_root_child_parsed)) + return make_parse_result(status_no_document_element, length - 1); + } + else + { + // roll back offset if it occurs on a null terminator in the source buffer + if (result.offset > 0 && static_cast<size_t>(result.offset) == length - 1 && endch == 0) + result.offset--; + } + + return result; + } + }; + + // Output facilities + PUGI__FN xml_encoding get_write_native_encoding() + { + #ifdef PUGIXML_WCHAR_MODE + return get_wchar_encoding(); + #else + return encoding_utf8; + #endif + } + + PUGI__FN xml_encoding get_write_encoding(xml_encoding encoding) + { + // replace wchar encoding with utf implementation + if (encoding == encoding_wchar) return get_wchar_encoding(); + + // replace utf16 encoding with utf16 with specific endianness + if (encoding == encoding_utf16) return is_little_endian() ? encoding_utf16_le : encoding_utf16_be; + + // replace utf32 encoding with utf32 with specific endianness + if (encoding == encoding_utf32) return is_little_endian() ? encoding_utf32_le : encoding_utf32_be; + + // only do autodetection if no explicit encoding is requested + if (encoding != encoding_auto) return encoding; + + // assume utf8 encoding + return encoding_utf8; + } + +#ifdef PUGIXML_WCHAR_MODE + PUGI__FN size_t get_valid_length(const char_t* data, size_t length) + { + assert(length > 0); + + // discard last character if it's the lead of a surrogate pair + return (sizeof(wchar_t) == 2 && static_cast<unsigned int>(static_cast<uint16_t>(data[length - 1]) - 0xD800) < 0x400) ? length - 1 : length; + } + + PUGI__FN size_t convert_buffer_output(char_t* r_char, uint8_t* r_u8, uint16_t* r_u16, uint32_t* r_u32, const char_t* data, size_t length, xml_encoding encoding) + { + // only endian-swapping is required + if (need_endian_swap_utf(encoding, get_wchar_encoding())) + { + convert_wchar_endian_swap(r_char, data, length); + + return length * sizeof(char_t); + } + + // convert to utf8 + if (encoding == encoding_utf8) + { + uint8_t* dest = r_u8; + uint8_t* end = utf_decoder<utf8_writer>::decode_wchar_block(data, length, dest); + + return static_cast<size_t>(end - dest); + } + + // convert to utf16 + if (encoding == encoding_utf16_be || encoding == encoding_utf16_le) + { + uint16_t* dest = r_u16; + + // convert to native utf16 + uint16_t* end = utf_decoder<utf16_writer>::decode_wchar_block(data, length, dest); + + // swap if necessary + xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be; + + if (native_encoding != encoding) convert_utf_endian_swap(dest, dest, static_cast<size_t>(end - dest)); + + return static_cast<size_t>(end - dest) * sizeof(uint16_t); + } + + // convert to utf32 + if (encoding == encoding_utf32_be || encoding == encoding_utf32_le) + { + uint32_t* dest = r_u32; + + // convert to native utf32 + uint32_t* end = utf_decoder<utf32_writer>::decode_wchar_block(data, length, dest); + + // swap if necessary + xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be; + + if (native_encoding != encoding) convert_utf_endian_swap(dest, dest, static_cast<size_t>(end - dest)); + + return static_cast<size_t>(end - dest) * sizeof(uint32_t); + } + + // convert to latin1 + if (encoding == encoding_latin1) + { + uint8_t* dest = r_u8; + uint8_t* end = utf_decoder<latin1_writer>::decode_wchar_block(data, length, dest); + + return static_cast<size_t>(end - dest); + } + + assert(!"Invalid encoding"); + return 0; + } +#else + PUGI__FN size_t get_valid_length(const char_t* data, size_t length) + { + assert(length > 4); + + for (size_t i = 1; i <= 4; ++i) + { + uint8_t ch = static_cast<uint8_t>(data[length - i]); + + // either a standalone character or a leading one + if ((ch & 0xc0) != 0x80) return length - i; + } + + // there are four non-leading characters at the end, sequence tail is broken so might as well process the whole chunk + return length; + } + + PUGI__FN size_t convert_buffer_output(char_t* /* r_char */, uint8_t* r_u8, uint16_t* r_u16, uint32_t* r_u32, const char_t* data, size_t length, xml_encoding encoding) + { + if (encoding == encoding_utf16_be || encoding == encoding_utf16_le) + { + uint16_t* dest = r_u16; + + // convert to native utf16 + uint16_t* end = utf_decoder<utf16_writer>::decode_utf8_block(reinterpret_cast<const uint8_t*>(data), length, dest); + + // swap if necessary + xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be; + + if (native_encoding != encoding) convert_utf_endian_swap(dest, dest, static_cast<size_t>(end - dest)); + + return static_cast<size_t>(end - dest) * sizeof(uint16_t); + } + + if (encoding == encoding_utf32_be || encoding == encoding_utf32_le) + { + uint32_t* dest = r_u32; + + // convert to native utf32 + uint32_t* end = utf_decoder<utf32_writer>::decode_utf8_block(reinterpret_cast<const uint8_t*>(data), length, dest); + + // swap if necessary + xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be; + + if (native_encoding != encoding) convert_utf_endian_swap(dest, dest, static_cast<size_t>(end - dest)); + + return static_cast<size_t>(end - dest) * sizeof(uint32_t); + } + + if (encoding == encoding_latin1) + { + uint8_t* dest = r_u8; + uint8_t* end = utf_decoder<latin1_writer>::decode_utf8_block(reinterpret_cast<const uint8_t*>(data), length, dest); + + return static_cast<size_t>(end - dest); + } + + assert(!"Invalid encoding"); + return 0; + } +#endif + + class xml_buffered_writer + { + xml_buffered_writer(const xml_buffered_writer&); + xml_buffered_writer& operator=(const xml_buffered_writer&); + + public: + xml_buffered_writer(xml_writer& writer_, xml_encoding user_encoding): writer(writer_), bufsize(0), encoding(get_write_encoding(user_encoding)) + { + PUGI__STATIC_ASSERT(bufcapacity >= 8); + } + + ~xml_buffered_writer() + { + flush(); + } + + void flush() + { + flush(buffer, bufsize); + bufsize = 0; + } + + void flush(const char_t* data, size_t size) + { + if (size == 0) return; + + // fast path, just write data + if (encoding == get_write_native_encoding()) + writer.write(data, size * sizeof(char_t)); + else + { + // convert chunk + size_t result = convert_buffer_output(scratch.data_char, scratch.data_u8, scratch.data_u16, scratch.data_u32, data, size, encoding); + assert(result <= sizeof(scratch)); + + // write data + writer.write(scratch.data_u8, result); + } + } + + void write(const char_t* data, size_t length) + { + if (bufsize + length > bufcapacity) + { + // flush the remaining buffer contents + flush(); + + // handle large chunks + if (length > bufcapacity) + { + if (encoding == get_write_native_encoding()) + { + // fast path, can just write data chunk + writer.write(data, length * sizeof(char_t)); + return; + } + + // need to convert in suitable chunks + while (length > bufcapacity) + { + // get chunk size by selecting such number of characters that are guaranteed to fit into scratch buffer + // and form a complete codepoint sequence (i.e. discard start of last codepoint if necessary) + size_t chunk_size = get_valid_length(data, bufcapacity); + + // convert chunk and write + flush(data, chunk_size); + + // iterate + data += chunk_size; + length -= chunk_size; + } + + // small tail is copied below + bufsize = 0; + } + } + + memcpy(buffer + bufsize, data, length * sizeof(char_t)); + bufsize += length; + } + + void write(const char_t* data) + { + write(data, strlength(data)); + } + + void write(char_t d0) + { + if (bufsize + 1 > bufcapacity) flush(); + + buffer[bufsize + 0] = d0; + bufsize += 1; + } + + void write(char_t d0, char_t d1) + { + if (bufsize + 2 > bufcapacity) flush(); + + buffer[bufsize + 0] = d0; + buffer[bufsize + 1] = d1; + bufsize += 2; + } + + void write(char_t d0, char_t d1, char_t d2) + { + if (bufsize + 3 > bufcapacity) flush(); + + buffer[bufsize + 0] = d0; + buffer[bufsize + 1] = d1; + buffer[bufsize + 2] = d2; + bufsize += 3; + } + + void write(char_t d0, char_t d1, char_t d2, char_t d3) + { + if (bufsize + 4 > bufcapacity) flush(); + + buffer[bufsize + 0] = d0; + buffer[bufsize + 1] = d1; + buffer[bufsize + 2] = d2; + buffer[bufsize + 3] = d3; + bufsize += 4; + } + + void write(char_t d0, char_t d1, char_t d2, char_t d3, char_t d4) + { + if (bufsize + 5 > bufcapacity) flush(); + + buffer[bufsize + 0] = d0; + buffer[bufsize + 1] = d1; + buffer[bufsize + 2] = d2; + buffer[bufsize + 3] = d3; + buffer[bufsize + 4] = d4; + bufsize += 5; + } + + void write(char_t d0, char_t d1, char_t d2, char_t d3, char_t d4, char_t d5) + { + if (bufsize + 6 > bufcapacity) flush(); + + buffer[bufsize + 0] = d0; + buffer[bufsize + 1] = d1; + buffer[bufsize + 2] = d2; + buffer[bufsize + 3] = d3; + buffer[bufsize + 4] = d4; + buffer[bufsize + 5] = d5; + bufsize += 6; + } + + // utf8 maximum expansion: x4 (-> utf32) + // utf16 maximum expansion: x2 (-> utf32) + // utf32 maximum expansion: x1 + enum + { + bufcapacitybytes = + #ifdef PUGIXML_MEMORY_OUTPUT_STACK + PUGIXML_MEMORY_OUTPUT_STACK + #else + 10240 + #endif + , + bufcapacity = bufcapacitybytes / (sizeof(char_t) + 4) + }; + + char_t buffer[bufcapacity]; + + union + { + uint8_t data_u8[4 * bufcapacity]; + uint16_t data_u16[2 * bufcapacity]; + uint32_t data_u32[bufcapacity]; + char_t data_char[bufcapacity]; + } scratch; + + xml_writer& writer; + size_t bufsize; + xml_encoding encoding; + }; + + PUGI__FN void text_output_escaped(xml_buffered_writer& writer, const char_t* s, chartypex_t type) + { + while (*s) + { + const char_t* prev = s; + + // While *s is a usual symbol + while (!PUGI__IS_CHARTYPEX(*s, type)) ++s; + + writer.write(prev, static_cast<size_t>(s - prev)); + + switch (*s) + { + case 0: break; + case '&': + writer.write('&', 'a', 'm', 'p', ';'); + ++s; + break; + case '<': + writer.write('&', 'l', 't', ';'); + ++s; + break; + case '>': + writer.write('&', 'g', 't', ';'); + ++s; + break; + case '"': + writer.write('&', 'q', 'u', 'o', 't', ';'); + ++s; + break; + default: // s is not a usual symbol + { + unsigned int ch = static_cast<unsigned int>(*s++); + assert(ch < 32); + + writer.write('&', '#', static_cast<char_t>((ch / 10) + '0'), static_cast<char_t>((ch % 10) + '0'), ';'); + } + } + } + } + + PUGI__FN void text_output(xml_buffered_writer& writer, const char_t* s, chartypex_t type, unsigned int flags) + { + if (flags & format_no_escapes) + writer.write(s); + else + text_output_escaped(writer, s, type); + } + + PUGI__FN void text_output_cdata(xml_buffered_writer& writer, const char_t* s) + { + do + { + writer.write('<', '!', '[', 'C', 'D'); + writer.write('A', 'T', 'A', '['); + + const char_t* prev = s; + + // look for ]]> sequence - we can't output it as is since it terminates CDATA + while (*s && !(s[0] == ']' && s[1] == ']' && s[2] == '>')) ++s; + + // skip ]] if we stopped at ]]>, > will go to the next CDATA section + if (*s) s += 2; + + writer.write(prev, static_cast<size_t>(s - prev)); + + writer.write(']', ']', '>'); + } + while (*s); + } + + PUGI__FN void node_output_attributes(xml_buffered_writer& writer, const xml_node& node, unsigned int flags) + { + const char_t* default_name = PUGIXML_TEXT(":anonymous"); + + for (xml_attribute a = node.first_attribute(); a; a = a.next_attribute()) + { + writer.write(' '); + writer.write(a.name()[0] ? a.name() : default_name); + writer.write('=', '"'); + + text_output(writer, a.value(), ctx_special_attr, flags); + + writer.write('"'); + } + } + + PUGI__FN void node_output(xml_buffered_writer& writer, const xml_node& node, const char_t* indent, unsigned int flags, unsigned int depth) + { + const char_t* default_name = PUGIXML_TEXT(":anonymous"); + + if ((flags & format_indent) != 0 && (flags & format_raw) == 0) + for (unsigned int i = 0; i < depth; ++i) writer.write(indent); + + switch (node.type()) + { + case node_document: + { + for (xml_node n = node.first_child(); n; n = n.next_sibling()) + node_output(writer, n, indent, flags, depth); + break; + } + + case node_element: + { + const char_t* name = node.name()[0] ? node.name() : default_name; + + writer.write('<'); + writer.write(name); + + node_output_attributes(writer, node, flags); + + if (flags & format_raw) + { + if (!node.first_child()) + writer.write(' ', '/', '>'); + else + { + writer.write('>'); + + for (xml_node n = node.first_child(); n; n = n.next_sibling()) + node_output(writer, n, indent, flags, depth + 1); + + writer.write('<', '/'); + writer.write(name); + writer.write('>'); + } + } + else if (!node.first_child()) + writer.write(' ', '/', '>', '\n'); + else if (node.first_child() == node.last_child() && (node.first_child().type() == node_pcdata || node.first_child().type() == node_cdata)) + { + writer.write('>'); + + if (node.first_child().type() == node_pcdata) + text_output(writer, node.first_child().value(), ctx_special_pcdata, flags); + else + text_output_cdata(writer, node.first_child().value()); + + writer.write('<', '/'); + writer.write(name); + writer.write('>', '\n'); + } + else + { + writer.write('>', '\n'); + + // <edit> + // Keep a <map>'s <key>s in line with itself + if ((flags & format_pretty_llsd) != 0 && strequal("map", name)) + { + for (xml_node n = node.first_child(); n; n = n.next_sibling()) + { + unsigned int child_depth = depth + 1; + if(node.type() == node_element) + { + const char_t* child_name = n.name()[0] ? n.name() : default_name; + if(strequal(child_name, "key")) + child_depth = depth; + } + node_output(writer, n, indent, flags, child_depth); + } + } + else + { + for (xml_node n = node.first_child(); n; n = n.next_sibling()) + node_output(writer, n, indent, flags, depth + 1); + } + // </edit> + if ((flags & format_indent) != 0 && (flags & format_raw) == 0) + for (unsigned int i = 0; i < depth; ++i) writer.write(indent); + + writer.write('<', '/'); + writer.write(name); + writer.write('>', '\n'); + } + + break; + } + + case node_pcdata: + text_output(writer, node.value(), ctx_special_pcdata, flags); + if ((flags & format_raw) == 0) writer.write('\n'); + break; + + case node_cdata: + text_output_cdata(writer, node.value()); + if ((flags & format_raw) == 0) writer.write('\n'); + break; + + case node_comment: + writer.write('<', '!', '-', '-'); + writer.write(node.value()); + writer.write('-', '-', '>'); + if ((flags & format_raw) == 0) writer.write('\n'); + break; + + case node_pi: + case node_declaration: + writer.write('<', '?'); + writer.write(node.name()[0] ? node.name() : default_name); + + if (node.type() == node_declaration) + { + node_output_attributes(writer, node, flags); + } + else if (node.value()[0]) + { + writer.write(' '); + writer.write(node.value()); + } + + writer.write('?', '>'); + if ((flags & format_raw) == 0) writer.write('\n'); + break; + + case node_doctype: + writer.write('<', '!', 'D', 'O', 'C'); + writer.write('T', 'Y', 'P', 'E'); + + if (node.value()[0]) + { + writer.write(' '); + writer.write(node.value()); + } + + writer.write('>'); + if ((flags & format_raw) == 0) writer.write('\n'); + break; + + default: + assert(!"Invalid node type"); + } + } + + inline bool has_declaration(const xml_node& node) + { + for (xml_node child = node.first_child(); child; child = child.next_sibling()) + { + xml_node_type type = child.type(); + + if (type == node_declaration) return true; + if (type == node_element) return false; + } + + return false; + } + + inline bool allow_insert_child(xml_node_type parent, xml_node_type child) + { + if (parent != node_document && parent != node_element) return false; + if (child == node_document || child == node_null) return false; + if (parent != node_document && (child == node_declaration || child == node_doctype)) return false; + + return true; + } + + PUGI__FN bool allow_move(const xml_node& parent, const xml_node& child) + { + // check that child can be a child of parent + if (!allow_insert_child(parent.type(), child.type())) + return false; + + // check that node is not moved between documents + if (parent.root() != child.root()) + return false; + + // check that new parent is not in the child subtree + xml_node cur = parent; + + while (cur) + { + if (cur == child) + return false; + + cur = cur.parent(); + } + + return true; + } + + PUGI__FN void recursive_copy_skip(xml_node& dest, const xml_node& source, const xml_node& skip) + { + assert(dest.type() == source.type()); + + switch (source.type()) + { + case node_element: + { + dest.set_name(source.name()); + + for (xml_attribute a = source.first_attribute(); a; a = a.next_attribute()) + dest.append_attribute(a.name()).set_value(a.value()); + + for (xml_node c = source.first_child(); c; c = c.next_sibling()) + { + if (c == skip) continue; + + xml_node cc = dest.append_child(c.type()); + assert(cc); + + recursive_copy_skip(cc, c, skip); + } + + break; + } + + case node_pcdata: + case node_cdata: + case node_comment: + case node_doctype: + dest.set_value(source.value()); + break; + + case node_pi: + dest.set_name(source.name()); + dest.set_value(source.value()); + break; + + case node_declaration: + { + dest.set_name(source.name()); + + for (xml_attribute a = source.first_attribute(); a; a = a.next_attribute()) + dest.append_attribute(a.name()).set_value(a.value()); + + break; + } + + default: + assert(!"Invalid node type"); + } + } + + inline bool is_text_node(xml_node_struct* node) + { + xml_node_type type = static_cast<xml_node_type>((node->header & impl::xml_memory_page_type_mask) + 1); + + return type == node_pcdata || type == node_cdata; + } + + // get value with conversion functions + PUGI__FN int get_integer_base(const char_t* value) + { + const char_t* s = value; + + while (PUGI__IS_CHARTYPE(*s, ct_space)) + s++; + + if (*s == '-') + s++; + + return (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) ? 16 : 10; + } + + PUGI__FN int get_value_int(const char_t* value, int def) + { + if (!value) return def; + + int base = get_integer_base(value); + + #ifdef PUGIXML_WCHAR_MODE + return static_cast<int>(wcstol(value, 0, base)); + #else + return static_cast<int>(strtol(value, 0, base)); + #endif + } + + PUGI__FN unsigned int get_value_uint(const char_t* value, unsigned int def) + { + if (!value) return def; + + int base = get_integer_base(value); + + #ifdef PUGIXML_WCHAR_MODE + return static_cast<unsigned int>(wcstoul(value, 0, base)); + #else + return static_cast<unsigned int>(strtoul(value, 0, base)); + #endif + } + + PUGI__FN double get_value_double(const char_t* value, double def) + { + if (!value) return def; + + #ifdef PUGIXML_WCHAR_MODE + return wcstod(value, 0); + #else + return strtod(value, 0); + #endif + } + + PUGI__FN float get_value_float(const char_t* value, float def) + { + if (!value) return def; + + #ifdef PUGIXML_WCHAR_MODE + return static_cast<float>(wcstod(value, 0)); + #else + return static_cast<float>(strtod(value, 0)); + #endif + } + + PUGI__FN bool get_value_bool(const char_t* value, bool def) + { + if (!value) return def; + + // only look at first char + char_t first = *value; + + // 1*, t* (true), T* (True), y* (yes), Y* (YES) + return (first == '1' || first == 't' || first == 'T' || first == 'y' || first == 'Y'); + } + +#ifdef PUGIXML_HAS_LONG_LONG + PUGI__FN long long get_value_llong(const char_t* value, long long def) + { + if (!value) return def; + + int base = get_integer_base(value); + + #ifdef PUGIXML_WCHAR_MODE + #ifdef PUGI__MSVC_CRT_VERSION + return _wcstoi64(value, 0, base); + #else + return wcstoll(value, 0, base); + #endif + #else + #ifdef PUGI__MSVC_CRT_VERSION + return _strtoi64(value, 0, base); + #else + return strtoll(value, 0, base); + #endif + #endif + } + + PUGI__FN unsigned long long get_value_ullong(const char_t* value, unsigned long long def) + { + if (!value) return def; + + int base = get_integer_base(value); + + #ifdef PUGIXML_WCHAR_MODE + #ifdef PUGI__MSVC_CRT_VERSION + return _wcstoui64(value, 0, base); + #else + return wcstoull(value, 0, base); + #endif + #else + #ifdef PUGI__MSVC_CRT_VERSION + return _strtoui64(value, 0, base); + #else + return strtoull(value, 0, base); + #endif + #endif + } +#endif + + // set value with conversion functions + PUGI__FN bool set_value_buffer(char_t*& dest, uintptr_t& header, uintptr_t header_mask, char (&buf)[128]) + { + #ifdef PUGIXML_WCHAR_MODE + char_t wbuf[128]; + impl::widen_ascii(wbuf, buf); + + return strcpy_insitu(dest, header, header_mask, wbuf); + #else + return strcpy_insitu(dest, header, header_mask, buf); + #endif + } + + PUGI__FN bool set_value_convert(char_t*& dest, uintptr_t& header, uintptr_t header_mask, int value) + { + char buf[128]; + sprintf(buf, "%d", value); + + return set_value_buffer(dest, header, header_mask, buf); + } + + PUGI__FN bool set_value_convert(char_t*& dest, uintptr_t& header, uintptr_t header_mask, unsigned int value) + { + char buf[128]; + sprintf(buf, "%u", value); + + return set_value_buffer(dest, header, header_mask, buf); + } + + PUGI__FN bool set_value_convert(char_t*& dest, uintptr_t& header, uintptr_t header_mask, double value) + { + char buf[128]; + sprintf(buf, "%g", value); + + return set_value_buffer(dest, header, header_mask, buf); + } + + PUGI__FN bool set_value_convert(char_t*& dest, uintptr_t& header, uintptr_t header_mask, bool value) + { + return strcpy_insitu(dest, header, header_mask, value ? PUGIXML_TEXT("true") : PUGIXML_TEXT("false")); + } + +#ifdef PUGIXML_HAS_LONG_LONG + PUGI__FN bool set_value_convert(char_t*& dest, uintptr_t& header, uintptr_t header_mask, long long value) + { + char buf[128]; + sprintf(buf, "%lld", value); + + return set_value_buffer(dest, header, header_mask, buf); + } + + PUGI__FN bool set_value_convert(char_t*& dest, uintptr_t& header, uintptr_t header_mask, unsigned long long value) + { + char buf[128]; + sprintf(buf, "%llu", value); + + return set_value_buffer(dest, header, header_mask, buf); + } +#endif + + // we need to get length of entire file to load it in memory; the only (relatively) sane way to do it is via seek/tell trick + PUGI__FN xml_parse_status get_file_size(FILE* file, size_t& out_result) + { + #if defined(PUGI__MSVC_CRT_VERSION) && PUGI__MSVC_CRT_VERSION >= 1400 && !defined(_WIN32_WCE) + // there are 64-bit versions of fseek/ftell, let's use them + typedef __int64 length_type; + + _fseeki64(file, 0, SEEK_END); + length_type length = _ftelli64(file); + _fseeki64(file, 0, SEEK_SET); + #elif defined(__MINGW32__) && !defined(__NO_MINGW_LFS) && !defined(__STRICT_ANSI__) + // there are 64-bit versions of fseek/ftell, let's use them + typedef off64_t length_type; + + fseeko64(file, 0, SEEK_END); + length_type length = ftello64(file); + fseeko64(file, 0, SEEK_SET); + #else + // if this is a 32-bit OS, long is enough; if this is a unix system, long is 64-bit, which is enough; otherwise we can't do anything anyway. + typedef long length_type; + + fseek(file, 0, SEEK_END); + length_type length = ftell(file); + fseek(file, 0, SEEK_SET); + #endif + + // check for I/O errors + if (length < 0) return status_io_error; + + // check for overflow + size_t result = static_cast<size_t>(length); + + if (static_cast<length_type>(result) != length) return status_out_of_memory; + + // finalize + out_result = result; + + return status_ok; + } + + PUGI__FN size_t zero_terminate_buffer(void* buffer, size_t size, xml_encoding encoding) + { + // We only need to zero-terminate if encoding conversion does not do it for us + #ifdef PUGIXML_WCHAR_MODE + xml_encoding wchar_encoding = get_wchar_encoding(); + + if (encoding == wchar_encoding || need_endian_swap_utf(encoding, wchar_encoding)) + { + size_t length = size / sizeof(char_t); + + static_cast<char_t*>(buffer)[length] = 0; + return (length + 1) * sizeof(char_t); + } + #else + if (encoding == encoding_utf8) + { + static_cast<char*>(buffer)[size] = 0; + return size + 1; + } + #endif + + return size; + } + + PUGI__FN xml_parse_result load_file_impl(xml_document& doc, FILE* file, unsigned int options, xml_encoding encoding) + { + if (!file) return make_parse_result(status_file_not_found); + + // get file size (can result in I/O errors) + size_t size = 0; + xml_parse_status size_status = get_file_size(file, size); + + if (size_status != status_ok) + { + fclose(file); + return make_parse_result(size_status); + } + + size_t max_suffix_size = sizeof(char_t); + + // allocate buffer for the whole file + char* contents = static_cast<char*>(xml_memory::allocate(size + max_suffix_size)); + + if (!contents) + { + fclose(file); + return make_parse_result(status_out_of_memory); + } + + // read file in memory + size_t read_size = fread(contents, 1, size, file); + fclose(file); + + if (read_size != size) + { + xml_memory::deallocate(contents); + return make_parse_result(status_io_error); + } + + xml_encoding real_encoding = get_buffer_encoding(encoding, contents, size); + + return doc.load_buffer_inplace_own(contents, zero_terminate_buffer(contents, size, real_encoding), options, real_encoding); + } + +#ifndef PUGIXML_NO_STL + template <typename T> struct xml_stream_chunk + { + static xml_stream_chunk* create() + { + void* memory = xml_memory::allocate(sizeof(xml_stream_chunk)); + + return new (memory) xml_stream_chunk(); + } + + static void destroy(void* ptr) + { + xml_stream_chunk* chunk = static_cast<xml_stream_chunk*>(ptr); + + // free chunk chain + while (chunk) + { + xml_stream_chunk* next = chunk->next; + xml_memory::deallocate(chunk); + chunk = next; + } + } + + xml_stream_chunk(): next(0), size(0) + { + } + + xml_stream_chunk* next; + size_t size; + + T data[xml_memory_page_size / sizeof(T)]; + }; + + template <typename T> PUGI__FN xml_parse_status load_stream_data_noseek(std::basic_istream<T>& stream, void** out_buffer, size_t* out_size) + { + buffer_holder chunks(0, xml_stream_chunk<T>::destroy); + + // read file to a chunk list + size_t total = 0; + xml_stream_chunk<T>* last = 0; + + while (!stream.eof()) + { + // allocate new chunk + xml_stream_chunk<T>* chunk = xml_stream_chunk<T>::create(); + if (!chunk) return status_out_of_memory; + + // append chunk to list + if (last) last = last->next = chunk; + else chunks.data = last = chunk; + + // read data to chunk + stream.read(chunk->data, static_cast<std::streamsize>(sizeof(chunk->data) / sizeof(T))); + chunk->size = static_cast<size_t>(stream.gcount()) * sizeof(T); + + // read may set failbit | eofbit in case gcount() is less than read length, so check for other I/O errors + if (stream.bad() || (!stream.eof() && stream.fail())) return status_io_error; + + // guard against huge files (chunk size is small enough to make this overflow check work) + if (total + chunk->size < total) return status_out_of_memory; + total += chunk->size; + } + + size_t max_suffix_size = sizeof(char_t); + + // copy chunk list to a contiguous buffer + char* buffer = static_cast<char*>(xml_memory::allocate(total + max_suffix_size)); + if (!buffer) return status_out_of_memory; + + char* write = buffer; + + for (xml_stream_chunk<T>* chunk = static_cast<xml_stream_chunk<T>*>(chunks.data); chunk; chunk = chunk->next) + { + assert(write + chunk->size <= buffer + total); + memcpy(write, chunk->data, chunk->size); + write += chunk->size; + } + + assert(write == buffer + total); + + // return buffer + *out_buffer = buffer; + *out_size = total; + + return status_ok; + } + + template <typename T> PUGI__FN xml_parse_status load_stream_data_seek(std::basic_istream<T>& stream, void** out_buffer, size_t* out_size) + { + // get length of remaining data in stream + typename std::basic_istream<T>::pos_type pos = stream.tellg(); + stream.seekg(0, std::ios::end); + std::streamoff length = stream.tellg() - pos; + stream.seekg(pos); + + if (stream.fail() || pos < 0) return status_io_error; + + // guard against huge files + size_t read_length = static_cast<size_t>(length); + + if (static_cast<std::streamsize>(read_length) != length || length < 0) return status_out_of_memory; + + size_t max_suffix_size = sizeof(char_t); + + // read stream data into memory (guard against stream exceptions with buffer holder) + buffer_holder buffer(xml_memory::allocate(read_length * sizeof(T) + max_suffix_size), xml_memory::deallocate); + if (!buffer.data) return status_out_of_memory; + + stream.read(static_cast<T*>(buffer.data), static_cast<std::streamsize>(read_length)); + + // read may set failbit | eofbit in case gcount() is less than read_length (i.e. line ending conversion), so check for other I/O errors + if (stream.bad() || (!stream.eof() && stream.fail())) return status_io_error; + + // return buffer + size_t actual_length = static_cast<size_t>(stream.gcount()); + assert(actual_length <= read_length); + + *out_buffer = buffer.release(); + *out_size = actual_length * sizeof(T); + + return status_ok; + } + + template <typename T> PUGI__FN xml_parse_result load_stream_impl(xml_document& doc, std::basic_istream<T>& stream, unsigned int options, xml_encoding encoding) + { + void* buffer = 0; + size_t size = 0; + xml_parse_status status = status_ok; + + // if stream has an error bit set, bail out (otherwise tellg() can fail and we'll clear error bits) + if (stream.fail()) return make_parse_result(status_io_error); + + // load stream to memory (using seek-based implementation if possible, since it's faster and takes less memory) + if (stream.tellg() < 0) + { + stream.clear(); // clear error flags that could be set by a failing tellg + status = load_stream_data_noseek(stream, &buffer, &size); + } + else + status = load_stream_data_seek(stream, &buffer, &size); + + if (status != status_ok) return make_parse_result(status); + + xml_encoding real_encoding = get_buffer_encoding(encoding, buffer, size); + + return doc.load_buffer_inplace_own(buffer, zero_terminate_buffer(buffer, size, real_encoding), options, real_encoding); + } +#endif + +#if defined(PUGI__MSVC_CRT_VERSION) || defined(__BORLANDC__) || (defined(__MINGW32__) && !defined(__STRICT_ANSI__)) + PUGI__FN FILE* open_file_wide(const wchar_t* path, const wchar_t* mode) + { + return _wfopen(path, mode); + } +#else + PUGI__FN char* convert_path_heap(const wchar_t* str) + { + assert(str); + + // first pass: get length in utf8 characters + size_t length = strlength_wide(str); + size_t size = as_utf8_begin(str, length); + + // allocate resulting string + char* result = static_cast<char*>(xml_memory::allocate(size + 1)); + if (!result) return 0; + + // second pass: convert to utf8 + as_utf8_end(result, size, str, length); + + return result; + } + + PUGI__FN FILE* open_file_wide(const wchar_t* path, const wchar_t* mode) + { + // there is no standard function to open wide paths, so our best bet is to try utf8 path + char* path_utf8 = convert_path_heap(path); + if (!path_utf8) return 0; + + // convert mode to ASCII (we mirror _wfopen interface) + char mode_ascii[4] = {0}; + for (size_t i = 0; mode[i]; ++i) mode_ascii[i] = static_cast<char>(mode[i]); + + // try to open the utf8 path + FILE* result = fopen(path_utf8, mode_ascii); + + // free dummy buffer + xml_memory::deallocate(path_utf8); + + return result; + } +#endif + + PUGI__FN bool save_file_impl(const xml_document& doc, FILE* file, const char_t* indent, unsigned int flags, xml_encoding encoding) + { + if (!file) return false; + + xml_writer_file writer(file); + doc.save(writer, indent, flags, encoding); + + int result = ferror(file); + + fclose(file); + + return result == 0; + } + + PUGI__FN xml_parse_result load_buffer_impl(xml_document_struct* doc, xml_node_struct* root, void* contents, size_t size, unsigned int options, xml_encoding encoding, bool is_mutable, bool own, char_t** out_buffer) + { + // check input buffer + assert(contents || size == 0); + + // get actual encoding + xml_encoding buffer_encoding = impl::get_buffer_encoding(encoding, contents, size); + + // get private buffer + char_t* buffer = 0; + size_t length = 0; + + if (!impl::convert_buffer(buffer, length, buffer_encoding, contents, size, is_mutable)) return impl::make_parse_result(status_out_of_memory); + + // delete original buffer if we performed a conversion + if (own && buffer != contents && contents) impl::xml_memory::deallocate(contents); + + // store buffer for offset_debug + doc->buffer = buffer; + + // parse + xml_parse_result res = impl::xml_parser::parse(buffer, length, doc, root, options); + + // remember encoding + res.encoding = buffer_encoding; + + // grab onto buffer if it's our buffer, user is responsible for deallocating contents himself + if (own || buffer != contents) *out_buffer = buffer; + + return res; + } +PUGI__NS_END + +namespace pugi +{ + PUGI__FN xml_writer_file::xml_writer_file(void* file_): file(file_) + { + } + + PUGI__FN void xml_writer_file::write(const void* data, size_t size) + { + size_t result = fwrite(data, 1, size, static_cast<FILE*>(file)); + (void)!result; // unfortunately we can't do proper error handling here + } + +#ifndef PUGIXML_NO_STL + PUGI__FN xml_writer_stream::xml_writer_stream(std::basic_ostream<char, std::char_traits<char> >& stream): narrow_stream(&stream), wide_stream(0) + { + } + + PUGI__FN xml_writer_stream::xml_writer_stream(std::basic_ostream<wchar_t, std::char_traits<wchar_t> >& stream): narrow_stream(0), wide_stream(&stream) + { + } + + PUGI__FN void xml_writer_stream::write(const void* data, size_t size) + { + if (narrow_stream) + { + assert(!wide_stream); + narrow_stream->write(reinterpret_cast<const char*>(data), static_cast<std::streamsize>(size)); + } + else + { + assert(wide_stream); + assert(size % sizeof(wchar_t) == 0); + + wide_stream->write(reinterpret_cast<const wchar_t*>(data), static_cast<std::streamsize>(size / sizeof(wchar_t))); + } + } +#endif + + PUGI__FN xml_tree_walker::xml_tree_walker(): _depth(0) + { + } + + PUGI__FN xml_tree_walker::~xml_tree_walker() + { + } + + PUGI__FN int xml_tree_walker::depth() const + { + return _depth; + } + + PUGI__FN bool xml_tree_walker::begin(xml_node&) + { + return true; + } + + PUGI__FN bool xml_tree_walker::end(xml_node&) + { + return true; + } + + PUGI__FN xml_attribute::xml_attribute(): _attr(0) + { + } + + PUGI__FN xml_attribute::xml_attribute(xml_attribute_struct* attr): _attr(attr) + { + } + + PUGI__FN static void unspecified_bool_xml_attribute(xml_attribute***) + { + } + + PUGI__FN xml_attribute::operator xml_attribute::unspecified_bool_type() const + { + return _attr ? unspecified_bool_xml_attribute : 0; + } + + PUGI__FN bool xml_attribute::operator!() const + { + return !_attr; + } + + PUGI__FN bool xml_attribute::operator==(const xml_attribute& r) const + { + return (_attr == r._attr); + } + + PUGI__FN bool xml_attribute::operator!=(const xml_attribute& r) const + { + return (_attr != r._attr); + } + + PUGI__FN bool xml_attribute::operator<(const xml_attribute& r) const + { + return (_attr < r._attr); + } + + PUGI__FN bool xml_attribute::operator>(const xml_attribute& r) const + { + return (_attr > r._attr); + } + + PUGI__FN bool xml_attribute::operator<=(const xml_attribute& r) const + { + return (_attr <= r._attr); + } + + PUGI__FN bool xml_attribute::operator>=(const xml_attribute& r) const + { + return (_attr >= r._attr); + } + + PUGI__FN xml_attribute xml_attribute::next_attribute() const + { + return _attr ? xml_attribute(_attr->next_attribute) : xml_attribute(); + } + + PUGI__FN xml_attribute xml_attribute::previous_attribute() const + { + return _attr && _attr->prev_attribute_c->next_attribute ? xml_attribute(_attr->prev_attribute_c) : xml_attribute(); + } + + PUGI__FN const char_t* xml_attribute::as_string(const char_t* def) const + { + return (_attr && _attr->value) ? _attr->value : def; + } + + PUGI__FN int xml_attribute::as_int(int def) const + { + return impl::get_value_int(_attr ? _attr->value : 0, def); + } + + PUGI__FN unsigned int xml_attribute::as_uint(unsigned int def) const + { + return impl::get_value_uint(_attr ? _attr->value : 0, def); + } + + PUGI__FN double xml_attribute::as_double(double def) const + { + return impl::get_value_double(_attr ? _attr->value : 0, def); + } + + PUGI__FN float xml_attribute::as_float(float def) const + { + return impl::get_value_float(_attr ? _attr->value : 0, def); + } + + PUGI__FN bool xml_attribute::as_bool(bool def) const + { + return impl::get_value_bool(_attr ? _attr->value : 0, def); + } + +#ifdef PUGIXML_HAS_LONG_LONG + PUGI__FN long long xml_attribute::as_llong(long long def) const + { + return impl::get_value_llong(_attr ? _attr->value : 0, def); + } + + PUGI__FN unsigned long long xml_attribute::as_ullong(unsigned long long def) const + { + return impl::get_value_ullong(_attr ? _attr->value : 0, def); + } +#endif + + PUGI__FN bool xml_attribute::empty() const + { + return !_attr; + } + + PUGI__FN const char_t* xml_attribute::name() const + { + return (_attr && _attr->name) ? _attr->name : PUGIXML_TEXT(""); + } + + PUGI__FN const char_t* xml_attribute::value() const + { + return (_attr && _attr->value) ? _attr->value : PUGIXML_TEXT(""); + } + + PUGI__FN size_t xml_attribute::hash_value() const + { + return static_cast<size_t>(reinterpret_cast<uintptr_t>(_attr) / sizeof(xml_attribute_struct)); + } + + PUGI__FN xml_attribute_struct* xml_attribute::internal_object() const + { + return _attr; + } + + PUGI__FN xml_attribute& xml_attribute::operator=(const char_t* rhs) + { + set_value(rhs); + return *this; + } + + PUGI__FN xml_attribute& xml_attribute::operator=(int rhs) + { + set_value(rhs); + return *this; + } + + PUGI__FN xml_attribute& xml_attribute::operator=(unsigned int rhs) + { + set_value(rhs); + return *this; + } + + PUGI__FN xml_attribute& xml_attribute::operator=(double rhs) + { + set_value(rhs); + return *this; + } + + PUGI__FN xml_attribute& xml_attribute::operator=(bool rhs) + { + set_value(rhs); + return *this; + } + +#ifdef PUGIXML_HAS_LONG_LONG + PUGI__FN xml_attribute& xml_attribute::operator=(long long rhs) + { + set_value(rhs); + return *this; + } + + PUGI__FN xml_attribute& xml_attribute::operator=(unsigned long long rhs) + { + set_value(rhs); + return *this; + } +#endif + + PUGI__FN bool xml_attribute::set_name(const char_t* rhs) + { + if (!_attr) return false; + + return impl::strcpy_insitu(_attr->name, _attr->header, impl::xml_memory_page_name_allocated_mask, rhs); + } + + PUGI__FN bool xml_attribute::set_value(const char_t* rhs) + { + if (!_attr) return false; + + return impl::strcpy_insitu(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs); + } + + PUGI__FN bool xml_attribute::set_value(int rhs) + { + if (!_attr) return false; + + return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs); + } + + PUGI__FN bool xml_attribute::set_value(unsigned int rhs) + { + if (!_attr) return false; + + return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs); + } + + PUGI__FN bool xml_attribute::set_value(double rhs) + { + if (!_attr) return false; + + return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs); + } + + PUGI__FN bool xml_attribute::set_value(bool rhs) + { + if (!_attr) return false; + + return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs); + } + +#ifdef PUGIXML_HAS_LONG_LONG + PUGI__FN bool xml_attribute::set_value(long long rhs) + { + if (!_attr) return false; + + return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs); + } + + PUGI__FN bool xml_attribute::set_value(unsigned long long rhs) + { + if (!_attr) return false; + + return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs); + } +#endif + +#ifdef __BORLANDC__ + PUGI__FN bool operator&&(const xml_attribute& lhs, bool rhs) + { + return (bool)lhs && rhs; + } + + PUGI__FN bool operator||(const xml_attribute& lhs, bool rhs) + { + return (bool)lhs || rhs; + } +#endif + + PUGI__FN xml_node::xml_node(): _root(0) + { + } + + PUGI__FN xml_node::xml_node(xml_node_struct* p): _root(p) + { + } + + PUGI__FN static void unspecified_bool_xml_node(xml_node***) + { + } + + PUGI__FN xml_node::operator xml_node::unspecified_bool_type() const + { + return _root ? unspecified_bool_xml_node : 0; + } + + PUGI__FN bool xml_node::operator!() const + { + return !_root; + } + + PUGI__FN xml_node::iterator xml_node::begin() const + { + return iterator(_root ? _root->first_child : 0, _root); + } + + PUGI__FN xml_node::iterator xml_node::end() const + { + return iterator(0, _root); + } + + PUGI__FN xml_node::attribute_iterator xml_node::attributes_begin() const + { + return attribute_iterator(_root ? _root->first_attribute : 0, _root); + } + + PUGI__FN xml_node::attribute_iterator xml_node::attributes_end() const + { + return attribute_iterator(0, _root); + } + + PUGI__FN xml_object_range<xml_node_iterator> xml_node::children() const + { + return xml_object_range<xml_node_iterator>(begin(), end()); + } + + PUGI__FN xml_object_range<xml_named_node_iterator> xml_node::children(const char_t* name_) const + { + return xml_object_range<xml_named_node_iterator>(xml_named_node_iterator(child(name_)._root, _root, name_), xml_named_node_iterator(0, _root, name_)); + } + + PUGI__FN xml_object_range<xml_attribute_iterator> xml_node::attributes() const + { + return xml_object_range<xml_attribute_iterator>(attributes_begin(), attributes_end()); + } + + PUGI__FN bool xml_node::operator==(const xml_node& r) const + { + return (_root == r._root); + } + + PUGI__FN bool xml_node::operator!=(const xml_node& r) const + { + return (_root != r._root); + } + + PUGI__FN bool xml_node::operator<(const xml_node& r) const + { + return (_root < r._root); + } + + PUGI__FN bool xml_node::operator>(const xml_node& r) const + { + return (_root > r._root); + } + + PUGI__FN bool xml_node::operator<=(const xml_node& r) const + { + return (_root <= r._root); + } + + PUGI__FN bool xml_node::operator>=(const xml_node& r) const + { + return (_root >= r._root); + } + + PUGI__FN bool xml_node::empty() const + { + return !_root; + } + + PUGI__FN const char_t* xml_node::name() const + { + return (_root && _root->name) ? _root->name : PUGIXML_TEXT(""); + } + + PUGI__FN xml_node_type xml_node::type() const + { + return _root ? static_cast<xml_node_type>((_root->header & impl::xml_memory_page_type_mask) + 1) : node_null; + } + + PUGI__FN const char_t* xml_node::value() const + { + return (_root && _root->value) ? _root->value : PUGIXML_TEXT(""); + } + + PUGI__FN xml_node xml_node::child(const char_t* name_) const + { + if (!_root) return xml_node(); + + for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) + if (i->name && impl::strequal(name_, i->name)) return xml_node(i); + + return xml_node(); + } + + PUGI__FN xml_attribute xml_node::attribute(const char_t* name_) const + { + if (!_root) return xml_attribute(); + + for (xml_attribute_struct* i = _root->first_attribute; i; i = i->next_attribute) + if (i->name && impl::strequal(name_, i->name)) + return xml_attribute(i); + + return xml_attribute(); + } + + PUGI__FN xml_node xml_node::next_sibling(const char_t* name_) const + { + if (!_root) return xml_node(); + + for (xml_node_struct* i = _root->next_sibling; i; i = i->next_sibling) + if (i->name && impl::strequal(name_, i->name)) return xml_node(i); + + return xml_node(); + } + + PUGI__FN xml_node xml_node::next_sibling() const + { + if (!_root) return xml_node(); + + if (_root->next_sibling) return xml_node(_root->next_sibling); + else return xml_node(); + } + + PUGI__FN xml_node xml_node::previous_sibling(const char_t* name_) const + { + if (!_root) return xml_node(); + + for (xml_node_struct* i = _root->prev_sibling_c; i->next_sibling; i = i->prev_sibling_c) + if (i->name && impl::strequal(name_, i->name)) return xml_node(i); + + return xml_node(); + } + + PUGI__FN xml_node xml_node::previous_sibling() const + { + if (!_root) return xml_node(); + + if (_root->prev_sibling_c->next_sibling) return xml_node(_root->prev_sibling_c); + else return xml_node(); + } + + PUGI__FN xml_node xml_node::parent() const + { + return _root ? xml_node(_root->parent) : xml_node(); + } + + PUGI__FN xml_node xml_node::root() const + { + if (!_root) return xml_node(); + + impl::xml_memory_page* page = reinterpret_cast<impl::xml_memory_page*>(_root->header & impl::xml_memory_page_pointer_mask); + + return xml_node(static_cast<impl::xml_document_struct*>(page->allocator)); + } + + PUGI__FN xml_text xml_node::text() const + { + return xml_text(_root); + } + + PUGI__FN const char_t* xml_node::child_value() const + { + if (!_root) return PUGIXML_TEXT(""); + + for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) + if (i->value && impl::is_text_node(i)) + return i->value; + + return PUGIXML_TEXT(""); + } + + PUGI__FN const char_t* xml_node::child_value(const char_t* name_) const + { + return child(name_).child_value(); + } + + PUGI__FN xml_attribute xml_node::first_attribute() const + { + return _root ? xml_attribute(_root->first_attribute) : xml_attribute(); + } + + PUGI__FN xml_attribute xml_node::last_attribute() const + { + return _root && _root->first_attribute ? xml_attribute(_root->first_attribute->prev_attribute_c) : xml_attribute(); + } + + PUGI__FN xml_node xml_node::first_child() const + { + return _root ? xml_node(_root->first_child) : xml_node(); + } + + PUGI__FN xml_node xml_node::last_child() const + { + return _root && _root->first_child ? xml_node(_root->first_child->prev_sibling_c) : xml_node(); + } + + PUGI__FN bool xml_node::set_name(const char_t* rhs) + { + switch (type()) + { + case node_pi: + case node_declaration: + case node_element: + return impl::strcpy_insitu(_root->name, _root->header, impl::xml_memory_page_name_allocated_mask, rhs); + + default: + return false; + } + } + + PUGI__FN bool xml_node::set_value(const char_t* rhs) + { + switch (type()) + { + case node_pi: + case node_cdata: + case node_pcdata: + case node_comment: + case node_doctype: + return impl::strcpy_insitu(_root->value, _root->header, impl::xml_memory_page_value_allocated_mask, rhs); + + default: + return false; + } + } + + PUGI__FN xml_attribute xml_node::append_attribute(const char_t* name_) + { + if (type() != node_element && type() != node_declaration) return xml_attribute(); + + xml_attribute a(impl::append_new_attribute(_root, impl::get_allocator(_root))); + + a.set_name(name_); + + return a; + } + + PUGI__FN xml_attribute xml_node::prepend_attribute(const char_t* name_) + { + if (type() != node_element && type() != node_declaration) return xml_attribute(); + + xml_attribute a(impl::allocate_attribute(impl::get_allocator(_root))); + if (!a) return xml_attribute(); + + xml_attribute_struct* head = _root->first_attribute; + + if (head) + { + a._attr->prev_attribute_c = head->prev_attribute_c; + head->prev_attribute_c = a._attr; + } + else + a._attr->prev_attribute_c = a._attr; + + a._attr->next_attribute = head; + _root->first_attribute = a._attr; + + a.set_name(name_); + + return a; + } + + PUGI__FN xml_attribute xml_node::insert_attribute_before(const char_t* name_, const xml_attribute& attr) + { + if ((type() != node_element && type() != node_declaration) || attr.empty()) return xml_attribute(); + + // check that attribute belongs to *this + xml_attribute_struct* cur = attr._attr; + + while (cur->prev_attribute_c->next_attribute) cur = cur->prev_attribute_c; + + if (cur != _root->first_attribute) return xml_attribute(); + + xml_attribute a(impl::allocate_attribute(impl::get_allocator(_root))); + if (!a) return xml_attribute(); + + if (attr._attr->prev_attribute_c->next_attribute) + attr._attr->prev_attribute_c->next_attribute = a._attr; + else + _root->first_attribute = a._attr; + + a._attr->prev_attribute_c = attr._attr->prev_attribute_c; + a._attr->next_attribute = attr._attr; + attr._attr->prev_attribute_c = a._attr; + + a.set_name(name_); + + return a; + } + + PUGI__FN xml_attribute xml_node::insert_attribute_after(const char_t* name_, const xml_attribute& attr) + { + if ((type() != node_element && type() != node_declaration) || attr.empty()) return xml_attribute(); + + // check that attribute belongs to *this + xml_attribute_struct* cur = attr._attr; + + while (cur->prev_attribute_c->next_attribute) cur = cur->prev_attribute_c; + + if (cur != _root->first_attribute) return xml_attribute(); + + xml_attribute a(impl::allocate_attribute(impl::get_allocator(_root))); + if (!a) return xml_attribute(); + + if (attr._attr->next_attribute) + attr._attr->next_attribute->prev_attribute_c = a._attr; + else + _root->first_attribute->prev_attribute_c = a._attr; + + a._attr->next_attribute = attr._attr->next_attribute; + a._attr->prev_attribute_c = attr._attr; + attr._attr->next_attribute = a._attr; + + a.set_name(name_); + + return a; + } + + PUGI__FN xml_attribute xml_node::append_copy(const xml_attribute& proto) + { + if (!proto) return xml_attribute(); + + xml_attribute result = append_attribute(proto.name()); + result.set_value(proto.value()); + + return result; + } + + PUGI__FN xml_attribute xml_node::prepend_copy(const xml_attribute& proto) + { + if (!proto) return xml_attribute(); + + xml_attribute result = prepend_attribute(proto.name()); + result.set_value(proto.value()); + + return result; + } + + PUGI__FN xml_attribute xml_node::insert_copy_after(const xml_attribute& proto, const xml_attribute& attr) + { + if (!proto) return xml_attribute(); + + xml_attribute result = insert_attribute_after(proto.name(), attr); + result.set_value(proto.value()); + + return result; + } + + PUGI__FN xml_attribute xml_node::insert_copy_before(const xml_attribute& proto, const xml_attribute& attr) + { + if (!proto) return xml_attribute(); + + xml_attribute result = insert_attribute_before(proto.name(), attr); + result.set_value(proto.value()); + + return result; + } + + PUGI__FN xml_node xml_node::append_child(xml_node_type type_) + { + if (!impl::allow_insert_child(this->type(), type_)) return xml_node(); + + xml_node n(impl::allocate_node(impl::get_allocator(_root), type_)); + if (!n) return xml_node(); + + impl::append_node(n._root, _root); + + if (type_ == node_declaration) n.set_name(PUGIXML_TEXT("xml")); + + return n; + } + + PUGI__FN xml_node xml_node::prepend_child(xml_node_type type_) + { + if (!impl::allow_insert_child(this->type(), type_)) return xml_node(); + + xml_node n(impl::allocate_node(impl::get_allocator(_root), type_)); + if (!n) return xml_node(); + + impl::prepend_node(n._root, _root); + + if (type_ == node_declaration) n.set_name(PUGIXML_TEXT("xml")); + + return n; + } + + PUGI__FN xml_node xml_node::insert_child_before(xml_node_type type_, const xml_node& node) + { + if (!impl::allow_insert_child(this->type(), type_)) return xml_node(); + if (!node._root || node._root->parent != _root) return xml_node(); + + xml_node n(impl::allocate_node(impl::get_allocator(_root), type_)); + if (!n) return xml_node(); + + impl::insert_node_before(n._root, node._root); + + if (type_ == node_declaration) n.set_name(PUGIXML_TEXT("xml")); + + return n; + } + + PUGI__FN xml_node xml_node::insert_child_after(xml_node_type type_, const xml_node& node) + { + if (!impl::allow_insert_child(this->type(), type_)) return xml_node(); + if (!node._root || node._root->parent != _root) return xml_node(); + + xml_node n(impl::allocate_node(impl::get_allocator(_root), type_)); + if (!n) return xml_node(); + + impl::insert_node_after(n._root, node._root); + + if (type_ == node_declaration) n.set_name(PUGIXML_TEXT("xml")); + + return n; + } + + PUGI__FN xml_node xml_node::append_child(const char_t* name_) + { + xml_node result = append_child(node_element); + + result.set_name(name_); + + return result; + } + + PUGI__FN xml_node xml_node::prepend_child(const char_t* name_) + { + xml_node result = prepend_child(node_element); + + result.set_name(name_); + + return result; + } + + PUGI__FN xml_node xml_node::insert_child_after(const char_t* name_, const xml_node& node) + { + xml_node result = insert_child_after(node_element, node); + + result.set_name(name_); + + return result; + } + + PUGI__FN xml_node xml_node::insert_child_before(const char_t* name_, const xml_node& node) + { + xml_node result = insert_child_before(node_element, node); + + result.set_name(name_); + + return result; + } + + PUGI__FN xml_node xml_node::append_copy(const xml_node& proto) + { + xml_node result = append_child(proto.type()); + + if (result) impl::recursive_copy_skip(result, proto, result); + + return result; + } + + PUGI__FN xml_node xml_node::prepend_copy(const xml_node& proto) + { + xml_node result = prepend_child(proto.type()); + + if (result) impl::recursive_copy_skip(result, proto, result); + + return result; + } + + PUGI__FN xml_node xml_node::insert_copy_after(const xml_node& proto, const xml_node& node) + { + xml_node result = insert_child_after(proto.type(), node); + + if (result) impl::recursive_copy_skip(result, proto, result); + + return result; + } + + PUGI__FN xml_node xml_node::insert_copy_before(const xml_node& proto, const xml_node& node) + { + xml_node result = insert_child_before(proto.type(), node); + + if (result) impl::recursive_copy_skip(result, proto, result); + + return result; + } + + PUGI__FN xml_node xml_node::append_move(const xml_node& moved) + { + if (!impl::allow_move(*this, moved)) return xml_node(); + + impl::remove_node(moved._root); + impl::append_node(moved._root, _root); + + return moved; + } + + PUGI__FN xml_node xml_node::prepend_move(const xml_node& moved) + { + if (!impl::allow_move(*this, moved)) return xml_node(); + + impl::remove_node(moved._root); + impl::prepend_node(moved._root, _root); + + return moved; + } + + PUGI__FN xml_node xml_node::insert_move_after(const xml_node& moved, const xml_node& node) + { + if (!impl::allow_move(*this, moved)) return xml_node(); + if (!node._root || node._root->parent != _root) return xml_node(); + if (moved._root == node._root) return xml_node(); + + impl::remove_node(moved._root); + impl::insert_node_after(moved._root, node._root); + + return moved; + } + + PUGI__FN xml_node xml_node::insert_move_before(const xml_node& moved, const xml_node& node) + { + if (!impl::allow_move(*this, moved)) return xml_node(); + if (!node._root || node._root->parent != _root) return xml_node(); + if (moved._root == node._root) return xml_node(); + + impl::remove_node(moved._root); + impl::insert_node_before(moved._root, node._root); + + return moved; + } + + PUGI__FN bool xml_node::remove_attribute(const char_t* name_) + { + return remove_attribute(attribute(name_)); + } + + PUGI__FN bool xml_node::remove_attribute(const xml_attribute& a) + { + if (!_root || !a._attr) return false; + + // check that attribute belongs to *this + xml_attribute_struct* attr = a._attr; + + while (attr->prev_attribute_c->next_attribute) attr = attr->prev_attribute_c; + + if (attr != _root->first_attribute) return false; + + if (a._attr->next_attribute) a._attr->next_attribute->prev_attribute_c = a._attr->prev_attribute_c; + else if (_root->first_attribute) _root->first_attribute->prev_attribute_c = a._attr->prev_attribute_c; + + if (a._attr->prev_attribute_c->next_attribute) a._attr->prev_attribute_c->next_attribute = a._attr->next_attribute; + else _root->first_attribute = a._attr->next_attribute; + + impl::destroy_attribute(a._attr, impl::get_allocator(_root)); + + return true; + } + + PUGI__FN bool xml_node::remove_child(const char_t* name_) + { + return remove_child(child(name_)); + } + + PUGI__FN bool xml_node::remove_child(const xml_node& n) + { + if (!_root || !n._root || n._root->parent != _root) return false; + + impl::remove_node(n._root); + + impl::destroy_node(n._root, impl::get_allocator(_root)); + + return true; + } + + PUGI__FN xml_parse_result xml_node::append_buffer(const void* contents, size_t size, unsigned int options, xml_encoding encoding) + { + // append_buffer is only valid for elements/documents + if (!impl::allow_insert_child(type(), node_element)) return impl::make_parse_result(status_append_invalid_root); + + // get document node + impl::xml_document_struct* doc = static_cast<impl::xml_document_struct*>(root()._root); + assert(doc); + + // get extra buffer element (we'll store the document fragment buffer there so that we can deallocate it later) + impl::xml_memory_page* page = 0; + impl::xml_extra_buffer* extra = static_cast<impl::xml_extra_buffer*>(doc->allocate_memory(sizeof(impl::xml_extra_buffer), page)); + (void)page; + + if (!extra) return impl::make_parse_result(status_out_of_memory); + + // save name; name of the root has to be NULL before parsing - otherwise closing node mismatches will not be detected at the top level + char_t* rootname = _root->name; + _root->name = 0; + + // parse + char_t* buffer = 0; + xml_parse_result res = impl::load_buffer_impl(doc, _root, const_cast<void*>(contents), size, options, encoding, false, false, &buffer); + + // restore name + _root->name = rootname; + + // add extra buffer to the list + extra->buffer = buffer; + extra->next = doc->extra_buffers; + doc->extra_buffers = extra; + + return res; + } + + PUGI__FN xml_node xml_node::find_child_by_attribute(const char_t* name_, const char_t* attr_name, const char_t* attr_value) const + { + if (!_root) return xml_node(); + + for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) + if (i->name && impl::strequal(name_, i->name)) + { + for (xml_attribute_struct* a = i->first_attribute; a; a = a->next_attribute) + if (a->name && impl::strequal(attr_name, a->name) && impl::strequal(attr_value, a->value ? a->value : PUGIXML_TEXT(""))) + return xml_node(i); + } + + return xml_node(); + } + + PUGI__FN xml_node xml_node::find_child_by_attribute(const char_t* attr_name, const char_t* attr_value) const + { + if (!_root) return xml_node(); + + for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) + for (xml_attribute_struct* a = i->first_attribute; a; a = a->next_attribute) + if (a->name && impl::strequal(attr_name, a->name) && impl::strequal(attr_value, a->value ? a->value : PUGIXML_TEXT(""))) + return xml_node(i); + + return xml_node(); + } + +#ifndef PUGIXML_NO_STL + PUGI__FN string_t xml_node::path(char_t delimiter) const + { + xml_node cursor = *this; // Make a copy. + + string_t result = cursor.name(); + + while (cursor.parent()) + { + cursor = cursor.parent(); + + string_t temp = cursor.name(); + temp += delimiter; + temp += result; + result.swap(temp); + } + + return result; + } +#endif + + PUGI__FN xml_node xml_node::first_element_by_path(const char_t* path_, char_t delimiter) const + { + xml_node found = *this; // Current search context. + + if (!_root || !path_ || !path_[0]) return found; + + if (path_[0] == delimiter) + { + // Absolute path; e.g. '/foo/bar' + found = found.root(); + ++path_; + } + + const char_t* path_segment = path_; + + while (*path_segment == delimiter) ++path_segment; + + const char_t* path_segment_end = path_segment; + + while (*path_segment_end && *path_segment_end != delimiter) ++path_segment_end; + + if (path_segment == path_segment_end) return found; + + const char_t* next_segment = path_segment_end; + + while (*next_segment == delimiter) ++next_segment; + + if (*path_segment == '.' && path_segment + 1 == path_segment_end) + return found.first_element_by_path(next_segment, delimiter); + else if (*path_segment == '.' && *(path_segment+1) == '.' && path_segment + 2 == path_segment_end) + return found.parent().first_element_by_path(next_segment, delimiter); + else + { + for (xml_node_struct* j = found._root->first_child; j; j = j->next_sibling) + { + if (j->name && impl::strequalrange(j->name, path_segment, static_cast<size_t>(path_segment_end - path_segment))) + { + xml_node subsearch = xml_node(j).first_element_by_path(next_segment, delimiter); + + if (subsearch) return subsearch; + } + } + + return xml_node(); + } + } + + PUGI__FN bool xml_node::traverse(xml_tree_walker& walker) + { + walker._depth = -1; + + xml_node arg_begin = *this; + if (!walker.begin(arg_begin)) return false; + + xml_node cur = first_child(); + + if (cur) + { + ++walker._depth; + + do + { + xml_node arg_for_each = cur; + if (!walker.for_each(arg_for_each)) + return false; + + if (cur.first_child()) + { + ++walker._depth; + cur = cur.first_child(); + } + else if (cur.next_sibling()) + cur = cur.next_sibling(); + else + { + // Borland C++ workaround + while (!cur.next_sibling() && cur != *this && !cur.parent().empty()) + { + --walker._depth; + cur = cur.parent(); + } + + if (cur != *this) + cur = cur.next_sibling(); + } + } + while (cur && cur != *this); + } + + assert(walker._depth == -1); + + xml_node arg_end = *this; + return walker.end(arg_end); + } + + PUGI__FN size_t xml_node::hash_value() const + { + return static_cast<size_t>(reinterpret_cast<uintptr_t>(_root) / sizeof(xml_node_struct)); + } + + PUGI__FN xml_node_struct* xml_node::internal_object() const + { + return _root; + } + + PUGI__FN void xml_node::print(xml_writer& writer, const char_t* indent, unsigned int flags, xml_encoding encoding, unsigned int depth) const + { + if (!_root) return; + + impl::xml_buffered_writer buffered_writer(writer, encoding); + + impl::node_output(buffered_writer, *this, indent, flags, depth); + } + +#ifndef PUGIXML_NO_STL + PUGI__FN void xml_node::print(std::basic_ostream<char, std::char_traits<char> >& stream, const char_t* indent, unsigned int flags, xml_encoding encoding, unsigned int depth) const + { + xml_writer_stream writer(stream); + + print(writer, indent, flags, encoding, depth); + } + + PUGI__FN void xml_node::print(std::basic_ostream<wchar_t, std::char_traits<wchar_t> >& stream, const char_t* indent, unsigned int flags, unsigned int depth) const + { + xml_writer_stream writer(stream); + + print(writer, indent, flags, encoding_wchar, depth); + } +#endif + + PUGI__FN ptrdiff_t xml_node::offset_debug() const + { + xml_node_struct* r = root()._root; + + if (!r) return -1; + + const char_t* buffer = static_cast<impl::xml_document_struct*>(r)->buffer; + + if (!buffer) return -1; + + switch (type()) + { + case node_document: + return 0; + + case node_element: + case node_declaration: + case node_pi: + return (_root->header & impl::xml_memory_page_name_allocated_mask) ? -1 : _root->name - buffer; + + case node_pcdata: + case node_cdata: + case node_comment: + case node_doctype: + return (_root->header & impl::xml_memory_page_value_allocated_mask) ? -1 : _root->value - buffer; + + default: + return -1; + } + } + +#ifdef __BORLANDC__ + PUGI__FN bool operator&&(const xml_node& lhs, bool rhs) + { + return (bool)lhs && rhs; + } + + PUGI__FN bool operator||(const xml_node& lhs, bool rhs) + { + return (bool)lhs || rhs; + } +#endif + + PUGI__FN xml_text::xml_text(xml_node_struct* root): _root(root) + { + } + + PUGI__FN xml_node_struct* xml_text::_data() const + { + if (!_root || impl::is_text_node(_root)) return _root; + + for (xml_node_struct* node = _root->first_child; node; node = node->next_sibling) + if (impl::is_text_node(node)) + return node; + + return 0; + } + + PUGI__FN xml_node_struct* xml_text::_data_new() + { + xml_node_struct* d = _data(); + if (d) return d; + + return xml_node(_root).append_child(node_pcdata).internal_object(); + } + + PUGI__FN xml_text::xml_text(): _root(0) + { + } + + PUGI__FN static void unspecified_bool_xml_text(xml_text***) + { + } + + PUGI__FN xml_text::operator xml_text::unspecified_bool_type() const + { + return _data() ? unspecified_bool_xml_text : 0; + } + + PUGI__FN bool xml_text::operator!() const + { + return !_data(); + } + + PUGI__FN bool xml_text::empty() const + { + return _data() == 0; + } + + PUGI__FN const char_t* xml_text::get() const + { + xml_node_struct* d = _data(); + + return (d && d->value) ? d->value : PUGIXML_TEXT(""); + } + + PUGI__FN const char_t* xml_text::as_string(const char_t* def) const + { + xml_node_struct* d = _data(); + + return (d && d->value) ? d->value : def; + } + + PUGI__FN int xml_text::as_int(int def) const + { + xml_node_struct* d = _data(); + + return impl::get_value_int(d ? d->value : 0, def); + } + + PUGI__FN unsigned int xml_text::as_uint(unsigned int def) const + { + xml_node_struct* d = _data(); + + return impl::get_value_uint(d ? d->value : 0, def); + } + + PUGI__FN double xml_text::as_double(double def) const + { + xml_node_struct* d = _data(); + + return impl::get_value_double(d ? d->value : 0, def); + } + + PUGI__FN float xml_text::as_float(float def) const + { + xml_node_struct* d = _data(); + + return impl::get_value_float(d ? d->value : 0, def); + } + + PUGI__FN bool xml_text::as_bool(bool def) const + { + xml_node_struct* d = _data(); + + return impl::get_value_bool(d ? d->value : 0, def); + } + +#ifdef PUGIXML_HAS_LONG_LONG + PUGI__FN long long xml_text::as_llong(long long def) const + { + xml_node_struct* d = _data(); + + return impl::get_value_llong(d ? d->value : 0, def); + } + + PUGI__FN unsigned long long xml_text::as_ullong(unsigned long long def) const + { + xml_node_struct* d = _data(); + + return impl::get_value_ullong(d ? d->value : 0, def); + } +#endif + + PUGI__FN bool xml_text::set(const char_t* rhs) + { + xml_node_struct* dn = _data_new(); + + return dn ? impl::strcpy_insitu(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false; + } + + PUGI__FN bool xml_text::set(int rhs) + { + xml_node_struct* dn = _data_new(); + + return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false; + } + + PUGI__FN bool xml_text::set(unsigned int rhs) + { + xml_node_struct* dn = _data_new(); + + return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false; + } + + PUGI__FN bool xml_text::set(double rhs) + { + xml_node_struct* dn = _data_new(); + + return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false; + } + + PUGI__FN bool xml_text::set(bool rhs) + { + xml_node_struct* dn = _data_new(); + + return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false; + } + +#ifdef PUGIXML_HAS_LONG_LONG + PUGI__FN bool xml_text::set(long long rhs) + { + xml_node_struct* dn = _data_new(); + + return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false; + } + + PUGI__FN bool xml_text::set(unsigned long long rhs) + { + xml_node_struct* dn = _data_new(); + + return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false; + } +#endif + + PUGI__FN xml_text& xml_text::operator=(const char_t* rhs) + { + set(rhs); + return *this; + } + + PUGI__FN xml_text& xml_text::operator=(int rhs) + { + set(rhs); + return *this; + } + + PUGI__FN xml_text& xml_text::operator=(unsigned int rhs) + { + set(rhs); + return *this; + } + + PUGI__FN xml_text& xml_text::operator=(double rhs) + { + set(rhs); + return *this; + } + + PUGI__FN xml_text& xml_text::operator=(bool rhs) + { + set(rhs); + return *this; + } + +#ifdef PUGIXML_HAS_LONG_LONG + PUGI__FN xml_text& xml_text::operator=(long long rhs) + { + set(rhs); + return *this; + } + + PUGI__FN xml_text& xml_text::operator=(unsigned long long rhs) + { + set(rhs); + return *this; + } +#endif + + PUGI__FN xml_node xml_text::data() const + { + return xml_node(_data()); + } + +#ifdef __BORLANDC__ + PUGI__FN bool operator&&(const xml_text& lhs, bool rhs) + { + return (bool)lhs && rhs; + } + + PUGI__FN bool operator||(const xml_text& lhs, bool rhs) + { + return (bool)lhs || rhs; + } +#endif + + PUGI__FN xml_node_iterator::xml_node_iterator() + { + } + + PUGI__FN xml_node_iterator::xml_node_iterator(const xml_node& node): _wrap(node), _parent(node.parent()) + { + } + + PUGI__FN xml_node_iterator::xml_node_iterator(xml_node_struct* ref, xml_node_struct* parent): _wrap(ref), _parent(parent) + { + } + + PUGI__FN bool xml_node_iterator::operator==(const xml_node_iterator& rhs) const + { + return _wrap._root == rhs._wrap._root && _parent._root == rhs._parent._root; + } + + PUGI__FN bool xml_node_iterator::operator!=(const xml_node_iterator& rhs) const + { + return _wrap._root != rhs._wrap._root || _parent._root != rhs._parent._root; + } + + PUGI__FN xml_node& xml_node_iterator::operator*() const + { + assert(_wrap._root); + return _wrap; + } + + PUGI__FN xml_node* xml_node_iterator::operator->() const + { + assert(_wrap._root); + return const_cast<xml_node*>(&_wrap); // BCC32 workaround + } + + PUGI__FN const xml_node_iterator& xml_node_iterator::operator++() + { + assert(_wrap._root); + _wrap._root = _wrap._root->next_sibling; + return *this; + } + + PUGI__FN xml_node_iterator xml_node_iterator::operator++(int) + { + xml_node_iterator temp = *this; + ++*this; + return temp; + } + + PUGI__FN const xml_node_iterator& xml_node_iterator::operator--() + { + _wrap = _wrap._root ? _wrap.previous_sibling() : _parent.last_child(); + return *this; + } + + PUGI__FN xml_node_iterator xml_node_iterator::operator--(int) + { + xml_node_iterator temp = *this; + --*this; + return temp; + } + + PUGI__FN xml_attribute_iterator::xml_attribute_iterator() + { + } + + PUGI__FN xml_attribute_iterator::xml_attribute_iterator(const xml_attribute& attr, const xml_node& parent): _wrap(attr), _parent(parent) + { + } + + PUGI__FN xml_attribute_iterator::xml_attribute_iterator(xml_attribute_struct* ref, xml_node_struct* parent): _wrap(ref), _parent(parent) + { + } + + PUGI__FN bool xml_attribute_iterator::operator==(const xml_attribute_iterator& rhs) const + { + return _wrap._attr == rhs._wrap._attr && _parent._root == rhs._parent._root; + } + + PUGI__FN bool xml_attribute_iterator::operator!=(const xml_attribute_iterator& rhs) const + { + return _wrap._attr != rhs._wrap._attr || _parent._root != rhs._parent._root; + } + + PUGI__FN xml_attribute& xml_attribute_iterator::operator*() const + { + assert(_wrap._attr); + return _wrap; + } + + PUGI__FN xml_attribute* xml_attribute_iterator::operator->() const + { + assert(_wrap._attr); + return const_cast<xml_attribute*>(&_wrap); // BCC32 workaround + } + + PUGI__FN const xml_attribute_iterator& xml_attribute_iterator::operator++() + { + assert(_wrap._attr); + _wrap._attr = _wrap._attr->next_attribute; + return *this; + } + + PUGI__FN xml_attribute_iterator xml_attribute_iterator::operator++(int) + { + xml_attribute_iterator temp = *this; + ++*this; + return temp; + } + + PUGI__FN const xml_attribute_iterator& xml_attribute_iterator::operator--() + { + _wrap = _wrap._attr ? _wrap.previous_attribute() : _parent.last_attribute(); + return *this; + } + + PUGI__FN xml_attribute_iterator xml_attribute_iterator::operator--(int) + { + xml_attribute_iterator temp = *this; + --*this; + return temp; + } + + PUGI__FN xml_named_node_iterator::xml_named_node_iterator(): _name(0) + { + } + + PUGI__FN xml_named_node_iterator::xml_named_node_iterator(const xml_node& node, const char_t* name): _wrap(node), _parent(node.parent()), _name(name) + { + } + + PUGI__FN xml_named_node_iterator::xml_named_node_iterator(xml_node_struct* ref, xml_node_struct* parent, const char_t* name): _wrap(ref), _parent(parent), _name(name) + { + } + + PUGI__FN bool xml_named_node_iterator::operator==(const xml_named_node_iterator& rhs) const + { + return _wrap._root == rhs._wrap._root && _parent._root == rhs._parent._root; + } + + PUGI__FN bool xml_named_node_iterator::operator!=(const xml_named_node_iterator& rhs) const + { + return _wrap._root != rhs._wrap._root || _parent._root != rhs._parent._root; + } + + PUGI__FN xml_node& xml_named_node_iterator::operator*() const + { + assert(_wrap._root); + return _wrap; + } + + PUGI__FN xml_node* xml_named_node_iterator::operator->() const + { + assert(_wrap._root); + return const_cast<xml_node*>(&_wrap); // BCC32 workaround + } + + PUGI__FN const xml_named_node_iterator& xml_named_node_iterator::operator++() + { + assert(_wrap._root); + _wrap = _wrap.next_sibling(_name); + return *this; + } + + PUGI__FN xml_named_node_iterator xml_named_node_iterator::operator++(int) + { + xml_named_node_iterator temp = *this; + ++*this; + return temp; + } + + PUGI__FN const xml_named_node_iterator& xml_named_node_iterator::operator--() + { + if (_wrap._root) + _wrap = _wrap.previous_sibling(_name); + else + { + _wrap = _parent.last_child(); + + if (!impl::strequal(_wrap.name(), _name)) + _wrap = _wrap.previous_sibling(_name); + } + + return *this; + } + + PUGI__FN xml_named_node_iterator xml_named_node_iterator::operator--(int) + { + xml_named_node_iterator temp = *this; + --*this; + return temp; + } + + PUGI__FN xml_parse_result::xml_parse_result(): status(status_internal_error), offset(0), encoding(encoding_auto) + { + } + + PUGI__FN xml_parse_result::operator bool() const + { + return status == status_ok; + } + + PUGI__FN const char* xml_parse_result::description() const + { + switch (status) + { + case status_ok: return "No error"; + + case status_file_not_found: return "File was not found"; + case status_io_error: return "Error reading from file/stream"; + case status_out_of_memory: return "Could not allocate memory"; + case status_internal_error: return "Internal error occurred"; + + case status_unrecognized_tag: return "Could not determine tag type"; + + case status_bad_pi: return "Error parsing document declaration/processing instruction"; + case status_bad_comment: return "Error parsing comment"; + case status_bad_cdata: return "Error parsing CDATA section"; + case status_bad_doctype: return "Error parsing document type declaration"; + case status_bad_pcdata: return "Error parsing PCDATA section"; + case status_bad_start_element: return "Error parsing start element tag"; + case status_bad_attribute: return "Error parsing element attribute"; + case status_bad_end_element: return "Error parsing end element tag"; + case status_end_element_mismatch: return "Start-end tags mismatch"; + + case status_append_invalid_root: return "Unable to append nodes: root is not an element or document"; + + case status_no_document_element: return "No document element found"; + + default: return "Unknown error"; + } + } + + PUGI__FN xml_document::xml_document(): _buffer(0) + { + create(); + } + + PUGI__FN xml_document::~xml_document() + { + destroy(); + } + + PUGI__FN void xml_document::reset() + { + destroy(); + create(); + } + + PUGI__FN void xml_document::reset(const xml_document& proto) + { + reset(); + + for (xml_node cur = proto.first_child(); cur; cur = cur.next_sibling()) + append_copy(cur); + } + + PUGI__FN void xml_document::create() + { + assert(!_root); + + // initialize sentinel page + PUGI__STATIC_ASSERT(sizeof(impl::xml_memory_page) + sizeof(impl::xml_document_struct) + impl::xml_memory_page_alignment <= sizeof(_memory)); + + // align upwards to page boundary + void* page_memory = reinterpret_cast<void*>((reinterpret_cast<uintptr_t>(_memory) + (impl::xml_memory_page_alignment - 1)) & ~(impl::xml_memory_page_alignment - 1)); + + // prepare page structure + impl::xml_memory_page* page = impl::xml_memory_page::construct(page_memory); + assert(page); + + page->busy_size = impl::xml_memory_page_size; + + // allocate new root + _root = new (page->data) impl::xml_document_struct(page); + _root->prev_sibling_c = _root; + + // setup sentinel page + page->allocator = static_cast<impl::xml_document_struct*>(_root); + } + + PUGI__FN void xml_document::destroy() + { + assert(_root); + + // destroy static storage + if (_buffer) + { + impl::xml_memory::deallocate(_buffer); + _buffer = 0; + } + + // destroy extra buffers (note: no need to destroy linked list nodes, they're allocated using document allocator) + for (impl::xml_extra_buffer* extra = static_cast<impl::xml_document_struct*>(_root)->extra_buffers; extra; extra = extra->next) + { + if (extra->buffer) impl::xml_memory::deallocate(extra->buffer); + } + + // destroy dynamic storage, leave sentinel page (it's in static memory) + impl::xml_memory_page* root_page = reinterpret_cast<impl::xml_memory_page*>(_root->header & impl::xml_memory_page_pointer_mask); + assert(root_page && !root_page->prev && !root_page->memory); + + for (impl::xml_memory_page* page = root_page->next; page; ) + { + impl::xml_memory_page* next = page->next; + + impl::xml_allocator::deallocate_page(page); + + page = next; + } + + _root = 0; + } + +#ifndef PUGIXML_NO_STL + PUGI__FN xml_parse_result xml_document::load(std::basic_istream<char, std::char_traits<char> >& stream, unsigned int options, xml_encoding encoding) + { + reset(); + + return impl::load_stream_impl(*this, stream, options, encoding); + } + + PUGI__FN xml_parse_result xml_document::load(std::basic_istream<wchar_t, std::char_traits<wchar_t> >& stream, unsigned int options) + { + reset(); + + return impl::load_stream_impl(*this, stream, options, encoding_wchar); + } +#endif + + PUGI__FN xml_parse_result xml_document::load(const char_t* contents, unsigned int options) + { + // Force native encoding (skip autodetection) + #ifdef PUGIXML_WCHAR_MODE + xml_encoding encoding = encoding_wchar; + #else + xml_encoding encoding = encoding_utf8; + #endif + + return load_buffer(contents, impl::strlength(contents) * sizeof(char_t), options, encoding); + } + + PUGI__FN xml_parse_result xml_document::load_file(const char* path_, unsigned int options, xml_encoding encoding) + { + reset(); + + FILE* file = fopen(path_, "rb"); + + return impl::load_file_impl(*this, file, options, encoding); + } + + PUGI__FN xml_parse_result xml_document::load_file(const wchar_t* path_, unsigned int options, xml_encoding encoding) + { + reset(); + + FILE* file = impl::open_file_wide(path_, L"rb"); + + return impl::load_file_impl(*this, file, options, encoding); + } + + PUGI__FN xml_parse_result xml_document::load_buffer(const void* contents, size_t size, unsigned int options, xml_encoding encoding) + { + reset(); + + return impl::load_buffer_impl(static_cast<impl::xml_document_struct*>(_root), _root, const_cast<void*>(contents), size, options, encoding, false, false, &_buffer); + } + + PUGI__FN xml_parse_result xml_document::load_buffer_inplace(void* contents, size_t size, unsigned int options, xml_encoding encoding) + { + reset(); + + return impl::load_buffer_impl(static_cast<impl::xml_document_struct*>(_root), _root, contents, size, options, encoding, true, false, &_buffer); + } + + PUGI__FN xml_parse_result xml_document::load_buffer_inplace_own(void* contents, size_t size, unsigned int options, xml_encoding encoding) + { + reset(); + + return impl::load_buffer_impl(static_cast<impl::xml_document_struct*>(_root), _root, contents, size, options, encoding, true, true, &_buffer); + } + + PUGI__FN void xml_document::save(xml_writer& writer, const char_t* indent, unsigned int flags, xml_encoding encoding) const + { + impl::xml_buffered_writer buffered_writer(writer, encoding); + + if ((flags & format_write_bom) && encoding != encoding_latin1) + { + // BOM always represents the codepoint U+FEFF, so just write it in native encoding + #ifdef PUGIXML_WCHAR_MODE + unsigned int bom = 0xfeff; + buffered_writer.write(static_cast<wchar_t>(bom)); + #else + buffered_writer.write('\xef', '\xbb', '\xbf'); + #endif + } + + if (!(flags & format_no_declaration) && !impl::has_declaration(*this)) + { + buffered_writer.write(PUGIXML_TEXT("<?xml version=\"1.0\"")); + if (encoding == encoding_latin1) buffered_writer.write(PUGIXML_TEXT(" encoding=\"ISO-8859-1\"")); + buffered_writer.write('?', '>'); + if (!(flags & format_raw)) buffered_writer.write('\n'); + } + + impl::node_output(buffered_writer, *this, indent, flags, 0); + } + +#ifndef PUGIXML_NO_STL + PUGI__FN void xml_document::save(std::basic_ostream<char, std::char_traits<char> >& stream, const char_t* indent, unsigned int flags, xml_encoding encoding) const + { + xml_writer_stream writer(stream); + + save(writer, indent, flags, encoding); + } + + PUGI__FN void xml_document::save(std::basic_ostream<wchar_t, std::char_traits<wchar_t> >& stream, const char_t* indent, unsigned int flags) const + { + xml_writer_stream writer(stream); + + save(writer, indent, flags, encoding_wchar); + } +#endif + + PUGI__FN bool xml_document::save_file(const char* path_, const char_t* indent, unsigned int flags, xml_encoding encoding) const + { + FILE* file = fopen(path_, (flags & format_save_file_text) ? "w" : "wb"); + return impl::save_file_impl(*this, file, indent, flags, encoding); + } + + PUGI__FN bool xml_document::save_file(const wchar_t* path_, const char_t* indent, unsigned int flags, xml_encoding encoding) const + { + FILE* file = impl::open_file_wide(path_, (flags & format_save_file_text) ? L"w" : L"wb"); + return impl::save_file_impl(*this, file, indent, flags, encoding); + } + + PUGI__FN xml_node xml_document::document_element() const + { + assert(_root); + + for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) + if ((i->header & impl::xml_memory_page_type_mask) + 1 == node_element) + return xml_node(i); + + return xml_node(); + } + +#ifndef PUGIXML_NO_STL + PUGI__FN std::string PUGIXML_FUNCTION as_utf8(const wchar_t* str) + { + assert(str); + + return impl::as_utf8_impl(str, impl::strlength_wide(str)); + } + + PUGI__FN std::string PUGIXML_FUNCTION as_utf8(const std::basic_string<wchar_t>& str) + { + return impl::as_utf8_impl(str.c_str(), str.size()); + } + + PUGI__FN std::basic_string<wchar_t> PUGIXML_FUNCTION as_wide(const char* str) + { + assert(str); + + return impl::as_wide_impl(str, strlen(str)); + } + + PUGI__FN std::basic_string<wchar_t> PUGIXML_FUNCTION as_wide(const std::string& str) + { + return impl::as_wide_impl(str.c_str(), str.size()); + } +#endif + + PUGI__FN void PUGIXML_FUNCTION set_memory_management_functions(allocation_function allocate, deallocation_function deallocate) + { + impl::xml_memory::allocate = allocate; + impl::xml_memory::deallocate = deallocate; + } + + PUGI__FN allocation_function PUGIXML_FUNCTION get_memory_allocation_function() + { + return impl::xml_memory::allocate; + } + + PUGI__FN deallocation_function PUGIXML_FUNCTION get_memory_deallocation_function() + { + return impl::xml_memory::deallocate; + } +} + +#if !defined(PUGIXML_NO_STL) && (defined(_MSC_VER) || defined(__ICC)) +namespace std +{ + // Workarounds for (non-standard) iterator category detection for older versions (MSVC7/IC8 and earlier) + PUGI__FN std::bidirectional_iterator_tag _Iter_cat(const pugi::xml_node_iterator&) + { + return std::bidirectional_iterator_tag(); + } + + PUGI__FN std::bidirectional_iterator_tag _Iter_cat(const pugi::xml_attribute_iterator&) + { + return std::bidirectional_iterator_tag(); + } + + PUGI__FN std::bidirectional_iterator_tag _Iter_cat(const pugi::xml_named_node_iterator&) + { + return std::bidirectional_iterator_tag(); + } +} +#endif + +#if !defined(PUGIXML_NO_STL) && defined(__SUNPRO_CC) +namespace std +{ + // Workarounds for (non-standard) iterator category detection + PUGI__FN std::bidirectional_iterator_tag __iterator_category(const pugi::xml_node_iterator&) + { + return std::bidirectional_iterator_tag(); + } + + PUGI__FN std::bidirectional_iterator_tag __iterator_category(const pugi::xml_attribute_iterator&) + { + return std::bidirectional_iterator_tag(); + } + + PUGI__FN std::bidirectional_iterator_tag __iterator_category(const pugi::xml_named_node_iterator&) + { + return std::bidirectional_iterator_tag(); + } +} +#endif + +#ifndef PUGIXML_NO_XPATH + +// STL replacements +PUGI__NS_BEGIN + struct equal_to + { + template <typename T> bool operator()(const T& lhs, const T& rhs) const + { + return lhs == rhs; + } + }; + + struct not_equal_to + { + template <typename T> bool operator()(const T& lhs, const T& rhs) const + { + return lhs != rhs; + } + }; + + struct less + { + template <typename T> bool operator()(const T& lhs, const T& rhs) const + { + return lhs < rhs; + } + }; + + struct less_equal + { + template <typename T> bool operator()(const T& lhs, const T& rhs) const + { + return lhs <= rhs; + } + }; + + template <typename T> void swap(T& lhs, T& rhs) + { + T temp = lhs; + lhs = rhs; + rhs = temp; + } + + template <typename I, typename Pred> I min_element(I begin, I end, const Pred& pred) + { + I result = begin; + + for (I it = begin + 1; it != end; ++it) + if (pred(*it, *result)) + result = it; + + return result; + } + + template <typename I> void reverse(I begin, I end) + { + while (end - begin > 1) swap(*begin++, *--end); + } + + template <typename I> I unique(I begin, I end) + { + // fast skip head + while (end - begin > 1 && *begin != *(begin + 1)) begin++; + + if (begin == end) return begin; + + // last written element + I write = begin++; + + // merge unique elements + while (begin != end) + { + if (*begin != *write) + *++write = *begin++; + else + begin++; + } + + // past-the-end (write points to live element) + return write + 1; + } + + template <typename I> void copy_backwards(I begin, I end, I target) + { + while (begin != end) *--target = *--end; + } + + template <typename I, typename Pred, typename T> void insertion_sort(I begin, I end, const Pred& pred, T*) + { + assert(begin != end); + + for (I it = begin + 1; it != end; ++it) + { + T val = *it; + + if (pred(val, *begin)) + { + // move to front + copy_backwards(begin, it, it + 1); + *begin = val; + } + else + { + I hole = it; + + // move hole backwards + while (pred(val, *(hole - 1))) + { + *hole = *(hole - 1); + hole--; + } + + // fill hole with element + *hole = val; + } + } + } + + // std variant for elements with == + template <typename I, typename Pred> void partition(I begin, I middle, I end, const Pred& pred, I* out_eqbeg, I* out_eqend) + { + I eqbeg = middle, eqend = middle + 1; + + // expand equal range + while (eqbeg != begin && *(eqbeg - 1) == *eqbeg) --eqbeg; + while (eqend != end && *eqend == *eqbeg) ++eqend; + + // process outer elements + I ltend = eqbeg, gtbeg = eqend; + + for (;;) + { + // find the element from the right side that belongs to the left one + for (; gtbeg != end; ++gtbeg) + if (!pred(*eqbeg, *gtbeg)) + { + if (*gtbeg == *eqbeg) swap(*gtbeg, *eqend++); + else break; + } + + // find the element from the left side that belongs to the right one + for (; ltend != begin; --ltend) + if (!pred(*(ltend - 1), *eqbeg)) + { + if (*eqbeg == *(ltend - 1)) swap(*(ltend - 1), *--eqbeg); + else break; + } + + // scanned all elements + if (gtbeg == end && ltend == begin) + { + *out_eqbeg = eqbeg; + *out_eqend = eqend; + return; + } + + // make room for elements by moving equal area + if (gtbeg == end) + { + if (--ltend != --eqbeg) swap(*ltend, *eqbeg); + swap(*eqbeg, *--eqend); + } + else if (ltend == begin) + { + if (eqend != gtbeg) swap(*eqbeg, *eqend); + ++eqend; + swap(*gtbeg++, *eqbeg++); + } + else swap(*gtbeg++, *--ltend); + } + } + + template <typename I, typename Pred> void median3(I first, I middle, I last, const Pred& pred) + { + if (pred(*middle, *first)) swap(*middle, *first); + if (pred(*last, *middle)) swap(*last, *middle); + if (pred(*middle, *first)) swap(*middle, *first); + } + + template <typename I, typename Pred> void median(I first, I middle, I last, const Pred& pred) + { + if (last - first <= 40) + { + // median of three for small chunks + median3(first, middle, last, pred); + } + else + { + // median of nine + size_t step = (last - first + 1) / 8; + + median3(first, first + step, first + 2 * step, pred); + median3(middle - step, middle, middle + step, pred); + median3(last - 2 * step, last - step, last, pred); + median3(first + step, middle, last - step, pred); + } + } + + template <typename I, typename Pred> void sort(I begin, I end, const Pred& pred) + { + // sort large chunks + while (end - begin > 32) + { + // find median element + I middle = begin + (end - begin) / 2; + median(begin, middle, end - 1, pred); + + // partition in three chunks (< = >) + I eqbeg, eqend; + partition(begin, middle, end, pred, &eqbeg, &eqend); + + // loop on larger half + if (eqbeg - begin > end - eqend) + { + sort(eqend, end, pred); + end = eqbeg; + } + else + { + sort(begin, eqbeg, pred); + begin = eqend; + } + } + + // insertion sort small chunk + if (begin != end) insertion_sort(begin, end, pred, &*begin); + } +PUGI__NS_END + +// Allocator used for AST and evaluation stacks +PUGI__NS_BEGIN + struct xpath_memory_block + { + xpath_memory_block* next; + size_t capacity; + + char data[ + #ifdef PUGIXML_MEMORY_XPATH_PAGE_SIZE + PUGIXML_MEMORY_XPATH_PAGE_SIZE + #else + 4096 + #endif + ]; + }; + + class xpath_allocator + { + xpath_memory_block* _root; + size_t _root_size; + + public: + #ifdef PUGIXML_NO_EXCEPTIONS + jmp_buf* error_handler; + #endif + + xpath_allocator(xpath_memory_block* root, size_t root_size = 0): _root(root), _root_size(root_size) + { + #ifdef PUGIXML_NO_EXCEPTIONS + error_handler = 0; + #endif + } + + void* allocate_nothrow(size_t size) + { + // align size so that we're able to store pointers in subsequent blocks + size = (size + sizeof(void*) - 1) & ~(sizeof(void*) - 1); + + if (_root_size + size <= _root->capacity) + { + void* buf = _root->data + _root_size; + _root_size += size; + return buf; + } + else + { + // make sure we have at least 1/4th of the page free after allocation to satisfy subsequent allocation requests + size_t block_capacity_base = sizeof(_root->data); + size_t block_capacity_req = size + block_capacity_base / 4; + size_t block_capacity = (block_capacity_base > block_capacity_req) ? block_capacity_base : block_capacity_req; + + size_t block_size = block_capacity + offsetof(xpath_memory_block, data); + + xpath_memory_block* block = static_cast<xpath_memory_block*>(xml_memory::allocate(block_size)); + if (!block) return 0; + + block->next = _root; + block->capacity = block_capacity; + + _root = block; + _root_size = size; + + return block->data; + } + } + + void* allocate(size_t size) + { + void* result = allocate_nothrow(size); + + if (!result) + { + #ifdef PUGIXML_NO_EXCEPTIONS + assert(error_handler); + longjmp(*error_handler, 1); + #else + throw std::bad_alloc(); + #endif + } + + return result; + } + + void* reallocate(void* ptr, size_t old_size, size_t new_size) + { + // align size so that we're able to store pointers in subsequent blocks + old_size = (old_size + sizeof(void*) - 1) & ~(sizeof(void*) - 1); + new_size = (new_size + sizeof(void*) - 1) & ~(sizeof(void*) - 1); + + // we can only reallocate the last object + assert(ptr == 0 || static_cast<char*>(ptr) + old_size == _root->data + _root_size); + + // adjust root size so that we have not allocated the object at all + bool only_object = (_root_size == old_size); + + if (ptr) _root_size -= old_size; + + // allocate a new version (this will obviously reuse the memory if possible) + void* result = allocate(new_size); + assert(result); + + // we have a new block + if (result != ptr && ptr) + { + // copy old data + assert(new_size >= old_size); + memcpy(result, ptr, old_size); + + // free the previous page if it had no other objects + if (only_object) + { + assert(_root->data == result); + assert(_root->next); + + xpath_memory_block* next = _root->next->next; + + if (next) + { + // deallocate the whole page, unless it was the first one + xml_memory::deallocate(_root->next); + _root->next = next; + } + } + } + + return result; + } + + void revert(const xpath_allocator& state) + { + // free all new pages + xpath_memory_block* cur = _root; + + while (cur != state._root) + { + xpath_memory_block* next = cur->next; + + xml_memory::deallocate(cur); + + cur = next; + } + + // restore state + _root = state._root; + _root_size = state._root_size; + } + + void release() + { + xpath_memory_block* cur = _root; + assert(cur); + + while (cur->next) + { + xpath_memory_block* next = cur->next; + + xml_memory::deallocate(cur); + + cur = next; + } + } + }; + + struct xpath_allocator_capture + { + xpath_allocator_capture(xpath_allocator* alloc): _target(alloc), _state(*alloc) + { + } + + ~xpath_allocator_capture() + { + _target->revert(_state); + } + + xpath_allocator* _target; + xpath_allocator _state; + }; + + struct xpath_stack + { + xpath_allocator* result; + xpath_allocator* temp; + }; + + struct xpath_stack_data + { + xpath_memory_block blocks[2]; + xpath_allocator result; + xpath_allocator temp; + xpath_stack stack; + + #ifdef PUGIXML_NO_EXCEPTIONS + jmp_buf error_handler; + #endif + + xpath_stack_data(): result(blocks + 0), temp(blocks + 1) + { + blocks[0].next = blocks[1].next = 0; + blocks[0].capacity = blocks[1].capacity = sizeof(blocks[0].data); + + stack.result = &result; + stack.temp = &temp; + + #ifdef PUGIXML_NO_EXCEPTIONS + result.error_handler = temp.error_handler = &error_handler; + #endif + } + + ~xpath_stack_data() + { + result.release(); + temp.release(); + } + }; +PUGI__NS_END + +// String class +PUGI__NS_BEGIN + class xpath_string + { + const char_t* _buffer; + bool _uses_heap; + + static char_t* duplicate_string(const char_t* string, size_t length, xpath_allocator* alloc) + { + char_t* result = static_cast<char_t*>(alloc->allocate((length + 1) * sizeof(char_t))); + assert(result); + + memcpy(result, string, length * sizeof(char_t)); + result[length] = 0; + + return result; + } + + static char_t* duplicate_string(const char_t* string, xpath_allocator* alloc) + { + return duplicate_string(string, strlength(string), alloc); + } + + public: + xpath_string(): _buffer(PUGIXML_TEXT("")), _uses_heap(false) + { + } + + explicit xpath_string(const char_t* str, xpath_allocator* alloc) + { + bool empty_ = (*str == 0); + + _buffer = empty_ ? PUGIXML_TEXT("") : duplicate_string(str, alloc); + _uses_heap = !empty_; + } + + explicit xpath_string(const char_t* str, bool use_heap): _buffer(str), _uses_heap(use_heap) + { + } + + xpath_string(const char_t* begin, const char_t* end, xpath_allocator* alloc) + { + assert(begin <= end); + + bool empty_ = (begin == end); + + _buffer = empty_ ? PUGIXML_TEXT("") : duplicate_string(begin, static_cast<size_t>(end - begin), alloc); + _uses_heap = !empty_; + } + + void append(const xpath_string& o, xpath_allocator* alloc) + { + // skip empty sources + if (!*o._buffer) return; + + // fast append for constant empty target and constant source + if (!*_buffer && !_uses_heap && !o._uses_heap) + { + _buffer = o._buffer; + } + else + { + // need to make heap copy + size_t target_length = strlength(_buffer); + size_t source_length = strlength(o._buffer); + size_t result_length = target_length + source_length; + + // allocate new buffer + char_t* result = static_cast<char_t*>(alloc->reallocate(_uses_heap ? const_cast<char_t*>(_buffer) : 0, (target_length + 1) * sizeof(char_t), (result_length + 1) * sizeof(char_t))); + assert(result); + + // append first string to the new buffer in case there was no reallocation + if (!_uses_heap) memcpy(result, _buffer, target_length * sizeof(char_t)); + + // append second string to the new buffer + memcpy(result + target_length, o._buffer, source_length * sizeof(char_t)); + result[result_length] = 0; + + // finalize + _buffer = result; + _uses_heap = true; + } + } + + const char_t* c_str() const + { + return _buffer; + } + + size_t length() const + { + return strlength(_buffer); + } + + char_t* data(xpath_allocator* alloc) + { + // make private heap copy + if (!_uses_heap) + { + _buffer = duplicate_string(_buffer, alloc); + _uses_heap = true; + } + + return const_cast<char_t*>(_buffer); + } + + bool empty() const + { + return *_buffer == 0; + } + + bool operator==(const xpath_string& o) const + { + return strequal(_buffer, o._buffer); + } + + bool operator!=(const xpath_string& o) const + { + return !strequal(_buffer, o._buffer); + } + + bool uses_heap() const + { + return _uses_heap; + } + }; + + PUGI__FN xpath_string xpath_string_const(const char_t* str) + { + return xpath_string(str, false); + } +PUGI__NS_END + +PUGI__NS_BEGIN + PUGI__FN bool starts_with(const char_t* string, const char_t* pattern) + { + while (*pattern && *string == *pattern) + { + string++; + pattern++; + } + + return *pattern == 0; + } + + PUGI__FN const char_t* find_char(const char_t* s, char_t c) + { + #ifdef PUGIXML_WCHAR_MODE + return wcschr(s, c); + #else + return strchr(s, c); + #endif + } + + PUGI__FN const char_t* find_substring(const char_t* s, const char_t* p) + { + #ifdef PUGIXML_WCHAR_MODE + // MSVC6 wcsstr bug workaround (if s is empty it always returns 0) + return (*p == 0) ? s : wcsstr(s, p); + #else + return strstr(s, p); + #endif + } + + // Converts symbol to lower case, if it is an ASCII one + PUGI__FN char_t tolower_ascii(char_t ch) + { + return static_cast<unsigned int>(ch - 'A') < 26 ? static_cast<char_t>(ch | ' ') : ch; + } + + PUGI__FN xpath_string string_value(const xpath_node& na, xpath_allocator* alloc) + { + if (na.attribute()) + return xpath_string_const(na.attribute().value()); + else + { + const xml_node& n = na.node(); + + switch (n.type()) + { + case node_pcdata: + case node_cdata: + case node_comment: + case node_pi: + return xpath_string_const(n.value()); + + case node_document: + case node_element: + { + xpath_string result; + + xml_node cur = n.first_child(); + + while (cur && cur != n) + { + if (cur.type() == node_pcdata || cur.type() == node_cdata) + result.append(xpath_string_const(cur.value()), alloc); + + if (cur.first_child()) + cur = cur.first_child(); + else if (cur.next_sibling()) + cur = cur.next_sibling(); + else + { + while (!cur.next_sibling() && cur != n) + cur = cur.parent(); + + if (cur != n) cur = cur.next_sibling(); + } + } + + return result; + } + + default: + return xpath_string(); + } + } + } + + PUGI__FN unsigned int node_height(xml_node n) + { + unsigned int result = 0; + + while (n) + { + ++result; + n = n.parent(); + } + + return result; + } + + PUGI__FN bool node_is_before(xml_node ln, unsigned int lh, xml_node rn, unsigned int rh) + { + // normalize heights + for (unsigned int i = rh; i < lh; i++) ln = ln.parent(); + for (unsigned int j = lh; j < rh; j++) rn = rn.parent(); + + // one node is the ancestor of the other + if (ln == rn) return lh < rh; + + // find common ancestor + while (ln.parent() != rn.parent()) + { + ln = ln.parent(); + rn = rn.parent(); + } + + // there is no common ancestor (the shared parent is null), nodes are from different documents + if (!ln.parent()) return ln < rn; + + // determine sibling order + for (; ln; ln = ln.next_sibling()) + if (ln == rn) + return true; + + return false; + } + + PUGI__FN bool node_is_ancestor(xml_node parent, xml_node node) + { + while (node && node != parent) node = node.parent(); + + return parent && node == parent; + } + + PUGI__FN const void* document_order(const xpath_node& xnode) + { + xml_node_struct* node = xnode.node().internal_object(); + + if (node) + { + if (node->name && (node->header & xml_memory_page_name_allocated_mask) == 0) return node->name; + if (node->value && (node->header & xml_memory_page_value_allocated_mask) == 0) return node->value; + return 0; + } + + xml_attribute_struct* attr = xnode.attribute().internal_object(); + + if (attr) + { + if ((attr->header & xml_memory_page_name_allocated_mask) == 0) return attr->name; + if ((attr->header & xml_memory_page_value_allocated_mask) == 0) return attr->value; + return 0; + } + + return 0; + } + + struct document_order_comparator + { + bool operator()(const xpath_node& lhs, const xpath_node& rhs) const + { + // optimized document order based check + const void* lo = document_order(lhs); + const void* ro = document_order(rhs); + + if (lo && ro) return lo < ro; + + // slow comparison + xml_node ln = lhs.node(), rn = rhs.node(); + + // compare attributes + if (lhs.attribute() && rhs.attribute()) + { + // shared parent + if (lhs.parent() == rhs.parent()) + { + // determine sibling order + for (xml_attribute a = lhs.attribute(); a; a = a.next_attribute()) + if (a == rhs.attribute()) + return true; + + return false; + } + + // compare attribute parents + ln = lhs.parent(); + rn = rhs.parent(); + } + else if (lhs.attribute()) + { + // attributes go after the parent element + if (lhs.parent() == rhs.node()) return false; + + ln = lhs.parent(); + } + else if (rhs.attribute()) + { + // attributes go after the parent element + if (rhs.parent() == lhs.node()) return true; + + rn = rhs.parent(); + } + + if (ln == rn) return false; + + unsigned int lh = node_height(ln); + unsigned int rh = node_height(rn); + + return node_is_before(ln, lh, rn, rh); + } + }; + + struct duplicate_comparator + { + bool operator()(const xpath_node& lhs, const xpath_node& rhs) const + { + if (lhs.attribute()) return rhs.attribute() ? lhs.attribute() < rhs.attribute() : true; + else return rhs.attribute() ? false : lhs.node() < rhs.node(); + } + }; + + PUGI__FN double gen_nan() + { + #if defined(__STDC_IEC_559__) || ((FLT_RADIX - 0 == 2) && (FLT_MAX_EXP - 0 == 128) && (FLT_MANT_DIG - 0 == 24)) + union { float f; uint32_t i; } u[sizeof(float) == sizeof(uint32_t) ? 1 : -1]; + u[0].i = 0x7fc00000; + return u[0].f; + #else + // fallback + const volatile double zero = 0.0; + return zero / zero; + #endif + } + + PUGI__FN bool is_nan(double value) + { + #if defined(PUGI__MSVC_CRT_VERSION) || defined(__BORLANDC__) + return !!_isnan(value); + #elif defined(fpclassify) && defined(FP_NAN) + return fpclassify(value) == FP_NAN; + #else + // fallback + const volatile double v = value; + return v != v; + #endif + } + + PUGI__FN const char_t* convert_number_to_string_special(double value) + { + #if defined(PUGI__MSVC_CRT_VERSION) || defined(__BORLANDC__) + if (_finite(value)) return (value == 0) ? PUGIXML_TEXT("0") : 0; + if (_isnan(value)) return PUGIXML_TEXT("NaN"); + return value > 0 ? PUGIXML_TEXT("Infinity") : PUGIXML_TEXT("-Infinity"); + #elif defined(fpclassify) && defined(FP_NAN) && defined(FP_INFINITE) && defined(FP_ZERO) + switch (fpclassify(value)) + { + case FP_NAN: + return PUGIXML_TEXT("NaN"); + + case FP_INFINITE: + return value > 0 ? PUGIXML_TEXT("Infinity") : PUGIXML_TEXT("-Infinity"); + + case FP_ZERO: + return PUGIXML_TEXT("0"); + + default: + return 0; + } + #else + // fallback + const volatile double v = value; + + if (v == 0) return PUGIXML_TEXT("0"); + if (v != v) return PUGIXML_TEXT("NaN"); + if (v * 2 == v) return value > 0 ? PUGIXML_TEXT("Infinity") : PUGIXML_TEXT("-Infinity"); + return 0; + #endif + } + + PUGI__FN bool convert_number_to_boolean(double value) + { + return (value != 0 && !is_nan(value)); + } + + PUGI__FN void truncate_zeros(char* begin, char* end) + { + while (begin != end && end[-1] == '0') end--; + + *end = 0; + } + + // gets mantissa digits in the form of 0.xxxxx with 0. implied and the exponent +#if defined(PUGI__MSVC_CRT_VERSION) && PUGI__MSVC_CRT_VERSION >= 1400 && !defined(_WIN32_WCE) + PUGI__FN void convert_number_to_mantissa_exponent(double value, char* buffer, size_t buffer_size, char** out_mantissa, int* out_exponent) + { + // get base values + int sign, exponent; + _ecvt_s(buffer, buffer_size, value, DBL_DIG + 1, &exponent, &sign); + + // truncate redundant zeros + truncate_zeros(buffer, buffer + strlen(buffer)); + + // fill results + *out_mantissa = buffer; + *out_exponent = exponent; + } +#else + PUGI__FN void convert_number_to_mantissa_exponent(double value, char* buffer, size_t buffer_size, char** out_mantissa, int* out_exponent) + { + // get a scientific notation value with IEEE DBL_DIG decimals + sprintf(buffer, "%.*e", DBL_DIG, value); + assert(strlen(buffer) < buffer_size); + (void)!buffer_size; + + // get the exponent (possibly negative) + char* exponent_string = strchr(buffer, 'e'); + assert(exponent_string); + + int exponent = atoi(exponent_string + 1); + + // extract mantissa string: skip sign + char* mantissa = buffer[0] == '-' ? buffer + 1 : buffer; + assert(mantissa[0] != '0' && mantissa[1] == '.'); + + // divide mantissa by 10 to eliminate integer part + mantissa[1] = mantissa[0]; + mantissa++; + exponent++; + + // remove extra mantissa digits and zero-terminate mantissa + truncate_zeros(mantissa, exponent_string); + + // fill results + *out_mantissa = mantissa; + *out_exponent = exponent; + } +#endif + + PUGI__FN xpath_string convert_number_to_string(double value, xpath_allocator* alloc) + { + // try special number conversion + const char_t* special = convert_number_to_string_special(value); + if (special) return xpath_string_const(special); + + // get mantissa + exponent form + char mantissa_buffer[32]; + + char* mantissa; + int exponent; + convert_number_to_mantissa_exponent(value, mantissa_buffer, sizeof(mantissa_buffer), &mantissa, &exponent); + + // allocate a buffer of suitable length for the number + size_t result_size = strlen(mantissa_buffer) + (exponent > 0 ? exponent : -exponent) + 4; + char_t* result = static_cast<char_t*>(alloc->allocate(sizeof(char_t) * result_size)); + assert(result); + + // make the number! + char_t* s = result; + + // sign + if (value < 0) *s++ = '-'; + + // integer part + if (exponent <= 0) + { + *s++ = '0'; + } + else + { + while (exponent > 0) + { + assert(*mantissa == 0 || static_cast<unsigned int>(static_cast<unsigned int>(*mantissa) - '0') <= 9); + *s++ = *mantissa ? *mantissa++ : '0'; + exponent--; + } + } + + // fractional part + if (*mantissa) + { + // decimal point + *s++ = '.'; + + // extra zeroes from negative exponent + while (exponent < 0) + { + *s++ = '0'; + exponent++; + } + + // extra mantissa digits + while (*mantissa) + { + assert(static_cast<unsigned int>(*mantissa - '0') <= 9); + *s++ = *mantissa++; + } + } + + // zero-terminate + assert(s < result + result_size); + *s = 0; + + return xpath_string(result, true); + } + + PUGI__FN bool check_string_to_number_format(const char_t* string) + { + // parse leading whitespace + while (PUGI__IS_CHARTYPE(*string, ct_space)) ++string; + + // parse sign + if (*string == '-') ++string; + + if (!*string) return false; + + // if there is no integer part, there should be a decimal part with at least one digit + if (!PUGI__IS_CHARTYPEX(string[0], ctx_digit) && (string[0] != '.' || !PUGI__IS_CHARTYPEX(string[1], ctx_digit))) return false; + + // parse integer part + while (PUGI__IS_CHARTYPEX(*string, ctx_digit)) ++string; + + // parse decimal part + if (*string == '.') + { + ++string; + + while (PUGI__IS_CHARTYPEX(*string, ctx_digit)) ++string; + } + + // parse trailing whitespace + while (PUGI__IS_CHARTYPE(*string, ct_space)) ++string; + + return *string == 0; + } + + PUGI__FN double convert_string_to_number(const char_t* string) + { + // check string format + if (!check_string_to_number_format(string)) return gen_nan(); + + // parse string + #ifdef PUGIXML_WCHAR_MODE + return wcstod(string, 0); + #else + return atof(string); + #endif + } + + PUGI__FN bool convert_string_to_number_scratch(char_t (&buffer)[32], const char_t* begin, const char_t* end, double* out_result) + { + size_t length = static_cast<size_t>(end - begin); + char_t* scratch = buffer; + + if (length >= sizeof(buffer) / sizeof(buffer[0])) + { + // need to make dummy on-heap copy + scratch = static_cast<char_t*>(xml_memory::allocate((length + 1) * sizeof(char_t))); + if (!scratch) return false; + } + + // copy string to zero-terminated buffer and perform conversion + memcpy(scratch, begin, length * sizeof(char_t)); + scratch[length] = 0; + + *out_result = convert_string_to_number(scratch); + + // free dummy buffer + if (scratch != buffer) xml_memory::deallocate(scratch); + + return true; + } + + PUGI__FN double round_nearest(double value) + { + return floor(value + 0.5); + } + + PUGI__FN double round_nearest_nzero(double value) + { + // same as round_nearest, but returns -0 for [-0.5, -0] + // ceil is used to differentiate between +0 and -0 (we return -0 for [-0.5, -0] and +0 for +0) + return (value >= -0.5 && value <= 0) ? ceil(value) : floor(value + 0.5); + } + + PUGI__FN const char_t* qualified_name(const xpath_node& node) + { + return node.attribute() ? node.attribute().name() : node.node().name(); + } + + PUGI__FN const char_t* local_name(const xpath_node& node) + { + const char_t* name = qualified_name(node); + const char_t* p = find_char(name, ':'); + + return p ? p + 1 : name; + } + + struct namespace_uri_predicate + { + const char_t* prefix; + size_t prefix_length; + + namespace_uri_predicate(const char_t* name) + { + const char_t* pos = find_char(name, ':'); + + prefix = pos ? name : 0; + prefix_length = pos ? static_cast<size_t>(pos - name) : 0; + } + + bool operator()(const xml_attribute& a) const + { + const char_t* name = a.name(); + + if (!starts_with(name, PUGIXML_TEXT("xmlns"))) return false; + + return prefix ? name[5] == ':' && strequalrange(name + 6, prefix, prefix_length) : name[5] == 0; + } + }; + + PUGI__FN const char_t* namespace_uri(const xml_node& node) + { + namespace_uri_predicate pred = node.name(); + + xml_node p = node; + + while (p) + { + xml_attribute a = p.find_attribute(pred); + + if (a) return a.value(); + + p = p.parent(); + } + + return PUGIXML_TEXT(""); + } + + PUGI__FN const char_t* namespace_uri(const xml_attribute& attr, const xml_node& parent) + { + namespace_uri_predicate pred = attr.name(); + + // Default namespace does not apply to attributes + if (!pred.prefix) return PUGIXML_TEXT(""); + + xml_node p = parent; + + while (p) + { + xml_attribute a = p.find_attribute(pred); + + if (a) return a.value(); + + p = p.parent(); + } + + return PUGIXML_TEXT(""); + } + + PUGI__FN const char_t* namespace_uri(const xpath_node& node) + { + return node.attribute() ? namespace_uri(node.attribute(), node.parent()) : namespace_uri(node.node()); + } + + PUGI__FN void normalize_space(char_t* buffer) + { + char_t* write = buffer; + + for (char_t* it = buffer; *it; ) + { + char_t ch = *it++; + + if (PUGI__IS_CHARTYPE(ch, ct_space)) + { + // replace whitespace sequence with single space + while (PUGI__IS_CHARTYPE(*it, ct_space)) it++; + + // avoid leading spaces + if (write != buffer) *write++ = ' '; + } + else *write++ = ch; + } + + // remove trailing space + if (write != buffer && PUGI__IS_CHARTYPE(write[-1], ct_space)) write--; + + // zero-terminate + *write = 0; + } + + PUGI__FN void translate(char_t* buffer, const char_t* from, const char_t* to) + { + size_t to_length = strlength(to); + + char_t* write = buffer; + + while (*buffer) + { + PUGI__DMC_VOLATILE char_t ch = *buffer++; + + const char_t* pos = find_char(from, ch); + + if (!pos) + *write++ = ch; // do not process + else if (static_cast<size_t>(pos - from) < to_length) + *write++ = to[pos - from]; // replace + } + + // zero-terminate + *write = 0; + } + + struct xpath_variable_boolean: xpath_variable + { + xpath_variable_boolean(): value(false) + { + } + + bool value; + char_t name[1]; + }; + + struct xpath_variable_number: xpath_variable + { + xpath_variable_number(): value(0) + { + } + + double value; + char_t name[1]; + }; + + struct xpath_variable_string: xpath_variable + { + xpath_variable_string(): value(0) + { + } + + ~xpath_variable_string() + { + if (value) xml_memory::deallocate(value); + } + + char_t* value; + char_t name[1]; + }; + + struct xpath_variable_node_set: xpath_variable + { + xpath_node_set value; + char_t name[1]; + }; + + static const xpath_node_set dummy_node_set; + + PUGI__FN unsigned int hash_string(const char_t* str) + { + // Jenkins one-at-a-time hash (http://en.wikipedia.org/wiki/Jenkins_hash_function#one-at-a-time) + unsigned int result = 0; + + while (*str) + { + result += static_cast<unsigned int>(*str++); + result += result << 10; + result ^= result >> 6; + } + + result += result << 3; + result ^= result >> 11; + result += result << 15; + + return result; + } + + template <typename T> PUGI__FN T* new_xpath_variable(const char_t* name) + { + size_t length = strlength(name); + if (length == 0) return 0; // empty variable names are invalid + + // $$ we can't use offsetof(T, name) because T is non-POD, so we just allocate additional length characters + void* memory = xml_memory::allocate(sizeof(T) + length * sizeof(char_t)); + if (!memory) return 0; + + T* result = new (memory) T(); + + memcpy(result->name, name, (length + 1) * sizeof(char_t)); + + return result; + } + + PUGI__FN xpath_variable* new_xpath_variable(xpath_value_type type, const char_t* name) + { + switch (type) + { + case xpath_type_node_set: + return new_xpath_variable<xpath_variable_node_set>(name); + + case xpath_type_number: + return new_xpath_variable<xpath_variable_number>(name); + + case xpath_type_string: + return new_xpath_variable<xpath_variable_string>(name); + + case xpath_type_boolean: + return new_xpath_variable<xpath_variable_boolean>(name); + + default: + return 0; + } + } + + template <typename T> PUGI__FN void delete_xpath_variable(T* var) + { + var->~T(); + xml_memory::deallocate(var); + } + + PUGI__FN void delete_xpath_variable(xpath_value_type type, xpath_variable* var) + { + switch (type) + { + case xpath_type_node_set: + delete_xpath_variable(static_cast<xpath_variable_node_set*>(var)); + break; + + case xpath_type_number: + delete_xpath_variable(static_cast<xpath_variable_number*>(var)); + break; + + case xpath_type_string: + delete_xpath_variable(static_cast<xpath_variable_string*>(var)); + break; + + case xpath_type_boolean: + delete_xpath_variable(static_cast<xpath_variable_boolean*>(var)); + break; + + default: + assert(!"Invalid variable type"); + } + } + + PUGI__FN xpath_variable* get_variable_scratch(char_t (&buffer)[32], xpath_variable_set* set, const char_t* begin, const char_t* end) + { + size_t length = static_cast<size_t>(end - begin); + char_t* scratch = buffer; + + if (length >= sizeof(buffer) / sizeof(buffer[0])) + { + // need to make dummy on-heap copy + scratch = static_cast<char_t*>(xml_memory::allocate((length + 1) * sizeof(char_t))); + if (!scratch) return 0; + } + + // copy string to zero-terminated buffer and perform lookup + memcpy(scratch, begin, length * sizeof(char_t)); + scratch[length] = 0; + + xpath_variable* result = set->get(scratch); + + // free dummy buffer + if (scratch != buffer) xml_memory::deallocate(scratch); + + return result; + } +PUGI__NS_END + +// Internal node set class +PUGI__NS_BEGIN + PUGI__FN xpath_node_set::type_t xpath_sort(xpath_node* begin, xpath_node* end, xpath_node_set::type_t type, bool rev) + { + xpath_node_set::type_t order = rev ? xpath_node_set::type_sorted_reverse : xpath_node_set::type_sorted; + + if (type == xpath_node_set::type_unsorted) + { + sort(begin, end, document_order_comparator()); + + type = xpath_node_set::type_sorted; + } + + if (type != order) reverse(begin, end); + + return order; + } + + PUGI__FN xpath_node xpath_first(const xpath_node* begin, const xpath_node* end, xpath_node_set::type_t type) + { + if (begin == end) return xpath_node(); + + switch (type) + { + case xpath_node_set::type_sorted: + return *begin; + + case xpath_node_set::type_sorted_reverse: + return *(end - 1); + + case xpath_node_set::type_unsorted: + return *min_element(begin, end, document_order_comparator()); + + default: + assert(!"Invalid node set type"); + return xpath_node(); + } + } + + class xpath_node_set_raw + { + xpath_node_set::type_t _type; + + xpath_node* _begin; + xpath_node* _end; + xpath_node* _eos; + + public: + xpath_node_set_raw(): _type(xpath_node_set::type_unsorted), _begin(0), _end(0), _eos(0) + { + } + + xpath_node* begin() const + { + return _begin; + } + + xpath_node* end() const + { + return _end; + } + + bool empty() const + { + return _begin == _end; + } + + size_t size() const + { + return static_cast<size_t>(_end - _begin); + } + + xpath_node first() const + { + return xpath_first(_begin, _end, _type); + } + + void push_back(const xpath_node& node, xpath_allocator* alloc) + { + if (_end == _eos) + { + size_t capacity = static_cast<size_t>(_eos - _begin); + + // get new capacity (1.5x rule) + size_t new_capacity = capacity + capacity / 2 + 1; + + // reallocate the old array or allocate a new one + xpath_node* data = static_cast<xpath_node*>(alloc->reallocate(_begin, capacity * sizeof(xpath_node), new_capacity * sizeof(xpath_node))); + assert(data); + + // finalize + _begin = data; + _end = data + capacity; + _eos = data + new_capacity; + } + + *_end++ = node; + } + + void append(const xpath_node* begin_, const xpath_node* end_, xpath_allocator* alloc) + { + size_t size_ = static_cast<size_t>(_end - _begin); + size_t capacity = static_cast<size_t>(_eos - _begin); + size_t count = static_cast<size_t>(end_ - begin_); + + if (size_ + count > capacity) + { + // reallocate the old array or allocate a new one + xpath_node* data = static_cast<xpath_node*>(alloc->reallocate(_begin, capacity * sizeof(xpath_node), (size_ + count) * sizeof(xpath_node))); + assert(data); + + // finalize + _begin = data; + _end = data + size_; + _eos = data + size_ + count; + } + + memcpy(_end, begin_, count * sizeof(xpath_node)); + _end += count; + } + + void sort_do() + { + _type = xpath_sort(_begin, _end, _type, false); + } + + void truncate(xpath_node* pos) + { + assert(_begin <= pos && pos <= _end); + + _end = pos; + } + + void remove_duplicates() + { + if (_type == xpath_node_set::type_unsorted) + sort(_begin, _end, duplicate_comparator()); + + _end = unique(_begin, _end); + } + + xpath_node_set::type_t type() const + { + return _type; + } + + void set_type(xpath_node_set::type_t value) + { + _type = value; + } + }; +PUGI__NS_END + +PUGI__NS_BEGIN + struct xpath_context + { + xpath_node n; + size_t position, size; + + xpath_context(const xpath_node& n_, size_t position_, size_t size_): n(n_), position(position_), size(size_) + { + } + }; + + enum lexeme_t + { + lex_none = 0, + lex_equal, + lex_not_equal, + lex_less, + lex_greater, + lex_less_or_equal, + lex_greater_or_equal, + lex_plus, + lex_minus, + lex_multiply, + lex_union, + lex_var_ref, + lex_open_brace, + lex_close_brace, + lex_quoted_string, + lex_number, + lex_slash, + lex_double_slash, + lex_open_square_brace, + lex_close_square_brace, + lex_string, + lex_comma, + lex_axis_attribute, + lex_dot, + lex_double_dot, + lex_double_colon, + lex_eof + }; + + struct xpath_lexer_string + { + const char_t* begin; + const char_t* end; + + xpath_lexer_string(): begin(0), end(0) + { + } + + bool operator==(const char_t* other) const + { + size_t length = static_cast<size_t>(end - begin); + + return strequalrange(other, begin, length); + } + }; + + class xpath_lexer + { + const char_t* _cur; + const char_t* _cur_lexeme_pos; + xpath_lexer_string _cur_lexeme_contents; + + lexeme_t _cur_lexeme; + + public: + explicit xpath_lexer(const char_t* query): _cur(query) + { + next(); + } + + const char_t* state() const + { + return _cur; + } + + void next() + { + const char_t* cur = _cur; + + while (PUGI__IS_CHARTYPE(*cur, ct_space)) ++cur; + + // save lexeme position for error reporting + _cur_lexeme_pos = cur; + + switch (*cur) + { + case 0: + _cur_lexeme = lex_eof; + break; + + case '>': + if (*(cur+1) == '=') + { + cur += 2; + _cur_lexeme = lex_greater_or_equal; + } + else + { + cur += 1; + _cur_lexeme = lex_greater; + } + break; + + case '<': + if (*(cur+1) == '=') + { + cur += 2; + _cur_lexeme = lex_less_or_equal; + } + else + { + cur += 1; + _cur_lexeme = lex_less; + } + break; + + case '!': + if (*(cur+1) == '=') + { + cur += 2; + _cur_lexeme = lex_not_equal; + } + else + { + _cur_lexeme = lex_none; + } + break; + + case '=': + cur += 1; + _cur_lexeme = lex_equal; + + break; + + case '+': + cur += 1; + _cur_lexeme = lex_plus; + + break; + + case '-': + cur += 1; + _cur_lexeme = lex_minus; + + break; + + case '*': + cur += 1; + _cur_lexeme = lex_multiply; + + break; + + case '|': + cur += 1; + _cur_lexeme = lex_union; + + break; + + case '$': + cur += 1; + + if (PUGI__IS_CHARTYPEX(*cur, ctx_start_symbol)) + { + _cur_lexeme_contents.begin = cur; + + while (PUGI__IS_CHARTYPEX(*cur, ctx_symbol)) cur++; + + if (cur[0] == ':' && PUGI__IS_CHARTYPEX(cur[1], ctx_symbol)) // qname + { + cur++; // : + + while (PUGI__IS_CHARTYPEX(*cur, ctx_symbol)) cur++; + } + + _cur_lexeme_contents.end = cur; + + _cur_lexeme = lex_var_ref; + } + else + { + _cur_lexeme = lex_none; + } + + break; + + case '(': + cur += 1; + _cur_lexeme = lex_open_brace; + + break; + + case ')': + cur += 1; + _cur_lexeme = lex_close_brace; + + break; + + case '[': + cur += 1; + _cur_lexeme = lex_open_square_brace; + + break; + + case ']': + cur += 1; + _cur_lexeme = lex_close_square_brace; + + break; + + case ',': + cur += 1; + _cur_lexeme = lex_comma; + + break; + + case '/': + if (*(cur+1) == '/') + { + cur += 2; + _cur_lexeme = lex_double_slash; + } + else + { + cur += 1; + _cur_lexeme = lex_slash; + } + break; + + case '.': + if (*(cur+1) == '.') + { + cur += 2; + _cur_lexeme = lex_double_dot; + } + else if (PUGI__IS_CHARTYPEX(*(cur+1), ctx_digit)) + { + _cur_lexeme_contents.begin = cur; // . + + ++cur; + + while (PUGI__IS_CHARTYPEX(*cur, ctx_digit)) cur++; + + _cur_lexeme_contents.end = cur; + + _cur_lexeme = lex_number; + } + else + { + cur += 1; + _cur_lexeme = lex_dot; + } + break; + + case '@': + cur += 1; + _cur_lexeme = lex_axis_attribute; + + break; + + case '"': + case '\'': + { + char_t terminator = *cur; + + ++cur; + + _cur_lexeme_contents.begin = cur; + while (*cur && *cur != terminator) cur++; + _cur_lexeme_contents.end = cur; + + if (!*cur) + _cur_lexeme = lex_none; + else + { + cur += 1; + _cur_lexeme = lex_quoted_string; + } + + break; + } + + case ':': + if (*(cur+1) == ':') + { + cur += 2; + _cur_lexeme = lex_double_colon; + } + else + { + _cur_lexeme = lex_none; + } + break; + + default: + if (PUGI__IS_CHARTYPEX(*cur, ctx_digit)) + { + _cur_lexeme_contents.begin = cur; + + while (PUGI__IS_CHARTYPEX(*cur, ctx_digit)) cur++; + + if (*cur == '.') + { + cur++; + + while (PUGI__IS_CHARTYPEX(*cur, ctx_digit)) cur++; + } + + _cur_lexeme_contents.end = cur; + + _cur_lexeme = lex_number; + } + else if (PUGI__IS_CHARTYPEX(*cur, ctx_start_symbol)) + { + _cur_lexeme_contents.begin = cur; + + while (PUGI__IS_CHARTYPEX(*cur, ctx_symbol)) cur++; + + if (cur[0] == ':') + { + if (cur[1] == '*') // namespace test ncname:* + { + cur += 2; // :* + } + else if (PUGI__IS_CHARTYPEX(cur[1], ctx_symbol)) // namespace test qname + { + cur++; // : + + while (PUGI__IS_CHARTYPEX(*cur, ctx_symbol)) cur++; + } + } + + _cur_lexeme_contents.end = cur; + + _cur_lexeme = lex_string; + } + else + { + _cur_lexeme = lex_none; + } + } + + _cur = cur; + } + + lexeme_t current() const + { + return _cur_lexeme; + } + + const char_t* current_pos() const + { + return _cur_lexeme_pos; + } + + const xpath_lexer_string& contents() const + { + assert(_cur_lexeme == lex_var_ref || _cur_lexeme == lex_number || _cur_lexeme == lex_string || _cur_lexeme == lex_quoted_string); + + return _cur_lexeme_contents; + } + }; + + enum ast_type_t + { + ast_unknown, + ast_op_or, // left or right + ast_op_and, // left and right + ast_op_equal, // left = right + ast_op_not_equal, // left != right + ast_op_less, // left < right + ast_op_greater, // left > right + ast_op_less_or_equal, // left <= right + ast_op_greater_or_equal, // left >= right + ast_op_add, // left + right + ast_op_subtract, // left - right + ast_op_multiply, // left * right + ast_op_divide, // left / right + ast_op_mod, // left % right + ast_op_negate, // left - right + ast_op_union, // left | right + ast_predicate, // apply predicate to set; next points to next predicate + ast_filter, // select * from left where right + ast_filter_posinv, // select * from left where right; proximity position invariant + ast_string_constant, // string constant + ast_number_constant, // number constant + ast_variable, // variable + ast_func_last, // last() + ast_func_position, // position() + ast_func_count, // count(left) + ast_func_id, // id(left) + ast_func_local_name_0, // local-name() + ast_func_local_name_1, // local-name(left) + ast_func_namespace_uri_0, // namespace-uri() + ast_func_namespace_uri_1, // namespace-uri(left) + ast_func_name_0, // name() + ast_func_name_1, // name(left) + ast_func_string_0, // string() + ast_func_string_1, // string(left) + ast_func_concat, // concat(left, right, siblings) + ast_func_starts_with, // starts_with(left, right) + ast_func_contains, // contains(left, right) + ast_func_substring_before, // substring-before(left, right) + ast_func_substring_after, // substring-after(left, right) + ast_func_substring_2, // substring(left, right) + ast_func_substring_3, // substring(left, right, third) + ast_func_string_length_0, // string-length() + ast_func_string_length_1, // string-length(left) + ast_func_normalize_space_0, // normalize-space() + ast_func_normalize_space_1, // normalize-space(left) + ast_func_translate, // translate(left, right, third) + ast_func_boolean, // boolean(left) + ast_func_not, // not(left) + ast_func_true, // true() + ast_func_false, // false() + ast_func_lang, // lang(left) + ast_func_number_0, // number() + ast_func_number_1, // number(left) + ast_func_sum, // sum(left) + ast_func_floor, // floor(left) + ast_func_ceiling, // ceiling(left) + ast_func_round, // round(left) + ast_step, // process set left with step + ast_step_root // select root node + }; + + enum axis_t + { + axis_ancestor, + axis_ancestor_or_self, + axis_attribute, + axis_child, + axis_descendant, + axis_descendant_or_self, + axis_following, + axis_following_sibling, + axis_namespace, + axis_parent, + axis_preceding, + axis_preceding_sibling, + axis_self + }; + + enum nodetest_t + { + nodetest_none, + nodetest_name, + nodetest_type_node, + nodetest_type_comment, + nodetest_type_pi, + nodetest_type_text, + nodetest_pi, + nodetest_all, + nodetest_all_in_namespace + }; + + template <axis_t N> struct axis_to_type + { + static const axis_t axis; + }; + + template <axis_t N> const axis_t axis_to_type<N>::axis = N; + + class xpath_ast_node + { + private: + // node type + char _type; + char _rettype; + + // for ast_step / ast_predicate + char _axis; + char _test; + + // tree node structure + xpath_ast_node* _left; + xpath_ast_node* _right; + xpath_ast_node* _next; + + union + { + // value for ast_string_constant + const char_t* string; + // value for ast_number_constant + double number; + // variable for ast_variable + xpath_variable* variable; + // node test for ast_step (node name/namespace/node type/pi target) + const char_t* nodetest; + } _data; + + xpath_ast_node(const xpath_ast_node&); + xpath_ast_node& operator=(const xpath_ast_node&); + + template <class Comp> static bool compare_eq(xpath_ast_node* lhs, xpath_ast_node* rhs, const xpath_context& c, const xpath_stack& stack, const Comp& comp) + { + xpath_value_type lt = lhs->rettype(), rt = rhs->rettype(); + + if (lt != xpath_type_node_set && rt != xpath_type_node_set) + { + if (lt == xpath_type_boolean || rt == xpath_type_boolean) + return comp(lhs->eval_boolean(c, stack), rhs->eval_boolean(c, stack)); + else if (lt == xpath_type_number || rt == xpath_type_number) + return comp(lhs->eval_number(c, stack), rhs->eval_number(c, stack)); + else if (lt == xpath_type_string || rt == xpath_type_string) + { + xpath_allocator_capture cr(stack.result); + + xpath_string ls = lhs->eval_string(c, stack); + xpath_string rs = rhs->eval_string(c, stack); + + return comp(ls, rs); + } + } + else if (lt == xpath_type_node_set && rt == xpath_type_node_set) + { + xpath_allocator_capture cr(stack.result); + + xpath_node_set_raw ls = lhs->eval_node_set(c, stack); + xpath_node_set_raw rs = rhs->eval_node_set(c, stack); + + for (const xpath_node* li = ls.begin(); li != ls.end(); ++li) + for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri) + { + xpath_allocator_capture cri(stack.result); + + if (comp(string_value(*li, stack.result), string_value(*ri, stack.result))) + return true; + } + + return false; + } + else + { + if (lt == xpath_type_node_set) + { + swap(lhs, rhs); + swap(lt, rt); + } + + if (lt == xpath_type_boolean) + return comp(lhs->eval_boolean(c, stack), rhs->eval_boolean(c, stack)); + else if (lt == xpath_type_number) + { + xpath_allocator_capture cr(stack.result); + + double l = lhs->eval_number(c, stack); + xpath_node_set_raw rs = rhs->eval_node_set(c, stack); + + for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri) + { + xpath_allocator_capture cri(stack.result); + + if (comp(l, convert_string_to_number(string_value(*ri, stack.result).c_str()))) + return true; + } + + return false; + } + else if (lt == xpath_type_string) + { + xpath_allocator_capture cr(stack.result); + + xpath_string l = lhs->eval_string(c, stack); + xpath_node_set_raw rs = rhs->eval_node_set(c, stack); + + for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri) + { + xpath_allocator_capture cri(stack.result); + + if (comp(l, string_value(*ri, stack.result))) + return true; + } + + return false; + } + } + + assert(!"Wrong types"); + return false; + } + + template <class Comp> static bool compare_rel(xpath_ast_node* lhs, xpath_ast_node* rhs, const xpath_context& c, const xpath_stack& stack, const Comp& comp) + { + xpath_value_type lt = lhs->rettype(), rt = rhs->rettype(); + + if (lt != xpath_type_node_set && rt != xpath_type_node_set) + return comp(lhs->eval_number(c, stack), rhs->eval_number(c, stack)); + else if (lt == xpath_type_node_set && rt == xpath_type_node_set) + { + xpath_allocator_capture cr(stack.result); + + xpath_node_set_raw ls = lhs->eval_node_set(c, stack); + xpath_node_set_raw rs = rhs->eval_node_set(c, stack); + + for (const xpath_node* li = ls.begin(); li != ls.end(); ++li) + { + xpath_allocator_capture cri(stack.result); + + double l = convert_string_to_number(string_value(*li, stack.result).c_str()); + + for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri) + { + xpath_allocator_capture crii(stack.result); + + if (comp(l, convert_string_to_number(string_value(*ri, stack.result).c_str()))) + return true; + } + } + + return false; + } + else if (lt != xpath_type_node_set && rt == xpath_type_node_set) + { + xpath_allocator_capture cr(stack.result); + + double l = lhs->eval_number(c, stack); + xpath_node_set_raw rs = rhs->eval_node_set(c, stack); + + for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri) + { + xpath_allocator_capture cri(stack.result); + + if (comp(l, convert_string_to_number(string_value(*ri, stack.result).c_str()))) + return true; + } + + return false; + } + else if (lt == xpath_type_node_set && rt != xpath_type_node_set) + { + xpath_allocator_capture cr(stack.result); + + xpath_node_set_raw ls = lhs->eval_node_set(c, stack); + double r = rhs->eval_number(c, stack); + + for (const xpath_node* li = ls.begin(); li != ls.end(); ++li) + { + xpath_allocator_capture cri(stack.result); + + if (comp(convert_string_to_number(string_value(*li, stack.result).c_str()), r)) + return true; + } + + return false; + } + else + { + assert(!"Wrong types"); + return false; + } + } + + void apply_predicate(xpath_node_set_raw& ns, size_t first, xpath_ast_node* expr, const xpath_stack& stack) + { + assert(ns.size() >= first); + + size_t i = 1; + size_t size = ns.size() - first; + + xpath_node* last = ns.begin() + first; + + // remove_if... or well, sort of + for (xpath_node* it = last; it != ns.end(); ++it, ++i) + { + xpath_context c(*it, i, size); + + if (expr->rettype() == xpath_type_number) + { + if (expr->eval_number(c, stack) == i) + *last++ = *it; + } + else if (expr->eval_boolean(c, stack)) + *last++ = *it; + } + + ns.truncate(last); + } + + void apply_predicates(xpath_node_set_raw& ns, size_t first, const xpath_stack& stack) + { + if (ns.size() == first) return; + + for (xpath_ast_node* pred = _right; pred; pred = pred->_next) + { + apply_predicate(ns, first, pred->_left, stack); + } + } + + void step_push(xpath_node_set_raw& ns, const xml_attribute& a, const xml_node& parent, xpath_allocator* alloc) + { + if (!a) return; + + const char_t* name = a.name(); + + // There are no attribute nodes corresponding to attributes that declare namespaces + // That is, "xmlns:..." or "xmlns" + if (starts_with(name, PUGIXML_TEXT("xmlns")) && (name[5] == 0 || name[5] == ':')) return; + + switch (_test) + { + case nodetest_name: + if (strequal(name, _data.nodetest)) ns.push_back(xpath_node(a, parent), alloc); + break; + + case nodetest_type_node: + case nodetest_all: + ns.push_back(xpath_node(a, parent), alloc); + break; + + case nodetest_all_in_namespace: + if (starts_with(name, _data.nodetest)) + ns.push_back(xpath_node(a, parent), alloc); + break; + + default: + ; + } + } + + void step_push(xpath_node_set_raw& ns, const xml_node& n, xpath_allocator* alloc) + { + if (!n) return; + + switch (_test) + { + case nodetest_name: + if (n.type() == node_element && strequal(n.name(), _data.nodetest)) ns.push_back(n, alloc); + break; + + case nodetest_type_node: + ns.push_back(n, alloc); + break; + + case nodetest_type_comment: + if (n.type() == node_comment) + ns.push_back(n, alloc); + break; + + case nodetest_type_text: + if (n.type() == node_pcdata || n.type() == node_cdata) + ns.push_back(n, alloc); + break; + + case nodetest_type_pi: + if (n.type() == node_pi) + ns.push_back(n, alloc); + break; + + case nodetest_pi: + if (n.type() == node_pi && strequal(n.name(), _data.nodetest)) + ns.push_back(n, alloc); + break; + + case nodetest_all: + if (n.type() == node_element) + ns.push_back(n, alloc); + break; + + case nodetest_all_in_namespace: + if (n.type() == node_element && starts_with(n.name(), _data.nodetest)) + ns.push_back(n, alloc); + break; + + default: + assert(!"Unknown axis"); + } + } + + template <class T> void step_fill(xpath_node_set_raw& ns, const xml_node& n, xpath_allocator* alloc, T) + { + const axis_t axis = T::axis; + + switch (axis) + { + case axis_attribute: + { + for (xml_attribute a = n.first_attribute(); a; a = a.next_attribute()) + step_push(ns, a, n, alloc); + + break; + } + + case axis_child: + { + for (xml_node c = n.first_child(); c; c = c.next_sibling()) + step_push(ns, c, alloc); + + break; + } + + case axis_descendant: + case axis_descendant_or_self: + { + if (axis == axis_descendant_or_self) + step_push(ns, n, alloc); + + xml_node cur = n.first_child(); + + while (cur && cur != n) + { + step_push(ns, cur, alloc); + + if (cur.first_child()) + cur = cur.first_child(); + else if (cur.next_sibling()) + cur = cur.next_sibling(); + else + { + while (!cur.next_sibling() && cur != n) + cur = cur.parent(); + + if (cur != n) cur = cur.next_sibling(); + } + } + + break; + } + + case axis_following_sibling: + { + for (xml_node c = n.next_sibling(); c; c = c.next_sibling()) + step_push(ns, c, alloc); + + break; + } + + case axis_preceding_sibling: + { + for (xml_node c = n.previous_sibling(); c; c = c.previous_sibling()) + step_push(ns, c, alloc); + + break; + } + + case axis_following: + { + xml_node cur = n; + + // exit from this node so that we don't include descendants + while (cur && !cur.next_sibling()) cur = cur.parent(); + cur = cur.next_sibling(); + + for (;;) + { + step_push(ns, cur, alloc); + + if (cur.first_child()) + cur = cur.first_child(); + else if (cur.next_sibling()) + cur = cur.next_sibling(); + else + { + while (cur && !cur.next_sibling()) cur = cur.parent(); + cur = cur.next_sibling(); + + if (!cur) break; + } + } + + break; + } + + case axis_preceding: + { + xml_node cur = n; + + while (cur && !cur.previous_sibling()) cur = cur.parent(); + cur = cur.previous_sibling(); + + for (;;) + { + if (cur.last_child()) + cur = cur.last_child(); + else + { + // leaf node, can't be ancestor + step_push(ns, cur, alloc); + + if (cur.previous_sibling()) + cur = cur.previous_sibling(); + else + { + do + { + cur = cur.parent(); + if (!cur) break; + + if (!node_is_ancestor(cur, n)) step_push(ns, cur, alloc); + } + while (!cur.previous_sibling()); + + cur = cur.previous_sibling(); + + if (!cur) break; + } + } + } + + break; + } + + case axis_ancestor: + case axis_ancestor_or_self: + { + if (axis == axis_ancestor_or_self) + step_push(ns, n, alloc); + + xml_node cur = n.parent(); + + while (cur) + { + step_push(ns, cur, alloc); + + cur = cur.parent(); + } + + break; + } + + case axis_self: + { + step_push(ns, n, alloc); + + break; + } + + case axis_parent: + { + if (n.parent()) step_push(ns, n.parent(), alloc); + + break; + } + + default: + assert(!"Unimplemented axis"); + } + } + + template <class T> void step_fill(xpath_node_set_raw& ns, const xml_attribute& a, const xml_node& p, xpath_allocator* alloc, T v) + { + const axis_t axis = T::axis; + + switch (axis) + { + case axis_ancestor: + case axis_ancestor_or_self: + { + if (axis == axis_ancestor_or_self && _test == nodetest_type_node) // reject attributes based on principal node type test + step_push(ns, a, p, alloc); + + xml_node cur = p; + + while (cur) + { + step_push(ns, cur, alloc); + + cur = cur.parent(); + } + + break; + } + + case axis_descendant_or_self: + case axis_self: + { + if (_test == nodetest_type_node) // reject attributes based on principal node type test + step_push(ns, a, p, alloc); + + break; + } + + case axis_following: + { + xml_node cur = p; + + for (;;) + { + if (cur.first_child()) + cur = cur.first_child(); + else if (cur.next_sibling()) + cur = cur.next_sibling(); + else + { + while (cur && !cur.next_sibling()) cur = cur.parent(); + cur = cur.next_sibling(); + + if (!cur) break; + } + + step_push(ns, cur, alloc); + } + + break; + } + + case axis_parent: + { + step_push(ns, p, alloc); + + break; + } + + case axis_preceding: + { + // preceding:: axis does not include attribute nodes and attribute ancestors (they are the same as parent's ancestors), so we can reuse node preceding + step_fill(ns, p, alloc, v); + break; + } + + default: + assert(!"Unimplemented axis"); + } + } + + template <class T> xpath_node_set_raw step_do(const xpath_context& c, const xpath_stack& stack, T v) + { + const axis_t axis = T::axis; + bool attributes = (axis == axis_ancestor || axis == axis_ancestor_or_self || axis == axis_descendant_or_self || axis == axis_following || axis == axis_parent || axis == axis_preceding || axis == axis_self); + + xpath_node_set_raw ns; + ns.set_type((axis == axis_ancestor || axis == axis_ancestor_or_self || axis == axis_preceding || axis == axis_preceding_sibling) ? xpath_node_set::type_sorted_reverse : xpath_node_set::type_sorted); + + if (_left) + { + xpath_node_set_raw s = _left->eval_node_set(c, stack); + + // self axis preserves the original order + if (axis == axis_self) ns.set_type(s.type()); + + for (const xpath_node* it = s.begin(); it != s.end(); ++it) + { + size_t size = ns.size(); + + // in general, all axes generate elements in a particular order, but there is no order guarantee if axis is applied to two nodes + if (axis != axis_self && size != 0) ns.set_type(xpath_node_set::type_unsorted); + + if (it->node()) + step_fill(ns, it->node(), stack.result, v); + else if (attributes) + step_fill(ns, it->attribute(), it->parent(), stack.result, v); + + apply_predicates(ns, size, stack); + } + } + else + { + if (c.n.node()) + step_fill(ns, c.n.node(), stack.result, v); + else if (attributes) + step_fill(ns, c.n.attribute(), c.n.parent(), stack.result, v); + + apply_predicates(ns, 0, stack); + } + + // child, attribute and self axes always generate unique set of nodes + // for other axis, if the set stayed sorted, it stayed unique because the traversal algorithms do not visit the same node twice + if (axis != axis_child && axis != axis_attribute && axis != axis_self && ns.type() == xpath_node_set::type_unsorted) + ns.remove_duplicates(); + + return ns; + } + + public: + xpath_ast_node(ast_type_t type, xpath_value_type rettype_, const char_t* value): + _type(static_cast<char>(type)), _rettype(static_cast<char>(rettype_)), _axis(0), _test(0), _left(0), _right(0), _next(0) + { + assert(type == ast_string_constant); + _data.string = value; + } + + xpath_ast_node(ast_type_t type, xpath_value_type rettype_, double value): + _type(static_cast<char>(type)), _rettype(static_cast<char>(rettype_)), _axis(0), _test(0), _left(0), _right(0), _next(0) + { + assert(type == ast_number_constant); + _data.number = value; + } + + xpath_ast_node(ast_type_t type, xpath_value_type rettype_, xpath_variable* value): + _type(static_cast<char>(type)), _rettype(static_cast<char>(rettype_)), _axis(0), _test(0), _left(0), _right(0), _next(0) + { + assert(type == ast_variable); + _data.variable = value; + } + + xpath_ast_node(ast_type_t type, xpath_value_type rettype_, xpath_ast_node* left = 0, xpath_ast_node* right = 0): + _type(static_cast<char>(type)), _rettype(static_cast<char>(rettype_)), _axis(0), _test(0), _left(left), _right(right), _next(0) + { + } + + xpath_ast_node(ast_type_t type, xpath_ast_node* left, axis_t axis, nodetest_t test, const char_t* contents): + _type(static_cast<char>(type)), _rettype(xpath_type_node_set), _axis(static_cast<char>(axis)), _test(static_cast<char>(test)), _left(left), _right(0), _next(0) + { + _data.nodetest = contents; + } + + void set_next(xpath_ast_node* value) + { + _next = value; + } + + void set_right(xpath_ast_node* value) + { + _right = value; + } + + bool eval_boolean(const xpath_context& c, const xpath_stack& stack) + { + switch (_type) + { + case ast_op_or: + return _left->eval_boolean(c, stack) || _right->eval_boolean(c, stack); + + case ast_op_and: + return _left->eval_boolean(c, stack) && _right->eval_boolean(c, stack); + + case ast_op_equal: + return compare_eq(_left, _right, c, stack, equal_to()); + + case ast_op_not_equal: + return compare_eq(_left, _right, c, stack, not_equal_to()); + + case ast_op_less: + return compare_rel(_left, _right, c, stack, less()); + + case ast_op_greater: + return compare_rel(_right, _left, c, stack, less()); + + case ast_op_less_or_equal: + return compare_rel(_left, _right, c, stack, less_equal()); + + case ast_op_greater_or_equal: + return compare_rel(_right, _left, c, stack, less_equal()); + + case ast_func_starts_with: + { + xpath_allocator_capture cr(stack.result); + + xpath_string lr = _left->eval_string(c, stack); + xpath_string rr = _right->eval_string(c, stack); + + return starts_with(lr.c_str(), rr.c_str()); + } + + case ast_func_contains: + { + xpath_allocator_capture cr(stack.result); + + xpath_string lr = _left->eval_string(c, stack); + xpath_string rr = _right->eval_string(c, stack); + + return find_substring(lr.c_str(), rr.c_str()) != 0; + } + + case ast_func_boolean: + return _left->eval_boolean(c, stack); + + case ast_func_not: + return !_left->eval_boolean(c, stack); + + case ast_func_true: + return true; + + case ast_func_false: + return false; + + case ast_func_lang: + { + if (c.n.attribute()) return false; + + xpath_allocator_capture cr(stack.result); + + xpath_string lang = _left->eval_string(c, stack); + + for (xml_node n = c.n.node(); n; n = n.parent()) + { + xml_attribute a = n.attribute(PUGIXML_TEXT("xml:lang")); + + if (a) + { + const char_t* value = a.value(); + + // strnicmp / strncasecmp is not portable + for (const char_t* lit = lang.c_str(); *lit; ++lit) + { + if (tolower_ascii(*lit) != tolower_ascii(*value)) return false; + ++value; + } + + return *value == 0 || *value == '-'; + } + } + + return false; + } + + case ast_variable: + { + assert(_rettype == _data.variable->type()); + + if (_rettype == xpath_type_boolean) + return _data.variable->get_boolean(); + + // fallthrough to type conversion + } + + default: + { + switch (_rettype) + { + case xpath_type_number: + return convert_number_to_boolean(eval_number(c, stack)); + + case xpath_type_string: + { + xpath_allocator_capture cr(stack.result); + + return !eval_string(c, stack).empty(); + } + + case xpath_type_node_set: + { + xpath_allocator_capture cr(stack.result); + + return !eval_node_set(c, stack).empty(); + } + + default: + assert(!"Wrong expression for return type boolean"); + return false; + } + } + } + } + + double eval_number(const xpath_context& c, const xpath_stack& stack) + { + switch (_type) + { + case ast_op_add: + return _left->eval_number(c, stack) + _right->eval_number(c, stack); + + case ast_op_subtract: + return _left->eval_number(c, stack) - _right->eval_number(c, stack); + + case ast_op_multiply: + return _left->eval_number(c, stack) * _right->eval_number(c, stack); + + case ast_op_divide: + return _left->eval_number(c, stack) / _right->eval_number(c, stack); + + case ast_op_mod: + return fmod(_left->eval_number(c, stack), _right->eval_number(c, stack)); + + case ast_op_negate: + return -_left->eval_number(c, stack); + + case ast_number_constant: + return _data.number; + + case ast_func_last: + return static_cast<double>(c.size); + + case ast_func_position: + return static_cast<double>(c.position); + + case ast_func_count: + { + xpath_allocator_capture cr(stack.result); + + return static_cast<double>(_left->eval_node_set(c, stack).size()); + } + + case ast_func_string_length_0: + { + xpath_allocator_capture cr(stack.result); + + return static_cast<double>(string_value(c.n, stack.result).length()); + } + + case ast_func_string_length_1: + { + xpath_allocator_capture cr(stack.result); + + return static_cast<double>(_left->eval_string(c, stack).length()); + } + + case ast_func_number_0: + { + xpath_allocator_capture cr(stack.result); + + return convert_string_to_number(string_value(c.n, stack.result).c_str()); + } + + case ast_func_number_1: + return _left->eval_number(c, stack); + + case ast_func_sum: + { + xpath_allocator_capture cr(stack.result); + + double r = 0; + + xpath_node_set_raw ns = _left->eval_node_set(c, stack); + + for (const xpath_node* it = ns.begin(); it != ns.end(); ++it) + { + xpath_allocator_capture cri(stack.result); + + r += convert_string_to_number(string_value(*it, stack.result).c_str()); + } + + return r; + } + + case ast_func_floor: + { + double r = _left->eval_number(c, stack); + + return r == r ? floor(r) : r; + } + + case ast_func_ceiling: + { + double r = _left->eval_number(c, stack); + + return r == r ? ceil(r) : r; + } + + case ast_func_round: + return round_nearest_nzero(_left->eval_number(c, stack)); + + case ast_variable: + { + assert(_rettype == _data.variable->type()); + + if (_rettype == xpath_type_number) + return _data.variable->get_number(); + + // fallthrough to type conversion + } + + default: + { + switch (_rettype) + { + case xpath_type_boolean: + return eval_boolean(c, stack) ? 1 : 0; + + case xpath_type_string: + { + xpath_allocator_capture cr(stack.result); + + return convert_string_to_number(eval_string(c, stack).c_str()); + } + + case xpath_type_node_set: + { + xpath_allocator_capture cr(stack.result); + + return convert_string_to_number(eval_string(c, stack).c_str()); + } + + default: + assert(!"Wrong expression for return type number"); + return 0; + } + + } + } + } + + xpath_string eval_string_concat(const xpath_context& c, const xpath_stack& stack) + { + assert(_type == ast_func_concat); + + xpath_allocator_capture ct(stack.temp); + + // count the string number + size_t count = 1; + for (xpath_ast_node* nc = _right; nc; nc = nc->_next) count++; + + // gather all strings + xpath_string static_buffer[4]; + xpath_string* buffer = static_buffer; + + // allocate on-heap for large concats + if (count > sizeof(static_buffer) / sizeof(static_buffer[0])) + { + buffer = static_cast<xpath_string*>(stack.temp->allocate(count * sizeof(xpath_string))); + assert(buffer); + } + + // evaluate all strings to temporary stack + xpath_stack swapped_stack = {stack.temp, stack.result}; + + buffer[0] = _left->eval_string(c, swapped_stack); + + size_t pos = 1; + for (xpath_ast_node* n = _right; n; n = n->_next, ++pos) buffer[pos] = n->eval_string(c, swapped_stack); + assert(pos == count); + + // get total length + size_t length = 0; + for (size_t i = 0; i < count; ++i) length += buffer[i].length(); + + // create final string + char_t* result = static_cast<char_t*>(stack.result->allocate((length + 1) * sizeof(char_t))); + assert(result); + + char_t* ri = result; + + for (size_t j = 0; j < count; ++j) + for (const char_t* bi = buffer[j].c_str(); *bi; ++bi) + *ri++ = *bi; + + *ri = 0; + + return xpath_string(result, true); + } + + xpath_string eval_string(const xpath_context& c, const xpath_stack& stack) + { + switch (_type) + { + case ast_string_constant: + return xpath_string_const(_data.string); + + case ast_func_local_name_0: + { + xpath_node na = c.n; + + return xpath_string_const(local_name(na)); + } + + case ast_func_local_name_1: + { + xpath_allocator_capture cr(stack.result); + + xpath_node_set_raw ns = _left->eval_node_set(c, stack); + xpath_node na = ns.first(); + + return xpath_string_const(local_name(na)); + } + + case ast_func_name_0: + { + xpath_node na = c.n; + + return xpath_string_const(qualified_name(na)); + } + + case ast_func_name_1: + { + xpath_allocator_capture cr(stack.result); + + xpath_node_set_raw ns = _left->eval_node_set(c, stack); + xpath_node na = ns.first(); + + return xpath_string_const(qualified_name(na)); + } + + case ast_func_namespace_uri_0: + { + xpath_node na = c.n; + + return xpath_string_const(namespace_uri(na)); + } + + case ast_func_namespace_uri_1: + { + xpath_allocator_capture cr(stack.result); + + xpath_node_set_raw ns = _left->eval_node_set(c, stack); + xpath_node na = ns.first(); + + return xpath_string_const(namespace_uri(na)); + } + + case ast_func_string_0: + return string_value(c.n, stack.result); + + case ast_func_string_1: + return _left->eval_string(c, stack); + + case ast_func_concat: + return eval_string_concat(c, stack); + + case ast_func_substring_before: + { + xpath_allocator_capture cr(stack.temp); + + xpath_stack swapped_stack = {stack.temp, stack.result}; + + xpath_string s = _left->eval_string(c, swapped_stack); + xpath_string p = _right->eval_string(c, swapped_stack); + + const char_t* pos = find_substring(s.c_str(), p.c_str()); + + return pos ? xpath_string(s.c_str(), pos, stack.result) : xpath_string(); + } + + case ast_func_substring_after: + { + xpath_allocator_capture cr(stack.temp); + + xpath_stack swapped_stack = {stack.temp, stack.result}; + + xpath_string s = _left->eval_string(c, swapped_stack); + xpath_string p = _right->eval_string(c, swapped_stack); + + const char_t* pos = find_substring(s.c_str(), p.c_str()); + if (!pos) return xpath_string(); + + const char_t* result = pos + p.length(); + + return s.uses_heap() ? xpath_string(result, stack.result) : xpath_string_const(result); + } + + case ast_func_substring_2: + { + xpath_allocator_capture cr(stack.temp); + + xpath_stack swapped_stack = {stack.temp, stack.result}; + + xpath_string s = _left->eval_string(c, swapped_stack); + size_t s_length = s.length(); + + double first = round_nearest(_right->eval_number(c, stack)); + + if (is_nan(first)) return xpath_string(); // NaN + else if (first >= s_length + 1) return xpath_string(); + + size_t pos = first < 1 ? 1 : static_cast<size_t>(first); + assert(1 <= pos && pos <= s_length + 1); + + const char_t* rbegin = s.c_str() + (pos - 1); + + return s.uses_heap() ? xpath_string(rbegin, stack.result) : xpath_string_const(rbegin); + } + + case ast_func_substring_3: + { + xpath_allocator_capture cr(stack.temp); + + xpath_stack swapped_stack = {stack.temp, stack.result}; + + xpath_string s = _left->eval_string(c, swapped_stack); + size_t s_length = s.length(); + + double first = round_nearest(_right->eval_number(c, stack)); + double last = first + round_nearest(_right->_next->eval_number(c, stack)); + + if (is_nan(first) || is_nan(last)) return xpath_string(); + else if (first >= s_length + 1) return xpath_string(); + else if (first >= last) return xpath_string(); + else if (last < 1) return xpath_string(); + + size_t pos = first < 1 ? 1 : static_cast<size_t>(first); + size_t end = last >= s_length + 1 ? s_length + 1 : static_cast<size_t>(last); + + assert(1 <= pos && pos <= end && end <= s_length + 1); + const char_t* rbegin = s.c_str() + (pos - 1); + const char_t* rend = s.c_str() + (end - 1); + + return (end == s_length + 1 && !s.uses_heap()) ? xpath_string_const(rbegin) : xpath_string(rbegin, rend, stack.result); + } + + case ast_func_normalize_space_0: + { + xpath_string s = string_value(c.n, stack.result); + + normalize_space(s.data(stack.result)); + + return s; + } + + case ast_func_normalize_space_1: + { + xpath_string s = _left->eval_string(c, stack); + + normalize_space(s.data(stack.result)); + + return s; + } + + case ast_func_translate: + { + xpath_allocator_capture cr(stack.temp); + + xpath_stack swapped_stack = {stack.temp, stack.result}; + + xpath_string s = _left->eval_string(c, stack); + xpath_string from = _right->eval_string(c, swapped_stack); + xpath_string to = _right->_next->eval_string(c, swapped_stack); + + translate(s.data(stack.result), from.c_str(), to.c_str()); + + return s; + } + + case ast_variable: + { + assert(_rettype == _data.variable->type()); + + if (_rettype == xpath_type_string) + return xpath_string_const(_data.variable->get_string()); + + // fallthrough to type conversion + } + + default: + { + switch (_rettype) + { + case xpath_type_boolean: + return xpath_string_const(eval_boolean(c, stack) ? PUGIXML_TEXT("true") : PUGIXML_TEXT("false")); + + case xpath_type_number: + return convert_number_to_string(eval_number(c, stack), stack.result); + + case xpath_type_node_set: + { + xpath_allocator_capture cr(stack.temp); + + xpath_stack swapped_stack = {stack.temp, stack.result}; + + xpath_node_set_raw ns = eval_node_set(c, swapped_stack); + return ns.empty() ? xpath_string() : string_value(ns.first(), stack.result); + } + + default: + assert(!"Wrong expression for return type string"); + return xpath_string(); + } + } + } + } + + xpath_node_set_raw eval_node_set(const xpath_context& c, const xpath_stack& stack) + { + switch (_type) + { + case ast_op_union: + { + xpath_allocator_capture cr(stack.temp); + + xpath_stack swapped_stack = {stack.temp, stack.result}; + + xpath_node_set_raw ls = _left->eval_node_set(c, swapped_stack); + xpath_node_set_raw rs = _right->eval_node_set(c, stack); + + // we can optimize merging two sorted sets, but this is a very rare operation, so don't bother + rs.set_type(xpath_node_set::type_unsorted); + + rs.append(ls.begin(), ls.end(), stack.result); + rs.remove_duplicates(); + + return rs; + } + + case ast_filter: + case ast_filter_posinv: + { + xpath_node_set_raw set = _left->eval_node_set(c, stack); + + // either expression is a number or it contains position() call; sort by document order + if (_type == ast_filter) set.sort_do(); + + apply_predicate(set, 0, _right, stack); + + return set; + } + + case ast_func_id: + return xpath_node_set_raw(); + + case ast_step: + { + switch (_axis) + { + case axis_ancestor: + return step_do(c, stack, axis_to_type<axis_ancestor>()); + + case axis_ancestor_or_self: + return step_do(c, stack, axis_to_type<axis_ancestor_or_self>()); + + case axis_attribute: + return step_do(c, stack, axis_to_type<axis_attribute>()); + + case axis_child: + return step_do(c, stack, axis_to_type<axis_child>()); + + case axis_descendant: + return step_do(c, stack, axis_to_type<axis_descendant>()); + + case axis_descendant_or_self: + return step_do(c, stack, axis_to_type<axis_descendant_or_self>()); + + case axis_following: + return step_do(c, stack, axis_to_type<axis_following>()); + + case axis_following_sibling: + return step_do(c, stack, axis_to_type<axis_following_sibling>()); + + case axis_namespace: + // namespaced axis is not supported + return xpath_node_set_raw(); + + case axis_parent: + return step_do(c, stack, axis_to_type<axis_parent>()); + + case axis_preceding: + return step_do(c, stack, axis_to_type<axis_preceding>()); + + case axis_preceding_sibling: + return step_do(c, stack, axis_to_type<axis_preceding_sibling>()); + + case axis_self: + return step_do(c, stack, axis_to_type<axis_self>()); + + default: + assert(!"Unknown axis"); + return xpath_node_set_raw(); + } + } + + case ast_step_root: + { + assert(!_right); // root step can't have any predicates + + xpath_node_set_raw ns; + + ns.set_type(xpath_node_set::type_sorted); + + if (c.n.node()) ns.push_back(c.n.node().root(), stack.result); + else if (c.n.attribute()) ns.push_back(c.n.parent().root(), stack.result); + + return ns; + } + + case ast_variable: + { + assert(_rettype == _data.variable->type()); + + if (_rettype == xpath_type_node_set) + { + const xpath_node_set& s = _data.variable->get_node_set(); + + xpath_node_set_raw ns; + + ns.set_type(s.type()); + ns.append(s.begin(), s.end(), stack.result); + + return ns; + } + + // fallthrough to type conversion + } + + default: + assert(!"Wrong expression for return type node set"); + return xpath_node_set_raw(); + } + } + + bool is_posinv() + { + switch (_type) + { + case ast_func_position: + return false; + + case ast_string_constant: + case ast_number_constant: + case ast_variable: + return true; + + case ast_step: + case ast_step_root: + return true; + + case ast_predicate: + case ast_filter: + case ast_filter_posinv: + return true; + + default: + if (_left && !_left->is_posinv()) return false; + + for (xpath_ast_node* n = _right; n; n = n->_next) + if (!n->is_posinv()) return false; + + return true; + } + } + + xpath_value_type rettype() const + { + return static_cast<xpath_value_type>(_rettype); + } + }; + + struct xpath_parser + { + xpath_allocator* _alloc; + xpath_lexer _lexer; + + const char_t* _query; + xpath_variable_set* _variables; + + xpath_parse_result* _result; + + char_t _scratch[32]; + + #ifdef PUGIXML_NO_EXCEPTIONS + jmp_buf _error_handler; + #endif + + void throw_error(const char* message) + { + _result->error = message; + _result->offset = _lexer.current_pos() - _query; + + #ifdef PUGIXML_NO_EXCEPTIONS + longjmp(_error_handler, 1); + #else + throw xpath_exception(*_result); + #endif + } + + void throw_error_oom() + { + #ifdef PUGIXML_NO_EXCEPTIONS + throw_error("Out of memory"); + #else + throw std::bad_alloc(); + #endif + } + + void* alloc_node() + { + void* result = _alloc->allocate_nothrow(sizeof(xpath_ast_node)); + + if (!result) throw_error_oom(); + + return result; + } + + const char_t* alloc_string(const xpath_lexer_string& value) + { + if (value.begin) + { + size_t length = static_cast<size_t>(value.end - value.begin); + + char_t* c = static_cast<char_t*>(_alloc->allocate_nothrow((length + 1) * sizeof(char_t))); + if (!c) throw_error_oom(); + assert(c); // workaround for clang static analysis + + memcpy(c, value.begin, length * sizeof(char_t)); + c[length] = 0; + + return c; + } + else return 0; + } + + xpath_ast_node* parse_function_helper(ast_type_t type0, ast_type_t type1, size_t argc, xpath_ast_node* args[2]) + { + assert(argc <= 1); + + if (argc == 1 && args[0]->rettype() != xpath_type_node_set) throw_error("Function has to be applied to node set"); + + return new (alloc_node()) xpath_ast_node(argc == 0 ? type0 : type1, xpath_type_string, args[0]); + } + + xpath_ast_node* parse_function(const xpath_lexer_string& name, size_t argc, xpath_ast_node* args[2]) + { + switch (name.begin[0]) + { + case 'b': + if (name == PUGIXML_TEXT("boolean") && argc == 1) + return new (alloc_node()) xpath_ast_node(ast_func_boolean, xpath_type_boolean, args[0]); + + break; + + case 'c': + if (name == PUGIXML_TEXT("count") && argc == 1) + { + if (args[0]->rettype() != xpath_type_node_set) throw_error("Function has to be applied to node set"); + return new (alloc_node()) xpath_ast_node(ast_func_count, xpath_type_number, args[0]); + } + else if (name == PUGIXML_TEXT("contains") && argc == 2) + return new (alloc_node()) xpath_ast_node(ast_func_contains, xpath_type_boolean, args[0], args[1]); + else if (name == PUGIXML_TEXT("concat") && argc >= 2) + return new (alloc_node()) xpath_ast_node(ast_func_concat, xpath_type_string, args[0], args[1]); + else if (name == PUGIXML_TEXT("ceiling") && argc == 1) + return new (alloc_node()) xpath_ast_node(ast_func_ceiling, xpath_type_number, args[0]); + + break; + + case 'f': + if (name == PUGIXML_TEXT("false") && argc == 0) + return new (alloc_node()) xpath_ast_node(ast_func_false, xpath_type_boolean); + else if (name == PUGIXML_TEXT("floor") && argc == 1) + return new (alloc_node()) xpath_ast_node(ast_func_floor, xpath_type_number, args[0]); + + break; + + case 'i': + if (name == PUGIXML_TEXT("id") && argc == 1) + return new (alloc_node()) xpath_ast_node(ast_func_id, xpath_type_node_set, args[0]); + + break; + + case 'l': + if (name == PUGIXML_TEXT("last") && argc == 0) + return new (alloc_node()) xpath_ast_node(ast_func_last, xpath_type_number); + else if (name == PUGIXML_TEXT("lang") && argc == 1) + return new (alloc_node()) xpath_ast_node(ast_func_lang, xpath_type_boolean, args[0]); + else if (name == PUGIXML_TEXT("local-name") && argc <= 1) + return parse_function_helper(ast_func_local_name_0, ast_func_local_name_1, argc, args); + + break; + + case 'n': + if (name == PUGIXML_TEXT("name") && argc <= 1) + return parse_function_helper(ast_func_name_0, ast_func_name_1, argc, args); + else if (name == PUGIXML_TEXT("namespace-uri") && argc <= 1) + return parse_function_helper(ast_func_namespace_uri_0, ast_func_namespace_uri_1, argc, args); + else if (name == PUGIXML_TEXT("normalize-space") && argc <= 1) + return new (alloc_node()) xpath_ast_node(argc == 0 ? ast_func_normalize_space_0 : ast_func_normalize_space_1, xpath_type_string, args[0], args[1]); + else if (name == PUGIXML_TEXT("not") && argc == 1) + return new (alloc_node()) xpath_ast_node(ast_func_not, xpath_type_boolean, args[0]); + else if (name == PUGIXML_TEXT("number") && argc <= 1) + return new (alloc_node()) xpath_ast_node(argc == 0 ? ast_func_number_0 : ast_func_number_1, xpath_type_number, args[0]); + + break; + + case 'p': + if (name == PUGIXML_TEXT("position") && argc == 0) + return new (alloc_node()) xpath_ast_node(ast_func_position, xpath_type_number); + + break; + + case 'r': + if (name == PUGIXML_TEXT("round") && argc == 1) + return new (alloc_node()) xpath_ast_node(ast_func_round, xpath_type_number, args[0]); + + break; + + case 's': + if (name == PUGIXML_TEXT("string") && argc <= 1) + return new (alloc_node()) xpath_ast_node(argc == 0 ? ast_func_string_0 : ast_func_string_1, xpath_type_string, args[0]); + else if (name == PUGIXML_TEXT("string-length") && argc <= 1) + return new (alloc_node()) xpath_ast_node(argc == 0 ? ast_func_string_length_0 : ast_func_string_length_1, xpath_type_number, args[0]); + else if (name == PUGIXML_TEXT("starts-with") && argc == 2) + return new (alloc_node()) xpath_ast_node(ast_func_starts_with, xpath_type_boolean, args[0], args[1]); + else if (name == PUGIXML_TEXT("substring-before") && argc == 2) + return new (alloc_node()) xpath_ast_node(ast_func_substring_before, xpath_type_string, args[0], args[1]); + else if (name == PUGIXML_TEXT("substring-after") && argc == 2) + return new (alloc_node()) xpath_ast_node(ast_func_substring_after, xpath_type_string, args[0], args[1]); + else if (name == PUGIXML_TEXT("substring") && (argc == 2 || argc == 3)) + return new (alloc_node()) xpath_ast_node(argc == 2 ? ast_func_substring_2 : ast_func_substring_3, xpath_type_string, args[0], args[1]); + else if (name == PUGIXML_TEXT("sum") && argc == 1) + { + if (args[0]->rettype() != xpath_type_node_set) throw_error("Function has to be applied to node set"); + return new (alloc_node()) xpath_ast_node(ast_func_sum, xpath_type_number, args[0]); + } + + break; + + case 't': + if (name == PUGIXML_TEXT("translate") && argc == 3) + return new (alloc_node()) xpath_ast_node(ast_func_translate, xpath_type_string, args[0], args[1]); + else if (name == PUGIXML_TEXT("true") && argc == 0) + return new (alloc_node()) xpath_ast_node(ast_func_true, xpath_type_boolean); + + break; + + default: + break; + } + + throw_error("Unrecognized function or wrong parameter count"); + + return 0; + } + + axis_t parse_axis_name(const xpath_lexer_string& name, bool& specified) + { + specified = true; + + switch (name.begin[0]) + { + case 'a': + if (name == PUGIXML_TEXT("ancestor")) + return axis_ancestor; + else if (name == PUGIXML_TEXT("ancestor-or-self")) + return axis_ancestor_or_self; + else if (name == PUGIXML_TEXT("attribute")) + return axis_attribute; + + break; + + case 'c': + if (name == PUGIXML_TEXT("child")) + return axis_child; + + break; + + case 'd': + if (name == PUGIXML_TEXT("descendant")) + return axis_descendant; + else if (name == PUGIXML_TEXT("descendant-or-self")) + return axis_descendant_or_self; + + break; + + case 'f': + if (name == PUGIXML_TEXT("following")) + return axis_following; + else if (name == PUGIXML_TEXT("following-sibling")) + return axis_following_sibling; + + break; + + case 'n': + if (name == PUGIXML_TEXT("namespace")) + return axis_namespace; + + break; + + case 'p': + if (name == PUGIXML_TEXT("parent")) + return axis_parent; + else if (name == PUGIXML_TEXT("preceding")) + return axis_preceding; + else if (name == PUGIXML_TEXT("preceding-sibling")) + return axis_preceding_sibling; + + break; + + case 's': + if (name == PUGIXML_TEXT("self")) + return axis_self; + + break; + + default: + break; + } + + specified = false; + return axis_child; + } + + nodetest_t parse_node_test_type(const xpath_lexer_string& name) + { + switch (name.begin[0]) + { + case 'c': + if (name == PUGIXML_TEXT("comment")) + return nodetest_type_comment; + + break; + + case 'n': + if (name == PUGIXML_TEXT("node")) + return nodetest_type_node; + + break; + + case 'p': + if (name == PUGIXML_TEXT("processing-instruction")) + return nodetest_type_pi; + + break; + + case 't': + if (name == PUGIXML_TEXT("text")) + return nodetest_type_text; + + break; + + default: + break; + } + + return nodetest_none; + } + + // PrimaryExpr ::= VariableReference | '(' Expr ')' | Literal | Number | FunctionCall + xpath_ast_node* parse_primary_expression() + { + switch (_lexer.current()) + { + case lex_var_ref: + { + xpath_lexer_string name = _lexer.contents(); + + if (!_variables) + throw_error("Unknown variable: variable set is not provided"); + + xpath_variable* var = get_variable_scratch(_scratch, _variables, name.begin, name.end); + + if (!var) + throw_error("Unknown variable: variable set does not contain the given name"); + + _lexer.next(); + + return new (alloc_node()) xpath_ast_node(ast_variable, var->type(), var); + } + + case lex_open_brace: + { + _lexer.next(); + + xpath_ast_node* n = parse_expression(); + + if (_lexer.current() != lex_close_brace) + throw_error("Unmatched braces"); + + _lexer.next(); + + return n; + } + + case lex_quoted_string: + { + const char_t* value = alloc_string(_lexer.contents()); + + xpath_ast_node* n = new (alloc_node()) xpath_ast_node(ast_string_constant, xpath_type_string, value); + _lexer.next(); + + return n; + } + + case lex_number: + { + double value = 0; + + if (!convert_string_to_number_scratch(_scratch, _lexer.contents().begin, _lexer.contents().end, &value)) + throw_error_oom(); + + xpath_ast_node* n = new (alloc_node()) xpath_ast_node(ast_number_constant, xpath_type_number, value); + _lexer.next(); + + return n; + } + + case lex_string: + { + xpath_ast_node* args[2] = {0}; + size_t argc = 0; + + xpath_lexer_string function = _lexer.contents(); + _lexer.next(); + + xpath_ast_node* last_arg = 0; + + if (_lexer.current() != lex_open_brace) + throw_error("Unrecognized function call"); + _lexer.next(); + + if (_lexer.current() != lex_close_brace) + args[argc++] = parse_expression(); + + while (_lexer.current() != lex_close_brace) + { + if (_lexer.current() != lex_comma) + throw_error("No comma between function arguments"); + _lexer.next(); + + xpath_ast_node* n = parse_expression(); + + if (argc < 2) args[argc] = n; + else last_arg->set_next(n); + + argc++; + last_arg = n; + } + + _lexer.next(); + + return parse_function(function, argc, args); + } + + default: + throw_error("Unrecognizable primary expression"); + + return 0; + } + } + + // FilterExpr ::= PrimaryExpr | FilterExpr Predicate + // Predicate ::= '[' PredicateExpr ']' + // PredicateExpr ::= Expr + xpath_ast_node* parse_filter_expression() + { + xpath_ast_node* n = parse_primary_expression(); + + while (_lexer.current() == lex_open_square_brace) + { + _lexer.next(); + + xpath_ast_node* expr = parse_expression(); + + if (n->rettype() != xpath_type_node_set) throw_error("Predicate has to be applied to node set"); + + bool posinv = expr->rettype() != xpath_type_number && expr->is_posinv(); + + n = new (alloc_node()) xpath_ast_node(posinv ? ast_filter_posinv : ast_filter, xpath_type_node_set, n, expr); + + if (_lexer.current() != lex_close_square_brace) + throw_error("Unmatched square brace"); + + _lexer.next(); + } + + return n; + } + + // Step ::= AxisSpecifier NodeTest Predicate* | AbbreviatedStep + // AxisSpecifier ::= AxisName '::' | '@'? + // NodeTest ::= NameTest | NodeType '(' ')' | 'processing-instruction' '(' Literal ')' + // NameTest ::= '*' | NCName ':' '*' | QName + // AbbreviatedStep ::= '.' | '..' + xpath_ast_node* parse_step(xpath_ast_node* set) + { + if (set && set->rettype() != xpath_type_node_set) + throw_error("Step has to be applied to node set"); + + bool axis_specified = false; + axis_t axis = axis_child; // implied child axis + + if (_lexer.current() == lex_axis_attribute) + { + axis = axis_attribute; + axis_specified = true; + + _lexer.next(); + } + else if (_lexer.current() == lex_dot) + { + _lexer.next(); + + return new (alloc_node()) xpath_ast_node(ast_step, set, axis_self, nodetest_type_node, 0); + } + else if (_lexer.current() == lex_double_dot) + { + _lexer.next(); + + return new (alloc_node()) xpath_ast_node(ast_step, set, axis_parent, nodetest_type_node, 0); + } + + nodetest_t nt_type = nodetest_none; + xpath_lexer_string nt_name; + + if (_lexer.current() == lex_string) + { + // node name test + nt_name = _lexer.contents(); + _lexer.next(); + + // was it an axis name? + if (_lexer.current() == lex_double_colon) + { + // parse axis name + if (axis_specified) throw_error("Two axis specifiers in one step"); + + axis = parse_axis_name(nt_name, axis_specified); + + if (!axis_specified) throw_error("Unknown axis"); + + // read actual node test + _lexer.next(); + + if (_lexer.current() == lex_multiply) + { + nt_type = nodetest_all; + nt_name = xpath_lexer_string(); + _lexer.next(); + } + else if (_lexer.current() == lex_string) + { + nt_name = _lexer.contents(); + _lexer.next(); + } + else throw_error("Unrecognized node test"); + } + + if (nt_type == nodetest_none) + { + // node type test or processing-instruction + if (_lexer.current() == lex_open_brace) + { + _lexer.next(); + + if (_lexer.current() == lex_close_brace) + { + _lexer.next(); + + nt_type = parse_node_test_type(nt_name); + + if (nt_type == nodetest_none) throw_error("Unrecognized node type"); + + nt_name = xpath_lexer_string(); + } + else if (nt_name == PUGIXML_TEXT("processing-instruction")) + { + if (_lexer.current() != lex_quoted_string) + throw_error("Only literals are allowed as arguments to processing-instruction()"); + + nt_type = nodetest_pi; + nt_name = _lexer.contents(); + _lexer.next(); + + if (_lexer.current() != lex_close_brace) + throw_error("Unmatched brace near processing-instruction()"); + _lexer.next(); + } + else + throw_error("Unmatched brace near node type test"); + + } + // QName or NCName:* + else + { + if (nt_name.end - nt_name.begin > 2 && nt_name.end[-2] == ':' && nt_name.end[-1] == '*') // NCName:* + { + nt_name.end--; // erase * + + nt_type = nodetest_all_in_namespace; + } + else nt_type = nodetest_name; + } + } + } + else if (_lexer.current() == lex_multiply) + { + nt_type = nodetest_all; + _lexer.next(); + } + else throw_error("Unrecognized node test"); + + xpath_ast_node* n = new (alloc_node()) xpath_ast_node(ast_step, set, axis, nt_type, alloc_string(nt_name)); + + xpath_ast_node* last = 0; + + while (_lexer.current() == lex_open_square_brace) + { + _lexer.next(); + + xpath_ast_node* expr = parse_expression(); + + xpath_ast_node* pred = new (alloc_node()) xpath_ast_node(ast_predicate, xpath_type_node_set, expr); + + if (_lexer.current() != lex_close_square_brace) + throw_error("Unmatched square brace"); + _lexer.next(); + + if (last) last->set_next(pred); + else n->set_right(pred); + + last = pred; + } + + return n; + } + + // RelativeLocationPath ::= Step | RelativeLocationPath '/' Step | RelativeLocationPath '//' Step + xpath_ast_node* parse_relative_location_path(xpath_ast_node* set) + { + xpath_ast_node* n = parse_step(set); + + while (_lexer.current() == lex_slash || _lexer.current() == lex_double_slash) + { + lexeme_t l = _lexer.current(); + _lexer.next(); + + if (l == lex_double_slash) + n = new (alloc_node()) xpath_ast_node(ast_step, n, axis_descendant_or_self, nodetest_type_node, 0); + + n = parse_step(n); + } + + return n; + } + + // LocationPath ::= RelativeLocationPath | AbsoluteLocationPath + // AbsoluteLocationPath ::= '/' RelativeLocationPath? | '//' RelativeLocationPath + xpath_ast_node* parse_location_path() + { + if (_lexer.current() == lex_slash) + { + _lexer.next(); + + xpath_ast_node* n = new (alloc_node()) xpath_ast_node(ast_step_root, xpath_type_node_set); + + // relative location path can start from axis_attribute, dot, double_dot, multiply and string lexemes; any other lexeme means standalone root path + lexeme_t l = _lexer.current(); + + if (l == lex_string || l == lex_axis_attribute || l == lex_dot || l == lex_double_dot || l == lex_multiply) + return parse_relative_location_path(n); + else + return n; + } + else if (_lexer.current() == lex_double_slash) + { + _lexer.next(); + + xpath_ast_node* n = new (alloc_node()) xpath_ast_node(ast_step_root, xpath_type_node_set); + n = new (alloc_node()) xpath_ast_node(ast_step, n, axis_descendant_or_self, nodetest_type_node, 0); + + return parse_relative_location_path(n); + } + + // else clause moved outside of if because of bogus warning 'control may reach end of non-void function being inlined' in gcc 4.0.1 + return parse_relative_location_path(0); + } + + // PathExpr ::= LocationPath + // | FilterExpr + // | FilterExpr '/' RelativeLocationPath + // | FilterExpr '//' RelativeLocationPath + // UnionExpr ::= PathExpr | UnionExpr '|' PathExpr + // UnaryExpr ::= UnionExpr | '-' UnaryExpr + xpath_ast_node* parse_path_or_unary_expression() + { + // Clarification. + // PathExpr begins with either LocationPath or FilterExpr. + // FilterExpr begins with PrimaryExpr + // PrimaryExpr begins with '$' in case of it being a variable reference, + // '(' in case of it being an expression, string literal, number constant or + // function call. + + if (_lexer.current() == lex_var_ref || _lexer.current() == lex_open_brace || + _lexer.current() == lex_quoted_string || _lexer.current() == lex_number || + _lexer.current() == lex_string) + { + if (_lexer.current() == lex_string) + { + // This is either a function call, or not - if not, we shall proceed with location path + const char_t* state = _lexer.state(); + + while (PUGI__IS_CHARTYPE(*state, ct_space)) ++state; + + if (*state != '(') return parse_location_path(); + + // This looks like a function call; however this still can be a node-test. Check it. + if (parse_node_test_type(_lexer.contents()) != nodetest_none) return parse_location_path(); + } + + xpath_ast_node* n = parse_filter_expression(); + + if (_lexer.current() == lex_slash || _lexer.current() == lex_double_slash) + { + lexeme_t l = _lexer.current(); + _lexer.next(); + + if (l == lex_double_slash) + { + if (n->rettype() != xpath_type_node_set) throw_error("Step has to be applied to node set"); + + n = new (alloc_node()) xpath_ast_node(ast_step, n, axis_descendant_or_self, nodetest_type_node, 0); + } + + // select from location path + return parse_relative_location_path(n); + } + + return n; + } + else if (_lexer.current() == lex_minus) + { + _lexer.next(); + + // precedence 7+ - only parses union expressions + xpath_ast_node* expr = parse_expression_rec(parse_path_or_unary_expression(), 7); + + return new (alloc_node()) xpath_ast_node(ast_op_negate, xpath_type_number, expr); + } + else + return parse_location_path(); + } + + struct binary_op_t + { + ast_type_t asttype; + xpath_value_type rettype; + int precedence; + + binary_op_t(): asttype(ast_unknown), rettype(xpath_type_none), precedence(0) + { + } + + binary_op_t(ast_type_t asttype_, xpath_value_type rettype_, int precedence_): asttype(asttype_), rettype(rettype_), precedence(precedence_) + { + } + + static binary_op_t parse(xpath_lexer& lexer) + { + switch (lexer.current()) + { + case lex_string: + if (lexer.contents() == PUGIXML_TEXT("or")) + return binary_op_t(ast_op_or, xpath_type_boolean, 1); + else if (lexer.contents() == PUGIXML_TEXT("and")) + return binary_op_t(ast_op_and, xpath_type_boolean, 2); + else if (lexer.contents() == PUGIXML_TEXT("div")) + return binary_op_t(ast_op_divide, xpath_type_number, 6); + else if (lexer.contents() == PUGIXML_TEXT("mod")) + return binary_op_t(ast_op_mod, xpath_type_number, 6); + else + return binary_op_t(); + + case lex_equal: + return binary_op_t(ast_op_equal, xpath_type_boolean, 3); + + case lex_not_equal: + return binary_op_t(ast_op_not_equal, xpath_type_boolean, 3); + + case lex_less: + return binary_op_t(ast_op_less, xpath_type_boolean, 4); + + case lex_greater: + return binary_op_t(ast_op_greater, xpath_type_boolean, 4); + + case lex_less_or_equal: + return binary_op_t(ast_op_less_or_equal, xpath_type_boolean, 4); + + case lex_greater_or_equal: + return binary_op_t(ast_op_greater_or_equal, xpath_type_boolean, 4); + + case lex_plus: + return binary_op_t(ast_op_add, xpath_type_number, 5); + + case lex_minus: + return binary_op_t(ast_op_subtract, xpath_type_number, 5); + + case lex_multiply: + return binary_op_t(ast_op_multiply, xpath_type_number, 6); + + case lex_union: + return binary_op_t(ast_op_union, xpath_type_node_set, 7); + + default: + return binary_op_t(); + } + } + }; + + xpath_ast_node* parse_expression_rec(xpath_ast_node* lhs, int limit) + { + binary_op_t op = binary_op_t::parse(_lexer); + + while (op.asttype != ast_unknown && op.precedence >= limit) + { + _lexer.next(); + + xpath_ast_node* rhs = parse_path_or_unary_expression(); + + binary_op_t nextop = binary_op_t::parse(_lexer); + + while (nextop.asttype != ast_unknown && nextop.precedence > op.precedence) + { + rhs = parse_expression_rec(rhs, nextop.precedence); + + nextop = binary_op_t::parse(_lexer); + } + + if (op.asttype == ast_op_union && (lhs->rettype() != xpath_type_node_set || rhs->rettype() != xpath_type_node_set)) + throw_error("Union operator has to be applied to node sets"); + + lhs = new (alloc_node()) xpath_ast_node(op.asttype, op.rettype, lhs, rhs); + + op = binary_op_t::parse(_lexer); + } + + return lhs; + } + + // Expr ::= OrExpr + // OrExpr ::= AndExpr | OrExpr 'or' AndExpr + // AndExpr ::= EqualityExpr | AndExpr 'and' EqualityExpr + // EqualityExpr ::= RelationalExpr + // | EqualityExpr '=' RelationalExpr + // | EqualityExpr '!=' RelationalExpr + // RelationalExpr ::= AdditiveExpr + // | RelationalExpr '<' AdditiveExpr + // | RelationalExpr '>' AdditiveExpr + // | RelationalExpr '<=' AdditiveExpr + // | RelationalExpr '>=' AdditiveExpr + // AdditiveExpr ::= MultiplicativeExpr + // | AdditiveExpr '+' MultiplicativeExpr + // | AdditiveExpr '-' MultiplicativeExpr + // MultiplicativeExpr ::= UnaryExpr + // | MultiplicativeExpr '*' UnaryExpr + // | MultiplicativeExpr 'div' UnaryExpr + // | MultiplicativeExpr 'mod' UnaryExpr + xpath_ast_node* parse_expression() + { + return parse_expression_rec(parse_path_or_unary_expression(), 0); + } + + xpath_parser(const char_t* query, xpath_variable_set* variables, xpath_allocator* alloc, xpath_parse_result* result): _alloc(alloc), _lexer(query), _query(query), _variables(variables), _result(result) + { + } + + xpath_ast_node* parse() + { + xpath_ast_node* result = parse_expression(); + + if (_lexer.current() != lex_eof) + { + // there are still unparsed tokens left, error + throw_error("Incorrect query"); + } + + return result; + } + + static xpath_ast_node* parse(const char_t* query, xpath_variable_set* variables, xpath_allocator* alloc, xpath_parse_result* result) + { + xpath_parser parser(query, variables, alloc, result); + + #ifdef PUGIXML_NO_EXCEPTIONS + int error = setjmp(parser._error_handler); + + return (error == 0) ? parser.parse() : 0; + #else + return parser.parse(); + #endif + } + }; + + struct xpath_query_impl + { + static xpath_query_impl* create() + { + void* memory = xml_memory::allocate(sizeof(xpath_query_impl)); + + return new (memory) xpath_query_impl(); + } + + static void destroy(void* ptr) + { + if (!ptr) return; + + // free all allocated pages + static_cast<xpath_query_impl*>(ptr)->alloc.release(); + + // free allocator memory (with the first page) + xml_memory::deallocate(ptr); + } + + xpath_query_impl(): root(0), alloc(&block) + { + block.next = 0; + block.capacity = sizeof(block.data); + } + + xpath_ast_node* root; + xpath_allocator alloc; + xpath_memory_block block; + }; + + PUGI__FN xpath_string evaluate_string_impl(xpath_query_impl* impl, const xpath_node& n, xpath_stack_data& sd) + { + if (!impl) return xpath_string(); + + #ifdef PUGIXML_NO_EXCEPTIONS + if (setjmp(sd.error_handler)) return xpath_string(); + #endif + + xpath_context c(n, 1, 1); + + return impl->root->eval_string(c, sd.stack); + } +PUGI__NS_END + +namespace pugi +{ +#ifndef PUGIXML_NO_EXCEPTIONS + PUGI__FN xpath_exception::xpath_exception(const xpath_parse_result& result_): _result(result_) + { + assert(_result.error); + } + + PUGI__FN const char* xpath_exception::what() const throw() + { + return _result.error; + } + + PUGI__FN const xpath_parse_result& xpath_exception::result() const + { + return _result; + } +#endif + + PUGI__FN xpath_node::xpath_node() + { + } + + PUGI__FN xpath_node::xpath_node(const xml_node& node_): _node(node_) + { + } + + PUGI__FN xpath_node::xpath_node(const xml_attribute& attribute_, const xml_node& parent_): _node(attribute_ ? parent_ : xml_node()), _attribute(attribute_) + { + } + + PUGI__FN xml_node xpath_node::node() const + { + return _attribute ? xml_node() : _node; + } + + PUGI__FN xml_attribute xpath_node::attribute() const + { + return _attribute; + } + + PUGI__FN xml_node xpath_node::parent() const + { + return _attribute ? _node : _node.parent(); + } + + PUGI__FN static void unspecified_bool_xpath_node(xpath_node***) + { + } + + PUGI__FN xpath_node::operator xpath_node::unspecified_bool_type() const + { + return (_node || _attribute) ? unspecified_bool_xpath_node : 0; + } + + PUGI__FN bool xpath_node::operator!() const + { + return !(_node || _attribute); + } + + PUGI__FN bool xpath_node::operator==(const xpath_node& n) const + { + return _node == n._node && _attribute == n._attribute; + } + + PUGI__FN bool xpath_node::operator!=(const xpath_node& n) const + { + return _node != n._node || _attribute != n._attribute; + } + +#ifdef __BORLANDC__ + PUGI__FN bool operator&&(const xpath_node& lhs, bool rhs) + { + return (bool)lhs && rhs; + } + + PUGI__FN bool operator||(const xpath_node& lhs, bool rhs) + { + return (bool)lhs || rhs; + } +#endif + + PUGI__FN void xpath_node_set::_assign(const_iterator begin_, const_iterator end_) + { + assert(begin_ <= end_); + + size_t size_ = static_cast<size_t>(end_ - begin_); + + if (size_ <= 1) + { + // deallocate old buffer + if (_begin != &_storage) impl::xml_memory::deallocate(_begin); + + // use internal buffer + if (begin_ != end_) _storage = *begin_; + + _begin = &_storage; + _end = &_storage + size_; + } + else + { + // make heap copy + xpath_node* storage = static_cast<xpath_node*>(impl::xml_memory::allocate(size_ * sizeof(xpath_node))); + + if (!storage) + { + #ifdef PUGIXML_NO_EXCEPTIONS + return; + #else + throw std::bad_alloc(); + #endif + } + + memcpy(storage, begin_, size_ * sizeof(xpath_node)); + + // deallocate old buffer + if (_begin != &_storage) impl::xml_memory::deallocate(_begin); + + // finalize + _begin = storage; + _end = storage + size_; + } + } + + PUGI__FN xpath_node_set::xpath_node_set(): _type(type_unsorted), _begin(&_storage), _end(&_storage) + { + } + + PUGI__FN xpath_node_set::xpath_node_set(const_iterator begin_, const_iterator end_, type_t type_): _type(type_), _begin(&_storage), _end(&_storage) + { + _assign(begin_, end_); + } + + PUGI__FN xpath_node_set::~xpath_node_set() + { + if (_begin != &_storage) impl::xml_memory::deallocate(_begin); + } + + PUGI__FN xpath_node_set::xpath_node_set(const xpath_node_set& ns): _type(ns._type), _begin(&_storage), _end(&_storage) + { + _assign(ns._begin, ns._end); + } + + PUGI__FN xpath_node_set& xpath_node_set::operator=(const xpath_node_set& ns) + { + if (this == &ns) return *this; + + _type = ns._type; + _assign(ns._begin, ns._end); + + return *this; + } + + PUGI__FN xpath_node_set::type_t xpath_node_set::type() const + { + return _type; + } + + PUGI__FN size_t xpath_node_set::size() const + { + return _end - _begin; + } + + PUGI__FN bool xpath_node_set::empty() const + { + return _begin == _end; + } + + PUGI__FN const xpath_node& xpath_node_set::operator[](size_t index) const + { + assert(index < size()); + return _begin[index]; + } + + PUGI__FN xpath_node_set::const_iterator xpath_node_set::begin() const + { + return _begin; + } + + PUGI__FN xpath_node_set::const_iterator xpath_node_set::end() const + { + return _end; + } + + PUGI__FN void xpath_node_set::sort(bool reverse) + { + _type = impl::xpath_sort(_begin, _end, _type, reverse); + } + + PUGI__FN xpath_node xpath_node_set::first() const + { + return impl::xpath_first(_begin, _end, _type); + } + + PUGI__FN xpath_parse_result::xpath_parse_result(): error("Internal error"), offset(0) + { + } + + PUGI__FN xpath_parse_result::operator bool() const + { + return error == 0; + } + + PUGI__FN const char* xpath_parse_result::description() const + { + return error ? error : "No error"; + } + + PUGI__FN xpath_variable::xpath_variable(): _type(xpath_type_none), _next(0) + { + } + + PUGI__FN const char_t* xpath_variable::name() const + { + switch (_type) + { + case xpath_type_node_set: + return static_cast<const impl::xpath_variable_node_set*>(this)->name; + + case xpath_type_number: + return static_cast<const impl::xpath_variable_number*>(this)->name; + + case xpath_type_string: + return static_cast<const impl::xpath_variable_string*>(this)->name; + + case xpath_type_boolean: + return static_cast<const impl::xpath_variable_boolean*>(this)->name; + + default: + assert(!"Invalid variable type"); + return 0; + } + } + + PUGI__FN xpath_value_type xpath_variable::type() const + { + return _type; + } + + PUGI__FN bool xpath_variable::get_boolean() const + { + return (_type == xpath_type_boolean) ? static_cast<const impl::xpath_variable_boolean*>(this)->value : false; + } + + PUGI__FN double xpath_variable::get_number() const + { + return (_type == xpath_type_number) ? static_cast<const impl::xpath_variable_number*>(this)->value : impl::gen_nan(); + } + + PUGI__FN const char_t* xpath_variable::get_string() const + { + const char_t* value = (_type == xpath_type_string) ? static_cast<const impl::xpath_variable_string*>(this)->value : 0; + return value ? value : PUGIXML_TEXT(""); + } + + PUGI__FN const xpath_node_set& xpath_variable::get_node_set() const + { + return (_type == xpath_type_node_set) ? static_cast<const impl::xpath_variable_node_set*>(this)->value : impl::dummy_node_set; + } + + PUGI__FN bool xpath_variable::set(bool value) + { + if (_type != xpath_type_boolean) return false; + + static_cast<impl::xpath_variable_boolean*>(this)->value = value; + return true; + } + + PUGI__FN bool xpath_variable::set(double value) + { + if (_type != xpath_type_number) return false; + + static_cast<impl::xpath_variable_number*>(this)->value = value; + return true; + } + + PUGI__FN bool xpath_variable::set(const char_t* value) + { + if (_type != xpath_type_string) return false; + + impl::xpath_variable_string* var = static_cast<impl::xpath_variable_string*>(this); + + // duplicate string + size_t size = (impl::strlength(value) + 1) * sizeof(char_t); + + char_t* copy = static_cast<char_t*>(impl::xml_memory::allocate(size)); + if (!copy) return false; + + memcpy(copy, value, size); + + // replace old string + if (var->value) impl::xml_memory::deallocate(var->value); + var->value = copy; + + return true; + } + + PUGI__FN bool xpath_variable::set(const xpath_node_set& value) + { + if (_type != xpath_type_node_set) return false; + + static_cast<impl::xpath_variable_node_set*>(this)->value = value; + return true; + } + + PUGI__FN xpath_variable_set::xpath_variable_set() + { + for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i) _data[i] = 0; + } + + PUGI__FN xpath_variable_set::~xpath_variable_set() + { + for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i) + { + xpath_variable* var = _data[i]; + + while (var) + { + xpath_variable* next = var->_next; + + impl::delete_xpath_variable(var->_type, var); + + var = next; + } + } + } + + PUGI__FN xpath_variable* xpath_variable_set::find(const char_t* name) const + { + const size_t hash_size = sizeof(_data) / sizeof(_data[0]); + size_t hash = impl::hash_string(name) % hash_size; + + // look for existing variable + for (xpath_variable* var = _data[hash]; var; var = var->_next) + if (impl::strequal(var->name(), name)) + return var; + + return 0; + } + + PUGI__FN xpath_variable* xpath_variable_set::add(const char_t* name, xpath_value_type type) + { + const size_t hash_size = sizeof(_data) / sizeof(_data[0]); + size_t hash = impl::hash_string(name) % hash_size; + + // look for existing variable + for (xpath_variable* var = _data[hash]; var; var = var->_next) + if (impl::strequal(var->name(), name)) + return var->type() == type ? var : 0; + + // add new variable + xpath_variable* result = impl::new_xpath_variable(type, name); + + if (result) + { + result->_type = type; + result->_next = _data[hash]; + + _data[hash] = result; + } + + return result; + } + + PUGI__FN bool xpath_variable_set::set(const char_t* name, bool value) + { + xpath_variable* var = add(name, xpath_type_boolean); + return var ? var->set(value) : false; + } + + PUGI__FN bool xpath_variable_set::set(const char_t* name, double value) + { + xpath_variable* var = add(name, xpath_type_number); + return var ? var->set(value) : false; + } + + PUGI__FN bool xpath_variable_set::set(const char_t* name, const char_t* value) + { + xpath_variable* var = add(name, xpath_type_string); + return var ? var->set(value) : false; + } + + PUGI__FN bool xpath_variable_set::set(const char_t* name, const xpath_node_set& value) + { + xpath_variable* var = add(name, xpath_type_node_set); + return var ? var->set(value) : false; + } + + PUGI__FN xpath_variable* xpath_variable_set::get(const char_t* name) + { + return find(name); + } + + PUGI__FN const xpath_variable* xpath_variable_set::get(const char_t* name) const + { + return find(name); + } + + PUGI__FN xpath_query::xpath_query(const char_t* query, xpath_variable_set* variables): _impl(0) + { + impl::xpath_query_impl* qimpl = impl::xpath_query_impl::create(); + + if (!qimpl) + { + #ifdef PUGIXML_NO_EXCEPTIONS + _result.error = "Out of memory"; + #else + throw std::bad_alloc(); + #endif + } + else + { + impl::buffer_holder impl_holder(qimpl, impl::xpath_query_impl::destroy); + + qimpl->root = impl::xpath_parser::parse(query, variables, &qimpl->alloc, &_result); + + if (qimpl->root) + { + _impl = static_cast<impl::xpath_query_impl*>(impl_holder.release()); + _result.error = 0; + } + } + } + + PUGI__FN xpath_query::~xpath_query() + { + impl::xpath_query_impl::destroy(_impl); + } + + PUGI__FN xpath_value_type xpath_query::return_type() const + { + if (!_impl) return xpath_type_none; + + return static_cast<impl::xpath_query_impl*>(_impl)->root->rettype(); + } + + PUGI__FN bool xpath_query::evaluate_boolean(const xpath_node& n) const + { + if (!_impl) return false; + + impl::xpath_context c(n, 1, 1); + impl::xpath_stack_data sd; + + #ifdef PUGIXML_NO_EXCEPTIONS + if (setjmp(sd.error_handler)) return false; + #endif + + return static_cast<impl::xpath_query_impl*>(_impl)->root->eval_boolean(c, sd.stack); + } + + PUGI__FN double xpath_query::evaluate_number(const xpath_node& n) const + { + if (!_impl) return impl::gen_nan(); + + impl::xpath_context c(n, 1, 1); + impl::xpath_stack_data sd; + + #ifdef PUGIXML_NO_EXCEPTIONS + if (setjmp(sd.error_handler)) return impl::gen_nan(); + #endif + + return static_cast<impl::xpath_query_impl*>(_impl)->root->eval_number(c, sd.stack); + } + +#ifndef PUGIXML_NO_STL + PUGI__FN string_t xpath_query::evaluate_string(const xpath_node& n) const + { + impl::xpath_stack_data sd; + + return impl::evaluate_string_impl(static_cast<impl::xpath_query_impl*>(_impl), n, sd).c_str(); + } +#endif + + PUGI__FN size_t xpath_query::evaluate_string(char_t* buffer, size_t capacity, const xpath_node& n) const + { + impl::xpath_stack_data sd; + + impl::xpath_string r = impl::evaluate_string_impl(static_cast<impl::xpath_query_impl*>(_impl), n, sd); + + size_t full_size = r.length() + 1; + + if (capacity > 0) + { + size_t size = (full_size < capacity) ? full_size : capacity; + assert(size > 0); + + memcpy(buffer, r.c_str(), (size - 1) * sizeof(char_t)); + buffer[size - 1] = 0; + } + + return full_size; + } + + PUGI__FN xpath_node_set xpath_query::evaluate_node_set(const xpath_node& n) const + { + if (!_impl) return xpath_node_set(); + + impl::xpath_ast_node* root = static_cast<impl::xpath_query_impl*>(_impl)->root; + + if (root->rettype() != xpath_type_node_set) + { + #ifdef PUGIXML_NO_EXCEPTIONS + return xpath_node_set(); + #else + xpath_parse_result res; + res.error = "Expression does not evaluate to node set"; + + throw xpath_exception(res); + #endif + } + + impl::xpath_context c(n, 1, 1); + impl::xpath_stack_data sd; + + #ifdef PUGIXML_NO_EXCEPTIONS + if (setjmp(sd.error_handler)) return xpath_node_set(); + #endif + + impl::xpath_node_set_raw r = root->eval_node_set(c, sd.stack); + + return xpath_node_set(r.begin(), r.end(), r.type()); + } + + PUGI__FN const xpath_parse_result& xpath_query::result() const + { + return _result; + } + + PUGI__FN static void unspecified_bool_xpath_query(xpath_query***) + { + } + + PUGI__FN xpath_query::operator xpath_query::unspecified_bool_type() const + { + return _impl ? unspecified_bool_xpath_query : 0; + } + + PUGI__FN bool xpath_query::operator!() const + { + return !_impl; + } + + PUGI__FN xpath_node xml_node::select_single_node(const char_t* query, xpath_variable_set* variables) const + { + xpath_query q(query, variables); + return select_single_node(q); + } + + PUGI__FN xpath_node xml_node::select_single_node(const xpath_query& query) const + { + xpath_node_set s = query.evaluate_node_set(*this); + return s.empty() ? xpath_node() : s.first(); + } + + PUGI__FN xpath_node_set xml_node::select_nodes(const char_t* query, xpath_variable_set* variables) const + { + xpath_query q(query, variables); + return select_nodes(q); + } + + PUGI__FN xpath_node_set xml_node::select_nodes(const xpath_query& query) const + { + return query.evaluate_node_set(*this); + } +} + +#endif + +#ifdef __BORLANDC__ +# pragma option pop +#endif + +// Intel C++ does not properly keep warning state for function templates, +// so popping warning state at the end of translation unit leads to warnings in the middle. +#if defined(_MSC_VER) && !defined(__INTEL_COMPILER) +# pragma warning(pop) +#endif + +// Undefine all local macros (makes sure we're not leaking macros in header-only mode) +#undef PUGI__NO_INLINE +#undef PUGI__STATIC_ASSERT +#undef PUGI__DMC_VOLATILE +#undef PUGI__MSVC_CRT_VERSION +#undef PUGI__NS_BEGIN +#undef PUGI__NS_END +#undef PUGI__FN +#undef PUGI__FN_NO_INLINE +#undef PUGI__IS_CHARTYPE_IMPL +#undef PUGI__IS_CHARTYPE +#undef PUGI__IS_CHARTYPEX +#undef PUGI__SKIPWS +#undef PUGI__OPTSET +#undef PUGI__PUSHNODE +#undef PUGI__POPNODE +#undef PUGI__SCANFOR +#undef PUGI__SCANWHILE +#undef PUGI__ENDSEG +#undef PUGI__THROW_ERROR +#undef PUGI__CHECK_ERROR + +#endif + +/** + * Copyright (c) 2006-2014 Arseny Kapoulkine + * + * Permission is hereby granted, 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. + */ diff --git a/indra/llxml/pugixml.hpp b/indra/llxml/pugixml.hpp new file mode 100644 index 0000000000000000000000000000000000000000..7ee844c8b1b6b0bf655b61b4a4706ecf207c26b3 --- /dev/null +++ b/indra/llxml/pugixml.hpp @@ -0,0 +1,1346 @@ +/** + * pugixml parser - version 1.4 + * -------------------------------------------------------- + * Copyright (C) 2006-2014, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) + * Report bugs and download new versions at http://pugixml.org/ + * + * This library is distributed under the MIT License. See notice at the end + * of this file. + * + * This work is based on the pugxml parser, which is: + * Copyright (C) 2003, by Kristen Wegner (kristen@tima.net) + */ + +#ifndef PUGIXML_VERSION +// Define version macro; evaluates to major * 100 + minor so that it's safe to use in less-than comparisons +# define PUGIXML_VERSION 140 +#endif + +// Include user configuration file (this can define various configuration macros) +#include "pugiconfig.hpp" + +#ifndef HEADER_PUGIXML_HPP +#define HEADER_PUGIXML_HPP + +// Include stddef.h for size_t and ptrdiff_t +#include <stddef.h> + +// Include exception header for XPath +#if !defined(PUGIXML_NO_XPATH) && !defined(PUGIXML_NO_EXCEPTIONS) +# include <exception> +#endif + +// Include STL headers +#ifndef PUGIXML_NO_STL +# include <iterator> +# include <iosfwd> +# include <string> +#endif + +// Macro for deprecated features +#ifndef PUGIXML_DEPRECATED +# if defined(__GNUC__) +# define PUGIXML_DEPRECATED __attribute__((deprecated)) +# elif defined(_MSC_VER) && _MSC_VER >= 1300 +# define PUGIXML_DEPRECATED __declspec(deprecated) +# else +# define PUGIXML_DEPRECATED +# endif +#endif + +// If no API is defined, assume default +#ifndef PUGIXML_API +# define PUGIXML_API +#endif + +// If no API for classes is defined, assume default +#ifndef PUGIXML_CLASS +# define PUGIXML_CLASS PUGIXML_API +#endif + +// If no API for functions is defined, assume default +#ifndef PUGIXML_FUNCTION +# define PUGIXML_FUNCTION PUGIXML_API +#endif + +// If the platform is known to have long long support, enable long long functions +#ifndef PUGIXML_HAS_LONG_LONG +# if defined(__cplusplus) && __cplusplus >= 201103 +# define PUGIXML_HAS_LONG_LONG +# elif defined(_MSC_VER) && _MSC_VER >= 1400 +# define PUGIXML_HAS_LONG_LONG +# endif +#endif + +// Character interface macros +#ifdef PUGIXML_WCHAR_MODE +# define PUGIXML_TEXT(t) L ## t +# define PUGIXML_CHAR wchar_t +#else +# define PUGIXML_TEXT(t) t +# define PUGIXML_CHAR char +#endif + +namespace pugi +{ + // Character type used for all internal storage and operations; depends on PUGIXML_WCHAR_MODE + typedef PUGIXML_CHAR char_t; + +#ifndef PUGIXML_NO_STL + // String type used for operations that work with STL string; depends on PUGIXML_WCHAR_MODE + typedef std::basic_string<PUGIXML_CHAR, std::char_traits<PUGIXML_CHAR>, std::allocator<PUGIXML_CHAR> > string_t; +#endif +} + +// The PugiXML namespace +namespace pugi +{ + // Tree node types + enum xml_node_type + { + node_null, // Empty (null) node handle + node_document, // A document tree's absolute root + node_element, // Element tag, i.e. '<node/>' + node_pcdata, // Plain character data, i.e. 'text' + node_cdata, // Character data, i.e. '<![CDATA[text]]>' + node_comment, // Comment tag, i.e. '<!-- text -->' + node_pi, // Processing instruction, i.e. '<?name?>' + node_declaration, // Document declaration, i.e. '<?xml version="1.0"?>' + node_doctype // Document type declaration, i.e. '<!DOCTYPE doc>' + }; + + // Parsing options + + // Minimal parsing mode (equivalent to turning all other flags off). + // Only elements and PCDATA sections are added to the DOM tree, no text conversions are performed. + const unsigned int parse_minimal = 0x0000; + + // This flag determines if processing instructions (node_pi) are added to the DOM tree. This flag is off by default. + const unsigned int parse_pi = 0x0001; + + // This flag determines if comments (node_comment) are added to the DOM tree. This flag is off by default. + const unsigned int parse_comments = 0x0002; + + // This flag determines if CDATA sections (node_cdata) are added to the DOM tree. This flag is on by default. + const unsigned int parse_cdata = 0x0004; + + // This flag determines if plain character data (node_pcdata) that consist only of whitespace are added to the DOM tree. + // This flag is off by default; turning it on usually results in slower parsing and more memory consumption. + const unsigned int parse_ws_pcdata = 0x0008; + + // This flag determines if character and entity references are expanded during parsing. This flag is on by default. + const unsigned int parse_escapes = 0x0010; + + // This flag determines if EOL characters are normalized (converted to #xA) during parsing. This flag is on by default. + const unsigned int parse_eol = 0x0020; + + // This flag determines if attribute values are normalized using CDATA normalization rules during parsing. This flag is on by default. + const unsigned int parse_wconv_attribute = 0x0040; + + // This flag determines if attribute values are normalized using NMTOKENS normalization rules during parsing. This flag is off by default. + const unsigned int parse_wnorm_attribute = 0x0080; + + // This flag determines if document declaration (node_declaration) is added to the DOM tree. This flag is off by default. + const unsigned int parse_declaration = 0x0100; + + // This flag determines if document type declaration (node_doctype) is added to the DOM tree. This flag is off by default. + const unsigned int parse_doctype = 0x0200; + + // This flag determines if plain character data (node_pcdata) that is the only child of the parent node and that consists only + // of whitespace is added to the DOM tree. + // This flag is off by default; turning it on may result in slower parsing and more memory consumption. + const unsigned int parse_ws_pcdata_single = 0x0400; + + // This flag determines if leading and trailing whitespace is to be removed from plain character data. This flag is off by default. + const unsigned int parse_trim_pcdata = 0x0800; + + // This flag determines if plain character data that does not have a parent node is added to the DOM tree, and if an empty document + // is a valid document. This flag is off by default. + const unsigned int parse_fragment = 0x1000; + + // The default parsing mode. + // Elements, PCDATA and CDATA sections are added to the DOM tree, character/reference entities are expanded, + // End-of-Line characters are normalized, attribute values are normalized using CDATA normalization rules. + const unsigned int parse_default = parse_cdata | parse_escapes | parse_wconv_attribute | parse_eol; + + // The full parsing mode. + // Nodes of all types are added to the DOM tree, character/reference entities are expanded, + // End-of-Line characters are normalized, attribute values are normalized using CDATA normalization rules. + const unsigned int parse_full = parse_default | parse_pi | parse_comments | parse_declaration | parse_doctype; + + // These flags determine the encoding of input data for XML document + enum xml_encoding + { + encoding_auto, // Auto-detect input encoding using BOM or < / <? detection; use UTF8 if BOM is not found + encoding_utf8, // UTF8 encoding + encoding_utf16_le, // Little-endian UTF16 + encoding_utf16_be, // Big-endian UTF16 + encoding_utf16, // UTF16 with native endianness + encoding_utf32_le, // Little-endian UTF32 + encoding_utf32_be, // Big-endian UTF32 + encoding_utf32, // UTF32 with native endianness + encoding_wchar, // The same encoding wchar_t has (either UTF16 or UTF32) + encoding_latin1 + }; + + // Formatting flags + + // Indent the nodes that are written to output stream with as many indentation strings as deep the node is in DOM tree. This flag is on by default. + const unsigned int format_indent = 0x01; + + // Write encoding-specific BOM to the output stream. This flag is off by default. + const unsigned int format_write_bom = 0x02; + + // Use raw output mode (no indentation and no line breaks are written). This flag is off by default. + const unsigned int format_raw = 0x04; + + // Omit default XML declaration even if there is no declaration in the document. This flag is off by default. + const unsigned int format_no_declaration = 0x08; + + // Don't escape attribute values and PCDATA contents. This flag is off by default. + const unsigned int format_no_escapes = 0x10; + + // Open file using text mode in xml_document::save_file. This enables special character (i.e. new-line) conversions on some systems. This flag is off by default. + const unsigned int format_save_file_text = 0x20; + + // <edit> + // Format the output so that keys in LLSD maps are distinct from their values. + const unsigned int format_pretty_llsd = 0x40; + // </edit> + + // The default set of formatting flags. + // Nodes are indented depending on their depth in DOM tree, a default declaration is output if document has none. + const unsigned int format_default = format_indent; + + // Forward declarations + struct xml_attribute_struct; + struct xml_node_struct; + + class xml_node_iterator; + class xml_attribute_iterator; + class xml_named_node_iterator; + + class xml_tree_walker; + + struct xml_parse_result; + + class xml_node; + + class xml_text; + + #ifndef PUGIXML_NO_XPATH + class xpath_node; + class xpath_node_set; + class xpath_query; + class xpath_variable_set; + #endif + + // Range-based for loop support + template <typename It> class xml_object_range + { + public: + typedef It const_iterator; + typedef It iterator; + + xml_object_range(It b, It e): _begin(b), _end(e) + { + } + + It begin() const { return _begin; } + It end() const { return _end; } + + private: + It _begin, _end; + }; + + // Writer interface for node printing (see xml_node::print) + class PUGIXML_CLASS xml_writer + { + public: + virtual ~xml_writer() {} + + // Write memory chunk into stream/file/whatever + virtual void write(const void* data, size_t size) = 0; + }; + + // xml_writer implementation for FILE* + class PUGIXML_CLASS xml_writer_file: public xml_writer + { + public: + // Construct writer from a FILE* object; void* is used to avoid header dependencies on stdio + xml_writer_file(void* file); + + virtual void write(const void* data, size_t size); + + private: + void* file; + }; + + #ifndef PUGIXML_NO_STL + // xml_writer implementation for streams + class PUGIXML_CLASS xml_writer_stream: public xml_writer + { + public: + // Construct writer from an output stream object + xml_writer_stream(std::basic_ostream<char, std::char_traits<char> >& stream); + xml_writer_stream(std::basic_ostream<wchar_t, std::char_traits<wchar_t> >& stream); + + virtual void write(const void* data, size_t size); + + private: + std::basic_ostream<char, std::char_traits<char> >* narrow_stream; + std::basic_ostream<wchar_t, std::char_traits<wchar_t> >* wide_stream; + }; + #endif + + // A light-weight handle for manipulating attributes in DOM tree + class PUGIXML_CLASS xml_attribute + { + friend class xml_attribute_iterator; + friend class xml_node; + + private: + xml_attribute_struct* _attr; + + typedef void (*unspecified_bool_type)(xml_attribute***); + + public: + // Default constructor. Constructs an empty attribute. + xml_attribute(); + + // Constructs attribute from internal pointer + explicit xml_attribute(xml_attribute_struct* attr); + + // Safe bool conversion operator + operator unspecified_bool_type() const; + + // Borland C++ workaround + bool operator!() const; + + // Comparison operators (compares wrapped attribute pointers) + bool operator==(const xml_attribute& r) const; + bool operator!=(const xml_attribute& r) const; + bool operator<(const xml_attribute& r) const; + bool operator>(const xml_attribute& r) const; + bool operator<=(const xml_attribute& r) const; + bool operator>=(const xml_attribute& r) const; + + // Check if attribute is empty + bool empty() const; + + // Get attribute name/value, or "" if attribute is empty + const char_t* name() const; + const char_t* value() const; + + // Get attribute value, or the default value if attribute is empty + const char_t* as_string(const char_t* def = PUGIXML_TEXT("")) const; + + // Get attribute value as a number, or the default value if conversion did not succeed or attribute is empty + int as_int(int def = 0) const; + unsigned int as_uint(unsigned int def = 0) const; + double as_double(double def = 0) const; + float as_float(float def = 0) const; + + #ifdef PUGIXML_HAS_LONG_LONG + long long as_llong(long long def = 0) const; + unsigned long long as_ullong(unsigned long long def = 0) const; + #endif + + // Get attribute value as bool (returns true if first character is in '1tTyY' set), or the default value if attribute is empty + bool as_bool(bool def = false) const; + + // Set attribute name/value (returns false if attribute is empty or there is not enough memory) + bool set_name(const char_t* rhs); + bool set_value(const char_t* rhs); + + // Set attribute value with type conversion (numbers are converted to strings, boolean is converted to "true"/"false") + bool set_value(int rhs); + bool set_value(unsigned int rhs); + bool set_value(double rhs); + bool set_value(bool rhs); + + #ifdef PUGIXML_HAS_LONG_LONG + bool set_value(long long rhs); + bool set_value(unsigned long long rhs); + #endif + + // Set attribute value (equivalent to set_value without error checking) + xml_attribute& operator=(const char_t* rhs); + xml_attribute& operator=(int rhs); + xml_attribute& operator=(unsigned int rhs); + xml_attribute& operator=(double rhs); + xml_attribute& operator=(bool rhs); + + #ifdef PUGIXML_HAS_LONG_LONG + xml_attribute& operator=(long long rhs); + xml_attribute& operator=(unsigned long long rhs); + #endif + + // Get next/previous attribute in the attribute list of the parent node + xml_attribute next_attribute() const; + xml_attribute previous_attribute() const; + + // Get hash value (unique for handles to the same object) + size_t hash_value() const; + + // Get internal pointer + xml_attribute_struct* internal_object() const; + }; + +#ifdef __BORLANDC__ + // Borland C++ workaround + bool PUGIXML_FUNCTION operator&&(const xml_attribute& lhs, bool rhs); + bool PUGIXML_FUNCTION operator||(const xml_attribute& lhs, bool rhs); +#endif + + // A light-weight handle for manipulating nodes in DOM tree + class PUGIXML_CLASS xml_node + { + friend class xml_attribute_iterator; + friend class xml_node_iterator; + friend class xml_named_node_iterator; + + protected: + xml_node_struct* _root; + + typedef void (*unspecified_bool_type)(xml_node***); + + public: + // Default constructor. Constructs an empty node. + xml_node(); + + // Constructs node from internal pointer + explicit xml_node(xml_node_struct* p); + + // Safe bool conversion operator + operator unspecified_bool_type() const; + + // Borland C++ workaround + bool operator!() const; + + // Comparison operators (compares wrapped node pointers) + bool operator==(const xml_node& r) const; + bool operator!=(const xml_node& r) const; + bool operator<(const xml_node& r) const; + bool operator>(const xml_node& r) const; + bool operator<=(const xml_node& r) const; + bool operator>=(const xml_node& r) const; + + // Check if node is empty. + bool empty() const; + + // Get node type + xml_node_type type() const; + + // Get node name, or "" if node is empty or it has no name + const char_t* name() const; + + // Get node value, or "" if node is empty or it has no value + // Note: For <node>text</node> node.value() does not return "text"! Use child_value() or text() methods to access text inside nodes. + const char_t* value() const; + + // Get attribute list + xml_attribute first_attribute() const; + xml_attribute last_attribute() const; + + // Get children list + xml_node first_child() const; + xml_node last_child() const; + + // Get next/previous sibling in the children list of the parent node + xml_node next_sibling() const; + xml_node previous_sibling() const; + + // Get parent node + xml_node parent() const; + + // Get root of DOM tree this node belongs to + xml_node root() const; + + // Get text object for the current node + xml_text text() const; + + // Get child, attribute or next/previous sibling with the specified name + xml_node child(const char_t* name) const; + xml_attribute attribute(const char_t* name) const; + xml_node next_sibling(const char_t* name) const; + xml_node previous_sibling(const char_t* name) const; + + // Get child value of current node; that is, value of the first child node of type PCDATA/CDATA + const char_t* child_value() const; + + // Get child value of child with specified name. Equivalent to child(name).child_value(). + const char_t* child_value(const char_t* name) const; + + // Set node name/value (returns false if node is empty, there is not enough memory, or node can not have name/value) + bool set_name(const char_t* rhs); + bool set_value(const char_t* rhs); + + // Add attribute with specified name. Returns added attribute, or empty attribute on errors. + xml_attribute append_attribute(const char_t* name); + xml_attribute prepend_attribute(const char_t* name); + xml_attribute insert_attribute_after(const char_t* name, const xml_attribute& attr); + xml_attribute insert_attribute_before(const char_t* name, const xml_attribute& attr); + + // Add a copy of the specified attribute. Returns added attribute, or empty attribute on errors. + xml_attribute append_copy(const xml_attribute& proto); + xml_attribute prepend_copy(const xml_attribute& proto); + xml_attribute insert_copy_after(const xml_attribute& proto, const xml_attribute& attr); + xml_attribute insert_copy_before(const xml_attribute& proto, const xml_attribute& attr); + + // Add child node with specified type. Returns added node, or empty node on errors. + xml_node append_child(xml_node_type type = node_element); + xml_node prepend_child(xml_node_type type = node_element); + xml_node insert_child_after(xml_node_type type, const xml_node& node); + xml_node insert_child_before(xml_node_type type, const xml_node& node); + + // Add child element with specified name. Returns added node, or empty node on errors. + xml_node append_child(const char_t* name); + xml_node prepend_child(const char_t* name); + xml_node insert_child_after(const char_t* name, const xml_node& node); + xml_node insert_child_before(const char_t* name, const xml_node& node); + + // Add a copy of the specified node as a child. Returns added node, or empty node on errors. + xml_node append_copy(const xml_node& proto); + xml_node prepend_copy(const xml_node& proto); + xml_node insert_copy_after(const xml_node& proto, const xml_node& node); + xml_node insert_copy_before(const xml_node& proto, const xml_node& node); + + // Move the specified node to become a child of this node. Returns moved node, or empty node on errors. + xml_node append_move(const xml_node& moved); + xml_node prepend_move(const xml_node& moved); + xml_node insert_move_after(const xml_node& moved, const xml_node& node); + xml_node insert_move_before(const xml_node& moved, const xml_node& node); + + // Remove specified attribute + bool remove_attribute(const xml_attribute& a); + bool remove_attribute(const char_t* name); + + // Remove specified child + bool remove_child(const xml_node& n); + bool remove_child(const char_t* name); + + // Parses buffer as an XML document fragment and appends all nodes as children of the current node. + // Copies/converts the buffer, so it may be deleted or changed after the function returns. + // Note: append_buffer allocates memory that has the lifetime of the owning document; removing the appended nodes does not immediately reclaim that memory. + xml_parse_result append_buffer(const void* contents, size_t size, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); + + // Find attribute using predicate. Returns first attribute for which predicate returned true. + template <typename Predicate> xml_attribute find_attribute(Predicate pred) const + { + if (!_root) return xml_attribute(); + + for (xml_attribute attrib = first_attribute(); attrib; attrib = attrib.next_attribute()) + if (pred(attrib)) + return attrib; + + return xml_attribute(); + } + + // Find child node using predicate. Returns first child for which predicate returned true. + template <typename Predicate> xml_node find_child(Predicate pred) const + { + if (!_root) return xml_node(); + + for (xml_node node = first_child(); node; node = node.next_sibling()) + if (pred(node)) + return node; + + return xml_node(); + } + + // Find node from subtree using predicate. Returns first node from subtree (depth-first), for which predicate returned true. + template <typename Predicate> xml_node find_node(Predicate pred) const + { + if (!_root) return xml_node(); + + xml_node cur = first_child(); + + while (cur._root && cur._root != _root) + { + if (pred(cur)) return cur; + + if (cur.first_child()) cur = cur.first_child(); + else if (cur.next_sibling()) cur = cur.next_sibling(); + else + { + while (!cur.next_sibling() && cur._root != _root) cur = cur.parent(); + + if (cur._root != _root) cur = cur.next_sibling(); + } + } + + return xml_node(); + } + + // Find child node by attribute name/value + xml_node find_child_by_attribute(const char_t* name, const char_t* attr_name, const char_t* attr_value) const; + xml_node find_child_by_attribute(const char_t* attr_name, const char_t* attr_value) const; + + #ifndef PUGIXML_NO_STL + // Get the absolute node path from root as a text string. + string_t path(char_t delimiter = '/') const; + #endif + + // Search for a node by path consisting of node names and . or .. elements. + xml_node first_element_by_path(const char_t* path, char_t delimiter = '/') const; + + // Recursively traverse subtree with xml_tree_walker + bool traverse(xml_tree_walker& walker); + + #ifndef PUGIXML_NO_XPATH + // Select single node by evaluating XPath query. Returns first node from the resulting node set. + xpath_node select_single_node(const char_t* query, xpath_variable_set* variables = 0) const; + xpath_node select_single_node(const xpath_query& query) const; + + // Select node set by evaluating XPath query + xpath_node_set select_nodes(const char_t* query, xpath_variable_set* variables = 0) const; + xpath_node_set select_nodes(const xpath_query& query) const; + #endif + + // Print subtree using a writer object + void print(xml_writer& writer, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto, unsigned int depth = 0) const; + + #ifndef PUGIXML_NO_STL + // Print subtree to stream + void print(std::basic_ostream<char, std::char_traits<char> >& os, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto, unsigned int depth = 0) const; + void print(std::basic_ostream<wchar_t, std::char_traits<wchar_t> >& os, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, unsigned int depth = 0) const; + #endif + + // Child nodes iterators + typedef xml_node_iterator iterator; + + iterator begin() const; + iterator end() const; + + // Attribute iterators + typedef xml_attribute_iterator attribute_iterator; + + attribute_iterator attributes_begin() const; + attribute_iterator attributes_end() const; + + // Range-based for support + xml_object_range<xml_node_iterator> children() const; + xml_object_range<xml_named_node_iterator> children(const char_t* name) const; + xml_object_range<xml_attribute_iterator> attributes() const; + + // Get node offset in parsed file/string (in char_t units) for debugging purposes + ptrdiff_t offset_debug() const; + + // Get hash value (unique for handles to the same object) + size_t hash_value() const; + + // Get internal pointer + xml_node_struct* internal_object() const; + }; + +#ifdef __BORLANDC__ + // Borland C++ workaround + bool PUGIXML_FUNCTION operator&&(const xml_node& lhs, bool rhs); + bool PUGIXML_FUNCTION operator||(const xml_node& lhs, bool rhs); +#endif + + // A helper for working with text inside PCDATA nodes + class PUGIXML_CLASS xml_text + { + friend class xml_node; + + xml_node_struct* _root; + + typedef void (*unspecified_bool_type)(xml_text***); + + explicit xml_text(xml_node_struct* root); + + xml_node_struct* _data_new(); + xml_node_struct* _data() const; + + public: + // Default constructor. Constructs an empty object. + xml_text(); + + // Safe bool conversion operator + operator unspecified_bool_type() const; + + // Borland C++ workaround + bool operator!() const; + + // Check if text object is empty + bool empty() const; + + // Get text, or "" if object is empty + const char_t* get() const; + + // Get text, or the default value if object is empty + const char_t* as_string(const char_t* def = PUGIXML_TEXT("")) const; + + // Get text as a number, or the default value if conversion did not succeed or object is empty + int as_int(int def = 0) const; + unsigned int as_uint(unsigned int def = 0) const; + double as_double(double def = 0) const; + float as_float(float def = 0) const; + + #ifdef PUGIXML_HAS_LONG_LONG + long long as_llong(long long def = 0) const; + unsigned long long as_ullong(unsigned long long def = 0) const; + #endif + + // Get text as bool (returns true if first character is in '1tTyY' set), or the default value if object is empty + bool as_bool(bool def = false) const; + + // Set text (returns false if object is empty or there is not enough memory) + bool set(const char_t* rhs); + + // Set text with type conversion (numbers are converted to strings, boolean is converted to "true"/"false") + bool set(int rhs); + bool set(unsigned int rhs); + bool set(double rhs); + bool set(bool rhs); + + #ifdef PUGIXML_HAS_LONG_LONG + bool set(long long rhs); + bool set(unsigned long long rhs); + #endif + + // Set text (equivalent to set without error checking) + xml_text& operator=(const char_t* rhs); + xml_text& operator=(int rhs); + xml_text& operator=(unsigned int rhs); + xml_text& operator=(double rhs); + xml_text& operator=(bool rhs); + + #ifdef PUGIXML_HAS_LONG_LONG + xml_text& operator=(long long rhs); + xml_text& operator=(unsigned long long rhs); + #endif + + // Get the data node (node_pcdata or node_cdata) for this object + xml_node data() const; + }; + +#ifdef __BORLANDC__ + // Borland C++ workaround + bool PUGIXML_FUNCTION operator&&(const xml_text& lhs, bool rhs); + bool PUGIXML_FUNCTION operator||(const xml_text& lhs, bool rhs); +#endif + + // Child node iterator (a bidirectional iterator over a collection of xml_node) + class PUGIXML_CLASS xml_node_iterator + { + friend class xml_node; + + private: + mutable xml_node _wrap; + xml_node _parent; + + xml_node_iterator(xml_node_struct* ref, xml_node_struct* parent); + + public: + // Iterator traits + typedef ptrdiff_t difference_type; + typedef xml_node value_type; + typedef xml_node* pointer; + typedef xml_node& reference; + + #ifndef PUGIXML_NO_STL + typedef std::bidirectional_iterator_tag iterator_category; + #endif + + // Default constructor + xml_node_iterator(); + + // Construct an iterator which points to the specified node + xml_node_iterator(const xml_node& node); + + // Iterator operators + bool operator==(const xml_node_iterator& rhs) const; + bool operator!=(const xml_node_iterator& rhs) const; + + xml_node& operator*() const; + xml_node* operator->() const; + + const xml_node_iterator& operator++(); + xml_node_iterator operator++(int); + + const xml_node_iterator& operator--(); + xml_node_iterator operator--(int); + }; + + // Attribute iterator (a bidirectional iterator over a collection of xml_attribute) + class PUGIXML_CLASS xml_attribute_iterator + { + friend class xml_node; + + private: + mutable xml_attribute _wrap; + xml_node _parent; + + xml_attribute_iterator(xml_attribute_struct* ref, xml_node_struct* parent); + + public: + // Iterator traits + typedef ptrdiff_t difference_type; + typedef xml_attribute value_type; + typedef xml_attribute* pointer; + typedef xml_attribute& reference; + + #ifndef PUGIXML_NO_STL + typedef std::bidirectional_iterator_tag iterator_category; + #endif + + // Default constructor + xml_attribute_iterator(); + + // Construct an iterator which points to the specified attribute + xml_attribute_iterator(const xml_attribute& attr, const xml_node& parent); + + // Iterator operators + bool operator==(const xml_attribute_iterator& rhs) const; + bool operator!=(const xml_attribute_iterator& rhs) const; + + xml_attribute& operator*() const; + xml_attribute* operator->() const; + + const xml_attribute_iterator& operator++(); + xml_attribute_iterator operator++(int); + + const xml_attribute_iterator& operator--(); + xml_attribute_iterator operator--(int); + }; + + // Named node range helper + class PUGIXML_CLASS xml_named_node_iterator + { + friend class xml_node; + + public: + // Iterator traits + typedef ptrdiff_t difference_type; + typedef xml_node value_type; + typedef xml_node* pointer; + typedef xml_node& reference; + + #ifndef PUGIXML_NO_STL + typedef std::bidirectional_iterator_tag iterator_category; + #endif + + // Default constructor + xml_named_node_iterator(); + + // Construct an iterator which points to the specified node + xml_named_node_iterator(const xml_node& node, const char_t* name); + + // Iterator operators + bool operator==(const xml_named_node_iterator& rhs) const; + bool operator!=(const xml_named_node_iterator& rhs) const; + + xml_node& operator*() const; + xml_node* operator->() const; + + const xml_named_node_iterator& operator++(); + xml_named_node_iterator operator++(int); + + const xml_named_node_iterator& operator--(); + xml_named_node_iterator operator--(int); + + private: + mutable xml_node _wrap; + xml_node _parent; + const char_t* _name; + + xml_named_node_iterator(xml_node_struct* ref, xml_node_struct* parent, const char_t* name); + }; + + // Abstract tree walker class (see xml_node::traverse) + class PUGIXML_CLASS xml_tree_walker + { + friend class xml_node; + + private: + int _depth; + + protected: + // Get current traversal depth + int depth() const; + + public: + xml_tree_walker(); + virtual ~xml_tree_walker(); + + // Callback that is called when traversal begins + virtual bool begin(xml_node& node); + + // Callback that is called for each node traversed + virtual bool for_each(xml_node& node) = 0; + + // Callback that is called when traversal ends + virtual bool end(xml_node& node); + }; + + // Parsing status, returned as part of xml_parse_result object + enum xml_parse_status + { + status_ok = 0, // No error + + status_file_not_found, // File was not found during load_file() + status_io_error, // Error reading from file/stream + status_out_of_memory, // Could not allocate memory + status_internal_error, // Internal error occurred + + status_unrecognized_tag, // Parser could not determine tag type + + status_bad_pi, // Parsing error occurred while parsing document declaration/processing instruction + status_bad_comment, // Parsing error occurred while parsing comment + status_bad_cdata, // Parsing error occurred while parsing CDATA section + status_bad_doctype, // Parsing error occurred while parsing document type declaration + status_bad_pcdata, // Parsing error occurred while parsing PCDATA section + status_bad_start_element, // Parsing error occurred while parsing start element tag + status_bad_attribute, // Parsing error occurred while parsing element attribute + status_bad_end_element, // Parsing error occurred while parsing end element tag + status_end_element_mismatch,// There was a mismatch of start-end tags (closing tag had incorrect name, some tag was not closed or there was an excessive closing tag) + + status_append_invalid_root, // Unable to append nodes since root type is not node_element or node_document (exclusive to xml_node::append_buffer) + + status_no_document_element // Parsing resulted in a document without element nodes + }; + + // Parsing result + struct PUGIXML_CLASS xml_parse_result + { + // Parsing status (see xml_parse_status) + xml_parse_status status; + + // Last parsed offset (in char_t units from start of input data) + ptrdiff_t offset; + + // Source document encoding + xml_encoding encoding; + + // Default constructor, initializes object to failed state + xml_parse_result(); + + // Cast to bool operator + operator bool() const; + + // Get error description + const char* description() const; + }; + + // Document class (DOM tree root) + class PUGIXML_CLASS xml_document: public xml_node + { + private: + char_t* _buffer; + + char _memory[192]; + + // Non-copyable semantics + xml_document(const xml_document&); + const xml_document& operator=(const xml_document&); + + void create(); + void destroy(); + + public: + // Default constructor, makes empty document + xml_document(); + + // Destructor, invalidates all node/attribute handles to this document + ~xml_document(); + + // Removes all nodes, leaving the empty document + void reset(); + + // Removes all nodes, then copies the entire contents of the specified document + void reset(const xml_document& proto); + + #ifndef PUGIXML_NO_STL + // Load document from stream. + xml_parse_result load(std::basic_istream<char, std::char_traits<char> >& stream, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); + xml_parse_result load(std::basic_istream<wchar_t, std::char_traits<wchar_t> >& stream, unsigned int options = parse_default); + #endif + + // Load document from zero-terminated string. No encoding conversions are applied. + xml_parse_result load(const char_t* contents, unsigned int options = parse_default); + + // Load document from file + xml_parse_result load_file(const char* path, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); + xml_parse_result load_file(const wchar_t* path, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); + + // Load document from buffer. Copies/converts the buffer, so it may be deleted or changed after the function returns. + xml_parse_result load_buffer(const void* contents, size_t size, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); + + // Load document from buffer, using the buffer for in-place parsing (the buffer is modified and used for storage of document data). + // You should ensure that buffer data will persist throughout the document's lifetime, and free the buffer memory manually once document is destroyed. + xml_parse_result load_buffer_inplace(void* contents, size_t size, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); + + // Load document from buffer, using the buffer for in-place parsing (the buffer is modified and used for storage of document data). + // You should allocate the buffer with pugixml allocation function; document will free the buffer when it is no longer needed (you can't use it anymore). + xml_parse_result load_buffer_inplace_own(void* contents, size_t size, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); + + // Save XML document to writer (semantics is slightly different from xml_node::print, see documentation for details). + void save(xml_writer& writer, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const; + + #ifndef PUGIXML_NO_STL + // Save XML document to stream (semantics is slightly different from xml_node::print, see documentation for details). + void save(std::basic_ostream<char, std::char_traits<char> >& stream, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const; + void save(std::basic_ostream<wchar_t, std::char_traits<wchar_t> >& stream, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default) const; + #endif + + // Save XML to file + bool save_file(const char* path, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const; + bool save_file(const wchar_t* path, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const; + + // Get document element + xml_node document_element() const; + }; + +#ifndef PUGIXML_NO_XPATH + // XPath query return type + enum xpath_value_type + { + xpath_type_none, // Unknown type (query failed to compile) + xpath_type_node_set, // Node set (xpath_node_set) + xpath_type_number, // Number + xpath_type_string, // String + xpath_type_boolean // Boolean + }; + + // XPath parsing result + struct PUGIXML_CLASS xpath_parse_result + { + // Error message (0 if no error) + const char* error; + + // Last parsed offset (in char_t units from string start) + ptrdiff_t offset; + + // Default constructor, initializes object to failed state + xpath_parse_result(); + + // Cast to bool operator + operator bool() const; + + // Get error description + const char* description() const; + }; + + // A single XPath variable + class PUGIXML_CLASS xpath_variable + { + friend class xpath_variable_set; + + protected: + xpath_value_type _type; + xpath_variable* _next; + + xpath_variable(); + + // Non-copyable semantics + xpath_variable(const xpath_variable&); + xpath_variable& operator=(const xpath_variable&); + + public: + // Get variable name + const char_t* name() const; + + // Get variable type + xpath_value_type type() const; + + // Get variable value; no type conversion is performed, default value (false, NaN, empty string, empty node set) is returned on type mismatch error + bool get_boolean() const; + double get_number() const; + const char_t* get_string() const; + const xpath_node_set& get_node_set() const; + + // Set variable value; no type conversion is performed, false is returned on type mismatch error + bool set(bool value); + bool set(double value); + bool set(const char_t* value); + bool set(const xpath_node_set& value); + }; + + // A set of XPath variables + class PUGIXML_CLASS xpath_variable_set + { + private: + xpath_variable* _data[64]; + + // Non-copyable semantics + xpath_variable_set(const xpath_variable_set&); + xpath_variable_set& operator=(const xpath_variable_set&); + + xpath_variable* find(const char_t* name) const; + + public: + // Default constructor/destructor + xpath_variable_set(); + ~xpath_variable_set(); + + // Add a new variable or get the existing one, if the types match + xpath_variable* add(const char_t* name, xpath_value_type type); + + // Set value of an existing variable; no type conversion is performed, false is returned if there is no such variable or if types mismatch + bool set(const char_t* name, bool value); + bool set(const char_t* name, double value); + bool set(const char_t* name, const char_t* value); + bool set(const char_t* name, const xpath_node_set& value); + + // Get existing variable by name + xpath_variable* get(const char_t* name); + const xpath_variable* get(const char_t* name) const; + }; + + // A compiled XPath query object + class PUGIXML_CLASS xpath_query + { + private: + void* _impl; + xpath_parse_result _result; + + typedef void (*unspecified_bool_type)(xpath_query***); + + // Non-copyable semantics + xpath_query(const xpath_query&); + xpath_query& operator=(const xpath_query&); + + public: + // Construct a compiled object from XPath expression. + // If PUGIXML_NO_EXCEPTIONS is not defined, throws xpath_exception on compilation errors. + explicit xpath_query(const char_t* query, xpath_variable_set* variables = 0); + + // Destructor + ~xpath_query(); + + // Get query expression return type + xpath_value_type return_type() const; + + // Evaluate expression as boolean value in the specified context; performs type conversion if necessary. + // If PUGIXML_NO_EXCEPTIONS is not defined, throws std::bad_alloc on out of memory errors. + bool evaluate_boolean(const xpath_node& n) const; + + // Evaluate expression as double value in the specified context; performs type conversion if necessary. + // If PUGIXML_NO_EXCEPTIONS is not defined, throws std::bad_alloc on out of memory errors. + double evaluate_number(const xpath_node& n) const; + + #ifndef PUGIXML_NO_STL + // Evaluate expression as string value in the specified context; performs type conversion if necessary. + // If PUGIXML_NO_EXCEPTIONS is not defined, throws std::bad_alloc on out of memory errors. + string_t evaluate_string(const xpath_node& n) const; + #endif + + // Evaluate expression as string value in the specified context; performs type conversion if necessary. + // At most capacity characters are written to the destination buffer, full result size is returned (includes terminating zero). + // If PUGIXML_NO_EXCEPTIONS is not defined, throws std::bad_alloc on out of memory errors. + // If PUGIXML_NO_EXCEPTIONS is defined, returns empty set instead. + size_t evaluate_string(char_t* buffer, size_t capacity, const xpath_node& n) const; + + // Evaluate expression as node set in the specified context. + // If PUGIXML_NO_EXCEPTIONS is not defined, throws xpath_exception on type mismatch and std::bad_alloc on out of memory errors. + // If PUGIXML_NO_EXCEPTIONS is defined, returns empty node set instead. + xpath_node_set evaluate_node_set(const xpath_node& n) const; + + // Get parsing result (used to get compilation errors in PUGIXML_NO_EXCEPTIONS mode) + const xpath_parse_result& result() const; + + // Safe bool conversion operator + operator unspecified_bool_type() const; + + // Borland C++ workaround + bool operator!() const; + }; + + #ifndef PUGIXML_NO_EXCEPTIONS + // XPath exception class + class PUGIXML_CLASS xpath_exception: public std::exception + { + private: + xpath_parse_result _result; + + public: + // Construct exception from parse result + explicit xpath_exception(const xpath_parse_result& result); + + // Get error message + virtual const char* what() const throw(); + + // Get parse result + const xpath_parse_result& result() const; + }; + #endif + + // XPath node class (either xml_node or xml_attribute) + class PUGIXML_CLASS xpath_node + { + private: + xml_node _node; + xml_attribute _attribute; + + typedef void (*unspecified_bool_type)(xpath_node***); + + public: + // Default constructor; constructs empty XPath node + xpath_node(); + + // Construct XPath node from XML node/attribute + xpath_node(const xml_node& node); + xpath_node(const xml_attribute& attribute, const xml_node& parent); + + // Get node/attribute, if any + xml_node node() const; + xml_attribute attribute() const; + + // Get parent of contained node/attribute + xml_node parent() const; + + // Safe bool conversion operator + operator unspecified_bool_type() const; + + // Borland C++ workaround + bool operator!() const; + + // Comparison operators + bool operator==(const xpath_node& n) const; + bool operator!=(const xpath_node& n) const; + }; + +#ifdef __BORLANDC__ + // Borland C++ workaround + bool PUGIXML_FUNCTION operator&&(const xpath_node& lhs, bool rhs); + bool PUGIXML_FUNCTION operator||(const xpath_node& lhs, bool rhs); +#endif + + // A fixed-size collection of XPath nodes + class PUGIXML_CLASS xpath_node_set + { + public: + // Collection type + enum type_t + { + type_unsorted, // Not ordered + type_sorted, // Sorted by document order (ascending) + type_sorted_reverse // Sorted by document order (descending) + }; + + // Constant iterator type + typedef const xpath_node* const_iterator; + + // We define non-constant iterator to be the same as constant iterator so that various generic algorithms (i.e. boost foreach) work + typedef const xpath_node* iterator; + + // Default constructor. Constructs empty set. + xpath_node_set(); + + // Constructs a set from iterator range; data is not checked for duplicates and is not sorted according to provided type, so be careful + xpath_node_set(const_iterator begin, const_iterator end, type_t type = type_unsorted); + + // Destructor + ~xpath_node_set(); + + // Copy constructor/assignment operator + xpath_node_set(const xpath_node_set& ns); + xpath_node_set& operator=(const xpath_node_set& ns); + + // Get collection type + type_t type() const; + + // Get collection size + size_t size() const; + + // Indexing operator + const xpath_node& operator[](size_t index) const; + + // Collection iterators + const_iterator begin() const; + const_iterator end() const; + + // Sort the collection in ascending/descending order by document order + void sort(bool reverse = false); + + // Get first node in the collection by document order + xpath_node first() const; + + // Check if collection is empty + bool empty() const; + + private: + type_t _type; + + xpath_node _storage; + + xpath_node* _begin; + xpath_node* _end; + + void _assign(const_iterator begin, const_iterator end); + }; +#endif + +#ifndef PUGIXML_NO_STL + // Convert wide string to UTF8 + std::basic_string<char, std::char_traits<char>, std::allocator<char> > PUGIXML_FUNCTION as_utf8(const wchar_t* str); + std::basic_string<char, std::char_traits<char>, std::allocator<char> > PUGIXML_FUNCTION as_utf8(const std::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t> >& str); + + // Convert UTF8 to wide string + std::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t> > PUGIXML_FUNCTION as_wide(const char* str); + std::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t> > PUGIXML_FUNCTION as_wide(const std::basic_string<char, std::char_traits<char>, std::allocator<char> >& str); +#endif + + // Memory allocation function interface; returns pointer to allocated memory or NULL on failure + typedef void* (*allocation_function)(size_t size); + + // Memory deallocation function interface + typedef void (*deallocation_function)(void* ptr); + + // Override default memory management functions. All subsequent allocations/deallocations will be performed via supplied functions. + void PUGIXML_FUNCTION set_memory_management_functions(allocation_function allocate, deallocation_function deallocate); + + // Get current memory management functions + allocation_function PUGIXML_FUNCTION get_memory_allocation_function(); + deallocation_function PUGIXML_FUNCTION get_memory_deallocation_function(); +} + +#if !defined(PUGIXML_NO_STL) && (defined(_MSC_VER) || defined(__ICC)) +namespace std +{ + // Workarounds for (non-standard) iterator category detection for older versions (MSVC7/IC8 and earlier) + std::bidirectional_iterator_tag PUGIXML_FUNCTION _Iter_cat(const pugi::xml_node_iterator&); + std::bidirectional_iterator_tag PUGIXML_FUNCTION _Iter_cat(const pugi::xml_attribute_iterator&); + std::bidirectional_iterator_tag PUGIXML_FUNCTION _Iter_cat(const pugi::xml_named_node_iterator&); +} +#endif + +#if !defined(PUGIXML_NO_STL) && defined(__SUNPRO_CC) +namespace std +{ + // Workarounds for (non-standard) iterator category detection + std::bidirectional_iterator_tag PUGIXML_FUNCTION __iterator_category(const pugi::xml_node_iterator&); + std::bidirectional_iterator_tag PUGIXML_FUNCTION __iterator_category(const pugi::xml_attribute_iterator&); + std::bidirectional_iterator_tag PUGIXML_FUNCTION __iterator_category(const pugi::xml_named_node_iterator&); +} +#endif + +#endif + +/** + * Copyright (c) 2006-2014 Arseny Kapoulkine + * + * Permission is hereby granted, 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. + */ diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 18f84fbca625319f805b1d65e5d797b5e6ecb117..0eee7fead6502bcadfe5819da96330fae7e2a67e 100755 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -210,6 +210,8 @@ set(viewer_SOURCE_FILES lldrawpoolwlsky.cpp lldroptarget.cpp lldynamictexture.cpp + lleasymessagereader.cpp + lleasymessagesender.cpp llemote.cpp llenvmanager.cpp llestateinfomodel.cpp @@ -294,6 +296,9 @@ set(viewer_SOURCE_FILES llfloatermap.cpp llfloatermediasettings.cpp llfloatermemleak.cpp + llfloatermessagebuilder.cpp + llfloatermessagelog.cpp + llfloatermessagerewriter.cpp llfloatermodelpreview.cpp llfloatermodeluploadbase.cpp llfloaternamedesc.cpp @@ -853,6 +858,8 @@ set(viewer_HEADER_FILES lldrawpoolwlsky.h lldroptarget.h lldynamictexture.h + lleasymessagereader.h + lleasymessagesender.h llemote.h llenvmanager.h llestateinfomodel.h @@ -937,6 +944,9 @@ set(viewer_HEADER_FILES llfloatermarketplacelistings.h llfloatermediasettings.h llfloatermemleak.h + llfloatermessagebuilder.h + llfloatermessagelog.h + llfloatermessagerewriter.h llfloatermodelpreview.h llfloatermodeluploadbase.h llfloaternamedesc.h diff --git a/indra/newview/lleasymessagereader.cpp b/indra/newview/lleasymessagereader.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a8080b1126936ca9f4fb1c7a0cd357db078ff7e2 --- /dev/null +++ b/indra/newview/lleasymessagereader.cpp @@ -0,0 +1,470 @@ +/** + * @file lleasymessagereader.cpp + * + * $LicenseInfo:firstyear=2015&license=viewerlgpl$ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "lleasymessagereader.h" +#include "llviewerregion.h" +#include "llworld.h" +#include "llsdserialize.h" + +#include <pugixml.hpp> +#include <libxml2/libxml/parser.h> +#include <boost/algorithm/string/predicate.hpp> + +//I doubt any of this is thread safe! +LLEasyMessageLogEntry::LLEasyMessageLogEntry(LogPayload entry, LLEasyMessageReader* message_reader) +: LLMessageLogEntry((*entry)) +, mEasyMessageReader(message_reader) +, mResponseMsg(NULL) +{ + mID.generate(); + mSequenceID = 0; + + if(mType == TEMPLATE) + { + mFlags = mData[0]; + + LLMessageTemplate* temp = NULL; + + if (mEasyMessageReader) + temp = mEasyMessageReader->decodeTemplateMessage( + &(mData[0]), mDataSize, mFromHost, mSequenceID); + + if (temp) + mNames.insert(temp->mName); + else + mNames.insert("Invalid"); + + mRegionHosts.insert(isOutgoing() ? mToHost : mFromHost); + } + else if(mType == HTTP_REQUEST)// not template + { + std::string base_url = get_base_cap_url(mURL); + + if(LLWorld::getInstance()->isCapURLMapped(base_url)) + { + CapUrlMatches matches = LLWorld::getInstance()->getCapURLMatches(base_url); + mNames = matches.mCapNames; + for(std::set<LLViewerRegion*>::iterator iter = matches.mRegions.begin(); iter != matches.mRegions.end(); ++iter) + { + mRegionHosts.insert((*iter)->getHost()); + } + } + else + mNames.insert(mURL); + } + else // not template + { + mNames.insert("SOMETHING ELSE"); + } +} + +LLEasyMessageLogEntry::~LLEasyMessageLogEntry() +{ + if(mResponseMsg) + delete mResponseMsg; +} +BOOL LLEasyMessageLogEntry::isOutgoing() +{ +#define LOCALHOST_ADDR 16777343 + return mFromHost == LLHost(LOCALHOST_ADDR, gMessageSystem->getListenPort()); +#undef LOCALHOST_ADDR +} +std::string LLEasyMessageLogEntry::getName() +{ + std::string message_names; + std::set<std::string>::iterator iter = mNames.begin(); + std::set<std::string>::const_iterator begin = mNames.begin(); + std::set<std::string>::const_iterator end = mNames.end(); + + while(iter != end) + { + if(iter != begin) + message_names += ", "; + + message_names += (*iter); + ++iter; + } + + return message_names; +} + +void LLEasyMessageLogEntry::setResponseMessage(LogPayload entry) +{ + // we already had a response set, somehow. just get rid of it + if(mResponseMsg) + delete mResponseMsg; + + mResponseMsg = new LLEasyMessageLogEntry(entry); + delete entry; +} +std::string LLEasyMessageLogEntry::getFull(BOOL beautify, BOOL show_header) +{ + std::ostringstream full; + if(mType == TEMPLATE) + { + LLMessageTemplate* temp = NULL; + + if(mEasyMessageReader) + temp = mEasyMessageReader->decodeTemplateMessage(&(mData[0]), mDataSize, mFromHost); + + if(temp) + { + full << (isOutgoing() ? "out " : "in "); + full << llformat("%s\n\n", temp->mName); + if(show_header) + { + full << "[Header]\n"; + full << llformat("SequenceID = %u\n", mSequenceID); + full << llformat("LL_ZERO_CODE_FLAG = %s\n", (mFlags & LL_ZERO_CODE_FLAG) ? "True" : "False"); + full << llformat("LL_RELIABLE_FLAG = %s\n", (mFlags & LL_RELIABLE_FLAG) ? "True" : "False"); + full << llformat("LL_RESENT_FLAG = %s\n", (mFlags & LL_RESENT_FLAG) ? "True" : "False"); + full << llformat("LL_ACK_FLAG = %s\n\n", (mFlags & LL_ACK_FLAG) ? "True" : "False"); + } + LLMessageTemplate::message_block_map_t::iterator blocks_end = temp->mMemberBlocks.end(); + for (LLMessageTemplate::message_block_map_t::iterator blocks_iter = temp->mMemberBlocks.begin(); + blocks_iter != blocks_end; ++blocks_iter) + { + LLMessageBlock* block = (*blocks_iter); + const char* block_name = block->mName; + S32 num_blocks = mEasyMessageReader->getNumberOfBlocks(block_name); + for(S32 block_num = 0; block_num < num_blocks; block_num++) + { + full << llformat("[%s]\n", block->mName); + LLMessageBlock::message_variable_map_t::iterator var_end = block->mMemberVariables.end(); + for (LLMessageBlock::message_variable_map_t::iterator var_iter = block->mMemberVariables.begin(); + var_iter != var_end; ++var_iter) + { + LLMessageVariable* variable = (*var_iter); + const char* var_name = variable->getName(); + BOOL returned_hex; + std::string value = mEasyMessageReader->var2Str(block_name, block_num, variable, returned_hex); + if(returned_hex) + full << llformat(" %s =| ", var_name); + else + full << llformat(" %s = ", var_name); + + full << value << "\n"; + } + } + } // blocks_iter + } + else + { + full << (isOutgoing() ? "out" : "in") << "\n"; + for(S32 i = 0; i < mDataSize; i++) + full << llformat("%02X ", mData[i]); + } + } + else if(mType == HTTP_REQUEST || HTTP_RESPONSE) + { + if(mType == HTTP_REQUEST) + full << llformat("%s %s\n", httpMethodAsVerb(mMethod).c_str(), mURL.c_str()); + if(mType == HTTP_RESPONSE) + full << llformat("%d\n", mStatusCode); + + if (mHeaders.isMap()) + { + LLSD::map_const_iterator iter = mHeaders.beginMap(); + LLSD::map_const_iterator end = mHeaders.endMap(); + + for (; iter != end; ++iter) + { + full << iter->first << ": " << iter->second.asString() << "\n"; + } + } + full << "\n"; + + if(mDataSize) + { + bool can_beautify = false; + if(beautify) + { + std::string content_type; + for(LLSD::map_iterator iter = mHeaders.beginMap(); iter != mHeaders.endMap(); ++iter) + { + if(boost::iequals(iter->first, "content-type")) + { + content_type = iter->second.asString(); + break; + } + } + + if(!content_type.empty()) + { + if(content_type == "application/llsd+xml" || content_type == "application/xml") + { + // Use PugiXML instead of LLXMLNode since Expat can change the semantics of + // input by dropping xml decls and expanding entities, as well as DoS the client. + // LLSDSerialize can't be used either since it uses Expat internally. + pugi::xml_document doc; + U32 parse_opts = (pugi::parse_default | pugi::parse_comments | pugi::parse_doctype + | pugi::parse_declaration | pugi::parse_pi) & ~(pugi::parse_escapes); + pugi::xml_parse_result res = doc.load_buffer(mData, mDataSize, parse_opts); + if(res) + { + U32 format_opts = pugi::format_default | pugi::format_no_escapes | pugi::format_no_declaration; + if(doc.child("llsd")) + format_opts |= pugi::format_pretty_llsd; + doc.save(full, " ", format_opts); + can_beautify = true; + } + else + { + LL_WARNS("EasyMessageReader") << "PugiXML failed with: " << res.description() << LL_ENDL; + } + } + } + } + if(!can_beautify) + full << mData; + } + } + //unsupported message type + else + { + full << "FIXME"; + } + return full.str(); +} + +std::string LLEasyMessageLogEntry::getResponseFull(BOOL beautify, BOOL show_header) +{ + if(!mResponseMsg) + return ""; + + return mResponseMsg->getFull(beautify, show_header); +} + +LLEasyMessageReader::LLEasyMessageReader() + : mTemplateMessageReader(gMessageSystem->mMessageNumbers) +{ +} + +LLEasyMessageReader::~LLEasyMessageReader() +{ +} + +//we might want the sequenceid of the packet, which we can't get from +//a messagetemplate pointer, allow for passing in a U32 to be replaced +//with the sequenceid +LLMessageTemplate* LLEasyMessageReader::decodeTemplateMessage(U8 *data, S32 data_len, LLHost from_host) +{ + U32 fake_id = 0; + return decodeTemplateMessage(data, data_len, from_host, fake_id); +} + +LLMessageTemplate* LLEasyMessageReader::decodeTemplateMessage(U8 *data, S32 data_len, LLHost from_host, U32& sequence_id) +{ + if(data_len > NET_BUFFER_SIZE) + { + LL_ERRS("") << "Tried to decode a template message of size " << data_len << ", greater than NET_BUFFER_SIZE!" << LL_ENDL; + return NULL; + } + U8 decode_buf[NET_BUFFER_SIZE]; + memcpy(&(decode_buf[0]), data, data_len); + U8* decodep = &(decode_buf[0]); + + LLMessageTemplate* message_template = NULL; + + gMessageSystem->zeroCodeExpand(&decodep, &data_len); + + if(data_len >= LL_MINIMUM_VALID_PACKET_SIZE) + { + sequence_id = ntohl(*((U32*)(&decodep[1]))); + mTemplateMessageReader.clearMessage(); + if(mTemplateMessageReader.validateMessage(decodep, data_len, from_host, TRUE)) + { + if(mTemplateMessageReader.decodeData(decodep, from_host, TRUE)) + { + message_template = mTemplateMessageReader.getTemplate(); + } + } + } + return message_template; +} + +S32 LLEasyMessageReader::getNumberOfBlocks(const char *blockname) +{ + return mTemplateMessageReader.getNumberOfBlocks(blockname); +} + +std::string LLEasyMessageReader::var2Str(const char* block_name, S32 block_num, LLMessageVariable* variable, BOOL &returned_hex, BOOL summary_mode) +{ + const char* var_name = variable->getName(); + e_message_variable_type var_type = variable->getType(); + + returned_hex = FALSE; + std::stringstream stream; + + char* value; + U32 valueU32; + U16 valueU16; + LLVector3 valueVector3; + LLVector3d valueVector3d; + LLVector4 valueVector4; + LLQuaternion valueQuaternion; + LLUUID valueLLUUID; + + switch(var_type) + { + case MVT_U8: + U8 valueU8; + mTemplateMessageReader.getU8(block_name, var_name, valueU8, block_num); + stream << U32(valueU8); + break; + case MVT_U16: + mTemplateMessageReader.getU16(block_name, var_name, valueU16, block_num); + stream << valueU16; + break; + case MVT_U32: + mTemplateMessageReader.getU32(block_name, var_name, valueU32, block_num); + stream << valueU32; + break; + case MVT_U64: + U64 valueU64; + mTemplateMessageReader.getU64(block_name, var_name, valueU64, block_num); + stream << valueU64; + break; + case MVT_S8: + S8 valueS8; + mTemplateMessageReader.getS8(block_name, var_name, valueS8, block_num); + stream << S32(valueS8); + break; + case MVT_S16: + S16 valueS16; + mTemplateMessageReader.getS16(block_name, var_name, valueS16, block_num); + stream << valueS16; + break; + case MVT_S32: + S32 valueS32; + mTemplateMessageReader.getS32(block_name, var_name, valueS32, block_num); + stream << valueS32; + break; + /*case MVT_S64: + S64 valueS64; + mTemplateMessageReader.getS64(block_name, var_name, valueS64, block_num); + stream << valueS64; + break;*/ + case MVT_F32: + F32 valueF32; + mTemplateMessageReader.getF32(block_name, var_name, valueF32, block_num); + stream << valueF32; + break; + case MVT_F64: + F64 valueF64; + mTemplateMessageReader.getF64(block_name, var_name, valueF64, block_num); + stream << valueF64; + break; + case MVT_LLVector3: + mTemplateMessageReader.getVector3(block_name, var_name, valueVector3, block_num); + //stream << valueVector3; + stream << "<" << valueVector3.mV[0] << ", " << valueVector3.mV[1] << ", " << valueVector3.mV[2] << ">"; + break; + case MVT_LLVector3d: + mTemplateMessageReader.getVector3d(block_name, var_name, valueVector3d, block_num); + //stream << valueVector3d; + stream << "<" << valueVector3d.mdV[0] << ", " << valueVector3d.mdV[1] << ", " << valueVector3d.mdV[2] << ">"; + break; + case MVT_LLVector4: + mTemplateMessageReader.getVector4(block_name, var_name, valueVector4, block_num); + //stream << valueVector4; + stream << "<" << valueVector4.mV[0] << ", " << valueVector4.mV[1] << ", " << valueVector4.mV[2] << ", " << valueVector4.mV[3] << ">"; + break; + case MVT_LLQuaternion: + mTemplateMessageReader.getQuat(block_name, var_name, valueQuaternion, block_num); + //stream << valueQuaternion; + stream << "<" << valueQuaternion.mQ[0] << ", " << valueQuaternion.mQ[1] << ", " << valueQuaternion.mQ[2] << ", " << valueQuaternion.mQ[3] << ">"; + break; + case MVT_LLUUID: + mTemplateMessageReader.getUUID(block_name, var_name, valueLLUUID, block_num); + stream << valueLLUUID; + break; + case MVT_BOOL: + BOOL valueBOOL; + mTemplateMessageReader.getBOOL(block_name, var_name, valueBOOL, block_num); + stream << valueBOOL; + break; + case MVT_IP_ADDR: + mTemplateMessageReader.getIPAddr(block_name, var_name, valueU32, block_num); + stream << LLHost(valueU32, 0).getIPString(); + break; + case MVT_IP_PORT: + mTemplateMessageReader.getIPPort(block_name, var_name, valueU16, block_num); + stream << valueU16; + case MVT_VARIABLE: + case MVT_FIXED: + default: + S32 size = mTemplateMessageReader.getSize(block_name, block_num, var_name); + if(size) + { + value = new char[size + 1]; + mTemplateMessageReader.getBinaryData(block_name, var_name, value, size, block_num); + value[size] = '\0'; + S32 readable = 0; + S32 unreadable = 0; + S32 end = (summary_mode && (size > 64)) ? 64 : size; + for(S32 i = 0; i < end; i++) + { + if(!value[i]) + { + if(i != (end - 1)) + { // don't want null terminator hiding data + unreadable = S32_MAX; + break; + } + } + else if(value[i] < 0x20 || value[i] >= 0x7F) + { + if(summary_mode) + unreadable++; + else + { // never want any wrong characters outside of summary mode + unreadable = S32_MAX; + break; + } + } + else readable++; + } + if(readable >= unreadable) + { + if(summary_mode && (size > 64)) + { + for(S32 i = 60; i < 63; i++) + value[i] = '.'; + value[63] = '\0'; + } + stream << value; + } + else + { + returned_hex = TRUE; + S32 end = (summary_mode && (size > 8)) ? 8 : size; + for(S32 i = 0; i < end; i++) + //stream << std::uppercase << std::hex << U32(value[i]) << " "; + stream << llformat("%02X ", (U8)value[i]); + if(summary_mode && (size > 8)) + stream << " ... "; + } + + delete[] value; + } + break; + } + + return stream.str(); +} diff --git a/indra/newview/lleasymessagereader.h b/indra/newview/lleasymessagereader.h new file mode 100644 index 0000000000000000000000000000000000000000..7b119bdbf96170e92e8b1a32a405cd760f992dde --- /dev/null +++ b/indra/newview/lleasymessagereader.h @@ -0,0 +1,75 @@ +/** + * @file lleasymessagereader.h + * + * $LicenseInfo:firstyear=2015&license=viewerlgpl$ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * $/LicenseInfo$ + **/ + +#ifndef EASY_MESSAGE_READER_H +#define EASY_MESSAGE_READER_H + +#include "llmessagelog.h" +#include "linden_common.h" +#include "message.h" +#include "lltemplatemessagereader.h" +#include "llmessagetemplate.h" + +class LLViewerRegion; + +class LLEasyMessageReader +{ +public: + LLEasyMessageReader(); + ~LLEasyMessageReader(); + + LLMessageTemplate* decodeTemplateMessage(U8* data, S32 data_len, LLHost from_host); + LLMessageTemplate* decodeTemplateMessage(U8* data, S32 data_len, LLHost from_host, U32& sequence_id); + + S32 getNumberOfBlocks(const char *blockname); + + std::string var2Str(const char* block_name, S32 block_num, LLMessageVariable* variable, BOOL &returned_hex, BOOL summary_mode=FALSE); + +private: + LLTemplateMessageReader mTemplateMessageReader; +}; + +class LLEasyMessageLogEntry : public LLMessageLogEntry +{ +public: + LLEasyMessageLogEntry(LogPayload entry, LLEasyMessageReader* message_reader = NULL); + LLEasyMessageLogEntry(LLEasyMessageReader* message_reader = NULL); + ~LLEasyMessageLogEntry(); + + std::string getFull(BOOL beautify = FALSE, BOOL show_header = FALSE); + std::string getName(); + std::string getResponseFull(BOOL beautify = FALSE, BOOL show_header = FALSE); + BOOL isOutgoing(); + + void setResponseMessage(LogPayload entry); + + LLUUID mID; + U32 mSequenceID; + //depending on how the server is configured, two cap handlers + //may have the exact same URI, meaning there may be multiple possible + //cap names for each message. Ditto for possible region hosts. + std::set<std::string> mNames; + std::set<LLHost> mRegionHosts; + std::string mSummary; + U32 mFlags; + +private: + LLEasyMessageLogEntry* mResponseMsg; + LLEasyMessageReader* mEasyMessageReader; +}; + +#endif diff --git a/indra/newview/lleasymessagesender.cpp b/indra/newview/lleasymessagesender.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1c6ede6d25445e34a434fd7a121383e8cff004b2 --- /dev/null +++ b/indra/newview/lleasymessagesender.cpp @@ -0,0 +1,851 @@ +/** + * @file lleasymessagesender.cpp + * + * $LicenseInfo:firstyear=2015&license=viewerlgpl$ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "lleasymessagesender.h" +#include "llmessagetemplate.h" +#include "lltemplatemessagebuilder.h" +#include "lltemplatemessagereader.h" +#include "llviewerregion.h" +#include "llagent.h" +#include "llnotificationsutil.h" +#include "llworld.h" +#include "llviewerregion.h" + +#include <boost/algorithm/string/join.hpp> + +//we don't use static methods to prepare for when this class will use its own message builder. + +LLEasyMessageSender::LLEasyMessageSender() +{ +} + +bool LLEasyMessageSender::sendMessage(const LLHost& region_host, const std::string& str_message) +{ + std::string msg_verb = str_message.substr(0, str_message.find(' ')); + LLStringUtil::toUpper(msg_verb); + if(msg_verb == "OUT" || msg_verb == "IN") + { + return sendLLUDPMessage(region_host, str_message); + } + else if(httpVerbAsMethod(msg_verb) != HTTP_INVALID) + { + return sendHTTPMessage(region_host, str_message); + } + else + { + printError(llformat("Unrecognized verb '%s'", msg_verb.c_str())); + } + return false; +} + +bool LLEasyMessageSender::sendLLUDPMessage(const LLHost& region_host, const std::string& str_message) +{ + std::vector<std::string> lines = split(str_message, "\n"); + if(!lines.size()) + { + LLNotificationsUtil::add("GenericAlert", LLSD().with("MESSAGE", "Not enough information :O")); + return false; + } + std::vector<std::string> tokens = split(lines[0], " "); + if(!tokens.size()) + { + LLNotificationsUtil::add("GenericAlert", LLSD().with("MESSAGE", "Not enough information :O")); + return false; + } + std::string dir_str = tokens[0]; + LLStringUtil::toUpper(dir_str); + + BOOL outgoing; + if(dir_str == "OUT") + outgoing = TRUE; + else if(dir_str == "IN") + outgoing = FALSE; + else + { + LLNotificationsUtil::add("GenericAlert", LLSD().with("MESSAGE", "Expected direction 'in' or 'out'")); + return false; + } + // Message + std::string message = "Invalid"; + if(tokens.size() > 1) + { + if(tokens.size() > 2) + { + LLNotificationsUtil::add("GenericAlert", LLSD().with("MESSAGE","Unexpected extra stuff at the top")); + return false; + } + message = tokens[1]; + LLStringUtil::trim(message); + } + // Body + std::vector<parts_block> parts; + if(lines.size() > 1) + { + std::vector<std::string>::iterator line_end = lines.end(); + std::vector<std::string>::iterator line_iter = lines.begin(); + ++line_iter; + std::string current_block = LLStringUtil::null; + S32 current_block_index = -1; + for( ; line_iter != line_end; ++line_iter) + { + std::string line = (*line_iter); + LLStringUtil::trim(line); + + //skip empty lines + if(!line.length()) + continue; + + //check if this line is the start of a new block + if(line.substr(0, 1) == "[" && line.substr(line.size() - 1, 1) == "]") + { + current_block = line.substr(1, line.length() - 2); + LLStringUtil::trim(current_block); + ++current_block_index; + parts_block pb; + pb.name = current_block; + parts.push_back(pb); + } + //should be a key->value pair + else + { + if(current_block.empty()) + { + LLNotificationsUtil::add("GenericAlert", LLSD().with("MESSAGE", "Got a field before the start of a block")); + return false; + } + + int eqpos = line.find("="); + if(eqpos == line.npos) + { + LLNotificationsUtil::add("GenericAlert", LLSD().with("MESSAGE", "Missing an equal sign")); + return false; + } + + std::string field = line.substr(0, eqpos); + LLStringUtil::trim(field); + if(!field.length()) + { + LLNotificationsUtil::add("GenericAlert", LLSD().with("MESSAGE", "Missing name of field")); + return false; + } + + std::string value = line.substr(eqpos + 1); + LLStringUtil::trim(value); + parts_var pv; + + //check if this is a hex value + if(value.substr(0, 1) == "|") + { + pv.hex = TRUE; + value = value.substr(1); + LLStringUtil::trim(value); + } + else + pv.hex = FALSE; + + pv.name = field; + pv.value = value; + parts[current_block_index].vars.push_back(pv); + } + } + } + + //Make sure everything's kosher with the message we built + + //check if the message type is one that we know about + std::map<const char *, LLMessageTemplate*>::iterator template_iter; + template_iter = gMessageSystem->mMessageTemplates.find( LLMessageStringTable::getInstance()->getString(message.c_str()) ); + if(template_iter == gMessageSystem->mMessageTemplates.end()) + { + LLNotificationsUtil::add("GenericAlert", LLSD().with("MESSAGE", llformat("Don't know how to build a '%s' message", message.c_str()))); + return false; + } + + LLMessageTemplate* temp = (*template_iter).second; + + std::vector<parts_block>::iterator parts_end = parts.end(); + std::vector<parts_block>::iterator parts_iter = parts.begin(); + LLMessageTemplate::message_block_map_t::iterator blocks_end = temp->mMemberBlocks.end(); + + for (LLMessageTemplate::message_block_map_t::iterator blocks_iter = temp->mMemberBlocks.begin(); + blocks_iter != blocks_end; ) + { + LLMessageBlock* block = (*blocks_iter); + const char* block_name = block->mName; + + //are we at the end of the block or does this block belongs at this spot in the message? + if(parts_iter == parts_end || (*parts_iter).name != block_name) + { + //did the block end too early? + if(block->mType != MBT_VARIABLE) + { + LLNotificationsUtil::add("GenericAlert", LLSD().with("MESSAGE",llformat("Expected '%s' block", block_name))); + return false; + } + + //skip to the next block + ++blocks_iter; + continue; + } + + std::vector<parts_var>::iterator part_var_end = (*parts_iter).vars.end(); + std::vector<parts_var>::iterator part_var_iter = (*parts_iter).vars.begin(); + LLMessageBlock::message_variable_map_t::iterator var_end = block->mMemberVariables.end(); + for (LLMessageBlock::message_variable_map_t::iterator var_iter = block->mMemberVariables.begin(); + var_iter != var_end; ++var_iter) + { + LLMessageVariable* variable = (*var_iter); + const char* var_name = variable->getName(); + + //are there less keypairs in this block than there should be? + if(part_var_iter == part_var_end || (*part_var_iter).name != var_name) + { + LLNotificationsUtil::add("GenericAlert", LLSD().with("MESSAGE", llformat("Expected '%s' field under '%s' block", var_name, block_name))); + return false; + } + + //keep the type of data that this value is supposed to contain for later + (*part_var_iter).var_type = variable->getType(); + + ++part_var_iter; + } + + //there were more keypairs in the block than there should have been + if(part_var_iter != part_var_end) + { + LLNotificationsUtil::add("GenericAlert", LLSD().with("MESSAGE", llformat("Unexpected field(s) at end of '%s' block", block_name))); + return false; + } + + ++parts_iter; + + //if this block isn't going to repeat, change to the next block + if(!((block->mType != MBT_SINGLE) && (parts_iter != parts_end) && ((*parts_iter).name == block_name))) + ++blocks_iter; + + } + + //there were more blocks specified in the message than there should have been + if(parts_iter != parts_end) + { + LLNotificationsUtil::add("GenericAlert", LLSD().with("MESSAGE", llformat("Unexpected block(s) at end: %s", (*parts_iter).name.c_str()))); + return false; + } + + // Build and send + gMessageSystem->newMessage( message.c_str() ); + for(parts_iter = parts.begin(); parts_iter != parts_end; ++parts_iter) + { + const char* block_name = (*parts_iter).name.c_str(); + gMessageSystem->nextBlock(block_name); + std::vector<parts_var>::iterator part_var_end = (*parts_iter).vars.end(); + for(std::vector<parts_var>::iterator part_var_iter = (*parts_iter).vars.begin(); + part_var_iter != part_var_end; ++part_var_iter) + { + parts_var pv = (*part_var_iter); + if(!addField(pv.var_type, pv.name.c_str(), pv.value, pv.hex)) + { + LLNotificationsUtil::add("GenericAlert", LLSD().with("MESSAGE", llformat("Error adding the provided data for %s '%s' to '%s' block", mvtstr(pv.var_type).c_str(), pv.name.c_str(), block_name))); + gMessageSystem->clearMessage(); + return false; + } + } + } + + + if(outgoing) + return (gMessageSystem->sendMessage(region_host) > 0); + else + { + U8 builtMessageBuffer[MAX_BUFFER_SIZE]; + + S32 message_size = gMessageSystem->mTemplateMessageBuilder->buildMessage(builtMessageBuffer, MAX_BUFFER_SIZE, 0); + gMessageSystem->clearMessage(); + return gMessageSystem->checkMessages(0, true, builtMessageBuffer, region_host, message_size); + } +} + +bool LLEasyMessageSender::sendHTTPMessage(const LLHost& region_host, const std::string& str_message) +{ + size_t header_start = str_message.find('\n'); + if(header_start == std::string::npos) + header_start = str_message.size(); + + std::string proto_line = str_message.substr(0, header_start); + std::vector<std::string> proto_tokens = split(proto_line, " "); + if(proto_tokens.size() != 2) + { + printError("Protocol line should be [VERB] [URL]"); + return false; + } + std::string http_verb = proto_tokens[0]; + LLStringUtil::toUpper(http_verb); + EHTTPMethod http_method = httpVerbAsMethod(http_verb); + if(http_method == HTTP_INVALID) + { + printError(llformat("Invalid HTTP verb '%s'", http_verb.c_str())); + return false; + } + std::string target = proto_tokens[1]; + + // This isn't a fully-qualified URL + if(target.find(':') == std::string::npos) + { + LLViewerRegion* region = LLWorld::getInstance()->getRegion(region_host); + if(!region) + { + printError("No region to do CAPS lookup on"); + return false; + } + + std::vector<std::string> split_url = split(target, "/"); + std::string cap_url = region->getCapability(split_url[0]); + if(cap_url.empty()) + { + printError(llformat("Invalid CAP name: %s", split_url[0].c_str())); + return false; + } + split_url[0] = cap_url; + target = boost::algorithm::join(split_url, "/"); + } + + LLSD header_map; + U8* body = NULL; + size_t body_len = 0; + + // TODO: This does way too many string copies. The path's not very hot + // but it's still gross. + // Start by adding any headers + if(header_start < str_message.size()) + { + static const std::string BODY_SEPARATOR("\n\n"); + size_t header_end = str_message.find(BODY_SEPARATOR); + if(header_end == std::string::npos) + header_end = str_message.size(); + if(header_start != header_end) + { + std::string header_section = str_message.substr(header_start, header_end - header_start); + std::vector<std::string> headers = split(header_section, "\n"); + for(std::vector<std::string>::iterator iter=headers.begin(); iter!=headers.end(); ++iter) + { + std::vector<std::string> header_tokens = split((*iter), ":"); + if(header_tokens.size() != 2) + { + printError(llformat("Malformed Header: %s", iter->c_str())); + return false; + } + + std::string header_name = header_tokens[0]; + std::string header_val = header_tokens[1]; + LLStringUtil::trim(header_name); + if(!header_val.empty() && header_val[0] == ' ') + header_val = header_val.substr(1); + + header_map[header_name] = header_val; + } + } + + // Now add the body + size_t body_start = header_end + BODY_SEPARATOR.size(); + if(body_start < str_message.size()) + { + std::string body_str = str_message.substr(body_start); + body_len = body_str.size(); + body = new U8[body_len]; + memcpy(body, body_str.c_str(), sizeof(U8) * body_len); + } + } + + LLHTTPClient::builderRequest(http_method, target, body, body_len, header_map); + return true; +} + +// Unused right now, but for the future! +#ifdef ALCH_ADDON_API +//wrapper so we can send whole message-builder format messages +bool LLEasyMessageSender::addonSendRawMessage(const std::string& region_host, const std::string& str_message) +{ + addonClearMessage(); + + LLHost proper_region_host = LLHost(region_host); + + //check that this was a valid host + if(proper_region_host.isOk()) + return sendMessage(proper_region_host, str_message); + + return false; +} + +bool LLEasyMessageSender::addonSendRawMessage(const std::string& str_message) +{ + return addonSendRawMessage(gAgent.getRegionHost().getString(), str_message); +} + +//buffered message builder methods +bool LLEasyMessageSender::addonSendMessage(const std::string& region_host) +{ + return addonSendRawMessage(region_host, mMessageBuffer); +} + +bool LLEasyMessageSender::addonSendMessage() +{ + return addonSendRawMessage(gAgent.getRegionHost().getString(), mMessageBuffer); +} + +void LLEasyMessageSender::addonNewMessage(const std::string& message_name, const std::string& direction, bool include_agent_boilerplate) +{ + //clear out any message that may be in the buffer + addonClearMessage(); + + mMessageBuffer = direction + " " + message_name + "\n"; + + //include the agentdata block with our agentid and sessionid automagically + if(include_agent_boilerplate) + mMessageBuffer += "[AgentData]\nAgentID = $AgentID\nSessionID = $SessionID\n"; +} + +void LLEasyMessageSender::addonClearMessage() +{ + mMessageBuffer = LLStringUtil::null; +} + +void LLEasyMessageSender::addonAddBlock(const std::string& blockname) +{ + mMessageBuffer += "[" + blockname + "]\n"; +} + +void LLEasyMessageSender::addonAddField(const std::string& name, const std::string& value) +{ + mMessageBuffer += name + " = " + value + "\n"; +} + +void LLEasyMessageSender::addonAddHexField(const std::string& name, const std::string& value) +{ + mMessageBuffer += name + " =| " + value + "\n"; +} +#endif // ALCH_ADDON_API + +BOOL LLEasyMessageSender::addField(e_message_variable_type var_type, const char* var_name, std::string input, BOOL hex) +{ + LLStringUtil::trim(input); + if(input.length() < 1 && var_type != MVT_VARIABLE) + return FALSE; + U8 valueU8; + U16 valueU16; + U32 valueU32; + U64 valueU64; + S8 valueS8; + S16 valueS16; + S32 valueS32; + // S64 valueS64; + F32 valueF32; + F64 valueF64; + LLVector3 valueVector3; + LLVector3d valueVector3d; + LLVector4 valueVector4; + LLQuaternion valueQuaternion; + LLUUID valueLLUUID; + BOOL valueBOOL; + std::string input_lower = input; + LLStringUtil::toLower(input_lower); + + if(input_lower == "$agentid") + input = gAgent.getID().asString(); + else if(input_lower == "$sessionid") + input = gAgent.getSessionID().asString(); + else if(input_lower == "$uuid") + { + LLUUID id; + id.generate(); + input = id.asString(); + } + else if(input_lower == "$circuitcode") + { + std::stringstream temp_stream; + temp_stream << gMessageSystem->mOurCircuitCode; + input = temp_stream.str(); + } + else if(input_lower == "$regionhandle") + { + std::stringstream temp_stream; + temp_stream << (gAgent.getRegion() ? gAgent.getRegion()->getHandle() : 0); + input = temp_stream.str(); + } + else if(input_lower == "$position" || input_lower == "$pos") + { + std::stringstream temp_stream; + valueVector3 = gAgent.getPositionAgent(); + temp_stream << "<" << valueVector3[0] << ", " << valueVector3[1] << ", " << valueVector3[2] << ">"; + input = temp_stream.str(); + } + + //convert from a text representation of hex to binary + if(hex) + { + if(var_type != MVT_VARIABLE && var_type != MVT_FIXED) + return FALSE; + + int len = input_lower.length(); + const char* cstr = input_lower.c_str(); + std::string new_input(""); + BOOL nibble = FALSE; + char byte = 0; + + for(int i = 0; i < len; i++) + { + char c = cstr[i]; + if(c >= 0x30 && c <= 0x39) + c -= 0x30; + else if(c >= 0x61 && c <= 0x66) + c -= 0x57; + else if(c != 0x20) + return FALSE; + else + continue; + if(!nibble) + byte = c << 4; + else + new_input.push_back(byte | c); + nibble = !nibble; + } + + if(nibble) + return FALSE; + + input = new_input; + } + + std::stringstream stream(input); + std::vector<std::string> tokens; + + switch(var_type) + { + case MVT_U8: + if(input.substr(0, 1) == "-") + return FALSE; + if((stream >> valueU32).fail()) + return FALSE; + valueU8 = (U8)valueU32; + gMessageSystem->addU8(var_name, valueU8); + return TRUE; + break; + case MVT_U16: + if(input.substr(0, 1) == "-") + return FALSE; + if((stream >> valueU16).fail()) + return FALSE; + gMessageSystem->addU16(var_name, valueU16); + return TRUE; + break; + case MVT_U32: + if(input.substr(0, 1) == "-") + return FALSE; + if((stream >> valueU32).fail()) + return FALSE; + gMessageSystem->addU32(var_name, valueU32); + return TRUE; + break; + case MVT_U64: + if(input.substr(0, 1) == "-") + return FALSE; + if((stream >> valueU64).fail()) + return FALSE; + gMessageSystem->addU64(var_name, valueU64); + return TRUE; + break; + case MVT_S8: + if((stream >> valueS8).fail()) + return FALSE; + gMessageSystem->addS8(var_name, valueS8); + return TRUE; + break; + case MVT_S16: + if((stream >> valueS16).fail()) + return FALSE; + gMessageSystem->addS16(var_name, valueS16); + return TRUE; + break; + case MVT_S32: + if((stream >> valueS32).fail()) + return FALSE; + gMessageSystem->addS32(var_name, valueS32); + return TRUE; + break; + /* + case MVT_S64: + if((stream >> valueS64).fail()) + return FALSE; + gMessageSystem->addS64(var_name, valueS64); + return TRUE; + break; + */ + case MVT_F32: + if((stream >> valueF32).fail()) + return FALSE; + gMessageSystem->addF32(var_name, valueF32); + return TRUE; + break; + case MVT_F64: + if((stream >> valueF64).fail()) + return FALSE; + gMessageSystem->addF64(var_name, valueF64); + return TRUE; + break; + case MVT_LLVector3: + LLStringUtil::trim(input); + if(input.substr(0, 1) != "<" || input.substr(input.length() - 1, 1) != ">") + return FALSE; + tokens = split(input.substr(1, input.length() - 2), ","); + if(tokens.size() != 3) + return FALSE; + for(int i = 0; i < 3; i++) + { + stream.clear(); + stream.str(tokens[i]); + if((stream >> valueF32).fail()) + return FALSE; + valueVector3.mV[i] = valueF32; + } + gMessageSystem->addVector3(var_name, valueVector3); + return TRUE; + break; + case MVT_LLVector3d: + LLStringUtil::trim(input); + if(input.substr(0, 1) != "<" || input.substr(input.length() - 1, 1) != ">") + return FALSE; + tokens = split(input.substr(1, input.length() - 2), ","); + if(tokens.size() != 3) + return FALSE; + for(int i = 0; i < 3; i++) + { + stream.clear(); + stream.str(tokens[i]); + if((stream >> valueF64).fail()) + return FALSE; + valueVector3d.mdV[i] = valueF64; + } + gMessageSystem->addVector3d(var_name, valueVector3d); + return TRUE; + break; + case MVT_LLVector4: + LLStringUtil::trim(input); + if(input.substr(0, 1) != "<" || input.substr(input.length() - 1, 1) != ">") + return FALSE; + tokens = split(input.substr(1, input.length() - 2), ","); + if(tokens.size() != 4) + return FALSE; + for(int i = 0; i < 4; i++) + { + stream.clear(); + stream.str(tokens[i]); + if((stream >> valueF32).fail()) + return FALSE; + valueVector4.mV[i] = valueF32; + } + gMessageSystem->addVector4(var_name, valueVector4); + return TRUE; + break; + case MVT_LLQuaternion: + LLStringUtil::trim(input); + if(input.substr(0, 1) != "<" || input.substr(input.length() - 1, 1) != ">") + return FALSE; + tokens = split(input.substr(1, input.length() - 2), ","); + if(tokens.size() == 3) + { + for(int i = 0; i < 3; i++) + { + stream.clear(); + stream.str(tokens[i]); + if((stream >> valueF32).fail()) + return FALSE; + valueVector3.mV[i] = valueF32; + } + valueQuaternion.unpackFromVector3(valueVector3); + } + else if(tokens.size() == 4) + { + for(int i = 0; i < 4; i++) + { + stream.clear(); + stream.str(tokens[i]); + if((stream >> valueF32).fail()) + return FALSE; + valueQuaternion.mQ[i] = valueF32; + } + } + else + return FALSE; + + gMessageSystem->addQuat(var_name, valueQuaternion); + return TRUE; + break; + case MVT_LLUUID: + if((stream >> valueLLUUID).fail()) + return FALSE; + gMessageSystem->addUUID(var_name, valueLLUUID); + return TRUE; + break; + case MVT_BOOL: + if(input_lower == "true") + valueBOOL = TRUE; + else if(input_lower == "false") + valueBOOL = FALSE; + else if((stream >> valueBOOL).fail()) + return FALSE; + gMessageSystem->addBOOL(var_name, valueBOOL); + //gMessageSystem->addU8(var_name, (U8)valueBOOL); + return TRUE; + break; + case MVT_IP_ADDR: + if((stream >> valueU32).fail()) + return FALSE; + gMessageSystem->addIPAddr(var_name, valueU32); + return TRUE; + break; + case MVT_IP_PORT: + if((stream >> valueU16).fail()) + return FALSE; + gMessageSystem->addIPPort(var_name, valueU16); + return TRUE; + break; + case MVT_VARIABLE: + if(!hex) + { + char* buffer = new char[input.size() + 1]; + strncpy(buffer, input.c_str(), input.size()); + buffer[input.size()] = '\0'; + gMessageSystem->addBinaryData(var_name, buffer, input.size() + 1); + delete[] buffer; + } + else + gMessageSystem->addBinaryData(var_name, input.c_str(), input.size()); + return TRUE; + break; + case MVT_FIXED: + if(!hex) + { + char* buffer = new char[input.size() + 1]; + strncpy(buffer, input.c_str(), input.size()); + buffer[input.size()] = '\0'; + gMessageSystem->addBinaryData(var_name, buffer, input.size()); + delete[] buffer; + } + else + gMessageSystem->addBinaryData(var_name, input.c_str(), input.size()); + return TRUE; + break; + default: + break; + } + return FALSE; +} + +void LLEasyMessageSender::printError(const std::string& error) +{ + LLSD args; + args["MESSAGE"] = error; + LLNotificationsUtil::add("GenericAlert", args); +} + +//convert a message variable type to it's string representation +std::string LLEasyMessageSender::mvtstr(e_message_variable_type var_type) +{ + switch(var_type) + { + case MVT_U8: + return "U8"; + break; + case MVT_U16: + return "U16"; + break; + case MVT_U32: + return "U32"; + break; + case MVT_U64: + return "U64"; + break; + case MVT_S8: + return "S8"; + break; + case MVT_S16: + return "S16"; + break; + case MVT_S32: + return "S32"; + break; + case MVT_S64: + return "S64"; + break; + case MVT_F32: + return "F32"; + break; + case MVT_F64: + return "F64"; + break; + case MVT_LLVector3: + return "LLVector3"; + break; + case MVT_LLVector3d: + return "LLVector3d"; + break; + case MVT_LLVector4: + return "LLVector4"; + break; + case MVT_LLQuaternion: + return "LLQuaternion"; + break; + case MVT_LLUUID: + return "LLUUID"; + break; + case MVT_BOOL: + return "BOOL"; + break; + case MVT_IP_ADDR: + return "IPADDR"; + break; + case MVT_IP_PORT: + return "IPPORT"; + break; + case MVT_VARIABLE: + return "Variable"; + break; + case MVT_FIXED: + return "Fixed"; + break; + default: + return "Missingno."; + break; + } +} + +inline std::vector<std::string> LLEasyMessageSender::split(const std::string& input, const std::string& separator) +{ + S32 size = input.length(); + char* buffer = new char[size + 1]; + strncpy(buffer, input.c_str(), size); + buffer[size] = '\0'; + std::vector<std::string> lines; + char* result = strtok(buffer, separator.c_str()); + while(result) + { + lines.push_back(result); + result = strtok(NULL, separator.c_str()); + } + delete[] buffer; + return lines; +} diff --git a/indra/newview/lleasymessagesender.h b/indra/newview/lleasymessagesender.h new file mode 100644 index 0000000000000000000000000000000000000000..9b06d24bf886c835f4c1c9e8f5f62541a13d3c4f --- /dev/null +++ b/indra/newview/lleasymessagesender.h @@ -0,0 +1,71 @@ +/** + * @file lleasymessagesender.h + * + * $LicenseInfo:firstyear=2015&license=viewerlgpl$ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * $/LicenseInfo$ + */ + +#ifndef LL_EASYMESSAGE_SENDER_H +#define LL_EASYMESSAGE_SENDER_H + +#include <llmessagetemplate.h> + +class LLEasyMessageSender +{ +public: + LLEasyMessageSender(); + + bool sendMessage(const LLHost& region_host, const std::string& str_message ); + bool sendLLUDPMessage(const LLHost& region_host, const std::string& str_message ); + bool sendHTTPMessage(const LLHost& region_host, const std::string& str_message ); + +#ifdef ALCH_ADDON_API + bool addonSendRawMessage(const std::string& region_host, const std::string& str_message); + bool addonSendRawMessage(const std::string& str_message); + bool addonSendMessage(const std::string& region_host); + bool addonSendMessage(); + void addonNewMessage(const std::string& message_name, const std::string& direction, bool include_agent_boilerplate=false); + void addonClearMessage(); + void addonAddBlock(const std::string& blockname); + void addonAddField(const std::string& name, const std::string& value); + void addonAddHexField(const std::string& name, const std::string& value); +#endif // ALCH_ADDON_API + +private: + + BOOL addField(e_message_variable_type var_type, const char* var_name, std::string input, BOOL hex); + + //a key->value pair in a message + struct parts_var + { + std::string name; + std::string value; + BOOL hex; + e_message_variable_type var_type; + }; + + //a block containing key->value pairs + struct parts_block + { + std::string name; + std::vector<parts_var> vars; + }; + + std::string mMessageBuffer; + + void printError(const std::string& error); + std::string mvtstr(e_message_variable_type var_type); + + std::vector<std::string> split(const std::string& input, const std::string& separator); +}; +#endif diff --git a/indra/newview/llfloatermessagebuilder.cpp b/indra/newview/llfloatermessagebuilder.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a141f498715ab99abd3eaf144c4cadea2cdbe9c2 --- /dev/null +++ b/indra/newview/llfloatermessagebuilder.cpp @@ -0,0 +1,398 @@ +/** + * @file lleasymessagesender.cpp + * + * $LicenseInfo:firstyear=2015&license=viewerlgpl$ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llfloatermessagebuilder.h" +#include "lluictrlfactory.h" +#include "llmessagetemplate.h" +#include "llagent.h" +#include "llviewerregion.h" // getHandle +#include "llcombobox.h" +#include "llselectmgr.h" // fill in stuff about selected object +#include "llparcel.h" +#include "llviewerparcelmgr.h" // same for parcel +#include "llscrolllistctrl.h" +#include "llworld.h" +#include "lltemplatemessagebuilder.h" +#include "llfloaterreg.h" +#include "llnotificationsutil.h" +#include "lleasymessagesender.h" +#include "lltextbase.h" + +//////////////////////////////// +// LLNetListItem +//////////////////////////////// +LLNetListItem::LLNetListItem(LLUUID id) +: mID(id), + mAutoName(TRUE), + mName("No name"), + mPreviousRegionName(""), + mCircuitData(NULL), + mHandle(0) +{ +} + +//////////////////////////////// +// LLFloaterMessageBuilder +//////////////////////////////// +std::list<LLNetListItem*> LLFloaterMessageBuilder::sNetListItems; + +LLFloaterMessageBuilder::LLFloaterMessageBuilder(const LLSD& key) +: LLFloater(key), + LLEventTimer(1.0f) +// mNetInfoMode(NI_NET) +{ +} + +void LLFloaterMessageBuilder::show(const std::string& initial_text) +{ + LLFloaterReg::showInstance("message_builder", initial_text.length() > 0 + ? LLSD().with("initial_text", initial_text) : LLSD()); +} + +void LLFloaterMessageBuilder::onOpen(const LLSD& key) +{ + if(key.has("initial_text")) + { + mInitialText = key["initial_text"].asString(); + getChild<LLTextBase>("message_edit")->setText(mInitialText); + } +} +BOOL LLFloaterMessageBuilder::tick() +{ + refreshNetList(); + return FALSE; +} +LLNetListItem* LLFloaterMessageBuilder::findNetListItem(LLHost host) +{ + std::list<LLNetListItem*>::iterator end = sNetListItems.end(); + for(std::list<LLNetListItem*>::iterator iter = sNetListItems.begin(); iter != end; ++iter) + if((*iter)->mCircuitData && (*iter)->mCircuitData->getHost() == host) + return (*iter); + return NULL; +} +LLNetListItem* LLFloaterMessageBuilder::findNetListItem(LLUUID id) +{ + std::list<LLNetListItem*>::iterator end = sNetListItems.end(); + for(std::list<LLNetListItem*>::iterator iter = sNetListItems.begin(); iter != end; ++iter) + if((*iter)->mID == id) + return (*iter); + return NULL; +} +void LLFloaterMessageBuilder::refreshNetList() +{ + LLScrollListCtrl* scrollp = getChild<LLScrollListCtrl>("net_list"); + // Update circuit data of net list items + std::vector<LLCircuitData*> circuits = gMessageSystem->getCircuit()->getCircuitDataList(); + std::vector<LLCircuitData*>::iterator circuits_end = circuits.end(); + for(std::vector<LLCircuitData*>::iterator iter = circuits.begin(); iter != circuits_end; ++iter) + { + LLNetListItem* itemp = findNetListItem((*iter)->getHost()); + if(!itemp) + { + LLUUID id; id.generate(); + itemp = new LLNetListItem(id); + sNetListItems.push_back(itemp); + } + itemp->mCircuitData = (*iter); + } + // Clear circuit data of items whose circuits are gone + std::list<LLNetListItem*>::iterator items_end = sNetListItems.end(); + for(std::list<LLNetListItem*>::iterator iter = sNetListItems.begin(); iter != items_end; ++iter) + { + if(std::find(circuits.begin(), circuits.end(), (*iter)->mCircuitData) == circuits.end()) + (*iter)->mCircuitData = NULL; + } + // Remove net list items that are totally useless now + for(std::list<LLNetListItem*>::iterator iter = sNetListItems.begin(); iter != sNetListItems.end();) + { + if((*iter)->mCircuitData == NULL) + iter = sNetListItems.erase(iter); + else ++iter; + } + // Update names of net list items + items_end = sNetListItems.end(); + for(std::list<LLNetListItem*>::iterator iter = sNetListItems.begin(); iter != items_end; ++iter) + { + LLNetListItem* itemp = (*iter); + if(itemp->mAutoName) + { + if(itemp->mCircuitData) + { + LLViewerRegion* regionp = LLWorld::getInstance()->getRegion(itemp->mCircuitData->getHost()); + if(regionp) + { + std::string name = regionp->getName(); + if(name == "") name = llformat("%s (awaiting region name)", itemp->mCircuitData->getHost().getString().c_str()); + itemp->mName = name; + itemp->mPreviousRegionName = name; + } + else + { + itemp->mName = itemp->mCircuitData->getHost().getString(); + if(itemp->mPreviousRegionName != "") + itemp->mName.append(llformat(" (was %s)", itemp->mPreviousRegionName.c_str())); + } + } + else + { + // an item just for an event queue, not handled yet + itemp->mName = "Something else"; + } + } + } + // Rebuild scroll list from scratch + LLUUID selected_id = scrollp->getFirstSelected() ? scrollp->getFirstSelected()->getUUID() : LLUUID::null; + S32 scroll_pos = scrollp->getScrollPos(); + scrollp->clearRows(); + for (LLNetListItem* itemp : sNetListItems) + { + LLSD element; + element["id"] = itemp->mID; + LLSD& text_column = element["columns"][0]; + text_column["column"] = "text"; + text_column["value"] = itemp->mName + (itemp->mCircuitData->getHost() == gAgent.getRegionHost() ? " (main)" : ""); + + LLSD& state_column = element["columns"][ 1]; + state_column["column"] = "state"; + state_column["value"] = ""; + + LLScrollListItem* scroll_itemp = scrollp->addElement(element); + BOOL has_live_circuit = itemp->mCircuitData && itemp->mCircuitData->isAlive(); + + LLScrollListText* state = (LLScrollListText*)scroll_itemp->getColumn(1); + + if(has_live_circuit) + state->setText(LLStringExplicit("Alive")); + else + state->setText(LLStringExplicit("Dead")); + } + if(selected_id.notNull()) + scrollp->selectByID(selected_id); + if(scroll_pos < scrollp->getItemCount()) + scrollp->setScrollPos(scroll_pos); +} +BOOL LLFloaterMessageBuilder::postBuild() +{ + getChild<LLTextBase>("message_edit")->setText(mInitialText); + getChild<LLUICtrl>("send_btn")->setCommitCallback(boost::bind(&LLFloaterMessageBuilder::onClickSend, this)); + std::vector<std::string> names; + LLComboBox* combo; + LLMessageSystem::message_template_name_map_t::iterator temp_end = gMessageSystem->mMessageTemplates.end(); + LLMessageSystem::message_template_name_map_t::iterator temp_iter; + std::vector<std::string>::iterator names_end; + std::vector<std::string>::iterator names_iter; + for(temp_iter = gMessageSystem->mMessageTemplates.begin(); temp_iter != temp_end; ++temp_iter) + if((*temp_iter).second->getTrust() == MT_NOTRUST) + names.push_back((*temp_iter).second->mName); + std::sort(names.begin(), names.end()); + combo = getChild<LLComboBox>("untrusted_message_combo"); + names_end = names.end(); + for(names_iter = names.begin(); names_iter != names_end; ++names_iter) + combo->add((*names_iter)); + names.clear(); + for(temp_iter = gMessageSystem->mMessageTemplates.begin(); temp_iter != temp_end; ++temp_iter) + if((*temp_iter).second->getTrust() == MT_TRUST) + names.push_back((*temp_iter).second->mName); + std::sort(names.begin(), names.end()); + combo = getChild<LLComboBox>("trusted_message_combo"); + names_end = names.end(); + for(names_iter = names.begin(); names_iter != names_end; ++names_iter) + combo->add((*names_iter)); + getChild<LLUICtrl>("untrusted_message_combo")->setCommitCallback(boost::bind(&LLFloaterMessageBuilder::onCommitPacketCombo, this, _1)); + getChild<LLUICtrl>("trusted_message_combo")->setCommitCallback(boost::bind(&LLFloaterMessageBuilder::onCommitPacketCombo, this, _1)); + return TRUE; +} + +void LLFloaterMessageBuilder::onCommitPacketCombo(LLUICtrl* ctrl) +{ + LLViewerObject* selected_objectp = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); + LLParcel* agent_parcelp = LLViewerParcelMgr::getInstance()->getAgentParcel(); + std::string message = ctrl->getValue(); + std::map<const char *, LLMessageTemplate*>::iterator template_iter; + template_iter = gMessageSystem->mMessageTemplates.find(LLMessageStringTable::getInstance()->getString(message.c_str())); + if(template_iter == gMessageSystem->mMessageTemplates.end()) + { + getChild<LLTextBase>("message_edit")->setText(LLStringUtil::null); + return; + } + std::string text(llformat((*template_iter).second->getTrust() == MT_NOTRUST + ? "out %s\n\n" : "in %s\n\n", message.c_str())); + LLMessageTemplate* temp = (*template_iter).second; + LLMessageTemplate::message_block_map_t::iterator blocks_end = temp->mMemberBlocks.end(); + for (LLMessageTemplate::message_block_map_t::iterator blocks_iter = temp->mMemberBlocks.begin(); + blocks_iter != blocks_end; ++blocks_iter) + { + LLMessageBlock* block = (*blocks_iter); + const char* block_name = block->mName; + std::string block_name_string = std::string(block_name); + S32 num_blocks = 1; + if(block->mType == MBT_MULTIPLE) + num_blocks = block->mNumber; + else if(("ObjectLink" == message && "ObjectData" == block_name_string)) + num_blocks = 2; + for(S32 i = 0; i < num_blocks; i++) + { + text.append(llformat("[%s]\n", block_name)); + LLMessageBlock::message_variable_map_t::iterator var_end = block->mMemberVariables.end(); + for (LLMessageBlock::message_variable_map_t::iterator var_iter = block->mMemberVariables.begin(); + var_iter != var_end; ++var_iter) + { + LLMessageVariable* variable = (*var_iter); + const char* var_name = variable->getName(); + std::string var_name_string = std::string(var_name); + text.append(llformat(" %s = ", var_name)); + std::string value(""); + S32 size = variable->getSize(); + switch(variable->getType()) + { + case MVT_U8: + case MVT_U16: + case MVT_U32: + case MVT_U64: + case MVT_S8: + case MVT_S16: + case MVT_S32: + case MVT_IP_ADDR: + case MVT_IP_PORT: + if("RegionHandle" == var_name_string || "Handle" == var_name_string) + value = "$RegionHandle"; + else if("CircuitCode" == var_name_string || "ViewerCircuitCode" == var_name_string + || ("Code" == var_name_string && "CircuitCode" == block_name_string) ) + { + value = "$CircuitCode"; + } + else if(selected_objectp && + ("ObjectLocalID" == var_name_string || "TaskLocalID" == var_name_string || ("LocalID" == var_name_string && ("ObjectData" == block_name_string || "UpdateData" == block_name_string || "InventoryData" == block_name_string)))) + { + std::stringstream temp_stream; + temp_stream << selected_objectp->getLocalID(); + value = temp_stream.str(); + } + else if(agent_parcelp && "LocalID" == var_name_string + && ("ParcelData" == block_name_string || message.find("Parcel") != message.npos)) + { + std::stringstream temp_stream; + temp_stream << agent_parcelp->getLocalID(); + value = temp_stream.str(); + } + else if("PCode" == var_name_string) + value = "9"; + else if("PathCurve" == var_name_string) + value = "16"; + else if("ProfileCurve" == var_name_string) + value = "1"; + else if("PathScaleX" == var_name_string || "PathScaleY" == var_name_string) + value = "100"; + else if("BypassRaycast" == var_name_string) + value = "1"; + else + value = "0"; + break; + case MVT_F32: + case MVT_F64: + value = "0.0"; + break; + case MVT_LLVector3: + case MVT_LLVector3d: + case MVT_LLQuaternion: + if("Position" == var_name_string || "RayStart" == var_name_string || "RayEnd" == var_name_string) + value = "$Position"; + else if("Scale" == var_name_string) + value = "<0.5, 0.5, 0.5>"; + else + value = "<0, 0, 0>"; + break; + case MVT_LLVector4: + value = "<0, 0, 0, 0>"; + break; + case MVT_LLUUID: + if("AgentID" == var_name_string) + value = "$AgentID"; + else if("SessionID" == var_name_string) + value = "$SessionID"; + else if("ObjectID" == var_name_string && selected_objectp) + value = selected_objectp->getID().asString(); + else if("ParcelID" == var_name_string && agent_parcelp) + value = agent_parcelp->getID().asString(); + else + value = "00000000-0000-0000-0000-000000000000"; + break; + case MVT_BOOL: + value = "false"; + break; + case MVT_VARIABLE: + value = "Hello, world!"; + break; + case MVT_FIXED: + for(S32 si = 0; si < size; si++) + value.append("a"); + break; + default: + value = ""; + break; + } + text.append(llformat("%s\n", value.c_str())); + } + } + } + text = text.substr(0, text.length() - 1); + getChild<LLTextBase>("message_edit")->setText(text); +} + +void LLFloaterMessageBuilder::onClickSend() +{ + LLScrollListCtrl* scrollp = getChild<LLScrollListCtrl>("net_list"); + LLScrollListItem* selected_itemp = scrollp->getFirstSelected(); + + LLHost end_point; + + //if a specific circuit is selected, send it to that, otherwise send it to the current sim + if(selected_itemp) + { + LLNetListItem* itemp = findNetListItem(selected_itemp->getUUID()); + LLScrollListText* textColumn = (LLScrollListText*)selected_itemp->getColumn(1); + + //why would you send data through a dead circuit? + if(textColumn->getValue().asString() == "Dead") + { + LLNotificationsUtil::add("GenericAlert", LLSD().with("MESSAGE", "No sending messages through dead circuits!")); + return; + } + + end_point = itemp->mCircuitData->getHost(); + } + else + end_point = gAgent.getRegionHost(); + + mMessageSender.sendMessage(end_point, getChild<LLTextBase>("message_edit")->getText()); +} + +BOOL LLFloaterMessageBuilder::handleKeyHere(KEY key, MASK mask) +{ + if(key == KEY_RETURN && (mask & MASK_CONTROL)) + { + onClickSend(); + return TRUE; + } + if(key == KEY_ESCAPE) + { + releaseFocus(); + return TRUE; + } + return FALSE; +} diff --git a/indra/newview/llfloatermessagebuilder.h b/indra/newview/llfloatermessagebuilder.h new file mode 100644 index 0000000000000000000000000000000000000000..3cc81f7e676b5f7818dfa70b7d8187029dcd3c6a --- /dev/null +++ b/indra/newview/llfloatermessagebuilder.h @@ -0,0 +1,75 @@ +/** + * @file lleasymessagesender.cpp + * + * $LicenseInfo:firstyear=2015&license=viewerlgpl$ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERMESSAGEBUILDER_H +#define LL_LLFLOATERMESSAGEBUILDER_H + +#include "lltemplatemessagereader.h" +#include "llmessagelog.h" +#include "lleventtimer.h" +#include "llcircuit.h" +#include "llfloater.h" +#include "llmsgvariabletype.h" +#include "lleasymessagesender.h" + +class LLUICtrl; + +struct LLNetListItem +{ + LLNetListItem(LLUUID id); + LLUUID mID; + BOOL mAutoName; + std::string mName; + std::string mPreviousRegionName; + U64 mHandle; + LLCircuitData* mCircuitData; +}; + +class LLFloaterMessageBuilder : public LLFloater, public LLEventTimer +{ +public: + LLFloaterMessageBuilder(const LLSD &); + ~LLFloaterMessageBuilder() {} + BOOL postBuild() override; + void onOpen(const LLSD& key) override; + static void show(const std::string& initial_text); + +private: + static std::list<LLNetListItem*> sNetListItems; + + BOOL tick() override; + void onClickSend(); + void onCommitPacketCombo(LLUICtrl* ctrl); + + static LLFloaterMessageBuilder* sInstance; + BOOL handleKeyHere(KEY key, MASK mask) override; + std::string mInitialText; + + static LLNetListItem* findNetListItem(LLHost host); + static LLNetListItem* findNetListItem(LLUUID id); + void refreshNetList(); + /*typedef enum e_net_info_mode + { + NI_NET, + NI_LOG + } ENetInfoMode; + ENetInfoMode mNetInfoMode;*/ + + LLEasyMessageSender mMessageSender; +}; + +#endif diff --git a/indra/newview/llfloatermessagelog.cpp b/indra/newview/llfloatermessagelog.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f93e2e794acff3d0a3b8ec4af9c69fd383df9d9e --- /dev/null +++ b/indra/newview/llfloatermessagelog.cpp @@ -0,0 +1,900 @@ +/** + * @file llfloatermessagelog.cpp + * + * $LicenseInfo:firstyear=2015&license=viewerlgpl$ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llfloatermessagelog.h" + +#include "llcheckboxctrl.h" +#include "llscrolllistctrl.h" +#include "lltexteditor.h" + +#include "llagent.h" +#include "lleasymessagereader.h" +#include "llfloatermessagebuilder.h" +#include "llfloaterreg.h" +#include "llmessagetemplate.h" +#include "llnotificationsutil.h" +#include "llviewermenu.h" +#include "llviewerregion.h" +#include "llviewerwindow.h" +#include "llworld.h" + +#include <boost/tokenizer.hpp> + +const std::string DEFAULT_FILTER = "!StartPingCheck !CompletePingCheck !PacketAck !SimulatorViewerTimeMessage !SimStats !AgentUpdate !AgentAnimation !AvatarAnimation !ViewerEffect !CoarseLocationUpdate !LayerData !CameraConstraint !ObjectUpdateCached !RequestMultipleObjects !ObjectUpdate !ObjectUpdateCompressed !ImprovedTerseObjectUpdate !KillObject !ImagePacket !SendXferPacket !ConfirmXferPacket !TransferPacket !SoundTrigger !AttachedSound !PreloadSound"; + +//TODO: replace all filtering code, esp start/stopApplyingFilter + +LLMessageLogFilter::LLMessageLogFilter(const std::string& filter) +{ + set(filter); +} + +void LLMessageLogFilter::set(const std::string& filter) +{ + mAsString = filter; + mPositiveNames.clear(); + mNegativeNames.clear(); + typedef boost::tokenizer<boost::char_separator<char> > tokenizer; + boost::char_separator<char> sep(" ",""); + tokenizer tokens(filter, sep); + tokenizer::iterator end = tokens.end(); + for(tokenizer::iterator iter = tokens.begin(); iter != end; ++iter) + { + std::string token = (*iter); + LLStringUtil::trim(token); + if(token.length()) + { + LLStringUtil::toLower(token); + + BOOL negative = token.find("!") == 0; + if(negative) + { + token = token.substr(1); + mNegativeNames.push_back(token); + } + else + mPositiveNames.push_back(token); + } + } +} + +//////////////////////////////// +// LLMessageLogFilterApply +//////////////////////////////// + +LLMessageLogFilterApply::LLMessageLogFilterApply(LLFloaterMessageLog* parent) +: LLEventTimer(0.1f) +, mFinished(FALSE) +, mProgress(0) +, mParent(parent) +{ + mIter = mParent->sMessageLogEntries.begin(); +} + +void LLMessageLogFilterApply::cancel() +{ + mFinished = TRUE; +} + +BOOL LLMessageLogFilterApply::tick() +{ + //we shouldn't even exist anymore, bail out + if(mFinished) + return TRUE; + + LLMutexLock lock(LLFloaterMessageLog::sMessageListMutex); + + LogPayloadList::iterator end = mParent->sMessageLogEntries.end(); + for(S32 i = 0; i < 256; i++) + { + if(mIter == end) + { + mFinished = TRUE; + + if(mParent->mMessageLogFilterApply == this) + { + mParent->stopApplyingFilter(); + } + + return TRUE; + } + + mParent->conditionalLog(*mIter); + + ++mIter; + ++mProgress; + } + + mParent->updateFilterStatus(); + return FALSE; +} + +LLMessageLogNetMan::LLMessageLogNetMan() + : LLEventTimer(1.0f), + mCancel(false) +{ +} + +void LLMessageLogNetMan::cancel() +{ + mCancel = true; +} + +BOOL LLMessageLogNetMan::tick() +{ + if(mCancel) + return TRUE; + + LLFloaterMessageLog* floaterp = static_cast<LLFloaterMessageLog*>(LLFloaterReg::findInstance("message_log")); + if (floaterp) + floaterp->updateGlobalNetList(); + return FALSE; +} + +LLMessageLogNetMan::~LLMessageLogNetMan() +{ +} + +//////////////////////////////// +// LLFloaterMessageLog +//////////////////////////////// + +std::list<LLNetListItem*> LLFloaterMessageLog::sNetListItems; +LogPayloadList LLFloaterMessageLog::sMessageLogEntries; +LLMessageLogNetMan* LLFloaterMessageLog::sNetListTimer = NULL; +LLMutex* LLFloaterMessageLog::sNetListMutex = NULL; +LLMutex* LLFloaterMessageLog::sMessageListMutex = NULL; +LLMutex* LLFloaterMessageLog::sIncompleteHTTPConvoMutex = NULL; + +LLFloaterMessageLog::LLFloaterMessageLog(const LLSD& key) +: LLFloater(key) +, mInfoPaneMode(IPANE_NET) +, mMessageLogFilterApply(NULL) +, mMessagelogScrollListCtrl(NULL) +, mMessagesLogged(0) +, mBeautifyMessages(false) +, mMessageLogFilter(DEFAULT_FILTER) +, mEasyMessageReader(new LLEasyMessageReader()) +{ + mCommitCallbackRegistrar.add("MessageLog.Filter.Action", boost::bind(&LLFloaterMessageLog::onClickFilterMenu, this, _2)); + if(!sNetListMutex) + sNetListMutex = new LLMutex(); + if(!sMessageListMutex) + sMessageListMutex = new LLMutex(); + if(!sIncompleteHTTPConvoMutex) + sIncompleteHTTPConvoMutex = new LLMutex(); +} + +LLFloaterMessageLog::~LLFloaterMessageLog() +{ + stopApplyingFilter(); + clearFloaterMessageItems(true); + + LLMessageLog::setCallback(NULL); + + sNetListTimer->cancel(); + sNetListTimer = NULL; + + sNetListMutex->lock(); + sNetListItems.clear(); + sNetListMutex->unlock(); + + clearMessageLogEntries(); + delete mEasyMessageReader; + delete sNetListMutex; + delete sMessageListMutex; + delete sIncompleteHTTPConvoMutex; + sNetListMutex = NULL; + sMessageListMutex = NULL; + sIncompleteHTTPConvoMutex = NULL; +} + +BOOL LLFloaterMessageLog::postBuild() +{ + mMessagelogScrollListCtrl = getChild<LLScrollListCtrl>("message_log"); + mMessagelogScrollListCtrl->setSortCallback(boost::bind(&LLFloaterMessageLog::sortMessageList, this, _1 ,_2, _3)); + + getChild<LLUICtrl>("net_list")->setCommitCallback(boost::bind(&LLFloaterMessageLog::onCommitNetList, this, _1)); + getChild<LLUICtrl>("message_log")->setCommitCallback(boost::bind(&LLFloaterMessageLog::onCommitMessageLog, this, _1)); + getChild<LLUICtrl>("filter_apply_btn")->setCommitCallback(boost::bind(&LLFloaterMessageLog::onClickFilterApply, this)); + getChild<LLUICtrl>("filter_edit")->setCommitCallback(boost::bind(&LLFloaterMessageLog::onCommitFilter, this)); + getChild<LLUICtrl>("wrap_net_info")->setCommitCallback(boost::bind(&LLFloaterMessageLog::onCheckWrapNetInfo, this, _1)); + getChild<LLUICtrl>("beautify_messages")->setCommitCallback(boost::bind(&LLFloaterMessageLog::onCheckBeautifyMessages, this, _1)); + getChild<LLUICtrl>("clear_log_btn")->setCommitCallback(boost::bind(&LLFloaterMessageLog::onClickClearLog, this)); + getChild<LLUICtrl>("msg_builder_send_btn")->setCommitCallback(boost::bind(&LLFloaterMessageLog::onClickSendToMessageBuilder, this)); + getChild<LLLineEditor>("filter_edit")->setText(mMessageLogFilter.asString()); + + startApplyingFilter(mMessageLogFilter.asString(), TRUE); + + updateGlobalNetList(true); + sNetListTimer = new LLMessageLogNetMan(); + + setInfoPaneMode(IPANE_NET); + wrapInfoPaneText(true); + + LLMessageLog::setCallback(onLog); + + return TRUE; +} + +void LLFloaterMessageLog::clearFloaterMessageItems(bool dying) +{ + if(!dying) + { + childSetEnabled("msg_builder_send_btn", false); + mMessagelogScrollListCtrl->clearRows(); + } + + sIncompleteHTTPConvoMutex->lock(); + mIncompleteHTTPConvos.clear(); + sIncompleteHTTPConvoMutex->unlock(); + + FloaterMessageList::iterator iter = mFloaterMessageLogItems.begin(); + FloaterMessageList::const_iterator end = mFloaterMessageLogItems.end(); + for (;iter != end; ++iter) + { + delete *iter; + } + + mFloaterMessageLogItems.clear(); +} + +void LLFloaterMessageLog::clearMessageLogEntries() +{ + LLMutexLock lock(sMessageListMutex); + //make sure to delete the objects referenced by these pointers first + LogPayloadList::iterator iter = sMessageLogEntries.begin(); + LogPayloadList::const_iterator end = sMessageLogEntries.end(); + for (;iter != end; ++iter) + { + delete *iter; + } + + sMessageLogEntries.clear(); +} + +void LLFloaterMessageLog::updateGlobalNetList(bool starting) +{ + //something tells me things aren't deallocated properly here, but + //valgrind isn't complaining + + LLMutexLock lock(sNetListMutex); + + // Update circuit data of net list items + std::vector<LLCircuitData*> circuits = gMessageSystem->getCircuit()->getCircuitDataList(); + std::vector<LLCircuitData*>::iterator circuits_end = circuits.end(); + for(std::vector<LLCircuitData*>::iterator iter = circuits.begin(); iter != circuits_end; ++iter) + { + LLNetListItem* itemp = findNetListItem((*iter)->getHost()); + if(!itemp) + { + LLUUID id; id.generate(); + itemp = new LLNetListItem(id); + sNetListItems.push_back(itemp); + } + itemp->mCircuitData = (*iter); + } + // Clear circuit data of items whose circuits are gone + std::list<LLNetListItem*>::iterator items_end = sNetListItems.end(); + for(std::list<LLNetListItem*>::iterator iter = sNetListItems.begin(); iter != items_end; ++iter) + { + if(std::find(circuits.begin(), circuits.end(), (*iter)->mCircuitData) == circuits.end()) + (*iter)->mCircuitData = NULL; + } + // Remove net list items that are totally useless now + for(std::list<LLNetListItem*>::iterator iter = sNetListItems.begin(); iter != sNetListItems.end();) + { + if((*iter)->mCircuitData == NULL) + { + delete *iter; + iter = sNetListItems.erase(iter); + } + else ++iter; + } + + if(!starting) + { + refreshNetList(); + refreshNetInfo(FALSE); + } +} + +LLNetListItem* LLFloaterMessageLog::findNetListItem(LLHost host) +{ + std::list<LLNetListItem*>::iterator end = sNetListItems.end(); + for(std::list<LLNetListItem*>::iterator iter = sNetListItems.begin(); iter != end; ++iter) + if((*iter)->mCircuitData && (*iter)->mCircuitData->getHost() == host) + return (*iter); + return NULL; +} + +LLNetListItem* LLFloaterMessageLog::findNetListItem(LLUUID id) +{ + std::list<LLNetListItem*>::iterator end = sNetListItems.end(); + for(std::list<LLNetListItem*>::iterator iter = sNetListItems.begin(); iter != end; ++iter) + if((*iter)->mID == id) + return (*iter); + return NULL; +} + +void LLFloaterMessageLog::refreshNetList() +{ + LLScrollListCtrl* scrollp = getChild<LLScrollListCtrl>("net_list"); + + // Update names of net list items + std::list<LLNetListItem*>::iterator items_end = sNetListItems.end(); + for(std::list<LLNetListItem*>::iterator iter = sNetListItems.begin(); iter != items_end; ++iter) + { + LLNetListItem* itemp = (*iter); + if(itemp->mAutoName) + { + if(itemp->mCircuitData) + { + LLViewerRegion* regionp = LLWorld::getInstance()->getRegion(itemp->mCircuitData->getHost()); + if(regionp) + { + std::string name = regionp->getName(); + if(name.empty()) + name = llformat("%s (awaiting region name)", itemp->mCircuitData->getHost().getString().c_str()); + itemp->mName = name; + itemp->mPreviousRegionName = name; + itemp->mHandle = regionp->getHandle(); + } + else + { + itemp->mName = itemp->mCircuitData->getHost().getString(); + if(!itemp->mPreviousRegionName.empty()) + itemp->mName.append(llformat(" (was %s)", itemp->mPreviousRegionName.c_str())); + } + } + else + { + // an item just for an event queue, not handled yet + itemp->mName = "Something else"; + } + } + } + // Rebuild scroll list from scratch + LLUUID selected_id = scrollp->getFirstSelected() ? scrollp->getFirstSelected()->getUUID() : LLUUID::null; + S32 scroll_pos = scrollp->getScrollPos(); + scrollp->clearRows(); + for(std::list<LLNetListItem*>::iterator iter = sNetListItems.begin(); iter != items_end; ++iter) + { + LLNetListItem* itemp = (*iter); + LLSD element; + element["id"] = itemp->mID; + LLSD& text_column = element["columns"][0]; + text_column["column"] = "text"; + text_column["value"] = itemp->mName + (itemp->mCircuitData->getHost() == gAgent.getRegionHost() ? " (main)" : LLStringUtil::null); + for(S32 i = 0; i < 2; ++i) + { + LLSD& icon_column = element["columns"][i + 1]; + icon_column["column"] = llformat("icon%d", i); + icon_column["type"] = "icon"; + icon_column["value"] = ""; + } + LLScrollListItem* scroll_itemp = scrollp->addElement(element); + BOOL has_live_circuit = itemp->mCircuitData && itemp->mCircuitData->isAlive(); + if(has_live_circuit) + { + LLScrollListIcon* icon = (LLScrollListIcon*)scroll_itemp->getColumn(1); + icon->setValue("Stop_Off"); + icon->setColor(LLColor4(1.0f,0.0f,0.0f,0.7f)); + icon->setClickCallback(onClickCloseCircuit, itemp); + } + else + { + LLScrollListIcon* icon = (LLScrollListIcon*)scroll_itemp->getColumn(1); + icon->setValue("Stop_Off"); + icon->setColor(LLColor4(1.0f,1.0f,1.0f,0.5f)); + icon->setClickCallback(NULL, NULL); + } + // Event queue isn't even supported yet... FIXME + LLScrollListIcon* icon = (LLScrollListIcon*)scroll_itemp->getColumn(2); + icon->setValue("Stop_Off"); + icon->setColor(LLColor4(0.1f,0.1f,0.1f,0.7f)); + icon->setClickCallback(NULL, NULL); + } + if(selected_id.notNull()) scrollp->selectByID(selected_id); + if(scroll_pos < scrollp->getItemCount()) scrollp->setScrollPos(scroll_pos); +} + +void LLFloaterMessageLog::refreshNetInfo(BOOL force) +{ + if(mInfoPaneMode != IPANE_NET) return; + LLScrollListCtrl* scrollp = getChild<LLScrollListCtrl>("net_list"); + LLScrollListItem* selected_itemp = scrollp->getFirstSelected(); + if(selected_itemp) + { + LLTextEditor* net_info = getChild<LLTextEditor>("net_info"); + if(!force && (net_info->hasSelection() || net_info->hasFocus())) return; + LLNetListItem* itemp = findNetListItem(selected_itemp->getUUID()); + if(itemp) + { + std::string info(llformat("%s, %d\n--------------------------------\n\n", itemp->mName.c_str(), itemp->mHandle)); + if(itemp->mCircuitData) + { + LLCircuitData* cdp = itemp->mCircuitData; + info.append("Circuit\n--------------------------------\n"); + info.append(llformat(" * Host: %s\n", cdp->getHost().getString().c_str())); + S32 seconds = (S32)cdp->getAgeInSeconds(); + S32 minutes = seconds / 60; + seconds = seconds % 60; + S32 hours = minutes / 60; + minutes = minutes % 60; + info.append(llformat(" * Age: %dh %dm %ds\n", hours, minutes, seconds)); + info.append(llformat(" * Alive: %s\n", cdp->isAlive() ? "yes" : "no")); + info.append(llformat(" * Blocked: %s\n", cdp->isBlocked() ? "yes" : "no")); + info.append(llformat(" * Allow timeout: %s\n", cdp->getAllowTimeout() ? "yes" : "no")); + info.append(llformat(" * Trusted: %s\n", cdp->getTrusted() ? "yes" : "no")); + info.append(llformat(" * Ping delay: %d\n", cdp->getPingDelay().value())); + info.append(llformat(" * Packets out: %d\n", cdp->getPacketsOut())); + info.append(llformat(" * Bytes out: %d\n", cdp->getBytesOut().value())); + info.append(llformat(" * Packets in: %d\n", cdp->getPacketsIn())); + info.append(llformat(" * Bytes in: %d\n", cdp->getBytesIn().value())); + info.append(llformat(" * Endpoint ID: %s\n", cdp->getLocalEndPointID().asString().c_str())); + info.append(llformat(" * Remote ID: %s\n", cdp->getRemoteID().asString().c_str())); + info.append(llformat(" * Remote session ID: %s\n", cdp->getRemoteSessionID().asString().c_str())); + info.append("\n"); + } + + getChild<LLTextBase>("net_info")->setText(info); + } + else + getChild<LLTextBase>("net_info")->setText(LLStringUtil::null); + } + else + getChild<LLTextBase>("net_info")->setText(LLStringUtil::null); +} + +void LLFloaterMessageLog::setInfoPaneMode(EInfoPaneMode mode) +{ + mInfoPaneMode = mode; + if(mode == IPANE_NET) + refreshNetInfo(TRUE); + + //we hide the regular net_info editor and show two panes for http log mode + getChild<LLView>("net_info")->setVisible(mode != IPANE_HTTP_LOG); + getChild<LLView>("conv_stack")->setVisible(mode == IPANE_HTTP_LOG); + getChild<LLView>("msg_builder_send_btn")->setEnabled(mode != IPANE_NET); +} + +// static +void LLFloaterMessageLog::onLog(LogPayload entry) +{ + LLFloaterMessageLog* floaterp = static_cast<LLFloaterMessageLog*>(LLFloaterReg::findInstance("message_log")); + if (!floaterp) + { + delete entry; + return; + } + if (entry->mType != LLMessageLogEntry::HTTP_RESPONSE) + { + sMessageListMutex->lock(); + while(!floaterp->mMessageLogFilterApply && sMessageLogEntries.size() > 4096) + { + //delete the raw message we're getting rid of + delete sMessageLogEntries.front(); + sMessageLogEntries.pop_front(); + } + + ++floaterp->mMessagesLogged; + + sMessageLogEntries.push_back(entry); + + sMessageListMutex->unlock(); + + floaterp->conditionalLog(entry); + } + //this is a response, try to add it to the relevant request + else + { + floaterp->pairHTTPResponse(entry); + } +} + +void LLFloaterMessageLog::conditionalLog(LogPayload entry) +{ + if(!mMessageLogFilterApply) + getChild<LLTextBase>("log_status_text")->setText(llformat("Showing %d messages of %d", mFloaterMessageLogItems.size(), mMessagesLogged)); + + FloaterMessageItem item = new LLEasyMessageLogEntry(entry, mEasyMessageReader); + + + std::set<std::string>::const_iterator end_msg_name = item->mNames.end(); + std::set<std::string>::iterator iter_msg_name = item->mNames.begin(); + + bool have_positive = false; + + for(; iter_msg_name != end_msg_name; ++iter_msg_name) + { + std::string find_name = *iter_msg_name; + LLStringUtil::toLower(find_name); + + //keep the message if we allowed its name so long as one of its other names hasn't been blacklisted + if(!have_positive && !mMessageLogFilter.mPositiveNames.empty()) + { + if(std::find(mMessageLogFilter.mPositiveNames.begin(), mMessageLogFilter.mPositiveNames.end(), find_name) != mMessageLogFilter.mPositiveNames.end()) + have_positive = true; + } + if(!mMessageLogFilter.mNegativeNames.empty()) + { + if(std::find(mMessageLogFilter.mNegativeNames.begin(), mMessageLogFilter.mNegativeNames.end(), find_name) != mMessageLogFilter.mNegativeNames.end()) + { + delete item; + return; + } + } + //we don't have any negative filters and we have a positive match + else if(have_positive) + break; + } + + //we had a positive filter but no positive matches + if(!mMessageLogFilter.mPositiveNames.empty() && !have_positive) + { + delete item; + return; + } + + sMessageListMutex->lock(); + mFloaterMessageLogItems.push_back(item); // moved from beginning... + sMessageListMutex->unlock(); + + if(item->mType == LLEasyMessageLogEntry::HTTP_REQUEST) + { + LLMutexLock lock(sIncompleteHTTPConvoMutex); + mIncompleteHTTPConvos.insert(HTTPConvoMap::value_type(item->mRequestID, item)); + } + + std::string net_name; + if(item->mRegionHosts.size() > 0) + { + //LLHost find_host = outgoing ? item->mToHost : item->mFromHost; + //net_name = find_host.getIPandPort(); + for(std::set<LLHost>::iterator host_iter = item->mRegionHosts.begin(); host_iter != item->mRegionHosts.end(); ++host_iter) + { + std::string region_name = LLStringUtil::null; + std::list<LLNetListItem*>::iterator end = sNetListItems.end(); + for(std::list<LLNetListItem*>::iterator iter = sNetListItems.begin(); iter != end; ++iter) + { + if((*host_iter) == (*iter)->mCircuitData->getHost()) + { + region_name += (*iter)->mName; + break; + } + } + if(region_name.empty()) + region_name = host_iter->getIPandPort(); + if(!net_name.empty()) + net_name += ", "; + net_name += region_name; + } + } + else + { + // This is neither a region CAP nor an LLUDP message. + net_name = "\?\?\?"; + } + + //add the message to the messagelog scroller + LLSD element; + element["id"] = item->mID; + LLSD& sequence_column = element["columns"][0]; + sequence_column["column"] = "sequence"; + sequence_column["value"] = llformat("%u", item->mSequenceID); + + LLSD& type_column = element["columns"][1]; + type_column["column"] = "type"; + switch(item->mType) + { + case LLEasyMessageLogEntry::TEMPLATE: + type_column["value"] = "UDP"; + break; + case LLEasyMessageLogEntry::HTTP_REQUEST: + type_column["value"] = "HTTP"; + break; + default: + type_column["value"] = "\?\?\?"; + } + + LLSD& direction_column = element["columns"][2]; + direction_column["column"] = "direction"; + if(item->mType == LLEasyMessageLogEntry::TEMPLATE) + direction_column["value"] = item->isOutgoing() ? "to" : "from"; + else if(item->mType == LLEasyMessageLogEntry::HTTP_REQUEST) + direction_column["value"] = "both"; + + LLSD& net_column = element["columns"][3]; + net_column["column"] = "net"; + net_column["value"] = net_name; + + LLSD& name_column = element["columns"][4]; + name_column["column"] = "name"; + name_column["value"] = item->getName(); + + LLSD& summary_column = element["columns"][5]; + summary_column["column"] = "summary"; + summary_column["value"] = item->mSummary; + + S32 scroll_pos = mMessagelogScrollListCtrl->getScrollPos(); + //llinfos << "msglog scrollpos: " << scroll_pos << " // getLinesPerPage: " << scrollp->getLinesPerPage() << " // item count: " << scrollp->getItemCount() << llendl; + + mMessagelogScrollListCtrl->addElement(element, ADD_BOTTOM); + + if(scroll_pos > mMessagelogScrollListCtrl->getItemCount() - mMessagelogScrollListCtrl->getLinesPerPage() - 4) + mMessagelogScrollListCtrl->setScrollPos(mMessagelogScrollListCtrl->getItemCount()); + + +} + +void LLFloaterMessageLog::pairHTTPResponse(LogPayload entry) +{ + LLMutexLock lock(sIncompleteHTTPConvoMutex); + HTTPConvoMap::iterator iter = mIncompleteHTTPConvos.find(entry->mRequestID); + + if(iter != mIncompleteHTTPConvos.end()) + { + iter->second->setResponseMessage(entry); + + //if this message was already selected in the message log, + //redisplay it to show the response as well. + LLScrollListItem* itemp = mMessagelogScrollListCtrl->getFirstSelected(); + + if(itemp && itemp->getUUID() == iter->second->mID) + { + showMessage(iter->second); + } + mIncompleteHTTPConvos.erase(iter); + } + else + delete entry; +} + +void LLFloaterMessageLog::onCommitNetList(LLUICtrl* ctrl) +{ + setInfoPaneMode(IPANE_NET); + refreshNetInfo(TRUE); +} + +void LLFloaterMessageLog::onCommitMessageLog(LLUICtrl* ctrl) +{ + showSelectedMessage(); +} + +void LLFloaterMessageLog::showSelectedMessage() +{ + LLScrollListItem* selected_itemp = mMessagelogScrollListCtrl->getFirstSelected(); + if (!selected_itemp) return; + LLUUID id = selected_itemp->getUUID(); + for (LLEasyMessageLogEntry* entryp : mFloaterMessageLogItems) + { + if(entryp->mID == id) + { + showMessage(entryp); + break; + } + } +} + +void LLFloaterMessageLog::showMessage(FloaterMessageItem item) +{ + if(item->mType == LLMessageLogEntry::TEMPLATE) + { + setInfoPaneMode(IPANE_TEMPLATE_LOG); + getChild<LLTextBase>("net_info")->setText(item->getFull(mBeautifyMessages)); + } + else if(item->mType == LLMessageLogEntry::HTTP_REQUEST) + { + setInfoPaneMode(IPANE_HTTP_LOG); + getChild<LLTextBase>("conv_request")->setText(item->getFull(mBeautifyMessages)); + getChild<LLTextBase>("conv_response")->setText(item->getResponseFull(mBeautifyMessages)); + } +} + +// static +BOOL LLFloaterMessageLog::onClickCloseCircuit(void* user_data) +{ + LLNetListItem* itemp = static_cast<LLNetListItem*>(user_data); + LLCircuitData* cdp = static_cast<LLCircuitData*>(itemp->mCircuitData); + if(!cdp) return FALSE; + LLHost myhost = cdp->getHost(); + LLSD args; + args["MESSAGE"] = "This will delete local circuit data.\nDo you want to tell the remote host to close the circuit too?"; + LLSD payload; + payload["circuittoclose"] = myhost.getString(); + LLNotificationsUtil::add("GenericAlertYesCancel", args, payload, onConfirmCloseCircuit); + return TRUE; +} + +// static +void LLFloaterMessageLog::onConfirmCloseCircuit(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + + LLCircuitData* cdp = gMessageSystem->mCircuitInfo.findCircuit(LLHost(notification["payload"]["circuittoclose"].asString())); + if(!cdp) return; + LLViewerRegion* regionp = LLWorld::getInstance()->getRegion(cdp->getHost()); + switch(option) + { + case 0: // yes + gMessageSystem->newMessageFast(_PREHASH_CloseCircuit); + gMessageSystem->sendReliable(cdp->getHost()); + break; + case 2: // cancel + return; + break; + case 1: // no + default: + break; + } + if(gMessageSystem->findCircuitCode(cdp->getHost())) + gMessageSystem->disableCircuit(cdp->getHost()); + else + gMessageSystem->getCircuit()->removeCircuitData(cdp->getHost()); + if(regionp) + { + LLHost myhost = regionp->getHost(); + LLSD args; + args["MESSAGE"] = "That host had a region associated with it.\nDo you want to clean that up?"; + LLSD payload; + payload["regionhost"] = myhost.getString(); + LLNotificationsUtil::add("GenericAlertYesCancel", args, payload, onConfirmRemoveRegion); + } +} + +// static +void LLFloaterMessageLog::onConfirmRemoveRegion(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if(option == 0) // yes + LLWorld::getInstance()->removeRegion(LLHost(notification["payload"]["regionhost"].asString())); +} + +void LLFloaterMessageLog::onClickFilterApply() +{ + startApplyingFilter(childGetValue("filter_edit"), TRUE); +} + +void LLFloaterMessageLog::startApplyingFilter(const std::string& filter, BOOL force) +{ + LLMessageLogFilter new_filter(filter); + if (force + || (new_filter.mNegativeNames != mMessageLogFilter.mNegativeNames) + || (new_filter.mPositiveNames != mMessageLogFilter.mPositiveNames)) + { + stopApplyingFilter(); + mMessageLogFilter = new_filter; + + mMessagesLogged = sMessageLogEntries.size(); + clearFloaterMessageItems(); + + getChild<LLScrollListCtrl>("message_log")->setVisible(false); + mMessageLogFilterApply = new LLMessageLogFilterApply(this); + } +} + +void LLFloaterMessageLog::stopApplyingFilter(bool quitting) +{ + if(mMessageLogFilterApply) + { + mMessageLogFilterApply->cancel(); + + if(!quitting) + { + getChild<LLScrollListCtrl>("message_log")->setVisible(true); + getChild<LLTextBase>("log_status_text")->setText(llformat("Showing %d messages from %d", mFloaterMessageLogItems.size(), mMessagesLogged)); + } + } + + mMessageLogFilterApply = NULL; +} + +void LLFloaterMessageLog::updateFilterStatus() +{ + if (!mMessageLogFilterApply) return; + + S32 progress = mMessageLogFilterApply->getProgress(); + S32 packets = sMessageLogEntries.size(); + S32 matches = mFloaterMessageLogItems.size(); + std::string text = llformat("Filtering ( %d / %d ), %d matches ...", progress, packets, matches); + getChild<LLTextBase>("log_status_text")->setText(text); +} + +void LLFloaterMessageLog::onCommitFilter() +{ + startApplyingFilter(childGetValue("filter_edit"), FALSE); +} + +void LLFloaterMessageLog::onClickClearLog() +{ + stopApplyingFilter(); + mMessagelogScrollListCtrl->clearRows(); + setInfoPaneMode(IPANE_NET); + clearMessageLogEntries(); + clearFloaterMessageItems(); + mMessagesLogged = 0; +} + +void LLFloaterMessageLog::onClickFilterMenu(const LLSD& user_data) +{ + getChild<LLLineEditor>("filter_edit")->setText(user_data.asString()); + startApplyingFilter(user_data.asString(), FALSE); +} + +void LLFloaterMessageLog::onClickSendToMessageBuilder() +{ + LLScrollListItem* selected_itemp = mMessagelogScrollListCtrl->getFirstSelected(); + + if (!selected_itemp) return; + + LLUUID id = selected_itemp->getUUID(); + FloaterMessageList::iterator end = mFloaterMessageLogItems.end(); + for(FloaterMessageList::iterator iter = mFloaterMessageLogItems.begin(); iter != end; ++iter) + { + if((*iter)->mID == id) + { + std::string message_text = (*iter)->getFull(getBeautifyMessages()); + LLFloaterMessageBuilder::show(message_text); + break; + } + } +} + +void LLFloaterMessageLog::onCheckWrapNetInfo(LLUICtrl* ctrl) +{ + wrapInfoPaneText(static_cast<LLCheckBoxCtrl*>(ctrl)->getValue()); +} + +void LLFloaterMessageLog::onCheckBeautifyMessages(LLUICtrl* ctrl) +{ + mBeautifyMessages = static_cast<LLCheckBoxCtrl*>(ctrl)->getValue(); + + //if we already had a message selected, we need to set the full + //text of the message again + showSelectedMessage(); +} + +void LLFloaterMessageLog::wrapInfoPaneText(bool wrap) +{ + getChild<LLTextEditor>("net_info")->setWordWrap(wrap); + getChild<LLTextEditor>("conv_request")->setWordWrap(wrap); + getChild<LLTextEditor>("conv_response")->setWordWrap(wrap); +} + +S32 LLFloaterMessageLog::sortMessageList(S32 col_idx, const LLScrollListItem* i1, const LLScrollListItem* i2) +{ + const LLScrollListCell *cell1 = i1->getColumn(col_idx); + const LLScrollListCell *cell2 = i2->getColumn(col_idx); + + //do a numeric sort on the sequence num + if(col_idx == 0) + { + S32 cell1_i = cell1->getValue().asInteger(); + S32 cell2_i = cell2->getValue().asInteger(); + + if(cell1_i == cell2_i) + return 0; + if(cell1_i > cell2_i) + return 1; + + return -1; + } + + return LLStringUtil::compareDict(cell1->getValue().asString(), cell2->getValue().asString()); +} diff --git a/indra/newview/llfloatermessagelog.h b/indra/newview/llfloatermessagelog.h new file mode 100644 index 0000000000000000000000000000000000000000..e4cfb1259e65828ae9e60db87a4b7560d025194f --- /dev/null +++ b/indra/newview/llfloatermessagelog.h @@ -0,0 +1,175 @@ +/** + * @file llfloatermessagelog.cpp + * + * $LicenseInfo:firstyear=2015&license=viewerlgpl$ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * $/LicenseInfo$ + */ + +#include "llfloater.h" +#include "llmessagelog.h" +#include "lleventtimer.h" + +#include <boost/unordered_map.hpp> + +struct LLNetListItem; +class LLScrollListCtrl; +class LLEasyMessageLogEntry; +class LLEasyMessageReader; +class LLEasyMessageLogEntry; +class LLFloaterMessageLog; + +typedef std::list<LogPayload> LogPayloadList; +typedef LLEasyMessageLogEntry* FloaterMessageItem; +typedef std::list<LLEasyMessageLogEntry*> FloaterMessageList; +typedef boost::unordered_map<U64, FloaterMessageItem> HTTPConvoMap; + +class LLMessageLogFilter +{ +public: + LLMessageLogFilter() {} + ~LLMessageLogFilter() {} + LLMessageLogFilter(const std::string& filter); + void set(const std::string& filter); + + std::string asString() {return mAsString;} + + //these should probably be unordered_sets + std::list<std::string> mPositiveNames; + std::list<std::string> mNegativeNames; + +protected: + std::string mAsString; +}; + +class LLMessageLogFilterApply : public LLEventTimer +{ + +public: + LLMessageLogFilterApply(LLFloaterMessageLog* parent); + void cancel(); + S32 getProgress() { return mProgress; } + +private: + BOOL tick(); + S32 mProgress; + BOOL mFinished; + LLFloaterMessageLog* mParent; + LogPayloadList::iterator mIter; +}; + + +class LLMessageLogNetMan : public LLEventTimer +{ +public: + LLMessageLogNetMan(); + ~LLMessageLogNetMan(); + + void cancel(); + +protected: + bool mCancel; + BOOL tick(); +}; + + +class LLFloaterMessageLog : public LLFloater +{ +public: + LLFloaterMessageLog(const LLSD& key); + ~LLFloaterMessageLog(); + static void onLog(LogPayload entry); + +protected: + BOOL postBuild(); + void updateGlobalNetList(bool starting = false); + static LLNetListItem* findNetListItem(LLHost host); + static LLNetListItem* findNetListItem(LLUUID id); + + + void refreshNetList(); + void refreshNetInfo(BOOL force); + + //the textbox(es) in the lower half of the floater can + //display two types of information, information about + //the circuit, or information about the selected message. + + //depending on which mode is set, certain UI elements may + //be enabled or disabled. + enum EInfoPaneMode { IPANE_NET, IPANE_TEMPLATE_LOG, IPANE_HTTP_LOG }; + void setInfoPaneMode(EInfoPaneMode mode); + void wrapInfoPaneText(bool wrap); + + void conditionalLog(LogPayload entry); + void pairHTTPResponse(LogPayload entry); + + void showSelectedMessage(); + void showMessage(FloaterMessageItem item); + bool getBeautifyMessages() { return mBeautifyMessages; } + + void onCommitNetList(LLUICtrl* ctrl); + void onCommitMessageLog(LLUICtrl* ctrl); + void onClickClearLog(); + void onCommitFilter(); + void onClickFilterMenu(const LLSD& user_data); + void onClickFilterApply(); + void onClickSendToMessageBuilder(); + void onCheckWrapNetInfo(LLUICtrl* ctrl); + void onCheckBeautifyMessages(LLUICtrl* ctrl); + static BOOL onClickCloseCircuit(void* user_data); + static void onConfirmCloseCircuit(const LLSD& notification, const LLSD& response); + static void onConfirmRemoveRegion(const LLSD& notification, const LLSD& response); + + LLScrollListCtrl* mMessagelogScrollListCtrl; + +public: + void startApplyingFilter(const std::string& filter, BOOL force); + +protected: + void stopApplyingFilter(bool quitting = false); + void updateFilterStatus(); + + LLMessageLogFilter mMessageLogFilter; + LLMessageLogFilterApply* mMessageLogFilterApply; + + static LLMessageLogNetMan* sNetListTimer; + EInfoPaneMode mInfoPaneMode; + + bool mBeautifyMessages; + +public: + static std::list<LLNetListItem*> sNetListItems; + + static LogPayloadList sMessageLogEntries; + FloaterMessageList mFloaterMessageLogItems; + + static LLMutex* sNetListMutex; + static LLMutex* sMessageListMutex; + static LLMutex* sIncompleteHTTPConvoMutex; + +protected: + HTTPConvoMap mIncompleteHTTPConvos; + + U32 mMessagesLogged; + + LLEasyMessageReader* mEasyMessageReader; + + S32 sortMessageList(S32,const LLScrollListItem*,const LLScrollListItem*); + + void clearMessageLogEntries(); + void clearFloaterMessageItems(bool dying = false); + + //this needs to be able to look through the list of raw messages + //to be able to create floater message items on a timer. + friend class LLMessageLogFilterApply; + friend class LLMessageLogNetMan; +}; diff --git a/indra/newview/llfloatermessagerewriter.cpp b/indra/newview/llfloatermessagerewriter.cpp new file mode 100644 index 0000000000000000000000000000000000000000..fdbf2ac68f0b643abfc07c826304f6b6aa0fe252 --- /dev/null +++ b/indra/newview/llfloatermessagerewriter.cpp @@ -0,0 +1,111 @@ +/** + * @file llfloatermessagewriter.cpp + * + * $LicenseInfo:firstyear=2015&license=viewerlgpl$ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llfloatermessagerewriter.h" +#include "llviewercontrol.h" +#include "llscrolllistctrl.h" +#include "llcombobox.h" +#include "lltexteditor.h" +#include "llviewerwindow.h" // alertXml +#include "llmessagetemplate.h" +#include "llmessageconfig.h" +#include "llmenugl.h" +#include "llmessageconfig.h" + +#include <boost/tokenizer.hpp> + +LLFloaterMessageRewriter::LLFloaterMessageRewriter(const LLSD& key) +: LLFloater(key) +{ +} + +BOOL LLFloaterMessageRewriter::postBuild() +{ + getChild<LLUICtrl>("save_rules")->setCommitCallback(boost::bind(&LLFloaterMessageRewriter::onClickSaveRules, this)); + getChild<LLUICtrl>("new_rule")->setCommitCallback(boost::bind(&LLFloaterMessageRewriter::onClickNewRule, this)); + refreshRuleList(); + return TRUE; +} + +void LLFloaterMessageRewriter::refreshRuleList() +{ + LLScrollListCtrl* scrollp = getChild<LLScrollListCtrl>("rule_list"); + + // Rebuild scroll list from scratch + LLUUID selected_id = scrollp->getFirstSelected() ? scrollp->getFirstSelected()->getUUID() : LLUUID::null; + S32 scroll_pos = scrollp->getScrollPos(); + scrollp->clearRows(); + + LLSD element; + element["id"] = 9999; + + LLSD& icon_column = element["columns"][0]; + icon_column["column"] = "icon_enabled"; + icon_column["type"] = "icon"; + icon_column["value"] = ""; + + LLSD& rule_column = element["columns"][1]; + rule_column["column"] = "RuleName"; + rule_column["value"] = "A Cool Rule #1"; +// rule_column["color"] = gColors.getColor("DefaultListText").getValue(); + + LLSD& direction_column = element["columns"][2]; + direction_column["column"] = "direction"; + direction_column["value"] = "Out"; +// direction_column["color"] = gColors.getColor("DefaultListText").getValue(); + + LLScrollListItem* scroll_itemp = scrollp->addElement(element); + BOOL rule_enabled = true; + + if(rule_enabled) + { + LLScrollListIcon* icon = (LLScrollListIcon*)scroll_itemp->getColumn(0); + icon->setValue("account_id_green.tga"); +// icon->setClickCallback(NULL, NULL); + } + else + { + LLScrollListIcon* icon = (LLScrollListIcon*)scroll_itemp->getColumn(0); + icon->setValue("account_id_green.tga"); +// icon->setClickCallback(NULL, NULL); + } + + if(selected_id.notNull()) scrollp->selectByID(selected_id); + if(scroll_pos < scrollp->getItemCount()) scrollp->setScrollPos(scroll_pos); + + LLComboBox* messageChooser = getChild<LLComboBox>("message_type"); + + LLSD& messages = LLMessageConfigFile::instance().mMessages; + + LLSD::map_iterator map_end = messages.endMap(); + for(LLSD::map_iterator map_iter = messages.beginMap() ; map_iter != map_end; ++map_iter) + { + std::string key((*map_iter).first); + messageChooser->add(key, ADD_BOTTOM, true); + } +} + +void LLFloaterMessageRewriter::onClickSaveRules() +{ + // *TODO: +} + +void LLFloaterMessageRewriter::onClickNewRule() +{ + // *TODO: +} diff --git a/indra/newview/llfloatermessagerewriter.h b/indra/newview/llfloatermessagerewriter.h new file mode 100644 index 0000000000000000000000000000000000000000..b7becf21adbbed21a27b6fe538fbe5e414cee5b1 --- /dev/null +++ b/indra/newview/llfloatermessagerewriter.h @@ -0,0 +1,33 @@ +/** + * @file llfloatermessagerewriter.h + * + * $LicenseInfo:firstyear=2015&license=viewerlgpl$ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * $/LicenseInfo$ + */ + +#include "llfloater.h" +#include "lltemplatemessagereader.h" +#include "llmessageconfig.h" + +class LLFloaterMessageRewriter : public LLFloater +{ +public: + LLFloaterMessageRewriter(const LLSD& key); + ~LLFloaterMessageRewriter() {} + BOOL postBuild() override; + +private: + void onClickSaveRules(); + void onClickNewRule(); + void refreshRuleList(); +}; diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp index d870da54afc3abc271222f0200f87b080dc6fa1f..f1de2168ab453ce436dc264b0b4be6a3c8ec42e0 100755 --- a/indra/newview/llviewerfloaterreg.cpp +++ b/indra/newview/llviewerfloaterreg.cpp @@ -89,6 +89,9 @@ #include "llfloatermarketplacelistings.h" #include "llfloatermediasettings.h" #include "llfloatermemleak.h" +#include "llfloatermessagelog.h" +#include "llfloatermessagebuilder.h" +#include "llfloatermessagerewriter.h" #include "llfloatermodelpreview.h" #include "llfloaternamedesc.h" #include "llfloaternotificationsconsole.h" @@ -269,7 +272,10 @@ void LLViewerFloaterReg::registerFloaters() LLFloaterReg::add("media_settings", "floater_media_settings.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterMediaSettings>); LLFloaterReg::add("marketplace_listings", "floater_marketplace_listings.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterMarketplaceListings>); LLFloaterReg::add("marketplace_validation", "floater_marketplace_validation.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterMarketplaceValidation>); + LLFloaterReg::add("message_builder", "floater_message_builder.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterMessageBuilder>); LLFloaterReg::add("message_critical", "floater_critical.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterTOS>); + LLFloaterReg::add("message_log", "floater_message_log.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterMessageLog>); + LLFloaterReg::add("message_rewriter", "floater_message_rewriter.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterMessageRewriter>); LLFloaterReg::add("message_tos", "floater_tos.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterTOS>); LLFloaterReg::add("moveview", "floater_moveview.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterMove>); LLFloaterReg::add("music_ticker", "floater_music_ticker.xml", (LLFloaterBuildFunc) &LLFloaterReg::build<LLFloater>); diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp index b38302393de6d01b52436b1db88e5c4e7e1ea406..1ce31e990f7e97e9b345f1788a8427a2c9050ebb 100755 --- a/indra/newview/llviewerregion.cpp +++ b/indra/newview/llviewerregion.cpp @@ -2977,7 +2977,7 @@ public: { LL_WARNS("AppInit", "SimulatorFeatures") << dumpResponse() << LL_ENDL; retry(); - } + } /* virtual */ void httpSuccess() { @@ -3016,6 +3016,31 @@ public: void LLViewerRegion::setCapability(const std::string& name, const std::string& url) { + bool add_to_mapping = true; + + std::string base_url = get_base_cap_url(url); + //we need a multimap, since CERTAIN PEOPLE use non-unique URIs for each Cap. + //let's check if this cap name is already registered for this URI + //TODO: Better represented as map of sets + if(mCapURLMappings.count(base_url) > 0) + { + boost::unordered_multimap<std::string, std::string>::iterator iter = mCapURLMappings.find(base_url); + boost::unordered_multimap<std::string, std::string>::const_iterator end = mCapURLMappings.end(); + + while(iter != end) + { + if(iter->second == name) + { + add_to_mapping = false; + break; + } + ++iter; + } + } + + if(add_to_mapping) + mCapURLMappings.insert(std::pair<std::string, std::string>(base_url, name)); + if(name == "EventQueueGet") { delete mImpl->mEventPoll; @@ -3092,6 +3117,43 @@ bool LLViewerRegion::isCapabilityAvailable(const std::string& name) const return true; } +std::set<std::string> LLViewerRegion::getCapURLNames(const std::string &cap_url) +{ + std::set<std::string> url_capnames; + if(mCapURLMappings.count(cap_url) > 0) + { + url_mapping_t::iterator iter; + + std::pair<url_mapping_t::iterator, + url_mapping_t::iterator> range; + + range = mCapURLMappings.equal_range(cap_url); + + for (iter=range.first; iter != range.second; ++iter) + { + url_capnames.insert(iter->second); + } + } + + return url_capnames; +} + + +bool LLViewerRegion::isCapURLMapped(const std::string &cap_url) +{ + return (mCapURLMappings.count(cap_url) > 0); +} + +std::set<std::string> LLViewerRegion::getAllCaps() +{ + std::set<std::string> url_capnames; + for(CapabilityMap::iterator iter=mImpl->mCapabilities.begin(); iter!=mImpl->mCapabilities.end(); ++iter) + { + url_capnames.insert(iter->first); + } + return url_capnames; +} + bool LLViewerRegion::capabilitiesReceived() const { return mCapabilitiesReceived; diff --git a/indra/newview/llviewerregion.h b/indra/newview/llviewerregion.h index a84b6f90c8b4edd64d0b40ce6ecff99d3f4f8741..c04b50f4c38bb9cb7b69a641eb68290570b974e5 100755 --- a/indra/newview/llviewerregion.h +++ b/indra/newview/llviewerregion.h @@ -31,6 +31,7 @@ // that are in to a particular region. #include <string> #include <boost/signals2.hpp> +#include <boost/unordered_map.hpp> #include "llwind.h" #include "v3dmath.h" @@ -42,6 +43,7 @@ #include "m4math.h" // LLMatrix4 #include "llhttpclient.h" #include "llframetimer.h" +#include "lleasymessagesender.h" // Surface id's #define LAND 1 @@ -262,6 +264,10 @@ public: // implements LLCapabilityProvider virtual std::string getCapability(const std::string& name) const; + virtual std::set<std::string> getCapURLNames(const std::string& cap_url); + virtual bool isCapURLMapped(const std::string& cap_url); + virtual std::set<std::string> getAllCaps(); + // has region received its final (not seed) capability list? bool capabilitiesReceived() const; void setCapabilitiesReceived(bool received); @@ -579,6 +585,10 @@ private: mutable tex_matrix_t mWorldMapTiles; std::set<std::string> mGodNames; + + LLEasyMessageSender mMessageSender; + typedef boost::unordered_multimap<std::string, std::string> url_mapping_t; + url_mapping_t mCapURLMappings; }; inline BOOL LLViewerRegion::getRegionProtocol(U64 protocol) const diff --git a/indra/newview/llworld.cpp b/indra/newview/llworld.cpp index 898612758e159986e5060ef02d5e4919cd91e99f..842b4aa9ff38202d4a42a63f15cc8be8d3b31d5a 100755 --- a/indra/newview/llworld.cpp +++ b/indra/newview/llworld.cpp @@ -1311,6 +1311,37 @@ void send_agent_pause() LLViewerStats::instance().getRecording().stop(); } +CapUrlMatches LLWorld::getCapURLMatches(const std::string &cap_url) +{ + std::set<std::string> url_capnames; + std::set<LLViewerRegion*> url_capregions; + + for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); + iter != LLWorld::getInstance()->getRegionList().end(); ++iter) + { + LLViewerRegion* regionp = *iter; + std::set<std::string> new_url_capnames = regionp->getCapURLNames(cap_url); + + if(new_url_capnames.size() > 0) + { + url_capregions.insert(regionp); + url_capnames.insert(new_url_capnames.begin(), new_url_capnames.end()); + } + } + + return CapUrlMatches(url_capregions, url_capnames); +} + + +bool LLWorld::isCapURLMapped(const std::string &cap_url) +{ + for (LLViewerRegion* regionp : LLWorld::getInstance()->getRegionList()) + { + if(regionp->isCapURLMapped(cap_url)) + return true; + } + return false; +} void send_agent_resume() { diff --git a/indra/newview/llworld.h b/indra/newview/llworld.h index d0e19d0c6059d620e338c7bed99906fedfca83cd..9b961a5984bc1baf287951ff1188c611b8ef2147 100755 --- a/indra/newview/llworld.h +++ b/indra/newview/llworld.h @@ -55,6 +55,19 @@ class LLCloudPuff; class LLCloudGroup; class LLVOAvatar; +class CapUrlMatches +{ +public: + CapUrlMatches(std::set<LLViewerRegion*>& regions, std::set<std::string>& cap_names) + { + mRegions = regions; + mCapNames = cap_names; + } + + std::set<LLViewerRegion*> mRegions; + std::set<std::string> mCapNames; +}; + // LLWorld maintains a stack of unused viewer_regions and an array of pointers to viewer regions // as simulators are connected to, viewer_regions are popped off the stack and connected as required // as simulators are removed, they are pushed back onto the stack @@ -155,7 +168,10 @@ public: void clearAllVisibleObjects(); void refreshLimits(); - + + virtual CapUrlMatches getCapURLMatches(const std::string& cap_url); + virtual bool isCapURLMapped(const std::string& cap_url); + public: typedef std::list<LLViewerRegion*> region_list_t; const region_list_t& getRegionList() const { return mActiveRegionList; } diff --git a/indra/newview/skins/default/xui/en/floater_message_builder.xml b/indra/newview/skins/default/xui/en/floater_message_builder.xml new file mode 100644 index 0000000000000000000000000000000000000000..461d6c92bbf9304de972b7e25a1aa5fa4e078675 --- /dev/null +++ b/indra/newview/skins/default/xui/en/floater_message_builder.xml @@ -0,0 +1,66 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<floater + single_instance="true" + name="Message Builder" + title="MESSAGE BUILDER" + width="400" + min_width="400" + height="600" + min_height="600" + can_close="true" + can_resize="true" + can_minimize="true" + save_rect="true"> + <scroll_list + column_padding="0" + draw_heading="false" + follows="left|top|right" + left="10" + top="25" + name="net_list" + search_column="0" + right="-10" + bottom="140"> + <scroll_list.columns dynamicwidth="true" name="text" label="text" /> + <scroll_list.columns name="state" label="state" width="35" /> + </scroll_list> + <combo_box + name="untrusted_message_combo" + allow_text_entry="false" + follows="left|top" + left="10" + top="150" + right="190" + bottom="170" + tool_tip="No trust"> + </combo_box> + <combo_box + name="trusted_message_combo" + allow_text_entry="false" + follows="left|top" + left="200" + top="150" + right="380" + bottom="170" + tool_tip="Trust"> + </combo_box> + <text_editor + parse_urls="false" + name="message_edit" + follows="left|top|right|bottom" + left="10" + top="180" + bottom="560" + right="390" + max_length="65535"> + </text_editor> + <button + name="send_btn" + follows="right|bottom" + left="310" + top="570" + right="390" + bottom="590" + label="Send" + tool_tip="Send (Ctrl+Enter)"/> +</floater> diff --git a/indra/newview/skins/default/xui/en/floater_message_log.xml b/indra/newview/skins/default/xui/en/floater_message_log.xml new file mode 100644 index 0000000000000000000000000000000000000000..c54f259e5329698d3beef922b13e440cb9777e73 --- /dev/null +++ b/indra/newview/skins/default/xui/en/floater_message_log.xml @@ -0,0 +1,210 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<floater + single_instance="true" + name="Message Log" + title="MESSAGE LOG" + width="400" + min_width="400" + height="600" + min_height="200" + can_close="true" + can_resize="true" + can_minimize="true" + save_rect="true"> + <layout_stack + clip="false" + follows="all" + layout="topleft" + left="2" + right="-2" + top="16" + height="550" + mouse_opaque="false" + name="a_stack" + orientation="vertical" + drag_handle_gap="6" + drag_handle_first_indent="10" + drag_handle_second_indent="10" + show_drag_handle="true" > + <layout_panel + follows="all" + auto_resize="true" + user_resize="true" + layout="topleft" + left="1" + right="-1" + min_height="20" + height="100" + name="net_panel" > + <scroll_list + column_padding="0" + draw_heading="false" + follows="all" + left="10" + top="4" + name="net_list" + search_column="0" + right="-10" + height="100"> + <scroll_list.columns dynamicwidth="true" name="text" label="text" /> + <scroll_list.columns name="icon0" label="icon0" width="12" /> + <scroll_list.columns name="icon1" label="icon1" width="12" /> + <scroll_list.columns name="icon2" label="icon2" width="12" /> + </scroll_list> + </layout_panel> + <layout_panel + follows="all" + auto_resize="true" + user_resize="true" + layout="topleft" + min_height="98" + height="226" + top="100" + name="message_panel"> + <menu_button + follows="left|top" + left="10" + top="4" + height="20" + width="20" + image_hover_unselected="Toolbar_Middle_Over" + image_overlay="OptionsMenu_Off" + image_selected="Toolbar_Middle_Selected" + image_unselected="Toolbar_Middle_Off" + menu_filename="menu_filter_choice.xml" + menu_position="bottomleft" + tool_tip="Filter selection" + layout="topleft" + name="filter_choice_btn" /> + <line_editor + name="filter_edit" + follows="left|top|right" + left_pad="2" + top_delta="0" + right="340" + height="20" + max_length="65535" /> + <button + name="filter_apply_btn" + follows="top|right" + left_pad="2" + top_delta="0" + width="20" + height="20" + label="✔"/> + <button + name="clear_log_btn" + follows="top|right" + left_pad="2" + top_delta="0" + width="20" + height="20" + label="C"/> + <scroll_list + column_padding="0" + draw_heading="true" + follows="all" + left="10" + right="-10" + top_pad="4" + height="180" + name="message_log" + search_column="0" + sort_column="-1" + bottom="-1"> + <scroll_list.columns name="sequence" label="Seq" width="48"/> + <scroll_list.columns name="type" label="Method" width="32"/> + <scroll_list.columns name="direction" label="Dir" width="32"/> + <scroll_list.columns name="net" label="Host" width="100"/> + <scroll_list.columns name="name" label="Type" width="128"/> + <scroll_list.columns name="summary" label="Summary" dynamicwidth="true"/> + </scroll_list> + <text + name="log_status_text" + follows="bottom|left|right" + left="10" + top_pad="2" + right="-10" + height="20"> + </text> + </layout_panel> + <layout_panel + follows="all" + auto_resize="true" + layout="topleft" + min_height="98" + height="246" + top_pad="1" + name="net_info_panel"> + <!-- Only visible in IPANE_NET / IPANE_TEMPLATE_LOG modes --> + <text_editor + parse_urls="true" + name="net_info" + follows="all" + left="10" + top="0" + bottom="-1" + right="-10" + enabled="false" + visible="true"> + </text_editor> + <!-- Only visible in IPANE_HTTP_LOG mode --> + <layout_stack + name="conv_stack" + follows="all" + left="10" + right="-10" + top="0" + bottom="-1"> + <layout_panel + follows="all"> + <text_editor + parse_urls="true" + name="conv_request" + follows="all" + enabled="false" + visible="true" + max_length="65535" /> + </layout_panel> + <layout_panel + follows="all"> + <text_editor + parse_urls="true" + name="conv_response" + follows="all" + enabled="false" + visible="true" + max_length="65535" /> + </layout_panel> + </layout_stack> + </layout_panel> + </layout_stack> + <check_box + name="wrap_net_info" + follows="left|bottom" + left="10" + top="570" + bottom="590" + label="Wrap Info Text" + enabled="true" + initial_value="true"/> + <!-- Format the message body to be more readable, but not exactly what was sent? --> + <check_box + name="beautify_messages" + follows="left|bottom" + left="115" + top="570" + bottom="590" + label="Beautify Messages" + enabled="true" + initial_value="false"/> + <button + name="msg_builder_send_btn" + follows="right|bottom" + left="245" + top="570" + right="390" + bottom="590" + label="Send to Message Builder" + enabled="false"/> +</floater> diff --git a/indra/newview/skins/default/xui/en/floater_message_rewriter.xml b/indra/newview/skins/default/xui/en/floater_message_rewriter.xml new file mode 100644 index 0000000000000000000000000000000000000000..28cf12dc639210982aabd32ee91d22303cdbba15 --- /dev/null +++ b/indra/newview/skins/default/xui/en/floater_message_rewriter.xml @@ -0,0 +1,67 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<floater + single_instance="true" + name="message_rewriter" + title="MESSAGE REWRITER" + width="400" + min_width="400" + height="500" + min_height="500" + can_close="true" + can_resize="true" + can_minimize="true" + save_rect="true"> + <scroll_list + column_padding="0" + draw_heading="true" + follows="left|top|right" + left="4" + top="4" + right="-4" + bottom="350" + name="rule_list" + search_column="0"> + <column name="icon_enabled" label="Enabled" width="24" /> + <column dynamicwidth="true" name="RuleName" label="Rule Name" /> + <column name="direction" label="In / Out" width="64" /> + </scroll_list> + <text + bg_visible="false" + top_pad="8" + follows="left|top" + font="SansSerifSmall" + height="14" + left="4" + name="message_type_lable" + width="80"> + Message Type: + </text> + <combo_box + allow_text_entry="false" + top_delta="-2" + follows="left|top" + height="20" + left_pad="0" + max_chars="100" + mouse_opaque="true" + name="message_type" + width="150" /> + <text_editor + name="rule_editor" + follows="left|top|right|bottom" + left="4" + top_pad="4" + height="92" + right="-4" + enabled="true" + max_length="65535" /> + <button + name="save_rules" + follows="right|bottom" + width="120" + height="20" + right="-4" + bottom="-4" + label="Save Rules" + enabled="true"/> +</floater> diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml index 06f8848403722e987729b42c2cf56582c8aba948..4296ca1ba7a60804bac3b973cef912acb21adf74 100755 --- a/indra/newview/skins/default/xui/en/menu_viewer.xml +++ b/indra/newview/skins/default/xui/en/menu_viewer.xml @@ -3167,6 +3167,39 @@ <menu_item_separator/> + <menu_item_check + label="Message Log..." + name="Message Log"> + <menu_item_check.on_check + function="Floater.Visible" + parameter="message_log" /> + <menu_item_check.on_click + function="Floater.Toggle" + parameter="message_log" /> + </menu_item_check> + <menu_item_check + label="Message Builder..." + name="Message Builder"> + <menu_item_check.on_check + function="Floater.Visible" + parameter="message_builder" /> + <menu_item_check.on_click + function="Floater.Toggle" + parameter="message_builder" /> + </menu_item_check> + <menu_item_check + label="Message Rewriter..." + name="Message Rewriter"> + <menu_item_check.on_check + function="Floater.Visible" + parameter="message_rewriter" /> + <menu_item_check.on_click + function="Floater.Toggle" + parameter="message_rewriter" /> + </menu_item_check> + + <menu_item_separator/> + <menu_item_call label="Enable Message Log" name="Enable Message Log">