diff --git a/.gitignore b/.gitignore index fcef601..467569f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ tmp -*.creator.user \ No newline at end of file +*.creator.user* \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 020b552..c7fa661 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,11 @@ project (ngrest) include(CheckCXXCompilerFlag) +check_cxx_compiler_flag(-std=gnu++11 HAS_CXX11) +if (HAS_CXX11) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11") +endif() + check_cxx_compiler_flag(-Wl,--no-undefined HAS_NO_UNDEFINED) if (HAS_NO_UNDEFINED) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--no-undefined") @@ -10,9 +15,17 @@ endif() check_cxx_compiler_flag(-pedantic HAS_PEDANTIC) if (HAS_PEDANTIC) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,--no-undefined") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pedantic") endif() +check_cxx_compiler_flag(-Wall HAS_WALL) +if (HAS_WALL) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") +endif() + +if (CMAKE_BUILD_TYPE MATCHES "DEBUG") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DDEBUG") +endif() set(PROJECT_DEPLOY_DIR ${PROJECT_BINARY_DIR}/deploy) set(PROJECT_INCLUDE_DIR ${PROJECT_DEPLOY_DIR}/include) @@ -23,5 +36,6 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_DEPLOY_DIR}/bin) include_directories("${PROJECT_INCLUDE_DIR}") add_subdirectory(core) +add_subdirectory(tools) add_subdirectory(tests) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index d0354b2..26a2e25 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -4,6 +4,7 @@ project (core) add_subdirectory(utils) add_subdirectory(common) add_subdirectory(json) +add_subdirectory(xml) add_subdirectory(engine) add_subdirectory(server) #add_subdirectory(transport) diff --git a/core/common/CMakeLists.txt b/core/common/CMakeLists.txt index 7d6f12c..f37179e 100644 --- a/core/common/CMakeLists.txt +++ b/core/common/CMakeLists.txt @@ -11,5 +11,3 @@ file(COPY ${NGRESTCOMMON_HEADERS} DESTINATION ${PROJECT_INCLUDE_DIR}/ngrest/comm add_library(ngrestcommon SHARED ${NGRESTCOMMON_SOURCES}) target_link_libraries(ngrestcommon ngrestutils) - -target_compile_features(ngrestcommon PRIVATE cxx_range_for) diff --git a/core/common/src/Message.h b/core/common/src/Message.h index eab7a4f..932f766 100644 --- a/core/common/src/Message.h +++ b/core/common/src/Message.h @@ -46,8 +46,6 @@ struct Response Node* node; -// char* body = nullptr; -// uint64_t bodySize = 0; MemPool poolBody; }; diff --git a/core/engine/CMakeLists.txt b/core/engine/CMakeLists.txt index ec8a48e..34bef9b 100644 --- a/core/engine/CMakeLists.txt +++ b/core/engine/CMakeLists.txt @@ -11,5 +11,3 @@ file(COPY ${NGRESTENGINE_HEADERS} DESTINATION ${PROJECT_INCLUDE_DIR}/ngrest/engi add_library(ngrestengine SHARED ${NGRESTENGINE_SOURCES}) target_link_libraries(ngrestengine ngrestutils ngrestcommon ngrestjson) - -target_compile_features(ngrestengine PRIVATE cxx_range_for) diff --git a/core/engine/src/Deployment.cpp b/core/engine/src/Deployment.cpp index 1ffae66..ef4dd59 100644 --- a/core/engine/src/Deployment.cpp +++ b/core/engine/src/Deployment.cpp @@ -1,75 +1,32 @@ -#include #include -#include +#include #include -#include -#include -#include -#include +#include +#include -#include "ServiceDescription.h" +#include "ServiceGroup.h" #include "ServiceWrapper.h" -#include "Transport.h" #include "Deployment.h" +#include "ServiceDispatcher.h" namespace ngrest { -struct Parameter +class Deployment::Impl { - std::string name; - std::string divider; -}; - -struct Resource -{ - std::vector parameters; - const OperationDescription* operation = nullptr; -}; +public: + std::unordered_map> serviceLibs; + ServiceDispatcher& dispatcher; -struct DeployedService -{ - ServiceWrapper* wrapper = nullptr; - std::unordered_multimap staticResources; - std::unordered_multimap paramResources; -}; - -struct Deployment::Impl -{ - std::unordered_map deployedServices; - - // location may be "add?a={a}&b={b}" or "get/{id}" or "echo" - void parseResource(const std::string& location, Resource& res, std::string& baseLocation) + Impl(ServiceDispatcher& dispatcher_): + dispatcher(dispatcher_) { - std::string::size_type begin = 0; - std::string::size_type end = 0; - Parameter parameter; - for (;;) { - begin = location.find('{', end); - if (begin == std::string::npos) - break; - - if (end) - parameter.divider = location.substr(end, begin - end); - - end = location.find('}', begin + 1); - NGREST_ASSERT(end != std::string::npos, - "'}' expected while parsing resource parameter: " + location); - parameter.name = location.substr(begin + 1, end - begin - 1); - - if (res.parameters.empty()) - baseLocation = location.substr(0, begin); // "add?a=" or "get/" - res.parameters.push_back(parameter); - ++end; - } - if (baseLocation.empty()) - baseLocation = location; } -}; +}; -Deployment::Deployment(): - impl(new Impl()) +Deployment::Deployment(ServiceDispatcher& dispatcher): + impl(new Impl(dispatcher)) { } @@ -78,228 +35,104 @@ Deployment::~Deployment() delete impl; } -void Deployment::registerService(ServiceWrapper* wrapper) +void Deployment::deployAll() { - NGREST_ASSERT_PARAM(wrapper); - - const ServiceDescription* serviceDescr = wrapper->description(); - NGREST_ASSERT(serviceDescr, "Service description is not set"); - - const std::string& serviceName = serviceDescr->name; - NGREST_ASSERT(!serviceName.empty(), "Service name cannot be empty"); - - LogDebug() << "Registering service " << serviceName; - - // use service name as location in case of service location is not set - const std::string& serviceLocation = serviceDescr->location.empty() - ? serviceName : serviceDescr->location; - // validate service location - NGREST_ASSERT(serviceLocation.find_first_of("{} \n\r\t?%&=\'\"") == std::string::npos, - "Service location is not valid"); - - // test if service already registered - DeployedService& deployedService = impl->deployedServices[serviceLocation]; - NGREST_ASSERT(!deployedService.wrapper, "Service " + serviceName + " is already registered"); - - // parse operations locations - for (auto it = serviceDescr->operations.begin(); it != serviceDescr->operations.end(); ++it) { - const OperationDescription& operationDescr = *it; - - NGREST_ASSERT(!operationDescr.name.empty(), "Operation name cannot be empty. " + serviceName); - - const std::string& operationLocation = operationDescr.location.empty() - ? operationDescr.name : operationDescr.location; - - // validate operation location - NGREST_ASSERT(operationLocation.find_first_of(" \n\r\t") == std::string::npos, - "Operation location is not valid: " + serviceName + ": " + operationLocation); - - LogDebug() << "Registering resource: " <parseResource(operationLocation, resource, baseLocation); - resource.operation = &operationDescr; // it's ok, because ServiceDescription stored statically - NGREST_ASSERT(!baseLocation.empty(), "BaseLocation is empty!"); // should never happen - - // check for path collision in resources - auto itExisting = deployedService.paramResources.find(baseLocation); - NGREST_ASSERT(itExisting == deployedService.paramResources.end() || - itExisting->second.operation->method != operationDescr.method, - "Static path " + baseLocation + " is already taken by " - + serviceName + "/" + itExisting->second.operation->name); - - itExisting = deployedService.staticResources.find(baseLocation); - NGREST_ASSERT(itExisting == deployedService.staticResources.end() || - itExisting->second.operation->method != operationDescr.method, - "Static path " + baseLocation + " is already taken by " - + serviceName + "/" + itExisting->second.operation->name); - + // find service libraries + StringList libs; + const std::string& servicesPath = "services"; + File(servicesPath).list(libs, "*" NGREST_LIBRARY_EXT, File::AttributeRegularFile); + + if (servicesPath.empty()) { + LogError() << "No services found"; + return; + } - if (resource.parameters.empty()) { - // static resource which don't have parameters - deployedService.staticResources.insert({{baseLocation, resource}}); - } else { - // parametrized resource - deployedService.paramResources.insert({{baseLocation, resource}}); + for (StringList::const_iterator it = libs.begin(); it != libs.end(); ++it) { + const std::string& servicePath = servicesPath + *it; + try { + deploy(servicePath); + } catch (const Exception& exception) { + LogWarning() << "Can't load service: " << servicePath << ": " << exception.what(); + } catch (...) { + LogWarning() << "Can't load service: " << servicePath << ": unknown error"; } } - - deployedService.wrapper = wrapper; - LogDebug() << "Service " << serviceName << " has been registered"; } -void Deployment::unregisterService(ServiceWrapper* wrapper) +void Deployment::deploy(const std::string& servicePath) { - NGREST_ASSERT_PARAM(wrapper); - - const ServiceDescription* serviceDescr = wrapper->description(); - NGREST_ASSERT(serviceDescr, "Service description is not set"); + // load service library + LogDebug() << "Deploying service library: " << servicePath << "..."; - const std::string& serviceName = serviceDescr->name; - NGREST_ASSERT(!serviceName.empty(), "Service name cannot be empty"); + Plugin serviceLib; + serviceLib.load(servicePath); - LogDebug() << "Unregistering service " << serviceName; + ServiceGroup* serviceGroup = serviceLib.getPluginSymbol(); + NGREST_ASSERT_NULL(serviceGroup); // should never happen - // use service name as location in case of service location is not set - const std::string& serviceLocation = serviceDescr->location.empty() ? - serviceName : serviceDescr->location; - // validate service location - NGREST_ASSERT(serviceLocation.find_first_of("{} \n\r\t?%&=\'\"") == std::string::npos, - "Service location is not valid"); + deployStatic(serviceGroup); - auto count = impl->deployedServices.erase(serviceLocation); - NGREST_ASSERT(count, "Service " + wrapper->description()->name + " is not registered"); - - LogDebug() << "Service " << serviceName << " has been unregistered"; + impl->serviceLibs[servicePath] = serviceLib; } -void Deployment::dispatchMessage(MessageContext* context) +void Deployment::undeploy(const std::string& servicePath) { - const std::string& path = context->request->path; - - LogDebug() << "Dispatching message " << path; + auto existing = impl->serviceLibs.find(servicePath); + NGREST_ASSERT(existing != impl->serviceLibs.end(), "Cannot undeploy service lib [" + + servicePath + "]: not deployed"); - // parse service location - std::string::size_type begin = path.find_first_not_of('/'); - NGREST_ASSERT(begin != std::string::npos, "Canot get start of service location in path: " + path); - std::string::size_type end = path.find('/', begin); - NGREST_ASSERT(end != std::string::npos, "Canot get end of service location in path: " + path); + ServiceGroup* serviceGroup = existing->second.getPluginSymbol(); + NGREST_ASSERT_NULL(serviceGroup); // should never happen - // validate service location - const std::string& serviceLocation = path.substr(begin, end - begin); - NGREST_ASSERT(serviceLocation.empty() || - serviceLocation.find_first_of("{} \n\r\t?%&=\'\"") == std::string::npos, - "Service location is not valid: " + serviceLocation); + undeployStatic(serviceGroup); - // parse operation location - begin = path.find_first_not_of('/', end); - NGREST_ASSERT(begin != std::string::npos, "Canot get start of service location in URL: " + path); - end = path.find('/', begin); - const std::string& opLocation = path.substr(begin, (end == std::string::npos) ? end : (end - begin)); - - NGREST_ASSERT(opLocation.empty() || opLocation.find_first_of(" \n\r\t") == std::string::npos, - "Operation location is not valid: " + opLocation); - - - auto itService = impl->deployedServices.find(serviceLocation); - NGREST_ASSERT(itService != impl->deployedServices.end(), - "Can't find service by path: " + path); + impl->serviceLibs.erase(existing); +} - DeployedService& service = itService->second; +void Deployment::deployStatic(ServiceGroup* serviceGroup) +{ + LogDebug() << "Deploying service group name: " << serviceGroup->getName(); - int method = context->transport->getRequestMethod(context->request); + const std::vector& serviceWrappers = serviceGroup->getServices(); - // first look path in static resources - auto itOpLocation = service.staticResources.equal_range(opLocation); - for (auto it = itOpLocation.first; it != itOpLocation.second; ++it) { - const Resource& resource = it->second; - if (resource.operation->method == method) { - // found it! - service.wrapper->invoke(resource.operation, context); - return; - } + if (serviceWrappers.empty()) { + LogError() << "Failed to deploy [" << serviceGroup->getName() << "]. No services."; + return; } - - // search for suitable parametrized resource - Resource* resource = nullptr; - std::string::size_type matchLength = 0; - - for (auto it = service.paramResources.begin(); it != service.paramResources.end(); ++it) { - const std::string& basePath = it->first; - const std::string::size_type basePathSize = basePath.size(); - if (!path.compare(begin, basePathSize, basePath) && (matchLength < basePathSize)) { - Resource& currResource = it->second; - if (currResource.operation->method == method) { - matchLength = basePathSize; - resource = &currResource; - } + auto it = serviceWrappers.begin(); + try { + for (; it != serviceWrappers.end(); ++it) + impl->dispatcher.registerService(*it); + } catch (...) { + // unregister services in case of error + for (; it != serviceWrappers.begin(); --it) { + try { + impl->dispatcher.unregisterService(*it); + } NGREST_CATCH_ALL; } - } - - NGREST_ASSERT(resource, "Resource not found for path: " + path); - NGREST_ASSERT(!context->request->node, "Request must have just one of query or body, but have both"); - - // generate OM from request - // path = "/calc/add?a={a}&b={b}" - // path = "/calc/add?a=1111111&b=2" - // ^ matchLength + LogError() << "Failed to deploy [" << serviceGroup->getName() << "]."; + throw; + } +} - begin += matchLength; - - const char* pathCStr = path.c_str(); - Object* requestNode = context->pool.alloc(); - NamedNode* lastNamedNode = nullptr; - - for (int i = 0, l = resource->parameters.size(); i < l; ++i) { - const Parameter& parameter = resource->parameters[i]; - - if ((i + 1) < l) { - const Parameter& nextParameter = resource->parameters[i]; - const std::string& nextDivider = nextParameter.divider; - end = path.find(nextDivider, begin); - NGREST_ASSERT(end != std::string::npos, - "Can't find divider [" + nextDivider + "] for path: " + path); - } else { - end = path.size(); - } - - const char* name = context->pool.putCString(parameter.name.c_str(), true); - const char* value = context->pool.putCString(pathCStr + begin, end - begin, true); - - NamedNode* namedNode = context->pool.alloc(); - namedNode->name = name; +void Deployment::undeployStatic(ServiceGroup* serviceGroup) +{ + LogDebug() << "Undeploying service group name: " << serviceGroup->getName(); - if (lastNamedNode) { - lastNamedNode->nextSibling = namedNode; - } else { - requestNode->firstChild = namedNode; - } - lastNamedNode = namedNode; + const std::vector& serviceWrappers = serviceGroup->getServices(); - Value* valueNode = context->pool.alloc(ValueType::String); - valueNode->value = value; - namedNode->node = valueNode; + if (serviceWrappers.empty()) { + LogError() << "Failed to deploy [" << serviceGroup->getName() << "]. No services."; + return; } -//#ifdef DEBUG - json::JsonWriter::write(requestNode, context->response->poolBody); - LogDebug() << "Generated request:\n---------------------\n" - << context->response->poolBody.flatten()->buffer - << "\n---------------------\n"; - context->response->poolBody.reset(); -//#endif - - context->request->node = requestNode; - - LogDebug() << "Invoking service operation " << service.wrapper->description()->name - << "/" << resource->operation->name; - service.wrapper->invoke(resource->operation, context); + for (auto it = serviceWrappers.begin(); it != serviceWrappers.end(); ++it) { + try { + impl->dispatcher.unregisterService(*it); + } NGREST_CATCH_ALL; + } } - } // namespace ngrest - diff --git a/core/engine/src/Deployment.h b/core/engine/src/Deployment.h index 44bbbbc..4d22e3a 100644 --- a/core/engine/src/Deployment.h +++ b/core/engine/src/Deployment.h @@ -1,26 +1,30 @@ #ifndef NGREST_DEPLOYMENT_H #define NGREST_DEPLOYMENT_H +#include + namespace ngrest { -class ServiceWrapper; -class Node; -class MessageContext; +class ServiceDispatcher; +class ServiceGroup; class Deployment { public: - Deployment(); + Deployment(ServiceDispatcher& dispatcher); ~Deployment(); - void registerService(ServiceWrapper* wrapper); - void unregisterService(ServiceWrapper* wrapper); + void deployAll(); + + void deploy(const std::string& servicePath); + void undeploy(const std::string& servicePath); - void dispatchMessage(MessageContext* context); + void deployStatic(ServiceGroup* serviceGroup); + void undeployStatic(ServiceGroup* serviceGroup); private: - struct Impl; - Impl* const impl; + class Impl; + Impl* impl; }; } // namespace ngrest diff --git a/core/engine/src/Engine.cpp b/core/engine/src/Engine.cpp index c08fc50..3e63b30 100644 --- a/core/engine/src/Engine.cpp +++ b/core/engine/src/Engine.cpp @@ -2,7 +2,7 @@ #include #include -#include "Deployment.h" +#include "ServiceDispatcher.h" #include "Transport.h" #include "Engine.h" @@ -36,8 +36,8 @@ class EngineHookCallback: public MessageCallback }; -Engine::Engine(Deployment& deployment_): - deployment(deployment_) +Engine::Engine(ServiceDispatcher& dispatcher_): + dispatcher(dispatcher_) { } @@ -57,7 +57,7 @@ void Engine::dispatchMessage(MessageContext* context) NGREST_ASSERT(context->request->node, "Failed to read request"); // should never throw } - deployment.dispatchMessage(context); + dispatcher.dispatchMessage(context); } catch (const Exception& err) { LogWarning() << /*err.getFileLine() << " " << */err.getFunction() << " : " << err.what(); context->callback->error(err); diff --git a/core/engine/src/Engine.h b/core/engine/src/Engine.h index 40471aa..4a38615 100644 --- a/core/engine/src/Engine.h +++ b/core/engine/src/Engine.h @@ -4,17 +4,17 @@ namespace ngrest { struct MessageContext; -class Deployment; +class ServiceDispatcher; class Engine { public: - Engine(Deployment& deployment); + Engine(ServiceDispatcher& dispatcher); void dispatchMessage(MessageContext* context); private: - Deployment& deployment; + ServiceDispatcher& dispatcher; }; } // namespace ngrest diff --git a/core/engine/src/ServiceDispatcher.cpp b/core/engine/src/ServiceDispatcher.cpp new file mode 100644 index 0000000..0a49968 --- /dev/null +++ b/core/engine/src/ServiceDispatcher.cpp @@ -0,0 +1,306 @@ +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "ServiceDescription.h" +#include "ServiceWrapper.h" +#include "Transport.h" +#include "ServiceDispatcher.h" + +namespace ngrest { + +struct Parameter +{ + std::string name; + std::string divider; +}; + +struct Resource +{ + std::vector parameters; + const OperationDescription* operation = nullptr; +}; + +struct DeployedService +{ + ServiceWrapper* wrapper = nullptr; + std::unordered_multimap staticResources; + std::unordered_multimap paramResources; +}; + +struct ServiceDispatcher::Impl +{ + std::unordered_map deployedServices; + + // location may be "add?a={a}&b={b}" or "get/{id}" or "echo" + void parseResource(const std::string& location, Resource& res, std::string& baseLocation) + { + std::string::size_type begin = 0; + std::string::size_type end = 0; + Parameter parameter; + for (;;) { + begin = location.find('{', end); + if (begin == std::string::npos) + break; + + if (end) + parameter.divider = location.substr(end, begin - end); + + end = location.find('}', begin + 1); + NGREST_ASSERT(end != std::string::npos, + "'}' expected while parsing resource parameter: " + location); + parameter.name = location.substr(begin + 1, end - begin - 1); + + if (res.parameters.empty()) + baseLocation = location.substr(0, begin); // "add?a=" or "get/" + res.parameters.push_back(parameter); + ++end; + } + if (baseLocation.empty()) + baseLocation = location; + } +}; + + +ServiceDispatcher::ServiceDispatcher(): + impl(new Impl()) +{ +} + +ServiceDispatcher::~ServiceDispatcher() +{ + delete impl; +} + +void ServiceDispatcher::registerService(ServiceWrapper* wrapper) +{ + NGREST_ASSERT_PARAM(wrapper); + + const ServiceDescription* serviceDescr = wrapper->getDescription(); + NGREST_ASSERT(serviceDescr, "Service description is not set"); + + const std::string& serviceName = serviceDescr->name; + NGREST_ASSERT(!serviceName.empty(), "Service name cannot be empty"); + + LogDebug() << "Registering service " << serviceName; + + // use service name as location in case of service location is not set + const std::string& serviceLocation = serviceDescr->location.empty() + ? serviceName : serviceDescr->location; + // validate service location + NGREST_ASSERT(serviceLocation.find_first_of("{} \n\r\t?%&=\'\"") == std::string::npos, + "Service location is not valid"); + + // test if service already registered + DeployedService& deployedService = impl->deployedServices[serviceLocation]; + NGREST_ASSERT(!deployedService.wrapper, "Service " + serviceName + " is already registered"); + + // parse operations locations + for (auto it = serviceDescr->operations.begin(); it != serviceDescr->operations.end(); ++it) { + const OperationDescription& operationDescr = *it; + + NGREST_ASSERT(!operationDescr.name.empty(), "Operation name cannot be empty. " + serviceName); + + const std::string& operationLocation = operationDescr.location.empty() + ? operationDescr.name : operationDescr.location; + + // validate operation location + NGREST_ASSERT(operationLocation.find_first_of(" \n\r\t") == std::string::npos, + "Operation location is not valid: " + serviceName + ": " + operationLocation); + + LogDebug() << "Registering resource: " <parseResource(operationLocation, resource, baseLocation); + resource.operation = &operationDescr; // it's ok, because ServiceDescription stored statically + NGREST_ASSERT(!baseLocation.empty(), "BaseLocation is empty!"); // should never happen + + // check for path collision in resources + auto itExisting = deployedService.paramResources.find(baseLocation); + NGREST_ASSERT(itExisting == deployedService.paramResources.end() || + itExisting->second.operation->method != operationDescr.method, + "Static path " + baseLocation + " is already taken by " + + serviceName + "/" + itExisting->second.operation->name); + + itExisting = deployedService.staticResources.find(baseLocation); + NGREST_ASSERT(itExisting == deployedService.staticResources.end() || + itExisting->second.operation->method != operationDescr.method, + "Static path " + baseLocation + " is already taken by " + + serviceName + "/" + itExisting->second.operation->name); + + + if (resource.parameters.empty()) { + // static resource which don't have parameters + deployedService.staticResources.insert({{baseLocation, resource}}); + } else { + // parametrized resource + deployedService.paramResources.insert({{baseLocation, resource}}); + } + } + + deployedService.wrapper = wrapper; + LogDebug() << "Service " << serviceName << " has been registered"; +} + +void ServiceDispatcher::unregisterService(ServiceWrapper* wrapper) +{ + NGREST_ASSERT_PARAM(wrapper); + + const ServiceDescription* serviceDescr = wrapper->getDescription(); + NGREST_ASSERT(serviceDescr, "Service description is not set"); + + const std::string& serviceName = serviceDescr->name; + NGREST_ASSERT(!serviceName.empty(), "Service name cannot be empty"); + + LogDebug() << "Unregistering service " << serviceName; + + // use service name as location in case of service location is not set + const std::string& serviceLocation = serviceDescr->location.empty() ? + serviceName : serviceDescr->location; + // validate service location + NGREST_ASSERT(serviceLocation.find_first_of("{} \n\r\t?%&=\'\"") == std::string::npos, + "Service location is not valid"); + + auto count = impl->deployedServices.erase(serviceLocation); + NGREST_ASSERT(count, "Service " + wrapper->getDescription()->name + " is not registered"); + + LogDebug() << "Service " << serviceName << " has been unregistered"; +} + +void ServiceDispatcher::dispatchMessage(MessageContext* context) +{ + const std::string& path = context->request->path; + + LogDebug() << "Dispatching message " << path; + + // parse service location + std::string::size_type begin = path.find_first_not_of('/'); + NGREST_ASSERT(begin != std::string::npos, "Canot get start of service location in path: " + path); + std::string::size_type end = path.find('/', begin); + NGREST_ASSERT(end != std::string::npos, "Canot get end of service location in path: " + path); + + // validate service location + const std::string& serviceLocation = path.substr(begin, end - begin); + NGREST_ASSERT(serviceLocation.empty() || + serviceLocation.find_first_of("{} \n\r\t?%&=\'\"") == std::string::npos, + "Service location is not valid: " + serviceLocation); + + // parse operation location + begin = path.find_first_not_of('/', end); + NGREST_ASSERT(begin != std::string::npos, "Canot get start of service location in URL: " + path); + end = path.find('/', begin); + const std::string& opLocation = path.substr(begin, (end == std::string::npos) ? end : (end - begin)); + + NGREST_ASSERT(opLocation.empty() || opLocation.find_first_of(" \n\r\t") == std::string::npos, + "Operation location is not valid: " + opLocation); + + + auto itService = impl->deployedServices.find(serviceLocation); + NGREST_ASSERT(itService != impl->deployedServices.end(), + "Can't find service by path: " + path); + + DeployedService& service = itService->second; + + int method = context->transport->getRequestMethod(context->request); + + // first look path in static resources + auto itOpLocation = service.staticResources.equal_range(opLocation); + for (auto it = itOpLocation.first; it != itOpLocation.second; ++it) { + const Resource& resource = it->second; + if (resource.operation->method == method) { + // found it! + service.wrapper->invoke(resource.operation, context); + return; + } + } + + + // search for suitable parametrized resource + Resource* resource = nullptr; + std::string::size_type matchLength = 0; + + for (auto it = service.paramResources.begin(); it != service.paramResources.end(); ++it) { + const std::string& basePath = it->first; + const std::string::size_type basePathSize = basePath.size(); + if (!path.compare(begin, basePathSize, basePath) && (matchLength < basePathSize)) { + Resource& currResource = it->second; + if (currResource.operation->method == method) { + matchLength = basePathSize; + resource = &currResource; + } + } + } + + NGREST_ASSERT(resource, "Resource not found for path: " + path); + NGREST_ASSERT(!context->request->node, "Request must have just one of query or body, but have both"); + + // generate OM from request + + // path = "/calc/add?a={a}&b={b}" + // path = "/calc/add?a=1111111&b=2" + // ^ matchLength + + + begin += matchLength; + + const char* pathCStr = path.c_str(); + Object* requestNode = context->pool.alloc(); + NamedNode* lastNamedNode = nullptr; + + for (int i = 0, l = resource->parameters.size(); i < l; ++i) { + const Parameter& parameter = resource->parameters[i]; + + if ((i + 1) < l) { + const Parameter& nextParameter = resource->parameters[i]; + const std::string& nextDivider = nextParameter.divider; + end = path.find(nextDivider, begin); + NGREST_ASSERT(end != std::string::npos, + "Can't find divider [" + nextDivider + "] for path: " + path); + } else { + end = path.size(); + } + + const char* name = context->pool.putCString(parameter.name.c_str(), true); + const char* value = context->pool.putCString(pathCStr + begin, end - begin, true); + + NamedNode* namedNode = context->pool.alloc(); + namedNode->name = name; + + if (lastNamedNode) { + lastNamedNode->nextSibling = namedNode; + } else { + requestNode->firstChild = namedNode; + } + lastNamedNode = namedNode; + + Value* valueNode = context->pool.alloc(ValueType::String); + valueNode->value = value; + namedNode->node = valueNode; + } + + +#ifdef DEBUG + json::JsonWriter::write(requestNode, context->response->poolBody); + LogDebug() << "Generated request:\n---------------------\n" + << context->response->poolBody.flatten()->buffer + << "\n---------------------\n"; + context->response->poolBody.reset(); +#endif + + context->request->node = requestNode; + + LogDebug() << "Invoking service operation " << service.wrapper->getDescription()->name + << "/" << resource->operation->name; + service.wrapper->invoke(resource->operation, context); +} + +} // namespace ngrest + diff --git a/core/engine/src/ServiceDispatcher.h b/core/engine/src/ServiceDispatcher.h new file mode 100644 index 0000000..cdbefa0 --- /dev/null +++ b/core/engine/src/ServiceDispatcher.h @@ -0,0 +1,27 @@ +#ifndef NGREST_SERVICEDISPATCHER_H +#define NGREST_SERVICEDISPATCHER_H + +namespace ngrest { + +class ServiceWrapper; +class MessageContext; + +class ServiceDispatcher +{ +public: + ServiceDispatcher(); + ~ServiceDispatcher(); + + void registerService(ServiceWrapper* wrapper); + void unregisterService(ServiceWrapper* wrapper); + + void dispatchMessage(MessageContext* context); + +private: + struct Impl; + Impl* const impl; +}; + +} // namespace ngrest + +#endif // NGREST_SERVICEDISPATCHER_H diff --git a/core/engine/src/ServiceGroup.cpp b/core/engine/src/ServiceGroup.cpp new file mode 100644 index 0000000..0c130a0 --- /dev/null +++ b/core/engine/src/ServiceGroup.cpp @@ -0,0 +1,15 @@ +#include "ServiceGroup.h" + +namespace ngrest { + +ServiceGroup::ServiceGroup() +{ + +} + +ServiceGroup::~ServiceGroup() +{ + +} + +} // namespace ngrest diff --git a/core/engine/src/ServiceGroup.h b/core/engine/src/ServiceGroup.h new file mode 100644 index 0000000..07f2ad3 --- /dev/null +++ b/core/engine/src/ServiceGroup.h @@ -0,0 +1,35 @@ +#ifndef NGREST_SERVICEGROUP_H +#define NGREST_SERVICEGROUP_H + +#include +#include + +namespace ngrest { + +class ServiceWrapper; + +/** + * @brief Group of services to deploy from plugin + */ +class ServiceGroup +{ +public: + ServiceGroup(); + virtual ~ServiceGroup(); + + /** + * @brief gets name of service group + * @return name of service group + */ + virtual std::string getName() = 0; + + /** + * @brief get list of service wrappers provided by group + * @return list of service wrappers + */ + virtual const std::vector& getServices() = 0; +}; + +} // namespace ngrest + +#endif // NGREST_SERVICEGROUP_H diff --git a/core/engine/src/ServiceWrapper.h b/core/engine/src/ServiceWrapper.h index 063f2a3..effbc72 100644 --- a/core/engine/src/ServiceWrapper.h +++ b/core/engine/src/ServiceWrapper.h @@ -14,12 +14,12 @@ class ServiceWrapper ServiceWrapper(); virtual ~ServiceWrapper(); - virtual Service* serviceImpl() = 0; + virtual Service* getServiceImpl() = 0; virtual void invoke(const OperationDescription* operation, MessageContext* context) = 0; // server side service description - virtual const ServiceDescription* description() = 0; + virtual const ServiceDescription* getDescription() = 0; }; } // namespace ngrest diff --git a/core/json/CMakeLists.txt b/core/json/CMakeLists.txt index 22670e7..a6a4769 100644 --- a/core/json/CMakeLists.txt +++ b/core/json/CMakeLists.txt @@ -10,5 +10,4 @@ file(COPY ${NGRESTJSON_HEADERS} DESTINATION ${PROJECT_INCLUDE_DIR}/ngrest/json/) add_library(ngrestjson SHARED ${NGRESTJSON_SOURCES}) -target_compile_features(ngrestjson PRIVATE cxx_range_for) target_link_libraries(ngrestjson ngrestutils ngrestcommon) diff --git a/core/json/src/JsonWriter.cpp b/core/json/src/JsonWriter.cpp index dcc2f9a..250e936 100644 --- a/core/json/src/JsonWriter.cpp +++ b/core/json/src/JsonWriter.cpp @@ -81,6 +81,11 @@ class JsonWriterImpl { } break; } + + case NodeType::NamedNode: + case NodeType::LinkedNode: { + NGREST_THROW_ASSERT("Cannot write Named or Linked node alone"); + } } } }; diff --git a/core/server/CMakeLists.txt b/core/server/CMakeLists.txt index d9fb43e..4947f02 100644 --- a/core/server/CMakeLists.txt +++ b/core/server/CMakeLists.txt @@ -8,5 +8,4 @@ FILE(GLOB NGRESTSERVER_HEADERS ${PROJECT_SOURCE_DIR}/*.h) add_executable(ngrestserver ${NGRESTSERVER_SOURCES}) -target_compile_features(ngrestserver PRIVATE cxx_range_for) target_link_libraries(ngrestserver ngrestutils ngrestcommon ngrestjson ngrestengine) diff --git a/core/server/src/ClientHandler.cpp b/core/server/src/ClientHandler.cpp index 767e267..e4b3998 100644 --- a/core/server/src/ClientHandler.cpp +++ b/core/server/src/ClientHandler.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -22,23 +23,30 @@ namespace ngrest { +static const uint64_t INVALID_VALUE = static_cast(-1); + struct MessageData { + ElapsedTimer timer; + uint64_t id; MessageContext context; MemPool poolStr; MemPool poolBody; bool usePoolBody = false; bool isChunked = false; - long contentLength = -1; + uint64_t contentLength = INVALID_VALUE; uint64_t httpBodyOffset = 0; - uint64_t httpBodyRemaining = -1; + uint64_t httpBodyRemaining = INVALID_VALUE; // uint64_t currentChunkRemaining = 0; bool processing = false; - MessageData(Transport* transport): + MessageData(uint64_t id_, Transport* transport): + timer(true), + id(id_), poolStr(2048), poolBody(1024) { + LogDebug() << "Start handling request " << id; context.transport = transport; context.request = context.pool.alloc(); context.response = context.pool.alloc(); @@ -134,7 +142,7 @@ bool ClientHandler::readyRead(int fd) MessageData* messageData; if (clientData->requests.empty()) { - messageData = new MessageData(&transport); + messageData = new MessageData(++lastId, &transport); clientData->requests.push_back(messageData); } else { messageData = clientData->requests.back(); @@ -142,7 +150,7 @@ bool ClientHandler::readyRead(int fd) if (messageData->processing) { // request is finished to read and now processing. // creating new request - messageData = new MessageData(&transport); + messageData = new MessageData(++lastId, &transport); clientData->requests.push_back(messageData); } } @@ -150,13 +158,14 @@ bool ClientHandler::readyRead(int fd) HttpRequest* httpRequest = static_cast(messageData->context.request); MemPool& pool = messageData->usePoolBody ? messageData->poolBody : messageData->poolStr; for (;;) { - uint64_t sizeToRead = (messageData->httpBodyRemaining != -1) + uint64_t sizeToRead = (messageData->httpBodyRemaining != INVALID_VALUE) ? messageData->httpBodyRemaining : TRY_BLOCK_SIZE; char* buffer = pool.grow(sizeToRead); ssize_t count = ::read(fd, buffer, sizeToRead); if (count == 0) { + // this should only happen in case of ioctl(fd, FIONREAD...) failure // EOF. The remote has closed the connection. - LogError() << "client #" << fd << " closed connection"; + LogDebug() << "client #" << fd << " closed connection"; return false; } @@ -164,7 +173,7 @@ bool ClientHandler::readyRead(int fd) // some error if (errno != EAGAIN) { LogError() << "failed to read block from client #" << fd - << ": " << strerror(errno); + << ": " << strerror(errno); return false; } @@ -173,10 +182,10 @@ bool ClientHandler::readyRead(int fd) break; } - if (count < sizeToRead) + if (count < static_cast(sizeToRead)) pool.shrinkLastChunk(sizeToRead - count); - if (messageData->httpBodyRemaining != -1) + if (messageData->httpBodyRemaining != INVALID_VALUE) messageData->httpBodyRemaining -= count; if (messageData->httpBodyOffset == 0) { @@ -406,7 +415,7 @@ void ClientHandler::processResponse(int clientFd, MessageData* messageData) ::write(clientFd, chunk->buffer, chunk->size); } - LogDebug() << "Request handling finished"; + LogDebug() << "Request " << messageData->id << " handled in " << messageData->timer.elapsed() << " microsecond(s)"; delete messageData; diff --git a/core/server/src/ClientHandler.h b/core/server/src/ClientHandler.h index 08ce623..49a5402 100644 --- a/core/server/src/ClientHandler.h +++ b/core/server/src/ClientHandler.h @@ -29,6 +29,7 @@ class ClientHandler: public ClientCallback void processError(int clientFd, MessageData* messageData, const Exception& error); private: + uint64_t lastId = 0; std::unordered_map clients; Engine& engine; Transport& transport; diff --git a/core/server/src/Server.cpp b/core/server/src/Server.cpp index 721dc69..bf41d61 100644 --- a/core/server/src/Server.cpp +++ b/core/server/src/Server.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -78,6 +79,13 @@ void Server::setClientCallback(ClientCallback* callback) int Server::exec() { + if (!callback) { + LogError() << "Client callback is not set!"; + return EXIT_FAILURE; + } + + LogInfo() << "Simple NGREST server started on port " << port << "."; + /* The event loop */ while (!isStopping) { int n = epoll_wait(fdEpoll, events, MAXEVENTS, -1); @@ -101,7 +109,7 @@ int Server::exec() completely, as we are running in edge-triggered mode and won't get a notification again for the same data. */ - handleRequest(i); + handleRequest(events[i].data.fd); } } } @@ -127,13 +135,13 @@ int Server::createServerSocket(const std::string& port) hints.ai_socktype = SOCK_STREAM; /* We want a TCP socket */ hints.ai_flags = AI_PASSIVE; /* All interfaces */ - int res = getaddrinfo(NULL, port.c_str(), &hints, &addr); + int res = getaddrinfo(nullptr, port.c_str(), &hints, &addr); if (res != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(res)); return -1; } - for (curr = addr; curr != NULL; curr = curr->ai_next) { + for (curr = addr; curr != nullptr; curr = curr->ai_next) { sock = socket(curr->ai_family, curr->ai_socktype, curr->ai_protocol); if (sock == -1) continue; @@ -152,12 +160,12 @@ int Server::createServerSocket(const std::string& port) freeaddrinfo(addr); - if (curr == NULL) { + if (curr == nullptr) { fprintf(stderr, "Could not bind\n"); return -1; } - LogInfo() << "Simple NGREST server started on port " << port << "."; + this->port = port; return sock; } @@ -184,9 +192,9 @@ bool Server::setupNonblock(int fd) bool Server::handleIncomingConnection() { while (!isStopping) { - struct sockaddr in_addr; - socklen_t in_len = sizeof(in_addr); - int fdIn = accept(fdServer, &in_addr, &in_len); + struct sockaddr inAddr; + socklen_t inLen = sizeof(inAddr); + int fdIn = accept(fdServer, &inAddr, &inLen); if (fdIn == -1) { if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) { /* We have processed all incoming connections. */ @@ -211,59 +219,29 @@ bool Server::handleIncomingConnection() if (res == -1) perror("epoll_ctl"); - if (callback) - callback->connected(event->data.fd, &in_addr); + callback->connected(event->data.fd, &inAddr); } return true; } -bool Server::handleRequest(int index) +void Server::handleRequest(int fd) { - if (callback) - return callback->readyRead(events[index].data.fd); - -// int fd = events[index].data.fd; -// bool done = false; -// while (!isStopping) { -// ssize_t count; -// char buf[512]; - -// count = read(fd, buf, sizeof(buf)); -// if (count == -1) { -// /* If errno == EAGAIN, that means we have read all -// data. So go back to the main loop. */ -// if (errno != EAGAIN) { -// perror("read"); -// done = true; -// } -// break; -// } - -// if (count == 0) { -// /* EOF. The remote has closed the connection. */ -// done = true; -// break; -// } - -// /* Write the buffer to standard output */ -// int res = write(1, buf, count); -// if (res == -1) { -// perror("write"); -// return false; -// } -// } - -// if (done) { -// printf("Closed connection on descriptor %d\n", -// fd); - -// /* Closing the descriptor will make epoll remove it -// from the set of descriptors which are monitored. */ -// close(fd); -// } + int64_t bytesAvail = 0; + int res = ioctl(fd, FIONREAD, &bytesAvail); + // ioctlsocket(socket, FIONREAD, &bytesAvail) + + // if res = 0, the query is ok, trust bytesAvail + // else if there are some bytes to read or the query is failed - we will try to read + if (res || bytesAvail) { + if (callback->readyRead(fd)) + return; + } else { + LogDebug() << "client #" << fd << " closed connection"; + } - return true; + close(fd); // disconnect client in case of errors + callback->disconnected(fd); } } diff --git a/core/server/src/Server.h b/core/server/src/Server.h index a0a45a9..cd81e5d 100644 --- a/core/server/src/Server.h +++ b/core/server/src/Server.h @@ -27,7 +27,7 @@ class Server int createServerSocket(const std::string& port); bool setupNonblock(int fd); bool handleIncomingConnection(); - bool handleRequest(int index); + void handleRequest(int fd); private: bool isStopping = false; @@ -36,6 +36,7 @@ class Server epoll_event* event = nullptr; epoll_event* events = nullptr; ClientCallback* callback = nullptr; + std::string port; }; } diff --git a/core/server/src/main.cpp b/core/server/src/main.cpp index 17abed3..2f27cb9 100644 --- a/core/server/src/main.cpp +++ b/core/server/src/main.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -31,9 +32,10 @@ int main(int argc, char* argv[]) } static ngrest::Server server; - ngrest::Deployment deployment; + ngrest::ServiceDispatcher dispatcher; + ngrest::Deployment deployment(dispatcher); ngrest::HttpTransport transport; - ngrest::Engine engine(deployment); + ngrest::Engine engine(dispatcher); ngrest::ClientHandler clientHandler(engine, transport); server.setClientCallback(&clientHandler); @@ -49,5 +51,10 @@ int main(int argc, char* argv[]) ::signal(SIGINT, signalHandler); ::signal(SIGTERM, signalHandler); + deployment.deployAll(); + + // TODO: implement file system watcher to re-load service dynamically + + return server.exec(); } diff --git a/core/service/CMakeLists.txt b/core/service/CMakeLists.txt index 508dfbe..3a5e6af 100644 --- a/core/service/CMakeLists.txt +++ b/core/service/CMakeLists.txt @@ -8,5 +8,4 @@ FILE(GLOB NGRESTSERVICE_HEADERS ${PROJECT_SOURCE_DIR}/*.h) add_executable(ngrestservice ${NGRESTSERVICE_SOURCES}) -target_compile_features(ngrestservice PRIVATE cxx_range_for) target_link_libraries(ngrestservice ngrestutils ngrestjson) diff --git a/core/utils/CMakeLists.txt b/core/utils/CMakeLists.txt index 9e37693..24b4959 100644 --- a/core/utils/CMakeLists.txt +++ b/core/utils/CMakeLists.txt @@ -10,4 +10,4 @@ file(COPY ${NGRESTUTILS_HEADERS} DESTINATION ${PROJECT_INCLUDE_DIR}/ngrest/utils add_library(ngrestutils SHARED ${NGRESTUTILS_SOURCES}) -target_compile_features(ngrestutils PRIVATE cxx_range_for) +target_link_libraries(ngrestutils dl) diff --git a/core/utils/src/DynamicLibrary.cpp b/core/utils/src/DynamicLibrary.cpp new file mode 100644 index 0000000..3de3c1c --- /dev/null +++ b/core/utils/src/DynamicLibrary.cpp @@ -0,0 +1,86 @@ +#ifndef WIN32 +#include +#else +#include +#endif +#include "Exception.h" +#include "Error.h" +#include "DynamicLibrary.h" + +namespace ngrest { + +DynamicLibrary::DynamicLibrary() +{ +} + +DynamicLibrary::~DynamicLibrary() +{ +} + +void DynamicLibrary::load(const std::string& libName, bool raw /*= false*/) +{ + NGREST_ASSERT(!handle, "Library is already loaded"); + + if (raw) { +#ifdef WIN32 + handle = LoadLibraryA(libName.c_str()); +#else + handle = dlopen(libName.c_str(), RTLD_LAZY); +#endif + } else { +#ifdef WIN32 + handle = LoadLibraryA((libName + NGREST_LIBRARY_EXT).c_str()); +#else + std::string::size_type pos = libName.find_last_of('/'); + if (pos == std::string::npos) { + handle = dlopen((NGREST_LIBRARY_PREFIX + libName + NGREST_LIBRARY_EXT).c_str(), RTLD_LAZY); + } else { + handle = dlopen((libName.substr(pos) + NGREST_LIBRARY_PREFIX + + libName.substr(pos + 1) + NGREST_LIBRARY_EXT).c_str(), RTLD_LAZY); + } +#endif + } + + NGREST_ASSERT(handle, "Failed load library [" + libName + "]: " + Error::getLastLibraryError()); + this->libName = libName; + this->raw = raw; +} + +const std::string& DynamicLibrary::name() const +{ + NGREST_ASSERT(handle, "Library is not loaded"); + return libName; +} + +void* DynamicLibrary::getSymbol(const std::string& symbol) const +{ + NGREST_ASSERT(handle, "Library is not loaded"); + +#ifdef WIN32 + void* result = reinterpret_cast(GetProcAddress(reinterpret_cast(handle), symbol.c_str())); +#else + void* result = dlsym(handle, symbol.c_str()); +#endif + + NGREST_ASSERT(result, "Failed to get symbol [" + symbol + "]: " + Error::getLastLibraryError()); + return result; +} + +void DynamicLibrary::unload() +{ + NGREST_ASSERT(handle, "Library is not loaded"); +#ifdef WIN32 + FreeLibrary(reinterpret_cast(handle)); +#else + dlclose(handle); +#endif + handle = nullptr; +} + +void DynamicLibrary::reload() +{ + unload(); + load(libName, raw); +} + +} diff --git a/core/utils/src/DynamicLibrary.h b/core/utils/src/DynamicLibrary.h new file mode 100644 index 0000000..f39fefe --- /dev/null +++ b/core/utils/src/DynamicLibrary.h @@ -0,0 +1,63 @@ +#ifndef NGREST_UTILS_DYNAMICLIBRARY_H +#define NGREST_UTILS_DYNAMICLIBRARY_H + +#include +#include "ngrestutilsexport.h" + +#ifdef WIN32 +#define NGREST_LIBRARY_PREFIX "" +#define NGREST_LIBRARY_EXT ".dll" +#else +#ifdef __APPLE__ +#define NGREST_LIBRARY_PREFIX "lib" +#define NGREST_LIBRARY_EXT ".dylib" +#else +#define NGREST_LIBRARY_PREFIX "lib" +#define NGREST_LIBRARY_EXT ".so" +#endif +#endif + +namespace ngrest { + +//! dynamic library +class NGREST_UTILS_EXPORT DynamicLibrary +{ +public: + //! constructor + DynamicLibrary(); + + //! destructor + virtual ~DynamicLibrary(); + + //! load dynamic library + /*! \param libName library name + \param raw if true libName contains full path to library + */ + void load(const std::string& libName, bool raw = false); + + //! get library name + /*! \return library name + */ + const std::string& name() const; + + //! get pointer to library symbol + /*! \param symbol symbol name + \return pointer to symbol + */ + void* getSymbol(const std::string& symbol) const; + + //! unload library + void unload(); + + //! reload library + void reload(); + +private: + void* handle = nullptr; //!< library handle + std::string libName; //!< library name + bool raw = false; //!< libName is full +}; + +} // namespace ngrest + +#endif // NGREST_UTILS_DYNAMICLIBRARY_H diff --git a/core/utils/src/ElapsedTimer.cpp b/core/utils/src/ElapsedTimer.cpp new file mode 100644 index 0000000..bc83b55 --- /dev/null +++ b/core/utils/src/ElapsedTimer.cpp @@ -0,0 +1,14 @@ +#include + +#include "ElapsedTimer.h" + +namespace ngrest { + +int64_t ElapsedTimer::getTime() +{ + struct timeval now; + gettimeofday(&now, nullptr); + return now.tv_sec * 1000000 + now.tv_usec; +} + +} // namespace ngrest diff --git a/core/utils/src/ElapsedTimer.h b/core/utils/src/ElapsedTimer.h new file mode 100644 index 0000000..1c1b462 --- /dev/null +++ b/core/utils/src/ElapsedTimer.h @@ -0,0 +1,47 @@ +#ifndef NGREST_ELAPSEDTIMER_H +#define NGREST_ELAPSEDTIMER_H + +#include + +namespace ngrest { + +/** + * @brief class to calculate elapsed time + */ +class ElapsedTimer +{ +public: + inline ElapsedTimer(bool startNow = false) + { + if (startNow) + start(); + } + + /** + * @brief start measuring + */ + inline void start() + { + started = getTime(); + } + + /** + * @brief returns the number of microseconds since this ElapsedTimer was started + */ + inline int64_t elapsed() const + { + return getTime() - started; + } + + /** + * @brief get current time in microseconds + */ + static int64_t getTime(); + +private: + int64_t started = 0; +}; + +} // namespace ngrest + +#endif // NGREST_ELAPSEDTIMER_H diff --git a/core/utils/src/Error.cpp b/core/utils/src/Error.cpp new file mode 100644 index 0000000..51a423d --- /dev/null +++ b/core/utils/src/Error.cpp @@ -0,0 +1,51 @@ +#ifdef WIN32 +#include +#else +#include +#include +#include +#endif +#include "Error.h" + +namespace ngrest { + +std::string Error::getError(long errorNo) +{ +#ifdef WIN32 + LPVOID buff = nullptr; + DWORD count = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, errorNo, 0, + reinterpret_cast(&buff), 0, nullptr); + + if (!count || !buff) { + return ""; + } else { + std::string error(reinterpret_cast(buff), static_cast(count)); + LocalFree(buff); + return error; + } +#else + return strerror(errorNo); +#endif +} + +std::string Error::getLastError() +{ +#ifdef WIN32 + return getError(GetLastError()); +#else + return getError(errno); +#endif +} + +std::string Error::getLastLibraryError() +{ +#ifdef WIN32 + return getError(GetLastError()); +#else + return dlerror(); +#endif +} + + +} diff --git a/core/utils/src/Error.h b/core/utils/src/Error.h new file mode 100644 index 0000000..31e11b7 --- /dev/null +++ b/core/utils/src/Error.h @@ -0,0 +1,32 @@ +#ifndef NGREST_UTILS_ERROR_H +#define NGREST_UTILS_ERROR_H + +#include +#include "ngrestutilsexport.h" + +namespace ngrest { + +//! get os error +class NGREST_UTILS_EXPORT Error +{ +public: + //! get error string by error no + /*! \param errorNo - error no + \return error string + */ + static std::string getError(long errorNo); + + //! get last error string + /*! \return last error string + */ + static std::string getLastError(); + + //! get last library error string + /*! \return last library error string + */ + static std::string getLastLibraryError(); +}; + +} + +#endif // NGREST_UTILS_ERROR_H diff --git a/core/utils/src/Exception.cpp b/core/utils/src/Exception.cpp index 0765130..d8c1e03 100644 --- a/core/utils/src/Exception.cpp +++ b/core/utils/src/Exception.cpp @@ -3,24 +3,23 @@ //#include "StackTracer.h" #include "Exception.h" -namespace ngrest -{ +namespace ngrest { Exception::Exception(const char* fileLine_, const char* function_, const std::string& description_): fileLine(fileLine_), function(function_), description(description_) { - // static short nEnableStackTracing = -1; - // if (nEnableStackTracing == -1) + // static short enableStackTracing = -1; + // if (enableStackTracing == -1) // { - // char* szStackTracingEnv = NULL; + // char* szStackTracingEnv = nullptr; //#if defined WIN32 && !defined __MINGW32__ - // _dupenv_s(&szStackTracingEnv, NULL, "NGREST_EXCEPTION_STACKTRACING"); + // _dupenv_s(&szStackTracingEnv, nullptr, "NGREST_EXCEPTION_STACKTRACING"); //#else // szStackTracingEnv = getenv("NGREST_EXCEPTION_STACKTRACING"); //#endif - // nEnableStackTracing = + // enableStackTracing = // (szStackTracingEnv && ((!strcmp(szStackTracingEnv, "1") || !strcmp(szStackTracingEnv, "TRUE")))) // ? 1 : 0; @@ -32,11 +31,11 @@ Exception::Exception(const char* fileLine_, const char* function_, const std::st //#endif // } - // if (nEnableStackTracing == 1) + // if (enableStackTracing == 1) // { - // std::string sStack; - // StackTracer::GetStackTraceStr(sStack); - // m_sDescr.append("\nTraced stack:\n").append(sStack); + // std::string stack; + // StackTracer::GetStackTraceStr(stack); + // descr.append("\nTraced stack:\n").append(stack); // } } diff --git a/core/utils/src/Exception.h b/core/utils/src/Exception.h index 9cf2d14..4957790 100644 --- a/core/utils/src/Exception.h +++ b/core/utils/src/Exception.h @@ -42,36 +42,25 @@ //! catch all exceptions #define NGREST_CATCH_ALL\ - catch (const ::ngrest::Exception& rException)\ -{\ - ::ngrest::LogError() << "Exception: \n" << rException.what();\ - }\ - catch (const std::exception& rException) \ -{\ - ::ngrest::LogError() << "std::exception: \n" << rException.what();\ - }\ - catch (...) \ -{\ - ::ngrest::LogError() << "unknown exception"; \ + catch (const ::ngrest::Exception& exception) {\ + ::ngrest::LogError() << "Exception: \n" << exception.what();\ + } catch (const std::exception& exception) {\ + ::ngrest::LogError() << "std::exception: \n" << exception.what();\ + } catch (...) {\ + ::ngrest::LogError() << "unknown exception"; \ } //! catch all exceptions and write description #define NGREST_CATCH_ALL_DESCR(DESCRIPTION)\ - catch (const ::ngrest::Exception& rException)\ -{\ - ::ngrest::LogError() << (DESCRIPTION) << "\nException: \n" << rException.what();\ - }\ - catch (const std::exception& rException) \ -{\ - ::ngrest::LogError() << (DESCRIPTION) << "\nstd::exception: \n" << rException.what();\ - }\ - catch (...) \ -{\ - ::ngrest::LogError() << (DESCRIPTION) << "\nunknown exception"; \ + catch (const ::ngrest::Exception& exception) {\ + ::ngrest::LogError() << (DESCRIPTION) << "\nException: \n" << exception.what();\ + } catch (const std::exception& exception) {\ + ::ngrest::LogError() << (DESCRIPTION) << "\nstd::exception: \n" << exception.what();\ + } catch (...) {\ + ::ngrest::LogError() << (DESCRIPTION) << "\nunknown exception"; \ } -namespace ngrest -{ +namespace ngrest { //! base ngrest exception class class NGREST_UTILS_EXPORT Exception: public std::exception @@ -134,10 +123,10 @@ class AssertException: public Exception //! exception constructor /*! \param fileLine - source file name and line number \param function - function signature - \param sDescr - description + \param description - description */ - inline AssertException(const char* fileLine, const char* function, const std::string& sDescr): - Exception(fileLine, function, sDescr) + inline AssertException(const char* fileLine, const char* function, const std::string& description): + Exception(fileLine, function, description) { } }; diff --git a/core/utils/src/File.cpp b/core/utils/src/File.cpp new file mode 100644 index 0000000..6067830 --- /dev/null +++ b/core/utils/src/File.cpp @@ -0,0 +1,163 @@ +#include +#include +#include +#ifdef WIN32 +#include +#include +#else +#include +#include +#include +#endif +#include "Exception.h" +#include "File.h" + +#ifdef WIN32 +#define stat _stat +#endif + +namespace ngrest { + +File::File(const std::string& path): + path(path) +{ +} + +void File::list(StringList& list, const std::string& mask /*= "*" */, + int attrs /*= AttributeAnyFile | AttributeDirectory */) +{ + list.clear(); +#ifdef WIN32 + _finddata_t searchData; // search record + intptr_t file = _findfirst((path + "\\" + mask).c_str(), &searchData); + + if (file != -1) { + do { + if (!isDots(searchData.name) && + (((attrs & AttributeDirectory) != 0) + && (searchData.attrib & _A_SUBDIR) != 0) || + ((attrs & AttributeAnyFile) != 0)) + list.push_back(searchData.name); + } while (!_findnext(file, &searchData)); + + _findclose(file); + } +#else + DIR* dir = opendir(path.c_str()); + if (dir) { + struct stat fileStat; + dirent* fileDirent = nullptr; + unsigned maskMode = + ((attrs & AttributeDirectory) ? S_IFDIR : 0) | + ((attrs & AttributeRegularFile) ? S_IFREG : 0) | + ((attrs & AttributeOtherFile) ? (S_IFMT & (~(S_IFREG | S_IFDIR))) : 0); + + while ((fileDirent = readdir(dir))) { + if (!isDots(fileDirent->d_name) && + !fnmatch(mask.c_str(), fileDirent->d_name, 0) && + !lstat(((path + "/") + fileDirent->d_name).c_str(), &fileStat) && + (fileStat.st_mode & maskMode) == (fileStat.st_mode & S_IFMT)) + list.push_back(fileDirent->d_name); + } + + closedir(dir); + } +#endif +} + +int File::getAttributes() +{ + int result = AttributeNone; + + struct stat fileStat; + if (!stat(path.c_str(), &fileStat)) { + if (fileStat.st_mode & S_IFDIR) { + result |= AttributeDirectory; + } else if (fileStat.st_mode & S_IFREG) { + result |= AttributeRegularFile; + } else { + result |= AttributeOtherFile; + } + } + return result; +} + +bool File::isExists() +{ + return getAttributes() != AttributeNone; +} + +bool File::isDirectory() +{ + return (getAttributes() & AttributeDirectory) != 0; +} + +bool File::isRegularFile() +{ + return (getAttributes() & AttributeRegularFile) != 0; +} + +bool File::isSystemFile() +{ + return (getAttributes() & AttributeOtherFile) != 0; +} + +bool File::isFile() +{ + return (getAttributes() & AttributeAnyFile) != 0; +} + +int64_t File::getTime() +{ + struct stat fileStat; + if (stat(path.c_str(), &fileStat)) + return 0; + + return static_cast(fileStat.st_mtime); +} + +bool File::mkdir() +{ +#ifndef WIN32 + int res = ::mkdir(path.c_str(), 0755); +#else + int res = _mkdir(path.c_str()); +#endif + NGREST_ASSERT(res != -1 || errno == EEXIST, "Failed to create dir [" + + path + "]: " + strerror(errno)); + + return !res; +} + +bool File::mkdirs() +{ + int res = -1; + for (std::string::size_type pos = 0; pos != std::string::npos;) { + pos = path.find_first_of("/\\", pos + 1); + const std::string& currDir = path.substr(0, pos); + if (!currDir.empty()) { +#ifndef WIN32 + res = ::mkdir(currDir.c_str(), 0755); +#else + res = _mkdir(currDir.c_str()); +#endif + NGREST_ASSERT(res != -1 || errno == EEXIST, "Failed to create dir [" + + currDir + "]: " + strerror(errno)); + } + } + + return !res; +} + +bool File::isDots(const char* name) +{ + return (name == nullptr || (name[0] == '.' && (name[1] == '\0' || + (name[1] == '.' && name[2] == '\0')))); +} + +bool File::isDots(const std::string& name) +{ + return isDots(name.c_str()); +} + +} diff --git a/core/utils/src/File.h b/core/utils/src/File.h new file mode 100644 index 0000000..151b8a6 --- /dev/null +++ b/core/utils/src/File.h @@ -0,0 +1,113 @@ +#ifndef NGREST_UTILS_FILE_H +#define NGREST_UTILS_FILE_H + +#include +#include +#include +#include "ngrestutilsexport.h" + +#ifdef _MSC_VER +#define NGREST_PATH_SEPARATOR "\\" +#else +#define NGREST_PATH_SEPARATOR "/" +#endif + +namespace ngrest { + +typedef std::list StringList; //!< list of strings + +//! file finder +class NGREST_UTILS_EXPORT File +{ +public: + //! file attributes + enum Attribute + { + AttributeNone = 0, //!< not a file nor directory + AttributeDirectory = 1, //!< directory + AttributeRegularFile = 2, //!< regular file + AttributeOtherFile = 4, //!< not a regular file (symlink, pipe, socket, etc...) + AttributeAnyFile = //!< any file (regular, symlink, pipe, socket, etc...) + AttributeRegularFile | AttributeOtherFile, + AttributeAny = //!< any file or directory + AttributeAnyFile | AttributeDirectory + }; + +public: + //! constructor + /*! \param path - path to the file or directory + */ + File(const std::string& path); + + //! find files/directories by name + /*! \param list - resulting list of files/directories + \param mask - file mask (shell pattern) + \param attrs - match by attributes + */ + void list(StringList& list, const std::string& mask = "*", int attrs = AttributeAny); + + //! get file/directory attributes + /*! if file does not exists returns AttributeNone + \return attributes + */ + int getAttributes(); + + //! test whether the file or directory exists + /*! \return true if the file or directory exists + */ + bool isExists(); + + //! tests whether path is a directory + /*! \return true if path is a directory + */ + bool isDirectory(); + + //! tests whether path is a regular file + /*! \return true if path is a regular file + */ + bool isRegularFile(); + + //! tests whether path is not a regular file (symlink, pipe, socket, etc...) + /*! \return true if path is not a regular file + */ + bool isSystemFile(); + + //! tests whether path is any file + /*! \return true if path is any file + */ + bool isFile(); + + //! get file's last modification time + /*! \return unix time + */ + int64_t getTime(); + + //! create the directory + /*! \return true if the directory was created + */ + bool mkdir(); + + //! create the directory, including parent directories + /*! \return true if the directory was created + */ + bool mkdirs(); + + //! is file name = "." or ".." + /*! \param name - filename + \return true if file name = "." or ".." + */ + static bool isDots(const char* name); + + //! is file name = "." or ".." + /*! \param name - filename + \return true if file name = "." or ".." + */ + static bool isDots(const std::string& name); + +private: + std::string path; //!< path to the file or directory +}; +} + +#endif // NGREST_UTILS_FILE_H + diff --git a/core/utils/src/Log.cpp b/core/utils/src/Log.cpp index 30ee2b4..fe1392d 100644 --- a/core/utils/src/Log.cpp +++ b/core/utils/src/Log.cpp @@ -12,8 +12,7 @@ #include "tocstring.h" #include "Log.h" -namespace ngrest -{ +namespace ngrest { inline const char* baseName(const char* path) { @@ -28,8 +27,8 @@ inline const char* baseName(const char* path) Log& Log::inst() { - static Log tInst; - return tInst; + static Log instance; + return instance; } Log::Log(): @@ -118,11 +117,10 @@ void Log::setLogStream(std::ostream* outStream) LogStream Log::write(LogLevel logLevel, const char* fileLine, const char* function) { if (!stream || logLevel > level) - return LogStream(NULL); + return LogStream(nullptr); if ((verbosity & LogVerbosityLevel)) { - switch (logLevel) - { + switch (logLevel) { case LogLevelAlert: *stream << "ALERT "; break; diff --git a/core/utils/src/Log.h b/core/utils/src/Log.h index 0c29730..b2943d2 100644 --- a/core/utils/src/Log.h +++ b/core/utils/src/Log.h @@ -10,8 +10,8 @@ #define NGREST_FILE_LINE __FILE__ "[" NGREST_TO_STRING(__LINE__) "] " #endif -namespace ngrest -{ +namespace ngrest { + //! put data to log #define NGREST_LOG_WRITE(NGREST_LOG_LEVEL)\ Log::inst().write(::ngrest::Log::NGREST_LOG_LEVEL, NGREST_FILE_LINE, __FUNCTION__) @@ -52,11 +52,11 @@ namespace ngrest //! log enter/exit function message #define LogEntry() \ - LogEntryScope tLogEntryScope(NGREST_FILE_LINE, __FUNCTION__); + LogEntryScope logEntryScope(NGREST_FILE_LINE, __FUNCTION__); //! log enter/exit function message with given level #define LogEntryL(NGREST_LOG_LEVEL) \ - LogEntryScope tLogEntryScope(NGREST_FILE_LINE, __FUNCTION__, NGREST_LOG_LEVEL); + LogEntryScope logEntryScope(NGREST_FILE_LINE, __FUNCTION__, NGREST_LOG_LEVEL); //! Logger class @@ -116,7 +116,7 @@ class NGREST_UTILS_EXPORT Log static Log& inst(); //! set log stream to output messages - /*! \param outStream - log stream (NULL - do not output) + /*! \param outStream - log stream (nullptr - do not output) */ void setLogStream(std::ostream* outStream); diff --git a/core/utils/src/LogStream.h b/core/utils/src/LogStream.h index 40166d7..13ff788 100644 --- a/core/utils/src/LogStream.h +++ b/core/utils/src/LogStream.h @@ -1,11 +1,11 @@ -#ifndef _NGREST_UTILS_LOGSTREAM_H_ -#define _NGREST_UTILS_LOGSTREAM_H_ +#ifndef NGREST_UTILS_LOGSTREAM_H +#define NGREST_UTILS_LOGSTREAM_H #include #include "ngrestutilsexport.h" -namespace ngrest -{ +namespace ngrest { + // wsdl compat typedef char byte; typedef unsigned char unsignedByte; @@ -43,8 +43,8 @@ class NGREST_UTILS_EXPORT LogStream } private: - friend LogStream& logEolOff(LogStream& rLogStream); - friend LogStream& LogEolOn(LogStream& rLogStream); + friend LogStream& logEolOff(LogStream& logStream); + friend LogStream& LogEolOn(LogStream& logStream); private: std::ostream* stream; @@ -52,19 +52,19 @@ class NGREST_UTILS_EXPORT LogStream }; //! disable carriage return -inline LogStream& logEolOff(LogStream& rLogStream) +inline LogStream& logEolOff(LogStream& logStream) { - rLogStream.writeEol = false; - return rLogStream; + logStream.writeEol = false; + return logStream; } //! enable carriage return -inline LogStream& LogEolOn(LogStream& rLogStream) +inline LogStream& LogEolOn(LogStream& logStream) { - rLogStream.writeEol = true; - return rLogStream; + logStream.writeEol = true; + return logStream; } } // namespace ngrest -#endif // _NGREST_UTILS_LOGSTREAM_H_ +#endif // NGREST_UTILS_LOGSTREAM_H diff --git a/core/utils/src/Plugin.h b/core/utils/src/Plugin.h new file mode 100644 index 0000000..52f866d --- /dev/null +++ b/core/utils/src/Plugin.h @@ -0,0 +1,42 @@ +#ifndef NGREST_UTILS_PLUGIN_H +#define NGREST_UTILS_PLUGIN_H + +#include "Exception.h" +#include "PluginExport.h" +#include "DynamicLibrary.h" + +namespace ngrest { + +template +class Plugin: public DynamicLibrary +{ +public: + void load(const std::string& path) + { + DynamicLibrary::load(path, true); + +#ifdef WIN32 + typedef PluginType* (*ngrestGetPlugin)(); + PNgrestGetPluginAddress getPluginAddr = reinterpret_cast( + getSymbol(NGREST_PLUGIN_EXPORTED_SYMBOL_STR)); + NGREST_ASSERT(getPluginAddr, "Error while getting NgrestGetPluginAddress"); + pluginSymbol = reinterpret_cast(getPluginAddr()); +#else + pluginSymbol = reinterpret_cast(getSymbol(NGREST_PLUGIN_EXPORTED_SYMBOL_STR)); +#endif + + NGREST_ASSERT(pluginSymbol, "Error while getting plugin object"); + } + + inline PluginType* getPluginSymbol() + { + return pluginSymbol; + } + +private: + PluginType* pluginSymbol; +}; + +} + +#endif // NGREST_UTILS_PLUGIN_H diff --git a/core/utils/src/PluginExport.h b/core/utils/src/PluginExport.h new file mode 100644 index 0000000..e648035 --- /dev/null +++ b/core/utils/src/PluginExport.h @@ -0,0 +1,17 @@ +#ifndef NGREST_UTILS_PLUGINEXPORT_H +#define NGREST_UTILS_PLUGINEXPORT_H + +#define NGREST_PLUGIN_EXPORTED_SYMBOL ngrestPlugin +#define NGREST_PLUGIN_EXPORTED_SYMBOL_STR "ngrestPlugin" + +#ifdef WIN32 +#define NGREST_DECLARE_PLUGIN(Clasname) \ + Clasname NGREST_PLUGIN_EXPORTED_SYMBOL; \ + extern "C" NGREST_DLL_EXPORT void* ngrestGetPlugin() \ +{ return &NGREST_PLUGIN_EXPORTED_SYMBOL; } +#else +#define NGREST_DECLARE_PLUGIN(Clasname) \ + Clasname NGREST_PLUGIN_EXPORTED_SYMBOL; +#endif + +#endif // NGREST_UTILS_PLUGINEXPORT_H diff --git a/core/utils/src/fromcstring.h b/core/utils/src/fromcstring.h index 171da5e..2cf9652 100644 --- a/core/utils/src/fromcstring.h +++ b/core/utils/src/fromcstring.h @@ -20,8 +20,8 @@ #define ngrest_strtold strtold #endif -namespace ngrest -{ +namespace ngrest { + typedef char byte; typedef unsigned char unsignedByte; @@ -42,35 +42,35 @@ inline bool fromCString(const char* string, bool& value) inline bool fromCString(const char* string, byte& value) { - char* end = NULL; + char* end = nullptr; value = static_cast(strtol(string, &end, 10)); return !end || *end == '\0'; } inline bool fromCString(const char* string, int& value) { - char* end = NULL; + char* end = nullptr; value = static_cast(strtol(string, &end, 10)); return !end || *end == '\0'; } inline bool fromCString(const char* string, short& value) { - char* end = NULL; + char* end = nullptr; value = static_cast(strtol(string, &end, 10)); return !end || *end == '\0'; } inline bool fromCString(const char* string, long& value) { - char* end = NULL; + char* end = nullptr; value = strtol(string, &end, 10); return !end || *end == '\0'; } inline bool fromCString(const char* string, long long& value) { - char* end = NULL; + char* end = nullptr; value = ngrest_strtoll(string, &end, 10); return !end || *end == '\0'; } @@ -78,35 +78,35 @@ inline bool fromCString(const char* string, long long& value) inline bool fromCString(const char* string, unsignedByte& value) { - char* end = NULL; + char* end = nullptr; value = static_cast(strtoul(string, &end, 10)); return !end || *end == '\0'; } inline bool fromCString(const char* string, unsigned int& value) { - char* end = NULL; + char* end = nullptr; value = static_cast(strtoul(string, &end, 10)); return !end || *end == '\0'; } inline bool fromCString(const char* string, unsigned short& value) { - char* end = NULL; + char* end = nullptr; value = static_cast(strtoul(string, &end, 10)); return !end || *end == '\0'; } inline bool fromCString(const char* string, unsigned long& value) { - char* end = NULL; + char* end = nullptr; value = strtoul(string, &end, 10); return !end || *end == '\0'; } inline bool fromCString(const char* string, unsigned long long& value) { - char* end = NULL; + char* end = nullptr; value = ngrest_strtoull(string, &end, 10); return !end || *end == '\0'; } @@ -114,21 +114,21 @@ inline bool fromCString(const char* string, unsigned long long& value) inline bool fromCString(const char* string, float& value) { - char* end = NULL; + char* end = nullptr; value = ngrest_strtof(string, &end); return !end || *end == '\0'; } inline bool fromCString(const char* string, double& value) { - char* end = NULL; + char* end = nullptr; value = strtod(string, &end); return !end || *end == '\0'; } inline bool fromCString(const char* string, long double& value) { - char* end = NULL; + char* end = nullptr; value = ngrest_strtold(string, &end); return !end || *end == '\0'; } @@ -137,41 +137,41 @@ template inline Type FromCStringDefault(const char* string, Type defaultValue) { Type result = 0; - return (string != NULL && fromCString(string, result)) ? result : defaultValue; + return (string != nullptr && fromCString(string, result)) ? result : defaultValue; } inline bool fromHexCString(const char* string, unsignedByte& value) { - char* end = NULL; + char* end = nullptr; value = static_cast(strtoul(string, &end, 16)); return !end || *end == '\0'; } inline bool fromHexCString(const char* string, unsigned int& value) { - char* end = NULL; + char* end = nullptr; value = static_cast(strtoul(string, &end, 16)); return !end || *end == '\0'; } inline bool fromHexCString(const char* string, unsigned short& value) { - char* end = NULL; + char* end = nullptr; value = static_cast(strtoul(string, &end, 16)); return !end || *end == '\0'; } inline bool fromHexCString(const char* string, unsigned long& value) { - char* end = NULL; + char* end = nullptr; value = strtoul(string, &end, 16); return !end || *end == '\0'; } inline bool fromHexCString(const char* string, unsigned long long& value) { - char* end = NULL; + char* end = nullptr; value = ngrest_strtoull(string, &end, 16); return !end || *end == '\0'; } @@ -180,7 +180,7 @@ template inline Type FromHexCStringDefault(const char* string, Type defaultValue) { Type result = 0; - return (string != NULL && fromHexCString(string, result)) ? result : defaultValue; + return (string != nullptr && fromHexCString(string, result)) ? result : defaultValue; } } diff --git a/core/utils/src/fromstring.h b/core/utils/src/fromstring.h index cfa9a14..4c32b1f 100644 --- a/core/utils/src/fromstring.h +++ b/core/utils/src/fromstring.h @@ -4,8 +4,7 @@ #ifndef NGREST_UTILS_FROMSTRING_H #define NGREST_UTILS_FROMSTRING_H -namespace ngrest -{ +namespace ngrest { template inline Type& fromString(const std::string& string, Type& value) diff --git a/core/utils/src/stringutils.h b/core/utils/src/stringutils.h new file mode 100644 index 0000000..3a74926 --- /dev/null +++ b/core/utils/src/stringutils.h @@ -0,0 +1,69 @@ +#ifndef NGREST_UTILS_STRINGUTILS_H +#define NGREST_UTILS_STRINGUTILS_H + +#include + +namespace ngrest { + +//! perform replace in string +/*! \param where - string where search and replace should be performed + \param what - what data replace + \param with - replace with data + \param all - true - replace all, false - replace only first + \return number of replaces made + */ +inline unsigned stringReplace(std::string& where, const std::string& what, + const std::string& with, bool all = false) +{ + unsigned count = 0; + std::string::size_type pos = 0; + while ((pos = where.find(what, pos)) != std::string::npos) { + where.replace(pos, what.size(), with); + ++count; + if (!all) + break; + pos += with.size(); + } + + return count; +} + +//! remove leading whitespaces +/*! \param string - string + \param whitespace - whitespace symbols + */ +inline void stringTrimLeft(std::string& str, const char* whitespace = " \n\r\t") +{ + if (whitespace) { + std::string::size_type pos = str.find_first_not_of(whitespace); + if (pos) + str.erase(0, pos); + } +} + +//! remove trailing whitespaces +/*! \param string - string + \param whitespace - space symbols + */ +inline void stringTrimRight(std::string& str, const char* whitespace = " \n\r\t") +{ + if (whitespace) { + std::string::size_type pos = str.find_last_not_of(whitespace); + if (pos != std::string::npos) + str.erase(pos + 1); + } +} + +//! remove leading and trailing whitespaces +/*! \param string - string + \param whitespace - space symbols + */ +inline void stringTrim(std::string& str, const char* whitespace = " \n\r\t") +{ + stringTrimLeft(str, whitespace); + stringTrimRight(str, whitespace); +} + +} + +#endif // NGREST_UTILS_STRINGUTILS_H diff --git a/core/utils/src/tocstring.h b/core/utils/src/tocstring.h index af3db06..099a8cd 100644 --- a/core/utils/src/tocstring.h +++ b/core/utils/src/tocstring.h @@ -10,8 +10,8 @@ #define ngrest_snprintf snprintf #endif -namespace ngrest -{ +namespace ngrest { + typedef char byte; typedef unsigned char unsignedByte; diff --git a/core/utils/src/tostring.h b/core/utils/src/tostring.h index dbcc52e..8637185 100644 --- a/core/utils/src/tostring.h +++ b/core/utils/src/tostring.h @@ -4,8 +4,7 @@ #include #include "tocstring.h" -namespace ngrest -{ +namespace ngrest { template std::string toString(Type value) @@ -28,9 +27,9 @@ template std::string toString(Type value, bool* ok) { char buffer[NUM_TO_STR_BUFF_SIZE]; - bool bIsOk = toCString(value, buffer, sizeof(buffer)); + bool isOk = toCString(value, buffer, sizeof(buffer)); if (ok) - *ok = bIsOk; + *ok = isOk; return buffer; } @@ -48,7 +47,7 @@ std::string& toString(Type value, std::string& result, bool* ok) template -std::string ToHexString(Type value) +std::string toHexString(Type value) { char buffer[NUM_TO_STR_BUFF_SIZE]; toHexCString(value, buffer, sizeof(buffer)); diff --git a/core/xml/CMakeLists.txt b/core/xml/CMakeLists.txt new file mode 100644 index 0000000..d8fbe2a --- /dev/null +++ b/core/xml/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 2.6) +project (ngrestxml CXX) + +set (PROJECT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src) + +FILE(GLOB NGRESTXML_SOURCES ${PROJECT_SOURCE_DIR}/*.cpp) +FILE(GLOB NGRESTXML_HEADERS ${PROJECT_SOURCE_DIR}/*.h) + +file(COPY ${NGRESTXML_HEADERS} DESTINATION ${PROJECT_INCLUDE_DIR}/ngrest/xml/) + +add_library(ngrestxml SHARED ${NGRESTXML_SOURCES}) + +target_link_libraries(ngrestxml dl) diff --git a/core/xml/src/Attribute.cpp b/core/xml/src/Attribute.cpp new file mode 100644 index 0000000..3522eb0 --- /dev/null +++ b/core/xml/src/Attribute.cpp @@ -0,0 +1,110 @@ +#include "Exception.h" +#include "Attribute.h" + +namespace ngrest { +namespace xml { + +Attribute::Attribute(const Attribute& attr): + prefix(attr.prefix), + name(attr.name), + value(attr.value), + nextSibling(nullptr) +{ +} + +Attribute::Attribute(const std::string& name): + nextSibling(nullptr) +{ + setName(name); +} + +Attribute::Attribute(const std::string& name, const std::string& value_): + value(value_), + nextSibling(nullptr) +{ + setName(name); +} + +Attribute::Attribute(const std::string& name_, const std::string& value_, const std::string& prefix_): + prefix(prefix_), + name(name_), + value(value_), + nextSibling(nullptr) +{ +} + +const std::string& Attribute::getPrefix() const +{ + return prefix; +} + +void Attribute::setPrefix(const std::string& prefix) +{ + this->prefix = prefix; +} + +const std::string& Attribute::getName() const +{ + return name; +} + +void Attribute::setName(const std::string& name) +{ + std::string::size_type pos = name.find_last_of(':'); + if (pos == std::string::npos) { + this->name = name; + prefix.erase(); + } else { + this->name.assign(name, pos + 1, std::string::npos); + prefix.assign(name, 0, pos); + } +} + +std::string Attribute::getPrefixName() const +{ + return prefix.empty() ? name : (prefix + ":" + name); +} + +const std::string& Attribute::getValue() const +{ + return value; +} + +void Attribute::setValue(const std::string& value) +{ + this->value = value; +} + + +const Attribute* Attribute::getNextSibling() const +{ + return nextSibling; +} + +Attribute* Attribute::getNextSibling() +{ + return nextSibling; +} + + +Attribute& Attribute::operator=(const Attribute& attr) +{ + prefix = attr.prefix; + name = attr.name; + value = attr.value; + return *this; +} + +bool Attribute::operator==(const Attribute& attr) const +{ + return name == attr.name && value == attr.value && prefix == attr.prefix; +} + +bool Attribute::operator!=(const Attribute& attr) const +{ + return !(operator==(attr)); +} + +} // namespace xml +} // namespace ngrest + diff --git a/core/xml/src/Attribute.h b/core/xml/src/Attribute.h new file mode 100644 index 0000000..701398a --- /dev/null +++ b/core/xml/src/Attribute.h @@ -0,0 +1,115 @@ +#ifndef NGREST_XML_ATTRIBUTE_H +#define NGREST_XML_ATTRIBUTE_H + +#include +#include "ngrestxmlexport.h" + +namespace ngrest { +namespace xml { + +//! xml-attribute +class NGREST_XML_EXPORT Attribute +{ +public: + //! copy constructor + /*! \param attr - source attribute + */ + Attribute(const Attribute& attr); + + //! initializing constructor + /*! \param name - attribute name + */ + Attribute(const std::string& name); + + //! initializing constructor + /*! \param name - attribute name + \param value - attribute value + */ + Attribute(const std::string& name, const std::string& value); + + //! initializing constructor + /*! \param name - attribute name + \param value - attribute value + \param prefix - attribute prefix + */ + Attribute(const std::string& name, const std::string& value, const std::string& prefix); + + + //! get prefix + /*! \return prefix + */ + const std::string& getPrefix() const; + + //! set prefix + /*! \param prefix - prefix + */ + void setPrefix(const std::string& prefix); + + //! get name + /*! \return name + */ + const std::string& getName() const; + + //! set name + /*! \param name - name + */ + void setName(const std::string& name); + + //! get attribute name with prefix + /*! \return attribute name with prefix + */ + std::string getPrefixName() const; + + //! get value + /*! \return value + */ + const std::string& getValue() const; + + //! set value + /*! \param value - value + */ + void setValue(const std::string& value); + + + //! get next sibling attribute + /*! \return next sibling attribute + */ + const Attribute* getNextSibling() const; + + //! get next sibling attribute + /*! \return next sibling attribute + */ + Attribute* getNextSibling(); + + + //! copy operator + /*! \param attr - source attribute + \return *this + */ + Attribute& operator=(const Attribute& attr); + + //! check whether the attributes are equal + /*! \param attr - other attribute + \return true, if attributes are equal + */ + bool operator==(const Attribute& attr) const; + + //! check whether the attributes are not equal + /*! \param attr - other attribute + \return true, if attributes are not equal + */ + bool operator!=(const Attribute& attr) const; + +private: + std::string prefix; //!< attribute namespace prefix + std::string name; //!< attribute name + std::string value; //!< attribute value + Attribute* nextSibling; //!< next attribute + friend class Element; +}; + +} // namespace xml +} // namespace ngrest + +#endif // NGREST_XML_ATTRIBUTE_H + diff --git a/core/xml/src/Declaration.cpp b/core/xml/src/Declaration.cpp new file mode 100644 index 0000000..c92d387 --- /dev/null +++ b/core/xml/src/Declaration.cpp @@ -0,0 +1,51 @@ +#include +#include "Attribute.h" +#include "Exception.h" +#include "Declaration.h" + +namespace ngrest { +namespace xml { + +Declaration::Declaration(): + version("1.0"), + encoding("UTF-8"), + standalone(Standalone::Undefined) +{ +} + +Declaration::~Declaration() +{ +} + +const std::string& Declaration::getVersion() const +{ + return version; +} + +void Declaration::setVersion(const std::string& version) +{ + this->version = version; +} + +const std::string& Declaration::getEncoding() const +{ + return encoding; +} + +void Declaration::setEncoding(const std::string& encoding) +{ + this->encoding = encoding; +} + +Declaration::Standalone Declaration::getStandalone() const +{ + return standalone; +} + +void Declaration::setStandalone(Declaration::Standalone standalone) +{ + this->standalone = standalone; +} + +} // namespace xml +} // namespace ngrest diff --git a/core/xml/src/Declaration.h b/core/xml/src/Declaration.h new file mode 100644 index 0000000..011098c --- /dev/null +++ b/core/xml/src/Declaration.h @@ -0,0 +1,71 @@ +#ifndef NGREST_XML_DECLARATION_H +#define NGREST_XML_DECLARATION_H + +#include +#include "ngrestxmlexport.h" + +namespace ngrest { +namespace xml { + +//! xml-document declaration +class NGREST_XML_EXPORT Declaration +{ +public: + enum class Standalone //! standalone + { + Undefined, //!< not defined + No, //!< standalone="no" + Yes //!< standalone="yes" + }; + +public: + //! constructor + Declaration(); + + //! destructor + ~Declaration(); + + //! get version + /*! \return version + */ + const std::string& getVersion() const; + + //! set version + /*! \param version - version + */ + void setVersion(const std::string& version); + + + //! get encoding + /*! \return encoding + */ + const std::string& getEncoding() const; + + //! set encoding + /*! \param encoding - encoding + */ + void setEncoding(const std::string& encoding); + + + //! get stand alone + /*! \return stand alone + */ + Standalone getStandalone() const; + + //! set stand alone + /*! \param standalone - standalone + */ + void setStandalone(Standalone standalone); + + +private: + std::string version; //!< xml document version(1.0) + std::string encoding; //!< encoding + Standalone standalone; //!< standalone declaration +}; + +} // namespace xml +} // namespace ngrest + +#endif // NGREST_XML_DECLARATION_H + diff --git a/core/xml/src/Document.cpp b/core/xml/src/Document.cpp new file mode 100644 index 0000000..1584c0f --- /dev/null +++ b/core/xml/src/Document.cpp @@ -0,0 +1,28 @@ +#include "Exception.h" +#include "Document.h" + +namespace ngrest { +namespace xml { + +const Declaration& Document::getDeclaration() const +{ + return declaration; +} + +Declaration& Document::getDeclaration() +{ + return declaration; +} + +const Element& Document::getRootElement() const +{ + return rootElement; +} + +Element& Document::getRootElement() +{ + return rootElement; +} + +} // namespace xml +} // namespace ngrest diff --git a/core/xml/src/Document.h b/core/xml/src/Document.h new file mode 100644 index 0000000..0da8963 --- /dev/null +++ b/core/xml/src/Document.h @@ -0,0 +1,44 @@ +#ifndef NGREST_XML_DOCUMENT_H +#define NGREST_XML_DOCUMENT_H + +#include "Declaration.h" +#include "Element.h" +#include "ngrestxmlexport.h" + +namespace ngrest { +namespace xml { + +//! XML Document +class NGREST_XML_EXPORT Document +{ +public: + //! get document declaration + /*! \return document declaration + */ + const Declaration& getDeclaration() const; + + //! get document declaration + /*! \return document declaration + */ + Declaration& getDeclaration(); + + //! get root Element + /*! \return root Element + */ + const Element& getRootElement() const; + + //! get root node + /*! \return root node + */ + Element& getRootElement(); + +private: + Declaration declaration; //!< xml-declaration + Element rootElement; //!< root element +}; + +} // namespace xml +} // namespace ngrest + + +#endif // NGREST_XML_DOCUMENT_H diff --git a/core/xml/src/Element.cpp b/core/xml/src/Element.cpp new file mode 100644 index 0000000..73b5155 --- /dev/null +++ b/core/xml/src/Element.cpp @@ -0,0 +1,984 @@ +#include +#include "Attribute.h" +#include "Namespace.h" +#include "Element.h" +#include "Exception.h" + +namespace ngrest { +namespace xml { + +Element::Element(Element* parent): + Node(Type::Element, parent), + firstAttribute(nullptr), + firstNamespace(nullptr), + firstChild(nullptr), + lastChild(nullptr) +{ +} + +Element::Element(const std::string& name, Element* parent): + Node(Type::Element, parent), + firstAttribute(nullptr), + firstNamespace(nullptr), + firstChild(nullptr), + lastChild(nullptr) +{ + setName(name); +} + +Element::Element(const std::string& name, const Value& value, Element* parent): + Node(Type::Element, parent), + firstAttribute(nullptr), + firstNamespace(nullptr), + firstChild(nullptr), + lastChild(nullptr) +{ + setName(name); + createText(value); +} + +Element::Element(const Element& element): + Node(Type::Element, nullptr), + firstAttribute(nullptr), + firstNamespace(nullptr), + firstChild(nullptr), + lastChild(nullptr) +{ + cloneElement(element); +} + +Element& Element::operator=(const Element& element) +{ + clear(); + return cloneElement(element); +} + +Element::~Element() +{ + clear(); +} + +Element* Element::cloneElement(bool recursive /*= true*/) const +{ + Element* clone = nullptr; + switch (type) { + case Type::Element: + clone = new Element; + break; + default: + NGREST_THROW_ASSERT("Can't create node clone"); + } + + try { + clone->cloneElement(*this, recursive); + } catch (...) { + delete clone; + throw; + } + + return clone; +} + +Element& Element::cloneElement(const Element& element, bool recursive) +{ + name = element.name; + prefix = element.prefix; + + // clone attributes + if (element.firstAttribute) { + Attribute* newAttribute = new Attribute(*element.firstAttribute); + firstAttribute = newAttribute; + + for (const Attribute* attribute = element.firstAttribute->nextSibling; + attribute; attribute = attribute->nextSibling) { + newAttribute->nextSibling = new Attribute(*attribute); + newAttribute = newAttribute->nextSibling; + } + } + + // clone namespaces + if (element.firstNamespace) { + // default namespace + Namespace* newNs = new Namespace(*element.firstNamespace); + firstNamespace = newNs; + + for (const Namespace* ns = element.firstNamespace->nextSibling; ns; ns = ns->nextSibling) { + newNs->nextSibling = new Namespace(*ns); + newNs = newNs->nextSibling; + } + + prefix = element.prefix; + } + + // clone children + if (recursive) { + for (const Node* child = element.firstChild; child; child = child->nextSibling) { + appendChild(child->clone()); + } + } + + return *this; +} + +const std::string& Element::getName() const +{ + return name; +} + +void Element::setName(const std::string& name) +{ + std::string::size_type pos = name.find_last_of(':'); + if (pos == std::string::npos) { + this->name = name; + prefix.erase(); + } else { + this->name.assign(name, pos + 1, std::string::npos); + prefix.assign(name, 0, pos); + } +} + +const std::string& Element::getPrefix() const +{ + return prefix; +} + +std::string Element::getPrefixName() const +{ + return prefix.empty() ? name : (prefix + ":" + name); +} + +std::string Element::GetChildrenText() const +{ + std::string text; + for (const Node* child = firstChild; child; child = child->nextSibling) + if (child->getType() == Type::Text) + text += child->getValue().asString(); + return text; +} + +const Value& Element::getValue() const +{ + static const Value empty; + + for (const Node* child = firstChild; child; child = child->nextSibling) + if (child->getType() == Type::Text) + return child->getValue(); + + return empty; +} + +const std::string& Element::getTextValue() const +{ + static const std::string empty; + + for (const Node* child = firstChild; child; child = child->nextSibling) + if (child->getType() == Type::Text) + return child->getTextValue(); + + return empty; +} + +void Element::setValue(const Value& value) +{ + Node* childText = nullptr; + { + Node* childToRemove = nullptr; + for (Node* child = firstChild; child; child = child->nextSibling) { + if (childToRemove) { + delete childToRemove; + childToRemove = nullptr; + } + + if (child->getType() == Type::Text) { + if (childText) { + childToRemove = child; + } else { + childText = child; + } + } + } + if (childToRemove) { + delete childToRemove; + } + } + + if (childText) { + childText->setValue(value); + } else { + createText(value); + } +} + +bool Element::isTextNull() const +{ + const Node* child = firstChild; + for (; child && child->getType() != Type::Text; child = child->nextSibling); + return !child; +} + +void Element::setTextIsNull() +{ + Node* childToRemove = nullptr; + for (Node* child = firstChild; child;) { + if (child->getType() == Type::Text) { + childToRemove = child; + child = child->nextSibling; + delete childToRemove; + } else { + child = child->nextSibling; + } + } +} + +bool Element::isEmpty() const +{ + return !firstChild || + ((firstChild == lastChild) && (firstChild->type == Type::Text) + && firstChild->getValue().asString().empty()); +} + +bool Element::isLeaf() const +{ + return !firstChild || + ((firstChild == lastChild) && (firstChild->type == Type::Text)); +} + +void Element::clear() +{ + if (firstAttribute) { + // destroy attributes + Attribute* attribute = firstAttribute; + Attribute* prevAttribute = nullptr; + while (attribute) { + prevAttribute = attribute; + attribute = attribute->nextSibling; + delete prevAttribute; + } + } + + if (firstNamespace) { + // destroy namespaces + Namespace* ns = firstNamespace; + Namespace* prevNs = nullptr; + while (ns) { + prevNs = ns; + ns = ns->nextSibling; + delete prevNs; + } + } + + if (firstChild) { + Node* childToRemove = nullptr; + for (Node* child = firstChild; child;) { + childToRemove = child; + child = child->nextSibling; + + childToRemove->parent = nullptr; // prevent unneeded detaching + delete childToRemove; + } + firstChild = nullptr; + lastChild = nullptr; + } +} + +// children management + +Element& Element::createElement() +{ + return appendChild(new Element()); +} + +Element& Element::createElement(const std::string& name) +{ + return appendChild(new Element(name)); +} + +Element& Element::createElement(const std::string& name, const Value& value) +{ + return appendChild(new Element(name, value)); +} + +Element& Element::createElementOnce(const std::string& name) +{ + Element* child = findChildElementByName(name); + if (child) + return *child; + + return appendChild(new Element(name)); +} + +Comment& Element::createComment() +{ + return appendChild(new Comment()); +} + +Comment& Element::createComment(const Value& value) +{ + return appendChild(new Comment(value)); +} + +Text& Element::createText() +{ + return appendChild(new Text()); +} + +Text& Element::createText(const Value& value) +{ + return appendChild(new Text(value)); +} + +Cdata& Element::createCdata() +{ + return appendChild(new Cdata()); +} + +Cdata& Element::createCdata(const Value& value) +{ + return appendChild(new Cdata(value)); +} + +Node& Element::insertChildBefore(Node* node, Node* before) +{ + NGREST_ASSERT_PARAM(node); + NGREST_ASSERT(node != this, "Can't append self as child"); + NGREST_ASSERT(!node->parent && !node->nextSibling && !node->previousSibling, + "Can't append node that is already in tree"); + + NGREST_ASSERT_PARAM(before); + NGREST_ASSERT(before->getParent() == this, "The 'Before' node belong to the different parent"); + NGREST_DEBUG_ASSERT(firstChild && lastChild, "Internal error"); + + if (before == firstChild) { + firstChild = node; + } else { + node->previousSibling = before->previousSibling; + before->previousSibling->nextSibling = node; + } + node->nextSibling = before; + before->previousSibling = node; + + node->parent = this; + + return *node; +} + +Element& Element::insertChildBefore(Element* node, Node* before) +{ + return static_cast(insertChildBefore(reinterpret_cast(node), before)); +} + +Comment& Element::insertChildBefore(Comment* node, Node* before) +{ + return static_cast(insertChildBefore(reinterpret_cast(node), before)); +} + +Text& Element::insertChildBefore(Text* node, Node* before) +{ + return static_cast(insertChildBefore(reinterpret_cast(node), before)); +} + +Cdata& Element::insertChildBefore(Cdata* node, Node* before) +{ + return static_cast(insertChildBefore(reinterpret_cast(node), before)); +} + +Node& Element::insertChildAfter(Node* node, Node* after) +{ + NGREST_ASSERT_PARAM(node); + NGREST_ASSERT(node != this, "Can't append self as child"); + NGREST_ASSERT(!node->parent && !node->nextSibling && !node->previousSibling, + "Can't append node that is already in tree"); + + NGREST_ASSERT_PARAM(after); + NGREST_ASSERT(after->getParent() == this, "The 'After' node belong to the different parent"); + NGREST_DEBUG_ASSERT(firstChild && lastChild, "Internal error"); + + if (after == lastChild) { + lastChild = node; + } else { + node->nextSibling = after->nextSibling; + after->nextSibling->previousSibling = node; + } + node->previousSibling = after; + after->nextSibling = node; + + node->parent = this; + + return *node; +} + +Element& Element::insertChildAfter(Element* node, Node* after) +{ + return static_cast(insertChildAfter(reinterpret_cast(node), after)); +} + +Comment& Element::insertChildAfter(Comment* node, Node* after) +{ + return static_cast(insertChildAfter(reinterpret_cast(node), after)); +} + +Text& Element::insertChildAfter(Text* node, Node* after) +{ + return static_cast(insertChildAfter(reinterpret_cast(node), after)); +} + +Cdata& Element::insertChildAfter(Cdata* node, Node* after) +{ + return static_cast(insertChildAfter(reinterpret_cast(node), after)); +} + +Node& Element::appendChild(Node* node) +{ + NGREST_ASSERT_PARAM(node); + NGREST_ASSERT(node != this, "Can't append self as child"); + NGREST_ASSERT(!node->parent && !node->nextSibling && !node->previousSibling, + "Can't append node that is already in tree"); + + if (!firstChild || !lastChild) { + NGREST_DEBUG_ASSERT(!firstChild && !lastChild, "Internal error"); + firstChild = node; + } else { + // append + NGREST_DEBUG_ASSERT(!lastChild->nextSibling, "Internal error"); + + lastChild->nextSibling = node; + node->previousSibling = lastChild; + } + + node->parent = this; + lastChild = node; + + return *node; +} + +Element& Element::appendChild(Element* node) +{ + return static_cast(appendChild(reinterpret_cast(node))); +} + +Comment& Element::appendChild(Comment* node) +{ + return static_cast(appendChild(reinterpret_cast(node))); +} + +Text& Element::appendChild(Text* node) +{ + return static_cast(appendChild(reinterpret_cast(node))); +} + +Cdata& Element::appendChild(Cdata* node) +{ + return static_cast(appendChild(reinterpret_cast(node))); +} + +void Element::removeChild(Node* node) +{ + NGREST_ASSERT_PARAM(node); + NGREST_ASSERT(node->getParent() == this, "This child node belong to the different parent"); + delete node; +} + +const Node* Element::getFirstChild() const +{ + return firstChild; +} + +Node* Element::getFirstChild() +{ + return firstChild; +} + +const Node* Element::getLastChild() const +{ + return lastChild; +} + +Node* Element::getLastChild() +{ + return lastChild; +} + +const Element* Element::getFirstChildElement() const +{ + const Node* node = firstChild; + for (; node && node->getType() != Type::Element; node = node->nextSibling); + return reinterpret_cast(node); +} + +Element* Element::getFirstChildElement() +{ + Node* node = firstChild; + for (; node && node->getType() != Type::Element; node = node->nextSibling); + return reinterpret_cast(node); +} + +const Element* Element::getLastChildElement() const +{ + const Node* node = lastChild; + for (; node && node->getType() != Type::Element; node = node->getPreviousSibling()); + return reinterpret_cast(node); +} + +Element* Element::getLastChildElement() +{ + Node* node = lastChild; + for (; node && node->getType() != Type::Element; node = node->getPreviousSibling()); + return reinterpret_cast(node); +} + +const Element* Element::findChildElementByName(const std::string& name) const +{ + const Element* firstChild = getFirstChildElement(); + return firstChild ? findChildElementByName(name, firstChild) : nullptr; +} + +Element* Element::findChildElementByName(const std::string& name) +{ + Element* firstChild = getFirstChildElement(); + return firstChild ? findChildElementByName(name, firstChild) : nullptr; +} + +const Element* Element::findChildElementByName(const std::string& name, const Element* begin) const +{ + if (begin) { + NGREST_ASSERT(begin->getParent() == this, "This child node belong to the different parent"); + + for (; begin && begin->name != name; begin = begin->getNextSiblingElement()); + } + return begin; +} + +Element* Element::findChildElementByName(const std::string& name, Element* begin) +{ + if (begin) { + NGREST_ASSERT(begin->getParent() == this, "This child node belong to the different parent"); + + for (; begin && begin->name != name; begin = begin->getNextSiblingElement()); + } + return begin; +} + +const Element& Element::getChildElementByName(const std::string& name) const +{ + const Element* element = findChildElementByName(name); + NGREST_ASSERT(element, "Child element with name [" + name + "] does not exists"); + return *element; +} + +Element& Element::getChildElementByName(const std::string& name) +{ + Element* element = findChildElementByName(name); + NGREST_ASSERT(element, "Child element with name [" + name + "] does not exists"); + return *element; +} + +unsigned Element::getChildrenCount() const +{ + unsigned result = 0; + for (Node* node = firstChild; node; node = node->nextSibling, ++result); + return result; +} + +unsigned Element::getChildrenElementsCount() const +{ + unsigned result = 0; + for (Node* node = firstChild; node; node = node->nextSibling) + if (node->getType() == Type::Element) + ++result; + return result; +} + + +// attributes management + +Attribute& Element::createAttribute(const std::string& name, const std::string& value) +{ + return appendAttribute(new Attribute(name, value)); +} + +Attribute& Element::createAttribute(const std::string& name, const std::string& value, + const std::string& prefix) +{ + return appendAttribute(new Attribute(name, value, prefix)); +} + +Attribute& Element::appendAttribute(const Attribute& attribute) +{ + return appendAttribute(new Attribute(attribute)); +} + +Attribute& Element::appendAttribute(Attribute* attribute) +{ + NGREST_ASSERT_PARAM(attribute); + + if (!firstAttribute) { + // there is no attributes yet. + firstAttribute = attribute; + } else { + // searching for last attribute + Attribute* lastAttribute = firstAttribute; + for (; lastAttribute->nextSibling; lastAttribute = lastAttribute->nextSibling); + lastAttribute->nextSibling = attribute; + } + + return *attribute; +} + +const Attribute* Element::findAttribute(const std::string& name) const +{ + const Attribute* attribute = firstAttribute; + for (; attribute && attribute->getName() != name; attribute = attribute->nextSibling); + return attribute; +} + +Attribute* Element::findAttribute(const std::string& name) +{ + Attribute* attribute = firstAttribute; + for (; attribute && attribute->getName() != name; attribute = attribute->nextSibling); + return attribute; +} + +const Attribute& Element::getAttribute(const std::string& name) const +{ + const Attribute* attribute = findAttribute(name); + NGREST_ASSERT(attribute, "Attribute with name [" + name + "] does not exists"); + return *attribute; +} + +Attribute& Element::getAttribute(const std::string& name) +{ + Attribute* attribute = findAttribute(name); + NGREST_ASSERT(attribute, "Attribute with name [" + name + "] does not exists"); + return *attribute; +} + +const std::string& Element::getAttributeValue(const std::string& name) const +{ + return getAttribute(name).getValue(); +} + +void Element::setAttributeValue(const std::string& name, const std::string& value) +{ + getAttribute(name).setValue(value); +} + +void Element::removeAttribute(Attribute* attribute) +{ + NGREST_ASSERT(firstAttribute, "There is no attributes in element"); + + if (firstAttribute == attribute) { + firstAttribute = firstAttribute->nextSibling; + } else { + // searching for attribute to remove + Attribute* prevAttribute = firstAttribute; + for (; prevAttribute->nextSibling && prevAttribute->nextSibling != attribute; + prevAttribute = prevAttribute->nextSibling); + + NGREST_ASSERT(prevAttribute->nextSibling, + "Attribute with name [" + attribute->getName() + "] does not exists"); + + prevAttribute->nextSibling = attribute->nextSibling; + } + delete attribute; +} + +void Element::removeAttributeByName(const std::string& name) +{ + NGREST_ASSERT(firstAttribute, "There is no attributes in element"); + + Attribute* attributeToRemove = nullptr; + if (firstAttribute->getName() == name) { + attributeToRemove = firstAttribute; + firstAttribute = firstAttribute->nextSibling; + } else { + // searching for attribute to remove + Attribute* prevAttribute = firstAttribute; + attributeToRemove = prevAttribute->nextSibling; + for (; attributeToRemove && attributeToRemove->getName() != name; + prevAttribute = attributeToRemove, attributeToRemove = attributeToRemove->nextSibling); + + prevAttribute->nextSibling = attributeToRemove->nextSibling; + } + + NGREST_ASSERT(attributeToRemove, "Attribute with name [" + name + "] does not exists"); + delete attributeToRemove; +} + +bool Element::isAttributeExists(const std::string& name) const +{ + return !!findAttribute(name); +} + + +const Attribute* Element::getFirstAttribute() const +{ + return firstAttribute; +} + +Attribute* Element::getFirstAttribute() +{ + return firstAttribute; +} + + + +// namespaces management + +Namespace& Element::declareDefaultNamespace(const std::string& uri) +{ + return declareNamespace(uri, ""); +} + +Namespace& Element::declareNamespace(const std::string& uri, const std::string& prefix) +{ + Namespace* prevNs = nullptr; + Namespace* ns = firstNamespace; + for (; ns && prefix != ns->getPrefix(); prevNs = ns, ns = ns->nextSibling); + if (ns) { + ns->setUri(uri); + } else { + ns = new Namespace(prefix, uri); + if (prevNs) { + prevNs->nextSibling = ns; + } else { + firstNamespace = ns; + } + } + + return *ns; +} + +Namespace& Element::declareNamespace(const Namespace& ns) +{ + return declareNamespace(ns.uri, ns.prefix); +} + +Namespace& Element::setNamespace(const std::string& uri, const std::string& prefix, + bool recursive /*= true*/) +{ + replacePrefix(prefix, recursive); + return declareNamespace(uri, prefix); +} + +Namespace& Element::setNamespace(const Namespace& ns, + bool recursive /*= true*/) +{ + replacePrefix(ns.prefix, recursive); + return declareNamespace(ns.uri, ns.prefix); +} + +const Namespace* Element::getNamespace() const +{ + return findNamespaceByPrefix(prefix); +} + +Namespace* Element::getNamespace() +{ + return findNamespaceByPrefix(prefix); +} + +const std::string& Element::getNamespacePrefixByUri(const std::string& uri) +{ + const Namespace* ns = findNamespaceByUri(uri); + NGREST_ASSERT(ns, "Namespace with URI [" + uri + "] is not declared in current element"); + return ns->getPrefix(); +} + +const std::string& Element::getNamespaceUriByPrefix(const std::string& prefix) +{ + const Namespace* ns = findNamespaceByPrefix(prefix); + NGREST_ASSERT(ns, "Namespace with prefix [" + prefix + "] is not declared in current element"); + return ns->getUri(); +} + +const Namespace* Element::findNamespaceByUri(const std::string& uri) const +{ + const Namespace* ns = firstNamespace; + for (; ns && uri != ns->getUri(); ns = ns->nextSibling); + return ns; +} + +Namespace* Element::findNamespaceByUri(const std::string& uri) +{ + Namespace* ns = firstNamespace; + for (; ns && uri != ns->getUri(); ns = ns->nextSibling); + return ns; +} + +const Namespace* Element::findNamespaceByPrefix(const std::string& prefix) const +{ + const Namespace* ns = firstNamespace; + for (; ns && prefix != ns->getPrefix(); ns = ns->nextSibling); + return ns; +} + +Namespace* Element::findNamespaceByPrefix(const std::string& prefix) +{ + Namespace* ns = firstNamespace; + for (; ns && prefix != ns->getPrefix(); ns = ns->nextSibling); + return ns; +} + +const Namespace* Element::getFirstNamespace() const +{ + return firstNamespace; +} + +Namespace* Element::getFirstNamespace() +{ + return firstNamespace; +} + +void Element::findElementNamespaceDeclarationByUri(const std::string& uri, + const Namespace** foundNamespace, + const Element** foundElement) const +{ + const Element* element = this; + const Namespace* ns = nullptr; + + for (; element; element = element->getParent()) { + ns = findNamespaceByUri(uri); + if (ns) + break; + } + + if (foundNamespace) + *foundNamespace = ns; + if (foundElement) + *foundElement = element; +} + +void Element::findElementNamespaceDeclarationByUri(const std::string& uri, + Namespace** foundNamespace, + Element** foundElement) +{ + Element* element = this; + Namespace* ns = nullptr; + + for (; element; element = element->getParent()) { + ns = findNamespaceByUri(uri); + if (ns) + break; + } + + if (foundNamespace) + *foundNamespace = ns; + if (foundElement) + *foundElement = element; +} + + +void Element::findElementNamespaceDeclarationByPrefix(const std::string& prefix, + const Namespace** foundNamespace, + const Element** foundElement) const +{ + const Element* element = this; + const Namespace* ns = nullptr; + + for (; element; element = element->getParent()) { + ns = element->findNamespaceByPrefix(prefix); + if (ns) + break; + } + + if (foundNamespace) + *foundNamespace = ns; + if (foundElement) + *foundElement = element; +} + +void Element::findElementNamespaceDeclarationByPrefix(const std::string& prefix, + Namespace** foundNamespace, + Element** foundElement) +{ + Element* element = this; + Namespace* ns = nullptr; + + for (; element; element = element->getParent()) { + ns = findNamespaceByPrefix(prefix); + if (ns) + break; + } + + if (foundNamespace) + *foundNamespace = ns; + if (foundElement) + *foundElement = element; +} + + +Namespace* Element::findNamespaceDeclarationByUri(const std::string& uri) +{ + Namespace* ns = nullptr; + findElementNamespaceDeclarationByUri(uri, &ns, nullptr); + return ns; +} + +const Namespace* Element::findNamespaceDeclarationByUri(const std::string& uri) const +{ + const Namespace* ns = nullptr; + findElementNamespaceDeclarationByUri(uri, &ns, nullptr); + return ns; +} + +Namespace* Element::findNamespaceDeclarationByPrefix(const std::string& prefix) +{ + Namespace* ns = nullptr; + findElementNamespaceDeclarationByPrefix(prefix, &ns, nullptr); + return ns; +} + +const Namespace* Element::findNamespaceDeclarationByPrefix(const std::string& prefix) const +{ + const Namespace* ns = nullptr; + findElementNamespaceDeclarationByPrefix(prefix, &ns, nullptr); + return ns; +} + + +Element* Element::findElementByNamespaceDeclarationUri(const std::string& uri) +{ + Element* element = nullptr; + findElementNamespaceDeclarationByUri(uri, nullptr, &element); + return element; +} + +const Element* Element::findElementByNamespaceDeclarationUri(const std::string& uri) const +{ + const Element* element = nullptr; + findElementNamespaceDeclarationByUri(uri, nullptr, &element); + return element; +} + +Element* Element::findElementByNamespaceDeclarationPrefix(const std::string& prefix) +{ + Element* element = nullptr; + findElementNamespaceDeclarationByPrefix(prefix, nullptr, &element); + return element; +} + +const Element* Element::findElementByNamespaceDeclarationPrefix(const std::string& prefix) const +{ + const Element* element = nullptr; + findElementNamespaceDeclarationByPrefix(prefix, nullptr, &element); + return element; +} + + +void Element::replacePrefix(const std::string& newPrefix, bool recursive) +{ + if (recursive) { + for (Element* element = getFirstChildElement(); element; + element = element->getNextSiblingElement()) { + if (element->prefix == prefix) { + element->replacePrefix(newPrefix, recursive); + } + } + } + + prefix = newPrefix; +} + +} // namespace xml +} // namespace ngrest + diff --git a/core/xml/src/Element.h b/core/xml/src/Element.h new file mode 100644 index 0000000..7178ebc --- /dev/null +++ b/core/xml/src/Element.h @@ -0,0 +1,699 @@ +#ifndef NGREST_XML_ELEMENT_H +#define NGREST_XML_ELEMENT_H + +#include +#include "Node.h" +#include "ngrestxmlexport.h" + +namespace ngrest { +namespace xml { + +class NGREST_XML_EXPORT Attribute; +class NGREST_XML_EXPORT Namespace; + +//! XML Element +class NGREST_XML_EXPORT Element: public Node +{ +public: + //! initializing constructor + /*! \param parent - parent node + */ + Element(Element* parent = nullptr); + + //! initializing constructor + /*! \param name - element name + \param parent - parent node + */ + Element(const std::string& name, Element* parent = nullptr); + + //! initializing constructor + /*! \param name - element name + \param value - element's text node value + \param parent - parent node + */ + Element(const std::string& name, const Value& value, Element* parent = nullptr); + + //! cloning constructor + /*! \param element - source element + */ + Element(const Element& element); + + //! cloning operator + /*! \param element - source element + \return *this + */ + Element& operator=(const Element& element); + + //! destructor + virtual ~Element(); + + + //! clone xml tree + /*! \param recursive - true - clone whole xml tree, false - clone this node only + \return cloned xml tree + */ + Element* cloneElement(bool recursive = true) const; + + //! clone xml tree to this element from given element + /*! \param element - source element + \param recursive - true - clone whole xml tree, false - clone this node only + \return *this + */ + Element& cloneElement(const Element& element, bool recursive = true); + + //! get element name + /*! \return const reference to element name + */ + const std::string& getName() const; + + //! set element name + /*! \param name - element name if form [prefix:]element_name + */ + void setName(const std::string& name); + + //! get prefix + /*! \return prefix + */ + const std::string& getPrefix() const; + + //! get node name with prefix + /*! \return node name with prefix + */ + std::string getPrefixName() const; + + + //! get concatenated text of children values + /*! \return text + */ + virtual std::string GetChildrenText() const; + + //! get first Text child content node + /*! simple method to access text child value + \return first child text, or empty string if no child text exists + */ + virtual const Value& getValue() const override; + + //! get node value + /*! \return node value + */ + virtual const std::string& getTextValue() const override; + + //! set node value + /*! simple method to access text child value + keep only one text child and set text value for it + \param value - new node value + */ + virtual void setValue(const Value& value) override; + + //! is text null + /*! \return true if no text children exists + */ + bool isTextNull() const; + + //! remove all text children + void setTextIsNull(); + + //! is element empty + /*! \return true if element have no child nodes + */ + bool isEmpty() const; + + //! is element have no child nodes or exactly one text child node + /*! \return true if element is leaf + */ + bool isLeaf() const; + + //! clear element for reuse + /*! remove all children, attributes, namespaces. + */ + void clear(); + + // children management + + //! create and append child element + /*! \return created element + */ + Element& createElement(); + + //! create and append child element + /*! \param name - element name + \return created element + */ + Element& createElement(const std::string& name); + + //! create and append child element + /*! \param name - element name + \param value - node value + \return created element + */ + Element& createElement(const std::string& name, const Value& value); + + //! create new child element if no child element with given name exists + /*! \param name - element name + \return created or already existing element + */ + Element& createElementOnce(const std::string& name); + + //! create and append child Comment + /*! \return created comment node + */ + Comment& createComment(); + + //! create and append child Comment + /*! \param value - node value + \return created comment node + */ + Comment& createComment(const Value& value); + + //! create and append child Text + /*! \return created text node + */ + Text& createText(); + + //! create and append child (Cdata, Comment or Text) + /*! \param value - node value + \return created text node + */ + Text& createText(const Value& value); + + //! create and append child Cdata type + /*! \return created CDATA node + */ + Cdata& createCdata(); + + //! create and append child Cdata type + /*! \param value - node value + \return created CDATA node + */ + Cdata& createCdata(const Value& value); + + + //! insert child node before element + /*! \param node - node to insert + \param before - existing child node to insert before + \return reference to appended child + */ + Node& insertChildBefore(Node* node, Node* before); + + //! insert child node before element + /*! \param node - node to insert + \param before - existing child node to insert before + \return reference to appended child + */ + Element& insertChildBefore(Element* node, Node* before); + + //! insert child node before element + /*! \param node - node to insert + \param before - existing child node to insert before + \return reference to appended child + */ + Comment& insertChildBefore(Comment* node, Node* before); + + //! insert child node before element + /*! \param node - node to insert + \param before - existing child node to insert before + \return reference to appended child + */ + Text& insertChildBefore(Text* node, Node* before); + + //! insert child node before element + /*! \param node - node to insert + \param before - existing child node to insert before + \return reference to appended child + */ + Cdata& insertChildBefore(Cdata* node, Node* before); + + + //! insert child node after element + /*! \param node - node to insert + \param after - existing child node to insert after + \return reference to appended child + */ + Node& insertChildAfter(Node* node, Node* after); + + //! insert child node after element + /*! \param node - node to insert + \param after - existing child node to insert after + \return reference to appended child + */ + Element& insertChildAfter(Element* node, Node* after); + + //! insert child node after element + /*! \param node - node to insert + \param after - existing child node to insert after + \return reference to appended child + */ + Comment& insertChildAfter(Comment* node, Node* after); + + //! insert child node after element + /*! \param node - node to insert + \param after - existing child node to insert after + \return reference to appended child + */ + Text& insertChildAfter(Text* node, Node* after); + + //! insert child node after element + /*! \param node - node to insert + \param after - existing child node to insert after + \return reference to appended child + */ + Cdata& insertChildAfter(Cdata* node, Node* after); + + + //! append child node + /*! \param node - child node + \return reference to appended child + */ + Node& appendChild(Node* node); + + //! append child element + /*! \param node - child element + \return reference to appended child + */ + Element& appendChild(Element* node); + + //! append child node + /*! \param node - child node + \return reference to appended child + */ + Comment& appendChild(Comment* node); + + //! append child node + /*! \param node - child node + \return reference to appended child + */ + Text& appendChild(Text* node); + + //! append child node + /*! \param node - child node + \return reference to appended child + */ + Cdata& appendChild(Cdata* node); + + + //! remove and free child node + /*! this function is eqivalent to "delete node" + \param node - child node to remove + */ + void removeChild(Node* node); + + + //! get first child + /*! \return first child or nullptr if there is no children + */ + const Node* getFirstChild() const; + + //! get first child + /*! \return first child or nullptr if there is no children + */ + Node* getFirstChild(); + + //! get last child + /*! \return last child or nullptr if there is no children + */ + const Node* getLastChild() const; + + //! get last child + /*! \return last child or nullptr if there is no children + */ + Node* getLastChild(); + + + //! get first child element + /*! \return first child element or nullptr if there is no child elements + */ + const Element* getFirstChildElement() const; + + //! get first child element + /*! \return first child element or nullptr if there is no child elements + */ + Element* getFirstChildElement(); + + //! get last child element + /*! \return last child element or nullptr if there is no child elements + */ + const Element* getLastChildElement() const; + + //! get last child element + /*! \return last child element or nullptr if there is no child elements + */ + Element* getLastChildElement(); + + + //! find child element by name + /*! \param name - child element name + \return child element or nullptr if no child found + */ + const Element* findChildElementByName(const std::string& name) const; + + //! find child element by name + /*! \param name - child element name + \return child element or nullptr if no child found + */ + Element* findChildElementByName(const std::string& name); + + //! find child element by name + /*! \param name - child element name + \param begin - begin search from + \return child element or nullptr if no child found + */ + const Element* findChildElementByName(const std::string& name, const Element* begin) const; + + //! find child element by name + /*! \param name - child element name + \param begin - begin search from + \return child element or nullptr if no child found + */ + Element* findChildElementByName(const std::string& name, Element* begin); + + //! get child element by name + /*! throws an exception if no child found + \param name - child element name + \return const reference to child element + */ + const Element& getChildElementByName(const std::string& name) const; + + //! get child element by name + /*! throws an exception if no child found + \param name - child element name + \return reference to child element + */ + Element& getChildElementByName(const std::string& name); + + + //! calculate the number of children + /*! \return number of children + */ + unsigned getChildrenCount() const; + + //! calculate the number of children elements + /*! \return number of children elements + */ + unsigned getChildrenElementsCount() const; + + + // attributes management + + //! create attribute with name and value + /*! \param name - attribute name + \param value - attribute value + */ + Attribute& createAttribute(const std::string& name, const std::string& value); + + //! create attribute with name, value and prefix + /*! \param name - attribute name + \param value - attribute value + \param prefix - attribute prefix + */ + Attribute& createAttribute(const std::string& name, const std::string& value, + const std::string& prefix); + + //! append attribute's copy + /*! \param attribute - attribute + */ + Attribute& appendAttribute(const Attribute& attribute); + + //! append attribute + /*! \param attribute - attribute + */ + Attribute& appendAttribute(Attribute* attribute); + + + //! find attribute by name + /*! \param name - attribute name + \return const iterator to attribute + */ + const Attribute* findAttribute(const std::string& name) const; + + //! find attribute by name + /*! \param name - attribute name + \return iterator to attribute + */ + Attribute* findAttribute(const std::string& name); + + //! get attribute by name + /*! throws an exception if no attribute found + \param name - attribute name + \return attribute value + */ + const Attribute& getAttribute(const std::string& name) const; + + //! get attribute by name + /*! throws an exception if no attribute found + \param name - attribute name + \return attribute value + */ + Attribute& getAttribute(const std::string& name); + + //! get attribute value by name + /*! throws an exception if no attribute found + \param name - attribute name + \return attribute value + */ + const std::string& getAttributeValue(const std::string& name) const; + + //! get attribute value by name + /*! throws an exception if no attribute found + \param name - attribute name + \param value - attribute value + \return attribute value + */ + void setAttributeValue(const std::string& name, const std::string& value); + + + //! remove attribute + /*! \param attribute - attribute to remove + */ + void removeAttribute(Attribute* attribute); + + //! remove attribute by name + /*! \param name - attribute name to remove + */ + void removeAttributeByName(const std::string& name); + + //! is attribute exists + /*! \param name - attribute name + \return true, if attribute exists + */ + bool isAttributeExists(const std::string& name) const; + + + //! get first attribute + /*! \return pointer to first attribute + */ + const Attribute* getFirstAttribute() const; + + //! get first attribute + /*! \return pointer to first attribute + */ + Attribute* getFirstAttribute(); + + + // namespaces management + + //! declare default namespace + /*! \param uri - namespace uri + */ + Namespace& declareDefaultNamespace(const std::string& uri); + + //! declare new namespace / modify existing + /*! \param uri - namespace uri + \param prefix - namespace prefix + */ + Namespace& declareNamespace(const std::string& uri, const std::string& prefix); + + //! declare new namespace (create clone) / modify existing + /*! \param ns - namespace + \return new namespace + */ + Namespace& declareNamespace(const Namespace& ns); + + //! declare and set new namespace + /*! \param uri - namespace uri + \param prefix - namespace prefix + \param recursive - set namespace for children too + \return new namespace + */ + Namespace& setNamespace(const std::string& uri, const std::string& prefix, + bool recursive = true); + + //! declare and set new namespace (copy) + /*! \param ns - namespace + \param recursive - set namespace for children too + \return new namespace + */ + Namespace& setNamespace(const Namespace& ns, bool recursive = true); + + + //! get namespace of element + /*! \return namespace of element + */ + const Namespace* getNamespace() const; + + //! get namespace of element + /*! \return namespace of element + */ + Namespace* getNamespace(); + + //! get namespace prefix by uri + /*! \param uri - uri + \return namespace prefix + */ + const std::string& getNamespacePrefixByUri(const std::string& uri); + + //! get namespace uri by prefix + /*! \param prefix - prefix + \return namespace uri + */ + const std::string& getNamespaceUriByPrefix(const std::string& prefix); + + //! find namespace by uri + /*! \param uri - namespace uri + \return namespace or nullptr, if no namespace found + */ + const Namespace* findNamespaceByUri(const std::string& uri) const; + + //! find namespace by uri + /*! \param uri - namespace uri + \return namespace or nullptr, if no namespace found + */ + Namespace* findNamespaceByUri(const std::string& uri); + + //! find namespace by prefix + /*! \param prefix - prefix + \return namespace or nullptr, if no namespace found + */ + const Namespace* findNamespaceByPrefix(const std::string& prefix) const; + + //! find namespace by prefix + /*! \param prefix - prefix + \return namespace or nullptr, if no namespace found + */ + Namespace* findNamespaceByPrefix(const std::string& prefix); + + //! get first namespace + /*! \return pointer to first namespace + */ + const Namespace* getFirstNamespace() const; + + //! get first declared namespace + /*! \return pointer to first declared namespace + */ + Namespace* getFirstNamespace(); + + + //! find element namespace declaration by uri + /*! start to find from current element and go up the hierarchy + \param uri - uri + \param foundNamespace - (optional) found namespace or nullptr + \param foundElement - (optional) found element or nullptr + */ + void findElementNamespaceDeclarationByUri(const std::string& uri, + const Namespace** foundNamespace, + const Element** foundElement) const; + + //! find element namespace declaration by uri + /*! start to find from current element and go up the hierarchy + \param uri - uri + \param foundNamespace - (optional) found namespace or nullptr + \param foundElement - (optional) found element or nullptr + */ + void findElementNamespaceDeclarationByUri(const std::string& uri, + Namespace** foundNamespace, + Element** foundElement); + + //! find element namespace declaration by prefix + /*! start to find from current element and go up the hierarchy + \param prefix - prefix + \param foundNamespace - (optional) found namespace or nullptr + \param foundElement - (optional) found element or nullptr + */ + void findElementNamespaceDeclarationByPrefix(const std::string& prefix, + const Namespace** foundNamespace, + const Element** foundElement) const; + + //! find element namespace declaration by prefix + /*! start to find from current element and go up the hierarchy + \param prefix - prefix + \param foundNamespace - (optional) found namespace or nullptr + \param foundElement - (optional) found element or nullptr + */ + void findElementNamespaceDeclarationByPrefix(const std::string& prefix, + Namespace** foundNamespace, + Element** foundElement); + + + //! find namespace declaration + /*! convinent wrapper for findElementNamespaceDeclarationByUri + \param uri - namespace uri to find + \return found namespace or nullptr + */ + const Namespace* findNamespaceDeclarationByUri(const std::string& uri) const; + + //! find namespace declaration + /*! convinent wrapper for findElementNamespaceDeclarationByUri + \param uri - namespace uri to find + \return found namespace or nullptr + */ + Namespace* findNamespaceDeclarationByUri(const std::string& uri); + + //! find element where given namespace is declared + /*! convinent wrapper for findElementNamespaceDeclarationByPrefix + \param prefix - prefix + \return element where this namespace is declared + */ + const Namespace* findNamespaceDeclarationByPrefix(const std::string& prefix) const; + + //! find element where given namespace is declared + /*! convinent wrapper for findElementNamespaceDeclarationByPrefix + \param prefix - prefix + \return element where this namespace is declared + */ + Namespace* findNamespaceDeclarationByPrefix(const std::string& prefix); + + + //! find element where given namespace is declared + /*! convinent wrapper for findElementNamespaceDeclarationByUri + \param uri - uri + \return element where this namespace is declared + */ + const Element* findElementByNamespaceDeclarationUri(const std::string& uri) const; + + //! find element where given namespace is declared + /*! convinent wrapper for findElementNamespaceDeclarationByUri + \param uri - uri + \return element where this namespace is declared + */ + Element* findElementByNamespaceDeclarationUri(const std::string& uri); + + //! find element where given namespace is declared + /*! convinent wrapper for findElementNamespaceDeclarationByPrefix + \param prefix - prefix + \return element where this namespace is declared + */ + const Element* findElementByNamespaceDeclarationPrefix(const std::string& prefix) const; + + //! find element where given namespace is declared + /*! convinent wrapper for findElementNamespaceDeclarationByPrefix + \param prefix - prefix + \return element where this namespace is declared + */ + Element* findElementByNamespaceDeclarationPrefix(const std::string& prefix); + +private: + void replacePrefix(const std::string& newPrefix, bool recursive); + +private: + std::string name; //!< element name + Attribute* firstAttribute; //!< first attribute + Namespace* firstNamespace; //!< first namespace + std::string prefix; //!< namespace prefix of element + Node* firstChild; //!< first child + Node* lastChild; //!< last child + friend class Node; +}; + +} // namespace xml +} // namespace ngrest + +#endif // #ifndef NGREST_XML_ELEMENT_H diff --git a/core/xml/src/Exception.h b/core/xml/src/Exception.h new file mode 100644 index 0000000..773eca5 --- /dev/null +++ b/core/xml/src/Exception.h @@ -0,0 +1,41 @@ +#ifndef NGREST_XML_EXCEPTION_H +#define NGREST_XML_EXCEPTION_H + +#include +#include +#include "ngrestxmlexport.h" + +//! throw xml exception +#define NGREST_XML_THROW(DESCRIPTION, XMLFILE, XMLLINE) \ + throw ::ngrest::xml::XmlException(NGREST_FILE_LINE, __FUNCTION__, DESCRIPTION, XMLFILE, XMLLINE); + +//! assert expression +#define NGREST_XML_ASSERT(EXPRESSION, DESCRIPTION, XMLFILE, XMLLINE) \ + if (!(EXPRESSION)) NGREST_XML_THROW(DESCRIPTION, XMLFILE, XMLLINE) + +namespace ngrest { +namespace xml { + +//! ngrest exception +class XmlException: public ::ngrest::Exception +{ +public: + //! exception constructor + /*! \param fileLine - source file name and line number + \param function - function signature + \param description - description + \param xmlFile - xml file name where error occured + \param xmlLine - xml file line where error occured + */ + inline XmlException(const char* fileLine, const char* function, std::string description, + const std::string& xmlFile, int xmlLine): + Exception(fileLine, function, description.append(": ") + .append(xmlFile).append(":").append(toString(xmlLine))) + { + } +}; + +} // namespace xml +} // namespace ngrest + +#endif // #ifndef NGREST_XML_EXCEPTION_H diff --git a/core/xml/src/Namespace.cpp b/core/xml/src/Namespace.cpp new file mode 100644 index 0000000..66f2a9e --- /dev/null +++ b/core/xml/src/Namespace.cpp @@ -0,0 +1,72 @@ +#include "Namespace.h" + +namespace ngrest { +namespace xml { + +Namespace::Namespace(const Namespace& ns): + prefix(ns.prefix), + uri(ns.uri), + nextSibling(nullptr) +{ +} + +Namespace::Namespace(const std::string& prefix_, const std::string& uri_): + prefix(prefix_), + uri(uri_), + nextSibling(nullptr) +{ +} + +Namespace& Namespace::operator=(const Namespace& ns) +{ + prefix = ns.prefix; + uri = ns.uri; + return *this; +} + + +const std::string& Namespace::getPrefix() const +{ + return prefix; +} + +void Namespace::setPrefix(const std::string& prefix) +{ + this->prefix = prefix; +} + + +const std::string& Namespace::getUri() const +{ + return uri; +} + +void Namespace::setUri(const std::string& uri) +{ + this->uri = uri; +} + +const Namespace* Namespace::getNextSibling() const +{ + return nextSibling; +} + +Namespace* Namespace::getNextSibling() +{ + return nextSibling; +} + + +bool Namespace::operator==(const Namespace& ns) const +{ + return prefix == ns.prefix && uri == ns.uri; +} + +bool Namespace::operator!=(const Namespace& ns) const +{ + return !(operator==(ns)); +} + +} // namespace xml +} // namespace ngrest + diff --git a/core/xml/src/Namespace.h b/core/xml/src/Namespace.h new file mode 100644 index 0000000..44ecad3 --- /dev/null +++ b/core/xml/src/Namespace.h @@ -0,0 +1,87 @@ +#ifndef NGREST_XML_NAMESPACE_H +#define NGREST_XML_NAMESPACE_H + +#include +#include +#include "ngrestxmlexport.h" + +namespace ngrest { +namespace xml { + +//! xml namespace +class NGREST_XML_EXPORT Namespace +{ +public: + //! copy constructor + Namespace(const Namespace& ns); + + //! constructor + /*! \param prefix - prefix + \param uri - uri + */ + Namespace(const std::string& prefix, const std::string& uri); + + + //! copy operator + /*! \param ns - source namespace + \return *this + */ + Namespace& operator=(const Namespace& ns); + + + //! get prefix + /*! \return prefix + */ + const std::string& getPrefix() const; + + //! set prefix + /*! \param prefix - prefix + */ + void setPrefix(const std::string& prefix); + + + //! get uri + /*! \return uri + */ + const std::string& getUri() const; + + //! set uri + /*! \param uri - uri + */ + void setUri(const std::string& uri); + + + //! get next sibling namespace + /*! \return next sibling namespace + */ + const Namespace* getNextSibling() const; + + //! get next sibling namespace + /*! \return next sibling namespace + */ + Namespace* getNextSibling(); + + + //! check whether the namespaces are equal + /*! \param ns - other namespace + \return true, if namespaces are equal + */ + bool operator==(const Namespace& ns) const; + + //! check whether the namespaces are not equal + /*! \param ns - other namespace + \return true, if namespaces are not equal + */ + bool operator!=(const Namespace& ns) const; + +private: + std::string prefix; //!< prefix + std::string uri; //!< uri + Namespace* nextSibling; //!< next namespace + friend class Element; +}; + +} // namespace xml +} // namespace ngrest + +#endif // NGREST_XML_NAMESPACE_H diff --git a/core/xml/src/Node.cpp b/core/xml/src/Node.cpp new file mode 100644 index 0000000..ea211e9 --- /dev/null +++ b/core/xml/src/Node.cpp @@ -0,0 +1,204 @@ +#include "Exception.h" +#include "Element.h" +#include "Node.h" + +namespace ngrest { +namespace xml { + +Node::Node(Type type_, Element* parent_): + type(type_), + parent(parent_), + nextSibling(nullptr), + previousSibling(nullptr) +{ + if (parent_) + parent_->appendChild(this); +} + +Node::Node(Type type_, const Value& value_, Element* parent_): + type(type_), + value(value_), + parent(parent_), + nextSibling(nullptr), + previousSibling(nullptr) +{ + if (parent_) + parent_->appendChild(this); +} + +Node::~Node() +{ + // detach only if parent set + if (parent) + Detach(); +} + + +Node::Type Node::getType() const +{ + return type; +} + +const Value& Node::getValue() const +{ + return value; +} + +const std::string& Node::getTextValue() const +{ + return value.asString(); +} + +void Node::setValue(const Value& value) +{ + this->value = value; +} + +const Element* Node::getParent() const +{ + return parent; +} + +Element* Node::getParent() +{ + return parent; +} + +Node* Node::clone() const +{ + Node* clone = nullptr; + switch (type) { + case Type::Comment: + clone = new Comment(value); + break; + case Type::Text: + clone = new Text(value); + break; + case Type::Cdata: + clone = new Cdata(value); + break; + case Type::Element: + clone = reinterpret_cast(this)->cloneElement(); + break; + default: + NGREST_THROW_ASSERT("Can't create node clone"); + } + + return clone; +} + +const Element& Node::getElement() const +{ + NGREST_ASSERT(type == Type::Element, "This node is not an Element"); + return static_cast(*this); +} + +Element& Node::getElement() +{ + NGREST_ASSERT(type == Type::Element, "This node is not an Element"); + return static_cast(*this); +} + + +const Node* Node::getNextSibling() const +{ + return nextSibling; +} + +Node* Node::getNextSibling() +{ + return nextSibling; +} + +const Node* Node::getPreviousSibling() const +{ + return previousSibling; +} + +Node* Node::getPreviousSibling() +{ + return previousSibling; +} + +const Element* Node::getNextSiblingElement() const +{ + const Node* node = nextSibling; + for (; node && node->getType() != Type::Element; node = node->nextSibling); + return reinterpret_cast(node); +} + +Element* Node::getNextSiblingElement() +{ + Node* node = nextSibling; + for (; node && node->getType() != Type::Element; node = node->nextSibling); + return reinterpret_cast(node); +} + +const Element* Node::getPreviousSiblingElement() const +{ + const Node* node = previousSibling; + for (; node && node->getType() != Type::Element; node = node->previousSibling); + return reinterpret_cast(node); +} + +Element* Node::getPreviousSiblingElement() +{ + Node* node = previousSibling; + for (; node && node->getType() != Type::Element; node = node->previousSibling); + return reinterpret_cast(node); +} + +Node* Node::Detach() +{ + if (parent) { + if (parent->firstChild == this) + parent->firstChild = nextSibling; + if (parent->lastChild == this) + parent->lastChild = previousSibling; + parent = nullptr; + } + if (nextSibling) + nextSibling->previousSibling = previousSibling; + if (previousSibling) + previousSibling->nextSibling = nextSibling; + nextSibling = nullptr; + previousSibling = nullptr; + + return this; +} + + +Comment::Comment(Element* parent): + Node(Type::Comment, parent) +{ +} + +Comment::Comment(const std::string& value, Element* parent): + Node(Type::Comment, value, parent) +{ +} + + +Text::Text(Element* parent): + Node(Type::Text, parent) +{ +} + +Text::Text(const std::string& value, Element* parent): + Node(Type::Text, value, parent) +{ +} + + +Cdata::Cdata(Element* parent): + Node(Type::Cdata, parent) +{ +} + +Cdata::Cdata(const std::string& value, Element* parent): + Node(Type::Cdata, value, parent) +{ +} + +} // namespace xml +} // namespace ngrest diff --git a/core/xml/src/Node.h b/core/xml/src/Node.h new file mode 100644 index 0000000..1756ab5 --- /dev/null +++ b/core/xml/src/Node.h @@ -0,0 +1,194 @@ +#ifndef NGREST_XML_NODE_H +#define NGREST_XML_NODE_H + +#include +#include "Value.h" +#include "ngrestxmlexport.h" + +namespace ngrest { +namespace xml { + +class Element; + +//! XML Node +class NGREST_XML_EXPORT Node +{ +public: + enum class Type //! node type + { + Unknown, //!< invalid/unknown type + Element, //!< element + Comment, //!< comment + Text, //!< text + Cdata //!< CDATA section + }; + +public: + //! destructor + virtual ~Node(); + + //! get node type + /*! \return node type + */ + Type getType() const; + + //! get node value + /*! \return node value + */ + virtual const Value& getValue() const; + + //! get node value + /*! \return node value + */ + virtual const std::string& getTextValue() const; + + //! set node value + /*! \param value - new node value + */ + virtual void setValue(const Value& value); + + //! get parent element + /*! \return pointer to parent node or nullptr + */ + const Element* getParent() const; + + //! get parent element + /*! \return pointer to parent node or nullptr + */ + Element* getParent(); + + + //! clone node + /*! \return cloned node + */ + Node* clone() const; + + + //! get element of this node (only if node type = Element) + /*! \return element + */ + const Element& getElement() const; + + //! get element of this node (only if node type = Element) + /*! \return element + */ + Element& getElement(); + + + //! get next sibling node + /*! \return next sibling node or nullptr if current node is last + */ + const Node* getNextSibling() const; + + //! get next sibling node + /*! \return next sibling node or nullptr if current node is last + */ + Node* getNextSibling(); + + //! get previous sibling node + /*! \return previous sibling node or nullptr if current node is first + */ + const Node* getPreviousSibling() const; + + //! get previous sibling node + /*! \return previous sibling node or nullptr if current node is first + */ + Node* getPreviousSibling(); + + + //! get next sibling element + /*! \return next sibling element or nullptr if current element is last + */ + const Element* getNextSiblingElement() const; + + //! get next sibling element + /*! \return next sibling element or nullptr if current element is last + */ + Element* getNextSiblingElement(); + + //! get previous sibling element + /*! \return previous sibling element or nullptr if current element is first + */ + const Element* getPreviousSiblingElement() const; + + //! get previous sibling element + /*! \return previous sibling element or nullptr if current element is first + */ + Element* getPreviousSiblingElement(); + + + //! detach node from tree + Node* Detach(); + +protected: + Node(Type type, Element* parent); + Node(Type type, const Value& value, Element* parent); + +private: + Node(const Node& node); + Node& operator=(const Node& node); + +private: + Type type; //!< node type + Value value; //!< node value + Element* parent; //!< parent element + Node* nextSibling; //!< next sibling + Node* previousSibling; //!< previous sibling + friend class Element; +}; + + +//! Comment node +class NGREST_XML_EXPORT Comment: public Node +{ +public: + //! constructor + /*! \param parent - parent element + */ + Comment(Element* parent = nullptr); + + //! constructor + /*! \param value - node value + \param parent - parent element + */ + Comment(const std::string& value, Element* parent = nullptr); +}; + + +//! Text node +class NGREST_XML_EXPORT Text: public Node +{ +public: + //! constructor + /*! \param parent - parent element + */ + Text(Element* parent = nullptr); + + //! constructor + /*! \param value - node value + \param parent - parent element + */ + Text(const std::string& value, Element* parent = nullptr); +}; + + +//! CDATA node +class NGREST_XML_EXPORT Cdata: public Node +{ +public: + //! constructor + /*! \param parent - parent element + */ + Cdata(Element* parent = nullptr); + + //! constructor + /*! \param value - node value + \param parent - parent element + */ + Cdata(const std::string& value, Element* parent = nullptr); +}; + +} // namespace xml +} // namespace ngrest + +#endif // #ifndef NGREST_XML_NODE_H diff --git a/core/xml/src/Value.cpp b/core/xml/src/Value.cpp new file mode 100644 index 0000000..c126ebd --- /dev/null +++ b/core/xml/src/Value.cpp @@ -0,0 +1,308 @@ +#include +#include +#include "Value.h" + +namespace ngrest { +namespace xml { + +Value::Value() +{ +} + +Value::Value(const Value& other): + value(other.value) +{ +} + +Value::Value(const std::string& value_): + value(value_) +{ +} + +Value::Value(const char* value_): + value(value_ ? value_ : "") +{ +} + +Value::Value(float value_): + value(toString(value_)) +{ +} + +Value::Value(double value_): + value(toString(value_)) +{ +} + +Value::Value(byte value_): + value(toString(value_)) +{ +} + +Value::Value(int value_): + value(toString(value_)) +{ +} + +Value::Value(short value_): + value(toString(value_)) +{ +} + +Value::Value(long value_): + value(toString(value_)) +{ +} + +Value::Value(long long value_): + value(toString(value_)) +{ +} + +Value::Value(unsignedByte value_): + value(toString(value_)) +{ +} + +Value::Value(unsigned int value_): + value(toString(value_)) +{ +} + +Value::Value(unsigned short value_): + value(toString(value_)) +{ +} + +Value::Value(unsigned long value_): + value(toString(value_)) +{ +} + +Value::Value(unsigned long long value_): + value(toString(value_)) +{ +} + +Value::Value(bool value_): + value(value_ ? "true" : "false") +{ +} + + +Value& Value::operator=(const Value& other) +{ + this->value = other.value; + return *this; +} + + +Value& Value::operator=(const std::string& value) +{ + this->value = value; + return *this; +} + +Value& Value::operator=(const char* value) +{ + this->value = value ? value : ""; + return *this; +} + +Value& Value::operator=(float value) +{ + toString(value, this->value); + return *this; +} + +Value& Value::operator=(double value) +{ + toString(value, this->value); + return *this; +} + +Value& Value::operator=(byte value) +{ + toString(value, this->value); + return *this; +} + +Value& Value::operator=(int value) +{ + toString(value, this->value); + return *this; +} + +Value& Value::operator=(short value) +{ + toString(value, this->value); + return *this; +} + +Value& Value::operator=(long value) +{ + toString(value, this->value); + return *this; +} + +Value& Value::operator=(long long value) +{ + toString(value, this->value); + return *this; +} + +Value& Value::operator=(unsignedByte value) +{ + toString(value, this->value); + return *this; +} + +Value& Value::operator=(unsigned int value) +{ + toString(value, this->value); + return *this; +} + +Value& Value::operator=(unsigned short value) +{ + toString(value, this->value); + return *this; +} + +Value& Value::operator=(unsigned long value) +{ + toString(value, this->value); + return *this; +} + +Value& Value::operator=(unsigned long long value) +{ + toString(value, this->value); + return *this; +} + +Value& Value::operator=(bool value) +{ + this->value = value ? "true" : "false"; + return *this; +} + + +Value::operator const std::string&() const +{ + return value; +} + +Value::operator float() const +{ + float result = 0; + fromString(this->value, result); + return result; +} + +Value::operator double() const +{ + double result = 0; + fromString(this->value, result); + return result; +} + +Value::operator byte() const +{ + byte result = 0; + fromString(this->value, result); + return result; +} + +Value::operator int() const +{ + int result = 0; + fromString(this->value, result); + return result; +} + +Value::operator short() const +{ + short result = 0; + fromString(this->value, result); + return result; +} + +Value::operator long() const +{ + long result = 0; + fromString(this->value, result); + return result; +} + +Value::operator long long() const +{ + long long result = 0; + fromString(this->value, result); + return result; +} + +Value::operator unsignedByte() const +{ + unsignedByte result = 0; + fromString(this->value, result); + return result; +} + +Value::operator unsigned int() const +{ + unsigned int result = 0; + fromString(this->value, result); + return result; +} + +Value::operator unsigned short() const +{ + unsigned short result = 0; + fromString(this->value, result); + return result; +} + +Value::operator unsigned long() const +{ + unsigned long result = 0; + fromString(this->value, result); + return result; +} + +Value::operator unsigned long long() const +{ + unsigned long long result = 0; + fromString(this->value, result); + return result; +} + +Value::operator bool() const +{ + return value == "true" || value == "1"; +} + + +Value::operator std::string&() +{ + return value; +} + +const std::string& Value::asString() const +{ + return value; +} + +std::string& Value::asString() +{ + return value; +} + +bool Value::operator==(const Value& value) const +{ + return value == value.value; +} + + +} +} diff --git a/core/xml/src/Value.h b/core/xml/src/Value.h new file mode 100644 index 0000000..46a66fe --- /dev/null +++ b/core/xml/src/Value.h @@ -0,0 +1,282 @@ +#ifndef NGREST_XML_VALUE_H +#define NGREST_XML_VALUE_H + +#include +#include "ngrestxmlexport.h" + +namespace ngrest { +namespace xml { + + typedef char byte; + typedef unsigned char unsignedByte; + + //! xml value + class NGREST_XML_EXPORT Value + { + public: + //! default constructor + Value(); + + //! initializing constructor + /*! \param other - value + */ + explicit Value(const Value& other); + + //! initializing constructor + /*! \param value - value + */ + Value(const std::string& value); + + //! initializing constructor + /*! \param value - value + */ + Value(const char* value); + + //! initializing constructor + /*! \param value - value + */ + Value(float value); + + //! initializing constructor + /*! \param value - value + */ + Value(double value); + + //! initializing constructor + /*! \param value - value + */ + Value(byte value); + + //! initializing constructor + /*! \param value - value + */ + Value(int value); + + //! initializing constructor + /*! \param value - value + */ + Value(short value); + + //! initializing constructor + /*! \param value - value + */ + Value(long value); + + //! initializing constructor + /*! \param value - value + */ + Value(long long value); + + //! initializing constructor + /*! \param value - value + */ + Value(unsignedByte value); + + //! initializing constructor + /*! \param value - value + */ + Value(unsigned int value); + + //! initializing constructor + /*! \param value - value + */ + Value(unsigned short value); + + //! initializing constructor + /*! \param value - value + */ + Value(unsigned long value); + + //! initializing constructor + /*! \param value - value + */ + Value(unsigned long long value); + + //! initializing constructor + /*! \param value - value + */ + Value(bool value); + + + //! copy operator + /*! \param other - value + */ + Value& operator=(const Value& other); + + //! copy operator + /*! \param value - value + */ + Value& operator=(const std::string& value); + + //! copy operator + /*! \param value - value + */ + Value& operator=(const char* value); + + //! copy operator + /*! \param value - value + */ + Value& operator=(float value); + + //! copy operator + /*! \param value - value + */ + Value& operator=(double value); + + //! copy operator + /*! \param value - value + */ + Value& operator=(byte value); + + //! copy operator + /*! \param value - value + */ + Value& operator=(int value); + + //! copy operator + /*! \param value - value + */ + Value& operator=(short value); + + //! copy operator + /*! \param value - value + */ + Value& operator=(long value); + + //! copy operator + /*! \param value - value + */ + Value& operator=(long long value); + + //! copy operator + /*! \param value - value + */ + Value& operator=(unsignedByte value); + + //! copy operator + /*! \param value - value + */ + Value& operator=(unsigned int value); + + //! copy operator + /*! \param value - value + */ + Value& operator=(unsigned short value); + + //! copy operator + /*! \param value - value + */ + Value& operator=(unsigned long value); + + //! copy operator + /*! \param value - value + */ + Value& operator=(unsigned long long value); + + //! copy operator + /*! \param value - value + */ + Value& operator=(bool value); + + + //! const value cast operator + /*! \return casted const value + */ + operator const std::string&() const; + + //! const value cast operator + /*! \return casted const value + */ + operator float() const; + + //! const value cast operator + /*! \return casted const value + */ + operator double() const; + + //! const value cast operator + /*! \return casted const value + */ + operator byte() const; + + //! const value cast operator + /*! \return casted const value + */ + operator int() const; + + //! const value cast operator + /*! \return casted const value + */ + operator short() const; + + //! const value cast operator + /*! \return casted const value + */ + operator long() const; + + //! const value cast operator + /*! \return casted const value + */ + operator long long() const; + + //! const value cast operator + /*! \return casted const value + */ + operator unsignedByte() const; + + //! const value cast operator + /*! \return casted const value + */ + operator unsigned int() const; + + //! const value cast operator + /*! \return casted const value + */ + operator unsigned short() const; + + //! const value cast operator + /*! \return casted const value + */ + operator unsigned long() const; + + //! const value cast operator + /*! \return casted const value + */ + operator unsigned long long() const; + + //! const value cast operator + /*! \return casted const value + */ + operator bool() const; + + + //! value cast operator + /*! \return value + */ + operator std::string&(); + + + //! explicit conversion to const string + /*! \return const string value + */ + const std::string& asString() const; + + //! explicit conversion to string + /*! \return string value + */ + std::string& asString(); + + //! test target value for equality with specified value + /*! \param value - other value + \return true, if values are equals + */ + bool operator==(const Value& value) const; + + private: + std::string value; //!< string value + }; + +} +} + +#endif // NGREST_XML_VALUE_H diff --git a/core/xml/src/XmlReader.cpp b/core/xml/src/XmlReader.cpp new file mode 100644 index 0000000..c34be58 --- /dev/null +++ b/core/xml/src/XmlReader.cpp @@ -0,0 +1,442 @@ +#include +#include +#include +#include "Exception.h" +#include "Document.h" +#include "Attribute.h" +#include "Namespace.h" +#include "Element.h" +#include "Node.h" +#include "XmlReader.h" + +namespace ngrest { +namespace xml { + +class XmlReader::XmlReaderImpl +{ +public: + XmlReaderImpl(std::istream& stream, bool stripWhitespace): + stream(stream), + stripWhitespace(stripWhitespace), + line(0) + { + } + + ~XmlReaderImpl() + { + } + + void readDeclaration(Declaration& declaration) + { + skipWs(); + + declaration.setVersion("1.0"); + declaration.setEncoding("UTF-8"); + declaration.setStandalone(Declaration::Standalone::Undefined); + + if (!test("")) + break; + + readStringBeforeChr(name, " \n\r\t="); + skipWs(); + NGREST_XML_ASSERT(test("="), "'=' expected while reading attribute/namespace [" + name + "]", + fileName, line); + skipWs(); + + readChar(ch); + NGREST_XML_ASSERT(ch == '"' || ch == '\'', + "quote expected while reading attribute/namespace [" + + name + "]", fileName, line); + readStringWithChr(value, ch); + + if (name == "version") { + declaration.setVersion(value); + } else if (name == "encoding") { + declaration.setEncoding(value); + } else if (name == "standalone") { + if (value == "yes") { + declaration.setStandalone(Declaration::Standalone::Yes); + } else if (value == "no") { + declaration.setStandalone(Declaration::Standalone::No); + } else { + NGREST_XML_THROW("Invalid Attribute value: " + value, fileName, line); + } + } else { + NGREST_XML_THROW("Invalid Attribute name: " + name, fileName, line); + } + } + } + + void readElement(Element& element) + { + NGREST_XML_ASSERT(test("<"), "Element start expected", "", line); + + char ch = '\0'; + std::string name; + std::string value; + readStringBeforeChr(name); + NGREST_XML_ASSERT(validateId(name), "Element name validation failed: [" + name + "]", + fileName, line); + element.setName(name); + + skipWs(); + + // read attributes and namespaces + while (!test(">")) { + // element without children + if (test("/>")) + return; + + readStringBeforeChr(name, " \n\r\t="); + skipWs(); + NGREST_XML_ASSERT(test("="), "'=' expected while reading attribute/namespace [" + name + "]", + fileName, line); + skipWs(); + + readChar(ch); + NGREST_XML_ASSERT(ch == '"' || ch == '\'', + "quote expected while reading attribute/namespace [" + + name + "]", fileName, line); + readStringWithChr(value, ch); + unescapeString(value); + + if (name == "xmlns") { + // default namespace + element.declareDefaultNamespace(value); + } else if (!name.compare(0, 6, "xmlns:")) { + // namespace + element.declareNamespace(value, name.substr(6)); + } else { + // attribute + element.createAttribute(name, value); + } + + skipWs(); + } + + // read children + + for (;;) { + // text node child + readStringWithChr(value, '<'); + unescapeString(value); + + if (!value.empty()) { + if (hasText(value) || !stripWhitespace) + element.createText(value); + } + + // some node + + // comment + if (test("!--")) { + ReadStringWithStr(value, "-->"); + element.createComment(value); + } else if (test("![CDATA[")) { + // cdata + ReadStringWithStr(value, "]]>"); + element.createCdata(value); + } else if (test("/")) { + // end of parent element + readStringBeforeChr(name); + NGREST_XML_ASSERT(name == element.getPrefixName(), + "Invalid name of element end." + " Found: [" + name + "] expected [" + element.getPrefixName() + "]", + fileName, line); + skipWs(); + NGREST_XML_ASSERT(test(">"), "'>' Expected", fileName, line); + break; + } else { + // child element + // moving back to the '<' char + stream.seekg(static_cast(-1), std::ios::cur); + readElement(element.createElement()); + } + } + } + + + void unescapeString(std::string& str) + { + std::string::size_type posStart = 0; + std::string::size_type posEnd = 0; + while ((posStart = str.find_first_of('&', posEnd)) != std::string::npos) { + posEnd = str.find_first_of(';', posStart); + NGREST_XML_ASSERT(posEnd != std::string::npos, "';' not found while unescaping string [" + + str + "]", fileName, line); + + const std::string& esc = str.substr(posStart + 1, posEnd - posStart - 1); + NGREST_XML_ASSERT(!esc.empty(), "Invalid sequence found while unescaping string [" + + str + "]", fileName, line); + + if (esc == "lt") { + str.replace(posStart, posEnd - posStart + 1, 1, '<'); + posEnd = posStart + 1; + } else if (esc == "gt") { + str.replace(posStart, posEnd - posStart + 1, 1, '>'); + posEnd = posStart + 1; + } else if (esc == "amp") { + str.replace(posStart, posEnd - posStart + 1, 1, '&'); + posEnd = posStart + 1; + } else if (esc == "apos") { + str.replace(posStart, posEnd - posStart + 1, 1, '\''); + posEnd = posStart + 1; + } else if (esc == "quot") { + str.replace(posStart, posEnd - posStart + 1, 1, '"'); + posEnd = posStart + 1; + } // utf-16 escaping: leave it as is + } + } + + + bool readChar(char& ch) + { + stream.get(ch); + if (ch == '\n') + ++line; + + return true; + } + + char readChar() + { + char ch = '\0'; + stream.get(ch); + if (ch == '\n') + ++line; + return ch; + } + + void skipWs() + { + char ch = '\0'; + static const std::string whitespace = " \t\n\r"; + + for (;;) { + ch = stream.peek(); + if (whitespace.find(ch) == std::string::npos) + break; + + if (ch == '\n') + ++line; + stream.ignore(); + } + } + + void readStringBeforeChr(std::string& str, const std::string& charset = + " \t\n\r!\"#$%&\'()*+,/;<=>?@[\\]^`{|}~") + { + char ch = '\0'; + str.erase(); + for (;;) { + ch = stream.peek(); + if (charset.find(ch) != std::string::npos) + break; + + if (ch == '\n') + ++line; + str.append(1, ch); + stream.ignore(); + } + } + + void readStringWithChr(std::string& str, const char end) + { + char ch = '\0'; + str.erase(); + for (;;) { + ch = stream.peek(); + if (end == ch) { + stream.ignore(); + break; + } + + if (ch == '\n') + ++line; + + str.append(1, ch); + stream.ignore(); + } + } + + void ReadStringWithStr(std::string& str, const char* end) + { + str.erase(); + char ch = '\0'; + const char* curr = nullptr; + + for (;;) { + curr = end; + for (;;) { + readChar(ch); + if (ch == *curr) + break; + + str.append(1, ch); + } + + // first matched char found + for (;;) { + ++curr; + if (!*curr) // found + return; + + // read and compare next char + readChar(ch); + if (ch != *curr) { + // adding matched chars + str.append(end, curr - end); + // continuing search + break; + } + } + } + } + + bool test(const char* str) + { + char ch = '\0'; + const char* curr = str; + unsigned lines = 0; + for (; *curr; ++curr) { + ch = stream.peek(); + + if (ch != *curr) { + if (curr != str) + stream.seekg(static_cast(str - curr), std::ios::cur); + return false; + } + + stream.ignore(); + if (ch == '\n') + ++lines; + } + + line += lines; + + return true; + } + + bool hasText(const std::string& str) + { + static const char* whitespace = " \t\n\r"; + return str.find_first_not_of(whitespace) != std::string::npos; + } + + bool validateId(const std::string& id) + { + static const std::string nonId = " \t\n\r!\"#$%&\'()*+,/;<=>?@[\\]^`{|}~"; + + if (id.empty()) + return false; + + if (id.find_first_of(nonId) != std::string::npos) + return false; + + const char first = id[0]; + if (first == '.' || first == '-' || (first >= '0' && first <= '9')) + return false; + + return true; + } + +public: + std::istream& stream; + std::string fileName; + std::string encoding; + bool stripWhitespace; + unsigned line; +}; + + +XmlReader::XmlReader(std::istream& stream, bool stripWhitespace): + impl(new XmlReaderImpl(stream, stripWhitespace)) +{ +} + +XmlReader::~XmlReader() +{ + delete impl; +} + +void XmlReader::setEncoding(const std::string& encoding) +{ + impl->encoding = encoding; +} + +void XmlReader::setFileName(const std::string& fileName) +{ + impl->fileName = fileName; +} + +void XmlReader::readDocument(Document& document) +{ + try { + impl->line = 0; + impl->stream.exceptions(std::ios::failbit | std::ios::badbit); + impl->line = 1; + impl->readDeclaration(document.getDeclaration()); + if (!impl->encoding.empty()) { + document.getDeclaration().setEncoding(impl->encoding); + } else { + impl->encoding = document.getDeclaration().getEncoding(); + } + impl->skipWs(); + std::string comment; + bool skip = false; + // skip comments before root element node + do { + skip = false; + if (impl->test(""); + skip = true; + } + impl->skipWs(); + if (impl->test("ReadStringWithStr(comment, "?>"); + skip = true; + } + impl->skipWs(); + } + while (skip); + impl->readElement(document.getRootElement()); + } + catch (const Exception&) + { + throw; + } + catch (const std::exception& exception) + { + NGREST_XML_THROW("Error while parsing file: " + Error::getLastError() + "(" + + std::string(exception.what()) + ")", impl->fileName, impl->line); + } +} + +void XmlReader::readDeclaration(Declaration& declaration) +{ + impl->readDeclaration(declaration); +} + +void XmlReader::readElement(Element& element) +{ + impl->readElement(element); +} + + +XmlFileReader::XmlFileReader(const std::string& fileName, bool stripWhitespace): + XmlReader(stream, stripWhitespace) +{ + setFileName(fileName); + stream.open(fileName.c_str(), std::ios::binary); +} + +} // namespace xml +} // namespace ngrest diff --git a/core/xml/src/XmlReader.h b/core/xml/src/XmlReader.h new file mode 100644 index 0000000..f2dd5aa --- /dev/null +++ b/core/xml/src/XmlReader.h @@ -0,0 +1,78 @@ +#ifndef NGREST_XML_XMLREADER_H +#define NGREST_XML_XMLREADER_H + +#include +#include +#include "ngrestxmlexport.h" + +namespace ngrest { +namespace xml { + +class Document; +class Declaration; +class Element; + +//! xml reader +class NGREST_XML_EXPORT XmlReader +{ +public: + //! constructor + /*! \param stream - input stream + \param stripWhitespace - ignore human-readable xml formatting + */ + XmlReader(std::istream& stream, bool stripWhitespace = true); + + //! destructor + ~XmlReader(); + + //! set xml document encoding + /*! if document encoding is set, encoding value from declaration will be overriden + else will be used value from declaration or "UTF-8" if encoding attribute is missing + \param encoding - xml document encoding + */ + void setEncoding(const std::string& encoding = ""); + + //! set file name + /*! \param fileName - file name + */ + void setFileName(const std::string& fileName); + + //! read xml document + /*! \param document - xml document + */ + void readDocument(Document& document); + + //! read xml declaration + /*! \param declaration - xml declaration + */ + void readDeclaration(Declaration& declaration); + + //! read element + /*! \param element - element + */ + void readElement(Element& element); + +private: + class XmlReaderImpl; + XmlReaderImpl* impl; +}; + + +//! xml file writer +class NGREST_XML_EXPORT XmlFileReader: public XmlReader +{ +public: + //! constructor + /*! \param fileName - file name + \param stripWhitespace - ignore human-readable xml formatting + */ + XmlFileReader(const std::string& fileName, bool stripWhitespace = true); + +private: + std::ifstream stream; +}; + +} // namespace xml +} // namespace ngrest + +#endif // #ifndef NGREST_XML_XMLREADER_H diff --git a/core/xml/src/XmlWriter.cpp b/core/xml/src/XmlWriter.cpp new file mode 100644 index 0000000..5375570 --- /dev/null +++ b/core/xml/src/XmlWriter.cpp @@ -0,0 +1,201 @@ +#include "Exception.h" +#include "Document.h" +#include "Declaration.h" +#include "Attribute.h" +#include "Namespace.h" +#include "Node.h" +#include "Element.h" +#include "XmlWriter.h" + +namespace ngrest { +namespace xml { + +XmlWriter::XmlWriter(std::ostream& stream, bool enableFormatting): + stream(stream), enableFormatting(enableFormatting), indent(0) +{ +} + +void XmlWriter::writeDocument(const Document& document) +{ + stream.exceptions(std::ios::failbit | std::ios::badbit); + writeDeclaration(document.getDeclaration()); + writeElement(document.getRootElement()); +} + +void XmlWriter::writeDeclaration(const Declaration& declaration) +{ + stream << ""; +} + +void XmlWriter::writeNode(const Node& node) +{ + switch (node.getType()) { + case Node::Type::Element: + writeElement(node.getElement()); + break; + + case Node::Type::Text: + writeText(static_cast(node)); + break; + + case Node::Type::Comment: + writeComment(static_cast(node)); + break; + + case Node::Type::Cdata: + writeCdata(static_cast(node)); + break; + + default: + NGREST_THROW_ASSERT("Invalid Node Type: " + toString(static_cast(node.getType()))); + } +} + +void XmlWriter::writeElement(const Element& element) +{ + writeIndent(); + stream << "<" << element.getPrefixName(); + + // write namespaces + for (const Namespace* ns = element.getFirstNamespace(); ns; ns = ns->getNextSibling()) + writeNamespace(*ns); + + // write attributes + for (const Attribute* attribute = element.getFirstAttribute(); + attribute; attribute = attribute->getNextSibling()) + writeAttribute(*attribute); + + if (element.isEmpty()) { + // end element + stream << "/>"; + } else { + stream << ">"; + + if (element.isLeaf()) { + writeText(static_cast(*element.getFirstChild())); + } else { + // write children + + ++indent; + for (const Node* node = element.getFirstChild(); node; node = node->getNextSibling()) + writeNode(*node); + --indent; + writeIndent(); + } + stream << ""; + } +} + +void XmlWriter::writeComment(const Comment& comment) +{ + writeIndent(); + stream << ""; +} + +void XmlWriter::writeText(const Text& text) +{ + stream << escapeString(text.getValue()); +} + +void XmlWriter::writeCdata(const Cdata& cdata) +{ + writeIndent(); + std::string str(cdata.getValue()); + std::string::size_type pos = 0; + while ((pos = str.find("]]>", pos)) != std::string::npos) { + str.replace(pos, 3, "]]]]>", 15); + pos += 15; + } + + stream << ""; +} + +void XmlWriter::writeAttribute(const Attribute& attribute) +{ + stream << " " << attribute.getPrefixName() << "=\"" << escapeString(attribute.getValue()) << "\""; +} + +void XmlWriter::writeNamespace(const Namespace& ns) +{ + if (ns.getPrefix().empty()) { + stream << " xmlns"; + } else { + stream << " xmlns:" << ns.getPrefix(); + } + + stream << "=\"" << escapeString(ns.getUri()) << "\""; +} + +std::string XmlWriter::escapeString(std::string str) +{ + static const char* szEscapeChars = "<>&'\""; + + std::string::size_type pos = 0; + while ((pos = str.find_first_of(szEscapeChars, pos)) != std::string::npos) { + switch (str[pos]) { + case '<': + str.replace(pos, 1, "<", 4); + pos += 4; + break; + + case '>': + str.replace(pos, 1, ">", 4); + pos += 4; + break; + + case '&': + str.replace(pos, 1, "&", 5); + pos += 5; + break; + + case '\'': + str.replace(pos, 1, "'", 6); + pos += 6; + break; + + case '"': + str.replace(pos, 1, """, 6); + pos += 6; + break; + } + } + + return str; +} + +void XmlWriter::writeIndent() +{ + if (enableFormatting) { + stream << "\n"; + for (unsigned i = indent; i; --i) + stream << " "; + } +} + +void XmlWriter::writeNewLine() +{ + if (enableFormatting) + stream << "\n"; +} + + +XmlFileWriter::XmlFileWriter(const std::string& fileName, bool enableFormatting): + XmlWriter(stream, enableFormatting) +{ + stream.open(fileName.c_str()); +} + +} // namespace xml +} // namespace ngrest diff --git a/core/xml/src/XmlWriter.h b/core/xml/src/XmlWriter.h new file mode 100644 index 0000000..c540082 --- /dev/null +++ b/core/xml/src/XmlWriter.h @@ -0,0 +1,66 @@ +#ifndef NGREST_XML_XMLWRITER_H +#define NGREST_XML_XMLWRITER_H + +#include +#include +#include "ngrestxmlexport.h" + +namespace ngrest { +namespace xml { + +class Document; +class Declaration; +class Node; +class Element; +class Comment; +class Text; +class Cdata; +class Attribute; +class Namespace; + +//! xml writer +class NGREST_XML_EXPORT XmlWriter +{ +public: + //! constructor + /*! \param stream - output stream + \param enableFormatting - produce human-readable xml + */ + XmlWriter(std::ostream& stream, bool enableFormatting = true); + + void writeDocument(const Document& document); + void writeDeclaration(const Declaration& declaration); + void writeNode(const Node& node); + void writeElement(const Element& element); + void writeComment(const Comment& comment); + void writeText(const Text& text); + void writeCdata(const Cdata& cdata); + void writeAttribute(const Attribute& attribute); + void writeNamespace(const Namespace& ns); + +private: + std::string escapeString(std::string str); + void writeIndent(); + void writeNewLine(); + +private: + std::ostream& stream; + bool enableFormatting; + unsigned indent; +}; + + +//! xml file writer +class NGREST_XML_EXPORT XmlFileWriter: public XmlWriter +{ +public: + XmlFileWriter(const std::string& fileName, bool enableFormatting = true); + +private: + std::ofstream stream; +}; + +} // namespace xml +} // namespace ngrest + +#endif // #ifndef NGREST_XML_XMLWRITER_H diff --git a/core/xml/src/ngrestxmlexport.h b/core/xml/src/ngrestxmlexport.h new file mode 100644 index 0000000..514933a --- /dev/null +++ b/core/xml/src/ngrestxmlexport.h @@ -0,0 +1,33 @@ +#ifndef NGRESTXMLEXPORT_H +#define NGRESTXMLEXPORT_H + +#ifdef WIN32 + #ifdef _MSC_VER + #pragma warning(disable: 4786 4251 4521) + #endif + + #ifdef NGREST_XML_DLL_EXPORTS + #define NGREST_XML_EXPORT __declspec(dllexport) + #else + #define NGREST_XML_EXPORT __declspec(dllimport) + #endif +#else + #define NGREST_XML_EXPORT +#endif + +#ifndef NGREST_DEPRECATED + #ifdef _MSC_VER + #if _MSC_FULL_VER >= 140050320 + #define NGREST_DEPRECATED(Replacement) __declspec(deprecated("You are using deprecated API. Consider using " #Replacement " instead.")) + #else + #define NGREST_DEPRECATED(Replacement) __declspec(deprecated) + #endif + #elif __GNUC__ >= 3 + #define NGREST_DEPRECATED(Replacement) __attribute__ ((deprecated)) + #else + #define NGREST_DEPRECATED(Replacement) + #endif +#endif + +#endif // NGRESTXMLEXPORT_H + diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 0000000..de9dcfd --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(echo) diff --git a/examples/echo/CMakeLists.txt b/examples/echo/CMakeLists.txt new file mode 100644 index 0000000..69f7a79 --- /dev/null +++ b/examples/echo/CMakeLists.txt @@ -0,0 +1 @@ +add_executable(echo Echo.cpp main.cpp) \ No newline at end of file diff --git a/examples/echo/Echo.cpp b/examples/echo/Echo.cpp new file mode 100644 index 0000000..9800969 --- /dev/null +++ b/examples/echo/Echo.cpp @@ -0,0 +1,14 @@ +#include "Echo.h" + +namespace ngrest { +namespace examples { +namespace echo { + +std::string Echo::echo(const std::string& str) +{ + return str; +} + +} +} +} diff --git a/examples/echo/Echo.h b/examples/echo/Echo.h new file mode 100644 index 0000000..bb70584 --- /dev/null +++ b/examples/echo/Echo.h @@ -0,0 +1,33 @@ +#ifndef NGREST_EXAMPLES_ECHO_H +#define NGREST_EXAMPLES_ECHO_H + +#include +#include + +namespace ngrest { +namespace examples { +namespace echo { + +// by default exposes Echo service relative to base URL: http://server:port/ngrest/examples/echo/Echo +class Echo: public Service +{ +public: + /* + example of POST request: + http://server:port/ngrest/examples/echo/Echo + -- body ----------------------- + { + "echo": { + str: "Hello Ngrest!" + } + } + -- end body ------------------- + */ + std::string echo(const std::string& str); +}; + +} +} +} + +#endif diff --git a/ngrest.files b/ngrest.files index 0abf36e..74e5092 100644 --- a/ngrest.files +++ b/ngrest.files @@ -21,6 +21,10 @@ core/engine/src/HttpTransport.cpp core/engine/src/HttpTransport.h core/engine/src/ServiceDescription.cpp core/engine/src/ServiceDescription.h +core/engine/src/ServiceDispatcher.cpp +core/engine/src/ServiceDispatcher.h +core/engine/src/ServiceGroup.cpp +core/engine/src/ServiceGroup.h core/engine/src/ServiceWrapper.cpp core/engine/src/ServiceWrapper.h core/engine/src/Transport.cpp @@ -41,8 +45,14 @@ core/server/src/Server.h core/server/src/strutils.h core/service/CMakeLists.txt core/utils/CMakeLists.txt +core/utils/src/DynamicLibrary.cpp +core/utils/src/DynamicLibrary.h +core/utils/src/Error.cpp +core/utils/src/Error.h core/utils/src/Exception.cpp core/utils/src/Exception.h +core/utils/src/File.cpp +core/utils/src/File.h core/utils/src/fromcstring.h core/utils/src/fromstring.h core/utils/src/Log.cpp @@ -51,10 +61,36 @@ core/utils/src/LogStream.h core/utils/src/MemPool.cpp core/utils/src/MemPool.h core/utils/src/ngrestutilsexport.h -core/utils/src/Pooler.cpp -core/utils/src/Pooler.h +core/utils/src/PluginExport.h +core/utils/src/Plugin.h +core/utils/src/stringutils.h core/utils/src/tocstring.h core/utils/src/tostring.h +core/xml/CMakeLists.txt +core/xml/src/Attribute.cpp +core/xml/src/Attribute.h +core/xml/src/Declaration.cpp +core/xml/src/Declaration.h +core/xml/src/Document.cpp +core/xml/src/Document.h +core/xml/src/Element.cpp +core/xml/src/Element.h +core/xml/src/Exception.h +core/xml/src/Namespace.cpp +core/xml/src/Namespace.h +core/xml/src/ngrestxmlexport.h +core/xml/src/Node.cpp +core/xml/src/Node.h +core/xml/src/Value.cpp +core/xml/src/Value.h +core/xml/src/XmlReader.cpp +core/xml/src/XmlReader.h +core/xml/src/XmlWriter.cpp +core/xml/src/XmlWriter.h +examples/CMakeLists.txt +examples/echo/CMakeLists.txt +examples/echo/Echo.cpp +examples/echo/Echo.h tests/CMakeLists.txt tests/deployment/CMakeLists.txt tests/deployment/src/main.cpp @@ -68,3 +104,29 @@ tests/json-benchmark/test.json tests/json/CMakeLists.txt tests/json/src/jsontest.cpp tests/json/test.json +tests/service/CMakeLists.txt +tests/service/src/TestService.cpp +tests/service/src/TestService.h +tools/CMakeLists.txt +tools/ngrestcg/CMakeLists.txt +tools/ngrestcgparser/CMakeLists.txt +tools/ngrestcgparser/src/CodegenParser.cpp +tools/ngrestcgparser/src/CodegenParser.h +tools/ngrestcgparser/src/Interface.cpp +tools/ngrestcgparser/src/Interface.h +tools/ngrestcgparser/src/ngrestcodegenparserexport.h +tools/ngrestcgparser/src/tools.cpp +tools/ngrestcgparser/src/tools.h +tools/ngrestcg/src/CodeGen.cpp +tools/ngrestcg/src/CodeGen.h +tools/ngrestcg/src/main.cpp +tools/ngrestcg/src/XmlGen.cpp +tools/ngrestcg/src/XmlGen.h +tools/parsers/CMakeLists.txt +tools/parsers/cpp/CMakeLists.txt +tools/parsers/cpp/src/CppParser.cpp +tools/parsers/cpp/src/CppParser.h +tests/deployment/src/TestServiceGroup.cpp +tests/deployment/src/TestServiceGroup.h +core/utils/src/ElapsedTimer.h +core/utils/src/ElapsedTimer.cpp diff --git a/ngrest.includes b/ngrest.includes index e5f6190..28ab9f7 100644 --- a/ngrest.includes +++ b/ngrest.includes @@ -1,12 +1,5 @@ -common/utils/src -common/json/src -common -common/json -common/utils ../ngrest-build/deploy/include -core/server/src -core/engine/src -core/utils/src -core/common/src -../ngrest-build/deploy/include/ngrest/common/src -tests/deployment/src +../../ngrest-build/deploy/include +../../../ngrest-build/deploy/include +../../../../ngrest-build/deploy/include +../../../../../ngrest-build/deploy/include diff --git a/tests/deployment/CMakeLists.txt b/tests/deployment/CMakeLists.txt index 43be894..150fb3f 100644 --- a/tests/deployment/CMakeLists.txt +++ b/tests/deployment/CMakeLists.txt @@ -13,5 +13,4 @@ list(REMOVE_ITEM NGRESTDEPLOYMENT_SOURCES ${PROJECT_SOURCE_DIR}/../../../core/se add_executable(ngrestdeployment ${NGRESTDEPLOYMENT_SOURCES}) -target_compile_features(ngrestdeployment PRIVATE cxx_range_for) target_link_libraries(ngrestdeployment ngrestutils ngrestcommon ngrestjson ngrestengine) diff --git a/tests/deployment/src/TestDeployment.cpp b/tests/deployment/src/TestDeployment.cpp index 3eeeafc..ade7d20 100644 --- a/tests/deployment/src/TestDeployment.cpp +++ b/tests/deployment/src/TestDeployment.cpp @@ -7,9 +7,9 @@ std::string TestDeployment::echoSync(const std::string& value) return "You said " + value; } -void TestDeployment::echoASync(const std::string& value, ngrest::Callback* callback) +void TestDeployment::echoASync(const std::string& value, ngrest::Callback& callback) { - callback->success("You said " + value); + callback.success("You said " + value); } } // namespace ngrest diff --git a/tests/deployment/src/TestDeployment.h b/tests/deployment/src/TestDeployment.h index 53c9898..bef656d 100644 --- a/tests/deployment/src/TestDeployment.h +++ b/tests/deployment/src/TestDeployment.h @@ -11,7 +11,7 @@ class TestDeployment: public ngrest::Service { public: std::string echoSync(const std::string& value); - void echoASync(const std::string& value, ngrest::Callback* callback); + void echoASync(const std::string& value, ngrest::Callback& callback); }; } // namespace ngrest diff --git a/tests/deployment/src/TestDeploymentWrapper.cpp b/tests/deployment/src/TestDeploymentWrapper.cpp index 4544747..78bcb54 100644 --- a/tests/deployment/src/TestDeploymentWrapper.cpp +++ b/tests/deployment/src/TestDeploymentWrapper.cpp @@ -20,7 +20,7 @@ TestDeploymentWrapper::~TestDeploymentWrapper() delete service; } -Service* TestDeploymentWrapper::serviceImpl() +Service* TestDeploymentWrapper::getServiceImpl() { return service; } @@ -96,14 +96,14 @@ void TestDeploymentWrapper::invoke(const OperationDescription* operation, Messag MessageContext* context; }; - service->echoASync(value, context->pool.alloc(context)); + service->echoASync(value, *context->pool.alloc(context)); } else { NGREST_THROW_ASSERT("No operation " + operation->name + " found"); } } -const ServiceDescription* TestDeploymentWrapper::description() +const ServiceDescription* TestDeploymentWrapper::getDescription() { static ServiceDescription description = { true, diff --git a/tests/deployment/src/TestDeploymentWrapper.h b/tests/deployment/src/TestDeploymentWrapper.h index d6839cf..46b00e1 100644 --- a/tests/deployment/src/TestDeploymentWrapper.h +++ b/tests/deployment/src/TestDeploymentWrapper.h @@ -13,9 +13,9 @@ class TestDeploymentWrapper: public ServiceWrapper TestDeploymentWrapper(); ~TestDeploymentWrapper(); - virtual Service* serviceImpl() override; + virtual Service* getServiceImpl() override; virtual void invoke(const OperationDescription* operation, MessageContext* context) override; - virtual const ServiceDescription* description() override; + virtual const ServiceDescription* getDescription() override; private: TestDeployment* service; diff --git a/tests/deployment/src/TestServiceGroup.cpp b/tests/deployment/src/TestServiceGroup.cpp new file mode 100644 index 0000000..92d00e1 --- /dev/null +++ b/tests/deployment/src/TestServiceGroup.cpp @@ -0,0 +1,32 @@ +#include + +#include "TestDeploymentWrapper.h" +#include "TestServiceGroup.h" + +namespace ngrest { + +TestServiceGroup::TestServiceGroup() +{ + services = {{ + new ::ngrest::TestDeploymentWrapper() + }}; +} + +TestServiceGroup::~TestServiceGroup() +{ + for (auto it = services.begin(); it != services.end(); ++it) + delete *it; + services.clear(); +} + +std::string TestServiceGroup::getName() +{ + return "test"; +} + +const std::vector& TestServiceGroup::getServices() +{ + return services; +} + +} diff --git a/tests/deployment/src/TestServiceGroup.h b/tests/deployment/src/TestServiceGroup.h new file mode 100644 index 0000000..91cc7b1 --- /dev/null +++ b/tests/deployment/src/TestServiceGroup.h @@ -0,0 +1,26 @@ +#ifndef NGREST_TESTSERVICEGROUP_H +#define NGREST_TESTSERVICEGROUP_H + +#include + +namespace ngrest { + +class TestServiceGroup: public ServiceGroup +{ +public: + TestServiceGroup(); + + ~TestServiceGroup(); + + std::string getName() override; + + const std::vector& getServices() override; + +private: + std::vector services; +}; + +} + + +#endif // NGREST_TESTSERVICEGROUP_H diff --git a/tests/deployment/src/main.cpp b/tests/deployment/src/main.cpp index 99ae8cf..24f582a 100644 --- a/tests/deployment/src/main.cpp +++ b/tests/deployment/src/main.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -10,7 +11,7 @@ #include "Server.h" #include "ClientHandler.h" -#include "TestDeploymentWrapper.h" +#include "TestServiceGroup.h" int help() { std::cerr << "ngrest_server [-h][-p ]" << std::endl @@ -33,9 +34,10 @@ int main(int argc, char* argv[]) } static ngrest::Server server; - ngrest::Deployment deployment; + ngrest::ServiceDispatcher dispatcher; + ngrest::Deployment deployment(dispatcher); ngrest::HttpTransport transport; - ngrest::Engine engine(deployment); + ngrest::Engine engine(dispatcher); ngrest::ClientHandler clientHandler(engine, transport); server.setClientCallback(&clientHandler); @@ -61,9 +63,8 @@ int main(int argc, char* argv[]) curl -i -X GET -H "Content-Type:application/json" 'http://localhost:9098/td/async/Hello_world' */ - ngrest::TestDeploymentWrapper testWrapper; - - deployment.registerService(&testWrapper); + ngrest::TestServiceGroup group; + deployment.deployStatic(&group); return server.exec(); } diff --git a/tests/json-benchmark/CMakeLists.txt b/tests/json-benchmark/CMakeLists.txt index f7c0fe2..3e237a4 100644 --- a/tests/json-benchmark/CMakeLists.txt +++ b/tests/json-benchmark/CMakeLists.txt @@ -8,5 +8,4 @@ FILE(GLOB NGRESTJSONBENCHMARK_SOURCES ${PROJECT_SOURCE_DIR}/*.cpp) add_executable(ngrestjsonbenchmark ${NGRESTJSONBENCHMARK_SOURCES}) -target_compile_features(ngrestjsonbenchmark PRIVATE cxx_range_for) target_link_libraries(ngrestjsonbenchmark ngrestutils ngrestjson json-c) diff --git a/tests/json-benchmark/src/jsonbenchmark.cpp b/tests/json-benchmark/src/jsonbenchmark.cpp index 49ce7bf..fe99116 100644 --- a/tests/json-benchmark/src/jsonbenchmark.cpp +++ b/tests/json-benchmark/src/jsonbenchmark.cpp @@ -17,9 +17,10 @@ #include -inline unsigned long long getTime() { +inline unsigned long long getTime() +{ struct timeval now; - gettimeofday(&now, NULL); + gettimeofday(&now, nullptr); return now.tv_sec * 1000 + now.tv_usec / 1000; } @@ -64,8 +65,7 @@ int main() struct json_tokener* tokener = json_tokener_new(); json_object* json_obj = json_tokener_parse_ex(tokener, chunk->buffer, chunk->size); - if (tokener->err != json_tokener_success) - { + if (tokener->err != json_tokener_success) { std::cerr << "FAILED" << std::endl; } json_tokener_free(tokener); diff --git a/tests/json/CMakeLists.txt b/tests/json/CMakeLists.txt index 775db8c..d34b0f8 100644 --- a/tests/json/CMakeLists.txt +++ b/tests/json/CMakeLists.txt @@ -8,5 +8,4 @@ FILE(GLOB NGRESTJSONTEST_SOURCES ${PROJECT_SOURCE_DIR}/*.cpp) add_executable(ngrestjsontest ${NGRESTJSONTEST_SOURCES}) -target_compile_features(ngrestjsontest PRIVATE cxx_range_for) target_link_libraries(ngrestjsontest ngrestutils ngrestjson) diff --git a/tests/service/CMakeLists.txt b/tests/service/CMakeLists.txt new file mode 100644 index 0000000..150fb3f --- /dev/null +++ b/tests/service/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 2.6) + +project (ngrestdeployment CXX) + +set (PROJECT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src) + +include_directories("${PROJECT_SOURCE_DIR}/../../../core/server/src/") + +FILE(GLOB NGRESTDEPLOYMENT_SOURCES ${PROJECT_SOURCE_DIR}/*.cpp ${PROJECT_SOURCE_DIR}/../../../core/server/src/*.cpp) + +list(REMOVE_ITEM NGRESTDEPLOYMENT_SOURCES ${PROJECT_SOURCE_DIR}/../../../core/server/src/main.cpp) + + +add_executable(ngrestdeployment ${NGRESTDEPLOYMENT_SOURCES}) + +target_link_libraries(ngrestdeployment ngrestutils ngrestcommon ngrestjson ngrestengine) diff --git a/tests/service/src/TestService.cpp b/tests/service/src/TestService.cpp new file mode 100644 index 0000000..c858ed4 --- /dev/null +++ b/tests/service/src/TestService.cpp @@ -0,0 +1,16 @@ +#include "TestService.h" + +namespace ngrest { + +std::string TestService::echoSync(const std::string& value) +{ + return "You said " + value; +} + +void TestService::echoASync(const std::string& value, ngrest::Callback& callback) +{ + callback.success("You said " + value); +} + +} // namespace ngrest + diff --git a/tests/service/src/TestService.h b/tests/service/src/TestService.h new file mode 100644 index 0000000..e4d0e30 --- /dev/null +++ b/tests/service/src/TestService.h @@ -0,0 +1,33 @@ +#ifndef NGREST_TESTSERVICE_H +#define NGREST_TESTSERVICE_H + +#include +#include +#include +#include + +namespace ngrest { + +struct Test +{ + struct Nested + { + bool b; + Node node; + }; + + int a; + std::string b; + Nested n; +}; + +class TestService: public ngrest::Service +{ +public: + std::string echoSync(const std::string& value); + void echoASync(const std::string& value, ngrest::Callback& callback); +}; + +} // namespace ngrest + +#endif // NGREST_TESTSERVICE_H diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt new file mode 100644 index 0000000..27943ea --- /dev/null +++ b/tools/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 2.6) +project (tools) + +add_subdirectory(ngrestcgparser) +add_subdirectory(ngrestcg) +add_subdirectory(parsers) diff --git a/tools/ngrestcg/CMakeLists.txt b/tools/ngrestcg/CMakeLists.txt new file mode 100644 index 0000000..796f399 --- /dev/null +++ b/tools/ngrestcg/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 2.6) +project (ngrestcg CXX) + +set (PROJECT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src) + +FILE(GLOB NGRESTCG_SOURCES ${PROJECT_SOURCE_DIR}/*.cpp) +FILE(GLOB NGRESTCG_HEADERS ${PROJECT_SOURCE_DIR}/*.h) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DVERSION_FULL=\\\"0.1\\\"") + +add_executable(ngrestcg ${NGRESTCG_SOURCES}) + +target_link_libraries(ngrestcg ngrestutils ngrestjson ngrestxml ngrestcgparser) diff --git a/tools/ngrestcg/src/CodeGen.cpp b/tools/ngrestcg/src/CodeGen.cpp new file mode 100644 index 0000000..f41eefa --- /dev/null +++ b/tools/ngrestcg/src/CodeGen.cpp @@ -0,0 +1,1234 @@ +#ifdef WIN32 +#include +#include +#include +#else +#include +#include +#include +#include +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "CodeGen.h" + +namespace ngrest { +namespace codegen { + +class TemplateParser +{ +public: + TemplateParser(): + line(0), indent(0), isNeedIndent(false), hasConfig(false) + { + variables.push(StringMap()); + } + + std::string GetElementPath(const xml::Element* element) const + { + if (element) { + std::string path = element->getName(); + while ((element = element->getParent())) + path = element->getName() + "." + path; + return path; + } else { + return ""; + } + } + + const xml::Element& getElement(const std::string& variableName, const xml::Element& elem) const + { + static xml::Element emptyElement; + const xml::Element* element = &elem; + std::string::size_type pos = variableName.find('.'); + std::string variable; + std::string serviceClass; + + if (pos != std::string::npos) { + if (variableName.size() == 1) + // reference to current node + return elem; + + if (!pos) { + bool isOpt = false; + variable = variableName.substr(1); + NGREST_ASSERT(!variable.empty(), "Element name expected in Name: " + variableName); + pos = variable.find('.'); + serviceClass = variable.substr(0, pos); // next element name + + if (serviceClass[0] == '*') { // serviceClass can't be empty + serviceClass.erase(0, 1); + isOpt = true; + } + + for (; element != nullptr; element = element->getParent()) { + const xml::Element* childElement = element->findChildElementByName(serviceClass); + if (childElement) { + if (pos == std::string::npos) { + return *childElement; + } + + variable.erase(0, pos + 1); + element = childElement; + break; + } + } + + if (!element && isOpt) + return emptyElement; + } else { + serviceClass = variableName.substr(0, pos); + variable = variableName.substr(pos + 1); + + while (element != nullptr && element->getName() != serviceClass) + element = element->getParent(); + } + + NGREST_ASSERT(element != nullptr, "\nCan't find node which match current class: \"" + serviceClass + + "\"\n context: " + GetElementPath(&elem) + "\n Variable: " + variableName + "\n"); + NGREST_ASSERT(element->getName() == serviceClass, "\nElement name does not match current class: \"" + + element->getName() + "\" <=> \"" + serviceClass + "\"\n context: " + + GetElementPath(&elem) + "\n"); + + while ((pos = variable.find('.')) != std::string::npos) { + const std::string& subClass = variable.substr(0, pos); + if (subClass[0] == '!' || subClass[0] == '$') + break; + + try { + if (subClass[0] == '*') { + const xml::Element* childElement = element->findChildElementByName(subClass.substr(1)); + element = childElement ? childElement : &emptyElement; + } else { + element = &element->getChildElementByName(subClass); + } + } catch(...) { + LogDebug() << "While parsing variable: [" << variableName << "]"; + throw; + } + + variable.erase(0, pos + 1); + } + } else { + NGREST_ASSERT(element->getName() == variableName, "node name does not match current class: \"" + + element->getName() + "\" <=> \"" + variableName + "\""); + return elem; + } + + if (variable[0] == '$') { + std::string property; + variable.erase(0, 1); + + pos = variable.find('.'); + + if (pos != std::string::npos) { + property = variable.substr(0, pos); + variable.erase(0, pos + 1); + } else { + property = variable; + variable.erase(); + } + + // number of this element by order + if (property == "num") { + static xml::Element elementNum("num"); + const xml::Element* parentElement = element->getParent(); + NGREST_ASSERT(parentElement != nullptr, "can't get number for node: " + element->getName()); + + int num = 0; + + for (const xml::Element* childElement = parentElement->getFirstChildElement(); + childElement; childElement = childElement->getNextSiblingElement()) { + if (childElement == element) + break; + + ++num; + } + + elementNum.setValue(toString(num)); + element = &elementNum; + } else if (property == "count") { + static xml::Element subElementCount("count"); + subElementCount.setValue(toString(element->getChildrenElementsCount())); + element = &subElementCount; + } else { + NGREST_THROW_ASSERT("Unknown Property: [" + variable + "]"); + } + + if (variable.empty()) + return *element; + } + + if (variable[0] == '!'){ + // exec function + variable.erase(0, 1); + element = &executeFunction(variable, *element); + while (!variable.empty()) { + // .!trimleft/:/.!dot + NGREST_ASSERT(variable.substr(0, 2) == ".!", "Junk [" + variable + "] in variable: [" + + variableName + "] at pos " + toString(variableName.size() - variable.size())); + variable.erase(0, 2); + element = &executeFunction(variable, *element); + } + return *element; + } else if (variable[0] == '*') { + // optional node + const xml::Element* childElement = element->findChildElementByName(variable.substr(1)); + if (childElement) { + return *childElement; + } else { + return emptyElement; + } + } + + return element->getChildElementByName(variable); + } + + std::string::size_type ParseParam(std::string& paramBegin) const + { + std::string::size_type pos = paramBegin.find_first_of("/\\"); + // slash unescaping + for (;;) { + NGREST_ASSERT(pos != std::string::npos, "Can't get param"); + + char found = paramBegin[pos]; + if (found == '\\') { + // unescape + if (pos < paramBegin.size()) { + switch (paramBegin[pos + 1]) { + case '/': paramBegin.replace(pos, 2, 1, '/'); break; + case 'r': paramBegin.replace(pos, 2, 1, '\r'); break; + case 'n': paramBegin.replace(pos, 2, 1, '\n'); break; + case 't': paramBegin.replace(pos, 2, 1, '\t'); break; + default: paramBegin.erase(pos, 1); + } + } + } else { + // '/' + break; + } + + pos = paramBegin.find_first_of("/\\", pos + 1); + } + return pos; + } + + const xml::Element& executeFunction(std::string& function, const xml::Element& element) const + { + static xml::Element resultElem("result"); + std::string result = element.getTextValue(); + result.clear(); + + if (function.substr(0, 9) == "mangledot") { + stringReplace(result, ".", "_", true); + function.erase(0, 9); + } else if (function.substr(0, 6) == "mangle") { + if (result.size() >= 2 && result.substr(0, 2) == "::") + result.erase(0, 2); + + if (result.size() >= 2 && result.substr(result.size() - 2, 2) == "::") + result.erase(result.size() - 2, 2); + + stringReplace(result, "::", "_", true); + function.erase(0, 6); + } else if (function.substr(0, 3) == "dot") { + if (result.size() >= 2 && result.substr(0, 2) == "::") + result.erase(0, 2); + + if (result.size() >= 2 && result.substr(result.size() - 2, 2) == "::") + result.erase(result.size() - 2, 2); + + stringReplace(result, "::", ".", true); + function.erase(0, 3); + } else if (function.substr(0, 3) == "not") { + function.erase(0, 3); + + result = result == "true" ? "false" : "true"; + } else if (function.substr(0, 7) == "equals/") { + function.erase(0, 7); + + std::string::size_type posEnd = ParseParam(function); + std::string what = function.substr(0, posEnd); + function.erase(0, posEnd + 1); + replaceToValue(what, element); + + result = (result == what) ? "true" : "false"; + } else if (function.substr(0, 6) == "match/") { + function.erase(0, 6); + + std::string::size_type posEnd = ParseParam(function); + std::string what = function.substr(0, posEnd); + function.erase(0, posEnd + 1); + replaceToValue(what, element); + + result = (result.find(what) != std::string::npos) ? "true" : "false"; + } else if (function.substr(0, 8) == "replace/") { + function.erase(0, 8); + + // what replace + std::string::size_type posEnd = ParseParam(function); + std::string what = function.substr(0, posEnd); + function.erase(0, posEnd + 1); + replaceToValue(what, element); + + // replace with + posEnd = ParseParam(function); + std::string with = function.substr(0, posEnd); + function.erase(0, posEnd + 1); + replaceToValue(with, element); + + stringReplace(result, what, with, true); + } else if (function.substr(0, 13) == "replacenotof/") { + function.erase(0, 13); + + // what replace + std::string::size_type posEnd = ParseParam(function); + std::string what = function.substr(0, posEnd); + function.erase(0, posEnd + 1); + replaceToValue(what, element); + + // replace with + posEnd = ParseParam(function); + std::string with = function.substr(0, posEnd); + function.erase(0, posEnd + 1); + replaceToValue(with, element); + + std::string::size_type posStart = 0; + while ((posEnd = result.find_first_of(what, posStart)) != std::string::npos) { + result.replace(posStart, posEnd - posStart, with); + posStart += with.size() + 1; + } + + if (posStart < result.size()) + result.replace(posStart, result.size() - posStart, with); + } else if (function.substr(0, 5) == "trim/") { + function.erase(0, 5); + + std::string::size_type posEnd = ParseParam(function); + std::string what = function.substr(0, posEnd); + function.erase(0, posEnd + 1); + replaceToValue(what, element); + + stringTrim(result, what.c_str()); + } else if (function.substr(0, 9) == "trimleft/") { + function.erase(0, 9); + + std::string::size_type posEnd = ParseParam(function); + std::string what = function.substr(0, posEnd); + function.erase(0, posEnd + 1); + replaceToValue(what, element); + + stringTrimLeft(result, what.c_str()); + } else if (function.substr(0, 10) == "trimright/") { + function.erase(0, 10); + + std::string::size_type posEnd = ParseParam(function); + std::string what = function.substr(0, posEnd); + function.erase(0, posEnd + 1); + replaceToValue(what, element); + + stringTrimRight(result, what.c_str()); + } else if (function.substr(0, 4) == "trim") { + stringTrim(result); + function.erase(0, 4); + } else if (function.substr(0, 8) == "trimleft") { + stringTrimLeft(result); + function.erase(0, 8); + } else if (function.substr(0, 7) == "tolower") { + std::transform(result.begin(), result.end(), result.begin(), ::tolower); + function.erase(0, 7); + } else if (function.substr(0, 7) == "toupper") { + std::transform(result.begin(), result.end(), result.begin(), ::toupper); + function.erase(0, 7); + } else if (function.substr(0, 11) == "tocamelcase") { + function.erase(0, 11); + std::string::size_type pos = 0; + while ((pos = result.find('_', pos)) != std::string::npos) { + if ((pos + 1) < result.size()) + result.replace(pos, 2, 1, ::toupper(result[pos + 1])); + } + } else if (function.substr(0, 9) == "trimright") { + stringTrimRight(result); + function.erase(0, 9); + } else if (function.substr(0, 7) == "append/") { + function.erase(0, 7); + std::string::size_type posEnd = ParseParam(function); + std::string what = function.substr(0, posEnd); + function.erase(0, posEnd + 1); + replaceToValue(what, element); + + result += what; + } else if (function.substr(0, 8) == "prepend/") { + function.erase(0, 8); + std::string::size_type posEnd = ParseParam(function); + std::string what = function.substr(0, posEnd); + function.erase(0, posEnd + 1); + replaceToValue(what, element); + + result = what + result; + } else if (function.substr(0, 9) == "deprefix/") { + function.erase(0, 9); + std::string::size_type posEnd = ParseParam(function); + std::string what = function.substr(0, posEnd); + function.erase(0, posEnd + 1); + replaceToValue(what, element); + + + if (result.substr(0, what.size()) == what) + result.erase(0, what.size()); + } else if (function.substr(0, 10) == "depostfix/") { + function.erase(0, 10); + std::string::size_type posEnd = ParseParam(function); + std::string what = function.substr(0, posEnd); + function.erase(0, posEnd + 1); + replaceToValue(what, element); + + std::string::size_type resSize = result.size(); + std::string::size_type whatSize = what.size(); + + if (resSize > whatSize && + result.substr(resSize - whatSize) == what) + result.erase(resSize - whatSize); + } else if (function.substr(0, 6) == "token/") { + function.erase(0, 6); + const std::string& val = element.getValue(); + + std::string::size_type posEnd = ParseParam(function); + std::string what = function.substr(0, posEnd); + function.erase(0, posEnd + 1); + replaceToValue(what, element); + + std::string::size_type pos = val.find_first_of(what); + + if (pos != std::string::npos) { + result = val.substr(0, pos); + } else { + result.erase(); + } + } else if (function.substr(0, 10) == "lasttoken/") { + function.erase(0, 10); + const std::string& val = element.getValue(); + + std::string::size_type posEnd = ParseParam(function); + std::string what = function.substr(0, posEnd); + function.erase(0, posEnd + 1); + replaceToValue(what, element); + + std::string::size_type pos = val.find_last_of(what); + + if (pos != std::string::npos) { + result = val.substr(pos + 1); + } else { + result.erase(); + } + } else if (function.substr(0, 4) == "cut/") { + function.erase(0, 4); + const std::string& val = element.getValue(); + + std::string::size_type posEnd = ParseParam(function); + std::string what = function.substr(0, posEnd); + function.erase(0, posEnd + 1); + replaceToValue(what, element); + + std::string::size_type pos = val.find_first_of(what); + + if (pos != std::string::npos) { + result = val.substr(pos + 1); + } else { + result.erase(); + } + } else if (function.substr(0, 8) == "cutlast/") { + function.erase(0, 8); + const std::string& val = element.getValue(); + + std::string::size_type posEnd = ParseParam(function); + std::string what = function.substr(0, posEnd); + function.erase(0, posEnd + 1); + replaceToValue(what, element); + + std::string::size_type pos = val.find_last_of(what); + + if (pos != std::string::npos) { + result = val.substr(0, pos); + } else { + result.erase(); + } + } else if (function.substr(0, 8) == "fixid") { + function.erase(0, 8); + fixId(result); + } else if (function.substr(0, 3) == "inc") { + double dTmp = 0; + fromString(result, dTmp); + toString(dTmp + 1, result); + function.erase(0, 3); + } else if (function.substr(0, 3) == "dec") { + double dTmp = 0; + fromString(result, dTmp); + toString(dTmp - 1, result); + function.erase(0, 3); + } else if (function.substr(0, 4) == "add/") { + std::string::size_type posWhat = function.find('/', 4); + + NGREST_ASSERT(posWhat != std::string::npos, "Can't get operand for add"); + + const std::string& what = function.substr(4, posWhat - 4); + double dOp1 = 0; + double dOp2 = 0; + fromString(result, dOp1); + fromString(what, dOp2); + toString(dOp1 + dOp2, result); + function.erase(0, posWhat + 1); + } else if (function.substr(0, 4) == "sub/") { + std::string::size_type posWhat = function.find('/', 4); + + NGREST_ASSERT(posWhat != std::string::npos, "Can't get operand for sub"); + const std::string& what = function.substr(4, posWhat - 4); + double dOp1 = 0; + double dOp2 = 0; + fromString(result, dOp1); + fromString(what, dOp2); + toString(dOp1 - dOp2, result); + function.erase(0, posWhat + 1); + } else if (function.substr(0, 5) == "trunc") { + double dTmp = 0; + fromString(result, dTmp); + toString(static_cast(dTmp), result); + function.erase(0, 5); + } else { + NGREST_THROW_ASSERT("function " + function + " is undefined"); + } + + resultElem.setValue(result); + return resultElem; + } + + + const std::string& getValue(const std::string& variableName, const xml::Element& element) const + { + return getElement(variableName, element).getValue(); + } + + std::string::size_type replaceToValueFindBracketMatch(std::string& str, + std::string::size_type posStart, + const xml::Element& element) const + { + int recursion = 1; + std::string::size_type posEnd = posStart; + while ((posEnd = str.find_first_of("()", posEnd)) != std::string::npos) { + if (str[posEnd] == ')') { + if (posEnd > 0 && str[posEnd - 1] == '\\') { + ++posEnd; + continue; + } + --recursion; + if (recursion == 0) + break; + } else { + // == '(' + // check for inline $() + if (str[posEnd - 1] == '$') { + std::string::size_type inlineEnd = replaceToValueFindBracketMatch(str, posEnd + 1, element); + NGREST_ASSERT(inlineEnd != std::string::npos, "end of inline variable name expected: [" + + str + "]"); + std::string inl = str.substr(posEnd - 1, inlineEnd - posEnd + 2); + replaceToValue(inl, element); + str.replace(posEnd - 1, inlineEnd - posEnd + 2, inl); + --posEnd; // move to prior the '$(' + continue; + } + + ++recursion; + } + ++posEnd; + } + return posEnd; + } + + + void processValue(const std::string& name, std::string& value, const xml::Element& element) const + { + if (name[0] == '$') { + if (name.substr(0, 16) == "$thisElementName") { + value = element.getName(); + if (name.size() == 16) + return; + xml::Element valElement(value); + valElement.setValue(value); + value = getValue(value + name.substr(16), valElement); + } else if (name.substr(0, 17) == "$thisElementValue") { + value = element.getTextValue(); + if (name.size() == 17) + return; + xml::Element valElement(value); + valElement.setValue(value); + value = getValue(value + name.substr(17), valElement); + } else if (name.substr(0, 16) == "$thisElementPath") { + value = GetElementPath(&element); + if (name.size() == 16) + return; + xml::Element valElement(value); + valElement.setValue(value); + value = getValue(value + name.substr(16), valElement); + } else { + std::string::size_type pos = name.find('.'); + if (pos != std::string::npos) { + // variable + functions + const std::string& varName = name.substr(1, pos - 1); + value = variables.top()[varName]; + // process functions and other + xml::Element varElement(varName); + varElement.setValue(value); + value = getValue(name.substr(1), varElement); + } else { + // variable only + value = variables.top()[name.substr(1)]; + } + } + } else { + // node value + value = getValue(name, element); + } + } + + void replaceToValue(std::string& str, const xml::Element& element) const + { + std::string::size_type posStart = 0; + std::string::size_type posEnd = 0; + + while((posStart = str.find("$(", posEnd)) != std::string::npos) { + if (posStart > 0 && str[posStart - 1] == '\\' && posEnd != posStart) { + str.erase(posStart - 1, 1); + posEnd = posStart + 1; + continue; + } + + posEnd = replaceToValueFindBracketMatch(str, posStart + 2, element); + + NGREST_ASSERT(posEnd != std::string::npos, "end of variable name expected: [" + str + "]"); + const std::string& expression = str.substr(posStart + 2, posEnd - posStart - 2); + std::string value; + + { // parse "node.name||$var.!func||some.other" + std::string::size_type namePosBegin = 0; + std::string::size_type namePosEnd = 0; + + // use first nonempty value + for(;;) { + namePosEnd = expression.find("||", namePosBegin); + + const std::string& name = + expression.substr(namePosBegin, (namePosEnd != std::string::npos) + ? namePosEnd - namePosBegin : namePosEnd); + std::string::size_type nameSize = name.size(); + + if (nameSize > 0 && name[0] == '"' && name[nameSize - 1] == '"') { + value = name.substr(1, nameSize - 2); + replaceToValue(value, element); + } else { + processValue(name, value, element); + } + + if (!value.empty() || namePosEnd == std::string::npos) + break; + + namePosBegin = namePosEnd + 2; + } + } + + str.replace(posStart, posEnd - posStart + 1, value); + posEnd = posStart + value.size(); + } + } + + void init(const std::string& inDir) + { + hasConfig = File(inDir + NGREST_PATH_SEPARATOR + "codegen.config").isRegularFile(); + if (!hasConfig) { + StringList files; + + File(inDir).list(files, "*.*", File::AttributeRegularFile); + for (StringList::const_iterator itFile = files.begin(); itFile != files.end(); ++itFile) { + if (itFile->find('$') != std::string::npos) { + templateFileList.push_back(*itFile); + } else { + constFileList.push_back(*itFile); + } + } + } + + this->inDir = inDir; + } + + void start(const std::string& outDir, const xml::Element& rootElement, bool updateOnly) + { + bool needUpdate = false; + + this->outDir = outDir; + + if (hasConfig) { + const std::string& inPath = inDir + "codegen.config"; + std::ostringstream outStream; + std::ifstream inStream; + + inStream.open(inPath.c_str()); + NGREST_ASSERT(inStream.good(), "can't open input file: " + inPath); + + indent = 0; + line = 0; + process(inStream, outStream, rootElement); + + inStream.close(); + } else { + const xml::Element& elementInterfaces = rootElement.getChildElementByName("interfaces"); + + for (const xml::Element* childElement = elementInterfaces.getFirstChildElement(); + childElement; childElement = childElement->getNextSiblingElement()) { + const xml::Element& elementInterface = *childElement; + + for (std::list::const_iterator itTemplateFile = templateFileList.begin(); + itTemplateFile != templateFileList.end(); ++itTemplateFile) { + std::string file = *itTemplateFile; + bool isProcessFile = false; + try { + replaceToValue(file, elementInterface); + isProcessFile = true; + } catch (const Exception&) { + LogDebug1() << "Skipping template file " << file + << " for interface " << elementInterface.getChildElementByName("nsName").getTextValue(); + continue; + } + + if (isProcessFile) { + // erase input path + std::string::size_type pos = file.find_last_of(NGREST_PATH_SEPARATOR); + if (pos != std::string::npos) + file.erase(0, pos + 1); + + processFile(inDir + *itTemplateFile, outDir, file, + elementInterface, updateOnly, needUpdate); + } + } + } // for interface + + for (std::list::const_iterator itTemplateFile = constFileList.begin(); + itTemplateFile != constFileList.end(); ++itTemplateFile) { + std::string file = *itTemplateFile; + + // erase input path + std::string::size_type pos = file.find_last_of(NGREST_PATH_SEPARATOR); + if (pos != std::string::npos) + file.erase(0, pos + 1); + + processFile(inDir + *itTemplateFile, outDir, file, rootElement, updateOnly, needUpdate); + } + } // has config + } + + void processIfeq(std::istream& in, std::ostream& fsOut, const xml::Element& element, std::string& line, + bool isNotEq = false) + { + bool isCurrentBlock = true; + bool isEq = false; + std::stringbuf data; + std::string lines; + int recursion = 1; + + replaceToValue(line, element); + + { //#ifeq(123,321) + int offsetPos = isNotEq ? 7 : 6; + std::string::size_type posStart = line.find(",", 6); + std::string::size_type posEnd = 0; + + NGREST_ASSERT(posStart != std::string::npos, "#ifeq expression is invalid!: \n----\n" + line + + "\n----\n"); + posEnd = line.find(')', posStart); + NGREST_ASSERT(posEnd != std::string::npos, "#ifeq expression is invalid!: \n----\n" + line + + "\n----\n"); + + std::string left = line.substr(offsetPos, posStart - offsetPos); + std::string right = line.substr(posStart + 1, posEnd - posStart - 1); + + isEq = false; + + std::string::size_type posStartLeft = 0; + std::string::size_type posStartRight = 0; + std::string::size_type posEndLeft = 0; + std::string::size_type posEndRight = 0; + do { + posEndLeft = left.find("||", posStartLeft); + const std::string& leftCmp = left.substr(posStartLeft, posEndLeft - posStartLeft); + + posStartRight = 0; + posEndRight = 0; + do { + posEndRight = right.find("||", posStartRight); + const std::string& rightCmp = right.substr(posStartRight, posEndRight - posStartRight); + + if (leftCmp == rightCmp) { + isEq = true; + break; + } + posStartRight = posEndRight + 2; + } while (posEndRight != std::string::npos); + posStartLeft = posEndLeft + 2; + } while (posEndLeft != std::string::npos && !isEq); + + if (isNotEq) { + isEq = !isEq; + } + } + + while (!in.eof() && in.good() && recursion > 0) { + if (in.peek() == '\n') { + line = "\n"; + } else { + in.get(data, '\n'); + line = data.str() + "\n"; + data.str(""); + } + in.ignore(); + in.peek(); // for EOF + + if (line.substr(0, 6) == "#ifeq(" || line.substr(0, 7) == "#ifneq(") { + ++recursion; + if (isCurrentBlock == isEq && recursion > 1) + lines += line; + } else if (line.substr(0, 5) == "#ifeqelse") { + if (isCurrentBlock == isEq && recursion > 1) + lines += line; + if (recursion == 1) + isEq = !isEq; + } else if (line.substr(0, 8) == "#ifeqend") { + if (isCurrentBlock == isEq && recursion > 1) + lines += line; + --recursion; + } else if (isCurrentBlock == isEq) { + lines += line; + } + } + + NGREST_ASSERT(recursion == 0, "Unexpected EOF while parsing: \n---------\n" + lines + + "\n------------\n"); + + { + std::istringstream stream(lines); + process(stream, fsOut, element); + } + } + + void processForEach(std::istream& in, std::ostream& out, const xml::Element& element, std::string& line) + { + std::stringbuf data; + std::string forEachExpr; + std::string lines; + int recursion = 1; + + std::string::size_type posStart = line.find("$(", 9); + std::string::size_type posEnd = 0; + + NGREST_ASSERT(posStart != std::string::npos, "foreach expression is invalid!"); + posEnd = line.find(')', posStart); + NGREST_ASSERT(posEnd != std::string::npos, "foreach expression is invalid!"); + forEachExpr = line.substr(posStart + 2, posEnd - posStart - 2); + + while (!in.eof() && in.good()) { + if (in.peek() == '\n') { + line = "\n"; + } else { + in.get(data, '\n'); + line = data.str(); + if (in.peek() == '\n') + line += "\n"; + data.str(""); + } + in.ignore(); + in.peek(); // for EOF + + if (line.substr(0, 9) == "#foreach ") { + ++recursion; + } else if (line.substr(0, 4) == "#forend") { + --recursion; + if (recursion == 0) + break; + } + + lines += line; + } + + NGREST_ASSERT(recursion == 0, "Unexpected EOF while parsing: \n---------\n" + lines + + "\n------------\n"); + + const xml::Element& subElement = getElement(forEachExpr, element); + + for (const xml::Element* childElement = subElement.getFirstChildElement(); + childElement; childElement = childElement->getNextSiblingElement()) + { + std::istringstream stream(lines); + process(stream, out, *childElement); + } + } + + void processContext(std::istream& fsIn, std::ostream& fsOut, const xml::Element& element, + std::string& line) + { + std::stringbuf data; + std::string contextExpr; + std::string lines; + int recursion = 1; + + std::string::size_type posStart = line.find("$(", 9); + std::string::size_type posEnd = 0; + + NGREST_ASSERT(posStart != std::string::npos, "context expression is invalid!"); + posEnd = line.find(')', posStart); + NGREST_ASSERT(posEnd != std::string::npos, "context expression is invalid!"); + contextExpr = line.substr(posStart, posEnd - posStart + 1); + + while (!fsIn.eof() && fsIn.good()) { + if (fsIn.peek() == '\n') { + line = "\n"; + } else { + fsIn.get(data, '\n'); + line = data.str(); + if (fsIn.peek() == '\n') + line += "\n"; + data.str(""); + } + fsIn.ignore(); + fsIn.peek(); // for EOF + + if (line.substr(0, 9) == "#context ") { + ++recursion; + } else if (line.substr(0, 11) == "#contextend") { + --recursion; + if (recursion == 0) + break; + } + + lines += line; + } + + NGREST_ASSERT(recursion == 0, "Unexpected EOF while parsing: \n---------\n" + lines + + "\n------------\n"); + + if (contextExpr[2] == '$') { + // variable + replaceToValue(contextExpr, element); + } else { + contextExpr.erase(0, 2); + contextExpr.erase(contextExpr.size() - 1); + } + + const xml::Element& subElement = getElement(contextExpr, element); + + std::istringstream stream(lines); + process(stream, fsOut, subElement); + } + + void processInclude(std::ostream& fsOut, const xml::Element& element, const std::string& line) + { + std::string includeFileName; + + char quote = *line.begin(); + std::string::size_type pos = 0; + + if (quote == '<') { + pos = line.find('>', 1); + NGREST_ASSERT(pos != std::string::npos, "cginclude expression is invalid!"); + includeFileName = inDir + "../" + line.substr(1, pos - 1); + } else if (quote == '"') { + pos = line.find('"', 1); + NGREST_ASSERT(pos != std::string::npos, "cginclude expression is invalid!"); + includeFileName = inDir + line.substr(1, pos - 1); + } else { + NGREST_THROW_ASSERT("cginclude expression is invalid!"); + } + +#ifdef WIN32 + stringReplace(includeFileName, "/", "\\", true); +#endif + + std::ifstream fsIncFile; + fsIncFile.open(includeFileName.c_str()); + + NGREST_ASSERT(fsIncFile.good(), "can't include file: " + includeFileName); + + std::string currInDir = inDir; + inDir = includeFileName.substr(0, includeFileName.find_last_of("/\\") + 1); + while (!fsIncFile.eof() && fsIncFile.good()) + process(fsIncFile, fsOut, element); + inDir = currInDir; + + fsIncFile.close(); + } + + void processIndent(const std::string& line) + { + std::string value = line.substr(8); + stringTrim(value); + if (value == "+") { + NGREST_ASSERT(indent < 1024, "Invalid indentation: " + toString(indent + 1) + + " while processing line: \n" + line); + ++indent; + } else if (value == "-") { + NGREST_ASSERT(indent > 0, "Invalid indentation: " + toString(indent - 1) + + " while processing line: \n" + line); + --indent; + } else { + int sign = 0; + int newIndent = indent; + if (value[0] == '+') { + value.erase(0, 1); + sign = 1; + } else if (value[0] == '-') { + value.erase(0, 1); + sign = -1; + } + + fromString(value, newIndent); + if (sign != 0) + newIndent = indent + sign * newIndent; + NGREST_ASSERT(newIndent < 1024 && newIndent >= 0, "Invalid indentation: " + toString(newIndent) + + " while processing line: \n" + line); + indent = newIndent; + } + } + + void process(std::istream& fsIn, std::ostream& out, const xml::Element& element) + { + std::string line; + std::stringbuf data; + + while (!fsIn.eof() && fsIn.good()) { + if (fsIn.peek() == '\n') { + line = "\n"; + } else { + fsIn.get(data, '\n'); + line = data.str(); + if (line.size() > 0 && *line.rbegin() == '\\') { + line.erase(line.size() - 1); + } else { + if (fsIn.peek() == '\n') + line += "\n"; + } + data.str(""); + } + + fsIn.ignore(); + fsIn.peek(); // for EOF + + if (line.substr(0, 5) == "#var ") { + std::string::size_type pos = line.find_first_of(" \t", 5); + std::string variable; + std::string value; + + if (pos == std::string::npos) { + variable = line.substr(5); + stringTrimRight(variable); + } else { + variable = line.substr(5, pos - 5); + value = line.substr(pos + 1, line.size() - pos - 2); + replaceToValue(value, element); + } + + NGREST_ASSERT(!variable.empty(), "invalid var declaration: " + line); + + variables.top()[variable] = value; + } else if (line.substr(0, 6) == "#ifeq(") { + processIfeq(fsIn, out, element, line); + } else if (line.substr(0, 7) == "#ifneq(") { + processIfeq(fsIn, out, element, line, true); + } else if (line.substr(0, 9) == "#foreach ") { + processForEach(fsIn, out, element, line); + } else if (line.substr(0, 9) == "#context ") { + processContext(fsIn, out, element, line); + } else if (line.substr(0, 10) == "#fileopen ") { + std::string fileName = line.substr(10); + replaceToValue(fileName, element); + stringTrim(fileName); + + NGREST_ASSERT(!fileName.empty(), "#fileopen: Filename is empty"); + + fileName = outDir + fileName; + const std::string& failedFileName = fileName + ".failed"; + + std::ofstream ofsFile(fileName.c_str()); + NGREST_ASSERT(ofsFile.good(), "can't open output file: " + fileName); + + std::cout << "Generating " << fileName << std::endl; + indent = 0; + try { + process(fsIn, ofsFile, element); + } catch(...) { + ofsFile.close(); + ::unlink(failedFileName.c_str()); + ::rename(fileName.c_str(), failedFileName.c_str()); + throw; + } + ofsFile.close(); + ::unlink(failedFileName.c_str()); + } else if (line.substr(0, 10) == "#fileclose") { + return; + } else if (line.substr(0, 7) == "#mkdir ") { + std::string dirName = line.substr(7); + replaceToValue(dirName, element); + stringTrim(dirName); + + File(outDir + dirName).mkdirs(); + } else if (line.substr(0, 11) == "#cginclude ") { + line.erase(0, 11); + stringTrim(line); + processInclude(out, element, line); + } else if (line.substr(0, 11) == "#cgwarning ") { + replaceToValue(line, element); + stringTrimRight(line, "\n\r"); + std::cerr << "Warning: " << line.substr(11) << std::endl; + } else if (line.substr(0, 9) == "#cgerror ") { + replaceToValue(line, element); + NGREST_THROW_ASSERT(line.substr(9)); + } else if (line.substr(0, 8) == "#indent ") { + processIndent(line); + } else if (line.substr(0, 11) == "#cgpushvars") { + variables.push(variables.top()); + } else if (line.substr(0, 10) == "#cgpopvars") { + variables.pop(); + } else if (line.substr(0, 11) == "#cgdumpvars") { + const StringMap& rmVars = variables.top(); + line = "variables dump:"; + for (StringMap::const_iterator itVar = rmVars.begin(); itVar != rmVars.end(); ++itVar) + line += "\n" + itVar->first + "=\"" + itVar->second + "\""; + out << line << "\n"; + } else if (!line.empty()) { + std::string indentStr; + NGREST_ASSERT(indent < 1024, "Invalid indentation: " + toString(indent)); + for (int i = 0; i < indent; ++i) { + indentStr += " "; + } + if (isNeedIndent && line[0] != '\n') { + line = indentStr + line; + isNeedIndent = false; + } + + std::string::size_type begin = 0; + std::string::size_type end = 0; + while ((end = line.find('\n', begin)) != std::string::npos) { + if (isNeedIndent && end > begin) { // line is not empty + line.insert(begin, indentStr); + end += indentStr.size(); + } + + begin = end + 1; + + isNeedIndent = true; + } + + replaceToValue(line, element); + + out << line; + } + ++this->line; + } + } + + void processFile(const std::string& in, const std::string& outDir, const std::string& outFile, + const xml::Element& elementInterface, bool updateOnly, bool& isNeedUpdate) + { + const std::string& out = outDir + outFile; + if (updateOnly) { + bool bIsStaticTemplate = elementInterface.getName() == "Project"; + int inTime = File(in).getTime(); + int outTime = File(out).getTime(); + + if (bIsStaticTemplate) { + if (!isNeedUpdate && outTime && outTime > inTime) { + std::cout << "Skipping " << out << std::endl; + return; + } + } else { + if (outTime && outTime > inTime) { + const std::string& fileName = outDir + elementInterface.getChildElementByName("fileName").getTextValue(); + int64_t interfaceTime = File(fileName).getTime(); + + if (outTime && outTime > interfaceTime) { + std::cout << "Skipping " << out << std::endl; + return; + } + + isNeedUpdate = true; + } + } + } + + { + std::ifstream fsIn; + std::ofstream fsOut; + + fsIn.open(in.c_str()); + NGREST_ASSERT(fsIn.good(), "can't open input file: " + in); + + fsOut.open(out.c_str()); + if (!fsOut.good()) { + fsIn.close(); + NGREST_THROW_ASSERT("can't open output file: " + out); + } + + std::cout << "Generating " << out << std::endl; + + line = 0; + indent = 0; + isNeedIndent = true; + process(fsIn, fsOut, elementInterface); + + fsIn.close(); + fsOut.close(); + } + } + + void setEnv(const StringMap& env) + { + variables.top() = env; + } + +private: + std::list templateFileList; + std::list constFileList; + mutable std::stack variables; + std::string inDir; + std::string outDir; + int line; + int indent; + bool isNeedIndent; + bool hasConfig; +}; + + +void CodeGen::start(const std::string& templateDir, const std::string& outDir, + const xml::Element& rootElement, bool updateOnly, const StringMap& env) +{ + TemplateParser templateParser; + + File(outDir).mkdirs(); + + templateParser.init(templateDir); + templateParser.setEnv(env); + templateParser.start(outDir, rootElement, updateOnly); +} +} +} diff --git a/tools/ngrestcg/src/CodeGen.h b/tools/ngrestcg/src/CodeGen.h new file mode 100644 index 0000000..63f06f6 --- /dev/null +++ b/tools/ngrestcg/src/CodeGen.h @@ -0,0 +1,32 @@ +#ifndef NGREST_CODEGEN_H +#define NGREST_CODEGEN_H + +#include +#include + +namespace ngrest { + +namespace xml { +class Element; +} + +namespace codegen { + +//! Code generator +class CodeGen +{ +public: + //! start code generation + /*! \param templateDir - path to templates + \param outDir - output directory + \param rootElement - root element, describing project + \param updateOnly - true: update files if needed, false: always update files + \param env - environment + */ + void start(const std::string& templateDir, const std::string& outDir, + const xml::Element& rootElement, bool updateOnly, const StringMap& env); +}; +} +} + +#endif // NGREST_CODEGEN_H diff --git a/tools/ngrestcg/src/XmlGen.cpp b/tools/ngrestcg/src/XmlGen.cpp new file mode 100644 index 0000000..bcf79b6 --- /dev/null +++ b/tools/ngrestcg/src/XmlGen.cpp @@ -0,0 +1,400 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "XmlGen.h" + +namespace ngrest { +namespace codegen { + +std::string typeToString(const DataType::Type type) +{ + switch (type) { + case DataType::Type::Generic: + return "generic"; + + case DataType::Type::String: + return "string"; + + case DataType::Type::DataObject: + return "dataobject"; + + case DataType::Type::Enum: + return "enum"; + + case DataType::Type::Struct: + return "struct"; + + case DataType::Type::Typedef: + return "typedef"; + + case DataType::Type::Template: + return "template"; + + default: + return "unknown"; + } +} + + +template +xml::Element& operator<<(xml::Element& element, const std::list& list) +{ + for(typename std::list::const_iterator it = list.begin(); it != list.end(); ++it) + element << *it; + return element; +} + +bool dataTypeToString(std::string& out, const DataType& dataType, bool asUsed = false, + bool noModifiers = false) +{ + if (!noModifiers && dataType.isConst) + out += "const "; + + if (!noModifiers && !dataType.prefix.empty()) + out += dataType.prefix + " "; + + if (asUsed && !dataType.usedName.empty()) { + out += dataType.usedName; + } else { + out += dataType.ns; + if (!dataType.ownerName.empty()) + out += dataType.ownerName + "::"; + out += dataType.name; + } + + bool bIsTemplate = !dataType.params.empty(); + if (!bIsTemplate) { + if (!noModifiers) { + out += (dataType.isRef ? "&" : ""); + } + return false; + } + + out += "<"; + std::string::size_type beginTemplatePos = out.size(); + bool space = !dataType.params.back().params.empty(); + bool first = true; + + for (std::list::const_iterator it = dataType.params.begin(); + it != dataType.params.end(); ++it) { + if (!first) + out += ", "; + bIsTemplate = dataTypeToString(out, *it, asUsed); + if (first) { + if (out.substr(beginTemplatePos, 2) == "::") + space = true; + first = false; + } + } + if (space) { // first parameter begins with :: or last parameter was template: insert spaces + out.insert(beginTemplatePos, " "); + out += ' '; + } + out += ">"; + + if (!noModifiers) + out += (dataType.isRef ? "&" : ""); + return true; +} + +xml::Element& operator<<(xml::Element& elemDataTypes, const DataType& dataType) +{ + std::string usedTypedef; + dataTypeToString(usedTypedef, dataType, true); + + std::string nsName; + dataTypeToString(nsName, dataType, false, true); + + std::string type; + dataTypeToString(type, dataType); + elemDataTypes.setValue(type); + + elemDataTypes.createElement("isConst", dataType.isConst); + elemDataTypes.createElement("isRef", dataType.isRef); + elemDataTypes.createElement("usedName", !dataType.usedName.empty() ? dataType.usedName : usedTypedef); + elemDataTypes.createElement("name", dataType.name); + elemDataTypes.createElement("nsName", nsName); + elemDataTypes.createElement("ns", dataType.ns); + elemDataTypes.createElement("type", typeToString(dataType.type)); + elemDataTypes.createElement("usedTypedef", usedTypedef); + + xml::Element& elemTemplateParams = elemDataTypes.createElement("templateParams"); + int num = 1; + for(std::list::const_iterator it = dataType.params.begin(); + it != dataType.params.end(); ++it, ++num) + elemTemplateParams.createElement("templateParam" + toString(num)) << *it; + + if (dataType.type == DataType::Type::Unknown) + LogWarning() << "Unknown datatype: " << (dataType.ns + dataType.name); + + return elemDataTypes; +} + +xml::Element& operator<<(xml::Element& elemParams, const Param& param) +{ + if (param.name.size() != 0 && param.dataType.name != "void") { + xml::Element& elemParam = elemParams.createElement("param", ""); + + elemParam.createElement("name", param.name); + elemParam.createElement("description", param.description); + elemParam.createElement("details", param.details); + + xml::Element& elemOptions = elemParam.createElement("options"); + for (StringMap::const_iterator itOption = param.options.begin(); + itOption != param.options.end(); ++itOption) + elemOptions.createElement(itOption->first, itOption->second); + elemParam.createElement("dataType") << param.dataType; + + std::string value = elemParams.getValue(); + if (!value.empty()) + value += ", "; + + dataTypeToString(value, param.dataType, true); + value += " " + param.name; + elemParams.setValue(value); + } + + return elemParams; +} + +xml::Element& operator<<(xml::Element& elemOperations, const Operation& operation) +{ + xml::Element& elemOperation = elemOperations.createElement("operation"); + + elemOperation.createElement("name", operation.name); + elemOperation.createElement("description", operation.description); + elemOperation.createElement("details", operation.details); + elemOperation.createElement("isConst", operation.isConst); + elemOperation.createElement("const", operation.isConst ? " const" : ""); + elemOperation.createElement("isAsynch", operation.isAsynch); + + xml::Element& elemOptions = elemOperation.createElement("options"); + + for (StringMap::const_iterator itOption = operation.options.begin(); + itOption != operation.options.end(); ++itOption) + elemOptions.createElement(itOption->first, itOption->second); + + elemOperation.createElement("params", "") << operation.params; + + xml::Element& elemReturn = elemOperation.createElement("return"); + elemReturn << operation.returnType.dataType; + elemReturn.createElement("responseName", operation.returnType.name); + + return elemOperations; +} + + +xml::Element& operator<<(xml::Element& elemEnumOperations, const Enum::Member& member) +{ + xml::Element& elemOperation = elemEnumOperations.createElement("operation"); + + elemOperation.createElement("name", member.name); + elemOperation.createElement("value", member.value); + elemOperation.createElement("description", member.description); + + return elemEnumOperations; +} + +void writeCppNs(xml::Element& node, const std::string& ns) +{ + const std::string& realNs = (ns.substr(0, 2) == "::") ? ns.substr(2) : ns; + + std::string startNs; + std::string endNs; + std::string::size_type pos = 0; + std::string::size_type prevPos = 0; + while((pos = realNs.find("::", pos)) != std::string::npos) { + startNs += "namespace " + realNs.substr(prevPos, pos - prevPos) + "\n{\n"; + endNs += "}\n"; + pos += 2; + prevPos = pos; + } + + node.createElement("startCppNs", startNs); + node.createElement("endCppNs", endNs); +} + +xml::Element& operator<<(xml::Element& elemServices, const Service& service) +{ + xml::Element& elemService = elemServices.createElement("service"); + + std::string serviceNs = (service.ns.substr(0, 2) == "::") ? + service.ns.substr(2) : service.ns; + stringReplace(serviceNs, "::", ".", true); + + elemService.createElement("name", service.name); + elemService.createElement("ns", service.ns); + elemService.createElement("nsName", service.ns + service.name); + elemService.createElement("serviceNs", serviceNs); + elemService.createElement("serviceNsName", serviceNs + service.name); + elemService.createElement("description", service.description); + elemService.createElement("details", service.details); + + xml::Element& elemOptions = elemService.createElement("options"); + for (StringMap::const_iterator itOption = service.options.begin(); + itOption != service.options.end(); ++itOption) + elemOptions.createElement(itOption->first, itOption->second); + + xml::Element& elemModules = elemService.createElement("modules"); + for (StringList::const_iterator itModule = service.modules.begin(); + itModule != service.modules.end(); ++itModule) + elemModules.createElement("module", *itModule); + + elemService.createElement("operations") << service.operations; + + writeCppNs(elemService, service.ns); + + return elemServices; +} + +xml::Element& operator<<(xml::Element& elemEnums, const Enum& en) +{ + NGREST_ASSERT(!en.isForward || en.isExtern, "Enum \"" + en.name + "\" is not fully declared"); + + xml::Element& elemEnum = elemEnums.createElement("enum"); + + elemEnum.createElement("name", en.name); + elemEnum.createElement("nsName", en.ns + + (en.ownerName.empty() ? "" : (en.ownerName + "::")) + en.name); + elemEnum.createElement("ns", en.ns); + elemEnum.createElement("owner", en.ownerName); + elemEnum.createElement("description", en.description); + elemEnum.createElement("details", en.details); + elemEnum.createElement("isExtern", en.isExtern); + elemEnum.createElement("members") << en.members; + + writeCppNs(elemEnum, en.ns); + + xml::Element& elemOptions = elemEnum.createElement("options"); + + for (StringMap::const_iterator itOption = en.options.begin(); + itOption != en.options.end(); ++itOption) + elemOptions.createElement(itOption->first, itOption->second); + + return elemEnums; +} + +xml::Element& operator<<(xml::Element& elemStructs, const Struct& structure) +{ + NGREST_ASSERT(!structure.isForward || structure.isExtern, + "Struct \"" + structure.name + "\" is not fully declared"); + + xml::Element& elemStruct = elemStructs.createElement("struct"); + + elemStruct.createElement("name", structure.name); + elemStruct.createElement("nsName", structure.ns + + (structure.ownerName.empty() ? "" : (structure.ownerName + "::")) + + structure.name); + elemStruct.createElement("ns", structure.ns); + elemStruct.createElement("owner", structure.ownerName); + + // parent + std::string::size_type pos = structure.parentName.find_last_of("::"); + const std::string parentName = (pos != std::string::npos) ? + structure.parentName.substr(pos + 1) : structure.parentName; + elemStruct.createElement("parentName", parentName); + elemStruct.createElement("parentUsedName", structure.parentName); + elemStruct.createElement("parentNs", structure.parentNs); + elemStruct.createElement("parentNsName", structure.parentNs + parentName); + elemStruct.createElement("description", structure.description); + elemStruct.createElement("details", structure.details); + elemStruct.createElement("isExtern", structure.isExtern); + elemStruct.createElement("operations", "") << structure.fields; + + writeCppNs(elemStruct, structure.ns); + + xml::Element& elemOptions = elemStruct.createElement("options"); + for (StringMap::const_iterator itOption = structure.options.begin(); + itOption != structure.options.end(); ++itOption) + elemOptions.createElement(itOption->first, itOption->second); + + elemStruct.createElement("enums") << structure.enums; + + elemStruct.createElement("structs") << structure.structs; + + return elemStructs; +} + +xml::Element& operator<<(xml::Element& elemTypedefs, const Typedef& td) +{ + xml::Element& elemTypedef = elemTypedefs.createElement("typedef"); + + elemTypedef.createElement("name", td.name); + elemTypedef.createElement("nsName", td.ns + td.name); + elemTypedef.createElement("ns", td.ns); + elemTypedef.createElement("description", td.description); + elemTypedef.createElement("details", td.details); + elemTypedef.createElement("dataType") << td.dataType; + elemTypedef.createElement("isExtern", td.isExtern); + + writeCppNs(elemTypedef, td.ns); + + xml::Element& elemOptions = elemTypedef.createElement("options"); + + for (StringMap::const_iterator itOption = td.options.begin(); + itOption != td.options.end(); ++itOption) { + elemOptions.createElement(itOption->first, itOption->second); + } + + return elemTypedefs; +} + +xml::Element& operator<<(xml::Element& elemInterfaces, const Interface& interface) +{ + xml::Element& elemInterface = elemInterfaces.createElement("interface"); + + elemInterface.createElement("name", interface.name); + elemInterface.createElement("ns", interface.ns); + elemInterface.createElement("nsName", interface.ns + interface.name); + elemInterface.createElement("fileName", interface.fileName); + elemInterface.createElement("filePath", interface.filePath); + + xml::Element& elemOptions = elemInterface.createElement("options"); + for (StringMap::const_iterator itOption = interface.options.begin(); + itOption != interface.options.end(); ++itOption) { + elemOptions.createElement(itOption->first, itOption->second); + } + + // included files + xml::Element& elemIncludes = elemInterface.createElement("includes"); + for (std::list::const_iterator itInclude = interface.includes.begin(); + itInclude != interface.includes.end(); ++itInclude) { + std::string ns = itInclude->ns.substr(0, 2) == "::" ? itInclude->ns.substr(2) : itInclude->ns; + stringReplace(ns, "::", ".", true); + + xml::Element& elemInclude = elemIncludes.createElement("include"); + elemInclude.createElement("name", itInclude->interfaceName); + elemInclude.createElement("ns", itInclude->ns); + elemInclude.createElement("nsName", ns + itInclude->interfaceName); + elemInclude.createElement("fileName", itInclude->fileName); + elemInclude.createElement("filePath", itInclude->filePath); +// elemInclude.createElement("targetNs", itInclude->targetNs); + } + + elemInterface.createElement("enums") << interface.enums; + elemInterface.createElement("structs") << interface.structs; + elemInterface.createElement("typedefs") << interface.typedefs; + elemInterface.createElement("services") << interface.services; + + return elemInterfaces; +} + +xml::Element& operator<<(xml::Element& rootNode, const Project& project) +{ + rootNode.setName("project"); + rootNode.createElement("name", project.name); + rootNode.createElement("ns", project.ns); + writeCppNs(rootNode, project.ns); + + rootNode.createElement("interfaces") << project.interfaces; + + return rootNode; +} + +} +} diff --git a/tools/ngrestcg/src/XmlGen.h b/tools/ngrestcg/src/XmlGen.h new file mode 100644 index 0000000..df34a65 --- /dev/null +++ b/tools/ngrestcg/src/XmlGen.h @@ -0,0 +1,24 @@ +#ifndef NGREST_XMLGEN_H +#define NGREST_XMLGEN_H + + +namespace ngrest { + +namespace xml { +class Element; +} + +namespace codegen { + +struct Project; + +//! process project struct into xml +/*! \param rootNode - resulting xml-project node + \param project - project + \return resulting xml-project node + */ +xml::Element& operator<<(xml::Element& rootNode, const Project& project); +} +} + +#endif // NGREST_XMLGEN_H diff --git a/tools/ngrestcg/src/main.cpp b/tools/ngrestcg/src/main.cpp new file mode 100644 index 0000000..70689d3 --- /dev/null +++ b/tools/ngrestcg/src/main.cpp @@ -0,0 +1,295 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "CodeGen.h" +#include "XmlGen.h" +#if !defined VERSION_FULL && defined _MSC_VER +#include "version.h" +#endif + +void help() +{ + std::cerr << "Code generator for ngrest\n" + "ngrest_codegen [source files][-t