Skip to content

Commit 0d90ac4

Browse files
authored
Merge pull request #30 from obs-ai/roy.add_jsonpath_array_parsing
Add jsonpath and array parsing
2 parents 1c893a0 + d444f55 commit 0d90ac4

21 files changed

+471
-235
lines changed

.github/scripts/.build.zsh

+2-1
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ ${_usage_host:-}"
230230
-DCODESIGN_IDENTITY=${CODESIGN_IDENT:--}
231231
)
232232
233-
cmake_build_args+=(--preset ${_preset} --parallel --config ${config} -- ONLY_ACTIVE_ARCH=NO -arch x86_64 -arch arm64)
233+
cmake_build_args+=(--preset ${_preset} --parallel --config ${config} -- ONLY_ACTIVE_ARCH=YES -arch x86_64 ) # -arch arm64
234234
cmake_install_args+=(build_macos --config ${config} --prefix "${project_root}/release/${config}")
235235
236236
local -a xcbeautify_opts=()
@@ -242,6 +242,7 @@ ${_usage_host:-}"
242242
-G "${generator}"
243243
-DQT_VERSION=${QT_VERSION:-6}
244244
-DCMAKE_BUILD_TYPE=${config}
245+
--compile-no-warning-as-error
245246
)
246247
247248
local cmake_version

.gitmodules

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
[submodule "vendor/curl"]
22
path = vendor/curl
33
url = https://github.com/curl/curl.git
4+
[submodule "vendor/jsoncons"]
5+
path = vendor/jsoncons
6+
url = https://github.com/danielaparker/jsoncons.git

CMakeLists.txt

+4
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,13 @@ else()
5959
target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE libpugixml)
6060
endif()
6161

62+
include(cmake/BuildJSONCONS.cmake)
63+
target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE jsoncons)
64+
6265
target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE vendor/nlohmann-json)
6366

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

6771
set_target_properties_plugin(${CMAKE_PROJECT_NAME} PROPERTIES OUTPUT_NAME ${_name})

README.md

