Skip to content

Commit

Permalink
Merge pull request #30 from obs-ai/roy.add_jsonpath_array_parsing
Browse files Browse the repository at this point in the history
Add jsonpath and array parsing
  • Loading branch information
royshil committed Oct 5, 2023
2 parents 1c893a0 + d444f55 commit 0d90ac4
Show file tree
Hide file tree
Showing 21 changed files with 471 additions and 235 deletions.
3 changes: 2 additions & 1 deletion .github/scripts/.build.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ ${_usage_host:-}"
-DCODESIGN_IDENTITY=${CODESIGN_IDENT:--}
)
cmake_build_args+=(--preset ${_preset} --parallel --config ${config} -- ONLY_ACTIVE_ARCH=NO -arch x86_64 -arch arm64)
cmake_build_args+=(--preset ${_preset} --parallel --config ${config} -- ONLY_ACTIVE_ARCH=YES -arch x86_64 ) # -arch arm64
cmake_install_args+=(build_macos --config ${config} --prefix "${project_root}/release/${config}")
local -a xcbeautify_opts=()
Expand All @@ -242,6 +242,7 @@ ${_usage_host:-}"
-G "${generator}"
-DQT_VERSION=${QT_VERSION:-6}
-DCMAKE_BUILD_TYPE=${config}
--compile-no-warning-as-error
)
local cmake_version
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[submodule "vendor/curl"]
path = vendor/curl
url = https://github.com/curl/curl.git
[submodule "vendor/jsoncons"]
path = vendor/jsoncons
url = https://github.com/danielaparker/jsoncons.git
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,13 @@ else()
target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE libpugixml)
endif()

include(cmake/BuildJSONCONS.cmake)
target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE jsoncons)

target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE vendor/nlohmann-json)

target_sources(${CMAKE_PROJECT_NAME} PRIVATE src/plugin-main.c src/url-source.cpp src/ui/RequestBuilder.cpp
src/request-data.cpp src/ui/text-render-helper.cpp src/url-source-info.c)
add_subdirectory(src/parsers)

