[sqlite] add an xml/xpath extension

pull/824/head
Timothy Stack 4 years ago
parent 9ed3a80326
commit 4ca6fd7bfd

@ -2,6 +2,8 @@ lnav v0.9.1:
Features:
* Added the ':filter-expr' command to filter log messages based on an SQL
expression. This command allows much greater control over filtering.
* Added an 'xpath()' table-valued function for extracting values from
strings containing XML snippets.
* Added the ':prompt' command to allow for more customization of prompts.
Combined with a custom keymapping, you can now open a prompt and prefill
it with a given value. For example, a key could be bound to the

@ -289,6 +289,7 @@ AC_CONFIG_FILES([src/Makefile])
AC_CONFIG_FILES([src/base/Makefile])
AC_CONFIG_FILES([src/fmtlib/Makefile])
AC_CONFIG_FILES([src/pcrepp/Makefile])
AC_CONFIG_FILES([src/pugixml/Makefile])
AC_CONFIG_FILES([src/yajl/Makefile])
AC_CONFIG_FILES([src/yajlpp/Makefile])
AC_CONFIG_FILES([test/Makefile])

@ -291,6 +291,7 @@ add_library(diag STATIC
papertrail_proc.cc
ptimec_rt.cc
pretty_printer.cc
pugixml/pugixml.cpp
readline_callbacks.cc
readline_curses.cc
readline_highlighters.cc
@ -330,6 +331,7 @@ add_library(diag STATIC
vt52_curses.cc
vtab_module.cc
log_vtab_impl.cc
xpath_vtab.cc
xterm_mouse.cc
yajlpp/yajlpp.cc
yajl/yajl.c
@ -407,6 +409,8 @@ add_library(diag STATIC
pretty_printer.hh
preview_status_source.hh
ptimec.hh
pugixml/pugiconfig.hpp
pugixml/pugixml.hpp
readline_callbacks.hh
readline_possibilities.hh
regexp_vtab.hh
@ -444,6 +448,7 @@ add_library(diag STATIC
vtab_module_json.hh
yajlpp/yajlpp.hh
yajlpp/yajlpp_def.hh
xpath_vtab.hh
mapbox/recursive_wrapper.hpp
mapbox/variant.hpp

@ -1,5 +1,5 @@
SUBDIRS = base fmtlib pcrepp yajl yajlpp .
SUBDIRS = base fmtlib pcrepp pugixml yajl yajlpp .
bin_PROGRAMS = lnav
@ -224,6 +224,7 @@ LDADD = \
base/libbase.a \
fmtlib/libcppfmt.a \
pcrepp/libpcrepp.a \
pugixml/libpugixml.a \
yajl/libyajl.a \
yajlpp/libyajlpp.a \
$(READLINE_LIBS) \
@ -369,6 +370,7 @@ noinst_HEADERS = \
vtab_module_json.hh \
log_vtab_impl.hh \
log_format_impls.cc \
xpath_vtab.hh \
xterm_mouse.hh \
spookyhash/SpookyV2.h \
ghc/filesystem.hpp \
@ -484,6 +486,7 @@ libdiag_a_SOURCES = \
vt52_curses.cc \
vtab_module.cc \
log_vtab_impl.cc \
xpath_vtab.cc \
xterm_mouse.cc \
spookyhash/SpookyV2.cpp

@ -838,7 +838,7 @@ char(*X*)
**See Also:**
:ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`
:ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath`
----
@ -874,7 +874,7 @@ charindex(*needle*, *haystack*, *\[start\]*)
**See Also:**
:ref:`char`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`
:ref:`char`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath`
----
@ -1159,7 +1159,7 @@ endswith(*str*, *suffix*)
**See Also:**
:ref:`char`, :ref:`charindex`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`
:ref:`char`, :ref:`charindex`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath`
----
@ -1220,7 +1220,7 @@ extract(*str*)
**See Also:**
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath`
----
@ -1386,7 +1386,7 @@ group_concat(*X*, *\[sep\]*)
**See Also:**
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath`
----
@ -1413,7 +1413,7 @@ group_spooky_hash(*str*)
**See Also:**
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath`
----
@ -1490,7 +1490,7 @@ instr(*haystack*, *needle*)
**See Also:**
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath`
----
@ -1873,7 +1873,7 @@ leftstr(*str*, *N*)
**See Also:**
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath`
----
@ -1900,7 +1900,7 @@ length(*str*)
**See Also:**
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath`
----
@ -2082,7 +2082,7 @@ lower(*str*)
**See Also:**
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath`
----
@ -2117,7 +2117,7 @@ ltrim(*str*, *\[chars\]*)
**See Also:**
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath`
----
@ -2289,7 +2289,7 @@ padc(*str*, *len*)
**See Also:**
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath`
----
@ -2324,7 +2324,7 @@ padl(*str*, *len*)
**See Also:**
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath`
----
@ -2359,7 +2359,7 @@ padr(*str*, *len*)
**See Also:**
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath`
----
@ -2466,7 +2466,7 @@ printf(*format*, *X*)
**See Also:**
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath`
----
@ -2493,7 +2493,7 @@ proper(*str*)
**See Also:**
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath`
----
@ -2692,7 +2692,7 @@ regexp_capture(*string*, *pattern*)
**See Also:**
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath`
----
@ -2734,7 +2734,7 @@ regexp_match(*re*, *str*)
**See Also:**
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_replace`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_replace`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath`
----
@ -2770,7 +2770,7 @@ regexp_replace(*str*, *re*, *repl*)
**See Also:**
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_match`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_match`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath`
----
@ -2806,7 +2806,7 @@ replace(*str*, *old*, *replacement*)
**See Also:**
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath`
----
@ -2834,7 +2834,7 @@ replicate(*str*, *N*)
**See Also:**
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath`
----
@ -2861,7 +2861,7 @@ reverse(*str*)
**See Also:**
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath`
----
@ -2896,7 +2896,7 @@ rightstr(*str*, *N*)
**See Also:**
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath`
----
@ -3000,7 +3000,7 @@ rtrim(*str*, *\[chars\]*)
**See Also:**
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath`
----
@ -3089,7 +3089,7 @@ spooky_hash(*str*)
**See Also:**
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath`
----
@ -3212,7 +3212,7 @@ startswith(*str*, *prefix*)
**See Also:**
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath`
----
@ -3240,7 +3240,7 @@ strfilter(*source*, *include*)
**See Also:**
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath`
----
@ -3333,7 +3333,7 @@ substr(*str*, *start*, *\[size\]*)
**See Also:**
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`trim`, :ref:`unicode`, :ref:`upper`
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath`
----
@ -3548,7 +3548,7 @@ trim(*str*, *\[chars\]*)
**See Also:**
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`unicode`, :ref:`upper`
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`unicode`, :ref:`upper`, :ref:`xpath`
----
@ -3606,7 +3606,7 @@ unicode(*X*)
**See Also:**
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`upper`
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`upper`, :ref:`xpath`
----
@ -3648,7 +3648,53 @@ upper(*str*)
**See Also:**
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`xpath`
----
.. _xpath:
xpath(*xpath*, *xmldoc*)
^^^^^^^^^^^^^^^^^^^^^^^^
A table-valued function that executes an xpath expression over an XML string and returns the selected values.
**Parameters:**
* **xpath\*** --- The XPATH expression to evaluate over the XML document.
* **xmldoc\*** --- The XML document as a string.
**Examples:**
To select the XML nodes on the path '/abc/def':
.. code-block:: custsqlite
;SELECT * FROM xpath('/abc/def', '<abc><def a="b">Hello</def><def>Bye</def></abc>')
result node_path node_attr node_text
<def a="b">Hello</def> /abc/def[1] {"a":"b"} Hello
<def>Bye</def> /abc/def[2] {} Bye
To select all 'a' attributes on the path '/abc/def':
.. code-block:: custsqlite
;SELECT * FROM xpath('/abc/def/@a', '<abc><def a="b">Hello</def><def>Bye</def></abc>')
result node_path node_attr node_text
b /abc/def[1]/@a {"a":"b"} Hello
To select the text nodes on the path '/abc/def':
.. code-block:: custsqlite
;SELECT * FROM xpath('/abc/def/text()', '<abc><def a="b">Hello &#x2605;</def></abc>')
result node_path node_attr node_text
Hello ★ /abc/def/text() {} Hello ★
**See Also:**
:ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`
----

@ -118,6 +118,7 @@
#include "file_vtab.hh"
#include "regexp_vtab.hh"
#include "fstat_vtab.hh"
#include "xpath_vtab.hh"
#include "textfile_highlighters.hh"
#include "base/future_util.hh"
@ -2184,6 +2185,7 @@ int main(int argc, char *argv[])
register_views_vtab(lnav_data.ld_db.in());
register_file_vtab(lnav_data.ld_db.in(), lnav_data.ld_active_files);
register_regexp_vtab(lnav_data.ld_db.in());
register_xpath_vtab(lnav_data.ld_db.in());
register_fstat_vtab(lnav_data.ld_db.in());
lnav_data.ld_vtab_manager = std::make_unique<log_vtab_manager>(

@ -0,0 +1,9 @@
noinst_LIBRARIES = libpugixml.a
noinst_HEADERS = \
pugiconfig.hpp \
pugixml.hpp
libpugixml_a_SOURCES = \
pugixml.cpp

@ -0,0 +1,77 @@
/**
* pugixml parser - version 1.11
* --------------------------------------------------------
* Copyright (C) 2006-2020, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com)
* Report bugs and download new versions at https://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
// Tune this constant to adjust max nesting for XPath queries
// #define PUGIXML_XPATH_DEPTH_LIMIT 1024
// 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-2020 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.
*/

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -28,6 +28,7 @@
#include "elem_to_json.hh"
#include "vtab_module.hh"
#include "vtab_module_json.hh"
#include "safe/safe.h"
#include "spookyhash/SpookyV2.h"
#include "optional.hpp"
@ -43,12 +44,14 @@ typedef struct {
static cache_entry *find_re(const char *re)
{
static unordered_map<string, cache_entry> CACHE;
using safe_cache = safe::Safe<unordered_map<string, cache_entry>>;
static safe_cache CACHE;
safe::WriteAccess<safe_cache> wcache(CACHE);
string re_str = re;
auto iter = CACHE.find(re_str);
auto iter = wcache->find(re_str);
if (iter == CACHE.end()) {
if (iter == wcache->end()) {
cache_entry c;
c.re2 = make_shared<pcrepp>(re_str);
@ -59,9 +62,9 @@ static cache_entry *find_re(const char *re)
e2 = sqlite3_mprintf("%s: %s", re, c.re->error().c_str());
throw pcrepp::error(e2.in(), 0);
}
CACHE[re_str] = c;
auto pair = wcache->insert(std::make_pair(re_str, c));
iter = CACHE.find(re_str);
iter = pair.first;
}
return &iter->second;

@ -376,7 +376,7 @@ void execute_examples()
attr_line_t al;
dos.list_value_for_overlay(db_tc,
0, 1,
vis_line_t(0),
0_vl,
al);
result.append(al);
for (int lpc = 0;
@ -387,6 +387,10 @@ void execute_examples()
false);
dls.text_attrs_for_line(db_tc, lpc,
al.get_attrs());
std::replace(al.get_string().begin(),
al.get_string().end(),
'\n',
' ');
result.append("\n")
.append(al);
}

@ -0,0 +1,411 @@
/**
* Copyright (c) 2020, Timothy Stack
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Timothy Stack nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include <sstream>
#include <unordered_map>
#include "base/lnav_log.hh"
#include "pugixml/pugixml.hpp"
#include "sql_util.hh"
#include "vtab_module.hh"
#include "yajlpp/yajlpp.hh"
using namespace std;
enum {
XP_COL_RESULT,
XP_COL_NODE_PATH,
XP_COL_NODE_ATTR,
XP_COL_NODE_TEXT,
XP_COL_XPATH,
XP_COL_VALUE,
};
static
thread_local std::unordered_map<std::string, pugi::xpath_query> QUERY_CACHE;
static
pugi::xpath_query checkout_query(const std::string& query)
{
auto iter = QUERY_CACHE.find(query);
if (iter == QUERY_CACHE.end()) {
auto xquery = pugi::xpath_query(query.c_str());
if (!xquery) {
return xquery;
}
auto pair = QUERY_CACHE.emplace(query, std::move(xquery));
iter = pair.first;
}
auto retval = std::move(iter->second);
QUERY_CACHE.erase(iter);
return retval;
}
static
void checkin_query(const std::string& query_str, pugi::xpath_query query)
{
if (!query) {
return;
}
QUERY_CACHE[query_str] = std::move(query);
}
static
std::string get_actual_path(const pugi::xml_node& node)
{
std::string retval;
auto curr = node;
while (curr) {
switch (curr.type()) {
case pugi::node_null:
break;
case pugi::node_pcdata:
retval += "text()";
break;
default: {
auto name = std::string(curr.name());
if (curr.previous_sibling(curr.name()) ||
curr.next_sibling(curr.name())) {
auto sibling = curr;
int index = 0;
while (sibling) {
index += 1;
sibling = sibling.previous_sibling(curr.name());
}
name += "[" + std::to_string(index) + "]";
}
if (retval.empty()) {
retval = name;
} else {
retval = name + std::string("/") + retval;
}
break;
}
}
curr = curr.parent();
}
return retval;
}
struct xpath_vtab {
static constexpr const char *CREATE_STMT = R"(
-- The xpath() table-valued function allows you to execute an xpath expression
CREATE TABLE xpath (
result text, -- The result of the xpath expression
node_path text, -- The absolute path to the node selected by the expression
node_attr text, -- The node attributes stored in a JSON object
node_text text, -- The text portion of the node selected by the expression
xpath text HIDDEN,
value text HIDDEN
);
)";
struct cursor {
sqlite3_vtab_cursor base;
sqlite3_int64 c_rowid{0};
string c_xpath;
string c_value;
pugi::xpath_query c_query;
pugi::xml_document c_doc;
pugi::xpath_node_set c_results;
cursor(sqlite3_vtab *vt)
: base({vt}) {
};
~cursor() {
this->reset();
}
int reset() {
this->c_rowid = 0;
checkin_query(this->c_xpath, std::move(this->c_query));
return SQLITE_OK;
};
int next() {
this->c_rowid += 1;
return SQLITE_OK;
};
int eof() {
return this->c_rowid >= this->c_results.size();
};
int get_rowid(sqlite3_int64 &rowid_out) {
rowid_out = this->c_rowid;
return SQLITE_OK;
};
};
int get_column(const cursor &vc, sqlite3_context *ctx, int col) {
switch (col) {
case XP_COL_RESULT: {
auto& xpath_node = vc.c_results[vc.c_rowid];
if (xpath_node.node()) {
ostringstream oss;
// XXX avoid the extra allocs
xpath_node.node().print(oss);
auto node_xml = oss.str();
sqlite3_result_text(ctx,
node_xml.c_str(),
node_xml.length(),
SQLITE_TRANSIENT);
} else if (xpath_node.attribute()) {
sqlite3_result_text(ctx,
xpath_node.attribute().value(),
-1,
SQLITE_TRANSIENT);
} else {
sqlite3_result_null(ctx);
}
break;
}
case XP_COL_NODE_PATH: {
auto& xpath_node = vc.c_results[vc.c_rowid];
auto x_node = xpath_node.node();
auto x_attr = xpath_node.attribute();
if (x_node || x_attr) {
if (!x_node) {
x_node = xpath_node.parent();
}
auto node_path = get_actual_path(x_node);
if (x_attr) {
node_path += "/@" + std::string(x_attr.name());
}
sqlite3_result_text(ctx,
node_path.c_str(),
node_path.length(),
SQLITE_TRANSIENT);
} else {
sqlite3_result_null(ctx);
}
break;
}
case XP_COL_NODE_ATTR: {
auto& xpath_node = vc.c_results[vc.c_rowid];
auto x_node = xpath_node.node();
auto x_attr = xpath_node.attribute();
if (x_node || x_attr) {
if (!x_node) {
x_node = xpath_node.parent();
}
yajlpp_gen gen;
yajl_gen_config(gen, yajl_gen_beautify, false);
{
yajlpp_map attrs(gen);
for (const auto& attr : x_node.attributes()) {
attrs.gen(attr.name());
attrs.gen(attr.value());
}
}
auto sf = gen.to_string_fragment();
sqlite3_result_text(ctx,
sf.data(),
sf.length(),
SQLITE_TRANSIENT);
sqlite3_result_subtype(ctx, 'J');
} else {
sqlite3_result_null(ctx);
}
break;
}
case XP_COL_NODE_TEXT: {
auto& xpath_node = vc.c_results[vc.c_rowid];
auto x_node = xpath_node.node();
auto x_attr = xpath_node.attribute();
if (x_node || x_attr) {
if (!x_node) {
x_node = xpath_node.parent();
}
auto node_text = x_node.text();
sqlite3_result_text(ctx,
node_text.get(),
-1,
SQLITE_TRANSIENT);
} else {
sqlite3_result_null(ctx);
}
break;
}
case XP_COL_XPATH:
sqlite3_result_text(ctx,
vc.c_xpath.c_str(),
vc.c_xpath.length(),
SQLITE_STATIC);
break;
case XP_COL_VALUE:
sqlite3_result_text(ctx,
vc.c_value.c_str(),
vc.c_value.length(),
SQLITE_STATIC);
break;
}
return SQLITE_OK;
}
};
static int rcBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo)
{
vtab_index_constraints vic(pIdxInfo);
vtab_index_usage viu(pIdxInfo);
for (auto iter = vic.begin(); iter != vic.end(); ++iter) {
if (iter->op != SQLITE_INDEX_CONSTRAINT_EQ) {
continue;
}
switch (iter->iColumn) {
case XP_COL_VALUE:
case XP_COL_XPATH:
viu.column_used(iter);
break;
}
}
viu.allocate_args(2);
return SQLITE_OK;
}
static int rcFilter(sqlite3_vtab_cursor *pVtabCursor,
int idxNum, const char *idxStr,
int argc, sqlite3_value **argv)
{
auto *pCur = (xpath_vtab::cursor *)pVtabCursor;
if (argc != 2) {
pCur->c_xpath.clear();
pCur->c_value.clear();
return SQLITE_OK;
}
pCur->c_value = (const char *) sqlite3_value_text(argv[1]);
auto parse_res = pCur->c_doc.load_string(pCur->c_value.c_str());
if (!parse_res) {
pVtabCursor->pVtab->zErrMsg = sqlite3_mprintf(
"Invalid XML document at offset %d: %s",
parse_res.offset, parse_res.description());
return SQLITE_ERROR;
}
pCur->c_xpath = (const char *) sqlite3_value_text(argv[0]);
pCur->c_query = checkout_query(pCur->c_xpath);
if (!pCur->c_query) {
auto& res = pCur->c_query.result();
pVtabCursor->pVtab->zErrMsg = sqlite3_mprintf(
"Invalid XPATH expression at offset %d: %s",
res.offset, res.description());
return SQLITE_ERROR;
}
pCur->c_rowid = 0;
pCur->c_results = pCur->c_doc.select_nodes(pCur->c_query);
return SQLITE_OK;
}
int register_xpath_vtab(sqlite3 *db)
{
static vtab_module<tvt_no_update<xpath_vtab>> XPATH_MODULE;
static help_text xpath_help = help_text("xpath",
"A table-valued function that executes an xpath expression over an XML "
"string and returns the selected values.")
.sql_table_valued_function()
.with_parameter({"xpath",
"The XPATH expression to evaluate over the XML document."})
.with_parameter({"xmldoc",
"The XML document as a string."})
.with_result({"result",
"The result of the XPATH expression."})
.with_result({"node_path",
"The absolute path to the node containing the result."})
.with_result({"node_attr",
"The node's attributes stored in JSON object."})
.with_result({"node_text",
"The node's text value."})
.with_tags({"string", "xml"})
.with_example({
"To select the XML nodes on the path '/abc/def'",
"SELECT * FROM xpath('/abc/def', '<abc><def a=\"b\">Hello</def><def>Bye</def></abc>')"
})
.with_example({
"To select all 'a' attributes on the path '/abc/def'",
"SELECT * FROM xpath('/abc/def/@a', '<abc><def a=\"b\">Hello</def><def>Bye</def></abc>')"
})
.with_example({
"To select the text nodes on the path '/abc/def'",
"SELECT * FROM xpath('/abc/def/text()', '<abc><def a=\"b\">Hello &#x2605;</def></abc>')"
});
int rc;
XPATH_MODULE.vm_module.xBestIndex = rcBestIndex;
XPATH_MODULE.vm_module.xFilter = rcFilter;
rc = XPATH_MODULE.create(db, "xpath");
sqlite_function_help.insert(make_pair("xpath", &xpath_help));
xpath_help.index_tags();
ensure(rc == SQLITE_OK);
return rc;
}

@ -0,0 +1,37 @@
/**
* Copyright (c) 2020, Timothy Stack
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Timothy Stack nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef xpath_vtab_hh
#define xpath_vtab_hh
#include <sqlite3.h>
int register_xpath_vtab(sqlite3 *db);
#endif

@ -77,6 +77,7 @@ LDADD = \
$(top_builddir)/src/yajl/libyajl.a \
$(top_builddir)/src/yajlpp/libyajlpp.a \
$(top_builddir)/src/base/libbase.a \
$(top_builddir)/src/pugixml/libpugixml.a \
$(CURSES_LIB) \
$(LIBARCHIVE_LIBS) \
$(SQLITE3_LIBS) \
@ -169,6 +170,7 @@ dist_noinst_SCRIPTS = \
test_sql_json_func.sh \
test_sql_str_func.sh \
test_sql_time_func.sh \
test_sql_xml_func.sh \
test_sql_fs_func.sh \
test_view_colors.sh \
test_vt52_curses.sh \
@ -337,6 +339,7 @@ TESTS = \
test_sql_fs_func.sh \
test_sql_str_func.sh \
test_sql_time_func.sh \
test_sql_xml_func.sh \
test_data_parser.sh \
test_pretty_print.sh \
test_vt52_curses.sh

@ -11,6 +11,7 @@
#include "auto_mem.hh"
#include "sqlite-extension-func.hh"
#include "regexp_vtab.hh"
#include "xpath_vtab.hh"
struct callback_state {
int cs_row;
@ -92,6 +93,7 @@ int main(int argc, char *argv[])
}
register_regexp_vtab(db.in());
register_xpath_vtab(db.in());
if (sqlite3_exec(db.in(),
stmt.c_str(),

@ -1236,7 +1236,7 @@ See Also
instr(), leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(),
printf(), proper(), regexp_capture(), regexp_match(), regexp_replace(),
replace(), replicate(), reverse(), rightstr(), rtrim(), spooky_hash(),
startswith(), strfilter(), substr(), trim(), unicode(), upper()
startswith(), strfilter(), substr(), trim(), unicode(), upper(), xpath()
Example
#1 To get a string with the code points 0x48 and 0x49:
;SELECT char(0x48, 0x49)
@ -1256,7 +1256,7 @@ See Also
leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(), printf(),
proper(), regexp_capture(), regexp_match(), regexp_replace(), replace(),
replicate(), reverse(), rightstr(), rtrim(), spooky_hash(), startswith(),
strfilter(), substr(), trim(), unicode(), upper()
strfilter(), substr(), trim(), unicode(), upper(), xpath()
Examples
#1 To search for the string 'abc' within 'abcabc' and starting at position 2:
;SELECT charindex('abc', 'abcabc', 2)
@ -1406,7 +1406,7 @@ See Also
leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(), printf(),
proper(), regexp_capture(), regexp_match(), regexp_replace(), replace(),
replicate(), reverse(), rightstr(), rtrim(), spooky_hash(), startswith(),
strfilter(), substr(), trim(), unicode(), upper()
strfilter(), substr(), trim(), unicode(), upper(), xpath()
Examples
#1 To test if the string 'notbad.jpg' ends with '.jpg':
;SELECT endswith('notbad.jpg', '.jpg')
@ -1440,7 +1440,7 @@ See Also
leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(), printf(),
proper(), regexp_capture(), regexp_match(), regexp_replace(), replace(),
replicate(), reverse(), rightstr(), rtrim(), spooky_hash(), startswith(),
strfilter(), substr(), trim(), unicode(), upper()
strfilter(), substr(), trim(), unicode(), upper(), xpath()
Examples
#1 To extract key/value pairs from a string:
;SELECT extract('foo=1 bar=2 name="Rolo Tomassi"')
@ -1522,7 +1522,7 @@ See Also
leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(), printf(),
proper(), regexp_capture(), regexp_match(), regexp_replace(), replace(),
replicate(), reverse(), rightstr(), rtrim(), spooky_hash(), startswith(),
strfilter(), substr(), trim(), unicode(), upper()
strfilter(), substr(), trim(), unicode(), upper(), xpath()
Examples
#1 To concatenate the values of the column 'ex_procname' from the table 'lnav_example_log'
:
@ -1549,7 +1549,7 @@ See Also
length(), lower(), ltrim(), padc(), padl(), padr(), printf(), proper(),
regexp_capture(), regexp_match(), regexp_replace(), replace(), replicate(),
reverse(), rightstr(), rtrim(), spooky_hash(), startswith(), strfilter(),
substr(), trim(), unicode(), upper()
substr(), trim(), unicode(), upper(), xpath()
Example
#1 To produce a hash of all of the values of 'column1':
;SELECT group_spooky_hash(column1) FROM (VALUES ('abc'), ('123'))
@ -1593,7 +1593,7 @@ See Also
leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(), printf(),
proper(), regexp_capture(), regexp_match(), regexp_replace(), replace(),
replicate(), reverse(), rightstr(), rtrim(), spooky_hash(), startswith(),
strfilter(), substr(), trim(), unicode(), upper()
strfilter(), substr(), trim(), unicode(), upper(), xpath()
Example
#1 To test get the position of 'b' in the string 'abc':
;SELECT instr('abc', 'b')
@ -1798,7 +1798,7 @@ See Also
instr(), length(), lower(), ltrim(), padc(), padl(), padr(), printf(), proper(),
regexp_capture(), regexp_match(), regexp_replace(), replace(), replicate(),
reverse(), rightstr(), rtrim(), spooky_hash(), startswith(), strfilter(),
substr(), trim(), unicode(), upper()
substr(), trim(), unicode(), upper(), xpath()
Examples
#1 To get the first character of the string 'abc':
;SELECT leftstr('abc', 1)
@ -1819,7 +1819,7 @@ See Also
instr(), leftstr(), lower(), ltrim(), padc(), padl(), padr(), printf(),
proper(), regexp_capture(), regexp_match(), regexp_replace(), replace(),
replicate(), reverse(), rightstr(), rtrim(), spooky_hash(), startswith(),
strfilter(), substr(), trim(), unicode(), upper()
strfilter(), substr(), trim(), unicode(), upper(), xpath()
Example
#1 To get the length of the string 'abc':
;SELECT length('abc')
@ -1914,7 +1914,7 @@ See Also
instr(), leftstr(), length(), ltrim(), padc(), padl(), padr(), printf(),
proper(), regexp_capture(), regexp_match(), regexp_replace(), replace(),
replicate(), reverse(), rightstr(), rtrim(), spooky_hash(), startswith(),
strfilter(), substr(), trim(), unicode(), upper()
strfilter(), substr(), trim(), unicode(), upper(), xpath()
Example
#1 To lowercase the string 'AbC':
;SELECT lower('AbC')
@ -1933,7 +1933,7 @@ See Also
instr(), leftstr(), length(), lower(), padc(), padl(), padr(), printf(),
proper(), regexp_capture(), regexp_match(), regexp_replace(), replace(),
replicate(), reverse(), rightstr(), rtrim(), spooky_hash(), startswith(),
strfilter(), substr(), trim(), unicode(), upper()
strfilter(), substr(), trim(), unicode(), upper(), xpath()
Examples
#1 To trim the leading whitespace from the string ' abc':
;SELECT ltrim(' abc')
@ -2031,7 +2031,7 @@ See Also
instr(), leftstr(), length(), lower(), ltrim(), padl(), padr(), printf(),
proper(), regexp_capture(), regexp_match(), regexp_replace(), replace(),
replicate(), reverse(), rightstr(), rtrim(), spooky_hash(), startswith(),
strfilter(), substr(), trim(), unicode(), upper()
strfilter(), substr(), trim(), unicode(), upper(), xpath()
Examples
#1 To pad the string 'abc' to a length of six characters:
;SELECT padc('abc', 6) || 'def'
@ -2053,7 +2053,7 @@ See Also
instr(), leftstr(), length(), lower(), ltrim(), padc(), padr(), printf(),
proper(), regexp_capture(), regexp_match(), regexp_replace(), replace(),
replicate(), reverse(), rightstr(), rtrim(), spooky_hash(), startswith(),
strfilter(), substr(), trim(), unicode(), upper()
strfilter(), substr(), trim(), unicode(), upper(), xpath()
Examples
#1 To pad the string 'abc' to a length of six characters:
;SELECT padl('abc', 6)
@ -2075,7 +2075,7 @@ See Also
instr(), leftstr(), length(), lower(), ltrim(), padc(), padl(), printf(),
proper(), regexp_capture(), regexp_match(), regexp_replace(), replace(),
replicate(), reverse(), rightstr(), rtrim(), spooky_hash(), startswith(),
strfilter(), substr(), trim(), unicode(), upper()
strfilter(), substr(), trim(), unicode(), upper(), xpath()
Examples
#1 To pad the string 'abc' to a length of six characters:
;SELECT padr('abc', 6) || 'def'
@ -2131,7 +2131,7 @@ See Also
instr(), leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(),
proper(), regexp_capture(), regexp_match(), regexp_replace(), replace(),
replicate(), reverse(), rightstr(), rtrim(), spooky_hash(), startswith(),
strfilter(), substr(), trim(), unicode(), upper()
strfilter(), substr(), trim(), unicode(), upper(), xpath()
Examples
#1 To substitute 'World' into the string 'Hello, %s!':
;SELECT printf('Hello, %s!', 'World')
@ -2155,7 +2155,7 @@ See Also
instr(), leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(),
printf(), regexp_capture(), regexp_match(), regexp_replace(), replace(),
replicate(), reverse(), rightstr(), rtrim(), spooky_hash(), startswith(),
strfilter(), substr(), trim(), unicode(), upper()
strfilter(), substr(), trim(), unicode(), upper(), xpath()
Example
#1 To capitalize the words in the string 'hello, world!':
;SELECT proper('hello, world!')
@ -2259,7 +2259,7 @@ See Also
instr(), leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(),
printf(), proper(), regexp_match(), regexp_replace(), replace(), replicate(),
reverse(), rightstr(), rtrim(), spooky_hash(), startswith(), strfilter(),
substr(), trim(), unicode(), upper()
substr(), trim(), unicode(), upper(), xpath()
Example
#1 To extract the key/value pairs 'a'/1 and 'b'/2 from the string 'a=1; b=2':
;SELECT * FROM regexp_capture('a=1; b=2', '(\w+)=(\d+)')
@ -2277,7 +2277,7 @@ See Also
instr(), leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(),
printf(), proper(), regexp_capture(), regexp_replace(), regexp_replace(),
replace(), replicate(), reverse(), rightstr(), rtrim(), spooky_hash(),
startswith(), strfilter(), substr(), trim(), unicode(), upper()
startswith(), strfilter(), substr(), trim(), unicode(), upper(), xpath()
Examples
#1 To capture the digits from the string '123':
;SELECT regexp_match('(\d+)', '123')
@ -2307,7 +2307,7 @@ See Also
instr(), leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(),
printf(), proper(), regexp_capture(), regexp_match(), regexp_match(), replace(),
replicate(), reverse(), rightstr(), rtrim(), spooky_hash(), startswith(),
strfilter(), substr(), trim(), unicode(), upper()
strfilter(), substr(), trim(), unicode(), upper(), xpath()
Examples
#1 To replace the word at the start of the string 'Hello, World!' with 'Goodbye':
;SELECT regexp_replace('Hello, World!', '^(\w+)', 'Goodbye')
@ -2331,7 +2331,7 @@ See Also
instr(), leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(),
printf(), proper(), regexp_capture(), regexp_match(), regexp_replace(),
replicate(), reverse(), rightstr(), rtrim(), spooky_hash(), startswith(),
strfilter(), substr(), trim(), unicode(), upper()
strfilter(), substr(), trim(), unicode(), upper(), xpath()
Examples
#1 To replace the string 'x' with 'z' in 'abc':
;SELECT replace('abc', 'x', 'z')
@ -2352,7 +2352,7 @@ See Also
instr(), leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(),
printf(), proper(), regexp_capture(), regexp_match(), regexp_replace(),
replace(), reverse(), rightstr(), rtrim(), spooky_hash(), startswith(),
strfilter(), substr(), trim(), unicode(), upper()
strfilter(), substr(), trim(), unicode(), upper(), xpath()
Example
#1 To repeat the string 'abc' three times:
;SELECT replicate('abc', 3)
@ -2368,7 +2368,7 @@ See Also
instr(), leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(),
printf(), proper(), regexp_capture(), regexp_match(), regexp_replace(),
replace(), replicate(), rightstr(), rtrim(), spooky_hash(), startswith(),
strfilter(), substr(), trim(), unicode(), upper()
strfilter(), substr(), trim(), unicode(), upper(), xpath()
Example
#1 To reverse the string 'abc':
;SELECT reverse('abc')
@ -2386,7 +2386,7 @@ See Also
instr(), leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(),
printf(), proper(), regexp_capture(), regexp_match(), regexp_replace(),
replace(), replicate(), reverse(), rtrim(), spooky_hash(), startswith(),
strfilter(), substr(), trim(), unicode(), upper()
strfilter(), substr(), trim(), unicode(), upper(), xpath()
Examples
#1 To get the last character of the string 'abc':
;SELECT rightstr('abc', 1)
@ -2446,7 +2446,7 @@ See Also
instr(), leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(),
printf(), proper(), regexp_capture(), regexp_match(), regexp_replace(),
replace(), replicate(), reverse(), rightstr(), spooky_hash(), startswith(),
strfilter(), substr(), trim(), unicode(), upper()
strfilter(), substr(), trim(), unicode(), upper(), xpath()
Examples
#1 To trim the whitespace from the end of the string 'abc ':
;SELECT rtrim('abc ')
@ -2488,7 +2488,7 @@ See Also
instr(), leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(),
printf(), proper(), regexp_capture(), regexp_match(), regexp_replace(),
replace(), replicate(), reverse(), rightstr(), rtrim(), startswith(),
strfilter(), substr(), trim(), unicode(), upper()
strfilter(), substr(), trim(), unicode(), upper(), xpath()
Examples
#1 To produce a hash for the string 'Hello, World!':
;SELECT spooky_hash('Hello, World!')
@ -2560,7 +2560,7 @@ See Also
instr(), leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(),
printf(), proper(), regexp_capture(), regexp_match(), regexp_replace(),
replace(), replicate(), reverse(), rightstr(), rtrim(), spooky_hash(),
strfilter(), substr(), trim(), unicode(), upper()
strfilter(), substr(), trim(), unicode(), upper(), xpath()
Examples
#1 To test if the string 'foobar' starts with 'foo':
;SELECT startswith('foobar', 'foo')
@ -2582,7 +2582,7 @@ See Also
instr(), leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(),
printf(), proper(), regexp_capture(), regexp_match(), regexp_replace(),
replace(), replicate(), reverse(), rightstr(), rtrim(), spooky_hash(),
startswith(), substr(), trim(), unicode(), upper()
startswith(), substr(), trim(), unicode(), upper(), xpath()
Example
#1 To get the 'b', 'c', and 'd' characters from the string 'abcabc':
;SELECT strfilter('abcabc', 'bcd')
@ -2630,7 +2630,7 @@ See Also
instr(), leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(),
printf(), proper(), regexp_capture(), regexp_match(), regexp_replace(),
replace(), replicate(), reverse(), rightstr(), rtrim(), spooky_hash(),
startswith(), strfilter(), trim(), unicode(), upper()
startswith(), strfilter(), trim(), unicode(), upper(), xpath()
Examples
#1 To get the substring starting at the second character until the end of the string 'abc'
:
@ -2756,7 +2756,7 @@ See Also
instr(), leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(),
printf(), proper(), regexp_capture(), regexp_match(), regexp_replace(),
replace(), replicate(), reverse(), rightstr(), rtrim(), spooky_hash(),
startswith(), strfilter(), substr(), unicode(), upper()
startswith(), strfilter(), substr(), unicode(), upper(), xpath()
Examples
#1 To trim whitespace from the start and end of the string ' abc ':
;SELECT trim(' abc ')
@ -2793,7 +2793,7 @@ See Also
instr(), leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(),
printf(), proper(), regexp_capture(), regexp_match(), regexp_replace(),
replace(), replicate(), reverse(), rightstr(), rtrim(), spooky_hash(),
startswith(), strfilter(), substr(), trim(), upper()
startswith(), strfilter(), substr(), trim(), upper(), xpath()
Example
#1 To get the unicode code point for the first character of 'abc':
;SELECT unicode('abc')
@ -2816,13 +2816,44 @@ See Also
instr(), leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(),
printf(), proper(), regexp_capture(), regexp_match(), regexp_replace(),
replace(), replicate(), reverse(), rightstr(), rtrim(), spooky_hash(),
startswith(), strfilter(), substr(), trim(), unicode()
startswith(), strfilter(), substr(), trim(), unicode(), xpath()
Example
#1 To uppercase the string 'aBc':
;SELECT upper('aBc')
Synopsis
xpath(xpath, xmldoc) -- A table-valued function that executes an xpath
expression over an XML string and returns the selected values.
Parameters
xpath The XPATH expression to evaluate over the XML document.
xmldoc The XML document as a string.
Results
result The result of the XPATH expression.
node_path The absolute path to the node containing the result.
node_attr The node's attributes stored in JSON object.
node_text The node's text value.
See Also
char(), charindex(), endswith(), extract(), group_concat(), group_spooky_hash(),
instr(), leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(),
printf(), proper(), regexp_capture(), regexp_match(), regexp_replace(),
replace(), replicate(), reverse(), rightstr(), rtrim(), spooky_hash(),
startswith(), strfilter(), substr(), trim(), unicode(), upper()
Examples
#1 To select the XML nodes on the path '/abc/def':
;SELECT * FROM xpath('/abc/def', '<abc><def a="b">Hello</def><def>Bye</def></abc>')
#2 To select all 'a' attributes on the path '/abc/def':
;SELECT * FROM xpath('/abc/def/@a', '<abc><def a="b">Hello</def><def>Bye</def></abc>')
#3 To select the text nodes on the path '/abc/def':
;SELECT * FROM xpath('/abc/def/text()', '<abc><def a="b">Hello &#x2605;</def></abc>')
Synopsis
zeroblob(N) -- Returns a BLOB consisting of N bytes of 0x00.
Parameter

@ -0,0 +1,67 @@
{
"xml_msg_log": {
"title": "",
"description": "",
"regex": {
"std": {
"pattern": "^\\[(?<timestamp>\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2},\\d{3})\\]\\s+(?<level>\\w+)\\s+\\[(?<module>[^:]*):(?<line>\\d+)\\]\\s*(?<body>[^\\n]*)\\n?(?<msg_data>(?:.|\\n)*)"
}
},
"level": {
"critical": "CRITICAL",
"error": "ERROR",
"warning": "WARNING",
"info": "INFO",
"debug": "DEBUG"
},
"value": {
"module": {
"kind": "string",
"identifier": true,
"description": "Python source module which emitted log entry"
},
"line": {
"kind": "integer",
"description": "Line number in the module where log entry was emitted"
},
"head": {
"kind": "string",
"description": "<head>",
"hidden": true
},
"source": {
"kind": "string",
"description": "request <source>",
"hidden": true
},
"msg_data": {
"kind": "string"
}
},
"highlights": {
"client_id": {
"pattern": "(?<=>)\\d+(?=<\/client>)",
"color": "Orange1",
"underline": true
},
"reply_error": {
"pattern": "(?<=<result>)ERROR(?=</result>)",
"color": "Red1"
}
},
"sample": [
{
"line": "[2020-12-10 06:56:41,477] INFO [m:108] Calling 'x' with params:",
"level": "info"
},
{
"line": "[2020-12-10 06:56:41,092] DEBUG [m:69] Full request text:\n<?xml version='1.0' encoding='iso-8859-2'?>\n<a-request>\n <head>\n x\n </head>\n <source>\n x\n </source>\n <request>\n <name>\n x\n </name>\n </request>\n</a-request>\n",
"level": "debug"
},
{
"line": "[2020-12-10 06:56:41,099] DEBUG [m:85] Full reply text:\n<?xml version='1.0' encoding='iso-8859-2'?>\n<a-reply>\n <head>\n x\n </head>\n <reply>\n <status>\n <result>OK</result>\n </status>\n <name>\n x\n </name>\n </reply>\n <technical-track>\n x\n </technical-track>\n</a-reply>\n",
"level": "debug"
}
]
}
}

@ -0,0 +1,36 @@
[2020-12-10 06:56:41,061] INFO [m:108] Calling 'x' with params:
[2020-12-10 06:56:41,092] DEBUG [connect.client:69] Full request text:
<?xml version='1.0' encoding='iso-8859-2'?>
<a-request>
<head>
x
</head>
<source>
x
</source>
<request>
<name>
x
</name>
</request>
</a-request>
[2020-12-10 06:56:41,099] DEBUG [m:85] Full reply text:
<?xml version='1.0' encoding='iso-8859-2'?>
<a-reply>
<head>
x
</head>
<reply>
<status>
<result>OK</result>
</status>
<name>
x
</name>
</reply>
<technical-track>
x
</technical-track>
</a-reply>

@ -819,7 +819,7 @@ EOF
schema_dump() {
${lnav_test} -n -c ';.schema' ${test_dir}/logfile_access_log.0 | head -n18
${lnav_test} -n -c ';.schema' ${test_dir}/logfile_access_log.0 | head -n19
}
run_test schema_dump
@ -836,6 +836,7 @@ CREATE VIEW lnav_view_filters_and_stats AS
SELECT * FROM lnav_view_filters LEFT NATURAL JOIN lnav_view_filter_stats;
CREATE VIRTUAL TABLE lnav_file USING lnav_file_impl();
CREATE VIRTUAL TABLE regexp_capture USING regexp_capture_impl();
CREATE VIRTUAL TABLE xpath USING xpath_impl();
CREATE VIRTUAL TABLE fstat USING fstat_impl();
CREATE TABLE http_status_codes (
status integer PRIMARY KEY,

@ -0,0 +1,40 @@
#! /bin/bash
run_test ./drive_sql "SELECT * FROM xpath('/abc[', '<abc/>')"
check_error_output "invalid xpath not reported?" <<EOF
error: sqlite3_exec failed -- Invalid XPATH expression at offset 5: Unrecognized node test
EOF
run_test ./drive_sql "SELECT * FROM xpath('/abc', '<abc')"
check_error_output "invalid XML not reported?" <<EOF
error: sqlite3_exec failed -- Invalid XML document at offset 3: Error parsing start element tag
EOF
run_test ./drive_sql "SELECT * FROM xpath('/abc/def', '<abc/>')"
check_output "got unexpected results" <<EOF
EOF
run_test ./drive_sql "SELECT * FROM xpath('/abc/def[@a=\"b\"]', '<abc><def/><def a=\"b\">ghi</def></abc>')"
check_output "got unexpected results" <<EOF
Row 0:
Column result: <def a="b">ghi</def>
Column node_path: /abc/def[2]
Column node_attr: {"a":"b"}
Column node_text: ghi
EOF
run_test ./drive_sql "SELECT * FROM xpath('/abc/def', '<abc><def>Hello &gt;</def></abc>')"
check_output "got unexpected results" <<EOF
Row 0:
Column result: <def>Hello &gt;</def>
Column node_path: /abc/def
Column node_attr: {}
Column node_text: Hello >
EOF
Loading…
Cancel
Save