+7
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,13 @@ Use the CI scripts again
9999
$ ./.github/scripts/build-linux.sh
100100
```
101101

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

104111
Use the CI scripts again, for example:

cmake/BuildJSONCONS.cmake

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
set(JSONCONS_BUILD_TESTS OFF)
2+
add_subdirectory(${CMAKE_SOURCE_DIR}/vendor/jsoncons ${CMAKE_BINARY_DIR}/jsoncons EXCLUDE_FROM_ALL)

cmake/BuildMyCurl.cmake

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ set(CURL_USE_LIBSSH2 OFF)
1515
set(BUILD_TESTING OFF)
1616
set(PICKY_COMPILER OFF)
1717

18-
add_subdirectory(vendor/curl EXCLUDE_FROM_ALL)
18+
add_subdirectory(${CMAKE_SOURCE_DIR}/vendor/curl EXCLUDE_FROM_ALL)
1919
if(OS_MACOS)
2020
target_compile_options(
2121
libcurl PRIVATE -Wno-error=ambiguous-macro -Wno-error=deprecated-declarations -Wno-error=unreachable-code

cmake/BuildPugiXML.cmake

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ ExternalProject_Add(
1010
pugixml_build
1111
URL https://github.com/zeux/pugixml/releases/download/v1.13/pugixml-1.13.tar.gz
1212
URL_MD5 3e4c588e03bdca140844f3c47c1a995e
13-
INSTALL_BYPRODUCTS <INSTALL_DIR>/lib/${CMAKE_STATIC_LIBRARY_PREFIX}pugixml${CMAKE_STATIC_LIBRARY_SUFFIX}
1413
CMAKE_GENERATOR ${CMAKE_GENERATOR}
14+
INSTALL_BYPRODUCTS <INSTALL_DIR>/lib/${CMAKE_STATIC_LIBRARY_PREFIX}pugixml${CMAKE_STATIC_LIBRARY_SUFFIX}
1515
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=<INSTALL_DIR> -DBUILD_SHARED_LIBS=OFF -DPUGIXML_BUILD_TESTS=OFF
1616
${PUGIXML_CMAKE_PLATFORM_OPTIONS})
1717

cmake/linux/compilerconfig.cmake

-3
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,6 @@ add_compile_options(
5656
# Add support for color diagnostics and CMake switch for warnings as errors to CMake < 3.24
5757
if(CMAKE_VERSION VERSION_LESS 3.24.0)
5858
add_compile_options($<$<C_COMPILER_ID:Clang>:-fcolor-diagnostics> $<$<CXX_COMPILER_ID:Clang>:-fcolor-diagnostics>)
59-
if(CMAKE_COMPILE_WARNING_AS_ERROR)
60-
add_compile_options(-Werror)
61-
endif()
6259
else()
6360
set(CMAKE_COLOR_DIAGNOSTICS ON)
6461
endif()

src/parsers/CMakeLists.txt

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
target_sources(${CMAKE_PROJECT_NAME} PRIVATE jsonpointer.cpp jsonpath.cpp regex.cpp xml.cpp errors.cpp)
2+
3+
# on linux, disable conversion errors
4+
if(UNIX AND NOT APPLE)
5+
set(CMAKE_COMPILE_WARNING_AS_ERROR OFF)
6+
add_compile_options(-Wno-error -Wno-conversion -Wno-shadow -Wno-unused-parameter)
7+
endif()

src/parsers/errors.cpp

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#include "plugin-support.h"
2+
#include "errors.h"
3+
4+
#include <obs-module.h>
5+
6+
struct request_data_handler_response make_fail_parse_response(const std::string &error_message)
7+
{
8+
obs_log(LOG_INFO, "Failed to parse response: %s", error_message.c_str());
9+
// Build an error response
10+
struct request_data_handler_response responseFail;
11+
responseFail.error_message = error_message;
12+
responseFail.status_code = URL_SOURCE_REQUEST_PARSING_ERROR_CODE;
13+
return responseFail;
14+
}

src/parsers/errors.h

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#ifndef PARSERS_ERRORS_H
2+
#define PARSERS_ERRORS_H
3+
4+
#include "request-data.h"
5+
6+
struct request_data_handler_response make_fail_parse_response(const std::string &error_message);
7+
8+
#endif // PARSERS_ERRORS_H

src/parsers/jsonpath.cpp

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
#include "request-data.h"
2+
#include "errors.h"
3+
4+
#include <jsoncons/basic_json.hpp>
5+
#include <jsoncons/json_parser.hpp>
6+
#include <jsoncons_ext/jsonpath/jsonpath.hpp>
7+
#include <obs-module.h>
8+
9+
struct request_data_handler_response parse_json(struct request_data_handler_response response,
10+
const url_source_request_data *request_data)
11+
{
12+
UNUSED_PARAMETER(request_data);
13+
14+
// Parse the response as JSON
15+
jsoncons::json json;
16+
try {
17+
json = jsoncons::json::parse(response.body);
18+
} catch (jsoncons::json_exception &e) {
19+
return make_fail_parse_response(e.what());
20+
}
21+
// Return the whole JSON object
22+
response.body_parts_parsed.push_back(json.as_string());
23+
return response;
24+
}
25+
26+
struct request_data_handler_response parse_json_path(struct request_data_handler_response response,
27+
const url_source_request_data *request_data)
28+
{
29+
30+
// Parse the response as JSON
31+
jsoncons::json json;
32+
try {
33+
json = jsoncons::json::parse(response.body);
34+
} catch (jsoncons::json_exception &e) {
35+
return make_fail_parse_response(e.what());
36+
}
37+
std::vector<std::string> parsed_output = {};
38+
// Get the output value
39+
if (request_data->output_json_path != "") {
40+
try {
41+
const auto value = jsoncons::jsonpath::json_query(
42+
json, request_data->output_json_path);
43+
if (value.is_array()) {
44+
// extract array items as strings
45+
for (const auto &item : value.array_range()) {
46+
parsed_output.push_back(item.as_string());
47+
}
48+
} else {
49+
parsed_output.push_back(value.as_string());
50+
}
51+
} catch (jsoncons::json_exception &e) {
52+
return make_fail_parse_response(e.what());
53+
}
54+
} else {
55+
// Return the whole JSON object
56+
parsed_output.clear();
57+
parsed_output.push_back(json.as_string());
58+
}
59+
response.body_parts_parsed = parsed_output;
60+
return response;
61+
}

src/parsers/jsonpointer.cpp

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#include "request-data.h"
2+
#include "errors.h"
3+
4+
#include <nlohmann/json.hpp>
5+
6+
struct request_data_handler_response
7+
parse_json_pointer(struct request_data_handler_response response,
8+
const url_source_request_data *request_data)
9+
{
10+
// Parse the response as JSON
11+
nlohmann::json json;
12+
try {
13+
json = nlohmann::json::parse(response.body);
14+
} catch (nlohmann::json::parse_error &e) {
15+
return make_fail_parse_response(e.what());
16+
}
17+
std::string parsed_output = "";
18+
// Get the output value
19+
if (request_data->output_json_pointer != "") {
20+
try {
21+
const auto value = json.at(nlohmann::json::json_pointer(
22+
request_data->output_json_pointer))
23+
.get<nlohmann::json>();
24+
if (value.is_string()) {
25+
parsed_output = value.get<std::string>();
26+
} else {
27+
parsed_output = value.dump();
28+
}
29+
// remove potential prefix and postfix quotes, conversion from string
30+
if (parsed_output.size() > 1 && parsed_output.front() == '"' &&
31+
parsed_output.back() == '"') {
32+
parsed_output = parsed_output.substr(1, parsed_output.size() - 2);
33+
}
34+
} catch (nlohmann::json::exception &e) {
35+
return make_fail_parse_response(e.what());
36+
}
37+
} else {
38+
// Return the whole JSON object
39+
parsed_output = json.dump();
40+
}
41+
response.body_parts_parsed.push_back(parsed_output);
42+
return response;
43+
}

src/parsers/parsers.h

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#ifndef PARSERS_H
2+
#define PARSERS_H
3+
4+
#include "request-data.h"
5+
6+
struct request_data_handler_response parse_json(struct request_data_handler_response response,
7+
const url_source_request_data *request_data);
8+
9+
struct request_data_handler_response
10+
parse_json_pointer(struct request_data_handler_response response,
11+
const url_source_request_data *request_data);
12+
13+
struct request_data_handler_response parse_json_path(struct request_data_handler_response response,
14+
const url_source_request_data *request_data);
15+
16+
struct request_data_handler_response parse_regex(struct request_data_handler_response response,
17+
const url_source_request_data *request_data);
18+
19+
struct request_data_handler_response parse_xml(struct request_data_handler_response response,
20+
const url_source_request_data *request_data);
21+
22+
struct request_data_handler_response
23+
parse_xml_by_xquery(struct request_data_handler_response response,
24+
const url_source_request_data *request_data);
25+
26+
#endif // PARSERS_H

src/parsers/regex.cpp

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
2+
#include "request-data.h"
3+
#include "plugin-support.h"
4+
5+
#include <regex>
6+
#include <obs-module.h>
7+
8+
struct request_data_handler_response parse_regex(struct request_data_handler_response response,
9+
const url_source_request_data *request_data)
10+
{
11+
std::string parsed_output = "";
12+
if (request_data->output_regex == "") {
13+
// Return the whole response body
14+
parsed_output = response.body;
15+
} else {
16+
// Parse the response as a regex
17+
std::regex regex(request_data->output_regex,
18+
std::regex_constants::ECMAScript | std::regex_constants::optimize);
19+
std::smatch match;
20+
if (std::regex_search(response.body, match, regex)) {
21+
if (match.size() > 1) {
22+
parsed_output = match[1].str();
23+
} else {
24+
parsed_output = match[0].str();
25+
}
26+
} else {
27+
obs_log(LOG_INFO, "Failed to match regex");
28+
// Return an error response
29+
struct request_data_handler_response responseFail;
30+
responseFail.error_message = "Failed to match regex";
31+
responseFail.status_code = URL_SOURCE_REQUEST_PARSING_ERROR_CODE;
32+
return responseFail;
33+
}
34+
}
35+
response.body_parts_parsed.push_back(parsed_output);
36+
return response;
37+
}

src/parsers/xml.cpp

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
2+
#include "request-data.h"
3+
#include "plugin-support.h"
4+
5+
#include <pugixml.hpp>
6+
#include <obs-module.h>
7+
8+
struct request_data_handler_response parse_xml(struct request_data_handler_response response,
9+
const url_source_request_data *request_data)
10+
{
11+
// Parse the response as XML using pugixml
12+
pugi::xml_document doc;
13+
pugi::xml_parse_result result = doc.load_string(response.body.c_str());
14+
if (!result) {
15+
obs_log(LOG_INFO, "Failed to parse XML response: %s", result.description());
16+
// Return an error response
17+
struct request_data_handler_response responseFail;
18+
responseFail.error_message = result.description();
19+
responseFail.status_code = URL_SOURCE_REQUEST_PARSING_ERROR_CODE;
20+
return responseFail;
21+
}
22+
std::string parsed_output = "";
23+
// Get the output value
24+
if (request_data->output_xpath != "") {
25+
pugi::xpath_node_set nodes = doc.select_nodes(request_data->output_xpath.c_str());
26+
if (nodes.size() > 0) {
27+
parsed_output = nodes[0].node().text().get();
28+
} else {
29+
obs_log(LOG_INFO, "Failed to get XML value");
30+
// Return an error response
31+
struct request_data_handler_response responseFail;
32+
responseFail.error_message = "Failed to get XML value";
33+
responseFail.status_code = URL_SOURCE_REQUEST_PARSING_ERROR_CODE;
34+
return responseFail;
35+
}
36+
} else {
37+
// Return the whole XML object
38+
parsed_output = response.body;
39+
}
40+
response.body_parts_parsed.push_back(parsed_output);
41+
return response;
42+
}
43+
44+
struct request_data_handler_response
45+
parse_xml_by_xquery(struct request_data_handler_response response,
46+
const url_source_request_data *request_data)
47+
{
48+
// Parse the response as XML using pugixml
49+
pugi::xml_document doc;
50+
pugi::xml_parse_result result = doc.load_string(response.body.c_str());
51+
if (!result) {
52+
obs_log(LOG_INFO, "Failed to parse XML response: %s", result.description());
53+
// Return an error response
54+
struct request_data_handler_response responseFail;
55+
responseFail.error_message = result.description();
56+
responseFail.status_code = URL_SOURCE_REQUEST_PARSING_ERROR_CODE;
57+
return responseFail;
58+
}
59+
std::string parsed_output = "";
60+
// Get the output value
61+
if (request_data->output_xquery != "") {
62+
pugi::xpath_query query_entity(request_data->output_xquery.c_str());
63+
std::string s = query_entity.evaluate_string(doc);
64+
parsed_output = s;
65+
} else {
66+
// Return the whole XML object
67+
parsed_output = response.body;
68+
}
69+
response.body_parts_parsed.push_back(parsed_output);
70+
return response;
71+
}

0 commit comments

Comments
 (0)