set_target_properties_plugin(${CMAKE_PROJECT_NAME} PROPERTIES OUTPUT_NAME ${_name})
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,13 @@ Use the CI scripts again
$ ./.github/scripts/build-linux.sh
```

Copy the results to the standard OBS folders on Ubuntu
```sh
$ sudo cp -R release/RelWithDebInfo/lib/* /usr/lib/x86_64-linux-gnu/
$ sudo cp -R release/RelWithDebInfo/share/* /usr/share/
```
Note: The official [OBS plugins guide](https://obsproject.com/kb/plugins-guide) recommends adding plugins to the `~/.config/obs-studio/plugins` folder.

### Windows

Use the CI scripts again, for example:
Expand Down
2 changes: 2 additions & 0 deletions cmake/BuildJSONCONS.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
set(JSONCONS_BUILD_TESTS OFF)
add_subdirectory(${CMAKE_SOURCE_DIR}/vendor/jsoncons ${CMAKE_BINARY_DIR}/jsoncons EXCLUDE_FROM_ALL)
2 changes: 1 addition & 1 deletion cmake/BuildMyCurl.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ set(CURL_USE_LIBSSH2 OFF)
set(BUILD_TESTING OFF)
set(PICKY_COMPILER OFF)

add_subdirectory(vendor/curl EXCLUDE_FROM_ALL)
add_subdirectory(${CMAKE_SOURCE_DIR}/vendor/curl EXCLUDE_FROM_ALL)
if(OS_MACOS)
target_compile_options(
libcurl PRIVATE -Wno-error=ambiguous-macro -Wno-error=deprecated-declarations -Wno-error=unreachable-code
Expand Down
2 changes: 1 addition & 1 deletion cmake/BuildPugiXML.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ ExternalProject_Add(
pugixml_build
URL https://github.com/zeux/pugixml/releases/download/v1.13/pugixml-1.13.tar.gz
URL_MD5 3e4c588e03bdca140844f3c47c1a995e
INSTALL_BYPRODUCTS <INSTALL_DIR>/lib/${CMAKE_STATIC_LIBRARY_PREFIX}pugixml${CMAKE_STATIC_LIBRARY_SUFFIX}
CMAKE_GENERATOR ${CMAKE_GENERATOR}
INSTALL_BYPRODUCTS <INSTALL_DIR>/lib/${CMAKE_STATIC_LIBRARY_PREFIX}pugixml${CMAKE_STATIC_LIBRARY_SUFFIX}
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=<INSTALL_DIR> -DBUILD_SHARED_LIBS=OFF -DPUGIXML_BUILD_TESTS=OFF
${PUGIXML_CMAKE_PLATFORM_OPTIONS})

Expand Down
3 changes: 0 additions & 3 deletions cmake/linux/compilerconfig.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,6 @@ add_compile_options(
# Add support for color diagnostics and CMake switch for warnings as errors to CMake < 3.24
if(CMAKE_VERSION VERSION_LESS 3.24.0)
add_compile_options($<$<C_COMPILER_ID:Clang>:-fcolor-diagnostics> $<$<CXX_COMPILER_ID:Clang>:-fcolor-diagnostics>)
if(CMAKE_COMPILE_WARNING_AS_ERROR)
add_compile_options(-Werror)
endif()
else()
set(CMAKE_COLOR_DIAGNOSTICS ON)
endif()
Expand Down
7 changes: 7 additions & 0 deletions src/parsers/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
target_sources(${CMAKE_PROJECT_NAME} PRIVATE jsonpointer.cpp jsonpath.cpp regex.cpp xml.cpp errors.cpp)

# on linux, disable conversion errors
if(UNIX AND NOT APPLE)
set(CMAKE_COMPILE_WARNING_AS_ERROR OFF)
add_compile_options(-Wno-error -Wno-conversion -Wno-shadow -Wno-unused-parameter)
endif()
14 changes: 14 additions & 0 deletions src/parsers/errors.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#include "plugin-support.h"
#include "errors.h"

#include <obs-module.h>

struct request_data_handler_response make_fail_parse_response(const std::string &error_message)
{
obs_log(LOG_INFO, "Failed to parse response: %s", error_message.c_str());
// Build an error response
struct request_data_handler_response responseFail;
responseFail.error_message = error_message;
responseFail.status_code = URL_SOURCE_REQUEST_PARSING_ERROR_CODE;
return responseFail;
}
8 changes: 8 additions & 0 deletions src/parsers/errors.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#ifndef PARSERS_ERRORS_H
#define PARSERS_ERRORS_H

#include "request-data.h"

struct request_data_handler_response make_fail_parse_response(const std::string &error_message);

#endif // PARSERS_ERRORS_H
61 changes: 61 additions & 0 deletions src/parsers/jsonpath.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#include "request-data.h"
#include "errors.h"

#include <jsoncons/basic_json.hpp>
#include <jsoncons/json_parser.hpp>
#include <jsoncons_ext/jsonpath/jsonpath.hpp>
#include <obs-module.h>

struct request_data_handler_response parse_json(struct request_data_handler_response response,
const url_source_request_data *request_data)
{
UNUSED_PARAMETER(request_data);

// Parse the response as JSON
jsoncons::json json;
try {
json = jsoncons::json::parse(response.body);
} catch (jsoncons::json_exception &e) {
return make_fail_parse_response(e.what());
}
// Return the whole JSON object
response.body_parts_parsed.push_back(json.as_string());
return response;
}

struct request_data_handler_response parse_json_path(struct request_data_handler_response response,
const url_source_request_data *request_data)
{

// Parse the response as JSON
jsoncons::json json;
try {
json = jsoncons::json::parse(response.body);
} catch (jsoncons::json_exception &e) {
return make_fail_parse_response(e.what());
}
std::vector<std::string> parsed_output = {};
// Get the output value
if (request_data->output_json_path != "") {
try {
const auto value = jsoncons::jsonpath::json_query(
json, request_data->output_json_path);
if (value.is_array()) {
// extract array items as strings
for (const auto &item : value.array_range()) {
parsed_output.push_back(item.as_string());
}
} else {
parsed_output.push_back(value.as_string());
}
} catch (jsoncons::json_exception &e) {
return make_fail_parse_response(e.what());
}
} else {
// Return the whole JSON object
parsed_output.clear();
parsed_output.push_back(json.as_string());
}
response.body_parts_parsed = parsed_output;
return response;
}
43 changes: 43 additions & 0 deletions src/parsers/jsonpointer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#include "request-data.h"
#include "errors.h"

#include <nlohmann/json.hpp>

struct request_data_handler_response
parse_json_pointer(struct request_data_handler_response response,
const url_source_request_data *request_data)
{
// Parse the response as JSON
nlohmann::json json;
try {
json = nlohmann::json::parse(response.body);
} catch (nlohmann::json::parse_error &e) {
return make_fail_parse_response(e.what());
}
std::string parsed_output = "";
// Get the output value
if (request_data->output_json_pointer != "") {
try {
const auto value = json.at(nlohmann::json::json_pointer(
request_data->output_json_pointer))
.get<nlohmann::json>();
if (value.is_string()) {
parsed_output = value.get<std::string>();
} else {
parsed_output = value.dump();
}
// remove potential prefix and postfix quotes, conversion from string
if (parsed_output.size() > 1 && parsed_output.front() == '"' &&
parsed_output.back() == '"') {
parsed_output = parsed_output.substr(1, parsed_output.size() - 2);
}
} catch (nlohmann::json::exception &e) {
return make_fail_parse_response(e.what());
}
} else {
// Return the whole JSON object
parsed_output = json.dump();
}
response.body_parts_parsed.push_back(parsed_output);
return response;
}
26 changes: 26 additions & 0 deletions src/parsers/parsers.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#ifndef PARSERS_H
#define PARSERS_H

#include "request-data.h"

struct request_data_handler_response parse_json(struct request_data_handler_response response,
const url_source_request_data *request_data);

struct request_data_handler_response
parse_json_pointer(struct request_data_handler_response response,
const url_source_request_data *request_data);

struct request_data_handler_response parse_json_path(struct request_data_handler_response response,
const url_source_request_data *request_data);

struct request_data_handler_response parse_regex(struct request_data_handler_response response,
const url_source_request_data *request_data);

struct request_data_handler_response parse_xml(struct request_data_handler_response response,
const url_source_request_data *request_data);

struct request_data_handler_response
parse_xml_by_xquery(struct request_data_handler_response response,
const url_source_request_data *request_data);

#endif // PARSERS_H
37 changes: 37 additions & 0 deletions src/parsers/regex.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@

#include "request-data.h"
#include "plugin-support.h"

#include <regex>
#include <obs-module.h>

struct request_data_handler_response parse_regex(struct request_data_handler_response response,
const url_source_request_data *request_data)
{
std::string parsed_output = "";
if (request_data->output_regex == "") {
// Return the whole response body
parsed_output = response.body;
} else {
// Parse the response as a regex
std::regex regex(request_data->output_regex,
std::regex_constants::ECMAScript | std::regex_constants::optimize);
std::smatch match;
if (std::regex_search(response.body, match, regex)) {
if (match.size() > 1) {
parsed_output = match[1].str();
} else {
parsed_output = match[0].str();
}
} else {
obs_log(LOG_INFO, "Failed to match regex");
// Return an error response
struct request_data_handler_response responseFail;
responseFail.error_message = "Failed to match regex";
responseFail.status_code = URL_SOURCE_REQUEST_PARSING_ERROR_CODE;
return responseFail;
}
}
response.body_parts_parsed.push_back(parsed_output);
return response;
}
71 changes: 71 additions & 0 deletions src/parsers/xml.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@

#include "request-data.h"
#include "plugin-support.h"

#include <pugixml.hpp>
#include <obs-module.h>

struct request_data_handler_response parse_xml(struct request_data_handler_response response,
const url_source_request_data *request_data)
{
// Parse the response as XML using pugixml
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_string(response.body.c_str());
if (!result) {
obs_log(LOG_INFO, "Failed to parse XML response: %s", result.description());
// Return an error response
struct request_data_handler_response responseFail;
responseFail.error_message = result.description();
responseFail.status_code = URL_SOURCE_REQUEST_PARSING_ERROR_CODE;
return responseFail;
}
std::string parsed_output = "";
// Get the output value
if (request_data->output_xpath != "") {
pugi::xpath_node_set nodes = doc.select_nodes(request_data->output_xpath.c_str());
if (nodes.size() > 0) {
parsed_output = nodes[0].node().text().get();
} else {
obs_log(LOG_INFO, "Failed to get XML value");
// Return an error response
struct request_data_handler_response responseFail;
responseFail.error_message = "Failed to get XML value";
responseFail.status_code = URL_SOURCE_REQUEST_PARSING_ERROR_CODE;
return responseFail;
}
} else {
// Return the whole XML object
parsed_output = response.body;
}
response.body_parts_parsed.push_back(parsed_output);
return response;
}

struct request_data_handler_response
parse_xml_by_xquery(struct request_data_handler_response response,
const url_source_request_data *request_data)
{
// Parse the response as XML using pugixml
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_string(response.body.c_str());
if (!result) {
obs_log(LOG_INFO, "Failed to parse XML response: %s", result.description());
// Return an error response
struct request_data_handler_response responseFail;
responseFail.error_message = result.description();
responseFail.status_code = URL_SOURCE_REQUEST_PARSING_ERROR_CODE;
return responseFail;
}
std::string parsed_output = "";
// Get the output value
if (request_data->output_xquery != "") {
pugi::xpath_query query_entity(request_data->output_xquery.c_str());
std::string s = query_entity.evaluate_string(doc);
parsed_output = s;
} else {
// Return the whole XML object
parsed_output = response.body;
}
response.body_parts_parsed.push_back(parsed_output);
return response;
}

0 comments on commit 0d90ac4

Please sign in to comment.