mirror of
https://gitlab.freedesktop.org/dbus/dbus.git
synced 2026-05-25 23:28:26 +02:00
XML: hard depends on expat and delete libxml
[The libxml code path has been broken for at least 2.5 years, and Expat is tiny, so there seems no point in supporting both. -smcv] Bug: https://bugs.freedesktop.org/show_bug.cgi?id=20253 Signed-off-by: Chengwei Yang <chengwei.yang@intel.com> Reviewed-by: Simon McVittie <simon.mcvittie@collabora.co.uk>
This commit is contained in:
parent
0a76508672
commit
46602768c5
7 changed files with 11 additions and 409 deletions
|
|
@ -141,9 +141,6 @@ DBUS_SYSTEM_BUS_DEFAULT_ADDRESS:STRING=unix:path=/var/run/dbus/system_bus_socket
|
|||
// Use atomic integer implementation for 486
|
||||
DBUS_USE_ATOMIC_INT_486:BOOL=OFF
|
||||
|
||||
// Use expat (== ON) or libxml2 (==OFF)
|
||||
DBUS_USE_EXPAT:BOOL=ON
|
||||
|
||||
win32 only:
|
||||
// enable win32 debug port for message output
|
||||
DBUS_USE_OUTPUT_DEBUG_STRING:BOOL=OFF
|
||||
|
|
|
|||
|
|
@ -42,12 +42,7 @@ agentdir=$(LAUNCHD_AGENT_DIR)
|
|||
agent_DATA=org.freedesktop.dbus-session.plist
|
||||
endif
|
||||
|
||||
if DBUS_USE_LIBXML
|
||||
XML_SOURCES=config-loader-libxml.c
|
||||
endif
|
||||
if DBUS_USE_EXPAT
|
||||
XML_SOURCES=config-loader-expat.c
|
||||
endif
|
||||
|
||||
if DBUS_BUS_ENABLE_KQUEUE
|
||||
DIR_WATCH_SOURCE=dir-watch-kqueue.c
|
||||
|
|
|
|||
|
|
@ -1,324 +0,0 @@
|
|||
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
|
||||
/* config-loader-libxml.c libxml2 XML loader
|
||||
*
|
||||
* Copyright (C) 2003 Red Hat, Inc.
|
||||
*
|
||||
* Licensed under the Academic Free License version 2.1
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
#include "config-parser.h"
|
||||
#include <dbus/dbus-internals.h>
|
||||
#include <libxml/xmlreader.h>
|
||||
#include <libxml/parser.h>
|
||||
#include <libxml/globals.h>
|
||||
#include <libxml/xmlmemory.h>
|
||||
#ifdef HAVE_ERRNO_H
|
||||
#include <errno.h>
|
||||
#endif
|
||||
#include <string.h>
|
||||
|
||||
/* About the error handling:
|
||||
* - setup a "structured" error handler that catches structural
|
||||
* errors and some oom errors
|
||||
* - assume that a libxml function returning an error code means
|
||||
* out-of-memory
|
||||
*/
|
||||
#define _DBUS_MAYBE_SET_OOM(e) (dbus_error_is_set(e) ? (void)0 : _DBUS_SET_OOM(e))
|
||||
|
||||
|
||||
static dbus_bool_t
|
||||
xml_text_start_element (BusConfigParser *parser,
|
||||
xmlTextReader *reader,
|
||||
DBusError *error)
|
||||
{
|
||||
const char *name;
|
||||
int n_attributes;
|
||||
const char **attribute_names, **attribute_values;
|
||||
dbus_bool_t ret;
|
||||
int i, status, is_empty;
|
||||
|
||||
_DBUS_ASSERT_ERROR_IS_CLEAR (error);
|
||||
|
||||
ret = FALSE;
|
||||
attribute_names = NULL;
|
||||
attribute_values = NULL;
|
||||
|
||||
name = xmlTextReaderConstName (reader);
|
||||
n_attributes = xmlTextReaderAttributeCount (reader);
|
||||
is_empty = xmlTextReaderIsEmptyElement (reader);
|
||||
|
||||
if (name == NULL || n_attributes < 0 || is_empty == -1)
|
||||
{
|
||||
_DBUS_MAYBE_SET_OOM (error);
|
||||
goto out;
|
||||
}
|
||||
|
||||
attribute_names = dbus_new0 (const char *, n_attributes + 1);
|
||||
attribute_values = dbus_new0 (const char *, n_attributes + 1);
|
||||
if (attribute_names == NULL || attribute_values == NULL)
|
||||
{
|
||||
_DBUS_SET_OOM (error);
|
||||
goto out;
|
||||
}
|
||||
i = 0;
|
||||
while ((status = xmlTextReaderMoveToNextAttribute (reader)) == 1)
|
||||
{
|
||||
_dbus_assert (i < n_attributes);
|
||||
attribute_names[i] = xmlTextReaderConstName (reader);
|
||||
attribute_values[i] = xmlTextReaderConstValue (reader);
|
||||
if (attribute_names[i] == NULL || attribute_values[i] == NULL)
|
||||
{
|
||||
_DBUS_MAYBE_SET_OOM (error);
|
||||
goto out;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (status == -1)
|
||||
{
|
||||
_DBUS_MAYBE_SET_OOM (error);
|
||||
goto out;
|
||||
}
|
||||
_dbus_assert (i == n_attributes);
|
||||
|
||||
ret = bus_config_parser_start_element (parser, name,
|
||||
attribute_names, attribute_values,
|
||||
error);
|
||||
if (ret && is_empty == 1)
|
||||
ret = bus_config_parser_end_element (parser, name, error);
|
||||
|
||||
out:
|
||||
dbus_free (attribute_names);
|
||||
dbus_free (attribute_values);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void xml_shut_up (void *ctx, const char *msg, ...)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
static void
|
||||
xml_text_reader_error (void *arg, xmlErrorPtr xml_error)
|
||||
{
|
||||
DBusError *error = arg;
|
||||
|
||||
#if 0
|
||||
_dbus_verbose ("XML_ERROR level=%d, domain=%d, code=%d, msg=%s\n",
|
||||
xml_error->level, xml_error->domain,
|
||||
xml_error->code, xml_error->message);
|
||||
#endif
|
||||
|
||||
if (!dbus_error_is_set (error))
|
||||
{
|
||||
if (xml_error->code == XML_ERR_NO_MEMORY)
|
||||
_DBUS_SET_OOM (error);
|
||||
else if (xml_error->level == XML_ERR_ERROR ||
|
||||
xml_error->level == XML_ERR_FATAL)
|
||||
dbus_set_error (error, DBUS_ERROR_FAILED,
|
||||
"Error loading config file: '%s'",
|
||||
xml_error->message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
BusConfigParser*
|
||||
bus_config_load (const DBusString *file,
|
||||
dbus_bool_t is_toplevel,
|
||||
const BusConfigParser *parent,
|
||||
DBusError *error)
|
||||
|
||||
{
|
||||
xmlTextReader *reader;
|
||||
BusConfigParser *parser;
|
||||
DBusString dirname, data;
|
||||
DBusError tmp_error;
|
||||
int ret;
|
||||
|
||||
_DBUS_ASSERT_ERROR_IS_CLEAR (error);
|
||||
|
||||
parser = NULL;
|
||||
reader = NULL;
|
||||
|
||||
if (!_dbus_string_init (&dirname))
|
||||
{
|
||||
_DBUS_SET_OOM (error);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!_dbus_string_init (&data))
|
||||
{
|
||||
_DBUS_SET_OOM (error);
|
||||
_dbus_string_free (&dirname);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (is_toplevel)
|
||||
{
|
||||
/* xmlMemSetup only fails if one of the functions is NULL */
|
||||
xmlMemSetup (dbus_free,
|
||||
dbus_malloc,
|
||||
dbus_realloc,
|
||||
_dbus_strdup);
|
||||
xmlInitParser ();
|
||||
xmlSetGenericErrorFunc (NULL, xml_shut_up);
|
||||
}
|
||||
|
||||
if (!_dbus_string_get_dirname (file, &dirname))
|
||||
{
|
||||
_DBUS_SET_OOM (error);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
parser = bus_config_parser_new (&dirname, is_toplevel, parent);
|
||||
if (parser == NULL)
|
||||
{
|
||||
_DBUS_SET_OOM (error);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
if (!_dbus_file_get_contents (&data, file, error))
|
||||
goto failed;
|
||||
|
||||
reader = xmlReaderForMemory (_dbus_string_get_const_data (&data),
|
||||
_dbus_string_get_length (&data),
|
||||
NULL, NULL, 0);
|
||||
if (reader == NULL)
|
||||
{
|
||||
_DBUS_SET_OOM (error);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
xmlTextReaderSetParserProp (reader, XML_PARSER_SUBST_ENTITIES, 1);
|
||||
|
||||
dbus_error_init (&tmp_error);
|
||||
xmlTextReaderSetStructuredErrorHandler (reader, xml_text_reader_error, &tmp_error);
|
||||
|
||||
while ((ret = xmlTextReaderRead (reader)) == 1)
|
||||
{
|
||||
int type;
|
||||
|
||||
if (dbus_error_is_set (&tmp_error))
|
||||
goto reader_out;
|
||||
|
||||
type = xmlTextReaderNodeType (reader);
|
||||
if (type == -1)
|
||||
{
|
||||
_DBUS_MAYBE_SET_OOM (&tmp_error);
|
||||
goto reader_out;
|
||||
}
|
||||
|
||||
switch ((xmlReaderTypes) type) {
|
||||
case XML_READER_TYPE_ELEMENT:
|
||||
xml_text_start_element (parser, reader, &tmp_error);
|
||||
break;
|
||||
|
||||
case XML_READER_TYPE_TEXT:
|
||||
case XML_READER_TYPE_CDATA:
|
||||
{
|
||||
DBusString content;
|
||||
const char *value;
|
||||
value = xmlTextReaderConstValue (reader);
|
||||
if (value != NULL)
|
||||
{
|
||||
_dbus_string_init_const (&content, value);
|
||||
bus_config_parser_content (parser, &content, &tmp_error);
|
||||
}
|
||||
else
|
||||
_DBUS_MAYBE_SET_OOM (&tmp_error);
|
||||
break;
|
||||
}
|
||||
|
||||
case XML_READER_TYPE_DOCUMENT_TYPE:
|
||||
{
|
||||
const char *name;
|
||||
name = xmlTextReaderConstName (reader);
|
||||
if (name != NULL)
|
||||
bus_config_parser_check_doctype (parser, name, &tmp_error);
|
||||
else
|
||||
_DBUS_MAYBE_SET_OOM (&tmp_error);
|
||||
break;
|
||||
}
|
||||
|
||||
case XML_READER_TYPE_END_ELEMENT:
|
||||
{
|
||||
const char *name;
|
||||
name = xmlTextReaderConstName (reader);
|
||||
if (name != NULL)
|
||||
bus_config_parser_end_element (parser, name, &tmp_error);
|
||||
else
|
||||
_DBUS_MAYBE_SET_OOM (&tmp_error);
|
||||
break;
|
||||
}
|
||||
|
||||
case XML_READER_TYPE_DOCUMENT:
|
||||
case XML_READER_TYPE_DOCUMENT_FRAGMENT:
|
||||
case XML_READER_TYPE_PROCESSING_INSTRUCTION:
|
||||
case XML_READER_TYPE_COMMENT:
|
||||
case XML_READER_TYPE_ENTITY:
|
||||
case XML_READER_TYPE_NOTATION:
|
||||
case XML_READER_TYPE_WHITESPACE:
|
||||
case XML_READER_TYPE_SIGNIFICANT_WHITESPACE:
|
||||
case XML_READER_TYPE_END_ENTITY:
|
||||
case XML_READER_TYPE_XML_DECLARATION:
|
||||
/* nothing to do, just read on */
|
||||
break;
|
||||
|
||||
case XML_READER_TYPE_NONE:
|
||||
case XML_READER_TYPE_ATTRIBUTE:
|
||||
case XML_READER_TYPE_ENTITY_REFERENCE:
|
||||
_dbus_assert_not_reached ("unexpected nodes in XML");
|
||||
}
|
||||
|
||||
if (dbus_error_is_set (&tmp_error))
|
||||
goto reader_out;
|
||||
}
|
||||
|
||||
if (ret == -1)
|
||||
_DBUS_MAYBE_SET_OOM (&tmp_error);
|
||||
|
||||
reader_out:
|
||||
xmlFreeTextReader (reader);
|
||||
reader = NULL;
|
||||
if (dbus_error_is_set (&tmp_error))
|
||||
{
|
||||
dbus_move_error (&tmp_error, error);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
if (!bus_config_parser_finished (parser, error))
|
||||
goto failed;
|
||||
_dbus_string_free (&dirname);
|
||||
_dbus_string_free (&data);
|
||||
if (is_toplevel)
|
||||
xmlCleanupParser();
|
||||
_DBUS_ASSERT_ERROR_IS_CLEAR (error);
|
||||
return parser;
|
||||
|
||||
failed:
|
||||
_DBUS_ASSERT_ERROR_IS_SET (error);
|
||||
_dbus_string_free (&dirname);
|
||||
_dbus_string_free (&data);
|
||||
if (is_toplevel)
|
||||
xmlCleanupParser();
|
||||
if (parser)
|
||||
bus_config_parser_unref (parser);
|
||||
_dbus_assert (reader == NULL); /* must go to reader_out first */
|
||||
return NULL;
|
||||
}
|
||||
|
|
@ -89,8 +89,6 @@ if (WIN32)
|
|||
addExplorerWrapper(${CMAKE_PROJECT_NAME})
|
||||
endif (WIN32)
|
||||
|
||||
option (DBUS_USE_EXPAT "Use expat (== ON) or libxml2 (==OFF)" ON)
|
||||
|
||||
if(NOT WIN32)
|
||||
option (DBUS_ENABLE_ABSTRACT_SOCKETS "enable support for abstract sockets" ON)
|
||||
set (CMAKE_THREAD_PREFER_PTHREAD ON)
|
||||
|
|
@ -104,11 +102,7 @@ option (DBUS_ENABLE_STATS "enable bus daemon usage statistics" OFF)
|
|||
|
||||
option (DBUS_ENABLE_STATS "enable bus daemon usage statistics" OFF)
|
||||
|
||||
if (DBUS_USE_EXPAT)
|
||||
find_package(EXPAT)
|
||||
else ()
|
||||
find_package(LibXml2)
|
||||
endif ()
|
||||
find_package(EXPAT)
|
||||
find_package(X11)
|
||||
|
||||
# analogous to AC_USE_SYSTEM_EXTENSIONS in configure.ac
|
||||
|
|
@ -309,16 +303,9 @@ if(NOT LIBXML2_FOUND AND NOT EXPAT_FOUND)
|
|||
message(FATAL "Neither expat nor libxml2 found!")
|
||||
endif(NOT LIBXML2_FOUND AND NOT EXPAT_FOUND)
|
||||
|
||||
if(DBUS_USE_EXPAT)
|
||||
SET(XML_LIB "Expat")
|
||||
SET(XML_LIBRARY ${EXPAT_LIBRARIES})
|
||||
SET(XML_INCLUDE_DIR ${EXPAT_INCLUDE_DIR})
|
||||
else(DBUS_USE_EXPAT)
|
||||
SET(XML_LIB "LibXML2")
|
||||
SET(XML_LIBRARY ${LIBXML2_LIBRARIES})
|
||||
SET(XML_INCLUDE_DIR ${LIBXML2_INCLUDE_DIR})
|
||||
endif(DBUS_USE_EXPAT)
|
||||
|
||||
SET(XML_LIB "Expat")
|
||||
SET(XML_LIBRARY ${EXPAT_LIBRARIES})
|
||||
SET(XML_INCLUDE_DIR ${EXPAT_INCLUDE_DIR})
|
||||
|
||||
#AC_ARG_WITH(init-scripts, AS_HELP_STRING([--with-init-scripts=[redhat]],[Style of init scripts to install]))
|
||||
#AC_ARG_WITH(session-socket-dir, AS_HELP_STRING([--with-session-socket-dir=[dirname]],[Where to put sockets for the per-login-session message bus]))
|
||||
|
|
|
|||
|
|
@ -29,11 +29,7 @@ FOREACH(FILE ${FILES})
|
|||
configure_file(${FILE} ${TARGET} )
|
||||
ENDFOREACH(FILE)
|
||||
|
||||
if(DBUS_USE_EXPAT)
|
||||
SET (XML_SOURCES ${BUS_DIR}/config-loader-expat.c)
|
||||
else(DBUS_USE_EXPAT)
|
||||
SET (XML_SOURCES ${BUS_DIR}/config-loader-libxml.c)
|
||||
endif (DBUS_USE_EXPAT)
|
||||
SET (XML_SOURCES ${BUS_DIR}/config-loader-expat.c)
|
||||
|
||||
# after next cvs update
|
||||
#set (DIR_WATCH_SOURCE ${BUS_DIR}/dir-watch-default.c)
|
||||
|
|
|
|||
|
|
@ -1,14 +1,7 @@
|
|||
|
||||
if (DBUS_INSTALL_SYSTEM_LIBS)
|
||||
if (MINGW)
|
||||
if (DBUS_USE_EXPAT)
|
||||
# expat
|
||||
install_files(/bin FILES ${LIBEXPAT_LIBRARIES})
|
||||
else (DBUS_USE_EXPAT)
|
||||
# xml2
|
||||
install_files(/bin FILES ${LIBXML2_LIBRARIES})
|
||||
install_files(/bin FILES ${LIBICONV_LIBRARIES})
|
||||
endif (DBUS_USE_EXPAT)
|
||||
install_files(/bin FILES ${LIBEXPAT_LIBRARIES})
|
||||
else (MINGW)
|
||||
INCLUDE(InstallRequiredSystemLibraries)
|
||||
endif (MINGW)
|
||||
|
|
|
|||
52
configure.ac
52
configure.ac
|
|
@ -158,7 +158,6 @@ AC_ARG_ENABLE(userdb-cache, AS_HELP_STRING([--enable-userdb-cache],[build with u
|
|||
AC_ARG_ENABLE(launchd, AS_HELP_STRING([--enable-launchd],[build with launchd auto-launch support]),enable_launchd=$enableval,enable_launchd=auto)
|
||||
AC_ARG_ENABLE(systemd, AS_HELP_STRING([--enable-systemd],[build with systemd at_console support]),enable_systemd=$enableval,enable_systemd=auto)
|
||||
|
||||
AC_ARG_WITH(xml, AS_HELP_STRING([--with-xml=[libxml/expat]],[XML library to use (libxml may be named libxml2 on some systems)]))
|
||||
AC_ARG_WITH(init-scripts, AS_HELP_STRING([--with-init-scripts=[redhat]],[Style of init scripts to install]))
|
||||
AC_ARG_WITH(session-socket-dir, AS_HELP_STRING([--with-session-socket-dir=[dirname]],[Where to put sockets for the per-login-session message bus]))
|
||||
AC_ARG_WITH(test-socket-dir, AS_HELP_STRING([--with-test-socket-dir=[dirname]],[Where to put sockets for make check]))
|
||||
|
|
@ -910,49 +909,13 @@ PKG_PROG_PKG_CONFIG
|
|||
|
||||
#### Sort out XML library
|
||||
|
||||
# see what we have
|
||||
AC_CHECK_LIB(expat, XML_ParserCreate_MM,
|
||||
[ AC_CHECK_HEADERS(expat.h, have_expat=true, have_expat=false) ],
|
||||
have_expat=false)
|
||||
[ AC_CHECK_HEADERS(expat.h, [],
|
||||
[AC_MSG_ERROR([Could not find expat.h, check config.log for failed attempts])]) ],
|
||||
[ AC_MSG_ERROR([Explicitly requested expat but expat not found]) ])
|
||||
|
||||
# see what we want to use
|
||||
dbus_use_libxml=false
|
||||
dbus_use_expat=false
|
||||
if test x$with_xml = xexpat; then
|
||||
if ! $have_expat ; then
|
||||
AC_MSG_ERROR([Explicitly requested expat but expat not found])
|
||||
fi
|
||||
dbus_use_expat=true
|
||||
elif test x$with_xml = xlibxml; then
|
||||
PKG_CHECK_MODULES(LIBXML, libxml-2.0 >= 2.6.0, have_libxml=true, have_libxml=false)
|
||||
if ! $have_libxml ; then
|
||||
AC_MSG_ERROR([Explicitly requested libxml but libxml not found])
|
||||
fi
|
||||
dbus_use_libxml=true
|
||||
else
|
||||
### expat is the default because libxml can't currently survive
|
||||
### our brutal OOM-handling unit test setup.
|
||||
### http://bugzilla.gnome.org/show_bug.cgi?id=109368
|
||||
if test x$have_expat = xfalse; then
|
||||
AC_MSG_ERROR([Could not find expat.h, check config.log for failed attempts])
|
||||
fi
|
||||
### By default, only use Expat since it's tested and known to work. If you're a
|
||||
### general-purpose OS vendor, please don't enable libxml. For embedded use
|
||||
### if your OS is built around libxml, that's another case.
|
||||
dbus_use_expat=true
|
||||
fi
|
||||
|
||||
AM_CONDITIONAL(DBUS_USE_EXPAT, $dbus_use_expat)
|
||||
AM_CONDITIONAL(DBUS_USE_LIBXML, $dbus_use_libxml)
|
||||
|
||||
if $dbus_use_expat; then
|
||||
XML_LIBS=-lexpat
|
||||
XML_CFLAGS=
|
||||
fi
|
||||
if $dbus_use_libxml; then
|
||||
XML_LIBS=$LIBXML_LIBS
|
||||
XML_CFLAGS=$LIBXML_CFLAGS
|
||||
fi
|
||||
XML_LIBS=-lexpat
|
||||
XML_CFLAGS=
|
||||
AC_SUBST([XML_CFLAGS])
|
||||
AC_SUBST([XML_LIBS])
|
||||
|
||||
|
|
@ -1899,7 +1862,6 @@ echo "
|
|||
Building XML docs: ${enable_xml_docs}
|
||||
Building cache support: ${enable_userdb_cache}
|
||||
Building launchd support: ${have_launchd}
|
||||
Using XML parser: ${with_xml}
|
||||
Init scripts style: ${with_init_scripts}
|
||||
Abstract socket names: ${ac_cv_have_abstract_sockets}
|
||||
System bus socket: ${DBUS_SYSTEM_SOCKET}
|
||||
|
|
@ -1937,10 +1899,6 @@ fi
|
|||
if test x$enable_checks = xno; then
|
||||
echo "NOTE: building without checks for arguments passed to public API makes it harder to debug apps using D-Bus, but will slightly decrease D-Bus library size and _very_ slightly improve performance."
|
||||
fi
|
||||
if test x$dbus_use_libxml = xtrue; then
|
||||
echo
|
||||
echo "WARNING: You have chosen to use libxml as your xml parser however this code path is not maintained by the D-Bus developers and if it breaks you get to keep the pieces. If you have selected this option in err please reconfigure with expat (e.g. --with-xml=expat)."
|
||||
fi
|
||||
|
||||
if test "x$DBUS_HAVE_INT64" = x0; then
|
||||
AC_MSG_WARN([You have disabled 64-bit integers via --without-64-bit.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue