From d6e549a7c4211cfba21179c5f403bc7e3d35b6aa Mon Sep 17 00:00:00 2001 From: DokurOmkar Date: Tue, 23 Jul 2024 14:11:12 -0700 Subject: [PATCH 01/24] VH 1319 - Updated the script to support versions that have multi architecture images --- configuration/initialization.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/configuration/initialization.sh b/configuration/initialization.sh index ba43e353a..7fa118d61 100755 --- a/configuration/initialization.sh +++ b/configuration/initialization.sh @@ -23,15 +23,16 @@ latest_version=$(echo "$release_info" | grep -o '"tag_name": *"[^"]*"' | cut -d # Fetching all tags from Git repository tags=$(git ls-remote --tags https://github.com/usdot-fhwa-OPS/V2X-Hub.git | awk -F/ '{print $3}' | sort -V) -# Remove curly braces, Properties found, and duplicate entries -updated_tags=$(echo "$tags" | sed 's/\^{}//' | grep -v '^Properties_Found$' | awk '!seen[$0]++') +# Remove curly braces, Properties found, duplicate entries, and show only versions above 7.0 +updated_tags=$(echo "$tags" | sed 's/\^{}//;s/^v//' | grep -v '^Properties_Found$' | awk '!seen[$0]++ && $1 >= "7.0"') # Displaying all available versions +echo "Note: V2X-Hub multi architecture deployments only work for the versions 7.0 and above." echo "Available versions:" echo "$updated_tags" # select a version or accept the latest version as default -read -r -p "Enter V2X-Hub Version (choose from the above, or press Enter to use latest version $latest_version): " chosen_version +read -r -p "Enter V2X-Hub Version (choose from the above, or press Enter to use the latest version $latest_version): " chosen_version V2XHUB_VERSION=${chosen_version:-$latest_version} # Enable Port Drayage functionality From bac1ef26980c05a92cd0063cb2585e9ca26279c7 Mon Sep 17 00:00:00 2001 From: DokurOmkar Date: Thu, 25 Jul 2024 11:18:37 -0700 Subject: [PATCH 02/24] VH 1319 - Updated the add user script to support both plain and sha2 passwords --- configuration/initialization.sh | 2 +- configuration/mysql/add_v2xhub_user.bash | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/configuration/initialization.sh b/configuration/initialization.sh index 7fa118d61..996c78487 100755 --- a/configuration/initialization.sh +++ b/configuration/initialization.sh @@ -150,6 +150,6 @@ fi # Create V2X Hub user cd "$mysqlDir" || return # return in case cd fails -./add_v2xhub_user.bash +./add_v2xhub_user.bash "$V2XHUB_VERSION" chromium-browser --ignore-certificate-errors localhost > /dev/null 2>&1 & diff --git a/configuration/mysql/add_v2xhub_user.bash b/configuration/mysql/add_v2xhub_user.bash index 6ee0250e4..c3bb636e4 100755 --- a/configuration/mysql/add_v2xhub_user.bash +++ b/configuration/mysql/add_v2xhub_user.bash @@ -2,6 +2,16 @@ # Fail when any command fails #set -e +# Check if V2XHUB_VERSION is passed +if [ -n "$1" ]; then + V2XHUB_VERSION="$1" +else + # Prompt user for V2XHUB_VERSION + read -p "Enter the deployed V2X-Hub version number: " V2XHUB_VERSION +fi + +echo "Adding V2X-Hub user for version: $V2XHUB_VERSION" + # Ensure mysql-client is installed arch=$(dpkg --print-architecture) # TODO: Add a common mysql-client that works for ARM and AMD devices @@ -34,7 +44,12 @@ if [ $PASS_LENGTH -ge 8 ] && echo $PASS | grep -q [a-z] && echo $PASS | grep -q done echo "VALID PASSWORD" echo "Enter MYSQL ROOT PASSWORD: " - mysql -uroot -p --silent -h127.0.0.1 -e "INSERT INTO IVP.user (IVP.user.username, IVP.user.password, IVP.user.accessLevel) VALUES('$USER', SHA2('$PASS', 256), 3)" + # Check if V2XHUB_VERSION is >= 7.5.0 + if [[ "$(echo "$V2XHUB_VERSION 7.5.0" | awk '{print ($1 >= $2)}')" -eq 1 ]]; then + mysql -uroot -p --silent -h127.0.0.1 -e "INSERT INTO IVP.user (IVP.user.username, IVP.user.password, IVP.user.accessLevel) VALUES('$USER', SHA2('$PASS', 256), 3)" + else + mysql -uroot -p --silent -h127.0.0.1 -e "INSERT INTO IVP.user (IVP.user.username, IVP.user.password, IVP.user.accessLevel) VALUES('$USER', '$PASS', 3)" + fi echo "V2X Hub user successfully added" else echo "INVALID PASSWORD" From cfd436961df360f2c8a43cbdbb20b19996131c7b Mon Sep 17 00:00:00 2001 From: DokurOmkar Date: Fri, 26 Jul 2024 11:49:25 -0700 Subject: [PATCH 03/24] VH 1319 - Updated the script based on PR feedback --- configuration/mysql/add_v2xhub_user.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configuration/mysql/add_v2xhub_user.bash b/configuration/mysql/add_v2xhub_user.bash index c3bb636e4..bc605c7f2 100755 --- a/configuration/mysql/add_v2xhub_user.bash +++ b/configuration/mysql/add_v2xhub_user.bash @@ -7,7 +7,7 @@ if [ -n "$1" ]; then V2XHUB_VERSION="$1" else # Prompt user for V2XHUB_VERSION - read -p "Enter the deployed V2X-Hub version number: " V2XHUB_VERSION + read -r -p "Enter the deployed V2X-Hub version number: " V2XHUB_VERSION fi echo "Adding V2X-Hub user for version: $V2XHUB_VERSION" From 2303a4952766c9f74b96c10ea3ee3ae55f2607c9 Mon Sep 17 00:00:00 2001 From: paulbourelly999 <77466294+paulbourelly999@users.noreply.github.com> Date: Wed, 31 Jul 2024 09:12:44 -0400 Subject: [PATCH 04/24] Vh 1216 fix spat plugin segfault simulation (#621) # PR Details ## Description This pull request updates the SPAT plugin to fix a number of issues with the legacy implementation. - Overly complex legacy code in SPaT Plugin - Reduced duplication using existing classes for SNMP Client and UDP Server. Previous implementation created custom SNMP Client and UDP server. - Reduced use of raw pointers to mitigate the possibility of memory leaks. - Improved documentation for SPaT plugin. - Added unit testing - Added functionality to consume J2735 UPER HEX SPaT from signal controller. Additionally to help provide these improvements in the SPaT plugin, several improvements were made in the TMX Utils functionality. - Fix SNMP Client to have working SNMP Set implementation (allows reused of existing client instead of standalone SNMP client implementation for SPaT plugin) - Improvement PluginClientClockAware base class to automatically subscribe to TimeSync messages and getter for clock to wait for clock initialization. These improvements removed all simulation specific implementation from inheriting classes including the SpatPlugin ## Related Issue [VH-1216](https://usdot-carma.atlassian.net/browse/VH-1216) ## Motivation and Context Fix SPaT plugin for simulation and improve overall usability and maintainability. ## How Has This Been Tested? Tested using virtual signal controller for both binary and hex spat payloads. Also tested in sim mode using the following steps 1) Deploy Virtual Signal Controller. 2) Deploy V2X-Hub in SIM MODE using Dev Container deployment 3) Run `src/v2i-hub/CDASimAdapter/scripts/send_timestep_udp.py` script to send periodic time sync messages. 4) Enable SPaT Plugin ## Types of changes - [x] Defect fix (non-breaking change that fixes an issue) - [x] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that cause existing functionality to change) ## Checklist: - [ ] I have added any new packages to the sonar-scanner.properties file - [x] My change requires a change to the documentation. - [x] I have updated the documentation accordingly. - [x] I have read the **CONTRIBUTING** document. [V2XHUB Contributing Guide](https://github.com/usdot-fhwa-OPS/V2X-Hub/blob/develop/Contributing.md) - [x] I have added tests to cover my changes. - [x] All new and existing tests passed. [VH-1216]: https://usdot-carma.atlassian.net/browse/VH-1216?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ --- src/tmx/Messages/test/Main.cpp | 23 + src/tmx/Messages/test/TestTimeSyncMessage.cpp | 10 + .../TmxUtils/{test => src}/MockSNMPClient.h | 0 src/tmx/TmxUtils/src/MockUdpServer.h | 2 +- .../TmxUtils/src/PluginClientClockAware.cpp | 13 +- src/tmx/TmxUtils/src/PluginClientClockAware.h | 11 +- src/tmx/TmxUtils/src/SNMPClient.cpp | 66 +- src/tmx/TmxUtils/src/SNMPClient.h | 12 + src/tmx/TmxUtils/test/J2735MessageTest.cpp | 2 +- src/tmx/TmxUtils/test/test_SNMPClient.cpp | 6 +- .../src/MUSTSensorDriverPlugin.cpp | 4 - src/v2i-hub/MapPlugin/src/MapPlugin.cpp | 3 +- src/v2i-hub/SpatPlugin/CMakeLists.txt | 50 +- src/v2i-hub/SpatPlugin/README.md | 82 +++ src/v2i-hub/SpatPlugin/docs/hex_tcpdump.png | Bin 0 -> 141628 bytes .../SpatPlugin/docs/spat_plugin_design.png | Bin 0 -> 21312 bytes src/v2i-hub/SpatPlugin/manifest.json | 35 +- src/v2i-hub/SpatPlugin/src/NTCIP1202.cpp | 116 ++-- src/v2i-hub/SpatPlugin/src/NTCIP1202.h | 45 +- src/v2i-hub/SpatPlugin/src/NTCIP1202OIDs.h | 29 + .../src/PedestrianDetectionForSPAT.cpp | 50 -- .../src/PedestrianDetectionForSPAT.h | 14 - .../src/SignalControllerConnection.cpp | 87 +++ .../src/SignalControllerConnection.h | 104 +++ src/v2i-hub/SpatPlugin/src/SpatPlugin.cpp | 273 ++++---- src/v2i-hub/SpatPlugin/src/SpatPlugin.h | 155 +++-- .../SpatPlugin/src/signalController.cpp | 289 --------- src/v2i-hub/SpatPlugin/src/signalController.h | 77 --- .../SpatPlugin/src/utils/PerformanceTimer.h | 36 - src/v2i-hub/SpatPlugin/test/Main.cpp | 17 +- .../{test_NTCIP1202.cpp => TestNTCIP1202.cpp} | 75 +-- .../test/TestSignalControllerConnection.cpp | 614 ++++++++++++++++++ .../test/testPedestrianDetectionForSPAT.cpp | 80 --- .../test_spat_binaries/spat_1721238398773.bin | Bin 0 -> 241 bytes 34 files changed, 1387 insertions(+), 993 deletions(-) create mode 100644 src/tmx/Messages/test/Main.cpp create mode 100644 src/tmx/Messages/test/TestTimeSyncMessage.cpp rename src/tmx/TmxUtils/{test => src}/MockSNMPClient.h (100%) create mode 100644 src/v2i-hub/SpatPlugin/README.md create mode 100644 src/v2i-hub/SpatPlugin/docs/hex_tcpdump.png create mode 100644 src/v2i-hub/SpatPlugin/docs/spat_plugin_design.png create mode 100644 src/v2i-hub/SpatPlugin/src/NTCIP1202OIDs.h delete mode 100644 src/v2i-hub/SpatPlugin/src/PedestrianDetectionForSPAT.cpp delete mode 100644 src/v2i-hub/SpatPlugin/src/PedestrianDetectionForSPAT.h create mode 100644 src/v2i-hub/SpatPlugin/src/SignalControllerConnection.cpp create mode 100644 src/v2i-hub/SpatPlugin/src/SignalControllerConnection.h delete mode 100644 src/v2i-hub/SpatPlugin/src/signalController.cpp delete mode 100644 src/v2i-hub/SpatPlugin/src/signalController.h delete mode 100644 src/v2i-hub/SpatPlugin/src/utils/PerformanceTimer.h rename src/v2i-hub/SpatPlugin/test/{test_NTCIP1202.cpp => TestNTCIP1202.cpp} (56%) create mode 100644 src/v2i-hub/SpatPlugin/test/TestSignalControllerConnection.cpp delete mode 100644 src/v2i-hub/SpatPlugin/test/testPedestrianDetectionForSPAT.cpp create mode 100644 src/v2i-hub/SpatPlugin/test/test_spat_binaries/spat_1721238398773.bin diff --git a/src/tmx/Messages/test/Main.cpp b/src/tmx/Messages/test/Main.cpp new file mode 100644 index 000000000..5c1d740bb --- /dev/null +++ b/src/tmx/Messages/test/Main.cpp @@ -0,0 +1,23 @@ +/** + * Copyright (C) 2024 LEIDOS. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +#include + +int main(int argc, char **argv) +{ + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/src/tmx/Messages/test/TestTimeSyncMessage.cpp b/src/tmx/Messages/test/TestTimeSyncMessage.cpp new file mode 100644 index 000000000..a853ed2b2 --- /dev/null +++ b/src/tmx/Messages/test/TestTimeSyncMessage.cpp @@ -0,0 +1,10 @@ +#include +#include "TimeSyncMessage.h" +namespace tmx::messages { + + TEST(TestTimeSyncMessage, to_string) { + TimeSyncMessage msg(20, 30); + std::string json = "{ \"timestep\":20, \"seq\":30}"; + EXPECT_EQ( json, msg.to_string()); + } +} \ No newline at end of file diff --git a/src/tmx/TmxUtils/test/MockSNMPClient.h b/src/tmx/TmxUtils/src/MockSNMPClient.h similarity index 100% rename from src/tmx/TmxUtils/test/MockSNMPClient.h rename to src/tmx/TmxUtils/src/MockSNMPClient.h diff --git a/src/tmx/TmxUtils/src/MockUdpServer.h b/src/tmx/TmxUtils/src/MockUdpServer.h index 533965711..0197f54bb 100644 --- a/src/tmx/TmxUtils/src/MockUdpServer.h +++ b/src/tmx/TmxUtils/src/MockUdpServer.h @@ -24,6 +24,6 @@ namespace tmx::utils { MOCK_METHOD(std::string, GetAddress, (), (const, override)); MOCK_METHOD(int, Receive, (char *msg, size_t maxSize), (override)); MOCK_METHOD(int, GetSocket, (), (override, const)); + MOCK_METHOD(std::string, stringTimedReceive, (int maxWait_ms), (override)); }; - } \ No newline at end of file diff --git a/src/tmx/TmxUtils/src/PluginClientClockAware.cpp b/src/tmx/TmxUtils/src/PluginClientClockAware.cpp index 4cf87fe6b..cf08e3b43 100644 --- a/src/tmx/TmxUtils/src/PluginClientClockAware.cpp +++ b/src/tmx/TmxUtils/src/PluginClientClockAware.cpp @@ -14,16 +14,16 @@ namespace tmx::utils { clock = std::make_shared(_simulation_mode); if (_simulation_mode) { AddMessageFilter(this, &PluginClientClockAware::HandleTimeSyncMessage); - + SubscribeToMessages(); } } void PluginClientClockAware::HandleTimeSyncMessage(tmx::messages::TimeSyncMessage &msg, routeable_message &routeableMsg ) { - PLOG(logDEBUG) << "Message Received " << msg.to_string() << std::endl; - this->getClock()->update( msg.get_timestep() ); - if (sim::is_simulation_mode() ) { + if (_simulation_mode ) { + PLOG(logDEBUG) << "Message Received " << msg.to_string() << std::endl; + clock->update( msg.get_timestep() ); SetStatus(Key_Simulation_Time_Step, Clock::ToUtcPreciseTimeString(msg.get_timestep())); } } @@ -39,4 +39,9 @@ namespace tmx::utils { return _simulation_mode; } + std::shared_ptr PluginClientClockAware::getClock() const { + clock->wait_for_initialization(); // Blocks until first call to update when in sim mode. + return clock; + } + } \ No newline at end of file diff --git a/src/tmx/TmxUtils/src/PluginClientClockAware.h b/src/tmx/TmxUtils/src/PluginClientClockAware.h index 974f55060..41512809a 100644 --- a/src/tmx/TmxUtils/src/PluginClientClockAware.h +++ b/src/tmx/TmxUtils/src/PluginClientClockAware.h @@ -33,15 +33,14 @@ class PluginClientClockAware : public PluginClient { protected: /** - * @brief Method for child classes to use to retrieve the clock object and get the simulation or real time. - * @return + * @brief Method for child classes to use to retrieve the clock object and get the simulation or real time. + * In simulation mode method will block until first time update has been received. + * @return clock */ - inline std::shared_ptr getClock() const { - return clock; - } + std::shared_ptr getClock() const ; void OnStateChange(IvpPluginState state) override; - + bool isSimulationMode() const; diff --git a/src/tmx/TmxUtils/src/SNMPClient.cpp b/src/tmx/TmxUtils/src/SNMPClient.cpp index e3398ebbd..1cbb53853 100644 --- a/src/tmx/TmxUtils/src/SNMPClient.cpp +++ b/src/tmx/TmxUtils/src/SNMPClient.cpp @@ -165,27 +165,17 @@ namespace tmx::utils PLOG(logINFO) << "Response request status: " << status << " (=" << (status == STAT_SUCCESS ? "SUCCESS" : "FAILED") << ")"; // Check GET response - if (status == STAT_SUCCESS && response && response->errstat == SNMP_ERR_NOERROR && request_type == request_type::GET) + if (status == STAT_SUCCESS && response && response->errstat == SNMP_ERR_NOERROR ) { - for (auto vars = response->variables; vars; vars = vars->next_variable) - { - // Get value of variable depending on ASN.1 type - // Variable could be a integer, string, bitstring, ojbid, counter : defined here https://github.com/net-snmp/net-snmp/blob/master/include/net-snmp/types.h - // get Integer value - if (vars->type == ASN_INTEGER && vars->val.integer) - { - val.type = snmp_response_obj::response_type::INTEGER; - val.val_int = *vars->val.integer; - } - else if (vars->type == ASN_OCTET_STR && vars->val.string) - { - size_t str_len = vars->val_len; - for (size_t i = 0; i < str_len; ++i) - { - val.val_string.push_back(vars->val.string[i]); - } - val.type = snmp_response_obj::response_type::STRING; - } + if ( request_type == request_type::GET ) { + process_snmp_get_response(val, *response); + } + else if( request_type == request_type::SET){ + process_snmp_set_response(val, input_oid); + } + else { + log_error(status, request_type, response); + return false; } } else @@ -208,6 +198,42 @@ namespace tmx::utils return port_; } + void snmp_client::process_snmp_get_response(snmp_response_obj &val, const snmp_pdu &response) const { + for (auto vars = response.variables; vars; vars = vars->next_variable) + { + // Get value of variable depending on ASN.1 type + // Variable could be a integer, string, bitstring, ojbid, counter : defined here https://github.com/net-snmp/net-snmp/blob/master/include/net-snmp/types.h + // get Integer value + if (vars->type == ASN_INTEGER && vars->val.integer) + { + val.type = snmp_response_obj::response_type::INTEGER; + val.val_int = *vars->val.integer; + } + else if (vars->type == ASN_OCTET_STR && vars->val.string) + { + size_t str_len = vars->val_len; + for (size_t i = 0; i < str_len; ++i) + { + val.val_string.push_back(vars->val.string[i]); + } + val.type = snmp_response_obj::response_type::STRING; + } + } + } + + void snmp_client::process_snmp_set_response( const snmp_response_obj &val, const std::string &input_oid) const { + if(val.type == snmp_response_obj::response_type::INTEGER){ + FILE_LOG(logDEBUG) << "Success in SET for OID: " << input_oid << " Value: " << val.val_int << std::endl; + } + + else if(val.type == snmp_response_obj::response_type::STRING){ + FILE_LOG(logDEBUG) << "Success in SET for OID: " << input_oid << " Value:" << std::endl; + for(auto data : val.val_string){ + FILE_LOG(logDEBUG) << data ; + } + } + } + void snmp_client::log_error(const int &status, const request_type &request_type, const snmp_pdu *response) const { diff --git a/src/tmx/TmxUtils/src/SNMPClient.h b/src/tmx/TmxUtils/src/SNMPClient.h index 75eaa9360..f36646a53 100644 --- a/src/tmx/TmxUtils/src/SNMPClient.h +++ b/src/tmx/TmxUtils/src/SNMPClient.h @@ -68,6 +68,18 @@ namespace tmx::utils int snmp_version_ = 3; // default to 3 since previous versions not compatable currently /*Time after which the the snmp request times out*/ int timeout_ = 10000; + /** + * @brief Helper method for populating snmp_respons_obj with SNMP get response. + * @param val response object + * @param response pdu + */ + void process_snmp_get_response(snmp_response_obj &val, const snmp_pdu &response) const; + /** + * @brief Helper method for logging successful SNMP set responses + * @param val response object + * @param input_oid OID + */ + void process_snmp_set_response( const snmp_response_obj &val, const std::string &input_oid) const; public: /** @brief Constructor for Traffic Signal Controller Service client. diff --git a/src/tmx/TmxUtils/test/J2735MessageTest.cpp b/src/tmx/TmxUtils/test/J2735MessageTest.cpp index 1bc605656..609cf456d 100644 --- a/src/tmx/TmxUtils/test/J2735MessageTest.cpp +++ b/src/tmx/TmxUtils/test/J2735MessageTest.cpp @@ -814,7 +814,7 @@ TEST_F(J2735MessageTest, EncodeSDSM) tmx::messages::SdsmEncodedMessage SdsmEncodeMessage; auto _sdsmMessage = new tmx::messages::SdsmMessage(message); tmx::messages::MessageFrameMessage frame_msg(_sdsmMessage->get_j2735_data()); - SdsmEncodeMessage.set_data(TmxJ2735EncodedMessage::encode_j2735_message>(frame_msg)); + SdsmEncodeMessage.set_data(TmxJ2735EncodedMessage::encode_j2735_message>(frame_msg)); free(message); free(frame_msg.get_j2735_data().get()); ASSERT_EQ(41, SdsmEncodeMessage.get_msgId()); diff --git a/src/tmx/TmxUtils/test/test_SNMPClient.cpp b/src/tmx/TmxUtils/test/test_SNMPClient.cpp index 3e775945c..55871477d 100644 --- a/src/tmx/TmxUtils/test/test_SNMPClient.cpp +++ b/src/tmx/TmxUtils/test/test_SNMPClient.cpp @@ -1,7 +1,7 @@ -#include "MockSNMPClient.h" -#include "gtest/gtest.h" -#include "RSU_MIB_4_1.h" +#include +#include +#include using namespace tmx::utils; using namespace std; diff --git a/src/v2i-hub/MUSTSensorDriverPlugin/src/MUSTSensorDriverPlugin.cpp b/src/v2i-hub/MUSTSensorDriverPlugin/src/MUSTSensorDriverPlugin.cpp index 946d67869..024430b4c 100644 --- a/src/v2i-hub/MUSTSensorDriverPlugin/src/MUSTSensorDriverPlugin.cpp +++ b/src/v2i-hub/MUSTSensorDriverPlugin/src/MUSTSensorDriverPlugin.cpp @@ -23,10 +23,6 @@ namespace MUSTSensorDriverPlugin { MUSTSensorDriverPlugin::MUSTSensorDriverPlugin(const string &name): PluginClientClockAware(name) { mustSensorPacketReceiverThread = std::make_unique(std::chrono::milliseconds(5)); - if (PluginClientClockAware::isSimulationMode()) { - PLOG(tmx::utils::logINFO) << "Simulation mode on " << std::endl; - SubscribeToMessages(); - } } void MUSTSensorDriverPlugin::OnStateChange(IvpPluginState state) { diff --git a/src/v2i-hub/MapPlugin/src/MapPlugin.cpp b/src/v2i-hub/MapPlugin/src/MapPlugin.cpp index 7ee8d4ae7..884a02217 100644 --- a/src/v2i-hub/MapPlugin/src/MapPlugin.cpp +++ b/src/v2i-hub/MapPlugin/src/MapPlugin.cpp @@ -132,8 +132,7 @@ namespace MapPlugin { std::unique_ptr msg; int activeAction = -1; - // wait for the clock to be initialized - getClock()->wait_for_initialization(); + while (_plugin->state != IvpPluginState_error) { if (_isMapFileNew) { diff --git a/src/v2i-hub/SpatPlugin/CMakeLists.txt b/src/v2i-hub/SpatPlugin/CMakeLists.txt index bdff7a0b8..fa987d581 100644 --- a/src/v2i-hub/SpatPlugin/CMakeLists.txt +++ b/src/v2i-hub/SpatPlugin/CMakeLists.txt @@ -1,44 +1,20 @@ -PROJECT ( SpatPlugin VERSION 7.6.0 LANGUAGES CXX ) - -SET (TMX_PLUGIN_NAME "SPAT") - -FIND_PACKAGE (XercesC REQUIRED) -FIND_PACKAGE (NetSNMP REQUIRED) -FIND_PACKAGE (carma-clock REQUIRED) - -BuildTmxPlugin () - -TARGET_INCLUDE_DIRECTORIES ( ${PROJECT_NAME} PUBLIC ${XercesC_INCLUDE_DIRS} ${NETSNMP_INCLUDE_DIRS}) -TARGET_LINK_LIBRARIES ( ${PROJECT_NAME} tmxutils ::carma-clock rdkafka++ jsoncpp ${XercesC_LIBRARY} ${NETSNMP_LIBRARIES}) - -################################ -# GTest -################################ -enable_testing() - -add_library(${PROJECT_NAME}_spat_lib src/NTCIP1202.cpp src/signalController.cpp src/PedestrianDetectionForSPAT.cpp) -target_include_directories( ${PROJECT_NAME}_spat_lib PUBLIC - ${PROJECT_SOURCE_DIR}/src - ${XercesC_INCLUDE_DIRS} ${NETSNMP_INCLUDE_DIRS}) -target_link_libraries(${PROJECT_NAME}_spat_lib PUBLIC - tmxutils ::carma-clock ${XercesC_LIBRARY} ${NETSNMP_LIBRARIES}) +project( SpatPlugin VERSION 7.6.0 LANGUAGES CXX ) +set(TMX_PLUGIN_NAME "SPAT") +set(CMAKE_CXX_STANDARD 17) +find_package(carma-clock REQUIRED) +BuildTmxPlugin() +target_link_libraries(${PROJECT_NAME} tmxutils ::carma-clock) +add_library(${PROJECT_NAME}_lib src/NTCIP1202.cpp + src/SignalControllerConnection.cpp) +target_link_libraries(${PROJECT_NAME}_lib PUBLIC tmxutils ::carma-clock ) ############# ## Testing ## ############# +enable_testing() set(BINARY ${PROJECT_NAME}_test) - file(GLOB_RECURSE TEST_SOURCES LIST_DIRECTORIES false test/*.h test/*.cpp) - -set(SOURCES ${TEST_SOURCES} - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/test) - +set(SOURCES ${TEST_SOURCES} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/test) add_executable(${BINARY} ${TEST_SOURCES}) - add_test(NAME ${BINARY} COMMAND ${BINARY}) -target_link_libraries(${BINARY} PUBLIC ${PROJECT_NAME}_spat_lib ${TMXAPI_LIBRARIES} - ${ASN_J2735_LIBRARIES} - ${MYSQL_LIBRARIES} - ${MYSQLCPPCONN_LIBRARIES} - tmxutils - ${UUID_LIBRARY} - gtest) \ No newline at end of file +target_include_directories(${BINARY} PUBLIC src/) +target_link_libraries(${BINARY} PUBLIC ${PROJECT_NAME}_lib gtest) \ No newline at end of file diff --git a/src/v2i-hub/SpatPlugin/README.md b/src/v2i-hub/SpatPlugin/README.md new file mode 100644 index 000000000..f106d1fb3 --- /dev/null +++ b/src/v2i-hub/SpatPlugin/README.md @@ -0,0 +1,82 @@ +# SPaT Plugin Documentation + +## Introduction + +The SPaT Plugin is responsible for receiving information from the Traffic Signal Controller (TSC or SC) necessary for broadcasting Signal Phase and Timing (SPaT) messages. This includes querying any SNMP objects to determine TSC state and listen for any broadcast SPaT information from the TSC. + +## Related Plugins + +A list of plugins related to the SPaT Plugin. + +### Immediate Forward Plugin + +For RSU Immediate Message Forwarding (IMF) functionality forward SPaT (Signal Phase and Timing Messages). + +### Map Plugin + +The SPaT and MAP messages are strongly related in that MAP messages are required to be able to understand SPaT messages. Map +messages offer data about which signal groups impact which lanes in a given intersection. + +## Configuration/Deployment + +This plugin has several configuration parameters. Below these are listed out as together with descriptions on how to set them. + +**Intersection_ID**: The intersection id for SPAT generated by this plugin (Note Only used in SPAT MODE = BINARY). + +**Intersection_Name**: The intersection name for SPAT generated by this plugin (Note Only used in SPAT MODE = BINARY). + +**SignalGroupMapping**: JSON data defining a list of SignalGroups and phases (Note Only used in SPAT MODE = BINARY). + +**Local_IP**: The IPv4 address of the local computer for receiving Traffic Signal Controller Broadcast Messages. + +**Local_UDP_Port**: The local UDP port for reception of Traffic Signal Controller Broadcast Messages from the TSC. + +**TSC_IP**: The IPv4 address of the destination Traffic Signal Controller (TSC). + +**TSC_SNMP_Port**: The destination port on the Traffic Signal Controller (TSC) for SNMP NTCIP 1202 communication." + +**TSC_SNMP_Community**: The SNMP Community used for sending SNMP NTCIP 1202 communication to Traffic Signal Controller (TSC). Please refer TSC vendor documentation for SNMP Community. + +**SPAT_Mode**: The format of received SPAT from Traffic Signal Controller (TSC). Acceptance values are BINARY and J2735_HEX. +> [!NOTE] +> **J2735_HEX** is a new added SPAT format. If your TSC is able to send UPER SPAT directly to an RSU, this is the format in which it is being sent. Below is a screen shot of J2735_HEX SPaT via TCP Dump +![Alt text](docs/hex_tcpdump.png) + +## Design + +![Alt text](docs/spat_plugin_design.png) +The diagram above illustrates roughly how the SPaT Plugin functions. The SPaT Plugin is able to get and set Traffic Signal Controller (TSC or SC) configuration via SNMP requests. The defined objects and behaviour are standardize via NTCIP 1202, the National Transportation Communications for ITS Protocol Object Definitions for Actuated Signal Controllers (ASC) Interface. It also receives live Signal Phase and Timing Data from the TSC via UDP packets. Using this information, the SPaT plugin generates J2725 SPaT messags which are eventually forwarded to and Road Side Unit (RSU) radio for broadcast to actors at or near the intersection. + +### Messages + +**SPAT**: This message contains information from the traffic signal controller about Signal Phase and Timing (SPaT). To use this information for vehicle control the MAP message is also required for mapping signal phase to lanes in an intersection. + +## Functionality Testing + +Testing the functionality of a configured instance of the SPaT plugin requires access to a Traffic Signal Controller (TSC) or Virtual Traffic Signal Controller. + +1) Configure the TSC to broadcast SPAT to an open port your edge device. + 1) Take note of the format of this data. This can be done by consulting the TSC manual or by inspecting it using `tcpdump -i any port -X`.**BINARY** payloads will be complete human unreadable collection of bytes and **J2735_HEX** will include human readable header information like `Type=SPAT`, `PSID=0x8002`, and a `Payload` followed by string HEX of the UPER encoded J2735 SPaT message. +2) Inspect the configurations of your TSC to determine Signal Group to phase mapping and the SNMP port. This can be found either at the **Channel Table** or the **Load Switch Configuration** on the TSC. Using these configurations create your signal group mapping JSON (Example shown below) +```json + +{"SignalGroups": + [ + {"SignalGroupId":1,"Phase":1,"Type":"vehicle"}, + {"SignalGroupId":2,"Phase":2,"Type":"vehicle"}, + {"SignalGroupId":3,"Phase":3,"Type":"vehicle"}, + {"SignalGroupId":4,"Phase":4,"Type":"vehicle"}, + {"SignalGroupId":5,"Phase":5,"Type":"vehicle"}, + {"SignalGroupId":6,"Phase":6,"Type":"vehicle"}, + {"SignalGroupId":7,"Phase":7,"Type":"vehicle"}, + {"SignalGroupId":8,"Phase":8,"Type":"vehicle"}, + {"SignalGroupId":9,"Phase":2,"Type":"pedestrian"}, + {"SignalGroupId":10,"Phase":4,"Type":"pedestrian"}, + {"SignalGroupId":11,"Phase":6,"Type":"pedestrian"}, + {"SignalGroupId":12,"Phase":8,"Type":"pedestrian"} + ] +} +``` + +3) Configure your SPaT Plugin with the information gathers in steps 1 and 2. +4) Check the Status and Messages tabs on the plugin after enabling it. Once enabled the SPAT Plugin should be receive SPAT messages at 10Hz (visible in the Messages tab) and have a status of "CONNECTED" \ No newline at end of file diff --git a/src/v2i-hub/SpatPlugin/docs/hex_tcpdump.png b/src/v2i-hub/SpatPlugin/docs/hex_tcpdump.png new file mode 100644 index 0000000000000000000000000000000000000000..3e3991b9b5d6bd4bcfe1749a067ff1bc698caf47 GIT binary patch literal 141628 zcmbTdQZQHggZJW3H{N3kt-}B#>JK|xDv7Yvx zbFMMti})g3URDeq1`7rV2nb$6Tv!nZ2ow_t2!sg=)-y3EkEqvCT!)Jd^oSjDd?+1PQ4NpQ zL&L%|5CV#D{J`L+)1)n?N1m?e_WtQ>CPSU>z8|RDZaLrgzUS^nrutIeOyZ>K<8aE~ z#y(~k`u`Dm20-}|MhO@Y{~0Hk{NUQ%mOp@}D|CU#tflLkP1!r(D0@#$yzS3Q79acc zdklc-VeL#Kni$9;BI57tR%M5oL;UIdfE(lQ>oL376_B6{=z!ocgXr)HF)hhr40e?m z^IY^``h0AOU%M!m!ya!9yd`y-s{F&dFNL|@!$i{%?Jt$jFfhpAoE;k8c1xr>>Ont$ z`M9(Up9zt$*q)&ZBV?{>GWP#qBAF_rQ7Gy^BScPFjTS#W%vmoO8*zDJfq>or8K~$X zBc=NFaH6N$kV~kTDR8<)Zp>=y7Y`+3U+pg(DwUu(qwqj)p0tE6*d#os|3V>=C7|_L z@AB{owyD-<#_`pvZ(VcL?u_Lu9(BCI4u7%G{~$CJCtgh#L{h*<5wT-6_PV%h1l#kj zO?(KMzfyfOQekp=Ll26B406}uEaAb?69$&61% zK?@dSW>P(?>sJI^`%)d1bJBrj{4x4uy(E@qR2D_5rH~>=AeRTN^G8Y#9YMn07?nq5 zt&1lu$_pyTQ8)aSQWY{Hxo>{VOhOo#q_kpSU4b(JY}b0EQlf)vp}OEG76qOTXR%JD z@FT**Q7s%Oo+0FG7Kj@V> z!yKkkg<@l%Gyk%jEts6eFzO4Vh|NSdNPCF^r;=#7js5k_=B|tmoZH3G=I9YL6T^W0 z`P-6;5d48uxPZn-?|A8Y4(R(tTL!X*4)_UD-_IRBa}@(i0%vM%wt~)*@fE0W5p}6K z#Xu)?d!QuR-o9c0a zTG=!H1{-&e{%bc;Bui$1{4y$ag8x!;;&LY$IiF&D`Wxz&!Ke^a-}RA z+Ch&nyB1a??QdPlWCBrZX@ z8Y3|5H|q_8L1Do)1o~NFt@0>RN-s5NcsyE4sgt?EWRaQb{ShN-06^qI5p$Y}hK+Sj z&J9wa^Qq!TRW3nir}26QI(V&rv=Fwcr9f3XVd%i1&rgW3P9|8ac2l zKM|Yk&L_iaA~xLw&1lnuw3Saw!B+a4yz>jVDc9@?CFhErv} znwp7t5HN)oMOM}Xg=pg^?b1dZfr@t+YCP7wvdRu7KS)xIR(W)Q2NM zIo%uC!*STW5agK{MihqgYw|27sWE*a_C1zoeonZsP$vDts|4ERmq$TVxYNgqUm@1$ z{Bz7Fh*-^(ndVe8;DU6xZa!e;XGj~G67QOZG7nJ1KL$gf!cie)XTFRN-d$7xVR)Al z9uyH$4IGiW_4JNoxi-cZhEE}gE}TKJ7lbZHg0Z=$Zif^1>>LrInurkAAf&t8glE_z zx8Nu>1#a1(QLau&aEwD!ZgG`3-o!*WaJ`23U6^A&%G5PboI3P#ld7D(5O$TYtOp0q zpWHc~stl(AK#p}~JQHBnrL|$s-|!o96g6D-FcKoD1;5_A+9n13KvU##rfCGPhkoav zs;!D_TT@j9M|;}vwJoEy9rM~2%IWH)@spT7F{bg~@4J^@kt-A@+Rv1hx)|}(2d0R? z<4iolGobn#4`pqZkU2O|=OMFDTfKD6Zi)_v#NRpjNR|q?bQ{u*UQXQ7{}GYF1eeiPs2*@8)1N}!iYMU@h<;O-+ip~Y7Z zg&McvLf2r8vzJ#-n3;fKla@%aE0u=;p%FJhQAO@91M_!d7lxWFqK8X0vmZia4=(Zp z9_$P16)?XfV5CoWa09~trbnXkGJQHjv;4yIk_i$n?5zHjisv4<`SoTJXLueKt9${~ z-|Db(K_)~013lD1J*zPDr)63WplT+pf$4LK5m?%%ZUG~luy5>!WrCt2PoPAg%@6#K zMaYcT!{F}>=%45bl;(zOXskB0LL{3-T*YIUcbEKW#QVJw>n-Q2BM`nH&sT~55rDt@ z$VXaL4qcl!H2Wwu0&lU<8$|qza?;z;`#x^Ue~3S?(6cu=K3_@u2x9zG^{Th(bVEk< zF}Iyf=7&hjSl-gFO)=-jKdB{rQU(nK`Z6N=8ZOyA#=}M1QS`k_f&|7N5ChAC6R5sAz(g9lOo88whfzv*HA@9%tuj+rQOavF9p& z-5+1KCk8H@mow|!e;Y8Z@YG?qQ^pvQsvSLHc}7|f2@)Z&Vz=%MGai$fdRSWs^xubW zuw8QFDv{Yihis|ZX0tBV`A3K2siokfIC*cq1-8uhasy$*zy30f{QQb1<|#x)>pOR! zweuWt+~>LuD_&#IXG?ESF&Jp>p~c;)DM|DOWg-TpRY<|^w*9#9hHlN!Bx6v;9QyQm z`YBf!85rWxRej}HkK>!ZfYRP3S?Ox?o}<`g3Y~D0omuncvT7s{I+E$k&FxF^X4XHJs;}~p49=(* zVMdfaCuj;H1U@8=iQNLxAbNNK;aFRjJ(%roZ@_T(V+8xeC~zZeO|DHXXmmomG?iv? zGL7HSKpqW#(%uN;=TX5e`)wEaG+H47BcYVl7#L(a4R(M*ttt5nJqcsAp3dUKJ~LFl zD08S4{dT8+W9FU;5*V;VA2bMLK;Hwrz#TD1KrM4WjXI)zxFXt?2AS|;l$SmiP$&rz zdmY%(39`#D<4Uuz0Ks$c?+U+lkaAR@-V9K3XoFv%mGF=;zx*%YjD;@d^2AZCN94}} z!fmTWdNNU%79D)^Z)Q-*x2I#U(Aoj;q4~}Z~ zlg8a~vsV8))JkQ42%VtAKr?IvSQ1rzWF9G35J4|A{XDNVP++Z5W_bFub~lDMPh-c) zyb^#Ae&0Hj7GfZtnHoRA>j44b`$bWBr_+Gg;x|A^4OYVh%zDvmWUh(tSVynJ-U?)+I<`V9wY)PR z<~;V&DRE1q!^w_${^5CTfex1qi*rxz8(GqVKmf~O)nxY2MakuJfyX%FeN24TA4)o{ z>_jYVjV?157uS!6+uLw^da&|$OfW8=z@M4Eb{Cxv&Vy`q&SM3nk@kUuwT5aOCd{+; zdkIW?{8A)E5P>SqMw^7+rYFN``I*hK)EumqmU}hWYId0)yMK z!=rtxWX6A`)IG@jaTD>LTly`-wDS#*{gBQxE}Ou(yU62el|w>vHg{F!o9m?`$=^H9 zDsz*{74W$ALr1-ji@{A`SE*AjwK>E-%8O%yiFGeEIsb_4?etY-{&@ieF*@yWqp3>o zL9^vD-esdy#VSV;*~$D-=lIi?x&al=uZCt5`HRJtIpI~2rplxjeq8RbGFnuGg0(BQ zRp!v%WBqE*-<`*@h?*xosUXz+f&Ha(<8-(3D=uA<$PHB6 z3?Uu(zEFFZ_KUp}x7uSjDrU`fUzhuY7D7rnbwD8U^rbK;xEn0bNwDFBWCu`j(8Nrf z?AFlM5Os}Nm=uD8C$2WWs0f%aAkvBm(^Qmz@!-hu>QYd>4NE0E1sdtx9`P6#Hgj2M zGbNM*detuIESzZYWNfxv(annIqzbXXf_3~>{B192L{mYJ{bkoN890Kx`8?CiDp;!C zc|q^Y1VA|4xv=z$Q5Q!e6de zrrjnZOT%4C!j{QkR@A%_c1$rUXX|H}{$zo#DU+vz5V?dZ{}3jQ!gy?#rSc%94F~6p zvb?RerFt6pTvEX{Z2`xUB1xa(dwe=Pe0;yXQu4cE&~AsIBQQw}Qw-c61F-!4r1M2l z!#@K!vXZX+rUf6JX!Pvc3T)0R9luJpkX)jlR3Fq6LiGjqA5>_MOn3?+MEjsGl18-m9hyBd&EN<5`iE_;vC__vQ3_2IJ)KSuEcO6NNbi=n}n@Q8orvfUp-vN zAM7z(4{u4qv?HLj6bPCWMUi(qq)^epih@Ma5prNwIpw_;dYO%&VqIp% zWn|fvHK&L;@TOjGRn)Z?S075gNqm{LoGk|CqIf)Ce33)Dn+Fm)em_6+eSoAUufQTX zIvTaUm=uf8E-bGGnVG2nOj3YoVPg^{tj!YX#WO80-G9n-jm;aXldHB}t)1agsM82a zi+#}E?AvKzTeTCu6jxRhjaLJ3bH)4G9FL{99K{X_9<_u2RK4G%81goNZaVg|pUs2C zGY(9IJ?Oydl|<_Z8-`$C6=$tStx?6}qp1Aj2>k;r`LH1;vW~Z1or}i>0@kWVNFQ)P zU{4Iw_qZyY4rYoyexX3YZIjeR@|-Q7l+W z0if+g))+EzmxfU5@Gq%0o^nbvUs1k`gf|@rL6@Tni1G5(mC38GsK?Nz)ni7|2B8J! zqJttqm#CWPm#?Eso9Na%75Ber+Mp*4T$spj_6Wk$g6Cd3I0Kwy$OJ0|9rN6-KNn>{ z^uN4shU_x1fh+@WU@VhHAI7R7yJhezv#8`DE7guEpZm$tiXL_=4|V@og>H(g?X$@@ z{#>LIleA!pf}T9cR1vx5?0pY{Vy251T1|DF9OMqtvZ?*qFwr|!#xM<00^0}}x0G9P z-mOtp#aL;H^PkeL7Sa_9eJ-P}t%0B}*RfI2pqKT5F+8MTWhVb^ACCuP5Z)gA0BebM zm3=;%{;WQZ$Esv4lb1Q3ab280c)raxERO-)Q?CTntrQFDb) zW=3vnvyG|lV@l}eYAycBUx?ES>1g}$eZN|Rm#X&F`O$rM1rMm>P4h zELSXPF5LaZ0lD9jkoQ?RrPiaJWl)4RJqB8 z;`_cIn;Cn|ou7M7YS*Ra^u)u_YqA@bOXewa3$@c@Jb7jRx}}RVLWsH4M-phO~Lz)z&v>|C2BQ>-N!A*d$upx5!5JBKM`-C>nDXPnAPEsgMEk|#|e@< zg6*bm``i-X4{(j?=ehwfw%}Q(tK!swNA;oRNd*y9nX)Ls@bDXb(JJsDBh!LIOj$S{ zNV199F^WfVN;^H-q$D`a%gd>5%`k&*&eOcv7{tg|iH?uOeX1Qn%XiJEjr1Jx z*Rm%H>L!plpb~70JlbPimiiE|fyXC*#zDON9*X&9Bxk<%?XZK$%j@s0A>=Qn;##_SSqi4`8%yR+P?85 zcahAR<^3jWIGBVcEELTQ$Hm4IW*-s!d88Z^280WC%G1&Nk%Z|QC=8`C55IS$g@2mvo z@8}te8^^q|jVr$)$6+1EjpU&HSNFEoyt*#vJN5<-;)JeVu{D2OWTkynu_sPO z(L1U492fJ_y;u7b?~)wW`O#80nJdKzVPtiJSxD2aT)5lD(i6Bv6LsInhSbGPeKG3| zm!`ggLKI^}c)_ss>hkrtZN0e}!7uc~;lc>oJ5DcrDb$fJsMx}B(x1sFarxbNa4J=` zrS()m9L=^fksEFMX>6@S&}4Lla7Y4wq*Ty_P+#%8Q=IQt47wDhJ^Ku#yw5u+3nlHm zR~WO$3S1qa<&Up$qngP_vngHn$z1ABCftmLzpLNOzQJ8*3_2_uCGO@iJv5XoJpl+#oMC=VXCWMfB_XR)@j7WUz5{|AtN2+<$WwG+ z_O1JaK=pp?zEaX!?U|I?(H@w}VhG8rQdQhBvLq--Iw9=_xG{@{G}#j9mO^5yjaS;~ zSV95#)fLJs5hVr~xIj+}Y%y+V-Cl8KPAm}~;>F2#lMLI=*ESUnEMNoA+d@dQ(2zumI{%wq8c85c8y4wID#m zvFDhDhtROmv<^ScMR{4r!LaAcHrsF@$PcVp{Y`$ejVx`)Ra&r7DljKqTzw|#+}r?8#TRUikIU=Irat~LHFQdU6GivO%- z&LnDv+T=Mlbjw=1r;ni*?CB-kS*7h6C^ck&BQwH5zE3DTNU&0!@nqD`P#o8ur?%yw z-OGgCXiEV`hnHdT#OhUuV6+XoTMMH#l+RB zw^tsXV*1flqlIb_w?eYB2*%x}s;LW9ePN)DtJ<#->C|*Qg{*7KXt}Hm<`Ph3_p-E& z1FX$Sg~4*JeP{<0q+V?+pb;LE*Lhll$$AJT*HSJ*(Y4BG8@bv1w)q>WkExt8JESy2 zW9f6sR(rO(1&YovHJJ!ZuG|a`3tBUds7BJE(x6_bxhuK!w#I@r*cv*<@sUBJNW={T zvKjM)W9}o_cQErpQG2C)gdJV%w4&jU2y>O@kDwKUtGnt$xqhPg2oml4Y1tzjhu?Uu zE3lib0f@QwW6fm~sXwzGXZoYo)%{rFLAc^i9UsCrx`W@F2|YVO=ZD^GsQZ%nOcaNw zjKsE{zUn5F>W^LE_UJv9lT^pHh3w{ZQ_2azq@u!qvAXCozhh!0%`n;xMz*<;{!9$Y z)%?!;_FoF68x!tY`EApx!cd=+bq@v<}J_BAxJxMC#OCMkaJ0crE=!OGf?Yac5KvE=DL6$QK>Q{bU zWhAsMEkSSep*Er7myaC2PtG8oH5tpuY{yu#E)=ksF`-a~L6>uzF5Q|NlZF&A4mV_~ zCP*ManCDz^1_!f$T+xm4-58Fc7|FCmD36G}J?e|IsvZO9GsZEM{L(Zm)4!q*nL>p` zFD9J+;7p2jLt$MuxAt(9Lz-H2XN+!t^f+0_A;F-zwF=r4mX)u_-V`aTpq_C!VvXGJ zmf=Yq6n9e@WF+#i10~}Xo#nD*L4?4E&W!^eF#R))2j4Ca6qsZT4A`mc60JyhE=h64 zy0JeysPYJg(htm$jR8` z%))cK1aee+CIG|XpNig~Hw08L+XnI2CTUrv>V*1PNgF@+nguJ!4E_C+qKWsob$#+b z)M5YJPin#8A9HRfhk{Q;AQytLO|1gs36rZSV8Wi~X$Q(Cu0&aw_XZF4AFS5e+`DGg zxZ%sa&5`=1)nqVPd{zUrJGIkLuwCe6zaDtNl0#~;=(#-WLN~sfX_tkDLQFqc`csVQFL0l$zPjRhCA5ygCYWggl8?=kL_GK>j!T#7k*yfDO^F!5)77I zmInxa!3NXbO=b)kGIa1Da8%*mA8!-xO`hym*y{L#PJ>c_rnvcxbAQ4!jaZ!2N75j{ado9{iXQM2FGnZdcULu-`@4wQNQmq;#of)^&O<}A3}$U zdJ5yv!1p7L{Ktyyo)4$JItUj5!Wbk%UO`@RUt7k7_6ifK=w?eFknLI4Tt6_nNLnbQ z%Ef~hb_j#2mnuXroADK{S&aqzcmD9T$V;UgA3uj7&)d_dJ4Yn5FGu3jI4^+qQlXqO zoH_e9wZ8di?7(x^tiHv&Oj>0K5}8IYaJ_fg8#$EqJoj;gR#-Of;0~KgM5vzV?bAv- zqxN?>X7~;XsCK*t=s0M%-Mry2MF?<+|4biHWX`ZY=QFC7;KyD;Xw#1O73fip#X<)L|{7bcIy5Nu9eJs}AHT<$ZF)-G!`MZz3ZM z7C6v8 zy_rd?$14brjcmQTb$|2ckw?5cAuk_{40{0Re}V>du^V+~*g`+|XEA=?AA^4?CcDY~ zdX}Xq{cprDtd3im-%svkALIv(urCK%(Y@DA2VRypb-_CpZ=*n!8?{!?%TK$YAkGMn zW@=l`Or-y#>vUs5Pg zAY%;?b%{Aj!Z$1BGdiE6wGklvxgZ;S^tn~jtOQNi>_{6#l{Foa+aI}~Nx^$${V;L$ zu_|^PnyJ>P?Q*Q+-=`*u(T{k>{46NY`|Y6;Pc8=opRt4*j2LMj6@2|*!>t@-Z|LH>fQ@7LO7Y!pb`NeP{ zu5H&;8eXZ*yTLdE9R1($B7#_)M$3hQEEJsvBAX0D5)2dZJUoxLN)SBA)}TLA0oAZ} za-S;tUf*#$It4GhK8Jy*;q8RuJ=inTgw4d!f{lu^C0_9}_DD>`6+wAb{ zt#-D3PEU3vf~J49yGf1|DSdV#OL(t86V3hfz1ZOnZ0)dJdy13pZ`{$wYr%MK|7omH9CuLxe`z&5}z?ON4 zRH%G6!oRxIXv;FwdT`KkzZ|}ND6I{E>S5#MGHa5zhQe^U=iWm@LXE>mNx}BGoKA7z zQAt^Bmw~Y1a3Q5+WM+Ffi!QsKDU}y{&RcnQajrm|#%*P}evD6N1^T(ZY@@cC=(m}Q z&>vg5*?oM`aU83coAX^LNY+K`4zaJxW&2jVrOCT0ys%G06~v#m)|=cH>+M)D084zu z$5&^qeRxYkqmTq>w1hz$@g<$H1JmwtDSf-e*Ed?qNBQG}z$6ge*^1KWTO0|W9t(Hw zob5@(^D%h$)Wi#{j*HTyQ#%)TsWG>mQ$_V;N1!_sNNDiqUJJBN4HrLyq4YgJGiF1KqL?S1)2dtQZvig0&09==2*&YLA}8)! z&*}}@W$Gj#FzvDCb#VfEx_Jn9=R#>1=lJRJ5ybo|iL=~vzP?0wJvh%18^OMeOE#0^ ze3&*SzmhH2%QhPIcER5MbliDvg>EaOzwzkcd|r@)v{){(K}Km7VjG3oZgD=CU{qL4 z_SNq2*gS@SXB*R7>P!jVaKZKDNKYTs{?sVu-D~E#1g;q*Z19j>RbdM_O6e1w8lhHi zeZQ_}3v>+;C*&+37*{;2jVnfZZCP>gDGyA8WB@1ZGxV7!|}Rl^ALk+1%7}6NaSxzNOsP}cg7IAHeFf=2a)VE3;S;- z0hsGLRq-m>&utijl8zLl*WAV(cRKuk9~FY(5~V(#*f_Cv-<{vl*W6xjuzs#xT}C)m z#F#T4E3p9)eUCks)c3qSncI_4B8gpaI@kX4e!BUD_+bG!$ZV6&Ho;~#>s`K&h*b$0 zspopB!oIZjIa59waVQQe*e&$s<@+94j5%gqEJ&aV80~4kKbX|I7c(Su+yY_o=&GZz zeyrq+Zk~j9qyJbwoDiGd^2sXxu%t--F}1%Y6H=qS!}Z=M*h#ssdq%* z?;lJ>k9)Y0usk1_D3B^cosXKUwa0FGT|$cGchL#jpEfqzAjzyOUy$S4T7_6znIXbh{M7g) zg)&stie?(;$2XiCglS(iqHkzjK4m{m4c|$tCAa0at?hzb^4Pki&hWx$-;*sxs>#r9 z>?3O(_Xr8Y3`tZ49Ov!JV*#C?I`9&b-e*i7pyBOSa>nxEZEX3h(KCig4cpBVUN zC2SEACjZo8OHn|XQQ&X%Ex|z`yP;{n#cT-pc>%CsNMJ9~gOCXmaj-&bMH@!RR8VjO z2Vbbp@^d2D;k3aJgoa#uFMr(mlD2d>Rtmp^f*q%Ylb8JjA!NKMIoXpez|yNT+8l5| zoqC?x>sDH7xGC*=yzU!woZE|pt0PA}?e|?yU8`x~H3EM8qsWRNIND;bOa&f7Nd0(L zWYFXMs`0wN?ywtX@ZC9QJX2fjjP*?u!`Z>K>F~Cy+8IFv7V`%&puCYKDLMc`y^fZj z+IO9~!Tn(8itke5#b??}4K|}&0T)ac3*T+X@v0TCqOd!&%R>QseYx@{-{dvBP3nwc zrZ0NCTt_;YN@niZzjB_Nbj-XQI^n++LBtca3BM8%5wpCi(8m_}4$J>3h{au~2v-ia zCpLhNC4RX1=+e@fTiXf33w!vA@I_*_nDYi#7E~n24i>$ioc{8)(|a}M=9zMWT8pXL zxZjh#Vn4#g`lLE?zNvsyaMp{6q7b*C#NrQ9CL$yN&b-gw)?bc3ogBRJ_PC@7|wX@LePjw8I z=LT`x6TLEk<~W|t!n>V}`gj4xgk4>j>CB&I+PiJd?sBK%V#j5|%JY`upIP1iOy)yGw)@w+yBnvR` z`>){Vf}lv9RX3E@uB*E*Seu#6^Gb0i)}0*sRhLHcvG@l0iBQSwHTEY;&0eq>Y>BPK zzo64@kP`j5lD5#)qu1b8G1Gu)lj~>;(-9V@9x0tyEVlJhtJMy>euRrar*dZ)*`I28 zLs%e8uGM&B@lXnO`pwKh)TYVFots`xpB>R-|*EWUS&KYzMkiD+UL z?92yfO<@y~OX^UzuMm#E__N@L7~CJp;P!GM?Wy1$Dn-!vx_sSaB583_eiPl{?Rcv<|DF?#t?cp%uf3FG zB6FdF%{Zm=bpWY2Eln1BMNrL!iQjSc__tCBhRli^BGa)-Lng^EGnk@*g>O z>A|0E4f3d!ujaD?F2#hO&Q47;ORI(;GdCH0gl7GV?tjf~i=CG7LPqY-lLWp= zwW;(*pA|(~U0N`S=e<$A8s$K27@Y|1`oaIMZdls6P{PO)gTKl@EV1-J-4c6&~0!&4%o%QY_#Of~L-=6F4z#)VX|*vx^=d8mAgq-f{d@u|zj zsKe>_yeM`V&z??y_BNH&yEvggGj0+?7a-)moVBnN)b8{tr&}9&&cS19CLM=oxrOHo{7WR!`_3EbVp1 z>v%5TL?ef+K@`m(VX<54%doLIKK*t~79HFA{(PAry>nFltu9Udue*`l1gLqDO-aR^ zl4(|v?_Y525#99|KV#eL=$b2^n&RZOKNMH26ax+i|LXA4jh>9nCJpe~cb-lg`6n~- zPXqlmcier*p7^HzH~gN;_I@o$GYm8R^I(1aozL(p`<7N&3C*u;He%JqvWqqm)!C+Z z9UHLw-VHxw=6cv88Cj3j)DQPB-6(bVnZ`{$h%M{mElfzgqyWD?I%h=D&@Abelqy>YYEZXbtKA z=fKDdGo05CJs6vVPOmkv~x}g>atksfWi_1(Rd~2|Y+YxXhPZn~|s%OZFFGa9k2%BLU z%(;!`C%_>cc)lji<2KNlPM#)8_Y>TAa1K!oapO?AmYmcMZ|ZmFEzhdh=L)R=p)CGP zlDbHI%S>>KS<`!z4m``J!SK(XP=tnpfcX!)op-B2S9}ENEvG)yYR~)R!ESht%Yrvi zpNyiJSv%gbU3oq--*MenN@wp7&F^Am#jDJ`f16A=(g4_%kFzirZ)5X!!7k1sl5ME< z0X@Cp$L5Qn&<9Y^m3vL_(AbY9+FW*Ai|RKH9e{3kSv2@;3a`4&CMY>(3A)(5(omo* zu+*`Pf?xZ7vgcQ~hkbCJcXaI*l7T+qWy|9t%gm4T$&x=V!2Qrm8gp`@x6mU5P3m4$ zGlvCD|9hqER+#XsQcaGJRSfjYXl5kBcJujVq)wFT$$Uef@L%9&o)qtFx!lO!i1}`5 zW;+}7M_~{KsW;Yq(m-bv$@Q+ADcs^Szc$=_Tapf3by}iHoMovPBsFA&o$e2^yh#bbbBHC z7#}|InCH1>w>{h4MIstF*@t{@V@8)(if+iQ z;54P z+xcZ~!A1(9iJ=l8;gdfvGQ};t0A!xFB1-_h_pFd)J$q6hAf8BKr?jXdu>e$h9*NBD zt)lpds#!pRViA_$i(K1{jSc>T<+DOpN&)JfeQrEw?|Lq5Lw_^Sd`SNFZ7?eYjTlH} zrO?h=S=_>DZkaKen`Xc1N?6%?Hi{L9NQIKaBO+Z~hO+9S_%wMjFfawGX%UdDBc1%m$mt1jN!TkWMSKuwl&%@X73`PfDw zw(FdA2dkxJ;XSt6T{d@tp_#<=mO7Gs)bBB$IB;Q$-N8oVU0C-`_@w}7y2_W{xQ14aNnCq^=i)wf(>h0D=6 zMg|1G(!}B&0hT{A;AAzARx~hwlJ-QYQBa&Nfhf7zS0M>_ zXSC=%ISIgP0irSR(_}&nBCI;I0+Vf2CT~GRcdu~JaxJ_Rl8CW`ss#H?k(mjJ=6>B? zaOGFod%ep)b}w2(I{nS$f01ps!kWu*V{WZWZSRcZ;;7>(+MIv6-#^LL)9e3>Y{h{K zrT=LOlDX`*3w(6UyhXgFjlUmkb9e4z{>Y;lZzj34P0@>qS zm^~$zJzi;_A^cyeUg9#V_|M-y*8)Q;(D+03nL8OA1138*HP(@Rwuu+p*01hUy058j zf%qeJnYy|4vxjcI!P)^P#s*MmFTD5gk&4<%kvA1_<-k$vZ-lE6n&=Wwhm9tifl%o`VYp4ZFhw>w0Hqh;53Klqcvy++FAp=`{5KD4`~nU9bl=%42F=p) zUtn!Kg+dR#;9|R)a0RGWtv4?&ojdmOv45+CeUQ6-$^%=PRQ>jm2-YISlk-N(3TZZ< z3j?pyb#ZhlB{4M%1{xw>^2HrTiKu{!Ifb^2j1NhOo=xd}zVb-MQyFthE!SEvS zn6Bk)F1yupLSUs9A-h*1^?ssry^tLr zvLx~+Z{kZh2EkUU;u!>$!s{0skY%-;qSnGhjf{*nJJ zAqnRy{{FrTtn`IwZRSNCz+xF1K8t}493j3aP3lqo;XBA z6J7ibA2)Ut48Hg z#kU^Ma6xrwRyyj>Kv>R&^>Il1@;QqWV_A~`=4M3WiX<4!VFjnjPTuLZ)kVUk*|hLc z`t@Gc5T(z6{|hj`f1wUG+o)kvb8-qmjOmIDN>*}la4B!=sxiRK%uEb{M_Fxp(b?2M8&JOTwACK5KeR}$zfXCPU>a=5FgT<^x0p8C0v5TT6puwvz40cYm)|2}Y1i4lf( ze;1a5EvR-dt<})zSfn90U*MJcUbHhUQq7P-;-%~n;SFEi`z2ujvt~EQ8oJ!h7F5K2 zCrrt9y|z6n;}ke_NXjN;%Kkdd)ohZ+epJ->4Top1+3rub%_!crpHrO=wiHw+o*Ys*njzDq;Bf8j#?vMU} z?mx}&wa)C`40Y7KD&j5_tM+T@$|Wq>P4_idc|MHMEZ1GKI}E?1grkRSCFmWGx4K`H zZfx_pxJy25#t7hu&ScC}t$ChQX}@+3oYT9yuiTLvAX$hYSG${$JI1hc)SwobLk9lc zsu1@(h`im@0QXC9#m3)~tOU+eAvpQ{HDq!H{6Ga3=_R72Dk^_>NiwK&y5T7|)A-4_ zYsBqZxydFjhC=eDkxYpRKo;ETTSf_Hp$5Y(Ai@J;o?7(7Jlbi!T1`yDvSDGVzJOjK z2{AiZ7K_EiX*!Vz6#FFkc`5=alQvw_E}IK!i=RYIh~sJ*W)m7c``(3`L@xi42_8VZ8-4d{mT`p z7gO*5)qsXdxEfmWL5G2+L2XCnFbS8t9zk#p4*Wz}+P)}9jxN#VbDuW44%%bl!iUj7yZ8P5@GjCK^u&5JJ)R)o?W-g8U`x`((dE=bL>C zX*U3z|8N=+eh-^EmC@y1LK**jcFV9bC3c8#>+fxkn`AX>ahJgW58oQ6o`!?nZ1WX! z4?_n#4+9ZR+ccTT3KR^CI$2R6spwC|g$}bu%o)SCI;V$ zYjg0Lt*YaB>Rj?VRnow5&hT7z-QD-$yL8um?$;yM%0<6tn+xZq3yylSO8`FeF0-50 z{@Y6SuSo}XN#rdwlXkk4w>KJ}E43SpFRw)SxzpP}DSQi`?iJllG;oxOdGoi=E$yUk ztmfWxrvuQmt+>Vp13WWS8_zNTNg1@|#W|Gl9M2Z}%HH(Uev z!p|e^@NW&}zuj-U_XGgt`dX%A1MY`Ha@gJJJoorwH?QQ2F^Y0 zyLC`7C(0T8Fq$$OZdR^azjZXv08@q0S*diHyVehL5C+*l7k);+ZtoV3ed9bLz-r2T z7@amYf9J#Ye2XN$(u~lmb2mMzDNLYsC!JYY>NBl$z1robj{6GAb}ZsizILT#$-(nB z-*mKV{6BP^b95zN*05u%W2<9yoOJAT+_94z+w9mK+qP|+9ox3;8+@7HJ2UUR-^{H2 z$Ni_)s#>?I&N=(p&ptK$_oTez-jrU?BQo*>@}&GjJ#^0M(767{`8 z&`+k#hP%bZb@Lzsfo*N=9g6!bsQ$SWs>lA-u4hSSTw+5)hu;c{&1!A8sD(pz?4w65 zty7(!_(T)#v*z!%bCtPZ&fhJV+Z>q}v62%jG5{?WL;8vm$B#03NP~n|kHcXkVzc^F zZj{KH^be!>-lqDd)z$Fj5E0T>PrdJB7EAquQ^OO}>3eC6bHP)XT~)Ul?A@6nU!6!& z(dqT3#9x1sd**8_ME1Q{<7s3I&=qFDjRV^N=LO(T(u3*nIiUrj13$M1u4VQ*@Yr9B zT)fWmZu@Avl=?5)X&IkJV1%*37NdS*cVPNzQ~M5I5t=~4}7q^UvA`h&*K!-(VQ zFPq3P&8BnC7rJhS^CKAAg06~2^nRzO zZQ%kgN*EA38=G84A+@)=S^Ce6Ka4}{Z z7nl3zPev=ttZuR6HkkomRLqjT>8LwVP_wZpFMl_c%AueK%4G358S}eqe-9fY`^UBe zJsiXqP^tN;>RpXpr;SodHLL13uVUTz7D48OA3c7Uea@N9uNR`lS5CSbTlYVt+K#E8 zbU%BrDGo|DmAI|*LcEwl2RzUEI(TUp&VuFfqMU&KZ-j?pFJc**9A)5Y3!~tcOIClc ztLOE?>8!!7@Xrp29luQXWPc-1`cr#UYYP0Ms%u0~tNe z_-Pv7^m-CnWvwmN>f8OO3KPN({yh#=iQsjXhDM-+ZTImUVzf*1_*eqjD<<4%BI@qm zSgNIZ<3@-83!k;=H-=NVP%a<(aMy{2{J)iauonl&lSV-kX5x|CU{w)00WWDa>!Sv4 z&B1%6WyKtGd0h3Mu@Z?{xF0W8LT=Q8@y0{#5n-}lR65>aCfyGJbKd9qm}GbwWVz__ zfAvsfa=X(VWgATO_1y;LdM{*olw1T#2CJW1M}z>O*)Lk~2r;kECK7gOW%(36T*XIy z$TNu+Y}WQX7d^oIDzi3P=eXlG8zaQdVXb()JXHU5K|70@KLvu*YoebV5#GIQR9a(z zL%M7&v@iZJUh!dquW?>}J(!Pp^GqmGsaEe|n~vGgv@cx3^juTXX|v-d2&eht zW#i#IVJ`oiU>9nQ1ScN>GGAOZe_*zfV;v``8L2H1iZG7Y;bu@u(L@f>=kfwe^zBSz zep~+U49cHL#lMs+R0yfED+(d=N^#$-IB^^b0id+YbAJWtIF0Bw=b56A1jbvoxC0N% zAbbN@LtN%J$esuCa+JR05?a+8gXfkSX|Pk7kUGIo%M>#EG+NnqWpxb99_?i3)L{~K z+ObgWhzfPqO3fw+r8QTjtQVaP=0lon#+2vlzRA|fjxX}Fdd`lgUUuh6Fh9c95UO zXMClFA>GYGQ*hBv=Ng*;;Y0zY-=^zgsgnAt53%ea=uq%rf+aHid?_87zgT^Lo*vCn z%iFRj?QCMDz&$Ufuxm2Y>$rJ(>WjA@!_o9{>7U4x&Hw5`m_eWh`bL2l@V+=Uh~~w1 zXTpruc$&OcwY(CPw_H1&*GHCJ+IuSRMemq*En9-pzjX&!dWB&zNC!`d1tB=wdHmv3@pP7n zMp01Tf50ubuye=LiC*F#@g$oA{y98qzV=y9X|;MnFi zDuMH(q0syN%Oe|!IWAs6@>}im(lqE&-z??9x%3##q1w_l+6+1xm=i~lR4JHB+qhzr z@KW()WS)ihL|dox_5wtsHj!%6H#WrbHtsFG^Y13H$^UT!J;bUDX2ANJDA3;UH#SR+Dvo-E)azO6bi0 zxEtN7Uby5(C@tkbZ1U=^TObG!Miin4h?^uHE*n2;?M=ehz_)P`hXzxX^zxq|8*$Ir z|6GhGW-XpYKsWzi)P);>K0}lHmpd}D`F}cOxt{kZ(Ef8jtho!zk)2$=xu{LxD8w!9 zN2k?n*7-&ycGHP?11Hh+_Nl$x^EV7%2}!8|j0&GxD<7QX4MS4;n-H7gcB`jBfzFLx ztk!h6_IMky^;~gGd31z1@>D7~zL&$>TW{&}UzHD2Lgc~!*B3d?c!tBtt)8!YXI)|D z*y_2`OkyD7bOG9>21|Mn{Hrbki)-6&Z@PdV%8yoIO(_G}A5>3euNkc0Wi`s)z*TEE zREr7O0i+~4X=0FUvHv<*6}%dvEgXr^FK`;aZpF- zXZ`ba?h=CF;9egf^5_EM#iFApI(ApIF*uML188$gTiU6TQ#NvwtU7Jmq@rtZ;_pFy zVy81f{(J?AY6{Ie)0zHbYK$6A+fJ~_25R_+4|d+=*QC1>t6$fKd?GCRXmfcHwFGn5 zfK-jbl&(*1!v-TEQwXS3SyrmEs@8Nm>6wJ>aCiQQ3*f2miEjA-0*SnQM@LTQ2lG-3 ze7^f-PX+&@llUG%X* zb#`ek8zODr+`%+m@ZXS~2clt}MuSyJ+2Wy(x9xk`XRq|tOAgrFNb3W+8V@CT1q3@| z9(JH%~Py%fh=Wvor=)aq9o8 z;*@bKbX(YTy*C2fo(!(yPFNn_ZKfyV zhL+5y_Gk_N)xIg;?h_ZWn{#c-f_74OPHd5g!0 z!n9d3>YA8F*Hq_VE_N3I7E<9Ewku8La_EV<{{gQ1hp(oyU%nwNyI8MPIggYT(p>N4 zk_xE;vR!G(zXE3MI>nUDzaI(Gc;fi6E+iltL-!ABdFcsTdB)%r7~#v%FGfFYv&hT% z(ok6Vbvm#5JYya}{krMGx_A+55Ak@{y zh!9DNv`SrSNpVRiW!sf7O+KcO!gTO?@NUrIGBkk}CkJCA4Er(Dx&`P!X?>~y>19vE zsuV|SD=W{|d^v!dPFFZs42Y9Y%oS$jFJA$LVDfttEmv8g+TfKTNtKvrbCV$ zPCz9&LhZfY%=mV=YaO#jWi5?m^S)=NPWflwv~ai7E_l^1sEzMj9(9+4^I>4#+V8ot z7XBwQdR{*s36hHNbkvVJkGv9p`}LEPqb~KT5&24<9wA0A+B=DtS`k7wgw|3IQS~<( zBwa2I2&MF0Woce)ekoZ%BlF>rgWW4OYvq(d1^K!0xxyn6^cf5-Ml!l{(n-iY4_eXl)v$_G zpIoIv^Okv|U8?*=VtDpUmX-~H*`VgBnXI?fiBifuO(OJ<&W}Oy^3})svi5BA&61)B zR%RL<$yoI~sz!s;hxX<;?nu`lv93^8f>#>he02lT_sNZf?)m!HYln%Bb9d?j1sXTf z+_Tz2_nW59c(&SnvRLw34Dyrd-IxZHT5^j-vKV$rpjg{OgbKaOb5>it=3{zG6K&=Y zXBN^==E?7b`XuC+r~Q3A>#d+rc05cr#&2i)Iqmm za?z`eY?!psULDfGlAQ2aT*|;E`hqv~MDBG|%j}DRhrq^H7G8`KE?;15xF?@o6-M)RjPUPb(qve}HQL+39#E z)KPO+)&uK%L&D7NUsyidgr~XDQtCrBxkR8_WILpn@pTiNdJcyC(32vcx1GD~ zd|A|`sz<0k^@JuhKd}(ZI1FP}bGyl1Ei`CeBq^D%?6EE4@Y#q(!_Hic+H82utfQq9 z0mSqG7;fB zd7K>F$R`{V(Y$T+`337)bJ2idH%@F-_#$-HmgAhV0oz%~JQHJ>7x=fFH!}8i+GW8rRG#G9iM=Nb z@StY#{t8HA6d{2NXQ$36Qy8 zqF|+^!!9w}Dw{q(+VE)9c`w!w%*E+F8_w6j-vwr)uANzecHH&2@|U5lMz+=yddG(x z(P?zK+3HQD2Pq#t1-VoG8O0Tr{qslKD$TfNfplL(-wutmVljzf!iL)t~g|4G{2I1aRXB$pkMXw!EzVe4I zCt5L9(N$5#MvFKg!y508o#lc zO3+=cEH6f3ah19MG_zM3;VbBz0Sg952V$Y1GpVsEwVX}gtt=U_ zkmcP|jo=@TEv&AkKD{V|`;?*v^DyURivy^YK}JgEs|LB8Vm~*2$S?MvrG@13?8*w& z)tj2)>^qi<&hqR|aEO-0A8;M}26oP7yj<2G!PR=+5`pu-!?To>848W-^)m`|-XmI& zQ)W%W;cZ>I4^Z|Pe8JMddEV9Fa`W+>?upth-uWrVQ%7eeFJ1A|wI?gV*=~uFSYCjb zA5QwtQtE?&yvUawWkLPoq|9GcTE1P-eg$io!!+)=UfJ`^VA|6KAnH*r0OB`IY7Pt8 zX5`XOnmC?hRd_q7rw3+U`oz-a%bY8Z$i*y*e_@>xxh8; z>F!u5ZeSs+yP_s6y4t*6U#sI#B)DzMYKbwzMx-MVIy z6Ixx*Tv6M~Owy2W@*dXMb}whOf$`S6nCxMX{=41rh@TFge@mb;cQ{LSh$vpiAPq=Q(dRKycJI)fhL*=MKyP3 zMvqoj`AC-Rj}Hy8g&gRGqGz?&VlL7`)nj7~@b^*|mY&@&tY{Ta?}4L@MdcNg8TJVK zgsG0%zM@o0u)?eBVL)ohSWW)@BC+$P=R=4g9Xrb_X-{eSv>Dw#J|Rz5EmC7A^cmdR z-N$F&?rWITq}0<(BCD=V<1+cVNDggqtU9^}9tRFQx|htvtXcK~UObgng7t4gKrPLb z`vWuf-7*)_ua++532v6hgoCfRD)$6Y2ADnWNGluO$1xx)!s4`uOZDyc&$vOZv>)n) z#7N$ibkfM*QErIa5|KV<2zh9SaPruR&q<IG623I<0=+#sVFPzsHUzFQwKJJo>F=LEb$R>*T9o@q}JiK)|g${jX)~6^U(URDA!fOIj%dBFBaZU{M>Bs(gkJl z(by-yX5n#%WZd+Wyv-W@u{(A8aquH7A&3h1!}GbSMN}uQ9NeL;^%QWy;q~13O4Nb& zMl1ufdvV1;0zM}7CKcSNS3?Ruo^;gMPD^Jav{yg4QbBM-Z3%>*GC1StpOKlZR@&Tz z63?b&9(QOu$Ear`E;+T2j(*4&Jyl7@^J?!}VYn~C7QZgH=b$F5eIy2-^JCF;Mrz)2 zn%PWfwEU^d0gyNqBo3g?L?9lCi+X8QuZNRqfgW}4&*L3IKGDt`GSva=sy4rMh}!5Rj_HXafI8| z3CxkWpGTGAF*~`U^3@DBxEqo^P~RuQ^Lm`l+3a-pbSU_?*lFtMKcweS$EywSJo5tO zEfuT^T{AZW)QbVN5hsmq2yd9o$wEmgsih1kupnTzY z>ZQ7cZs&e&weY}xCy9lDdY6F`aYvj|LfQAq^W9L0$pS1W5*@s9Scu2VFbaIel_Il1 zc!8H>6KVn}Ht|2>NftrZt7V@%K?Ys;ujBr9>|e(NOnk7V-WGYqw?5dgeaNA!mKr@P zdaCOCt{1Q9Oz2@5KHT^&eR0wBX2Hq4Dq!{L?i&!^PhYBYpF>g~VI#r;XGXJC&nORF zI!Qz*-H-WLN|-N5hq)NH;muIf<|9-F3cLc zN_eAbyX1S45g+gZ$2m%Sf0|>mCZbNK?8ipGZbg4N$g2+Ch|OD0vC&z6*!8-G$)ZD` zmyal^q^#=r>G8(I$@<mQg96J-y?Sg8?WfSF!Yf18W z7LRru7Ce9l(h|V4!YupMoFj6YlTmal@(8}Z>gm*-`Ga>9b#9c77vH;w`K2-FTj9`< zN*AJ+m+3T2v&5H9YYm4J=Ad60xr_g8e zSW=Q%_ONc~sgbcD*Uu&EFZKn^`j^+jcwMo2;YWM~9)2aUsBj}s)ozGl{w8zOS8M+; zaGQ+{hl1?l@}too#dadqXCgNINyjaR9O}FbiR`tjX5Oc!tG~)lkFaW}&64Qo2pfrz zz(=E+xSM3j>1eXpR(k$+Ysx?y8d>Wwh1`%$JifdPwVN6x5nu&|N=>Y}FW~SQoS;S$ z_TA*1a)7(TaAnhxs5`M8_wSZ926DiUK4i4dZH)g^U0XY`#e^Ao%;$1!^#6wo@aUdM zPQ32(0BJq?J))QZO69P-?JH_E>YDjx-1DV6ri;w8OwTQr8-mXvQ&i7!kT%DF5R|iQ zWD^hG(8`8EhDP9G9cafG11aAvEcG2hzRHx>jxPW1mF6H$HrPFg zkNpQ;Wq%onm*&B)Y{0Vawd4!N4AYPTnU#eqP@;uHmFt1a_7@x$V)b3{eGbo2@@UvcJK$E!(PdKEqG5x=XyYe=mo!ZtNAlq5sRSBwBBG_c%PfYw+O;KLAS)7 z&W>%IfPw-e(|t48%h@*kN3pVI&pl#1B}b32_e;>$w|R-lS%D@g*0y1Bpzt?)4wEt1 zBXbl0nGuhBq|l3RL6INa)Xy`+{ON``Pdd7Lu*X8V;|OvIiL<_U-RD0h^awQHmM>sT zykB6EdDOy2zj%;GCk$EIYc$#KfRMW>--KnqWn%S?{IcyyTE8&LokQQB>z?VuJ$7s+ zz4de+O0w2nu4&_4oS0TtRgOl@nKKmIT)lLD(Ysz8tKLo=2$23zy8FfnPEcwxSRf#* z)5?%sc<-{{?u!FDO*`j4qi`y7I}~{olo>gv^h~n7lsG?uy$E^9wUGnF{^ZA zo%=gb7v0Sg^W`XIr%Nt_iMx46^Z*Wuqdp2t<--~a^mUHb3kcR|BmfkGAH2XPCM5VW zo27-7%cVD-d=MREbMvGZFGautnxzvzlr=s2ltHNO@(yKibYX1L^y7$xecDbbf~=}w z6dIzCxU-fs==mGWdp@gJEgiI{dHr&l4r2I)c8j$_=ToNYBs*aTIT{WjFJV-p_c){p1v zemd$PVm3K+iw18dx~_)^RQA(fP-0~pQ2(~!UW|RY$bP0n<|26emCddC_C+)g`pfRKom7m1i@A0R+dxLa_CAT5G!wI-oUu#Bto9FaNkG0^t6bN%e>`8oke#&p z77ltsQkVrcK?ySP>F$>gvl8?f7lb-&;Wv*9=PZZ6Zvx~yN zu|5cc z`Sol0_c6EIRCpbtAv*Z2<67%@-=u_@86kfZRJtc)5|EDIomc4hHJz_&1-X0A z*BN}oS`iB}pN(xd$gh}KTOxO}D>P8Tx0*2pSN|S&eS~ndY}+w*hp5B*aR7+aGTMi2b1>@WK5xAo4NR>{8w(nkCpv7;lL_#NKxRaf3iNFTv z5Zq3-&ri-oovPh!(Q|v%FZqCGJ&F(C{|Hth5<5Ur-hrE%#K6`ur$OzRO zWo0~RVQC3npa82L$=$}9AW^PMgTo@khsenhZmGYmeO#>ra+V9`JKvp0wPMC zoAqq8CZSl*ow^L|&RvPbZh^lUHaQe;0!Mox7bTY-Z;Bbn>oMAqXmsCLzKYJ7_Q?Co z(}-K@Emmg2qtbJ~O#7=Mavb)r`z=T5J9n$+9vs}ezB9EjgceTWwDw`5R5aasAuGM@qR06 z9`XX^coSwRdmp-Dm;7En7VI!ltC$<(W%hpDObP#m5tH^qyR`e;?;rjHXZW@ky8s`9rhO#N!HnP} z+ODPdhIgUXdugQ6pEa8ttkzX7tb=9{bUUW=F`#_Fs`iQ}djN=e&(27yC)p?|!HRD26F!1f?#`yQ%jPVH zw{|D&j~=QB7*OsQiANP=!n7V9U&(l$wIzG&{DGBZe`J}7C?jR1w!?@e2Src8s|*t} z-Lmdp6jw%n5#5rAOBosQ{&*#IGd>usV31v-*=ttz?a7m4YUoz(j*q>+a|)E&5JRtq zFWT!0p~9S5T)bGS4JmoJB%h(-N9X8fzQJ!(cgGE%`e-LTVcsp(w|;tya$9$;bJrh2 zX>St+JrbS!eNv_nXTxg-2skywlV{cNc`Ai$VM-q?DBr9J2K@*cJ=TWavbzPqIC&ic-F+O9HZe*xu&_JH6an_H=$8ckbF zMr5pce9vyWKT0^#{@V8ADhcd!Tq)OqdMeAnlKh=E@BuZ>jk|8fj8SE>+c&!O#)Hg% zOMBs%blO7*BFE=i?VH&=m#_GRiOsAg91y;WFA*zABw7hIynGx3jYvSH@NnST=mYLe zK|Jv}P(+jWnvGiWUT}XQs@zjQm`w3)0KmE1hqeu(&c3G?u7vI3G0i8D1pE;){Iz?J zn&&U93Z1YW=_9C-CAOTYF3QI_bPVHmaxj1T1Du2ZakG>5Za$S!Cv8&w$TTY~&M#xl zsYshWga6L<7kZn~?8)ei;`HX~l~70%7M41Wj@{AP5%*%m#)4SR*PELe#{RWkQF>b1 z9N0$%fua;#P*U8v_+PGV+YN6cBlf-Y4Y_rZ1D56y61%2Q`Mw8r_2!v`h-LujGo^1x zzhKG-&N1cS7SwPS6^Vs6CB*XNNt)fizFv@6xZC}jUpPb@zgHIBM+8I8TN&*yNRkyM zqA(5ru+^ z-Fi%9{Go|ay+ZMxKfB|-)77r(%K;vt$tdiz+$nOs%Ix*k5AsC8XG={eHoo#&nl|h? zjIwl*oQDT3_7TH{Qp?R-fdTxbuP`(ew^fP3$Szq!Q>TCFC9jxNwyM1Rn6+ZL!36Et zjA7PHY4C<;$Qcle0&8WX7FU*g!Zji%FYnq`ZsHEICMVK_Bo}s3Uf(j-qTKYU!$tH} z_EAU5Mu|`eqQgN>iQdlYayub4!>EIeHI6OxG^6=m96T9-5Mq15F@f3QxM+Q5W$*XS8cO@8=`lTB<5FfXO2WkBEKnGC;s_(dl=dn*WgZU4AafkCOU zlfxaIN~AHXwQ2Ag-?eBO>00Ce15WXtqe6uHg2~9Z2Q?X`q6#h()$pQz zV-4ViGDEUgOie!bU9y78PIEp|Mva+3RW@wE(fW=AxJT^I9dMprL~Fs>pIG+4;|)ankBFI z@E&*PEbeLeL;{vU6zX7!{_k=bd4GFZ zJs|LcnvO6q=2@4$x*9f(QH&};6--I5MebgJsFhdkMm6B)gQ zt8+m;WE2&OV9WujgQI~XuziM*PF5W&iBY1E1zFQ1mY^?CC_vK37xA1X54wqOZ?$cT z!@AEU9i#QciXmgJtGnt^e#8}_PfyY@7;TTKY%fDt!jij56>Q{(uNbQH(`3~MLebTo z2^oS?H$dcjR_X>lDXXzL$qOiQCNY@c9kn*dQu1R+44LTom8k*tTjj7z2nsJ89$nmg zL%yppz}a^7P|eHOtVV+<_ZXW4xRn969WqLeCcUy_eS;DZH=JEvk58Wbuvh=_is9$$ zkqI0EqNC~YP#jkik;j#^zFj?+>Cm&kPEwM*tVWCNeeTFG7mhL&{gEJh!+$G?Y&tWb zVhYr@r~a;OHY9>|Tq?WM^d`7VgW)7H?{d?CDWf_{i=Wqa)7)if;Z1Wkzg!l`pao@e z#i$@NBUh$kJMv;rh>c5GVISA85FKgg-=PrEsB!@|u`JE^(81T0j)`y$kLLcybBTP$ zf2L?@u2dE=i|>rJmC|F|&461F9iJSlOv-8YzfkRrEP@!*oXx#+O2dF^T_w>&O(SLcQ^bu7>9|L)qbwIuua zxaYGN-I+0~MAbW<)v$`xWdiPwV?@ZZbmb{p<%NT~Q13npns$T6aHH4oQK89{OTF?f z*YlkyW&yX{L_A@Ug?+VkhIn&x1NvzlEz(cqM+LvDRe^qfT7(hGDL3*H)e3pCp5nvS zS&0!PN5(@8r#lN-S)ok1s&~Rl5NJ%dc1-;%kb|&dx$ATZ>EutfNA1ZHu@CMSq8!}#_TV!N~ia~T(z+(8eHB(VjhQ8#YsHiW=niTA~<6wRb84g$5Z*8s$ zhnZ8*Ouiqqh>5>36?A`&MnAgY^bEemW*}@y4BEpR!iY$!-~rJUF>KZ8W~yMC#=i* zTeGT^(3p#}=;a(=~4Q%~pcxlN#97A^F}Jpl7Zn-UOJS zKDz&iEq7+(d|tEZR*Zw+M3+?$Qg1F$3jdj)cYCc~N+8Q)27^vqN;z#XL(Y&ILV*7x z_%Xm)OFA|5Lnvhk2+4IV3qJ^zMfZTXX_cwF*>tqhWmUsro@eZrm({Y+!5+xyA~;}- zAdiU`0}2KmiGtAH|AaprRb9SexB>nJM{MimL;{S8KAKFmCY@HvK zmoR(lh9HSJt(FbTB5W5wV_arYV&1R}eda7~T7?;Udq))W!oFJb@>;S72j>A85rS#Z z5Oi65fF*8cXCL=UYGRuYQv-KTOD9b|lgRYAJ)1bKAQgmIo-Dsb?-KFHCp>OP8UaJ} zKCHSjN@7C0zz0BSlPK_c%5NSXgitrSgglpJl$_+bha^unPVC=V@%c5J6f(=I$L&Pl zPW)nm%?1BsW9qP{F%<}66O1Ve2w^vg3yBsrwk&8od2(=>+(p1ekM4;9Ff1v!%~UM3 zcu;9EZgGRM^F0|K*L%b4*anqg70DJH)x`fnv0LvMM>dHap`z7kxOc{QI}*)H?@}#D^uAdUf(2#@ zb&R^%kfdW#$L+0PRv=`=&CT&F%2}f|`Qn2e5;907U-Xcdr_no=#+y)+jG_M9&q+MOltfmmF{MXSHvVqHlY83=YZ+^TlHz7+Hm*8pX)@E zUA0wr9$7dL)BENlO;K!hlb#;#2XND)5Knw8(r?rfGWS{ymlUVMOZC5VC#7q2@4-{6+$b6wEzYD_2e$2H@%}plUYwD@EwnyG9JNkwl&vH-lU+V(<`ZbvUwYYs6XbaH zMeyquZpH^&(haLl2cwc=l#lA`e#GKx?{Of92~5s8sDkI0aV2(OWJQnBZ9FKfOD8Ks~*EI#8{mg2Bd`AW%)z4dB|FZGDwpJeG+wLLG1(KG)W_+IkCt2z(Zx9 z)$TATf85fP0qsSs7CNCXxMF^2y$91_EjB%!W^**?mq?*LjnVYfUronAS#!JE67U*! z-Nr02x3)i^7?fyS+=5--!~XFO^97il!u%^a3<3LBaySD_CWLv!y&Lm-Ze_gV;Z>XN z-!Eu-@0d?qHNX>Z)=={B2MblJ0U!POeWz;@-_^Y1Ou!b|Ljxj_u;zV+;+Un8&@<94 zVlVionuf-2mCVE|z6`bib1YaD27xVbgj{G0wYHU?BONU>HH*G|ir*u|aV9hKqP=vF zOR~Z45CX$lTb*m80tCb7Aq@wIw@K)3vD+K-UU zyejf*PX706l1GEZ4VFkaFl-3lAAq&)ZIo^(#ej2k0d>C+H+-mW0Pz<-B>NA$S>@3x z&J0+$u#KoqBm9Ll2eUhD#F;t-8e))S1pOZjB7dRmw(g@R%(qQ&M_^f8W|336RT$*& z4Yk<#e!eG!#4L}6-t3*&^GMr4)!$RpW#}J)C*j9yUX#p;O!cd=F0Xn{<~ZHKtCiD% zMCC{BMvc53Ys55EEu#pn>t5%9d09))siX{ZF6-ypRgb<Umn`Zii8+5*4=8{dfMV zVGz#duKNQj*VKM)l+0lB;p-=jGqw`h-zQ7ekdTC%5lROoR_m};Ga>uDae2uu(Bmd4 z-?cq7=3HkGc-N&A6oPdCvtF?(1{&sE+D>xb3(RC0ywUo}SeLV!ehO-GFzxD%yZ(W= zER$!e`i;-h`D5j?o!rK`?1Y41$zp3!Xkn2B~ykx5}4_I!ry0}R&GlE(1<#xr#SQzH|88)*f#p-^5byG zL=H)fFdNN@bdIw}*FIye=Oo5RFSKHZ?n5B~e-LaiE=Z8;A*>K=*{rOm~0`1XyIqQ%Bp=P|(dNJaP&!Mmp1J^rFv0ObX`l1hrI z>_XC2CG`~uTAC3SD7w2BES(8L*FL0Q-0p;H>=jqx4L@ma``-RGUDV{2(qHF}ch*5a zUUJ4>&9fW^&wwRW{Qd&X&R??kohVKj#gDLAea_NxPf%Q9=Ju%$>6T4B4Gy9VD_a5t zfw}%@33_^l77eXV51f|vD~987AX(*K%(1sOW6<+xUDbWVx+w&s2x;@rZYc>Sh2+^i zS`m>LAACz=&-NHz;O1fqjnjoe8#aXh&@M{ZUL>nl5y%Lt^xQ5Z$omdSufI?g%tqs5 z$?%M#4H(idA})+})SC_4Jy-*Da3m1M+v+o|%bR-D<0JyHgG_A~u0SVgPC}>&N09-? zRj`3(2NOa^kLT7F3J!*tB_X{TX(a|Wjqw%^L-6yPF%Wdyf zFcA?|ncAJrt{V`_^19YSPoL%ISK2XgCudahzVRLJPH`vsRq+E4PvCs)e#iY$KpVHE z0EMYW3afiymlLC{H?3opa}tw zFW1D_dhMn61k<}NosPce6_Qw*(I13fKpiYZgS4ZFDAcF{S3_IkkQWS0{fvqHlcA9p zm*z@)gs&_834Z&~k}1v#kMvj`a^J%Env9vrroz?1ci%c+4!x3B#8AiB^=fK0Lv1n| zXH(OvUriwNlBEms{rowr7<7}^3Y9xIh1M-?ko9 zF^ecD8)eV-Ww&#G{SCZ=<}g1J?1N;CKK5avr%HT#tc4gbTexhx9uZz7a9ufpm?NrH zma<_=t8^rtj>b*1`(b65E!R~S8FZ6Wa43*60sI5Ad!yqL8zN{zz>Z_vgljmOsT6If4-*?j#?R^OQYuD{L?HRxc3-`@fNjPmk%5m5Xo7< z3=Q{SP!N`~a~M0+ zriR90whCu;AO;kd9&VCUk1_O{bn9TE26hX zy1Fj)+eO{u#A}I-*RcWHx>fg%4t~&PqqCbvvaoA6K>H_c%Z7`vuA^2@>*qiMLeNbt zP^K+nMn_9Slf=5*q|tS7-Jn^{F+Ht{XmW;Bt=PONDj{^gL;~{S)^diB8NE%=;2ol1mcS5GD1)X(7r~54%t97<>d-v;K>V z$hC&1JRNNiOaBiSV0uB}+=6k%Fz{IEJv4ks!4U2KTjwiq~oy1WTB%p7Nm zX!IQD#FG`R*FdsMsXQN6f>WVpQj+9ta3Oq97W)JxxOL5G__=wEk8BUJJP3jh3>qkl zya0AFMQ4`)DLz^Ke?SKhPe&E_epL4SygdtHdia|1)_38|Ls3f;sGO*)VHsPEkb9XV{U0^gqe*d zSap$*O8O%LVeY4GLz3s^%`Ju_HxHqmEwKj$g#WW#r+ZNchSLv?yn~YG!54cqipse3 zM1}Sh9^|jZsTfH)ndCtrIno~ZsVX$Y1xgVcx=9D;g>64~7-xL%XF=??x>*U}&$%$# zFlQt_&NdJt-x=2a2DoZSi_EXc^HuuYUbUOrIN*;=3`g8s_q!0h9s&(^4;@4xQT#7m z^D9=z#RSE81%ujGIf2J}ZISxk#p}SxS(&@C4DAjvpk;{E8XSqOm74Gnk{aFT3s{5G z%?#5+ie4I!bAB=KhED#cBI`ym?_u@T!EYB5N18ryfDr52kt$}p496#-z`f?TV)+M+ zq8jouz$*D))BFw`;`>!cdR?iv?Q09LThm^9$(!79B3g6JUp&chh4&q&V)2iC%mT6K zdS^ExMTKg0im|LaJW^6p#Kw$;CDa&q+B_!Iwh{$o0%pVGFSn~w>M>e zo|)z5$6U!O!e@G9ttdwI561BNgg}f6H_|asLPSgRx#dC!G!m-x>5z%Pz>^FP@u0$H zyiV`;9*}=V_s)tb!2{qRp)+f(LgfA*)aGx$ooL|N6YK}xgm>KAUqea$Y1RGB zuX~EY*}DAPT*@91#u6|9QnI+`6r>Xnm{25!^<&M=ZOIa4x7cK7N8g7yG%Pk#8e|F{ z<(>?z7xS-k`*b6`>Kjns@glAYW6kawlx;%s^VgdOAOONrLFj#gkI}wlsDFd~gN%gZ z#PvHQDY5(Ftdxp%hMI*Xr{+z><$&ngYy#>Wnzs9fCHH4`R>e^ zTT(`^gSzANB7txS-v<%^@&$zZGvWKB|3Tn zM$KfI9(F&8Br*1pHXg(OqwKB1;!M{4?Z#b$Yvb3;YQmZp{N@#A?Kh!5ekaNx5D`rf5Y75|Z5}sajng{)tI%{> z^_;zzuU%i|ZP%e90)Q^$zxi}MAcCYm4v_p5fn>E~Y?EqYUtrZ62-HpTvN6~$RndF~ z1ic*sMM3--|Il3ro3A;rL|iy{TKI(J{h}gsElAzywE2OHe6i~TH-e1|S*CX*3qjc& z3jYBfD2L=(pIQh|-S0~e=s+RLZ#g1Jh9@H>WZL!mWO7~2x-a?Z?d#iv>fZFnHp!Oj+zI|&ur+-FeiY(Ihv0i$qX$in|glbZa+ujdXj8t^rA;|5@rMf^AUXqN#1enT)t*&<^? z0|7;k3kNByR=JE&Z~ufDQ;;CFK+V2W5~!s;v4MuecY>@H-SivY9|LNg_9&?S6~?}A zR4>NV)+2*e(`s<)KSVqi4ddWJ8abcslE2+GA%Xkp> zH?dakc5>OQB#sdjmjgeWKPcaxt#>U!<+kJXKhRuLE2>IC}E6eTDA3aml#1pC1!wS$K1yS#XFK(Uw1NIO#FO1^M%v6#pQL zlnGxTs;ncI^D90t4@furiw6AT<_iLU)!BvNFaOoY%l`Wd9IX}X1l&@@2LU=0IqRR_Pk7rM*y$Wo$A+aa*uz~io3KAB{;x)|d)B z2ZrS=O*lXzBC*I5|J$vRkq(=b*IhsUz_^qr>zWr*K3pPYfua=ynBGNx&=!N^gVqIi zk*_h=Kg1;>1aJaO=uWDL4-K1vKCm+8cgn5B6mEfsjwyX)zbOoM`(lS#@pQh7ah8fB zpskTOnr5on&40iE;y0N%jRiaR6|gr^ITPJ&U~P?pGSi-b-dD#L`jqpkV14-36s0KzTu@j=o-1V7LrmEV9V#V>^;nb%b`);KzqaVV*QXhOD!&+@v_~b z8bS$gys4(SW5|;hvt@EApqtlRWKHT|T=Hr$bhYm8*u93&-fT`^!;tTkMm-+0W1juR zH)O^;u>(Z2_Gl20Dd}s|AQqC5++=?YSEBWGq6(n&G2tji;i8q&(qhF$gwI2*tXbRTHFp#r=5}ID`SaWIc-AZCE^nOY2?J6{0d2mwQ|?1Mvhs4S_2~B$-wlIwbMW_ z4=Cj9eTY$wVUZHO%D079>n&>wYPBYJT^HhjHrdX~E|x(gVH&=8)it5^i^V(c>QhXL~Z|iOMNhh;*2nNZYP#4bqhPDFb z25?={e5Ey?%9bEZI6B1xecZ88UF$Q)UY3&+Og|^1fmbk}fSElPucC}nsHQq!J242f z5YL5znOpecxIZ?{^E8#thT6a`er`gGr9hMq0rD_^ZW7RNPTcdFkGcX{Cj3QXfR#&5@*fBsjt{-h2 z##Fup6(wtP9%o^Dx!?B}m2#`=w_5w=g4Wdg`#~`x?MGAf2}AcF*By>ar;em~*e_ei zK(uaL2>KmJ6IitN)lT$Db;+GI&(Noi6S{Nq79$|z0;UpquoH#Wu__OZ%|3w1{Cb0x zv8`?WI;!S+ygDykEoKRWjJ%kSZ4VsTy3SW4W7j^S=8;iI+m8YuUR9Dg&#a&08YpF1 zHqS2oUZuAsvM|1JE{WuD1qTKv;6Cv>?Rd^~eb3b*A3uzB*pZ??W+ycxvG5|SwN!3= zcbpLzeQTiBtCM|^D(rjjb!g=y0Z0nxYr$8tvs7Z_GA8CV8=rn3k0^&D{mVJAO*(}5 zAM%0O*83mH2LQzvn!omc1fO2R5}2$wIpQndP@kf?@4@YRwh;J#E(AdSSO~=ReGi#d zS5;P~{~vQFiO|n%d~7`6=?9BZ2Lt`Tg0>Hrx^DG7H;K__(OWr7Z|esEI;MHX1p|5U z>ab+_c@C6Klkdy z7%B)}45#BjX7+C~o8OEu21a%6OFnkS7;)vC4o}hgGvww*!K)?=^WG{X7A0<&o{vd$ z-CTxz4#7)S>ht%_JUPoLWkYlL z%!?CU>bLNg&2>BD=#G{7uCn9m-a0tAGP}I*$UFLC2nM3k7H6%?3k9yK$zhSx6j>h6 z)qB!XQ@+A84}J@e?z&R)NHiBJKux}`A8_${IPtz9ZbSIXaCvAe0Xc#|2Jr>?*nNT= zLUamx<>cg+$2IF8)(p-GY*)=Mtm#tB^{P~TpsuAYr{PEh`FE~lY3&6hHRCs8#&VQ9VBD{cn zK3mN(n67w~-A}5{kaR=m0<$xDv%z$AOiDCOxN*Hntsoy0y3hlpJ+dRI;dJ=f%Jjw| zCj_EXhdS}|3P8ieMpHz?TOU7M*NRTmzH|+AF6PERj9gOM`p_WtvAE9#^izlP7ub&? zMiArFkp%zxLH4M<@CxyZnLFqiXuV+3@=E8m{hC?ZfdDikL1kNQbQnDtzG`3s6C)+sdWknyq^E@+}0q-CMDlCwtDWR$BOzBrJiSA zny=mwNkm{CRA|nHH&O%qoXgDPJzqljQ#nQ_&?4|(c|ThJXEz-!(GqS`<7TE}1VYG_ z=YMk$?}Y|O@Gzv9`bfwx*jMe5=LKYX@t0FDA7~yT3zHDyKah^o#zF-+c`nWnPTR`2 z5!*xs5jm^PZ%~;Ft@no?_68sT%GJszyQ23$X&y&&5MT_PZHv&^|I3e9b6Hckq@Nc+ zUGux4qxYq8n+*Q}nGmh;TVQO}vn)8x?sy<3@9;XjjUn(l4>Qv(TF43-|JUNpKZ(oF z8aAVW^J_2-mg7au|4c?4@!>B3y>yT_iUK7esezGbe#M`kk*_G7!>>u*Ii4|n5_2+RbWY-roGNr&a$wm2w0Hw5RC2ItR9dXP-x1BSw#>n;m z+}lG#daFWmswqEa)IVqp0guG~!94^SCuJcgtCR-_Mr?{vm;T}re_!nF_VE-NH@+Br zq4%R85c=O?J0KQvh#$SJENTF?pE_yW-xvJx=&b_T9FZSFr_^BqM7|~e2T4E<0Ws)^ z6Rbs%7C-~b^Hy@l7SlMXPFF0JC~yCYNj@ zui&{MrY+@VYv$aZpXo6PkbE~;8*1+0p-;a^lzrpl z;CRkGr@hqYiMBHG9Mjf%-A2tdC&J7}efxANnbyeXHm}>9#fi`PZPWh&GN^gl*G{UT z@0xOIFEiF2%@e`Nqz3WGJp{EG67+T-ce91$N}gR0xHk+CO;Atks`alUTz+LgqcP3! zg|trxbtFS{)MMs{&;BWwoDB2hoXh7A?jiiyqY>IKA0#jj#4?AOb6EPeKKd|5Thw-F z>SUCo!s@w7d_*shmlzffb`cmwy(5Z7c)RWIe~wmOnvQBWa%3Oi2_dNFQ)h_&cj8b{ zKEhjXs(rb*`WsHObZI9z%Z*)m$co^kw{I`t<}4d6X{22j?N; zquBe5#u#kOXR1>bouc)5$ud=^@k+uqp8>e+{2mrFL=%vS!6nR$hbnvr01Q>gwv zWs2u=Qz}oMkU(R2$jO9095E*T*-)bQUopsg`56PekcEWKYiMxTi(FjmS5|nI9u?5M zhyb&#aLNDTKIrwk%w5*C!>ka$Cb;3*<9@|+y}rw!fe}{x#rpT13oLKd^ ze}Kyg)TCyIBLd)$@<5?<{!tzYv+`7EruHaBn00ltra$B z@|0~mu15M7>ECl|c$fvhl&U{&)Nma~F9$o?#_(EMA>D)H(iR@hc9$Wapy8!P|IYpC zz)k&fGR7-B+oHbD^JTSL845T)BzjJd>P?v1tNV^6Gglo2fgYox7Awcw{(&@WDy=q# zWKP#!9Q($~5KXHh2LxF^A7J&RJ7Q`!nW=Wf@}CY&-;i>4WBB^f%TsyE&HzSVCyA@i zUJ&{x*T%+eqAY$ZIO3_03|aoZ0_i$SzTq!`>TZMNtI=(zhsQVX3?rv!XuCZxt6wEp z5$vAJ*~ZPwq3|AMYg!W57k?~iv>2=EVhKhhg;J{(0?=W2RlWq0ygiTpESw)f1XuIf zgH|)jdnLkbN@dvx&vQAu&x&=6ho`j;gq~E1XBTk zQgVS3D4^LRd2ZjjKogZs7Dy~SpZZX8EfaEES&FI+?+J7SbSo-S(xA)A zN#EQ)$dDQ_^^*KSBOfk5qNx3#I-UFBJlKFp(yP$=cuYOUf+b=3u zYfi^vnU}E0e|yEejrCI2I-S~&M`X8*%lFQUY* z4nRCB;dmzq`}zsvU+%}?iOK~+HJ{8pEA!OPpK~~drH^$j(RzO+^^6O3-EnkqFi~Cf z2fp#a?E7LTM8~1!EG%T@O7G7lC8Wry;m>AP^#@c0A*PL|jH~Op<>u8r)bEYD^>}A; zSQW(jz_^+q86l(2Y`CotXmGrr=sa(feVtH2bmD8>ksxWx1pn)IfHQ2Gjeglbc7M;jxttDMaFjU!UX4j|b0mebB-#y`^gdwYgBa!HI4G&)Y_ z+H9j};{ZX|bD!=0aWF>Q=HPujC)UODxgUb3V(+LNwzYCdJw_i9V*!gpr-EoVI=^nM zyG@8Jpp@P<+4A~u-BlSIJeKYlRngN-&nVj`%pH5KfZaR-2aZ9*<$>~didq%dp(JwI zLJ|}AoXg#4L#?*w<`zZnZNr2u7R7jNxBNuE3f`%bjyQz*>}MryCVGuTaA_Gh1Yt?W zlHW1UsiR8a9k_~Ww^weC=R*#mWmc^Z$L3s?I|H(Rdr}H)K|1fG*2q=UG~Fnon05hJMwWGCI<-p+ybzIG>76@GKPVdhJ6$^e)W*!jgsWpamF zNlh9>X-+vo7?{GiQvf^ZpJMs#+G5Xg<*=*{g6r}8!Q-Zj1Dfg|d9!Il&xGi$@YnDH zYVGD;?(EcZ>qdyQF8*$xazfqp!0G62hqO zITTftWTTaZ<#I9#Hs4j(+Fek#>7)Q6!}o8_MH@9F49@4} z%X2>kGN-oFxI{m2q?aO|H3rgztr6LuIP3r(J9qR<8M_O~Y8KU|jnVRo3}xoyqXm{2 zWR46EIsvTQ06c}u9enp@EN1v(;r00J4sBk#uelzeVxn0#>*`{baQtz6m;C66RrcGx zsx5ol#gRC=Qsx~I5icoh`aqaWJnLtB0s+M{3psRy+SB3`w{E z;B*$Y(sRGAr|NgP;yzv}r|R_b76z^#9*uD2R79_^klezJ6sap}n~#Xaaqt}B?~lZD zV%8*y_81HQj{zk2VE}0YLV#6WGKk<80;ETPFZIO(!E?c_XBcs@$l?WTW%9 zqo9tf(|9JfSDc?CsU}B#>cCu;{4e^?)Gtoo1i>r-q!twJVW`YdMpy{f5IaI_P;@K* zp}5z)dYI1r$)r(@jF%5?tX$am-!8z%|CshO7+4)1gYr~k&TP5!rah`!_j@WQ4REX` zpzUCTX@*)m4dW-|z`A{r`B)rw<}p*`LWEJ5|)>OWH5JqPz&XX3B5k9JwZf)RO(+Lt&K z>R}m~b{8=Nb0$@T)fmo%Tk`a{A@Hm=s~HhKnqwju+_AqrzYVIcMLV-9* z%KsyMSg&1FLFbrTd@Vv$b|qpD#w$qRbz2yg;=Qi_@_I+-Zw0(bI6~4@W(4&m?0i7! zi()q=(42dHuHd_qf6Y9a+Y$S=BXMsV+V&LlShTihF%}8Spkd7-e|o@lDnp`xb-f| zgg1JcCbMmj|KcD|Xx%(iyCtgp0}b8dft6M%FTlKI)VzafTpqh5p?vM6OWC%!gQE)c`y1A_T!o zO_;X}Y_QvZ>J96o2@AhW$^P%D=sR?EJ-nP5M-$9(ZZq-B^)@Ez5=2!jTingT16uq~ zz`x|Gmb2QCg6r=Dh*7A?cL%IUW;@m}K0dA|B*PRbn8MTnct2t2UAINuQin$d1}KS_8JA?rcJz988HzB&_~d z8WMlvInTS_$1MD~g|~Elq}KOiopR+^78`o*cG3=*O-6Q#1(|P0Q*C4ku3b}pXE2sn z0LFZaCYtHbB7wW{Hy(&!5`{HDUHT#X**0k7OrMHC^^>;2Q|bCSdqzA$76dX=w}xB) z8Gt8<2k{uM13quku@WOz;cxYd#%Z066tFbbwB)AHVIO>A3j=L#eow)+i&?z^r;nA# znt+ZTFOzbjRi=CWuuQk#fZ%HB^a-|I=>GwU>Fl-D(z!E}HesR$cHOf_EC$7hgdYAE z+263hc_1d@*8ovbpxeG(<6;Dimq>L_lamd2QSHD$;bxKGaFb_`OeFX8p9#qXv8v0f zufY+W`1dsh-^($2WHBQ$jJqDjRxs7YhHhNaDv!;LTQq0`PgQ!uCEaIhMND zKOhL(rf(*fl9lS<)@5Z9Am-zc#t?7$&YwL}uhcq>@|&+4z|9a;4>?Z>dz_sk0sANv zq3~Z~W_uEzi}doAb(QJ;Y7R8OhOvIN*HyfX(Y5!7Pxv1N7ZQHL#V_p4KEsqT^1%g; z82SEr*q#KKIny-^HmyLNOEQKL^-nHaj~6xTWERM%h$+gHZIbC&wg=Em&L}jL6#0U6kqZd;$XmzQ00S74;oys%Y&;I^rQ1p!L7Ut8IIq zCdr0TgVRG^Yvoz2j8Y&(NPMqxA+uvM_IX)<)(O|~kj54J85&}60&hH|Uf8FJBN{7t zM@uJdWQJ8;U=;Wh%SdWaoqBm8Q8ZtcYC30+?)|ymeU9&7?$h-^TjqW86yNg6|Rv|)XbrG!(MXMU~W+y$Z@)>6)7|oA)ixkq;Y=orseZf+d zUaQ#&rro5m;Kc3gprI!U()!!*;4O2;p58)XA!vSkHRYq!rssP!RI7?<9n8vU+6a4T z?Z$S4P~`MQHt+LNS(+wF15}bwrIKDbc_iI-QJ#3?%7X^BcB?dY$ZC(xr!TPBL;cKOehVO0)$;Un0_+ zWz&r#_)cKsAT(!AFAe7C$2vG*7lH*^70dv6)zfdxKK(Xejc_W`%azW{w&VI*!&ZJ- ztJID7X$j0EQR;nWr}5{4{|3^873CQ0y8X`4wS}|wOFfz8MoZbZ^;ZcOXR-bcq4^eh zzTNg*-;T-_d|g;!asIV2)@OfVHtO`7!Z+P5tH`Z}+C&M=MIY3fCBl+kNp+ za$%3`t8UenAh>NY*ea)BScDW)66>ddQ#kjM;V-xw2l)a^O;-~=s|^W)P;Zy0*Ikms zD$SFq8&RQBTxf|?pU}F6LnS5!`vSx1YMyw+~KiI_3Y4)%bqaqs$W|Q`{Q~l@;b$ukqFk=_ z?#-&aZWbA3cz=PAO_f=Dvb{3%ofbtNWZj+WWy44!3?Am+m#D%j{J=bct(h}QDA2vq zsQFEB3Fnz-ve5=Q=HU6#j>a``zxExR3h?XSLy!W^?2R1sK`dNOorHl59r%y&(2mym z{AKrK{eJITb?x8p>5oV4BEWhgu|!_8g8VevFTn*W!QXu5C8=wqZ;7fc$U50R@r6Zk01O%3Q+{9^cC~RF@p&InP*G{Y0{rB+hn1+7;1_0H1XE9B2Z!c zw(7vmLR3!7wEdRx*mE()#$zX_&`DanKdZ9dKzyEH%ug5fOfhYHX_h?&0W1bH;`V^a z-?*aCkgDVI*KO`VDEomx)e{7zGnvWv?PP4@6@;)9O@LRp_U= zSK&EAds-&WJ^S0Tc86GQ-0}hfC|d7Fmpa$oY|4;eAP#B|$JB3Ozpl{eKzI(%bHs6? zGM1t1!N;`S!t*b=sQ&D9NAf#r=#&URSW!>`BsOT;L|4j7v#cu-+6C>_Z@HAG=8rX& z_phe|J6g7Ih(HlZok>!?(Ev-b(RhU`1>(Tam>Q(sc~%bc7a!90%gn{<5f~Iil_bXp zXJvDD;j!z$;ocSbfO_r}N{(>Kxoe919@F2K_U9}FZDFuYm6wGXt*oCtZa3t$mdn%f z?lG-zIe(xPbDGce3~B}alBbD1BTZj9<*KQ;aAp@pdpPL5#w4vIg7lLz;DRV8${RGA z##9t^wKZHfVb&#Gc_;t$+3V+q(1u^~Gw(t_QNLX3SBA&A$ZxHh(9cD*riOe#*qB}e z$Jo*)pYD~4N}cTUUbYhLaf6_M`HgqqL_Gf|_+~xk>grL)j(iO<+Tv`R6)hpIq53%ZuQYN%L0|cQFp#Q876ExLFeX7h99BEtClH=aIMv3Yoa(L_m zj*AtBs)XYQP^K-4Eio~V)G>XQU z<`@BoMy+pyDNf}n@%V)jduhLQ-G|m^tSknVx((6{^Yo*iXy&xx1$aqadzZ;;TH&dT zRgH$A7PTJ2iVoEa#~;P(k}}|i$RUd0OTIHk+q}(Bxjvd?981)nzim=L*e@ptf2kQC zdp1GG4GlFt$~)0ET43n@K0aT&h~DPXscqV_akiy3on34~@cx9Wt!>i6RUQl~%G3p! z3|(?^^|toH+)K)eB@4rLxDxwKARx3;;VYn+*TyF#kRw_@L~tq0;R8(wkl*GhSl?6gvRteN{g_3w4>4S~0ug-|1y z9FQ?MkZj;p7;0$a;5S3l@h?NGb%$oI{OdL2!+OX0kc*2r!A*si+)BNa87Zr))Ysj?AHj&=Bns8TiI7&XFXSy084UllFfiOYLAbl;iAiPZWEFOKZKEJp0KI)>oyh27sMj07CZ}V&*2A0YdVFVEQFPs`Xeu&hI;A zl`qi-3vIHC=Erz|HBZPBV`edU2-ow=P>`1=V9`-@mwRd4O4ZfffNckS@&?J76t9eF zU=?v#Wti;~t-ylW>G5L;J-SkA>%>eev)y;ouLy;@5TZgcfwS3zH#jzi0x`lJggyAX z$Ukz9Qc`}?P~Ks4sW!Nc%X&8D$H$vF{Xk1h|LH# z|8RDE3_{&=BMohE!>OqSR!E&v)PStIeF|L{fhua7h&VuiW=*w}XcwV`POXRJQKi3B zNYC=q9o~1zyYR_jPT@0)@P7fFa(WD(M-%c}Zbtn~K-_g8Z@|jBnVP7iz}v2Vu4^2x z=$&SxKPh|SncTFMc*c4-kp^zdM1%s-1OeRa48{s16(TNXfrT~?8OUFbs!>at%78f2 zIy>YXbFOMntRi80{Nar z^eUfmu7v(IB`kyJ@4_iD@)bA5nIZiIGEX;au#BD^o89g+uL~q#RC7yF6%P;Kfd}>j zJ4|deQ3&KneEl2rrizZ7rVrntgJmD(d#>FQP4cBuHSrnW6v4u2d>6Ek5nOeEXbdjG zZb8FSZ<(FMH6yXHx><0ir?R=A+cH>dwT^RpjDciBzrCydKBmaiYa55)Xx@U?YfzbZ z<$E!H2`&$t0a2Fk-0(^$!l=2Tec8~=Nx^;MG;FnEwc%9`xtS_#N23Y4xsn zwXv`s>2^>G4Oo^aMZXZ?GOTHr&$#yo#begax~k!n765EPqC11ZeFs~>Djc`D^3#KP z4R0BU>DQXB%~Mrf#2v3R^&yr=FTIeq8LL~Zy%BqfeLGcHWk%r-40& z`^gdP*Y7|y8<3>lH#?UJ`2sTuwB?3A^_?EgoMx@nI1R|Gs$sn4}E%PorHsGB^LCuf;L4cTVSHv4p^*SHc$$D zsexZiGQ(g}_oH_-TRGM&6Paii8`=;!OKmN|%0uTHs!LIjOBVA{b4qo76j()pG*&8$ zF#@d*wmm%;bEXf3Z!k1kAWSC^NeB0yxw?|>q642{$7d4EX%5R4Fb&>9 zd1tlpfDsakc{npvNi-?g>F7NK)!hF5ct>ukZGe?y=1H(n!;g_KYIViL_&#EAw&^t` zpEv5mv~VN`g~@A9p*OTPgrok#cOd>pdoSd(R2+ICmZT zY`79*Q=o`S^w0g?MRH6E!HHJ$mba|e?MgQXoAp9*jfF$z*E`p5r?Y8mQ!T3t0*}U) z-%Q2#@;#19NP>cFFZx@na)324EFP4Om6s_&0+>RY>}P#}4FE}}2WWQaa0EHkUX-~o z)+s^-giye46GOh>^L=}V^0+Q9p(gh4n~M z*L8&FC?!o+h4)zN#>i--zpS?wX))lk6*w(i071VZGR_;lwiN2h?+}C8IhdR{c&&pq z)qf=E_dJeW!g`EkeQ%6haV|_lFr?IHG%LCZM}E(`DAd5d8C{HsaaT+auGie8Q-woN z*Rl2$E~+OF+OV#wRUR<216Rzf=bM$<3mdU5r^Bau;iYx&@RTL`Mi$7Mcaxa_Zqr^Y zqifPW=&{sJG8aR%bGy?>o6zjd@W6qw%-HK_I?j|h+~&=I<=;cbr*Iqn#u5o1Y zVo1p7vxNcjQ35>?c;OiC%{ck#iy$v= z8$`G9HvK{PrTv2}FWciq2Zj;kb0@=z^eg8kK97U4d#jH>K;W;)Z`IS((*{@YxN-HT^M&)fVV5I^`$9fVB@#+RY|@_x3(+KX6>A6q1(-9?Mh z(A9;%!^m$<7SWGY*tBOXzv2h1U6db+NwG;FRw_h%b$5?#8iP%(y&e*CN@~@QVh{Lz zN@X|hYu-5v$OT@Ze%egylVIu;6VGG2xqULbWhT(C?{#k#x48h#G<%7cS6`2-XzoG`y6qrk^KD&P9VKeJQ?X^eu9-t zLn(q3rSNP&t&YCX*el-~-FjW%q;D3G76?hShl)f-fFRm!_TyNTb@;%s%HZ8Q>?=uW z%5S|GGh zWE6tUQfC7|-AT_6LD)bfPl>x^tMDPb*ZC*thLSFsJwi3sntYMczW@tL@xxrW(0bNSB4B@;nMSvbhyab+@qiWJCQfeN;?B za4!M5S`2rsjT;r#uFWE3(G({c{qogeZ7wpYHoUybpNM17T` z_8Y};?j+bn#d=&0Jary7##LRmy<7oGm_I0Fyq>7a(NR$9Q&t8e3fflmco`7Qvg1oatFzGlogsr#Jl>uYuvPj9SLpGc)nc=wMg? z=~`!hKwzr&J?$KMov}^M%Y`ZwNkb|)V_7Fy`e&zg`B9%i{ zTP-6|6_FBhQg>1d(8w()j21&WO_e@}te|;ylc)?(NmN50mru{WAgiIc{)317dps1Q zn{|M0m(U|hW&Des{6$bU!>I-D^4p76jZFRtU^bFKjdYK29Vj*M{s;eneDPx_7A$E; zd#GX8jr)|0{SV9o_|>_P^YNVWK?mAR9a`+J6t&<##)SqN@Zf}( zzsLDUK;q87{+#&YS=@!TAhz=~675BGxp&Hp&=Hz+PRwSu!>&ykfuPBeRnjgG%_8}+ z>fzq{ezsvuc1Y|Ks_P|b^d0?S{OL=P|IeL_nPeDy^iY)h`%%Jz$!u8Ik69a>KbrZg z>n6nO63M{&*y|6(Ebf!8$gd&8hw|!;_mjVRC6L18G%TQDWsiBhY%lg+W*ZwW7tWsO zaPnulaR8Jr9zTX>*wZ!m9!~T5*z0Kythqdo)*Z%eXf{O*9>}@*ez8kDBt~xqpC9?+ z#u6Gdoo&bCikZugWNNpZ+Y0E^iLi=BQxJpYPJQ#z^664Em=r8ZQBrf;2y4|!c&z0; z%arWs%jIFd0vGSXMy2W!pwPkl@bo(m8uw}%<2TvFdg9+1tSCO+#*C(M zd$Lr@D#@T}5Bs*GJ9W!*$iwK6d9cjcyJ#MJOxCfi7^w!A{R;48q5lK=UxYO+oh(W@vG-y3Yau7ibl)9b9;vv*Y1Mz^rRwi9l-zAF*L>Oz*)b?B1X9@Wl0 zPWu%w-CFD|YkpRfl|N7-{fruMf{xN)ZB z7;d!<=iQteBK|T)TMNbgsum=jfS`fi^$OsN4u-z^YW55!EMmvx*8%*%Z9rOaj-ixi ziq==yWArDvuQkhiw5ptDudhg_*l?L3xj=Y*^1dG;pT$)ZlFu4ukGHkTW7XM$S`h>u zl2Oc{vq4BU#(J;wDxB;un)7*dvb=}{bISg90ZJEacpW1&na2)nM%Mk(N8p45Vo7yI zdlVX{##z8|!KslV5m3Z17?QQ^=bd!K<;9&jVQHurR9u)fsJbDSJ;aQT4Z2CODHt(`KiW(cj`umT z!4NDk>h}57c|V(&gEOklhw*5do_a6qS>u=FS|B&PF6R77?0IM<8?HU&0k!H>|9ncY zJ|n0y#`|{r(VlSB+2Jb!M2LYu!j%OawSd)Ja^2Mcz84$P2|m4D4J8`?3ClmoPMEwW zk)TE6H33q7_>4)ANxjYeZvS{%eoT|))YOG%D?@U8*SriuWfdE|gM7~rnBqf#w&q}K zMw&#~x=Rg)!|-fevvKij7pVI8IBl)8m@lKB04i_41Ao{rVGqzvrD^A1ma&_pYFioD zp?jd|X7f-REN>DPp%|ypef4F{+Jz8qcoqnRyw^1F5mLBfL|8H2@bp)4c6+yZ(`AA| z5LXgLc)H75o!GPh=5maArS|YD(Jc>b_b#9+=f(MuVL|9alluwoAgiP|pQfyk@~Loz z7PwTzoSojk?l8FHGhisXxk1dQM&zZjqx6l1tBZVaK{tzyso1*pv<*jIUX*hufr({P z&g0RLpWJaR8kJ8FGsxXwcvh21P{Bp67J>#DH1vXQc2*%Jkdjo&b59EO6O?DspLh1y zb}-hE*N0HDi=)1_{BF=4xK}dlko7`tpJ8~=q1w<0>+i6tXT!D3TTxPx5(?A4XV)(* zbLgaD@6C$7k|CH(w7W8DgVSN}`%NTbzR&+eutuV)O7Q#Z^R!_U-=eMi`kll(?cg;& z>7pP+KacyLD5{a zx49mXu%8y-g-X@DGT?ZaYzOj?rbe2DfMBiV+zzx5oh<4d45Ha!^X5GZ3qRYp7!kX4 zK)MN^#@6TMZY27VZ#$#6#!(!LCI`CYpC?z(Y$_3gH%j-ARH`$PpFW|5p{>7FS1r=F~rn~BE^4dXx)SvA#3TKKqA4k&< zV;%+{HtC#$TQyMQ$Z2T0%FB1H^@D(Q*uRsl%0RrhYS{G9*6q3VnZ}0i&bRw>M(Sq{ z;FFn62XADF`YI!pEjJu-PYazaR+sjh#!{Gbc=)2pOX7V%nujFd|?*&M*1B zS;&L!GU&ruD`b8SAfB;o{P8LY`|1sE%ZaR#vgtEyStXov-pmHaH$gG008;?IoE=Ds z8V3gi;YD0Jud#VPRp(f@X)Cg5wkW^IqnP@hGhL40DGMyLRc|DfMHi34G@Wo^~190L=cWUz%0_X3Mmxl zaZ8b}q_K5>CdDKg)9%b^BC-Xsu&dcl#m}kgwlmIWT-xS@a*VlWPkI zi{#|;=ep~#0Unu?d-*r7={!FDJobvvJRJB}#r7-3uk?8&bIl6Y0SI4>Bw=^^{Ulw~ zewN5^`l!ttt?cq+@NDDJa&=w<7@9hua!QFe|2&jVYo(L5$%_X7B$PHX30 zB5k&i+3wJsPd744>p6cinTn9D1v;t;)w zjLqi1i$^}1y~a3r1ZG7)4JTF<$Cack{!n>ra+`R(XA`_Tl{3V_z%5hWK_cjoyI>{D z<}hu!f2hvw(Al!Kg3SgY+W1+>3W_7>Qx}(w4_lB;iBq(F^19SAFvI99c9Q83nLph?pQGD}@XQf@eu-Bu zhdIx96!kkk>pEqeCvUkWT^k`kpTmW=t0k7zKzrYC*fL`Y9qEdGZFVShqJXzT?jTrP zuB0K<{b72N%2{Bl=S=`NgKd(dlL*sTwu z<_CmP$v8Y=w{sWO z543={r?{9B`%Zg)VPyv$aCosRV3-bG;IEW%2VJ&f3NRxc|v|8}E-Tfc>{ajLa z6~&dT-HigjURX#U#?5l`LGnw{gcBoY7kAPDoU*F2Bht2~zE?>+j4Bj^-${S+Q7Nni z6FW0AA3W|=`J?7l!^6}|-`3`g6-?$*g~uJzii6=>T3f#`*87{MMq&XKwfiPBS$Zx) zd~og)5I{Ap-|O#V=O=-Lk1i;nl3#))E6$P4k{oX$$00)9}@jO0SQ ztfAM`)+UjKM)4U+(!u?bd9$n;dQ3k$l8!-8nPx%kFSm{u+lz>* zKF13|`%HV~_kUPf3NI%D@+0EMi6|hKYOYkaA)$bv12TRDRVQ;3?l+kT`Yf`H3|rf8 z^JI|vH5$-iTO(hEpirbQDrHBJ&0tW!C)FEYz+kQ=8Llkw4!!vnN7%zVp%W17EE{>z zN{24)8tT0z_N{h3=VVIy8R}#ag}`}dgtXp7MGho>i5gw5eJA_G<+3HX*!)bC+91ZK z>5T+-;3S(DEK0lCB_6n$7LcXDC=!B$9N-#+j%OWz00mIA3W=mtSlT>Tvg-Cz%jqO* ztxR|F6*_b3b*rK|(`jjPORN*C$jyEo4Q3>K?7f6+Ii`kXrkR>ow!1FHAQtVKk5n9x zM_%V-V8Sg1XuCWe8sTdElto-P>kBz zWfVgDZ9qmuhpGT{w(VqMJCkH$+niux+qRvFZQC{`HYT=lGJAg; z|NrfMPMzwim8z^t_sUxD>VErrulsqX4XJsF<6^^LneapIY1pUB*lqJ56Ys_Ur^PDi^T{eVwIo}|ifCzR+=>4Lc&paf!J;9k zmUEsvh2|<^BBTv%f&JBf#WE{+M|rmWdu)q|ktpN>%&#J>@BqH?k!+Iz-Eh zr4Sa#7`r-3Ivc_F0=p|LfhX|rGB4N0oAkAV5x8t{9*&f$Y~cKiTKL-HQI+PmZiy`^ z&!A{$0w@-~brX&4-q_$J1Y*5|`4|wbCDEBzPr%rETks9eI#416f&#foX?cvM(e}gF zNiO4^DyR?3Ueh9IC{Zu(2MYfel(d(PFlVL)@3b=cQ`AX?2 zI?6#qX7JiowiU|L2@GfIku$*xK6ik}KzI5L+}GSIr|$S1Qb>=0x&+V{JBXn zk}}pzjcR&vj=ar9aB*xW(@aK0W`^g6tI?^~Evcl1JciQ}lBe5VJqqk3B4!YVKm?Oi zlAp6BX_wBd)Uab^{fi>h+IoCbpWR+G(15Pdg@qu1O{)NH5aK`XIbeu%_Xw*AGqJjW zppkyC(92&}D_yJnND%2vSS&CMe7#Orv*kIAf8B}z=f;9x%QJYoR-wgKMc_RS)U8ID zqsS4U^Gy-+k}KynYqujJEr=~y&`9;Xv}LVyTf;ziWwcqZ@s^^v8#+Mq;s_Yx;8m6S zX}<#9VxuTNIwFtp&?28B{|SQz5=#n6PsktWIZVs-wQss#Yyl5imfePtskNq9 zv0c=&+V@cE4sc5%#}mi4yGm<$T!es)8v(GK3B&;X-F~sXi5gJ@{C&s*{G2=8kTKuy zbae7orMRAa^-{THY;xNEr%$^#FxrmBH|u>2Oe}<}kKTZcu!sZ*AK(3=a83FhH8@8P zS1Q>f>af@>kcoSEmo5zw<+Q4`iDrvliDz0*>|F|2{E^zMwUOdI3NRs?A5I;bo9umj ziq&Hx>w{(jUE=5>;>K%1C9!pbl}#{m|A!RsBGafrSv#Mv8kY9JC>>1ntDnT#`*i zZQB``KbSvRez25W=y1~5YkQ75I|&W<@+))7a;0$AQSlcS^waz@-rid(P3^XH(sbxO zkCLBd8`TsWL4Ul9Gxjq2Y(?|r250NK(m778aQsWABWy+l$A+I8Za{b6`Jq6x2y5m( zieP^71BMJZXLu93poCCR;6*9vMDO^*b$rA~vXQP^2cVeJ4G;YaL#H{Io&`b9Fc`~F zUWM{EL2gCfgu*_-YzFR6GTC*Y0C3z&b2werL}>>U%V!4ie$7Zi>qQP#7cRMlyy4A) zxDkS4$S4l_`M0eD2voLN`}KKR*5x|~Z{%7pn}0zxZgFMT*A3SG#153Dn5PCFsl>?N zwXzjiZ}T=?mE>}=TISc-$yBL(hz{j@%lJ5&{>ASAC4`k!t6@Y34;^?LF<<^T!ms1J zxq&j+vLAIi0S*67VgJKoOO>+c#khib3GU_-!%3w722lB_E*?=g@AIA8DQv@!uA!y( zf5)pA!%(uWGRsHCY$5Nc>oTnOYuNlo;B-q(E&-QLw+Y$j>&IG@0G;$AceyR{bJiPZ z`Oh+)r!L>l*VAs2bAyu3PHJ(w-WM(#b?j^W^MKyrp=4nTn&_|=@!!K)UF-vZ9=Lq2 zlg2PhA2(PHi%M>{M$+@tC+N@Up^GahFwJ&GDK0X`FLR(oBi*D;$oiB>L{~RHc_j-5 zsS|yy>hRQ~p!c4jTK!-eYmWMP%`7hv_J#bYk4xavT4X{5O}b6Mq!@eC6~ z=H6)#vU$k)Cysiy{t1@@0v5;z0TwiQ0ux2%u_B+4(H%L14h19K@e^ThMCV{Y-2}-b z@qt}zR)=JS1jKS(5ns7hcmp+4J#hs{aQOcwxlG6cz~0;Yg!z2E#FV#Bc(W7S+r2PA z$vcJpLO!QHFrg6Mba{lC)FR*eY@uW+#4~K}1}C*ABtxj(Tz7^;A{<3u#trjarM~gu zq-LO~#Mob7>J?(PJ7epAm#}*;8|(1Wi`3Pi3BiZw4r#Y24kr4|$h&ZZA5S2e}{f265c8JeQj-4_wJ~CYw;fYgri8sra!WEM1=3YVBbUrJ%FxH07kb z&pTOf6I?w@wkYXY{~f0it>+o=233~Y60;iPm&+nADSA?Ftz^G9 zV1a5*?mr|`vwQb%)X{4f+z9!q+?w7G1zz6yo)Z9w7^(rF1WPi)kRER)=$Jj`#AcI^`A)S%sQ zC&yqe){NIlqk(~hs>W0W2qp2T{fATMeOGQ(SdvrvI6V#Rt|bwPFBn9CtRl=f4sH@x z%FmH(YXCK*3@|K`mZryaXNq9)DgjjhZ@K6)Z}IJBq(Gz(4>UBMH_`3PIjy7JX8BZj z8N1}D3tuWepPd%EgyHF4w=>0hqLt%xBL7gWTpQU|-DTb4d__r9BYB${UF_OTyHUf^ za^2qP-d8W^^0dutc1@-!XONaVw-kZ(TF8tz8X%JZ4+`9OxVPrn)@-Z<$rZJOaBiw) zVlAR|_2Z)$+K`Y|goK22r$0X57RpfcYc3rmdM~rP?g&}j|5sq@$tLjMz|{86Q($io zRn?j))|Sadi$Y+iOAaUds_B(bd*KIeP=6~C6{kvm@HSEce#6n2`r}SyM=JOooOQ=P z5KD9mLFw?GLB$!t{o7p_%iE5$`ea~hi8ZGAH(kIsp%F?nM%cCpC9IRp7Te(_z^9S2 zneZU7`!lZeu6JFS&q8#T*#e$9%dhYy+$Ee2D=dxTt|hieQs>&FYadHbgF5z}ehc-? zKkEA_Df_t{i>_qIEy719*sVhLQBszjZVb!W4%l$vW%}HdCu{F8CpbUp=fUk+p;@yL z^2dlY)np*NAj|h=+&fcfrL$7kSqAr*0?0kEvchFfAZu&C%-26xcpwWjH*mrKTG_vp zJ!P`y0Q>|22Y>+;bI7O{)4SaKJ@9s*@ui5`E5rWVYm1P(3Q#h?7Uh3aT5C{Wq*+P; z*Yt(q)vvS3QRs_b)-L@UEl1aij#o<=GdMcwWIbD~-tbL0dgqE|bY!2_ma9CirZTdL zxKos39xnWv7c0@fzkUj6*fms1Y8#{4n%gJ*4+ouY!-k0l0TR%Q9>4*NJuni%ZQMFn z={SNqDM?TMxb+#dy1btUV}&c)E;{w3HpaY}laGnhSDZ1ZIJ@C>{2qL*3L?H83~F`L zl@C^EU!T;?5kpA;%AmwDfydN_?btdDf^byI9rbT`bZ2L*Y&(s2bdp8=+4ZeNvVV^3 z{F=EEW_1fwrT3=_pj&8aL|uz$Xlm$3OumXpmOc7!Y?Lm!ZBTI>?u2FZYPpRM z_M%8A<|oVQfG0_SCm)$g6(9k`e7tLqerm$$Mt_7zl4ao}CX>-b^v^WGP!Y#GglK`h zwCROx05-oi-ssdzh(cV)`ip2KKFy+6|MnyOnCImib&t>7e(7 z#)s?d-f!I#S6UK6$vTbGk#wE1x!h4CmyWasiDtKnx(faw=Q8VW7m*O>%P&5Pb zuAm);isZ3au6F3cn-eH8rQIkLa)44LTQt*D9A}gT4&L8zdYN5A?8z)Vi?>dBkQ8&f zzje!OI0RwvCIKaS7PCQYh{j33U|wGXlAh#DSv7$3IWr5j^g%AVHm>%z;iGrNU7^!^ zF0rZ%j5&@hX2!Gu%vYlOADrytcJTCgy7+C2>QO5eLb00>ne6>&rAHxgh#`Pf{7Q=Q zH1yPiK?UAP?$nU~{|M9t^*03SyA_oia9i+PDF!BAQj4#1c?k(I**jSN3x$3%mKrWp zah;bDHi#m1u!4%{QbNT6fXQOv9>PJb=&bsdcet z?5Nf!>NMNg9?6Ss`)(`{l{HB8Ht}qI{iH?|3iFLr7FJk)vx>3qsxamtlG{^B;3tRO zc%mP<**Q&vj>lx+hh=e5_Txkv{<|+MQS^^&py6KG=t!7kFl{1T0Djq6q0_d4QQnJW z6Nipu9m!1ZjA^QXsf6UaNoX1+`8D)MoV}AlJmhhlBZe;`5MsgGd0|^9402_K6^VvY zqEt>Y56a*LWyPXq?n+M$909i#Re3SFg$1F6tL=eH`1&%hnf7`+wzJo~meX2~Y)y>d zu?HrTu84NmPsHwXG?^95+?KOVm!arFO{YjrHeJYSm2tVmS>I%T){vQ584OTojP8{m z6G$V`3sV;SM<+xiqNX=3zN|2OIa|xuK9jI^XKN^;2pt+7RZ>*6)qicJU?m=1*+1Yf z%dIkFcZ!tw#%%a(&CYO^MN19K#BH28Z2CzHUU18Fn`B|su43SUY+dz#fkEn_NQ{0d zBPDJ)Sm)KAFDG6(Bgh%;V1w3~n6r0;vDUG#TW6VBG?O<+eCExwBc(n=_N|-oqA^7i zS3VedaLlanF$&yI40jM4R1yhY+GgA1^y^7LW)O@&0Im8)4E{kgyhfYwB*TI*HZ|jE zxb%6=_f5p3r21Y_N~!V0eqmnXU(KPg&~=rBco0i`okA4$;D($5q2skkAQmX*cQE^29k z87a3@?v9z1nh#Ztj#8%`)YnOg!9*8T=pRm|=sEZCMXa3qWD|%B5>@|ilkh_@w9n~c zzSbG5Z&3ikmxkI^?Qf-2v>y-9pI!4y3P`eD5&CbmC@Mix5{E*_T(RZhV!V~0qw+@j z5lpLIQ&gHM@i8sUafZ=*>#3vllW95}z**_o7rz5nGzvE;D!mZmYwXh!5+cC+3v#N5 zQvY8Z!d_v{fT4uwY2&})R4KgQaO#ql2%L~PsSooJ0mU~NP$YT;EYx48bW*&C=R$Mf)}=h5{sqkK2{IEJSJELN=jSe$L@dSTPIewTarF! zY#Sw@_$hYP@@Z)%5o2mqyk$+Kbe+>_&r9=xJ{e6jIw5@MUyYj2t-nqbbi z?HuHyNJbwF!{2Zs3>Jyj)r*n#^|?~O)V87ko*6)x@>>!E$eMpL!0{BnJ$1gEWiAZbN#mnOQyO;maY0I!RCd& z0i$a0NS5Bg_-610gOf?W=-sAW<@3|(k={rhArrNf`u7EJij|4RpzM!pE?zv#z55_G zcu~r}u&VAgojx1Owj`X9S&t6<_v8xr%Dh39`X?UAwXyd^iNPAO$|rZIZv**7b27ze zSy37)8pcz-@9u^U-%4fRSZ*lwck9fMuBN#B8+XNwhgkW*Pu(;IGu_kzohz~3&f*(K zPnWo?+f9UIqe+BkhSrCh2w4ssSR|$0hH?`Um;2xS3%}tZ>wOSB((u#B7Wy+k)u}@} zR&w_d2)ehtRGl>%;`(PUu+}ZD>9AOHcM7z zVKGYyQfn>u^F=L)Psz2?kYyiVPr{K;wm*ML?Xyx_%uJA@&+Arn< zL%&tVC?!VoC#l!0t<|uS*u-R6xtu`I@OJTBfqp+!K=^&AVrluw)9P*@SQ2EIt@S+Sp%`&-Iwr~mwXeyBW z4*mWBcAKpV*?W@s8&BDDP6z5WU4OSHb@&0_7O8Zhu{r>tnV|nBP<|+sl?s5inn%Ys z*u$xR6EQd6g?th_6kAb!-|<1j(f+=(4=OhZRd|=kMrOJQEl`rt$J?BLThvT54q3Tq;(|5jBLb?S0cFb%^)NHITHd@RTofko6 zt#iC)Wjjz)b>Ril4>`|Xcl(?r<{nK<7mzc*~2?=a@@t z?RijNS*h*MUNw0Uy`n{-abrrtYzP-#AK6`dAv3KuaUprjK~VAa*{=0dvxUxNCi1m9 zZftnA>5f9MlU{~?`2`ab5r%)$9NqI^(D9rFyzYjo8;2_Idp|&Qt%gbLLCiJBu*^oj zC@F}g`dsHL89Io|^po@kwmSzO%bDd%eXD1fD5Zn*0uBN;0p`{&COx9LRysJkP<-3*cxb1<7O92P zZ&Ii6ytqxtS}rSYbM~cM4dj^)wkl!t`+IQlzHNCCK(rgeUFX*ut~&pY`}n9~KbEY& zW#qfk->seVR8gF4@8OzULrX$)a7FHbbFUGDtI_QGyZf+Rsf-3r9nLA{ zfj7a5q4Rk^ePB%}3>V%|Z^9pVY@lC&^SJ#3ovQ&EAxt31WaJo`?d&zr3=63JIuLWb zzcH!8W+Q%+0u{o3$Oy6hc1;cHeQ` z+_wWBJPGu|aj0<%hi9>DipZ9t{|Wn)R+c$}ezy^qlKsKHQ|5qqxXGWHFd#Zw+DJ$+ z(nJ2ubzIHJ?51$%3Rp5mPs3OhJ(hD1I4cS^^Yh9UjTdNy^g+Y?M@f;hGv-g|xNJI< zA29;2;b2_;Vk4$A4(t|m4GsB>pEoqu@tyo@J4feFXJ?}I6!*;f$!`lOh{O&QPPNYC zpKP@MN2nHm@^7KqZfpl#i_H_p{BF1k>ZEY&?8-5!ac5`y^NwCfoI?JgutG2r;pS~5 zYWN&NZ3})FK}=tkyh9FWP8nyHOO?g5);lSGP~lSMDuEZK5IkfwjXyhG<*+lud!``rhREQ*y_au3qA^WpuP``!( zfTP3kXc>}p7i_Un?KG3?M3~bHE4&}`sD;0rTC#??caKv!e*KztKaF3eI+d+_xP

9c6)m&z^Hk3OF_&y1&{Qh%jg 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 TQSwIs=ub-QgJ^}YzW@IL+x`-Q literal 0 HcmV?d00001 diff --git a/src/v2i-hub/SpatPlugin/docs/spat_plugin_design.png b/src/v2i-hub/SpatPlugin/docs/spat_plugin_design.png new file mode 100644 index 0000000000000000000000000000000000000000..de85b4ffecd51fbe38e11cf0d2bc00c17f1bfb0c GIT binary patch literal 21312 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 literal 0 HcmV?d00001 diff --git a/src/v2i-hub/SpatPlugin/manifest.json b/src/v2i-hub/SpatPlugin/manifest.json index 2d8b957f4..1297658a9 100644 --- a/src/v2i-hub/SpatPlugin/manifest.json +++ b/src/v2i-hub/SpatPlugin/manifest.json @@ -1,6 +1,6 @@ { "name":"SPAT", - "description":"Plugin that reads PTLM data from a configuration file, receives live data from the signal controller, and publishes a J2735 SPAT message.", + "description":"The SPaT plugin receives live Signal Phase and Timing data from the Traffic Signal Controller and publishes a J2735 SPAT message.", "version":"@PROJECT_VERSION@", "exeLocation":"/bin/SpatPlugin", "coreIpAddr":"127.0.0.1", @@ -10,48 +10,53 @@ "type":"J2735", "subtype":"SPAT-P", "description":"Signal Phase and Timing (SPAT) status for the signalized intersection." - }, - { - "type":"SIGCONT", - "subtype":"ACT", - "description":"Current signal controller action" } ], "configuration":[ { "key":"Intersection_Id", "default":"1", - "description":"The intersection id for SPAT generated by this plugin." + "description":"The intersection id for SPAT generated by this plugin (Note Only used in SPAT MODE = BINARY)." }, { "key":"Intersection_Name", "default":"Intersection", - "description":"The intersection name for SPAT generated by this plugin." + "description":"The intersection name for SPAT generated by this plugin (Note Only used in SPAT MODE = BINARY)." }, { "key":"SignalGroupMapping", "default":"{\"SignalGroups\":[{\"SignalGroupId\":1,\"Phase\":1,\"Type\":\"vehicle\"},{\"SignalGroupId\":2,\"Phase\":2,\"Type\":\"vehicle\"},{\"SignalGroupId\":3,\"Phase\":3,\"Type\":\"vehicle\"},{\"SignalGroupId\":4,\"Phase\":4,\"Type\":\"vehicle\"},{\"SignalGroupId\":5,\"Phase\":5,\"Type\":\"vehicle\"},{\"SignalGroupId\":6,\"Phase\":6,\"Type\":\"vehicle\"},{\"SignalGroupId\":7,\"Phase\":7,\"Type\":\"vehicle\"},{\"SignalGroupId\":8,\"Phase\":8,\"Type\":\"vehicle\"},{\"SignalGroupId\":22,\"Phase\":2,\"Type\":\"pedestrian\"},{\"SignalGroupId\":24,\"Phase\":4,\"Type\":\"pedestrian\"},{\"SignalGroupId\":26,\"Phase\":6,\"Type\":\"pedestrian\"},{\"SignalGroupId\":28,\"Phase\":8,\"Type\":\"pedestrian\"}]}", - "description":"JSON data defining a list of SignalGroups and phases." + "description":"JSON data defining a list of SignalGroups and phases (Note Only used in SPAT MODE = BINARY)." }, { "key":"Local_IP", - "default":"", + "default":"127.0.0.1", "description":"The IPv4 address of the local computer for receiving Traffic Signal Controller Broadcast Messages." }, { "key":"Local_UDP_Port", - "default":"local port", + "default":"6053", "description":"The local UDP port for reception of Traffic Signal Controller Broadcast Messages from the TSC." }, { "key":"TSC_IP", - "default":"", + "default":"127.0.0.1", "description":"The IPv4 address of the destination Traffic Signal Controller (TSC)." }, { - "key":"TSC_Remote_SNMP_Port", - "default":"", - "description":"The destination port on the Traffic Signal Controller (TSC) for SNMP NTCIP communication." + "key":"TSC_SNMP_Port", + "default":"5050", + "description":"The destination port on the Traffic Signal Controller (TSC) for SNMP NTCIP 1202 communication." + }, + { + "key":"TSC_SNMP_Community", + "default":"public", + "description":"The SNMP Community used for sending SNMP NTCIP 1202 communication to Traffic Signal Controller (TSC). Please refer TSC vendor documentation for SNMP Community." + }, + { + "key":"SPAT_Mode", + "default":"BINARY", + "description":"The format of received SPAT from Traffic Signal Controller (TSC). Acceptance values are BINARY and J2735_HEX." }, { "key":"LogLevel", diff --git a/src/v2i-hub/SpatPlugin/src/NTCIP1202.cpp b/src/v2i-hub/SpatPlugin/src/NTCIP1202.cpp index 77cac309c..0e8773629 100644 --- a/src/v2i-hub/SpatPlugin/src/NTCIP1202.cpp +++ b/src/v2i-hub/SpatPlugin/src/NTCIP1202.cpp @@ -1,20 +1,20 @@ -/* - * NTCIP1202.cpp +/** + * Copyright (C) 2024 LEIDOS. * - * Created on: Apr 3, 2017 - * Author: ivp + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. */ -#include -#include #include "NTCIP1202.h" -#include -#include -#include -#include -#include -#include -#include using namespace std; using namespace boost::property_tree; @@ -38,13 +38,13 @@ void Ntcip1202::setSignalGroupMappingList(string json) ptree root; read_json(ss, root); - for(auto & signalGroup : root.get_child("SignalGroups")) + for( const auto &[key, value]: root.get_child("SignalGroups")) { - int signalGroupId = signalGroup.second.get("SignalGroupId"); - int phaseNumber = signalGroup.second.get("Phase", 0); - string typeName = signalGroup.second.get("Type"); + int signalGroupId = value.get("SignalGroupId"); + int phaseNumber = value.get("Phase", 0); + string typeName = value.get("Type"); - PLOG(logDEBUG) <<"signalGroupId: "< lock(_spat_lock); std::memcpy(&ntcip1202Data, buff, numBytes); @@ -186,27 +185,10 @@ bool Ntcip1202::isPhaseFlashing() void Ntcip1202::printDebug() { - //printf("phase %d spatVehMinTimeToChange: %02x\r\n",1, ntcip1202Data.phaseTimes[1].spatVehMinTimeToChange); - - //printf("header: %02x\r\n", ntcip1202Data.header); - //printf("phases: %02x\r\n", ntcip1202Data.numOfPhases); - - /*for(int i=0; i<16; i++) - { - printf("phase %d number: %02x\r\n",i, ntcip1202Data.phaseTimes[i].phaseNumber); - - printf("phase %d spatVehMinTimeToChange: %02x\r\n",i, ntcip1202Data.phaseTimes[i].spatVehMinTimeToChange); - printf("phase %d spatVehMaxTimeToChange: %02x\r\n",i, ntcip1202Data.phaseTimes[i].spatVehMaxTimeToChange); - printf("phase %d spatPedMinTimeToChange: %02x\r\n",i, ntcip1202Data.phaseTimes[i].spatPedMinTimeToChange); - printf("phase %d spatPedMaxTimeToChange: %02x\r\n",i, ntcip1202Data.phaseTimes[i].spatPedMaxTimeToChange); - printf("phase %d spatOvlpMinTimeToChange: %02x\r\n",i, ntcip1202Data.phaseTimes[i].spatOvlpMinTimeToChange); - printf("phase %d spatOvpMaxTimeToChange: %02x\r\n",i, ntcip1202Data.phaseTimes[i].spatOvpMaxTimeToChange); - } -*/ for(int i=0; i<16; i++) { int phaseNum = i+1; - PLOG(logDEBUG3) << "Phase " << phaseNum << + PLOG(logDEBUG) << "Phase " << phaseNum << ", Green " << getPhaseGreensStatus(phaseNum) << ", Yellow " << getPhaseYellowStatus(phaseNum) << ", Red " << getPhaseRedStatus(phaseNum) << @@ -216,9 +198,9 @@ void Ntcip1202::printDebug() } } -bool Ntcip1202::ToJ2735r41SPAT(SPAT* spat, char* intersectionName, IntersectionID_t intersectionId) +void Ntcip1202::ToJ2735SPAT(SPAT* spat, unsigned long msEpoch , const std::string &intersectionName, IntersectionID_t intersectionId) { - time_t epochSec = clock->nowInSeconds(); + time_t epochSec = msEpoch/1000; struct tm utctime; gmtime_r( &epochSec, &utctime ); @@ -227,11 +209,9 @@ bool Ntcip1202::ToJ2735r41SPAT(SPAT* spat, char* intersectionName, IntersectionI long minOfYear = utctime.tm_min + (utctime.tm_hour * 60) + (utctime.tm_yday * 24 * 60); // Calculate the millisecond of the minute - auto epochMs = clock->nowInMilliseconds(); + auto epochMs = msEpoch; long msOfMin = 1000 * (epochSec % 60) + (epochMs % 1000); - std::lock_guard lock(_spat_lock); - ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_SPAT, spat); #if SAEJ2735_SPEC < 63 @@ -242,9 +222,9 @@ bool Ntcip1202::ToJ2735r41SPAT(SPAT* spat, char* intersectionName, IntersectionI intersection->name = (DescriptiveName_t *) calloc(1, sizeof(DescriptiveName_t)); - intersection->name->size = strlen(intersectionName); - intersection->name->buf = (uint8_t *) calloc(1, strlen(intersectionName)); - memcpy(intersection->name->buf, intersectionName, strlen(intersectionName)); + intersection->name->size = intersectionName.length(); + intersection->name->buf = (uint8_t *) calloc(1, intersectionName.length()); + memcpy(intersection->name->buf, intersectionName.c_str(), intersectionName.length()); intersection->id.id = intersectionId; intersection->revision = (MsgCount_t) 1; @@ -276,7 +256,7 @@ bool Ntcip1202::ToJ2735r41SPAT(SPAT* spat, char* intersectionName, IntersectionI MovementState *movement = (MovementState *) calloc(1, sizeof(MovementState)); movement->signalGroup = it->SignalGroupId; - populateVehicleSignalGroup(movement, phase); + populateVehicleSignalGroup(movement, phase, msEpoch); ASN_SEQUENCE_ADD(&intersection->states.list, movement); } @@ -293,7 +273,7 @@ bool Ntcip1202::ToJ2735r41SPAT(SPAT* spat, char* intersectionName, IntersectionI MovementState *movement = (MovementState *) calloc(1, sizeof(MovementState)); movement->signalGroup = it->SignalGroupId; - populatePedestrianSignalGroup(movement, phase); + populatePedestrianSignalGroup(movement, phase, msEpoch); ASN_SEQUENCE_ADD(&intersection->states.list, movement); } @@ -309,7 +289,7 @@ bool Ntcip1202::ToJ2735r41SPAT(SPAT* spat, char* intersectionName, IntersectionI MovementState *movement = (MovementState *) calloc(1, sizeof(MovementState)); movement->signalGroup = it->SignalGroupId; - populateOverlapSignalGroup(movement, phase); + populateOverlapSignalGroup(movement, phase, msEpoch); ASN_SEQUENCE_ADD(&intersection->states.list, movement); } @@ -320,12 +300,9 @@ bool Ntcip1202::ToJ2735r41SPAT(SPAT* spat, char* intersectionName, IntersectionI } ASN_SEQUENCE_ADD(&(spat->intersections.list), intersection); - - - return true; } -void Ntcip1202::populateVehicleSignalGroup(MovementState *movement, int phase) +void Ntcip1202::populateVehicleSignalGroup(MovementState *movement, int phase, unsigned long msEpoch) { MovementEvent *stateTimeSpeed = (MovementEvent *) calloc(1, sizeof(MovementEvent)); @@ -334,10 +311,7 @@ void Ntcip1202::populateVehicleSignalGroup(MovementState *movement, int phase) if(getPhaseRedStatus(phase)) { - PLOG(logDEBUG3) << "Phase " << phase << - " Red " << getPhaseRedStatus(phase) << - ", isFlashing " << isFlashing << - ", forceFlashing " << forceFlashing ; + PLOG(logDEBUG3) << "Phase " << phase << " Red " << getPhaseRedStatus(phase) << ", isFlashing " << isFlashing << ", forceFlashing " << forceFlashing ; if(isFlashing) stateTimeSpeed->eventState = MovementPhaseState_stop_Then_Proceed; else @@ -364,30 +338,20 @@ void Ntcip1202::populateVehicleSignalGroup(MovementState *movement, int phase) } stateTimeSpeed->timing = (TimeChangeDetails * ) calloc(1, sizeof(TimeChangeDetails)); - stateTimeSpeed->timing->minEndTime = getAdjustedTime(getVehicleMinTime(phase)); + stateTimeSpeed->timing->minEndTime = getAdjustedTime(getVehicleMinTime(phase),msEpoch); if (getVehicleMaxTime(phase) > 0) { stateTimeSpeed->timing->maxEndTime = (TimeMark_t *) calloc(1, sizeof(TimeMark_t)); - *(stateTimeSpeed->timing->maxEndTime) = getAdjustedTime(getVehicleMaxTime(phase)); + *(stateTimeSpeed->timing->maxEndTime) = getAdjustedTime(getVehicleMaxTime(phase), msEpoch); } //we only get a phase number 1-16 from ped detect, assume its a ped phase -// if(getSpatPedestrianDetect(phase)) -// { -// movement->maneuverAssistList = (ManeuverAssistList *) calloc(1, sizeof(ManeuverAssistList)); -// ConnectionManeuverAssist *pedDetect = (ConnectionManeuverAssist *) calloc(1, sizeof(ConnectionManeuverAssist)); -// pedDetect->connectionID = 0; -// pedDetect->pedBicycleDetect = (PedestrianBicycleDetect_t *) calloc(1, sizeof(PedestrianBicycleDetect_t)); -// -// *(pedDetect->pedBicycleDetect) = 1; -// ASN_SEQUENCE_ADD(&movement->maneuverAssistList->list, pedDetect); -// } ASN_SEQUENCE_ADD(&movement->state_time_speed.list, stateTimeSpeed); } -void Ntcip1202::populatePedestrianSignalGroup(MovementState *movement, int phase) +void Ntcip1202::populatePedestrianSignalGroup(MovementState *movement, int phase, unsigned long msEpoch) { MovementEvent *stateTimeSpeed = (MovementEvent *) calloc(1, sizeof(MovementEvent)); @@ -409,12 +373,12 @@ void Ntcip1202::populatePedestrianSignalGroup(MovementState *movement, int phase } stateTimeSpeed->timing = (TimeChangeDetails * ) calloc(1, sizeof(TimeChangeDetails)); - stateTimeSpeed->timing->minEndTime = getAdjustedTime(getPedMinTime(phase)); + stateTimeSpeed->timing->minEndTime = getAdjustedTime(getPedMinTime(phase), msEpoch); if (ntcip1202Data.phaseTimes[phase].spatPedMaxTimeToChange > 0) { stateTimeSpeed->timing->maxEndTime = (TimeMark_t *) calloc(1, sizeof(TimeMark_t)); - *(stateTimeSpeed->timing->maxEndTime) = getAdjustedTime(getPedMaxTime(phase)); + *(stateTimeSpeed->timing->maxEndTime) = getAdjustedTime(getPedMaxTime(phase), msEpoch); } if(getSpatPedestrianDetect(phase)) @@ -430,7 +394,7 @@ void Ntcip1202::populatePedestrianSignalGroup(MovementState *movement, int phase ASN_SEQUENCE_ADD(&movement->state_time_speed.list, stateTimeSpeed); } -void Ntcip1202::populateOverlapSignalGroup(MovementState *movement, int phase) +void Ntcip1202::populateOverlapSignalGroup(MovementState *movement, int phase, unsigned long msEpoch) { MovementEvent *stateTimeSpeed = (MovementEvent *) calloc(1, sizeof(MovementEvent)); @@ -465,12 +429,12 @@ void Ntcip1202::populateOverlapSignalGroup(MovementState *movement, int phase) } stateTimeSpeed->timing = (TimeChangeDetails * ) calloc(1, sizeof(TimeChangeDetails)); - stateTimeSpeed->timing->minEndTime = getAdjustedTime(getOverlapMinTime(phase)); + stateTimeSpeed->timing->minEndTime = getAdjustedTime(getOverlapMinTime(phase), msEpoch); if (getOverlapMaxTime(phase) > 0) { stateTimeSpeed->timing->maxEndTime = (TimeMark_t *) calloc(1, sizeof(TimeMark_t)); - *(stateTimeSpeed->timing->maxEndTime) = getAdjustedTime(getOverlapMaxTime(phase)); + *(stateTimeSpeed->timing->maxEndTime) = getAdjustedTime(getOverlapMaxTime(phase), msEpoch); } //we only get a phase number 1-16 from ped detect, assume its a ped phase @@ -518,13 +482,13 @@ int Ntcip1202::getPedestrianSignalGroupForPhase(int phase) return signalGroupId; } -long Ntcip1202::getAdjustedTime(unsigned int offset_tenthofSec) +long Ntcip1202::getAdjustedTime(unsigned int offset_tenthofSec, unsigned long msEpoch) const { // generate J2735 TimeMark which is: // Tenths of a second in the current or next hour // In units of 1/10th second from UTC time // first get new time is absolute milliseconds - auto epochMs = clock->nowInMilliseconds() + (offset_tenthofSec * 100); + auto epochMs = msEpoch + (offset_tenthofSec * 100); // get minute and second of hour from UTC time time_t epochSec = epochMs / 1000; struct tm utctime; diff --git a/src/v2i-hub/SpatPlugin/src/NTCIP1202.h b/src/v2i-hub/SpatPlugin/src/NTCIP1202.h index b3bc9a549..2ebb279a8 100644 --- a/src/v2i-hub/SpatPlugin/src/NTCIP1202.h +++ b/src/v2i-hub/SpatPlugin/src/NTCIP1202.h @@ -1,19 +1,35 @@ -/* - * NTCIP1202.h +/** + * Copyright (C) 2024 LEIDOS. * - * Created on: Apr 3, 2017 - * Author: ivp + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. */ -#ifndef SRC_NTCIP1202_H_ -#define SRC_NTCIP1202_H_ +#pragma once #include #include #include +#include +#include +#include +#include +#include +#include -#include "carma-clock/carma_clock.h" +#include +#include +#include using namespace std; @@ -68,8 +84,6 @@ struct SignalGroupMapping class Ntcip1202 { public: - inline explicit Ntcip1202(std::shared_ptr clock) : - clock(clock) {}; void setSignalGroupMappingList(string json); void copyBytesIntoNtcip1202(char* buff, int numBytes); @@ -100,32 +114,29 @@ class Ntcip1202 uint16_t getOverlapMinTime(int phaseNumber); uint16_t getOverlapMaxTime(int phaseNumber); - long getAdjustedTime(unsigned int offset); + long getAdjustedTime(unsigned int offset_tenthofSec, unsigned long msEpoch) const; bool isFlashingStatus(); bool isPhaseFlashing(); - bool ToJ2735r41SPAT(SPAT* spat, char* intersectionName, IntersectionID_t intersectionId); + void ToJ2735SPAT(SPAT* spat, unsigned long msEpoch , const std::string &intersectionName, IntersectionID_t intersectionId); void printDebug(); private: - std::shared_ptr clock; Ntcip1202Ext ntcip1202Data; std::map _phaseToIndexMapping; - std::mutex _spat_lock; list signalGroupMappingList; int getVehicleSignalGroupForPhase(int phase); int getPedestrianSignalGroupForPhase(int phase); - void populateVehicleSignalGroup(MovementState *movement, int phase); - void populatePedestrianSignalGroup(MovementState *movement, int phase); - void populateOverlapSignalGroup(MovementState *movement, int phase); + void populateVehicleSignalGroup(MovementState *movement, int phase, unsigned long msEpoch); + void populatePedestrianSignalGroup(MovementState *movement, int phase, unsigned long msEpoch); + void populateOverlapSignalGroup(MovementState *movement, int phase, unsigned long msEpoch); }; -#endif /* SRC_NTCIP1202_H_ */ diff --git a/src/v2i-hub/SpatPlugin/src/NTCIP1202OIDs.h b/src/v2i-hub/SpatPlugin/src/NTCIP1202OIDs.h new file mode 100644 index 000000000..1d4789f1d --- /dev/null +++ b/src/v2i-hub/SpatPlugin/src/NTCIP1202OIDs.h @@ -0,0 +1,29 @@ +/** + * Copyright (C) 2024 LEIDOS. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +#pragma once + +#include +/** + * @brief Contains OIDs (Object Identifier) described in NTCIP 1202 V3 (National Transporation Communication for ITS Protocol Object + * Definitions for Actuated Signal Controllers (ASC) Interface) used for SPaT Plugin functionality. + */ +namespace NTCIP1202V2{ + /** + * @brief OID for ENABLE_SPAT + */ + static const std::string ENABLE_SPAT_OID = "1.3.6.1.4.1.1206.3.5.2.9.44.1.0"; +} \ No newline at end of file diff --git a/src/v2i-hub/SpatPlugin/src/PedestrianDetectionForSPAT.cpp b/src/v2i-hub/SpatPlugin/src/PedestrianDetectionForSPAT.cpp deleted file mode 100644 index 235618446..000000000 --- a/src/v2i-hub/SpatPlugin/src/PedestrianDetectionForSPAT.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include "PedestrianDetectionForSPAT.h" - -#include - -using namespace tmx::messages; - -void PedestrianDetectionForSPAT::updateEncodedSpat(SpatEncodedMessage & spatEncodedMsg, - std::shared_ptr _spatMessage, - const std::string & currentPedLanes) const -{ - // Add pedestrian lanes with active detections and clear the rest - auto spat = _spatMessage->get_j2735_data(); - // check for valid SPAT and also if ped lanes is filled in - if (spat && spat->intersections.list.array && spat->intersections.list.count > 0 && currentPedLanes.length()) { - // parse ped zone string into set of int values - std::vector zones; - { - std::vector zoneList(currentPedLanes.length()); - std::copy(currentPedLanes.begin(), currentPedLanes.end(), zoneList.begin()); - char *restOfString = nullptr; - auto c = strtok_r(zoneList.data(), ",", &restOfString); - - while (c != nullptr) { - zones.push_back(strtol(c, nullptr, 0)); - c = strtok_r(nullptr, ",", &restOfString); - } - } - - if (!zones.empty()) { - ManeuverAssistList *&mas = spat->intersections.list.array[0]->maneuverAssistList; - mas = (ManeuverAssistList *) calloc(1, sizeof(ManeuverAssistList)); - std::sort(zones.begin(), zones.end()); - // add a connection maneuver for each ped zone and set the ped detect flag - std::for_each(zones.begin(), zones.end(), [&mas](LaneConnectionID_t connectionId) - { - auto connectionManManeuverAssist = (ConnectionManeuverAssist *) calloc(1, sizeof(ConnectionManeuverAssist)); - connectionManManeuverAssist->connectionID = connectionId; - connectionManManeuverAssist->pedBicycleDetect = (PedestrianBicycleDetect_t *) calloc(1, sizeof(PedestrianBicycleDetect_t)); - *(connectionManManeuverAssist->pedBicycleDetect) = 1; - ASN_SEQUENCE_ADD(mas, connectionManManeuverAssist); - } - ); - } - } - - MessageFrameMessage frame(_spatMessage->get_j2735_data()); - spatEncodedMsg.set_data(TmxJ2735EncodedMessage::encode_j2735_message>(frame)); - //Free the memory allocated for MessageFrame - free(frame.get_j2735_data().get()); -} diff --git a/src/v2i-hub/SpatPlugin/src/PedestrianDetectionForSPAT.h b/src/v2i-hub/SpatPlugin/src/PedestrianDetectionForSPAT.h deleted file mode 100644 index 3d9ed440f..000000000 --- a/src/v2i-hub/SpatPlugin/src/PedestrianDetectionForSPAT.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include - -/** - * A helper class to add pedestrian detection elements to a SPAT message. -*/ -class PedestrianDetectionForSPAT -{ -public: - void updateEncodedSpat(tmx::messages::SpatEncodedMessage & spatEncodedMsg, - std::shared_ptr _spatMessage, - const std::string & currentPedLanes) const; -}; diff --git a/src/v2i-hub/SpatPlugin/src/SignalControllerConnection.cpp b/src/v2i-hub/SpatPlugin/src/SignalControllerConnection.cpp new file mode 100644 index 000000000..d5579886e --- /dev/null +++ b/src/v2i-hub/SpatPlugin/src/SignalControllerConnection.cpp @@ -0,0 +1,87 @@ +/** + * Copyright (C) 2024 LEIDOS. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +#include "SignalControllerConnection.h" + +namespace SpatPlugin { + + SignalControllerConnection::SignalControllerConnection(const std::string &localIp, unsigned int localPort, const std::string &signalGroupMapping, const std::string &scIp, unsigned int scSNMPPort, const std::string &scSNMPCommunity, const std::string &intersectionName, unsigned int intersectionId) : spatPacketReceiver(std::make_shared(localIp, localPort)) ,scSNMPClient(std::make_shared(scIp, scSNMPPort ,scSNMPCommunity, "", "", "")), signalGroupMapping(signalGroupMapping), intersectionName(intersectionName), intersectionId(intersectionId) { + + }; + bool SignalControllerConnection::initializeSignalControllerConnection(bool enable_spat) const { + // TODO : Update to more generic TSC Initialization process that simply follows NTCIP 1202 version guidelines. Also + // set intersection ID in J2735_HEX SPAT Mode. The HEX payload should include a intersection ID. + bool status = true; + if (enable_spat) + { + // For binary SPAT a value of 2 enables original SPAT binary broadcast on the TSC and a value of 6 enables original SPAT plugin additional Pedestrian Information. + // NOTE: Pedestrian information is untested. + tmx::utils::snmp_response_obj enable_spat_resp; + enable_spat_resp.val_int = 2; + enable_spat_resp.type = tmx::utils::snmp_response_obj::response_type::INTEGER; + status = status && scSNMPClient->process_snmp_request(NTCIP1202V2::ENABLE_SPAT_OID, tmx::utils::request_type::SET, enable_spat_resp); + } + + return status; + }; + + void SignalControllerConnection::receiveBinarySPAT(const std::shared_ptr &spat, uint64_t timeMs ) const { + FILE_LOG(tmx::utils::logDEBUG) << "Receiving binary SPAT ..." << std::endl; + char buf[SPAT_BINARY_BUFFER_SIZE]; + auto numBytes = spatPacketReceiver->TimedReceive(buf, SPAT_BINARY_BUFFER_SIZE, UDP_SERVER_TIMEOUT_MS); + if (numBytes > 0) + { + // Convert Binary buffer to SPAT pointer + Ntcip1202 ntcip1202; + ntcip1202.setSignalGroupMappingList(this->signalGroupMapping); + ntcip1202.copyBytesIntoNtcip1202(buf, numBytes); + ntcip1202.ToJ2735SPAT(spat.get(),timeMs, intersectionName, intersectionId); + if (tmx::utils::FILELog::ReportingLevel() >= tmx::utils::logDEBUG) + { + xer_fprint(stdout, &asn_DEF_SPAT, spat.get()); + } + } + else { + throw tmx::utils::UdpServerRuntimeError("UDP Server error occured or socket time out."); + } + } + + void SignalControllerConnection::receiveUPERSPAT(std::shared_ptr &spatEncoded_ptr) const { + FILE_LOG(tmx::utils::logDEBUG1) << "Receiving J2725 HEX SPAT ..." << std::endl; + auto payload = spatPacketReceiver->stringTimedReceive( UDP_SERVER_TIMEOUT_MS ); + auto index = payload.find("Payload="); + if ( index != std::string::npos ) { + // Retreive hex string payload + auto hex = payload.substr(index + 8); + // Remove new lines and empty space + hex.erase(std::remove(hex.begin(), hex.end(), '\n'), hex.end()); + hex.erase(std::remove(hex.begin(), hex.end(), ' '), hex.end()); + FILE_LOG(tmx::utils::logDEBUG1) << "Reading HEX String " << hex << std::endl; + // Convert to byte stream + tmx::byte_stream bytes = tmx::byte_stream_decode(hex); + // Read SpateEncodedMessage from bytes + tmx::messages::J2735MessageFactory myFactory; + spatEncoded_ptr.reset(dynamic_cast(myFactory.NewMessage(bytes))); + if (tmx::utils::FILELog::ReportingLevel() >= tmx::utils::logDEBUG) + { + xer_fprint(stdout, &asn_DEF_SPAT, spatEncoded_ptr->decode_j2735_message().get_j2735_data().get()); + } + } + else { + throw tmx::TmxException("Could not find UPER Payload in received SPAT UDP Packet!"); + } + } + +} \ No newline at end of file diff --git a/src/v2i-hub/SpatPlugin/src/SignalControllerConnection.h b/src/v2i-hub/SpatPlugin/src/SignalControllerConnection.h new file mode 100644 index 000000000..8370d8f58 --- /dev/null +++ b/src/v2i-hub/SpatPlugin/src/SignalControllerConnection.h @@ -0,0 +1,104 @@ +/** + * Copyright (C) 2024 LEIDOS. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "NTCIP1202.h" +#include "NTCIP1202OIDs.h" + + + +namespace SpatPlugin { + /** + * @brief Class to represent Traffic Signal Controller (TSC or SC) connection. Includes a UDP Server for listening for + * SPaT data broadcast from TSC. Also includes an SNMP Client for request or setting status of SNMP objects described + * in NTCIP 1202 (Object Definitions for Actuated Signal Controllers). + */ + class SignalControllerConnection + { + private: + /** + * @brief UDP Server for receiving SPaT packets from TSC. + */ + std::shared_ptr spatPacketReceiver; + /** + * @brief SNMP Client for requesting or setting status of SNMP Objects on + * TSC. + */ + std::shared_ptr scSNMPClient; + /** + * @brief String that describes phase to signal group mapping configured on + * the TSC. TODO: Remove in place of SNMP requests on tables. + */ + std::string signalGroupMapping; + /** + * @brief String name of intersection in SPaT messages. + */ + std::string intersectionName; + /** + * @brief Numeric identifier for intersection in SPaT messages. + */ + unsigned int intersectionId; + + const static unsigned int SPAT_BINARY_BUFFER_SIZE = 1000; + + const static unsigned int UDP_SERVER_TIMEOUT_MS = 1000; + + friend class TestSignalControllerConnection; + + public: + /** + * @brief Constructor for Signal Controller Connection. + * @param localIp IP address of device connecting to signal controller. This will be the IP of the UDP Server listening for SPaT data. + * @param localPort port on which to listen for SPaT data. + * @param signalGroupMapping JSON mapping of phases to signal groups + * @param scIp IP address of TSC + * @param scSNMPPort port of SNMP Server on TSC + * @param intersectionName Name of intersection + * @param intersectionID Intersection ID. + */ + SignalControllerConnection(const std::string &localIp, unsigned int localPort, const std::string &signalGroupMapping, const std::string &scIp, unsigned int scSNMPPort, const std::string &scSNMPCommunity, const std::string &intersectionName, unsigned int intersectionID); + + /** + * @brief Method attempts to send SNMP SET requests to initialize the TSC. NOTE: Some of the + * OIDs in called in this initialize method may not be exposed by a TSC depending on which + * version of the NTCIP 1202 standard they are complaint with. To avoid failures please use + * the bool flags to indicate which OIDs need to be set for initialization. + * @param enable_spat bool flag on whether to attempt to set enable spat to true (NOT available for NTCIP 1202 versions >= 3 ) + * @return true if successful and false if not. + */ + bool initializeSignalControllerConnection(bool enable_spat) const; + /** + * @brief Method to receive SPaT data in binary format from TSC. + * @param spat an empty SPaT pointer to which the SPAT data will be written. + * @param timeMs current time in ms from epoch to use for message timestamp. + */ + void receiveBinarySPAT(const std::shared_ptr &spat, uint64_t timeMs) const; + /** + * @brief Method to receive SPaT data in UPER Hex format from TSC. + * @param spatEncoded_ptr Empty SpatEncodedMessage to which the UPER encoded SPaT data will be written. + */ + void receiveUPERSPAT(std::shared_ptr &spatEncoded_ptr) const; + }; +} \ No newline at end of file diff --git a/src/v2i-hub/SpatPlugin/src/SpatPlugin.cpp b/src/v2i-hub/SpatPlugin/src/SpatPlugin.cpp index c47a4863f..1a7b9ca96 100644 --- a/src/v2i-hub/SpatPlugin/src/SpatPlugin.cpp +++ b/src/v2i-hub/SpatPlugin/src/SpatPlugin.cpp @@ -1,6 +1,20 @@ +/** + * Copyright (C) 2024 LEIDOS. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ #include "SpatPlugin.h" -#include using namespace std; using namespace tmx::messages; @@ -8,170 +22,131 @@ using namespace tmx::utils; namespace SpatPlugin { -SpatPlugin::SpatPlugin(string name) : - PluginClientClockAware(name), sc(getClock()), intersectionId(0) { - AddMessageFilter(this, &SpatPlugin::HandlePedestrianDetection); - SubscribeToMessages(); -} - -SpatPlugin::~SpatPlugin() { - -} - -void SpatPlugin::UpdateConfigSettings() { - - GetConfigValue("SignalGroupMapping", signalGroupMappingJson, &data_lock); - GetConfigValue("Local_IP", localIp, &data_lock); - GetConfigValue("Local_UDP_Port", localUdpPort, &data_lock); - GetConfigValue("TSC_IP", tscIp, &data_lock); - GetConfigValue("TSC_Remote_SNMP_Port", tscRemoteSnmpPort, - &data_lock); - GetConfigValue("Intersection_Name", intersectionName, - &data_lock); - GetConfigValue("Intersection_Id", intersectionId, &data_lock); - - isConfigurationLoaded = true; -} - -void SpatPlugin::OnConfigChanged(const char *key, const char *value) { - PluginClient::OnConfigChanged(key, value); - UpdateConfigSettings(); -} - -void SpatPlugin::OnStateChange(IvpPluginState state) { - PluginClientClockAware::OnStateChange(state); - - if (state == IvpPluginState_registered) { - UpdateConfigSettings(); + SpatPlugin::SpatPlugin(const std::string &name) :PluginClientClockAware(name) { + spatReceiverThread = std::make_unique(std::chrono::milliseconds(5)); } -} -void SpatPlugin::HandlePedestrianDetection(PedestrianMessage &pedMsg, routeable_message &routeableMsg) { - lock_guard lock(data_lock); - _pedMessage = pedMsg; -} - -int SpatPlugin::Main() { - - int iCounter = 0; - - PLOG(logINFO) << "Waiting for clock initialization"; - - // wait for the clock to be initialized and record the time when it is ready - getClock()->wait_for_initialization(); - auto nextSpatTime = getClock()->nowInMilliseconds(); - PLOG(logINFO) << "Initial nextSpatTime=" << nextSpatTime; - - try { - while (_plugin->state != IvpPluginState_error) { - // wait to send next message - if (isConfigurationLoaded) { - if (!isConfigured) { - - usleep(200000); + SpatPlugin::~SpatPlugin() { - int action = sc.getActionNumber(); - - /*pthread_mutex_lock(&gSettingsMutex); - std::cout << "Get PTLM file specified by configuration settings" << std::endl; - std::string ptlmFile = GetPtlmFile(action); - pthread_mutex_unlock(&gSettingsMutex); - */ - //if (!ptlmFile.empty()) - //{ - _actionNumber = action; - - // sc.spat_message_mutex does not need locked because the thread is not running yet. - - { - std::lock_guard lock(data_lock); - string ptlm = ""; - sc.setConfigs(localIp, localUdpPort, tscIp, - tscRemoteSnmpPort, ptlm, intersectionName, - intersectionId); - } - // Start the signal controller thread. - sc.Start(signalGroupMappingJson); - // Give the spatdata pointer to the message class - //smr41.setSpatData(sc.getSpatData()); + } - isConfigured = true; - //} + void SpatPlugin::UpdateConfigSettings() { + + if (this->IsPluginState(IvpPluginState_registered)) { + std::string signal_group_mapping_json; + std::string ip_address; + unsigned int port; + std::string signal_controller_ip; + unsigned int signal_controller_snmp_port; + std::string signal_controller_snmp_community; + std::string intersection_name; + unsigned int intersection_id; + GetConfigValue("SignalGroupMapping", signal_group_mapping_json, &data_lock); + GetConfigValue("Local_IP", ip_address, &data_lock); + GetConfigValue("Local_UDP_Port", port, &data_lock); + GetConfigValue("TSC_IP", signal_controller_ip, &data_lock); + GetConfigValue("TSC_SNMP_Port", signal_controller_snmp_port,&data_lock); + GetConfigValue("TSC_SNMP_Community", signal_controller_snmp_community,&data_lock); + + GetConfigValue("Intersection_Name", intersection_name,&data_lock); + GetConfigValue("Intersection_Id", intersection_id, &data_lock); + GetConfigValue("SPAT_Mode", spatMode, &data_lock); + + if (scConnection) { + scConnection.reset(new SignalControllerConnection(ip_address, port, signal_group_mapping_json, signal_controller_ip, signal_controller_snmp_port, signal_controller_snmp_community ,intersection_name, intersection_id)); + } + else { + scConnection = std::make_unique(ip_address, port, signal_group_mapping_json, signal_controller_ip, signal_controller_snmp_port,signal_controller_snmp_community, intersection_name, intersection_id); + } + // Only enable spat broadcast in simulation mode. TFHRC TSCs do not expose this OID so calls to it will fail in hardware deployment + auto connected = scConnection->initializeSignalControllerConnection(PluginClientClockAware::isSimulationMode()); + if ( connected ) { + SetStatus(keyConnectionStatus, "IDLE"); + try { + spatReceiverThread->AddPeriodicTick([this]() + { + this->processSpat(); + if (!this->isConnected) { + SetStatus(keyConnectionStatus, "CONNECTED"); + this->isConnected = true; + } + }, // end of lambda expression + std::chrono::milliseconds(5) + ); + + spatReceiverThread->Start(); } + catch (const TmxException &e) { + PLOG(tmx::utils::logERROR) << "Encountered error " << e.what() << " during SPAT Processing." << std::endl + << e.GetBacktrace(); + SetStatus(keyConnectionStatus, "DISCONNECTED"); + this->isConnected = false; - // SPaT must be sent exactly every 100 ms. So adjust for how long it took to do the last send. - nextSpatTime += 100; - getClock()->sleep_until(nextSpatTime); - - iCounter++; - - bool messageSent = false; - - // Update PTLM file if the action number has changed. - int actionNumber = sc.getActionNumber(); - if (_actionNumber != actionNumber) { - _actionNumber = actionNumber; - - //pthread_mutex_lock(&gSettingsMutex); - //std::string ptlmFile = GetPtlmFile(_actionNumber); - //pthread_mutex_unlock(&gSettingsMutex); - - /*if (!ptlmFile.empty()) - { - pthread_mutex_lock(&sc.spat_message_mutex); - sc.updatePtlmFile(ptlmFile.c_str()); - pthread_mutex_unlock(&sc.spat_message_mutex); - }*/ } - if (sc.getIsConnected()) { - SetStatus("TSC Connection", "Connected"); - - // Add pedestrian detection - string pedZones; - { - lock_guard lock(data_lock); - pedZones = _pedMessage.get_DetectionZones(); - } - if (!pedZones.empty()) { - PLOG(logDEBUG) << "Pedestrians detected in lanes " << pedZones; - } - - SpatEncodedMessage spatEncodedMsg; - sc.getEncodedSpat(&spatEncodedMsg, pedZones); - - spatEncodedMsg.set_flags(IvpMsgFlags_RouteDSRC); - spatEncodedMsg.addDsrcMetadata(0x8002); - - //PLOG(logDEBUG) << spatEncodedMsg; + } + else { + PLOG(tmx::utils::logERROR) << "Traffic Signal Controller at " << signal_controller_ip << ":" << signal_controller_snmp_port << " failed!"; + SetStatus(keyConnectionStatus, "DISCONNECTED"); + this->isConnected = false; - BroadcastMessage(static_cast(spatEncodedMsg)); + } + } + } - if (iCounter % 20 == 0) { - iCounter = 0; - // Action Number - IvpMessage *actionMsg = ivpSigCont_createMsg( - sc.getActionNumber()); - if (actionMsg != NULL) { - ivp_broadcastMessage(_plugin, actionMsg); - ivpMsg_destroy(actionMsg); - } + void SpatPlugin::processSpat() { + if (this->scConnection ) { + PLOG(tmx::utils::logDEBUG) << "Processing SPAT ... " << std::endl; + try { + + if (spatMode == "J2735_HEX") { + auto spatEncoded_ptr = std::make_shared(); + scConnection->receiveUPERSPAT(spatEncoded_ptr); + spatEncoded_ptr->set_flags(IvpMsgFlags_RouteDSRC); + spatEncoded_ptr->addDsrcMetadata(tmx::messages::api::msgPSID::signalPhaseAndTimingMessage_PSID); + auto rMsg = dynamic_cast(spatEncoded_ptr.get()); + BroadcastMessage(*rMsg); + } + else { + if ( spatMode != "BINARY"){ + PLOG(tmx::utils::logWARNING) << spatMode << " is an unsupport SPAT MODE. Defaulting to BINARY. Supported options are BINARY and J2735_HEX"; } - - } else { - SetStatus("TSC Connection", "Disconnected"); + auto spat_ptr = std::make_shared(); + PLOG(logDEBUG) << "Starting SPaT Receiver ..."; + scConnection->receiveBinarySPAT(spat_ptr, PluginClientClockAware::getClock()->nowInMilliseconds()); + tmx::messages::SpatMessage _spatMessage(spat_ptr); + auto spatEncoded_ptr = std::make_shared(); + spatEncoded_ptr->initialize(_spatMessage,"", 0U, IvpMsgFlags_RouteDSRC); + spatEncoded_ptr->addDsrcMetadata(tmx::messages::api::msgPSID::signalPhaseAndTimingMessage_PSID); + auto rMsg = dynamic_cast(spatEncoded_ptr.get()); + BroadcastMessage(*rMsg); } } + catch (const UdpServerRuntimeError &e) { + PLOG(tmx::utils::logWARNING) << "Encountered UDP Server Runtime Error" << e.what() << " attempting to process SPAT." << std::endl + << e.GetBacktrace(); + } + catch (const tmx::TmxException &e) { + PLOG(tmx::utils::logERROR) << "Encountered Tmx Exception " << e.what() << " attempting to process SPAT." << std::endl + << e.GetBacktrace(); + skippedMessages++; + SetStatus(keySkippedMessages, skippedMessages); + } + } - } catch (exception &ex) { - stringstream ss; - ss << "SpatPlugin terminating from unhandled exception: " << ex.what(); + } + void SpatPlugin::OnConfigChanged(const char *key, const char *value) { + PluginClient::OnConfigChanged(key, value); + UpdateConfigSettings(); + } - ivp_addEventLog(_plugin, IvpLogLevel_error, ss.str().c_str()); - std::terminate(); + void SpatPlugin::OnStateChange(IvpPluginState state) { + PluginClientClockAware::OnStateChange(state); + + if (state == IvpPluginState_registered) { + UpdateConfigSettings(); + } } - return EXIT_SUCCESS; -} } /* End namespace SpatPlugin */ diff --git a/src/v2i-hub/SpatPlugin/src/SpatPlugin.h b/src/v2i-hub/SpatPlugin/src/SpatPlugin.h index 0d0b9987f..bb3bc9182 100644 --- a/src/v2i-hub/SpatPlugin/src/SpatPlugin.h +++ b/src/v2i-hub/SpatPlugin/src/SpatPlugin.h @@ -1,78 +1,101 @@ -/* - * SpatPlugin.h +/** + * Copyright (C) 2024 LEIDOS. * - * Created on: April 20, 2017 - * Author: zinkg + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. */ - -#ifndef SPATPLUGIN_H_ -#define SPATPLUGIN_H_ +#pragma once #include #include #include #include #include -#include "PluginClientClockAware.h" -#include "UdpClient.h" -#include "signalController.h" - -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include "SignalControllerConnection.h" namespace SpatPlugin { - -class SpatPlugin: public tmx::utils::PluginClientClockAware { - -public: - - SpatPlugin(std::string name); - virtual ~SpatPlugin(); - virtual int Main(); - - void HandlePedestrianDetection(tmx::messages::PedestrianMessage &pedMsg, tmx::routeable_message &routeableMsg); - -protected: - - void UpdateConfigSettings(); - - // Virtual method overrides. - void OnConfigChanged(const char *key, const char *value); - void OnStateChange(IvpPluginState state); - - -private: - - - unsigned char derEncoded[4000]; - unsigned int derEncodedBytes = 0; - - SignalController sc; - int _actionNumber = -1; - - std::mutex data_lock; - std::string localIp; - std::string localUdpPort; - std::string tscIp; - std::string tscRemoteSnmpPort; - std::string signalGroupMappingJson; - - std::string intersectionName; - - int intersectionId; - - bool isConfigurationLoaded = false; - bool isConfigured = false; - - bool encodeSpat(); - bool createUPERframe_DERencoded_msg(); - - tmx::messages::PedestrianMessage _pedMessage; -}; + /** + * @brief The SPaT Plugin is responsible for receiving information from the Traffic Signal Controller (TSC or SC) necessary + * for broadcasting Signal Phase and Timing (SPaT) messages. This includes querying any SNMP objects to determine + * TSC state and listen for any broadcast SPaT information from the TSC. + */ + class SpatPlugin: public tmx::utils::PluginClientClockAware { + + public: + /** + * @brief Plugin Constructor. + * @param name Plugin Name. + */ + explicit SpatPlugin(const std::string &name); + /** + * @brief Plugin Destructor + */ + virtual ~SpatPlugin(); + + + protected: + /** + * @brief Method to update plugin after configuration settings have changed. + */ + void UpdateConfigSettings(); + + // Virtual method overrides. + void OnConfigChanged(const char *key, const char *value) override; + void OnStateChange(IvpPluginState state) override; + + private: + /** + * @brief Mutex for thread safety for configuration parameters. + */ + std::mutex data_lock; + /** + * @brief Thread timer used to periodically consume broadcast SPaT + * data from the TSC . + */ + std::unique_ptr spatReceiverThread; + /** + * @brief TSC Connection. + */ + std::unique_ptr scConnection; + /** + * @brief String describing the expected format of received SPaT data. + */ + std::string spatMode = ""; + /** + * @brief Key for state object describing TSC Connection Status. + */ + const char* keyConnectionStatus = "Connection Status"; + /** + * @brief Key for counting the number of received packets from TSC that + * have been skipped due to errors. + */ + const char* keySkippedMessages = "Skipped Messages"; + /** + * @brief Count of received packets from the TSC that have been skipped due to + * errors. + */ + uint skippedMessages = 0; + /** + * @brief Bool flag for TSC connection status. + */ + bool isConnected = false; + /** + * @brief Method to receive and process TSC broadcast SPaT data. + */ + void processSpat(); + }; } /* namespace SpatPlugin */ -#endif /* SPATPLUGIN_H_ */ diff --git a/src/v2i-hub/SpatPlugin/src/signalController.cpp b/src/v2i-hub/SpatPlugin/src/signalController.cpp deleted file mode 100644 index c75ef757a..000000000 --- a/src/v2i-hub/SpatPlugin/src/signalController.cpp +++ /dev/null @@ -1,289 +0,0 @@ -/* - ============================================================================ - Name : snmpClient.cpp - Author : William Gibbs - Version : - Copyright : Battelle - Description : Query Signal Controller and populate the SPaT message - ============================================================================ - */ -#include -#include -#include -#include - -#include -#include -#ifndef __CYGWIN__ -#include -#endif -#include -#include -#include - -#include - -#include "signalController.h" - -#include "NTCIP1202.h" -#include "PedestrianDetectionForSPAT.h" - -using namespace tmx::messages; -using namespace tmx::utils; -using namespace std; - -SignalController::~SignalController() { - SNMPCloseSession(); -} - -void SignalController::Start(std::string signalGroupMappingJson) -{ - _signalGroupMappingJson = signalGroupMappingJson; - - // Create mutex for the Spat message - pthread_mutex_init(&spat_message_mutex, nullptr); - // launch update thread - sigcon_thread_id = boost::thread(&SignalController::start_signalController, this); - // test code - normalstate = 0x01; - crossstate = 0x04; -} - -// get sockaddr, IPv4 or IPv6: -void *SignalController::get_in_addr(struct sockaddr *sa) -{ - if (sa->sa_family == AF_INET) { - return &(((struct sockaddr_in*)sa)->sin_addr); - } - - return &(((struct sockaddr_in6*)sa)->sin6_addr); -} - -void SignalController::setConfigs(std::string localIp, std::string localUdpPort, std::string tscIp, std::string tscRemoteSnmpPort, std::string ptlmFile, std::string intersectionName, int intersectionId) -{ - _localIp = strdup(localIp.c_str()); - _localUdpPort = strdup(localUdpPort.c_str()); - _intersectionId = intersectionId; - _intersectionName = strdup(intersectionName.c_str()); - _tscIp = tscIp; - _tscRemoteSnmpPort = stoi(tscRemoteSnmpPort); -} - -void SignalController::start_signalController() -{ -#ifndef __CYGWIN__ - prctl(PR_SET_NAME, "SpatGenSC", 0, 0, 0); -#endif - - pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,nullptr); - pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,nullptr); - - int maxDataSize = 1000; - - int sockfd, numbytes; - char buf[maxDataSize]; - struct addrinfo hints, *servinfo; - int rv; - struct timeval tv; - int on = 1; - int errnoVal; - - //Enable SPAT - // 0 = disable - // 2 = enable SPAT - // 6 = enable SPAT wit pedestrian data - PLOG(logINFO) << "Enable SPAT Sent"; - SNMPSet("1.3.6.1.4.1.1206.3.5.2.9.44.1.0", 2); - SNMPCloseSession(); - - // Create UDP Socket - memset(&hints, 0, sizeof hints); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_DGRAM; - hints.ai_protocol = IPPROTO_UDP; - hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; - - EthernetIsConnected = 0; - IsReceiving = 0; - - while (1) { - PLOG(logDEBUG) << "Top of While Loop"; - if ((rv = getaddrinfo(_localIp, _localUdpPort, &hints, &servinfo)) != 0) { - PLOG(logERROR) << "Getaddrinfo Failed " << _localIp << " " << _localUdpPort << ". Exiting thread!!!"; - return; - } - PLOG(logDEBUG) << "Getting Socket"; - if ((sockfd = socket(servinfo->ai_family, servinfo->ai_socktype, servinfo->ai_protocol)) == -1) { - PLOG(logERROR) << "Get Socket Failed " << _localIp << " " << _localUdpPort << ". Exiting thread!!!"; - return; - } - - rv = setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)); - - // Set the socket to time out on reads if no data comes in during the timeout value then the socket will close and - // then try to re-open during the normal execution - // Wait up to 10 seconds. - tv.tv_sec = 10; - tv.tv_usec = 0; - setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO,(struct timeval *)&tv,sizeof(struct timeval)); - - if (bind(sockfd,servinfo->ai_addr,servinfo->ai_addrlen)==-1) { - PLOG(logERROR) << "Could not bind to Socket " << _localIp << " " << _localUdpPort << ". Exiting thread!!!"; - return; - } - - if (servinfo == nullptr) { - PLOG(logERROR) << "Could not connect"; - EthernetIsConnected = 0; - } - else { - PLOG(logDEBUG) << "Connected"; - EthernetIsConnected = 1; - } - - if (EthernetIsConnected) { - //printf("Signal Controller UDP Client Connected to %s:%s\n",_localIp, _localUdpPort); - freeaddrinfo(servinfo); // all done with this structure -// client_socket = sockfd; - // Receive Packets and process until disconnected - while(EthernetIsConnected) { - //printf("Signal Controller ethernet connected, reading data\n"); - numbytes = recv(sockfd, buf, maxDataSize-1, 0); - errnoVal = errno; - //printf("Signal Controller read %d bytes\n", numbytes); - //TODO - Check the start byte for 0xcd, then check for len of 245. - //TODO - store in temp space if less than 245, send only from 0xcd (byte 0) to byte 245 to new processing function - if ((numbytes == -1) || (numbytes == 0)){ - if (numbytes == 0 || errnoVal == EAGAIN || errnoVal == EWOULDBLOCK) { - PLOG(logINFO) << "Signal Controller Timed out"; - } else { - PLOG(logINFO) << "Signal Controller Client closed"; - EthernetIsConnected = 0; - close(sockfd); - } - IsReceiving = 0; - } - else { - - IsReceiving = 1; - pthread_mutex_lock(&spat_message_mutex); - auto ntcip1202 = std::make_shared(clock); - ntcip1202->setSignalGroupMappingList(_signalGroupMappingJson); - //printf("Signal Controller calling ntcip1202 copyBytesIntoNtcip1202"); - ntcip1202->copyBytesIntoNtcip1202(buf, numbytes); - - //printf("Signal Controller calling ntcip1202 ToJ2735r41SPAT"); - SPAT *_spat = (SPAT *) calloc(1, sizeof(SPAT)); - ntcip1202->ToJ2735r41SPAT(_spat, _intersectionName, _intersectionId); - - //printf("Signal Controller calling _spatMessage set_j2735_data\n"); - //_spatMessage.set_j2735_data(_spat); - if (_spatMessage != nullptr) - { - _spatMessage = nullptr; - } - _spatMessage = std::make_shared(_spat); - - pthread_mutex_unlock(&spat_message_mutex); - PLOG(logDEBUG) << *_spatMessage; - } - } - } - sleep(3); - } -} - -void SignalController::getEncodedSpat(SpatEncodedMessage* spatEncodedMsg, std::string currentPedLanes) -{ - pthread_mutex_lock(&spat_message_mutex); - - if (_spatMessage != nullptr) { - PedestrianDetectionForSPAT pedDetect; - pedDetect.updateEncodedSpat(*spatEncodedMsg, _spatMessage, currentPedLanes); - } - - pthread_mutex_unlock(&spat_message_mutex); - -} - -int SignalController::getIsConnected() -{ - return EthernetIsConnected && IsReceiving; -} - -int SignalController::getActionNumber() -{ - return 1;//sd.actionNumber; -} - -void SignalController::SNMPOpenSession() -{ - //check for valid TSC info - if (_tscIp == "" || _tscRemoteSnmpPort == 0) - return; - //open snmp session - snmp_sess_init(&_session_info); - string peername = _tscIp; - peername.append(":"); - peername.append(to_string(_tscRemoteSnmpPort)); - _session_info.peername = (char*)peername.c_str(); - _session_info.version = SNMP_VERSION_1; - _session_info.community = (u_char*)"public"; - _session_info.community_len = strlen("public"); - _session = snmp_open(&_session_info); - if (_session) - _snmpSessionOpen = true; -} - -void SignalController::SNMPCloseSession() -{ - //close session - if (_snmpSessionOpen) - snmp_close(_session); - _snmpSessionOpen = false; -} - -bool SignalController::SNMPSet(string targetOid, int32_t value) -{ - return SNMPSet(targetOid, ASN_INTEGER, (const void *)&value, sizeof(value)); -} - -bool SignalController::SNMPSet(string targetOid, u_char type, const void *value, size_t len) -{ - struct snmp_pdu *pdu; - struct snmp_pdu *response; - oid anOID[MAX_OID_LEN]; - size_t anOID_len = MAX_OID_LEN; - int status; - bool rc = true; - - //check is snmp session open - if (!_snmpSessionOpen) - { - SNMPOpenSession(); - if (!_snmpSessionOpen) - return false; - _snmpDestinationChanged = false; - } - //check destination change - if (_snmpDestinationChanged) - { - SNMPCloseSession(); - SNMPOpenSession(); - if (!_snmpSessionOpen) - return false; - _snmpDestinationChanged = false; - } - - pdu = snmp_pdu_create(SNMP_MSG_SET); - read_objid(targetOid.c_str(), anOID, &anOID_len); - snmp_pdu_add_variable(pdu, anOID, anOID_len, type, value, len); - status = snmp_synch_response(_session, pdu, &response); - if (status != STAT_SUCCESS || response->errstat != SNMP_ERR_NOERROR) - rc = false; - if (response) - snmp_free_pdu(response); - - return rc; -} - diff --git a/src/v2i-hub/SpatPlugin/src/signalController.h b/src/v2i-hub/SpatPlugin/src/signalController.h deleted file mode 100644 index a31fdce69..000000000 --- a/src/v2i-hub/SpatPlugin/src/signalController.h +++ /dev/null @@ -1,77 +0,0 @@ -/* - * snmpClient.h - * - * Created on: Aug 22, 2014 - * Author: gibbsw - */ - -#ifndef SIGNALCONTROLLER_H_ -#define SIGNALCONTROLLER_H_ - -#include -#include - -#include - -#include -#include - -#include "carma-clock/carma_clock.h" - -class SignalController -{ - public: - inline explicit SignalController(std::shared_ptr clock) : - clock(clock) {}; - ~SignalController(); - - void Start(std::string signalGroupMappingJson); - void spat_load(); - void start_signalController(); - int getActionNumber(); - void setConfigs(std::string ip, std::string udpPort, std::string snmpIP, std::string snmpPort, std::string ptlmFile, std::string intersectionName, int intersectionId); - void updatePtlmFile(const char* ptlmFile); - int getIsConnected(); - - //int getDerEncodedSpat(unsigned char* derEncodedBuffer); - - void getEncodedSpat(tmx::messages::SpatEncodedMessage* spatEncodedMsg, std::string currentPedLanes = ""); - - pthread_mutex_t spat_message_mutex; - boost::thread sigcon_thread_id; - - void SNMPOpenSession(); - void SNMPCloseSession(); - bool SNMPSet(std::string targetOid, int32_t value); - bool SNMPSet(std::string targetOid, u_char type, const void *value, size_t len); - - private: - void *get_in_addr(struct sockaddr *); - - std::shared_ptr clock; - - // Local IP address and UDP port for reception of SPAT dSPaTDataata from the TSC. - char* _localIp; - char* _localUdpPort; - char* _intersectionName; - int _intersectionId; - std::string _tscIp; - uint32_t _tscRemoteSnmpPort; - - std::string _signalGroupMappingJson; - std::shared_ptr _spatMessage; - int counter; - unsigned long normalstate; - unsigned long crossstate; - int EthernetIsConnected; - int IsReceiving; - - //snmp - struct snmp_session _session_info; - struct snmp_session *_session{NULL}; - bool _snmpSessionOpen{false}; - bool _snmpDestinationChanged{false}; - -}; - -#endif /* SIGNALCONTROLLER_H_ */ diff --git a/src/v2i-hub/SpatPlugin/src/utils/PerformanceTimer.h b/src/v2i-hub/SpatPlugin/src/utils/PerformanceTimer.h deleted file mode 100644 index b7952aa69..000000000 --- a/src/v2i-hub/SpatPlugin/src/utils/PerformanceTimer.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * PerformanceTimer.h - * - * Created on: Dec 5, 2014 - * Author: ivp - */ - -#ifndef PERFORMANCETIMER_H_ -#define PERFORMANCETIMER_H_ - -#include - -class PerformanceTimer -{ -public: - // Returns the current high-resolution system time in UTC. - static boost::posix_time::ptime Now() { return boost::posix_time::microsec_clock::universal_time(); } - - // Construct and start the timer. - PerformanceTimer() : _startTime( Now() ) {}; - - // Reset the timer to the current time. - void Reset() { _startTime = Now(); } - - // Returns the elapsed time. - boost::posix_time::time_duration const Elapsed() - { - return Now() - _startTime; - } - -private: - // The time of class construction or when last Reset(). - boost::posix_time::ptime _startTime; -}; - -#endif /* PERFORMANCETIMER_H_ */ diff --git a/src/v2i-hub/SpatPlugin/test/Main.cpp b/src/v2i-hub/SpatPlugin/test/Main.cpp index 75163d417..5c1d740bb 100644 --- a/src/v2i-hub/SpatPlugin/test/Main.cpp +++ b/src/v2i-hub/SpatPlugin/test/Main.cpp @@ -1,8 +1,17 @@ -/* - * Main.cpp +/** + * Copyright (C) 2024 LEIDOS. * - * Created on: May 10, 2016 - * Author: ivp + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. */ #include diff --git a/src/v2i-hub/SpatPlugin/test/test_NTCIP1202.cpp b/src/v2i-hub/SpatPlugin/test/TestNTCIP1202.cpp similarity index 56% rename from src/v2i-hub/SpatPlugin/test/test_NTCIP1202.cpp rename to src/v2i-hub/SpatPlugin/test/TestNTCIP1202.cpp index cc9c671a8..ff30ba42a 100644 --- a/src/v2i-hub/SpatPlugin/test/test_NTCIP1202.cpp +++ b/src/v2i-hub/SpatPlugin/test/TestNTCIP1202.cpp @@ -1,25 +1,30 @@ - +/** + * Copyright (C) 2024 LEIDOS. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ #include #include #include +#include using namespace fwha_stol::lib::time; TEST(NTCIP1202Test, copyBytesIntoNtcip1202) { - DescriptiveName_t *update_to_intersection_name = (DescriptiveName_t *)calloc(1, sizeof(DescriptiveName_t)); - char *my_string = (char*) "test intersection name"; - stringstream ss; - update_to_intersection_name->buf = reinterpret_cast(my_string); - ss << update_to_intersection_name->buf; - ASSERT_EQ(ss.str(), "test intersection name"); - - IntersectionReferenceID_t *update_to_intersection_id = (IntersectionReferenceID_t *)calloc(1, sizeof(IntersectionReferenceID_t)); - update_to_intersection_id->id = 9012; + uint64_t tsMsec = 1677775434400; - auto clock = std::make_shared(); - clock->wait_for_initialization(); - auto ntcip1202_p = std::make_shared(clock); + auto ntcip1202_p = std::make_shared(); unsigned int raw_data[] = {4294967245, 16, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 118, 0, 118, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 4294967208, 0, 4294967208, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 118, 0, 118, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 4294967208, 0, 4294967208, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4294967295, 4294967261, 0, 0, 0, 34, 4294967295, 4294967295, 0, 0, 0, 0, 4294967295, 4294967295, 0, 0, 0, 0, 0, 0, 0, 0, 4294967168, 0, 8, 103, 1, 10, 4294967237, 0, 0}; int numBytes = sizeof(raw_data)/sizeof(unsigned int); char buf[ numBytes] = {}; @@ -32,52 +37,38 @@ TEST(NTCIP1202Test, copyBytesIntoNtcip1202) ntcip1202_p->copyBytesIntoNtcip1202(buf, numBytes); SPAT *spat_ptr = (SPAT *)calloc(1, sizeof(SPAT)); - ntcip1202_p->ToJ2735r41SPAT(spat_ptr, reinterpret_cast(update_to_intersection_name->buf), update_to_intersection_id->id); + ntcip1202_p->ToJ2735SPAT(spat_ptr,tsMsec, "test intersection name", 9012); + ASSERT_EQ(3, spat_ptr->intersections.list.array[0]->states.list.array[0]->state_time_speed.list.array[0]->eventState); + free(spat_ptr); } -TEST(NTCIP1202Test, ToJ2735r41SPAT) +TEST(NTCIP1202Test, ToJ2735SPAT) { - auto clock = std::make_shared(); - clock->wait_for_initialization(); - auto ntcip1202_p = std::make_shared(clock); - SPAT *spat_ptr = (SPAT *)calloc(1, sizeof(SPAT)); + uint64_t tsMsec = 1677775434400; - char *my_string = (char*)"test intersection name"; - stringstream ss; - - DescriptiveName_t *update_to_intersection_name = (DescriptiveName_t *)calloc(1, sizeof(DescriptiveName_t)); - update_to_intersection_name->buf = reinterpret_cast(my_string); - ss << update_to_intersection_name->buf; - ASSERT_EQ(ss.str(), "test intersection name"); - - IntersectionReferenceID_t *update_to_intersection_id = (IntersectionReferenceID_t *)calloc(1, sizeof(IntersectionReferenceID_t)); - update_to_intersection_id->id = 9012; - - - bool transform_status = ntcip1202_p->ToJ2735r41SPAT(spat_ptr, reinterpret_cast(update_to_intersection_name->buf), update_to_intersection_id->id); + auto ntcip1202_p = std::make_shared(); + SPAT *spat_ptr = (SPAT *)calloc(1, sizeof(SPAT)); + ntcip1202_p->ToJ2735SPAT(spat_ptr, tsMsec, "test intersection name", 9012); auto _spatMessage = std::make_shared(spat_ptr); auto spat = _spatMessage->get_j2735_data(); - ASSERT_EQ(transform_status, true); } TEST(NTCIP1202Test, TestAdjustedTime) { - auto clock = std::make_shared(true); - // 1677775434 = 2023-02-03 16:43:54 - timeStampMilliseconds tsMsec = ((uint64_t)1677775434 * 1000) + 400; + // 1677775434400 = 2023-02-03 16:43:54.400 + uint64_t tsMsec = 1677775434400; auto baseTenthsOfSeconds = 43 * 600 + 54 * 10 + 4; - clock->update(tsMsec); - auto ntcip1202_p = std::make_shared(clock); - auto result = ntcip1202_p->getAdjustedTime(0); + auto ntcip1202_p = std::make_shared(); + auto result = ntcip1202_p->getAdjustedTime(0, tsMsec); EXPECT_EQ(baseTenthsOfSeconds, result); - result = ntcip1202_p->getAdjustedTime(46); + result = ntcip1202_p->getAdjustedTime(46, tsMsec); EXPECT_EQ(baseTenthsOfSeconds + 46, result); // cross minute boundary - result = ntcip1202_p->getAdjustedTime(200); + result = ntcip1202_p->getAdjustedTime(200, tsMsec); EXPECT_EQ(baseTenthsOfSeconds + 200, result); // cross hour boundary - result = ntcip1202_p->getAdjustedTime(10200); + result = ntcip1202_p->getAdjustedTime(10200, tsMsec); EXPECT_EQ((baseTenthsOfSeconds + 10200) % 36000, result); } \ No newline at end of file diff --git a/src/v2i-hub/SpatPlugin/test/TestSignalControllerConnection.cpp b/src/v2i-hub/SpatPlugin/test/TestSignalControllerConnection.cpp new file mode 100644 index 000000000..a95b53374 --- /dev/null +++ b/src/v2i-hub/SpatPlugin/test/TestSignalControllerConnection.cpp @@ -0,0 +1,614 @@ +/** + * Copyright (C) 2024 LEIDOS. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +#include +#include +#include +#include +#include +#include + +using testing::_; +using testing::Action; +using testing::ByRef; +using testing::DoDefault; +using testing::Return; +using testing::SetArgPointee; +using testing::SetArgReferee; +using testing::SetArrayArgument; +using testing::Throw; +namespace SpatPlugin { + class TestSignalControllerConnection : public ::testing::Test + { + public: + TestSignalControllerConnection() { + + } + void SetUp() { + std::string signalGroupMapping = R"( + {"SignalGroups": + [ + {"SignalGroupId":1,"Phase":1,"Type":"vehicle"}, + {"SignalGroupId":2,"Phase":2,"Type":"vehicle"}, + {"SignalGroupId":3,"Phase":3,"Type":"vehicle"}, + {"SignalGroupId":4,"Phase":4,"Type":"vehicle"}, + {"SignalGroupId":5,"Phase":5,"Type":"vehicle"}, + {"SignalGroupId":6,"Phase":6,"Type":"vehicle"}, + {"SignalGroupId":7,"Phase":7,"Type":"vehicle"}, + {"SignalGroupId":8,"Phase":8,"Type":"vehicle"}, + {"SignalGroupId":9,"Phase":2,"Type":"pedestrian"}, + {"SignalGroupId":10,"Phase":4,"Type":"pedestrian"}, + {"SignalGroupId":11,"Phase":6,"Type":"pedestrian"}, + {"SignalGroupId":12,"Phase":8,"Type":"pedestrian"} + ] + } + )"; + signalControllerConnection = std::make_unique("127.0.0.1", 5000, signalGroupMapping, "", 5020,"administrator", "someIntersection", 9001); + mockSnmpClient = std::make_shared("127.0.0.1", 6045, "administrator", "", "", ""); + mockUdpServer = std::make_shared(); + signalControllerConnection->scSNMPClient = mockSnmpClient; + signalControllerConnection->spatPacketReceiver = mockUdpServer; + } + + std::vector read_binary_file(std::string name) + { + std::ifstream file(name.c_str(), std::ios::binary); + std::vector buf; + + if (!file.good()) + { + throw runtime_error("Could not open file " + name); + } + + file.unsetf(std::ios::skipws); + file.seekg(0, std::ios::end); + const size_t size = file.tellg(); + + file.seekg(0, std::ios::beg); + buf.resize(size); + file.read(buf.data(), size); + file.close(); + + return buf; + } + + std::shared_ptr mockSnmpClient; + std::shared_ptr mockUdpServer; + + std::unique_ptr signalControllerConnection; + }; + + TEST_F(TestSignalControllerConnection, initialize) { + tmx::utils::snmp_response_obj enable_spat; + enable_spat.type = tmx::utils::snmp_response_obj::response_type::INTEGER; + enable_spat.val_int = 2; + EXPECT_CALL(*mockSnmpClient, process_snmp_request(NTCIP1202V2::ENABLE_SPAT_OID, tmx::utils::request_type::SET, enable_spat)).WillOnce(testing::DoAll(SetArgReferee<2>(enable_spat), Return(true))); + EXPECT_TRUE(signalControllerConnection->initializeSignalControllerConnection(true)); + } + + TEST_F(TestSignalControllerConnection, receiveBinarySPAT) { + auto spat_binary_buf = read_binary_file("../../SpatPlugin/test/test_spat_binaries/spat_1721238398773.bin"); + EXPECT_CALL(*mockUdpServer, TimedReceive(_, 1000, 1000)).WillOnce(testing::DoAll(SetArrayArgument<0>(spat_binary_buf.begin(), spat_binary_buf.end()), Return(spat_binary_buf.size()))); + auto spat = std::make_shared(); + signalControllerConnection->receiveBinarySPAT(spat, 1721238398773); + /** + * + + + someIntersection + + 9001 + + 1 + + 0000000000000000 + + 286186 + 38773 + + + 1 + + + + + 28027 + 21522 + + + + + + 2 + + + + + 27987 + 21522 + + + + + + 9 + + + + + 27987 + + + + + + 3 + + + + + 28027 + 21522 + + + + + + 4 + + + + + 28027 + 21522 + + + + + + 10 + + + + + 28027 + + + + + + 5 + + + + + 28027 + 21522 + + + + + + 6 + + + + + 27987 + 21522 + + + + + + 11 + + + + + 27987 + + + + + + 0 + + + + + + 7 + + + + + 28027 + 21522 + + + + + + 8 + + + + + 28027 + 21522 + + + + + + 12 + + + + + 28027 + + + + + + 0 + + + + + + + + + */ + EXPECT_EQ(9001, spat->intersections.list.array[0]->id.id); + EXPECT_EQ(286186, *spat->intersections.list.array[0]->moy); + EXPECT_EQ(38773, *spat->intersections.list.array[0]->timeStamp); + // Signal Group 1 + EXPECT_EQ(1, spat->intersections.list.array[0]->states.list.array[0]->signalGroup); + EXPECT_EQ(3, spat->intersections.list.array[0]->states.list.array[0]->state_time_speed.list.array[0]->eventState); + EXPECT_EQ(28027, spat->intersections.list.array[0]->states.list.array[0]->state_time_speed.list.array[0]->timing->minEndTime); + EXPECT_EQ(21522, *spat->intersections.list.array[0]->states.list.array[0]->state_time_speed.list.array[0]->timing->maxEndTime); + // Signal Group 2 + EXPECT_EQ(2, spat->intersections.list.array[0]->states.list.array[1]->signalGroup); + EXPECT_EQ(6, spat->intersections.list.array[0]->states.list.array[1]->state_time_speed.list.array[0]->eventState); + EXPECT_EQ(27987, spat->intersections.list.array[0]->states.list.array[1]->state_time_speed.list.array[0]->timing->minEndTime); + EXPECT_EQ(21522, *spat->intersections.list.array[0]->states.list.array[1]->state_time_speed.list.array[0]->timing->maxEndTime); + // Signal Group 9 + EXPECT_EQ(9, spat->intersections.list.array[0]->states.list.array[2]->signalGroup); + EXPECT_EQ(3, spat->intersections.list.array[0]->states.list.array[2]->state_time_speed.list.array[0]->eventState); + EXPECT_EQ(27987, spat->intersections.list.array[0]->states.list.array[2]->state_time_speed.list.array[0]->timing->minEndTime); + // Signal Group 3 + EXPECT_EQ(3, spat->intersections.list.array[0]->states.list.array[3]->signalGroup); + EXPECT_EQ(3, spat->intersections.list.array[0]->states.list.array[3]->state_time_speed.list.array[0]->eventState); + EXPECT_EQ(28027, spat->intersections.list.array[0]->states.list.array[3]->state_time_speed.list.array[0]->timing->minEndTime); + EXPECT_EQ(21522, *spat->intersections.list.array[0]->states.list.array[3]->state_time_speed.list.array[0]->timing->maxEndTime); + // Signal Group 4 + EXPECT_EQ(4, spat->intersections.list.array[0]->states.list.array[4]->signalGroup); + EXPECT_EQ(3, spat->intersections.list.array[0]->states.list.array[4]->state_time_speed.list.array[0]->eventState); + EXPECT_EQ(28027, spat->intersections.list.array[0]->states.list.array[4]->state_time_speed.list.array[0]->timing->minEndTime); + EXPECT_EQ(21522, *spat->intersections.list.array[0]->states.list.array[4]->state_time_speed.list.array[0]->timing->maxEndTime); + // // Signal Group 10 + EXPECT_EQ(10, spat->intersections.list.array[0]->states.list.array[5]->signalGroup); + EXPECT_EQ(3, spat->intersections.list.array[0]->states.list.array[5]->state_time_speed.list.array[0]->eventState); + EXPECT_EQ(28027, spat->intersections.list.array[0]->states.list.array[5]->state_time_speed.list.array[0]->timing->minEndTime); + // // Signal Group 5 + EXPECT_EQ(5, spat->intersections.list.array[0]->states.list.array[6]->signalGroup); + EXPECT_EQ(3, spat->intersections.list.array[0]->states.list.array[6]->state_time_speed.list.array[0]->eventState); + EXPECT_EQ(28027, spat->intersections.list.array[0]->states.list.array[6]->state_time_speed.list.array[0]->timing->minEndTime); + EXPECT_EQ(21522, *spat->intersections.list.array[0]->states.list.array[6]->state_time_speed.list.array[0]->timing->maxEndTime); + // Signal Group 6 + EXPECT_EQ(6, spat->intersections.list.array[0]->states.list.array[7]->signalGroup); + EXPECT_EQ(6, spat->intersections.list.array[0]->states.list.array[7]->state_time_speed.list.array[0]->eventState); + EXPECT_EQ(27987, spat->intersections.list.array[0]->states.list.array[7]->state_time_speed.list.array[0]->timing->minEndTime); + EXPECT_EQ(21522, *spat->intersections.list.array[0]->states.list.array[7]->state_time_speed.list.array[0]->timing->maxEndTime); + // Signal Group 11 + EXPECT_EQ(11, spat->intersections.list.array[0]->states.list.array[8]->signalGroup); + EXPECT_EQ(3, spat->intersections.list.array[0]->states.list.array[8]->state_time_speed.list.array[0]->eventState); + EXPECT_EQ(27987, spat->intersections.list.array[0]->states.list.array[8]->state_time_speed.list.array[0]->timing->minEndTime); + // Signal Group 7 + EXPECT_EQ(7, spat->intersections.list.array[0]->states.list.array[9]->signalGroup); + EXPECT_EQ(3, spat->intersections.list.array[0]->states.list.array[9]->state_time_speed.list.array[0]->eventState); + EXPECT_EQ(28027, spat->intersections.list.array[0]->states.list.array[9]->state_time_speed.list.array[0]->timing->minEndTime); + EXPECT_EQ(21522, *spat->intersections.list.array[0]->states.list.array[9]->state_time_speed.list.array[0]->timing->maxEndTime); + // Signal Group 8 + EXPECT_EQ(8, spat->intersections.list.array[0]->states.list.array[10]->signalGroup); + EXPECT_EQ(3, spat->intersections.list.array[0]->states.list.array[10]->state_time_speed.list.array[0]->eventState); + EXPECT_EQ(28027, spat->intersections.list.array[0]->states.list.array[10]->state_time_speed.list.array[0]->timing->minEndTime); + EXPECT_EQ(21522, *spat->intersections.list.array[0]->states.list.array[10]->state_time_speed.list.array[0]->timing->maxEndTime); + // Signal Group 12 + EXPECT_EQ(12, spat->intersections.list.array[0]->states.list.array[11]->signalGroup); + EXPECT_EQ(3, spat->intersections.list.array[0]->states.list.array[11]->state_time_speed.list.array[0]->eventState); + EXPECT_EQ(28027, spat->intersections.list.array[0]->states.list.array[11]->state_time_speed.list.array[0]->timing->minEndTime); + } + TEST_F(TestSignalControllerConnection, receiveBinarySPATException) { + EXPECT_CALL(*mockUdpServer, TimedReceive(_, 1000, 1000)).WillOnce(testing::DoAll( Return(0))); + auto spat = std::make_shared(); + EXPECT_THROW(signalControllerConnection->receiveBinarySPAT(spat, 1721238398773), tmx::utils::UdpServerRuntimeError); + } + + TEST_F(TestSignalControllerConnection, receiveUPERSPAT) { + std::string uper_hex = R"( + Version=0.7 + Type=SPAT + PSID=0x8002 + Priority=7 + TxMode=CONT + TxChannel=172 + TxInterval=0 + DeliveryStart= + DeliveryStop= + Signature=True + Encryption=False + Payload=00136b4457f20180000000208457f2c7c20b0010434162bc650001022a0b0be328000c10d058af194000808682c578ca00050434162bc650003022a0b0be328001c10d058af194001008682c578ca000904341617c650005021a0b15e328002c10d0585f194001808682c578ca00 + )"; + EXPECT_CALL(*mockUdpServer, stringTimedReceive(1000)).WillOnce(testing::DoAll(Return(uper_hex))); + + /** + * + 284658 + + + + 0 + + 0 + + 0000001000001000 + + 284658 + 51138 + + + 1 + + + + + 11351 + 36000 + + + + + + 2 + + + + + 11311 + 36000 + + + + + + 3 + + + + + 11351 + 36000 + + + + + + 4 + + + + + 11351 + 36000 + + + + + + 5 + + + + + 11351 + 36000 + + + + + + 6 + + + + + 11311 + 36000 + + + + + + 7 + + + + + 11351 + 36000 + + + + + + 8 + + + + + 11351 + 36000 + + + + + + 9 + + + + + 11311 + 36000 + + + + + + 10 + + + + + 11351 + 36000 + + + + + + 11 + + + + + 11311 + 36000 + + + + + + 12 + + + + + 11351 + 36000 + + + + + + + + + */ + auto spatEncoded_ptr = std::make_shared(); + signalControllerConnection->receiveUPERSPAT(spatEncoded_ptr); + auto spat = spatEncoded_ptr->decode_j2735_message().get_j2735_data(); + EXPECT_EQ(284658L, *spat->timeStamp); + EXPECT_EQ(0, spat->intersections.list.array[0]->id.id); + EXPECT_EQ(284658L, *spat->intersections.list.array[0]->moy); + EXPECT_EQ(51138, *spat->intersections.list.array[0]->timeStamp); + // Signal Group 1 + EXPECT_EQ(1, spat->intersections.list.array[0]->states.list.array[0]->signalGroup); + EXPECT_EQ(3, spat->intersections.list.array[0]->states.list.array[0]->state_time_speed.list.array[0]->eventState); + EXPECT_EQ(11351, spat->intersections.list.array[0]->states.list.array[0]->state_time_speed.list.array[0]->timing->minEndTime); + EXPECT_EQ(36000, *spat->intersections.list.array[0]->states.list.array[0]->state_time_speed.list.array[0]->timing->maxEndTime); + // Signal Group 2 + EXPECT_EQ(2, spat->intersections.list.array[0]->states.list.array[1]->signalGroup); + EXPECT_EQ(5, spat->intersections.list.array[0]->states.list.array[1]->state_time_speed.list.array[0]->eventState); + EXPECT_EQ(11311, spat->intersections.list.array[0]->states.list.array[1]->state_time_speed.list.array[0]->timing->minEndTime); + EXPECT_EQ(36000, *spat->intersections.list.array[0]->states.list.array[1]->state_time_speed.list.array[0]->timing->maxEndTime); + // Signal Group 3 + EXPECT_EQ(3, spat->intersections.list.array[0]->states.list.array[2]->signalGroup); + EXPECT_EQ(3, spat->intersections.list.array[0]->states.list.array[2]->state_time_speed.list.array[0]->eventState); + EXPECT_EQ(11351, spat->intersections.list.array[0]->states.list.array[2]->state_time_speed.list.array[0]->timing->minEndTime); + EXPECT_EQ(36000, *spat->intersections.list.array[0]->states.list.array[2]->state_time_speed.list.array[0]->timing->maxEndTime); + // Signal Group 4 + EXPECT_EQ(4, spat->intersections.list.array[0]->states.list.array[3]->signalGroup); + EXPECT_EQ(3, spat->intersections.list.array[0]->states.list.array[3]->state_time_speed.list.array[0]->eventState); + EXPECT_EQ(11351, spat->intersections.list.array[0]->states.list.array[3]->state_time_speed.list.array[0]->timing->minEndTime); + EXPECT_EQ(36000, *spat->intersections.list.array[0]->states.list.array[3]->state_time_speed.list.array[0]->timing->maxEndTime); + // Signal Group 5 + EXPECT_EQ(5, spat->intersections.list.array[0]->states.list.array[4]->signalGroup); + EXPECT_EQ(3, spat->intersections.list.array[0]->states.list.array[4]->state_time_speed.list.array[0]->eventState); + EXPECT_EQ(11351, spat->intersections.list.array[0]->states.list.array[4]->state_time_speed.list.array[0]->timing->minEndTime); + EXPECT_EQ(36000, *spat->intersections.list.array[0]->states.list.array[4]->state_time_speed.list.array[0]->timing->maxEndTime); + // Signal Group 6 + EXPECT_EQ(6, spat->intersections.list.array[0]->states.list.array[5]->signalGroup); + EXPECT_EQ(5, spat->intersections.list.array[0]->states.list.array[5]->state_time_speed.list.array[0]->eventState); + EXPECT_EQ(11311, spat->intersections.list.array[0]->states.list.array[5]->state_time_speed.list.array[0]->timing->minEndTime); + EXPECT_EQ(36000, *spat->intersections.list.array[0]->states.list.array[5]->state_time_speed.list.array[0]->timing->maxEndTime); + // Signal Group 7 + EXPECT_EQ(7, spat->intersections.list.array[0]->states.list.array[6]->signalGroup); + EXPECT_EQ(3, spat->intersections.list.array[0]->states.list.array[6]->state_time_speed.list.array[0]->eventState); + EXPECT_EQ(11351, spat->intersections.list.array[0]->states.list.array[6]->state_time_speed.list.array[0]->timing->minEndTime); + EXPECT_EQ(36000, *spat->intersections.list.array[0]->states.list.array[6]->state_time_speed.list.array[0]->timing->maxEndTime); + // Signal Group 8 + EXPECT_EQ(8, spat->intersections.list.array[0]->states.list.array[7]->signalGroup); + EXPECT_EQ(3, spat->intersections.list.array[0]->states.list.array[7]->state_time_speed.list.array[0]->eventState); + EXPECT_EQ(11351, spat->intersections.list.array[0]->states.list.array[7]->state_time_speed.list.array[0]->timing->minEndTime); + EXPECT_EQ(36000, *spat->intersections.list.array[0]->states.list.array[7]->state_time_speed.list.array[0]->timing->maxEndTime); + // Signal Group 9 + EXPECT_EQ(9, spat->intersections.list.array[0]->states.list.array[8]->signalGroup); + EXPECT_EQ(3, spat->intersections.list.array[0]->states.list.array[8]->state_time_speed.list.array[0]->eventState); + EXPECT_EQ(11311, spat->intersections.list.array[0]->states.list.array[8]->state_time_speed.list.array[0]->timing->minEndTime); + EXPECT_EQ(36000, *spat->intersections.list.array[0]->states.list.array[8]->state_time_speed.list.array[0]->timing->maxEndTime); + // Signal Group 10 + EXPECT_EQ(10, spat->intersections.list.array[0]->states.list.array[9]->signalGroup); + EXPECT_EQ(3, spat->intersections.list.array[0]->states.list.array[9]->state_time_speed.list.array[0]->eventState); + EXPECT_EQ(11351, spat->intersections.list.array[0]->states.list.array[9]->state_time_speed.list.array[0]->timing->minEndTime); + EXPECT_EQ(36000, *spat->intersections.list.array[0]->states.list.array[9]->state_time_speed.list.array[0]->timing->maxEndTime); + // Signal Group 11 + EXPECT_EQ(11, spat->intersections.list.array[0]->states.list.array[10]->signalGroup); + EXPECT_EQ(3, spat->intersections.list.array[0]->states.list.array[10]->state_time_speed.list.array[0]->eventState); + EXPECT_EQ(11311, spat->intersections.list.array[0]->states.list.array[10]->state_time_speed.list.array[0]->timing->minEndTime); + EXPECT_EQ(36000, *spat->intersections.list.array[0]->states.list.array[10]->state_time_speed.list.array[0]->timing->maxEndTime); + // Signal Group 12 + EXPECT_EQ(12, spat->intersections.list.array[0]->states.list.array[11]->signalGroup); + EXPECT_EQ(3, spat->intersections.list.array[0]->states.list.array[11]->state_time_speed.list.array[0]->eventState); + EXPECT_EQ(11351, spat->intersections.list.array[0]->states.list.array[11]->state_time_speed.list.array[0]->timing->minEndTime); + EXPECT_EQ(36000, *spat->intersections.list.array[0]->states.list.array[11]->state_time_speed.list.array[0]->timing->maxEndTime); + } + + TEST_F(TestSignalControllerConnection, receiveUPERSPATException) { + std::string without_paylod = R"( + Version=0.7 + Type=SPAT + PSID=0x8002 + Priority=7 + TxMode=CONT + TxChannel=172 + TxInterval=0 + DeliveryStart= + DeliveryStop= + Signature=True + Encryption=False + )"; + EXPECT_CALL(*mockUdpServer, stringTimedReceive(1000)).WillOnce(testing::DoAll(Return(without_paylod))); + + auto spatEncoded_ptr = std::make_shared(); + EXPECT_THROW(signalControllerConnection->receiveUPERSPAT(spatEncoded_ptr), tmx::TmxException); + } +} \ No newline at end of file diff --git a/src/v2i-hub/SpatPlugin/test/testPedestrianDetectionForSPAT.cpp b/src/v2i-hub/SpatPlugin/test/testPedestrianDetectionForSPAT.cpp deleted file mode 100644 index e404307b2..000000000 --- a/src/v2i-hub/SpatPlugin/test/testPedestrianDetectionForSPAT.cpp +++ /dev/null @@ -1,80 +0,0 @@ -#include -#include - -using namespace tmx::messages; - -static std::shared_ptr createSPAT() { - auto spatPtr = std::make_shared(); - auto intersectionState = (IntersectionState *)calloc(1, sizeof(IntersectionState)); - intersectionState->id.id = 1; - ASN_SEQUENCE_ADD(&(spatPtr->intersections), intersectionState); - - for (int i = 1; i <= 2; i++) { - auto movementState = (MovementState *)calloc(1, sizeof(MovementState)); - movementState->signalGroup = i; - ASN_SEQUENCE_ADD(&(intersectionState->states), movementState); - - auto movementEvent = (MovementEvent* )calloc(1, sizeof(MovementEvent)); - movementEvent->eventState = - i % 2 == 0 ? MovementPhaseState_protected_Movement_Allowed : MovementPhaseState_stop_And_Remain; - ASN_SEQUENCE_ADD(&(movementState->state_time_speed), movementEvent); - } - return spatPtr; -} - -TEST(PedestrianDetectionForSPAT, updateEncodedSpat_testIncompleteSpat) -{ - PedestrianDetectionForSPAT pedSPAT; - SpatEncodedMessage spatEncoded; - // create incomplete message - auto spatPtr = std::make_shared(); - auto spatMessage = std::make_shared(spatPtr); - bool execptionCaught = false; - try { - pedSPAT.updateEncodedSpat(spatEncoded, spatMessage, ""); - } catch (std::exception & e) { - // should not encode but should get here - execptionCaught = true; - } - EXPECT_EQ(execptionCaught, true); -} - -TEST(PedestrianDetectionForSPAT, updateEncodedSpat) -{ - PedestrianDetectionForSPAT pedSPAT; - // set up a J2735 SPAT to use and add to - auto spatPtr = createSPAT(); - - // first encode the message as is - tmx::messages::SpatEncodedMessage spatEncoded; - tmx::messages::MessageFrameMessage frame(spatPtr); - spatEncoded.set_data(tmx::messages::TmxJ2735EncodedMessage::encode_j2735_message>(frame)); - auto originalSpatHex = spatEncoded.get_payload_str(); - EXPECT_STRNE(originalSpatHex.c_str(), ""); - - // test once with no ped zones and ensure it is the same - { - SpatEncodedMessage spatEncoded; - auto spatMessage = std::make_shared(spatPtr); - pedSPAT.updateEncodedSpat(spatEncoded, spatMessage, ""); - EXPECT_STREQ(spatEncoded.get_payload_str().c_str(), originalSpatHex.c_str()); - } - - // test with ped zones to see that it updates - { - SpatEncodedMessage spatEncoded; - auto spatMessage = std::make_shared(spatPtr); - pedSPAT.updateEncodedSpat(spatEncoded, spatMessage, "2"); - // check hex is not equal to orginal - EXPECT_STRNE(spatEncoded.get_payload_str().c_str(), originalSpatHex.c_str()); - // check that the ped detect was added - ASSERT_EQ(spatPtr->intersections.list.count, 1); - ASSERT_EQ(spatPtr->intersections.list.array[0]->states.list.count, 2); - EXPECT_EQ(spatPtr->intersections.list.array[0]->states.list.array[0]->signalGroup, 1); - EXPECT_EQ(spatPtr->intersections.list.array[0]->states.list.array[1]->signalGroup, 2); - ASSERT_NE(spatPtr->intersections.list.array[0]->maneuverAssistList, nullptr); - EXPECT_NE(spatPtr->intersections.list.array[0]->maneuverAssistList->list.count, 0); - ASSERT_NE(spatPtr->intersections.list.array[0]->maneuverAssistList->list.array[0]->pedBicycleDetect, nullptr); - EXPECT_EQ(*(spatPtr->intersections.list.array[0]->maneuverAssistList->list.array[0]->pedBicycleDetect), 1); - } -} diff --git a/src/v2i-hub/SpatPlugin/test/test_spat_binaries/spat_1721238398773.bin b/src/v2i-hub/SpatPlugin/test/test_spat_binaries/spat_1721238398773.bin new file mode 100644 index 0000000000000000000000000000000000000000..694c2e4b560c87a0b5001e5081b46b4ab0ddcdd7 GIT binary patch literal 241 zcmX>rz{sHS|33p1FflNII1mA5WEmDP2db16S%wW+4?D692eKYcm>nRR3z@@>%;7=i j@FH{gkU9Lw906p`|GPk*62u1xk1;TCurMkpGB5xD@01|c literal 0 HcmV?d00001 From 799b0a837fed2f66a4442fe95dfbdbd7f2226b2a Mon Sep 17 00:00:00 2001 From: Peyton Johnson Date: Wed, 31 Jul 2024 11:41:31 -0400 Subject: [PATCH 05/24] Initial commit for integration testing port drayage with C1T --- configuration/docker-compose.yml | 2 +- .../mysql/garage_port_drayage/README.md | 11 +++++ .../garage_port_drayage/port_drayage.sql | 49 +++++++++++++++++++ 3 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 configuration/mysql/garage_port_drayage/README.md create mode 100644 configuration/mysql/garage_port_drayage/port_drayage.sql diff --git a/configuration/docker-compose.yml b/configuration/docker-compose.yml index 61f61926d..7f4fa4e23 100755 --- a/configuration/docker-compose.yml +++ b/configuration/docker-compose.yml @@ -14,7 +14,7 @@ services: - mysql_root_password volumes: - ./mysql/localhost.sql:/docker-entrypoint-initdb.d/localhost.sql - - ./mysql/port_drayage.sql:/docker-entrypoint-initdb.d/port_drayage.sql + - ./mysql/garage_port_drayage/port_drayage.sql:/docker-entrypoint-initdb.d/port_drayage.sql - mysql-datavolume:/var/lib/mysql php: diff --git a/configuration/mysql/garage_port_drayage/README.md b/configuration/mysql/garage_port_drayage/README.md new file mode 100644 index 000000000..5642f7235 --- /dev/null +++ b/configuration/mysql/garage_port_drayage/README.md @@ -0,0 +1,11 @@ +# C1T Garage Actions +These are Port Drayage actions created for the Saxton garage for testing of C1T functionality. + +## Instructions +Replace the port_drayage.sql file in docker-compose.yml with the file in this directory. +``` +db: + image: mysql:8.0 + volumes: + - ./mysql/garage_port_drayage/port_drayage.sql:/docker-entrypoint-initdb.d/port_drayage.sql +``` diff --git a/configuration/mysql/garage_port_drayage/port_drayage.sql b/configuration/mysql/garage_port_drayage/port_drayage.sql new file mode 100644 index 000000000..b1426768e --- /dev/null +++ b/configuration/mysql/garage_port_drayage/port_drayage.sql @@ -0,0 +1,49 @@ +-- MySQL 8.0 for Linux amd64 (x86_64) and arm64 (aarch64) +-- +-- Host: 127.0.0.1 Database: PORT_DRAYAGE +-- ------------------------------------------------------ +-- Server version 7.6.0 +-- Current Database: `PORT_DRAYAGE` +-- + +CREATE DATABASE /*!32312 IF NOT EXISTS*/ `PORT_DRAYAGE` /*!40100 DEFAULT CHARACTER SET latin1 */; + +USE `PORT_DRAYAGE`; + +-- +-- Table structure for table `first_action` +-- + +DROP TABLE IF EXISTS `first_action`; +CREATE TABLE `first_action` ( + `cmv_id` varchar(20) NOT NULL, + `cargo_id` varchar(20) DEFAULT NULL, + `destination_lat` decimal(9,7) NOT NULL, + `destination_long` decimal(9,7) NOT NULL, + `operation` varchar(20) NOT NULL, + `action_id` varchar(36) NOT NULL, + `next_action` varchar(36) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +LOCK TABLES `first_action` WRITE; +INSERT INTO `first_action` VALUES ('C1T-1','CARGO_A',-5.8,3.7,'PICKUP','4bea1c45-e421-11eb-a8cc-000c29ae3c1t','32320c8a-e422-11eb-a8cc-000c29ae3c1t'); +UNLOCK TABLES; + +-- +-- Table structure for table `freight` +-- + +DROP TABLE IF EXISTS `freight`; +CREATE TABLE `freight` ( + `cmv_id` varchar(20) NOT NULL, + `cargo_id` varchar(20) DEFAULT NULL, + `destination_lat` decimal(9,7) NOT NULL, + `destination_long` decimal(9,7) NOT NULL, + `operation` varchar(20) NOT NULL, + `action_id` varchar(36) NOT NULL, + `next_action` varchar(36) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +LOCK TABLES `freight` WRITE; +INSERT INTO `freight` VALUES ('C1T-1',NULL,0.2,0.0,'EXIT_STAGING_AREA','32320c8a-e422-11eb-a8cc-000c29ae3c1t','4ace39e6-ee36-11eb-9a03-0242ac130c1t'); +UNLOCK TABLES; From 9b97cf6106a6ca5c0a156dc4cba046859d3401cb Mon Sep 17 00:00:00 2001 From: Peyton Johnson Date: Wed, 31 Jul 2024 13:01:14 -0400 Subject: [PATCH 06/24] First pass at sql script configuration for C1T Port Drayage locations --- .../mysql/garage_port_drayage/port_drayage.sql | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/configuration/mysql/garage_port_drayage/port_drayage.sql b/configuration/mysql/garage_port_drayage/port_drayage.sql index b1426768e..b0840edbc 100644 --- a/configuration/mysql/garage_port_drayage/port_drayage.sql +++ b/configuration/mysql/garage_port_drayage/port_drayage.sql @@ -45,5 +45,13 @@ CREATE TABLE `freight` ( ) ENGINE=InnoDB DEFAULT CHARSET=latin1; LOCK TABLES `freight` WRITE; -INSERT INTO `freight` VALUES ('C1T-1',NULL,0.2,0.0,'EXIT_STAGING_AREA','32320c8a-e422-11eb-a8cc-000c29ae3c1t','4ace39e6-ee36-11eb-9a03-0242ac130c1t'); +INSERT INTO `freight` VALUES ('C1T-1',NULL,-1.4,-1.4,'ENTER_STAGING_AREA','one','two'), +('C1T-1',CARGO_A,-1.4,-1.4,'PICKUP','two','three'), +('C1T-1',CARGO_A,-1.4,-1.4,'EXIT_STAGING_AREA','three','four'), +('C1T-1',CARGO_A,-1.4,-1.4,'ENTER_PORT','four','five'), +('C1T-1',CARGO_A,-1.4,-1.4,'DROPOFF','five','six'), +('C1T-1',CARGO_B,-1.4,-1.4,'PICKUP','six','seven'), +('C1T-1',CARGO_B,-1.4,-1.4,'PORT_CHECKPOINT','seven','eight'), +('C1T-1',CARGO_B,-1.4,-1.4,'EXIT_PORT','eight','nine'), +('C1T-1',CARGO_B,-1.4,-1.4,'ENTER_STAGING_AREA','nine','ten'); UNLOCK TABLES; From 42a9cc3fb190498298757e36b1e41f03d854f750 Mon Sep 17 00:00:00 2001 From: Peyton Johnson Date: Thu, 1 Aug 2024 11:46:50 -0400 Subject: [PATCH 07/24] Added stopping locations for Port Drayage based on route graph nodes --- .../mysql/garage_port_drayage/port_drayage.sql | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/configuration/mysql/garage_port_drayage/port_drayage.sql b/configuration/mysql/garage_port_drayage/port_drayage.sql index b0840edbc..4a7f476eb 100644 --- a/configuration/mysql/garage_port_drayage/port_drayage.sql +++ b/configuration/mysql/garage_port_drayage/port_drayage.sql @@ -45,13 +45,5 @@ CREATE TABLE `freight` ( ) ENGINE=InnoDB DEFAULT CHARSET=latin1; LOCK TABLES `freight` WRITE; -INSERT INTO `freight` VALUES ('C1T-1',NULL,-1.4,-1.4,'ENTER_STAGING_AREA','one','two'), -('C1T-1',CARGO_A,-1.4,-1.4,'PICKUP','two','three'), -('C1T-1',CARGO_A,-1.4,-1.4,'EXIT_STAGING_AREA','three','four'), -('C1T-1',CARGO_A,-1.4,-1.4,'ENTER_PORT','four','five'), -('C1T-1',CARGO_A,-1.4,-1.4,'DROPOFF','five','six'), -('C1T-1',CARGO_B,-1.4,-1.4,'PICKUP','six','seven'), -('C1T-1',CARGO_B,-1.4,-1.4,'PORT_CHECKPOINT','seven','eight'), -('C1T-1',CARGO_B,-1.4,-1.4,'EXIT_PORT','eight','nine'), -('C1T-1',CARGO_B,-1.4,-1.4,'ENTER_STAGING_AREA','nine','ten'); -UNLOCK TABLES; +INSERT INTO `freight` VALUES ('C1T-1',NULL,-1.4,-1.4,'ENTER_STAGING_AREA','one','two'),('C1T-1','CARGO_A',-0.4,-0.4,'PICKUP','two','three'),('C1T-1','CARGO_A',-1.4,-3.4,'EXIT_STAGING_AREA','three','four'),('C1T-1','CARGO_A',-3.4,-3.4,'ENTER_PORT','four','five'),('C1T-1','CARGO_A',-6.4,-1.4,'DROPOFF','five','six'),('C1T-1','CARGO_B',-6.4,0.6,'PICKUP','six','seven'),('C1T-1','CARGO_B',-5.4,2.6,'PORT_CHECKPOINT','seven','eight'),('C1T-1','CARGO_B',-2.4,-2.4,'EXIT_PORT','eight','nine'),('C1T-1','CARGO_B',-1.4,-1.4,'ENTER_STAGING_AREA','nine','ten'); +UNLOCK TABLES; \ No newline at end of file From 4847aa7fdd30bf31f013cfe74fc3920d302fffe6 Mon Sep 17 00:00:00 2001 From: paulbourelly999 <77466294+paulbourelly999@users.noreply.github.com> Date: Thu, 1 Aug 2024 16:03:30 -0400 Subject: [PATCH 08/24] Install carma-clock-1 from ubuntu distribution (correct version) (#627) # PR Details ## Description This PR is to correct the version of `carma-clock-1` V2X-Hub pulls from our Debian repository. Previously it pulled from develop bucket which is no longer used and therefore contains versions of the library from 2023. Currently we push a debian package for each ubuntu distribution. ## Related Issue ## Motivation and Context Pull correct version of `carma-clock-1` package created from https://github.com/usdot-fhwa-stol/carma-time-lib ## How Has This Been Tested? CI Process ## Types of changes - [x] Defect fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that cause existing functionality to change) ## Checklist: - [ ] I have added any new packages to the sonar-scanner.properties file - [ ] My change requires a change to the documentation. - [ ] I have updated the documentation accordingly. - [x] I have read the **CONTRIBUTING** document. [V2XHUB Contributing Guide](https://github.com/usdot-fhwa-OPS/V2X-Hub/blob/develop/Contributing.md) - [ ] I have added tests to cover my changes. - [x] All new and existing tests passed. --- scripts/install_dependencies.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/install_dependencies.sh b/scripts/install_dependencies.sh index c4ce8727e..d20f05ea2 100755 --- a/scripts/install_dependencies.sh +++ b/scripts/install_dependencies.sh @@ -3,8 +3,11 @@ # exit on errors set -e +# Get ubuntu distribution code name. All STOL APT debian packages are pushed to S3 bucket based on distribution codename. +. /etc/lsb-release + # add the STOL APT repository -echo "deb [trusted=yes] http://s3.amazonaws.com/stol-apt-repository develop main" > /etc/apt/sources.list.d/stol-apt-repository.list +echo "deb [trusted=yes] http://s3.amazonaws.com/stol-apt-repository ${DISTRIB_CODENAME} main" > /etc/apt/sources.list.d/stol-apt-repository.list apt-get update From be5a5af9f314b23d2c61ea359989e2d27f3ee3d9 Mon Sep 17 00:00:00 2001 From: dan-du-car <62157949+dan-du-car@users.noreply.github.com> Date: Tue, 13 Aug 2024 12:40:38 -0400 Subject: [PATCH 09/24] FCP-5: SensorDetectedObject TMX message definition (#628) # PR Details ## Description Updating SensorDetectedObject message to allow for TMX JSON serialization/deserialization. ## Related Issue https://usdot-carma.atlassian.net/browse/FCP-5 ## Motivation and Context Freight Cooperative perception ## How Has This Been Tested? Unit test ## Types of changes - [x] Defect fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that cause existing functionality to change) ## Checklist: - [ ] I have added any new packages to the sonar-scanner.properties file - [ ] My change requires a change to the documentation. - [ ] I have updated the documentation accordingly. - [x] I have read the **CONTRIBUTING** document. [V2XHUB Contributing Guide](https://github.com/usdot-fhwa-OPS/V2X-Hub/blob/develop/Contributing.md) - [ ] I have added tests to cover my changes. - [ ] All new and existing tests passed. --- .sonarqube/sonar-scanner.properties | 6 +- src/tmx/Messages/CMakeLists.txt | 15 ++ src/tmx/Messages/include/Covariance.h | 20 ++ src/tmx/Messages/include/Position.h | 27 +++ .../Messages/include/SensorDetectedObject.h | 95 +++++---- src/tmx/Messages/include/Size.h | 27 +++ src/tmx/Messages/include/Velocity.h | 27 +++ .../test/SensorDetectedObjectTest.cpp | 192 ++++++++++++++++++ src/tmx/Messages/test/TestTimeSyncMessage.cpp | 2 +- src/tmx/TmxApi/tmx/messages/message.hpp | 101 +++++++++ .../src/MUSTSensorDetection.cpp | 28 +-- .../src/MUSTSensorDetection.h | 2 +- .../test/TestMUSTSensorDetection.cpp | 24 +-- 13 files changed, 493 insertions(+), 73 deletions(-) create mode 100644 src/tmx/Messages/include/Covariance.h create mode 100644 src/tmx/Messages/include/Position.h create mode 100644 src/tmx/Messages/include/Size.h create mode 100644 src/tmx/Messages/include/Velocity.h create mode 100644 src/tmx/Messages/test/SensorDetectedObjectTest.cpp diff --git a/.sonarqube/sonar-scanner.properties b/.sonarqube/sonar-scanner.properties index fdce595fc..22232d9f9 100644 --- a/.sonarqube/sonar-scanner.properties +++ b/.sonarqube/sonar-scanner.properties @@ -58,7 +58,8 @@ sonar.modules= PedestrianPlugin, \ CDASimAdapter, \ RSUHealthMonitorPlugin, \ TelematicBridgePlugin, \ - MUSTSensorDriverPlugin + MUSTSensorDriverPlugin, \ + Messages @@ -66,6 +67,7 @@ TmxCore.sonar.projectBaseDir =src/tmx/TmxCore TmxCtl.sonar.projectBaseDir =src/tmx/TmxCtl TmxTools.sonar.projectBaseDir =src/tmx/TmxTools TmxUtils.sonar.projectBaseDir =src/tmx/TmxUtils +Messages.sonar.projectBaseDir =src/tmx/Messages CARMACloudPlugin.sonar.projectBaseDir =src/v2i-hub/CARMACloudPlugin CommandPlugin.sonar.projectBaseDir =src/v2i-hub/CommandPlugin CswPlugin.sonar.projectBaseDir =src/v2i-hub/CswPlugin @@ -97,6 +99,7 @@ TmxCore.sonar.sources =src TmxCtl.sonar.sources =src TmxTools.sonar.sources =src TmxUtils.sonar.sources =src +Messages.sonar.sources =include TmxUtils.sonar.exclusions =test/** MessageLoggerPlugin.sonar.sources =src CswPlugin.sonar.sources =src @@ -132,6 +135,7 @@ MUSTSensorDriverPlugin.sonar.sources =src # Tests # Note: For C++ setting this field does not cause test analysis to occur. It only allows the test source code to be evaluated. TmxUtils.sonar.tests=test +Messages.sonar.tests=test #TmxCore.sonar.tests=test #TmxCtl.sonar.tests=test #TmxTools.sonar.tests=test diff --git a/src/tmx/Messages/CMakeLists.txt b/src/tmx/Messages/CMakeLists.txt index 3b866ee13..634a5a142 100644 --- a/src/tmx/Messages/CMakeLists.txt +++ b/src/tmx/Messages/CMakeLists.txt @@ -5,3 +5,18 @@ SET (TMXMESSAGES_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include" PARENT_SCOPE) INSTALL (DIRECTORY include DESTINATION . COMPONENT ${PROJECT_NAME} FILES_MATCHING PATTERN "*.h*") + +############# +## Testing ## +############# +enable_testing() + +set(BINARY ${PROJECT_NAME}_test) + +file(GLOB_RECURSE TEST_SOURCES LIST_DIRECTORIES false test/*.h test/*.cpp) + +add_executable(${BINARY} ${TEST_SOURCES}) + +add_test(NAME ${BINARY} COMMAND ${BINARY}) +target_include_directories(${BINARY} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include) +target_link_libraries(${BINARY} PUBLIC ${TMXAPI_LIBRARIES} gtest) \ No newline at end of file diff --git a/src/tmx/Messages/include/Covariance.h b/src/tmx/Messages/include/Covariance.h new file mode 100644 index 000000000..ebf7dca53 --- /dev/null +++ b/src/tmx/Messages/include/Covariance.h @@ -0,0 +1,20 @@ +#pragma once +#include +namespace tmx::messages +{ + struct Covariance{ + double value; + Covariance()=default; + explicit Covariance(double value):value(value){}; + static message_tree_type to_tree(const Covariance& cov){ + message_tree_type tree; + tree.put("",cov.value); + return tree; + } + static Covariance from_tree(const message_tree_type& tree){ + Covariance cov; + cov.value = tree.get(""); + return cov; + } + }; +} \ No newline at end of file diff --git a/src/tmx/Messages/include/Position.h b/src/tmx/Messages/include/Position.h new file mode 100644 index 000000000..8e0d2b029 --- /dev/null +++ b/src/tmx/Messages/include/Position.h @@ -0,0 +1,27 @@ +#pragma once +#include +namespace tmx::messages +{ + // Cartesian positiion of object. Assumed to be ENU coordinate frame. + struct Position{ + double x; + double y; + double z; + Position()=default; + explicit Position(double x, double y, double z):x(x),y(y),z(z){}; + static message_tree_type to_tree(const Position& pos){ + message_tree_type tree; + tree.put("x", pos.x); + tree.put("y", pos.y); + tree.put("z", pos.z); + return tree; + } + static Position from_tree(const message_tree_type& tree){ + Position pos; + pos.x = tree.get("x"); + pos.y = tree.get("y"); + pos.z = tree.get("z"); + return pos; + } + }; +} \ No newline at end of file diff --git a/src/tmx/Messages/include/SensorDetectedObject.h b/src/tmx/Messages/include/SensorDetectedObject.h index d275bbf7e..4def40746 100644 --- a/src/tmx/Messages/include/SensorDetectedObject.h +++ b/src/tmx/Messages/include/SensorDetectedObject.h @@ -1,54 +1,61 @@ -#ifndef INCLUDE_SIMULATED_SensorDetectedObject_H_ -#define INCLUDE_SIMULATED_SensorDetectedObject_H_ +#pragma once #include #include -#include -#include +#include "Position.h" +#include "Covariance.h" +#include "Velocity.h" +#include "Size.h" -namespace tmx +namespace tmx::messages { - namespace messages - { - /** - * This SensorDetectedObject is used to communicate the sensor detected object information with various applications. - * This message is the generic representation of a sensor detection. - */ - class SensorDetectedObject : public tmx::message - { - public: - SensorDetectedObject(){}; - SensorDetectedObject(const tmx::message_container_type &contents) : tmx::message(contents) {}; - ~SensorDetectedObject(){}; - // Message type for routing this message through TMX core - static constexpr const char *MessageType = MSGTYPE_APPLICATION_STRING; + /** + * This SensorDetectedObject is used to communicate the sensor detected object information with various applications. + * This message is the generic representation of a sensor detection. + */ + class SensorDetectedObject : public tmx::message + { + public: + SensorDetectedObject()=default; + explicit SensorDetectedObject(const tmx::message_container_type &contents) : tmx::message(contents) {}; + ~SensorDetectedObject() override{}; + // Message type for routing this message through TMX core + static constexpr const char *MessageType = MSGTYPE_APPLICATION_STRING; + + // // Message sub type for routing this message through TMX core + static constexpr const char *MessageSubType = MSGSUBTYPE_SENSOR_DETECTED_OBJECT_STRING; - // // Message sub type for routing this message through TMX core - static constexpr const char *MessageSubType = MSGSUBTYPE_SENSOR_DETECTED_OBJECT_STRING; + //Flag to indicate whether sensor detected object is simulated. + std_attribute(this->msg, bool, isSimulated, false,); + // Classification of detected object. + std_attribute(this->msg, std::string, type, "",); + // Confidence of type classification + std_attribute(this->msg, double, confidence, 0.0,); + // Unique indentifier of sensor reporting detection. + std_attribute(this->msg, std::string, sensorId, "", ); + // String describing projection used to convert cartesian data to WGS84 data. + std_attribute(this->msg, std::string, projString, "", ); + // Unique identifier of detected object. + std_attribute(this->msg, int, objectId, 0, ); - // TODO: Convert this member variable to std::attributes and handle nested object and arrays. (see [CloudHeartbeatMessage.h](./CloudHearbeatMessage.h) array_attribute ) - - // Classification of detected object - std::string type = ""; - // Confidence of type classification - double confidence = 0.0; - // Unique indentifier of sensor reporting detection - std::string sensorId = ""; - // String describing projection used to convert cartesian data to WGS84 data - std::string projString = ""; - // Unique identifier of detected object - int objectId = 0; - // Cartesian positiion of object. Assumed to be ENU coordinate frame. - tmx::utils::Point position = tmx::utils::Point(); - // Cartesian velocity vector of object. Assumed to be ENU coordinate frame. - tmx::utils::Vector3d velocity = tmx::utils::Vector3d(); - // Epoch time in milliseconds - long timestamp = 0; - - }; + + object_attribute(Position, position); + two_dimension_array_attribute(Covariance, positionCovariance); + //Linear velocity in meter per second + object_attribute(Velocity, velocity); + //Covariance associated with linear velocity. + two_dimension_array_attribute(Covariance, velocityCovariance); + //Angular velocity in radians per second. + object_attribute(Velocity, angularVelocity); + //Covariance associated with angular velocity. + two_dimension_array_attribute(Covariance, angularVelocityCovariance); - } + // Epoch time in milliseconds. + // long timestamp = 0; + std_attribute(this->msg, long, timestamp, 0, ); + object_attribute(Size, size); + + }; -}; // namespace tmx -#endif +} \ No newline at end of file diff --git a/src/tmx/Messages/include/Size.h b/src/tmx/Messages/include/Size.h new file mode 100644 index 000000000..c94e72362 --- /dev/null +++ b/src/tmx/Messages/include/Size.h @@ -0,0 +1,27 @@ +#pragma once +#include +namespace tmx::messages +{ + //Length, width and height of object in meter. + struct Size{ + double length; + double width; + double height; + Size()=default; + explicit Size(double length, double width, double height): length(length), width(width), height(height){}; + static message_tree_type to_tree(const Size& size){ + message_tree_type tree; + tree.put("length", size.length); + tree.put("width", size.width); + tree.put("height", size.height); + return tree; + } + static Size from_tree(const message_tree_type & tree){ + Size size; + size.length = tree.get("length"); + size.width = tree.get("width"); + size.height = tree.get("height"); + return size; + } + }; +} \ No newline at end of file diff --git a/src/tmx/Messages/include/Velocity.h b/src/tmx/Messages/include/Velocity.h new file mode 100644 index 000000000..e8eed0e92 --- /dev/null +++ b/src/tmx/Messages/include/Velocity.h @@ -0,0 +1,27 @@ +#pragma once +#include +namespace tmx::messages +{ + // Cartesian velocity vector of object. Assumed to be ENU coordinate frame. + struct Velocity{ + double x; + double y; + double z; + Velocity()=default; + explicit Velocity(double x, double y, double z):x(x),y(y),z(z){}; + static message_tree_type to_tree(const Velocity& velocity){ + message_tree_type tree; + tree.put("x", velocity.x); + tree.put("y", velocity.y); + tree.put("z", velocity.z); + return tree; + } + static Velocity from_tree(const message_tree_type& tree){ + Velocity velocity; + velocity.x = tree.get("x"); + velocity.y = tree.get("y"); + velocity.z = tree.get("z"); + return velocity; + } + }; +} \ No newline at end of file diff --git a/src/tmx/Messages/test/SensorDetectedObjectTest.cpp b/src/tmx/Messages/test/SensorDetectedObjectTest.cpp new file mode 100644 index 000000000..7a49e3ce0 --- /dev/null +++ b/src/tmx/Messages/test/SensorDetectedObjectTest.cpp @@ -0,0 +1,192 @@ +#include +#include "SensorDetectedObject.h" + +namespace tmx::messages{ + class SensorDetectedObjectTest : public testing::Test{ + protected: + std::shared_ptr tmxSdsmPtr; + SensorDetectedObjectTest(){ + tmxSdsmPtr = std::make_shared(); + } + void SetUp() override { + tmxSdsmPtr->set_isSimulated(false); + Position pos(1.0, 2.3, 2.0); + tmxSdsmPtr->set_position(pos); + tmxSdsmPtr->set_projString("+proj=tmerc +lat_0=38.95197911150576 +lon_0=-77.14835128349988 +k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m +geoidgrids=egm96_15.gtx +vunits=m +no_defs"); + tmxSdsmPtr->set_timestamp(12222222222); + tmxSdsmPtr->set_sensorId("SomeID"); + tmxSdsmPtr->set_type("Car"); + tmxSdsmPtr->set_confidence(0.7); + tmxSdsmPtr->set_objectId(123); + + Velocity vel(1.0, 0.3, 2.0); + tmxSdsmPtr->set_velocity(vel); + tmxSdsmPtr->set_angularVelocity(vel); + + std::vector covs { + Covariance(12), + Covariance(11), + Covariance(13), + Covariance(14), + Covariance(15) + }; + int covarianceSize = 3; + std::vector> covs2d; + for(int i=0; iset_positionCovariance(covs2d); + tmxSdsmPtr->set_velocityCovariance(covs2d); + tmxSdsmPtr->set_angularVelocityCovariance(covs2d); + } + }; + + TEST_F(SensorDetectedObjectTest, attributes){ + EXPECT_EQ(false, tmxSdsmPtr->get_isSimulated()); + EXPECT_EQ(0.7, tmxSdsmPtr->get_confidence()); + EXPECT_EQ("SomeID", tmxSdsmPtr->get_sensorId()); + EXPECT_EQ(12222222222, tmxSdsmPtr->get_timestamp()); + EXPECT_EQ(123, tmxSdsmPtr->get_objectId()); + EXPECT_EQ("+proj=tmerc +lat_0=38.95197911150576 +lon_0=-77.14835128349988 +k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m +geoidgrids=egm96_15.gtx +vunits=m +no_defs", tmxSdsmPtr->get_projString()); + EXPECT_EQ(1.0, tmxSdsmPtr->get_position().x); + EXPECT_NEAR(2.3, tmxSdsmPtr->get_position().y, 0.01); + EXPECT_EQ(2.0, tmxSdsmPtr->get_position().z); + EXPECT_EQ(1.0, tmxSdsmPtr->get_velocity().x); + EXPECT_NEAR(0.3, tmxSdsmPtr->get_velocity().y, 0.01); + EXPECT_EQ(2.0, tmxSdsmPtr->get_velocity().z); + EXPECT_EQ(1.0, tmxSdsmPtr->get_angularVelocity().x); + EXPECT_NEAR(0.3, tmxSdsmPtr->get_angularVelocity().y, 0.01); + EXPECT_EQ(2.0, tmxSdsmPtr->get_angularVelocity().z); + EXPECT_EQ(3, tmxSdsmPtr->get_positionCovariance().size()); + EXPECT_EQ(3, tmxSdsmPtr->get_angularVelocityCovariance().size()); + EXPECT_EQ(3, tmxSdsmPtr->get_velocityCovariance().size()); + } + + TEST_F(SensorDetectedObjectTest, to_string){ + std::string expectedStr = "{\"isSimulated\":\"0\",\"position\":{\"x\":\"1\",\"y\":\"2.2999999999999998\",\"z\":\"2\"},\"projString\":\"+proj=tmerc +lat_0=38.95197911150576 +lon_0=-77.14835128349988 +k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m +geoidgrids=egm96_15.gtx +vunits=m +no_defs\",\"timestamp\":\"12222222222\",\"sensorId\":\"SomeID\",\"type\":\"Car\",\"confidence\":\"0.69999999999999996\",\"objectId\":\"123\",\"velocity\":{\"x\":\"1\",\"y\":\"0.29999999999999999\",\"z\":\"2\"},\"angularVelocity\":{\"x\":\"1\",\"y\":\"0.29999999999999999\",\"z\":\"2\"},\"positionCovariance\":[[\"12\",\"11\",\"13\",\"14\",\"15\"],[\"12\",\"11\",\"13\",\"14\",\"15\"],[\"12\",\"11\",\"13\",\"14\",\"15\"]],\"velocityCovariance\":[[\"12\",\"11\",\"13\",\"14\",\"15\"],[\"12\",\"11\",\"13\",\"14\",\"15\"],[\"12\",\"11\",\"13\",\"14\",\"15\"]],\"angularVelocityCovariance\":[[\"12\",\"11\",\"13\",\"14\",\"15\"],[\"12\",\"11\",\"13\",\"14\",\"15\"],[\"12\",\"11\",\"13\",\"14\",\"15\"]]}\n"; + EXPECT_EQ(expectedStr, tmxSdsmPtr->to_string()); + } + + TEST_F(SensorDetectedObjectTest, deserialize){ + auto tmxSdsmPtr2 = std::make_shared(); + std::string expectedStr = R"( + { + "isSimulated": 1, + "type": "CAR", + "confidence": 1, + "sensorId": "IntersectionLidar", + "projString": "+proj=tmerc +lat_0=0 +lon_0=0 +k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m +geoidgrids=egm96_15.gtx +vunits=m +no_defs", + "objectId": 207, + "position": { + "x": -5.021, + "y": 64.234, + "z": -10.297 + }, + "positionCovariance": [ + [ + 0.04000000000000001, + 0, + 0 + ], + [ + 0, + 0.04000000000000001, + 0 + ], + [ + 0, + 0, + 0.04000000000000001 + ] + ], + "velocity": { + "x": 0, + "y": 0, + "z": 0 + }, + "velocityCovariance": [ + [ + 0.04000000000000001, + 0, + 0 + ], + [ + 0, + 0.04000000000000001, + 0 + ], + [ + 0, + 0, + 0.04000000000000001 + ] + ], + "angularVelocity": { + "x": 0, + "y": 0, + "z": 0 + }, + "angularVelocityCovariance": [ + [ + 0.010000000000000002, + 0, + 0 + ], + [ + 0, + 0.010000000000000002, + 0 + ], + [ + 0, + 0, + 0.010000000000000002 + ] + ], + "size": { + "length": 2.257, + "height": 1.003, + "width": 0.762 + }, + "timestamp": 110200 + } + )"; + tmxSdsmPtr2->set_contents(expectedStr); + EXPECT_EQ(expectedStr, tmxSdsmPtr2->to_string()); + EXPECT_EQ(true, tmxSdsmPtr2->get_isSimulated()); + EXPECT_EQ(-5.021, tmxSdsmPtr2->get_position().x); + EXPECT_NEAR(64.234, tmxSdsmPtr2->get_position().y, 0.01); + EXPECT_EQ(-10.297, tmxSdsmPtr2->get_position().z); + EXPECT_EQ("CAR", tmxSdsmPtr2->get_type()); + EXPECT_EQ(1.0, tmxSdsmPtr2->get_confidence()); + EXPECT_EQ("IntersectionLidar", tmxSdsmPtr2->get_sensorId()); + EXPECT_EQ("+proj=tmerc +lat_0=0 +lon_0=0 +k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m +geoidgrids=egm96_15.gtx +vunits=m +no_defs", tmxSdsmPtr2->get_projString()); + EXPECT_EQ(207, tmxSdsmPtr2->get_objectId()); + EXPECT_EQ(0.0, tmxSdsmPtr2->get_velocity().x); + EXPECT_EQ(0.0, tmxSdsmPtr2->get_velocity().y); + EXPECT_EQ(0.0, tmxSdsmPtr2->get_velocity().z); + EXPECT_EQ(0.0, tmxSdsmPtr2->get_angularVelocity().x); + EXPECT_EQ(-0.0, tmxSdsmPtr2->get_angularVelocity().y); + EXPECT_EQ(-0.0, tmxSdsmPtr2->get_angularVelocity().z); + EXPECT_EQ(2.257, tmxSdsmPtr2->get_size().length); + EXPECT_EQ(1.003, tmxSdsmPtr2->get_size().height); + EXPECT_EQ(0.762, tmxSdsmPtr2->get_size().width); + EXPECT_EQ(110200, tmxSdsmPtr2->get_timestamp()); + + EXPECT_EQ(3, tmxSdsmPtr2->get_positionCovariance().size()); + EXPECT_EQ(3, tmxSdsmPtr2->get_positionCovariance().begin()->size()); + EXPECT_EQ(3, tmxSdsmPtr2->get_angularVelocityCovariance().size()); + EXPECT_EQ(3, tmxSdsmPtr2->get_angularVelocityCovariance().begin()->size()); + EXPECT_EQ(3, tmxSdsmPtr2->get_velocityCovariance().size()); + EXPECT_EQ(3, tmxSdsmPtr2->get_velocityCovariance().begin()->size()); + + EXPECT_NEAR(0.04,tmxSdsmPtr2->get_positionCovariance().begin()->begin()->value, 0.0001); + EXPECT_EQ(0.0, tmxSdsmPtr2->get_positionCovariance().begin()->back().value); + + EXPECT_NEAR(0.04, tmxSdsmPtr2->get_velocityCovariance().begin()->begin()->value, 0.0001); + EXPECT_EQ(0.0, tmxSdsmPtr2->get_velocityCovariance().begin()->back().value); + + EXPECT_NEAR(0.01, tmxSdsmPtr2->get_angularVelocityCovariance().begin()->begin()->value, 0.0001); + EXPECT_EQ(0.0, tmxSdsmPtr2->get_angularVelocityCovariance().begin()->back().value); + } +} \ No newline at end of file diff --git a/src/tmx/Messages/test/TestTimeSyncMessage.cpp b/src/tmx/Messages/test/TestTimeSyncMessage.cpp index a853ed2b2..13b27c69d 100644 --- a/src/tmx/Messages/test/TestTimeSyncMessage.cpp +++ b/src/tmx/Messages/test/TestTimeSyncMessage.cpp @@ -4,7 +4,7 @@ namespace tmx::messages { TEST(TestTimeSyncMessage, to_string) { TimeSyncMessage msg(20, 30); - std::string json = "{ \"timestep\":20, \"seq\":30}"; + std::string json = "{\"timestep\":\"20\",\"seq\":\"30\"}\n"; EXPECT_EQ( json, msg.to_string()); } } \ No newline at end of file diff --git a/src/tmx/TmxApi/tmx/messages/message.hpp b/src/tmx/TmxApi/tmx/messages/message.hpp index 18570d558..94d26a88d 100644 --- a/src/tmx/TmxApi/tmx/messages/message.hpp +++ b/src/tmx/TmxApi/tmx/messages/message.hpp @@ -22,6 +22,16 @@ void add_to_##NAME(ELEMENT element) { add_array_element(#NAME, element); } \ void erase_##NAME() { erase_array(#NAME); } + +#define two_dimension_array_attribute(ELEMENT, NAME) \ + std::vector> get_##NAME () { return get_two_dimension_array(#NAME); } \ + void set_##NAME(std::vector> array) { return set_two_dimension_array(#NAME, array); } + +#define object_attribute(ELEMENT, NAME) \ + ELEMENT get_##NAME() {return get_object(#NAME); } \ + void set_##NAME(ELEMENT obj) {return set_object(#NAME, obj); } \ + void erase_##NAME(){erase_object(#NAME); } + namespace tmx { @@ -402,6 +412,97 @@ class tmx_message { tree.get().push_back(typename message_tree_type::value_type("", Element::to_tree(element))); } + /** + * Get the entire contents of a two dimension array field as a vector. + * Note that the template Element type must contain methods with the following signatures: + * - static Element from_tree(message_tree_type&) + * - static message_tree_type to_tree(Element element) + * @param The name of the array field. + * @returns A two dimension array of all array elements. + */ + template + std::vector> get_two_dimension_array(std::string arrayName) + { + std::vector> ret; + boost::optional tree = this->as_tree(arrayName); + if(tree) + { + for(auto& outer_pair: tree.get()){ + std::vector temp; + for(auto& inner_pair: outer_pair.second){ + Element element = Element::from_tree(inner_pair.second); + temp.push_back(element); + } + ret.push_back(temp); + } + } + return ret; + } + + /** + * Set the entire contents of a two dimension array field. + * Note that the template Element type must contain methods with the following signatures: + * - static Element from_tree(message_tree_type&) + * - static message_tree_type to_tree(Element element) + * @param The name of the array field. + * @param array A two dimenstion array containing all elements to set. + */ + template + void set_two_dimension_array(std::string arrayName, std::vector> array) + { + erase_array(arrayName); + boost::optional tree = this->as_tree(arrayName); + if (!tree) + { + // Add the empty array + message_tree_type emptyTree; + this->as_tree().get().add_child(arrayName, emptyTree); + tree = this->as_tree(arrayName); + } + + for(auto& nested_array: array){ + boost::property_tree::ptree subtree; + //Populate nested array + for(auto& element: nested_array){ + subtree.push_back(typename message_tree_type::value_type("", Element::to_tree(element))); + } + //Add nested array + tree.get().push_back(typename message_tree_type::value_type("", subtree)); + } + } + + /*** + * @brief Get the content of an object fields + * @param Name of the object + * @param Object An object containing all the fields + */ + template + Element get_object(const std::string& objectName){ + boost::optional tree = this->as_tree(); + return Element::from_tree(tree.get().get_child(objectName)); + } + + /** + * @brief Set the content of an object fields + * @param Name of the object + * @param Object An object containing all the fields to set + */ + template + void set_object(const std::string& objectName, Element obj) + { + erase_object(objectName); + this->as_tree().get().add_child(objectName, Element::to_tree(obj)); + } + /** + * @brief Erase a certain object from the tree given the object name. + * @param Name of the object + * @param Object An object to be erased from the tree + */ + void erase_object(const std::string& objName) + { + this->as_tree().get().erase(objName); + } + /** * Erase the entire contents of an array field. * @param The name of the array field. diff --git a/src/v2i-hub/MUSTSensorDriverPlugin/src/MUSTSensorDetection.cpp b/src/v2i-hub/MUSTSensorDriverPlugin/src/MUSTSensorDetection.cpp index 95b514d9e..8d37af661 100644 --- a/src/v2i-hub/MUSTSensorDriverPlugin/src/MUSTSensorDetection.cpp +++ b/src/v2i-hub/MUSTSensorDriverPlugin/src/MUSTSensorDetection.cpp @@ -31,15 +31,15 @@ namespace MUSTSensorDriverPlugin { tmx::messages::SensorDetectedObject mustDetectionToSensorDetectedObject(const MUSTSensorDetection &detection, std::string_view sensorId, std::string_view projString) { tmx::messages::SensorDetectedObject detectedObject; - detectedObject.objectId = detection.trackID; - detectedObject.position.X = detection.position_x; - detectedObject.position.Y = detection.position_y; - detectedObject.confidence = detection.confidence; - detectedObject.timestamp = static_cast(detection.timestamp*1000); // convert decimal seconds to int milliseconds. - detectedObject.velocity = headingSpeedToVelocity(detection.heading, detection.speed); - detectedObject.type = detectionClassificationToSensorDetectedObjectType(detection.cl); - detectedObject.sensorId = sensorId; - detectedObject.projString = projString; + detectedObject.set_objectId(detection.trackID); + tmx::messages::Position pos(detection.position_x, detection.position_y, 0); + detectedObject.set_position(pos); + detectedObject.set_confidence(detection.confidence); + detectedObject.set_timestamp(static_cast(detection.timestamp*1000)); // convert decimal seconds to int milliseconds. + detectedObject.set_velocity(headingSpeedToVelocity(detection.heading, detection.speed)); + detectedObject.set_type(detectionClassificationToSensorDetectedObjectType(detection.cl)); + detectedObject.set_sensorId(std::string(sensorId)); + detectedObject.set_projString(std::string(projString)); return detectedObject; } DetectionClassification fromStringToDetectionClassification(const std::string &str) noexcept { @@ -77,15 +77,15 @@ namespace MUSTSensorDriverPlugin { } }; - tmx::utils::Vector3d headingSpeedToVelocity(double heading, double speed) { + tmx::messages::Velocity headingSpeedToVelocity(double heading, double speed) { // Convert North East heading to Angle with 0 at (1, 0) (See README Unit Circle) heading = heading - 270; // factor for converting heading from degrees to radians auto headingInRad = M_PI / 180; - tmx::utils::Vector3d velocity; - velocity.X = std::cos(headingInRad * heading) * speed; - velocity.Y = std::sin(headingInRad * heading) * speed; - velocity.Z = 0; + tmx::messages::Velocity velocity; + velocity.x = std::cos(headingInRad * heading) * speed; + velocity.y = std::sin(headingInRad * heading) * speed; + velocity.z = 0; return velocity; }; } diff --git a/src/v2i-hub/MUSTSensorDriverPlugin/src/MUSTSensorDetection.h b/src/v2i-hub/MUSTSensorDriverPlugin/src/MUSTSensorDetection.h index 313204811..37e0858ec 100644 --- a/src/v2i-hub/MUSTSensorDriverPlugin/src/MUSTSensorDetection.h +++ b/src/v2i-hub/MUSTSensorDriverPlugin/src/MUSTSensorDetection.h @@ -118,5 +118,5 @@ namespace MUSTSensorDriverPlugin { * @param speed double speed in m/s * @return tmx::utils::Vector3d velocity. */ - tmx::utils::Vector3d headingSpeedToVelocity(double heading, double speed); + tmx::messages::Velocity headingSpeedToVelocity(double heading, double speed); } \ No newline at end of file diff --git a/src/v2i-hub/MUSTSensorDriverPlugin/test/TestMUSTSensorDetection.cpp b/src/v2i-hub/MUSTSensorDriverPlugin/test/TestMUSTSensorDetection.cpp index a50783d3a..0f745df77 100644 --- a/src/v2i-hub/MUSTSensorDriverPlugin/test/TestMUSTSensorDetection.cpp +++ b/src/v2i-hub/MUSTSensorDriverPlugin/test/TestMUSTSensorDetection.cpp @@ -62,16 +62,16 @@ TEST(TestMUSTSensorDetection, mustDetectionToSensorDetectedObject ) { auto sensorDetectedObject = mustDetectionToSensorDetectedObject(detection, "MUSTSensor1", "PROJ String"); - EXPECT_EQ(detection.trackID, sensorDetectedObject.objectId); - EXPECT_DOUBLE_EQ(detection.confidence, sensorDetectedObject.confidence); - EXPECT_DOUBLE_EQ(detection.position_x, sensorDetectedObject.position.X); - EXPECT_DOUBLE_EQ(detection.position_y, sensorDetectedObject.position.Y); - EXPECT_NEAR(4.33, sensorDetectedObject.velocity.Y, 0.001); - EXPECT_NEAR(2.5, sensorDetectedObject.velocity.X, 0.001); - EXPECT_STRCASEEQ("SEDAN", sensorDetectedObject.type.c_str()); - EXPECT_EQ(1719506355400, sensorDetectedObject.timestamp); - EXPECT_EQ("MUSTSensor1", sensorDetectedObject.sensorId); - EXPECT_EQ("PROJ String", sensorDetectedObject.projString); + EXPECT_EQ(detection.trackID, sensorDetectedObject.get_objectId()); + EXPECT_DOUBLE_EQ(detection.confidence, sensorDetectedObject.get_confidence()); + EXPECT_DOUBLE_EQ(detection.position_x, sensorDetectedObject.get_position().x); + EXPECT_DOUBLE_EQ(detection.position_y, sensorDetectedObject.get_position().y); + EXPECT_NEAR(4.33, sensorDetectedObject.get_velocity().y, 0.001); + EXPECT_NEAR(2.5, sensorDetectedObject.get_velocity().x, 0.001); + EXPECT_STRCASEEQ("SEDAN", sensorDetectedObject.get_type().c_str()); + EXPECT_EQ(1719506355400, sensorDetectedObject.get_timestamp()); + EXPECT_EQ("MUSTSensor1", sensorDetectedObject.get_sensorId()); + EXPECT_EQ("PROJ String", sensorDetectedObject.get_projString()); } TEST(TestMUSTSensorDetection, detectionClassificationToSensorDetectedObjectType ) { @@ -84,6 +84,6 @@ TEST(TestMUSTSensorDetection, detectionClassificationToSensorDetectedObjectType TEST(TestMUSTSensorDetection, headingSpeedToVelocity ) { auto velocity = headingSpeedToVelocity(30, 5); - EXPECT_NEAR(4.33, velocity.Y, 0.001); - EXPECT_NEAR(-2.5, velocity.X, 0.001); + EXPECT_NEAR(4.33, velocity.y, 0.001); + EXPECT_NEAR(-2.5, velocity.x, 0.001); } \ No newline at end of file From 532ea702ba92b64f4e33a8375487f26d650de7af Mon Sep 17 00:00:00 2001 From: Peyton Johnson Date: Wed, 14 Aug 2024 14:11:21 -0400 Subject: [PATCH 10/24] Added .sql files for each port pickup lane option, updated docker compose to reflect --- configuration/docker-compose.yml | 3 ++ .../garage_port_drayage/port_drayage.sql | 6 +-- .../port_drayage_lane1.sql | 49 +++++++++++++++++++ .../port_drayage_lane2.sql | 49 +++++++++++++++++++ .../port_drayage_lane3.sql | 49 +++++++++++++++++++ 5 files changed, 153 insertions(+), 3 deletions(-) create mode 100644 configuration/mysql/garage_port_drayage/port_drayage_lane1.sql create mode 100644 configuration/mysql/garage_port_drayage/port_drayage_lane2.sql create mode 100644 configuration/mysql/garage_port_drayage/port_drayage_lane3.sql diff --git a/configuration/docker-compose.yml b/configuration/docker-compose.yml index 7f4fa4e23..381b52a67 100755 --- a/configuration/docker-compose.yml +++ b/configuration/docker-compose.yml @@ -15,6 +15,9 @@ services: volumes: - ./mysql/localhost.sql:/docker-entrypoint-initdb.d/localhost.sql - ./mysql/garage_port_drayage/port_drayage.sql:/docker-entrypoint-initdb.d/port_drayage.sql + - ./mysql/garage_port_drayage/port_drayage_lane1.sql:/docker-entrypoint-initdb.d/port_drayage_lane1.sql + - ./mysql/garage_port_drayage/port_drayage_lane2.sql:/docker-entrypoint-initdb.d/port_drayage_lane2.sql + - ./mysql/garage_port_drayage/port_drayage_lane3.sql:/docker-entrypoint-initdb.d/port_drayage_lane3.sql - mysql-datavolume:/var/lib/mysql php: diff --git a/configuration/mysql/garage_port_drayage/port_drayage.sql b/configuration/mysql/garage_port_drayage/port_drayage.sql index 4a7f476eb..5df1efa56 100644 --- a/configuration/mysql/garage_port_drayage/port_drayage.sql +++ b/configuration/mysql/garage_port_drayage/port_drayage.sql @@ -26,7 +26,7 @@ CREATE TABLE `first_action` ( ) ENGINE=InnoDB DEFAULT CHARSET=latin1; LOCK TABLES `first_action` WRITE; -INSERT INTO `first_action` VALUES ('C1T-1','CARGO_A',-5.8,3.7,'PICKUP','4bea1c45-e421-11eb-a8cc-000c29ae3c1t','32320c8a-e422-11eb-a8cc-000c29ae3c1t'); +INSERT INTO `first_action` VALUES ('C1T-1','NULL',-1.4,-1.4,'ENTER_STAGING_AREA','one','two'); UNLOCK TABLES; -- @@ -45,5 +45,5 @@ CREATE TABLE `freight` ( ) ENGINE=InnoDB DEFAULT CHARSET=latin1; LOCK TABLES `freight` WRITE; -INSERT INTO `freight` VALUES ('C1T-1',NULL,-1.4,-1.4,'ENTER_STAGING_AREA','one','two'),('C1T-1','CARGO_A',-0.4,-0.4,'PICKUP','two','three'),('C1T-1','CARGO_A',-1.4,-3.4,'EXIT_STAGING_AREA','three','four'),('C1T-1','CARGO_A',-3.4,-3.4,'ENTER_PORT','four','five'),('C1T-1','CARGO_A',-6.4,-1.4,'DROPOFF','five','six'),('C1T-1','CARGO_B',-6.4,0.6,'PICKUP','six','seven'),('C1T-1','CARGO_B',-5.4,2.6,'PORT_CHECKPOINT','seven','eight'),('C1T-1','CARGO_B',-2.4,-2.4,'EXIT_PORT','eight','nine'),('C1T-1','CARGO_B',-1.4,-1.4,'ENTER_STAGING_AREA','nine','ten'); -UNLOCK TABLES; \ No newline at end of file +INSERT INTO `freight` VALUES ('C1T-1','NULL',-2.4,-2.4,'EXIT_PORT','zero','one'),('C1T-1','NULL',-1.4,-1.4,'ENTER_STAGING_AREA','one','two'),('C1T-1','CARGO_A',-0.4,-0.4,'PICKUP','two','three'),('C1T-1','CARGO_A',-3.4,-1.4,'EXIT_STAGING_AREA','three','four'),('C1T-1','CARGO_A',-3.4,-3.4,'ENTER_PORT','four','five'),('C1T-1','CARGO_A',-1.4,-6.4,'DROPOFF','five','six'),('C1T-1','CARGO_B',0.6,-6.4,'PICKUP','six','seven'),('C1T-1','CARGO_B',2.6,-5.4,'PORT_CHECKPOINT','seven','eight'),('C1T-1','CARGO_B',-2.4,-2.4,'EXIT_PORT','eight','nine'); +UNLOCK TABLES; diff --git a/configuration/mysql/garage_port_drayage/port_drayage_lane1.sql b/configuration/mysql/garage_port_drayage/port_drayage_lane1.sql new file mode 100644 index 000000000..3d4431784 --- /dev/null +++ b/configuration/mysql/garage_port_drayage/port_drayage_lane1.sql @@ -0,0 +1,49 @@ +-- MySQL 8.0 for Linux amd64 (x86_64) and arm64 (aarch64) +-- +-- Host: 127.0.0.1 Database: PORT_DRAYAGE +-- ------------------------------------------------------ +-- Server version 7.6.0 +-- Current Database: `PORT_DRAYAGE` +-- + +CREATE DATABASE /*!32312 IF NOT EXISTS*/ `PORT_DRAYAGE` /*!40100 DEFAULT CHARACTER SET latin1 */; + +USE `PORT_DRAYAGE`; + +-- +-- Table structure for table `first_action` +-- + +DROP TABLE IF EXISTS `first_action`; +CREATE TABLE `first_action` ( + `cmv_id` varchar(20) NOT NULL, + `cargo_id` varchar(20) DEFAULT NULL, + `destination_lat` decimal(9,7) NOT NULL, + `destination_long` decimal(9,7) NOT NULL, + `operation` varchar(20) NOT NULL, + `action_id` varchar(36) NOT NULL, + `next_action` varchar(36) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +LOCK TABLES `first_action` WRITE; +INSERT INTO `first_action` VALUES ('C1T-1','NULL',-1.4,-1.4,'ENTER_STAGING_AREA','one','two'); +UNLOCK TABLES; + +-- +-- Table structure for table `freight` +-- + +DROP TABLE IF EXISTS `freight`; +CREATE TABLE `freight` ( + `cmv_id` varchar(20) NOT NULL, + `cargo_id` varchar(20) DEFAULT NULL, + `destination_lat` decimal(9,7) NOT NULL, + `destination_long` decimal(9,7) NOT NULL, + `operation` varchar(20) NOT NULL, + `action_id` varchar(36) NOT NULL, + `next_action` varchar(36) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +LOCK TABLES `freight` WRITE; +INSERT INTO `freight` VALUES ('C1T-1','NULL',-2.4,-2.4,'EXIT_PORT','zero','one'),('C1T-1','NULL',-1.4,-1.4,'ENTER_STAGING_AREA','one','two'),('C1T-1','CARGO_A',-0.4,-0.4,'PICKUP','two','three'),('C1T-1','CARGO_A',-3.4,-1.4,'EXIT_STAGING_AREA','three','four'),('C1T-1','CARGO_A',-3.4,-3.4,'ENTER_PORT','four','five'),('C1T-1','CARGO_A',-1.4,-6.4,'DROPOFF','five','six'),('C1T-1','CARGO_B',0.2,-6.2,'PICKUP','six','seven'),('C1T-1','CARGO_B',0.6,-5.4,'PORT_CHECKPOINT','seven','eight'),('C1T-1','CARGO_B',-2.4,-2.4,'EXIT_PORT','eight','nine'); +UNLOCK TABLES; diff --git a/configuration/mysql/garage_port_drayage/port_drayage_lane2.sql b/configuration/mysql/garage_port_drayage/port_drayage_lane2.sql new file mode 100644 index 000000000..bf5acb38c --- /dev/null +++ b/configuration/mysql/garage_port_drayage/port_drayage_lane2.sql @@ -0,0 +1,49 @@ +-- MySQL 8.0 for Linux amd64 (x86_64) and arm64 (aarch64) +-- +-- Host: 127.0.0.1 Database: PORT_DRAYAGE +-- ------------------------------------------------------ +-- Server version 7.6.0 +-- Current Database: `PORT_DRAYAGE` +-- + +CREATE DATABASE /*!32312 IF NOT EXISTS*/ `PORT_DRAYAGE` /*!40100 DEFAULT CHARACTER SET latin1 */; + +USE `PORT_DRAYAGE`; + +-- +-- Table structure for table `first_action` +-- + +DROP TABLE IF EXISTS `first_action`; +CREATE TABLE `first_action` ( + `cmv_id` varchar(20) NOT NULL, + `cargo_id` varchar(20) DEFAULT NULL, + `destination_lat` decimal(9,7) NOT NULL, + `destination_long` decimal(9,7) NOT NULL, + `operation` varchar(20) NOT NULL, + `action_id` varchar(36) NOT NULL, + `next_action` varchar(36) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +LOCK TABLES `first_action` WRITE; +INSERT INTO `first_action` VALUES ('C1T-1','NULL',-1.4,-1.4,'ENTER_STAGING_AREA','one','two'); +UNLOCK TABLES; + +-- +-- Table structure for table `freight` +-- + +DROP TABLE IF EXISTS `freight`; +CREATE TABLE `freight` ( + `cmv_id` varchar(20) NOT NULL, + `cargo_id` varchar(20) DEFAULT NULL, + `destination_lat` decimal(9,7) NOT NULL, + `destination_long` decimal(9,7) NOT NULL, + `operation` varchar(20) NOT NULL, + `action_id` varchar(36) NOT NULL, + `next_action` varchar(36) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +LOCK TABLES `freight` WRITE; +INSERT INTO `freight` VALUES ('C1T-1','NULL',-2.4,-2.4,'EXIT_PORT','zero','one'),('C1T-1','NULL',-1.4,-1.4,'ENTER_STAGING_AREA','one','two'),('C1T-1','CARGO_A',-0.4,-0.4,'PICKUP','two','three'),('C1T-1','CARGO_A',-3.4,-1.4,'EXIT_STAGING_AREA','three','four'),('C1T-1','CARGO_A',-3.4,-3.4,'ENTER_PORT','four','five'),('C1T-1','CARGO_A',-1.4,-6.4,'DROPOFF','five','six'),('C1T-1','CARGO_B',1.2,-6.2,'PICKUP','six','seven'),('C1T-1','CARGO_B',1.6,-5.4,'PORT_CHECKPOINT','seven','eight'),('C1T-1','CARGO_B',-2.4,-2.4,'EXIT_PORT','eight','nine'); +UNLOCK TABLES; diff --git a/configuration/mysql/garage_port_drayage/port_drayage_lane3.sql b/configuration/mysql/garage_port_drayage/port_drayage_lane3.sql new file mode 100644 index 000000000..cb49f0498 --- /dev/null +++ b/configuration/mysql/garage_port_drayage/port_drayage_lane3.sql @@ -0,0 +1,49 @@ +-- MySQL 8.0 for Linux amd64 (x86_64) and arm64 (aarch64) +-- +-- Host: 127.0.0.1 Database: PORT_DRAYAGE +-- ------------------------------------------------------ +-- Server version 7.6.0 +-- Current Database: `PORT_DRAYAGE` +-- + +CREATE DATABASE /*!32312 IF NOT EXISTS*/ `PORT_DRAYAGE` /*!40100 DEFAULT CHARACTER SET latin1 */; + +USE `PORT_DRAYAGE`; + +-- +-- Table structure for table `first_action` +-- + +DROP TABLE IF EXISTS `first_action`; +CREATE TABLE `first_action` ( + `cmv_id` varchar(20) NOT NULL, + `cargo_id` varchar(20) DEFAULT NULL, + `destination_lat` decimal(9,7) NOT NULL, + `destination_long` decimal(9,7) NOT NULL, + `operation` varchar(20) NOT NULL, + `action_id` varchar(36) NOT NULL, + `next_action` varchar(36) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +LOCK TABLES `first_action` WRITE; +INSERT INTO `first_action` VALUES ('C1T-1','NULL',-1.4,-1.4,'ENTER_STAGING_AREA','one','two'); +UNLOCK TABLES; + +-- +-- Table structure for table `freight` +-- + +DROP TABLE IF EXISTS `freight`; +CREATE TABLE `freight` ( + `cmv_id` varchar(20) NOT NULL, + `cargo_id` varchar(20) DEFAULT NULL, + `destination_lat` decimal(9,7) NOT NULL, + `destination_long` decimal(9,7) NOT NULL, + `operation` varchar(20) NOT NULL, + `action_id` varchar(36) NOT NULL, + `next_action` varchar(36) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +LOCK TABLES `freight` WRITE; +INSERT INTO `freight` VALUES ('C1T-1','NULL',-2.4,-2.4,'EXIT_PORT','zero','one'),('C1T-1','NULL',-1.4,-1.4,'ENTER_STAGING_AREA','one','two'),('C1T-1','CARGO_A',-0.4,-0.4,'PICKUP','two','three'),('C1T-1','CARGO_A',-3.4,-1.4,'EXIT_STAGING_AREA','three','four'),('C1T-1','CARGO_A',-3.4,-3.4,'ENTER_PORT','four','five'),('C1T-1','CARGO_A',-1.4,-6.4,'DROPOFF','five','six'),('C1T-1','CARGO_B',2.4,-6.2,'PICKUP','six','seven'),('C1T-1','CARGO_B',2.6,-5.4,'PORT_CHECKPOINT','seven','eight'),('C1T-1','CARGO_B',-2.4,-2.4,'EXIT_PORT','eight','nine'); +UNLOCK TABLES; From eb539afe75557c9d64c32bf229e8a7a03715a387 Mon Sep 17 00:00:00 2001 From: Will Martin Date: Fri, 16 Aug 2024 10:59:30 -0400 Subject: [PATCH 11/24] Fix ped plugin (#620) # PR Details ## Description These changes address a memory leak in the WebService/PSM functionality of the plugin. It also allows a user to switch between using FLIR(WebSocket) and PSM(WebService) without crashing or generating segmentation faults. ## Related Issue #497 ## Motivation and Context Users of this plugin have noticed memory loss and error code generation while using this plugin. Users have also requested a fix for the #497 issue. ## How Has This Been Tested? Tested using a script to send 1000 PSM XMLs to the plugin. No messages were lost. Tested using local FLIR camera to ensure functionality was maintained. Switched between DataProviders (PSM/FLIR) in various combinations to ensure threads were closed or joined properly. ## Types of changes - [X] Defect fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that cause existing functionality to change) ## Checklist: - [ ] I have added any new packages to the sonar-scanner.properties file - [ ] My change requires a change to the documentation. - [ ] I have updated the documentation accordingly. - [X] I have read the **CONTRIBUTING** document. [V2XHUB Contributing Guide](https://github.com/usdot-fhwa-OPS/V2X-Hub/blob/develop/Contributing.md) - [ ] I have added tests to cover my changes. - [ ] All new and existing tests passed. --- .../EmptyPlugin/CMakeLists.txt | 2 +- .../EmptyPlugin/src/EmptyPlugin.cpp | 2 +- ext/build.sh | 8 + src/tmx/TmxApi/CMakeLists.txt | 4 +- .../TmxApi/tmx/messages/message_document.hpp | 2 +- src/tmx/TmxApi/tmx/pugixml/pugiconfig.hpp | 74 - src/tmx/TmxApi/tmx/pugixml/pugixml.cpp | 12444 ---------------- src/tmx/TmxApi/tmx/pugixml/pugixml.hpp | 1400 -- src/tmx/TmxApi/tmx/pugixml/readme.txt | 52 - .../CommandPlugin/src/CommandPlugin.cpp | 4 +- src/v2i-hub/PedestrianPlugin/manifest.json | 5 - .../PedestrianPlugin/scripts/sendPSM.py | 41 + .../PedestrianPlugin/src/PedestrianPlugin.cpp | 337 +- .../src/include/PedestrianPlugin.hpp | 120 +- 14 files changed, 282 insertions(+), 14213 deletions(-) delete mode 100644 src/tmx/TmxApi/tmx/pugixml/pugiconfig.hpp delete mode 100644 src/tmx/TmxApi/tmx/pugixml/pugixml.cpp delete mode 100644 src/tmx/TmxApi/tmx/pugixml/pugixml.hpp delete mode 100644 src/tmx/TmxApi/tmx/pugixml/readme.txt create mode 100644 src/v2i-hub/PedestrianPlugin/scripts/sendPSM.py diff --git a/examples/tmx-exampleapps/EmptyPlugin/CMakeLists.txt b/examples/tmx-exampleapps/EmptyPlugin/CMakeLists.txt index 038a9c8ab..1914db8a7 100644 --- a/examples/tmx-exampleapps/EmptyPlugin/CMakeLists.txt +++ b/examples/tmx-exampleapps/EmptyPlugin/CMakeLists.txt @@ -2,4 +2,4 @@ PROJECT ( EmptyPlugin VERSION 3.0.0 LANGUAGES CXX ) BuildTmxPlugin ( ) -TARGET_LINK_LIBRARIES (${PROJECT_NAME} tmxutils) \ No newline at end of file +TARGET_LINK_LIBRARIES (${PROJECT_NAME} tmxutils pugixml) \ No newline at end of file diff --git a/examples/tmx-exampleapps/EmptyPlugin/src/EmptyPlugin.cpp b/examples/tmx-exampleapps/EmptyPlugin/src/EmptyPlugin.cpp index 370420a28..c2eff693d 100644 --- a/examples/tmx-exampleapps/EmptyPlugin/src/EmptyPlugin.cpp +++ b/examples/tmx-exampleapps/EmptyPlugin/src/EmptyPlugin.cpp @@ -52,7 +52,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/ext/build.sh b/ext/build.sh index d5c773685..50dc3e717 100755 --- a/ext/build.sh +++ b/ext/build.sh @@ -72,3 +72,11 @@ make -j${numCPU} make install popd +# pugixml +pushd /tmp +git clone https://github.com/zeux/pugixml.git --branch v1.14 +cd pugixml +cmake . +make -j${numCPU} +make install +popd diff --git a/src/tmx/TmxApi/CMakeLists.txt b/src/tmx/TmxApi/CMakeLists.txt index 448a66b54..320073f91 100644 --- a/src/tmx/TmxApi/CMakeLists.txt +++ b/src/tmx/TmxApi/CMakeLists.txt @@ -44,7 +44,7 @@ IF (NOT TMXAPI_NO_STATIC) TARGET_INCLUDE_DIRECTORIES (${PROJECT_NAME}Static SYSTEM PUBLIC $ ${Boost_INCLUDE_DIRS}) - TARGET_LINK_LIBRARIES (${PROJECT_NAME}Static ${ASN_J2735_LIBRARIES} ${Boost_LIBRARIES} ${ICU_LIBRARIES}) + TARGET_LINK_LIBRARIES (${PROJECT_NAME}Static ${ASN_J2735_LIBRARIES} ${Boost_LIBRARIES} ${ICU_LIBRARIES} pugixml) IF (Boost_DEFS) TARGET_COMPILE_DEFINITIONS (${PROJECT_NAME}Static PUBLIC ${Boost_DEFS}) ENDIF (Boost_DEFS) @@ -68,7 +68,7 @@ IF (NOT TMXAPI_NO_SHARED) TARGET_INCLUDE_DIRECTORIES (${PROJECT_NAME} SYSTEM PUBLIC $ ${Boost_INCLUDE_DIRS}) - TARGET_LINK_LIBRARIES (${PROJECT_NAME} PUBLIC ${ASN_J2735_LIBRARIES} ${Boost_LIBRARIES} ${ICU_LIBRARIES}) + TARGET_LINK_LIBRARIES (${PROJECT_NAME} PUBLIC ${ASN_J2735_LIBRARIES} ${Boost_LIBRARIES} ${ICU_LIBRARIES} pugixml) IF (Boost_DEFS) TARGET_COMPILE_DEFINITIONS (${PROJECT_NAME} PUBLIC ${Boost_DEFS}) ENDIF (Boost_DEFS) diff --git a/src/tmx/TmxApi/tmx/messages/message_document.hpp b/src/tmx/TmxApi/tmx/messages/message_document.hpp index d3c3d1db2..b58a7e8d8 100644 --- a/src/tmx/TmxApi/tmx/messages/message_document.hpp +++ b/src/tmx/TmxApi/tmx/messages/message_document.hpp @@ -11,7 +11,7 @@ #include #include -#include +#include namespace tmx { diff --git a/src/tmx/TmxApi/tmx/pugixml/pugiconfig.hpp b/src/tmx/TmxApi/tmx/pugixml/pugiconfig.hpp deleted file mode 100644 index e50b580bf..000000000 --- a/src/tmx/TmxApi/tmx/pugixml/pugiconfig.hpp +++ /dev/null @@ -1,74 +0,0 @@ -/** - * pugixml parser - version 1.7 - * -------------------------------------------------------- - * Copyright (C) 2006-2015, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) - * Report bugs and download new versions at http://pugixml.org/ - * - * This library is distributed under the MIT License. See notice at the end - * of this file. - * - * This work is based on the pugxml parser, which is: - * Copyright (C) 2003, by Kristen Wegner (kristen@tima.net) - */ - -#ifndef HEADER_PUGICONFIG_HPP -#define HEADER_PUGICONFIG_HPP - -// Uncomment this to enable wchar_t mode -// #define PUGIXML_WCHAR_MODE - -// Uncomment this to enable compact mode -// #define PUGIXML_COMPACT - -// Uncomment this to disable XPath -// #define PUGIXML_NO_XPATH - -// Uncomment this to disable STL -// #define PUGIXML_NO_STL - -// Uncomment this to disable exceptions -// #define PUGIXML_NO_EXCEPTIONS - -// Set this to control attributes for public classes/functions, i.e.: -// #define PUGIXML_API __declspec(dllexport) // to export all public symbols from DLL -// #define PUGIXML_CLASS __declspec(dllimport) // to import all classes from DLL -// #define PUGIXML_FUNCTION __fastcall // to set calling conventions to all public functions to fastcall -// In absence of PUGIXML_CLASS/PUGIXML_FUNCTION definitions PUGIXML_API is used instead - -// Tune these constants to adjust memory-related behavior -// #define PUGIXML_MEMORY_PAGE_SIZE 32768 -// #define PUGIXML_MEMORY_OUTPUT_STACK 10240 -// #define PUGIXML_MEMORY_XPATH_PAGE_SIZE 4096 - -// Uncomment this to switch to header-only version -// #define PUGIXML_HEADER_ONLY - -// Uncomment this to enable long long support -// #define PUGIXML_HAS_LONG_LONG - -#endif - -/** - * Copyright (c) 2006-2015 Arseny Kapoulkine - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ diff --git a/src/tmx/TmxApi/tmx/pugixml/pugixml.cpp b/src/tmx/TmxApi/tmx/pugixml/pugixml.cpp deleted file mode 100644 index 737733e64..000000000 --- a/src/tmx/TmxApi/tmx/pugixml/pugixml.cpp +++ /dev/null @@ -1,12444 +0,0 @@ -/** - * pugixml parser - version 1.7 - * -------------------------------------------------------- - * Copyright (C) 2006-2015, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) - * Report bugs and download new versions at http://pugixml.org/ - * - * This library is distributed under the MIT License. See notice at the end - * of this file. - * - * This work is based on the pugxml parser, which is: - * Copyright (C) 2003, by Kristen Wegner (kristen@tima.net) - */ - -#ifndef SOURCE_PUGIXML_CPP -#define SOURCE_PUGIXML_CPP - -#include "pugixml.hpp" - -#include -#include -#include -#include -#include - -#ifdef PUGIXML_WCHAR_MODE -# include -#endif - -#ifndef PUGIXML_NO_XPATH -# include -# include -# ifdef PUGIXML_NO_EXCEPTIONS -# include -# endif -#endif - -#ifndef PUGIXML_NO_STL -# include -# include -# include -#endif - -// For placement new -#include - -#ifdef _MSC_VER -# pragma warning(push) -# pragma warning(disable: 4127) // conditional expression is constant -# pragma warning(disable: 4324) // structure was padded due to __declspec(align()) -# pragma warning(disable: 4611) // interaction between '_setjmp' and C++ object destruction is non-portable -# pragma warning(disable: 4702) // unreachable code -# pragma warning(disable: 4996) // this function or variable may be unsafe -# pragma warning(disable: 4793) // function compiled as native: presence of '_setjmp' makes a function unmanaged -#endif - -#ifdef __INTEL_COMPILER -# pragma warning(disable: 177) // function was declared but never referenced -# pragma warning(disable: 279) // controlling expression is constant -# pragma warning(disable: 1478 1786) // function was declared "deprecated" -# pragma warning(disable: 1684) // conversion from pointer to same-sized integral type -#endif - -#if defined(__BORLANDC__) && defined(PUGIXML_HEADER_ONLY) -# pragma warn -8080 // symbol is declared but never used; disabling this inside push/pop bracket does not make the warning go away -#endif - -#ifdef __BORLANDC__ -# pragma option push -# pragma warn -8008 // condition is always false -# pragma warn -8066 // unreachable code -#endif - -#ifdef __SNC__ -// Using diag_push/diag_pop does not disable the warnings inside templates due to a compiler bug -# pragma diag_suppress=178 // function was declared but never referenced -# pragma diag_suppress=237 // controlling expression is constant -#endif - -// Inlining controls -#if defined(_MSC_VER) && _MSC_VER >= 1300 -# define PUGI__NO_INLINE __declspec(noinline) -#elif defined(__GNUC__) -# define PUGI__NO_INLINE __attribute__((noinline)) -#else -# define PUGI__NO_INLINE -#endif - -// Branch weight controls -#if defined(__GNUC__) -# define PUGI__UNLIKELY(cond) __builtin_expect(cond, 0) -#else -# define PUGI__UNLIKELY(cond) (cond) -#endif - -// Simple static assertion -#define PUGI__STATIC_ASSERT(cond) { static const char condition_failed[(cond) ? 1 : -1] = {0}; (void)condition_failed[0]; } - -// Digital Mars C++ bug workaround for passing char loaded from memory via stack -#ifdef __DMC__ -# define PUGI__DMC_VOLATILE volatile -#else -# define PUGI__DMC_VOLATILE -#endif - -// Borland C++ bug workaround for not defining ::memcpy depending on header include order (can't always use std::memcpy because some compilers don't have it at all) -#if defined(__BORLANDC__) && !defined(__MEM_H_USING_LIST) -using std::memcpy; -using std::memmove; -using std::memset; -#endif - -// In some environments MSVC is a compiler but the CRT lacks certain MSVC-specific features -#if defined(_MSC_VER) && !defined(__S3E__) -# define PUGI__MSVC_CRT_VERSION _MSC_VER -#endif - -#ifdef PUGIXML_HEADER_ONLY -# define PUGI__NS_BEGIN namespace pugi { namespace impl { -# define PUGI__NS_END } } -# define PUGI__FN inline -# define PUGI__FN_NO_INLINE inline -#else -# if defined(_MSC_VER) && _MSC_VER < 1300 // MSVC6 seems to have an amusing bug with anonymous namespaces inside namespaces -# define PUGI__NS_BEGIN namespace pugi { namespace impl { -# define PUGI__NS_END } } -# else -# define PUGI__NS_BEGIN namespace pugi { namespace impl { namespace { -# define PUGI__NS_END } } } -# endif -# define PUGI__FN -# define PUGI__FN_NO_INLINE PUGI__NO_INLINE -#endif - -// uintptr_t -#if !defined(_MSC_VER) || _MSC_VER >= 1600 -# include -#else -namespace pugi -{ -# ifndef _UINTPTR_T_DEFINED - typedef size_t uintptr_t; -# endif - - typedef unsigned __int8 uint8_t; - typedef unsigned __int16 uint16_t; - typedef unsigned __int32 uint32_t; -} -#endif - -// Memory allocation -PUGI__NS_BEGIN - PUGI__FN void* default_allocate(size_t size) - { - return malloc(size); - } - - PUGI__FN void default_deallocate(void* ptr) - { - free(ptr); - } - - template - struct xml_memory_management_function_storage - { - static allocation_function allocate; - static deallocation_function deallocate; - }; - - // Global allocation functions are stored in class statics so that in header mode linker deduplicates them - // Without a template<> we'll get multiple definitions of the same static - template allocation_function xml_memory_management_function_storage::allocate = default_allocate; - template deallocation_function xml_memory_management_function_storage::deallocate = default_deallocate; - - typedef xml_memory_management_function_storage xml_memory; -PUGI__NS_END - -// String utilities -PUGI__NS_BEGIN - // Get string length - PUGI__FN size_t strlength(const char_t* s) - { - assert(s); - - #ifdef PUGIXML_WCHAR_MODE - return wcslen(s); - #else - return strlen(s); - #endif - } - - // Compare two strings - PUGI__FN bool strequal(const char_t* src, const char_t* dst) - { - assert(src && dst); - - #ifdef PUGIXML_WCHAR_MODE - return wcscmp(src, dst) == 0; - #else - return strcmp(src, dst) == 0; - #endif - } - - // Compare lhs with [rhs_begin, rhs_end) - PUGI__FN bool strequalrange(const char_t* lhs, const char_t* rhs, size_t count) - { - for (size_t i = 0; i < count; ++i) - if (lhs[i] != rhs[i]) - return false; - - return lhs[count] == 0; - } - - // Get length of wide string, even if CRT lacks wide character support - PUGI__FN size_t strlength_wide(const wchar_t* s) - { - assert(s); - - #ifdef PUGIXML_WCHAR_MODE - return wcslen(s); - #else - const wchar_t* end = s; - while (*end) end++; - return static_cast(end - s); - #endif - } -PUGI__NS_END - -// auto_ptr-like object for exception recovery -PUGI__NS_BEGIN - template struct auto_deleter - { - T* data; - D deleter; - - auto_deleter(T* data_, D deleter_): data(data_), deleter(deleter_) - { - } - - ~auto_deleter() - { - if (data) deleter(data); - } - - T* release() - { - T* result = data; - data = 0; - return result; - } - }; -PUGI__NS_END - -#ifdef PUGIXML_COMPACT -PUGI__NS_BEGIN - class compact_hash_table - { - public: - compact_hash_table(): _items(0), _capacity(0), _count(0) - { - } - - void clear() - { - if (_items) - { - xml_memory::deallocate(_items); - _items = 0; - _capacity = 0; - _count = 0; - } - } - - void** find(const void* key) - { - assert(key); - - if (_capacity == 0) return 0; - - size_t hashmod = _capacity - 1; - size_t bucket = hash(key) & hashmod; - - for (size_t probe = 0; probe <= hashmod; ++probe) - { - item_t& probe_item = _items[bucket]; - - if (probe_item.key == key) - return &probe_item.value; - - if (probe_item.key == 0) - return 0; - - // hash collision, quadratic probing - bucket = (bucket + probe + 1) & hashmod; - } - - assert(!"Hash table is full"); - return 0; - } - - void** insert(const void* key) - { - assert(key); - assert(_capacity != 0 && _count < _capacity - _capacity / 4); - - size_t hashmod = _capacity - 1; - size_t bucket = hash(key) & hashmod; - - for (size_t probe = 0; probe <= hashmod; ++probe) - { - item_t& probe_item = _items[bucket]; - - if (probe_item.key == 0) - { - probe_item.key = key; - _count++; - return &probe_item.value; - } - - if (probe_item.key == key) - return &probe_item.value; - - // hash collision, quadratic probing - bucket = (bucket + probe + 1) & hashmod; - } - - assert(!"Hash table is full"); - return 0; - } - - bool reserve() - { - if (_count + 16 >= _capacity - _capacity / 4) - return rehash(); - - return true; - } - - private: - struct item_t - { - const void* key; - void* value; - }; - - item_t* _items; - size_t _capacity; - - size_t _count; - - bool rehash(); - - static unsigned int hash(const void* key) - { - unsigned int h = static_cast(reinterpret_cast(key)); - - // MurmurHash3 32-bit finalizer - h ^= h >> 16; - h *= 0x85ebca6bu; - h ^= h >> 13; - h *= 0xc2b2ae35u; - h ^= h >> 16; - - return h; - } - }; - - PUGI__FN_NO_INLINE bool compact_hash_table::rehash() - { - compact_hash_table rt; - rt._capacity = (_capacity == 0) ? 32 : _capacity * 2; - rt._items = static_cast(xml_memory::allocate(sizeof(item_t) * rt._capacity)); - - if (!rt._items) - return false; - - memset(rt._items, 0, sizeof(item_t) * rt._capacity); - - for (size_t i = 0; i < _capacity; ++i) - if (_items[i].key) - *rt.insert(_items[i].key) = _items[i].value; - - if (_items) - xml_memory::deallocate(_items); - - _capacity = rt._capacity; - _items = rt._items; - - assert(_count == rt._count); - - return true; - } - -PUGI__NS_END -#endif - -PUGI__NS_BEGIN - static const size_t xml_memory_page_size = - #ifdef PUGIXML_MEMORY_PAGE_SIZE - PUGIXML_MEMORY_PAGE_SIZE - #else - 32768 - #endif - ; - -#ifdef PUGIXML_COMPACT - static const uintptr_t xml_memory_block_alignment = 4; - - static const uintptr_t xml_memory_page_alignment = sizeof(void*); -#else - static const uintptr_t xml_memory_block_alignment = sizeof(void*); - - static const uintptr_t xml_memory_page_alignment = 64; - static const uintptr_t xml_memory_page_pointer_mask = ~(xml_memory_page_alignment - 1); -#endif - - // extra metadata bits - static const uintptr_t xml_memory_page_contents_shared_mask = 32; - static const uintptr_t xml_memory_page_name_allocated_mask = 16; - static const uintptr_t xml_memory_page_value_allocated_mask = 8; - static const uintptr_t xml_memory_page_type_mask = 7; - - // combined masks for string uniqueness - static const uintptr_t xml_memory_page_name_allocated_or_shared_mask = xml_memory_page_name_allocated_mask | xml_memory_page_contents_shared_mask; - static const uintptr_t xml_memory_page_value_allocated_or_shared_mask = xml_memory_page_value_allocated_mask | xml_memory_page_contents_shared_mask; - -#ifdef PUGIXML_COMPACT - #define PUGI__GETPAGE_IMPL(header) (header).get_page() -#else - #define PUGI__GETPAGE_IMPL(header) reinterpret_cast((header) & impl::xml_memory_page_pointer_mask) -#endif - - #define PUGI__GETPAGE(n) PUGI__GETPAGE_IMPL((n)->header) - #define PUGI__NODETYPE(n) static_cast(((n)->header & impl::xml_memory_page_type_mask) + 1) - - struct xml_allocator; - - struct xml_memory_page - { - static xml_memory_page* construct(void* memory) - { - xml_memory_page* result = static_cast(memory); - - result->allocator = 0; - result->prev = 0; - result->next = 0; - result->busy_size = 0; - result->freed_size = 0; - - #ifdef PUGIXML_COMPACT - result->compact_string_base = 0; - result->compact_shared_parent = 0; - result->compact_page_marker = 0; - #endif - - return result; - } - - xml_allocator* allocator; - - xml_memory_page* prev; - xml_memory_page* next; - - size_t busy_size; - size_t freed_size; - - #ifdef PUGIXML_COMPACT - char_t* compact_string_base; - void* compact_shared_parent; - uint32_t* compact_page_marker; - #endif - }; - - struct xml_memory_string_header - { - uint16_t page_offset; // offset from page->data - uint16_t full_size; // 0 if string occupies whole page - }; - - struct xml_allocator - { - xml_allocator(xml_memory_page* root): _root(root), _busy_size(root->busy_size) - { - #ifdef PUGIXML_COMPACT - _hash = 0; - #endif - } - - xml_memory_page* allocate_page(size_t data_size) - { - size_t size = sizeof(xml_memory_page) + data_size; - - // allocate block with some alignment, leaving memory for worst-case padding - void* memory = xml_memory::allocate(size + xml_memory_page_alignment); - if (!memory) return 0; - - // align to next page boundary (note: this guarantees at least 1 usable byte before the page) - char* page_memory = reinterpret_cast((reinterpret_cast(memory) + xml_memory_page_alignment) & ~(xml_memory_page_alignment - 1)); - - // prepare page structure - xml_memory_page* page = xml_memory_page::construct(page_memory); - assert(page); - - page->allocator = _root->allocator; - - // record the offset for freeing the memory block - assert(page_memory > memory && page_memory - static_cast(memory) <= 127); - page_memory[-1] = static_cast(page_memory - static_cast(memory)); - - return page; - } - - static void deallocate_page(xml_memory_page* page) - { - char* page_memory = reinterpret_cast(page); - - xml_memory::deallocate(page_memory - page_memory[-1]); - } - - void* allocate_memory_oob(size_t size, xml_memory_page*& out_page); - - void* allocate_memory(size_t size, xml_memory_page*& out_page) - { - if (PUGI__UNLIKELY(_busy_size + size > xml_memory_page_size)) - return allocate_memory_oob(size, out_page); - - void* buf = reinterpret_cast(_root) + sizeof(xml_memory_page) + _busy_size; - - _busy_size += size; - - out_page = _root; - - return buf; - } - - #ifdef PUGIXML_COMPACT - void* allocate_object(size_t size, xml_memory_page*& out_page) - { - void* result = allocate_memory(size + sizeof(uint32_t), out_page); - if (!result) return 0; - - // adjust for marker - ptrdiff_t offset = static_cast(result) - reinterpret_cast(out_page->compact_page_marker); - - if (PUGI__UNLIKELY(static_cast(offset) >= 256 * xml_memory_block_alignment)) - { - // insert new marker - uint32_t* marker = static_cast(result); - - *marker = static_cast(reinterpret_cast(marker) - reinterpret_cast(out_page)); - out_page->compact_page_marker = marker; - - // since we don't reuse the page space until we reallocate it, we can just pretend that we freed the marker block - // this will make sure deallocate_memory correctly tracks the size - out_page->freed_size += sizeof(uint32_t); - - return marker + 1; - } - else - { - // roll back uint32_t part - _busy_size -= sizeof(uint32_t); - - return result; - } - } - #else - void* allocate_object(size_t size, xml_memory_page*& out_page) - { - return allocate_memory(size, out_page); - } - #endif - - void deallocate_memory(void* ptr, size_t size, xml_memory_page* page) - { - if (page == _root) page->busy_size = _busy_size; - - assert(ptr >= reinterpret_cast(page) + sizeof(xml_memory_page) && ptr < reinterpret_cast(page) + sizeof(xml_memory_page) + page->busy_size); - (void)!ptr; - - page->freed_size += size; - assert(page->freed_size <= page->busy_size); - - if (page->freed_size == page->busy_size) - { - if (page->next == 0) - { - assert(_root == page); - - // top page freed, just reset sizes - page->busy_size = 0; - page->freed_size = 0; - - #ifdef PUGIXML_COMPACT - // reset compact state to maximize efficiency - page->compact_string_base = 0; - page->compact_shared_parent = 0; - page->compact_page_marker = 0; - #endif - - _busy_size = 0; - } - else - { - assert(_root != page); - assert(page->prev); - - // remove from the list - page->prev->next = page->next; - page->next->prev = page->prev; - - // deallocate - deallocate_page(page); - } - } - } - - char_t* allocate_string(size_t length) - { - static const size_t max_encoded_offset = (1 << 16) * xml_memory_block_alignment; - - PUGI__STATIC_ASSERT(xml_memory_page_size <= max_encoded_offset); - - // allocate memory for string and header block - size_t size = sizeof(xml_memory_string_header) + length * sizeof(char_t); - - // round size up to block alignment boundary - size_t full_size = (size + (xml_memory_block_alignment - 1)) & ~(xml_memory_block_alignment - 1); - - xml_memory_page* page; - xml_memory_string_header* header = static_cast(allocate_memory(full_size, page)); - - if (!header) return 0; - - // setup header - ptrdiff_t page_offset = reinterpret_cast(header) - reinterpret_cast(page) - sizeof(xml_memory_page); - - assert(page_offset % xml_memory_block_alignment == 0); - assert(page_offset >= 0 && static_cast(page_offset) < max_encoded_offset); - header->page_offset = static_cast(static_cast(page_offset) / xml_memory_block_alignment); - - // full_size == 0 for large strings that occupy the whole page - assert(full_size % xml_memory_block_alignment == 0); - assert(full_size < max_encoded_offset || (page->busy_size == full_size && page_offset == 0)); - header->full_size = static_cast(full_size < max_encoded_offset ? full_size / xml_memory_block_alignment : 0); - - // round-trip through void* to avoid 'cast increases required alignment of target type' warning - // header is guaranteed a pointer-sized alignment, which should be enough for char_t - return static_cast(static_cast(header + 1)); - } - - void deallocate_string(char_t* string) - { - // this function casts pointers through void* to avoid 'cast increases required alignment of target type' warnings - // we're guaranteed the proper (pointer-sized) alignment on the input string if it was allocated via allocate_string - - // get header - xml_memory_string_header* header = static_cast(static_cast(string)) - 1; - assert(header); - - // deallocate - size_t page_offset = sizeof(xml_memory_page) + header->page_offset * xml_memory_block_alignment; - xml_memory_page* page = reinterpret_cast(static_cast(reinterpret_cast(header) - page_offset)); - - // if full_size == 0 then this string occupies the whole page - size_t full_size = header->full_size == 0 ? page->busy_size : header->full_size * xml_memory_block_alignment; - - deallocate_memory(header, full_size, page); - } - - bool reserve() - { - #ifdef PUGIXML_COMPACT - return _hash->reserve(); - #else - return true; - #endif - } - - xml_memory_page* _root; - size_t _busy_size; - - #ifdef PUGIXML_COMPACT - compact_hash_table* _hash; - #endif - }; - - PUGI__FN_NO_INLINE void* xml_allocator::allocate_memory_oob(size_t size, xml_memory_page*& out_page) - { - const size_t large_allocation_threshold = xml_memory_page_size / 4; - - xml_memory_page* page = allocate_page(size <= large_allocation_threshold ? xml_memory_page_size : size); - out_page = page; - - if (!page) return 0; - - if (size <= large_allocation_threshold) - { - _root->busy_size = _busy_size; - - // insert page at the end of linked list - page->prev = _root; - _root->next = page; - _root = page; - - _busy_size = size; - } - else - { - // insert page before the end of linked list, so that it is deleted as soon as possible - // the last page is not deleted even if it's empty (see deallocate_memory) - assert(_root->prev); - - page->prev = _root->prev; - page->next = _root; - - _root->prev->next = page; - _root->prev = page; - - page->busy_size = size; - } - - return reinterpret_cast(page) + sizeof(xml_memory_page); - } -PUGI__NS_END - -#ifdef PUGIXML_COMPACT -PUGI__NS_BEGIN - static const uintptr_t compact_alignment_log2 = 2; - static const uintptr_t compact_alignment = 1 << compact_alignment_log2; - - class compact_header - { - public: - compact_header(xml_memory_page* page, unsigned int flags) - { - PUGI__STATIC_ASSERT(xml_memory_block_alignment == compact_alignment); - - ptrdiff_t offset = (reinterpret_cast(this) - reinterpret_cast(page->compact_page_marker)); - assert(offset % compact_alignment == 0 && static_cast(offset) < 256 * compact_alignment); - - _page = static_cast(offset >> compact_alignment_log2); - _flags = static_cast(flags); - } - - void operator&=(uintptr_t mod) - { - _flags &= static_cast(mod); - } - - void operator|=(uintptr_t mod) - { - _flags |= static_cast(mod); - } - - uintptr_t operator&(uintptr_t mod) const - { - return _flags & mod; - } - - xml_memory_page* get_page() const - { - // round-trip through void* to silence 'cast increases required alignment of target type' warnings - const char* page_marker = reinterpret_cast(this) - (_page << compact_alignment_log2); - const char* page = page_marker - *reinterpret_cast(static_cast(page_marker)); - - return const_cast(reinterpret_cast(static_cast(page))); - } - - private: - unsigned char _page; - unsigned char _flags; - }; - - PUGI__FN xml_memory_page* compact_get_page(const void* object, int header_offset) - { - const compact_header* header = reinterpret_cast(static_cast(object) - header_offset); - - return header->get_page(); - } - - template PUGI__FN_NO_INLINE T* compact_get_value(const void* object) - { - return static_cast(*compact_get_page(object, header_offset)->allocator->_hash->find(object)); - } - - template PUGI__FN_NO_INLINE void compact_set_value(const void* object, T* value) - { - *compact_get_page(object, header_offset)->allocator->_hash->insert(object) = value; - } - - template class compact_pointer - { - public: - compact_pointer(): _data(0) - { - } - - void operator=(const compact_pointer& rhs) - { - *this = rhs + 0; - } - - void operator=(T* value) - { - if (value) - { - // value is guaranteed to be compact-aligned; 'this' is not - // our decoding is based on 'this' aligned to compact alignment downwards (see operator T*) - // so for negative offsets (e.g. -3) we need to adjust the diff by compact_alignment - 1 to - // compensate for arithmetic shift rounding for negative values - ptrdiff_t diff = reinterpret_cast(value) - reinterpret_cast(this); - ptrdiff_t offset = ((diff + int(compact_alignment - 1)) >> compact_alignment_log2) - start; - - if (static_cast(offset) <= 253) - _data = static_cast(offset + 1); - else - { - compact_set_value(this, value); - - _data = 255; - } - } - else - _data = 0; - } - - operator T*() const - { - if (_data) - { - if (_data < 255) - { - uintptr_t base = reinterpret_cast(this) & ~(compact_alignment - 1); - - return reinterpret_cast(base + ((_data - 1 + start) << compact_alignment_log2)); - } - else - return compact_get_value(this); - } - else - return 0; - } - - T* operator->() const - { - return *this; - } - - private: - unsigned char _data; - }; - - template class compact_pointer_parent - { - public: - compact_pointer_parent(): _data(0) - { - } - - void operator=(const compact_pointer_parent& rhs) - { - *this = rhs + 0; - } - - void operator=(T* value) - { - if (value) - { - // value is guaranteed to be compact-aligned; 'this' is not - // our decoding is based on 'this' aligned to compact alignment downwards (see operator T*) - // so for negative offsets (e.g. -3) we need to adjust the diff by compact_alignment - 1 to - // compensate for arithmetic shift behavior for negative values - ptrdiff_t diff = reinterpret_cast(value) - reinterpret_cast(this); - ptrdiff_t offset = ((diff + int(compact_alignment - 1)) >> compact_alignment_log2) + 65533; - - if (static_cast(offset) <= 65533) - { - _data = static_cast(offset + 1); - } - else - { - xml_memory_page* page = compact_get_page(this, header_offset); - - if (PUGI__UNLIKELY(page->compact_shared_parent == 0)) - page->compact_shared_parent = value; - - if (page->compact_shared_parent == value) - { - _data = 65534; - } - else - { - compact_set_value(this, value); - - _data = 65535; - } - } - } - else - { - _data = 0; - } - } - - operator T*() const - { - if (_data) - { - if (_data < 65534) - { - uintptr_t base = reinterpret_cast(this) & ~(compact_alignment - 1); - - return reinterpret_cast(base + ((_data - 1 - 65533) << compact_alignment_log2)); - } - else if (_data == 65534) - return static_cast(compact_get_page(this, header_offset)->compact_shared_parent); - else - return compact_get_value(this); - } - else - return 0; - } - - T* operator->() const - { - return *this; - } - - private: - uint16_t _data; - }; - - template class compact_string - { - public: - compact_string(): _data(0) - { - } - - void operator=(const compact_string& rhs) - { - *this = rhs + 0; - } - - void operator=(char_t* value) - { - if (value) - { - xml_memory_page* page = compact_get_page(this, header_offset); - - if (PUGI__UNLIKELY(page->compact_string_base == 0)) - page->compact_string_base = value; - - ptrdiff_t offset = value - page->compact_string_base; - - if (static_cast(offset) < (65535 << 7)) - { - // round-trip through void* to silence 'cast increases required alignment of target type' warnings - uint16_t* base = reinterpret_cast(static_cast(reinterpret_cast(this) - base_offset)); - - if (*base == 0) - { - *base = static_cast((offset >> 7) + 1); - _data = static_cast((offset & 127) + 1); - } - else - { - ptrdiff_t remainder = offset - ((*base - 1) << 7); - - if (static_cast(remainder) <= 253) - { - _data = static_cast(remainder + 1); - } - else - { - compact_set_value(this, value); - - _data = 255; - } - } - } - else - { - compact_set_value(this, value); - - _data = 255; - } - } - else - { - _data = 0; - } - } - - operator char_t*() const - { - if (_data) - { - if (_data < 255) - { - xml_memory_page* page = compact_get_page(this, header_offset); - - // round-trip through void* to silence 'cast increases required alignment of target type' warnings - const uint16_t* base = reinterpret_cast(static_cast(reinterpret_cast(this) - base_offset)); - assert(*base); - - ptrdiff_t offset = ((*base - 1) << 7) + (_data - 1); - - return page->compact_string_base + offset; - } - else - { - return compact_get_value(this); - } - } - else - return 0; - } - - private: - unsigned char _data; - }; -PUGI__NS_END -#endif - -#ifdef PUGIXML_COMPACT -namespace pugi -{ - struct xml_attribute_struct - { - xml_attribute_struct(impl::xml_memory_page* page): header(page, 0), namevalue_base(0) - { - PUGI__STATIC_ASSERT(sizeof(xml_attribute_struct) == 8); - } - - impl::compact_header header; - - uint16_t namevalue_base; - - impl::compact_string<4, 2> name; - impl::compact_string<5, 3> value; - - impl::compact_pointer prev_attribute_c; - impl::compact_pointer next_attribute; - }; - - struct xml_node_struct - { - xml_node_struct(impl::xml_memory_page* page, xml_node_type type): header(page, type - 1), namevalue_base(0) - { - PUGI__STATIC_ASSERT(sizeof(xml_node_struct) == 12); - } - - impl::compact_header header; - - uint16_t namevalue_base; - - impl::compact_string<4, 2> name; - impl::compact_string<5, 3> value; - - impl::compact_pointer_parent parent; - - impl::compact_pointer first_child; - - impl::compact_pointer prev_sibling_c; - impl::compact_pointer next_sibling; - - impl::compact_pointer first_attribute; - }; -} -#else -namespace pugi -{ - struct xml_attribute_struct - { - xml_attribute_struct(impl::xml_memory_page* page): header(reinterpret_cast(page)), name(0), value(0), prev_attribute_c(0), next_attribute(0) - { - } - - uintptr_t header; - - char_t* name; - char_t* value; - - xml_attribute_struct* prev_attribute_c; - xml_attribute_struct* next_attribute; - }; - - struct xml_node_struct - { - xml_node_struct(impl::xml_memory_page* page, xml_node_type type): header(reinterpret_cast(page) | (type - 1)), name(0), value(0), parent(0), first_child(0), prev_sibling_c(0), next_sibling(0), first_attribute(0) - { - } - - uintptr_t header; - - char_t* name; - char_t* value; - - xml_node_struct* parent; - - xml_node_struct* first_child; - - xml_node_struct* prev_sibling_c; - xml_node_struct* next_sibling; - - xml_attribute_struct* first_attribute; - }; -} -#endif - -PUGI__NS_BEGIN - struct xml_extra_buffer - { - char_t* buffer; - xml_extra_buffer* next; - }; - - struct xml_document_struct: public xml_node_struct, public xml_allocator - { - xml_document_struct(xml_memory_page* page): xml_node_struct(page, node_document), xml_allocator(page), buffer(0), extra_buffers(0) - { - #ifdef PUGIXML_COMPACT - _hash = &hash; - #endif - } - - const char_t* buffer; - - xml_extra_buffer* extra_buffers; - - #ifdef PUGIXML_COMPACT - compact_hash_table hash; - #endif - }; - - template inline xml_allocator& get_allocator(const Object* object) - { - assert(object); - - return *PUGI__GETPAGE(object)->allocator; - } - - template inline xml_document_struct& get_document(const Object* object) - { - assert(object); - - return *static_cast(PUGI__GETPAGE(object)->allocator); - } -PUGI__NS_END - -// Low-level DOM operations -PUGI__NS_BEGIN - inline xml_attribute_struct* allocate_attribute(xml_allocator& alloc) - { - xml_memory_page* page; - void* memory = alloc.allocate_object(sizeof(xml_attribute_struct), page); - if (!memory) return 0; - - return new (memory) xml_attribute_struct(page); - } - - inline xml_node_struct* allocate_node(xml_allocator& alloc, xml_node_type type) - { - xml_memory_page* page; - void* memory = alloc.allocate_object(sizeof(xml_node_struct), page); - if (!memory) return 0; - - return new (memory) xml_node_struct(page, type); - } - - inline void destroy_attribute(xml_attribute_struct* a, xml_allocator& alloc) - { - if (a->header & impl::xml_memory_page_name_allocated_mask) - alloc.deallocate_string(a->name); - - if (a->header & impl::xml_memory_page_value_allocated_mask) - alloc.deallocate_string(a->value); - - alloc.deallocate_memory(a, sizeof(xml_attribute_struct), PUGI__GETPAGE(a)); - } - - inline void destroy_node(xml_node_struct* n, xml_allocator& alloc) - { - if (n->header & impl::xml_memory_page_name_allocated_mask) - alloc.deallocate_string(n->name); - - if (n->header & impl::xml_memory_page_value_allocated_mask) - alloc.deallocate_string(n->value); - - for (xml_attribute_struct* attr = n->first_attribute; attr; ) - { - xml_attribute_struct* next = attr->next_attribute; - - destroy_attribute(attr, alloc); - - attr = next; - } - - for (xml_node_struct* child = n->first_child; child; ) - { - xml_node_struct* next = child->next_sibling; - - destroy_node(child, alloc); - - child = next; - } - - alloc.deallocate_memory(n, sizeof(xml_node_struct), PUGI__GETPAGE(n)); - } - - inline void append_node(xml_node_struct* child, xml_node_struct* node) - { - child->parent = node; - - xml_node_struct* head = node->first_child; - - if (head) - { - xml_node_struct* tail = head->prev_sibling_c; - - tail->next_sibling = child; - child->prev_sibling_c = tail; - head->prev_sibling_c = child; - } - else - { - node->first_child = child; - child->prev_sibling_c = child; - } - } - - inline void prepend_node(xml_node_struct* child, xml_node_struct* node) - { - child->parent = node; - - xml_node_struct* head = node->first_child; - - if (head) - { - child->prev_sibling_c = head->prev_sibling_c; - head->prev_sibling_c = child; - } - else - child->prev_sibling_c = child; - - child->next_sibling = head; - node->first_child = child; - } - - inline void insert_node_after(xml_node_struct* child, xml_node_struct* node) - { - xml_node_struct* parent = node->parent; - - child->parent = parent; - - if (node->next_sibling) - node->next_sibling->prev_sibling_c = child; - else - parent->first_child->prev_sibling_c = child; - - child->next_sibling = node->next_sibling; - child->prev_sibling_c = node; - - node->next_sibling = child; - } - - inline void insert_node_before(xml_node_struct* child, xml_node_struct* node) - { - xml_node_struct* parent = node->parent; - - child->parent = parent; - - if (node->prev_sibling_c->next_sibling) - node->prev_sibling_c->next_sibling = child; - else - parent->first_child = child; - - child->prev_sibling_c = node->prev_sibling_c; - child->next_sibling = node; - - node->prev_sibling_c = child; - } - - inline void remove_node(xml_node_struct* node) - { - xml_node_struct* parent = node->parent; - - if (node->next_sibling) - node->next_sibling->prev_sibling_c = node->prev_sibling_c; - else - parent->first_child->prev_sibling_c = node->prev_sibling_c; - - if (node->prev_sibling_c->next_sibling) - node->prev_sibling_c->next_sibling = node->next_sibling; - else - parent->first_child = node->next_sibling; - - node->parent = 0; - node->prev_sibling_c = 0; - node->next_sibling = 0; - } - - inline void append_attribute(xml_attribute_struct* attr, xml_node_struct* node) - { - xml_attribute_struct* head = node->first_attribute; - - if (head) - { - xml_attribute_struct* tail = head->prev_attribute_c; - - tail->next_attribute = attr; - attr->prev_attribute_c = tail; - head->prev_attribute_c = attr; - } - else - { - node->first_attribute = attr; - attr->prev_attribute_c = attr; - } - } - - inline void prepend_attribute(xml_attribute_struct* attr, xml_node_struct* node) - { - xml_attribute_struct* head = node->first_attribute; - - if (head) - { - attr->prev_attribute_c = head->prev_attribute_c; - head->prev_attribute_c = attr; - } - else - attr->prev_attribute_c = attr; - - attr->next_attribute = head; - node->first_attribute = attr; - } - - inline void insert_attribute_after(xml_attribute_struct* attr, xml_attribute_struct* place, xml_node_struct* node) - { - if (place->next_attribute) - place->next_attribute->prev_attribute_c = attr; - else - node->first_attribute->prev_attribute_c = attr; - - attr->next_attribute = place->next_attribute; - attr->prev_attribute_c = place; - place->next_attribute = attr; - } - - inline void insert_attribute_before(xml_attribute_struct* attr, xml_attribute_struct* place, xml_node_struct* node) - { - if (place->prev_attribute_c->next_attribute) - place->prev_attribute_c->next_attribute = attr; - else - node->first_attribute = attr; - - attr->prev_attribute_c = place->prev_attribute_c; - attr->next_attribute = place; - place->prev_attribute_c = attr; - } - - inline void remove_attribute(xml_attribute_struct* attr, xml_node_struct* node) - { - if (attr->next_attribute) - attr->next_attribute->prev_attribute_c = attr->prev_attribute_c; - else - node->first_attribute->prev_attribute_c = attr->prev_attribute_c; - - if (attr->prev_attribute_c->next_attribute) - attr->prev_attribute_c->next_attribute = attr->next_attribute; - else - node->first_attribute = attr->next_attribute; - - attr->prev_attribute_c = 0; - attr->next_attribute = 0; - } - - PUGI__FN_NO_INLINE xml_node_struct* append_new_node(xml_node_struct* node, xml_allocator& alloc, xml_node_type type = node_element) - { - if (!alloc.reserve()) return 0; - - xml_node_struct* child = allocate_node(alloc, type); - if (!child) return 0; - - append_node(child, node); - - return child; - } - - PUGI__FN_NO_INLINE xml_attribute_struct* append_new_attribute(xml_node_struct* node, xml_allocator& alloc) - { - if (!alloc.reserve()) return 0; - - xml_attribute_struct* attr = allocate_attribute(alloc); - if (!attr) return 0; - - append_attribute(attr, node); - - return attr; - } -PUGI__NS_END - -// Helper classes for code generation -PUGI__NS_BEGIN - struct opt_false - { - enum { value = 0 }; - }; - - struct opt_true - { - enum { value = 1 }; - }; -PUGI__NS_END - -// Unicode utilities -PUGI__NS_BEGIN - inline uint16_t endian_swap(uint16_t value) - { - return static_cast(((value & 0xff) << 8) | (value >> 8)); - } - - inline uint32_t endian_swap(uint32_t value) - { - return ((value & 0xff) << 24) | ((value & 0xff00) << 8) | ((value & 0xff0000) >> 8) | (value >> 24); - } - - struct utf8_counter - { - typedef size_t value_type; - - static value_type low(value_type result, uint32_t ch) - { - // U+0000..U+007F - if (ch < 0x80) return result + 1; - // U+0080..U+07FF - else if (ch < 0x800) return result + 2; - // U+0800..U+FFFF - else return result + 3; - } - - static value_type high(value_type result, uint32_t) - { - // U+10000..U+10FFFF - return result + 4; - } - }; - - struct utf8_writer - { - typedef uint8_t* value_type; - - static value_type low(value_type result, uint32_t ch) - { - // U+0000..U+007F - if (ch < 0x80) - { - *result = static_cast(ch); - return result + 1; - } - // U+0080..U+07FF - else if (ch < 0x800) - { - result[0] = static_cast(0xC0 | (ch >> 6)); - result[1] = static_cast(0x80 | (ch & 0x3F)); - return result + 2; - } - // U+0800..U+FFFF - else - { - result[0] = static_cast(0xE0 | (ch >> 12)); - result[1] = static_cast(0x80 | ((ch >> 6) & 0x3F)); - result[2] = static_cast(0x80 | (ch & 0x3F)); - return result + 3; - } - } - - static value_type high(value_type result, uint32_t ch) - { - // U+10000..U+10FFFF - result[0] = static_cast(0xF0 | (ch >> 18)); - result[1] = static_cast(0x80 | ((ch >> 12) & 0x3F)); - result[2] = static_cast(0x80 | ((ch >> 6) & 0x3F)); - result[3] = static_cast(0x80 | (ch & 0x3F)); - return result + 4; - } - - static value_type any(value_type result, uint32_t ch) - { - return (ch < 0x10000) ? low(result, ch) : high(result, ch); - } - }; - - struct utf16_counter - { - typedef size_t value_type; - - static value_type low(value_type result, uint32_t) - { - return result + 1; - } - - static value_type high(value_type result, uint32_t) - { - return result + 2; - } - }; - - struct utf16_writer - { - typedef uint16_t* value_type; - - static value_type low(value_type result, uint32_t ch) - { - *result = static_cast(ch); - - return result + 1; - } - - static value_type high(value_type result, uint32_t ch) - { - uint32_t msh = static_cast(ch - 0x10000) >> 10; - uint32_t lsh = static_cast(ch - 0x10000) & 0x3ff; - - result[0] = static_cast(0xD800 + msh); - result[1] = static_cast(0xDC00 + lsh); - - return result + 2; - } - - static value_type any(value_type result, uint32_t ch) - { - return (ch < 0x10000) ? low(result, ch) : high(result, ch); - } - }; - - struct utf32_counter - { - typedef size_t value_type; - - static value_type low(value_type result, uint32_t) - { - return result + 1; - } - - static value_type high(value_type result, uint32_t) - { - return result + 1; - } - }; - - struct utf32_writer - { - typedef uint32_t* value_type; - - static value_type low(value_type result, uint32_t ch) - { - *result = ch; - - return result + 1; - } - - static value_type high(value_type result, uint32_t ch) - { - *result = ch; - - return result + 1; - } - - static value_type any(value_type result, uint32_t ch) - { - *result = ch; - - return result + 1; - } - }; - - struct latin1_writer - { - typedef uint8_t* value_type; - - static value_type low(value_type result, uint32_t ch) - { - *result = static_cast(ch > 255 ? '?' : ch); - - return result + 1; - } - - static value_type high(value_type result, uint32_t ch) - { - (void)ch; - - *result = '?'; - - return result + 1; - } - }; - - struct utf8_decoder - { - typedef uint8_t type; - - template static inline typename Traits::value_type process(const uint8_t* data, size_t size, typename Traits::value_type result, Traits) - { - const uint8_t utf8_byte_mask = 0x3f; - - while (size) - { - uint8_t lead = *data; - - // 0xxxxxxx -> U+0000..U+007F - if (lead < 0x80) - { - result = Traits::low(result, lead); - data += 1; - size -= 1; - - // process aligned single-byte (ascii) blocks - if ((reinterpret_cast(data) & 3) == 0) - { - // round-trip through void* to silence 'cast increases required alignment of target type' warnings - while (size >= 4 && (*static_cast(static_cast(data)) & 0x80808080) == 0) - { - result = Traits::low(result, data[0]); - result = Traits::low(result, data[1]); - result = Traits::low(result, data[2]); - result = Traits::low(result, data[3]); - data += 4; - size -= 4; - } - } - } - // 110xxxxx -> U+0080..U+07FF - else if (static_cast(lead - 0xC0) < 0x20 && size >= 2 && (data[1] & 0xc0) == 0x80) - { - result = Traits::low(result, ((lead & ~0xC0) << 6) | (data[1] & utf8_byte_mask)); - data += 2; - size -= 2; - } - // 1110xxxx -> U+0800-U+FFFF - else if (static_cast(lead - 0xE0) < 0x10 && size >= 3 && (data[1] & 0xc0) == 0x80 && (data[2] & 0xc0) == 0x80) - { - result = Traits::low(result, ((lead & ~0xE0) << 12) | ((data[1] & utf8_byte_mask) << 6) | (data[2] & utf8_byte_mask)); - data += 3; - size -= 3; - } - // 11110xxx -> U+10000..U+10FFFF - else if (static_cast(lead - 0xF0) < 0x08 && size >= 4 && (data[1] & 0xc0) == 0x80 && (data[2] & 0xc0) == 0x80 && (data[3] & 0xc0) == 0x80) - { - result = Traits::high(result, ((lead & ~0xF0) << 18) | ((data[1] & utf8_byte_mask) << 12) | ((data[2] & utf8_byte_mask) << 6) | (data[3] & utf8_byte_mask)); - data += 4; - size -= 4; - } - // 10xxxxxx or 11111xxx -> invalid - else - { - data += 1; - size -= 1; - } - } - - return result; - } - }; - - template struct utf16_decoder - { - typedef uint16_t type; - - template static inline typename Traits::value_type process(const uint16_t* data, size_t size, typename Traits::value_type result, Traits) - { - while (size) - { - uint16_t lead = opt_swap::value ? endian_swap(*data) : *data; - - // U+0000..U+D7FF - if (lead < 0xD800) - { - result = Traits::low(result, lead); - data += 1; - size -= 1; - } - // U+E000..U+FFFF - else if (static_cast(lead - 0xE000) < 0x2000) - { - result = Traits::low(result, lead); - data += 1; - size -= 1; - } - // surrogate pair lead - else if (static_cast(lead - 0xD800) < 0x400 && size >= 2) - { - uint16_t next = opt_swap::value ? endian_swap(data[1]) : data[1]; - - if (static_cast(next - 0xDC00) < 0x400) - { - result = Traits::high(result, 0x10000 + ((lead & 0x3ff) << 10) + (next & 0x3ff)); - data += 2; - size -= 2; - } - else - { - data += 1; - size -= 1; - } - } - else - { - data += 1; - size -= 1; - } - } - - return result; - } - }; - - template struct utf32_decoder - { - typedef uint32_t type; - - template static inline typename Traits::value_type process(const uint32_t* data, size_t size, typename Traits::value_type result, Traits) - { - while (size) - { - uint32_t lead = opt_swap::value ? endian_swap(*data) : *data; - - // U+0000..U+FFFF - if (lead < 0x10000) - { - result = Traits::low(result, lead); - data += 1; - size -= 1; - } - // U+10000..U+10FFFF - else - { - result = Traits::high(result, lead); - data += 1; - size -= 1; - } - } - - return result; - } - }; - - struct latin1_decoder - { - typedef uint8_t type; - - template static inline typename Traits::value_type process(const uint8_t* data, size_t size, typename Traits::value_type result, Traits) - { - while (size) - { - result = Traits::low(result, *data); - data += 1; - size -= 1; - } - - return result; - } - }; - - template struct wchar_selector; - - template <> struct wchar_selector<2> - { - typedef uint16_t type; - typedef utf16_counter counter; - typedef utf16_writer writer; - typedef utf16_decoder decoder; - }; - - template <> struct wchar_selector<4> - { - typedef uint32_t type; - typedef utf32_counter counter; - typedef utf32_writer writer; - typedef utf32_decoder decoder; - }; - - typedef wchar_selector::counter wchar_counter; - typedef wchar_selector::writer wchar_writer; - - struct wchar_decoder - { - typedef wchar_t type; - - template static inline typename Traits::value_type process(const wchar_t* data, size_t size, typename Traits::value_type result, Traits traits) - { - typedef wchar_selector::decoder decoder; - - return decoder::process(reinterpret_cast(data), size, result, traits); - } - }; - -#ifdef PUGIXML_WCHAR_MODE - PUGI__FN void convert_wchar_endian_swap(wchar_t* result, const wchar_t* data, size_t length) - { - for (size_t i = 0; i < length; ++i) - result[i] = static_cast(endian_swap(static_cast::type>(data[i]))); - } -#endif -PUGI__NS_END - -PUGI__NS_BEGIN - enum chartype_t - { - ct_parse_pcdata = 1, // \0, &, \r, < - ct_parse_attr = 2, // \0, &, \r, ', " - ct_parse_attr_ws = 4, // \0, &, \r, ', ", \n, tab - ct_space = 8, // \r, \n, space, tab - ct_parse_cdata = 16, // \0, ], >, \r - ct_parse_comment = 32, // \0, -, >, \r - ct_symbol = 64, // Any symbol > 127, a-z, A-Z, 0-9, _, :, -, . - ct_start_symbol = 128 // Any symbol > 127, a-z, A-Z, _, : - }; - - static const unsigned char chartype_table[256] = - { - 55, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 0, 0, 63, 0, 0, // 0-15 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16-31 - 8, 0, 6, 0, 0, 0, 7, 6, 0, 0, 0, 0, 0, 96, 64, 0, // 32-47 - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 192, 0, 1, 0, 48, 0, // 48-63 - 0, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, // 64-79 - 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 0, 0, 16, 0, 192, // 80-95 - 0, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, // 96-111 - 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 0, 0, 0, 0, 0, // 112-127 - - 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, // 128+ - 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192 - }; - - enum chartypex_t - { - ctx_special_pcdata = 1, // Any symbol >= 0 and < 32 (except \t, \r, \n), &, <, > - ctx_special_attr = 2, // Any symbol >= 0 and < 32 (except \t), &, <, >, " - ctx_start_symbol = 4, // Any symbol > 127, a-z, A-Z, _ - ctx_digit = 8, // 0-9 - ctx_symbol = 16 // Any symbol > 127, a-z, A-Z, 0-9, _, -, . - }; - - static const unsigned char chartypex_table[256] = - { - 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 2, 3, 3, 2, 3, 3, // 0-15 - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 16-31 - 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 16, 16, 0, // 32-47 - 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 0, 0, 3, 0, 3, 0, // 48-63 - - 0, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, // 64-79 - 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 0, 0, 0, 0, 20, // 80-95 - 0, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, // 96-111 - 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 0, 0, 0, 0, 0, // 112-127 - - 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, // 128+ - 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, - 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, - 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, - 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, - 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, - 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, - 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20 - }; - -#ifdef PUGIXML_WCHAR_MODE - #define PUGI__IS_CHARTYPE_IMPL(c, ct, table) ((static_cast(c) < 128 ? table[static_cast(c)] : table[128]) & (ct)) -#else - #define PUGI__IS_CHARTYPE_IMPL(c, ct, table) (table[static_cast(c)] & (ct)) -#endif - - #define PUGI__IS_CHARTYPE(c, ct) PUGI__IS_CHARTYPE_IMPL(c, ct, chartype_table) - #define PUGI__IS_CHARTYPEX(c, ct) PUGI__IS_CHARTYPE_IMPL(c, ct, chartypex_table) - - PUGI__FN bool is_little_endian() - { - unsigned int ui = 1; - - return *reinterpret_cast(&ui) == 1; - } - - PUGI__FN xml_encoding get_wchar_encoding() - { - PUGI__STATIC_ASSERT(sizeof(wchar_t) == 2 || sizeof(wchar_t) == 4); - - if (sizeof(wchar_t) == 2) - return is_little_endian() ? encoding_utf16_le : encoding_utf16_be; - else - return is_little_endian() ? encoding_utf32_le : encoding_utf32_be; - } - - PUGI__FN xml_encoding guess_buffer_encoding(uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3) - { - // look for BOM in first few bytes - if (d0 == 0 && d1 == 0 && d2 == 0xfe && d3 == 0xff) return encoding_utf32_be; - if (d0 == 0xff && d1 == 0xfe && d2 == 0 && d3 == 0) return encoding_utf32_le; - if (d0 == 0xfe && d1 == 0xff) return encoding_utf16_be; - if (d0 == 0xff && d1 == 0xfe) return encoding_utf16_le; - if (d0 == 0xef && d1 == 0xbb && d2 == 0xbf) return encoding_utf8; - - // look for <, (contents); - - PUGI__DMC_VOLATILE uint8_t d0 = data[0], d1 = data[1], d2 = data[2], d3 = data[3]; - - return guess_buffer_encoding(d0, d1, d2, d3); - } - - PUGI__FN bool get_mutable_buffer(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable) - { - size_t length = size / sizeof(char_t); - - if (is_mutable) - { - out_buffer = static_cast(const_cast(contents)); - out_length = length; - } - else - { - char_t* buffer = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); - if (!buffer) return false; - - if (contents) - memcpy(buffer, contents, length * sizeof(char_t)); - else - assert(length == 0); - - buffer[length] = 0; - - out_buffer = buffer; - out_length = length + 1; - } - - return true; - } - -#ifdef PUGIXML_WCHAR_MODE - PUGI__FN bool need_endian_swap_utf(xml_encoding le, xml_encoding re) - { - return (le == encoding_utf16_be && re == encoding_utf16_le) || (le == encoding_utf16_le && re == encoding_utf16_be) || - (le == encoding_utf32_be && re == encoding_utf32_le) || (le == encoding_utf32_le && re == encoding_utf32_be); - } - - PUGI__FN bool convert_buffer_endian_swap(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable) - { - const char_t* data = static_cast(contents); - size_t length = size / sizeof(char_t); - - if (is_mutable) - { - char_t* buffer = const_cast(data); - - convert_wchar_endian_swap(buffer, data, length); - - out_buffer = buffer; - out_length = length; - } - else - { - char_t* buffer = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); - if (!buffer) return false; - - convert_wchar_endian_swap(buffer, data, length); - buffer[length] = 0; - - out_buffer = buffer; - out_length = length + 1; - } - - return true; - } - - template PUGI__FN bool convert_buffer_generic(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, D) - { - const typename D::type* data = static_cast(contents); - size_t data_length = size / sizeof(typename D::type); - - // first pass: get length in wchar_t units - size_t length = D::process(data, data_length, 0, wchar_counter()); - - // allocate buffer of suitable length - char_t* buffer = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); - if (!buffer) return false; - - // second pass: convert utf16 input to wchar_t - wchar_writer::value_type obegin = reinterpret_cast(buffer); - wchar_writer::value_type oend = D::process(data, data_length, obegin, wchar_writer()); - - assert(oend == obegin + length); - *oend = 0; - - out_buffer = buffer; - out_length = length + 1; - - return true; - } - - PUGI__FN bool convert_buffer(char_t*& out_buffer, size_t& out_length, xml_encoding encoding, const void* contents, size_t size, bool is_mutable) - { - // get native encoding - xml_encoding wchar_encoding = get_wchar_encoding(); - - // fast path: no conversion required - if (encoding == wchar_encoding) - return get_mutable_buffer(out_buffer, out_length, contents, size, is_mutable); - - // only endian-swapping is required - if (need_endian_swap_utf(encoding, wchar_encoding)) - return convert_buffer_endian_swap(out_buffer, out_length, contents, size, is_mutable); - - // source encoding is utf8 - if (encoding == encoding_utf8) - return convert_buffer_generic(out_buffer, out_length, contents, size, utf8_decoder()); - - // source encoding is utf16 - if (encoding == encoding_utf16_be || encoding == encoding_utf16_le) - { - xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be; - - return (native_encoding == encoding) ? - convert_buffer_generic(out_buffer, out_length, contents, size, utf16_decoder()) : - convert_buffer_generic(out_buffer, out_length, contents, size, utf16_decoder()); - } - - // source encoding is utf32 - if (encoding == encoding_utf32_be || encoding == encoding_utf32_le) - { - xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be; - - return (native_encoding == encoding) ? - convert_buffer_generic(out_buffer, out_length, contents, size, utf32_decoder()) : - convert_buffer_generic(out_buffer, out_length, contents, size, utf32_decoder()); - } - - // source encoding is latin1 - if (encoding == encoding_latin1) - return convert_buffer_generic(out_buffer, out_length, contents, size, latin1_decoder()); - - assert(!"Invalid encoding"); - return false; - } -#else - template PUGI__FN bool convert_buffer_generic(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, D) - { - const typename D::type* data = static_cast(contents); - size_t data_length = size / sizeof(typename D::type); - - // first pass: get length in utf8 units - size_t length = D::process(data, data_length, 0, utf8_counter()); - - // allocate buffer of suitable length - char_t* buffer = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); - if (!buffer) return false; - - // second pass: convert utf16 input to utf8 - uint8_t* obegin = reinterpret_cast(buffer); - uint8_t* oend = D::process(data, data_length, obegin, utf8_writer()); - - assert(oend == obegin + length); - *oend = 0; - - out_buffer = buffer; - out_length = length + 1; - - return true; - } - - PUGI__FN size_t get_latin1_7bit_prefix_length(const uint8_t* data, size_t size) - { - for (size_t i = 0; i < size; ++i) - if (data[i] > 127) - return i; - - return size; - } - - PUGI__FN bool convert_buffer_latin1(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable) - { - const uint8_t* data = static_cast(contents); - size_t data_length = size; - - // get size of prefix that does not need utf8 conversion - size_t prefix_length = get_latin1_7bit_prefix_length(data, data_length); - assert(prefix_length <= data_length); - - const uint8_t* postfix = data + prefix_length; - size_t postfix_length = data_length - prefix_length; - - // if no conversion is needed, just return the original buffer - if (postfix_length == 0) return get_mutable_buffer(out_buffer, out_length, contents, size, is_mutable); - - // first pass: get length in utf8 units - size_t length = prefix_length + latin1_decoder::process(postfix, postfix_length, 0, utf8_counter()); - - // allocate buffer of suitable length - char_t* buffer = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); - if (!buffer) return false; - - // second pass: convert latin1 input to utf8 - memcpy(buffer, data, prefix_length); - - uint8_t* obegin = reinterpret_cast(buffer); - uint8_t* oend = latin1_decoder::process(postfix, postfix_length, obegin + prefix_length, utf8_writer()); - - assert(oend == obegin + length); - *oend = 0; - - out_buffer = buffer; - out_length = length + 1; - - return true; - } - - PUGI__FN bool convert_buffer(char_t*& out_buffer, size_t& out_length, xml_encoding encoding, const void* contents, size_t size, bool is_mutable) - { - // fast path: no conversion required - if (encoding == encoding_utf8) - return get_mutable_buffer(out_buffer, out_length, contents, size, is_mutable); - - // source encoding is utf16 - if (encoding == encoding_utf16_be || encoding == encoding_utf16_le) - { - xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be; - - return (native_encoding == encoding) ? - convert_buffer_generic(out_buffer, out_length, contents, size, utf16_decoder()) : - convert_buffer_generic(out_buffer, out_length, contents, size, utf16_decoder()); - } - - // source encoding is utf32 - if (encoding == encoding_utf32_be || encoding == encoding_utf32_le) - { - xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be; - - return (native_encoding == encoding) ? - convert_buffer_generic(out_buffer, out_length, contents, size, utf32_decoder()) : - convert_buffer_generic(out_buffer, out_length, contents, size, utf32_decoder()); - } - - // source encoding is latin1 - if (encoding == encoding_latin1) - return convert_buffer_latin1(out_buffer, out_length, contents, size, is_mutable); - - assert(!"Invalid encoding"); - return false; - } -#endif - - PUGI__FN size_t as_utf8_begin(const wchar_t* str, size_t length) - { - // get length in utf8 characters - return wchar_decoder::process(str, length, 0, utf8_counter()); - } - - PUGI__FN void as_utf8_end(char* buffer, size_t size, const wchar_t* str, size_t length) - { - // convert to utf8 - uint8_t* begin = reinterpret_cast(buffer); - uint8_t* end = wchar_decoder::process(str, length, begin, utf8_writer()); - - assert(begin + size == end); - (void)!end; - (void)!size; - } - -#ifndef PUGIXML_NO_STL - PUGI__FN std::string as_utf8_impl(const wchar_t* str, size_t length) - { - // first pass: get length in utf8 characters - size_t size = as_utf8_begin(str, length); - - // allocate resulting string - std::string result; - result.resize(size); - - // second pass: convert to utf8 - if (size > 0) as_utf8_end(&result[0], size, str, length); - - return result; - } - - PUGI__FN std::basic_string as_wide_impl(const char* str, size_t size) - { - const uint8_t* data = reinterpret_cast(str); - - // first pass: get length in wchar_t units - size_t length = utf8_decoder::process(data, size, 0, wchar_counter()); - - // allocate resulting string - std::basic_string result; - result.resize(length); - - // second pass: convert to wchar_t - if (length > 0) - { - wchar_writer::value_type begin = reinterpret_cast(&result[0]); - wchar_writer::value_type end = utf8_decoder::process(data, size, begin, wchar_writer()); - - assert(begin + length == end); - (void)!end; - } - - return result; - } -#endif - - template - inline bool strcpy_insitu_allow(size_t length, const Header& header, uintptr_t header_mask, char_t* target) - { - // never reuse shared memory - if (header & xml_memory_page_contents_shared_mask) return false; - - size_t target_length = strlength(target); - - // always reuse document buffer memory if possible - if ((header & header_mask) == 0) return target_length >= length; - - // reuse heap memory if waste is not too great - const size_t reuse_threshold = 32; - - return target_length >= length && (target_length < reuse_threshold || target_length - length < target_length / 2); - } - - template - PUGI__FN bool strcpy_insitu(String& dest, Header& header, uintptr_t header_mask, const char_t* source, size_t source_length) - { - if (source_length == 0) - { - // empty string and null pointer are equivalent, so just deallocate old memory - xml_allocator* alloc = PUGI__GETPAGE_IMPL(header)->allocator; - - if (header & header_mask) alloc->deallocate_string(dest); - - // mark the string as not allocated - dest = 0; - header &= ~header_mask; - - return true; - } - else if (dest && strcpy_insitu_allow(source_length, header, header_mask, dest)) - { - // we can reuse old buffer, so just copy the new data (including zero terminator) - memcpy(dest, source, source_length * sizeof(char_t)); - dest[source_length] = 0; - - return true; - } - else - { - xml_allocator* alloc = PUGI__GETPAGE_IMPL(header)->allocator; - - if (!alloc->reserve()) return false; - - // allocate new buffer - char_t* buf = alloc->allocate_string(source_length + 1); - if (!buf) return false; - - // copy the string (including zero terminator) - memcpy(buf, source, source_length * sizeof(char_t)); - buf[source_length] = 0; - - // deallocate old buffer (*after* the above to protect against overlapping memory and/or allocation failures) - if (header & header_mask) alloc->deallocate_string(dest); - - // the string is now allocated, so set the flag - dest = buf; - header |= header_mask; - - return true; - } - } - - struct gap - { - char_t* end; - size_t size; - - gap(): end(0), size(0) - { - } - - // Push new gap, move s count bytes further (skipping the gap). - // Collapse previous gap. - void push(char_t*& s, size_t count) - { - if (end) // there was a gap already; collapse it - { - // Move [old_gap_end, new_gap_start) to [old_gap_start, ...) - assert(s >= end); - memmove(end - size, end, reinterpret_cast(s) - reinterpret_cast(end)); - } - - s += count; // end of current gap - - // "merge" two gaps - end = s; - size += count; - } - - // Collapse all gaps, return past-the-end pointer - char_t* flush(char_t* s) - { - if (end) - { - // Move [old_gap_end, current_pos) to [old_gap_start, ...) - assert(s >= end); - memmove(end - size, end, reinterpret_cast(s) - reinterpret_cast(end)); - - return s - size; - } - else return s; - } - }; - - PUGI__FN char_t* strconv_escape(char_t* s, gap& g) - { - char_t* stre = s + 1; - - switch (*stre) - { - case '#': // &#... - { - unsigned int ucsc = 0; - - if (stre[1] == 'x') // &#x... (hex code) - { - stre += 2; - - char_t ch = *stre; - - if (ch == ';') return stre; - - for (;;) - { - if (static_cast(ch - '0') <= 9) - ucsc = 16 * ucsc + (ch - '0'); - else if (static_cast((ch | ' ') - 'a') <= 5) - ucsc = 16 * ucsc + ((ch | ' ') - 'a' + 10); - else if (ch == ';') - break; - else // cancel - return stre; - - ch = *++stre; - } - - ++stre; - } - else // &#... (dec code) - { - char_t ch = *++stre; - - if (ch == ';') return stre; - - for (;;) - { - if (static_cast(static_cast(ch) - '0') <= 9) - ucsc = 10 * ucsc + (ch - '0'); - else if (ch == ';') - break; - else // cancel - return stre; - - ch = *++stre; - } - - ++stre; - } - - #ifdef PUGIXML_WCHAR_MODE - s = reinterpret_cast(wchar_writer::any(reinterpret_cast(s), ucsc)); - #else - s = reinterpret_cast(utf8_writer::any(reinterpret_cast(s), ucsc)); - #endif - - g.push(s, stre - s); - return stre; - } - - case 'a': // &a - { - ++stre; - - if (*stre == 'm') // &am - { - if (*++stre == 'p' && *++stre == ';') // & - { - *s++ = '&'; - ++stre; - - g.push(s, stre - s); - return stre; - } - } - else if (*stre == 'p') // &ap - { - if (*++stre == 'o' && *++stre == 's' && *++stre == ';') // ' - { - *s++ = '\''; - ++stre; - - g.push(s, stre - s); - return stre; - } - } - break; - } - - case 'g': // &g - { - if (*++stre == 't' && *++stre == ';') // > - { - *s++ = '>'; - ++stre; - - g.push(s, stre - s); - return stre; - } - break; - } - - case 'l': // &l - { - if (*++stre == 't' && *++stre == ';') // < - { - *s++ = '<'; - ++stre; - - g.push(s, stre - s); - return stre; - } - break; - } - - case 'q': // &q - { - if (*++stre == 'u' && *++stre == 'o' && *++stre == 't' && *++stre == ';') // " - { - *s++ = '"'; - ++stre; - - g.push(s, stre - s); - return stre; - } - break; - } - - default: - break; - } - - return stre; - } - - // Parser utilities - #define PUGI__ENDSWITH(c, e) ((c) == (e) || ((c) == 0 && endch == (e))) - #define PUGI__SKIPWS() { while (PUGI__IS_CHARTYPE(*s, ct_space)) ++s; } - #define PUGI__OPTSET(OPT) ( optmsk & (OPT) ) - #define PUGI__PUSHNODE(TYPE) { cursor = append_new_node(cursor, alloc, TYPE); if (!cursor) PUGI__THROW_ERROR(status_out_of_memory, s); } - #define PUGI__POPNODE() { cursor = cursor->parent; } - #define PUGI__SCANFOR(X) { while (*s != 0 && !(X)) ++s; } - #define PUGI__SCANWHILE(X) { while (X) ++s; } - #define PUGI__SCANWHILE_UNROLL(X) { for (;;) { char_t ss = s[0]; if (PUGI__UNLIKELY(!(X))) { break; } ss = s[1]; if (PUGI__UNLIKELY(!(X))) { s += 1; break; } ss = s[2]; if (PUGI__UNLIKELY(!(X))) { s += 2; break; } ss = s[3]; if (PUGI__UNLIKELY(!(X))) { s += 3; break; } s += 4; } } - #define PUGI__ENDSEG() { ch = *s; *s = 0; ++s; } - #define PUGI__THROW_ERROR(err, m) return error_offset = m, error_status = err, static_cast(0) - #define PUGI__CHECK_ERROR(err, m) { if (*s == 0) PUGI__THROW_ERROR(err, m); } - - PUGI__FN char_t* strconv_comment(char_t* s, char_t endch) - { - gap g; - - while (true) - { - PUGI__SCANWHILE_UNROLL(!PUGI__IS_CHARTYPE(ss, ct_parse_comment)); - - if (*s == '\r') // Either a single 0x0d or 0x0d 0x0a pair - { - *s++ = '\n'; // replace first one with 0x0a - - if (*s == '\n') g.push(s, 1); - } - else if (s[0] == '-' && s[1] == '-' && PUGI__ENDSWITH(s[2], '>')) // comment ends here - { - *g.flush(s) = 0; - - return s + (s[2] == '>' ? 3 : 2); - } - else if (*s == 0) - { - return 0; - } - else ++s; - } - } - - PUGI__FN char_t* strconv_cdata(char_t* s, char_t endch) - { - gap g; - - while (true) - { - PUGI__SCANWHILE_UNROLL(!PUGI__IS_CHARTYPE(ss, ct_parse_cdata)); - - if (*s == '\r') // Either a single 0x0d or 0x0d 0x0a pair - { - *s++ = '\n'; // replace first one with 0x0a - - if (*s == '\n') g.push(s, 1); - } - else if (s[0] == ']' && s[1] == ']' && PUGI__ENDSWITH(s[2], '>')) // CDATA ends here - { - *g.flush(s) = 0; - - return s + 1; - } - else if (*s == 0) - { - return 0; - } - else ++s; - } - } - - typedef char_t* (*strconv_pcdata_t)(char_t*); - - template struct strconv_pcdata_impl - { - static char_t* parse(char_t* s) - { - gap g; - - char_t* begin = s; - - while (true) - { - PUGI__SCANWHILE_UNROLL(!PUGI__IS_CHARTYPE(ss, ct_parse_pcdata)); - - if (*s == '<') // PCDATA ends here - { - char_t* end = g.flush(s); - - if (opt_trim::value) - while (end > begin && PUGI__IS_CHARTYPE(end[-1], ct_space)) - --end; - - *end = 0; - - return s + 1; - } - else if (opt_eol::value && *s == '\r') // Either a single 0x0d or 0x0d 0x0a pair - { - *s++ = '\n'; // replace first one with 0x0a - - if (*s == '\n') g.push(s, 1); - } - else if (opt_escape::value && *s == '&') - { - s = strconv_escape(s, g); - } - else if (*s == 0) - { - char_t* end = g.flush(s); - - if (opt_trim::value) - while (end > begin && PUGI__IS_CHARTYPE(end[-1], ct_space)) - --end; - - *end = 0; - - return s; - } - else ++s; - } - } - }; - - PUGI__FN strconv_pcdata_t get_strconv_pcdata(unsigned int optmask) - { - PUGI__STATIC_ASSERT(parse_escapes == 0x10 && parse_eol == 0x20 && parse_trim_pcdata == 0x0800); - - switch (((optmask >> 4) & 3) | ((optmask >> 9) & 4)) // get bitmask for flags (eol escapes trim) - { - case 0: return strconv_pcdata_impl::parse; - case 1: return strconv_pcdata_impl::parse; - case 2: return strconv_pcdata_impl::parse; - case 3: return strconv_pcdata_impl::parse; - case 4: return strconv_pcdata_impl::parse; - case 5: return strconv_pcdata_impl::parse; - case 6: return strconv_pcdata_impl::parse; - case 7: return strconv_pcdata_impl::parse; - default: assert(false); return 0; // should not get here - } - } - - typedef char_t* (*strconv_attribute_t)(char_t*, char_t); - - template struct strconv_attribute_impl - { - static char_t* parse_wnorm(char_t* s, char_t end_quote) - { - gap g; - - // trim leading whitespaces - if (PUGI__IS_CHARTYPE(*s, ct_space)) - { - char_t* str = s; - - do ++str; - while (PUGI__IS_CHARTYPE(*str, ct_space)); - - g.push(s, str - s); - } - - while (true) - { - PUGI__SCANWHILE_UNROLL(!PUGI__IS_CHARTYPE(ss, ct_parse_attr_ws | ct_space)); - - if (*s == end_quote) - { - char_t* str = g.flush(s); - - do *str-- = 0; - while (PUGI__IS_CHARTYPE(*str, ct_space)); - - return s + 1; - } - else if (PUGI__IS_CHARTYPE(*s, ct_space)) - { - *s++ = ' '; - - if (PUGI__IS_CHARTYPE(*s, ct_space)) - { - char_t* str = s + 1; - while (PUGI__IS_CHARTYPE(*str, ct_space)) ++str; - - g.push(s, str - s); - } - } - else if (opt_escape::value && *s == '&') - { - s = strconv_escape(s, g); - } - else if (!*s) - { - return 0; - } - else ++s; - } - } - - static char_t* parse_wconv(char_t* s, char_t end_quote) - { - gap g; - - while (true) - { - PUGI__SCANWHILE_UNROLL(!PUGI__IS_CHARTYPE(ss, ct_parse_attr_ws)); - - if (*s == end_quote) - { - *g.flush(s) = 0; - - return s + 1; - } - else if (PUGI__IS_CHARTYPE(*s, ct_space)) - { - if (*s == '\r') - { - *s++ = ' '; - - if (*s == '\n') g.push(s, 1); - } - else *s++ = ' '; - } - else if (opt_escape::value && *s == '&') - { - s = strconv_escape(s, g); - } - else if (!*s) - { - return 0; - } - else ++s; - } - } - - static char_t* parse_eol(char_t* s, char_t end_quote) - { - gap g; - - while (true) - { - PUGI__SCANWHILE_UNROLL(!PUGI__IS_CHARTYPE(ss, ct_parse_attr)); - - if (*s == end_quote) - { - *g.flush(s) = 0; - - return s + 1; - } - else if (*s == '\r') - { - *s++ = '\n'; - - if (*s == '\n') g.push(s, 1); - } - else if (opt_escape::value && *s == '&') - { - s = strconv_escape(s, g); - } - else if (!*s) - { - return 0; - } - else ++s; - } - } - - static char_t* parse_simple(char_t* s, char_t end_quote) - { - gap g; - - while (true) - { - PUGI__SCANWHILE_UNROLL(!PUGI__IS_CHARTYPE(ss, ct_parse_attr)); - - if (*s == end_quote) - { - *g.flush(s) = 0; - - return s + 1; - } - else if (opt_escape::value && *s == '&') - { - s = strconv_escape(s, g); - } - else if (!*s) - { - return 0; - } - else ++s; - } - } - }; - - PUGI__FN strconv_attribute_t get_strconv_attribute(unsigned int optmask) - { - PUGI__STATIC_ASSERT(parse_escapes == 0x10 && parse_eol == 0x20 && parse_wconv_attribute == 0x40 && parse_wnorm_attribute == 0x80); - - switch ((optmask >> 4) & 15) // get bitmask for flags (wconv wnorm eol escapes) - { - case 0: return strconv_attribute_impl::parse_simple; - case 1: return strconv_attribute_impl::parse_simple; - case 2: return strconv_attribute_impl::parse_eol; - case 3: return strconv_attribute_impl::parse_eol; - case 4: return strconv_attribute_impl::parse_wconv; - case 5: return strconv_attribute_impl::parse_wconv; - case 6: return strconv_attribute_impl::parse_wconv; - case 7: return strconv_attribute_impl::parse_wconv; - case 8: return strconv_attribute_impl::parse_wnorm; - case 9: return strconv_attribute_impl::parse_wnorm; - case 10: return strconv_attribute_impl::parse_wnorm; - case 11: return strconv_attribute_impl::parse_wnorm; - case 12: return strconv_attribute_impl::parse_wnorm; - case 13: return strconv_attribute_impl::parse_wnorm; - case 14: return strconv_attribute_impl::parse_wnorm; - case 15: return strconv_attribute_impl::parse_wnorm; - default: assert(false); return 0; // should not get here - } - } - - inline xml_parse_result make_parse_result(xml_parse_status status, ptrdiff_t offset = 0) - { - xml_parse_result result; - result.status = status; - result.offset = offset; - - return result; - } - - struct xml_parser - { - xml_allocator alloc; - xml_allocator* alloc_state; - char_t* error_offset; - xml_parse_status error_status; - - xml_parser(xml_allocator* alloc_): alloc(*alloc_), alloc_state(alloc_), error_offset(0), error_status(status_ok) - { - } - - ~xml_parser() - { - *alloc_state = alloc; - } - - // DOCTYPE consists of nested sections of the following possible types: - // , , "...", '...' - // - // - // First group can not contain nested groups - // Second group can contain nested groups of the same type - // Third group can contain all other groups - char_t* parse_doctype_primitive(char_t* s) - { - if (*s == '"' || *s == '\'') - { - // quoted string - char_t ch = *s++; - PUGI__SCANFOR(*s == ch); - if (!*s) PUGI__THROW_ERROR(status_bad_doctype, s); - - s++; - } - else if (s[0] == '<' && s[1] == '?') - { - // - s += 2; - PUGI__SCANFOR(s[0] == '?' && s[1] == '>'); // no need for ENDSWITH because ?> can't terminate proper doctype - if (!*s) PUGI__THROW_ERROR(status_bad_doctype, s); - - s += 2; - } - else if (s[0] == '<' && s[1] == '!' && s[2] == '-' && s[3] == '-') - { - s += 4; - PUGI__SCANFOR(s[0] == '-' && s[1] == '-' && s[2] == '>'); // no need for ENDSWITH because --> can't terminate proper doctype - if (!*s) PUGI__THROW_ERROR(status_bad_doctype, s); - - s += 3; - } - else PUGI__THROW_ERROR(status_bad_doctype, s); - - return s; - } - - char_t* parse_doctype_ignore(char_t* s) - { - size_t depth = 0; - - assert(s[0] == '<' && s[1] == '!' && s[2] == '['); - s += 3; - - while (*s) - { - if (s[0] == '<' && s[1] == '!' && s[2] == '[') - { - // nested ignore section - s += 3; - depth++; - } - else if (s[0] == ']' && s[1] == ']' && s[2] == '>') - { - // ignore section end - s += 3; - - if (depth == 0) - return s; - - depth--; - } - else s++; - } - - PUGI__THROW_ERROR(status_bad_doctype, s); - } - - char_t* parse_doctype_group(char_t* s, char_t endch) - { - size_t depth = 0; - - assert((s[0] == '<' || s[0] == 0) && s[1] == '!'); - s += 2; - - while (*s) - { - if (s[0] == '<' && s[1] == '!' && s[2] != '-') - { - if (s[2] == '[') - { - // ignore - s = parse_doctype_ignore(s); - if (!s) return s; - } - else - { - // some control group - s += 2; - depth++; - } - } - else if (s[0] == '<' || s[0] == '"' || s[0] == '\'') - { - // unknown tag (forbidden), or some primitive group - s = parse_doctype_primitive(s); - if (!s) return s; - } - else if (*s == '>') - { - if (depth == 0) - return s; - - depth--; - s++; - } - else s++; - } - - if (depth != 0 || endch != '>') PUGI__THROW_ERROR(status_bad_doctype, s); - - return s; - } - - char_t* parse_exclamation(char_t* s, xml_node_struct* cursor, unsigned int optmsk, char_t endch) - { - // parse node contents, starting with exclamation mark - ++s; - - if (*s == '-') // 'value = s; // Save the offset. - } - - if (PUGI__OPTSET(parse_eol) && PUGI__OPTSET(parse_comments)) - { - s = strconv_comment(s, endch); - - if (!s) PUGI__THROW_ERROR(status_bad_comment, cursor->value); - } - else - { - // Scan for terminating '-->'. - PUGI__SCANFOR(s[0] == '-' && s[1] == '-' && PUGI__ENDSWITH(s[2], '>')); - PUGI__CHECK_ERROR(status_bad_comment, s); - - if (PUGI__OPTSET(parse_comments)) - *s = 0; // Zero-terminate this segment at the first terminating '-'. - - s += (s[2] == '>' ? 3 : 2); // Step over the '\0->'. - } - } - else PUGI__THROW_ERROR(status_bad_comment, s); - } - else if (*s == '[') - { - // 'value = s; // Save the offset. - - if (PUGI__OPTSET(parse_eol)) - { - s = strconv_cdata(s, endch); - - if (!s) PUGI__THROW_ERROR(status_bad_cdata, cursor->value); - } - else - { - // Scan for terminating ']]>'. - PUGI__SCANFOR(s[0] == ']' && s[1] == ']' && PUGI__ENDSWITH(s[2], '>')); - PUGI__CHECK_ERROR(status_bad_cdata, s); - - *s++ = 0; // Zero-terminate this segment. - } - } - else // Flagged for discard, but we still have to scan for the terminator. - { - // Scan for terminating ']]>'. - PUGI__SCANFOR(s[0] == ']' && s[1] == ']' && PUGI__ENDSWITH(s[2], '>')); - PUGI__CHECK_ERROR(status_bad_cdata, s); - - ++s; - } - - s += (s[1] == '>' ? 2 : 1); // Step over the last ']>'. - } - else PUGI__THROW_ERROR(status_bad_cdata, s); - } - else if (s[0] == 'D' && s[1] == 'O' && s[2] == 'C' && s[3] == 'T' && s[4] == 'Y' && s[5] == 'P' && PUGI__ENDSWITH(s[6], 'E')) - { - s -= 2; - - if (cursor->parent) PUGI__THROW_ERROR(status_bad_doctype, s); - - char_t* mark = s + 9; - - s = parse_doctype_group(s, endch); - if (!s) return s; - - assert((*s == 0 && endch == '>') || *s == '>'); - if (*s) *s++ = 0; - - if (PUGI__OPTSET(parse_doctype)) - { - while (PUGI__IS_CHARTYPE(*mark, ct_space)) ++mark; - - PUGI__PUSHNODE(node_doctype); - - cursor->value = mark; - } - } - else if (*s == 0 && endch == '-') PUGI__THROW_ERROR(status_bad_comment, s); - else if (*s == 0 && endch == '[') PUGI__THROW_ERROR(status_bad_cdata, s); - else PUGI__THROW_ERROR(status_unrecognized_tag, s); - - return s; - } - - char_t* parse_question(char_t* s, xml_node_struct*& ref_cursor, unsigned int optmsk, char_t endch) - { - // load into registers - xml_node_struct* cursor = ref_cursor; - char_t ch = 0; - - // parse node contents, starting with question mark - ++s; - - // read PI target - char_t* target = s; - - if (!PUGI__IS_CHARTYPE(*s, ct_start_symbol)) PUGI__THROW_ERROR(status_bad_pi, s); - - PUGI__SCANWHILE(PUGI__IS_CHARTYPE(*s, ct_symbol)); - PUGI__CHECK_ERROR(status_bad_pi, s); - - // determine node type; stricmp / strcasecmp is not portable - bool declaration = (target[0] | ' ') == 'x' && (target[1] | ' ') == 'm' && (target[2] | ' ') == 'l' && target + 3 == s; - - if (declaration ? PUGI__OPTSET(parse_declaration) : PUGI__OPTSET(parse_pi)) - { - if (declaration) - { - // disallow non top-level declarations - if (cursor->parent) PUGI__THROW_ERROR(status_bad_pi, s); - - PUGI__PUSHNODE(node_declaration); - } - else - { - PUGI__PUSHNODE(node_pi); - } - - cursor->name = target; - - PUGI__ENDSEG(); - - // parse value/attributes - if (ch == '?') - { - // empty node - if (!PUGI__ENDSWITH(*s, '>')) PUGI__THROW_ERROR(status_bad_pi, s); - s += (*s == '>'); - - PUGI__POPNODE(); - } - else if (PUGI__IS_CHARTYPE(ch, ct_space)) - { - PUGI__SKIPWS(); - - // scan for tag end - char_t* value = s; - - PUGI__SCANFOR(s[0] == '?' && PUGI__ENDSWITH(s[1], '>')); - PUGI__CHECK_ERROR(status_bad_pi, s); - - if (declaration) - { - // replace ending ? with / so that 'element' terminates properly - *s = '/'; - - // we exit from this function with cursor at node_declaration, which is a signal to parse() to go to LOC_ATTRIBUTES - s = value; - } - else - { - // store value and step over > - cursor->value = value; - - PUGI__POPNODE(); - - PUGI__ENDSEG(); - - s += (*s == '>'); - } - } - else PUGI__THROW_ERROR(status_bad_pi, s); - } - else - { - // scan for tag end - PUGI__SCANFOR(s[0] == '?' && PUGI__ENDSWITH(s[1], '>')); - PUGI__CHECK_ERROR(status_bad_pi, s); - - s += (s[1] == '>' ? 2 : 1); - } - - // store from registers - ref_cursor = cursor; - - return s; - } - - char_t* parse_tree(char_t* s, xml_node_struct* root, unsigned int optmsk, char_t endch) - { - strconv_attribute_t strconv_attribute = get_strconv_attribute(optmsk); - strconv_pcdata_t strconv_pcdata = get_strconv_pcdata(optmsk); - - char_t ch = 0; - xml_node_struct* cursor = root; - char_t* mark = s; - - while (*s != 0) - { - if (*s == '<') - { - ++s; - - LOC_TAG: - if (PUGI__IS_CHARTYPE(*s, ct_start_symbol)) // '<#...' - { - PUGI__PUSHNODE(node_element); // Append a new node to the tree. - - cursor->name = s; - - PUGI__SCANWHILE_UNROLL(PUGI__IS_CHARTYPE(ss, ct_symbol)); // Scan for a terminator. - PUGI__ENDSEG(); // Save char in 'ch', terminate & step over. - - if (ch == '>') - { - // end of tag - } - else if (PUGI__IS_CHARTYPE(ch, ct_space)) - { - LOC_ATTRIBUTES: - while (true) - { - PUGI__SKIPWS(); // Eat any whitespace. - - if (PUGI__IS_CHARTYPE(*s, ct_start_symbol)) // <... #... - { - xml_attribute_struct* a = append_new_attribute(cursor, alloc); // Make space for this attribute. - if (!a) PUGI__THROW_ERROR(status_out_of_memory, s); - - a->name = s; // Save the offset. - - PUGI__SCANWHILE_UNROLL(PUGI__IS_CHARTYPE(ss, ct_symbol)); // Scan for a terminator. - PUGI__ENDSEG(); // Save char in 'ch', terminate & step over. - - if (PUGI__IS_CHARTYPE(ch, ct_space)) - { - PUGI__SKIPWS(); // Eat any whitespace. - - ch = *s; - ++s; - } - - if (ch == '=') // '<... #=...' - { - PUGI__SKIPWS(); // Eat any whitespace. - - if (*s == '"' || *s == '\'') // '<... #="...' - { - ch = *s; // Save quote char to avoid breaking on "''" -or- '""'. - ++s; // Step over the quote. - a->value = s; // Save the offset. - - s = strconv_attribute(s, ch); - - if (!s) PUGI__THROW_ERROR(status_bad_attribute, a->value); - - // After this line the loop continues from the start; - // Whitespaces, / and > are ok, symbols and EOF are wrong, - // everything else will be detected - if (PUGI__IS_CHARTYPE(*s, ct_start_symbol)) PUGI__THROW_ERROR(status_bad_attribute, s); - } - else PUGI__THROW_ERROR(status_bad_attribute, s); - } - else PUGI__THROW_ERROR(status_bad_attribute, s); - } - else if (*s == '/') - { - ++s; - - if (*s == '>') - { - PUGI__POPNODE(); - s++; - break; - } - else if (*s == 0 && endch == '>') - { - PUGI__POPNODE(); - break; - } - else PUGI__THROW_ERROR(status_bad_start_element, s); - } - else if (*s == '>') - { - ++s; - - break; - } - else if (*s == 0 && endch == '>') - { - break; - } - else PUGI__THROW_ERROR(status_bad_start_element, s); - } - - // !!! - } - else if (ch == '/') // '<#.../' - { - if (!PUGI__ENDSWITH(*s, '>')) PUGI__THROW_ERROR(status_bad_start_element, s); - - PUGI__POPNODE(); // Pop. - - s += (*s == '>'); - } - else if (ch == 0) - { - // we stepped over null terminator, backtrack & handle closing tag - --s; - - if (endch != '>') PUGI__THROW_ERROR(status_bad_start_element, s); - } - else PUGI__THROW_ERROR(status_bad_start_element, s); - } - else if (*s == '/') - { - ++s; - - char_t* name = cursor->name; - if (!name) PUGI__THROW_ERROR(status_end_element_mismatch, s); - - while (PUGI__IS_CHARTYPE(*s, ct_symbol)) - { - if (*s++ != *name++) PUGI__THROW_ERROR(status_end_element_mismatch, s); - } - - if (*name) - { - if (*s == 0 && name[0] == endch && name[1] == 0) PUGI__THROW_ERROR(status_bad_end_element, s); - else PUGI__THROW_ERROR(status_end_element_mismatch, s); - } - - PUGI__POPNODE(); // Pop. - - PUGI__SKIPWS(); - - if (*s == 0) - { - if (endch != '>') PUGI__THROW_ERROR(status_bad_end_element, s); - } - else - { - if (*s != '>') PUGI__THROW_ERROR(status_bad_end_element, s); - ++s; - } - } - else if (*s == '?') // 'first_child) continue; - } - } - - if (!PUGI__OPTSET(parse_trim_pcdata)) - s = mark; - - if (cursor->parent || PUGI__OPTSET(parse_fragment)) - { - PUGI__PUSHNODE(node_pcdata); // Append a new node on the tree. - cursor->value = s; // Save the offset. - - s = strconv_pcdata(s); - - PUGI__POPNODE(); // Pop since this is a standalone. - - if (!*s) break; - } - else - { - PUGI__SCANFOR(*s == '<'); // '...<' - if (!*s) break; - - ++s; - } - - // We're after '<' - goto LOC_TAG; - } - } - - // check that last tag is closed - if (cursor != root) PUGI__THROW_ERROR(status_end_element_mismatch, s); - - return s; - } - - #ifdef PUGIXML_WCHAR_MODE - static char_t* parse_skip_bom(char_t* s) - { - unsigned int bom = 0xfeff; - return (s[0] == static_cast(bom)) ? s + 1 : s; - } - #else - static char_t* parse_skip_bom(char_t* s) - { - return (s[0] == '\xef' && s[1] == '\xbb' && s[2] == '\xbf') ? s + 3 : s; - } - #endif - - static bool has_element_node_siblings(xml_node_struct* node) - { - while (node) - { - if (PUGI__NODETYPE(node) == node_element) return true; - - node = node->next_sibling; - } - - return false; - } - - static xml_parse_result parse(char_t* buffer, size_t length, xml_document_struct* xmldoc, xml_node_struct* root, unsigned int optmsk) - { - // early-out for empty documents - if (length == 0) - return make_parse_result(PUGI__OPTSET(parse_fragment) ? status_ok : status_no_document_element); - - // get last child of the root before parsing - xml_node_struct* last_root_child = root->first_child ? root->first_child->prev_sibling_c + 0 : 0; - - // create parser on stack - xml_parser parser(static_cast(xmldoc)); - - // save last character and make buffer zero-terminated (speeds up parsing) - char_t endch = buffer[length - 1]; - buffer[length - 1] = 0; - - // skip BOM to make sure it does not end up as part of parse output - char_t* buffer_data = parse_skip_bom(buffer); - - // perform actual parsing - parser.parse_tree(buffer_data, root, optmsk, endch); - - xml_parse_result result = make_parse_result(parser.error_status, parser.error_offset ? parser.error_offset - buffer : 0); - assert(result.offset >= 0 && static_cast(result.offset) <= length); - - if (result) - { - // since we removed last character, we have to handle the only possible false positive (stray <) - if (endch == '<') - return make_parse_result(status_unrecognized_tag, length - 1); - - // check if there are any element nodes parsed - xml_node_struct* first_root_child_parsed = last_root_child ? last_root_child->next_sibling + 0 : root->first_child+ 0; - - if (!PUGI__OPTSET(parse_fragment) && !has_element_node_siblings(first_root_child_parsed)) - return make_parse_result(status_no_document_element, length - 1); - } - else - { - // roll back offset if it occurs on a null terminator in the source buffer - if (result.offset > 0 && static_cast(result.offset) == length - 1 && endch == 0) - result.offset--; - } - - return result; - } - }; - - // Output facilities - PUGI__FN xml_encoding get_write_native_encoding() - { - #ifdef PUGIXML_WCHAR_MODE - return get_wchar_encoding(); - #else - return encoding_utf8; - #endif - } - - PUGI__FN xml_encoding get_write_encoding(xml_encoding encoding) - { - // replace wchar encoding with utf implementation - if (encoding == encoding_wchar) return get_wchar_encoding(); - - // replace utf16 encoding with utf16 with specific endianness - if (encoding == encoding_utf16) return is_little_endian() ? encoding_utf16_le : encoding_utf16_be; - - // replace utf32 encoding with utf32 with specific endianness - if (encoding == encoding_utf32) return is_little_endian() ? encoding_utf32_le : encoding_utf32_be; - - // only do autodetection if no explicit encoding is requested - if (encoding != encoding_auto) return encoding; - - // assume utf8 encoding - return encoding_utf8; - } - - template PUGI__FN size_t convert_buffer_output_generic(typename T::value_type dest, const char_t* data, size_t length, D, T) - { - PUGI__STATIC_ASSERT(sizeof(char_t) == sizeof(typename D::type)); - - typename T::value_type end = D::process(reinterpret_cast(data), length, dest, T()); - - return static_cast(end - dest) * sizeof(*dest); - } - - template PUGI__FN size_t convert_buffer_output_generic(typename T::value_type dest, const char_t* data, size_t length, D, T, bool opt_swap) - { - PUGI__STATIC_ASSERT(sizeof(char_t) == sizeof(typename D::type)); - - typename T::value_type end = D::process(reinterpret_cast(data), length, dest, T()); - - if (opt_swap) - { - for (typename T::value_type i = dest; i != end; ++i) - *i = endian_swap(*i); - } - - return static_cast(end - dest) * sizeof(*dest); - } - -#ifdef PUGIXML_WCHAR_MODE - PUGI__FN size_t get_valid_length(const char_t* data, size_t length) - { - if (length < 1) return 0; - - // discard last character if it's the lead of a surrogate pair - return (sizeof(wchar_t) == 2 && static_cast(static_cast(data[length - 1]) - 0xD800) < 0x400) ? length - 1 : length; - } - - PUGI__FN size_t convert_buffer_output(char_t* r_char, uint8_t* r_u8, uint16_t* r_u16, uint32_t* r_u32, const char_t* data, size_t length, xml_encoding encoding) - { - // only endian-swapping is required - if (need_endian_swap_utf(encoding, get_wchar_encoding())) - { - convert_wchar_endian_swap(r_char, data, length); - - return length * sizeof(char_t); - } - - // convert to utf8 - if (encoding == encoding_utf8) - return convert_buffer_output_generic(r_u8, data, length, wchar_decoder(), utf8_writer()); - - // convert to utf16 - if (encoding == encoding_utf16_be || encoding == encoding_utf16_le) - { - xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be; - - return convert_buffer_output_generic(r_u16, data, length, wchar_decoder(), utf16_writer(), native_encoding != encoding); - } - - // convert to utf32 - if (encoding == encoding_utf32_be || encoding == encoding_utf32_le) - { - xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be; - - return convert_buffer_output_generic(r_u32, data, length, wchar_decoder(), utf32_writer(), native_encoding != encoding); - } - - // convert to latin1 - if (encoding == encoding_latin1) - return convert_buffer_output_generic(r_u8, data, length, wchar_decoder(), latin1_writer()); - - assert(!"Invalid encoding"); - return 0; - } -#else - PUGI__FN size_t get_valid_length(const char_t* data, size_t length) - { - if (length < 5) return 0; - - for (size_t i = 1; i <= 4; ++i) - { - uint8_t ch = static_cast(data[length - i]); - - // either a standalone character or a leading one - if ((ch & 0xc0) != 0x80) return length - i; - } - - // there are four non-leading characters at the end, sequence tail is broken so might as well process the whole chunk - return length; - } - - PUGI__FN size_t convert_buffer_output(char_t* /* r_char */, uint8_t* r_u8, uint16_t* r_u16, uint32_t* r_u32, const char_t* data, size_t length, xml_encoding encoding) - { - if (encoding == encoding_utf16_be || encoding == encoding_utf16_le) - { - xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be; - - return convert_buffer_output_generic(r_u16, data, length, utf8_decoder(), utf16_writer(), native_encoding != encoding); - } - - if (encoding == encoding_utf32_be || encoding == encoding_utf32_le) - { - xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be; - - return convert_buffer_output_generic(r_u32, data, length, utf8_decoder(), utf32_writer(), native_encoding != encoding); - } - - if (encoding == encoding_latin1) - return convert_buffer_output_generic(r_u8, data, length, utf8_decoder(), latin1_writer()); - - assert(!"Invalid encoding"); - return 0; - } -#endif - - class xml_buffered_writer - { - xml_buffered_writer(const xml_buffered_writer&); - xml_buffered_writer& operator=(const xml_buffered_writer&); - - public: - xml_buffered_writer(xml_writer& writer_, xml_encoding user_encoding): writer(writer_), bufsize(0), encoding(get_write_encoding(user_encoding)) - { - PUGI__STATIC_ASSERT(bufcapacity >= 8); - } - - size_t flush() - { - flush(buffer, bufsize); - bufsize = 0; - return 0; - } - - void flush(const char_t* data, size_t size) - { - if (size == 0) return; - - // fast path, just write data - if (encoding == get_write_native_encoding()) - writer.write(data, size * sizeof(char_t)); - else - { - // convert chunk - size_t result = convert_buffer_output(scratch.data_char, scratch.data_u8, scratch.data_u16, scratch.data_u32, data, size, encoding); - assert(result <= sizeof(scratch)); - - // write data - writer.write(scratch.data_u8, result); - } - } - - void write_direct(const char_t* data, size_t length) - { - // flush the remaining buffer contents - flush(); - - // handle large chunks - if (length > bufcapacity) - { - if (encoding == get_write_native_encoding()) - { - // fast path, can just write data chunk - writer.write(data, length * sizeof(char_t)); - return; - } - - // need to convert in suitable chunks - while (length > bufcapacity) - { - // get chunk size by selecting such number of characters that are guaranteed to fit into scratch buffer - // and form a complete codepoint sequence (i.e. discard start of last codepoint if necessary) - size_t chunk_size = get_valid_length(data, bufcapacity); - assert(chunk_size); - - // convert chunk and write - flush(data, chunk_size); - - // iterate - data += chunk_size; - length -= chunk_size; - } - - // small tail is copied below - bufsize = 0; - } - - memcpy(buffer + bufsize, data, length * sizeof(char_t)); - bufsize += length; - } - - void write_buffer(const char_t* data, size_t length) - { - size_t offset = bufsize; - - if (offset + length <= bufcapacity) - { - memcpy(buffer + offset, data, length * sizeof(char_t)); - bufsize = offset + length; - } - else - { - write_direct(data, length); - } - } - - void write_string(const char_t* data) - { - // write the part of the string that fits in the buffer - size_t offset = bufsize; - - while (*data && offset < bufcapacity) - buffer[offset++] = *data++; - - // write the rest - if (offset < bufcapacity) - { - bufsize = offset; - } - else - { - // backtrack a bit if we have split the codepoint - size_t length = offset - bufsize; - size_t extra = length - get_valid_length(data - length, length); - - bufsize = offset - extra; - - write_direct(data - extra, strlength(data) + extra); - } - } - - void write(char_t d0) - { - size_t offset = bufsize; - if (offset > bufcapacity - 1) offset = flush(); - - buffer[offset + 0] = d0; - bufsize = offset + 1; - } - - void write(char_t d0, char_t d1) - { - size_t offset = bufsize; - if (offset > bufcapacity - 2) offset = flush(); - - buffer[offset + 0] = d0; - buffer[offset + 1] = d1; - bufsize = offset + 2; - } - - void write(char_t d0, char_t d1, char_t d2) - { - size_t offset = bufsize; - if (offset > bufcapacity - 3) offset = flush(); - - buffer[offset + 0] = d0; - buffer[offset + 1] = d1; - buffer[offset + 2] = d2; - bufsize = offset + 3; - } - - void write(char_t d0, char_t d1, char_t d2, char_t d3) - { - size_t offset = bufsize; - if (offset > bufcapacity - 4) offset = flush(); - - buffer[offset + 0] = d0; - buffer[offset + 1] = d1; - buffer[offset + 2] = d2; - buffer[offset + 3] = d3; - bufsize = offset + 4; - } - - void write(char_t d0, char_t d1, char_t d2, char_t d3, char_t d4) - { - size_t offset = bufsize; - if (offset > bufcapacity - 5) offset = flush(); - - buffer[offset + 0] = d0; - buffer[offset + 1] = d1; - buffer[offset + 2] = d2; - buffer[offset + 3] = d3; - buffer[offset + 4] = d4; - bufsize = offset + 5; - } - - void write(char_t d0, char_t d1, char_t d2, char_t d3, char_t d4, char_t d5) - { - size_t offset = bufsize; - if (offset > bufcapacity - 6) offset = flush(); - - buffer[offset + 0] = d0; - buffer[offset + 1] = d1; - buffer[offset + 2] = d2; - buffer[offset + 3] = d3; - buffer[offset + 4] = d4; - buffer[offset + 5] = d5; - bufsize = offset + 6; - } - - // utf8 maximum expansion: x4 (-> utf32) - // utf16 maximum expansion: x2 (-> utf32) - // utf32 maximum expansion: x1 - enum - { - bufcapacitybytes = - #ifdef PUGIXML_MEMORY_OUTPUT_STACK - PUGIXML_MEMORY_OUTPUT_STACK - #else - 10240 - #endif - , - bufcapacity = bufcapacitybytes / (sizeof(char_t) + 4) - }; - - char_t buffer[bufcapacity]; - - union - { - uint8_t data_u8[4 * bufcapacity]; - uint16_t data_u16[2 * bufcapacity]; - uint32_t data_u32[bufcapacity]; - char_t data_char[bufcapacity]; - } scratch; - - xml_writer& writer; - size_t bufsize; - xml_encoding encoding; - }; - - PUGI__FN void text_output_escaped(xml_buffered_writer& writer, const char_t* s, chartypex_t type) - { - while (*s) - { - const char_t* prev = s; - - // While *s is a usual symbol - PUGI__SCANWHILE_UNROLL(!PUGI__IS_CHARTYPEX(ss, type)); - - writer.write_buffer(prev, static_cast(s - prev)); - - switch (*s) - { - case 0: break; - case '&': - writer.write('&', 'a', 'm', 'p', ';'); - ++s; - break; - case '<': - writer.write('&', 'l', 't', ';'); - ++s; - break; - case '>': - writer.write('&', 'g', 't', ';'); - ++s; - break; - case '"': - writer.write('&', 'q', 'u', 'o', 't', ';'); - ++s; - break; - default: // s is not a usual symbol - { - unsigned int ch = static_cast(*s++); - assert(ch < 32); - - writer.write('&', '#', static_cast((ch / 10) + '0'), static_cast((ch % 10) + '0'), ';'); - } - } - } - } - - PUGI__FN void text_output(xml_buffered_writer& writer, const char_t* s, chartypex_t type, unsigned int flags) - { - if (flags & format_no_escapes) - writer.write_string(s); - else - text_output_escaped(writer, s, type); - } - - PUGI__FN void text_output_cdata(xml_buffered_writer& writer, const char_t* s) - { - do - { - writer.write('<', '!', '[', 'C', 'D'); - writer.write('A', 'T', 'A', '['); - - const char_t* prev = s; - - // look for ]]> sequence - we can't output it as is since it terminates CDATA - while (*s && !(s[0] == ']' && s[1] == ']' && s[2] == '>')) ++s; - - // skip ]] if we stopped at ]]>, > will go to the next CDATA section - if (*s) s += 2; - - writer.write_buffer(prev, static_cast(s - prev)); - - writer.write(']', ']', '>'); - } - while (*s); - } - - PUGI__FN void text_output_indent(xml_buffered_writer& writer, const char_t* indent, size_t indent_length, unsigned int depth) - { - switch (indent_length) - { - case 1: - { - for (unsigned int i = 0; i < depth; ++i) - writer.write(indent[0]); - break; - } - - case 2: - { - for (unsigned int i = 0; i < depth; ++i) - writer.write(indent[0], indent[1]); - break; - } - - case 3: - { - for (unsigned int i = 0; i < depth; ++i) - writer.write(indent[0], indent[1], indent[2]); - break; - } - - case 4: - { - for (unsigned int i = 0; i < depth; ++i) - writer.write(indent[0], indent[1], indent[2], indent[3]); - break; - } - - default: - { - for (unsigned int i = 0; i < depth; ++i) - writer.write_buffer(indent, indent_length); - } - } - } - - PUGI__FN void node_output_comment(xml_buffered_writer& writer, const char_t* s) - { - writer.write('<', '!', '-', '-'); - - while (*s) - { - const char_t* prev = s; - - // look for -\0 or -- sequence - we can't output it since -- is illegal in comment body - while (*s && !(s[0] == '-' && (s[1] == '-' || s[1] == 0))) ++s; - - writer.write_buffer(prev, static_cast(s - prev)); - - if (*s) - { - assert(*s == '-'); - - writer.write('-', ' '); - ++s; - } - } - - writer.write('-', '-', '>'); - } - - PUGI__FN void node_output_pi_value(xml_buffered_writer& writer, const char_t* s) - { - while (*s) - { - const char_t* prev = s; - - // look for ?> sequence - we can't output it since ?> terminates PI - while (*s && !(s[0] == '?' && s[1] == '>')) ++s; - - writer.write_buffer(prev, static_cast(s - prev)); - - if (*s) - { - assert(s[0] == '?' && s[1] == '>'); - - writer.write('?', ' ', '>'); - s += 2; - } - } - } - - PUGI__FN void node_output_attributes(xml_buffered_writer& writer, xml_node_struct* node, const char_t* indent, size_t indent_length, unsigned int flags, unsigned int depth) - { - const char_t* default_name = PUGIXML_TEXT(":anonymous"); - - for (xml_attribute_struct* a = node->first_attribute; a; a = a->next_attribute) - { - if ((flags & (format_indent_attributes | format_raw)) == format_indent_attributes) - { - writer.write('\n'); - - text_output_indent(writer, indent, indent_length, depth + 1); - } - else - { - writer.write(' '); - } - - writer.write_string(a->name ? a->name + 0 : default_name); - writer.write('=', '"'); - - if (a->value) - text_output(writer, a->value, ctx_special_attr, flags); - - writer.write('"'); - } - } - - PUGI__FN bool node_output_start(xml_buffered_writer& writer, xml_node_struct* node, const char_t* indent, size_t indent_length, unsigned int flags, unsigned int depth) - { - const char_t* default_name = PUGIXML_TEXT(":anonymous"); - const char_t* name = node->name ? node->name + 0 : default_name; - - writer.write('<'); - writer.write_string(name); - - if (node->first_attribute) - node_output_attributes(writer, node, indent, indent_length, flags, depth); - - if (!node->first_child) - { - writer.write(' ', '/', '>'); - - return false; - } - else - { - writer.write('>'); - - return true; - } - } - - PUGI__FN void node_output_end(xml_buffered_writer& writer, xml_node_struct* node) - { - const char_t* default_name = PUGIXML_TEXT(":anonymous"); - const char_t* name = node->name ? node->name + 0 : default_name; - - writer.write('<', '/'); - writer.write_string(name); - writer.write('>'); - } - - PUGI__FN void node_output_simple(xml_buffered_writer& writer, xml_node_struct* node, unsigned int flags) - { - const char_t* default_name = PUGIXML_TEXT(":anonymous"); - - switch (PUGI__NODETYPE(node)) - { - case node_pcdata: - text_output(writer, node->value ? node->value + 0 : PUGIXML_TEXT(""), ctx_special_pcdata, flags); - break; - - case node_cdata: - text_output_cdata(writer, node->value ? node->value + 0 : PUGIXML_TEXT("")); - break; - - case node_comment: - node_output_comment(writer, node->value ? node->value + 0 : PUGIXML_TEXT("")); - break; - - case node_pi: - writer.write('<', '?'); - writer.write_string(node->name ? node->name + 0 : default_name); - - if (node->value) - { - writer.write(' '); - node_output_pi_value(writer, node->value); - } - - writer.write('?', '>'); - break; - - case node_declaration: - writer.write('<', '?'); - writer.write_string(node->name ? node->name + 0 : default_name); - node_output_attributes(writer, node, PUGIXML_TEXT(""), 0, flags | format_raw, 0); - writer.write('?', '>'); - break; - - case node_doctype: - writer.write('<', '!', 'D', 'O', 'C'); - writer.write('T', 'Y', 'P', 'E'); - - if (node->value) - { - writer.write(' '); - writer.write_string(node->value); - } - - writer.write('>'); - break; - - default: - assert(!"Invalid node type"); - } - } - - enum indent_flags_t - { - indent_newline = 1, - indent_indent = 2 - }; - - PUGI__FN void node_output(xml_buffered_writer& writer, xml_node_struct* root, const char_t* indent, unsigned int flags, unsigned int depth) - { - size_t indent_length = ((flags & (format_indent | format_indent_attributes)) && (flags & format_raw) == 0) ? strlength(indent) : 0; - unsigned int indent_flags = indent_indent; - - xml_node_struct* node = root; - - do - { - assert(node); - - // begin writing current node - if (PUGI__NODETYPE(node) == node_pcdata || PUGI__NODETYPE(node) == node_cdata) - { - node_output_simple(writer, node, flags); - - indent_flags = 0; - } - else - { - if ((indent_flags & indent_newline) && (flags & format_raw) == 0) - writer.write('\n'); - - if ((indent_flags & indent_indent) && indent_length) - text_output_indent(writer, indent, indent_length, depth); - - if (PUGI__NODETYPE(node) == node_element) - { - indent_flags = indent_newline | indent_indent; - - if (node_output_start(writer, node, indent, indent_length, flags, depth)) - { - node = node->first_child; - depth++; - continue; - } - } - else if (PUGI__NODETYPE(node) == node_document) - { - indent_flags = indent_indent; - - if (node->first_child) - { - node = node->first_child; - continue; - } - } - else - { - node_output_simple(writer, node, flags); - - indent_flags = indent_newline | indent_indent; - } - } - - // continue to the next node - while (node != root) - { - if (node->next_sibling) - { - node = node->next_sibling; - break; - } - - node = node->parent; - - // write closing node - if (PUGI__NODETYPE(node) == node_element) - { - depth--; - - if ((indent_flags & indent_newline) && (flags & format_raw) == 0) - writer.write('\n'); - - if ((indent_flags & indent_indent) && indent_length) - text_output_indent(writer, indent, indent_length, depth); - - node_output_end(writer, node); - - indent_flags = indent_newline | indent_indent; - } - } - } - while (node != root); - - if ((indent_flags & indent_newline) && (flags & format_raw) == 0) - writer.write('\n'); - } - - PUGI__FN bool has_declaration(xml_node_struct* node) - { - for (xml_node_struct* child = node->first_child; child; child = child->next_sibling) - { - xml_node_type type = PUGI__NODETYPE(child); - - if (type == node_declaration) return true; - if (type == node_element) return false; - } - - return false; - } - - PUGI__FN bool is_attribute_of(xml_attribute_struct* attr, xml_node_struct* node) - { - for (xml_attribute_struct* a = node->first_attribute; a; a = a->next_attribute) - if (a == attr) - return true; - - return false; - } - - PUGI__FN bool allow_insert_attribute(xml_node_type parent) - { - return parent == node_element || parent == node_declaration; - } - - PUGI__FN bool allow_insert_child(xml_node_type parent, xml_node_type child) - { - if (parent != node_document && parent != node_element) return false; - if (child == node_document || child == node_null) return false; - if (parent != node_document && (child == node_declaration || child == node_doctype)) return false; - - return true; - } - - PUGI__FN bool allow_move(xml_node parent, xml_node child) - { - // check that child can be a child of parent - if (!allow_insert_child(parent.type(), child.type())) - return false; - - // check that node is not moved between documents - if (parent.root() != child.root()) - return false; - - // check that new parent is not in the child subtree - xml_node cur = parent; - - while (cur) - { - if (cur == child) - return false; - - cur = cur.parent(); - } - - return true; - } - - template - PUGI__FN void node_copy_string(String& dest, Header& header, uintptr_t header_mask, char_t* source, Header& source_header, xml_allocator* alloc) - { - assert(!dest && (header & header_mask) == 0); - - if (source) - { - if (alloc && (source_header & header_mask) == 0) - { - dest = source; - - // since strcpy_insitu can reuse document buffer memory we need to mark both source and dest as shared - header |= xml_memory_page_contents_shared_mask; - source_header |= xml_memory_page_contents_shared_mask; - } - else - strcpy_insitu(dest, header, header_mask, source, strlength(source)); - } - } - - PUGI__FN void node_copy_contents(xml_node_struct* dn, xml_node_struct* sn, xml_allocator* shared_alloc) - { - node_copy_string(dn->name, dn->header, xml_memory_page_name_allocated_mask, sn->name, sn->header, shared_alloc); - node_copy_string(dn->value, dn->header, xml_memory_page_value_allocated_mask, sn->value, sn->header, shared_alloc); - - for (xml_attribute_struct* sa = sn->first_attribute; sa; sa = sa->next_attribute) - { - xml_attribute_struct* da = append_new_attribute(dn, get_allocator(dn)); - - if (da) - { - node_copy_string(da->name, da->header, xml_memory_page_name_allocated_mask, sa->name, sa->header, shared_alloc); - node_copy_string(da->value, da->header, xml_memory_page_value_allocated_mask, sa->value, sa->header, shared_alloc); - } - } - } - - PUGI__FN void node_copy_tree(xml_node_struct* dn, xml_node_struct* sn) - { - xml_allocator& alloc = get_allocator(dn); - xml_allocator* shared_alloc = (&alloc == &get_allocator(sn)) ? &alloc : 0; - - node_copy_contents(dn, sn, shared_alloc); - - xml_node_struct* dit = dn; - xml_node_struct* sit = sn->first_child; - - while (sit && sit != sn) - { - if (sit != dn) - { - xml_node_struct* copy = append_new_node(dit, alloc, PUGI__NODETYPE(sit)); - - if (copy) - { - node_copy_contents(copy, sit, shared_alloc); - - if (sit->first_child) - { - dit = copy; - sit = sit->first_child; - continue; - } - } - } - - // continue to the next node - do - { - if (sit->next_sibling) - { - sit = sit->next_sibling; - break; - } - - sit = sit->parent; - dit = dit->parent; - } - while (sit != sn); - } - } - - PUGI__FN void node_copy_attribute(xml_attribute_struct* da, xml_attribute_struct* sa) - { - xml_allocator& alloc = get_allocator(da); - xml_allocator* shared_alloc = (&alloc == &get_allocator(sa)) ? &alloc : 0; - - node_copy_string(da->name, da->header, xml_memory_page_name_allocated_mask, sa->name, sa->header, shared_alloc); - node_copy_string(da->value, da->header, xml_memory_page_value_allocated_mask, sa->value, sa->header, shared_alloc); - } - - inline bool is_text_node(xml_node_struct* node) - { - xml_node_type type = PUGI__NODETYPE(node); - - return type == node_pcdata || type == node_cdata; - } - - // get value with conversion functions - template U string_to_integer(const char_t* value, U minneg, U maxpos) - { - U result = 0; - const char_t* s = value; - - while (PUGI__IS_CHARTYPE(*s, ct_space)) - s++; - - bool negative = (*s == '-'); - - s += (*s == '+' || *s == '-'); - - bool overflow = false; - - if (s[0] == '0' && (s[1] | ' ') == 'x') - { - s += 2; - - // since overflow detection relies on length of the sequence skip leading zeros - while (*s == '0') - s++; - - const char_t* start = s; - - for (;;) - { - if (static_cast(*s - '0') < 10) - result = result * 16 + (*s - '0'); - else if (static_cast((*s | ' ') - 'a') < 6) - result = result * 16 + ((*s | ' ') - 'a' + 10); - else - break; - - s++; - } - - size_t digits = static_cast(s - start); - - overflow = digits > sizeof(U) * 2; - } - else - { - // since overflow detection relies on length of the sequence skip leading zeros - while (*s == '0') - s++; - - const char_t* start = s; - - for (;;) - { - if (static_cast(*s - '0') < 10) - result = result * 10 + (*s - '0'); - else - break; - - s++; - } - - size_t digits = static_cast(s - start); - - PUGI__STATIC_ASSERT(sizeof(U) == 8 || sizeof(U) == 4 || sizeof(U) == 2); - - const size_t max_digits10 = sizeof(U) == 8 ? 20 : sizeof(U) == 4 ? 10 : 5; - const char_t max_lead = sizeof(U) == 8 ? '1' : sizeof(U) == 4 ? '4' : '6'; - const size_t high_bit = sizeof(U) * 8 - 1; - - overflow = digits >= max_digits10 && !(digits == max_digits10 && (*start < max_lead || (*start == max_lead && result >> high_bit))); - } - - if (negative) - return (overflow || result > minneg) ? 0 - minneg : 0 - result; - else - return (overflow || result > maxpos) ? maxpos : result; - } - - PUGI__FN int get_value_int(const char_t* value) - { - return string_to_integer(value, 0 - static_cast(INT_MIN), INT_MAX); - } - - PUGI__FN unsigned int get_value_uint(const char_t* value) - { - return string_to_integer(value, 0, UINT_MAX); - } - - PUGI__FN double get_value_double(const char_t* value) - { - #ifdef PUGIXML_WCHAR_MODE - return wcstod(value, 0); - #else - return strtod(value, 0); - #endif - } - - PUGI__FN float get_value_float(const char_t* value) - { - #ifdef PUGIXML_WCHAR_MODE - return static_cast(wcstod(value, 0)); - #else - return static_cast(strtod(value, 0)); - #endif - } - - PUGI__FN bool get_value_bool(const char_t* value) - { - // only look at first char - char_t first = *value; - - // 1*, t* (true), T* (True), y* (yes), Y* (YES) - return (first == '1' || first == 't' || first == 'T' || first == 'y' || first == 'Y'); - } - -#ifdef PUGIXML_HAS_LONG_LONG - PUGI__FN long long get_value_llong(const char_t* value) - { - return string_to_integer(value, 0 - static_cast(LLONG_MIN), LLONG_MAX); - } - - PUGI__FN unsigned long long get_value_ullong(const char_t* value) - { - return string_to_integer(value, 0, ULLONG_MAX); - } -#endif - - template - PUGI__FN char_t* integer_to_string(char_t* begin, char_t* end, U value, bool negative) - { - char_t* result = end - 1; - U rest = negative ? 0 - value : value; - - do - { - *result-- = static_cast('0' + (rest % 10)); - rest /= 10; - } - while (rest); - - assert(result >= begin); - (void)begin; - - *result = '-'; - - return result + !negative; - } - - // set value with conversion functions - template - PUGI__FN bool set_value_ascii(String& dest, Header& header, uintptr_t header_mask, char* buf) - { - #ifdef PUGIXML_WCHAR_MODE - char_t wbuf[128]; - assert(strlen(buf) < sizeof(wbuf) / sizeof(wbuf[0])); - - size_t offset = 0; - for (; buf[offset]; ++offset) wbuf[offset] = buf[offset]; - - return strcpy_insitu(dest, header, header_mask, wbuf, offset); - #else - return strcpy_insitu(dest, header, header_mask, buf, strlen(buf)); - #endif - } - - template - PUGI__FN bool set_value_convert(String& dest, Header& header, uintptr_t header_mask, int value) - { - char_t buf[64]; - char_t* end = buf + sizeof(buf) / sizeof(buf[0]); - char_t* begin = integer_to_string(buf, end, value, value < 0); - - return strcpy_insitu(dest, header, header_mask, begin, end - begin); - } - - template - PUGI__FN bool set_value_convert(String& dest, Header& header, uintptr_t header_mask, unsigned int value) - { - char_t buf[64]; - char_t* end = buf + sizeof(buf) / sizeof(buf[0]); - char_t* begin = integer_to_string(buf, end, value, false); - - return strcpy_insitu(dest, header, header_mask, begin, end - begin); - } - - template - PUGI__FN bool set_value_convert(String& dest, Header& header, uintptr_t header_mask, float value) - { - char buf[128]; - sprintf(buf, "%.9g", value); - - return set_value_ascii(dest, header, header_mask, buf); - } - - template - PUGI__FN bool set_value_convert(String& dest, Header& header, uintptr_t header_mask, double value) - { - char buf[128]; - sprintf(buf, "%.17g", value); - - return set_value_ascii(dest, header, header_mask, buf); - } - - template - PUGI__FN bool set_value_convert(String& dest, Header& header, uintptr_t header_mask, bool value) - { - return strcpy_insitu(dest, header, header_mask, value ? PUGIXML_TEXT("true") : PUGIXML_TEXT("false"), value ? 4 : 5); - } - -#ifdef PUGIXML_HAS_LONG_LONG - template - PUGI__FN bool set_value_convert(String& dest, Header& header, uintptr_t header_mask, long long value) - { - char_t buf[64]; - char_t* end = buf + sizeof(buf) / sizeof(buf[0]); - char_t* begin = integer_to_string(buf, end, value, value < 0); - - return strcpy_insitu(dest, header, header_mask, begin, end - begin); - } - - template - PUGI__FN bool set_value_convert(String& dest, Header& header, uintptr_t header_mask, unsigned long long value) - { - char_t buf[64]; - char_t* end = buf + sizeof(buf) / sizeof(buf[0]); - char_t* begin = integer_to_string(buf, end, value, false); - - return strcpy_insitu(dest, header, header_mask, begin, end - begin); - } -#endif - - PUGI__FN xml_parse_result load_buffer_impl(xml_document_struct* doc, xml_node_struct* root, void* contents, size_t size, unsigned int options, xml_encoding encoding, bool is_mutable, bool own, char_t** out_buffer) - { - // check input buffer - if (!contents && size) return make_parse_result(status_io_error); - - // get actual encoding - xml_encoding buffer_encoding = impl::get_buffer_encoding(encoding, contents, size); - - // get private buffer - char_t* buffer = 0; - size_t length = 0; - - if (!impl::convert_buffer(buffer, length, buffer_encoding, contents, size, is_mutable)) return impl::make_parse_result(status_out_of_memory); - - // delete original buffer if we performed a conversion - if (own && buffer != contents && contents) impl::xml_memory::deallocate(contents); - - // grab onto buffer if it's our buffer, user is responsible for deallocating contents himself - if (own || buffer != contents) *out_buffer = buffer; - - // store buffer for offset_debug - doc->buffer = buffer; - - // parse - xml_parse_result res = impl::xml_parser::parse(buffer, length, doc, root, options); - - // remember encoding - res.encoding = buffer_encoding; - - return res; - } - - // we need to get length of entire file to load it in memory; the only (relatively) sane way to do it is via seek/tell trick - PUGI__FN xml_parse_status get_file_size(FILE* file, size_t& out_result) - { - #if defined(PUGI__MSVC_CRT_VERSION) && PUGI__MSVC_CRT_VERSION >= 1400 && !defined(_WIN32_WCE) - // there are 64-bit versions of fseek/ftell, let's use them - typedef __int64 length_type; - - _fseeki64(file, 0, SEEK_END); - length_type length = _ftelli64(file); - _fseeki64(file, 0, SEEK_SET); - #elif defined(__MINGW32__) && !defined(__NO_MINGW_LFS) && (!defined(__STRICT_ANSI__) || defined(__MINGW64_VERSION_MAJOR)) - // there are 64-bit versions of fseek/ftell, let's use them - typedef off64_t length_type; - - fseeko64(file, 0, SEEK_END); - length_type length = ftello64(file); - fseeko64(file, 0, SEEK_SET); - #else - // if this is a 32-bit OS, long is enough; if this is a unix system, long is 64-bit, which is enough; otherwise we can't do anything anyway. - typedef long length_type; - - fseek(file, 0, SEEK_END); - length_type length = ftell(file); - fseek(file, 0, SEEK_SET); - #endif - - // check for I/O errors - if (length < 0) return status_io_error; - - // check for overflow - size_t result = static_cast(length); - - if (static_cast(result) != length) return status_out_of_memory; - - // finalize - out_result = result; - - return status_ok; - } - - // This function assumes that buffer has extra sizeof(char_t) writable bytes after size - PUGI__FN size_t zero_terminate_buffer(void* buffer, size_t size, xml_encoding encoding) - { - // We only need to zero-terminate if encoding conversion does not do it for us - #ifdef PUGIXML_WCHAR_MODE - xml_encoding wchar_encoding = get_wchar_encoding(); - - if (encoding == wchar_encoding || need_endian_swap_utf(encoding, wchar_encoding)) - { - size_t length = size / sizeof(char_t); - - static_cast(buffer)[length] = 0; - return (length + 1) * sizeof(char_t); - } - #else - if (encoding == encoding_utf8) - { - static_cast(buffer)[size] = 0; - return size + 1; - } - #endif - - return size; - } - - PUGI__FN xml_parse_result load_file_impl(xml_document_struct* doc, FILE* file, unsigned int options, xml_encoding encoding, char_t** out_buffer) - { - if (!file) return make_parse_result(status_file_not_found); - - // get file size (can result in I/O errors) - size_t size = 0; - xml_parse_status size_status = get_file_size(file, size); - if (size_status != status_ok) return make_parse_result(size_status); - - size_t max_suffix_size = sizeof(char_t); - - // allocate buffer for the whole file - char* contents = static_cast(xml_memory::allocate(size + max_suffix_size)); - if (!contents) return make_parse_result(status_out_of_memory); - - // read file in memory - size_t read_size = fread(contents, 1, size, file); - - if (read_size != size) - { - xml_memory::deallocate(contents); - return make_parse_result(status_io_error); - } - - xml_encoding real_encoding = get_buffer_encoding(encoding, contents, size); - - return load_buffer_impl(doc, doc, contents, zero_terminate_buffer(contents, size, real_encoding), options, real_encoding, true, true, out_buffer); - } - -#ifndef PUGIXML_NO_STL - template struct xml_stream_chunk - { - static xml_stream_chunk* create() - { - void* memory = xml_memory::allocate(sizeof(xml_stream_chunk)); - if (!memory) return 0; - - return new (memory) xml_stream_chunk(); - } - - static void destroy(xml_stream_chunk* chunk) - { - // free chunk chain - while (chunk) - { - xml_stream_chunk* next_ = chunk->next; - - xml_memory::deallocate(chunk); - - chunk = next_; - } - } - - xml_stream_chunk(): next(0), size(0) - { - } - - xml_stream_chunk* next; - size_t size; - - T data[xml_memory_page_size / sizeof(T)]; - }; - - template PUGI__FN xml_parse_status load_stream_data_noseek(std::basic_istream& stream, void** out_buffer, size_t* out_size) - { - auto_deleter > chunks(0, xml_stream_chunk::destroy); - - // read file to a chunk list - size_t total = 0; - xml_stream_chunk* last = 0; - - while (!stream.eof()) - { - // allocate new chunk - xml_stream_chunk* chunk = xml_stream_chunk::create(); - if (!chunk) return status_out_of_memory; - - // append chunk to list - if (last) last = last->next = chunk; - else chunks.data = last = chunk; - - // read data to chunk - stream.read(chunk->data, static_cast(sizeof(chunk->data) / sizeof(T))); - chunk->size = static_cast(stream.gcount()) * sizeof(T); - - // read may set failbit | eofbit in case gcount() is less than read length, so check for other I/O errors - if (stream.bad() || (!stream.eof() && stream.fail())) return status_io_error; - - // guard against huge files (chunk size is small enough to make this overflow check work) - if (total + chunk->size < total) return status_out_of_memory; - total += chunk->size; - } - - size_t max_suffix_size = sizeof(char_t); - - // copy chunk list to a contiguous buffer - char* buffer = static_cast(xml_memory::allocate(total + max_suffix_size)); - if (!buffer) return status_out_of_memory; - - char* write = buffer; - - for (xml_stream_chunk* chunk = chunks.data; chunk; chunk = chunk->next) - { - assert(write + chunk->size <= buffer + total); - memcpy(write, chunk->data, chunk->size); - write += chunk->size; - } - - assert(write == buffer + total); - - // return buffer - *out_buffer = buffer; - *out_size = total; - - return status_ok; - } - - template PUGI__FN xml_parse_status load_stream_data_seek(std::basic_istream& stream, void** out_buffer, size_t* out_size) - { - // get length of remaining data in stream - typename std::basic_istream::pos_type pos = stream.tellg(); - stream.seekg(0, std::ios::end); - std::streamoff length = stream.tellg() - pos; - stream.seekg(pos); - - if (stream.fail() || pos < 0) return status_io_error; - - // guard against huge files - size_t read_length = static_cast(length); - - if (static_cast(read_length) != length || length < 0) return status_out_of_memory; - - size_t max_suffix_size = sizeof(char_t); - - // read stream data into memory (guard against stream exceptions with buffer holder) - auto_deleter buffer(xml_memory::allocate(read_length * sizeof(T) + max_suffix_size), xml_memory::deallocate); - if (!buffer.data) return status_out_of_memory; - - stream.read(static_cast(buffer.data), static_cast(read_length)); - - // read may set failbit | eofbit in case gcount() is less than read_length (i.e. line ending conversion), so check for other I/O errors - if (stream.bad() || (!stream.eof() && stream.fail())) return status_io_error; - - // return buffer - size_t actual_length = static_cast(stream.gcount()); - assert(actual_length <= read_length); - - *out_buffer = buffer.release(); - *out_size = actual_length * sizeof(T); - - return status_ok; - } - - template PUGI__FN xml_parse_result load_stream_impl(xml_document_struct* doc, std::basic_istream& stream, unsigned int options, xml_encoding encoding, char_t** out_buffer) - { - void* buffer = 0; - size_t size = 0; - xml_parse_status status = status_ok; - - // if stream has an error bit set, bail out (otherwise tellg() can fail and we'll clear error bits) - if (stream.fail()) return make_parse_result(status_io_error); - - // load stream to memory (using seek-based implementation if possible, since it's faster and takes less memory) - if (stream.tellg() < 0) - { - stream.clear(); // clear error flags that could be set by a failing tellg - status = load_stream_data_noseek(stream, &buffer, &size); - } - else - status = load_stream_data_seek(stream, &buffer, &size); - - if (status != status_ok) return make_parse_result(status); - - xml_encoding real_encoding = get_buffer_encoding(encoding, buffer, size); - - return load_buffer_impl(doc, doc, buffer, zero_terminate_buffer(buffer, size, real_encoding), options, real_encoding, true, true, out_buffer); - } -#endif - -#if defined(PUGI__MSVC_CRT_VERSION) || defined(__BORLANDC__) || (defined(__MINGW32__) && (!defined(__STRICT_ANSI__) || defined(__MINGW64_VERSION_MAJOR))) - PUGI__FN FILE* open_file_wide(const wchar_t* path, const wchar_t* mode) - { - return _wfopen(path, mode); - } -#else - PUGI__FN char* convert_path_heap(const wchar_t* str) - { - assert(str); - - // first pass: get length in utf8 characters - size_t length = strlength_wide(str); - size_t size = as_utf8_begin(str, length); - - // allocate resulting string - char* result = static_cast(xml_memory::allocate(size + 1)); - if (!result) return 0; - - // second pass: convert to utf8 - as_utf8_end(result, size, str, length); - - // zero-terminate - result[size] = 0; - - return result; - } - - PUGI__FN FILE* open_file_wide(const wchar_t* path, const wchar_t* mode) - { - // there is no standard function to open wide paths, so our best bet is to try utf8 path - char* path_utf8 = convert_path_heap(path); - if (!path_utf8) return 0; - - // convert mode to ASCII (we mirror _wfopen interface) - char mode_ascii[4] = {0}; - for (size_t i = 0; mode[i]; ++i) mode_ascii[i] = static_cast(mode[i]); - - // try to open the utf8 path - FILE* result = fopen(path_utf8, mode_ascii); - - // free dummy buffer - xml_memory::deallocate(path_utf8); - - return result; - } -#endif - - PUGI__FN bool save_file_impl(const xml_document& doc, FILE* file, const char_t* indent, unsigned int flags, xml_encoding encoding) - { - if (!file) return false; - - xml_writer_file writer(file); - doc.save(writer, indent, flags, encoding); - - return ferror(file) == 0; - } - - struct name_null_sentry - { - xml_node_struct* node; - char_t* name; - - name_null_sentry(xml_node_struct* node_): node(node_), name(node_->name) - { - node->name = 0; - } - - ~name_null_sentry() - { - node->name = name; - } - }; -PUGI__NS_END - -namespace pugi -{ - PUGI__FN xml_writer_file::xml_writer_file(void* file_): file(file_) - { - } - - PUGI__FN void xml_writer_file::write(const void* data, size_t size) - { - size_t result = fwrite(data, 1, size, static_cast(file)); - (void)!result; // unfortunately we can't do proper error handling here - } - -#ifndef PUGIXML_NO_STL - PUGI__FN xml_writer_stream::xml_writer_stream(std::basic_ostream >& stream): narrow_stream(&stream), wide_stream(0) - { - } - - PUGI__FN xml_writer_stream::xml_writer_stream(std::basic_ostream >& stream): narrow_stream(0), wide_stream(&stream) - { - } - - PUGI__FN void xml_writer_stream::write(const void* data, size_t size) - { - if (narrow_stream) - { - assert(!wide_stream); - narrow_stream->write(reinterpret_cast(data), static_cast(size)); - } - else - { - assert(wide_stream); - assert(size % sizeof(wchar_t) == 0); - - wide_stream->write(reinterpret_cast(data), static_cast(size / sizeof(wchar_t))); - } - } -#endif - - PUGI__FN xml_tree_walker::xml_tree_walker(): _depth(0) - { - } - - PUGI__FN xml_tree_walker::~xml_tree_walker() - { - } - - PUGI__FN int xml_tree_walker::depth() const - { - return _depth; - } - - PUGI__FN bool xml_tree_walker::begin(xml_node&) - { - return true; - } - - PUGI__FN bool xml_tree_walker::end(xml_node&) - { - return true; - } - - PUGI__FN xml_attribute::xml_attribute(): _attr(0) - { - } - - PUGI__FN xml_attribute::xml_attribute(xml_attribute_struct* attr): _attr(attr) - { - } - - PUGI__FN static void unspecified_bool_xml_attribute(xml_attribute***) - { - } - - PUGI__FN xml_attribute::operator xml_attribute::unspecified_bool_type() const - { - return _attr ? unspecified_bool_xml_attribute : 0; - } - - PUGI__FN bool xml_attribute::operator!() const - { - return !_attr; - } - - PUGI__FN bool xml_attribute::operator==(const xml_attribute& r) const - { - return (_attr == r._attr); - } - - PUGI__FN bool xml_attribute::operator!=(const xml_attribute& r) const - { - return (_attr != r._attr); - } - - PUGI__FN bool xml_attribute::operator<(const xml_attribute& r) const - { - return (_attr < r._attr); - } - - PUGI__FN bool xml_attribute::operator>(const xml_attribute& r) const - { - return (_attr > r._attr); - } - - PUGI__FN bool xml_attribute::operator<=(const xml_attribute& r) const - { - return (_attr <= r._attr); - } - - PUGI__FN bool xml_attribute::operator>=(const xml_attribute& r) const - { - return (_attr >= r._attr); - } - - PUGI__FN xml_attribute xml_attribute::next_attribute() const - { - return _attr ? xml_attribute(_attr->next_attribute) : xml_attribute(); - } - - PUGI__FN xml_attribute xml_attribute::previous_attribute() const - { - return _attr && _attr->prev_attribute_c->next_attribute ? xml_attribute(_attr->prev_attribute_c) : xml_attribute(); - } - - PUGI__FN const char_t* xml_attribute::as_string(const char_t* def) const - { - return (_attr && _attr->value) ? _attr->value + 0 : def; - } - - PUGI__FN int xml_attribute::as_int(int def) const - { - return (_attr && _attr->value) ? impl::get_value_int(_attr->value) : def; - } - - PUGI__FN unsigned int xml_attribute::as_uint(unsigned int def) const - { - return (_attr && _attr->value) ? impl::get_value_uint(_attr->value) : def; - } - - PUGI__FN double xml_attribute::as_double(double def) const - { - return (_attr && _attr->value) ? impl::get_value_double(_attr->value) : def; - } - - PUGI__FN float xml_attribute::as_float(float def) const - { - return (_attr && _attr->value) ? impl::get_value_float(_attr->value) : def; - } - - PUGI__FN bool xml_attribute::as_bool(bool def) const - { - return (_attr && _attr->value) ? impl::get_value_bool(_attr->value) : def; - } - -#ifdef PUGIXML_HAS_LONG_LONG - PUGI__FN long long xml_attribute::as_llong(long long def) const - { - return (_attr && _attr->value) ? impl::get_value_llong(_attr->value) : def; - } - - PUGI__FN unsigned long long xml_attribute::as_ullong(unsigned long long def) const - { - return (_attr && _attr->value) ? impl::get_value_ullong(_attr->value) : def; - } -#endif - - PUGI__FN bool xml_attribute::empty() const - { - return !_attr; - } - - PUGI__FN const char_t* xml_attribute::name() const - { - return (_attr && _attr->name) ? _attr->name + 0 : PUGIXML_TEXT(""); - } - - PUGI__FN const char_t* xml_attribute::value() const - { - return (_attr && _attr->value) ? _attr->value + 0 : PUGIXML_TEXT(""); - } - - PUGI__FN size_t xml_attribute::hash_value() const - { - return static_cast(reinterpret_cast(_attr) / sizeof(xml_attribute_struct)); - } - - PUGI__FN xml_attribute_struct* xml_attribute::internal_object() const - { - return _attr; - } - - PUGI__FN xml_attribute& xml_attribute::operator=(const char_t* rhs) - { - set_value(rhs); - return *this; - } - - PUGI__FN xml_attribute& xml_attribute::operator=(int rhs) - { - set_value(rhs); - return *this; - } - - PUGI__FN xml_attribute& xml_attribute::operator=(unsigned int rhs) - { - set_value(rhs); - return *this; - } - - PUGI__FN xml_attribute& xml_attribute::operator=(double rhs) - { - set_value(rhs); - return *this; - } - - PUGI__FN xml_attribute& xml_attribute::operator=(float rhs) - { - set_value(rhs); - return *this; - } - - PUGI__FN xml_attribute& xml_attribute::operator=(bool rhs) - { - set_value(rhs); - return *this; - } - -#ifdef PUGIXML_HAS_LONG_LONG - PUGI__FN xml_attribute& xml_attribute::operator=(long long rhs) - { - set_value(rhs); - return *this; - } - - PUGI__FN xml_attribute& xml_attribute::operator=(unsigned long long rhs) - { - set_value(rhs); - return *this; - } -#endif - - PUGI__FN bool xml_attribute::set_name(const char_t* rhs) - { - if (!_attr) return false; - - return impl::strcpy_insitu(_attr->name, _attr->header, impl::xml_memory_page_name_allocated_mask, rhs, impl::strlength(rhs)); - } - - PUGI__FN bool xml_attribute::set_value(const char_t* rhs) - { - if (!_attr) return false; - - return impl::strcpy_insitu(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, impl::strlength(rhs)); - } - - PUGI__FN bool xml_attribute::set_value(int rhs) - { - if (!_attr) return false; - - return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs); - } - - PUGI__FN bool xml_attribute::set_value(unsigned int rhs) - { - if (!_attr) return false; - - return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs); - } - - PUGI__FN bool xml_attribute::set_value(double rhs) - { - if (!_attr) return false; - - return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs); - } - - PUGI__FN bool xml_attribute::set_value(float rhs) - { - if (!_attr) return false; - - return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs); - } - - PUGI__FN bool xml_attribute::set_value(bool rhs) - { - if (!_attr) return false; - - return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs); - } - -#ifdef PUGIXML_HAS_LONG_LONG - PUGI__FN bool xml_attribute::set_value(long long rhs) - { - if (!_attr) return false; - - return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs); - } - - PUGI__FN bool xml_attribute::set_value(unsigned long long rhs) - { - if (!_attr) return false; - - return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs); - } -#endif - -#ifdef __BORLANDC__ - PUGI__FN bool operator&&(const xml_attribute& lhs, bool rhs) - { - return (bool)lhs && rhs; - } - - PUGI__FN bool operator||(const xml_attribute& lhs, bool rhs) - { - return (bool)lhs || rhs; - } -#endif - - PUGI__FN xml_node::xml_node(): _root(0) - { - } - - PUGI__FN xml_node::xml_node(xml_node_struct* p): _root(p) - { - } - - PUGI__FN static void unspecified_bool_xml_node(xml_node***) - { - } - - PUGI__FN xml_node::operator xml_node::unspecified_bool_type() const - { - return _root ? unspecified_bool_xml_node : 0; - } - - PUGI__FN bool xml_node::operator!() const - { - return !_root; - } - - PUGI__FN xml_node::iterator xml_node::begin() const - { - return iterator(_root ? _root->first_child + 0 : 0, _root); - } - - PUGI__FN xml_node::iterator xml_node::end() const - { - return iterator(0, _root); - } - - PUGI__FN xml_node::attribute_iterator xml_node::attributes_begin() const - { - return attribute_iterator(_root ? _root->first_attribute + 0 : 0, _root); - } - - PUGI__FN xml_node::attribute_iterator xml_node::attributes_end() const - { - return attribute_iterator(0, _root); - } - - PUGI__FN xml_object_range xml_node::children() const - { - return xml_object_range(begin(), end()); - } - - PUGI__FN xml_object_range xml_node::children(const char_t* name_) const - { - return xml_object_range(xml_named_node_iterator(child(name_)._root, _root, name_), xml_named_node_iterator(0, _root, name_)); - } - - PUGI__FN xml_object_range xml_node::attributes() const - { - return xml_object_range(attributes_begin(), attributes_end()); - } - - PUGI__FN bool xml_node::operator==(const xml_node& r) const - { - return (_root == r._root); - } - - PUGI__FN bool xml_node::operator!=(const xml_node& r) const - { - return (_root != r._root); - } - - PUGI__FN bool xml_node::operator<(const xml_node& r) const - { - return (_root < r._root); - } - - PUGI__FN bool xml_node::operator>(const xml_node& r) const - { - return (_root > r._root); - } - - PUGI__FN bool xml_node::operator<=(const xml_node& r) const - { - return (_root <= r._root); - } - - PUGI__FN bool xml_node::operator>=(const xml_node& r) const - { - return (_root >= r._root); - } - - PUGI__FN bool xml_node::empty() const - { - return !_root; - } - - PUGI__FN const char_t* xml_node::name() const - { - return (_root && _root->name) ? _root->name + 0 : PUGIXML_TEXT(""); - } - - PUGI__FN xml_node_type xml_node::type() const - { - return _root ? PUGI__NODETYPE(_root) : node_null; - } - - PUGI__FN const char_t* xml_node::value() const - { - return (_root && _root->value) ? _root->value + 0 : PUGIXML_TEXT(""); - } - - PUGI__FN xml_node xml_node::child(const char_t* name_) const - { - if (!_root) return xml_node(); - - for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) - if (i->name && impl::strequal(name_, i->name)) return xml_node(i); - - return xml_node(); - } - - PUGI__FN xml_attribute xml_node::attribute(const char_t* name_) const - { - if (!_root) return xml_attribute(); - - for (xml_attribute_struct* i = _root->first_attribute; i; i = i->next_attribute) - if (i->name && impl::strequal(name_, i->name)) - return xml_attribute(i); - - return xml_attribute(); - } - - PUGI__FN xml_node xml_node::next_sibling(const char_t* name_) const - { - if (!_root) return xml_node(); - - for (xml_node_struct* i = _root->next_sibling; i; i = i->next_sibling) - if (i->name && impl::strequal(name_, i->name)) return xml_node(i); - - return xml_node(); - } - - PUGI__FN xml_node xml_node::next_sibling() const - { - return _root ? xml_node(_root->next_sibling) : xml_node(); - } - - PUGI__FN xml_node xml_node::previous_sibling(const char_t* name_) const - { - if (!_root) return xml_node(); - - for (xml_node_struct* i = _root->prev_sibling_c; i->next_sibling; i = i->prev_sibling_c) - if (i->name && impl::strequal(name_, i->name)) return xml_node(i); - - return xml_node(); - } - - PUGI__FN xml_attribute xml_node::attribute(const char_t* name_, xml_attribute& hint_) const - { - xml_attribute_struct* hint = hint_._attr; - - // if hint is not an attribute of node, behavior is not defined - assert(!hint || (_root && impl::is_attribute_of(hint, _root))); - - if (!_root) return xml_attribute(); - - // optimistically search from hint up until the end - for (xml_attribute_struct* i = hint; i; i = i->next_attribute) - if (i->name && impl::strequal(name_, i->name)) - { - // update hint to maximize efficiency of searching for consecutive attributes - hint_._attr = i->next_attribute; - - return xml_attribute(i); - } - - // wrap around and search from the first attribute until the hint - // 'j' null pointer check is technically redundant, but it prevents a crash in case the assertion above fails - for (xml_attribute_struct* j = _root->first_attribute; j && j != hint; j = j->next_attribute) - if (j->name && impl::strequal(name_, j->name)) - { - // update hint to maximize efficiency of searching for consecutive attributes - hint_._attr = j->next_attribute; - - return xml_attribute(j); - } - - return xml_attribute(); - } - - PUGI__FN xml_node xml_node::previous_sibling() const - { - if (!_root) return xml_node(); - - if (_root->prev_sibling_c->next_sibling) return xml_node(_root->prev_sibling_c); - else return xml_node(); - } - - PUGI__FN xml_node xml_node::parent() const - { - return _root ? xml_node(_root->parent) : xml_node(); - } - - PUGI__FN xml_node xml_node::root() const - { - return _root ? xml_node(&impl::get_document(_root)) : xml_node(); - } - - PUGI__FN xml_text xml_node::text() const - { - return xml_text(_root); - } - - PUGI__FN const char_t* xml_node::child_value() const - { - if (!_root) return PUGIXML_TEXT(""); - - for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) - if (impl::is_text_node(i) && i->value) - return i->value; - - return PUGIXML_TEXT(""); - } - - PUGI__FN const char_t* xml_node::child_value(const char_t* name_) const - { - return child(name_).child_value(); - } - - PUGI__FN xml_attribute xml_node::first_attribute() const - { - return _root ? xml_attribute(_root->first_attribute) : xml_attribute(); - } - - PUGI__FN xml_attribute xml_node::last_attribute() const - { - return _root && _root->first_attribute ? xml_attribute(_root->first_attribute->prev_attribute_c) : xml_attribute(); - } - - PUGI__FN xml_node xml_node::first_child() const - { - return _root ? xml_node(_root->first_child) : xml_node(); - } - - PUGI__FN xml_node xml_node::last_child() const - { - return _root && _root->first_child ? xml_node(_root->first_child->prev_sibling_c) : xml_node(); - } - - PUGI__FN bool xml_node::set_name(const char_t* rhs) - { - xml_node_type type_ = _root ? PUGI__NODETYPE(_root) : node_null; - - if (type_ != node_element && type_ != node_pi && type_ != node_declaration) - return false; - - return impl::strcpy_insitu(_root->name, _root->header, impl::xml_memory_page_name_allocated_mask, rhs, impl::strlength(rhs)); - } - - PUGI__FN bool xml_node::set_value(const char_t* rhs) - { - xml_node_type type_ = _root ? PUGI__NODETYPE(_root) : node_null; - - if (type_ != node_pcdata && type_ != node_cdata && type_ != node_comment && type_ != node_pi && type_ != node_doctype) - return false; - - return impl::strcpy_insitu(_root->value, _root->header, impl::xml_memory_page_value_allocated_mask, rhs, impl::strlength(rhs)); - } - - PUGI__FN xml_attribute xml_node::append_attribute(const char_t* name_) - { - if (!impl::allow_insert_attribute(type())) return xml_attribute(); - - impl::xml_allocator& alloc = impl::get_allocator(_root); - if (!alloc.reserve()) return xml_attribute(); - - xml_attribute a(impl::allocate_attribute(alloc)); - if (!a) return xml_attribute(); - - impl::append_attribute(a._attr, _root); - - a.set_name(name_); - - return a; - } - - PUGI__FN xml_attribute xml_node::prepend_attribute(const char_t* name_) - { - if (!impl::allow_insert_attribute(type())) return xml_attribute(); - - impl::xml_allocator& alloc = impl::get_allocator(_root); - if (!alloc.reserve()) return xml_attribute(); - - xml_attribute a(impl::allocate_attribute(alloc)); - if (!a) return xml_attribute(); - - impl::prepend_attribute(a._attr, _root); - - a.set_name(name_); - - return a; - } - - PUGI__FN xml_attribute xml_node::insert_attribute_after(const char_t* name_, const xml_attribute& attr) - { - if (!impl::allow_insert_attribute(type())) return xml_attribute(); - if (!attr || !impl::is_attribute_of(attr._attr, _root)) return xml_attribute(); - - impl::xml_allocator& alloc = impl::get_allocator(_root); - if (!alloc.reserve()) return xml_attribute(); - - xml_attribute a(impl::allocate_attribute(alloc)); - if (!a) return xml_attribute(); - - impl::insert_attribute_after(a._attr, attr._attr, _root); - - a.set_name(name_); - - return a; - } - - PUGI__FN xml_attribute xml_node::insert_attribute_before(const char_t* name_, const xml_attribute& attr) - { - if (!impl::allow_insert_attribute(type())) return xml_attribute(); - if (!attr || !impl::is_attribute_of(attr._attr, _root)) return xml_attribute(); - - impl::xml_allocator& alloc = impl::get_allocator(_root); - if (!alloc.reserve()) return xml_attribute(); - - xml_attribute a(impl::allocate_attribute(alloc)); - if (!a) return xml_attribute(); - - impl::insert_attribute_before(a._attr, attr._attr, _root); - - a.set_name(name_); - - return a; - } - - PUGI__FN xml_attribute xml_node::append_copy(const xml_attribute& proto) - { - if (!proto) return xml_attribute(); - if (!impl::allow_insert_attribute(type())) return xml_attribute(); - - impl::xml_allocator& alloc = impl::get_allocator(_root); - if (!alloc.reserve()) return xml_attribute(); - - xml_attribute a(impl::allocate_attribute(alloc)); - if (!a) return xml_attribute(); - - impl::append_attribute(a._attr, _root); - impl::node_copy_attribute(a._attr, proto._attr); - - return a; - } - - PUGI__FN xml_attribute xml_node::prepend_copy(const xml_attribute& proto) - { - if (!proto) return xml_attribute(); - if (!impl::allow_insert_attribute(type())) return xml_attribute(); - - impl::xml_allocator& alloc = impl::get_allocator(_root); - if (!alloc.reserve()) return xml_attribute(); - - xml_attribute a(impl::allocate_attribute(alloc)); - if (!a) return xml_attribute(); - - impl::prepend_attribute(a._attr, _root); - impl::node_copy_attribute(a._attr, proto._attr); - - return a; - } - - PUGI__FN xml_attribute xml_node::insert_copy_after(const xml_attribute& proto, const xml_attribute& attr) - { - if (!proto) return xml_attribute(); - if (!impl::allow_insert_attribute(type())) return xml_attribute(); - if (!attr || !impl::is_attribute_of(attr._attr, _root)) return xml_attribute(); - - impl::xml_allocator& alloc = impl::get_allocator(_root); - if (!alloc.reserve()) return xml_attribute(); - - xml_attribute a(impl::allocate_attribute(alloc)); - if (!a) return xml_attribute(); - - impl::insert_attribute_after(a._attr, attr._attr, _root); - impl::node_copy_attribute(a._attr, proto._attr); - - return a; - } - - PUGI__FN xml_attribute xml_node::insert_copy_before(const xml_attribute& proto, const xml_attribute& attr) - { - if (!proto) return xml_attribute(); - if (!impl::allow_insert_attribute(type())) return xml_attribute(); - if (!attr || !impl::is_attribute_of(attr._attr, _root)) return xml_attribute(); - - impl::xml_allocator& alloc = impl::get_allocator(_root); - if (!alloc.reserve()) return xml_attribute(); - - xml_attribute a(impl::allocate_attribute(alloc)); - if (!a) return xml_attribute(); - - impl::insert_attribute_before(a._attr, attr._attr, _root); - impl::node_copy_attribute(a._attr, proto._attr); - - return a; - } - - PUGI__FN xml_node xml_node::append_child(xml_node_type type_) - { - if (!impl::allow_insert_child(type(), type_)) return xml_node(); - - impl::xml_allocator& alloc = impl::get_allocator(_root); - if (!alloc.reserve()) return xml_node(); - - xml_node n(impl::allocate_node(alloc, type_)); - if (!n) return xml_node(); - - impl::append_node(n._root, _root); - - if (type_ == node_declaration) n.set_name(PUGIXML_TEXT("xml")); - - return n; - } - - PUGI__FN xml_node xml_node::prepend_child(xml_node_type type_) - { - if (!impl::allow_insert_child(type(), type_)) return xml_node(); - - impl::xml_allocator& alloc = impl::get_allocator(_root); - if (!alloc.reserve()) return xml_node(); - - xml_node n(impl::allocate_node(alloc, type_)); - if (!n) return xml_node(); - - impl::prepend_node(n._root, _root); - - if (type_ == node_declaration) n.set_name(PUGIXML_TEXT("xml")); - - return n; - } - - PUGI__FN xml_node xml_node::insert_child_before(xml_node_type type_, const xml_node& node) - { - if (!impl::allow_insert_child(type(), type_)) return xml_node(); - if (!node._root || node._root->parent != _root) return xml_node(); - - impl::xml_allocator& alloc = impl::get_allocator(_root); - if (!alloc.reserve()) return xml_node(); - - xml_node n(impl::allocate_node(alloc, type_)); - if (!n) return xml_node(); - - impl::insert_node_before(n._root, node._root); - - if (type_ == node_declaration) n.set_name(PUGIXML_TEXT("xml")); - - return n; - } - - PUGI__FN xml_node xml_node::insert_child_after(xml_node_type type_, const xml_node& node) - { - if (!impl::allow_insert_child(type(), type_)) return xml_node(); - if (!node._root || node._root->parent != _root) return xml_node(); - - impl::xml_allocator& alloc = impl::get_allocator(_root); - if (!alloc.reserve()) return xml_node(); - - xml_node n(impl::allocate_node(alloc, type_)); - if (!n) return xml_node(); - - impl::insert_node_after(n._root, node._root); - - if (type_ == node_declaration) n.set_name(PUGIXML_TEXT("xml")); - - return n; - } - - PUGI__FN xml_node xml_node::append_child(const char_t* name_) - { - xml_node result = append_child(node_element); - - result.set_name(name_); - - return result; - } - - PUGI__FN xml_node xml_node::prepend_child(const char_t* name_) - { - xml_node result = prepend_child(node_element); - - result.set_name(name_); - - return result; - } - - PUGI__FN xml_node xml_node::insert_child_after(const char_t* name_, const xml_node& node) - { - xml_node result = insert_child_after(node_element, node); - - result.set_name(name_); - - return result; - } - - PUGI__FN xml_node xml_node::insert_child_before(const char_t* name_, const xml_node& node) - { - xml_node result = insert_child_before(node_element, node); - - result.set_name(name_); - - return result; - } - - PUGI__FN xml_node xml_node::append_copy(const xml_node& proto) - { - xml_node_type type_ = proto.type(); - if (!impl::allow_insert_child(type(), type_)) return xml_node(); - - impl::xml_allocator& alloc = impl::get_allocator(_root); - if (!alloc.reserve()) return xml_node(); - - xml_node n(impl::allocate_node(alloc, type_)); - if (!n) return xml_node(); - - impl::append_node(n._root, _root); - impl::node_copy_tree(n._root, proto._root); - - return n; - } - - PUGI__FN xml_node xml_node::prepend_copy(const xml_node& proto) - { - xml_node_type type_ = proto.type(); - if (!impl::allow_insert_child(type(), type_)) return xml_node(); - - impl::xml_allocator& alloc = impl::get_allocator(_root); - if (!alloc.reserve()) return xml_node(); - - xml_node n(impl::allocate_node(alloc, type_)); - if (!n) return xml_node(); - - impl::prepend_node(n._root, _root); - impl::node_copy_tree(n._root, proto._root); - - return n; - } - - PUGI__FN xml_node xml_node::insert_copy_after(const xml_node& proto, const xml_node& node) - { - xml_node_type type_ = proto.type(); - if (!impl::allow_insert_child(type(), type_)) return xml_node(); - if (!node._root || node._root->parent != _root) return xml_node(); - - impl::xml_allocator& alloc = impl::get_allocator(_root); - if (!alloc.reserve()) return xml_node(); - - xml_node n(impl::allocate_node(alloc, type_)); - if (!n) return xml_node(); - - impl::insert_node_after(n._root, node._root); - impl::node_copy_tree(n._root, proto._root); - - return n; - } - - PUGI__FN xml_node xml_node::insert_copy_before(const xml_node& proto, const xml_node& node) - { - xml_node_type type_ = proto.type(); - if (!impl::allow_insert_child(type(), type_)) return xml_node(); - if (!node._root || node._root->parent != _root) return xml_node(); - - impl::xml_allocator& alloc = impl::get_allocator(_root); - if (!alloc.reserve()) return xml_node(); - - xml_node n(impl::allocate_node(alloc, type_)); - if (!n) return xml_node(); - - impl::insert_node_before(n._root, node._root); - impl::node_copy_tree(n._root, proto._root); - - return n; - } - - PUGI__FN xml_node xml_node::append_move(const xml_node& moved) - { - if (!impl::allow_move(*this, moved)) return xml_node(); - - impl::xml_allocator& alloc = impl::get_allocator(_root); - if (!alloc.reserve()) return xml_node(); - - // disable document_buffer_order optimization since moving nodes around changes document order without changing buffer pointers - impl::get_document(_root).header |= impl::xml_memory_page_contents_shared_mask; - - impl::remove_node(moved._root); - impl::append_node(moved._root, _root); - - return moved; - } - - PUGI__FN xml_node xml_node::prepend_move(const xml_node& moved) - { - if (!impl::allow_move(*this, moved)) return xml_node(); - - impl::xml_allocator& alloc = impl::get_allocator(_root); - if (!alloc.reserve()) return xml_node(); - - // disable document_buffer_order optimization since moving nodes around changes document order without changing buffer pointers - impl::get_document(_root).header |= impl::xml_memory_page_contents_shared_mask; - - impl::remove_node(moved._root); - impl::prepend_node(moved._root, _root); - - return moved; - } - - PUGI__FN xml_node xml_node::insert_move_after(const xml_node& moved, const xml_node& node) - { - if (!impl::allow_move(*this, moved)) return xml_node(); - if (!node._root || node._root->parent != _root) return xml_node(); - if (moved._root == node._root) return xml_node(); - - impl::xml_allocator& alloc = impl::get_allocator(_root); - if (!alloc.reserve()) return xml_node(); - - // disable document_buffer_order optimization since moving nodes around changes document order without changing buffer pointers - impl::get_document(_root).header |= impl::xml_memory_page_contents_shared_mask; - - impl::remove_node(moved._root); - impl::insert_node_after(moved._root, node._root); - - return moved; - } - - PUGI__FN xml_node xml_node::insert_move_before(const xml_node& moved, const xml_node& node) - { - if (!impl::allow_move(*this, moved)) return xml_node(); - if (!node._root || node._root->parent != _root) return xml_node(); - if (moved._root == node._root) return xml_node(); - - impl::xml_allocator& alloc = impl::get_allocator(_root); - if (!alloc.reserve()) return xml_node(); - - // disable document_buffer_order optimization since moving nodes around changes document order without changing buffer pointers - impl::get_document(_root).header |= impl::xml_memory_page_contents_shared_mask; - - impl::remove_node(moved._root); - impl::insert_node_before(moved._root, node._root); - - return moved; - } - - PUGI__FN bool xml_node::remove_attribute(const char_t* name_) - { - return remove_attribute(attribute(name_)); - } - - PUGI__FN bool xml_node::remove_attribute(const xml_attribute& a) - { - if (!_root || !a._attr) return false; - if (!impl::is_attribute_of(a._attr, _root)) return false; - - impl::xml_allocator& alloc = impl::get_allocator(_root); - if (!alloc.reserve()) return false; - - impl::remove_attribute(a._attr, _root); - impl::destroy_attribute(a._attr, alloc); - - return true; - } - - PUGI__FN bool xml_node::remove_child(const char_t* name_) - { - return remove_child(child(name_)); - } - - PUGI__FN bool xml_node::remove_child(const xml_node& n) - { - if (!_root || !n._root || n._root->parent != _root) return false; - - impl::xml_allocator& alloc = impl::get_allocator(_root); - if (!alloc.reserve()) return false; - - impl::remove_node(n._root); - impl::destroy_node(n._root, alloc); - - return true; - } - - PUGI__FN xml_parse_result xml_node::append_buffer(const void* contents, size_t size, unsigned int options, xml_encoding encoding) - { - // append_buffer is only valid for elements/documents - if (!impl::allow_insert_child(type(), node_element)) return impl::make_parse_result(status_append_invalid_root); - - // get document node - impl::xml_document_struct* doc = &impl::get_document(_root); - - // disable document_buffer_order optimization since in a document with multiple buffers comparing buffer pointers does not make sense - doc->header |= impl::xml_memory_page_contents_shared_mask; - - // get extra buffer element (we'll store the document fragment buffer there so that we can deallocate it later) - impl::xml_memory_page* page = 0; - impl::xml_extra_buffer* extra = static_cast(doc->allocate_memory(sizeof(impl::xml_extra_buffer), page)); - (void)page; - - if (!extra) return impl::make_parse_result(status_out_of_memory); - - // add extra buffer to the list - extra->buffer = 0; - extra->next = doc->extra_buffers; - doc->extra_buffers = extra; - - // name of the root has to be NULL before parsing - otherwise closing node mismatches will not be detected at the top level - impl::name_null_sentry sentry(_root); - - return impl::load_buffer_impl(doc, _root, const_cast(contents), size, options, encoding, false, false, &extra->buffer); - } - - PUGI__FN xml_node xml_node::find_child_by_attribute(const char_t* name_, const char_t* attr_name, const char_t* attr_value) const - { - if (!_root) return xml_node(); - - for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) - if (i->name && impl::strequal(name_, i->name)) - { - for (xml_attribute_struct* a = i->first_attribute; a; a = a->next_attribute) - if (a->name && impl::strequal(attr_name, a->name) && impl::strequal(attr_value, a->value ? a->value + 0 : PUGIXML_TEXT(""))) - return xml_node(i); - } - - return xml_node(); - } - - PUGI__FN xml_node xml_node::find_child_by_attribute(const char_t* attr_name, const char_t* attr_value) const - { - if (!_root) return xml_node(); - - for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) - for (xml_attribute_struct* a = i->first_attribute; a; a = a->next_attribute) - if (a->name && impl::strequal(attr_name, a->name) && impl::strequal(attr_value, a->value ? a->value + 0 : PUGIXML_TEXT(""))) - return xml_node(i); - - return xml_node(); - } - -#ifndef PUGIXML_NO_STL - PUGI__FN string_t xml_node::path(char_t delimiter) const - { - if (!_root) return string_t(); - - size_t offset = 0; - - for (xml_node_struct* i = _root; i; i = i->parent) - { - offset += (i != _root); - offset += i->name ? impl::strlength(i->name) : 0; - } - - string_t result; - result.resize(offset); - - for (xml_node_struct* j = _root; j; j = j->parent) - { - if (j != _root) - result[--offset] = delimiter; - - if (j->name && *j->name) - { - size_t length = impl::strlength(j->name); - - offset -= length; - memcpy(&result[offset], j->name, length * sizeof(char_t)); - } - } - - assert(offset == 0); - - return result; - } -#endif - - PUGI__FN xml_node xml_node::first_element_by_path(const char_t* path_, char_t delimiter) const - { - xml_node found = *this; // Current search context. - - if (!_root || !path_ || !path_[0]) return found; - - if (path_[0] == delimiter) - { - // Absolute path; e.g. '/foo/bar' - found = found.root(); - ++path_; - } - - const char_t* path_segment = path_; - - while (*path_segment == delimiter) ++path_segment; - - const char_t* path_segment_end = path_segment; - - while (*path_segment_end && *path_segment_end != delimiter) ++path_segment_end; - - if (path_segment == path_segment_end) return found; - - const char_t* next_segment = path_segment_end; - - while (*next_segment == delimiter) ++next_segment; - - if (*path_segment == '.' && path_segment + 1 == path_segment_end) - return found.first_element_by_path(next_segment, delimiter); - else if (*path_segment == '.' && *(path_segment+1) == '.' && path_segment + 2 == path_segment_end) - return found.parent().first_element_by_path(next_segment, delimiter); - else - { - for (xml_node_struct* j = found._root->first_child; j; j = j->next_sibling) - { - if (j->name && impl::strequalrange(j->name, path_segment, static_cast(path_segment_end - path_segment))) - { - xml_node subsearch = xml_node(j).first_element_by_path(next_segment, delimiter); - - if (subsearch) return subsearch; - } - } - - return xml_node(); - } - } - - PUGI__FN bool xml_node::traverse(xml_tree_walker& walker) - { - walker._depth = -1; - - xml_node arg_begin = *this; - if (!walker.begin(arg_begin)) return false; - - xml_node cur = first_child(); - - if (cur) - { - ++walker._depth; - - do - { - xml_node arg_for_each = cur; - if (!walker.for_each(arg_for_each)) - return false; - - if (cur.first_child()) - { - ++walker._depth; - cur = cur.first_child(); - } - else if (cur.next_sibling()) - cur = cur.next_sibling(); - else - { - // Borland C++ workaround - while (!cur.next_sibling() && cur != *this && !cur.parent().empty()) - { - --walker._depth; - cur = cur.parent(); - } - - if (cur != *this) - cur = cur.next_sibling(); - } - } - while (cur && cur != *this); - } - - assert(walker._depth == -1); - - xml_node arg_end = *this; - return walker.end(arg_end); - } - - PUGI__FN size_t xml_node::hash_value() const - { - return static_cast(reinterpret_cast(_root) / sizeof(xml_node_struct)); - } - - PUGI__FN xml_node_struct* xml_node::internal_object() const - { - return _root; - } - - PUGI__FN void xml_node::print(xml_writer& writer, const char_t* indent, unsigned int flags, xml_encoding encoding, unsigned int depth) const - { - if (!_root) return; - - impl::xml_buffered_writer buffered_writer(writer, encoding); - - impl::node_output(buffered_writer, _root, indent, flags, depth); - - buffered_writer.flush(); - } - -#ifndef PUGIXML_NO_STL - PUGI__FN void xml_node::print(std::basic_ostream >& stream, const char_t* indent, unsigned int flags, xml_encoding encoding, unsigned int depth) const - { - xml_writer_stream writer(stream); - - print(writer, indent, flags, encoding, depth); - } - - PUGI__FN void xml_node::print(std::basic_ostream >& stream, const char_t* indent, unsigned int flags, unsigned int depth) const - { - xml_writer_stream writer(stream); - - print(writer, indent, flags, encoding_wchar, depth); - } -#endif - - PUGI__FN ptrdiff_t xml_node::offset_debug() const - { - if (!_root) return -1; - - impl::xml_document_struct& doc = impl::get_document(_root); - - // we can determine the offset reliably only if there is exactly once parse buffer - if (!doc.buffer || doc.extra_buffers) return -1; - - switch (type()) - { - case node_document: - return 0; - - case node_element: - case node_declaration: - case node_pi: - return _root->name && (_root->header & impl::xml_memory_page_name_allocated_or_shared_mask) == 0 ? _root->name - doc.buffer : -1; - - case node_pcdata: - case node_cdata: - case node_comment: - case node_doctype: - return _root->value && (_root->header & impl::xml_memory_page_value_allocated_or_shared_mask) == 0 ? _root->value - doc.buffer : -1; - - default: - return -1; - } - } - -#ifdef __BORLANDC__ - PUGI__FN bool operator&&(const xml_node& lhs, bool rhs) - { - return (bool)lhs && rhs; - } - - PUGI__FN bool operator||(const xml_node& lhs, bool rhs) - { - return (bool)lhs || rhs; - } -#endif - - PUGI__FN xml_text::xml_text(xml_node_struct* root): _root(root) - { - } - - PUGI__FN xml_node_struct* xml_text::_data() const - { - if (!_root || impl::is_text_node(_root)) return _root; - - for (xml_node_struct* node = _root->first_child; node; node = node->next_sibling) - if (impl::is_text_node(node)) - return node; - - return 0; - } - - PUGI__FN xml_node_struct* xml_text::_data_new() - { - xml_node_struct* d = _data(); - if (d) return d; - - return xml_node(_root).append_child(node_pcdata).internal_object(); - } - - PUGI__FN xml_text::xml_text(): _root(0) - { - } - - PUGI__FN static void unspecified_bool_xml_text(xml_text***) - { - } - - PUGI__FN xml_text::operator xml_text::unspecified_bool_type() const - { - return _data() ? unspecified_bool_xml_text : 0; - } - - PUGI__FN bool xml_text::operator!() const - { - return !_data(); - } - - PUGI__FN bool xml_text::empty() const - { - return _data() == 0; - } - - PUGI__FN const char_t* xml_text::get() const - { - xml_node_struct* d = _data(); - - return (d && d->value) ? d->value + 0 : PUGIXML_TEXT(""); - } - - PUGI__FN const char_t* xml_text::as_string(const char_t* def) const - { - xml_node_struct* d = _data(); - - return (d && d->value) ? d->value + 0 : def; - } - - PUGI__FN int xml_text::as_int(int def) const - { - xml_node_struct* d = _data(); - - return (d && d->value) ? impl::get_value_int(d->value) : def; - } - - PUGI__FN unsigned int xml_text::as_uint(unsigned int def) const - { - xml_node_struct* d = _data(); - - return (d && d->value) ? impl::get_value_uint(d->value) : def; - } - - PUGI__FN double xml_text::as_double(double def) const - { - xml_node_struct* d = _data(); - - return (d && d->value) ? impl::get_value_double(d->value) : def; - } - - PUGI__FN float xml_text::as_float(float def) const - { - xml_node_struct* d = _data(); - - return (d && d->value) ? impl::get_value_float(d->value) : def; - } - - PUGI__FN bool xml_text::as_bool(bool def) const - { - xml_node_struct* d = _data(); - - return (d && d->value) ? impl::get_value_bool(d->value) : def; - } - -#ifdef PUGIXML_HAS_LONG_LONG - PUGI__FN long long xml_text::as_llong(long long def) const - { - xml_node_struct* d = _data(); - - return (d && d->value) ? impl::get_value_llong(d->value) : def; - } - - PUGI__FN unsigned long long xml_text::as_ullong(unsigned long long def) const - { - xml_node_struct* d = _data(); - - return (d && d->value) ? impl::get_value_ullong(d->value) : def; - } -#endif - - PUGI__FN bool xml_text::set(const char_t* rhs) - { - xml_node_struct* dn = _data_new(); - - return dn ? impl::strcpy_insitu(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, impl::strlength(rhs)) : false; - } - - PUGI__FN bool xml_text::set(int rhs) - { - xml_node_struct* dn = _data_new(); - - return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false; - } - - PUGI__FN bool xml_text::set(unsigned int rhs) - { - xml_node_struct* dn = _data_new(); - - return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false; - } - - PUGI__FN bool xml_text::set(float rhs) - { - xml_node_struct* dn = _data_new(); - - return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false; - } - - PUGI__FN bool xml_text::set(double rhs) - { - xml_node_struct* dn = _data_new(); - - return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false; - } - - PUGI__FN bool xml_text::set(bool rhs) - { - xml_node_struct* dn = _data_new(); - - return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false; - } - -#ifdef PUGIXML_HAS_LONG_LONG - PUGI__FN bool xml_text::set(long long rhs) - { - xml_node_struct* dn = _data_new(); - - return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false; - } - - PUGI__FN bool xml_text::set(unsigned long long rhs) - { - xml_node_struct* dn = _data_new(); - - return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false; - } -#endif - - PUGI__FN xml_text& xml_text::operator=(const char_t* rhs) - { - set(rhs); - return *this; - } - - PUGI__FN xml_text& xml_text::operator=(int rhs) - { - set(rhs); - return *this; - } - - PUGI__FN xml_text& xml_text::operator=(unsigned int rhs) - { - set(rhs); - return *this; - } - - PUGI__FN xml_text& xml_text::operator=(double rhs) - { - set(rhs); - return *this; - } - - PUGI__FN xml_text& xml_text::operator=(float rhs) - { - set(rhs); - return *this; - } - - PUGI__FN xml_text& xml_text::operator=(bool rhs) - { - set(rhs); - return *this; - } - -#ifdef PUGIXML_HAS_LONG_LONG - PUGI__FN xml_text& xml_text::operator=(long long rhs) - { - set(rhs); - return *this; - } - - PUGI__FN xml_text& xml_text::operator=(unsigned long long rhs) - { - set(rhs); - return *this; - } -#endif - - PUGI__FN xml_node xml_text::data() const - { - return xml_node(_data()); - } - -#ifdef __BORLANDC__ - PUGI__FN bool operator&&(const xml_text& lhs, bool rhs) - { - return (bool)lhs && rhs; - } - - PUGI__FN bool operator||(const xml_text& lhs, bool rhs) - { - return (bool)lhs || rhs; - } -#endif - - PUGI__FN xml_node_iterator::xml_node_iterator() - { - } - - PUGI__FN xml_node_iterator::xml_node_iterator(const xml_node& node): _wrap(node), _parent(node.parent()) - { - } - - PUGI__FN xml_node_iterator::xml_node_iterator(xml_node_struct* ref, xml_node_struct* parent): _wrap(ref), _parent(parent) - { - } - - PUGI__FN bool xml_node_iterator::operator==(const xml_node_iterator& rhs) const - { - return _wrap._root == rhs._wrap._root && _parent._root == rhs._parent._root; - } - - PUGI__FN bool xml_node_iterator::operator!=(const xml_node_iterator& rhs) const - { - return _wrap._root != rhs._wrap._root || _parent._root != rhs._parent._root; - } - - PUGI__FN xml_node& xml_node_iterator::operator*() const - { - assert(_wrap._root); - return _wrap; - } - - PUGI__FN xml_node* xml_node_iterator::operator->() const - { - assert(_wrap._root); - return const_cast(&_wrap); // BCC32 workaround - } - - PUGI__FN const xml_node_iterator& xml_node_iterator::operator++() - { - assert(_wrap._root); - _wrap._root = _wrap._root->next_sibling; - return *this; - } - - PUGI__FN xml_node_iterator xml_node_iterator::operator++(int) - { - xml_node_iterator temp = *this; - ++*this; - return temp; - } - - PUGI__FN const xml_node_iterator& xml_node_iterator::operator--() - { - _wrap = _wrap._root ? _wrap.previous_sibling() : _parent.last_child(); - return *this; - } - - PUGI__FN xml_node_iterator xml_node_iterator::operator--(int) - { - xml_node_iterator temp = *this; - --*this; - return temp; - } - - PUGI__FN xml_attribute_iterator::xml_attribute_iterator() - { - } - - PUGI__FN xml_attribute_iterator::xml_attribute_iterator(const xml_attribute& attr, const xml_node& parent): _wrap(attr), _parent(parent) - { - } - - PUGI__FN xml_attribute_iterator::xml_attribute_iterator(xml_attribute_struct* ref, xml_node_struct* parent): _wrap(ref), _parent(parent) - { - } - - PUGI__FN bool xml_attribute_iterator::operator==(const xml_attribute_iterator& rhs) const - { - return _wrap._attr == rhs._wrap._attr && _parent._root == rhs._parent._root; - } - - PUGI__FN bool xml_attribute_iterator::operator!=(const xml_attribute_iterator& rhs) const - { - return _wrap._attr != rhs._wrap._attr || _parent._root != rhs._parent._root; - } - - PUGI__FN xml_attribute& xml_attribute_iterator::operator*() const - { - assert(_wrap._attr); - return _wrap; - } - - PUGI__FN xml_attribute* xml_attribute_iterator::operator->() const - { - assert(_wrap._attr); - return const_cast(&_wrap); // BCC32 workaround - } - - PUGI__FN const xml_attribute_iterator& xml_attribute_iterator::operator++() - { - assert(_wrap._attr); - _wrap._attr = _wrap._attr->next_attribute; - return *this; - } - - PUGI__FN xml_attribute_iterator xml_attribute_iterator::operator++(int) - { - xml_attribute_iterator temp = *this; - ++*this; - return temp; - } - - PUGI__FN const xml_attribute_iterator& xml_attribute_iterator::operator--() - { - _wrap = _wrap._attr ? _wrap.previous_attribute() : _parent.last_attribute(); - return *this; - } - - PUGI__FN xml_attribute_iterator xml_attribute_iterator::operator--(int) - { - xml_attribute_iterator temp = *this; - --*this; - return temp; - } - - PUGI__FN xml_named_node_iterator::xml_named_node_iterator(): _name(0) - { - } - - PUGI__FN xml_named_node_iterator::xml_named_node_iterator(const xml_node& node, const char_t* name): _wrap(node), _parent(node.parent()), _name(name) - { - } - - PUGI__FN xml_named_node_iterator::xml_named_node_iterator(xml_node_struct* ref, xml_node_struct* parent, const char_t* name): _wrap(ref), _parent(parent), _name(name) - { - } - - PUGI__FN bool xml_named_node_iterator::operator==(const xml_named_node_iterator& rhs) const - { - return _wrap._root == rhs._wrap._root && _parent._root == rhs._parent._root; - } - - PUGI__FN bool xml_named_node_iterator::operator!=(const xml_named_node_iterator& rhs) const - { - return _wrap._root != rhs._wrap._root || _parent._root != rhs._parent._root; - } - - PUGI__FN xml_node& xml_named_node_iterator::operator*() const - { - assert(_wrap._root); - return _wrap; - } - - PUGI__FN xml_node* xml_named_node_iterator::operator->() const - { - assert(_wrap._root); - return const_cast(&_wrap); // BCC32 workaround - } - - PUGI__FN const xml_named_node_iterator& xml_named_node_iterator::operator++() - { - assert(_wrap._root); - _wrap = _wrap.next_sibling(_name); - return *this; - } - - PUGI__FN xml_named_node_iterator xml_named_node_iterator::operator++(int) - { - xml_named_node_iterator temp = *this; - ++*this; - return temp; - } - - PUGI__FN const xml_named_node_iterator& xml_named_node_iterator::operator--() - { - if (_wrap._root) - _wrap = _wrap.previous_sibling(_name); - else - { - _wrap = _parent.last_child(); - - if (!impl::strequal(_wrap.name(), _name)) - _wrap = _wrap.previous_sibling(_name); - } - - return *this; - } - - PUGI__FN xml_named_node_iterator xml_named_node_iterator::operator--(int) - { - xml_named_node_iterator temp = *this; - --*this; - return temp; - } - - PUGI__FN xml_parse_result::xml_parse_result(): status(status_internal_error), offset(0), encoding(encoding_auto) - { - } - - PUGI__FN xml_parse_result::operator bool() const - { - return status == status_ok; - } - - PUGI__FN const char* xml_parse_result::description() const - { - switch (status) - { - case status_ok: return "No error"; - - case status_file_not_found: return "File was not found"; - case status_io_error: return "Error reading from file/stream"; - case status_out_of_memory: return "Could not allocate memory"; - case status_internal_error: return "Internal error occurred"; - - case status_unrecognized_tag: return "Could not determine tag type"; - - case status_bad_pi: return "Error parsing document declaration/processing instruction"; - case status_bad_comment: return "Error parsing comment"; - case status_bad_cdata: return "Error parsing CDATA section"; - case status_bad_doctype: return "Error parsing document type declaration"; - case status_bad_pcdata: return "Error parsing PCDATA section"; - case status_bad_start_element: return "Error parsing start element tag"; - case status_bad_attribute: return "Error parsing element attribute"; - case status_bad_end_element: return "Error parsing end element tag"; - case status_end_element_mismatch: return "Start-end tags mismatch"; - - case status_append_invalid_root: return "Unable to append nodes: root is not an element or document"; - - case status_no_document_element: return "No document element found"; - - default: return "Unknown error"; - } - } - - PUGI__FN xml_document::xml_document(): _buffer(0) - { - create(); - } - - PUGI__FN xml_document::~xml_document() - { - destroy(); - } - - PUGI__FN void xml_document::reset() - { - destroy(); - create(); - } - - PUGI__FN void xml_document::reset(const xml_document& proto) - { - reset(); - - for (xml_node cur = proto.first_child(); cur; cur = cur.next_sibling()) - append_copy(cur); - } - - PUGI__FN void xml_document::create() - { - assert(!_root); - - #ifdef PUGIXML_COMPACT - const size_t page_offset = sizeof(uint32_t); - #else - const size_t page_offset = 0; - #endif - - // initialize sentinel page - PUGI__STATIC_ASSERT(sizeof(impl::xml_memory_page) + sizeof(impl::xml_document_struct) + impl::xml_memory_page_alignment - sizeof(void*) + page_offset <= sizeof(_memory)); - - // align upwards to page boundary - void* page_memory = reinterpret_cast((reinterpret_cast(_memory) + (impl::xml_memory_page_alignment - 1)) & ~(impl::xml_memory_page_alignment - 1)); - - // prepare page structure - impl::xml_memory_page* page = impl::xml_memory_page::construct(page_memory); - assert(page); - - page->busy_size = impl::xml_memory_page_size; - - // setup first page marker - #ifdef PUGIXML_COMPACT - // round-trip through void* to avoid 'cast increases required alignment of target type' warning - page->compact_page_marker = reinterpret_cast(static_cast(reinterpret_cast(page) + sizeof(impl::xml_memory_page))); - *page->compact_page_marker = sizeof(impl::xml_memory_page); - #endif - - // allocate new root - _root = new (reinterpret_cast(page) + sizeof(impl::xml_memory_page) + page_offset) impl::xml_document_struct(page); - _root->prev_sibling_c = _root; - - // setup sentinel page - page->allocator = static_cast(_root); - - // verify the document allocation - assert(reinterpret_cast(_root) + sizeof(impl::xml_document_struct) <= _memory + sizeof(_memory)); - } - - PUGI__FN void xml_document::destroy() - { - assert(_root); - - // destroy static storage - if (_buffer) - { - impl::xml_memory::deallocate(_buffer); - _buffer = 0; - } - - // destroy extra buffers (note: no need to destroy linked list nodes, they're allocated using document allocator) - for (impl::xml_extra_buffer* extra = static_cast(_root)->extra_buffers; extra; extra = extra->next) - { - if (extra->buffer) impl::xml_memory::deallocate(extra->buffer); - } - - // destroy dynamic storage, leave sentinel page (it's in static memory) - impl::xml_memory_page* root_page = PUGI__GETPAGE(_root); - assert(root_page && !root_page->prev); - assert(reinterpret_cast(root_page) >= _memory && reinterpret_cast(root_page) < _memory + sizeof(_memory)); - - for (impl::xml_memory_page* page = root_page->next; page; ) - { - impl::xml_memory_page* next = page->next; - - impl::xml_allocator::deallocate_page(page); - - page = next; - } - - #ifdef PUGIXML_COMPACT - // destroy hash table - static_cast(_root)->hash.clear(); - #endif - - _root = 0; - } - -#ifndef PUGIXML_NO_STL - PUGI__FN xml_parse_result xml_document::load(std::basic_istream >& stream, unsigned int options, xml_encoding encoding) - { - reset(); - - return impl::load_stream_impl(static_cast(_root), stream, options, encoding, &_buffer); - } - - PUGI__FN xml_parse_result xml_document::load(std::basic_istream >& stream, unsigned int options) - { - reset(); - - return impl::load_stream_impl(static_cast(_root), stream, options, encoding_wchar, &_buffer); - } -#endif - - PUGI__FN xml_parse_result xml_document::load_string(const char_t* contents, unsigned int options) - { - // Force native encoding (skip autodetection) - #ifdef PUGIXML_WCHAR_MODE - xml_encoding encoding = encoding_wchar; - #else - xml_encoding encoding = encoding_utf8; - #endif - - return load_buffer(contents, impl::strlength(contents) * sizeof(char_t), options, encoding); - } - - PUGI__FN xml_parse_result xml_document::load(const char_t* contents, unsigned int options) - { - return load_string(contents, options); - } - - PUGI__FN xml_parse_result xml_document::load_file(const char* path_, unsigned int options, xml_encoding encoding) - { - reset(); - - using impl::auto_deleter; // MSVC7 workaround - auto_deleter file(fopen(path_, "rb"), fclose); - - return impl::load_file_impl(static_cast(_root), file.data, options, encoding, &_buffer); - } - - PUGI__FN xml_parse_result xml_document::load_file(const wchar_t* path_, unsigned int options, xml_encoding encoding) - { - reset(); - - using impl::auto_deleter; // MSVC7 workaround - auto_deleter file(impl::open_file_wide(path_, L"rb"), fclose); - - return impl::load_file_impl(static_cast(_root), file.data, options, encoding, &_buffer); - } - - PUGI__FN xml_parse_result xml_document::load_buffer(const void* contents, size_t size, unsigned int options, xml_encoding encoding) - { - reset(); - - return impl::load_buffer_impl(static_cast(_root), _root, const_cast(contents), size, options, encoding, false, false, &_buffer); - } - - PUGI__FN xml_parse_result xml_document::load_buffer_inplace(void* contents, size_t size, unsigned int options, xml_encoding encoding) - { - reset(); - - return impl::load_buffer_impl(static_cast(_root), _root, contents, size, options, encoding, true, false, &_buffer); - } - - PUGI__FN xml_parse_result xml_document::load_buffer_inplace_own(void* contents, size_t size, unsigned int options, xml_encoding encoding) - { - reset(); - - return impl::load_buffer_impl(static_cast(_root), _root, contents, size, options, encoding, true, true, &_buffer); - } - - PUGI__FN void xml_document::save(xml_writer& writer, const char_t* indent, unsigned int flags, xml_encoding encoding) const - { - impl::xml_buffered_writer buffered_writer(writer, encoding); - - if ((flags & format_write_bom) && encoding != encoding_latin1) - { - // BOM always represents the codepoint U+FEFF, so just write it in native encoding - #ifdef PUGIXML_WCHAR_MODE - unsigned int bom = 0xfeff; - buffered_writer.write(static_cast(bom)); - #else - buffered_writer.write('\xef', '\xbb', '\xbf'); - #endif - } - - if (!(flags & format_no_declaration) && !impl::has_declaration(_root)) - { - buffered_writer.write_string(PUGIXML_TEXT("'); - if (!(flags & format_raw)) buffered_writer.write('\n'); - } - - impl::node_output(buffered_writer, _root, indent, flags, 0); - - buffered_writer.flush(); - } - -#ifndef PUGIXML_NO_STL - PUGI__FN void xml_document::save(std::basic_ostream >& stream, const char_t* indent, unsigned int flags, xml_encoding encoding) const - { - xml_writer_stream writer(stream); - - save(writer, indent, flags, encoding); - } - - PUGI__FN void xml_document::save(std::basic_ostream >& stream, const char_t* indent, unsigned int flags) const - { - xml_writer_stream writer(stream); - - save(writer, indent, flags, encoding_wchar); - } -#endif - - PUGI__FN bool xml_document::save_file(const char* path_, const char_t* indent, unsigned int flags, xml_encoding encoding) const - { - using impl::auto_deleter; // MSVC7 workaround - auto_deleter file(fopen(path_, (flags & format_save_file_text) ? "w" : "wb"), fclose); - - return impl::save_file_impl(*this, file.data, indent, flags, encoding); - } - - PUGI__FN bool xml_document::save_file(const wchar_t* path_, const char_t* indent, unsigned int flags, xml_encoding encoding) const - { - using impl::auto_deleter; // MSVC7 workaround - auto_deleter file(impl::open_file_wide(path_, (flags & format_save_file_text) ? L"w" : L"wb"), fclose); - - return impl::save_file_impl(*this, file.data, indent, flags, encoding); - } - - PUGI__FN xml_node xml_document::document_element() const - { - assert(_root); - - for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) - if (PUGI__NODETYPE(i) == node_element) - return xml_node(i); - - return xml_node(); - } - -#ifndef PUGIXML_NO_STL - PUGI__FN std::string PUGIXML_FUNCTION as_utf8(const wchar_t* str) - { - assert(str); - - return impl::as_utf8_impl(str, impl::strlength_wide(str)); - } - - PUGI__FN std::string PUGIXML_FUNCTION as_utf8(const std::basic_string& str) - { - return impl::as_utf8_impl(str.c_str(), str.size()); - } - - PUGI__FN std::basic_string PUGIXML_FUNCTION as_wide(const char* str) - { - assert(str); - - return impl::as_wide_impl(str, strlen(str)); - } - - PUGI__FN std::basic_string PUGIXML_FUNCTION as_wide(const std::string& str) - { - return impl::as_wide_impl(str.c_str(), str.size()); - } -#endif - - PUGI__FN void PUGIXML_FUNCTION set_memory_management_functions(allocation_function allocate, deallocation_function deallocate) - { - impl::xml_memory::allocate = allocate; - impl::xml_memory::deallocate = deallocate; - } - - PUGI__FN allocation_function PUGIXML_FUNCTION get_memory_allocation_function() - { - return impl::xml_memory::allocate; - } - - PUGI__FN deallocation_function PUGIXML_FUNCTION get_memory_deallocation_function() - { - return impl::xml_memory::deallocate; - } -} - -#if !defined(PUGIXML_NO_STL) && (defined(_MSC_VER) || defined(__ICC)) -namespace std -{ - // Workarounds for (non-standard) iterator category detection for older versions (MSVC7/IC8 and earlier) - PUGI__FN std::bidirectional_iterator_tag _Iter_cat(const pugi::xml_node_iterator&) - { - return std::bidirectional_iterator_tag(); - } - - PUGI__FN std::bidirectional_iterator_tag _Iter_cat(const pugi::xml_attribute_iterator&) - { - return std::bidirectional_iterator_tag(); - } - - PUGI__FN std::bidirectional_iterator_tag _Iter_cat(const pugi::xml_named_node_iterator&) - { - return std::bidirectional_iterator_tag(); - } -} -#endif - -#if !defined(PUGIXML_NO_STL) && defined(__SUNPRO_CC) -namespace std -{ - // Workarounds for (non-standard) iterator category detection - PUGI__FN std::bidirectional_iterator_tag __iterator_category(const pugi::xml_node_iterator&) - { - return std::bidirectional_iterator_tag(); - } - - PUGI__FN std::bidirectional_iterator_tag __iterator_category(const pugi::xml_attribute_iterator&) - { - return std::bidirectional_iterator_tag(); - } - - PUGI__FN std::bidirectional_iterator_tag __iterator_category(const pugi::xml_named_node_iterator&) - { - return std::bidirectional_iterator_tag(); - } -} -#endif - -#ifndef PUGIXML_NO_XPATH -// STL replacements -PUGI__NS_BEGIN - struct equal_to - { - template bool operator()(const T& lhs, const T& rhs) const - { - return lhs == rhs; - } - }; - - struct not_equal_to - { - template bool operator()(const T& lhs, const T& rhs) const - { - return lhs != rhs; - } - }; - - struct less - { - template bool operator()(const T& lhs, const T& rhs) const - { - return lhs < rhs; - } - }; - - struct less_equal - { - template bool operator()(const T& lhs, const T& rhs) const - { - return lhs <= rhs; - } - }; - - template void swap(T& lhs, T& rhs) - { - T temp = lhs; - lhs = rhs; - rhs = temp; - } - - template I min_element(I begin, I end, const Pred& pred) - { - I result = begin; - - for (I it = begin + 1; it != end; ++it) - if (pred(*it, *result)) - result = it; - - return result; - } - - template void reverse(I begin, I end) - { - while (end - begin > 1) swap(*begin++, *--end); - } - - template I unique(I begin, I end) - { - // fast skip head - while (end - begin > 1 && *begin != *(begin + 1)) begin++; - - if (begin == end) return begin; - - // last written element - I write = begin++; - - // merge unique elements - while (begin != end) - { - if (*begin != *write) - *++write = *begin++; - else - begin++; - } - - // past-the-end (write points to live element) - return write + 1; - } - - template void copy_backwards(I begin, I end, I target) - { - while (begin != end) *--target = *--end; - } - - template void insertion_sort(I begin, I end, const Pred& pred, T*) - { - assert(begin != end); - - for (I it = begin + 1; it != end; ++it) - { - T val = *it; - - if (pred(val, *begin)) - { - // move to front - copy_backwards(begin, it, it + 1); - *begin = val; - } - else - { - I hole = it; - - // move hole backwards - while (pred(val, *(hole - 1))) - { - *hole = *(hole - 1); - hole--; - } - - // fill hole with element - *hole = val; - } - } - } - - // std variant for elements with == - template void partition(I begin, I middle, I end, const Pred& pred, I* out_eqbeg, I* out_eqend) - { - I eqbeg = middle, eqend = middle + 1; - - // expand equal range - while (eqbeg != begin && *(eqbeg - 1) == *eqbeg) --eqbeg; - while (eqend != end && *eqend == *eqbeg) ++eqend; - - // process outer elements - I ltend = eqbeg, gtbeg = eqend; - - for (;;) - { - // find the element from the right side that belongs to the left one - for (; gtbeg != end; ++gtbeg) - if (!pred(*eqbeg, *gtbeg)) - { - if (*gtbeg == *eqbeg) swap(*gtbeg, *eqend++); - else break; - } - - // find the element from the left side that belongs to the right one - for (; ltend != begin; --ltend) - if (!pred(*(ltend - 1), *eqbeg)) - { - if (*eqbeg == *(ltend - 1)) swap(*(ltend - 1), *--eqbeg); - else break; - } - - // scanned all elements - if (gtbeg == end && ltend == begin) - { - *out_eqbeg = eqbeg; - *out_eqend = eqend; - return; - } - - // make room for elements by moving equal area - if (gtbeg == end) - { - if (--ltend != --eqbeg) swap(*ltend, *eqbeg); - swap(*eqbeg, *--eqend); - } - else if (ltend == begin) - { - if (eqend != gtbeg) swap(*eqbeg, *eqend); - ++eqend; - swap(*gtbeg++, *eqbeg++); - } - else swap(*gtbeg++, *--ltend); - } - } - - template void median3(I first, I middle, I last, const Pred& pred) - { - if (pred(*middle, *first)) swap(*middle, *first); - if (pred(*last, *middle)) swap(*last, *middle); - if (pred(*middle, *first)) swap(*middle, *first); - } - - template void median(I first, I middle, I last, const Pred& pred) - { - if (last - first <= 40) - { - // median of three for small chunks - median3(first, middle, last, pred); - } - else - { - // median of nine - size_t step = (last - first + 1) / 8; - - median3(first, first + step, first + 2 * step, pred); - median3(middle - step, middle, middle + step, pred); - median3(last - 2 * step, last - step, last, pred); - median3(first + step, middle, last - step, pred); - } - } - - template void sort(I begin, I end, const Pred& pred) - { - // sort large chunks - while (end - begin > 32) - { - // find median element - I middle = begin + (end - begin) / 2; - median(begin, middle, end - 1, pred); - - // partition in three chunks (< = >) - I eqbeg, eqend; - partition(begin, middle, end, pred, &eqbeg, &eqend); - - // loop on larger half - if (eqbeg - begin > end - eqend) - { - sort(eqend, end, pred); - end = eqbeg; - } - else - { - sort(begin, eqbeg, pred); - begin = eqend; - } - } - - // insertion sort small chunk - if (begin != end) insertion_sort(begin, end, pred, &*begin); - } -PUGI__NS_END - -// Allocator used for AST and evaluation stacks -PUGI__NS_BEGIN - static const size_t xpath_memory_page_size = - #ifdef PUGIXML_MEMORY_XPATH_PAGE_SIZE - PUGIXML_MEMORY_XPATH_PAGE_SIZE - #else - 4096 - #endif - ; - - static const uintptr_t xpath_memory_block_alignment = sizeof(double) > sizeof(void*) ? sizeof(double) : sizeof(void*); - - struct xpath_memory_block - { - xpath_memory_block* next; - size_t capacity; - - union - { - char data[xpath_memory_page_size]; - double alignment; - }; - }; - - class xpath_allocator - { - xpath_memory_block* _root; - size_t _root_size; - - public: - #ifdef PUGIXML_NO_EXCEPTIONS - jmp_buf* error_handler; - #endif - - xpath_allocator(xpath_memory_block* root, size_t root_size = 0): _root(root), _root_size(root_size) - { - #ifdef PUGIXML_NO_EXCEPTIONS - error_handler = 0; - #endif - } - - void* allocate_nothrow(size_t size) - { - // round size up to block alignment boundary - size = (size + xpath_memory_block_alignment - 1) & ~(xpath_memory_block_alignment - 1); - - if (_root_size + size <= _root->capacity) - { - void* buf = &_root->data[0] + _root_size; - _root_size += size; - return buf; - } - else - { - // make sure we have at least 1/4th of the page free after allocation to satisfy subsequent allocation requests - size_t block_capacity_base = sizeof(_root->data); - size_t block_capacity_req = size + block_capacity_base / 4; - size_t block_capacity = (block_capacity_base > block_capacity_req) ? block_capacity_base : block_capacity_req; - - size_t block_size = block_capacity + offsetof(xpath_memory_block, data); - - xpath_memory_block* block = static_cast(xml_memory::allocate(block_size)); - if (!block) return 0; - - block->next = _root; - block->capacity = block_capacity; - - _root = block; - _root_size = size; - - return block->data; - } - } - - void* allocate(size_t size) - { - void* result = allocate_nothrow(size); - - if (!result) - { - #ifdef PUGIXML_NO_EXCEPTIONS - assert(error_handler); - longjmp(*error_handler, 1); - #else - throw std::bad_alloc(); - #endif - } - - return result; - } - - void* reallocate(void* ptr, size_t old_size, size_t new_size) - { - // round size up to block alignment boundary - old_size = (old_size + xpath_memory_block_alignment - 1) & ~(xpath_memory_block_alignment - 1); - new_size = (new_size + xpath_memory_block_alignment - 1) & ~(xpath_memory_block_alignment - 1); - - // we can only reallocate the last object - assert(ptr == 0 || static_cast(ptr) + old_size == &_root->data[0] + _root_size); - - // adjust root size so that we have not allocated the object at all - bool only_object = (_root_size == old_size); - - if (ptr) _root_size -= old_size; - - // allocate a new version (this will obviously reuse the memory if possible) - void* result = allocate(new_size); - assert(result); - - // we have a new block - if (result != ptr && ptr) - { - // copy old data - assert(new_size >= old_size); - memcpy(result, ptr, old_size); - - // free the previous page if it had no other objects - if (only_object) - { - assert(_root->data == result); - assert(_root->next); - - xpath_memory_block* next = _root->next->next; - - if (next) - { - // deallocate the whole page, unless it was the first one - xml_memory::deallocate(_root->next); - _root->next = next; - } - } - } - - return result; - } - - void revert(const xpath_allocator& state) - { - // free all new pages - xpath_memory_block* cur = _root; - - while (cur != state._root) - { - xpath_memory_block* next = cur->next; - - xml_memory::deallocate(cur); - - cur = next; - } - - // restore state - _root = state._root; - _root_size = state._root_size; - } - - void release() - { - xpath_memory_block* cur = _root; - assert(cur); - - while (cur->next) - { - xpath_memory_block* next = cur->next; - - xml_memory::deallocate(cur); - - cur = next; - } - } - }; - - struct xpath_allocator_capture - { - xpath_allocator_capture(xpath_allocator* alloc): _target(alloc), _state(*alloc) - { - } - - ~xpath_allocator_capture() - { - _target->revert(_state); - } - - xpath_allocator* _target; - xpath_allocator _state; - }; - - struct xpath_stack - { - xpath_allocator* result; - xpath_allocator* temp; - }; - - struct xpath_stack_data - { - xpath_memory_block blocks[2]; - xpath_allocator result; - xpath_allocator temp; - xpath_stack stack; - - #ifdef PUGIXML_NO_EXCEPTIONS - jmp_buf error_handler; - #endif - - xpath_stack_data(): result(blocks + 0), temp(blocks + 1) - { - blocks[0].next = blocks[1].next = 0; - blocks[0].capacity = blocks[1].capacity = sizeof(blocks[0].data); - - stack.result = &result; - stack.temp = &temp; - - #ifdef PUGIXML_NO_EXCEPTIONS - result.error_handler = temp.error_handler = &error_handler; - #endif - } - - ~xpath_stack_data() - { - result.release(); - temp.release(); - } - }; -PUGI__NS_END - -// String class -PUGI__NS_BEGIN - class xpath_string - { - const char_t* _buffer; - bool _uses_heap; - size_t _length_heap; - - static char_t* duplicate_string(const char_t* string, size_t length, xpath_allocator* alloc) - { - char_t* result = static_cast(alloc->allocate((length + 1) * sizeof(char_t))); - assert(result); - - memcpy(result, string, length * sizeof(char_t)); - result[length] = 0; - - return result; - } - - xpath_string(const char_t* buffer, bool uses_heap_, size_t length_heap): _buffer(buffer), _uses_heap(uses_heap_), _length_heap(length_heap) - { - } - - public: - static xpath_string from_const(const char_t* str) - { - return xpath_string(str, false, 0); - } - - static xpath_string from_heap_preallocated(const char_t* begin, const char_t* end) - { - assert(begin <= end && *end == 0); - - return xpath_string(begin, true, static_cast(end - begin)); - } - - static xpath_string from_heap(const char_t* begin, const char_t* end, xpath_allocator* alloc) - { - assert(begin <= end); - - size_t length = static_cast(end - begin); - - return length == 0 ? xpath_string() : xpath_string(duplicate_string(begin, length, alloc), true, length); - } - - xpath_string(): _buffer(PUGIXML_TEXT("")), _uses_heap(false), _length_heap(0) - { - } - - void append(const xpath_string& o, xpath_allocator* alloc) - { - // skip empty sources - if (!*o._buffer) return; - - // fast append for constant empty target and constant source - if (!*_buffer && !_uses_heap && !o._uses_heap) - { - _buffer = o._buffer; - } - else - { - // need to make heap copy - size_t target_length = length(); - size_t source_length = o.length(); - size_t result_length = target_length + source_length; - - // allocate new buffer - char_t* result = static_cast(alloc->reallocate(_uses_heap ? const_cast(_buffer) : 0, (target_length + 1) * sizeof(char_t), (result_length + 1) * sizeof(char_t))); - assert(result); - - // append first string to the new buffer in case there was no reallocation - if (!_uses_heap) memcpy(result, _buffer, target_length * sizeof(char_t)); - - // append second string to the new buffer - memcpy(result + target_length, o._buffer, source_length * sizeof(char_t)); - result[result_length] = 0; - - // finalize - _buffer = result; - _uses_heap = true; - _length_heap = result_length; - } - } - - const char_t* c_str() const - { - return _buffer; - } - - size_t length() const - { - return _uses_heap ? _length_heap : strlength(_buffer); - } - - char_t* data(xpath_allocator* alloc) - { - // make private heap copy - if (!_uses_heap) - { - size_t length_ = strlength(_buffer); - - _buffer = duplicate_string(_buffer, length_, alloc); - _uses_heap = true; - _length_heap = length_; - } - - return const_cast(_buffer); - } - - bool empty() const - { - return *_buffer == 0; - } - - bool operator==(const xpath_string& o) const - { - return strequal(_buffer, o._buffer); - } - - bool operator!=(const xpath_string& o) const - { - return !strequal(_buffer, o._buffer); - } - - bool uses_heap() const - { - return _uses_heap; - } - }; -PUGI__NS_END - -PUGI__NS_BEGIN - PUGI__FN bool starts_with(const char_t* string, const char_t* pattern) - { - while (*pattern && *string == *pattern) - { - string++; - pattern++; - } - - return *pattern == 0; - } - - PUGI__FN const char_t* find_char(const char_t* s, char_t c) - { - #ifdef PUGIXML_WCHAR_MODE - return wcschr(s, c); - #else - return strchr(s, c); - #endif - } - - PUGI__FN const char_t* find_substring(const char_t* s, const char_t* p) - { - #ifdef PUGIXML_WCHAR_MODE - // MSVC6 wcsstr bug workaround (if s is empty it always returns 0) - return (*p == 0) ? s : wcsstr(s, p); - #else - return strstr(s, p); - #endif - } - - // Converts symbol to lower case, if it is an ASCII one - PUGI__FN char_t tolower_ascii(char_t ch) - { - return static_cast(ch - 'A') < 26 ? static_cast(ch | ' ') : ch; - } - - PUGI__FN xpath_string string_value(const xpath_node& na, xpath_allocator* alloc) - { - if (na.attribute()) - return xpath_string::from_const(na.attribute().value()); - else - { - xml_node n = na.node(); - - switch (n.type()) - { - case node_pcdata: - case node_cdata: - case node_comment: - case node_pi: - return xpath_string::from_const(n.value()); - - case node_document: - case node_element: - { - xpath_string result; - - xml_node cur = n.first_child(); - - while (cur && cur != n) - { - if (cur.type() == node_pcdata || cur.type() == node_cdata) - result.append(xpath_string::from_const(cur.value()), alloc); - - if (cur.first_child()) - cur = cur.first_child(); - else if (cur.next_sibling()) - cur = cur.next_sibling(); - else - { - while (!cur.next_sibling() && cur != n) - cur = cur.parent(); - - if (cur != n) cur = cur.next_sibling(); - } - } - - return result; - } - - default: - return xpath_string(); - } - } - } - - PUGI__FN bool node_is_before_sibling(xml_node_struct* ln, xml_node_struct* rn) - { - assert(ln->parent == rn->parent); - - // there is no common ancestor (the shared parent is null), nodes are from different documents - if (!ln->parent) return ln < rn; - - // determine sibling order - xml_node_struct* ls = ln; - xml_node_struct* rs = rn; - - while (ls && rs) - { - if (ls == rn) return true; - if (rs == ln) return false; - - ls = ls->next_sibling; - rs = rs->next_sibling; - } - - // if rn sibling chain ended ln must be before rn - return !rs; - } - - PUGI__FN bool node_is_before(xml_node_struct* ln, xml_node_struct* rn) - { - // find common ancestor at the same depth, if any - xml_node_struct* lp = ln; - xml_node_struct* rp = rn; - - while (lp && rp && lp->parent != rp->parent) - { - lp = lp->parent; - rp = rp->parent; - } - - // parents are the same! - if (lp && rp) return node_is_before_sibling(lp, rp); - - // nodes are at different depths, need to normalize heights - bool left_higher = !lp; - - while (lp) - { - lp = lp->parent; - ln = ln->parent; - } - - while (rp) - { - rp = rp->parent; - rn = rn->parent; - } - - // one node is the ancestor of the other - if (ln == rn) return left_higher; - - // find common ancestor... again - while (ln->parent != rn->parent) - { - ln = ln->parent; - rn = rn->parent; - } - - return node_is_before_sibling(ln, rn); - } - - PUGI__FN bool node_is_ancestor(xml_node_struct* parent, xml_node_struct* node) - { - while (node && node != parent) node = node->parent; - - return parent && node == parent; - } - - PUGI__FN const void* document_buffer_order(const xpath_node& xnode) - { - xml_node_struct* node = xnode.node().internal_object(); - - if (node) - { - if ((get_document(node).header & xml_memory_page_contents_shared_mask) == 0) - { - if (node->name && (node->header & impl::xml_memory_page_name_allocated_or_shared_mask) == 0) return node->name; - if (node->value && (node->header & impl::xml_memory_page_value_allocated_or_shared_mask) == 0) return node->value; - } - - return 0; - } - - xml_attribute_struct* attr = xnode.attribute().internal_object(); - - if (attr) - { - if ((get_document(attr).header & xml_memory_page_contents_shared_mask) == 0) - { - if ((attr->header & impl::xml_memory_page_name_allocated_or_shared_mask) == 0) return attr->name; - if ((attr->header & impl::xml_memory_page_value_allocated_or_shared_mask) == 0) return attr->value; - } - - return 0; - } - - return 0; - } - - struct document_order_comparator - { - bool operator()(const xpath_node& lhs, const xpath_node& rhs) const - { - // optimized document order based check - const void* lo = document_buffer_order(lhs); - const void* ro = document_buffer_order(rhs); - - if (lo && ro) return lo < ro; - - // slow comparison - xml_node ln = lhs.node(), rn = rhs.node(); - - // compare attributes - if (lhs.attribute() && rhs.attribute()) - { - // shared parent - if (lhs.parent() == rhs.parent()) - { - // determine sibling order - for (xml_attribute a = lhs.attribute(); a; a = a.next_attribute()) - if (a == rhs.attribute()) - return true; - - return false; - } - - // compare attribute parents - ln = lhs.parent(); - rn = rhs.parent(); - } - else if (lhs.attribute()) - { - // attributes go after the parent element - if (lhs.parent() == rhs.node()) return false; - - ln = lhs.parent(); - } - else if (rhs.attribute()) - { - // attributes go after the parent element - if (rhs.parent() == lhs.node()) return true; - - rn = rhs.parent(); - } - - if (ln == rn) return false; - - if (!ln || !rn) return ln < rn; - - return node_is_before(ln.internal_object(), rn.internal_object()); - } - }; - - struct duplicate_comparator - { - bool operator()(const xpath_node& lhs, const xpath_node& rhs) const - { - if (lhs.attribute()) return rhs.attribute() ? lhs.attribute() < rhs.attribute() : true; - else return rhs.attribute() ? false : lhs.node() < rhs.node(); - } - }; - - PUGI__FN double gen_nan() - { - #if defined(__STDC_IEC_559__) || ((FLT_RADIX - 0 == 2) && (FLT_MAX_EXP - 0 == 128) && (FLT_MANT_DIG - 0 == 24)) - union { float f; uint32_t i; } u[sizeof(float) == sizeof(uint32_t) ? 1 : -1]; - u[0].i = 0x7fc00000; - return u[0].f; - #else - // fallback - const volatile double zero = 0.0; - return zero / zero; - #endif - } - - PUGI__FN bool is_nan(double value) - { - #if defined(PUGI__MSVC_CRT_VERSION) || defined(__BORLANDC__) - return !!_isnan(value); - #elif defined(fpclassify) && defined(FP_NAN) - return fpclassify(value) == FP_NAN; - #else - // fallback - const volatile double v = value; - return v != v; - #endif - } - - PUGI__FN const char_t* convert_number_to_string_special(double value) - { - #if defined(PUGI__MSVC_CRT_VERSION) || defined(__BORLANDC__) - if (_finite(value)) return (value == 0) ? PUGIXML_TEXT("0") : 0; - if (_isnan(value)) return PUGIXML_TEXT("NaN"); - return value > 0 ? PUGIXML_TEXT("Infinity") : PUGIXML_TEXT("-Infinity"); - #elif defined(fpclassify) && defined(FP_NAN) && defined(FP_INFINITE) && defined(FP_ZERO) - switch (fpclassify(value)) - { - case FP_NAN: - return PUGIXML_TEXT("NaN"); - - case FP_INFINITE: - return value > 0 ? PUGIXML_TEXT("Infinity") : PUGIXML_TEXT("-Infinity"); - - case FP_ZERO: - return PUGIXML_TEXT("0"); - - default: - return 0; - } - #else - // fallback - const volatile double v = value; - - if (v == 0) return PUGIXML_TEXT("0"); - if (v != v) return PUGIXML_TEXT("NaN"); - if (v * 2 == v) return value > 0 ? PUGIXML_TEXT("Infinity") : PUGIXML_TEXT("-Infinity"); - return 0; - #endif - } - - PUGI__FN bool convert_number_to_boolean(double value) - { - return (value != 0 && !is_nan(value)); - } - - PUGI__FN void truncate_zeros(char* begin, char* end) - { - while (begin != end && end[-1] == '0') end--; - - *end = 0; - } - - // gets mantissa digits in the form of 0.xxxxx with 0. implied and the exponent -#if defined(PUGI__MSVC_CRT_VERSION) && PUGI__MSVC_CRT_VERSION >= 1400 && !defined(_WIN32_WCE) - PUGI__FN void convert_number_to_mantissa_exponent(double value, char* buffer, size_t buffer_size, char** out_mantissa, int* out_exponent) - { - // get base values - int sign, exponent; - _ecvt_s(buffer, buffer_size, value, DBL_DIG + 1, &exponent, &sign); - - // truncate redundant zeros - truncate_zeros(buffer, buffer + strlen(buffer)); - - // fill results - *out_mantissa = buffer; - *out_exponent = exponent; - } -#else - PUGI__FN void convert_number_to_mantissa_exponent(double value, char* buffer, size_t buffer_size, char** out_mantissa, int* out_exponent) - { - // get a scientific notation value with IEEE DBL_DIG decimals - sprintf(buffer, "%.*e", DBL_DIG, value); - assert(strlen(buffer) < buffer_size); - (void)!buffer_size; - - // get the exponent (possibly negative) - char* exponent_string = strchr(buffer, 'e'); - assert(exponent_string); - - int exponent = atoi(exponent_string + 1); - - // extract mantissa string: skip sign - char* mantissa = buffer[0] == '-' ? buffer + 1 : buffer; - assert(mantissa[0] != '0' && mantissa[1] == '.'); - - // divide mantissa by 10 to eliminate integer part - mantissa[1] = mantissa[0]; - mantissa++; - exponent++; - - // remove extra mantissa digits and zero-terminate mantissa - truncate_zeros(mantissa, exponent_string); - - // fill results - *out_mantissa = mantissa; - *out_exponent = exponent; - } -#endif - - PUGI__FN xpath_string convert_number_to_string(double value, xpath_allocator* alloc) - { - // try special number conversion - const char_t* special = convert_number_to_string_special(value); - if (special) return xpath_string::from_const(special); - - // get mantissa + exponent form - char mantissa_buffer[32]; - - char* mantissa; - int exponent; - convert_number_to_mantissa_exponent(value, mantissa_buffer, sizeof(mantissa_buffer), &mantissa, &exponent); - - // allocate a buffer of suitable length for the number - size_t result_size = strlen(mantissa_buffer) + (exponent > 0 ? exponent : -exponent) + 4; - char_t* result = static_cast(alloc->allocate(sizeof(char_t) * result_size)); - assert(result); - - // make the number! - char_t* s = result; - - // sign - if (value < 0) *s++ = '-'; - - // integer part - if (exponent <= 0) - { - *s++ = '0'; - } - else - { - while (exponent > 0) - { - assert(*mantissa == 0 || static_cast(static_cast(*mantissa) - '0') <= 9); - *s++ = *mantissa ? *mantissa++ : '0'; - exponent--; - } - } - - // fractional part - if (*mantissa) - { - // decimal point - *s++ = '.'; - - // extra zeroes from negative exponent - while (exponent < 0) - { - *s++ = '0'; - exponent++; - } - - // extra mantissa digits - while (*mantissa) - { - assert(static_cast(*mantissa - '0') <= 9); - *s++ = *mantissa++; - } - } - - // zero-terminate - assert(s < result + result_size); - *s = 0; - - return xpath_string::from_heap_preallocated(result, s); - } - - PUGI__FN bool check_string_to_number_format(const char_t* string) - { - // parse leading whitespace - while (PUGI__IS_CHARTYPE(*string, ct_space)) ++string; - - // parse sign - if (*string == '-') ++string; - - if (!*string) return false; - - // if there is no integer part, there should be a decimal part with at least one digit - if (!PUGI__IS_CHARTYPEX(string[0], ctx_digit) && (string[0] != '.' || !PUGI__IS_CHARTYPEX(string[1], ctx_digit))) return false; - - // parse integer part - while (PUGI__IS_CHARTYPEX(*string, ctx_digit)) ++string; - - // parse decimal part - if (*string == '.') - { - ++string; - - while (PUGI__IS_CHARTYPEX(*string, ctx_digit)) ++string; - } - - // parse trailing whitespace - while (PUGI__IS_CHARTYPE(*string, ct_space)) ++string; - - return *string == 0; - } - - PUGI__FN double convert_string_to_number(const char_t* string) - { - // check string format - if (!check_string_to_number_format(string)) return gen_nan(); - - // parse string - #ifdef PUGIXML_WCHAR_MODE - return wcstod(string, 0); - #else - return strtod(string, 0); - #endif - } - - PUGI__FN bool convert_string_to_number_scratch(char_t (&buffer)[32], const char_t* begin, const char_t* end, double* out_result) - { - size_t length = static_cast(end - begin); - char_t* scratch = buffer; - - if (length >= sizeof(buffer) / sizeof(buffer[0])) - { - // need to make dummy on-heap copy - scratch = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); - if (!scratch) return false; - } - - // copy string to zero-terminated buffer and perform conversion - memcpy(scratch, begin, length * sizeof(char_t)); - scratch[length] = 0; - - *out_result = convert_string_to_number(scratch); - - // free dummy buffer - if (scratch != buffer) xml_memory::deallocate(scratch); - - return true; - } - - PUGI__FN double round_nearest(double value) - { - return floor(value + 0.5); - } - - PUGI__FN double round_nearest_nzero(double value) - { - // same as round_nearest, but returns -0 for [-0.5, -0] - // ceil is used to differentiate between +0 and -0 (we return -0 for [-0.5, -0] and +0 for +0) - return (value >= -0.5 && value <= 0) ? ceil(value) : floor(value + 0.5); - } - - PUGI__FN const char_t* qualified_name(const xpath_node& node) - { - return node.attribute() ? node.attribute().name() : node.node().name(); - } - - PUGI__FN const char_t* local_name(const xpath_node& node) - { - const char_t* name = qualified_name(node); - const char_t* p = find_char(name, ':'); - - return p ? p + 1 : name; - } - - struct namespace_uri_predicate - { - const char_t* prefix; - size_t prefix_length; - - namespace_uri_predicate(const char_t* name) - { - const char_t* pos = find_char(name, ':'); - - prefix = pos ? name : 0; - prefix_length = pos ? static_cast(pos - name) : 0; - } - - bool operator()(xml_attribute a) const - { - const char_t* name = a.name(); - - if (!starts_with(name, PUGIXML_TEXT("xmlns"))) return false; - - return prefix ? name[5] == ':' && strequalrange(name + 6, prefix, prefix_length) : name[5] == 0; - } - }; - - PUGI__FN const char_t* namespace_uri(xml_node node) - { - namespace_uri_predicate pred = node.name(); - - xml_node p = node; - - while (p) - { - xml_attribute a = p.find_attribute(pred); - - if (a) return a.value(); - - p = p.parent(); - } - - return PUGIXML_TEXT(""); - } - - PUGI__FN const char_t* namespace_uri(xml_attribute attr, xml_node parent) - { - namespace_uri_predicate pred = attr.name(); - - // Default namespace does not apply to attributes - if (!pred.prefix) return PUGIXML_TEXT(""); - - xml_node p = parent; - - while (p) - { - xml_attribute a = p.find_attribute(pred); - - if (a) return a.value(); - - p = p.parent(); - } - - return PUGIXML_TEXT(""); - } - - PUGI__FN const char_t* namespace_uri(const xpath_node& node) - { - return node.attribute() ? namespace_uri(node.attribute(), node.parent()) : namespace_uri(node.node()); - } - - PUGI__FN char_t* normalize_space(char_t* buffer) - { - char_t* write = buffer; - - for (char_t* it = buffer; *it; ) - { - char_t ch = *it++; - - if (PUGI__IS_CHARTYPE(ch, ct_space)) - { - // replace whitespace sequence with single space - while (PUGI__IS_CHARTYPE(*it, ct_space)) it++; - - // avoid leading spaces - if (write != buffer) *write++ = ' '; - } - else *write++ = ch; - } - - // remove trailing space - if (write != buffer && PUGI__IS_CHARTYPE(write[-1], ct_space)) write--; - - // zero-terminate - *write = 0; - - return write; - } - - PUGI__FN char_t* translate(char_t* buffer, const char_t* from, const char_t* to, size_t to_length) - { - char_t* write = buffer; - - while (*buffer) - { - PUGI__DMC_VOLATILE char_t ch = *buffer++; - - const char_t* pos = find_char(from, ch); - - if (!pos) - *write++ = ch; // do not process - else if (static_cast(pos - from) < to_length) - *write++ = to[pos - from]; // replace - } - - // zero-terminate - *write = 0; - - return write; - } - - PUGI__FN unsigned char* translate_table_generate(xpath_allocator* alloc, const char_t* from, const char_t* to) - { - unsigned char table[128] = {0}; - - while (*from) - { - unsigned int fc = static_cast(*from); - unsigned int tc = static_cast(*to); - - if (fc >= 128 || tc >= 128) - return 0; - - // code=128 means "skip character" - if (!table[fc]) - table[fc] = static_cast(tc ? tc : 128); - - from++; - if (tc) to++; - } - - for (int i = 0; i < 128; ++i) - if (!table[i]) - table[i] = static_cast(i); - - void* result = alloc->allocate_nothrow(sizeof(table)); - - if (result) - { - memcpy(result, table, sizeof(table)); - } - - return static_cast(result); - } - - PUGI__FN char_t* translate_table(char_t* buffer, const unsigned char* table) - { - char_t* write = buffer; - - while (*buffer) - { - char_t ch = *buffer++; - unsigned int index = static_cast(ch); - - if (index < 128) - { - unsigned char code = table[index]; - - // code=128 means "skip character" (table size is 128 so 128 can be a special value) - // this code skips these characters without extra branches - *write = static_cast(code); - write += 1 - (code >> 7); - } - else - { - *write++ = ch; - } - } - - // zero-terminate - *write = 0; - - return write; - } - - inline bool is_xpath_attribute(const char_t* name) - { - return !(starts_with(name, PUGIXML_TEXT("xmlns")) && (name[5] == 0 || name[5] == ':')); - } - - struct xpath_variable_boolean: xpath_variable - { - xpath_variable_boolean(): xpath_variable(xpath_type_boolean), value(false) - { - } - - bool value; - char_t name[1]; - }; - - struct xpath_variable_number: xpath_variable - { - xpath_variable_number(): xpath_variable(xpath_type_number), value(0) - { - } - - double value; - char_t name[1]; - }; - - struct xpath_variable_string: xpath_variable - { - xpath_variable_string(): xpath_variable(xpath_type_string), value(0) - { - } - - ~xpath_variable_string() - { - if (value) xml_memory::deallocate(value); - } - - char_t* value; - char_t name[1]; - }; - - struct xpath_variable_node_set: xpath_variable - { - xpath_variable_node_set(): xpath_variable(xpath_type_node_set) - { - } - - xpath_node_set value; - char_t name[1]; - }; - - static const xpath_node_set dummy_node_set; - - PUGI__FN unsigned int hash_string(const char_t* str) - { - // Jenkins one-at-a-time hash (http://en.wikipedia.org/wiki/Jenkins_hash_function#one-at-a-time) - unsigned int result = 0; - - while (*str) - { - result += static_cast(*str++); - result += result << 10; - result ^= result >> 6; - } - - result += result << 3; - result ^= result >> 11; - result += result << 15; - - return result; - } - - template PUGI__FN T* new_xpath_variable(const char_t* name) - { - size_t length = strlength(name); - if (length == 0) return 0; // empty variable names are invalid - - // $$ we can't use offsetof(T, name) because T is non-POD, so we just allocate additional length characters - void* memory = xml_memory::allocate(sizeof(T) + length * sizeof(char_t)); - if (!memory) return 0; - - T* result = new (memory) T(); - - memcpy(result->name, name, (length + 1) * sizeof(char_t)); - - return result; - } - - PUGI__FN xpath_variable* new_xpath_variable(xpath_value_type type, const char_t* name) - { - switch (type) - { - case xpath_type_node_set: - return new_xpath_variable(name); - - case xpath_type_number: - return new_xpath_variable(name); - - case xpath_type_string: - return new_xpath_variable(name); - - case xpath_type_boolean: - return new_xpath_variable(name); - - default: - return 0; - } - } - - template PUGI__FN void delete_xpath_variable(T* var) - { - var->~T(); - xml_memory::deallocate(var); - } - - PUGI__FN void delete_xpath_variable(xpath_value_type type, xpath_variable* var) - { - switch (type) - { - case xpath_type_node_set: - delete_xpath_variable(static_cast(var)); - break; - - case xpath_type_number: - delete_xpath_variable(static_cast(var)); - break; - - case xpath_type_string: - delete_xpath_variable(static_cast(var)); - break; - - case xpath_type_boolean: - delete_xpath_variable(static_cast(var)); - break; - - default: - assert(!"Invalid variable type"); - } - } - - PUGI__FN bool copy_xpath_variable(xpath_variable* lhs, const xpath_variable* rhs) - { - switch (rhs->type()) - { - case xpath_type_node_set: - return lhs->set(static_cast(rhs)->value); - - case xpath_type_number: - return lhs->set(static_cast(rhs)->value); - - case xpath_type_string: - return lhs->set(static_cast(rhs)->value); - - case xpath_type_boolean: - return lhs->set(static_cast(rhs)->value); - - default: - assert(!"Invalid variable type"); - return false; - } - } - - PUGI__FN bool get_variable_scratch(char_t (&buffer)[32], xpath_variable_set* set, const char_t* begin, const char_t* end, xpath_variable** out_result) - { - size_t length = static_cast(end - begin); - char_t* scratch = buffer; - - if (length >= sizeof(buffer) / sizeof(buffer[0])) - { - // need to make dummy on-heap copy - scratch = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); - if (!scratch) return false; - } - - // copy string to zero-terminated buffer and perform lookup - memcpy(scratch, begin, length * sizeof(char_t)); - scratch[length] = 0; - - *out_result = set->get(scratch); - - // free dummy buffer - if (scratch != buffer) xml_memory::deallocate(scratch); - - return true; - } -PUGI__NS_END - -// Internal node set class -PUGI__NS_BEGIN - PUGI__FN xpath_node_set::type_t xpath_get_order(const xpath_node* begin, const xpath_node* end) - { - if (end - begin < 2) - return xpath_node_set::type_sorted; - - document_order_comparator cmp; - - bool first = cmp(begin[0], begin[1]); - - for (const xpath_node* it = begin + 1; it + 1 < end; ++it) - if (cmp(it[0], it[1]) != first) - return xpath_node_set::type_unsorted; - - return first ? xpath_node_set::type_sorted : xpath_node_set::type_sorted_reverse; - } - - PUGI__FN xpath_node_set::type_t xpath_sort(xpath_node* begin, xpath_node* end, xpath_node_set::type_t type, bool rev) - { - xpath_node_set::type_t order = rev ? xpath_node_set::type_sorted_reverse : xpath_node_set::type_sorted; - - if (type == xpath_node_set::type_unsorted) - { - xpath_node_set::type_t sorted = xpath_get_order(begin, end); - - if (sorted == xpath_node_set::type_unsorted) - { - sort(begin, end, document_order_comparator()); - - type = xpath_node_set::type_sorted; - } - else - type = sorted; - } - - if (type != order) reverse(begin, end); - - return order; - } - - PUGI__FN xpath_node xpath_first(const xpath_node* begin, const xpath_node* end, xpath_node_set::type_t type) - { - if (begin == end) return xpath_node(); - - switch (type) - { - case xpath_node_set::type_sorted: - return *begin; - - case xpath_node_set::type_sorted_reverse: - return *(end - 1); - - case xpath_node_set::type_unsorted: - return *min_element(begin, end, document_order_comparator()); - - default: - assert(!"Invalid node set type"); - return xpath_node(); - } - } - - class xpath_node_set_raw - { - xpath_node_set::type_t _type; - - xpath_node* _begin; - xpath_node* _end; - xpath_node* _eos; - - public: - xpath_node_set_raw(): _type(xpath_node_set::type_unsorted), _begin(0), _end(0), _eos(0) - { - } - - xpath_node* begin() const - { - return _begin; - } - - xpath_node* end() const - { - return _end; - } - - bool empty() const - { - return _begin == _end; - } - - size_t size() const - { - return static_cast(_end - _begin); - } - - xpath_node first() const - { - return xpath_first(_begin, _end, _type); - } - - void push_back_grow(const xpath_node& node, xpath_allocator* alloc); - - void push_back(const xpath_node& node, xpath_allocator* alloc) - { - if (_end != _eos) - *_end++ = node; - else - push_back_grow(node, alloc); - } - - void append(const xpath_node* begin_, const xpath_node* end_, xpath_allocator* alloc) - { - if (begin_ == end_) return; - - size_t size_ = static_cast(_end - _begin); - size_t capacity = static_cast(_eos - _begin); - size_t count = static_cast(end_ - begin_); - - if (size_ + count > capacity) - { - // reallocate the old array or allocate a new one - xpath_node* data = static_cast(alloc->reallocate(_begin, capacity * sizeof(xpath_node), (size_ + count) * sizeof(xpath_node))); - assert(data); - - // finalize - _begin = data; - _end = data + size_; - _eos = data + size_ + count; - } - - memcpy(_end, begin_, count * sizeof(xpath_node)); - _end += count; - } - - void sort_do() - { - _type = xpath_sort(_begin, _end, _type, false); - } - - void truncate(xpath_node* pos) - { - assert(_begin <= pos && pos <= _end); - - _end = pos; - } - - void remove_duplicates() - { - if (_type == xpath_node_set::type_unsorted) - sort(_begin, _end, duplicate_comparator()); - - _end = unique(_begin, _end); - } - - xpath_node_set::type_t type() const - { - return _type; - } - - void set_type(xpath_node_set::type_t value) - { - _type = value; - } - }; - - PUGI__FN_NO_INLINE void xpath_node_set_raw::push_back_grow(const xpath_node& node, xpath_allocator* alloc) - { - size_t capacity = static_cast(_eos - _begin); - - // get new capacity (1.5x rule) - size_t new_capacity = capacity + capacity / 2 + 1; - - // reallocate the old array or allocate a new one - xpath_node* data = static_cast(alloc->reallocate(_begin, capacity * sizeof(xpath_node), new_capacity * sizeof(xpath_node))); - assert(data); - - // finalize - _begin = data; - _end = data + capacity; - _eos = data + new_capacity; - - // push - *_end++ = node; - } -PUGI__NS_END - -PUGI__NS_BEGIN - struct xpath_context - { - xpath_node n; - size_t position, size; - - xpath_context(const xpath_node& n_, size_t position_, size_t size_): n(n_), position(position_), size(size_) - { - } - }; - - enum lexeme_t - { - lex_none = 0, - lex_equal, - lex_not_equal, - lex_less, - lex_greater, - lex_less_or_equal, - lex_greater_or_equal, - lex_plus, - lex_minus, - lex_multiply, - lex_union, - lex_var_ref, - lex_open_brace, - lex_close_brace, - lex_quoted_string, - lex_number, - lex_slash, - lex_double_slash, - lex_open_square_brace, - lex_close_square_brace, - lex_string, - lex_comma, - lex_axis_attribute, - lex_dot, - lex_double_dot, - lex_double_colon, - lex_eof - }; - - struct xpath_lexer_string - { - const char_t* begin; - const char_t* end; - - xpath_lexer_string(): begin(0), end(0) - { - } - - bool operator==(const char_t* other) const - { - size_t length = static_cast(end - begin); - - return strequalrange(other, begin, length); - } - }; - - class xpath_lexer - { - const char_t* _cur; - const char_t* _cur_lexeme_pos; - xpath_lexer_string _cur_lexeme_contents; - - lexeme_t _cur_lexeme; - - public: - explicit xpath_lexer(const char_t* query): _cur(query) - { - next(); - } - - const char_t* state() const - { - return _cur; - } - - void next() - { - const char_t* cur = _cur; - - while (PUGI__IS_CHARTYPE(*cur, ct_space)) ++cur; - - // save lexeme position for error reporting - _cur_lexeme_pos = cur; - - switch (*cur) - { - case 0: - _cur_lexeme = lex_eof; - break; - - case '>': - if (*(cur+1) == '=') - { - cur += 2; - _cur_lexeme = lex_greater_or_equal; - } - else - { - cur += 1; - _cur_lexeme = lex_greater; - } - break; - - case '<': - if (*(cur+1) == '=') - { - cur += 2; - _cur_lexeme = lex_less_or_equal; - } - else - { - cur += 1; - _cur_lexeme = lex_less; - } - break; - - case '!': - if (*(cur+1) == '=') - { - cur += 2; - _cur_lexeme = lex_not_equal; - } - else - { - _cur_lexeme = lex_none; - } - break; - - case '=': - cur += 1; - _cur_lexeme = lex_equal; - - break; - - case '+': - cur += 1; - _cur_lexeme = lex_plus; - - break; - - case '-': - cur += 1; - _cur_lexeme = lex_minus; - - break; - - case '*': - cur += 1; - _cur_lexeme = lex_multiply; - - break; - - case '|': - cur += 1; - _cur_lexeme = lex_union; - - break; - - case '$': - cur += 1; - - if (PUGI__IS_CHARTYPEX(*cur, ctx_start_symbol)) - { - _cur_lexeme_contents.begin = cur; - - while (PUGI__IS_CHARTYPEX(*cur, ctx_symbol)) cur++; - - if (cur[0] == ':' && PUGI__IS_CHARTYPEX(cur[1], ctx_symbol)) // qname - { - cur++; // : - - while (PUGI__IS_CHARTYPEX(*cur, ctx_symbol)) cur++; - } - - _cur_lexeme_contents.end = cur; - - _cur_lexeme = lex_var_ref; - } - else - { - _cur_lexeme = lex_none; - } - - break; - - case '(': - cur += 1; - _cur_lexeme = lex_open_brace; - - break; - - case ')': - cur += 1; - _cur_lexeme = lex_close_brace; - - break; - - case '[': - cur += 1; - _cur_lexeme = lex_open_square_brace; - - break; - - case ']': - cur += 1; - _cur_lexeme = lex_close_square_brace; - - break; - - case ',': - cur += 1; - _cur_lexeme = lex_comma; - - break; - - case '/': - if (*(cur+1) == '/') - { - cur += 2; - _cur_lexeme = lex_double_slash; - } - else - { - cur += 1; - _cur_lexeme = lex_slash; - } - break; - - case '.': - if (*(cur+1) == '.') - { - cur += 2; - _cur_lexeme = lex_double_dot; - } - else if (PUGI__IS_CHARTYPEX(*(cur+1), ctx_digit)) - { - _cur_lexeme_contents.begin = cur; // . - - ++cur; - - while (PUGI__IS_CHARTYPEX(*cur, ctx_digit)) cur++; - - _cur_lexeme_contents.end = cur; - - _cur_lexeme = lex_number; - } - else - { - cur += 1; - _cur_lexeme = lex_dot; - } - break; - - case '@': - cur += 1; - _cur_lexeme = lex_axis_attribute; - - break; - - case '"': - case '\'': - { - char_t terminator = *cur; - - ++cur; - - _cur_lexeme_contents.begin = cur; - while (*cur && *cur != terminator) cur++; - _cur_lexeme_contents.end = cur; - - if (!*cur) - _cur_lexeme = lex_none; - else - { - cur += 1; - _cur_lexeme = lex_quoted_string; - } - - break; - } - - case ':': - if (*(cur+1) == ':') - { - cur += 2; - _cur_lexeme = lex_double_colon; - } - else - { - _cur_lexeme = lex_none; - } - break; - - default: - if (PUGI__IS_CHARTYPEX(*cur, ctx_digit)) - { - _cur_lexeme_contents.begin = cur; - - while (PUGI__IS_CHARTYPEX(*cur, ctx_digit)) cur++; - - if (*cur == '.') - { - cur++; - - while (PUGI__IS_CHARTYPEX(*cur, ctx_digit)) cur++; - } - - _cur_lexeme_contents.end = cur; - - _cur_lexeme = lex_number; - } - else if (PUGI__IS_CHARTYPEX(*cur, ctx_start_symbol)) - { - _cur_lexeme_contents.begin = cur; - - while (PUGI__IS_CHARTYPEX(*cur, ctx_symbol)) cur++; - - if (cur[0] == ':') - { - if (cur[1] == '*') // namespace test ncname:* - { - cur += 2; // :* - } - else if (PUGI__IS_CHARTYPEX(cur[1], ctx_symbol)) // namespace test qname - { - cur++; // : - - while (PUGI__IS_CHARTYPEX(*cur, ctx_symbol)) cur++; - } - } - - _cur_lexeme_contents.end = cur; - - _cur_lexeme = lex_string; - } - else - { - _cur_lexeme = lex_none; - } - } - - _cur = cur; - } - - lexeme_t current() const - { - return _cur_lexeme; - } - - const char_t* current_pos() const - { - return _cur_lexeme_pos; - } - - const xpath_lexer_string& contents() const - { - assert(_cur_lexeme == lex_var_ref || _cur_lexeme == lex_number || _cur_lexeme == lex_string || _cur_lexeme == lex_quoted_string); - - return _cur_lexeme_contents; - } - }; - - enum ast_type_t - { - ast_unknown, - ast_op_or, // left or right - ast_op_and, // left and right - ast_op_equal, // left = right - ast_op_not_equal, // left != right - ast_op_less, // left < right - ast_op_greater, // left > right - ast_op_less_or_equal, // left <= right - ast_op_greater_or_equal, // left >= right - ast_op_add, // left + right - ast_op_subtract, // left - right - ast_op_multiply, // left * right - ast_op_divide, // left / right - ast_op_mod, // left % right - ast_op_negate, // left - right - ast_op_union, // left | right - ast_predicate, // apply predicate to set; next points to next predicate - ast_filter, // select * from left where right - ast_string_constant, // string constant - ast_number_constant, // number constant - ast_variable, // variable - ast_func_last, // last() - ast_func_position, // position() - ast_func_count, // count(left) - ast_func_id, // id(left) - ast_func_local_name_0, // local-name() - ast_func_local_name_1, // local-name(left) - ast_func_namespace_uri_0, // namespace-uri() - ast_func_namespace_uri_1, // namespace-uri(left) - ast_func_name_0, // name() - ast_func_name_1, // name(left) - ast_func_string_0, // string() - ast_func_string_1, // string(left) - ast_func_concat, // concat(left, right, siblings) - ast_func_starts_with, // starts_with(left, right) - ast_func_contains, // contains(left, right) - ast_func_substring_before, // substring-before(left, right) - ast_func_substring_after, // substring-after(left, right) - ast_func_substring_2, // substring(left, right) - ast_func_substring_3, // substring(left, right, third) - ast_func_string_length_0, // string-length() - ast_func_string_length_1, // string-length(left) - ast_func_normalize_space_0, // normalize-space() - ast_func_normalize_space_1, // normalize-space(left) - ast_func_translate, // translate(left, right, third) - ast_func_boolean, // boolean(left) - ast_func_not, // not(left) - ast_func_true, // true() - ast_func_false, // false() - ast_func_lang, // lang(left) - ast_func_number_0, // number() - ast_func_number_1, // number(left) - ast_func_sum, // sum(left) - ast_func_floor, // floor(left) - ast_func_ceiling, // ceiling(left) - ast_func_round, // round(left) - ast_step, // process set left with step - ast_step_root, // select root node - - ast_opt_translate_table, // translate(left, right, third) where right/third are constants - ast_opt_compare_attribute // @name = 'string' - }; - - enum axis_t - { - axis_ancestor, - axis_ancestor_or_self, - axis_attribute, - axis_child, - axis_descendant, - axis_descendant_or_self, - axis_following, - axis_following_sibling, - axis_namespace, - axis_parent, - axis_preceding, - axis_preceding_sibling, - axis_self - }; - - enum nodetest_t - { - nodetest_none, - nodetest_name, - nodetest_type_node, - nodetest_type_comment, - nodetest_type_pi, - nodetest_type_text, - nodetest_pi, - nodetest_all, - nodetest_all_in_namespace - }; - - enum predicate_t - { - predicate_default, - predicate_posinv, - predicate_constant, - predicate_constant_one - }; - - enum nodeset_eval_t - { - nodeset_eval_all, - nodeset_eval_any, - nodeset_eval_first - }; - - template struct axis_to_type - { - static const axis_t axis; - }; - - template const axis_t axis_to_type::axis = N; - - class xpath_ast_node - { - private: - // node type - char _type; - char _rettype; - - // for ast_step - char _axis; - - // for ast_step/ast_predicate/ast_filter - char _test; - - // tree node structure - xpath_ast_node* _left; - xpath_ast_node* _right; - xpath_ast_node* _next; - - union - { - // value for ast_string_constant - const char_t* string; - // value for ast_number_constant - double number; - // variable for ast_variable - xpath_variable* variable; - // node test for ast_step (node name/namespace/node type/pi target) - const char_t* nodetest; - // table for ast_opt_translate_table - const unsigned char* table; - } _data; - - xpath_ast_node(const xpath_ast_node&); - xpath_ast_node& operator=(const xpath_ast_node&); - - template static bool compare_eq(xpath_ast_node* lhs, xpath_ast_node* rhs, const xpath_context& c, const xpath_stack& stack, const Comp& comp) - { - xpath_value_type lt = lhs->rettype(), rt = rhs->rettype(); - - if (lt != xpath_type_node_set && rt != xpath_type_node_set) - { - if (lt == xpath_type_boolean || rt == xpath_type_boolean) - return comp(lhs->eval_boolean(c, stack), rhs->eval_boolean(c, stack)); - else if (lt == xpath_type_number || rt == xpath_type_number) - return comp(lhs->eval_number(c, stack), rhs->eval_number(c, stack)); - else if (lt == xpath_type_string || rt == xpath_type_string) - { - xpath_allocator_capture cr(stack.result); - - xpath_string ls = lhs->eval_string(c, stack); - xpath_string rs = rhs->eval_string(c, stack); - - return comp(ls, rs); - } - } - else if (lt == xpath_type_node_set && rt == xpath_type_node_set) - { - xpath_allocator_capture cr(stack.result); - - xpath_node_set_raw ls = lhs->eval_node_set(c, stack, nodeset_eval_all); - xpath_node_set_raw rs = rhs->eval_node_set(c, stack, nodeset_eval_all); - - for (const xpath_node* li = ls.begin(); li != ls.end(); ++li) - for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri) - { - xpath_allocator_capture cri(stack.result); - - if (comp(string_value(*li, stack.result), string_value(*ri, stack.result))) - return true; - } - - return false; - } - else - { - if (lt == xpath_type_node_set) - { - swap(lhs, rhs); - swap(lt, rt); - } - - if (lt == xpath_type_boolean) - return comp(lhs->eval_boolean(c, stack), rhs->eval_boolean(c, stack)); - else if (lt == xpath_type_number) - { - xpath_allocator_capture cr(stack.result); - - double l = lhs->eval_number(c, stack); - xpath_node_set_raw rs = rhs->eval_node_set(c, stack, nodeset_eval_all); - - for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri) - { - xpath_allocator_capture cri(stack.result); - - if (comp(l, convert_string_to_number(string_value(*ri, stack.result).c_str()))) - return true; - } - - return false; - } - else if (lt == xpath_type_string) - { - xpath_allocator_capture cr(stack.result); - - xpath_string l = lhs->eval_string(c, stack); - xpath_node_set_raw rs = rhs->eval_node_set(c, stack, nodeset_eval_all); - - for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri) - { - xpath_allocator_capture cri(stack.result); - - if (comp(l, string_value(*ri, stack.result))) - return true; - } - - return false; - } - } - - assert(!"Wrong types"); - return false; - } - - static bool eval_once(xpath_node_set::type_t type, nodeset_eval_t eval) - { - return type == xpath_node_set::type_sorted ? eval != nodeset_eval_all : eval == nodeset_eval_any; - } - - template static bool compare_rel(xpath_ast_node* lhs, xpath_ast_node* rhs, const xpath_context& c, const xpath_stack& stack, const Comp& comp) - { - xpath_value_type lt = lhs->rettype(), rt = rhs->rettype(); - - if (lt != xpath_type_node_set && rt != xpath_type_node_set) - return comp(lhs->eval_number(c, stack), rhs->eval_number(c, stack)); - else if (lt == xpath_type_node_set && rt == xpath_type_node_set) - { - xpath_allocator_capture cr(stack.result); - - xpath_node_set_raw ls = lhs->eval_node_set(c, stack, nodeset_eval_all); - xpath_node_set_raw rs = rhs->eval_node_set(c, stack, nodeset_eval_all); - - for (const xpath_node* li = ls.begin(); li != ls.end(); ++li) - { - xpath_allocator_capture cri(stack.result); - - double l = convert_string_to_number(string_value(*li, stack.result).c_str()); - - for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri) - { - xpath_allocator_capture crii(stack.result); - - if (comp(l, convert_string_to_number(string_value(*ri, stack.result).c_str()))) - return true; - } - } - - return false; - } - else if (lt != xpath_type_node_set && rt == xpath_type_node_set) - { - xpath_allocator_capture cr(stack.result); - - double l = lhs->eval_number(c, stack); - xpath_node_set_raw rs = rhs->eval_node_set(c, stack, nodeset_eval_all); - - for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri) - { - xpath_allocator_capture cri(stack.result); - - if (comp(l, convert_string_to_number(string_value(*ri, stack.result).c_str()))) - return true; - } - - return false; - } - else if (lt == xpath_type_node_set && rt != xpath_type_node_set) - { - xpath_allocator_capture cr(stack.result); - - xpath_node_set_raw ls = lhs->eval_node_set(c, stack, nodeset_eval_all); - double r = rhs->eval_number(c, stack); - - for (const xpath_node* li = ls.begin(); li != ls.end(); ++li) - { - xpath_allocator_capture cri(stack.result); - - if (comp(convert_string_to_number(string_value(*li, stack.result).c_str()), r)) - return true; - } - - return false; - } - else - { - assert(!"Wrong types"); - return false; - } - } - - static void apply_predicate_boolean(xpath_node_set_raw& ns, size_t first, xpath_ast_node* expr, const xpath_stack& stack, bool once) - { - assert(ns.size() >= first); - assert(expr->rettype() != xpath_type_number); - - size_t i = 1; - size_t size = ns.size() - first; - - xpath_node* last = ns.begin() + first; - - // remove_if... or well, sort of - for (xpath_node* it = last; it != ns.end(); ++it, ++i) - { - xpath_context c(*it, i, size); - - if (expr->eval_boolean(c, stack)) - { - *last++ = *it; - - if (once) break; - } - } - - ns.truncate(last); - } - - static void apply_predicate_number(xpath_node_set_raw& ns, size_t first, xpath_ast_node* expr, const xpath_stack& stack, bool once) - { - assert(ns.size() >= first); - assert(expr->rettype() == xpath_type_number); - - size_t i = 1; - size_t size = ns.size() - first; - - xpath_node* last = ns.begin() + first; - - // remove_if... or well, sort of - for (xpath_node* it = last; it != ns.end(); ++it, ++i) - { - xpath_context c(*it, i, size); - - if (expr->eval_number(c, stack) == i) - { - *last++ = *it; - - if (once) break; - } - } - - ns.truncate(last); - } - - static void apply_predicate_number_const(xpath_node_set_raw& ns, size_t first, xpath_ast_node* expr, const xpath_stack& stack) - { - assert(ns.size() >= first); - assert(expr->rettype() == xpath_type_number); - - size_t size = ns.size() - first; - - xpath_node* last = ns.begin() + first; - - xpath_context c(xpath_node(), 1, size); - - double er = expr->eval_number(c, stack); - - if (er >= 1.0 && er <= size) - { - size_t eri = static_cast(er); - - if (er == eri) - { - xpath_node r = last[eri - 1]; - - *last++ = r; - } - } - - ns.truncate(last); - } - - void apply_predicate(xpath_node_set_raw& ns, size_t first, const xpath_stack& stack, bool once) - { - if (ns.size() == first) return; - - assert(_type == ast_filter || _type == ast_predicate); - - if (_test == predicate_constant || _test == predicate_constant_one) - apply_predicate_number_const(ns, first, _right, stack); - else if (_right->rettype() == xpath_type_number) - apply_predicate_number(ns, first, _right, stack, once); - else - apply_predicate_boolean(ns, first, _right, stack, once); - } - - void apply_predicates(xpath_node_set_raw& ns, size_t first, const xpath_stack& stack, nodeset_eval_t eval) - { - if (ns.size() == first) return; - - bool last_once = eval_once(ns.type(), eval); - - for (xpath_ast_node* pred = _right; pred; pred = pred->_next) - pred->apply_predicate(ns, first, stack, !pred->_next && last_once); - } - - bool step_push(xpath_node_set_raw& ns, xml_attribute_struct* a, xml_node_struct* parent, xpath_allocator* alloc) - { - assert(a); - - const char_t* name = a->name ? a->name + 0 : PUGIXML_TEXT(""); - - switch (_test) - { - case nodetest_name: - if (strequal(name, _data.nodetest) && is_xpath_attribute(name)) - { - ns.push_back(xpath_node(xml_attribute(a), xml_node(parent)), alloc); - return true; - } - break; - - case nodetest_type_node: - case nodetest_all: - if (is_xpath_attribute(name)) - { - ns.push_back(xpath_node(xml_attribute(a), xml_node(parent)), alloc); - return true; - } - break; - - case nodetest_all_in_namespace: - if (starts_with(name, _data.nodetest) && is_xpath_attribute(name)) - { - ns.push_back(xpath_node(xml_attribute(a), xml_node(parent)), alloc); - return true; - } - break; - - default: - ; - } - - return false; - } - - bool step_push(xpath_node_set_raw& ns, xml_node_struct* n, xpath_allocator* alloc) - { - assert(n); - - xml_node_type type = PUGI__NODETYPE(n); - - switch (_test) - { - case nodetest_name: - if (type == node_element && n->name && strequal(n->name, _data.nodetest)) - { - ns.push_back(xml_node(n), alloc); - return true; - } - break; - - case nodetest_type_node: - ns.push_back(xml_node(n), alloc); - return true; - - case nodetest_type_comment: - if (type == node_comment) - { - ns.push_back(xml_node(n), alloc); - return true; - } - break; - - case nodetest_type_text: - if (type == node_pcdata || type == node_cdata) - { - ns.push_back(xml_node(n), alloc); - return true; - } - break; - - case nodetest_type_pi: - if (type == node_pi) - { - ns.push_back(xml_node(n), alloc); - return true; - } - break; - - case nodetest_pi: - if (type == node_pi && n->name && strequal(n->name, _data.nodetest)) - { - ns.push_back(xml_node(n), alloc); - return true; - } - break; - - case nodetest_all: - if (type == node_element) - { - ns.push_back(xml_node(n), alloc); - return true; - } - break; - - case nodetest_all_in_namespace: - if (type == node_element && n->name && starts_with(n->name, _data.nodetest)) - { - ns.push_back(xml_node(n), alloc); - return true; - } - break; - - default: - assert(!"Unknown axis"); - } - - return false; - } - - template void step_fill(xpath_node_set_raw& ns, xml_node_struct* n, xpath_allocator* alloc, bool once, T) - { - const axis_t axis = T::axis; - - switch (axis) - { - case axis_attribute: - { - for (xml_attribute_struct* a = n->first_attribute; a; a = a->next_attribute) - if (step_push(ns, a, n, alloc) & once) - return; - - break; - } - - case axis_child: - { - for (xml_node_struct* c = n->first_child; c; c = c->next_sibling) - if (step_push(ns, c, alloc) & once) - return; - - break; - } - - case axis_descendant: - case axis_descendant_or_self: - { - if (axis == axis_descendant_or_self) - if (step_push(ns, n, alloc) & once) - return; - - xml_node_struct* cur = n->first_child; - - while (cur) - { - if (step_push(ns, cur, alloc) & once) - return; - - if (cur->first_child) - cur = cur->first_child; - else - { - while (!cur->next_sibling) - { - cur = cur->parent; - - if (cur == n) return; - } - - cur = cur->next_sibling; - } - } - - break; - } - - case axis_following_sibling: - { - for (xml_node_struct* c = n->next_sibling; c; c = c->next_sibling) - if (step_push(ns, c, alloc) & once) - return; - - break; - } - - case axis_preceding_sibling: - { - for (xml_node_struct* c = n->prev_sibling_c; c->next_sibling; c = c->prev_sibling_c) - if (step_push(ns, c, alloc) & once) - return; - - break; - } - - case axis_following: - { - xml_node_struct* cur = n; - - // exit from this node so that we don't include descendants - while (!cur->next_sibling) - { - cur = cur->parent; - - if (!cur) return; - } - - cur = cur->next_sibling; - - while (cur) - { - if (step_push(ns, cur, alloc) & once) - return; - - if (cur->first_child) - cur = cur->first_child; - else - { - while (!cur->next_sibling) - { - cur = cur->parent; - - if (!cur) return; - } - - cur = cur->next_sibling; - } - } - - break; - } - - case axis_preceding: - { - xml_node_struct* cur = n; - - // exit from this node so that we don't include descendants - while (!cur->prev_sibling_c->next_sibling) - { - cur = cur->parent; - - if (!cur) return; - } - - cur = cur->prev_sibling_c; - - while (cur) - { - if (cur->first_child) - cur = cur->first_child->prev_sibling_c; - else - { - // leaf node, can't be ancestor - if (step_push(ns, cur, alloc) & once) - return; - - while (!cur->prev_sibling_c->next_sibling) - { - cur = cur->parent; - - if (!cur) return; - - if (!node_is_ancestor(cur, n)) - if (step_push(ns, cur, alloc) & once) - return; - } - - cur = cur->prev_sibling_c; - } - } - - break; - } - - case axis_ancestor: - case axis_ancestor_or_self: - { - if (axis == axis_ancestor_or_self) - if (step_push(ns, n, alloc) & once) - return; - - xml_node_struct* cur = n->parent; - - while (cur) - { - if (step_push(ns, cur, alloc) & once) - return; - - cur = cur->parent; - } - - break; - } - - case axis_self: - { - step_push(ns, n, alloc); - - break; - } - - case axis_parent: - { - if (n->parent) - step_push(ns, n->parent, alloc); - - break; - } - - default: - assert(!"Unimplemented axis"); - } - } - - template void step_fill(xpath_node_set_raw& ns, xml_attribute_struct* a, xml_node_struct* p, xpath_allocator* alloc, bool once, T v) - { - const axis_t axis = T::axis; - - switch (axis) - { - case axis_ancestor: - case axis_ancestor_or_self: - { - if (axis == axis_ancestor_or_self && _test == nodetest_type_node) // reject attributes based on principal node type test - if (step_push(ns, a, p, alloc) & once) - return; - - xml_node_struct* cur = p; - - while (cur) - { - if (step_push(ns, cur, alloc) & once) - return; - - cur = cur->parent; - } - - break; - } - - case axis_descendant_or_self: - case axis_self: - { - if (_test == nodetest_type_node) // reject attributes based on principal node type test - step_push(ns, a, p, alloc); - - break; - } - - case axis_following: - { - xml_node_struct* cur = p; - - while (cur) - { - if (cur->first_child) - cur = cur->first_child; - else - { - while (!cur->next_sibling) - { - cur = cur->parent; - - if (!cur) return; - } - - cur = cur->next_sibling; - } - - if (step_push(ns, cur, alloc) & once) - return; - } - - break; - } - - case axis_parent: - { - step_push(ns, p, alloc); - - break; - } - - case axis_preceding: - { - // preceding:: axis does not include attribute nodes and attribute ancestors (they are the same as parent's ancestors), so we can reuse node preceding - step_fill(ns, p, alloc, once, v); - break; - } - - default: - assert(!"Unimplemented axis"); - } - } - - template void step_fill(xpath_node_set_raw& ns, const xpath_node& xn, xpath_allocator* alloc, bool once, T v) - { - const axis_t axis = T::axis; - const bool axis_has_attributes = (axis == axis_ancestor || axis == axis_ancestor_or_self || axis == axis_descendant_or_self || axis == axis_following || axis == axis_parent || axis == axis_preceding || axis == axis_self); - - if (xn.node()) - step_fill(ns, xn.node().internal_object(), alloc, once, v); - else if (axis_has_attributes && xn.attribute() && xn.parent()) - step_fill(ns, xn.attribute().internal_object(), xn.parent().internal_object(), alloc, once, v); - } - - template xpath_node_set_raw step_do(const xpath_context& c, const xpath_stack& stack, nodeset_eval_t eval, T v) - { - const axis_t axis = T::axis; - const bool axis_reverse = (axis == axis_ancestor || axis == axis_ancestor_or_self || axis == axis_preceding || axis == axis_preceding_sibling); - const xpath_node_set::type_t axis_type = axis_reverse ? xpath_node_set::type_sorted_reverse : xpath_node_set::type_sorted; - - bool once = - (axis == axis_attribute && _test == nodetest_name) || - (!_right && eval_once(axis_type, eval)) || - (_right && !_right->_next && _right->_test == predicate_constant_one); - - xpath_node_set_raw ns; - ns.set_type(axis_type); - - if (_left) - { - xpath_node_set_raw s = _left->eval_node_set(c, stack, nodeset_eval_all); - - // self axis preserves the original order - if (axis == axis_self) ns.set_type(s.type()); - - for (const xpath_node* it = s.begin(); it != s.end(); ++it) - { - size_t size = ns.size(); - - // in general, all axes generate elements in a particular order, but there is no order guarantee if axis is applied to two nodes - if (axis != axis_self && size != 0) ns.set_type(xpath_node_set::type_unsorted); - - step_fill(ns, *it, stack.result, once, v); - if (_right) apply_predicates(ns, size, stack, eval); - } - } - else - { - step_fill(ns, c.n, stack.result, once, v); - if (_right) apply_predicates(ns, 0, stack, eval); - } - - // child, attribute and self axes always generate unique set of nodes - // for other axis, if the set stayed sorted, it stayed unique because the traversal algorithms do not visit the same node twice - if (axis != axis_child && axis != axis_attribute && axis != axis_self && ns.type() == xpath_node_set::type_unsorted) - ns.remove_duplicates(); - - return ns; - } - - public: - xpath_ast_node(ast_type_t type, xpath_value_type rettype_, const char_t* value): - _type(static_cast(type)), _rettype(static_cast(rettype_)), _axis(0), _test(0), _left(0), _right(0), _next(0) - { - assert(type == ast_string_constant); - _data.string = value; - } - - xpath_ast_node(ast_type_t type, xpath_value_type rettype_, double value): - _type(static_cast(type)), _rettype(static_cast(rettype_)), _axis(0), _test(0), _left(0), _right(0), _next(0) - { - assert(type == ast_number_constant); - _data.number = value; - } - - xpath_ast_node(ast_type_t type, xpath_value_type rettype_, xpath_variable* value): - _type(static_cast(type)), _rettype(static_cast(rettype_)), _axis(0), _test(0), _left(0), _right(0), _next(0) - { - assert(type == ast_variable); - _data.variable = value; - } - - xpath_ast_node(ast_type_t type, xpath_value_type rettype_, xpath_ast_node* left = 0, xpath_ast_node* right = 0): - _type(static_cast(type)), _rettype(static_cast(rettype_)), _axis(0), _test(0), _left(left), _right(right), _next(0) - { - } - - xpath_ast_node(ast_type_t type, xpath_ast_node* left, axis_t axis, nodetest_t test, const char_t* contents): - _type(static_cast(type)), _rettype(xpath_type_node_set), _axis(static_cast(axis)), _test(static_cast(test)), _left(left), _right(0), _next(0) - { - assert(type == ast_step); - _data.nodetest = contents; - } - - xpath_ast_node(ast_type_t type, xpath_ast_node* left, xpath_ast_node* right, predicate_t test): - _type(static_cast(type)), _rettype(xpath_type_node_set), _axis(0), _test(static_cast(test)), _left(left), _right(right), _next(0) - { - assert(type == ast_filter || type == ast_predicate); - } - - void set_next(xpath_ast_node* value) - { - _next = value; - } - - void set_right(xpath_ast_node* value) - { - _right = value; - } - - bool eval_boolean(const xpath_context& c, const xpath_stack& stack) - { - switch (_type) - { - case ast_op_or: - return _left->eval_boolean(c, stack) || _right->eval_boolean(c, stack); - - case ast_op_and: - return _left->eval_boolean(c, stack) && _right->eval_boolean(c, stack); - - case ast_op_equal: - return compare_eq(_left, _right, c, stack, equal_to()); - - case ast_op_not_equal: - return compare_eq(_left, _right, c, stack, not_equal_to()); - - case ast_op_less: - return compare_rel(_left, _right, c, stack, less()); - - case ast_op_greater: - return compare_rel(_right, _left, c, stack, less()); - - case ast_op_less_or_equal: - return compare_rel(_left, _right, c, stack, less_equal()); - - case ast_op_greater_or_equal: - return compare_rel(_right, _left, c, stack, less_equal()); - - case ast_func_starts_with: - { - xpath_allocator_capture cr(stack.result); - - xpath_string lr = _left->eval_string(c, stack); - xpath_string rr = _right->eval_string(c, stack); - - return starts_with(lr.c_str(), rr.c_str()); - } - - case ast_func_contains: - { - xpath_allocator_capture cr(stack.result); - - xpath_string lr = _left->eval_string(c, stack); - xpath_string rr = _right->eval_string(c, stack); - - return find_substring(lr.c_str(), rr.c_str()) != 0; - } - - case ast_func_boolean: - return _left->eval_boolean(c, stack); - - case ast_func_not: - return !_left->eval_boolean(c, stack); - - case ast_func_true: - return true; - - case ast_func_false: - return false; - - case ast_func_lang: - { - if (c.n.attribute()) return false; - - xpath_allocator_capture cr(stack.result); - - xpath_string lang = _left->eval_string(c, stack); - - for (xml_node n = c.n.node(); n; n = n.parent()) - { - xml_attribute a = n.attribute(PUGIXML_TEXT("xml:lang")); - - if (a) - { - const char_t* value = a.value(); - - // strnicmp / strncasecmp is not portable - for (const char_t* lit = lang.c_str(); *lit; ++lit) - { - if (tolower_ascii(*lit) != tolower_ascii(*value)) return false; - ++value; - } - - return *value == 0 || *value == '-'; - } - } - - return false; - } - - case ast_opt_compare_attribute: - { - const char_t* value = (_right->_type == ast_string_constant) ? _right->_data.string : _right->_data.variable->get_string(); - - xml_attribute attr = c.n.node().attribute(_left->_data.nodetest); - - return attr && strequal(attr.value(), value) && is_xpath_attribute(attr.name()); - } - - case ast_variable: - { - assert(_rettype == _data.variable->type()); - - if (_rettype == xpath_type_boolean) - return _data.variable->get_boolean(); - - // fallthrough to type conversion - } - - default: - { - switch (_rettype) - { - case xpath_type_number: - return convert_number_to_boolean(eval_number(c, stack)); - - case xpath_type_string: - { - xpath_allocator_capture cr(stack.result); - - return !eval_string(c, stack).empty(); - } - - case xpath_type_node_set: - { - xpath_allocator_capture cr(stack.result); - - return !eval_node_set(c, stack, nodeset_eval_any).empty(); - } - - default: - assert(!"Wrong expression for return type boolean"); - return false; - } - } - } - } - - double eval_number(const xpath_context& c, const xpath_stack& stack) - { - switch (_type) - { - case ast_op_add: - return _left->eval_number(c, stack) + _right->eval_number(c, stack); - - case ast_op_subtract: - return _left->eval_number(c, stack) - _right->eval_number(c, stack); - - case ast_op_multiply: - return _left->eval_number(c, stack) * _right->eval_number(c, stack); - - case ast_op_divide: - return _left->eval_number(c, stack) / _right->eval_number(c, stack); - - case ast_op_mod: - return fmod(_left->eval_number(c, stack), _right->eval_number(c, stack)); - - case ast_op_negate: - return -_left->eval_number(c, stack); - - case ast_number_constant: - return _data.number; - - case ast_func_last: - return static_cast(c.size); - - case ast_func_position: - return static_cast(c.position); - - case ast_func_count: - { - xpath_allocator_capture cr(stack.result); - - return static_cast(_left->eval_node_set(c, stack, nodeset_eval_all).size()); - } - - case ast_func_string_length_0: - { - xpath_allocator_capture cr(stack.result); - - return static_cast(string_value(c.n, stack.result).length()); - } - - case ast_func_string_length_1: - { - xpath_allocator_capture cr(stack.result); - - return static_cast(_left->eval_string(c, stack).length()); - } - - case ast_func_number_0: - { - xpath_allocator_capture cr(stack.result); - - return convert_string_to_number(string_value(c.n, stack.result).c_str()); - } - - case ast_func_number_1: - return _left->eval_number(c, stack); - - case ast_func_sum: - { - xpath_allocator_capture cr(stack.result); - - double r = 0; - - xpath_node_set_raw ns = _left->eval_node_set(c, stack, nodeset_eval_all); - - for (const xpath_node* it = ns.begin(); it != ns.end(); ++it) - { - xpath_allocator_capture cri(stack.result); - - r += convert_string_to_number(string_value(*it, stack.result).c_str()); - } - - return r; - } - - case ast_func_floor: - { - double r = _left->eval_number(c, stack); - - return r == r ? floor(r) : r; - } - - case ast_func_ceiling: - { - double r = _left->eval_number(c, stack); - - return r == r ? ceil(r) : r; - } - - case ast_func_round: - return round_nearest_nzero(_left->eval_number(c, stack)); - - case ast_variable: - { - assert(_rettype == _data.variable->type()); - - if (_rettype == xpath_type_number) - return _data.variable->get_number(); - - // fallthrough to type conversion - } - - default: - { - switch (_rettype) - { - case xpath_type_boolean: - return eval_boolean(c, stack) ? 1 : 0; - - case xpath_type_string: - { - xpath_allocator_capture cr(stack.result); - - return convert_string_to_number(eval_string(c, stack).c_str()); - } - - case xpath_type_node_set: - { - xpath_allocator_capture cr(stack.result); - - return convert_string_to_number(eval_string(c, stack).c_str()); - } - - default: - assert(!"Wrong expression for return type number"); - return 0; - } - - } - } - } - - xpath_string eval_string_concat(const xpath_context& c, const xpath_stack& stack) - { - assert(_type == ast_func_concat); - - xpath_allocator_capture ct(stack.temp); - - // count the string number - size_t count = 1; - for (xpath_ast_node* nc = _right; nc; nc = nc->_next) count++; - - // gather all strings - xpath_string static_buffer[4]; - xpath_string* buffer = static_buffer; - - // allocate on-heap for large concats - if (count > sizeof(static_buffer) / sizeof(static_buffer[0])) - { - buffer = static_cast(stack.temp->allocate(count * sizeof(xpath_string))); - assert(buffer); - } - - // evaluate all strings to temporary stack - xpath_stack swapped_stack = {stack.temp, stack.result}; - - buffer[0] = _left->eval_string(c, swapped_stack); - - size_t pos = 1; - for (xpath_ast_node* n = _right; n; n = n->_next, ++pos) buffer[pos] = n->eval_string(c, swapped_stack); - assert(pos == count); - - // get total length - size_t length = 0; - for (size_t i = 0; i < count; ++i) length += buffer[i].length(); - - // create final string - char_t* result = static_cast(stack.result->allocate((length + 1) * sizeof(char_t))); - assert(result); - - char_t* ri = result; - - for (size_t j = 0; j < count; ++j) - for (const char_t* bi = buffer[j].c_str(); *bi; ++bi) - *ri++ = *bi; - - *ri = 0; - - return xpath_string::from_heap_preallocated(result, ri); - } - - xpath_string eval_string(const xpath_context& c, const xpath_stack& stack) - { - switch (_type) - { - case ast_string_constant: - return xpath_string::from_const(_data.string); - - case ast_func_local_name_0: - { - xpath_node na = c.n; - - return xpath_string::from_const(local_name(na)); - } - - case ast_func_local_name_1: - { - xpath_allocator_capture cr(stack.result); - - xpath_node_set_raw ns = _left->eval_node_set(c, stack, nodeset_eval_first); - xpath_node na = ns.first(); - - return xpath_string::from_const(local_name(na)); - } - - case ast_func_name_0: - { - xpath_node na = c.n; - - return xpath_string::from_const(qualified_name(na)); - } - - case ast_func_name_1: - { - xpath_allocator_capture cr(stack.result); - - xpath_node_set_raw ns = _left->eval_node_set(c, stack, nodeset_eval_first); - xpath_node na = ns.first(); - - return xpath_string::from_const(qualified_name(na)); - } - - case ast_func_namespace_uri_0: - { - xpath_node na = c.n; - - return xpath_string::from_const(namespace_uri(na)); - } - - case ast_func_namespace_uri_1: - { - xpath_allocator_capture cr(stack.result); - - xpath_node_set_raw ns = _left->eval_node_set(c, stack, nodeset_eval_first); - xpath_node na = ns.first(); - - return xpath_string::from_const(namespace_uri(na)); - } - - case ast_func_string_0: - return string_value(c.n, stack.result); - - case ast_func_string_1: - return _left->eval_string(c, stack); - - case ast_func_concat: - return eval_string_concat(c, stack); - - case ast_func_substring_before: - { - xpath_allocator_capture cr(stack.temp); - - xpath_stack swapped_stack = {stack.temp, stack.result}; - - xpath_string s = _left->eval_string(c, swapped_stack); - xpath_string p = _right->eval_string(c, swapped_stack); - - const char_t* pos = find_substring(s.c_str(), p.c_str()); - - return pos ? xpath_string::from_heap(s.c_str(), pos, stack.result) : xpath_string(); - } - - case ast_func_substring_after: - { - xpath_allocator_capture cr(stack.temp); - - xpath_stack swapped_stack = {stack.temp, stack.result}; - - xpath_string s = _left->eval_string(c, swapped_stack); - xpath_string p = _right->eval_string(c, swapped_stack); - - const char_t* pos = find_substring(s.c_str(), p.c_str()); - if (!pos) return xpath_string(); - - const char_t* rbegin = pos + p.length(); - const char_t* rend = s.c_str() + s.length(); - - return s.uses_heap() ? xpath_string::from_heap(rbegin, rend, stack.result) : xpath_string::from_const(rbegin); - } - - case ast_func_substring_2: - { - xpath_allocator_capture cr(stack.temp); - - xpath_stack swapped_stack = {stack.temp, stack.result}; - - xpath_string s = _left->eval_string(c, swapped_stack); - size_t s_length = s.length(); - - double first = round_nearest(_right->eval_number(c, stack)); - - if (is_nan(first)) return xpath_string(); // NaN - else if (first >= s_length + 1) return xpath_string(); - - size_t pos = first < 1 ? 1 : static_cast(first); - assert(1 <= pos && pos <= s_length + 1); - - const char_t* rbegin = s.c_str() + (pos - 1); - const char_t* rend = s.c_str() + s.length(); - - return s.uses_heap() ? xpath_string::from_heap(rbegin, rend, stack.result) : xpath_string::from_const(rbegin); - } - - case ast_func_substring_3: - { - xpath_allocator_capture cr(stack.temp); - - xpath_stack swapped_stack = {stack.temp, stack.result}; - - xpath_string s = _left->eval_string(c, swapped_stack); - size_t s_length = s.length(); - - double first = round_nearest(_right->eval_number(c, stack)); - double last = first + round_nearest(_right->_next->eval_number(c, stack)); - - if (is_nan(first) || is_nan(last)) return xpath_string(); - else if (first >= s_length + 1) return xpath_string(); - else if (first >= last) return xpath_string(); - else if (last < 1) return xpath_string(); - - size_t pos = first < 1 ? 1 : static_cast(first); - size_t end = last >= s_length + 1 ? s_length + 1 : static_cast(last); - - assert(1 <= pos && pos <= end && end <= s_length + 1); - const char_t* rbegin = s.c_str() + (pos - 1); - const char_t* rend = s.c_str() + (end - 1); - - return (end == s_length + 1 && !s.uses_heap()) ? xpath_string::from_const(rbegin) : xpath_string::from_heap(rbegin, rend, stack.result); - } - - case ast_func_normalize_space_0: - { - xpath_string s = string_value(c.n, stack.result); - - char_t* begin = s.data(stack.result); - char_t* end = normalize_space(begin); - - return xpath_string::from_heap_preallocated(begin, end); - } - - case ast_func_normalize_space_1: - { - xpath_string s = _left->eval_string(c, stack); - - char_t* begin = s.data(stack.result); - char_t* end = normalize_space(begin); - - return xpath_string::from_heap_preallocated(begin, end); - } - - case ast_func_translate: - { - xpath_allocator_capture cr(stack.temp); - - xpath_stack swapped_stack = {stack.temp, stack.result}; - - xpath_string s = _left->eval_string(c, stack); - xpath_string from = _right->eval_string(c, swapped_stack); - xpath_string to = _right->_next->eval_string(c, swapped_stack); - - char_t* begin = s.data(stack.result); - char_t* end = translate(begin, from.c_str(), to.c_str(), to.length()); - - return xpath_string::from_heap_preallocated(begin, end); - } - - case ast_opt_translate_table: - { - xpath_string s = _left->eval_string(c, stack); - - char_t* begin = s.data(stack.result); - char_t* end = translate_table(begin, _data.table); - - return xpath_string::from_heap_preallocated(begin, end); - } - - case ast_variable: - { - assert(_rettype == _data.variable->type()); - - if (_rettype == xpath_type_string) - return xpath_string::from_const(_data.variable->get_string()); - - // fallthrough to type conversion - } - - default: - { - switch (_rettype) - { - case xpath_type_boolean: - return xpath_string::from_const(eval_boolean(c, stack) ? PUGIXML_TEXT("true") : PUGIXML_TEXT("false")); - - case xpath_type_number: - return convert_number_to_string(eval_number(c, stack), stack.result); - - case xpath_type_node_set: - { - xpath_allocator_capture cr(stack.temp); - - xpath_stack swapped_stack = {stack.temp, stack.result}; - - xpath_node_set_raw ns = eval_node_set(c, swapped_stack, nodeset_eval_first); - return ns.empty() ? xpath_string() : string_value(ns.first(), stack.result); - } - - default: - assert(!"Wrong expression for return type string"); - return xpath_string(); - } - } - } - } - - xpath_node_set_raw eval_node_set(const xpath_context& c, const xpath_stack& stack, nodeset_eval_t eval) - { - switch (_type) - { - case ast_op_union: - { - xpath_allocator_capture cr(stack.temp); - - xpath_stack swapped_stack = {stack.temp, stack.result}; - - xpath_node_set_raw ls = _left->eval_node_set(c, swapped_stack, eval); - xpath_node_set_raw rs = _right->eval_node_set(c, stack, eval); - - // we can optimize merging two sorted sets, but this is a very rare operation, so don't bother - rs.set_type(xpath_node_set::type_unsorted); - - rs.append(ls.begin(), ls.end(), stack.result); - rs.remove_duplicates(); - - return rs; - } - - case ast_filter: - { - xpath_node_set_raw set = _left->eval_node_set(c, stack, _test == predicate_constant_one ? nodeset_eval_first : nodeset_eval_all); - - // either expression is a number or it contains position() call; sort by document order - if (_test != predicate_posinv) set.sort_do(); - - bool once = eval_once(set.type(), eval); - - apply_predicate(set, 0, stack, once); - - return set; - } - - case ast_func_id: - return xpath_node_set_raw(); - - case ast_step: - { - switch (_axis) - { - case axis_ancestor: - return step_do(c, stack, eval, axis_to_type()); - - case axis_ancestor_or_self: - return step_do(c, stack, eval, axis_to_type()); - - case axis_attribute: - return step_do(c, stack, eval, axis_to_type()); - - case axis_child: - return step_do(c, stack, eval, axis_to_type()); - - case axis_descendant: - return step_do(c, stack, eval, axis_to_type()); - - case axis_descendant_or_self: - return step_do(c, stack, eval, axis_to_type()); - - case axis_following: - return step_do(c, stack, eval, axis_to_type()); - - case axis_following_sibling: - return step_do(c, stack, eval, axis_to_type()); - - case axis_namespace: - // namespaced axis is not supported - return xpath_node_set_raw(); - - case axis_parent: - return step_do(c, stack, eval, axis_to_type()); - - case axis_preceding: - return step_do(c, stack, eval, axis_to_type()); - - case axis_preceding_sibling: - return step_do(c, stack, eval, axis_to_type()); - - case axis_self: - return step_do(c, stack, eval, axis_to_type()); - - default: - assert(!"Unknown axis"); - return xpath_node_set_raw(); - } - } - - case ast_step_root: - { - assert(!_right); // root step can't have any predicates - - xpath_node_set_raw ns; - - ns.set_type(xpath_node_set::type_sorted); - - if (c.n.node()) ns.push_back(c.n.node().root(), stack.result); - else if (c.n.attribute()) ns.push_back(c.n.parent().root(), stack.result); - - return ns; - } - - case ast_variable: - { - assert(_rettype == _data.variable->type()); - - if (_rettype == xpath_type_node_set) - { - const xpath_node_set& s = _data.variable->get_node_set(); - - xpath_node_set_raw ns; - - ns.set_type(s.type()); - ns.append(s.begin(), s.end(), stack.result); - - return ns; - } - - // fallthrough to type conversion - } - - default: - assert(!"Wrong expression for return type node set"); - return xpath_node_set_raw(); - } - } - - void optimize(xpath_allocator* alloc) - { - if (_left) _left->optimize(alloc); - if (_right) _right->optimize(alloc); - if (_next) _next->optimize(alloc); - - optimize_self(alloc); - } - - void optimize_self(xpath_allocator* alloc) - { - // Rewrite [position()=expr] with [expr] - // Note that this step has to go before classification to recognize [position()=1] - if ((_type == ast_filter || _type == ast_predicate) && - _right->_type == ast_op_equal && _right->_left->_type == ast_func_position && _right->_right->_rettype == xpath_type_number) - { - _right = _right->_right; - } - - // Classify filter/predicate ops to perform various optimizations during evaluation - if (_type == ast_filter || _type == ast_predicate) - { - assert(_test == predicate_default); - - if (_right->_type == ast_number_constant && _right->_data.number == 1.0) - _test = predicate_constant_one; - else if (_right->_rettype == xpath_type_number && (_right->_type == ast_number_constant || _right->_type == ast_variable || _right->_type == ast_func_last)) - _test = predicate_constant; - else if (_right->_rettype != xpath_type_number && _right->is_posinv_expr()) - _test = predicate_posinv; - } - - // Rewrite descendant-or-self::node()/child::foo with descendant::foo - // The former is a full form of //foo, the latter is much faster since it executes the node test immediately - // Do a similar kind of rewrite for self/descendant/descendant-or-self axes - // Note that we only rewrite positionally invariant steps (//foo[1] != /descendant::foo[1]) - if (_type == ast_step && (_axis == axis_child || _axis == axis_self || _axis == axis_descendant || _axis == axis_descendant_or_self) && _left && - _left->_type == ast_step && _left->_axis == axis_descendant_or_self && _left->_test == nodetest_type_node && !_left->_right && - is_posinv_step()) - { - if (_axis == axis_child || _axis == axis_descendant) - _axis = axis_descendant; - else - _axis = axis_descendant_or_self; - - _left = _left->_left; - } - - // Use optimized lookup table implementation for translate() with constant arguments - if (_type == ast_func_translate && _right->_type == ast_string_constant && _right->_next->_type == ast_string_constant) - { - unsigned char* table = translate_table_generate(alloc, _right->_data.string, _right->_next->_data.string); - - if (table) - { - _type = ast_opt_translate_table; - _data.table = table; - } - } - - // Use optimized path for @attr = 'value' or @attr = $value - if (_type == ast_op_equal && - _left->_type == ast_step && _left->_axis == axis_attribute && _left->_test == nodetest_name && !_left->_left && !_left->_right && - (_right->_type == ast_string_constant || (_right->_type == ast_variable && _right->_rettype == xpath_type_string))) - { - _type = ast_opt_compare_attribute; - } - } - - bool is_posinv_expr() const - { - switch (_type) - { - case ast_func_position: - case ast_func_last: - return false; - - case ast_string_constant: - case ast_number_constant: - case ast_variable: - return true; - - case ast_step: - case ast_step_root: - return true; - - case ast_predicate: - case ast_filter: - return true; - - default: - if (_left && !_left->is_posinv_expr()) return false; - - for (xpath_ast_node* n = _right; n; n = n->_next) - if (!n->is_posinv_expr()) return false; - - return true; - } - } - - bool is_posinv_step() const - { - assert(_type == ast_step); - - for (xpath_ast_node* n = _right; n; n = n->_next) - { - assert(n->_type == ast_predicate); - - if (n->_test != predicate_posinv) - return false; - } - - return true; - } - - xpath_value_type rettype() const - { - return static_cast(_rettype); - } - }; - - struct xpath_parser - { - xpath_allocator* _alloc; - xpath_lexer _lexer; - - const char_t* _query; - xpath_variable_set* _variables; - - xpath_parse_result* _result; - - char_t _scratch[32]; - - #ifdef PUGIXML_NO_EXCEPTIONS - jmp_buf _error_handler; - #endif - - void throw_error(const char* message) - { - _result->error = message; - _result->offset = _lexer.current_pos() - _query; - - #ifdef PUGIXML_NO_EXCEPTIONS - longjmp(_error_handler, 1); - #else - throw xpath_exception(*_result); - #endif - } - - void throw_error_oom() - { - #ifdef PUGIXML_NO_EXCEPTIONS - throw_error("Out of memory"); - #else - throw std::bad_alloc(); - #endif - } - - void* alloc_node() - { - void* result = _alloc->allocate_nothrow(sizeof(xpath_ast_node)); - - if (!result) throw_error_oom(); - - return result; - } - - const char_t* alloc_string(const xpath_lexer_string& value) - { - if (value.begin) - { - size_t length = static_cast(value.end - value.begin); - - char_t* c = static_cast(_alloc->allocate_nothrow((length + 1) * sizeof(char_t))); - if (!c) throw_error_oom(); - assert(c); // workaround for clang static analysis - - memcpy(c, value.begin, length * sizeof(char_t)); - c[length] = 0; - - return c; - } - else return 0; - } - - xpath_ast_node* parse_function_helper(ast_type_t type0, ast_type_t type1, size_t argc, xpath_ast_node* args[2]) - { - assert(argc <= 1); - - if (argc == 1 && args[0]->rettype() != xpath_type_node_set) throw_error("Function has to be applied to node set"); - - return new (alloc_node()) xpath_ast_node(argc == 0 ? type0 : type1, xpath_type_string, args[0]); - } - - xpath_ast_node* parse_function(const xpath_lexer_string& name, size_t argc, xpath_ast_node* args[2]) - { - switch (name.begin[0]) - { - case 'b': - if (name == PUGIXML_TEXT("boolean") && argc == 1) - return new (alloc_node()) xpath_ast_node(ast_func_boolean, xpath_type_boolean, args[0]); - - break; - - case 'c': - if (name == PUGIXML_TEXT("count") && argc == 1) - { - if (args[0]->rettype() != xpath_type_node_set) throw_error("Function has to be applied to node set"); - return new (alloc_node()) xpath_ast_node(ast_func_count, xpath_type_number, args[0]); - } - else if (name == PUGIXML_TEXT("contains") && argc == 2) - return new (alloc_node()) xpath_ast_node(ast_func_contains, xpath_type_boolean, args[0], args[1]); - else if (name == PUGIXML_TEXT("concat") && argc >= 2) - return new (alloc_node()) xpath_ast_node(ast_func_concat, xpath_type_string, args[0], args[1]); - else if (name == PUGIXML_TEXT("ceiling") && argc == 1) - return new (alloc_node()) xpath_ast_node(ast_func_ceiling, xpath_type_number, args[0]); - - break; - - case 'f': - if (name == PUGIXML_TEXT("false") && argc == 0) - return new (alloc_node()) xpath_ast_node(ast_func_false, xpath_type_boolean); - else if (name == PUGIXML_TEXT("floor") && argc == 1) - return new (alloc_node()) xpath_ast_node(ast_func_floor, xpath_type_number, args[0]); - - break; - - case 'i': - if (name == PUGIXML_TEXT("id") && argc == 1) - return new (alloc_node()) xpath_ast_node(ast_func_id, xpath_type_node_set, args[0]); - - break; - - case 'l': - if (name == PUGIXML_TEXT("last") && argc == 0) - return new (alloc_node()) xpath_ast_node(ast_func_last, xpath_type_number); - else if (name == PUGIXML_TEXT("lang") && argc == 1) - return new (alloc_node()) xpath_ast_node(ast_func_lang, xpath_type_boolean, args[0]); - else if (name == PUGIXML_TEXT("local-name") && argc <= 1) - return parse_function_helper(ast_func_local_name_0, ast_func_local_name_1, argc, args); - - break; - - case 'n': - if (name == PUGIXML_TEXT("name") && argc <= 1) - return parse_function_helper(ast_func_name_0, ast_func_name_1, argc, args); - else if (name == PUGIXML_TEXT("namespace-uri") && argc <= 1) - return parse_function_helper(ast_func_namespace_uri_0, ast_func_namespace_uri_1, argc, args); - else if (name == PUGIXML_TEXT("normalize-space") && argc <= 1) - return new (alloc_node()) xpath_ast_node(argc == 0 ? ast_func_normalize_space_0 : ast_func_normalize_space_1, xpath_type_string, args[0], args[1]); - else if (name == PUGIXML_TEXT("not") && argc == 1) - return new (alloc_node()) xpath_ast_node(ast_func_not, xpath_type_boolean, args[0]); - else if (name == PUGIXML_TEXT("number") && argc <= 1) - return new (alloc_node()) xpath_ast_node(argc == 0 ? ast_func_number_0 : ast_func_number_1, xpath_type_number, args[0]); - - break; - - case 'p': - if (name == PUGIXML_TEXT("position") && argc == 0) - return new (alloc_node()) xpath_ast_node(ast_func_position, xpath_type_number); - - break; - - case 'r': - if (name == PUGIXML_TEXT("round") && argc == 1) - return new (alloc_node()) xpath_ast_node(ast_func_round, xpath_type_number, args[0]); - - break; - - case 's': - if (name == PUGIXML_TEXT("string") && argc <= 1) - return new (alloc_node()) xpath_ast_node(argc == 0 ? ast_func_string_0 : ast_func_string_1, xpath_type_string, args[0]); - else if (name == PUGIXML_TEXT("string-length") && argc <= 1) - return new (alloc_node()) xpath_ast_node(argc == 0 ? ast_func_string_length_0 : ast_func_string_length_1, xpath_type_number, args[0]); - else if (name == PUGIXML_TEXT("starts-with") && argc == 2) - return new (alloc_node()) xpath_ast_node(ast_func_starts_with, xpath_type_boolean, args[0], args[1]); - else if (name == PUGIXML_TEXT("substring-before") && argc == 2) - return new (alloc_node()) xpath_ast_node(ast_func_substring_before, xpath_type_string, args[0], args[1]); - else if (name == PUGIXML_TEXT("substring-after") && argc == 2) - return new (alloc_node()) xpath_ast_node(ast_func_substring_after, xpath_type_string, args[0], args[1]); - else if (name == PUGIXML_TEXT("substring") && (argc == 2 || argc == 3)) - return new (alloc_node()) xpath_ast_node(argc == 2 ? ast_func_substring_2 : ast_func_substring_3, xpath_type_string, args[0], args[1]); - else if (name == PUGIXML_TEXT("sum") && argc == 1) - { - if (args[0]->rettype() != xpath_type_node_set) throw_error("Function has to be applied to node set"); - return new (alloc_node()) xpath_ast_node(ast_func_sum, xpath_type_number, args[0]); - } - - break; - - case 't': - if (name == PUGIXML_TEXT("translate") && argc == 3) - return new (alloc_node()) xpath_ast_node(ast_func_translate, xpath_type_string, args[0], args[1]); - else if (name == PUGIXML_TEXT("true") && argc == 0) - return new (alloc_node()) xpath_ast_node(ast_func_true, xpath_type_boolean); - - break; - - default: - break; - } - - throw_error("Unrecognized function or wrong parameter count"); - - return 0; - } - - axis_t parse_axis_name(const xpath_lexer_string& name, bool& specified) - { - specified = true; - - switch (name.begin[0]) - { - case 'a': - if (name == PUGIXML_TEXT("ancestor")) - return axis_ancestor; - else if (name == PUGIXML_TEXT("ancestor-or-self")) - return axis_ancestor_or_self; - else if (name == PUGIXML_TEXT("attribute")) - return axis_attribute; - - break; - - case 'c': - if (name == PUGIXML_TEXT("child")) - return axis_child; - - break; - - case 'd': - if (name == PUGIXML_TEXT("descendant")) - return axis_descendant; - else if (name == PUGIXML_TEXT("descendant-or-self")) - return axis_descendant_or_self; - - break; - - case 'f': - if (name == PUGIXML_TEXT("following")) - return axis_following; - else if (name == PUGIXML_TEXT("following-sibling")) - return axis_following_sibling; - - break; - - case 'n': - if (name == PUGIXML_TEXT("namespace")) - return axis_namespace; - - break; - - case 'p': - if (name == PUGIXML_TEXT("parent")) - return axis_parent; - else if (name == PUGIXML_TEXT("preceding")) - return axis_preceding; - else if (name == PUGIXML_TEXT("preceding-sibling")) - return axis_preceding_sibling; - - break; - - case 's': - if (name == PUGIXML_TEXT("self")) - return axis_self; - - break; - - default: - break; - } - - specified = false; - return axis_child; - } - - nodetest_t parse_node_test_type(const xpath_lexer_string& name) - { - switch (name.begin[0]) - { - case 'c': - if (name == PUGIXML_TEXT("comment")) - return nodetest_type_comment; - - break; - - case 'n': - if (name == PUGIXML_TEXT("node")) - return nodetest_type_node; - - break; - - case 'p': - if (name == PUGIXML_TEXT("processing-instruction")) - return nodetest_type_pi; - - break; - - case 't': - if (name == PUGIXML_TEXT("text")) - return nodetest_type_text; - - break; - - default: - break; - } - - return nodetest_none; - } - - // PrimaryExpr ::= VariableReference | '(' Expr ')' | Literal | Number | FunctionCall - xpath_ast_node* parse_primary_expression() - { - switch (_lexer.current()) - { - case lex_var_ref: - { - xpath_lexer_string name = _lexer.contents(); - - if (!_variables) - throw_error("Unknown variable: variable set is not provided"); - - xpath_variable* var = 0; - if (!get_variable_scratch(_scratch, _variables, name.begin, name.end, &var)) - throw_error_oom(); - - if (!var) - throw_error("Unknown variable: variable set does not contain the given name"); - - _lexer.next(); - - return new (alloc_node()) xpath_ast_node(ast_variable, var->type(), var); - } - - case lex_open_brace: - { - _lexer.next(); - - xpath_ast_node* n = parse_expression(); - - if (_lexer.current() != lex_close_brace) - throw_error("Unmatched braces"); - - _lexer.next(); - - return n; - } - - case lex_quoted_string: - { - const char_t* value = alloc_string(_lexer.contents()); - - xpath_ast_node* n = new (alloc_node()) xpath_ast_node(ast_string_constant, xpath_type_string, value); - _lexer.next(); - - return n; - } - - case lex_number: - { - double value = 0; - - if (!convert_string_to_number_scratch(_scratch, _lexer.contents().begin, _lexer.contents().end, &value)) - throw_error_oom(); - - xpath_ast_node* n = new (alloc_node()) xpath_ast_node(ast_number_constant, xpath_type_number, value); - _lexer.next(); - - return n; - } - - case lex_string: - { - xpath_ast_node* args[2] = {0}; - size_t argc = 0; - - xpath_lexer_string function = _lexer.contents(); - _lexer.next(); - - xpath_ast_node* last_arg = 0; - - if (_lexer.current() != lex_open_brace) - throw_error("Unrecognized function call"); - _lexer.next(); - - if (_lexer.current() != lex_close_brace) - args[argc++] = parse_expression(); - - while (_lexer.current() != lex_close_brace) - { - if (_lexer.current() != lex_comma) - throw_error("No comma between function arguments"); - _lexer.next(); - - xpath_ast_node* n = parse_expression(); - - if (argc < 2) args[argc] = n; - else last_arg->set_next(n); - - argc++; - last_arg = n; - } - - _lexer.next(); - - return parse_function(function, argc, args); - } - - default: - throw_error("Unrecognizable primary expression"); - - return 0; - } - } - - // FilterExpr ::= PrimaryExpr | FilterExpr Predicate - // Predicate ::= '[' PredicateExpr ']' - // PredicateExpr ::= Expr - xpath_ast_node* parse_filter_expression() - { - xpath_ast_node* n = parse_primary_expression(); - - while (_lexer.current() == lex_open_square_brace) - { - _lexer.next(); - - xpath_ast_node* expr = parse_expression(); - - if (n->rettype() != xpath_type_node_set) throw_error("Predicate has to be applied to node set"); - - n = new (alloc_node()) xpath_ast_node(ast_filter, n, expr, predicate_default); - - if (_lexer.current() != lex_close_square_brace) - throw_error("Unmatched square brace"); - - _lexer.next(); - } - - return n; - } - - // Step ::= AxisSpecifier NodeTest Predicate* | AbbreviatedStep - // AxisSpecifier ::= AxisName '::' | '@'? - // NodeTest ::= NameTest | NodeType '(' ')' | 'processing-instruction' '(' Literal ')' - // NameTest ::= '*' | NCName ':' '*' | QName - // AbbreviatedStep ::= '.' | '..' - xpath_ast_node* parse_step(xpath_ast_node* set) - { - if (set && set->rettype() != xpath_type_node_set) - throw_error("Step has to be applied to node set"); - - bool axis_specified = false; - axis_t axis = axis_child; // implied child axis - - if (_lexer.current() == lex_axis_attribute) - { - axis = axis_attribute; - axis_specified = true; - - _lexer.next(); - } - else if (_lexer.current() == lex_dot) - { - _lexer.next(); - - return new (alloc_node()) xpath_ast_node(ast_step, set, axis_self, nodetest_type_node, 0); - } - else if (_lexer.current() == lex_double_dot) - { - _lexer.next(); - - return new (alloc_node()) xpath_ast_node(ast_step, set, axis_parent, nodetest_type_node, 0); - } - - nodetest_t nt_type = nodetest_none; - xpath_lexer_string nt_name; - - if (_lexer.current() == lex_string) - { - // node name test - nt_name = _lexer.contents(); - _lexer.next(); - - // was it an axis name? - if (_lexer.current() == lex_double_colon) - { - // parse axis name - if (axis_specified) throw_error("Two axis specifiers in one step"); - - axis = parse_axis_name(nt_name, axis_specified); - - if (!axis_specified) throw_error("Unknown axis"); - - // read actual node test - _lexer.next(); - - if (_lexer.current() == lex_multiply) - { - nt_type = nodetest_all; - nt_name = xpath_lexer_string(); - _lexer.next(); - } - else if (_lexer.current() == lex_string) - { - nt_name = _lexer.contents(); - _lexer.next(); - } - else throw_error("Unrecognized node test"); - } - - if (nt_type == nodetest_none) - { - // node type test or processing-instruction - if (_lexer.current() == lex_open_brace) - { - _lexer.next(); - - if (_lexer.current() == lex_close_brace) - { - _lexer.next(); - - nt_type = parse_node_test_type(nt_name); - - if (nt_type == nodetest_none) throw_error("Unrecognized node type"); - - nt_name = xpath_lexer_string(); - } - else if (nt_name == PUGIXML_TEXT("processing-instruction")) - { - if (_lexer.current() != lex_quoted_string) - throw_error("Only literals are allowed as arguments to processing-instruction()"); - - nt_type = nodetest_pi; - nt_name = _lexer.contents(); - _lexer.next(); - - if (_lexer.current() != lex_close_brace) - throw_error("Unmatched brace near processing-instruction()"); - _lexer.next(); - } - else - throw_error("Unmatched brace near node type test"); - - } - // QName or NCName:* - else - { - if (nt_name.end - nt_name.begin > 2 && nt_name.end[-2] == ':' && nt_name.end[-1] == '*') // NCName:* - { - nt_name.end--; // erase * - - nt_type = nodetest_all_in_namespace; - } - else nt_type = nodetest_name; - } - } - } - else if (_lexer.current() == lex_multiply) - { - nt_type = nodetest_all; - _lexer.next(); - } - else throw_error("Unrecognized node test"); - - xpath_ast_node* n = new (alloc_node()) xpath_ast_node(ast_step, set, axis, nt_type, alloc_string(nt_name)); - - xpath_ast_node* last = 0; - - while (_lexer.current() == lex_open_square_brace) - { - _lexer.next(); - - xpath_ast_node* expr = parse_expression(); - - xpath_ast_node* pred = new (alloc_node()) xpath_ast_node(ast_predicate, 0, expr, predicate_default); - - if (_lexer.current() != lex_close_square_brace) - throw_error("Unmatched square brace"); - _lexer.next(); - - if (last) last->set_next(pred); - else n->set_right(pred); - - last = pred; - } - - return n; - } - - // RelativeLocationPath ::= Step | RelativeLocationPath '/' Step | RelativeLocationPath '//' Step - xpath_ast_node* parse_relative_location_path(xpath_ast_node* set) - { - xpath_ast_node* n = parse_step(set); - - while (_lexer.current() == lex_slash || _lexer.current() == lex_double_slash) - { - lexeme_t l = _lexer.current(); - _lexer.next(); - - if (l == lex_double_slash) - n = new (alloc_node()) xpath_ast_node(ast_step, n, axis_descendant_or_self, nodetest_type_node, 0); - - n = parse_step(n); - } - - return n; - } - - // LocationPath ::= RelativeLocationPath | AbsoluteLocationPath - // AbsoluteLocationPath ::= '/' RelativeLocationPath? | '//' RelativeLocationPath - xpath_ast_node* parse_location_path() - { - if (_lexer.current() == lex_slash) - { - _lexer.next(); - - xpath_ast_node* n = new (alloc_node()) xpath_ast_node(ast_step_root, xpath_type_node_set); - - // relative location path can start from axis_attribute, dot, double_dot, multiply and string lexemes; any other lexeme means standalone root path - lexeme_t l = _lexer.current(); - - if (l == lex_string || l == lex_axis_attribute || l == lex_dot || l == lex_double_dot || l == lex_multiply) - return parse_relative_location_path(n); - else - return n; - } - else if (_lexer.current() == lex_double_slash) - { - _lexer.next(); - - xpath_ast_node* n = new (alloc_node()) xpath_ast_node(ast_step_root, xpath_type_node_set); - n = new (alloc_node()) xpath_ast_node(ast_step, n, axis_descendant_or_self, nodetest_type_node, 0); - - return parse_relative_location_path(n); - } - - // else clause moved outside of if because of bogus warning 'control may reach end of non-void function being inlined' in gcc 4.0.1 - return parse_relative_location_path(0); - } - - // PathExpr ::= LocationPath - // | FilterExpr - // | FilterExpr '/' RelativeLocationPath - // | FilterExpr '//' RelativeLocationPath - // UnionExpr ::= PathExpr | UnionExpr '|' PathExpr - // UnaryExpr ::= UnionExpr | '-' UnaryExpr - xpath_ast_node* parse_path_or_unary_expression() - { - // Clarification. - // PathExpr begins with either LocationPath or FilterExpr. - // FilterExpr begins with PrimaryExpr - // PrimaryExpr begins with '$' in case of it being a variable reference, - // '(' in case of it being an expression, string literal, number constant or - // function call. - - if (_lexer.current() == lex_var_ref || _lexer.current() == lex_open_brace || - _lexer.current() == lex_quoted_string || _lexer.current() == lex_number || - _lexer.current() == lex_string) - { - if (_lexer.current() == lex_string) - { - // This is either a function call, or not - if not, we shall proceed with location path - const char_t* state = _lexer.state(); - - while (PUGI__IS_CHARTYPE(*state, ct_space)) ++state; - - if (*state != '(') return parse_location_path(); - - // This looks like a function call; however this still can be a node-test. Check it. - if (parse_node_test_type(_lexer.contents()) != nodetest_none) return parse_location_path(); - } - - xpath_ast_node* n = parse_filter_expression(); - - if (_lexer.current() == lex_slash || _lexer.current() == lex_double_slash) - { - lexeme_t l = _lexer.current(); - _lexer.next(); - - if (l == lex_double_slash) - { - if (n->rettype() != xpath_type_node_set) throw_error("Step has to be applied to node set"); - - n = new (alloc_node()) xpath_ast_node(ast_step, n, axis_descendant_or_self, nodetest_type_node, 0); - } - - // select from location path - return parse_relative_location_path(n); - } - - return n; - } - else if (_lexer.current() == lex_minus) - { - _lexer.next(); - - // precedence 7+ - only parses union expressions - xpath_ast_node* expr = parse_expression_rec(parse_path_or_unary_expression(), 7); - - return new (alloc_node()) xpath_ast_node(ast_op_negate, xpath_type_number, expr); - } - else - return parse_location_path(); - } - - struct binary_op_t - { - ast_type_t asttype; - xpath_value_type rettype; - int precedence; - - binary_op_t(): asttype(ast_unknown), rettype(xpath_type_none), precedence(0) - { - } - - binary_op_t(ast_type_t asttype_, xpath_value_type rettype_, int precedence_): asttype(asttype_), rettype(rettype_), precedence(precedence_) - { - } - - static binary_op_t parse(xpath_lexer& lexer) - { - switch (lexer.current()) - { - case lex_string: - if (lexer.contents() == PUGIXML_TEXT("or")) - return binary_op_t(ast_op_or, xpath_type_boolean, 1); - else if (lexer.contents() == PUGIXML_TEXT("and")) - return binary_op_t(ast_op_and, xpath_type_boolean, 2); - else if (lexer.contents() == PUGIXML_TEXT("div")) - return binary_op_t(ast_op_divide, xpath_type_number, 6); - else if (lexer.contents() == PUGIXML_TEXT("mod")) - return binary_op_t(ast_op_mod, xpath_type_number, 6); - else - return binary_op_t(); - - case lex_equal: - return binary_op_t(ast_op_equal, xpath_type_boolean, 3); - - case lex_not_equal: - return binary_op_t(ast_op_not_equal, xpath_type_boolean, 3); - - case lex_less: - return binary_op_t(ast_op_less, xpath_type_boolean, 4); - - case lex_greater: - return binary_op_t(ast_op_greater, xpath_type_boolean, 4); - - case lex_less_or_equal: - return binary_op_t(ast_op_less_or_equal, xpath_type_boolean, 4); - - case lex_greater_or_equal: - return binary_op_t(ast_op_greater_or_equal, xpath_type_boolean, 4); - - case lex_plus: - return binary_op_t(ast_op_add, xpath_type_number, 5); - - case lex_minus: - return binary_op_t(ast_op_subtract, xpath_type_number, 5); - - case lex_multiply: - return binary_op_t(ast_op_multiply, xpath_type_number, 6); - - case lex_union: - return binary_op_t(ast_op_union, xpath_type_node_set, 7); - - default: - return binary_op_t(); - } - } - }; - - xpath_ast_node* parse_expression_rec(xpath_ast_node* lhs, int limit) - { - binary_op_t op = binary_op_t::parse(_lexer); - - while (op.asttype != ast_unknown && op.precedence >= limit) - { - _lexer.next(); - - xpath_ast_node* rhs = parse_path_or_unary_expression(); - - binary_op_t nextop = binary_op_t::parse(_lexer); - - while (nextop.asttype != ast_unknown && nextop.precedence > op.precedence) - { - rhs = parse_expression_rec(rhs, nextop.precedence); - - nextop = binary_op_t::parse(_lexer); - } - - if (op.asttype == ast_op_union && (lhs->rettype() != xpath_type_node_set || rhs->rettype() != xpath_type_node_set)) - throw_error("Union operator has to be applied to node sets"); - - lhs = new (alloc_node()) xpath_ast_node(op.asttype, op.rettype, lhs, rhs); - - op = binary_op_t::parse(_lexer); - } - - return lhs; - } - - // Expr ::= OrExpr - // OrExpr ::= AndExpr | OrExpr 'or' AndExpr - // AndExpr ::= EqualityExpr | AndExpr 'and' EqualityExpr - // EqualityExpr ::= RelationalExpr - // | EqualityExpr '=' RelationalExpr - // | EqualityExpr '!=' RelationalExpr - // RelationalExpr ::= AdditiveExpr - // | RelationalExpr '<' AdditiveExpr - // | RelationalExpr '>' AdditiveExpr - // | RelationalExpr '<=' AdditiveExpr - // | RelationalExpr '>=' AdditiveExpr - // AdditiveExpr ::= MultiplicativeExpr - // | AdditiveExpr '+' MultiplicativeExpr - // | AdditiveExpr '-' MultiplicativeExpr - // MultiplicativeExpr ::= UnaryExpr - // | MultiplicativeExpr '*' UnaryExpr - // | MultiplicativeExpr 'div' UnaryExpr - // | MultiplicativeExpr 'mod' UnaryExpr - xpath_ast_node* parse_expression() - { - return parse_expression_rec(parse_path_or_unary_expression(), 0); - } - - xpath_parser(const char_t* query, xpath_variable_set* variables, xpath_allocator* alloc, xpath_parse_result* result): _alloc(alloc), _lexer(query), _query(query), _variables(variables), _result(result) - { - } - - xpath_ast_node* parse() - { - xpath_ast_node* result = parse_expression(); - - if (_lexer.current() != lex_eof) - { - // there are still unparsed tokens left, error - throw_error("Incorrect query"); - } - - return result; - } - - static xpath_ast_node* parse(const char_t* query, xpath_variable_set* variables, xpath_allocator* alloc, xpath_parse_result* result) - { - xpath_parser parser(query, variables, alloc, result); - - #ifdef PUGIXML_NO_EXCEPTIONS - int error = setjmp(parser._error_handler); - - return (error == 0) ? parser.parse() : 0; - #else - return parser.parse(); - #endif - } - }; - - struct xpath_query_impl - { - static xpath_query_impl* create() - { - void* memory = xml_memory::allocate(sizeof(xpath_query_impl)); - if (!memory) return 0; - - return new (memory) xpath_query_impl(); - } - - static void destroy(xpath_query_impl* impl) - { - // free all allocated pages - impl->alloc.release(); - - // free allocator memory (with the first page) - xml_memory::deallocate(impl); - } - - xpath_query_impl(): root(0), alloc(&block) - { - block.next = 0; - block.capacity = sizeof(block.data); - } - - xpath_ast_node* root; - xpath_allocator alloc; - xpath_memory_block block; - }; - - PUGI__FN xpath_string evaluate_string_impl(xpath_query_impl* impl, const xpath_node& n, xpath_stack_data& sd) - { - if (!impl) return xpath_string(); - - #ifdef PUGIXML_NO_EXCEPTIONS - if (setjmp(sd.error_handler)) return xpath_string(); - #endif - - xpath_context c(n, 1, 1); - - return impl->root->eval_string(c, sd.stack); - } - - PUGI__FN impl::xpath_ast_node* evaluate_node_set_prepare(xpath_query_impl* impl) - { - if (!impl) return 0; - - if (impl->root->rettype() != xpath_type_node_set) - { - #ifdef PUGIXML_NO_EXCEPTIONS - return 0; - #else - xpath_parse_result res; - res.error = "Expression does not evaluate to node set"; - - throw xpath_exception(res); - #endif - } - - return impl->root; - } -PUGI__NS_END - -namespace pugi -{ -#ifndef PUGIXML_NO_EXCEPTIONS - PUGI__FN xpath_exception::xpath_exception(const xpath_parse_result& result_): _result(result_) - { - assert(_result.error); - } - - PUGI__FN const char* xpath_exception::what() const throw() - { - return _result.error; - } - - PUGI__FN const xpath_parse_result& xpath_exception::result() const - { - return _result; - } -#endif - - PUGI__FN xpath_node::xpath_node() - { - } - - PUGI__FN xpath_node::xpath_node(const xml_node& node_): _node(node_) - { - } - - PUGI__FN xpath_node::xpath_node(const xml_attribute& attribute_, const xml_node& parent_): _node(attribute_ ? parent_ : xml_node()), _attribute(attribute_) - { - } - - PUGI__FN xml_node xpath_node::node() const - { - return _attribute ? xml_node() : _node; - } - - PUGI__FN xml_attribute xpath_node::attribute() const - { - return _attribute; - } - - PUGI__FN xml_node xpath_node::parent() const - { - return _attribute ? _node : _node.parent(); - } - - PUGI__FN static void unspecified_bool_xpath_node(xpath_node***) - { - } - - PUGI__FN xpath_node::operator xpath_node::unspecified_bool_type() const - { - return (_node || _attribute) ? unspecified_bool_xpath_node : 0; - } - - PUGI__FN bool xpath_node::operator!() const - { - return !(_node || _attribute); - } - - PUGI__FN bool xpath_node::operator==(const xpath_node& n) const - { - return _node == n._node && _attribute == n._attribute; - } - - PUGI__FN bool xpath_node::operator!=(const xpath_node& n) const - { - return _node != n._node || _attribute != n._attribute; - } - -#ifdef __BORLANDC__ - PUGI__FN bool operator&&(const xpath_node& lhs, bool rhs) - { - return (bool)lhs && rhs; - } - - PUGI__FN bool operator||(const xpath_node& lhs, bool rhs) - { - return (bool)lhs || rhs; - } -#endif - - PUGI__FN void xpath_node_set::_assign(const_iterator begin_, const_iterator end_, type_t type_) - { - assert(begin_ <= end_); - - size_t size_ = static_cast(end_ - begin_); - - if (size_ <= 1) - { - // deallocate old buffer - if (_begin != &_storage) impl::xml_memory::deallocate(_begin); - - // use internal buffer - if (begin_ != end_) _storage = *begin_; - - _begin = &_storage; - _end = &_storage + size_; - _type = type_; - } - else - { - // make heap copy - xpath_node* storage = static_cast(impl::xml_memory::allocate(size_ * sizeof(xpath_node))); - - if (!storage) - { - #ifdef PUGIXML_NO_EXCEPTIONS - return; - #else - throw std::bad_alloc(); - #endif - } - - memcpy(storage, begin_, size_ * sizeof(xpath_node)); - - // deallocate old buffer - if (_begin != &_storage) impl::xml_memory::deallocate(_begin); - - // finalize - _begin = storage; - _end = storage + size_; - _type = type_; - } - } - -#if __cplusplus >= 201103 - PUGI__FN void xpath_node_set::_move(xpath_node_set& rhs) - { - _type = rhs._type; - _storage = rhs._storage; - _begin = (rhs._begin == &rhs._storage) ? &_storage : rhs._begin; - _end = _begin + (rhs._end - rhs._begin); - - rhs._type = type_unsorted; - rhs._begin = &rhs._storage; - rhs._end = rhs._begin; - } -#endif - - PUGI__FN xpath_node_set::xpath_node_set(): _type(type_unsorted), _begin(&_storage), _end(&_storage) - { - } - - PUGI__FN xpath_node_set::xpath_node_set(const_iterator begin_, const_iterator end_, type_t type_): _type(type_unsorted), _begin(&_storage), _end(&_storage) - { - _assign(begin_, end_, type_); - } - - PUGI__FN xpath_node_set::~xpath_node_set() - { - if (_begin != &_storage) - impl::xml_memory::deallocate(_begin); - } - - PUGI__FN xpath_node_set::xpath_node_set(const xpath_node_set& ns): _type(type_unsorted), _begin(&_storage), _end(&_storage) - { - _assign(ns._begin, ns._end, ns._type); - } - - PUGI__FN xpath_node_set& xpath_node_set::operator=(const xpath_node_set& ns) - { - if (this == &ns) return *this; - - _assign(ns._begin, ns._end, ns._type); - - return *this; - } - -#if __cplusplus >= 201103 - PUGI__FN xpath_node_set::xpath_node_set(xpath_node_set&& rhs): _type(type_unsorted), _begin(&_storage), _end(&_storage) - { - _move(rhs); - } - - PUGI__FN xpath_node_set& xpath_node_set::operator=(xpath_node_set&& rhs) - { - if (this == &rhs) return *this; - - if (_begin != &_storage) - impl::xml_memory::deallocate(_begin); - - _move(rhs); - - return *this; - } -#endif - - PUGI__FN xpath_node_set::type_t xpath_node_set::type() const - { - return _type; - } - - PUGI__FN size_t xpath_node_set::size() const - { - return _end - _begin; - } - - PUGI__FN bool xpath_node_set::empty() const - { - return _begin == _end; - } - - PUGI__FN const xpath_node& xpath_node_set::operator[](size_t index) const - { - assert(index < size()); - return _begin[index]; - } - - PUGI__FN xpath_node_set::const_iterator xpath_node_set::begin() const - { - return _begin; - } - - PUGI__FN xpath_node_set::const_iterator xpath_node_set::end() const - { - return _end; - } - - PUGI__FN void xpath_node_set::sort(bool reverse) - { - _type = impl::xpath_sort(_begin, _end, _type, reverse); - } - - PUGI__FN xpath_node xpath_node_set::first() const - { - return impl::xpath_first(_begin, _end, _type); - } - - PUGI__FN xpath_parse_result::xpath_parse_result(): error("Internal error"), offset(0) - { - } - - PUGI__FN xpath_parse_result::operator bool() const - { - return error == 0; - } - - PUGI__FN const char* xpath_parse_result::description() const - { - return error ? error : "No error"; - } - - PUGI__FN xpath_variable::xpath_variable(xpath_value_type type_): _type(type_), _next(0) - { - } - - PUGI__FN const char_t* xpath_variable::name() const - { - switch (_type) - { - case xpath_type_node_set: - return static_cast(this)->name; - - case xpath_type_number: - return static_cast(this)->name; - - case xpath_type_string: - return static_cast(this)->name; - - case xpath_type_boolean: - return static_cast(this)->name; - - default: - assert(!"Invalid variable type"); - return 0; - } - } - - PUGI__FN xpath_value_type xpath_variable::type() const - { - return _type; - } - - PUGI__FN bool xpath_variable::get_boolean() const - { - return (_type == xpath_type_boolean) ? static_cast(this)->value : false; - } - - PUGI__FN double xpath_variable::get_number() const - { - return (_type == xpath_type_number) ? static_cast(this)->value : impl::gen_nan(); - } - - PUGI__FN const char_t* xpath_variable::get_string() const - { - const char_t* value = (_type == xpath_type_string) ? static_cast(this)->value : 0; - return value ? value : PUGIXML_TEXT(""); - } - - PUGI__FN const xpath_node_set& xpath_variable::get_node_set() const - { - return (_type == xpath_type_node_set) ? static_cast(this)->value : impl::dummy_node_set; - } - - PUGI__FN bool xpath_variable::set(bool value) - { - if (_type != xpath_type_boolean) return false; - - static_cast(this)->value = value; - return true; - } - - PUGI__FN bool xpath_variable::set(double value) - { - if (_type != xpath_type_number) return false; - - static_cast(this)->value = value; - return true; - } - - PUGI__FN bool xpath_variable::set(const char_t* value) - { - if (_type != xpath_type_string) return false; - - impl::xpath_variable_string* var = static_cast(this); - - // duplicate string - size_t size = (impl::strlength(value) + 1) * sizeof(char_t); - - char_t* copy = static_cast(impl::xml_memory::allocate(size)); - if (!copy) return false; - - memcpy(copy, value, size); - - // replace old string - if (var->value) impl::xml_memory::deallocate(var->value); - var->value = copy; - - return true; - } - - PUGI__FN bool xpath_variable::set(const xpath_node_set& value) - { - if (_type != xpath_type_node_set) return false; - - static_cast(this)->value = value; - return true; - } - - PUGI__FN xpath_variable_set::xpath_variable_set() - { - for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i) - _data[i] = 0; - } - - PUGI__FN xpath_variable_set::~xpath_variable_set() - { - for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i) - _destroy(_data[i]); - } - - PUGI__FN xpath_variable_set::xpath_variable_set(const xpath_variable_set& rhs) - { - for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i) - _data[i] = 0; - - _assign(rhs); - } - - PUGI__FN xpath_variable_set& xpath_variable_set::operator=(const xpath_variable_set& rhs) - { - if (this == &rhs) return *this; - - _assign(rhs); - - return *this; - } - -#if __cplusplus >= 201103 - PUGI__FN xpath_variable_set::xpath_variable_set(xpath_variable_set&& rhs) - { - for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i) - { - _data[i] = rhs._data[i]; - rhs._data[i] = 0; - } - } - - PUGI__FN xpath_variable_set& xpath_variable_set::operator=(xpath_variable_set&& rhs) - { - for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i) - { - _destroy(_data[i]); - - _data[i] = rhs._data[i]; - rhs._data[i] = 0; - } - - return *this; - } -#endif - - PUGI__FN void xpath_variable_set::_assign(const xpath_variable_set& rhs) - { - xpath_variable_set temp; - - for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i) - if (rhs._data[i] && !_clone(rhs._data[i], &temp._data[i])) - return; - - _swap(temp); - } - - PUGI__FN void xpath_variable_set::_swap(xpath_variable_set& rhs) - { - for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i) - { - xpath_variable* chain = _data[i]; - - _data[i] = rhs._data[i]; - rhs._data[i] = chain; - } - } - - PUGI__FN xpath_variable* xpath_variable_set::_find(const char_t* name) const - { - const size_t hash_size = sizeof(_data) / sizeof(_data[0]); - size_t hash = impl::hash_string(name) % hash_size; - - // look for existing variable - for (xpath_variable* var = _data[hash]; var; var = var->_next) - if (impl::strequal(var->name(), name)) - return var; - - return 0; - } - - PUGI__FN bool xpath_variable_set::_clone(xpath_variable* var, xpath_variable** out_result) - { - xpath_variable* last = 0; - - while (var) - { - // allocate storage for new variable - xpath_variable* nvar = impl::new_xpath_variable(var->_type, var->name()); - if (!nvar) return false; - - // link the variable to the result immediately to handle failures gracefully - if (last) - last->_next = nvar; - else - *out_result = nvar; - - last = nvar; - - // copy the value; this can fail due to out-of-memory conditions - if (!impl::copy_xpath_variable(nvar, var)) return false; - - var = var->_next; - } - - return true; - } - - PUGI__FN void xpath_variable_set::_destroy(xpath_variable* var) - { - while (var) - { - xpath_variable* next = var->_next; - - impl::delete_xpath_variable(var->_type, var); - - var = next; - } - } - - PUGI__FN xpath_variable* xpath_variable_set::add(const char_t* name, xpath_value_type type) - { - const size_t hash_size = sizeof(_data) / sizeof(_data[0]); - size_t hash = impl::hash_string(name) % hash_size; - - // look for existing variable - for (xpath_variable* var = _data[hash]; var; var = var->_next) - if (impl::strequal(var->name(), name)) - return var->type() == type ? var : 0; - - // add new variable - xpath_variable* result = impl::new_xpath_variable(type, name); - - if (result) - { - result->_next = _data[hash]; - - _data[hash] = result; - } - - return result; - } - - PUGI__FN bool xpath_variable_set::set(const char_t* name, bool value) - { - xpath_variable* var = add(name, xpath_type_boolean); - return var ? var->set(value) : false; - } - - PUGI__FN bool xpath_variable_set::set(const char_t* name, double value) - { - xpath_variable* var = add(name, xpath_type_number); - return var ? var->set(value) : false; - } - - PUGI__FN bool xpath_variable_set::set(const char_t* name, const char_t* value) - { - xpath_variable* var = add(name, xpath_type_string); - return var ? var->set(value) : false; - } - - PUGI__FN bool xpath_variable_set::set(const char_t* name, const xpath_node_set& value) - { - xpath_variable* var = add(name, xpath_type_node_set); - return var ? var->set(value) : false; - } - - PUGI__FN xpath_variable* xpath_variable_set::get(const char_t* name) - { - return _find(name); - } - - PUGI__FN const xpath_variable* xpath_variable_set::get(const char_t* name) const - { - return _find(name); - } - - PUGI__FN xpath_query::xpath_query(const char_t* query, xpath_variable_set* variables): _impl(0) - { - impl::xpath_query_impl* qimpl = impl::xpath_query_impl::create(); - - if (!qimpl) - { - #ifdef PUGIXML_NO_EXCEPTIONS - _result.error = "Out of memory"; - #else - throw std::bad_alloc(); - #endif - } - else - { - using impl::auto_deleter; // MSVC7 workaround - auto_deleter impl(qimpl, impl::xpath_query_impl::destroy); - - qimpl->root = impl::xpath_parser::parse(query, variables, &qimpl->alloc, &_result); - - if (qimpl->root) - { - qimpl->root->optimize(&qimpl->alloc); - - _impl = impl.release(); - _result.error = 0; - } - } - } - - PUGI__FN xpath_query::xpath_query(): _impl(0) - { - } - - PUGI__FN xpath_query::~xpath_query() - { - if (_impl) - impl::xpath_query_impl::destroy(static_cast(_impl)); - } - -#if __cplusplus >= 201103 - PUGI__FN xpath_query::xpath_query(xpath_query&& rhs) - { - _impl = rhs._impl; - _result = rhs._result; - rhs._impl = 0; - rhs._result = xpath_parse_result(); - } - - PUGI__FN xpath_query& xpath_query::operator=(xpath_query&& rhs) - { - if (this == &rhs) return *this; - - if (_impl) - impl::xpath_query_impl::destroy(static_cast(_impl)); - - _impl = rhs._impl; - _result = rhs._result; - rhs._impl = 0; - rhs._result = xpath_parse_result(); - - return *this; - } -#endif - - PUGI__FN xpath_value_type xpath_query::return_type() const - { - if (!_impl) return xpath_type_none; - - return static_cast(_impl)->root->rettype(); - } - - PUGI__FN bool xpath_query::evaluate_boolean(const xpath_node& n) const - { - if (!_impl) return false; - - impl::xpath_context c(n, 1, 1); - impl::xpath_stack_data sd; - - #ifdef PUGIXML_NO_EXCEPTIONS - if (setjmp(sd.error_handler)) return false; - #endif - - return static_cast(_impl)->root->eval_boolean(c, sd.stack); - } - - PUGI__FN double xpath_query::evaluate_number(const xpath_node& n) const - { - if (!_impl) return impl::gen_nan(); - - impl::xpath_context c(n, 1, 1); - impl::xpath_stack_data sd; - - #ifdef PUGIXML_NO_EXCEPTIONS - if (setjmp(sd.error_handler)) return impl::gen_nan(); - #endif - - return static_cast(_impl)->root->eval_number(c, sd.stack); - } - -#ifndef PUGIXML_NO_STL - PUGI__FN string_t xpath_query::evaluate_string(const xpath_node& n) const - { - impl::xpath_stack_data sd; - - impl::xpath_string r = impl::evaluate_string_impl(static_cast(_impl), n, sd); - - return string_t(r.c_str(), r.length()); - } -#endif - - PUGI__FN size_t xpath_query::evaluate_string(char_t* buffer, size_t capacity, const xpath_node& n) const - { - impl::xpath_stack_data sd; - - impl::xpath_string r = impl::evaluate_string_impl(static_cast(_impl), n, sd); - - size_t full_size = r.length() + 1; - - if (capacity > 0) - { - size_t size = (full_size < capacity) ? full_size : capacity; - assert(size > 0); - - memcpy(buffer, r.c_str(), (size - 1) * sizeof(char_t)); - buffer[size - 1] = 0; - } - - return full_size; - } - - PUGI__FN xpath_node_set xpath_query::evaluate_node_set(const xpath_node& n) const - { - impl::xpath_ast_node* root = impl::evaluate_node_set_prepare(static_cast(_impl)); - if (!root) return xpath_node_set(); - - impl::xpath_context c(n, 1, 1); - impl::xpath_stack_data sd; - - #ifdef PUGIXML_NO_EXCEPTIONS - if (setjmp(sd.error_handler)) return xpath_node_set(); - #endif - - impl::xpath_node_set_raw r = root->eval_node_set(c, sd.stack, impl::nodeset_eval_all); - - return xpath_node_set(r.begin(), r.end(), r.type()); - } - - PUGI__FN xpath_node xpath_query::evaluate_node(const xpath_node& n) const - { - impl::xpath_ast_node* root = impl::evaluate_node_set_prepare(static_cast(_impl)); - if (!root) return xpath_node(); - - impl::xpath_context c(n, 1, 1); - impl::xpath_stack_data sd; - - #ifdef PUGIXML_NO_EXCEPTIONS - if (setjmp(sd.error_handler)) return xpath_node(); - #endif - - impl::xpath_node_set_raw r = root->eval_node_set(c, sd.stack, impl::nodeset_eval_first); - - return r.first(); - } - - PUGI__FN const xpath_parse_result& xpath_query::result() const - { - return _result; - } - - PUGI__FN static void unspecified_bool_xpath_query(xpath_query***) - { - } - - PUGI__FN xpath_query::operator xpath_query::unspecified_bool_type() const - { - return _impl ? unspecified_bool_xpath_query : 0; - } - - PUGI__FN bool xpath_query::operator!() const - { - return !_impl; - } - - PUGI__FN xpath_node xml_node::select_node(const char_t* query, xpath_variable_set* variables) const - { - xpath_query q(query, variables); - return select_node(q); - } - - PUGI__FN xpath_node xml_node::select_node(const xpath_query& query) const - { - return query.evaluate_node(*this); - } - - PUGI__FN xpath_node_set xml_node::select_nodes(const char_t* query, xpath_variable_set* variables) const - { - xpath_query q(query, variables); - return select_nodes(q); - } - - PUGI__FN xpath_node_set xml_node::select_nodes(const xpath_query& query) const - { - return query.evaluate_node_set(*this); - } - - PUGI__FN xpath_node xml_node::select_single_node(const char_t* query, xpath_variable_set* variables) const - { - xpath_query q(query, variables); - return select_single_node(q); - } - - PUGI__FN xpath_node xml_node::select_single_node(const xpath_query& query) const - { - return query.evaluate_node(*this); - } -} - -#endif - -#ifdef __BORLANDC__ -# pragma option pop -#endif - -// Intel C++ does not properly keep warning state for function templates, -// so popping warning state at the end of translation unit leads to warnings in the middle. -#if defined(_MSC_VER) && !defined(__INTEL_COMPILER) -# pragma warning(pop) -#endif - -// Undefine all local macros (makes sure we're not leaking macros in header-only mode) -#undef PUGI__NO_INLINE -#undef PUGI__UNLIKELY -#undef PUGI__STATIC_ASSERT -#undef PUGI__DMC_VOLATILE -#undef PUGI__MSVC_CRT_VERSION -#undef PUGI__NS_BEGIN -#undef PUGI__NS_END -#undef PUGI__FN -#undef PUGI__FN_NO_INLINE -#undef PUGI__GETPAGE_IMPL -#undef PUGI__GETPAGE -#undef PUGI__NODETYPE -#undef PUGI__IS_CHARTYPE_IMPL -#undef PUGI__IS_CHARTYPE -#undef PUGI__IS_CHARTYPEX -#undef PUGI__ENDSWITH -#undef PUGI__SKIPWS -#undef PUGI__OPTSET -#undef PUGI__PUSHNODE -#undef PUGI__POPNODE -#undef PUGI__SCANFOR -#undef PUGI__SCANWHILE -#undef PUGI__SCANWHILE_UNROLL -#undef PUGI__ENDSEG -#undef PUGI__THROW_ERROR -#undef PUGI__CHECK_ERROR - -#endif - -/** - * Copyright (c) 2006-2015 Arseny Kapoulkine - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ diff --git a/src/tmx/TmxApi/tmx/pugixml/pugixml.hpp b/src/tmx/TmxApi/tmx/pugixml/pugixml.hpp deleted file mode 100644 index 9f7c3fbcf..000000000 --- a/src/tmx/TmxApi/tmx/pugixml/pugixml.hpp +++ /dev/null @@ -1,1400 +0,0 @@ -/** - * pugixml parser - version 1.7 - * -------------------------------------------------------- - * Copyright (C) 2006-2015, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) - * Report bugs and download new versions at http://pugixml.org/ - * - * This library is distributed under the MIT License. See notice at the end - * of this file. - * - * This work is based on the pugxml parser, which is: - * Copyright (C) 2003, by Kristen Wegner (kristen@tima.net) - */ - -#ifndef PUGIXML_VERSION -// Define version macro; evaluates to major * 100 + minor so that it's safe to use in less-than comparisons -# define PUGIXML_VERSION 170 -#endif - -// Include user configuration file (this can define various configuration macros) -#include "pugiconfig.hpp" - -#ifndef HEADER_PUGIXML_HPP -#define HEADER_PUGIXML_HPP - -// Include stddef.h for size_t and ptrdiff_t -#include - -// Include exception header for XPath -#if !defined(PUGIXML_NO_XPATH) && !defined(PUGIXML_NO_EXCEPTIONS) -# include -#endif - -// Include STL headers -#ifndef PUGIXML_NO_STL -# include -# include -# include -#endif - -// Macro for deprecated features -#ifndef PUGIXML_DEPRECATED -# if defined(__GNUC__) -# define PUGIXML_DEPRECATED __attribute__((deprecated)) -# elif defined(_MSC_VER) && _MSC_VER >= 1300 -# define PUGIXML_DEPRECATED __declspec(deprecated) -# else -# define PUGIXML_DEPRECATED -# endif -#endif - -// If no API is defined, assume default -#ifndef PUGIXML_API -# define PUGIXML_API -#endif - -// If no API for classes is defined, assume default -#ifndef PUGIXML_CLASS -# define PUGIXML_CLASS PUGIXML_API -#endif - -// If no API for functions is defined, assume default -#ifndef PUGIXML_FUNCTION -# define PUGIXML_FUNCTION PUGIXML_API -#endif - -// If the platform is known to have long long support, enable long long functions -#ifndef PUGIXML_HAS_LONG_LONG -# if __cplusplus >= 201103 -# define PUGIXML_HAS_LONG_LONG -# elif defined(_MSC_VER) && _MSC_VER >= 1400 -# define PUGIXML_HAS_LONG_LONG -# endif -#endif - -// Character interface macros -#ifdef PUGIXML_WCHAR_MODE -# define PUGIXML_TEXT(t) L ## t -# define PUGIXML_CHAR wchar_t -#else -# define PUGIXML_TEXT(t) t -# define PUGIXML_CHAR char -#endif - -namespace pugi -{ - // Character type used for all internal storage and operations; depends on PUGIXML_WCHAR_MODE - typedef PUGIXML_CHAR char_t; - -#ifndef PUGIXML_NO_STL - // String type used for operations that work with STL string; depends on PUGIXML_WCHAR_MODE - typedef std::basic_string, std::allocator > string_t; -#endif -} - -// The PugiXML namespace -namespace pugi -{ - // Tree node types - enum xml_node_type - { - node_null, // Empty (null) node handle - node_document, // A document tree's absolute root - node_element, // Element tag, i.e. '' - node_pcdata, // Plain character data, i.e. 'text' - node_cdata, // Character data, i.e. '' - node_comment, // Comment tag, i.e. '' - node_pi, // Processing instruction, i.e. '' - node_declaration, // Document declaration, i.e. '' - node_doctype // Document type declaration, i.e. '' - }; - - // Parsing options - - // Minimal parsing mode (equivalent to turning all other flags off). - // Only elements and PCDATA sections are added to the DOM tree, no text conversions are performed. - const unsigned int parse_minimal = 0x0000; - - // This flag determines if processing instructions (node_pi) are added to the DOM tree. This flag is off by default. - const unsigned int parse_pi = 0x0001; - - // This flag determines if comments (node_comment) are added to the DOM tree. This flag is off by default. - const unsigned int parse_comments = 0x0002; - - // This flag determines if CDATA sections (node_cdata) are added to the DOM tree. This flag is on by default. - const unsigned int parse_cdata = 0x0004; - - // This flag determines if plain character data (node_pcdata) that consist only of whitespace are added to the DOM tree. - // This flag is off by default; turning it on usually results in slower parsing and more memory consumption. - const unsigned int parse_ws_pcdata = 0x0008; - - // This flag determines if character and entity references are expanded during parsing. This flag is on by default. - const unsigned int parse_escapes = 0x0010; - - // This flag determines if EOL characters are normalized (converted to #xA) during parsing. This flag is on by default. - const unsigned int parse_eol = 0x0020; - - // This flag determines if attribute values are normalized using CDATA normalization rules during parsing. This flag is on by default. - const unsigned int parse_wconv_attribute = 0x0040; - - // This flag determines if attribute values are normalized using NMTOKENS normalization rules during parsing. This flag is off by default. - const unsigned int parse_wnorm_attribute = 0x0080; - - // This flag determines if document declaration (node_declaration) is added to the DOM tree. This flag is off by default. - const unsigned int parse_declaration = 0x0100; - - // This flag determines if document type declaration (node_doctype) is added to the DOM tree. This flag is off by default. - const unsigned int parse_doctype = 0x0200; - - // This flag determines if plain character data (node_pcdata) that is the only child of the parent node and that consists only - // of whitespace is added to the DOM tree. - // This flag is off by default; turning it on may result in slower parsing and more memory consumption. - const unsigned int parse_ws_pcdata_single = 0x0400; - - // This flag determines if leading and trailing whitespace is to be removed from plain character data. This flag is off by default. - const unsigned int parse_trim_pcdata = 0x0800; - - // This flag determines if plain character data that does not have a parent node is added to the DOM tree, and if an empty document - // is a valid document. This flag is off by default. - const unsigned int parse_fragment = 0x1000; - - // The default parsing mode. - // Elements, PCDATA and CDATA sections are added to the DOM tree, character/reference entities are expanded, - // End-of-Line characters are normalized, attribute values are normalized using CDATA normalization rules. - const unsigned int parse_default = parse_cdata | parse_escapes | parse_wconv_attribute | parse_eol; - - // The full parsing mode. - // Nodes of all types are added to the DOM tree, character/reference entities are expanded, - // End-of-Line characters are normalized, attribute values are normalized using CDATA normalization rules. - const unsigned int parse_full = parse_default | parse_pi | parse_comments | parse_declaration | parse_doctype; - - // These flags determine the encoding of input data for XML document - enum xml_encoding - { - encoding_auto, // Auto-detect input encoding using BOM or < / class xml_object_range - { - public: - typedef It const_iterator; - typedef It iterator; - - xml_object_range(It b, It e): _begin(b), _end(e) - { - } - - It begin() const { return _begin; } - It end() const { return _end; } - - private: - It _begin, _end; - }; - - // Writer interface for node printing (see xml_node::print) - class PUGIXML_CLASS xml_writer - { - public: - virtual ~xml_writer() {} - - // Write memory chunk into stream/file/whatever - virtual void write(const void* data, size_t size) = 0; - }; - - // xml_writer implementation for FILE* - class PUGIXML_CLASS xml_writer_file: public xml_writer - { - public: - // Construct writer from a FILE* object; void* is used to avoid header dependencies on stdio - xml_writer_file(void* file); - - virtual void write(const void* data, size_t size); - - private: - void* file; - }; - - #ifndef PUGIXML_NO_STL - // xml_writer implementation for streams - class PUGIXML_CLASS xml_writer_stream: public xml_writer - { - public: - // Construct writer from an output stream object - xml_writer_stream(std::basic_ostream >& stream); - xml_writer_stream(std::basic_ostream >& stream); - - virtual void write(const void* data, size_t size); - - private: - std::basic_ostream >* narrow_stream; - std::basic_ostream >* wide_stream; - }; - #endif - - // A light-weight handle for manipulating attributes in DOM tree - class PUGIXML_CLASS xml_attribute - { - friend class xml_attribute_iterator; - friend class xml_node; - - private: - xml_attribute_struct* _attr; - - typedef void (*unspecified_bool_type)(xml_attribute***); - - public: - // Default constructor. Constructs an empty attribute. - xml_attribute(); - - // Constructs attribute from internal pointer - explicit xml_attribute(xml_attribute_struct* attr); - - // Safe bool conversion operator - operator unspecified_bool_type() const; - - // Borland C++ workaround - bool operator!() const; - - // Comparison operators (compares wrapped attribute pointers) - bool operator==(const xml_attribute& r) const; - bool operator!=(const xml_attribute& r) const; - bool operator<(const xml_attribute& r) const; - bool operator>(const xml_attribute& r) const; - bool operator<=(const xml_attribute& r) const; - bool operator>=(const xml_attribute& r) const; - - // Check if attribute is empty - bool empty() const; - - // Get attribute name/value, or "" if attribute is empty - const char_t* name() const; - const char_t* value() const; - - // Get attribute value, or the default value if attribute is empty - const char_t* as_string(const char_t* def = PUGIXML_TEXT("")) const; - - // Get attribute value as a number, or the default value if conversion did not succeed or attribute is empty - int as_int(int def = 0) const; - unsigned int as_uint(unsigned int def = 0) const; - double as_double(double def = 0) const; - float as_float(float def = 0) const; - - #ifdef PUGIXML_HAS_LONG_LONG - long long as_llong(long long def = 0) const; - unsigned long long as_ullong(unsigned long long def = 0) const; - #endif - - // Get attribute value as bool (returns true if first character is in '1tTyY' set), or the default value if attribute is empty - bool as_bool(bool def = false) const; - - // Set attribute name/value (returns false if attribute is empty or there is not enough memory) - bool set_name(const char_t* rhs); - bool set_value(const char_t* rhs); - - // Set attribute value with type conversion (numbers are converted to strings, boolean is converted to "true"/"false") - bool set_value(int rhs); - bool set_value(unsigned int rhs); - bool set_value(double rhs); - bool set_value(float rhs); - bool set_value(bool rhs); - - #ifdef PUGIXML_HAS_LONG_LONG - bool set_value(long long rhs); - bool set_value(unsigned long long rhs); - #endif - - // Set attribute value (equivalent to set_value without error checking) - xml_attribute& operator=(const char_t* rhs); - xml_attribute& operator=(int rhs); - xml_attribute& operator=(unsigned int rhs); - xml_attribute& operator=(double rhs); - xml_attribute& operator=(float rhs); - xml_attribute& operator=(bool rhs); - - #ifdef PUGIXML_HAS_LONG_LONG - xml_attribute& operator=(long long rhs); - xml_attribute& operator=(unsigned long long rhs); - #endif - - // Get next/previous attribute in the attribute list of the parent node - xml_attribute next_attribute() const; - xml_attribute previous_attribute() const; - - // Get hash value (unique for handles to the same object) - size_t hash_value() const; - - // Get internal pointer - xml_attribute_struct* internal_object() const; - }; - -#ifdef __BORLANDC__ - // Borland C++ workaround - bool PUGIXML_FUNCTION operator&&(const xml_attribute& lhs, bool rhs); - bool PUGIXML_FUNCTION operator||(const xml_attribute& lhs, bool rhs); -#endif - - // A light-weight handle for manipulating nodes in DOM tree - class PUGIXML_CLASS xml_node - { - friend class xml_attribute_iterator; - friend class xml_node_iterator; - friend class xml_named_node_iterator; - - protected: - xml_node_struct* _root; - - typedef void (*unspecified_bool_type)(xml_node***); - - public: - // Default constructor. Constructs an empty node. - xml_node(); - - // Constructs node from internal pointer - explicit xml_node(xml_node_struct* p); - - // Safe bool conversion operator - operator unspecified_bool_type() const; - - // Borland C++ workaround - bool operator!() const; - - // Comparison operators (compares wrapped node pointers) - bool operator==(const xml_node& r) const; - bool operator!=(const xml_node& r) const; - bool operator<(const xml_node& r) const; - bool operator>(const xml_node& r) const; - bool operator<=(const xml_node& r) const; - bool operator>=(const xml_node& r) const; - - // Check if node is empty. - bool empty() const; - - // Get node type - xml_node_type type() const; - - // Get node name, or "" if node is empty or it has no name - const char_t* name() const; - - // Get node value, or "" if node is empty or it has no value - // Note: For text node.value() does not return "text"! Use child_value() or text() methods to access text inside nodes. - const char_t* value() const; - - // Get attribute list - xml_attribute first_attribute() const; - xml_attribute last_attribute() const; - - // Get children list - xml_node first_child() const; - xml_node last_child() const; - - // Get next/previous sibling in the children list of the parent node - xml_node next_sibling() const; - xml_node previous_sibling() const; - - // Get parent node - xml_node parent() const; - - // Get root of DOM tree this node belongs to - xml_node root() const; - - // Get text object for the current node - xml_text text() const; - - // Get child, attribute or next/previous sibling with the specified name - xml_node child(const char_t* name) const; - xml_attribute attribute(const char_t* name) const; - xml_node next_sibling(const char_t* name) const; - xml_node previous_sibling(const char_t* name) const; - - // Get attribute, starting the search from a hint (and updating hint so that searching for a sequence of attributes is fast) - xml_attribute attribute(const char_t* name, xml_attribute& hint) const; - - // Get child value of current node; that is, value of the first child node of type PCDATA/CDATA - const char_t* child_value() const; - - // Get child value of child with specified name. Equivalent to child(name).child_value(). - const char_t* child_value(const char_t* name) const; - - // Set node name/value (returns false if node is empty, there is not enough memory, or node can not have name/value) - bool set_name(const char_t* rhs); - bool set_value(const char_t* rhs); - - // Add attribute with specified name. Returns added attribute, or empty attribute on errors. - xml_attribute append_attribute(const char_t* name); - xml_attribute prepend_attribute(const char_t* name); - xml_attribute insert_attribute_after(const char_t* name, const xml_attribute& attr); - xml_attribute insert_attribute_before(const char_t* name, const xml_attribute& attr); - - // Add a copy of the specified attribute. Returns added attribute, or empty attribute on errors. - xml_attribute append_copy(const xml_attribute& proto); - xml_attribute prepend_copy(const xml_attribute& proto); - xml_attribute insert_copy_after(const xml_attribute& proto, const xml_attribute& attr); - xml_attribute insert_copy_before(const xml_attribute& proto, const xml_attribute& attr); - - // Add child node with specified type. Returns added node, or empty node on errors. - xml_node append_child(xml_node_type type = node_element); - xml_node prepend_child(xml_node_type type = node_element); - xml_node insert_child_after(xml_node_type type, const xml_node& node); - xml_node insert_child_before(xml_node_type type, const xml_node& node); - - // Add child element with specified name. Returns added node, or empty node on errors. - xml_node append_child(const char_t* name); - xml_node prepend_child(const char_t* name); - xml_node insert_child_after(const char_t* name, const xml_node& node); - xml_node insert_child_before(const char_t* name, const xml_node& node); - - // Add a copy of the specified node as a child. Returns added node, or empty node on errors. - xml_node append_copy(const xml_node& proto); - xml_node prepend_copy(const xml_node& proto); - xml_node insert_copy_after(const xml_node& proto, const xml_node& node); - xml_node insert_copy_before(const xml_node& proto, const xml_node& node); - - // Move the specified node to become a child of this node. Returns moved node, or empty node on errors. - xml_node append_move(const xml_node& moved); - xml_node prepend_move(const xml_node& moved); - xml_node insert_move_after(const xml_node& moved, const xml_node& node); - xml_node insert_move_before(const xml_node& moved, const xml_node& node); - - // Remove specified attribute - bool remove_attribute(const xml_attribute& a); - bool remove_attribute(const char_t* name); - - // Remove specified child - bool remove_child(const xml_node& n); - bool remove_child(const char_t* name); - - // Parses buffer as an XML document fragment and appends all nodes as children of the current node. - // Copies/converts the buffer, so it may be deleted or changed after the function returns. - // Note: append_buffer allocates memory that has the lifetime of the owning document; removing the appended nodes does not immediately reclaim that memory. - xml_parse_result append_buffer(const void* contents, size_t size, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); - - // Find attribute using predicate. Returns first attribute for which predicate returned true. - template xml_attribute find_attribute(Predicate pred) const - { - if (!_root) return xml_attribute(); - - for (xml_attribute attrib = first_attribute(); attrib; attrib = attrib.next_attribute()) - if (pred(attrib)) - return attrib; - - return xml_attribute(); - } - - // Find child node using predicate. Returns first child for which predicate returned true. - template xml_node find_child(Predicate pred) const - { - if (!_root) return xml_node(); - - for (xml_node node = first_child(); node; node = node.next_sibling()) - if (pred(node)) - return node; - - return xml_node(); - } - - // Find node from subtree using predicate. Returns first node from subtree (depth-first), for which predicate returned true. - template xml_node find_node(Predicate pred) const - { - if (!_root) return xml_node(); - - xml_node cur = first_child(); - - while (cur._root && cur._root != _root) - { - if (pred(cur)) return cur; - - if (cur.first_child()) cur = cur.first_child(); - else if (cur.next_sibling()) cur = cur.next_sibling(); - else - { - while (!cur.next_sibling() && cur._root != _root) cur = cur.parent(); - - if (cur._root != _root) cur = cur.next_sibling(); - } - } - - return xml_node(); - } - - // Find child node by attribute name/value - xml_node find_child_by_attribute(const char_t* name, const char_t* attr_name, const char_t* attr_value) const; - xml_node find_child_by_attribute(const char_t* attr_name, const char_t* attr_value) const; - - #ifndef PUGIXML_NO_STL - // Get the absolute node path from root as a text string. - string_t path(char_t delimiter = '/') const; - #endif - - // Search for a node by path consisting of node names and . or .. elements. - xml_node first_element_by_path(const char_t* path, char_t delimiter = '/') const; - - // Recursively traverse subtree with xml_tree_walker - bool traverse(xml_tree_walker& walker); - - #ifndef PUGIXML_NO_XPATH - // Select single node by evaluating XPath query. Returns first node from the resulting node set. - xpath_node select_node(const char_t* query, xpath_variable_set* variables = 0) const; - xpath_node select_node(const xpath_query& query) const; - - // Select node set by evaluating XPath query - xpath_node_set select_nodes(const char_t* query, xpath_variable_set* variables = 0) const; - xpath_node_set select_nodes(const xpath_query& query) const; - - // (deprecated: use select_node instead) Select single node by evaluating XPath query. - xpath_node select_single_node(const char_t* query, xpath_variable_set* variables = 0) const; - xpath_node select_single_node(const xpath_query& query) const; - - #endif - - // Print subtree using a writer object - void print(xml_writer& writer, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto, unsigned int depth = 0) const; - - #ifndef PUGIXML_NO_STL - // Print subtree to stream - void print(std::basic_ostream >& os, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto, unsigned int depth = 0) const; - void print(std::basic_ostream >& os, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, unsigned int depth = 0) const; - #endif - - // Child nodes iterators - typedef xml_node_iterator iterator; - - iterator begin() const; - iterator end() const; - - // Attribute iterators - typedef xml_attribute_iterator attribute_iterator; - - attribute_iterator attributes_begin() const; - attribute_iterator attributes_end() const; - - // Range-based for support - xml_object_range children() const; - xml_object_range children(const char_t* name) const; - xml_object_range attributes() const; - - // Get node offset in parsed file/string (in char_t units) for debugging purposes - ptrdiff_t offset_debug() const; - - // Get hash value (unique for handles to the same object) - size_t hash_value() const; - - // Get internal pointer - xml_node_struct* internal_object() const; - }; - -#ifdef __BORLANDC__ - // Borland C++ workaround - bool PUGIXML_FUNCTION operator&&(const xml_node& lhs, bool rhs); - bool PUGIXML_FUNCTION operator||(const xml_node& lhs, bool rhs); -#endif - - // A helper for working with text inside PCDATA nodes - class PUGIXML_CLASS xml_text - { - friend class xml_node; - - xml_node_struct* _root; - - typedef void (*unspecified_bool_type)(xml_text***); - - explicit xml_text(xml_node_struct* root); - - xml_node_struct* _data_new(); - xml_node_struct* _data() const; - - public: - // Default constructor. Constructs an empty object. - xml_text(); - - // Safe bool conversion operator - operator unspecified_bool_type() const; - - // Borland C++ workaround - bool operator!() const; - - // Check if text object is empty - bool empty() const; - - // Get text, or "" if object is empty - const char_t* get() const; - - // Get text, or the default value if object is empty - const char_t* as_string(const char_t* def = PUGIXML_TEXT("")) const; - - // Get text as a number, or the default value if conversion did not succeed or object is empty - int as_int(int def = 0) const; - unsigned int as_uint(unsigned int def = 0) const; - double as_double(double def = 0) const; - float as_float(float def = 0) const; - - #ifdef PUGIXML_HAS_LONG_LONG - long long as_llong(long long def = 0) const; - unsigned long long as_ullong(unsigned long long def = 0) const; - #endif - - // Get text as bool (returns true if first character is in '1tTyY' set), or the default value if object is empty - bool as_bool(bool def = false) const; - - // Set text (returns false if object is empty or there is not enough memory) - bool set(const char_t* rhs); - - // Set text with type conversion (numbers are converted to strings, boolean is converted to "true"/"false") - bool set(int rhs); - bool set(unsigned int rhs); - bool set(double rhs); - bool set(float rhs); - bool set(bool rhs); - - #ifdef PUGIXML_HAS_LONG_LONG - bool set(long long rhs); - bool set(unsigned long long rhs); - #endif - - // Set text (equivalent to set without error checking) - xml_text& operator=(const char_t* rhs); - xml_text& operator=(int rhs); - xml_text& operator=(unsigned int rhs); - xml_text& operator=(double rhs); - xml_text& operator=(float rhs); - xml_text& operator=(bool rhs); - - #ifdef PUGIXML_HAS_LONG_LONG - xml_text& operator=(long long rhs); - xml_text& operator=(unsigned long long rhs); - #endif - - // Get the data node (node_pcdata or node_cdata) for this object - xml_node data() const; - }; - -#ifdef __BORLANDC__ - // Borland C++ workaround - bool PUGIXML_FUNCTION operator&&(const xml_text& lhs, bool rhs); - bool PUGIXML_FUNCTION operator||(const xml_text& lhs, bool rhs); -#endif - - // Child node iterator (a bidirectional iterator over a collection of xml_node) - class PUGIXML_CLASS xml_node_iterator - { - friend class xml_node; - - private: - mutable xml_node _wrap; - xml_node _parent; - - xml_node_iterator(xml_node_struct* ref, xml_node_struct* parent); - - public: - // Iterator traits - typedef ptrdiff_t difference_type; - typedef xml_node value_type; - typedef xml_node* pointer; - typedef xml_node& reference; - - #ifndef PUGIXML_NO_STL - typedef std::bidirectional_iterator_tag iterator_category; - #endif - - // Default constructor - xml_node_iterator(); - - // Construct an iterator which points to the specified node - xml_node_iterator(const xml_node& node); - - // Iterator operators - bool operator==(const xml_node_iterator& rhs) const; - bool operator!=(const xml_node_iterator& rhs) const; - - xml_node& operator*() const; - xml_node* operator->() const; - - const xml_node_iterator& operator++(); - xml_node_iterator operator++(int); - - const xml_node_iterator& operator--(); - xml_node_iterator operator--(int); - }; - - // Attribute iterator (a bidirectional iterator over a collection of xml_attribute) - class PUGIXML_CLASS xml_attribute_iterator - { - friend class xml_node; - - private: - mutable xml_attribute _wrap; - xml_node _parent; - - xml_attribute_iterator(xml_attribute_struct* ref, xml_node_struct* parent); - - public: - // Iterator traits - typedef ptrdiff_t difference_type; - typedef xml_attribute value_type; - typedef xml_attribute* pointer; - typedef xml_attribute& reference; - - #ifndef PUGIXML_NO_STL - typedef std::bidirectional_iterator_tag iterator_category; - #endif - - // Default constructor - xml_attribute_iterator(); - - // Construct an iterator which points to the specified attribute - xml_attribute_iterator(const xml_attribute& attr, const xml_node& parent); - - // Iterator operators - bool operator==(const xml_attribute_iterator& rhs) const; - bool operator!=(const xml_attribute_iterator& rhs) const; - - xml_attribute& operator*() const; - xml_attribute* operator->() const; - - const xml_attribute_iterator& operator++(); - xml_attribute_iterator operator++(int); - - const xml_attribute_iterator& operator--(); - xml_attribute_iterator operator--(int); - }; - - // Named node range helper - class PUGIXML_CLASS xml_named_node_iterator - { - friend class xml_node; - - public: - // Iterator traits - typedef ptrdiff_t difference_type; - typedef xml_node value_type; - typedef xml_node* pointer; - typedef xml_node& reference; - - #ifndef PUGIXML_NO_STL - typedef std::bidirectional_iterator_tag iterator_category; - #endif - - // Default constructor - xml_named_node_iterator(); - - // Construct an iterator which points to the specified node - xml_named_node_iterator(const xml_node& node, const char_t* name); - - // Iterator operators - bool operator==(const xml_named_node_iterator& rhs) const; - bool operator!=(const xml_named_node_iterator& rhs) const; - - xml_node& operator*() const; - xml_node* operator->() const; - - const xml_named_node_iterator& operator++(); - xml_named_node_iterator operator++(int); - - const xml_named_node_iterator& operator--(); - xml_named_node_iterator operator--(int); - - private: - mutable xml_node _wrap; - xml_node _parent; - const char_t* _name; - - xml_named_node_iterator(xml_node_struct* ref, xml_node_struct* parent, const char_t* name); - }; - - // Abstract tree walker class (see xml_node::traverse) - class PUGIXML_CLASS xml_tree_walker - { - friend class xml_node; - - private: - int _depth; - - protected: - // Get current traversal depth - int depth() const; - - public: - xml_tree_walker(); - virtual ~xml_tree_walker(); - - // Callback that is called when traversal begins - virtual bool begin(xml_node& node); - - // Callback that is called for each node traversed - virtual bool for_each(xml_node& node) = 0; - - // Callback that is called when traversal ends - virtual bool end(xml_node& node); - }; - - // Parsing status, returned as part of xml_parse_result object - enum xml_parse_status - { - status_ok = 0, // No error - - status_file_not_found, // File was not found during load_file() - status_io_error, // Error reading from file/stream - status_out_of_memory, // Could not allocate memory - status_internal_error, // Internal error occurred - - status_unrecognized_tag, // Parser could not determine tag type - - status_bad_pi, // Parsing error occurred while parsing document declaration/processing instruction - status_bad_comment, // Parsing error occurred while parsing comment - status_bad_cdata, // Parsing error occurred while parsing CDATA section - status_bad_doctype, // Parsing error occurred while parsing document type declaration - status_bad_pcdata, // Parsing error occurred while parsing PCDATA section - status_bad_start_element, // Parsing error occurred while parsing start element tag - status_bad_attribute, // Parsing error occurred while parsing element attribute - status_bad_end_element, // Parsing error occurred while parsing end element tag - status_end_element_mismatch,// There was a mismatch of start-end tags (closing tag had incorrect name, some tag was not closed or there was an excessive closing tag) - - status_append_invalid_root, // Unable to append nodes since root type is not node_element or node_document (exclusive to xml_node::append_buffer) - - status_no_document_element // Parsing resulted in a document without element nodes - }; - - // Parsing result - struct PUGIXML_CLASS xml_parse_result - { - // Parsing status (see xml_parse_status) - xml_parse_status status; - - // Last parsed offset (in char_t units from start of input data) - ptrdiff_t offset; - - // Source document encoding - xml_encoding encoding; - - // Default constructor, initializes object to failed state - xml_parse_result(); - - // Cast to bool operator - operator bool() const; - - // Get error description - const char* description() const; - }; - - // Document class (DOM tree root) - class PUGIXML_CLASS xml_document: public xml_node - { - private: - char_t* _buffer; - - char _memory[192]; - - // Non-copyable semantics - xml_document(const xml_document&); - xml_document& operator=(const xml_document&); - - void create(); - void destroy(); - - public: - // Default constructor, makes empty document - xml_document(); - - // Destructor, invalidates all node/attribute handles to this document - ~xml_document(); - - // Removes all nodes, leaving the empty document - void reset(); - - // Removes all nodes, then copies the entire contents of the specified document - void reset(const xml_document& proto); - - #ifndef PUGIXML_NO_STL - // Load document from stream. - xml_parse_result load(std::basic_istream >& stream, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); - xml_parse_result load(std::basic_istream >& stream, unsigned int options = parse_default); - #endif - - // (deprecated: use load_string instead) Load document from zero-terminated string. No encoding conversions are applied. - xml_parse_result load(const char_t* contents, unsigned int options = parse_default); - - // Load document from zero-terminated string. No encoding conversions are applied. - xml_parse_result load_string(const char_t* contents, unsigned int options = parse_default); - - // Load document from file - xml_parse_result load_file(const char* path, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); - xml_parse_result load_file(const wchar_t* path, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); - - // Load document from buffer. Copies/converts the buffer, so it may be deleted or changed after the function returns. - xml_parse_result load_buffer(const void* contents, size_t size, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); - - // Load document from buffer, using the buffer for in-place parsing (the buffer is modified and used for storage of document data). - // You should ensure that buffer data will persist throughout the document's lifetime, and free the buffer memory manually once document is destroyed. - xml_parse_result load_buffer_inplace(void* contents, size_t size, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); - - // Load document from buffer, using the buffer for in-place parsing (the buffer is modified and used for storage of document data). - // You should allocate the buffer with pugixml allocation function; document will free the buffer when it is no longer needed (you can't use it anymore). - xml_parse_result load_buffer_inplace_own(void* contents, size_t size, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); - - // Save XML document to writer (semantics is slightly different from xml_node::print, see documentation for details). - void save(xml_writer& writer, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const; - - #ifndef PUGIXML_NO_STL - // Save XML document to stream (semantics is slightly different from xml_node::print, see documentation for details). - void save(std::basic_ostream >& stream, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const; - void save(std::basic_ostream >& stream, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default) const; - #endif - - // Save XML to file - bool save_file(const char* path, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const; - bool save_file(const wchar_t* path, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const; - - // Get document element - xml_node document_element() const; - }; - -#ifndef PUGIXML_NO_XPATH - // XPath query return type - enum xpath_value_type - { - xpath_type_none, // Unknown type (query failed to compile) - xpath_type_node_set, // Node set (xpath_node_set) - xpath_type_number, // Number - xpath_type_string, // String - xpath_type_boolean // Boolean - }; - - // XPath parsing result - struct PUGIXML_CLASS xpath_parse_result - { - // Error message (0 if no error) - const char* error; - - // Last parsed offset (in char_t units from string start) - ptrdiff_t offset; - - // Default constructor, initializes object to failed state - xpath_parse_result(); - - // Cast to bool operator - operator bool() const; - - // Get error description - const char* description() const; - }; - - // A single XPath variable - class PUGIXML_CLASS xpath_variable - { - friend class xpath_variable_set; - - protected: - xpath_value_type _type; - xpath_variable* _next; - - xpath_variable(xpath_value_type type); - - // Non-copyable semantics - xpath_variable(const xpath_variable&); - xpath_variable& operator=(const xpath_variable&); - - public: - // Get variable name - const char_t* name() const; - - // Get variable type - xpath_value_type type() const; - - // Get variable value; no type conversion is performed, default value (false, NaN, empty string, empty node set) is returned on type mismatch error - bool get_boolean() const; - double get_number() const; - const char_t* get_string() const; - const xpath_node_set& get_node_set() const; - - // Set variable value; no type conversion is performed, false is returned on type mismatch error - bool set(bool value); - bool set(double value); - bool set(const char_t* value); - bool set(const xpath_node_set& value); - }; - - // A set of XPath variables - class PUGIXML_CLASS xpath_variable_set - { - private: - xpath_variable* _data[64]; - - void _assign(const xpath_variable_set& rhs); - void _swap(xpath_variable_set& rhs); - - xpath_variable* _find(const char_t* name) const; - - static bool _clone(xpath_variable* var, xpath_variable** out_result); - static void _destroy(xpath_variable* var); - - public: - // Default constructor/destructor - xpath_variable_set(); - ~xpath_variable_set(); - - // Copy constructor/assignment operator - xpath_variable_set(const xpath_variable_set& rhs); - xpath_variable_set& operator=(const xpath_variable_set& rhs); - - #if __cplusplus >= 201103 - // Move semantics support - xpath_variable_set(xpath_variable_set&& rhs); - xpath_variable_set& operator=(xpath_variable_set&& rhs); - #endif - - // Add a new variable or get the existing one, if the types match - xpath_variable* add(const char_t* name, xpath_value_type type); - - // Set value of an existing variable; no type conversion is performed, false is returned if there is no such variable or if types mismatch - bool set(const char_t* name, bool value); - bool set(const char_t* name, double value); - bool set(const char_t* name, const char_t* value); - bool set(const char_t* name, const xpath_node_set& value); - - // Get existing variable by name - xpath_variable* get(const char_t* name); - const xpath_variable* get(const char_t* name) const; - }; - - // A compiled XPath query object - class PUGIXML_CLASS xpath_query - { - private: - void* _impl; - xpath_parse_result _result; - - typedef void (*unspecified_bool_type)(xpath_query***); - - // Non-copyable semantics - xpath_query(const xpath_query&); - xpath_query& operator=(const xpath_query&); - - public: - // Construct a compiled object from XPath expression. - // If PUGIXML_NO_EXCEPTIONS is not defined, throws xpath_exception on compilation errors. - explicit xpath_query(const char_t* query, xpath_variable_set* variables = 0); - - // Constructor - xpath_query(); - - // Destructor - ~xpath_query(); - - #if __cplusplus >= 201103 - // Move semantics support - xpath_query(xpath_query&& rhs); - xpath_query& operator=(xpath_query&& rhs); - #endif - - // Get query expression return type - xpath_value_type return_type() const; - - // Evaluate expression as boolean value in the specified context; performs type conversion if necessary. - // If PUGIXML_NO_EXCEPTIONS is not defined, throws std::bad_alloc on out of memory errors. - bool evaluate_boolean(const xpath_node& n) const; - - // Evaluate expression as double value in the specified context; performs type conversion if necessary. - // If PUGIXML_NO_EXCEPTIONS is not defined, throws std::bad_alloc on out of memory errors. - double evaluate_number(const xpath_node& n) const; - - #ifndef PUGIXML_NO_STL - // Evaluate expression as string value in the specified context; performs type conversion if necessary. - // If PUGIXML_NO_EXCEPTIONS is not defined, throws std::bad_alloc on out of memory errors. - string_t evaluate_string(const xpath_node& n) const; - #endif - - // Evaluate expression as string value in the specified context; performs type conversion if necessary. - // At most capacity characters are written to the destination buffer, full result size is returned (includes terminating zero). - // If PUGIXML_NO_EXCEPTIONS is not defined, throws std::bad_alloc on out of memory errors. - // If PUGIXML_NO_EXCEPTIONS is defined, returns empty set instead. - size_t evaluate_string(char_t* buffer, size_t capacity, const xpath_node& n) const; - - // Evaluate expression as node set in the specified context. - // If PUGIXML_NO_EXCEPTIONS is not defined, throws xpath_exception on type mismatch and std::bad_alloc on out of memory errors. - // If PUGIXML_NO_EXCEPTIONS is defined, returns empty node set instead. - xpath_node_set evaluate_node_set(const xpath_node& n) const; - - // Evaluate expression as node set in the specified context. - // Return first node in document order, or empty node if node set is empty. - // If PUGIXML_NO_EXCEPTIONS is not defined, throws xpath_exception on type mismatch and std::bad_alloc on out of memory errors. - // If PUGIXML_NO_EXCEPTIONS is defined, returns empty node instead. - xpath_node evaluate_node(const xpath_node& n) const; - - // Get parsing result (used to get compilation errors in PUGIXML_NO_EXCEPTIONS mode) - const xpath_parse_result& result() const; - - // Safe bool conversion operator - operator unspecified_bool_type() const; - - // Borland C++ workaround - bool operator!() const; - }; - - #ifndef PUGIXML_NO_EXCEPTIONS - // XPath exception class - class PUGIXML_CLASS xpath_exception: public std::exception - { - private: - xpath_parse_result _result; - - public: - // Construct exception from parse result - explicit xpath_exception(const xpath_parse_result& result); - - // Get error message - virtual const char* what() const throw(); - - // Get parse result - const xpath_parse_result& result() const; - }; - #endif - - // XPath node class (either xml_node or xml_attribute) - class PUGIXML_CLASS xpath_node - { - private: - xml_node _node; - xml_attribute _attribute; - - typedef void (*unspecified_bool_type)(xpath_node***); - - public: - // Default constructor; constructs empty XPath node - xpath_node(); - - // Construct XPath node from XML node/attribute - xpath_node(const xml_node& node); - xpath_node(const xml_attribute& attribute, const xml_node& parent); - - // Get node/attribute, if any - xml_node node() const; - xml_attribute attribute() const; - - // Get parent of contained node/attribute - xml_node parent() const; - - // Safe bool conversion operator - operator unspecified_bool_type() const; - - // Borland C++ workaround - bool operator!() const; - - // Comparison operators - bool operator==(const xpath_node& n) const; - bool operator!=(const xpath_node& n) const; - }; - -#ifdef __BORLANDC__ - // Borland C++ workaround - bool PUGIXML_FUNCTION operator&&(const xpath_node& lhs, bool rhs); - bool PUGIXML_FUNCTION operator||(const xpath_node& lhs, bool rhs); -#endif - - // A fixed-size collection of XPath nodes - class PUGIXML_CLASS xpath_node_set - { - public: - // Collection type - enum type_t - { - type_unsorted, // Not ordered - type_sorted, // Sorted by document order (ascending) - type_sorted_reverse // Sorted by document order (descending) - }; - - // Constant iterator type - typedef const xpath_node* const_iterator; - - // We define non-constant iterator to be the same as constant iterator so that various generic algorithms (i.e. boost foreach) work - typedef const xpath_node* iterator; - - // Default constructor. Constructs empty set. - xpath_node_set(); - - // Constructs a set from iterator range; data is not checked for duplicates and is not sorted according to provided type, so be careful - xpath_node_set(const_iterator begin, const_iterator end, type_t type = type_unsorted); - - // Destructor - ~xpath_node_set(); - - // Copy constructor/assignment operator - xpath_node_set(const xpath_node_set& ns); - xpath_node_set& operator=(const xpath_node_set& ns); - - #if __cplusplus >= 201103 - // Move semantics support - xpath_node_set(xpath_node_set&& rhs); - xpath_node_set& operator=(xpath_node_set&& rhs); - #endif - - // Get collection type - type_t type() const; - - // Get collection size - size_t size() const; - - // Indexing operator - const xpath_node& operator[](size_t index) const; - - // Collection iterators - const_iterator begin() const; - const_iterator end() const; - - // Sort the collection in ascending/descending order by document order - void sort(bool reverse = false); - - // Get first node in the collection by document order - xpath_node first() const; - - // Check if collection is empty - bool empty() const; - - private: - type_t _type; - - xpath_node _storage; - - xpath_node* _begin; - xpath_node* _end; - - void _assign(const_iterator begin, const_iterator end, type_t type); - void _move(xpath_node_set& rhs); - }; -#endif - -#ifndef PUGIXML_NO_STL - // Convert wide string to UTF8 - std::basic_string, std::allocator > PUGIXML_FUNCTION as_utf8(const wchar_t* str); - std::basic_string, std::allocator > PUGIXML_FUNCTION as_utf8(const std::basic_string, std::allocator >& str); - - // Convert UTF8 to wide string - std::basic_string, std::allocator > PUGIXML_FUNCTION as_wide(const char* str); - std::basic_string, std::allocator > PUGIXML_FUNCTION as_wide(const std::basic_string, std::allocator >& str); -#endif - - // Memory allocation function interface; returns pointer to allocated memory or NULL on failure - typedef void* (*allocation_function)(size_t size); - - // Memory deallocation function interface - typedef void (*deallocation_function)(void* ptr); - - // Override default memory management functions. All subsequent allocations/deallocations will be performed via supplied functions. - void PUGIXML_FUNCTION set_memory_management_functions(allocation_function allocate, deallocation_function deallocate); - - // Get current memory management functions - allocation_function PUGIXML_FUNCTION get_memory_allocation_function(); - deallocation_function PUGIXML_FUNCTION get_memory_deallocation_function(); -} - -#if !defined(PUGIXML_NO_STL) && (defined(_MSC_VER) || defined(__ICC)) -namespace std -{ - // Workarounds for (non-standard) iterator category detection for older versions (MSVC7/IC8 and earlier) - std::bidirectional_iterator_tag PUGIXML_FUNCTION _Iter_cat(const pugi::xml_node_iterator&); - std::bidirectional_iterator_tag PUGIXML_FUNCTION _Iter_cat(const pugi::xml_attribute_iterator&); - std::bidirectional_iterator_tag PUGIXML_FUNCTION _Iter_cat(const pugi::xml_named_node_iterator&); -} -#endif - -#if !defined(PUGIXML_NO_STL) && defined(__SUNPRO_CC) -namespace std -{ - // Workarounds for (non-standard) iterator category detection - std::bidirectional_iterator_tag PUGIXML_FUNCTION __iterator_category(const pugi::xml_node_iterator&); - std::bidirectional_iterator_tag PUGIXML_FUNCTION __iterator_category(const pugi::xml_attribute_iterator&); - std::bidirectional_iterator_tag PUGIXML_FUNCTION __iterator_category(const pugi::xml_named_node_iterator&); -} -#endif - -#endif - -// Make sure implementation is included in header-only mode -// Use macro expansion in #include to work around QMake (QTBUG-11923) -#if defined(PUGIXML_HEADER_ONLY) && !defined(PUGIXML_SOURCE) -# define PUGIXML_SOURCE "pugixml.cpp" -# include PUGIXML_SOURCE -#endif - -/** - * Copyright (c) 2006-2015 Arseny Kapoulkine - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ diff --git a/src/tmx/TmxApi/tmx/pugixml/readme.txt b/src/tmx/TmxApi/tmx/pugixml/readme.txt deleted file mode 100644 index 9d3833c9b..000000000 --- a/src/tmx/TmxApi/tmx/pugixml/readme.txt +++ /dev/null @@ -1,52 +0,0 @@ -pugixml 1.7 - an XML processing library - -Copyright (C) 2006-2015, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) -Report bugs and download new versions at http://pugixml.org/ - -This is the distribution of pugixml, which is a C++ XML processing library, -which consists of a DOM-like interface with rich traversal/modification -capabilities, an extremely fast XML parser which constructs the DOM tree from -an XML file/buffer, and an XPath 1.0 implementation for complex data-driven -tree queries. Full Unicode support is also available, with Unicode interface -variants and conversions between different Unicode encodings (which happen -automatically during parsing/saving). - -The distribution contains the following folders: - - contrib/ - various contributions to pugixml - - docs/ - documentation - docs/samples - pugixml usage examples - docs/quickstart.html - quick start guide - docs/manual.html - complete manual - - scripts/ - project files for IDE/build systems - - src/ - header and source files - - readme.txt - this file. - -This library is distributed under the MIT License: - -Copyright (c) 2006-2015 Arseny Kapoulkine - -Permission is hereby granted, free of charge, to any person -obtaining a copy of this software and associated documentation -files (the "Software"), to deal in the Software without -restriction, including without limitation the rights to use, -copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. diff --git a/src/v2i-hub/CommandPlugin/src/CommandPlugin.cpp b/src/v2i-hub/CommandPlugin/src/CommandPlugin.cpp index 60f7d5834..c85596dea 100644 --- a/src/v2i-hub/CommandPlugin/src/CommandPlugin.cpp +++ b/src/v2i-hub/CommandPlugin/src/CommandPlugin.cpp @@ -633,10 +633,10 @@ int CommandPlugin::WSCallbackBASE64( argsList[arg.first] = arg.second.data(); } } - catch (exception argsEx) + catch (const exception &argsEx) { //no args - FILE_LOG(logDEBUG) << "WSCallbackBASE64 process command error: no arguments"; + FILE_LOG(logDEBUG) << "WSCallbackBASE64 process command error: no arguments. " << argsEx.what(); } //check authorization diff --git a/src/v2i-hub/PedestrianPlugin/manifest.json b/src/v2i-hub/PedestrianPlugin/manifest.json index 6bb73e15b..de45e998b 100644 --- a/src/v2i-hub/PedestrianPlugin/manifest.json +++ b/src/v2i-hub/PedestrianPlugin/manifest.json @@ -18,11 +18,6 @@ "default":"1000", "description":"The frequency to send the PSM in milliseconds." }, - { - "key":"Instance", - "default":"0", - "description":"The instance of Pedestrian plugin." - }, { "key":"WebServiceIP", "default":"127.0.0.1", diff --git a/src/v2i-hub/PedestrianPlugin/scripts/sendPSM.py b/src/v2i-hub/PedestrianPlugin/scripts/sendPSM.py new file mode 100644 index 000000000..47b923548 --- /dev/null +++ b/src/v2i-hub/PedestrianPlugin/scripts/sendPSM.py @@ -0,0 +1,41 @@ +import aiohttp +import asyncio + +url = "http://127.0.0.1:9000" +data = ''' + + + 0 + 0 + 87654321 + 400774115-1248282086538 + 25525565535 + 0 + 270 + + + 6 + +''' + +headers = { + 'Content-Type': 'application/xml', +} + +async def send_request(session, i): + async with session.post(url, data=data, headers=headers) as response: + if response.status == 201: + print(f"Request {i} succeeded") + else: + print(f"Request {i} failed with status code {response.status}") + +async def main(): + async with aiohttp.ClientSession() as session: + tasks = [] + for i in range(1000): + tasks.append(asyncio.create_task(send_request(session, i))) + await asyncio.sleep(0.1) # maintain the interval between requests + await asyncio.gather(*tasks) + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/src/v2i-hub/PedestrianPlugin/src/PedestrianPlugin.cpp b/src/v2i-hub/PedestrianPlugin/src/PedestrianPlugin.cpp index 64d192c7f..bec74b3c2 100644 --- a/src/v2i-hub/PedestrianPlugin/src/PedestrianPlugin.cpp +++ b/src/v2i-hub/PedestrianPlugin/src/PedestrianPlugin.cpp @@ -2,139 +2,160 @@ // Name : PedestrianPlugin.cpp // Author : FHWA Saxton Transportation Operations Laboratory // Version : -// Copyright : Copyright (c) 2019 FHWA Saxton Transportation Operations Laboratory. All rights reserved. +// Copyright : Copyright (c) 2024 FHWA Saxton Transportation Operations Laboratory. All rights reserved. // Description : Pedestrian Plugin //========================================================================== #include "include/PedestrianPlugin.hpp" +using namespace tmx; +using namespace tmx::messages; +using namespace tmx::utils; +using namespace OpenAPI; namespace PedestrianPlugin { /** - * Construct a new PedestrianPlugin with the given name. - * + * @brief Construct a new PedestrianPlugin with the given name. * @param name The name to give the plugin for identification purposes. */ -PedestrianPlugin::PedestrianPlugin(string name): PluginClient(name) +PedestrianPlugin::PedestrianPlugin(const std::string &name) : PluginClient(name) { - // The log level can be changed from the default here. - FILELog::ReportingLevel() = FILELog::FromString("DEBUG"); - - AddMessageFilter(this, &PedestrianPlugin::HandleMapDataMessage); - - AddMessageFilter (this, &PedestrianPlugin::HandleBasicSafetyMessage); - - // Subscribe to all messages specified by the filters above. - SubscribeToMessages(); -} - -void PedestrianPlugin::PedestrianRequestHandler(QHttpEngine::Socket *socket) -{ - auto router = QSharedPointer::create(); - QString st; - while(socket->bytesAvailable()>0) - { - st.append(socket->readAll()); - } - QByteArray array = st.toLocal8Bit(); - - char* psmMsgdef = array.data(); - // Catch parse exceptions - try { - BroadcastPsm(psmMsgdef); - writeResponse(QHttpEngine::Socket::Created, socket); - } - catch(const J2735Exception &e) { - PLOG(logERROR) << "Error parsing file: " << e.what() << std::endl; - writeResponse(QHttpEngine::Socket::BadRequest, socket); - } } int PedestrianPlugin::StartWebSocket() { - PLOG(logDEBUG) << "In PedestrianPlugin::StartWebSocket " << std::endl; - // The io_context is required for all I/O - net::io_context ioc; - + PLOG(logDEBUG) << "In PedestrianPlugin::StartWebSocket "; flirSession = std::make_shared(ioc); // Launch the asynchronous operation flirSession->run(webSocketIP.c_str(), webSocketURLExt.c_str(), cameraRotation, hostString.c_str()); - PLOG(logDEBUG) << "Successfully running the I/O service" << std::endl; + PLOG(logDEBUG) << "Successfully running the I/O service"; + runningWebSocket = true; - // Run the I/O service. The call will return when - // the socket is closed. + // Run the I/O service. The call will return when the socket is closed. ioc.run(); - return EXIT_SUCCESS; + return EXIT_SUCCESS; } -int PedestrianPlugin::checkXML() +void PedestrianPlugin::StopWebSocket() { - //first xml will be empty string - std::string lastGeneratedXML = ""; + if (flirSession && runningWebSocket) + { + PLOG(logDEBUG) << "Stopping WebSocket session"; + beast::error_code ec; + flirSession->on_close(ec); + runningWebSocket = false; + } + else + { + PLOG(logDEBUG) << "WebSocket session was not running or already stopped."; + } +} - //if a new psm xml has been generated the FLIR web socket, send it to the BroadcastPSM function +void PedestrianPlugin::checkXML() +{ while (true) { if (flirSession == nullptr) { - PLOG(logDEBUG) << "flir session not yet initialized: " << std::endl; + PLOG(logDEBUG) << "FLIR session not yet initialized: "; } else { - //retrieve the PSM queue and send each one to be broadcast, then pop - std::queue currentPSMQueue = flirSession->getPSMQueue(); + // Retrieve the PSM queue and send each one to be broadcast, then pop. + std::queue currentPSMQueue = flirSession->getPSMQueue(); while(!currentPSMQueue.empty()) { - char* char_arr = ¤tPSMQueue.front()[0]; + const char* char_arr = ¤tPSMQueue.front()[0]; BroadcastPsm(char_arr); currentPSMQueue.pop(); - } + } } - - } - return EXIT_SUCCESS; + } } -int PedestrianPlugin::StartWebService() +void PedestrianPlugin::PedestrianRequestHandler(QHttpEngine::Socket *socket) { - //Web services - char *placeholderX[1]={0}; - int placeholderC=1; - QCoreApplication a(placeholderC,placeholderX); - - QHostAddress address = QHostAddress(QString::fromStdString (webip)); - quint16 port = static_cast(webport); + QByteArray st; + while(socket->bytesAvailable() > 0) + { + auto readBytes = socket->readAll(); + st.append(readBytes); + } + if(st.size() == 0) + { + PLOG(logERROR) << "Received PSM is empty and skipped."; + socket->setStatusCode(QHttpEngine::Socket::BadRequest); + socket->writeHeaders(); + socket->close(); + return; + } + PLOG(logINFO) << "Received PSM bytes size: " << st.size(); - QSharedPointer handler(new OpenAPI::OAIApiRequestHandler()); - handler = QSharedPointer (new OpenAPI::OAIApiRequestHandler()); + std::string psmMsgdef = st.data(); + std::list psmSL = {}; + psmSL.push_back(psmMsgdef); - QObject::connect(handler.data(), &OpenAPI::OAIApiRequestHandler::requestReceived, [&](QHttpEngine::Socket *socket) { + // Catch parse exceptions + try { + for(const auto& psm_s: psmSL) + { + BroadcastPsm(psm_s); + socket->setStatusCode(QHttpEngine::Socket::Created); + } + } + catch(const J2735Exception &e) { + PLOG(logERROR) << "Error encoding received PSM data " << psmMsgdef << std::endl << e.what(); + socket->setStatusCode(QHttpEngine::Socket::BadRequest); + } + + socket->writeHeaders(); + socket->close(); +} - this->PedestrianRequestHandler(socket); - }); +int PedestrianPlugin::StartWebService() +{ + PLOG(logDEBUG) << "In PedestrianPlugin::StartWebService"; - QHttpEngine::Server server(handler.data()); + // Web services + std::array placeholderX = {nullptr}; + int placeholderC = 1; + QCoreApplication a(placeholderC, placeholderX.data()); - if (!server.listen(address, port)) { + auto address = QHostAddress(QString::fromStdString (webip)); + + QHttpEngine::QObjectHandler apiHandler; + apiHandler.registerMethod(PSM_Receive, [this](QHttpEngine::Socket *socket) + { + this->PedestrianRequestHandler(socket); + }); + QHttpEngine::Server server(&apiHandler); + + if (!server.listen(address, webport)) + { qCritical("Unable to listen on the specified port."); return 1; } - return a.exec(); + runningWebService = true; + return QCoreApplication::exec(); } -PedestrianPlugin::~PedestrianPlugin() +void PedestrianPlugin::StopWebService() { - if (_signSimClient != NULL) - delete _signSimClient; + if (runningWebService) + { + PLOG(logDEBUG) << "Stopping WebService"; + QCoreApplication::quit(); + } + runningWebService = false; } void PedestrianPlugin::UpdateConfigSettings() @@ -143,54 +164,56 @@ void PedestrianPlugin::UpdateConfigSettings() // This method does NOT execute in the main thread, so variables must be protected // (e.g. using std::atomic, std::mutex, etc.). - int instance; - std::lock_guard lock(_cfgLock); - - GetConfigValue("WebServiceIP",webip); - GetConfigValue("WebServicePort",webport); - GetConfigValue("WebSocketHost",webSocketIP); - GetConfigValue("WebSocketPort",webSocketURLExt); - GetConfigValue("Instance", instance); - GetConfigValue("DataProvider", dataprovider); - GetConfigValue("FLIRCameraRotation",cameraRotation); - GetConfigValue("HostString",hostString); - - PLOG(logDEBUG) << "Pedestrian data provider: "<< dataprovider.c_str() << std::endl; - PLOG(logDEBUG) << "Before creating websocket to: " << webSocketIP.c_str() << " on port: " << webSocketURLExt.c_str() << std::endl; + GetConfigValue("WebServiceIP", webip, &_cfgLock); + GetConfigValue("WebServicePort", webport, &_cfgLock); + GetConfigValue("WebSocketHost", webSocketIP, &_cfgLock); + GetConfigValue("WebSocketPort", webSocketURLExt, &_cfgLock); + GetConfigValue("DataProvider", dataprovider, &_cfgLock); + GetConfigValue("FLIRCameraRotation", cameraRotation, &_cfgLock); + GetConfigValue("HostString", hostString, &_cfgLock); + PLOG(logDEBUG) << "Pedestrian data provider: " << dataprovider; + if (dataprovider.compare("FLIR") == 0) - { - try - { - std::thread webthread(&PedestrianPlugin::StartWebSocket,this); - PLOG(logDEBUG) << "Thread started!!: " << std::endl; - - webthread.detach(); // wait for the thread to finish - - std::thread xmlThread(&PedestrianPlugin::checkXML,this); - PLOG(logDEBUG) << "XML Thread started!!: " << std::endl; - - xmlThread.detach(); // wait for the thread to finish + { + StopWebService(); + if (!runningWebSocket) + { + PLOG(logDEBUG) << "Starting WebSocket Thread"; + std::thread webSocketThread(&PedestrianPlugin::StartWebSocket, this); + PLOG(logDEBUG) << "WebSocket Thread started!!"; + + PLOG(logDEBUG) << "Starting XML Thread"; + std::thread xmlThread(&PedestrianPlugin::checkXML, this); + PLOG(logDEBUG) << "XML Thread started!!"; + webSocketThread.join(); // wait for the thread to finish + xmlThread.join(); // wait for the thread to finish + } + } - } - catch(const std::exception& e) - { - PLOG(logERROR) << "Error connecting to websocket: " << e.what() << std::endl; - } - - - } - else // default if PSM XML data consumed using the webservice implementation + else if (dataprovider.compare("PSM") == 0) // default if PSM XML data consumed using the webservice implementation { - std::thread webthread(&PedestrianPlugin::StartWebService,this); - webthread.detach(); // wait for the thread to finish + StopWebSocket(); + if (!runningWebService) + { + PLOG(logDEBUG) << "Starting WebService Thread"; + std::thread webServiceThread(&PedestrianPlugin::StartWebService, this); + PLOG(logDEBUG) << "WebService Thread started"; + webServiceThread.join(); // wait for the thread to finish + } + } + else + { + PLOG(logWARNING) << "Invalid configured data provider. Pedestrian Plugin requires valid data provider (FLIR, PSM)!"; + StopWebService(); + StopWebSocket(); } + } void PedestrianPlugin::OnConfigChanged(const char *key, const char *value) { PluginClient::OnConfigChanged(key, value); - UpdateConfigSettings(); } void PedestrianPlugin::OnStateChange(IvpPluginState state) @@ -200,103 +223,51 @@ void PedestrianPlugin::OnStateChange(IvpPluginState state) if (state == IvpPluginState_registered) { UpdateConfigSettings(); - SetStatus("ReceivedMaps", 0); } } -void PedestrianPlugin::HandleMapDataMessage(MapDataMessage &msg, routeable_message &routeableMsg) +void PedestrianPlugin::BroadcastPsm(const std::string &psmJson) { - static std::atomic count {0}; - - int mapCount = count; - SetStatus("ReceivedMaps", mapCount); -} - - - -void PedestrianPlugin::HandleBasicSafetyMessage(BsmMessage &msg, routeable_message &routeableMsg) { - PLOG(logDEBUG)<<"HandleBasicSafetyMessage"; -} - - -void PedestrianPlugin::BroadcastPsm(char * psmJson) { //overloaded - - PsmMessage psmmessage; PsmEncodedMessage psmENC; tmx::message_container_type container; - std::unique_ptr msg; - - try - { - std::stringstream ss; - ss << psmJson; - container.load(ss); - psmmessage.set_contents(container.get_storage().get_tree()); + std::stringstream ss; + ss << psmJson; - const std::string psmString(psmJson); + container.load(ss); + psmmessage.set_contents(container.get_storage().get_tree()); - psmENC.encode_j2735_message(psmmessage); + psmENC.encode_j2735_message(psmmessage); - msg.reset(); - msg.reset(dynamic_cast(factory.NewMessage(api::MSGSUBTYPE_PERSONALSAFETYMESSAGE_STRING))); + auto msg = std::make_unique(); + msg.reset(); + msg.reset(dynamic_cast(factory.NewMessage(api::MSGSUBTYPE_PERSONALSAFETYMESSAGE_STRING))); - string enc = psmENC.get_encoding(); - msg->refresh_timestamp(); - msg->set_payload(psmENC.get_payload_str()); - msg->set_encoding(enc); - msg->set_flags(IvpMsgFlags_RouteDSRC); - msg->addDsrcMetadata(0x8002); - msg->refresh_timestamp(); + std::string enc = psmENC.get_encoding(); + msg->refresh_timestamp(); + msg->set_payload(psmENC.get_payload_str()); + msg->set_encoding(enc); + msg->set_flags(IvpMsgFlags_RouteDSRC); + msg->addDsrcMetadata(tmx::messages::api::personalSafetyMessage_PSID); + msg->refresh_timestamp(); - routeable_message *rMsg = dynamic_cast(msg.get()); - BroadcastMessage(*rMsg); + auto *rMsg = dynamic_cast(msg.get()); - PLOG(logINFO) << " Pedestrian Plugin :: Broadcast PSM:: " << psmENC.get_payload_str() << std::endl; - } - catch(const std::exception& e) - { - PLOG(logWARNING) << "Error: " << e.what() << " broadcasting PSM for xml: " << psmJson << std::endl; - - } - - + BroadcastMessage(*rMsg); + PLOG(logINFO) << " Pedestrian Plugin :: Broadcast PSM:: " << psmENC.get_payload_str(); } -/** - * Write HTTP response. - */ -void PedestrianPlugin::writeResponse(int responseCode , QHttpEngine::Socket *socket) { - socket->setStatusCode(responseCode); - socket->writeHeaders(); - if(socket->isOpen()){ - socket->close(); - } - -} - - int PedestrianPlugin::Main() { PLOG(logINFO) << "Starting plugin."; - uint msCount = 0; while (_plugin->state != IvpPluginState_error) { - - msCount += 10; - if (_plugin->state == IvpPluginState_registered) { - PersonalSafetyMessage psm_1; - PersonalSafetyMessage &psm = psm_1; - //BroadcastPsm(psm); - this_thread::sleep_for(chrono::milliseconds(100)); - - msCount = 0; } } diff --git a/src/v2i-hub/PedestrianPlugin/src/include/PedestrianPlugin.hpp b/src/v2i-hub/PedestrianPlugin/src/include/PedestrianPlugin.hpp index 8964496f0..31d1a7667 100644 --- a/src/v2i-hub/PedestrianPlugin/src/include/PedestrianPlugin.hpp +++ b/src/v2i-hub/PedestrianPlugin/src/include/PedestrianPlugin.hpp @@ -2,7 +2,7 @@ // Name : PedestrianPlugin.cpp // Author : FHWA Saxton Transportation Operations Laboratory // Version : -// Copyright : Copyright (c) 2019 FHWA Saxton Transportation Operations Laboratory. All rights reserved. +// Copyright : Copyright (c) 2024 FHWA Saxton Transportation Operations Laboratory. All rights reserved. // Description : Pedestrian Plugin //========================================================================== #pragma once @@ -13,13 +13,9 @@ #include #include -#include -#include -#include -#include +#include #include #include -#include #include #include @@ -46,26 +42,77 @@ #include #include - -using namespace std; -using namespace tmx; -using namespace tmx::messages; -using namespace tmx::utils; -using namespace OpenAPI; - namespace PedestrianPlugin { /** - * This plugin is an example to demonstrate the capabilities of a TMX plugin. + * @brief Plugin used to encode Personal Safety Messages (PSMs) + * from an XML input via HTTP POST or metadata received from the FLIR API. */ class PedestrianPlugin: public PluginClient { public: - PedestrianPlugin(std::string); - PedestrianPlugin(); - virtual ~PedestrianPlugin(); - int Main(); + explicit PedestrianPlugin(const std::string &name); + int Main() override; + +protected: + /** + * @brief Called everytime a configuration value is changed for the plugin. + */ + void UpdateConfigSettings(); + // Virtual method overrides START + /** + * @brief Overrides PluginClient OnConfigChanged(const char *key, const char *value) method + * and calls UpdateConfigSettings() on each configuration change. + * @param key string key of the configuration value that has changed. + * @param value new value of the configuration that has changed. + */ + void OnConfigChanged(const char *key, const char *value) override; + /** + * @brief Overrides PluginClient OnStateChange(IvpPluginState state) method. + * @param state new state of the plugin. + */ + void OnStateChange(IvpPluginState state) override; + // Virtual method overrides END. + + /** + * @brief Add DSRC metadata and broadcast PSM + * @param string PSM in JSON format + */ + void BroadcastPsm(const std::string &psmJson); + + /** + * @brief Starts WebService to handle incoming HTTP POST requests. + */ + int StartWebService(); + /** + * @brief Starts Asyncronous WebSocket Client to connect to FLIR WebSocket Server. + */ + int StartWebSocket(); + /** + * @brief Stops WebService before FLIR WebSocket enabled. + */ + void StopWebService(); + /** + * @brief Stops WebSocket Client session before HTTP POST WebService is enabled. + */ + void StopWebSocket(); + /** + * @brief Handles HTTP POST requests and sends response to sender. + * If valid XML is received, sends data to BroadcastPsm. + * @param QHttpEngine::Socket Socket created in WebService. + */ + void PedestrianRequestHandler(QHttpEngine::Socket *socket); + /** + * @brief Sends new PSM XMLs generated by FLIR WebSocket session to BroadcastPsm. + */ + void checkXML(); + +private: + J2735MessageFactory factory; + + std::mutex _cfgLock; + uint16_t webport; std::string webip; std::string webSocketIP; @@ -75,38 +122,15 @@ class PedestrianPlugin: public PluginClient std::shared_ptr flirSession; std::string hostString; -protected: - void UpdateConfigSettings(); - - // Virtual method overrides. - void OnConfigChanged(const char *key, const char *value); - void OnStateChange(IvpPluginState state); - - void HandleMapDataMessage(MapDataMessage &msg, routeable_message &routeableMsg); - void HandleBasicSafetyMessage(BsmMessage &msg, routeable_message &routeableMsg); - void BroadcastPsm(char *psmJson); - - int StartWebService(); - void PedestrianRequestHandler(QHttpEngine::Socket *socket); - void writeResponse(int responseCode , QHttpEngine::Socket *socket); - - int StartWebSocket(); - - void OnWebSocketConnected(); - void OnWebSocketDataReceived(QString message); - void OnWebSocketClosed(); - - int checkXML(); - + bool runningWebSocket = false; + bool runningWebService = false; + // The io_context is required for all I/O + net::io_context ioc; -private: - tmx::utils::UdpClient *_signSimClient = NULL; - J2735MessageFactory factory; - - + // TODO: Set an endpoint for PSM post messages and update documentation. + // API URL to accept PSM XML + const QString PSM_Receive = ""; }; -std::mutex _cfgLock; }; - From 576e484a89033b1b406901e19c16d509beab12d6 Mon Sep 17 00:00:00 2001 From: dan-du-car <62157949+dan-du-car@users.noreply.github.com> Date: Thu, 22 Aug 2024 08:23:36 -0400 Subject: [PATCH 12/24] Update detection classifications in MUSTSensorDriverPlugin (#631) # PR Details ## Description Update detection classifications in MUSTSensorDriverPlugin ## Related Issue https://usdot-carma.atlassian.net/browse/FCP-28 ## Motivation and Context Freight Cooperative Perception ## How Has This Been Tested? Unit test ## Types of changes - [x] Defect fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that cause existing functionality to change) ## Checklist: - [ ] I have added any new packages to the sonar-scanner.properties file - [ ] My change requires a change to the documentation. - [ ] I have updated the documentation accordingly. - [x] I have read the **CONTRIBUTING** document. [V2XHUB Contributing Guide](https://github.com/usdot-fhwa-OPS/V2X-Hub/blob/develop/Contributing.md) - [ ] I have added tests to cover my changes. - [ ] All new and existing tests passed. --- .../MUSTSensorDriverPlugin/src/MUSTSensorDetection.h | 8 +++++++- .../test/TestMUSTSensorDetection.cpp | 6 ++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/v2i-hub/MUSTSensorDriverPlugin/src/MUSTSensorDetection.h b/src/v2i-hub/MUSTSensorDriverPlugin/src/MUSTSensorDetection.h index 37e0858ec..9140b0252 100644 --- a/src/v2i-hub/MUSTSensorDriverPlugin/src/MUSTSensorDetection.h +++ b/src/v2i-hub/MUSTSensorDriverPlugin/src/MUSTSensorDetection.h @@ -16,6 +16,9 @@ namespace MUSTSensorDriverPlugin { enum class DetectionClassification { SEDAN, TRUCK, + BUS, + PICKUP_TRUCK, + PEDESTRIAN, VAN, NA }; @@ -46,7 +49,10 @@ namespace MUSTSensorDriverPlugin { const static std::unordered_map stringToDetectionClassificationMap = { {"sedan", DetectionClassification::SEDAN}, {"truck", DetectionClassification::TRUCK}, - {"van", DetectionClassification::VAN} + {"van", DetectionClassification::VAN}, + {"bus", DetectionClassification::BUS}, + {"pickup truck", DetectionClassification::PICKUP_TRUCK}, + {"pedestrian", DetectionClassification::PEDESTRIAN} }; /** diff --git a/src/v2i-hub/MUSTSensorDriverPlugin/test/TestMUSTSensorDetection.cpp b/src/v2i-hub/MUSTSensorDriverPlugin/test/TestMUSTSensorDetection.cpp index 0f745df77..8a62e1c99 100644 --- a/src/v2i-hub/MUSTSensorDriverPlugin/test/TestMUSTSensorDetection.cpp +++ b/src/v2i-hub/MUSTSensorDriverPlugin/test/TestMUSTSensorDetection.cpp @@ -18,6 +18,9 @@ TEST(TestMUSTSensorDetection, fromStringToDetectionClassification) EXPECT_EQ(DetectionClassification::SEDAN, fromStringToDetectionClassification("sedan")); EXPECT_EQ(DetectionClassification::VAN, fromStringToDetectionClassification("van")); EXPECT_EQ(DetectionClassification::TRUCK, fromStringToDetectionClassification("truck")); + EXPECT_EQ(DetectionClassification::BUS, fromStringToDetectionClassification("bus")); + EXPECT_EQ(DetectionClassification::PICKUP_TRUCK, fromStringToDetectionClassification("pickup truck")); + EXPECT_EQ(DetectionClassification::PEDESTRIAN, fromStringToDetectionClassification("pedestrian")); EXPECT_EQ(DetectionClassification::NA, fromStringToDetectionClassification("not_a_classification")); } @@ -78,6 +81,9 @@ TEST(TestMUSTSensorDetection, detectionClassificationToSensorDetectedObjectType EXPECT_STRCASEEQ("SEDAN", detectionClassificationToSensorDetectedObjectType(DetectionClassification::SEDAN).c_str()); EXPECT_STRCASEEQ("VAN", detectionClassificationToSensorDetectedObjectType(DetectionClassification::VAN).c_str()); EXPECT_STRCASEEQ("TRUCK", detectionClassificationToSensorDetectedObjectType(DetectionClassification::TRUCK).c_str()); + EXPECT_STRCASEEQ("BUS", detectionClassificationToSensorDetectedObjectType(DetectionClassification::BUS).c_str()); + EXPECT_STRCASEEQ("PICKUP TRUCK", detectionClassificationToSensorDetectedObjectType(DetectionClassification::PICKUP_TRUCK).c_str()); + EXPECT_STRCASEEQ("PEDESTRIAN", detectionClassificationToSensorDetectedObjectType(DetectionClassification::PEDESTRIAN).c_str()); EXPECT_THROW(detectionClassificationToSensorDetectedObjectType(DetectionClassification::NA).c_str(), std::runtime_error); } From 162d0d7ac13ad3097198ebbf60b42b6bedd5e3f7 Mon Sep 17 00:00:00 2001 From: Peyton Johnson Date: Thu, 22 Aug 2024 17:00:04 -0400 Subject: [PATCH 13/24] Added configurations for newest port drayage layout for C1T with two lanes --- configuration/docker-compose.yml | 1 - .../port_drayage_lane1.sql | 4 +- .../port_drayage_lane2.sql | 4 +- .../port_drayage_lane3.sql | 49 ------------------- 4 files changed, 4 insertions(+), 54 deletions(-) delete mode 100644 configuration/mysql/garage_port_drayage/port_drayage_lane3.sql diff --git a/configuration/docker-compose.yml b/configuration/docker-compose.yml index 381b52a67..f96bddbfd 100755 --- a/configuration/docker-compose.yml +++ b/configuration/docker-compose.yml @@ -17,7 +17,6 @@ services: - ./mysql/garage_port_drayage/port_drayage.sql:/docker-entrypoint-initdb.d/port_drayage.sql - ./mysql/garage_port_drayage/port_drayage_lane1.sql:/docker-entrypoint-initdb.d/port_drayage_lane1.sql - ./mysql/garage_port_drayage/port_drayage_lane2.sql:/docker-entrypoint-initdb.d/port_drayage_lane2.sql - - ./mysql/garage_port_drayage/port_drayage_lane3.sql:/docker-entrypoint-initdb.d/port_drayage_lane3.sql - mysql-datavolume:/var/lib/mysql php: diff --git a/configuration/mysql/garage_port_drayage/port_drayage_lane1.sql b/configuration/mysql/garage_port_drayage/port_drayage_lane1.sql index 3d4431784..6f99b950c 100644 --- a/configuration/mysql/garage_port_drayage/port_drayage_lane1.sql +++ b/configuration/mysql/garage_port_drayage/port_drayage_lane1.sql @@ -26,7 +26,7 @@ CREATE TABLE `first_action` ( ) ENGINE=InnoDB DEFAULT CHARSET=latin1; LOCK TABLES `first_action` WRITE; -INSERT INTO `first_action` VALUES ('C1T-1','NULL',-1.4,-1.4,'ENTER_STAGING_AREA','one','two'); +INSERT INTO `first_action` VALUES ('C1T-1','NULL',-2.3,0.7,'ENTER_STAGING_AREA','one','two'); UNLOCK TABLES; -- @@ -45,5 +45,5 @@ CREATE TABLE `freight` ( ) ENGINE=InnoDB DEFAULT CHARSET=latin1; LOCK TABLES `freight` WRITE; -INSERT INTO `freight` VALUES ('C1T-1','NULL',-2.4,-2.4,'EXIT_PORT','zero','one'),('C1T-1','NULL',-1.4,-1.4,'ENTER_STAGING_AREA','one','two'),('C1T-1','CARGO_A',-0.4,-0.4,'PICKUP','two','three'),('C1T-1','CARGO_A',-3.4,-1.4,'EXIT_STAGING_AREA','three','four'),('C1T-1','CARGO_A',-3.4,-3.4,'ENTER_PORT','four','five'),('C1T-1','CARGO_A',-1.4,-6.4,'DROPOFF','five','six'),('C1T-1','CARGO_B',0.2,-6.2,'PICKUP','six','seven'),('C1T-1','CARGO_B',0.6,-5.4,'PORT_CHECKPOINT','seven','eight'),('C1T-1','CARGO_B',-2.4,-2.4,'EXIT_PORT','eight','nine'); +INSERT INTO `freight` VALUES ('C1T-1','NULL',-0.4,-0.4,'EXIT_PORT','zero','one'),('C1T-1','NULL',-2.3,0.7,'ENTER_STAGING_AREA','one','two'),('C1T-1','CARGO_A',-3.4,-1.4,'PICKUP','two','three'),('C1T-1','CARGO_A',-3.4,-3.4,'EXIT_STAGING_AREA','three','four'),('C1T-1','CARGO_A',-3.4,-5.4,'ENTER_PORT','four','five'),('C1T-1','CARGO_A',-0.4,-6.4,'DROPOFF','five','six'),('C1T-1','CARGO_B',1.6,-4.7,'PICKUP','six','seven'),('C1T-1','CARGO_B',0.4,-3.4,'PORT_CHECKPOINT','seven','eight'),('C1T-1','CARGO_B',-0.3,0.2,'EXIT_PORT','eight','nine'); UNLOCK TABLES; diff --git a/configuration/mysql/garage_port_drayage/port_drayage_lane2.sql b/configuration/mysql/garage_port_drayage/port_drayage_lane2.sql index bf5acb38c..0e111bd22 100644 --- a/configuration/mysql/garage_port_drayage/port_drayage_lane2.sql +++ b/configuration/mysql/garage_port_drayage/port_drayage_lane2.sql @@ -26,7 +26,7 @@ CREATE TABLE `first_action` ( ) ENGINE=InnoDB DEFAULT CHARSET=latin1; LOCK TABLES `first_action` WRITE; -INSERT INTO `first_action` VALUES ('C1T-1','NULL',-1.4,-1.4,'ENTER_STAGING_AREA','one','two'); +INSERT INTO `first_action` VALUES ('C1T-1','NULL',-2.3,0.7,'ENTER_STAGING_AREA','one','two'); UNLOCK TABLES; -- @@ -45,5 +45,5 @@ CREATE TABLE `freight` ( ) ENGINE=InnoDB DEFAULT CHARSET=latin1; LOCK TABLES `freight` WRITE; -INSERT INTO `freight` VALUES ('C1T-1','NULL',-2.4,-2.4,'EXIT_PORT','zero','one'),('C1T-1','NULL',-1.4,-1.4,'ENTER_STAGING_AREA','one','two'),('C1T-1','CARGO_A',-0.4,-0.4,'PICKUP','two','three'),('C1T-1','CARGO_A',-3.4,-1.4,'EXIT_STAGING_AREA','three','four'),('C1T-1','CARGO_A',-3.4,-3.4,'ENTER_PORT','four','five'),('C1T-1','CARGO_A',-1.4,-6.4,'DROPOFF','five','six'),('C1T-1','CARGO_B',1.2,-6.2,'PICKUP','six','seven'),('C1T-1','CARGO_B',1.6,-5.4,'PORT_CHECKPOINT','seven','eight'),('C1T-1','CARGO_B',-2.4,-2.4,'EXIT_PORT','eight','nine'); +INSERT INTO `freight` VALUES ('C1T-1','NULL',-0.4,-0.4,'EXIT_PORT','zero','one'),('C1T-1','NULL',-2.3,0.7,'ENTER_STAGING_AREA','one','two'),('C1T-1','CARGO_A',-3.4,-1.4,'PICKUP','two','three'),('C1T-1','CARGO_A',-3.4,-3.4,'EXIT_STAGING_AREA','three','four'),('C1T-1','CARGO_A',-3.4,-5.4,'ENTER_PORT','four','five'),('C1T-1','CARGO_A',-0.4,-6.4,'DROPOFF','five','six'),('C1T-1','CARGO_B',2.6,-4.7,'PICKUP','six','seven'),('C1T-1','CARGO_B',0.4,-3.4,'PORT_CHECKPOINT','seven','eight'),('C1T-1','CARGO_B',-0.3,0.2,'EXIT_PORT','eight','nine'); UNLOCK TABLES; diff --git a/configuration/mysql/garage_port_drayage/port_drayage_lane3.sql b/configuration/mysql/garage_port_drayage/port_drayage_lane3.sql deleted file mode 100644 index cb49f0498..000000000 --- a/configuration/mysql/garage_port_drayage/port_drayage_lane3.sql +++ /dev/null @@ -1,49 +0,0 @@ --- MySQL 8.0 for Linux amd64 (x86_64) and arm64 (aarch64) --- --- Host: 127.0.0.1 Database: PORT_DRAYAGE --- ------------------------------------------------------ --- Server version 7.6.0 --- Current Database: `PORT_DRAYAGE` --- - -CREATE DATABASE /*!32312 IF NOT EXISTS*/ `PORT_DRAYAGE` /*!40100 DEFAULT CHARACTER SET latin1 */; - -USE `PORT_DRAYAGE`; - --- --- Table structure for table `first_action` --- - -DROP TABLE IF EXISTS `first_action`; -CREATE TABLE `first_action` ( - `cmv_id` varchar(20) NOT NULL, - `cargo_id` varchar(20) DEFAULT NULL, - `destination_lat` decimal(9,7) NOT NULL, - `destination_long` decimal(9,7) NOT NULL, - `operation` varchar(20) NOT NULL, - `action_id` varchar(36) NOT NULL, - `next_action` varchar(36) NOT NULL -) ENGINE=InnoDB DEFAULT CHARSET=latin1; - -LOCK TABLES `first_action` WRITE; -INSERT INTO `first_action` VALUES ('C1T-1','NULL',-1.4,-1.4,'ENTER_STAGING_AREA','one','two'); -UNLOCK TABLES; - --- --- Table structure for table `freight` --- - -DROP TABLE IF EXISTS `freight`; -CREATE TABLE `freight` ( - `cmv_id` varchar(20) NOT NULL, - `cargo_id` varchar(20) DEFAULT NULL, - `destination_lat` decimal(9,7) NOT NULL, - `destination_long` decimal(9,7) NOT NULL, - `operation` varchar(20) NOT NULL, - `action_id` varchar(36) NOT NULL, - `next_action` varchar(36) NOT NULL -) ENGINE=InnoDB DEFAULT CHARSET=latin1; - -LOCK TABLES `freight` WRITE; -INSERT INTO `freight` VALUES ('C1T-1','NULL',-2.4,-2.4,'EXIT_PORT','zero','one'),('C1T-1','NULL',-1.4,-1.4,'ENTER_STAGING_AREA','one','two'),('C1T-1','CARGO_A',-0.4,-0.4,'PICKUP','two','three'),('C1T-1','CARGO_A',-3.4,-1.4,'EXIT_STAGING_AREA','three','four'),('C1T-1','CARGO_A',-3.4,-3.4,'ENTER_PORT','four','five'),('C1T-1','CARGO_A',-1.4,-6.4,'DROPOFF','five','six'),('C1T-1','CARGO_B',2.4,-6.2,'PICKUP','six','seven'),('C1T-1','CARGO_B',2.6,-5.4,'PORT_CHECKPOINT','seven','eight'),('C1T-1','CARGO_B',-2.4,-2.4,'EXIT_PORT','eight','nine'); -UNLOCK TABLES; From 1615eb5dcf61e3bb34fe58b20e23eab7fe8a2816 Mon Sep 17 00:00:00 2001 From: dan-du-car <62157949+dan-du-car@users.noreply.github.com> Date: Wed, 28 Aug 2024 11:37:25 -0400 Subject: [PATCH 14/24] MustSensorDriverPlugin: Convert the classification confidence to decimal from percentage unit % (#634) # PR Details ## Description The MUST Sensor provides classification confidence as a decimal value between 0 and 100 % while the Sensor Detected Object message expects confidence as a decimal value between 0 and 1. PR includes appropriate conversion logic ## Related Issue [FCP-30](https://usdot-carma.atlassian.net/browse/FCP-30) ## Motivation and Context Provide correct data translation between MUST sensor output and Sensor Detected Object message for data accuracy in the SDMS Pipeline ## How Has This Been Tested? Unit Testing and Local Integration Testing ## Types of changes - [x] Defect fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that cause existing functionality to change) ## Checklist: - [ ] I have added any new packages to the sonar-scanner.properties file - [ ] My change requires a change to the documentation. - [ ] I have updated the documentation accordingly. - [x] I have read the **CONTRIBUTING** document. [V2XHUB Contributing Guide](https://github.com/usdot-fhwa-OPS/V2X-Hub/blob/develop/Contributing.md) - [ ] I have added tests to cover my changes. - [x] All new and existing tests passed. [FCP-30]: https://usdot-carma.atlassian.net/browse/FCP-30?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ --- src/v2i-hub/CARMAStreetsPlugin/src/CARMAStreetsPlugin.cpp | 2 +- src/v2i-hub/MUSTSensorDriverPlugin/src/MUSTSensorDetection.cpp | 2 +- .../MUSTSensorDriverPlugin/test/TestMUSTSensorDetection.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/v2i-hub/CARMAStreetsPlugin/src/CARMAStreetsPlugin.cpp b/src/v2i-hub/CARMAStreetsPlugin/src/CARMAStreetsPlugin.cpp index 9a23f0324..ab5d3d71f 100755 --- a/src/v2i-hub/CARMAStreetsPlugin/src/CARMAStreetsPlugin.cpp +++ b/src/v2i-hub/CARMAStreetsPlugin/src/CARMAStreetsPlugin.cpp @@ -711,7 +711,7 @@ void CARMAStreetsPlugin::HandleSimulatedSensorDetectedMessage(SensorDetectedObje // is currently out of scope. TMX Messages should be correctly serialize to // and from json. This temporary fix simply using regex to look for numeric, // null, and bool values and removes the quotations around them. - PLOG(logDEBUG) << "Produce sensor detected message in JSON format: " << msg.to_string() <(detection.timestamp*1000)); // convert decimal seconds to int milliseconds. detectedObject.set_velocity(headingSpeedToVelocity(detection.heading, detection.speed)); detectedObject.set_type(detectionClassificationToSensorDetectedObjectType(detection.cl)); diff --git a/src/v2i-hub/MUSTSensorDriverPlugin/test/TestMUSTSensorDetection.cpp b/src/v2i-hub/MUSTSensorDriverPlugin/test/TestMUSTSensorDetection.cpp index 8a62e1c99..d065d31a3 100644 --- a/src/v2i-hub/MUSTSensorDriverPlugin/test/TestMUSTSensorDetection.cpp +++ b/src/v2i-hub/MUSTSensorDriverPlugin/test/TestMUSTSensorDetection.cpp @@ -66,7 +66,7 @@ TEST(TestMUSTSensorDetection, mustDetectionToSensorDetectedObject ) { auto sensorDetectedObject = mustDetectionToSensorDetectedObject(detection, "MUSTSensor1", "PROJ String"); EXPECT_EQ(detection.trackID, sensorDetectedObject.get_objectId()); - EXPECT_DOUBLE_EQ(detection.confidence, sensorDetectedObject.get_confidence()); + EXPECT_DOUBLE_EQ(detection.confidence/100.0, sensorDetectedObject.get_confidence()); EXPECT_DOUBLE_EQ(detection.position_x, sensorDetectedObject.get_position().x); EXPECT_DOUBLE_EQ(detection.position_y, sensorDetectedObject.get_position().y); EXPECT_NEAR(4.33, sensorDetectedObject.get_velocity().y, 0.001); From 6c4151e74e7d473cc08871a3b87c7f57dd93455b Mon Sep 17 00:00:00 2001 From: Saikrishna Bairamoni <84093461+SaikrishnaBairamoni@users.noreply.github.com> Date: Thu, 29 Aug 2024 01:28:13 -0400 Subject: [PATCH 15/24] Sync develop to master (#635) # PR Details Sync the develop branch with the master branch after completing both the Miura Xil Cloud release and the Neon WFD release. ## Description ## Related Issue ## Motivation and Context ## How Has This Been Tested? ## Types of changes - [ ] Defect fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that cause existing functionality to change) ## Checklist: - [ ] I have added any new packages to the sonar-scanner.properties file - [ ] My change requires a change to the documentation. - [ ] I have updated the documentation accordingly. - [ ] I have read the **CONTRIBUTING** document. [V2XHUB Contributing Guide](https://github.com/usdot-fhwa-OPS/V2X-Hub/blob/develop/Contributing.md) - [ ] I have added tests to cover my changes. - [ ] All new and existing tests passed. --------- Co-authored-by: maefromm --- docs/Release_notes.md | 71 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/docs/Release_notes.md b/docs/Release_notes.md index d7b326b3b..385b8849e 100644 --- a/docs/Release_notes.md +++ b/docs/Release_notes.md @@ -1,6 +1,77 @@ V2X-Hub Release Notes --------------------------------- +Version 7.8.0, released Aug 26th, 2024 +-------------------------------------------------------- + +**Summary:** +V2X Hub release 7.8.0 includes significant enhancements such as a new telematics module for streaming V2X Hub data into the telematics server and Influx database, and an RSU Health Monitor to stream J2735 data from V2X Hub to the cloud in near real-time. The telematics plugin now subscribes to all TMX messages from V2X Hub, improving data integration. Additionally, this release addresses key issues, such as fixing the telematics bridge memory usage and correcting the handling of null topics, ensuring accurate data management and system reliability. + +Enhancement in this release: + +- V2X-Hub PR 564: Implemented a RSU Health Monitor Plugin to directly interface with RSUs connected to V2X Hub via SNMP protocol. +- V2X-Hub PR 565: Implemented a telematics plugin to subscribe to all TMX messages from V2X Hub. +- V2X-Hub PR 567: Implemented the telematic bridge to stream J2735 data from V2XHub to the cloud in near real-time. This includes forwarding JSON messages, + registering with WFD cloud services, mapping TMX message types to topics, and streaming requested TMX messages in JSON format. +- Issue 591: Created a telematics module to stream V2X Hub data into the telematics server and Influx database. This includes connecting to V2X Hub, + subscribing/unsubscribing to topics, and streaming data to the telematics tool. +- V2X-Hub PR 599: Fixed memory usage issue in the telematics bridge, which increased indefinitely when forwarding messages to the telematics tool. +- V2X-Hub PR 606: Updated the RSU Health Monitor plugin to support monitoring the health status of multiple RSUs per V2X Hub instance. +- V2X-Hub PR 613: Added configuration parameters for RSU Health Monitor Plugin to identify the source of payload when multiple RSUs are connected. + +Fixes in this release: + +- N/A + +Version 7.7.0, released Aug 15th, 2024 +-------------------------------------------------------- + +**Summary:** +V2X Hub release 7.7.0 introduces key enhancements and fixes to improve data streaming, upgrade MySQL to version 8.0, and support multi-architecture Docker deployments. This release includes updates to initialization scripts and addresses clock reference issues in simulation mode. Additionally, this release removes obsolete Docker tags and makes the SENSOR_JSON_FILE_PATH an optional environment variable to enhance flexibility in simulation setups. + +Enhancement in this release: + +- V2X-Hub PR 612: Updated MySQL to 8.0 to support multi-architecture Docker deployment, removed architecture-based configuration folders, and updated initialization scripts. +- V2X-Hub PR 611: Updated TCR message oldest field to use data sent by carma-platform in simulation mode to address clock reference issues. +- V2X-Hub PR 615: Updated the initialization script to generate a .env file based on user input. + +Fixes in this release: + +- V2X-Hub PR 614: Removed the "version:" tag at the top of the docker-compose files. These are no longer needed and are obsolete in current and future versions of Docker. +- V2X-Hub PR 616: Updated SENSOR_JSON_FILE_PATH as optional environment variable in simulation since spawning sensors is not required for the base line functionality of V2X-Hub. Before this update CDASimAdapter would accept empty SENSOR_JSON_FILE_PATH files but not missing ones. + + +Version 7.6.0, released April 10th, 2024 +-------------------------------------------------------- + +**Summary:** +V2X Hub release 7.6.0 includes functionality for integrating V2X-Hub with simulated sensors in CDASim. CDASim can now publish detection data to V2X-Hub for ingestion. Furthermore, this release added a functionality to encode/decode J3224 Sensor Data Sharing Message (SDSM) and forward these messages between CARMA-Streets and CDASim for Vulnerable Road User (VRU) Cooperative Perception testing in simulation. Unrelated improvements include initial integration with telematics tool and general system improvements. + +**V2X Hub CDASim Functionalities ** + +Enhancements in this release: + +- Issue 549/PR 550: Moved Kafka time producer from CDASimAdapter to CARMA-Streets plugin, and updated CARMA-Streets Plugin to be simulation time aware. CDASim Adapter was previously forwarding time sync messages directly to CARMA-Streets. Improved CARMA Streets Plugin to extend our PluginClientTimeAware class to make it aware of simulation. +- Issue 590: Add CDA Sim simulated sensor integration. Allow V2X-Hub to consume detection data from simulated sensor in CDASim. +- Issue 589: Implement support for CARMA-Street SDSM functionality. This includes adding SDSM encoding/decoding and message forwarding between CARMA-Streets and CDASim + +Fixes in this release: + +- Issue 538 & Issue543 & PR 547: Fixed CARMA Streets Plugin Kafka Consumers. 1) Removed the Kafka consumer/producer initialization during config parameter update. 2) Replaced consumer creation with v2xhub kafka_client library. 3) Added producer creation in kafka_client library. +- PR 12 (not V2X Hub Repo): Fixed wait_for_initialization for multiple threads + +Other Enhancements: + +- PR 545: Added PSID metadata to BSMs and SRMs that are sent to the Message Receiver plugin. If RouteMessage is enabled, the messages will be available for the Immediate Forward plugin to use +- PR 592 / Issue 593: Update initialization script to install necessary dependencies, create secret files, prompt user to enter MySQL passwords, and create v2xhub user and open web browser to correct URL. +- Issue 591: V2X-Hub Telematics tool integration. Allows V2X-Hub to stream data to telematics tool for data visualization. Includes development of new RSU Health Monitoring Plugin. +- PR 580: Added a copy command from the dependency container to the final container for /usr/local/include/ which ensures the header files get copied to the final container. + +Known issues related to this release: + +- Issue 540: CDASim Time Synchronization is non-time-regulating. If simulation runs too fast (faster than real-time) for V2X Hub to keep up, V2X Hub can fall behind in time. +- Issue 507: SPaT plugin throws segfault when in SIM MODE. + Version 7.5.1, released June 21st, 2023 -------------------------------------------------------- From dd1a41736317749cd8c6227ab8de6fe11f40d689 Mon Sep 17 00:00:00 2001 From: paulbourelly999 <77466294+paulbourelly999@users.noreply.github.com> Date: Thu, 29 Aug 2024 09:18:01 -0400 Subject: [PATCH 16/24] Add position and velocity covariance matrix for MUST sensor detection data (#638) # PR Details ## Description This PR adds position and velocity covariance matrix to Sensor Detected Object messages created by the MUST Sensor Plugin based on configured parameters for position variance and velocity variance ## Related Issue [FCP-30 ](https://usdot-carma.atlassian.net/browse/FCP-30) ## Motivation and Context For generation of SDSMs position covariance information is required to enable fusion of sensor information ## How Has This Been Tested? Unit testing and local integration testing ## Types of changes - [x] Defect fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that cause existing functionality to change) ## Checklist: - [ ] I have added any new packages to the sonar-scanner.properties file - [x] My change requires a change to the documentation. - [x] I have updated the documentation accordingly. - [x] I have read the **CONTRIBUTING** document. [V2XHUB Contributing Guide](https://github.com/usdot-fhwa-OPS/V2X-Hub/blob/develop/Contributing.md) - [x] I have added tests to cover my changes. - [x] All new and existing tests passed. --- src/v2i-hub/MUSTSensorDriverPlugin/README.md | 7 ++++++ .../MUSTSensorDriverPlugin/manifest.json | 11 +++++++++ .../src/MUSTSensorDetection.cpp | 15 +++++++++++- .../src/MUSTSensorDetection.h | 2 +- .../src/MUSTSensorDriverPlugin.cpp | 4 +++- .../src/MUSTSensorDriverPlugin.h | 4 ++++ .../test/TestMUSTSensorDetection.cpp | 24 +++++++++++++++++-- 7 files changed, 62 insertions(+), 5 deletions(-) diff --git a/src/v2i-hub/MUSTSensorDriverPlugin/README.md b/src/v2i-hub/MUSTSensorDriverPlugin/README.md index fd607eb26..f51522fc3 100644 --- a/src/v2i-hub/MUSTSensorDriverPlugin/README.md +++ b/src/v2i-hub/MUSTSensorDriverPlugin/README.md @@ -34,6 +34,13 @@ This plugin has several configuration parameters. Below these are listed out as > [!NOTE] > Both **CARMA Streets** and our vehicle automatation **CARMA Platform** rely on the PROJ4 library for projecting data between internal local maps coordinate frames and WSG84. Additional documentation on the projection string can be found in PROJ documentation ()()() +**DetectionPositionVariance**: Variance of the reported positon data coming from the sensor. Value is used for cooperative perception messages that enable sensor fusion like the SDMS. + +**DetectionVelocityVariance**: Variance of the reported velocity data coming from the sensor. Value is used for cooperative perception messages that enable sensor fusion like the SDMS. + +> [!NOTE] +> Measurement variance can be calcuted from Sensor accuracy data information. For instance if a sensor position accurancy is +/- 0.5 m, assuming a normal distribution and a 95% confidence interval 0.5 m represent 2 standard deviations. Variance is equal the standard deviation squared so a position measurement accuracy of +/- 0.5 m. the variance would be 0.0625 (`(O.5/2)^2`). + After setting these configuration parameters the plugin can simply be enabled. ## Design diff --git a/src/v2i-hub/MUSTSensorDriverPlugin/manifest.json b/src/v2i-hub/MUSTSensorDriverPlugin/manifest.json index dcd15c60f..31348cb46 100755 --- a/src/v2i-hub/MUSTSensorDriverPlugin/manifest.json +++ b/src/v2i-hub/MUSTSensorDriverPlugin/manifest.json @@ -37,6 +37,17 @@ "key":"ProjectionString", "default":"+proj=tmerc +lat_0=0 +lon_0=0 +k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m +geoidgrids=egm96_15.gtx +vunits=m +no_defs", "description":"Projection string for projecting cartesian detection data into WSG84 coordinates." + }, + { + "key":"DetectionPositionVariance", + "default":"0.0625", + "description":"Variance of the reported positon data coming from the sensor. Value is used for cooperative perception messages that enable sensor fusion like the SDMS." + }, + { + "key":"DetectionVelocityVariance", + "default":"0.0625", + "description":"Variance of the reported velocity data coming from the sensor. Value is used for cooperative perception messages that enable sensor fusion like the SDMS." } + ] } \ No newline at end of file diff --git a/src/v2i-hub/MUSTSensorDriverPlugin/src/MUSTSensorDetection.cpp b/src/v2i-hub/MUSTSensorDriverPlugin/src/MUSTSensorDetection.cpp index 0256bb93a..0c36fad1a 100644 --- a/src/v2i-hub/MUSTSensorDriverPlugin/src/MUSTSensorDetection.cpp +++ b/src/v2i-hub/MUSTSensorDriverPlugin/src/MUSTSensorDetection.cpp @@ -29,7 +29,7 @@ namespace MUSTSensorDriverPlugin { return detection; } - tmx::messages::SensorDetectedObject mustDetectionToSensorDetectedObject(const MUSTSensorDetection &detection, std::string_view sensorId, std::string_view projString) { + tmx::messages::SensorDetectedObject mustDetectionToSensorDetectedObject(const MUSTSensorDetection &detection, std::string_view sensorId, std::string_view projString, double positionVariance, double velocityVariance) { tmx::messages::SensorDetectedObject detectedObject; detectedObject.set_objectId(detection.trackID); tmx::messages::Position pos(detection.position_x, detection.position_y, 0); @@ -38,8 +38,21 @@ namespace MUSTSensorDriverPlugin { detectedObject.set_timestamp(static_cast(detection.timestamp*1000)); // convert decimal seconds to int milliseconds. detectedObject.set_velocity(headingSpeedToVelocity(detection.heading, detection.speed)); detectedObject.set_type(detectionClassificationToSensorDetectedObjectType(detection.cl)); + std::vector> positionCov(3, std::vector(3,tmx::messages::Covariance(0.0) )); + // Set X and Y position variance in covariance matrix + positionCov[0][0] = tmx::messages::Covariance(positionVariance); + positionCov[1][1] = tmx::messages::Covariance(positionVariance); + detectedObject.set_positionCovariance(positionCov); + + // Set X and Y Velocity variance in covariance matrix + std::vector> velocityCov(3, std::vector(3,tmx::messages::Covariance(0.0) )); + velocityCov[0][0] = tmx::messages::Covariance(velocityVariance); + velocityCov[1][1] = tmx::messages::Covariance(velocityVariance); + detectedObject.set_velocityCovariance(velocityCov); + detectedObject.set_sensorId(std::string(sensorId)); detectedObject.set_projString(std::string(projString)); + return detectedObject; } DetectionClassification fromStringToDetectionClassification(const std::string &str) noexcept { diff --git a/src/v2i-hub/MUSTSensorDriverPlugin/src/MUSTSensorDetection.h b/src/v2i-hub/MUSTSensorDriverPlugin/src/MUSTSensorDetection.h index 9140b0252..96de21088 100644 --- a/src/v2i-hub/MUSTSensorDriverPlugin/src/MUSTSensorDetection.h +++ b/src/v2i-hub/MUSTSensorDriverPlugin/src/MUSTSensorDetection.h @@ -116,7 +116,7 @@ namespace MUSTSensorDriverPlugin { * @param projString std::string describing reference point and WGS84 projection of detection information * @return tmx::messages::SensorDetectedObject */ - tmx::messages::SensorDetectedObject mustDetectionToSensorDetectedObject(const MUSTSensorDetection &detection, std::string_view sensorId, std::string_view projString); + tmx::messages::SensorDetectedObject mustDetectionToSensorDetectedObject(const MUSTSensorDetection &detection, std::string_view sensorId, std::string_view projString, double positionVariance, double velocityVariance); /** * @brief Function to convert MUSTSensor provided heading and speed to a velocity vector diff --git a/src/v2i-hub/MUSTSensorDriverPlugin/src/MUSTSensorDriverPlugin.cpp b/src/v2i-hub/MUSTSensorDriverPlugin/src/MUSTSensorDriverPlugin.cpp index 024430b4c..6aa623330 100644 --- a/src/v2i-hub/MUSTSensorDriverPlugin/src/MUSTSensorDriverPlugin.cpp +++ b/src/v2i-hub/MUSTSensorDriverPlugin/src/MUSTSensorDriverPlugin.cpp @@ -49,6 +49,8 @@ namespace MUSTSensorDriverPlugin { unsigned int port; GetConfigValue("DetectionReceiverIP", ip_address); GetConfigValue("DetectionReceiverPort", port); + GetConfigValue("DetectionPositionVariance", positionVariance); + GetConfigValue("DetectionVelocityVariance", velocityVariance); createUdpServer(ip_address, port); SetStatus(keyMUSTSensorConnectionStatus, "IDLE"); @@ -69,7 +71,7 @@ namespace MUSTSensorDriverPlugin { connected = true; SetStatus(keyMUSTSensorConnectionStatus, "CONNECTED"); } - tmx::messages::SensorDetectedObject msg = mustDetectionToSensorDetectedObject(detection, sensorId, projString); + tmx::messages::SensorDetectedObject msg = mustDetectionToSensorDetectedObject(detection, sensorId, projString,positionVariance, velocityVariance); PLOG(logDEBUG1) << "Sending Simulated SensorDetectedObject Message " << msg << std::endl; this->BroadcastMessage(msg, _name, 0 , IvpMsgFlags_None); } diff --git a/src/v2i-hub/MUSTSensorDriverPlugin/src/MUSTSensorDriverPlugin.h b/src/v2i-hub/MUSTSensorDriverPlugin/src/MUSTSensorDriverPlugin.h index 3d9e33d8c..af3f2893b 100644 --- a/src/v2i-hub/MUSTSensorDriverPlugin/src/MUSTSensorDriverPlugin.h +++ b/src/v2i-hub/MUSTSensorDriverPlugin/src/MUSTSensorDriverPlugin.h @@ -48,6 +48,10 @@ namespace MUSTSensorDriverPlugin std::string projString; + double positionVariance = 0.0; + + double velocityVariance = 0.0; + bool connected = false; // Message receiver thread id diff --git a/src/v2i-hub/MUSTSensorDriverPlugin/test/TestMUSTSensorDetection.cpp b/src/v2i-hub/MUSTSensorDriverPlugin/test/TestMUSTSensorDetection.cpp index d065d31a3..a72a29609 100644 --- a/src/v2i-hub/MUSTSensorDriverPlugin/test/TestMUSTSensorDetection.cpp +++ b/src/v2i-hub/MUSTSensorDriverPlugin/test/TestMUSTSensorDetection.cpp @@ -62,8 +62,8 @@ TEST(TestMUSTSensorDetection, mustDetectionToSensorDetectedObject ) { detection.timestamp = 1719506355.4; detection.trackID = 324; detection.speed = 5; - - auto sensorDetectedObject = mustDetectionToSensorDetectedObject(detection, "MUSTSensor1", "PROJ String"); + // 0.0625 variance corresponds to 0.25 std. Assuming Normal distribution and a 95% confidence interval corresponds to +/- 0.5m or m/s respectively. + auto sensorDetectedObject = mustDetectionToSensorDetectedObject(detection, "MUSTSensor1", "PROJ String", 0.0625, 0.0625); EXPECT_EQ(detection.trackID, sensorDetectedObject.get_objectId()); EXPECT_DOUBLE_EQ(detection.confidence/100.0, sensorDetectedObject.get_confidence()); @@ -75,6 +75,26 @@ TEST(TestMUSTSensorDetection, mustDetectionToSensorDetectedObject ) { EXPECT_EQ(1719506355400, sensorDetectedObject.get_timestamp()); EXPECT_EQ("MUSTSensor1", sensorDetectedObject.get_sensorId()); EXPECT_EQ("PROJ String", sensorDetectedObject.get_projString()); + EXPECT_DOUBLE_EQ( 0.0625, sensorDetectedObject.get_positionCovariance()[0][0].value); + EXPECT_DOUBLE_EQ( 0.0, sensorDetectedObject.get_positionCovariance()[0][1].value); + EXPECT_DOUBLE_EQ( 0.0, sensorDetectedObject.get_positionCovariance()[0][2].value); + EXPECT_DOUBLE_EQ( 0.0, sensorDetectedObject.get_positionCovariance()[1][0].value); + EXPECT_DOUBLE_EQ( 0.0625, sensorDetectedObject.get_positionCovariance()[1][1].value); + EXPECT_DOUBLE_EQ( 0.0, sensorDetectedObject.get_positionCovariance()[1][2].value); + EXPECT_DOUBLE_EQ( 0.0, sensorDetectedObject.get_positionCovariance()[2][0].value); + EXPECT_DOUBLE_EQ( 0.0, sensorDetectedObject.get_positionCovariance()[2][1].value); + EXPECT_DOUBLE_EQ( 0.0, sensorDetectedObject.get_positionCovariance()[2][2].value); + + EXPECT_DOUBLE_EQ( 0.0625, sensorDetectedObject.get_velocityCovariance()[0][0].value); + EXPECT_DOUBLE_EQ( 0.0, sensorDetectedObject.get_velocityCovariance()[0][1].value); + EXPECT_DOUBLE_EQ( 0.0, sensorDetectedObject.get_velocityCovariance()[0][2].value); + EXPECT_DOUBLE_EQ( 0.0, sensorDetectedObject.get_velocityCovariance()[1][0].value); + EXPECT_DOUBLE_EQ( 0.0625, sensorDetectedObject.get_velocityCovariance()[1][1].value); + EXPECT_DOUBLE_EQ( 0.0, sensorDetectedObject.get_velocityCovariance()[1][2].value); + EXPECT_DOUBLE_EQ( 0.0, sensorDetectedObject.get_velocityCovariance()[2][0].value); + EXPECT_DOUBLE_EQ( 0.0, sensorDetectedObject.get_velocityCovariance()[2][1].value); + EXPECT_DOUBLE_EQ( 0.0, sensorDetectedObject.get_velocityCovariance()[2][2].value); + } TEST(TestMUSTSensorDetection, detectionClassificationToSensorDetectedObjectType ) { From 4f12a4d6c31c99eb14d946f5f92833c7e26343ec Mon Sep 17 00:00:00 2001 From: Peyton Johnson Date: Thu, 29 Aug 2024 15:50:11 -0400 Subject: [PATCH 17/24] Updated docker-compose file to add comment addressing usage of sql files for C1T --- configuration/docker-compose.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/configuration/docker-compose.yml b/configuration/docker-compose.yml index f96bddbfd..be01ea0b9 100755 --- a/configuration/docker-compose.yml +++ b/configuration/docker-compose.yml @@ -14,9 +14,14 @@ services: - mysql_root_password volumes: - ./mysql/localhost.sql:/docker-entrypoint-initdb.d/localhost.sql - - ./mysql/garage_port_drayage/port_drayage.sql:/docker-entrypoint-initdb.d/port_drayage.sql - - ./mysql/garage_port_drayage/port_drayage_lane1.sql:/docker-entrypoint-initdb.d/port_drayage_lane1.sql - - ./mysql/garage_port_drayage/port_drayage_lane2.sql:/docker-entrypoint-initdb.d/port_drayage_lane2.sql + - ./mysql/localhost.sql:/docker-entrypoint-initdb.d/port_drayage.sql + # The following 3 volumes are used for C1T. By default the standard "port_drayage.sql" file + # will be loaded. Comment out the "port_drayage.sql" file above and uncomment the following + # three files to enable the C1T sql files to be loaded. The container volumes MUST be reset + # to allow for this change, which can be done with "docker compose down -v" + #- ./mysql/garage_port_drayage/port_drayage.sql:/docker-entrypoint-initdb.d/port_drayage.sql + #- ./mysql/garage_port_drayage/port_drayage_lane1.sql:/docker-entrypoint-initdb.d/port_drayage_lane1.sql + #- ./mysql/garage_port_drayage/port_drayage_lane2.sql:/docker-entrypoint-initdb.d/port_drayage_lane2.sql - mysql-datavolume:/var/lib/mysql php: From 02fd2a52d85018edc289e590c098bed0201c3e94 Mon Sep 17 00:00:00 2001 From: Peyton Johnson Date: Fri, 30 Aug 2024 09:26:38 -0400 Subject: [PATCH 18/24] Updated readme for mysql garage configuration --- configuration/mysql/garage_port_drayage/README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/configuration/mysql/garage_port_drayage/README.md b/configuration/mysql/garage_port_drayage/README.md index 5642f7235..f33191298 100644 --- a/configuration/mysql/garage_port_drayage/README.md +++ b/configuration/mysql/garage_port_drayage/README.md @@ -1,11 +1,13 @@ # C1T Garage Actions -These are Port Drayage actions created for the Saxton garage for testing of C1T functionality. +These are Port Drayage actions created the CDA1Tenth functionality and specifically tailored for the Turner Fairbank Highway Research Center's Saxton Laboratory garage demonstration. ## Instructions -Replace the port_drayage.sql file in docker-compose.yml with the file in this directory. +Replace the port_drayage.sql file in docker-compose.yml with the files in this directory. ``` db: image: mysql:8.0 volumes: - ./mysql/garage_port_drayage/port_drayage.sql:/docker-entrypoint-initdb.d/port_drayage.sql + - ./mysql/garage_port_drayage/port_drayage_lane1.sql:/docker-entrypoint-initdb.d/port_drayage_lane1.sql + - ./mysql/garage_port_drayage/port_drayage_lane2.sql:/docker-entrypoint-initdb.d/port_drayage_lane2.sql ``` From 4f3d360a1dfe35c7e9a1c480d7c7b49c38b97f1f Mon Sep 17 00:00:00 2001 From: Peyton Johnson Date: Tue, 3 Sep 2024 10:29:15 -0400 Subject: [PATCH 19/24] Added documentation to visualize the Port Drayage layout --- .../mysql/garage_port_drayage/README.md | 5 +++++ .../docs/garage_diagram.PNG | Bin 0 -> 134258 bytes 2 files changed, 5 insertions(+) create mode 100644 configuration/mysql/garage_port_drayage/docs/garage_diagram.PNG diff --git a/configuration/mysql/garage_port_drayage/README.md b/configuration/mysql/garage_port_drayage/README.md index f33191298..0b1efbf17 100644 --- a/configuration/mysql/garage_port_drayage/README.md +++ b/configuration/mysql/garage_port_drayage/README.md @@ -11,3 +11,8 @@ db: - ./mysql/garage_port_drayage/port_drayage_lane1.sql:/docker-entrypoint-initdb.d/port_drayage_lane1.sql - ./mysql/garage_port_drayage/port_drayage_lane2.sql:/docker-entrypoint-initdb.d/port_drayage_lane2.sql ``` + +## Reference Layout +The following diagram displays the C1T Port Drayage layout used in the Saxton Garage. The above SQL files should be selected based on if the first (port_drayage_lane1.sql) or second (port_drayage_lane2.sql) lane will be used for the Port Pickup action. + +![Alt text](docs/garage_diagram.PNG) \ No newline at end of file diff --git a/configuration/mysql/garage_port_drayage/docs/garage_diagram.PNG b/configuration/mysql/garage_port_drayage/docs/garage_diagram.PNG new file mode 100644 index 0000000000000000000000000000000000000000..71872f3ebe7c26bb59812945979dbd61bf810f2e GIT binary patch literal 134258 zcma&NbySpJ_%8~gARsw(m$ZZ+Ie>tqgoJbnA|TyEgMjo9(lIEF64KoRNT+lT-3*P? zdC~9r-LvlfivkM;1qDYz{*^ik$^#hi@A@%1@QH$A zRXYmGU9Q3_Nll2+Zj*aDRcFdwG)}d!4Q(|lWk@Lw*&8}Ce1-R~2z!O|-wq0YO?Ms? zwuoo!)iW=4dilVHM`&xmw&U!^#(64eLpyWZag&*C(!j(=7Z-J%9J>Ds|vKq zq&1K~8x|Db=z9L=#btDBTMW65v7@!}R&imKgDwmSY#H+APO+mz%sJ1yKgCm#Lqre}h?Gsal1elot+8*_ttS~i#&{s{qmiRW zDW}&VcJw{rs6d^(RX9jp2@t6V-eP?DcU|BI8O*{#Q^{Tp1zPnq*Vs{3`W%5hhFrDy z_e-Rz%$1R=acbfDf(F|kNU~V@Z3-)#|35E#rI#NOFN;av$w`s}#s>Mu9s+7ok>>>@ z`?sv^j7+bFT3i5A23=+3ZoFFP#v>8W2S;1>hqZ32S(gJvtcSX4@l(PuXEuiabSbNb zh5c_k;p&G}r;G;2a)UuY!9Z2#MydIq5G(J)Qszc%~#46xIGDj&}?%yDG~6)FLW29!JoJeVG1 zieShMOEmK}rMi4LNiFI+$k`u6m?>n2WiRA$_B;dZ{Pb7Yn7#g-M|c)oc5wO!0ieY2 zELO#LOBcG)OW;~ll*y-z;Tt3A6>iBV7h4fpLe7It%KC2m6`1E#LXubI&YP{vW0ls6 zydqAB2YV7O>DHw)H6Nr`H`ao*`lS zUS{BX?-=m4R@@|K>$T9lgHH{1>Kc@uixu=eE*-*43=8t}DPyqJ;FS01`cydp2xsZg z7c#~lD!h%Vae_Uvevl~B9u*t}sPNvF|GxgJCZSVe=%(3uh+wADHgnGy;&J!9j4q+4 z(=g=zkMGy+xnCRjPca_}zWu)Oa0&18AYar>-h|#&2Ee})gh_H-y8vH)9y_tc&WovIfd*Qr`M=hGP0iojf#X)fIIqE5f<*MoVHMmsyH*!N$2chS&Up9*PV9gG{ zJ()u3V6cRvOAnHv-#azxAzb!vt_FPy%xCS~!Vd=Y-M>xjdA&))6=VEl&Oi@1!Kerj zom&qbZ6*r^tM;ZM^s093%tTH5HB))aOERL9Rp&?iji%A}PL#2&dw(okK+ zn&qoSi%aiXRN~x<5#CcI zOlDVvozMKh@*edLRPFRYSWj#rxcYo(Ohp|wg|;WG-#CcvAKCr6b~sLoxsP}tvXGR;Zwznvlhd**RME*^&VUO5`fIYltpWxvwvVvy*-qEP4S1wfC>pJnQ@|+-E4&4zr zh|~b9wkYoi9^h(e7JKRnbfmhbr42nRaJxx#qrl{({67@pN>TZocc47;$Apb3Ilm2q za(aF1E*Z`-XQoX~GGJOjivP(UE1v`+g|rJtl9^SFU*V)x*PN;qYiT%oNc?rs`x5Y} zTvqCD{G?n=b}ZI;7tfsL@Pa;QG|Af9P=CEK%Y1pQCfMjLH0MgJol}eAPF|y|SvntC ze@3NbD|&`Gc$^=5#QJ#7>9vD!2mSw46mus@N?Co`2D_OozGp_wBPwbbBdeLLI_^yO z8UKXMR^%ivK$?Xt#OnRZ;KHXrNN!q=+cQFrdz;-GEbjKuz7w!*{C$bEUSg&#wqjbY)G);op*J%SU zBL^`F$K996fT;65d}$DK(YGT92%Gz#Nn9ka37mO+kHE7fytEp0YvkCsr=8*P`Peh^ zz&^SD>gaD4$5}cuuiE~&f;+t`fg(h=R1wcLFF(8b_eJ-fFO(x9M=1-QKa0F=kWTU) z&r){hjf(g`l6?Cbc1S#PioCg7Z0{95J>Vuw+|jfO%mz7NGfBPs zOht~pDL34GvM(6NuGRV2!P2i)Q!k&8`t3)-7oV&UHaQhir=K;OKN>P-J&TMHs9^lY zF<}#?21b&he?dXiIqn!$bhM^7`C&|@@T{p}PWnHcljZttti}CNvlwbjMDs9$OpOTAbNX%RR5oB2UN@BwbzW0C_x^kl;lJ__We;waIMwdTAinyn9k!UPaj z2I`)3gpWC3)`GkIIF)6W8~!`=6c}=%YL3Z@QPYq5UP%FU_9Yds%u}=OmyuT|v@+RU z1C?d#es;@;8>D}IG6U#d402pa1P)cn9J|?!BV`ZW-oYxpIWJb$ZzLec1@eo*M0Ad^6k(HT(69(aj&iB%yW|) zF2-^wxx9q#1DAr-D$NYl!Zk=Nau`gbD3IhfpTH5uQVDDczWuVPt#oLbpioi6Tl7 z*5k*9?_{ya0<8%h)ND-29F_dTU-SpGDP7W{u{$n>D)8HFdtV7lTwgw=(wkj;LwW)AJj{DEGkvfB98Swr(GEnjM;>$5xSwE zr3Y&AEOtDfTue{+epAMCt$rrr_?AIu^po%FgG*ZJMf#Q9HnPmzwS?6+Y9con!HR|Y zN@9lXoEmL1cP^WaysaojPNO<%_f!;1pU<(InB_^1@Y%~Hu`vw)ye1T5IGO|4#~rS(sz^4?X$yrq-Sc#h=)D}Ujg68m+I+68N3NpB}|b)uRTj0 z#14e=VpWd~JNfLSqQFL)U~F9RR^V}5Xc?s{@z6;t@9NY8IPhx^`74ragzirV+H6D$ zg(i5#pYoj~@WKD@s>z3l6ro!YZj}N^SHO5E)&pQKfp|aJVTFNayImTcd}0nygsV{; zwUJ6Rqs^TK5E9P0?gB_2I`NRuV*!}Rz)FXe_y3iA$)N}~>e+1im<8jFPE6o&d99Au zMF0281+`LgA_B%%BoC2+$qdz@yqgk`CsAqqv%ZOm+Hn{&0VAMy|sQeL|*BJ;r&y6M#FhN&?ZiK9{xEQKH$2&j-Og0%ks0D zDE~q*L@Wyyoe~+o$fMae`-4j*lkh|E@t|B9H)r=9eAy`{GbfCt*@T`m@>Q@QSdFAr zb-MdU`bB~c#F9G?A|2?{`Q_5)&WhrYyw77R2Jh&Yg2=B_3ww6U-iGGIj%R7Ml+Cq+ z#r_Ms!f!z)%kYlNSQ7hCPqS8e4%h`}u38)h0_u6+ibbXH@Il@zi2z54o@}5mtuAh$ zCJ|qGi9>$MT?$8^`RVnt^?Z_J@*9oM=c%dJRw#)WqxOm?U#S__iGNIFcj@j~^@QV$ ze^sEoYVuu~Fr|3v@zvOV>>CRXjd(z>_IKMW&;8@3l^18&2N3Q6GoRW^hsWZBULGDd zq<-rMQ_;go$XmD-${p@c@C^1t@jU+#)_|8Qi_Un+>!Hu1(5c=^L-`jx zokSHy0??2a@p$HId)%Xlo~*DT&S=gklE`lPHf>$`-^@1H%h_mr_bcE z+22}QN{U-HmVB+n!ObM#xTV1zMC)@hYP!Oy$@d|!e_B=RDgc}TIABv-NXl(^OJke? za2ipOmQyLuc;;T1sj;qy zy5=!@qOI!tT)=iONK*&^ZR?OC^7nLW|-Z&1Ul8I=a>p@3>$JX*0% zX#6t&-bIhIC1Tvet&&jVmMSd8Icel%`t>KJjJjW1Q_%U4J$vlLqPOb5+H>vocnHhP zyHQ2$ZxJslA(wx^ee69JTfI>y6ARh=?#t|y?&v^)x$fgAE3=%gUm|jpQ5q@zYHZx> z>0AQg=)rK5>mO-$Or^G^_Q4*m&8Iop2|Z0c6}9Y*4Cc39aPms;+#q*O4DWtVD*jm& z)=c+!eOV{^b&!Kw`Qq1+@%+2T+rQnDn}Tw^4%#G_p6rR7Rv%hiF9+(s6zXfz`|bJo z8ZCPT92|1^i^b$yV73YQSs4X8>=D{4LXA*ncwFi1i7Zo-no8sw(-T6|@I}BLA1HqD zP&_wfh=TC|i6>POlsA}XRY5vBC&OXc)9YIKah^o4!BNz9x-Po9xhk{zKwMt*oF>cg zDtZ_e*loG^4U~u`=d`&L%ja@&;c+Fd{*YqH=ltv@N6lgB&}znSGk(C#*bRE)`{%kL z-shyK)>{qnD-0Vz8kj)ipoze#jr69EJA{sMvq_2PE8L~DfAJ`w3sL%a5azPnvEXX7 zm~Bae?#L)+zryPV`4A3C16mwezjT+dqY0y113;f1rW{+RWpi9=QHy+*Ap^{_oiZv$ z^1gP z2}5;s|E7D77W>0wvzc4CWX@g#?(|l5O}W*|L{p{Hp4L_V!v{~wI&l(faf#9Nyfabp zA|?5k4R1$ONWd+i+%KjaFwO*4Q0w_o!nWR}%~WO6WeD=~rS|iM>oHx09f3VwoTX4J zd=|^004miZI%tS;AlNA!ms(6&4w_lzDCF^nFhk_3ItrtE9nJ<{%esj|t{V$vF6L2< z6`J%0$Wk@=hinuHZ!%DeXEt0|UtPvb3#*W0ikPi}F4=%W7!5fFk1uXHA52~Dmj~nQ zX7;@76*Z*d#dxF?or?Bw5_J;ejMiQEsaq|#9C&1cx5}r%Ki&v4x{~`cz(7g>Ngjx~ z-}>-&)3iChCXQ-&z0TIGcd?8%T|8uHfme)b-*GVmgImbyh$^GmAwea@?9p~yoBQ~S z+c##tr#4SHd4h?gLy_cuigcxi1iyCbl^b>~cYmsV*)xBDukwopn>B*kTdA|czx6RQ z9K@IwGk~r#VidZ=5%?X?gK* z{&tz?hPu_A6GNft@C?J{3{h(9OUBzN&&Ji$I-nNwrj|@r(osa*McLbkTalEU4c3NH zyBdZyQ&P_$=Y(qa&2QV~gF_ zLTme_*83Vj=}1-GI0kC!MqSH0kMw3aib|ZwJcRpd1RK|R+H+^|s;#Mv?-L(QXo=VA z-&EE6zRx^jtkbQ`(FDha8H34d@XJ0$tqZB}F72`R9{NDR zU5wgUYX41$>P)Y+Oa|Dxmv*Y`xC19B>phmbJ`{t^UPg_9aql$%sT33W=F}?*xS4R$ zW$yCf4~LDdzq#US+Wp0{59>xT7(hQ{hG;TkFLYrY<7kcbDyYSDx(crB3?&o+)N z5Jj;ZyaaRnwz2F4X_iiMZmJ)0$Xh6{y!jpZ`fcY*T6hEGfv+|X6KIiu^)cInkRct3 z3G6MnD-LAvDNXE8!K+&8X|PvYg9?cF(~_;JDM> zDr4a<^mq9Rb_A@HOSMXDir^MR;=}1;v-zwriIr7KGOgD8ojS=Mtbp7a=HfQJVkOBRZ_479uZE=ybop z3AC1^&#d+G9SQ)Q#VSE=A;H_HQ7!@k?OPeX8v$aGE?Pma$$8-$rq5t8%Hmbfp~r+O zWgX*XTr^v7uV5sP&Ky}#+NWJ*DOr*EsO6yxZGS6%Qala-uMW>5l7+xQPsnli-fo zGQ0{Kf@nz?{Ian*w{~tLtcFU4giD})<_N9H<>7Vi(Q|CRUx+oFw#TB#rQLlHvV@6Q zi;AiG+CQ|!(>bk)iU4s5U~)m$64v8_ab#q3W{d3`KrMLu;5|qq-A#_ip+;mPgfjFH z|7|>Kb~=wY_>emPH-UG2!rdbj@AasTgWxR}QTTDfXIuEcU>dAMH)~n1lL8Z)cDjc=E zu5dLQov=_ltDZa-ZFQTcwS;0QVhZDpX7=}=}(WS&N)21ph*>pnk6zu!XL9lyCBdR zR-?~jajAuSj4J$hNH0>f?7s2gkf``O(Cm$^>Y<$WcD;o!6K z?C9zi_U`L7q~>j4i1QIqe2)vVv3Qy%K{eO1{3oNK`@HM&<#gbQ)WAOf`(<(ACSZqz zk4n(iPU#X==1%PvPwl+z+T-gcmZe5@l{!J|QH{u{9LDaUvEaS~jDxu&3EZ&Y3f9p4 z-h6eY9#t^)ekotz#A}ax2fbVpiHSHDg@dBCKY1f`GdH+yfYwG>`UWGqY)CG|ome0F z3I%{*Wqg%Z-KeQ8rf35?m8p4LItpLbx$=<}NyEwH z5tgf!kNCZN!4%IO1#rj&zx;{LP_d7MDK^O|<WuRXn&99`Z*KWkTHkebVZprI|*D7{W!F_+XyZmpv$0xx*D(?;z>`%rZ)G*{d z68RB0-MW@Cu*1Odlr%(6Bcm_eT)xPOIQp>ke%f$xssrcHp}D6_=`?ryL>^T|K{JW@F^NwsCucUs{rTW%h4H zw1xDoT*B~2bdQtNFBPlMXqfBtU2>GbleRPFeDBrZ8+xaD$!yPu^ke3N_Vv1nNUIUW zuaYO)y`%>=N}B$R0bumrW0p@rAsyLIuIHZ>P`zK`0#R$MmwxRX5FKer@McIN`(!_N zCFHZC%{{8Ds+o5HHco2RD3Mh1)=u6)kOF~{`wKr8LbC`?HF_NBS#896T>>cQ)k`OO zd_8oFX!>+AWRU+6+PeFmgp_Wv=uBGuvh!gT`wI$6RqU{WD`Bc&Eiic~%X<)I_yO5^ z@oa(cfYHx>Mdk_Y5x9l;Z`f`(+uneBDUY#%3NaI#d@5w=_K=#4gJ^7mPJ;v~N zB5y7=pTjIW`7Fp^XdpEat$Pi`Pc`Za$G^BtIwx#yr4AH)NTWH6YYbSi7y)brccbMN z3hXZ>-(XvS@W0!(*)b>nA@FtP)Un13g#Dfnnlysw$={fdRb53z-JADg-4k_bU+9GT zo6J8}Jd4%P0te_1zuFY`CcHW6(f*WhTYQz7d(ikM+V@REK5M|E;NJYWko-y&hmmfdm8B(+T5F$#--w-mhpcFrlJ=RZV3NILjQ|Z( zxbeG}YZ^T|SwuspKAD)D`I8X#vNoUWad&olJKN_$c7?Nju%4^)$@1No*n|5%$ITX> zc(0k!O^j?_){JjGTmO~=bvQjPzLGvYUSToHC29IRthe%Rv^j?MQ22}^(pyT%V;r>q z!gZZt$H}2S)fE}~^dMeT+@cbag^#Zc77UjY<1LlTq(AdS^QuW8P|^{?L(P5y2Je#a zYb6%VOSywJ=yk&GZa-?2p%TeziYG)e(IWd7n!=dfJi~km$@UssZYX-so@KMfF8Qi$ z3npCGRgJvl5D`rDctQ4hZt`$%V<)taV@u|HrXR;8(~HfM&$SY-+Nt4z#%({Aw9gs{ zhQu&b-b4t@7phl%)?)C;86)nMC)y@ZB?{^U4eb?;eGPuG$49z|w*IQ>y4y7KhEpo~hVHKs0oHsgN7?hf4M7hg#M6KT1ypnzgIY<~6H7 z^5hAMUt`Uy=O@(`#ra;J9{-r!8U7w6+Ib`O;8PD*Cz^-%2XaBm4DmZI9CF?iqf&z* zsEZVs{!48$2*%=N7%IgLW5pMj1|y#oD$ky&4GL?pg5NF{k+wZ9UgUFvk#dv@U}4Ti zMX&mJt1*8&Dtu3L8MZM*lk+@5WIHV)zIOTc%{F(!h5wTGalpo>vZl+^M1+%K+fpel zSkPcb@LH$si4r}`8?AtOC+SZlbpv| zE)fiMbaSh>NSf_EwpiQ#7SL%)lMnBsd$!ooG4^7|ohThr?trMvsI3MSc{)>OfLEHN z`E?O>o@4rLy%*=&6>zLReoc{tNJawW+`5?QR`Bw&O2E6Q*In{5@Nxq=Q zEi$DFAt-)AI6vVMS{teE;BYf{uBa?R zRBJ7!rvr}8tT(lqEmJ*2XT%>c+zYVhv>Zngbg3~{C7i{>` zuk0OzktB=;JG%Lj9Bo@p8U>vojZhXl&i2!cGk#GeQA~u#L4?t;eaW0kQT(e*$zqz5 zo9&D*;gvLB0dD~Rl|@{ATRLIY`E`?cTMhNRfh28(=9I&Z#nMFzLY#3qd?OZ=WZ52n z*)_7QanohH$G_4E%AXMygDr-QsD$QIZ!5n9cMlku21bP}C=wHLoJFA$>y5b^6}$!# z&_MG*fN0(O0PkGaA1>^jG=OYabIqEVcr;DN8BV8L>*2PW7*$@w#Zed$-7w3G^P>UV z{uZ_JqFSz}Zt7|hs*^U%`+S5B++ORKwp7z}MgLslHBcU6H!eUpF%0LZ>xxGTS~var&60EW z*R4&sv=a82JVD*;1L+Ns>{`VZVt7mD5?V|?MjPt6Zdje56gUU0RvHUE3TGQ2TS1{A-jB%~OG{AtR{)FWfAaA1|`fXdxn2ry8ZwE(b0(`@u;U z4-M=A1paJGn#}Uyi^y}-mdQFIE@Y7z*1LiLt7iwD$F-JK`lXVU8%w+mxQz*qRfpWM z39;uxqG}{>&nl`q6+;v4Ph-m7l~yErhobvA8v7@7 z4yKOxg!a|%&YY`kXBHTlBQ80@V)y@)9oPsqV<6|wv;c zJO!!{!j@Bz0~NOh=v;7Uux$h0^b+&YoXXszXt15vs*EFbPuE{fV0NsJoNPan756)P z@_Uy*m3c5#Zj2>5Pvtj_;HU@n{LvpvJ?>l$qNg7(EB9uu7yxJ2k6fAD2 zgnbwOEC2{)q3adR1oALXL41BcJTFs93=y^iriV$(D&?d*+t0c(Ii>pSm!AsMF8Y4| zPIi6Wx`NTH*4|*|)A?r)ArcqG0ZV=W*o9!K>GS8x5*2e_59_57rJ7M{^Y0ovWoeet zm5o`fyu4QXPF_#`Oh|K&0f8CTtq`WaeO|RiQ>bIyudRT)^_?~nA_ZRdzQW7Ma?9|X zO$)#1V)&;PErc)xVM93wGOvzZ&uUmZ1R$dj?HPstnE=kT-uM9yvR9VFmHhh z)plC2_k#>@1P)FynC8M(1}QGPmG-o66pzN!s9*GLEus(zW&j;vLgE629`&Fsuy$3s z7udIfgrJzAVNr+!TwQ_;3AO^`9fiqpxNh;b*3+a%JIaiyej`yOdVEsLhi1cl-~4D$j!65G)2Q+2uV1l-s}C2=H}=tdIiz#m(jkZXd7ZeX*$tKp zc6ukQww?xVM7%!5hw5x&G#K4uKha!eBl&u_cV zHdzEw#I#i2#z(M}98(Eexv9gC&*Y55>z*g4!I12^2b+ba3xy95Yy;Z&5Wy$^#R3*; zjt*polM#i%5%U_bP)X*2_5tTa@GH*Km2eiOvV&H&ppah{jS$2-17`$3D1Wd55oQ7W zlm+PoNk+L^yt^oIB9;Vzfm|Wr%C}ab3i9AgOrC)&1fCsF2PFrZ7buIYSpJ~FdjNVW zMBLkt^-)N;#WFKB!sFzj-SZ>3^LXvnS&3U>cO;2w`iE|7J|v-M^AGBsW!EmOPZT?Y z?FEp(;6b>>I~GHPl|9e%K|!k-?;X#>M&Jp5Ikw-nQ>p7*2IiS7DZlu@sQ>S0ej~yv z1|k072o`jVWyoPuB&jMG+-#IBP}%wm7XBAbAOZ?zaGFavi?{tk)^AB<(1Cd(1byj& zBzQ;APB|j67?4_YW zV6z2?G}Ux5z34gSU!+Cg$A@Lkl1PKv6XhMR+CjeTPLS140svjiI!$vQ8=*Rw8GJ>P zAM~}WzJh0qM724r_sYU?Li33Hiaa{Lod_xJwM;F#X6_O0VP_PKqacNl1g-(T(f}6? zwwaaw2l+#WJ#R^G*CGhj4dH?D?v)Or-SIpASLnMOw`&PgFR6sP=JH0mkmRHLknMT^B7_ zraskq3?L!Zv@f5;qL(c}=&LWFi>TdLE$_(p`^u=gN&1%g?$Gh=1&s$W7PC1@8x z{lQ?xs&iOq5x}{M`P(mb=x&TQ;ug_XHJD(rZN0JRR=kM1cYQQhG3u*P%kvOX!G|v< zGJ@Gc2~>CaEr%_*8|5qpTdXPWghd=S<1@N3wD;5TL3Y=T9Yk7(EDYrG9^L2&B0QtU zk+uu70oRQ|>}ZKa%h;oE47P)7;Z#$HdDMqFGO`Geyqa)tML~ zk*IykQ%>vGvn0PZmd)~a!GjVsSPc+U(GkIl$K^r{Zco;ac@vXlZZDv0-Js*xL2IZco)QFxdK5x9hz^d(KWD+?S48@94 z!1i@o^sN&__J4)bOmMmoJ#ebpxIDAZA&=*ptRgj>X7#4T&FSRLN0capYjHG zlf`XQv#rF6F|W88&8TV0r-y5p-6;_tJbO4nr~sFeld)J(0KX5yWB$YjTb%K>faTVA z{ratFR8Z1w)6ZW!=Br>cxn5DfL1x9T(6#3>7&Cv?e$b|Fy}inR!rMW#wqnI}jXYz_ z#f)v>L^7`J(I>D4O`b8(mvVc~dDn_%OSD^j?qf`tm*5sW5;2^!4Z~m?QhG4!@B8R$ zu^A0wXmUxt|H1<9!#f=2CTZbl7m!B8FCRHQ-{{S8qhl$t{4m)STy5U2F4#iwF zVL+!B@m1l3Esj@RM2mH*-Mqx7{)_lm*xJ6*q@~TOEXs3uRN~5470yLTCLZuKIo%Wl z9W1TppvEG9*;_J}XbuV#^d2k97uIt6Zd5-L3|u<-4xV3322-O%319spuV6IfiMpK> zKo>oGKbDr}Hk^txYuP!YTzNDwQ0YA&t5@T}V!Ke5^kKkQv4WIS z^3yt}ft?wx>ve8pwq!2{O-|!jfMz0@?oXGho201g4+DITcrg}+g)doE4dgxK0^_x@ zUW%>@L~TXZMOfY6#yU(>lxB2O+P^bYw8OVn+ThleiAXHXEw&E6HMCDGeI>4s~HW&Q$OCi}?=fH$ErxJK1=xU7U}j?>m2zwfkNIkMhNI zvk?)_SG$hHM~VOgm*Ia;9*kmgp)Nbo?HA9Ue+4cPr^axS|0JESEYe7*Q#^eIaI7si ze<@V^IczYx6;Qgvf+QCH@eu!#$sg3bgFgL3v^tyHjY4@fbD9=nsQr9m$OE_`v|tVx zVT6;N4wq=yr#?6PwzqQWep?Liwa0%P@Z6KIi5cYAFKhg4gTo&YWj&%7^ZnCEFg)g_ zH{JFs!EH61D z>=(#G8hoi2sU!KuY+g5cHQeGB(s8p+n=-xN%AAx*4V)}v}lL=Tkv5!`$ z90F^D^sR-Hc+6v!#2&-8AN)s9dwvv+CR*~%`h&6R8ds~>qW{& z8voGgzFh!#y0iH!WD%D-gR|U?YGT*)JC|K=^y-}$*OFIG>t45%F=l%7r5mX~gOQO1 zW=>h}MkVbn_@x6#ipebf+(3BFlADrjD8TH?rel5Ocwb4pl*|WVHR&QQ`_c(Ne&aH;qaaT6ZV^%&9I*u9puOKKTMr!f@L!B5xns)_dpH zkIz%Z>(v|(Zw@t;(=Bk-^^xOxG$}p~#bv^17xqJ@X}0jHm@_mR-I!?NGLBvxcQS-~ z0J!kzOw{b&CQHy4^J93*Ye|A<+rX_|W&ZD|q?aOX`d-jsIGgic=u<>Dq45u+2!9lh zvd&pTr2Z__XP1}Q2fS`Zt`g`GDmu4*+Ur`Bzw~UMdpSxUBZqdpG#H<^(eM&q)(FUjLJtoz$) zc!nySk{p>X2XsC!j`dO(%ash58LX|EB5TZKrSvT9e&VPvjKo}=v8b4?br+U~X>3g2 zJh*%p9?12FQt7yaTTs0pU~kqb*k0#8Ut>NJXDAi)9M0D)o*u8ZS`85}Y*rWB9SS)o%`yrJRp-M^nH-$zWiJHx( z#V<1n>TbRj1Ki3n+;x*TAse$7lyU(Mrg{~UuhVG37)vt-2H4?!XpTw}kdyc(N?0jx zrzNYbne;57sCwfy03rgj1yhMjuIm!FLlmo-r(0@D(MJ$`;_P5WCG$ zi;?6sno3zZmwmd>et{9JC<~S&L)zHDE6TTuzMJLz>L;D-dwkg${I~z?l-z73rECM| zlnc{ApA1RQ%H}U-`KVI7aIB{GS-Q5#SRJcjBF-=3rC%e)Q(sW{WVSznI^!_qCXDA14Yj$cvb64P{xUKQjeqB$baBWXceKpUjtiCNkXrsBbe= zcrab)T!32CnGMowC?deinE&iC4L2h_a0qNO1%+u-R1@^AUz6gRLd50T-=Zk`&isk8>V+sc$!p3O5%LG3cIl42jqd0b) zG1`$q(L*z+Px*Pvc~`01l=#%Z951%z%^Os62pI*3i;o9KPj~LpN@lMo4NPc!Ie4AK z67~cF*af%g)`>yQXC!!`61Z6Gurg4;G5J;TE2f`f_tC1Vg~p z_sEX~YyHdqkFzNxrFyhPL%Xd=SrLJ{TBpF3%pjMdCm3+q-oQzMVwVR)f{kh_kEtVA zWkI})4TYQFC@Q{$?pD?p0*fcX zEd5LIqOTT>%qUqIB~UX)k{hqWpE_%lfO$WGq;39#VYVo@V~}odHcx zBuS1>&VkMMxvy2VP?uKMH-#U}D0qFDU)W7gV-bjay`FJb3lN3s#2&>hku@M+&2sT0 zbc2?KoRm-z3uWD{t6}+s1W?D0b!w|YMAbOmkHCy1zdNWoh5lucPIkb}T=i?(ndbAy zYX*=cTq;rO1jqwzJ3G(va)?c2QW{4U&N_93%Y#G}n!QyI6qx@G-Ue{ptQ;w!Szyo& zb(?sO&>0H_?8`j6}uFB3iwcuZEaz-o#G}Lm$EdAE9=x@Eof4G{8WU2}?8VaMZh; z#si@>D4F9E4>{Id1g1ix-{JE=>uHm(QX)g_i*L}xcr3$en>7P3(1?eo{znnA>2JXr z*xj*aSorP$t-XyGDhD?Hq23+Nk^?eXb#6bobUE8Q-?KUg=sP)5c7(AiUEJvxk+CX` zJwS^*Ha4GJJ>a078xkODS2`9qi$8ZSBXbesUmmgH`ln|1p6av~+gys|wHK05(|($H=ZIS)n~~i#A2kUk|xI1<5)` z*%swA_-ca9`~%q@iAuyhgF*Tq0f-mc)`&&uyd8;Z3DGZRm73!L8KL?87m$P2rM<_& z8sHzv)Q3rCHN&oHw+vWj&o5`XR13y`zSSho?v&C!imRFLd_Wud7Hro2nmd6_kjhLD zW;x3ad)dq9abM!zAeFf9r8Yu7=}{Hh=C=b8F`uXbb7(bV7t%c4Pon+s%+ifK!!#jiJE>KFT z_8p_!rshR!tEkjF(9n^x=-9yPlJK}>MY{~IRQ4C`FThT9Qon4u^)5^d(mVNhbDpn# z2F5Btw)&kQc@JhhXq`R=J8bKbEwy8`99RtX=tz>b(?_G_n^{v@B!VdO)hct!=+@UU_$QT=4pvahp3MI0>R4rnE~de6qs4r)Q{)jX3&K z7=30<_}_8#&xYF=EgG%C&PsI}bTB%@(`*De8kHv7=4Gr6^3{rTx7*$LrV%ikLrvA8XhKy92PJ%r)OlhAeNgyalEqLsa+uT1sCDQ zYZ(|DW4hm-A?&{^Xge>g!;;KW%qQs790^Pw2@mOnHX3c!y#Ewv!ZZxMHeWvrCR-7a z4z;j7WS0H*G4%rCsU3ss3n&2hndrlb7L%`*k7%*t+n95!f`U zu+c~ss{5Et85o6k=3H`=Pc}6!n5@JZF!yR;YkrQ8ymeK&}n zn>e|JfOi3g+~z}t#k5F)BncN$M&bkwJT)q&Pl%ia!rLKNsjOWah>ewk~g zvld^)FNRP@vlb@orV0@~Od8;T8fPz3sTCZVDJ>g51hI1GyBv%%xLM-GdbV$1*XUH3 z^R9#_)K%zWQ5KlMQd*#X2Jd_3Ia;H2XGGNQax`y#pcdQ}qblsn`lHe|k_d#p<&hI* z&-HOU>&uPJPG>8|y8M)!88Loi#;E-zz3uCwRZ>5zrrP}!tA%>XC^EkJYCqrZ=i6Og z(a&*(oaT8)MeY-h-kcj5_r7=%``qc*!Qp067g`hh`c`)~61&Lu*v`dIKyR?}rGQS!kTni@Mol7=E7yUg%Hq9UgZh1%%y5;YKsu1{Z`< z@L#T(1GDEpAkTs5Os9C+*g72h<MKqN|?1eq3muM*XI9G^ zFrG<-#2kIdaXJK#<1J)IO6Z<6&a#kqX9bxuU%J*@`_Wm*s&DskpR|#gMW2 ztjHg?{R75;8Dt^>lj+6*$?_hQY?4cpQl>2Yo)uks4&}`03 zp+wVg9mTS^m+5-ddy+mFP|PmMEnegA_M}DF`wx(Cc^dI(3c>mK*#;^)Y6PabuEefa zZ6#`RjoJA-FJ%r{2*K4gd`#Sz9p8dezlPpOaIe#L`nxhaOsTwc%-S2~Ed^G42cC=E z!%(*&gX4=nqz&cWlYme~(MUu96cTR8Ph5<|=%Dz|u}oR@R`QVH1}6VUJ%%gQ;YjI} z|1=vN$fI(APy;A`2>(xV`gNkZLJ&V-NO>>TH#(^DS=lGDdhks`kgbeY>h1z;su3 z-VDnnEL4pd%dOAV7q(iBwJ;0&eXsZ0gP&qBUJU5DeG|skN;7*WH`_%YvoB5`0FEJ9 z>l6>Td6;Qj{ASijI_U}WVeBgN+fhv5P<;_Osb+ctQpZ42i8=!3(^qg1vg5})ULZMO zuFryx{ug=g85Q-iZI22lNX|*3qU0b!L1>Z)h$1aP1<4=+N|e;(Bsq&Dfd+}9WRM^k zy2;oC1qCEDNRZg%%&TtQ`|NZ7_q}oM{qV+k<9*s=kFhshzp7QMs^*+)t^#8tj=Y)= zY`eB(08D%Rl_gFVCdE_3R>1vWJCOQmV+tvXYtm`zr8iQtiaCj_b@J%&CC6$-nc$#9 z@0Q^T1&G<*FK2?M%Zj|BcpmxDu@KUSr2DD5alUOhjH*P3tW96*{xR-ek`IH8rBC(Hzto zKR&V*WrsE0r7w9V!M|+ zCWQ_9Cu(h#Ao(aGm2H;lkLXbCxf5?Cd~S;-Xe2==p|Av>7*q;|}D4XC|^ySM@oLc+QB<;)OVv z7)nK(H%RrJbT3`An(B8Pj`?hOZr*GqLxPyZ=z}ujWy^xkB(!!cf`wC$HFjk* z%CfyFIV1xQD3~pMRqM-S4L!(6(31~|B`0sif;6F}2NhH4UWjim z%6ETW8g+7jAGcB_wuQ3oJo-$!IO@ER?uQ#*|+h}CEwaVtl-Pl|j`jdi~@EO2-i6F736X<4VQUX7w+~~ z=~ph=C7lb^e)!>YrPuaTdMG!&B;!RRrs7~2ngd@&np(uh*9RTOgf?bI(BhNqBd)0hOO zKEeIPzb-0ny@bc;H^BhT1J@P>{DT*jyFlmEA8=*K=Csk~E5qY*GwoXsOUBdy!c{7H z1#7n8(@sY~`@Ke3qzg@KP>WaaN@MB=*K`6K&1{sPQC`m@`5gM^Ro@uP1@+2Wwy0Tz z_Ik?{o2J|E;Fqb8xvj`34m}n6IEi<%j26<0ORl#1m@o;d=~5(~Teqj>?r7NBS8l$q zkzM5czD>|oJyHnw>=3hPzj;{dWwUvNd~UV+O0uooA!vkw8U=*d-w#?prfXXY1J*(d zwKBkhi&x+ppTbjUF|s;LJF+Bsr+T;+flH4Wc5t~@~#ckmD5}q#ipkOk9$t`P$^jY5nDbBp30SW z^<-Q5I+wubW#enpvA-Yy)6GWOhG5XE0tEph531GlWt3CrxY#&nA1^M8~VV0 z{9F1Tui00eoTyZ`^t9zx`OYaHs~Jf(O?Opg@{)gLvRNgq2}i5jxL}~=oxGb6ydG?7 z%3Oy03hNoJUz-?<;Phg0MDSiv;b2C5j?%nLC4N)}>iWd4JsZBrq&n;0$esIQd>LUO~j$Oo?u|=DvCIm9qGQtka3%(p4C*96WvWdP67Ft62(=GG6Lt4@W5x*lHghvd{V`!d&#Lq5f5kh*=`or1T{JVa@tsMm|vm{{>ec^qe4S!V*Qz>F(vt zL)dsm&dG)<;;31H;(%$~;N^2_(U_S9xfF#~mM5EpaZ{o-pPc~BlNTBLZjb2mE9LTE zA>NdB!Ko4JXcic$eH|T3`zk@p3D;}F3{f-kkSCMzCrkyfb~R0El&|qdG_U&rK1Oh| zko1S-lXE}hmXaI9*^kW7!X8&X=obg}{m7OKbHCLi!oBkOfvfdc7M^vjIzJrq_l%71 ze}LsN6q~!fM=wr`Jg>j)UdP$Ud6x=hHFYrUbyb87oubhi#`9!IHqpau2H6~?CdK)# zrkFNKM&YJtjCuIZJR=`tVfEDr%kuk}NE&+7Y_kO{S6M`J-E6w5HXpG+)&8|9`w`o| zSL2F;W3AQx+^GD|@VB_L#(o0V84kKSu(!c4ri9+iu%8F#`08 zTy!g!pPTx&83ECB)ddO!v!oe0li0)^-nnx#&wgnc6ICp|HOZi`9&j z$vnVpttVXr3DHoOse*;iqJoZpL!!fU zM<>;PsM)?U#b{w(&k!ujnV2mj^^xL0_j$2~Og^Lj(d)V==3)VEvsj_udruV60@NkgiI0k(d<_CBT;K#o)f!S;FU zZ!y)qGIvqcEOe0m`tkB4dQI!^y3OS320kDd{bwkFhXaB8ZFlh#u@|)uAVUQIHsP1T z@+<2(1oP30wP*9}#-+HY71FjZpgM2SKsk}YWru!%zQlc?LDPx3<|=wRMT094GU;=> zSmoe;Q>}B`r&F3CWcMzEAw{C`XqDB}UZ6T1v@o@YL*&FZj2_Scr($Uo-bCm~%NC>n zJnd1|Z|>Z*t%v~r9_F#vb3sIFLO2c~E+5N21lWF6r%2B$l2@e%^tZ#f zs#Hvgz&{RjMX!r*^zbk*CCXuUdg@G=<>b~ado3~QUkPT1h?C-xwB@e^F}L(pWs(r$ zO_04ka^Ds|mWex3K%^*5qA6}c7G7@eDMr2{YBO@8l&?P48bN8c$ym}b2Z%5)C{kQQ zOb?RsB>h;^+A`58NvRKgc)Ja=yez-lFMKApAJ8`Jn14JO7?>E^)6wdd)W8^- zk?K~MDfAaGs{xns$V4;W?3U5PT7W1Z#Tz&q`Qg)=7(rbfNcJtHc&(Q`h8}DmwSwvA zFN!vaNY%4x6C)D!=Q1eUQGJpR`xC*wUB2xOhK^#By^kTg_%mHlDwtIt49AGdm>#u` zwG-ioYi6NS41A>FqN=y%NKm2F9N}znG4!c3S9E4pVx6rv2r(=LYo``B@BI~ z1^6NvEhC!oE+H-+WdA_wLEm$iSfL#ady@v&>A}dS)WRA`H zS|5F$Yp!PfWQ^bWR@a+5nOxUl)AjN5tzyg(F5_pU+W&`>P7(z3Apdrjfmq$4EsUqL zWTR{&OBmiv50%i6&_a-nQRaNB$sDZNR1E>Dit~qO+&k~DvR!mJdU8Omxsf&9wHj~3 zF6Her|1#ZqEN#enslFmHqgU!CbSpXSxgm5X`SKjCkefP80D6QD8Y{=G-2ODh1SNv@ z9jyD$JkQ5M)Mn04a9U*0T4iV46DI&B+WbJ+*f3o)E!nGv@Aez#~^b*md zIyrZIFgD-lRx0AGpTpZ~Fm%K_9PUV3$D1enatiFmU>>u1L+582^1@y*k_*BI)^AVV zFk>%Ucb?aDM>C3yCjvNFwY!JXZ5_bPe=k?2H#icB5Y!muv$Dk-gx)nPBi5_oMxya;P6O8(g)LOtA`YK&8a_fAX-!XS3u#7 zRs()YftA`AapNvh`(;l7I>m>|lODyZiBTt~Bs1dfHmaQR$228Zj>uKej*B{0HzaNb zUv4CARL@{G`}cW^`A1w-+JngOle*Lx3Y;kN&nE$2ony|O-;sa8A%5VVfUf|FvFX$y zsfgxBk33q2|K+D+3_7N>w&*2B?Dy7{jc>NY>wFQo^*=S>{#~xu?6I}u3on_~& zCc(_(Io|T3o)@HtQVZvYTOKGbKY(QCl!o$LNVkPO_0(Xvh&fx`C&NWzqPTvVw@f?J ze*0$(!#{@=qy`aZ1Gw|=w003L#W~X)=`mG5F8yMJn)p;YfhrU%%#%OMjN-*0!Vx#6 zOnxdGLI(D9At5pFS4(&sWjd^G!c>{g@(M;-FS04NR_M?9Ht^VKyw@tpWvxD=wVxeC z+5V4Aa;7{mFd*C#eY?!`ucXu;6DY_9*M2G(4QL}{FRv+7vM~@8Q4P*K@r6ATsJcCjm?^>PsOqmvn1gpEow< zqnU{Y!Y$uj>~ZJ_ur{TJu0P&1+Oq%P!$V>!y*H87^UyE1{0gZYlVuj{Dami%Bh;y< zDn$gY^*!sitd04vG*#&mn~P>^uMm_IKFmrC^L(1haGvK^XV@t2x;^$Jg^eM}BM zq?{BIo}W%~2mxsS74X%XZ><>*c5~Ol&64k|_oDd8O9?uE-eDNWEBIq4g{5g{Ie+&3 zmFzQ%0tM+&Ag?m8{ti^5_2k^Tc3Z}CqTju|XHNT%1CzALX(em?1{pAJNu z2F5BEs68wneCB>$st47v%JyyU*tU0dUUmJB)+p0ztd8{6&clDkia*AKN<+M zcE9laSAtT&6|KO3W{GHp9h}0^uO-_YXOr=NXHZynI@cUXSMK+5mk@IWq_!FZE1-SMj&kvC_%2BOuV4Hp0m-Mdpz-gzNCut=+KhLK0(&NV6g{|{eoW7Bt zvg&n3&VIX7x}QcyJy!!xO2X(8j!IAP?NVA3f5HHl{G(i>lRv=blz-n=@YF7w=SoHJ zT&BR^cXqulTe))KN9wU8zsssNzx5O!ulGc%TXL#65SuwK2{MEJqX{v`KqPDscOI}{*hMgTkV@0d_UtTS$6}dpw<}xgkEdK|0^7X z##PO`r@p7Nd4dZVf!z{6Tnt>m?zV^VJiN@%iYEXKf-x}kFGQz7ECqV$q1A(s@pi$$ zZY2Xl3?llHQ0jRqiAbiK<329aB|7M%d7X`oBpvjxM>^=0k_9922Fa^Jj>y!+`e!~( z8&?wQw?sbeE=)%HH9r4Ix$*4h(DJv3G;JnGx*7||xx3bd4m%R=lyC)9uMspCWmpzq zfS$EXB#NX;5*@TT_a(u^jZL$eEjHZp7goG68(1c%AosEKR=G3&Bgi)1Mgfs6+wt9s zM8iVZ%?nCIOPwYh&E?7uAk0udh^Rlu8Hj?MCgv8f=VON7Led_e?I7)`;0kyRQJ5&c z%^ONeQiRqEMbMfJzfYoXA7qT`Oc%dY`$7uqYe2o9=|XKplgLEEo#>g%-j;{HYM z49B~R-}hyr7K}uXI1(Vo#6P?@P|hff{Ll}c6Vsm4_2TCb1ImPF8R;HdAD*ir(88#* z;SQrUup>jx4btCff_ks3jE2V#?EPkzii{FJTk@?PG7uxOGe7b*k{ z#28)a&@1Z|M2kUhepq+5dcQcJ1GjwtC+}fsx@|5Qxhzi+AC2AO&&Cj?aka@s-^4ox z$FEjpj#Jq71?!wSWM$piWN+A$$~KtrBpAAUfk*o~&oIk)dvz(w@Q1M^awdTf&YZ{R zN})0_Fb{XuT^+Cp5rP(fgF;InH)9&zgS(dx$P>E3JFD^N+;LXCej@eOX7#-rpS_>W zZW^W%Ww<`V2pZrd7Y$&Cu!94N2qScaUUcXY|6=VP9jp3_cNqMeFB{j17CYZfq&Pv> zEewLs+CX4`{UqkyY_2Ync3fkU?G!ITV(`&H_vs*kr(CA7ZS51e(_r@A{g&h3Am4!o6<1%lDx@jPvn1KJvt{vAU}uiUbBH>o@Q&zu%in=`%M(~L%7R@(iPnFI zsMs#AZ{oOdbBU26@69ezVr{PQz3~-&?jSl!j8|B&P0yj{&gXj)Wyj*qcc;F&`i@+4 zPgJAlhcn;i4(HLZH9x!If9y+z+>}u&8I--@TQ=@J=&;9nT%&g5Q;Xi*Cw-ZIs8i&Y z;R#QI)P9q*@#l?_B6_Iy{pCSc04Z?I<3x+I@M|*vz2J^Q%Ct}_$r&=zSS%kb( z-(mJlpYxup)~X4nTNL(I+ii?`uF;hF%D?i;eHDRZ_}`qr`4e?qx9y&SVR5npmE~O?W!UdhZVRdpWep{#?k*?;ePfV{UjGE}5mbF)kl9ej>i+l3G0hKYZ<@ z0P!8Y)xD4P~tUJyjE82$-18f;lLKbs;T;k8940B7Z~X$l!j-fU$fb-V9<5K~V1HM+_v z{0%Q+v(8_8g$Xk|4~DvZx{yz<)N{u3{qe8-t%~7lnN3i}ICpv_w%y^Uh>Bp3bkn4r z&*|$s1}ClX3nGr+eShRl&tF@ps(rMu8vpc$=fQa0Q@Z`1we3T%X}QX!zQ%M2Enb5M zQDP5MTM%TAYhs=*o-KxrTEgCdC^?MlE^^0a{>cC*1@4e-1 zc|u;aGbF1*wX*023R2;@V|S#jmsT=83%{V}E@r4FE<@#eFi!^WU~*BBXM9zz2rKI3 zrbalZotc$bzwg=bp`pJ8+@-3>!6leZnXa|@y-hd><`q<}5j_T34 zRVy)+c#MRe)EA{r(5f6I0->M2T9d?PY-3Jb*Q<`x)K!^n%lMEulmprw<8*kQ8zN7a zon8i91gZ*wwXIa6Pk$AFT%JwKy~zc@L>{BvQjiK{b0)E)V7s*y!-~qf0^i?^ze5}G z#R&q!TmeIi8e1ImUO%(nY&G|O+}hrd{H?@Ik40mhU*nb!NifI7Z-1u!eAXXBdBHUD zGMw>5EjRjvN49*0?UV1kebGyFoSHjgNKpURhWuiSQd9NV6IGdu>7Z5-cdPnWAb^K( z(3rjZd{hcMkUP3^Mj}+2SfC z*_659DimuMW+sy9^dM_u`-xJge6r3{vK~*i9X-r?&Up@jdj~A1o|Kf;Sl((3EXhX) zj=n)+9mo)A^>$=Udxak_J33|3aAgs|83cUc=*1sjR}C$RMG*^mJYgzQBEA||P$z+R zjP}2*2f^O|5Vp@)3mf2v_Nk=wF>31?W%)|19UZ_`!9XlCresfaxspJ2##&$xR&s-su3pDn*UA?%D^ z+xeQ9TXOVS?&$cW?bQ0#dWH@f;d%g%A8x4Sg-ILL)GpYaFQV?Hu_s0ES5X^Od=9BH z&I5PdtdNIn`GGEP6p^l;MrZ^r=dLf6_#!B?0Yq%2C<)JxE-d%Ppy zfgEk|Mf$z8%*{=YFS+GSWaF$m6+{IumclCe^&ek<3F4Ec2zIBeD`==TM+w5!C^i(z zlS7?fdY9vr!(+W@cX1%fa-qxO@LliG%GO@i@zz3BkMks^DRt|#b5kl;|Isa=1WME&nc1o%L=a4b1+wr5%|6-!vpnY|y&DJmj3F$C=Yk0-BT?B7*435RnJYcqrq zXy&3OIv-5$TlXT&r+ZwfvfP7PdYe}0^+cWF&{Lt3m}pqVEAV{*Dk1G5w%s>8zlQT% zuoOh=HNZy-YS8<@x&0v|`}h&-wY-Yfwy&aMd*D8EDRiFE`LsZONGVlBPYubpjC< zjvGxi0_-PJcO8~5zM(>ORU}@10D-2p^cd9#esVUU^o9n)>_{PJj=?@Zc{wnl56jYJ zOp4FvAt-;90aF>$p#A_fI?cvMXktlz2{d!4=l4dL7kwzW!AN1nWtYdRf&$HQ!7Etz zRC<)vaS(E-U3@C^9aoNV+0(l?goY=Sv1(o?IFv_)0Q0y@)z$0PE_yBlf_9I(({vo< zog}T%mBb>A%qx>znWA&eZ`s;-7qo>Koq4#$Ss>aVHq^=ABv$d|fg+@@{!rsMklpyr zy`Swuxt2UdnPT59mqVY1RZr$~JaSakRaz9oB%~nE0TXZ3lTgb%u5BV6lNr*cpY4)52W3-eY-iU%sxm&&GGhBLJYdSgg_c&-}jyvRMhfO_ugT-ICl++CHj zj}AY4i8mvqti5wl*<%VZSulGJQoVWM*z3sQ(|$qu7WwD3d5guoUt9|vA4)XUxPo&< zjw0BlcFGzw=*&FVKv>VSDJ?awex!dXiF+o>@E;z<@@*}zGw-}URPrp=0EB|NbQAi*UkFE{)U~I%zwmfG0Oeiq@I$7#_Dqv&to;1FOZ&@pl9maFh^D(CKg= zvdWs2v>fpyGzZSyGlJFBd-vsbp&EAv|BuV*LfNwR)>JKY#lGhGL}JT*@b1$&7^jzc z|F~fO{QC)Os)p;%#SQISid-Kkleb^DZ@;Y}CSGzJviNrG|LJ>zR3Wqg^uoiahe{#E z$8zdUr9b7)k&SX4uQto&p;Ht&jlRLvsiEhYRnCh3+OLee8);+q5;LuRnn@JtAZK%# zX3!dm610Stk-96aa%M<JQCzox zbm}wXklNWqw@pXtO{Y24$BAQaQVDxZ>F8eGWfV$l`hz^&(gzPd3c$XGa_x%l6=UJn zPwUxeKvggeAgL*rOO7g*7EYwaFUeJF2mHNtZdexgV=aRTW^FC#T`@X>SZ3unE{7G? zV{y6Pcz${vWhK!QucrkVk&aC|<^ti%obWT~lx3pM{6`Ru{HNJ---%w&KaF=FxNDOi z>dsP+f^&hQn-g6%&WB;4`SUXO3SsDPanh9pXu+E}`Rx~la>NP@tfmbenfmb-< z6RfQ2d(yBhaBH%=B|TUVvDSgaD{)4f!c6z%-i$mx5xOns^7kGCSCrnDSV9X6kt$*Z z_c)9X+tvV!@j2#visdCir#!>Lx;vuusLN$eYt*AIQ^|Y!<=8~Z4^JV+(jPdCv5Mjk z#t8UF99@LI?Q-!gIt5TG=ZLVI;1!dYH3fO9*rOvMJ+Q(5gCGB`3J3_qu9cdO)o>iY zg$s@ET?TL-fy z$_*hZY$xLsF*cLFIW_9#%{!pw$QgGDjc9ENdP_%X#${IQ!w$X3PW8Gzv@DVuEBQDM z!WNSu0Cng1hhe~W&{zyaR_LYLfji0eo2rV*eK$ZP{qm2YJYwDiHifEKAA*heMJ{{6 zG)NTqk3gV;>ce^s)*%6%0v0-eU#mz*)WII(+tN6e_Ye&V8#jEXFuLSR_VkW#WOy1( z&Y;ZhKGrnG*nnHl#1q`OW0-oF03RqjZd`+{;aWwfQ2ZeXKo1lrpa$6-{$cg2_sYWy z*(+n3l)4X*Lo$in+K^2YS0!NC&YvB9QpSjC_|6kJ*tIO$QV0mS+3k^1> z@L*LfXCuJ6RRBb8 zdv-y>Dl(zBWMqkv4z;w@Xgcr0Eb&l1o)^0lx47EHRKUBIa+eW+AXOmKP+}=ZkUSzP zZG}TcfO`;D-SDl0LQxD?w*yPxg2Px^|Cy=1iW&fG|_ z9Yy&3NU%-iCcJR?Huw=iILyxr)F$^SO6U-~9tM4zZPj7rfR$xWfg3Z^e3LnyJ$7b| zA*sM`a0xu)Zg2+ESZ(6ob08mOUJc{fX}Z0)^#1B4Tb4i^U;OYx@k6Ww&hw_bJ%@^q z*wnsmzuVPQqjYu-oqdbu9X6^&0_gV276 z=Z9OX{@+h$7ZsE*v8*+KnF5qsDTR~cg7NplD^o=Kne#_jCXA{)`GuBxhStqj0}N$M z3YhH^td>i`{CbdR;bi?7A!|#?SZVIDL9H}@R*)_gBi#l3hqWFA&%RX_dHWp|IIZa} zLY%j3N;$V>JSbsM!jYnrK;HDk1dcFk^jpPz$@+A}^4<;0lE<9bjkR*n*FZw51IJZM zu$^I3!*<29@oW@@@WS`wJXOQka4oku_}D_XSY}WH3^}laX$^kXuS?geZEMZG5=z(& z8dMB@tYDKd*;l96atG@Ax>A1vp1gOGC(%sRRzp9kwm*^Uk5$F&gSLL&~=tn zrc`-VJj~XtxLd=)%+CP1T-y#g6(*j?KzY{Tg@}_6$uDqWlp;gWinO*6%u)io#2(nf zUJ7v3Dp@he;_|m0t(_0GPAV##H$B|kWZcRK1tM-Hyf4gV!9<;H7xip%w?pVrQTn^1 zk@u;d1W#>JJSbD)dapBd#6T$coFzK$VSTL?Xf5jcgbE!-?lSV-ZR?AH+3t4Y%yRmI z=zvq1U>b$-TG@RK7GiCNYMbMB^$H7>?20H9f^_Vf^Xz4hA~T`7A7^&fyj29BQ1yWR zL=~?~bVgkHkLE5Rn9?=)yBe~8g8pCupYOk+ymb6fcj_~d3m#r;Y6NVoHa(xjS{3vc zSZgKhpb?#43zeO<1m-FaA)*|c%NdWwkzywY;q*qnpk7XyI66B5bWCCwX4pp(`HsHw zf95Y5@dxuHEEAE>rYzB+9oeV| zaXczC_z;%>^&3ESf4sVVR-#EhI`dx0L<=?4ZU~Z)NICHB4pWTBMruTKT8#-&KShRA zDh622?dgd(JW;)jGJwVGutTgztyPDvH;7k-;+e5h9L@`TOKgbK@N20Cjln=`L?+=N zkqLE6 z83k%1LWc^kJ1Wa(C94E^X}g2PUbx$4i_6Hl(jDnH7vR|lpkJ=c!KOLvq(FzUV5Ir;#R&LiAPs__bYNMn1%I_TBv-hzgZ#POV-h z1Zk4gl;EXNxjDzl0KQ*5kkA{^6}<1pS*gfWa7I-c?33xc&g zrlsW|N`I_olk<(lyM+KIr7sN?)8xHwk$%_TD)$T{Mc`IvxjHl|qRyx%i;Y~JjHbAk zRuVSGP`gb=>WpF>jL=pAT)wUEnh` z`5_6}%Mv2(epy~?p`6_#=lX_%RnE5hIq?5<{o?eiSJ^c+zgL$jl*Q~~-~c-!-aLE_ zv_Q%R9?18x4np7F6$HJR&dv|pRLq$qp#`3kRi~kkihtNGoylBM8yBde~{k;^xJ1!!3nBjAN=amaB*vFD~S ze=02cWJ~7)qFVAp z{NA~pYG#j=-E7zNiOkOpgb_ier8adM4ML!bs#KCu@k!a+W_fhivYbOCFxN!IqiCRm z;L1jgyqaK=t}EH?SDvYU@*-;hG~N07{N+>6yuQcSCaUH1!O0)(CfnLM?E@+N!7)QD z&48evbS#=~&0TV0qtXicqW1}O&!$;;?O@W}0{faOv&?a(Hv{6WSuB69z-0UjP*EA` z`i<{3Z9;&$#vS>_cBdI)*6Xa#7|T`J@TAh$*Aw`+@!ct17Pnes`krykJ{QQUMRNNn zLw{z*aih-H&|IM4Tg$>f-B%e?@ieY8O=@^el6{msr&`j;^=>k=447NpK4jipXC4CP z;&kqqWza8pds#lsgPe&v$I_QJ0+0JT-Z*T(1$0onU|4ArxH5y8PdxpCc6ySRa%>s! zU7q{RLF%)?n77!XppBL&*vp5b#p=KdhYQYxL{?$0Q*uNx-OHYyQSWWNYP0OjBETw8 zwwz2XE<_L#T|vWA4J-apIy9m4QhUX#j88*#lr-IY?|8|s zajAT>i%#|Ii{WUTBT+h(BKLa^Ep5xZ_Vyyuww;LUpF z$BBTRv%ve#ajRG?#hV=?cqR>AI-Mk=A#$!v=V^;SHU~9MH`n@jI+n(X>aYq2&y6iPzCQ*s zt>HO~t1GQ6u7R5on?CJGBKs6>g?)Y3buuqdaMLYk-aZ@PUnM}W`1 z+3eGRf34!Q{_6Y1rncd80;)*G)^&S4w6#b#IoS#v^bV`|ydDWJi?!qbnZBd({hsRQ zd$)8)UILYHdCfOpOCFToBEl6D$}_2h%+ByCB`1n;&cZKqQh+#Kh*Z7Bb#d+L0N3y9 zVWP|Y&ydtd{ISD}M2^8vqB9_<6WTeCmd=1mukzT-V(hrM&Bn&9=X?j>Tlp3rKk2pw z3b&^P!9BDTpqE=y!+spNh1w8|R@cdf3aPx=zX4a^msy{m52)hVJ`_Hw;=lN_@3zH@ zIBaWi(2lC>?9!C{118h9P@YjqJHeZ&^B%-RYebzPF+|^>>qNC69x3koFhFf6qjRM| zCEQAL6ri|tGiJi}Oo2JsMk&lp&MeHsafx5#h>l~8&>BVsdZB1bd!HD39-*8u2&;@6`OK6M(Cs9{ zM6AOwn7Lsx0E@QtIY-=qzLvTeVo}fHy?jqq9My@>OtcDSnP~V*LJPEXI%TD$AM{{R zoR@MRWY&Y+NF4vB874&@Jk+?iY;2?^%)JuL3%8KFu^tYdB;G9Q)+ac_slo)-4@`r>I~5!w!9{K={X-hdhsLMkt3#x81UUN zv$5}{fBDZK9b4%xAI)U}T+F1WIB=&(>7m@+EAx3=yWE#%qg-w;B?9!%>o3V; z2v(o``$3Cklg)AOVjYY+02ei1Os(^I6uUY><;acgWt8c<@$bUmN9@9*?M=W>_1Ar(}VtEi1-+l;>LyD2u@vg`gHmX(Qp`|wfQ?WVy zmsn5^{4aS7e`$E5%diiAtY#f(G&?)3Ned;6jEgFG69yc-zeZoDoV`-SCI6rPz7Ft= zzcVv(D9D&AcA;^?v!E~g#isE8WB^4m^(MKPPq}LmTFms^LK_N3ga7&Pn_qp@FT84G zT=hoqwWx#l_xIOJEbQ%QN9e<>klNwoZS);7Hyvr9eCz<*WD{cDRcAomitA1k#6}U; z!$tdj1l9`z3-~pozn3eCINjf6Kify-BtLlYDypehncQy42lLu&n#jfPphdi5Uwm%l z`5O<6SK$i+PaDukJ>zKVMV>xn^ZBHdD^T0p<%eGEcr){o@Ahp7hU)$!2jf~^Aj&BSIzgU&lua!{Y;qD55N5CB0y}26`ijlKl>AP#-v;w^O?UR z6dqa@O2D^fH5~|+AE#2hHKvk=c}QL9f*5Op_=>#0?LrMa%y1G=0Av!O`)t`tp1*{l z>67<_k*$=!IMZNu<8k}wFfsJUC|JZ>*+lq`kz&-$A~^Z)r9m(+of*J?ts5;ir7Q*W z9LO}CvE6s*wbV?k5{Nh9Xmg!D{sjsokI#vcwGTPo!f~quG^h}P;g3;A&&_v2#jN#C zE*hTR^d^;y=wWKS?or+MK|Plc{V5A?EO0mdJ*+*LDMBiCBbuSo{5};rg;z@u>#>}I z<&NLO=88fr(XjW~%8Xj%P@siF>&WRLZ_+-gMirw?;f|rnLknb@xQTC?(STG&m zlLCJJ`i4VxigBuUq8;z%V~1K6b|I*7s*8pJgZugm&kEqE-%E)FPM818ouUjVHu|gg zvArxJ;*b6q+hiH2fkFL~BJ+bkSmXJ~H|(%T(3K$TwvX)>eP^Y`{1vfk`GZ%;Rl3}a zw%SMq8bZ?e>pO%RI|iOXZDgfg!7OB6Zv+2>Z6fR^G`TNa%El^^T_ zESShb2eXx2`^5HYbQw@^lVUMj>UZPy734c3o`T(6vBqoCF?KccqKAjs2}?t(1Ra8u z58ll@c(uQ>^n-Ek-iU4Eal^BoVzl%o0l;*bWy}Q8pl#*j{%8Or;i3an$LUjIF8$Fv zV_Q5^V(Tnm_f_bjl=P^Zg771z+>*W~)>j=-r%EFuo?N=rFyZ+D+P6^zeOlWp+pEkFhO{;!}C?k7@(9A zPqmqpYKS^Jv>dx=P^$dH)&3`=q3K&6yHpp0i5iAh-DDqiEx$ZONYLmqSlb{|Ah9DO z!JgR#-@fNa#5M1L^c($Wf8Z!btV-UN<(7wEebwm#HY>qd^{2uue?Gn!AV#p42{)cx zMH}b8VzFD&Q-OfjCu=;c-1!CDTci7yYShV3^IcJ=-~BkK$TC;TRyu6I#8JVlFar7VLcS)mzJ_!$v$8dOlC)^K-Nz7LMa6O{u za%(^W%R-f^QRUL!Vth$x&&&VuAn!twN;d6|YS$9pRNC%dbWC|tYB8e7zx~3cuJ~)} z1Wi)h|9A>%P!uW%BkOO$X-4&1n%7@!b^M#XqnBVYTM zTH*Pmzx;m2%bUxwb2hmj`W!a9bOt&b+)!Bd|NO3s>_S)v!|-+#$%Y2(qA z-*|Fw0uJLlk2$++9tKfh=Zp%syZ|T$GH^)7ZiLeVW*tL7=p4s~O98 zFhE&jYuU#q4UhKu$^iD=t|te?`&>e6t=6@`V7*tBUHKlw-9v1>3fRc)>XHyg=GuY- ziW19+1>@2K$DiMzM&?Yzw$wgw9vCgNNqw)E8v1dqTbjbhaNtBO&>B}j)^ug{}sw3=akE!lCy%XAi=HP zpP4m0Y{8?$>JqWnRcw8#p!f+WPkqG%qC67zi83}RaXZjM;W0gQP4_*T`BEIPO_iCb zLIAbm-O|%_GvwmK!tbCqfgd;P|C|@Q5fG+8bov;3+wV07%x6vZ_&jHP5%-~c;j*+XeNLTYORkWS#X=GaxIG!{<}4X8178qdu5Fdn>WN~J@u~>0(>EJ5X?;3 z>>_I^wy?Phy!cEpg*ruzH$t;av&}IVis@1f&Pp?=mpN@M%7+7cm=ztX>09H8*bC#o zwg4j{Zra^y49A+`9(?G}=rE5nt82n1Mb8yu1Hk`!9FgL5d^a!Jf+fv;GguiT<8jkQK3&@#-}IFZoS2f*vhsW;`HU3He81 zWt7xkZ!)av0b9pQT^l(Upi_S9x2kYFE{?v%-d;h~Jec+3KU6C? z@4_w5Vjpe)D0jRLnAktj4qk%6z=Dc<9H_u7YSJJNz>S7Q*no{k$^4+WW&O zfDfRY0(|D>%B};8lTnR%iw;hU4yOIL?(3g_3@kFrPJTwXW0OSS&mt=dQqN#b3vpTu z44ZdkWj$#=IS>5c%zh`(fCy>{VtYGP20R5JeZEo+(S5HPSXM2?zPno(xRlfaK1d2T zdaMcwcT>lf$o?fg7zfq?viQH6Q2X`;t@HnFiVM(EVN!Hf>wZrblNY0 z(FFr}V;*2#1$^3xI+Y+lraWgSn2*5T#3~1-HNeXO9H7j9DXXP`kJWDkV@?Hk++F5O zDTWQ(4kq%Ch;@AN+XG6fcSK*HQkXA9ovPfLm|2%{7{f=RP67rfO#KqNs|@W7^Q#ZH zBmqY+(AzBr$N+r_IBuWfY7i7n0Yi>WSr|fQ~N~+y#G2EB@@A|K^APfA&8i?_FsLEqEXj!T;a`6f3Er2@*R{ z=GP>-1Nq*uZZmisqM?UzHWx+)E0Wz9zTLJ;@UZyDo02u)Jq3Oi@Shrz;1UkXh}RWc zDb0Yoh*p(9m9!(Z38YkqYRO^S#9uc-Jt3%c#oewnfqD>-6+_@MaRfs0|KjaCqnhlR zc2%S(U8I+Qs7S9;13^JLL_np3fb`x&jR;Byk=_DQMQPGIB28+5AiehccMJf^8PW7$p55q zuYmI!$b;O0x*h^#1fYjl)@z0qm)!C?=f*Rj$R+afHJ`Ek2Or3u^PhYXnDYZ*vQjfZ zT@(YfH3;uOLa*UvOojG*Oiq_TH2ys^t6QV@o%jww-0`2pQ~?;}`Tw*eAS5$!xIYKb z8swfYn+PJaHL0xDHq#%l{iB z6bxdL1soHZO-4rMXrOShJLrF-YIA(5`r^YqXia1oP_L6JMU2;;9%v9umjcIa*{_9^ zadE`rM2vWTIqXeg)FmCp)^_txZD?r;R?rOqp=L(jLt1l}e2cJp0F20sf2Gm_YM6id z!l>k?^}sFy3jWJu&2b-Ji9#BYg2O^TAN*s7M!Bv_H^Q$8CTtf(`*xVJ{JpjXKkIw0 zuN(>H^NAk^c4bSPVlOifK*IjR1Dk8pbgl^6@VDXONItHYfM= zqqEB|VgC%+iy5D$e*r?4feRPng$ek73m@eG6lPT1(Tgd3xvsR2F!4Q!053-a1mBsFEgO+CC3G{e*nM1v^gYCNxZ}ye%@CBs_xkTO8M>r zS?s^b_jL{~tH0Y0R5T&49RIP_#gHk18n>*)4Yrj806+KNtqx!^yUq=P)+0cqIURP* zI8iu#ohT-Bkk^)K~K^#6kmyvu>m zjnyS0EMg)z_UJg!%^Hqof;fq%{a{T|5NeMjzx_`X01U1-L)VlRsYZaqZ#(!W8vJJg z0C-BGW2 z$+RU$Zd2M>BnlzIaq1i3?B5>#?~DCE@7LfpuqlpdYu^9kUVZ}TbAS#5t!GD^BG?FX zpLpRH{*7hJysmHFhySf%>TIBsh0l?;^+#4qEaRUip( zz9iUwp=Ucu6t$pihtfD5zS)y7t4e0!F~uUFgt~UA|NL|9S=}^90zIMOa;*sfG_XvM z2ss87!$fT#vv-uU&hO_l{@-TyJ|+Z!*8+SzK)b9#;9l`mWv@Y9H%Y5v=lGrNXbN%h zkRW0ffItl)o&;DLK@2SWx3e21AhYE1Km)iqr)8)Mj?$DB&(q#9dqi9-rO%_}{o+B5 z{9jUvdpB*EV?U2xUjbbE;TvG|ROg9=D6iB=-f>3S>a7EDOGlqV`|kFHDuw<{0;2v1 z&lGDHRNyD|H2eh%?BYbOcf`CW3|(`x$LT@eRY zfIF8l2i`MrHM<;{6^nIRQ@G>SiXk1zLsIA?SlN=~e zjgcH_*4AeK;&H1td1v4eA}$g(S_3e6wI2kimHBw0nMS}5iGA>oiAYYX6ycl?)HjvFIRy6WALe@GG48f!Q$uMqX!ct1 zz7PH}P2g<{8Of$J-yA&b=11*!lny!#G|7V2z>amU`69f@2dPH0RQWFvTK3tkyh*o{ zsJ4Q!u1%)EyFdZq9Mn6TOG6RefFI^XO z4ow~>CPCw?SLA(zw;O&&5|(N-!B#rc`14$1|Jz%>=KbpN&iioMA?(h&)?0I$6>Y@} z^xbG%MnX9dUSaz#-nc0$elA2btVFLRD79wyIq#%~8SVEOMh&9ZF z(wYqxJp%Tn{oAuQgtnvkX*=-@RqOA_3FNxea@cYT^y$M1wqF9J&`N>EQfI#3(~T&^ zQ|sM6o%Nonyp2+Y8<&@@IfpeTyLomk0paBpnFY$`CONY>+tg~UxXq_`HET>sK~X#u z=*cLC3G9|g2|a-mt}Zsw{CRKP3VCM9&keuLak6jEk3Nk!t;4hldDRqk(riacZ42Mj zxPB%8SZTDk$P#Q`wj|kH7Oic3iOo70kba@VmniB2OC06#ixw^R3zrf6a99eNRW-r> zyzP$((K=vAq$|$2AH_2zH`z&i0^C@g`rDcqeEA(%rLIh_=*wLT83Tu|7ZS=>Rio9?!)7@wA-8*}`$1;x5P$&PDz5;L$*I2TbV~UCwDRmL^jCaK0YhOU zu*28=xNZVLMM$uCjuS}2l;*Gf;BY|KGsa#ei-Y;V2m;SNf?K!u>HvwwO2>nNNcel~ zg1J=@;c_}gKAO;#)hnk9S!PAS{i@Vy@txV*Usi{I-<$Jv4deitf+Hm;#%l^cpC(sn zpUXTh&+xf2s56{1@L8^oap|)a<6;#6vFW%WW?d;gyTmCferZ(?y9TNF=S%YWx9d&q zG>~ZmvX1K?lenjL{F_{vr>uf={QjOF+J2lA&Hu*D6O(LO|E`08yW)+lpg2bnqzINx zIX~HPM|%|@@{Irh%a1JUgFB%J#iat?tT17kj~4rf_#zIYW@(;)P;k8eVG%D8M7H@! zD2FG__CwlIY+_>Rti7T%hkBaRvm%P1ZWJXkeYUd#d8O#-wXf zXEU*u_bIkL!l|APJ1ROQ)6mQ&gWp8Inhx_~3;9bEa*=SI-{Bo~+m9=qv<)T?E&26p zviThhQ4!TeTh9%~yq;$I8jKdvSs$c6b$VICxw_=V)7!gdJX&Ti!fPV_d%&b7JJZOr zLXXB?cPeexa3bbSDU(vfo9KM{134Z;(T_AGbT59W)gR@nrHy+1L{;TSs;UP>Btdh$ zBP>%zWyY~nQ_mt2HS#wq2nHAHnr!|KYg9T~NJ$wrmoXOWPG`JnnMm`rcYN06IhGgW zhA2SJ!jomL<^#A>;8$5Z{_h@cczuFjmWcR!?5~M5?yA4a>E3Z)9eWfhAg!VpHSM4~ zH68zhwm)rVF8(WHvEgucd`m_KUn;7%G9>Wkc^X6*w{6vDQq5Y)qZ=X8D>+pDV#fM1 z{icZj>=Onli~FSXE9!zyYok2u&eR*3E&iXIvHr@XvzN~NxGiW)b_^5@4e=Dsl9oyx z{AuLjAe}AKf{oMLO^vl=_s+b$G?ldIY4niO*01xq!J9lY)%n4^&BFLywbf3zW2Kjs zc}26z*P|4_LE}NklH}U$+1WmxsWy9UNQ3m0*ir5e38VEfj?5a&#Uj+W;W_=w#H%LD zo_DziC}~JGS%PCWP17Z)=}PfnCVmSWt5E6X&oTM4kkLWN;f6Eg@3o_R^&(yIFNvv= z2x)kkX(SF1I3w-dnVN`=Zccc?aUxovO_)0FAno|ek7p?_LxHN`7HZCdZr%K`x`FkH z#i8)Jxd0rqA7weGZ?m=}m1sJRPLH_xlTjU>e)-tmOF(1Lh8?w$!Fqlg0Z1@p-BI2P}?h{frgU&e$~` zUG<<4X!ck30^L3DKcU^NXrZ{Gn)iZKmZ>6C)`78d;<0%|p&adV^@xSgSSU%Bsexj2 zhGh+-jIj0cav2GoOzfwqRND^^x;B3(BgUA^N_egkfd z=?>2nK+SJE^^?|Pa}-H)o3V>A94_I=CPYeuY*Wa_kQ13j51HEYS7#K|M)NX-vj19p zv*V+$5Sil0a4Okg?wm(+pDI5x|Ixwu%j4>N&--^D{LCKqaG&vDX{$-B9LArhhc$9# zNB|;rzw8F<*b_E}!?@fhoHukmB-4x%q8T_Rs(2$rOYPLRBAx9GXJ$hcxrJL|aky+f zGFY!@c1@&+LqNsH*uH^E2Xi;>r+~>5^ZDNFO~WEnJyw{ClnHCZ@*L6(7%8{xZ&z~g znhXcc8qRz=-WD;l7|syg_)O57Ij)m->|kU4u&iRD86jd7pu_$V71i$kb-RhTUov$l zAwBhQV52@UqHA;LBYV`w;t;`MTKe}x5U9y^Wj{_1lH

d6RQDH$@MXdq>iMw`KSfn4wPHTnbZ`!!kX`Qt{qoFV>@QqM?5wZh3Zvfd?Sv6 zjqc`Qz(-2NfT+eEJ`& ziaYTp6zOi%tnv^qa~rA!e@jl|aXYpvvVS(!ImGqcCe>i&E35kER#qK-|I>VesTDFe zCsortOM?Q6w&W$)a8BfQZyhZ*>T7y5;kOdZhCSL*FHowyM*pg^wssR^*nuaDf#&Dv zpE8F9qiP zF1Dn_ibrrCuM~TEyP42#S6oQ_aHn5%!akE?%1K(wC}pQ#KKF^o zoVv7i?c|u&TM~)yf4_qeu}TO2M)T|W0BjaMXjc8>zc1LFdrnB}n^u)j!wkak_Jfj> zdr<+9U_THQh<@lta6b6RvSxXD=b~x`wc}=MABOm_-Z}9uSR1&mgV|wWp<1(@gylD# z&chhnc^P5rJ04wXpb4e728wB1;f83fHD^(ahDzDpIaMAJ^!17Ak8{U>N3|WQ)6KPt z*FUm|B`w4DGXa>$=$7q88jKjdT?~Y}76ak(@>C!(FNoq58k#;#m}uR&{82pV?4?T- zUHiB1Y6~>?h>N$~OqNeO2xymrUUlTNGP|$y%^?f>3_~oNg^LYQLuGoNoODXEYZ5Mp zKBub3ML=t(u-X5i>#L)pY`eWJ6eOjQ7GY?lq=u00p`=R?0Yy57mQLvu7`nR~1cnxd zMp}k$hi<9Z-$xczV3a+{>9!xrw%A2867OJ&L7C&LO`hS)flj7DwT2Xbgj1X%5kW>0#J2gfVH7fI(j#?><5K`Hz54n4~D48hT*XWLtvGXIO-_ zQDJDh&4ZNx-6a|I@RU%GEL8n}85p!A#*2Xmg_){l0~_B>eP9aEZxH+bh%U#cCW?W9 z#%{UHuu3nGO#A%XT}t#2q&JaFiU|4NL<9JV4XyTr@A5_&8DU;V2IkDH+icS-k9PRL z(tl37ovhA{zgI6df@LuO{j4?J4(1E0O=hXJI2+Rdca?I_unmRnFs^MC+Se{tuZHbB#OSIrR*IL{!F(44$@R{)*HD_in4eMK+f0+G(CY z$aRw;d2N6;_Gg8<{Qb(~fpDFNKWnn@|968^siT=iV)P`oFiHK++f_UJar8IKEa{h- zW{1{yLujjxo+LAj^>9{fg6mcqQG7~zboCH@gBjNUO_hV;e$d<9Bb$mU5_A&LOX8Hg z*bPHcI3Si5re5sMBgeJ{a_?}gqB2|GDzs8s*b~6v+-q3%p?K+h-mU3l0PiV*zQJ*( z#PhD8E7g=0;?cJ(xJ@7$;Y*~T;5;->=U+*4h!ZOH8@E4EtC)c2`W#owm`4tE@rN_vA>@;R^jpUOwo;3~SE|3~OdK1Kt-ueQ|3s@FXKxiR0NW;W zp*a9DIF7s|aZIa}Uhgh~{ENVlxEFsqn&)tsTESpg(;cgTkh@ry(H_Ml4BjBOar3TO zA0uj=zvyUyuq$S?Bqf>%wOjan5}@jiy=pVX2E3#Q9HQwasgf3>U1SL{N=i4 zUilnek}p_okg(aZ^{zZPoIj%sW1N}T!9+J5hzchT%fGx41B1*FG4_vvuW;;P%TUf7 z_wM3-0%>So;H+efDTPsLQpNRt2XLDOagUQ0{-Z)T*~SAD9C^+$%oK+t-AsRRhf>gL zaw5qd_=M&keaP^$56^+#uGddj#gMRzTpXwDUmN?l0W(V>Vjr}3ofQDwXtHwD%Q;M> zz;FBtf_h4;7%1+8HIG71^1FCCHgWB_qIJg!jO_^`wtfV0|8I1n1JP;kOQO7u9-}L?zYwmKmtfHzUiTYbAJtMkDlZjmGE?t~EvCH$zX%SKD^h+a*F`HD{4t z<|faSboH3V*CVz%VE=-4Obo#J$`CU&4*izO6IvHT=?^*e&}g@ebfcPANHU!`$L$lT zlF_H_vjIQ^q$r`%`4&`mzu9{Y_w{;KAWv|f^JP@1c-!BnBRR7C2w1k3)qo%tF z#B;6t=D&(P84KY5?0WwqArv%XgVVs&!c#BHP7{gHhoWh%EC2SVRsQX3b`#hO04z@G zgT#3o=QjsJKO>&8g+rxr3; z5IJzo>|#m;hNEnl2gPJ4HW)N*1ffytTiSDo~Dy9GbKlw9dii+M$|8dN|(%v%d5>gF$ z*gJyY&?N<|*~-frq^Q-3JL=Bn^&VK(fp0Hkl%-AuA;2-d7aO`2(XX(DffA9h>S`NF ztm7iZioSzRffeU_L?Tp`wGp`jvNny}30qNGG8;ea9W|}5-jaIw&r_q*y+Rh=(PW5q z3Sm1~Xq}hX!iO?5lKlz#>#@^8a!|og}?wn%_VYWrH6xr3pVe-wIO`R1J{; z+m%th>3#6KZBTYGxLfHyA7q|JrOttvHQS7yeAW4e0qL)RAZJT^Pbhat5->H}pJvXO z@dKcNHmmwNx|eMOyUpog7gC=rY`-j{c(&CDBtzzn?C8{Oww(#fU7WOj48|3r(&TqfV!^@*v3P%f5C^)E2LbFb5X66HD zc9S16?BY}EQoHecAa!iL;HCcU=YKhQY(fF&yglWQ_oUyy(azm|$|KjO+fi(JBFe z$Ps01Py|~PpGZBd?RH9(H&)rION0o=4)3h#;-C+)G|ZZXyOZ+MG%)tCAcX~J0Eldt(ZB;pVBMLbyZPw~O0{9^qI%%#56^Q;7loY`A;i&5o2U(nJTFF>`K zS2=8yqOV@REl%RYdu$oN408kQJp6ml%yoWom$}oy1n+ifFl2mMFDogoF^=8!fTdpB z5H+CFI5evzsa5vL@u1m$kF!x}e^8g9tH?7^eW_w^b8xSxtA?meocAts$Yo% zv+uwrk#CT2OY2h+JG#6;KxS zc}NQZ+2el3N_}LY1n$^ z;?>}w&To??C{N+JObFo1el_9HL)(pFc?l3>&}W*h_@2AW#;e`(mXmN@ii z42e7OZbsx;&o}UURmS>v)Fc~Y)1ryXcd`lL5B$sB<4ghA8ZhqQ7#pIw5u!hT@z-*- z*{Z_#?wq+!N?9RR{$o@gyo`!F?Snr$}M{#BQ#goW2F2-sNvX59;pP?2bLta*Fj3R3BWEMItxgn;%gGk7Xm27RDE&EgoDJZ z27b~%A+}2?6S{!B2*vsxDqe%nplXutQ54lVB?%y}Oahg-M2yK%YR(|~vmlQt9fNV7 zI;g$D9#>mb(HNGzBJ2$jx=|Wrp_?--F1Rtb|B^!X(?Xk>ALE;lVxR`r>w}Ljazr3l zh#b(ij`QuVW!7b&AhW#E*qBX5%saUWf!6Sk&rA8=qIoh_R_}O-6GCQ?Gu1zzGk@Wd z+kA1TZ~k?>geYqc6cQ7_Gl0Ne<9V&xcjcxv?U8n_923j2+Q;A6O=k z&{K9G6+s#*37MhhdNS>c!2U=vTybs%9}f{u_X*AyRS^LuUHh6c2Di{x{%M9Jy{o%?fr~ibK=IUyj=@w~ zzD>1=0u#A1eI16-eZWLBO~Egf?`|kSbi5_m8pb1ssS7S$$&LS5>LAFDvVu`3TG=CK zhPXx4)L=|GEOYb;|G}C!*BHiCxX6;Avpvu<1o2_oDtl09q4?(5;9jhsf4FnEi%(@i zRe%-&G=Vg@@AmRj0hC(pg<{8n3XPb0#c$oq1g@{b}`&hua=bw;Ras zOr04Yb)@Oh*jffWu?#bxcO&{TpA;!b{)o>`?9gv zGbZ2Ua?7SBJ~Y6&f0jAVp;FBz{|{2#1owLexpB7}{&+^bWVruU%J03FH?NF0=CsJU;O@bBq~>gWv=k>KDpi!HBH2+{5|A zG7p_Ld$Vk9ohCW>49y_H+S3cylM@$q z(wcui{MKxX?{4)mF0nesJScu8&ZlVW;X-H@jRX%+O7#+oiu`7!ga`QC0p?+;MVKA#aMr;v zZ7(07#>mMcoUsx_zjxGELR9H>eVlUPxOlfs^8o47FCN zNm~9}c=bmwTzQHW3(5(MdgyDb?9?*3I52CvJm>Yj(u7G_x&~s1xmI(~cewh4`->Xl zu3N1I+2^Lq)&e*gK%>;z*FfE1y-DjCr3fefQG0^8zF{KVG)t0&%BC$)P?ryL+zVX5?%1V=|d5?}Y zxuCG@E=q1ZjmxN!PNdV}rHmVp6R6H^_E%k_wsGMdX93e&+#Bi(%PWq^A8520pxiG# z%}^x8$ZW`2)HcZnwy3or2d(lpG0&_4{N+if*1TOOo%Y@)=mRjbI>!4k_!?ERzCOYH zgF_uy!>F#?s6MsMn?>c?9td!UIU_6@w7*J3Rva`*zh9y)NA ze9ARLri?nCoLmeFJ^kfLwzfJ0B7aK+O_Xd$lJ(X8D%VG1l_Lv@qk8$3!u8-KpB<$h zSnKCUeAD*F;ES40qo#zXD?~6(VUP(^~==6s;C|c66vRz=LiOhw>%Xwe# z*(9nhWFCyC`sUAE^LL;c955fj*TyDB5rgELUT}Ur#|xzN*Ms{DNkt0P^tKmM%SeBM z{Ukws3QBh6SLX^q`4LRw_`Xk+(;}xUZ*hS`811}_3)C+5BqB8`E0DOeZUhDArJ7WxJauDGIU{K;y}yMIDc^y<#;6>7KhBm}rfHo2tIy7#>{>8I z&s@d)EQgB2Uk|(nPp67&UYOM~Qdib#6|^+iE31qq7`Tw*<6;Ap#4h&H6nP^7RTHF$ zp2y5-w)y9ND=|0s21XcQKKhuq1N6hdIjXHJrL6QBbe@*39O~JXG+gU67F)F`)1kj&Nr-ZDh-yS^` zbXuRQ$vu4YEp`cU^ZScfhn=mks~@l|hSL?)WrhsT&>|k%shWaOl)swAE{ZGdGm&6w zkbbIdC#Cw`vgjUNZX=;&SIR(U3yHMb##`M;x|*gidkd%X;Yr`T@dG60crk(dmUst9 zOQ*^(gt!=|7q2@|%1SGhTi30TYgE;&FsS=A)(${AJhHfG@vP!4BQUlC^?Y*Hu1jZM zg2LMBZZHLs(GM!IuVNTj?Sf?H*KGFQjYkmPb`w0RON~)y-R2w0*yq9lsj#C6tEMTU z6Qa7`_r5qi5f2x18P2hHY~&+IUC`hesV#}hCL-eQSco-hd1j3wRMXT+S{D4HJR$tH zZx+TSKR#%ZkJlv>bAFg;dGQS5^JgvYkm>Cnq$RB>pF}@*+3bIQ^vphl!^62Y zdkv=mDCC0_U+Z0fJJF;DKeVJw=9C-$*xT?T$8+_k4(hqCi%`=vwZhx6nXlFqKp#J< z>Zmfeg7W4yhpq&dnTF_Jzel+pZ?nB^%U3X#;jVft_s%&$PIRh#XtfdfP0!rBm_jLb zQNCXkfaxY+O|4-3dn5HQ_e%SPAQ%I(2KxJ0v>kSK)zTM2M+7$fgFi4xWPp!i=?i`& z2U-&6caPu)N5{NuDYA6~8)Zxz%MXWHqaJ-W3(ptCtpOSjsfrX`H&l(NzEdeH0#{-~ zq`TBtk#gXUOjC1urZ+cxT$T``Ce}K|pHP$B=+_*cbQK=N-yn|3jGy zYUC+83Qmg1<4zv{{;JEk1Fae>WF}J*DR0bo*I2FII+m&G2G(x!g0fuuD|bjcc5R zeOFp2ilMPAAd^ z9-rCBGG|um!2UNsBN8A)2rZ9*kF?$P+S+d(vccph<`*V#GFQBMD}984a%c%^oh)B8 zMEOk1u3FN}I(%%IONz|zX^x5+P?X=xmmr2foH0!MbM`w zE!-|%U74LJGscEJk4Qh^NDF1`2Qh#C@|401t+MAOb80z#YPOU>!|E93M_)``k$Cjh z8skF!qNfLGS*!kc!E5?EV*rDom6+WCq(vsyxN`OVlfrUDVOKW>Xr41Wgqvf#wQ0W> z(x2NHra5q!P>Mh{tCZiC8x@O)FZgGQmA|#h4o-c^{pQ>mEihJh1T*JN=l=17@dOMi_N#O5(;qt9|xb1hJ_o?2Z*yn+z{Zg1q4zGzHDMy6oLa zo5wV%w|d{*cOjGlu)wd+Wjvo?<%X!|kHc<)0t$54{LMu#Sy_=GmvkBp$ACcrbXZmo zE#id9GpIgcNj?rJDt`-w%V;%Q{(U~VAED;5V}-VT8=vA2Dtpn1G-hQ|hE)wb3DQLV z2&6Usc9>Obl!yq3j}4dp&}35q6}38kdG?jCe0x2#suM3T7q{lEN@> zpHDH4$U3RPt_ZOm82So|c9&w@P2&e(Nc@B3kC_7;;^+CZtc}E~3rIHUddIe% z$a8J8_I+)$4DaKq?4Lcyl7BJd){V)GlL)g3zcVSFN+^byYXfcKXfMEyIU&kvSIHhY z!ULGG;T4*eTP#HT-K(m^o6r3%kkX;{<4dNZhWG2p0N}nTmFQ#29azn3g9jKcuAM8C zoN8B8^UrU<+{XNDI&Fa;cb>nmzwGCxZq^fw54LZgF;>CvpJFmpvAc_nzrGD>6M1m@ zbKZUfS0k@8Q%AGu#n<>ULuM=sl`o6`iOpMM!uRa z#H{R@B1~PYvoJ;>$J*kQLN5m?ox;8U^BS~~$w0mkh=;(h#P%L3(Nz2-PQ|de>`tn1 zqb}2bCLyg9wIb*}YaWG%usfbFxj25e@Jyf#=mtdKxR2a8YMsY<_UuzaKhB9%AY6^XXFmWF+?c6&9NG6c)YHc|&v%19wI|nnGj73z;1d1NmN= zu=t&tc;8v^b>|TATGo>PiPjw_^{?=KlYtERY%wGGbD2!%kb4pFtv;#9*^}|l=5l>d zf--M1Iuhe|v*WFypVTCg{tXiQ-*LkLKk9neySWy+XLun!4SF8{OG@a~JXel`erS0W zsf627dE==^<%`D0=ZKo*OO3-1C0|61Bj+D&I`_WMZoE)1D+WbDyz$K*Hg;eW@=bSS zg-<*$WHe{(ukn~kPUJw!nAK!V=5krwf~=CcPvEZ4>y!gnjJ++%j;NY^2_O zn~Zf;G$Al@AnqOK2RF$}NoGS;Y3f~JMbL1tp#1eP4z;ipiO6jL#i{QlX1jze7Bl3j1EQxT-I!kg};6smUQI;JNW1_@)71?$PW1fe~?vS zFAPVi>!|eNNX>Sun?OG~kif{y-}79F5hV$Y6I#VDKUc1xY$uzbjjc8xy*w;+{PkO| z(f5&NTyPCPQ^@KziA@1Nu)A2Y!=T5YRiKeiHeoC=YE!}U4b&UZi~xQSSvZ`)&Td>5 zkek;*Ukuf;!vrlb0z8n?AsdSv`c z&g~)Jnk;aAeR6S%2HYY3xk=nzWjkClI@gCdVx|k9Ct?Cn-)JOJeME$+7EHdK3fZY7YX+=bi(%A;*vwK^Tb5|XGU*xx6jzEtS{~l#0atfM0JQ@^ssBTwUt#7l4x2m_eM)7M?C}to5Z2jLKgP8U3T43y}?p1 z*kKkZc;|K$d(M&}oPZ6MwD|=Cmvkv~>N!xG{|^CIktAWMz|V-!jP6!l=0-I7FP9_8 zlp9R9|1-wTm=iSXwsY<;0e?_bOZV%F&*wRvj!nDi<^`l4oHmG4(8eSsm%XgsD)|Yj zD@H`A`KuC~65{2Zj!c#T`rrp_T}8zUe@9~eVsse4>dy2Yh-t~-`M zB@Xmq+GL&W{Z&rEuIgh(yTjVH4peO?svMoT=d=}joiUxS7!CGKM4@Tz3~L6 zYPDo0mgakuZWNlnMXx=6CbEKCsGEg#b!-;%Tdz~(3*yp-4WIFU7ZFMpq=F353O^|k z^3caQkR^+=zB@zU!p{gGfYp;;)zI}sDFwPshVBm~gv^8AhJoPt8(*h*1*DbwaCCoG zEIdE@xR8NMO~gm8BSNxZwuTK4d)bs@cQ;GmoqxSgoKBu!&3Ff*Kc~fNgOvNuwuNl- zrl(Za@R(HU=Iwm&Wr~L@Z!?y;y*Q%O=rin7YsC5)iNj?`J*K=rMT^P3)M|%HbT5w` zeSMqGNRuirh-x|*;e4Kc(n5&Hv`o%oe{Fi_0lLP|g6f8exZ8$q@RL2UtK`A{ke8*W zv|w?J^+>!V)Z795&KB12rpQ4vy%MruJX++p*HCC8q$^WNxryRGHm!ZkoWA()ZOdfQKCp*Jd95>o2SY*}VS*k)6Qv2dTveYd%17IAh?`HSf4%jHU7LzMcL zn^8t@`sn{2D5q|4cGyPY=`U4*W$fx()$}a(y4!iaL?z1quKBE(E>Q=#UX>MPnqXwV z)D*T1CASLTUBE_3ELF}-cqjwTQ{2_`gqYV+85H;h)4~ znCQ(6-5B64AE+Gqd-?hF%n(JvaQhYWG3Mi(tQbib_s2xU<~e&snlh=ksYEgu-Z zb$X^}*2|b;qn1ph?G~C}oOm#3+f^0<;{Z!Eo|8|m( z349!niMj5;9ur7ZKmL3LBz{c{{~YOtuf(3xD@`$DYKs{OFaP)(=PoZ8V19wRMAVPr z8zg0M2GW;4et=w(R!wnO4@n$Y=#W_P4r>qBNy+vkj8B7tz17$@6f~9ewrWPC+X2&z z{-uhqA6%$ORbCV0?UaKZKxmiX+o5(vZR0$6sCrnodesgd{{6oBC7T94QmW0wos?pRoucW$9v;B>+4rHz_^^gPs ztbrH68VIwSu=y@~!D_{N8NOmHUGxkh!CKLN>#s*l=oAF0c!#}*=_LH$0jmHo+OJ;q z43$51?f?}yW@Y5*%HTwNrKa_oXYNkr7aJU6zWp16%WBDde6>0eC5cbb3rv9~Bm3OT z^4Rob-pJgkW{1|20S3`m*{b-`bu+en@NVT|b%wS(`YN_3Ln7>o}T^t6`# z5B;mh?-vvtfzpUyn%FSRdMKkTyT!u>3)H@^z_=_Z@W=9r;FaBzi#F4G&`d?()LQGp zlb?IhztRk94|N-^hxe^+I-}!6jC$m*Mnt#*{eSL1f@ob%H5_tkB^aKJ$+0 z%p=2!693{x`pH$ZP2ChufZU>SHm|LZfr@wf-+TLyCxy((gU3(adA4h=yV_^K;m#4o zIR3I}4~4~S>-cQihGkqsuK~;Nu&^EMpZ-Tct_2YI@*{62omi$h{ zOQ1676IJELY$p$MGP9G@RHFmd=tSwy-Z!rY6!vK0(>l^+2lyz$vV+=W=(Kfa>a9Tu z7jj_az-78FxByxtkBcB%Y%N~|;`BqGPKAac-HK7m5F5-g8Ct6S|GF>v9 zvkDi{$neiE3s)mJ$f=1hGv(RO^A!QOO!E|$uaB9Z_1fl-j))Ptb}=i`#X;f&=($;KbdM){5*j@mGj}F`_p|w%{Go<8*{vT{huSTtQx_j zx;QyUa|>)+kMxsl{oZ1?T^Mt?w(MLfne33@_)Dd>ndVh)J@&(g@b(<7eboc1-9#(X z!G_-Oo4c?|a?Bc3`h=OAdIDy)l}b2B7PY%hs-Od7@@JT%m%~BLtBbc1G^rZa?o>bG z`X-i^-wdy63b(gEo{|l+=Cy3SCc4JRnM|1?taPoG1SP~sn}BUj z2>Zb`jlrp3*>SI^C&0b~>rrdr1f5dkqf#Ju4UU^azAq+;^{Ym#-lpsQUPdOm^im`K zvMtNfCn>_-Oexl{fMkNt9q^V~0z98;Bofu7X#g<8iNSz6g80t8T9&$e{U*ZcZ zr@9#u4>Bi$J0w};{3M=u9pnB`&V=DY0ZUWqq!)+fPu**Zi>kfxx^s=3O)&Y&IY~i?G=~t@ zL<1szgO9q0Gtqv}sWQJgjpR=EHu4;~74!Aj{sRWB0>8!%3@gatQ{X@~QQN0QYc}kF zx9qK9FF>#pBJP4uCM5D{M#}N-B?&)^9w^nc$H|48*08}9yFfEHn+K&94Ubh z^d)ELlfMZx&##JaizR;|%+V#9WCD{kwHS`-=UzLhY}nNsf;%kFM%XWnFjfC-2<%f+ zcmKIC=IVO5630LG+yLxwaJ#Idnc=Br77+g&Wi2emkWWc%Mj>N8Zbz_qWDxv%&E@$OSWzNB2Q=J9XPLG|Y znj3Eh!X&_+ju4=6g1gNiKJpvSl067#0rXgkwpj$!5@ z)fbGW&-&l9c!A`yk4nk=gcoLxvG&MWll^yAj=OP&;79Pr;x&=#618I=$Hl4aa#rse zM}~-Rbl!+_tOkBIw856#@Op;FKE>YP5*<%uR5h&8-4FJ^7B5VGKcmX*rn&@*`96jG?qnBxZ=>siGnVI;~(UC+a0z`hbhB znr(4EMJ?-8c{=uJUtfT#_tDihr%lXNU5ve=3Y^LN%=g6#zFFHHIL68g~ulc34*A1ur zqjiEWRVsGzk)~t}`}4kNPIS6p+BEE5#NgVdN|3mSDz$`&1Q8;pwj9miPY*600;^a1 z`+~+uH4&U(;$3p4VOehIqk3ly&x?HFwYc&2luvThsxk|sergW&Kh42UXgE$%K4>eD zHtI#*u;#C7Gc!5ezNXD5J6)Nu$=08?m+aFL8)Uk`Llkw2OrTI!%=YCoy5a)jr}zz0 zn`;+3W+pOe4)+F``m0V@o!%QM%dzeG^9ZS)Y0z%D4=@f*4K$^MK5j}dwNI0%cEGX~ zpr?b0z}4TrZxc(RrDKr2-+kqZ#v&PT#x3aaLpPm0Db&Vt=7Ub93$#I059RQy$EiCF zcRXHnS2~5orE2T>EcG4k{>f5te{U#9o%7@69@mc_;z#-P&(C9&zwAXL58d6zoFHGL zF~9HhV#V!TD#vXZGk{&JJAB@Xvdb?&DgfxpL7KEuric^h-!6zG)H4LzSH|~Xj z650SM4JLSn_AK69-986s)EsnN=8R(gB)`||)Y{w6Uk^T4*R9|B{Q9$m$M5ZwNvoZ=6^>peUs@O{9PvSE z#)Ix5Tt9HV1*4Scw4+@1Q?U{p7jL1WI*V>+O~bsRlth{%Rj!`xtWA~{*43tHgKKBU zQLea%vCmY10CA(gI=#QwEaEx0&O{>_4-cFAK(-;`vQx*X-V;SwK->hM+p5#MGc0o# z=sVMqBOiWR__4}b%3ELQBVXP0Gl+;3`7JmE#N2QG#FRMi2zC5HUo zH2#!#d2$1)=DTO3fAL+Ni|V`f=*#9y+nF-Rfg&cKZLM|-}I-M+Ct$3PA}or9!GQgmo9+rfhp2wqWwEC zT1*rVT(nFS_oKECuVe>`hgL$LK=p4bMF{zVg1r?w8`OlbhErH|2U$Zj4z-;3h87=r z_~;bLKlzRWRdChDQR?mgL4x-0NFQUzTk2_$gzE$(SFTfy?9i--ER z*vk*A$0rF&1w}ll)0%8~S^P)u^|q~bT)j7p0tfsxqJzD3)T+(=G1dZ1bC0Z?QlSZhO2)rt2z;9tE%3xNx|IR7M2`La2?xM01DuF3>E z7g;Ro7HYkJ>0ff}N4Y}=2&uoXE#Vn{#HGr9a;ekBC^&y|ueG`NX#T{)#}xUq@8O{@ z*v^$VHGknMvLW%+;p=F|Db>N6clze%y{cH#eHEvgwS8D&hto|6)W! zKz@90R8j%d<03A8l@p$!W#lb`^Gy_x9t|6wlTivd>r(xhn1QSwRYs;uQnCN!D&(Hy z?KwDXruy1XPICCTp@qB?5#7Ume7woDZ=bF;7D5EK^*a&SDA`;UvHoKxz{R0Yg-vX+ z=U#cC zh!qJjNNN+KXz|nJhgVc&N!AN3`|iFmT!%B{QlBy<{Rf+A?G=-&li061{Hk`JK-&Cn zuM^8F?@Gov%delURoW_I-CsU@Z=no!t~H&>W!bOmA^cNtP*G9yu{ec$+hD?d3j2zV zHY0y49B<_F+h)rj{#L4yCJV!rNZe~#7f)bn@rr?b^pQ9a`SGbmG*{T9Ox*CBnGBt0!?j@Q_ zth##6DPIPDqIktXnRbySBI9{(nC|Ub|T5tFP0BK z1>-cIQpG?hJI`!~gwKM^#-c?+&@ER;=PR*&2ox~;RzfVM&r&2Qsa&VnQZ2T2V>x+W zAa;9)$h@mmr99FRVsu7)t;Uytt>ATp4R5$&hk%%rI?gX`qQAI&8ZMqwSC#vW4Y|SBNZD^-V zW-D)kZ1*d@E)DgU!D3oB3b1{Q7ud1o>Z;FZA(}|u+ZcazJ{~3~DNA7K(982E65zTI z5SCBoO*!=71aw>5)VP_&)RZ*t3dd$6giI^x*)Gmf);g>co&_7fgUAnPGeMHA&DD9z zA}`cyowJ%LPs%3`$)Gowsmnka#ur}!AdCh$lZlv1KJIBC1wL94e|A6fbWs553e#p5 ziBEBF;j)g~PsjSLVy{$DgC!~5u=3zFEg$$QEHbS1^+m8p%_oi=R?qBKJVi}_roq;m z_U(#vD*>*%oLFoR4_)IY~RlK zEEom-wo-97fm6lvoeCDWKN*mi*{_Ue(JZAl-&M z$*E`x3o9aTf_fj1Vk4Bpw$C9>R_2RXrCl*j<^2bAP6L!B8jm|Oz49cV5a<*8W`1U@HP1?2$=O6fK^9-1e8QI)`^T496;m|furGBE#=rcF`W5RKn)e-P>Tm>p&eSx}v^&$QMIWue|wng&NpPoE9t z0O^-Zh-LNXPEz&>R6`3{7G&U@;=3vqo31@oc zSqgIdg%Aj#AJ@vi*{aHC+R{sXomWk*aMASwrTv3)w^T^_=xM^Jy65BZis+=c!XBsX z-@aSe|2eHJc~OrLSft8`p>Gg(TuPF|3(mC z7r;fj@4=;$SG$(>{8T&e_x+3Oh^alQe+G1XZh5yH1V%4I&H%GHyYtSLB(=hCp89Z< zfPJArUnYKcLe}=Dc-UVu4YQ^QYElAPbr=b*tnnJxb3BbT8i~$xsFM254kI37feIfy z_M;46^Q-uwef?RJfe4B}YV>FQN={~+#8WTN45mg%d7OX%NE*jlx)R+NI0w0oPcI}R zaaIq`3n7JWT9ituT(Ph%8)1BcNFwRMeDxO+09t;>gy|}Q)ny{`^V2D%yQCpGUVbuT zeRk>=J3>NR@J2{dnKQ7B{%UMrvSng*$2IC5czHhLtuMXsc#*!togM3^!=_uZ=JYMy zj44?sB0~7d=fnO0OUl#}JX~FYXNFGo5@MBBW6{zIV9}p*VjMr1qnwzVra3LqA!{9}x)yHFTOC!cRyuFj%8eX?=8U|3h4W~yGu8+sOjY*hZ>KUF-_KT3h5Bff zC}52&EYkQBgu&DQoG0FK%HQJawV&p@5i-DSCz1;9BXsZ9*B!4}^9`C6Mp>Ggv=C8t z3J3=#gv=OQUp0RbDG;7e6jYM3L=VhT`$U#|OVEZ0eLv*0vvuiSdLn?j_uvHYLCH+x z)yRoes)(sKP1mpF-}9W<(aWt1PHG~Jo~3^3JZZs~PphX5AjZWu{7ia6D2*4a__$lF zI(qEQleDQxDihwn7|))k$rI{@0>U1*zYGlPh&+;sxu*uh-T=~)HA%pAxjQl%BY+vY zmDCpo>G+q=sR@a;Z&;I7s$9=6(pEKIiZ8y|k;exBrY zTpst%07O%n4`oXBmB3rFU*kKCJ;T$~E_%%~9v_jQVf;{llJVS8W|T zcYkUP>~@_hq}+w7wojQAwcK|Bx^HdZyIe!OyyEAr6W)GoH?~(l4=PxMp8EC7#Iw5FTDWc3w!fxXV_T*uZle1qQIByjN9dZM}#9m%5cD@k9{XOI9H zu=?Y#IsG%$7Atx3I&$J-(Gc0S8O*E~E|DqrYy-Q`6>>WZj<{+>D^{O9dbYwB&vSbl zX_TKHex5n+CAAT>*-c+O(_joB6I@uGBFT%%K(B)G=5bY1@uiZ>FJC`dTxE!eE36kD zr4OJsp#jEJRu(bk=h-Dw+i-%A5kC#4vVI-cuvPV7m3lH1A(HP^r2KSZGt&U2qya(q z>8e$c#V_PV<{cy+W3lVDFQ=iT!C4y?HKaVjU7RGp$~jm-(<}JFB;BOwBXRHDLnRSG zC`cm12fuw&C~v6?z@Zdo(&OW__%jmCaON(Xr^^A6+uU+Ue=VdcarK5Ro5$(aC5QEP zJH0%E8vL%b%N01e^G=8kDo~DikKaApD9hiEwxlU75m<_!tJ>f&TOpf$TxAwJgG2Kf zPXWA-Uljr>&CFFV6%9v3A-GAr*n1rkK12QTR$CZ%cx6G`ujydoud3^K=Cq7pa%tUd z6Y=l9bkdwDkl6IQ*9L@XQpzt62tG-B)74J{>It(jY<;MKGJKehrow z_)0`5b*xu8(~N${^58bxas7Ius@Wh>+2e5$n#K?#Zw&+>1{v0Dr*FwOHw!;)u9u0% zq_>}~+`jorB&iL5qYsn}DLPwZ6cvp#0fKF?K(u;ZM3(#|0cHma1{yT7_nkQZ=)lD- zOrmP>xv+InN$>9xr|=CpzjFRdXNZQFZMaH)xO= zxu0*M`WS8QL#uxSDN1R|Brmbcip_e% zgj$;sHu&Ngmdr^e{6b%fd%Y);I0ZbAcM?&s6Eb1$ye>%3Z~5DrBnJm5yOnsUomy&`O5SPa&I!mEjC^jvhe-hHaDrEV6~DS zE~Xj!c&R`q_bR8XS=NpSBQ3{ra8^oA)$doRF!d=I@v?WVTp9Yc+CYz^GaHVWLq7&! z8RU0PTInFLTLkdbH_i-rJ}7rb3WwVaftmS`rj|I=5gNomITAVNG$Cnxp1TQ<<>1eppmKb^AZrn-1?7Kp_O^+)5*{4}0zv|rtUD<6QK%>8HUB5umEDt&qd;2f$)yuhllS+%xEV%D#W7)MK{2}^x1TX^!wF3$Ai zo?MEe>b-$qp!@g4ULX`JgcBAMdp)y$Z&;@sN$XlaMNFe2-gUX&o9DbGf2k=Zy}Z;E z$*WAv1gTaM?wN9VCg~bEqwMj~6ska0q<@wB=5Oh7!rwZZfWtq}Q0`Nwrd0?p6J_a2 zDX+0MmtY_+7Cc8v2%RSPdCeHVwYgw})m9JWwmvh0s^w@`va-|4$lYYOYNO=LQ&t;2 zdMH~}m6K4kuprp;xKHtnK7dEiC}16*ip<_J&xZKESe8?grzGwYUum1M&aj*u8(yYKWbraKUyNVaD6}=PU*mALS^?OHz zdsxq--kMErUCYdLCb4VS50bjn4sO>B17Pk?_}1U`WyTWj%8GQu5gk*E6ZQDQFHZ#a zSL(((@60DXwP%#;(p9Iwl`CtXR_u(aLQvj1=*uUOWvhs=H?q-#J!2Qp;SwGeeVm}} zl&dkdsdCKAtJ5zAUU)He%|}`DUP^C5N+wZG*-cL^wypcaZJs@oRu`UoAc@s9u+--o z+a-HjV43kSqVKywS*N29o4fX4j$Rq}Y#iKZ75h14<&i*PMUHz$eD8dOMF>s9t(um! zv8q#Pt_#0+1<1OVmBWZha_UQw)b(pjW8syPYD`OI^ea@hGW1iAZ9z8BDIJSBj|3sn z;%tI@cRZTU?^lid_RT;?lx0m(Ym*kEe=3mj=^QI};rwARf(@`kf$>91K7|FH=Z^%4 zj1bR$x!sIJPnt;vw%*AKk79l&O4&`no~JKdB_lUqY+O}jvOuWKv7vUo%<jg!iG~~>Ynn6>W%hW-{B-O=buvIk3%IDcUy5_nY`DOjLk!vCgx|wY%;&leKI)1|lhL^7V z=w{TRdsy7K+0J&(*o{=Xzk|mTuKi>fLa(fok=vUZfrkfw)yv0^?uyc+ZQNbzM85dt zQ28WbZP>hal(Px_I6YB?_)NpMhGnv3@N?M@gg%v^bkd6>CqpvU4D|GR%5>+_JBbX; zVS>(1h*N@bsdMd=_%G-W>SDjtJ${>(=1_h5Qb2+ah4D`Sy@?%2t7vj<;~c7jJ*zZiwfyKD1UhDPDRl@;b~`)&3(iKP5|UXx}oO-&m%g?POD|zptVe z+W*=kElw2kB94={#H*Y3L=fUoFRBT}Hx#td8SYmw+ngmY6-XbB6l0hem)m9gVX~g0 z))iS{@SF$A>qK^JVA2#T>>N(@A%E$Rsl)B*GShbR80E#0jKmQRey7?>p?{RM$Mi%6IBZK ze%;AqQ@(RK`X#$6`B}$GFqjx$u9O#D(XyNw@Hiu#GYuHpsnB&UkkZkX zCjD3y9W+%KPtOo$HxF``=G9E)SA_`Le_VVhYg1{nJEty%E<3f<@Moax}ItC_CVkD`>VfrS4 zA_L8LAil0NPL1h|{u3KgDRMETALQR7r;EglNY?oBu*0p|wDqwd$(QDyDIPB-%TqD-48Yte-o zc||6=$AUV2S(4YJ39At3`!$?G!-N)Aw)JhA_~JU^ysaGc#m0+oKaH#nzn9Mbh@|g# zI&C^ug+!EMlblc0b(#a;Q$sp)KU(3d(!G!NpB%gxTA*L{`lSGx|w`n5|sR%KH- zDEMp=?yC?Ao7wwt$c$eCYC#&}bULlf;hg8aOMiQ+%l-8QqQ%o8iR866?3TozV9bn` z+p!DH-sS#VeB$O5y`+09LduCEK!ZxOEEXBtV)Zo=23FL~4xPoZDa96=BbIw;M!s`1 z=*_FVl){SrY9PbKEz&v=dFa=3C|&>0Hv7$ke6z1a-)4+-KRgvS%MB@b&@0F@Otbyj z`;kaT^ud!)d=>a;yK1q>5s~&DvYnOl@2CA&L1am>TUv$#fp5Y4s0nuJe_T4Uy$mA! z_%g8HC-#kej2mebJQ=L$M}Q!IOrtI9WVB)ygP~ba-3YnIKg!eeE4YqUyIIfhnXKcA z3i15&l6*A@*Zp?9D;g?T<|Kb*48k9&XHv>x%U#h8=hvwh+`xwRQ*;1R#0SU3q14ud zNaGSu&n2&(_Y}?r(39?d2V5>SVe*=7)?Og?>F7*^k#iNOUcv$Y8CPWWs2RnbJA@tO zn+!JJUt`?UmL_aYpMYI!YPZx`ZTZEyp=s}R)4SMT;l=2}UqoRGT>f9p&sIpwM9A<( zUa>aEcG{eJfc6w&|2}HMR!mn(!Y+>dlk_{p6v}cD)I00SkBdF^xlPHcmr>3`+4Hb^ zEUE7@bTIzr8Z?ROueO3|4VttY^Bz(sJh?wC_y7`*E&W%&Q6<<(7>6- zE{=c}XZ61^(X(+?zL{$=*Pq7QtPqy>N_W0KE{YLbnEp+=(nSyK%1h%nSVV@_+dm-z z@~9RXq_9|lSVpENFVwb-P_q5mQ4LxWV%Q8kv$Zg=E;nVWm$^%{Mi#YD&{3~>QF<=` zB>)}M^U`tsw1kBhiRiTwH}yErW<A`NJG>p)mW^PM@6e41hFPp13~x99y_ofadh` z?UuVA-BF~ha-V0DhUim~nHw>dz?dF!Ex9JvP>m5oMt3t^I5$Wg!L18Rl1iug(f`TdIeu zm#%wFj4$%52CrW?v!;-)1EheEV9a05##Z(ex9-Jx);Vy})K$3~$QGf%_Yvq~MThHR zCBsjO*PBhHEh0aPk3s@0EeK|S;{t!76f{6l(p@b88K!`CGid`o&KAeW@j4BcUtmb| z5HG$(Hz}DmcxU^Tv-dEY&x{5RXn^9c1~DH2Fc~54B7nKa^15)4hM8Jowznh>6nu6S z%txTcJxWScIB$lBL^~FEieaBwa73K;iR3rxe=pGZUnv2^C5p|x2yRoDyZV(Qc}nmf z#&>{9%cLAJUh&!9zY*XR^mR>`asp#Ul2Hyu1cX+vD98#-fJ3GOhtJ$#Au50w09P15 z27~}I_5u!WwNny@&o*2(gOJsUC8-_?&p%_b1-7!gMJ$uW{~pta+)58DCJI)~rF~V` zghwH29TZsi09p@KrT>5TmihFIpnf5beAD@i*8R9&lKFC|j4m_fa^X@WHHN>EIwEZ7 z5B)sqeEsjFp9e=mq=~$O*_K#aI{pOS7GNc}v1SFI9cB;qd8^jfrLXYT+3^AWgq&3~ zc)WXbus*cPWrK-H%>K>XPi89Pxg6y%%MWHjsfp6@o-D1oyT_4i@NZYr8LD0Sm_Uad zgs&rijbe1&F3E~T%+QU*r1Vx1cNX(B+m2P@g0uH|icpQtlTitfzLCIH%?Eu_zon%n zPe^(sZPkP=u@0n!Zc?$M9|$d?1NxWP{GJq$0H z>#+G=RN&k4eMv71X&@3lHcFGUPF>$t%eeHI zlp>5VB{LPt1WOrWE3c>FOzBc@2`H$fBd9)R=?%aPLJ(HE(jS2FtijsN=^SQiRH-Ou zlm7lYSfEyJh-g8BD%Bi#Um$Id2NI;bD}CdBy;?ebMjI|?ioT?>;7W+yX+6haj!e)k z`W=Yegq}0Bc{Se!cS&(DrAeOfb{-uu0#`?kC5mcnT(;peB@Kh8(~sH3?3c_x7R(h_ zH84`fJbLab4)?HXNYDv~7=PT_rOhL}guK&SdT@o&y@dITWS$PO&irXED9hZFl>Qv2 zM{K(mATw4Age=|+ z##{arH*Za&)Fx!l;dvL|UT$p3lpEvnC$feonQ=KGv4rNeG66eQ=5qZI>(D*J)kwOL zn>{0!pkOJ29@C`IC^xN5N+**47)d`ihu?)eUF}b;RcQ%u)yk}UEQ)zJf6A3{ITqll z1qo=e8#hAKK0Z601XgxF@S)9eF_l6?SB@bDx6mg0xtM-OaK0oD96(!>nw!bgnsj(I z3z=7{>s8jIJ@NBnrY+~o#*Z@Utt9kju`B?aQ`|dI#qIJ+^l?pWWKwefUbpNBJ@z;KDBXv~7rW zBx>e*j2Kjo9b_H>_Lz}X#`hh$WSW%9hhVfRfr;GqKg+VmPhNQO(I15Q-)1m{aHeN*!Ir7OC~A#I z=+kciD@tgHzX0l}Q?OyFVvgEr3h_;mQ7Y3sRb&k3n5)Ke$WMncSBf`rHhtbUJYIfT zGQaF|#hgWXw_tB#fCfG8cJN%ozw-oQBlm9ctZ+XoR@U#%?W*M}!MaM9z&(}6SK9$`xN{ICsKlNhgS^Xx9KJ5*+o=(%GdpNFbiDrHJmZ~g}j CF#Ux9 literal 0 HcmV?d00001 From e4a709fd42a6fe4bd00e794cb448bb6ad3e88d4d Mon Sep 17 00:00:00 2001 From: Peyton Johnson Date: Tue, 3 Sep 2024 10:31:34 -0400 Subject: [PATCH 20/24] Updated reference locations in base sql file --- configuration/mysql/garage_port_drayage/port_drayage.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/configuration/mysql/garage_port_drayage/port_drayage.sql b/configuration/mysql/garage_port_drayage/port_drayage.sql index 5df1efa56..0e111bd22 100644 --- a/configuration/mysql/garage_port_drayage/port_drayage.sql +++ b/configuration/mysql/garage_port_drayage/port_drayage.sql @@ -26,7 +26,7 @@ CREATE TABLE `first_action` ( ) ENGINE=InnoDB DEFAULT CHARSET=latin1; LOCK TABLES `first_action` WRITE; -INSERT INTO `first_action` VALUES ('C1T-1','NULL',-1.4,-1.4,'ENTER_STAGING_AREA','one','two'); +INSERT INTO `first_action` VALUES ('C1T-1','NULL',-2.3,0.7,'ENTER_STAGING_AREA','one','two'); UNLOCK TABLES; -- @@ -45,5 +45,5 @@ CREATE TABLE `freight` ( ) ENGINE=InnoDB DEFAULT CHARSET=latin1; LOCK TABLES `freight` WRITE; -INSERT INTO `freight` VALUES ('C1T-1','NULL',-2.4,-2.4,'EXIT_PORT','zero','one'),('C1T-1','NULL',-1.4,-1.4,'ENTER_STAGING_AREA','one','two'),('C1T-1','CARGO_A',-0.4,-0.4,'PICKUP','two','three'),('C1T-1','CARGO_A',-3.4,-1.4,'EXIT_STAGING_AREA','three','four'),('C1T-1','CARGO_A',-3.4,-3.4,'ENTER_PORT','four','five'),('C1T-1','CARGO_A',-1.4,-6.4,'DROPOFF','five','six'),('C1T-1','CARGO_B',0.6,-6.4,'PICKUP','six','seven'),('C1T-1','CARGO_B',2.6,-5.4,'PORT_CHECKPOINT','seven','eight'),('C1T-1','CARGO_B',-2.4,-2.4,'EXIT_PORT','eight','nine'); +INSERT INTO `freight` VALUES ('C1T-1','NULL',-0.4,-0.4,'EXIT_PORT','zero','one'),('C1T-1','NULL',-2.3,0.7,'ENTER_STAGING_AREA','one','two'),('C1T-1','CARGO_A',-3.4,-1.4,'PICKUP','two','three'),('C1T-1','CARGO_A',-3.4,-3.4,'EXIT_STAGING_AREA','three','four'),('C1T-1','CARGO_A',-3.4,-5.4,'ENTER_PORT','four','five'),('C1T-1','CARGO_A',-0.4,-6.4,'DROPOFF','five','six'),('C1T-1','CARGO_B',2.6,-4.7,'PICKUP','six','seven'),('C1T-1','CARGO_B',0.4,-3.4,'PORT_CHECKPOINT','seven','eight'),('C1T-1','CARGO_B',-0.3,0.2,'EXIT_PORT','eight','nine'); UNLOCK TABLES; From f30f2feca2facf66a4bbe648d9876c04c66aa501 Mon Sep 17 00:00:00 2001 From: Peyton Johnson Date: Tue, 3 Sep 2024 11:20:36 -0400 Subject: [PATCH 21/24] Corrected filepath --- configuration/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configuration/docker-compose.yml b/configuration/docker-compose.yml index be01ea0b9..3ac32dd28 100755 --- a/configuration/docker-compose.yml +++ b/configuration/docker-compose.yml @@ -14,7 +14,7 @@ services: - mysql_root_password volumes: - ./mysql/localhost.sql:/docker-entrypoint-initdb.d/localhost.sql - - ./mysql/localhost.sql:/docker-entrypoint-initdb.d/port_drayage.sql + - ./mysql/port_drayage.sql:/docker-entrypoint-initdb.d/port_drayage.sql # The following 3 volumes are used for C1T. By default the standard "port_drayage.sql" file # will be loaded. Comment out the "port_drayage.sql" file above and uncomment the following # three files to enable the C1T sql files to be loaded. The container volumes MUST be reset From 755bdad33f1537e8197e87c33166e43cb1671fbc Mon Sep 17 00:00:00 2001 From: Peyton Johnson Date: Wed, 4 Sep 2024 09:56:43 -0400 Subject: [PATCH 22/24] Made Port Drayage destinations as stored in the SQL database more specific to reflect the real-world locations in the environment --- configuration/mysql/garage_port_drayage/port_drayage.sql | 4 ++-- .../mysql/garage_port_drayage/port_drayage_lane1.sql | 4 ++-- .../mysql/garage_port_drayage/port_drayage_lane2.sql | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/configuration/mysql/garage_port_drayage/port_drayage.sql b/configuration/mysql/garage_port_drayage/port_drayage.sql index 0e111bd22..bac758d58 100644 --- a/configuration/mysql/garage_port_drayage/port_drayage.sql +++ b/configuration/mysql/garage_port_drayage/port_drayage.sql @@ -26,7 +26,7 @@ CREATE TABLE `first_action` ( ) ENGINE=InnoDB DEFAULT CHARSET=latin1; LOCK TABLES `first_action` WRITE; -INSERT INTO `first_action` VALUES ('C1T-1','NULL',-2.3,0.7,'ENTER_STAGING_AREA','one','two'); +INSERT INTO `first_action` VALUES ('C1T-1','NULL',-2.46,0.56,'ENTER_STAGING_AREA','one','two'); UNLOCK TABLES; -- @@ -45,5 +45,5 @@ CREATE TABLE `freight` ( ) ENGINE=InnoDB DEFAULT CHARSET=latin1; LOCK TABLES `freight` WRITE; -INSERT INTO `freight` VALUES ('C1T-1','NULL',-0.4,-0.4,'EXIT_PORT','zero','one'),('C1T-1','NULL',-2.3,0.7,'ENTER_STAGING_AREA','one','two'),('C1T-1','CARGO_A',-3.4,-1.4,'PICKUP','two','three'),('C1T-1','CARGO_A',-3.4,-3.4,'EXIT_STAGING_AREA','three','four'),('C1T-1','CARGO_A',-3.4,-5.4,'ENTER_PORT','four','five'),('C1T-1','CARGO_A',-0.4,-6.4,'DROPOFF','five','six'),('C1T-1','CARGO_B',2.6,-4.7,'PICKUP','six','seven'),('C1T-1','CARGO_B',0.4,-3.4,'PORT_CHECKPOINT','seven','eight'),('C1T-1','CARGO_B',-0.3,0.2,'EXIT_PORT','eight','nine'); +INSERT INTO `freight` VALUES ('C1T-1','NULL',-0.45,-0.45,'EXIT_PORT','zero','one'),('C1T-1','NULL',-2.46,0.56,'ENTER_STAGING_AREA','one','two'),('C1T-1','CARGO_A',-3.45,-1.44,'PICKUP','two','three'),('C1T-1','CARGO_A',-3.45,-3.45,'EXIT_STAGING_AREA','three','four'),('C1T-1','CARGO_A',-3.45,-6.45,'ENTER_PORT','four','five'),('C1T-1','CARGO_A',-1.45,-6.45,'DROPOFF','five','six'),('C1T-1','CARGO_B',2.55,-4.5,'PICKUP','six','seven'),('C1T-1','CARGO_B',0.55,-3.45,'PORT_CHECKPOINT','seven','eight'),('C1T-1','CARGO_B',-0.45,-0.45,'EXIT_PORT','eight','nine'); UNLOCK TABLES; diff --git a/configuration/mysql/garage_port_drayage/port_drayage_lane1.sql b/configuration/mysql/garage_port_drayage/port_drayage_lane1.sql index 6f99b950c..0b9f0cd94 100644 --- a/configuration/mysql/garage_port_drayage/port_drayage_lane1.sql +++ b/configuration/mysql/garage_port_drayage/port_drayage_lane1.sql @@ -26,7 +26,7 @@ CREATE TABLE `first_action` ( ) ENGINE=InnoDB DEFAULT CHARSET=latin1; LOCK TABLES `first_action` WRITE; -INSERT INTO `first_action` VALUES ('C1T-1','NULL',-2.3,0.7,'ENTER_STAGING_AREA','one','two'); +INSERT INTO `first_action` VALUES ('C1T-1','NULL',-2.46,0.56,'ENTER_STAGING_AREA','one','two'); UNLOCK TABLES; -- @@ -45,5 +45,5 @@ CREATE TABLE `freight` ( ) ENGINE=InnoDB DEFAULT CHARSET=latin1; LOCK TABLES `freight` WRITE; -INSERT INTO `freight` VALUES ('C1T-1','NULL',-0.4,-0.4,'EXIT_PORT','zero','one'),('C1T-1','NULL',-2.3,0.7,'ENTER_STAGING_AREA','one','two'),('C1T-1','CARGO_A',-3.4,-1.4,'PICKUP','two','three'),('C1T-1','CARGO_A',-3.4,-3.4,'EXIT_STAGING_AREA','three','four'),('C1T-1','CARGO_A',-3.4,-5.4,'ENTER_PORT','four','five'),('C1T-1','CARGO_A',-0.4,-6.4,'DROPOFF','five','six'),('C1T-1','CARGO_B',1.6,-4.7,'PICKUP','six','seven'),('C1T-1','CARGO_B',0.4,-3.4,'PORT_CHECKPOINT','seven','eight'),('C1T-1','CARGO_B',-0.3,0.2,'EXIT_PORT','eight','nine'); +INSERT INTO `freight` VALUES ('C1T-1','NULL',-0.45,-0.45,'EXIT_PORT','zero','one'),('C1T-1','NULL',-2.46,0.56,'ENTER_STAGING_AREA','one','two'),('C1T-1','CARGO_A',-3.45,-1.44,'PICKUP','two','three'),('C1T-1','CARGO_A',-3.45,-3.45,'EXIT_STAGING_AREA','three','four'),('C1T-1','CARGO_A',-3.45,-6.45,'ENTER_PORT','four','five'),('C1T-1','CARGO_A',-1.45,-6.45,'DROPOFF','five','six'),('C1T-1','CARGO_B',1.55,-4.5,'PICKUP','six','seven'),('C1T-1','CARGO_B',0.55,-3.45,'PORT_CHECKPOINT','seven','eight'),('C1T-1','CARGO_B',-0.45,-0.45,'EXIT_PORT','eight','nine'); UNLOCK TABLES; diff --git a/configuration/mysql/garage_port_drayage/port_drayage_lane2.sql b/configuration/mysql/garage_port_drayage/port_drayage_lane2.sql index 0e111bd22..bac758d58 100644 --- a/configuration/mysql/garage_port_drayage/port_drayage_lane2.sql +++ b/configuration/mysql/garage_port_drayage/port_drayage_lane2.sql @@ -26,7 +26,7 @@ CREATE TABLE `first_action` ( ) ENGINE=InnoDB DEFAULT CHARSET=latin1; LOCK TABLES `first_action` WRITE; -INSERT INTO `first_action` VALUES ('C1T-1','NULL',-2.3,0.7,'ENTER_STAGING_AREA','one','two'); +INSERT INTO `first_action` VALUES ('C1T-1','NULL',-2.46,0.56,'ENTER_STAGING_AREA','one','two'); UNLOCK TABLES; -- @@ -45,5 +45,5 @@ CREATE TABLE `freight` ( ) ENGINE=InnoDB DEFAULT CHARSET=latin1; LOCK TABLES `freight` WRITE; -INSERT INTO `freight` VALUES ('C1T-1','NULL',-0.4,-0.4,'EXIT_PORT','zero','one'),('C1T-1','NULL',-2.3,0.7,'ENTER_STAGING_AREA','one','two'),('C1T-1','CARGO_A',-3.4,-1.4,'PICKUP','two','three'),('C1T-1','CARGO_A',-3.4,-3.4,'EXIT_STAGING_AREA','three','four'),('C1T-1','CARGO_A',-3.4,-5.4,'ENTER_PORT','four','five'),('C1T-1','CARGO_A',-0.4,-6.4,'DROPOFF','five','six'),('C1T-1','CARGO_B',2.6,-4.7,'PICKUP','six','seven'),('C1T-1','CARGO_B',0.4,-3.4,'PORT_CHECKPOINT','seven','eight'),('C1T-1','CARGO_B',-0.3,0.2,'EXIT_PORT','eight','nine'); +INSERT INTO `freight` VALUES ('C1T-1','NULL',-0.45,-0.45,'EXIT_PORT','zero','one'),('C1T-1','NULL',-2.46,0.56,'ENTER_STAGING_AREA','one','two'),('C1T-1','CARGO_A',-3.45,-1.44,'PICKUP','two','three'),('C1T-1','CARGO_A',-3.45,-3.45,'EXIT_STAGING_AREA','three','four'),('C1T-1','CARGO_A',-3.45,-6.45,'ENTER_PORT','four','five'),('C1T-1','CARGO_A',-1.45,-6.45,'DROPOFF','five','six'),('C1T-1','CARGO_B',2.55,-4.5,'PICKUP','six','seven'),('C1T-1','CARGO_B',0.55,-3.45,'PORT_CHECKPOINT','seven','eight'),('C1T-1','CARGO_B',-0.45,-0.45,'EXIT_PORT','eight','nine'); UNLOCK TABLES; From e7ecbc24f85a05ca07d2e21eaf4bab2fa1470a05 Mon Sep 17 00:00:00 2001 From: Peyton Johnson Date: Wed, 4 Sep 2024 14:17:46 -0400 Subject: [PATCH 23/24] Updated the Port Exit location to better correspond with the appropriate graph node --- configuration/mysql/garage_port_drayage/port_drayage.sql | 2 +- configuration/mysql/garage_port_drayage/port_drayage_lane1.sql | 2 +- configuration/mysql/garage_port_drayage/port_drayage_lane2.sql | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/configuration/mysql/garage_port_drayage/port_drayage.sql b/configuration/mysql/garage_port_drayage/port_drayage.sql index bac758d58..f2faf64e0 100644 --- a/configuration/mysql/garage_port_drayage/port_drayage.sql +++ b/configuration/mysql/garage_port_drayage/port_drayage.sql @@ -45,5 +45,5 @@ CREATE TABLE `freight` ( ) ENGINE=InnoDB DEFAULT CHARSET=latin1; LOCK TABLES `freight` WRITE; -INSERT INTO `freight` VALUES ('C1T-1','NULL',-0.45,-0.45,'EXIT_PORT','zero','one'),('C1T-1','NULL',-2.46,0.56,'ENTER_STAGING_AREA','one','two'),('C1T-1','CARGO_A',-3.45,-1.44,'PICKUP','two','three'),('C1T-1','CARGO_A',-3.45,-3.45,'EXIT_STAGING_AREA','three','four'),('C1T-1','CARGO_A',-3.45,-6.45,'ENTER_PORT','four','five'),('C1T-1','CARGO_A',-1.45,-6.45,'DROPOFF','five','six'),('C1T-1','CARGO_B',2.55,-4.5,'PICKUP','six','seven'),('C1T-1','CARGO_B',0.55,-3.45,'PORT_CHECKPOINT','seven','eight'),('C1T-1','CARGO_B',-0.45,-0.45,'EXIT_PORT','eight','nine'); +INSERT INTO `freight` VALUES ('C1T-1','NULL',-0.45,-0.45,'EXIT_PORT','zero','one'),('C1T-1','NULL',-2.46,0.56,'ENTER_STAGING_AREA','one','two'),('C1T-1','CARGO_A',-3.45,-1.44,'PICKUP','two','three'),('C1T-1','CARGO_A',-3.45,-3.45,'EXIT_STAGING_AREA','three','four'),('C1T-1','CARGO_A',-3.45,-5.45,'ENTER_PORT','four','five'),('C1T-1','CARGO_A',-1.45,-6.45,'DROPOFF','five','six'),('C1T-1','CARGO_B',2.55,-4.5,'PICKUP','six','seven'),('C1T-1','CARGO_B',0.55,-3.45,'PORT_CHECKPOINT','seven','eight'),('C1T-1','CARGO_B',-0.45,-0.45,'EXIT_PORT','eight','nine'); UNLOCK TABLES; diff --git a/configuration/mysql/garage_port_drayage/port_drayage_lane1.sql b/configuration/mysql/garage_port_drayage/port_drayage_lane1.sql index 0b9f0cd94..3bb41a7e1 100644 --- a/configuration/mysql/garage_port_drayage/port_drayage_lane1.sql +++ b/configuration/mysql/garage_port_drayage/port_drayage_lane1.sql @@ -45,5 +45,5 @@ CREATE TABLE `freight` ( ) ENGINE=InnoDB DEFAULT CHARSET=latin1; LOCK TABLES `freight` WRITE; -INSERT INTO `freight` VALUES ('C1T-1','NULL',-0.45,-0.45,'EXIT_PORT','zero','one'),('C1T-1','NULL',-2.46,0.56,'ENTER_STAGING_AREA','one','two'),('C1T-1','CARGO_A',-3.45,-1.44,'PICKUP','two','three'),('C1T-1','CARGO_A',-3.45,-3.45,'EXIT_STAGING_AREA','three','four'),('C1T-1','CARGO_A',-3.45,-6.45,'ENTER_PORT','four','five'),('C1T-1','CARGO_A',-1.45,-6.45,'DROPOFF','five','six'),('C1T-1','CARGO_B',1.55,-4.5,'PICKUP','six','seven'),('C1T-1','CARGO_B',0.55,-3.45,'PORT_CHECKPOINT','seven','eight'),('C1T-1','CARGO_B',-0.45,-0.45,'EXIT_PORT','eight','nine'); +INSERT INTO `freight` VALUES ('C1T-1','NULL',-0.45,-0.45,'EXIT_PORT','zero','one'),('C1T-1','NULL',-2.46,0.56,'ENTER_STAGING_AREA','one','two'),('C1T-1','CARGO_A',-3.45,-1.44,'PICKUP','two','three'),('C1T-1','CARGO_A',-3.45,-3.45,'EXIT_STAGING_AREA','three','four'),('C1T-1','CARGO_A',-3.45,-5.45,'ENTER_PORT','four','five'),('C1T-1','CARGO_A',-1.45,-6.45,'DROPOFF','five','six'),('C1T-1','CARGO_B',1.55,-4.5,'PICKUP','six','seven'),('C1T-1','CARGO_B',0.55,-3.45,'PORT_CHECKPOINT','seven','eight'),('C1T-1','CARGO_B',-0.45,-0.45,'EXIT_PORT','eight','nine'); UNLOCK TABLES; diff --git a/configuration/mysql/garage_port_drayage/port_drayage_lane2.sql b/configuration/mysql/garage_port_drayage/port_drayage_lane2.sql index bac758d58..f2faf64e0 100644 --- a/configuration/mysql/garage_port_drayage/port_drayage_lane2.sql +++ b/configuration/mysql/garage_port_drayage/port_drayage_lane2.sql @@ -45,5 +45,5 @@ CREATE TABLE `freight` ( ) ENGINE=InnoDB DEFAULT CHARSET=latin1; LOCK TABLES `freight` WRITE; -INSERT INTO `freight` VALUES ('C1T-1','NULL',-0.45,-0.45,'EXIT_PORT','zero','one'),('C1T-1','NULL',-2.46,0.56,'ENTER_STAGING_AREA','one','two'),('C1T-1','CARGO_A',-3.45,-1.44,'PICKUP','two','three'),('C1T-1','CARGO_A',-3.45,-3.45,'EXIT_STAGING_AREA','three','four'),('C1T-1','CARGO_A',-3.45,-6.45,'ENTER_PORT','four','five'),('C1T-1','CARGO_A',-1.45,-6.45,'DROPOFF','five','six'),('C1T-1','CARGO_B',2.55,-4.5,'PICKUP','six','seven'),('C1T-1','CARGO_B',0.55,-3.45,'PORT_CHECKPOINT','seven','eight'),('C1T-1','CARGO_B',-0.45,-0.45,'EXIT_PORT','eight','nine'); +INSERT INTO `freight` VALUES ('C1T-1','NULL',-0.45,-0.45,'EXIT_PORT','zero','one'),('C1T-1','NULL',-2.46,0.56,'ENTER_STAGING_AREA','one','two'),('C1T-1','CARGO_A',-3.45,-1.44,'PICKUP','two','three'),('C1T-1','CARGO_A',-3.45,-3.45,'EXIT_STAGING_AREA','three','four'),('C1T-1','CARGO_A',-3.45,-5.45,'ENTER_PORT','four','five'),('C1T-1','CARGO_A',-1.45,-6.45,'DROPOFF','five','six'),('C1T-1','CARGO_B',2.55,-4.5,'PICKUP','six','seven'),('C1T-1','CARGO_B',0.55,-3.45,'PORT_CHECKPOINT','seven','eight'),('C1T-1','CARGO_B',-0.45,-0.45,'EXIT_PORT','eight','nine'); UNLOCK TABLES; From 7dd7a9018f153b4907ee075ba819f44ebd2fa67b Mon Sep 17 00:00:00 2001 From: Saikrishna Bairamoni <84093461+SaikrishnaBairamoni@users.noreply.github.com> Date: Wed, 11 Sep 2024 11:03:31 -0400 Subject: [PATCH 24/24] Update Release_notes.md --- docs/Release_notes.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/Release_notes.md b/docs/Release_notes.md index 385b8849e..6966fdca3 100644 --- a/docs/Release_notes.md +++ b/docs/Release_notes.md @@ -1,6 +1,17 @@ V2X-Hub Release Notes --------------------------------- +Version 7.8.1, released Sep 11th, 2024 +-------------------------------------------------------- + +**Summary:** +V2X Hub release 7.8.1 includes additional SQL files which are created to store command sequences V2X Hub sends to a C1T vehicle to coordinate Port Drayage. These files contain actions and destination points configured to send the vehicle through a pre-designed mock port environment, with one instructing the vehicle to pick up cargo in Lane 1 and the other instructing the vehicle to proceed to Lane 2. + +Enhancement in this release: + +- V2X-Hub PR 639: Added SQL files for use with the C1T Port Drayage configuration and updated docker compose files to streamline deployment. +- V2X-Hub PR 641: Updated SQL Port Drayage action locations to improve accuracy in tracking port exits. + Version 7.8.0, released Aug 26th, 2024 --------------------------------------------------------

;8+`Ud73tS7!$Lb*J9)w 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