mirror of
https://github.com/owntone/owntone-server.git
synced 2025-02-09 12:48:09 -05:00
Merge pull request #1761 from owntone/mxml_to_libmxml_1
This commit is contained in:
commit
f8d42a2fef
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
@ -49,7 +49,7 @@ jobs:
|
|||||||
# uses a compiled language
|
# uses a compiled language
|
||||||
- run: |
|
- run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -yq build-essential clang clang-tools git autotools-dev autoconf libtool gettext gawk gperf bison flex libconfuse-dev libunistring-dev libsqlite3-dev libavcodec-dev libavformat-dev libavfilter-dev libswscale-dev libavutil-dev libasound2-dev libmxml-dev libgcrypt20-dev libavahi-client-dev zlib1g-dev libevent-dev libplist-dev libsodium-dev libcurl4-openssl-dev libjson-c-dev libprotobuf-c-dev libpulse-dev libwebsockets-dev libgnutls28-dev
|
sudo apt-get install -yq build-essential clang clang-tools git autotools-dev autoconf libtool gettext gawk gperf bison flex libconfuse-dev libunistring-dev libsqlite3-dev libavcodec-dev libavformat-dev libavfilter-dev libswscale-dev libavutil-dev libasound2-dev libxml2-dev libgcrypt20-dev libavahi-client-dev zlib1g-dev libevent-dev libplist-dev libsodium-dev libcurl4-openssl-dev libjson-c-dev libprotobuf-c-dev libpulse-dev libwebsockets-dev libgnutls28-dev
|
||||||
autoreconf -vi
|
autoreconf -vi
|
||||||
./configure --enable-lastfm --enable-chromecast
|
./configure --enable-lastfm --enable-chromecast
|
||||||
scan-build --status-bugs -disable-checker deadcode.DeadStores --exclude src/parsers make
|
scan-build --status-bugs -disable-checker deadcode.DeadStores --exclude src/parsers make
|
||||||
|
8
.github/workflows/macos.yml
vendored
8
.github/workflows/macos.yml
vendored
@ -38,12 +38,6 @@ jobs:
|
|||||||
sudo ln -s /usr/local/opt/bison/bin/bison /usr/local/bin/bison
|
sudo ln -s /usr/local/opt/bison/bin/bison /usr/local/bin/bison
|
||||||
sudo ln -s /usr/local/opt/flex/bin/flex /usr/local/bin/flex
|
sudo ln -s /usr/local/opt/flex/bin/flex /usr/local/bin/flex
|
||||||
|
|
||||||
- name: Install libmxml
|
|
||||||
# Homebrew by default comes with libmxml 4, but it isn't compatible with mxml 3 which we need
|
|
||||||
run: |
|
|
||||||
wget https://raw.githubusercontent.com/Homebrew/homebrew-core/71bfcd3624ee88eee1e2ea6653753dafd48e7fcf/Formula/lib/libmxml.rb
|
|
||||||
brew install --build-from-source libmxml.rb
|
|
||||||
|
|
||||||
- name: Install libinotify-kqueue
|
- name: Install libinotify-kqueue
|
||||||
# brew does not have libinotify package
|
# brew does not have libinotify package
|
||||||
run: |
|
run: |
|
||||||
@ -76,7 +70,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Install other dependencies
|
- name: Install other dependencies
|
||||||
run: |
|
run: |
|
||||||
brew install libunistring confuse libplist libwebsockets libevent libgcrypt json-c protobuf-c libsodium gnutls pulseaudio openssl
|
brew install libxml2 libunistring confuse libplist libwebsockets libevent libgcrypt json-c protobuf-c libsodium gnutls pulseaudio openssl
|
||||||
|
|
||||||
- name: Configure
|
- name: Configure
|
||||||
# We configure a non-privileged setup, since how to add a "owntone" system
|
# We configure a non-privileged setup, since how to add a "owntone" system
|
||||||
|
2
.github/workflows/ubuntu.yml
vendored
2
.github/workflows/ubuntu.yml
vendored
@ -27,7 +27,7 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -yq build-essential clang clang-tools git autotools-dev autoconf libtool gettext gawk gperf bison flex libconfuse-dev libunistring-dev libsqlite3-dev libavcodec-dev libavformat-dev libavfilter-dev libswscale-dev libavutil-dev libasound2-dev libmxml-dev libgcrypt20-dev libavahi-client-dev zlib1g-dev libevent-dev libplist-dev libsodium-dev libcurl4-openssl-dev libjson-c-dev libprotobuf-c-dev libpulse-dev libwebsockets-dev libgnutls28-dev
|
sudo apt-get install -yq build-essential clang clang-tools git autotools-dev autoconf libtool gettext gawk gperf bison flex libconfuse-dev libunistring-dev libsqlite3-dev libavcodec-dev libavformat-dev libavfilter-dev libswscale-dev libavutil-dev libasound2-dev libxml2-dev libgcrypt20-dev libavahi-client-dev zlib1g-dev libevent-dev libplist-dev libsodium-dev libcurl4-openssl-dev libjson-c-dev libprotobuf-c-dev libpulse-dev libwebsockets-dev libgnutls28-dev
|
||||||
|
|
||||||
- name: Build and check
|
- name: Build and check
|
||||||
run: |
|
run: |
|
||||||
|
@ -120,13 +120,7 @@ OWNTONE_MODULES_CHECK([OWNTONE], [ZLIB], [zlib], [deflate], [zlib.h])
|
|||||||
OWNTONE_MODULES_CHECK([OWNTONE], [CONFUSE], [libconfuse >= 3.0], [cfg_init], [confuse.h])
|
OWNTONE_MODULES_CHECK([OWNTONE], [CONFUSE], [libconfuse >= 3.0], [cfg_init], [confuse.h])
|
||||||
OWNTONE_MODULES_CHECK([OWNTONE], [LIBCURL], [libcurl], [curl_global_init], [curl/curl.h])
|
OWNTONE_MODULES_CHECK([OWNTONE], [LIBCURL], [libcurl], [curl_global_init], [curl/curl.h])
|
||||||
OWNTONE_MODULES_CHECK([OWNTONE], [LIBSODIUM], [libsodium], [sodium_init], [sodium.h])
|
OWNTONE_MODULES_CHECK([OWNTONE], [LIBSODIUM], [libsodium], [sodium_init], [sodium.h])
|
||||||
|
OWNTONE_MODULES_CHECK([OWNTONE], [LIBXML2], [libxml-2.0], [xmlInitParser], [libxml/parser.h])
|
||||||
OWNTONE_MODULES_CHECK([OWNTONE], [MINIXML], [mxml],
|
|
||||||
[mxmlNewElement], [mxml.h],
|
|
||||||
[
|
|
||||||
dnl See mxml-compat.h
|
|
||||||
AC_CHECK_FUNCS([mxmlGetOpaque] [mxmlGetText] [mxmlGetType] [mxmlGetFirstChild])
|
|
||||||
])
|
|
||||||
|
|
||||||
OWNTONE_MODULES_CHECK([COMMON], [SQLITE3], [sqlite3 >= 3.5.0],
|
OWNTONE_MODULES_CHECK([COMMON], [SQLITE3], [sqlite3 >= 3.5.0],
|
||||||
[sqlite3_initialize], [sqlite3.h],
|
[sqlite3_initialize], [sqlite3.h],
|
||||||
|
@ -15,7 +15,7 @@ sudo apt-get install \
|
|||||||
build-essential git autotools-dev autoconf automake libtool gettext gawk \
|
build-essential git autotools-dev autoconf automake libtool gettext gawk \
|
||||||
gperf bison flex libconfuse-dev libunistring-dev libsqlite3-dev \
|
gperf bison flex libconfuse-dev libunistring-dev libsqlite3-dev \
|
||||||
libavcodec-dev libavformat-dev libavfilter-dev libswscale-dev libavutil-dev \
|
libavcodec-dev libavformat-dev libavfilter-dev libswscale-dev libavutil-dev \
|
||||||
libasound2-dev libmxml-dev libgcrypt20-dev libavahi-client-dev zlib1g-dev \
|
libasound2-dev libxml2-dev libgcrypt20-dev libavahi-client-dev zlib1g-dev \
|
||||||
libevent-dev libplist-dev libsodium-dev libjson-c-dev libwebsockets-dev \
|
libevent-dev libplist-dev libsodium-dev libjson-c-dev libwebsockets-dev \
|
||||||
libcurl4-openssl-dev libprotobuf-c-dev
|
libcurl4-openssl-dev libprotobuf-c-dev
|
||||||
```
|
```
|
||||||
@ -73,7 +73,7 @@ will need ffmpeg. You can google how to do that. Then run:
|
|||||||
```bash
|
```bash
|
||||||
sudo dnf install \
|
sudo dnf install \
|
||||||
git automake autoconf gettext-devel gperf gawk libtool bison flex \
|
git automake autoconf gettext-devel gperf gawk libtool bison flex \
|
||||||
sqlite-devel libconfuse-devel libunistring-devel mxml-devel libevent-devel \
|
sqlite-devel libconfuse-devel libunistring-devel libxml2-devel libevent-devel \
|
||||||
avahi-devel libgcrypt-devel zlib-devel alsa-lib-devel ffmpeg-devel \
|
avahi-devel libgcrypt-devel zlib-devel alsa-lib-devel ffmpeg-devel \
|
||||||
libplist-devel libsodium-devel json-c-devel libwebsockets-devel \
|
libplist-devel libsodium-devel json-c-devel libwebsockets-devel \
|
||||||
libcurl-devel protobuf-c-devel
|
libcurl-devel protobuf-c-devel
|
||||||
@ -137,11 +137,9 @@ Install MacPorts (which requires Xcode): <https://www.macports.org/install.php>
|
|||||||
sudo port install \
|
sudo port install \
|
||||||
autoconf automake libtool pkgconfig git gperf bison flex libgcrypt \
|
autoconf automake libtool pkgconfig git gperf bison flex libgcrypt \
|
||||||
libunistring libconfuse ffmpeg libevent json-c libwebsockets curl \
|
libunistring libconfuse ffmpeg libevent json-c libwebsockets curl \
|
||||||
libplist libsodium protobuf-c
|
libplist libsodium protobuf-c libxml2
|
||||||
```
|
```
|
||||||
|
|
||||||
Download, configure, build, and install the [Mini-XML library](https://www.msweet.org/mxml/)
|
|
||||||
|
|
||||||
Download, configure, build and install the [libinotify-kqueue library](https://github.com/libinotify-kqueue/libinotify-kqueue)
|
Download, configure, build and install the [libinotify-kqueue library](https://github.com/libinotify-kqueue/libinotify-kqueue)
|
||||||
|
|
||||||
Add the following to `.bashrc`:
|
Add the following to `.bashrc`:
|
||||||
@ -226,7 +224,7 @@ Libraries:
|
|||||||
- [FFmpeg](https://ffmpeg.org/)
|
- [FFmpeg](https://ffmpeg.org/)
|
||||||
- [libconfuse](https://github.com/libconfuse/libconfuse)
|
- [libconfuse](https://github.com/libconfuse/libconfuse)
|
||||||
- [libevent](https://libevent.org/) 2.1.4+
|
- [libevent](https://libevent.org/) 2.1.4+
|
||||||
- [Mini-XML](https://www.msweet.org/mxml/) (aka mxml or libmxml)
|
- [libxml2](https://gitlab.gnome.org/GNOME/libxml2)
|
||||||
- [Libgcrypt](https://gnupg.org/software/libgcrypt/) 1.2.0+
|
- [Libgcrypt](https://gnupg.org/software/libgcrypt/) 1.2.0+
|
||||||
- [zlib](https://zlib.net/)
|
- [zlib](https://zlib.net/)
|
||||||
- [libunistring](https://www.gnu.org/software/libunistring/) 0.9.3+
|
- [libunistring](https://www.gnu.org/software/libunistring/) 0.9.3+
|
||||||
|
@ -19,7 +19,7 @@ Url: https://github.com/owntone/owntone-server
|
|||||||
Source0: https://github.com/owntone/%{name}/archive/%{version}/%{name}-%{version}.tar.xz
|
Source0: https://github.com/owntone/%{name}/archive/%{version}/%{name}-%{version}.tar.xz
|
||||||
%{?systemd_ordering}
|
%{?systemd_ordering}
|
||||||
BuildRequires: gcc, make, bison, flex, systemd, pkgconfig, libunistring-devel
|
BuildRequires: gcc, make, bison, flex, systemd, pkgconfig, libunistring-devel
|
||||||
BuildRequires: pkgconfig(zlib), pkgconfig(libconfuse), pkgconfig(mxml)
|
BuildRequires: pkgconfig(zlib), pkgconfig(libconfuse), pkgconfig(libxml2)
|
||||||
BuildRequires: pkgconfig(sqlite3) >= 3.5.0, pkgconfig(libevent) >= 2.0.0
|
BuildRequires: pkgconfig(sqlite3) >= 3.5.0, pkgconfig(libevent) >= 2.0.0
|
||||||
BuildRequires: pkgconfig(json-c), libgcrypt-devel >= 1.2.0
|
BuildRequires: pkgconfig(json-c), libgcrypt-devel >= 1.2.0
|
||||||
BuildRequires: libgpg-error-devel >= 1.6
|
BuildRequires: libgpg-error-devel >= 1.6
|
||||||
|
@ -20,7 +20,7 @@ if [ "$yn" != "y" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
DEPS="gmake autoconf automake libtool gettext gperf glib pkgconf wget git \
|
DEPS="gmake autoconf automake libtool gettext gperf glib pkgconf wget git \
|
||||||
ffmpeg libconfuse libevent mxml libgcrypt libunistring libiconv curl \
|
ffmpeg libconfuse libevent libxml2 libgcrypt libunistring libiconv curl \
|
||||||
libplist libinotify avahi sqlite3 alsa-lib libsodium json-c libwebsockets
|
libplist libinotify avahi sqlite3 alsa-lib libsodium json-c libwebsockets
|
||||||
protobuf-c bison flex"
|
protobuf-c bison flex"
|
||||||
echo "The script can install the following dependency packages for you:"
|
echo "The script can install the following dependency packages for you:"
|
||||||
|
@ -40,7 +40,7 @@
|
|||||||
#include "parsers/rsp_parser.h"
|
#include "parsers/rsp_parser.h"
|
||||||
|
|
||||||
#define RSP_VERSION "1.0"
|
#define RSP_VERSION "1.0"
|
||||||
#define RSP_XML_ROOT "?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\" ?"
|
#define RSP_XML_DECLARATION "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\" ?>"
|
||||||
|
|
||||||
#define F_FULL (1 << 0)
|
#define F_FULL (1 << 0)
|
||||||
#define F_BROWSE (1 << 1)
|
#define F_BROWSE (1 << 1)
|
||||||
@ -124,7 +124,7 @@ xml_to_evbuf(struct evbuffer *evbuf, xml_node *tree)
|
|||||||
char *xml;
|
char *xml;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
xml = xml_to_string(tree);
|
xml = xml_to_string(tree, RSP_XML_DECLARATION);
|
||||||
if (!xml)
|
if (!xml)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_RSP, "Could not finalize RSP reply\n");
|
DPRINTF(E_LOG, L_RSP, "Could not finalize RSP reply\n");
|
||||||
@ -142,44 +142,53 @@ xml_to_evbuf(struct evbuffer *evbuf, xml_node *tree)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static int
|
||||||
rsp_xml_response_new(xml_node **xml_ptr, xml_node **response_ptr, int errorcode, const char *errorstring, int records, int totalrecords)
|
rsp_xml_response_new(xml_node **response_ptr, int errorcode, const char *errorstring, int records, int totalrecords)
|
||||||
{
|
{
|
||||||
xml_node *xml = xml_new_node(NULL, RSP_XML_ROOT, NULL);
|
xml_node *node;
|
||||||
xml_node *response = xml_new_node(xml, "response", NULL);
|
xml_node *response = xml_new_node(NULL, "response", NULL);
|
||||||
xml_node *status = xml_new_node(response, "status", NULL);
|
xml_node *status = xml_new_node(response, "status", NULL);
|
||||||
|
|
||||||
|
if (!response || !status)
|
||||||
|
return -1;
|
||||||
|
|
||||||
xml_new_node_textf(status, "errorcode", "%d", errorcode);
|
xml_new_node_textf(status, "errorcode", "%d", errorcode);
|
||||||
xml_new_node(status, "errorstring", errorstring);
|
node = xml_new_node(status, "errorstring", errorstring);
|
||||||
|
if (errorstring && *errorstring == '\0')
|
||||||
|
xml_new_text(node, ""); // Prevents sending <errorstring/> which the Soundbridge may not understand
|
||||||
|
|
||||||
xml_new_node_textf(status, "records", "%d", records);
|
xml_new_node_textf(status, "records", "%d", records);
|
||||||
xml_new_node_textf(status, "totalrecords", "%d", totalrecords);
|
xml_new_node_textf(status, "totalrecords", "%d", totalrecords);
|
||||||
|
|
||||||
if (response_ptr)
|
if (response_ptr)
|
||||||
*response_ptr = response;
|
*response_ptr = response;
|
||||||
if (xml_ptr)
|
|
||||||
*xml_ptr = xml;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
rsp_send_error(struct httpd_request *hreq, char *errmsg)
|
rsp_send_error(struct httpd_request *hreq, char *errmsg)
|
||||||
{
|
{
|
||||||
xml_node *xml;
|
xml_node *response = NULL;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
rsp_xml_response_new(&xml, NULL, 1, errmsg, 0, 0);
|
CHECK_ERR(L_RSP, rsp_xml_response_new(&response, 1, errmsg, 0, 0));
|
||||||
ret = xml_to_evbuf(hreq->out_body, xml);
|
|
||||||
xml_free(xml);
|
|
||||||
|
|
||||||
|
ret = xml_to_evbuf(hreq->out_body, response);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
goto error;
|
||||||
httpd_send_error(hreq, HTTP_SERVUNAVAIL, "Internal Server Error");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
httpd_header_add(hreq->out_headers, "Content-Type", "text/xml; charset=utf-8");
|
httpd_header_add(hreq->out_headers, "Content-Type", "text/xml; charset=utf-8");
|
||||||
httpd_header_add(hreq->out_headers, "Connection", "close");
|
httpd_header_add(hreq->out_headers, "Connection", "close");
|
||||||
|
|
||||||
httpd_send_reply(hreq, HTTP_OK, "OK", HTTPD_SEND_NO_GZIP);
|
httpd_send_reply(hreq, HTTP_OK, "OK", HTTPD_SEND_NO_GZIP);
|
||||||
|
|
||||||
|
xml_free(response);
|
||||||
|
return;
|
||||||
|
|
||||||
|
error:
|
||||||
|
httpd_send_error(hreq, HTTP_SERVUNAVAIL, "Internal Server Error");
|
||||||
|
xml_free(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@ -305,7 +314,6 @@ rsp_request_authorize(struct httpd_request *hreq)
|
|||||||
static int
|
static int
|
||||||
rsp_reply_info(struct httpd_request *hreq)
|
rsp_reply_info(struct httpd_request *hreq)
|
||||||
{
|
{
|
||||||
xml_node *xml;
|
|
||||||
xml_node *response;
|
xml_node *response;
|
||||||
xml_node *info;
|
xml_node *info;
|
||||||
cfg_t *lib;
|
cfg_t *lib;
|
||||||
@ -317,7 +325,7 @@ rsp_reply_info(struct httpd_request *hreq)
|
|||||||
lib = cfg_getsec(cfg, "library");
|
lib = cfg_getsec(cfg, "library");
|
||||||
library = cfg_getstr(lib, "name");
|
library = cfg_getstr(lib, "name");
|
||||||
|
|
||||||
rsp_xml_response_new(&xml, &response, 0, "", 0, 0);
|
CHECK_ERR(L_RSP, rsp_xml_response_new(&response, 0, "", 0, 0));
|
||||||
|
|
||||||
info = xml_new_node(response, "info", NULL);
|
info = xml_new_node(response, "info", NULL);
|
||||||
|
|
||||||
@ -326,7 +334,7 @@ rsp_reply_info(struct httpd_request *hreq)
|
|||||||
xml_new_node(info, "server-version", VERSION);
|
xml_new_node(info, "server-version", VERSION);
|
||||||
xml_new_node(info, "name", library);
|
xml_new_node(info, "name", library);
|
||||||
|
|
||||||
rsp_send_reply(hreq, xml);
|
rsp_send_reply(hreq, response);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -336,7 +344,6 @@ rsp_reply_db(struct httpd_request *hreq)
|
|||||||
struct query_params qp;
|
struct query_params qp;
|
||||||
struct db_playlist_info dbpli;
|
struct db_playlist_info dbpli;
|
||||||
char **strval;
|
char **strval;
|
||||||
xml_node *xml;
|
|
||||||
xml_node *response;
|
xml_node *response;
|
||||||
xml_node *pls;
|
xml_node *pls;
|
||||||
xml_node *pl;
|
xml_node *pl;
|
||||||
@ -357,7 +364,7 @@ rsp_reply_db(struct httpd_request *hreq)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
rsp_xml_response_new(&xml, &response, 0, "", qp.results, qp.results);
|
CHECK_ERR(L_RSP, rsp_xml_response_new(&response, 0, "", qp.results, qp.results));
|
||||||
|
|
||||||
pls = xml_new_node(response, "playlists", NULL);
|
pls = xml_new_node(response, "playlists", NULL);
|
||||||
|
|
||||||
@ -386,7 +393,7 @@ rsp_reply_db(struct httpd_request *hreq)
|
|||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_RSP, "Error fetching results\n");
|
DPRINTF(E_LOG, L_RSP, "Error fetching results\n");
|
||||||
|
|
||||||
xml_free(xml);
|
xml_free(response);
|
||||||
db_query_end(&qp);
|
db_query_end(&qp);
|
||||||
rsp_send_error(hreq, "Error fetching query results");
|
rsp_send_error(hreq, "Error fetching query results");
|
||||||
return -1;
|
return -1;
|
||||||
@ -394,7 +401,7 @@ rsp_reply_db(struct httpd_request *hreq)
|
|||||||
|
|
||||||
/* HACK
|
/* HACK
|
||||||
* Add a dummy empty string to the playlists element if there is no data
|
* Add a dummy empty string to the playlists element if there is no data
|
||||||
* to return - this prevents mxml from sending out an empty <playlists/>
|
* to return - this prevents us from sending out an empty <playlists/>
|
||||||
* tag that the SoundBridge does not handle. It's hackish, but it works.
|
* tag that the SoundBridge does not handle. It's hackish, but it works.
|
||||||
*/
|
*/
|
||||||
if (qp.results == 0)
|
if (qp.results == 0)
|
||||||
@ -402,7 +409,7 @@ rsp_reply_db(struct httpd_request *hreq)
|
|||||||
|
|
||||||
db_query_end(&qp);
|
db_query_end(&qp);
|
||||||
|
|
||||||
rsp_send_reply(hreq, xml);
|
rsp_send_reply(hreq, response);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -473,7 +480,6 @@ rsp_reply_playlist(struct httpd_request *hreq)
|
|||||||
struct query_params qp;
|
struct query_params qp;
|
||||||
const char *param;
|
const char *param;
|
||||||
const char *client_codecs;
|
const char *client_codecs;
|
||||||
xml_node *xml;
|
|
||||||
xml_node *response;
|
xml_node *response;
|
||||||
xml_node *items;
|
xml_node *items;
|
||||||
int mode;
|
int mode;
|
||||||
@ -538,7 +544,7 @@ rsp_reply_playlist(struct httpd_request *hreq)
|
|||||||
if (qp.limit && (records > qp.limit))
|
if (qp.limit && (records > qp.limit))
|
||||||
records = qp.limit;
|
records = qp.limit;
|
||||||
|
|
||||||
rsp_xml_response_new(&xml, &response, 0, "", records, qp.results);
|
CHECK_ERR(L_RSP, rsp_xml_response_new(&response, 0, "", records, qp.results));
|
||||||
|
|
||||||
// Add a parent items block (all items), and then one item per file
|
// Add a parent items block (all items), and then one item per file
|
||||||
items = xml_new_node(response, "items", NULL);
|
items = xml_new_node(response, "items", NULL);
|
||||||
@ -554,7 +560,7 @@ rsp_reply_playlist(struct httpd_request *hreq)
|
|||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_RSP, "Error fetching results\n");
|
DPRINTF(E_LOG, L_RSP, "Error fetching results\n");
|
||||||
|
|
||||||
xml_free(xml);
|
xml_free(response);
|
||||||
db_query_end(&qp);
|
db_query_end(&qp);
|
||||||
rsp_send_error(hreq, "Error fetching query results");
|
rsp_send_error(hreq, "Error fetching query results");
|
||||||
return -1;
|
return -1;
|
||||||
@ -562,7 +568,7 @@ rsp_reply_playlist(struct httpd_request *hreq)
|
|||||||
|
|
||||||
/* HACK
|
/* HACK
|
||||||
* Add a dummy empty string to the items element if there is no data
|
* Add a dummy empty string to the items element if there is no data
|
||||||
* to return - this prevents mxml from sending out an empty <items/>
|
* to return - this prevents us from sending out an empty <items/>
|
||||||
* tag that the SoundBridge does not handle. It's hackish, but it works.
|
* tag that the SoundBridge does not handle. It's hackish, but it works.
|
||||||
*/
|
*/
|
||||||
if (qp.results == 0)
|
if (qp.results == 0)
|
||||||
@ -570,7 +576,7 @@ rsp_reply_playlist(struct httpd_request *hreq)
|
|||||||
|
|
||||||
db_query_end(&qp);
|
db_query_end(&qp);
|
||||||
|
|
||||||
rsp_send_reply(hreq, xml);
|
rsp_send_reply(hreq, response);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -580,7 +586,6 @@ rsp_reply_browse(struct httpd_request *hreq)
|
|||||||
{
|
{
|
||||||
struct query_params qp;
|
struct query_params qp;
|
||||||
char *browse_item;
|
char *browse_item;
|
||||||
xml_node *xml;
|
|
||||||
xml_node *response;
|
xml_node *response;
|
||||||
xml_node *items;
|
xml_node *items;
|
||||||
int records;
|
int records;
|
||||||
@ -643,7 +648,7 @@ rsp_reply_browse(struct httpd_request *hreq)
|
|||||||
if (qp.limit && (records > qp.limit))
|
if (qp.limit && (records > qp.limit))
|
||||||
records = qp.limit;
|
records = qp.limit;
|
||||||
|
|
||||||
rsp_xml_response_new(&xml, &response, 0, "", records, qp.results);
|
CHECK_ERR(L_RSP, rsp_xml_response_new(&response, 0, "", records, qp.results));
|
||||||
|
|
||||||
items = xml_new_node(response, "items", NULL);
|
items = xml_new_node(response, "items", NULL);
|
||||||
|
|
||||||
@ -660,7 +665,7 @@ rsp_reply_browse(struct httpd_request *hreq)
|
|||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_RSP, "Error fetching results\n");
|
DPRINTF(E_LOG, L_RSP, "Error fetching results\n");
|
||||||
|
|
||||||
xml_free(xml);
|
xml_free(response);
|
||||||
db_query_end(&qp);
|
db_query_end(&qp);
|
||||||
rsp_send_error(hreq, "Error fetching query results");
|
rsp_send_error(hreq, "Error fetching query results");
|
||||||
return -1;
|
return -1;
|
||||||
@ -668,7 +673,7 @@ rsp_reply_browse(struct httpd_request *hreq)
|
|||||||
|
|
||||||
/* HACK
|
/* HACK
|
||||||
* Add a dummy empty string to the items element if there is no data
|
* Add a dummy empty string to the items element if there is no data
|
||||||
* to return - this prevents mxml from sending out an empty <items/>
|
* to return - this prevents us from sending out an empty <items/>
|
||||||
* tag that the SoundBridge does not handle. It's hackish, but it works.
|
* tag that the SoundBridge does not handle. It's hackish, but it works.
|
||||||
*/
|
*/
|
||||||
if (qp.results == 0)
|
if (qp.results == 0)
|
||||||
@ -676,7 +681,7 @@ rsp_reply_browse(struct httpd_request *hreq)
|
|||||||
|
|
||||||
db_query_end(&qp);
|
db_query_end(&qp);
|
||||||
|
|
||||||
rsp_send_reply(hreq, xml);
|
rsp_send_reply(hreq, response);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
403
src/misc_xml.c
403
src/misc_xml.c
@ -15,19 +15,6 @@
|
|||||||
* along with this program; if not, write to the Free Software
|
* along with this program; if not, write to the Free Software
|
||||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
*
|
*
|
||||||
*
|
|
||||||
* About pipe.c
|
|
||||||
* --------------
|
|
||||||
* This module will read a PCM16 stream from a named pipe and write it to the
|
|
||||||
* input buffer. The user may start/stop playback from a pipe by selecting it
|
|
||||||
* through a client. If the user has configured pipe_autostart, then pipes in
|
|
||||||
* the library will also be watched for data, and playback will start/stop
|
|
||||||
* automatically.
|
|
||||||
*
|
|
||||||
* The module will also look for pipes with a .metadata suffix, and if found,
|
|
||||||
* the metadata will be parsed and fed to the player. The metadata must be in
|
|
||||||
* the format Shairport uses for this purpose.
|
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifdef HAVE_CONFIG_H
|
#ifdef HAVE_CONFIG_H
|
||||||
@ -36,162 +23,18 @@
|
|||||||
|
|
||||||
#include <stdio.h> // fopen
|
#include <stdio.h> // fopen
|
||||||
#include <stdarg.h> // va_*
|
#include <stdarg.h> // va_*
|
||||||
#include <ctype.h>
|
#include <string.h> // strlen
|
||||||
|
#include <ctype.h> // isspace
|
||||||
|
|
||||||
#include <mxml.h>
|
#include <libxml/parser.h>
|
||||||
|
#include <libxml/tree.h>
|
||||||
typedef mxml_node_t xml_node;
|
|
||||||
|
|
||||||
|
|
||||||
/* ---------------- Compability with older versions of mxml ----------------- */
|
|
||||||
|
|
||||||
// mxml 2.10 has a memory leak in mxmlDelete, see https://github.com/michaelrsweet/mxml/issues/183
|
|
||||||
// - and since this is the version in Ubuntu 18.04 LTS and Raspian Stretch, we
|
|
||||||
// fix it by including a fixed mxmlDelete here. It should be removed once the
|
|
||||||
// major distros no longer have 2.10. The below code is msweet's fixed mxml.
|
|
||||||
#if (MXML_MAJOR_VERSION == 2) && (MXML_MINOR_VERSION <= 10)
|
|
||||||
|
|
||||||
#define mxmlDelete compat_mxmlDelete
|
|
||||||
|
|
||||||
static void
|
|
||||||
compat_mxml_free(mxml_node_t *node)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
|
|
||||||
switch (node->type)
|
|
||||||
{
|
|
||||||
case MXML_ELEMENT :
|
|
||||||
if (node->value.element.name)
|
|
||||||
free(node->value.element.name);
|
|
||||||
|
|
||||||
if (node->value.element.num_attrs)
|
|
||||||
{
|
|
||||||
for (i = 0; i < node->value.element.num_attrs; i ++)
|
|
||||||
{
|
|
||||||
if (node->value.element.attrs[i].name)
|
|
||||||
free(node->value.element.attrs[i].name);
|
|
||||||
if (node->value.element.attrs[i].value)
|
|
||||||
free(node->value.element.attrs[i].value);
|
|
||||||
}
|
|
||||||
|
|
||||||
free(node->value.element.attrs);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case MXML_INTEGER :
|
|
||||||
break;
|
|
||||||
case MXML_OPAQUE :
|
|
||||||
if (node->value.opaque)
|
|
||||||
free(node->value.opaque);
|
|
||||||
break;
|
|
||||||
case MXML_REAL :
|
|
||||||
break;
|
|
||||||
case MXML_TEXT :
|
|
||||||
if (node->value.text.string)
|
|
||||||
free(node->value.text.string);
|
|
||||||
break;
|
|
||||||
case MXML_CUSTOM :
|
|
||||||
if (node->value.custom.data &&
|
|
||||||
node->value.custom.destroy)
|
|
||||||
(*(node->value.custom.destroy))(node->value.custom.data);
|
|
||||||
break;
|
|
||||||
default :
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
free(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
__attribute__((unused)) static void
|
|
||||||
compat_mxmlDelete(mxml_node_t *node)
|
|
||||||
{
|
|
||||||
mxml_node_t *current,
|
|
||||||
*next;
|
|
||||||
|
|
||||||
if (!node)
|
|
||||||
return;
|
|
||||||
|
|
||||||
mxmlRemove(node);
|
|
||||||
for (current = node->child; current; current = next)
|
|
||||||
{
|
|
||||||
if ((next = current->child) != NULL)
|
|
||||||
{
|
|
||||||
current->child = NULL;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((next = current->next) == NULL)
|
|
||||||
{
|
|
||||||
if ((next = current->parent) == node)
|
|
||||||
next = NULL;
|
|
||||||
}
|
|
||||||
compat_mxml_free(current);
|
|
||||||
}
|
|
||||||
|
|
||||||
compat_mxml_free(node);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* For compability with mxml 2.6 */
|
|
||||||
#ifndef HAVE_MXMLGETTEXT
|
|
||||||
__attribute__((unused)) static const char * /* O - Text string or NULL */
|
|
||||||
mxmlGetText(mxml_node_t *node, /* I - Node to get */
|
|
||||||
int *whitespace) /* O - 1 if string is preceded by whitespace, 0 otherwise */
|
|
||||||
{
|
|
||||||
if (node->type == MXML_TEXT)
|
|
||||||
return (node->value.text.string);
|
|
||||||
else if (node->type == MXML_ELEMENT &&
|
|
||||||
node->child &&
|
|
||||||
node->child->type == MXML_TEXT)
|
|
||||||
return (node->child->value.text.string);
|
|
||||||
else
|
|
||||||
return (NULL);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef HAVE_MXMLGETOPAQUE
|
|
||||||
__attribute__((unused)) static const char * /* O - Opaque string or NULL */
|
|
||||||
mxmlGetOpaque(mxml_node_t *node) /* I - Node to get */
|
|
||||||
{
|
|
||||||
if (!node)
|
|
||||||
return (NULL);
|
|
||||||
|
|
||||||
if (node->type == MXML_OPAQUE)
|
|
||||||
return (node->value.opaque);
|
|
||||||
else if (node->type == MXML_ELEMENT &&
|
|
||||||
node->child &&
|
|
||||||
node->child->type == MXML_OPAQUE)
|
|
||||||
return (node->child->value.opaque);
|
|
||||||
else
|
|
||||||
return (NULL);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef HAVE_MXMLGETFIRSTCHILD
|
|
||||||
__attribute__((unused)) static mxml_node_t * /* O - First child or NULL */
|
|
||||||
mxmlGetFirstChild(mxml_node_t *node) /* I - Node to get */
|
|
||||||
{
|
|
||||||
if (!node || node->type != MXML_ELEMENT)
|
|
||||||
return (NULL);
|
|
||||||
|
|
||||||
return (node->child);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef HAVE_MXMLGETTYPE
|
|
||||||
__attribute__((unused)) static mxml_type_t /* O - Type of node */
|
|
||||||
mxmlGetType(mxml_node_t *node) /* I - Node to get */
|
|
||||||
{
|
|
||||||
return (node->type);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
typedef xmlNode xml_node;
|
||||||
|
|
||||||
/* --------------------------------- Helpers -------------------------------- */
|
/* --------------------------------- Helpers -------------------------------- */
|
||||||
|
|
||||||
// We get values from mxml via GetOpaque, but that means they can whitespace,
|
static char *
|
||||||
// thus we trim them. A bit dirty, since the values are in principle const.
|
trim(char *str)
|
||||||
static const char *
|
|
||||||
trim(const char *str)
|
|
||||||
{
|
{
|
||||||
char *term;
|
char *term;
|
||||||
|
|
||||||
@ -205,7 +48,6 @@ trim(const char *str)
|
|||||||
while (term != str && isspace(*(term - 1)))
|
while (term != str && isspace(*(term - 1)))
|
||||||
term--;
|
term--;
|
||||||
|
|
||||||
// Dirty write to the const string from mxml
|
|
||||||
*term = '\0';
|
*term = '\0';
|
||||||
|
|
||||||
return str;
|
return str;
|
||||||
@ -215,9 +57,25 @@ trim(const char *str)
|
|||||||
/* -------------------------- Wrapper implementation ------------------------ */
|
/* -------------------------- Wrapper implementation ------------------------ */
|
||||||
|
|
||||||
char *
|
char *
|
||||||
xml_to_string(xml_node *top)
|
xml_to_string(xml_node *top, const char *xml_declaration)
|
||||||
{
|
{
|
||||||
return mxmlSaveAllocString(top, MXML_NO_CALLBACK);
|
xmlBuffer *buf;
|
||||||
|
char *s;
|
||||||
|
|
||||||
|
buf = xmlBufferCreate();
|
||||||
|
if (!buf)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (xml_declaration)
|
||||||
|
xmlBufferWriteChar(buf, xml_declaration);
|
||||||
|
|
||||||
|
xmlNodeDump(buf, top->doc, top, 0, 0);
|
||||||
|
|
||||||
|
s = strdup((char *)buf->content);
|
||||||
|
|
||||||
|
xmlBufferFree(buf);
|
||||||
|
|
||||||
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This works both for well-formed xml strings (beginning with <?xml..) and for
|
// This works both for well-formed xml strings (beginning with <?xml..) and for
|
||||||
@ -225,99 +83,90 @@ xml_to_string(xml_node *top)
|
|||||||
xml_node *
|
xml_node *
|
||||||
xml_from_string(const char *string)
|
xml_from_string(const char *string)
|
||||||
{
|
{
|
||||||
mxml_node_t *top;
|
xmlDocPtr doc;
|
||||||
mxml_node_t *node;
|
|
||||||
|
|
||||||
top = mxmlNewXML("1.0");
|
doc = xmlReadMemory(string, strlen(string), NULL, NULL, XML_PARSE_NOBLANKS | XML_PARSE_NOCDATA | XML_PARSE_NONET);
|
||||||
if (!top)
|
if (!doc)
|
||||||
goto error;
|
return NULL;
|
||||||
|
|
||||||
node = mxmlLoadString(top, string, MXML_OPAQUE_CALLBACK);
|
return xmlDocGetRootElement(doc);
|
||||||
if (!node)
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
return top;
|
|
||||||
|
|
||||||
error:
|
|
||||||
mxmlDelete(top);
|
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
xml_node *
|
xml_node *
|
||||||
xml_from_file(const char *path)
|
xml_from_file(const char *path)
|
||||||
{
|
{
|
||||||
FILE *fp;
|
xmlDocPtr doc;
|
||||||
mxml_node_t *top;
|
|
||||||
mxml_node_t *node;
|
|
||||||
|
|
||||||
top = mxmlNewXML("1.0");
|
doc = xmlReadFile(path, NULL, XML_PARSE_NOBLANKS | XML_PARSE_NOCDATA | XML_PARSE_NONET);
|
||||||
if (!top)
|
if (!doc)
|
||||||
goto error;
|
return NULL;
|
||||||
|
|
||||||
fp = fopen(path, "r");
|
return xmlDocGetRootElement(doc);
|
||||||
node = mxmlLoadFile(top, fp, MXML_OPAQUE_CALLBACK);
|
|
||||||
fclose(fp);
|
|
||||||
|
|
||||||
if (!node)
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
return top;
|
|
||||||
|
|
||||||
error:
|
|
||||||
mxmlDelete(top);
|
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
xml_free(xml_node *top)
|
xml_free(xml_node *top)
|
||||||
{
|
{
|
||||||
mxmlDelete(top);
|
xmlFreeDoc(top->doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
xml_node *
|
xml_node *
|
||||||
xml_get_node(xml_node *top, const char *path)
|
xml_get_child(xml_node *top, const char *name)
|
||||||
{
|
{
|
||||||
mxml_node_t *node;
|
xml_node *cur;
|
||||||
mxml_type_t type;
|
|
||||||
|
|
||||||
// This example shows why we can't just return the result of mxmlFindPath:
|
for (cur = xmlFirstElementChild(top); cur; cur = xmlNextElementSibling(cur))
|
||||||
// <?xml version="1.0""?><rss>
|
{
|
||||||
// <channel>
|
if (xmlStrEqual(BAD_CAST name, cur->name))
|
||||||
// <title><![CDATA[Tissages]]></title>
|
break;
|
||||||
// mxmlFindPath(top, "rss/channel") will return an OPAQUE node where the
|
}
|
||||||
// opaque value is just the whitespace. What we want is the ELEMENT parent,
|
|
||||||
// because that's the one we can use to search for children nodes ("title").
|
|
||||||
node = mxmlFindPath(top, path);
|
|
||||||
type = mxmlGetType(node);
|
|
||||||
if (type == MXML_ELEMENT)
|
|
||||||
return node;
|
|
||||||
|
|
||||||
return mxmlGetParent(node);
|
return cur;
|
||||||
}
|
}
|
||||||
|
|
||||||
xml_node *
|
xml_node *
|
||||||
xml_get_next(xml_node *top, xml_node *node)
|
xml_get_next(xml_node *top, xml_node *node)
|
||||||
{
|
{
|
||||||
const char *name;
|
return xmlNextElementSibling(node);
|
||||||
const char *s;
|
|
||||||
|
|
||||||
name = mxmlGetElement(node);
|
|
||||||
if (!name)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
while ( (node = mxmlGetNextSibling(node)) )
|
|
||||||
{
|
|
||||||
s = mxmlGetElement(node);
|
|
||||||
if (s && strcmp(s, name) == 0)
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Walks through the children of the "path" node until it finds one that is
|
// We don't use xpath because I couldn't figure how to make it search in a node
|
||||||
// not just whitespace and returns a trimmed value (except for CDATA). Means
|
// subtree instead of in the entire xmlDoc + it is more complex than the below.
|
||||||
// that these variations will all give the same result:
|
// If the XML is <foo><bar>value</bar></foo> then both path = "foo/bar" and path
|
||||||
|
// = "bar" (so a path without the top element) will return "value".
|
||||||
|
xml_node *
|
||||||
|
xml_get_node(xml_node *top, const char *path)
|
||||||
|
{
|
||||||
|
xml_node *node = top;
|
||||||
|
char *path_cpy;
|
||||||
|
char *needle;
|
||||||
|
char *ptr;
|
||||||
|
|
||||||
|
if (!top)
|
||||||
|
return NULL;
|
||||||
|
if (!path)
|
||||||
|
return top;
|
||||||
|
|
||||||
|
path_cpy = strdup(path);
|
||||||
|
|
||||||
|
needle = strtok_r(path_cpy, "/", &ptr);
|
||||||
|
if (!needle)
|
||||||
|
node = NULL;
|
||||||
|
else if (xmlStrEqual(BAD_CAST needle, node->name))
|
||||||
|
needle = strtok_r(NULL, "/", &ptr); // Descend one level down the path
|
||||||
|
|
||||||
|
while (node && needle)
|
||||||
|
{
|
||||||
|
node = xml_get_child(node, needle);
|
||||||
|
needle = strtok_r(NULL, "/", &ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(path_cpy);
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
// These variations will all give the same result:
|
||||||
//
|
//
|
||||||
// <foo>FOO FOO</foo><bar>\nBAR BAR \n</bar>
|
// <foo>FOO FOO</foo><bar>\nBAR BAR \n</bar>
|
||||||
// <foo>FOO FOO</foo><bar><![CDATA[BAR BAR]]></bar>
|
// <foo>FOO FOO</foo><bar><![CDATA[BAR BAR]]></bar>
|
||||||
@ -325,50 +174,69 @@ xml_get_next(xml_node *top, xml_node *node)
|
|||||||
const char *
|
const char *
|
||||||
xml_get_val(xml_node *top, const char *path)
|
xml_get_val(xml_node *top, const char *path)
|
||||||
{
|
{
|
||||||
mxml_node_t *parent;
|
xml_node *node;
|
||||||
mxml_node_t *node;
|
|
||||||
mxml_type_t type;
|
|
||||||
const char *s = "";
|
|
||||||
|
|
||||||
parent = xml_get_node(top, path);
|
node = xml_get_node(top, path);
|
||||||
if (!parent)
|
if (!node || !node->children)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
for (node = mxmlGetFirstChild(parent); node; node = mxmlGetNextSibling(node))
|
return trim((char *)node->children->content);
|
||||||
{
|
|
||||||
type = mxmlGetType(node);
|
|
||||||
if (type == MXML_OPAQUE)
|
|
||||||
s = trim(mxmlGetOpaque(node));
|
|
||||||
else if (type == MXML_ELEMENT)
|
|
||||||
s = mxmlGetCDATA(node);
|
|
||||||
|
|
||||||
if (s && *s != '\0')
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return s;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *
|
const char *
|
||||||
xml_get_attr(xml_node *top, const char *path, const char *name)
|
xml_get_attr(xml_node *top, const char *path, const char *name)
|
||||||
{
|
{
|
||||||
mxml_node_t *node = mxmlFindPath(top, path);
|
xml_node *node;
|
||||||
|
xmlAttr *prop;
|
||||||
|
|
||||||
return mxmlElementGetAttr(node, name);
|
node = xml_get_node(top, path);
|
||||||
|
if (!node)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
prop = xmlHasProp(node, BAD_CAST name);
|
||||||
|
if (!prop || !prop->children)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
return trim((char *)prop->children->content);
|
||||||
|
}
|
||||||
|
|
||||||
|
xml_node *
|
||||||
|
xml_new(void)
|
||||||
|
{
|
||||||
|
xmlDoc *doc;
|
||||||
|
|
||||||
|
doc = xmlNewDoc(BAD_CAST "1.0");
|
||||||
|
if (!doc)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
return xmlDocGetRootElement(doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
xml_node *
|
xml_node *
|
||||||
xml_new_node(xml_node *parent, const char *name, const char *val)
|
xml_new_node(xml_node *parent, const char *name, const char *val)
|
||||||
{
|
{
|
||||||
if (!parent)
|
xml_node *node;
|
||||||
parent = MXML_NO_PARENT;
|
xmlDoc *doc = NULL;
|
||||||
|
|
||||||
mxml_node_t *node = mxmlNewElement(parent, name);
|
doc = parent ? parent->doc : xmlNewDoc(BAD_CAST "1.0");
|
||||||
if (!val)
|
if (!doc)
|
||||||
return node; // We're done, caller gets an ELEMENT to use as parent
|
goto error;
|
||||||
|
|
||||||
|
node = xmlNewDocNode(doc, NULL, BAD_CAST name, BAD_CAST val);
|
||||||
|
if (!node)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (parent)
|
||||||
|
xmlAddChild(parent, node);
|
||||||
|
else
|
||||||
|
xmlDocSetRootElement(doc, node);
|
||||||
|
|
||||||
mxmlNewText(node, 0, val);
|
|
||||||
return node;
|
return node;
|
||||||
|
|
||||||
|
error:
|
||||||
|
if (!parent)
|
||||||
|
xmlFreeDoc(doc);
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
xml_node *
|
xml_node *
|
||||||
@ -376,7 +244,7 @@ xml_new_node_textf(xml_node *parent, const char *name, const char *format, ...)
|
|||||||
{
|
{
|
||||||
char *s = NULL;
|
char *s = NULL;
|
||||||
va_list va;
|
va_list va;
|
||||||
mxml_node_t *node;
|
xml_node *node;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
va_start(va, format);
|
va_start(va, format);
|
||||||
@ -393,8 +261,17 @@ xml_new_node_textf(xml_node *parent, const char *name, const char *format, ...)
|
|||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
xml_node *
|
||||||
xml_new_text(xml_node *parent, const char *val)
|
xml_new_text(xml_node *parent, const char *val)
|
||||||
{
|
{
|
||||||
mxmlNewText(parent, 0, val);
|
xml_node *node;
|
||||||
|
|
||||||
|
if (!parent)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
node = xmlNewDocText(parent->doc, BAD_CAST val);
|
||||||
|
if (!node)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
return xmlAddChild(parent, node);
|
||||||
}
|
}
|
||||||
|
@ -1,55 +1,43 @@
|
|||||||
#ifndef SRC_MISC_XML_H_
|
#ifndef SRC_MISC_XML_H_
|
||||||
#define SRC_MISC_XML_H_
|
#define SRC_MISC_XML_H_
|
||||||
|
|
||||||
// This wraps mxml and adds some convenience functions. This also means that
|
// This wraps libxml2 and adds some convenience functions
|
||||||
// callers don't need to concern themselves with changes and bugs in various
|
|
||||||
// versions of mxml.
|
|
||||||
|
|
||||||
typedef void xml_node;
|
typedef void xml_node;
|
||||||
|
|
||||||
// Wraps mxmlSaveAllocString. Returns NULL on error.
|
|
||||||
char *
|
char *
|
||||||
xml_to_string(xml_node *top);
|
xml_to_string(xml_node *top, const char *xml_declaration);
|
||||||
|
|
||||||
// Wraps mxmlNewXML and mxmlLoadString, so creates an xml struct with the parsed
|
|
||||||
// content of string. Returns NULL on error.
|
|
||||||
xml_node *
|
xml_node *
|
||||||
xml_from_string(const char *string);
|
xml_from_string(const char *string);
|
||||||
|
|
||||||
// Wraps mxmlNewXML and mxmlLoadFile, so creates an xml struct with the parsed
|
|
||||||
// content of string. Returns NULL on error.
|
|
||||||
xml_node *
|
xml_node *
|
||||||
xml_from_file(const char *path);
|
xml_from_file(const char *path);
|
||||||
|
|
||||||
// Wraps mxmlDelete, which will free node + underlying nodes
|
|
||||||
void
|
void
|
||||||
xml_free(xml_node *top);
|
xml_free(xml_node *top);
|
||||||
|
|
||||||
// Wraps mxmlFindPath.
|
|
||||||
xml_node *
|
xml_node *
|
||||||
xml_get_node(xml_node *top, const char *path);
|
xml_get_node(xml_node *top, const char *path);
|
||||||
|
|
||||||
// Wraps mxmlGetNextSibling, but only returns sibling nodes that have the same
|
// Only returns sibling nodes that have the same name as input node
|
||||||
// name as input node.
|
|
||||||
xml_node *
|
xml_node *
|
||||||
xml_get_next(xml_node *top, xml_node *node);
|
xml_get_next(xml_node *top, xml_node *node);
|
||||||
|
|
||||||
// Wraps mxmlFindPath and mxmlGetOpaque + mxmlGetCDATA. Returns NULL if nothing
|
|
||||||
// can be found.
|
|
||||||
const char *
|
const char *
|
||||||
xml_get_val(xml_node *top, const char *path);
|
xml_get_val(xml_node *top, const char *path);
|
||||||
|
|
||||||
// Wraps mxmlFindPath and mxmlElementGetAttr. Returns NULL if nothing can be
|
|
||||||
// found.
|
|
||||||
const char *
|
const char *
|
||||||
xml_get_attr(xml_node *top, const char *path, const char *name);
|
xml_get_attr(xml_node *top, const char *path, const char *name);
|
||||||
|
|
||||||
|
// Will create a new XML document with the node as root if parent is NULL
|
||||||
xml_node *
|
xml_node *
|
||||||
xml_new_node(xml_node *parent, const char *name, const char *val);
|
xml_new_node(xml_node *parent, const char *name, const char *val);
|
||||||
|
|
||||||
xml_node *
|
xml_node *
|
||||||
xml_new_node_textf(xml_node *parent, const char *name, const char *format, ...);
|
xml_new_node_textf(xml_node *parent, const char *name, const char *format, ...);
|
||||||
|
|
||||||
|
// Adds a text node to parent, which must be an element node
|
||||||
void
|
void
|
||||||
xml_new_text(xml_node *parent, const char *val);
|
xml_new_text(xml_node *parent, const char *val);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user