2005-08-01 23:17:22 -04:00
|
|
|
/*
|
|
|
|
* $Id$
|
|
|
|
*
|
|
|
|
* This is really two parts -- the lexer and the parser. Converting
|
|
|
|
* a parse tree back to a format that works with the database backend
|
|
|
|
* is left to the db backend.
|
|
|
|
*
|
|
|
|
* Oh, and this is called "smart-parser" because it parses terms for
|
|
|
|
* specifying smart playlists, not because it is particularly smart. :)
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
2006-02-26 03:46:24 -05:00
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
# include "config.h"
|
|
|
|
#endif
|
|
|
|
|
2005-08-01 23:17:22 -04:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
2005-10-29 17:23:43 -04:00
|
|
|
#include <time.h>
|
2005-08-01 23:17:22 -04:00
|
|
|
|
2006-04-15 18:39:45 -04:00
|
|
|
#include "daapd.h"
|
2005-08-01 23:17:22 -04:00
|
|
|
#include "err.h"
|
|
|
|
|
2006-03-12 06:30:58 -05:00
|
|
|
#ifdef HAVE_SQL
|
|
|
|
extern int db_sql_escape(char *buffer, int *size, char *fmt, ...);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
2005-10-13 03:38:22 -04:00
|
|
|
typedef struct tag_token {
|
2005-08-14 23:16:36 -04:00
|
|
|
int token_id;
|
|
|
|
union {
|
2005-10-13 03:38:22 -04:00
|
|
|
char *cvalue;
|
|
|
|
int ivalue;
|
2005-10-29 17:23:43 -04:00
|
|
|
time_t tvalue;
|
2005-08-14 23:16:36 -04:00
|
|
|
} data;
|
2005-10-13 03:38:22 -04:00
|
|
|
} SP_TOKEN;
|
2005-08-14 23:16:36 -04:00
|
|
|
|
2005-10-18 18:35:10 -04:00
|
|
|
typedef struct tag_sp_node {
|
|
|
|
union {
|
|
|
|
struct tag_sp_node *node;
|
|
|
|
char *field;
|
|
|
|
} left;
|
|
|
|
|
|
|
|
int op;
|
2005-10-20 03:33:58 -04:00
|
|
|
int op_type;
|
2005-11-06 16:06:07 -05:00
|
|
|
int not_flag;
|
2005-10-18 18:35:10 -04:00
|
|
|
|
|
|
|
union {
|
|
|
|
struct tag_sp_node *node;
|
|
|
|
int ivalue;
|
|
|
|
char *cvalue;
|
2005-10-29 17:23:43 -04:00
|
|
|
time_t tvalue;
|
2005-10-18 18:35:10 -04:00
|
|
|
} right;
|
|
|
|
} SP_NODE;
|
|
|
|
|
2006-04-10 00:52:14 -04:00
|
|
|
#define SP_OPTYPE_INVALID 0
|
|
|
|
#define SP_OPTYPE_ANDOR 1
|
|
|
|
#define SP_OPTYPE_STRING 2
|
|
|
|
#define SP_OPTYPE_INT 3
|
|
|
|
#define SP_OPTYPE_DATE 4
|
2005-10-20 03:33:58 -04:00
|
|
|
|
2006-04-10 00:52:14 -04:00
|
|
|
#define SP_HINT_NONE 0
|
|
|
|
#define SP_HINT_STRING 1
|
|
|
|
#define SP_HINT_INT 2
|
|
|
|
#define SP_HINT_DATE 3
|
2006-03-09 18:54:00 -05:00
|
|
|
|
2005-10-13 03:38:22 -04:00
|
|
|
/*
|
2005-10-02 18:48:07 -04:00
|
|
|
#define T_ID 0x00
|
|
|
|
#define T_PATH 0x01
|
|
|
|
#define T_TITLE 0x02
|
|
|
|
#define T_ARTIST 0x03
|
|
|
|
#define T_ALBUM 0x04
|
|
|
|
#define T_GENRE 0x05
|
|
|
|
#define T_COMMENT 0x06
|
|
|
|
#define T_TYPE 0x07
|
|
|
|
#define T_COMPOSER 0x08
|
|
|
|
#define T_ORCHESTRA 0x09
|
|
|
|
#define T_GROUPING 0x0a
|
|
|
|
#define T_URL 0x0b
|
|
|
|
#define T_BITRATE 0x0c
|
|
|
|
#define T_SAMPLERATE 0x0d
|
|
|
|
#define T_SONG_LENGTH 0x0e
|
|
|
|
#define T_FILE_SIZE 0x0f
|
|
|
|
#define T_YEAR 0x10
|
|
|
|
#define T_TRACK 0x11
|
|
|
|
#define T_TOTAL_TRACKS 0x12
|
|
|
|
#define T_DISC 0x13
|
|
|
|
#define T_TOTAL_DISCS 0x14
|
|
|
|
#define T_BPM 0x15
|
|
|
|
#define T_COMPILATION 0x16
|
|
|
|
#define T_RATING 0x17
|
|
|
|
#define T_PLAYCOUNT 0x18
|
|
|
|
#define T_DATA_KIND 0x19
|
|
|
|
#define T_ITEM_KIND 0x1a
|
|
|
|
#define T_DESCRIPTION 0x1b
|
|
|
|
#define T_TIME_ADDED 0x1c
|
|
|
|
#define T_TIME_MODIFIED 0x0d
|
|
|
|
#define T_TIME_PLAYED 0x1d
|
|
|
|
#define T_TIME_STAMP 0x1e
|
|
|
|
#define T_DISABLED 0x1f
|
|
|
|
#define T_SAMPLE_COUNT 0x1e
|
|
|
|
#define T_FORCE_UPDATE 0x1f
|
|
|
|
#define T_CODECTYPE 0x20
|
|
|
|
#define T_IDX 0x21
|
2005-10-13 03:38:22 -04:00
|
|
|
*/
|
|
|
|
|
2005-10-18 18:35:10 -04:00
|
|
|
/**
|
2005-10-13 03:38:22 -04:00
|
|
|
* high 4 bits:
|
|
|
|
*
|
2005-10-18 18:35:10 -04:00
|
|
|
* 0x8000 -
|
2005-10-13 03:38:22 -04:00
|
|
|
* 0x4000 -
|
|
|
|
* 0x2000 - data is string
|
|
|
|
* 0x1000 - data is int
|
2005-10-17 00:57:06 -04:00
|
|
|
*
|
2005-10-18 18:35:10 -04:00
|
|
|
* 0x0800 -
|
2005-10-17 00:57:06 -04:00
|
|
|
* 0x0400 -
|
|
|
|
* 0x0200 -
|
|
|
|
* 0x0100 -
|
2005-10-13 03:38:22 -04:00
|
|
|
*/
|
|
|
|
|
|
|
|
#define T_STRING 0x2001
|
2005-10-14 00:11:06 -04:00
|
|
|
#define T_INT_FIELD 0x2002
|
2005-10-13 03:38:22 -04:00
|
|
|
#define T_STRING_FIELD 0x2003
|
2005-10-14 00:11:06 -04:00
|
|
|
#define T_DATE_FIELD 0x2004
|
2005-10-02 18:48:07 -04:00
|
|
|
|
2005-10-13 03:38:22 -04:00
|
|
|
#define T_OPENPAREN 0x0005
|
|
|
|
#define T_CLOSEPAREN 0x0006
|
2005-10-17 00:57:06 -04:00
|
|
|
#define T_LESS 0x0007
|
|
|
|
#define T_LESSEQUAL 0x0008
|
|
|
|
#define T_GREATER 0x0009
|
|
|
|
#define T_GREATEREQUAL 0x000a
|
|
|
|
#define T_EQUAL 0x000b
|
|
|
|
#define T_OR 0x000c
|
|
|
|
#define T_AND 0x000d
|
|
|
|
#define T_QUOTE 0x000e
|
|
|
|
#define T_NUMBER 0x000f
|
2005-10-23 20:18:08 -04:00
|
|
|
#define T_INCLUDES 0x0010
|
2005-10-29 17:23:43 -04:00
|
|
|
#define T_BEFORE 0x0011
|
|
|
|
#define T_AFTER 0x0012
|
|
|
|
#define T_AGO 0x0013
|
|
|
|
#define T_TODAY 0x0014
|
|
|
|
#define T_THE 0x0015
|
|
|
|
#define T_DAY 0x0016
|
|
|
|
#define T_WEEK 0x0017
|
|
|
|
#define T_MONTH 0x0018
|
|
|
|
#define T_YEAR 0x0019
|
|
|
|
#define T_DATE 0x001a
|
2005-11-06 16:06:07 -05:00
|
|
|
#define T_NOT 0x001b
|
2005-12-13 15:42:03 -05:00
|
|
|
#define T_STARTSWITH 0x001c
|
|
|
|
#define T_ENDSWITH 0x001d
|
|
|
|
#define T_LAST 0x001e
|
2005-10-17 00:57:06 -04:00
|
|
|
|
2006-03-10 18:41:13 -05:00
|
|
|
/* specific to query extensions */
|
|
|
|
#define T_GREATERAND 0x001f
|
|
|
|
#define T_EXPRQUOTE 0x0020
|
|
|
|
|
2005-10-17 00:57:06 -04:00
|
|
|
#define T_EOF 0x00fd
|
|
|
|
#define T_BOF 0x00fe
|
|
|
|
#define T_ERROR 0x00ff
|
|
|
|
|
|
|
|
char *sp_token_descr[] = {
|
|
|
|
"unknown",
|
|
|
|
"literal string",
|
|
|
|
"integer field",
|
|
|
|
"string field",
|
|
|
|
"date field",
|
|
|
|
"(",
|
|
|
|
")",
|
|
|
|
"<",
|
|
|
|
"<=",
|
|
|
|
">",
|
|
|
|
">=",
|
|
|
|
"=",
|
|
|
|
"or",
|
|
|
|
"and",
|
|
|
|
"quote",
|
2005-10-23 20:18:08 -04:00
|
|
|
"number",
|
2005-10-29 17:23:43 -04:00
|
|
|
"like",
|
|
|
|
"before",
|
|
|
|
"after",
|
|
|
|
"ago",
|
|
|
|
"today",
|
|
|
|
"the",
|
|
|
|
"day(s)",
|
|
|
|
"week(s)",
|
|
|
|
"month(s)",
|
|
|
|
"year(s)",
|
2005-11-06 16:06:07 -05:00
|
|
|
"date",
|
2005-12-13 15:42:03 -05:00
|
|
|
"not",
|
|
|
|
"like",
|
|
|
|
"like"
|
2005-10-17 00:57:06 -04:00
|
|
|
};
|
2005-10-13 03:38:22 -04:00
|
|
|
|
|
|
|
typedef struct tag_fieldlookup {
|
|
|
|
int type;
|
|
|
|
char *name;
|
2006-03-10 00:51:53 -05:00
|
|
|
char *xlat;
|
2005-10-13 03:38:22 -04:00
|
|
|
} FIELDLOOKUP;
|
|
|
|
|
2006-03-09 18:54:00 -05:00
|
|
|
/* normal terminators, in-string terminators, escapes, quotes */
|
|
|
|
char *sp_terminators[2][4] = {
|
|
|
|
{ " \t\n\r\"<>=()|&!", "\"","\"","\"" },
|
2006-03-12 06:30:58 -05:00
|
|
|
{ "()'+: -,", "'", "\\*'","" }
|
2006-03-07 18:37:42 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
FIELDLOOKUP sp_symbols_0[] = {
|
2006-03-10 00:51:53 -05:00
|
|
|
{ T_OR, "||", NULL },
|
|
|
|
{ T_AND, "&&", NULL },
|
|
|
|
{ T_EQUAL, "=", NULL },
|
|
|
|
{ T_LESSEQUAL, "<=", NULL },
|
|
|
|
{ T_LESS, "<", NULL },
|
|
|
|
{ T_GREATEREQUAL, ">=", NULL },
|
|
|
|
{ T_GREATER, ">", NULL },
|
|
|
|
{ T_OPENPAREN, "(", NULL },
|
|
|
|
{ T_CLOSEPAREN, ")", NULL },
|
|
|
|
{ T_NOT, "!", NULL },
|
|
|
|
{ 0, NULL, NULL }
|
2006-03-07 18:37:42 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
FIELDLOOKUP sp_symbols_1[] = {
|
2006-03-10 18:41:13 -05:00
|
|
|
{ T_OPENPAREN, "(", NULL },
|
|
|
|
{ T_CLOSEPAREN, ")", NULL },
|
|
|
|
{ T_EXPRQUOTE, "'", NULL },
|
|
|
|
{ T_GREATERAND, "+", NULL },
|
|
|
|
{ T_GREATERAND, " ", NULL },
|
|
|
|
{ T_LESS,"-", NULL },
|
|
|
|
{ T_OR, ",", NULL },
|
|
|
|
{ T_EQUAL, ":", NULL },
|
|
|
|
{ T_NOT, "!", NULL },
|
2006-03-10 00:51:53 -05:00
|
|
|
{ 0, NULL, NULL }
|
2006-03-07 18:37:42 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
FIELDLOOKUP *sp_symbols[2] = {
|
|
|
|
sp_symbols_0, sp_symbols_1
|
|
|
|
};
|
|
|
|
|
|
|
|
FIELDLOOKUP sp_fields_0[] = {
|
2006-03-10 00:51:53 -05:00
|
|
|
{ T_INT_FIELD, "id", NULL },
|
|
|
|
{ T_STRING_FIELD, "path", NULL },
|
|
|
|
{ T_STRING_FIELD, "fname", NULL },
|
|
|
|
{ T_STRING_FIELD, "title", NULL },
|
|
|
|
{ T_STRING_FIELD, "artist", NULL },
|
|
|
|
{ T_STRING_FIELD, "album", NULL },
|
|
|
|
{ T_STRING_FIELD, "genre", NULL },
|
|
|
|
{ T_STRING_FIELD, "comment", NULL },
|
|
|
|
{ T_STRING_FIELD, "type", NULL },
|
|
|
|
{ T_STRING_FIELD, "composer", NULL },
|
|
|
|
{ T_STRING_FIELD, "orchestra", NULL },
|
|
|
|
{ T_STRING_FIELD, "grouping", NULL },
|
|
|
|
{ T_STRING_FIELD, "url", NULL },
|
|
|
|
{ T_INT_FIELD, "bitrate", NULL },
|
|
|
|
{ T_INT_FIELD, "samplerate", NULL },
|
|
|
|
{ T_INT_FIELD, "song_length", NULL },
|
|
|
|
{ T_INT_FIELD, "file_size", NULL },
|
|
|
|
{ T_INT_FIELD, "year", NULL },
|
|
|
|
{ T_INT_FIELD, "track", NULL },
|
|
|
|
{ T_INT_FIELD, "total_tracks", NULL },
|
|
|
|
{ T_INT_FIELD, "disc", NULL },
|
|
|
|
{ T_INT_FIELD, "total_discs", NULL },
|
|
|
|
{ T_INT_FIELD, "bpm", NULL },
|
|
|
|
{ T_INT_FIELD, "compilation", NULL },
|
|
|
|
{ T_INT_FIELD, "rating", NULL },
|
|
|
|
{ T_INT_FIELD, "play_count", NULL },
|
|
|
|
{ T_INT_FIELD, "data_kind", NULL },
|
|
|
|
{ T_INT_FIELD, "item_kind", NULL },
|
|
|
|
{ T_STRING_FIELD, "description", NULL },
|
|
|
|
{ T_DATE_FIELD, "time_added", NULL },
|
|
|
|
{ T_DATE_FIELD, "time_modified", NULL },
|
|
|
|
{ T_DATE_FIELD, "time_played", NULL },
|
|
|
|
{ T_DATE_FIELD, "db_timestamp", NULL },
|
|
|
|
{ T_INT_FIELD, "sample_count", NULL },
|
|
|
|
{ T_INT_FIELD, "force_update", NULL },
|
|
|
|
{ T_STRING_FIELD, "codectype", NULL },
|
|
|
|
{ T_INT_FIELD, "idx", NULL },
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-10-17 00:57:06 -04:00
|
|
|
/* end of db fields */
|
2006-03-10 00:51:53 -05:00
|
|
|
{ T_OR, "or", NULL },
|
|
|
|
{ T_AND, "and", NULL },
|
|
|
|
{ T_INCLUDES, "includes", NULL },
|
|
|
|
{ T_BEFORE, "before", NULL },
|
|
|
|
{ T_AFTER, "after", NULL },
|
|
|
|
{ T_AGO, "ago", NULL },
|
|
|
|
{ T_TODAY, "today", NULL },
|
|
|
|
{ T_THE, "the", NULL },
|
|
|
|
{ T_DAY, "days", NULL },
|
|
|
|
{ T_DAY, "day", NULL },
|
|
|
|
{ T_WEEK, "weeks", NULL },
|
|
|
|
{ T_WEEK, "week", NULL },
|
|
|
|
{ T_MONTH, "months", NULL },
|
|
|
|
{ T_MONTH, "month", NULL },
|
|
|
|
{ T_YEAR, "years", NULL },
|
|
|
|
{ T_YEAR, "year", NULL },
|
|
|
|
{ T_NOT, "not", NULL },
|
|
|
|
{ T_STARTSWITH, "startswith", NULL },
|
|
|
|
{ T_ENDSWITH, "endswith", NULL },
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-10-17 00:57:06 -04:00
|
|
|
/* end */
|
2006-03-10 00:51:53 -05:00
|
|
|
{ 0, NULL, NULL }
|
2006-03-07 18:37:42 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
FIELDLOOKUP sp_fields_1[] = {
|
2006-03-10 18:41:13 -05:00
|
|
|
{ T_STRING_FIELD, "dmap.itemname", "title" },
|
|
|
|
{ T_INT_FIELD, "dmap.itemid", "id" },
|
|
|
|
{ T_STRING_FIELD, "daap.songalbum", "album" },
|
|
|
|
{ T_STRING_FIELD, "daap.songartist", "artist" },
|
|
|
|
{ T_INT_FIELD, "daap.songbitrate", "bitrate" },
|
|
|
|
{ T_STRING_FIELD, "daap.songcomment", "comment" },
|
|
|
|
{ T_INT_FIELD, "daap.songcompilation", "compilation" },
|
|
|
|
{ T_STRING_FIELD, "daap.songcomposer", "composer" },
|
|
|
|
{ T_INT_FIELD, "daap.songdatakind", "data_kind" },
|
|
|
|
{ T_STRING_FIELD, "daap.songdataurl", "url" },
|
|
|
|
{ T_INT_FIELD, "daap.songdateadded", "time_added" },
|
|
|
|
{ T_INT_FIELD, "daap.songdatemodified", "time_modified" },
|
|
|
|
{ T_STRING_FIELD, "daap.songdescription", "description" },
|
|
|
|
{ T_INT_FIELD, "daap.songdisccount", "total_discs" },
|
|
|
|
{ T_INT_FIELD, "daap.songdiscnumber", "disc" },
|
|
|
|
{ T_STRING_FIELD, "daap.songformat", "type" },
|
|
|
|
{ T_STRING_FIELD, "daap.songgenre", "genre" },
|
|
|
|
{ T_INT_FIELD, "daap.songsamplerate", "samplerate" },
|
|
|
|
{ T_INT_FIELD, "daap.songsize", "file_size" },
|
2006-03-10 00:51:53 -05:00
|
|
|
// { T_INT_FIELD, "daap.songstarttime", 0 },
|
2006-03-10 18:41:13 -05:00
|
|
|
{ T_INT_FIELD, "daap.songstoptime", "song_length" },
|
|
|
|
{ T_INT_FIELD, "daap.songtime", "song_length" },
|
|
|
|
{ T_INT_FIELD, "daap.songtrackcount", "total_tracks" },
|
|
|
|
{ T_INT_FIELD, "daap.songtracknumber", "track" },
|
|
|
|
{ T_INT_FIELD, "daap.songyear", "year" },
|
2006-03-10 00:51:53 -05:00
|
|
|
|
|
|
|
{ 0, NULL, NULL }
|
2006-03-07 18:37:42 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
FIELDLOOKUP *sp_fields[2] = {
|
|
|
|
sp_fields_0, sp_fields_1
|
2005-08-14 23:16:36 -04:00
|
|
|
};
|
|
|
|
|
2005-10-18 18:35:10 -04:00
|
|
|
typedef struct tag_parsetree {
|
2005-10-22 19:05:29 -04:00
|
|
|
int in_string;
|
2006-03-07 18:37:42 -05:00
|
|
|
int token_list;
|
2005-08-01 23:17:22 -04:00
|
|
|
char *term;
|
2005-10-06 00:48:04 -04:00
|
|
|
char *current;
|
2005-10-13 03:38:22 -04:00
|
|
|
SP_TOKEN token;
|
2005-10-22 19:05:29 -04:00
|
|
|
int token_pos;
|
2005-10-20 03:33:58 -04:00
|
|
|
SP_NODE *tree;
|
2005-10-22 19:05:29 -04:00
|
|
|
char *error;
|
|
|
|
char level;
|
2005-08-01 23:17:22 -04:00
|
|
|
} PARSESTRUCT, *PARSETREE;
|
|
|
|
|
2005-10-29 17:23:43 -04:00
|
|
|
#define SP_E_SUCCESS 0x00
|
|
|
|
#define SP_E_CLOSE 0x01
|
|
|
|
#define SP_E_FIELD 0x02
|
|
|
|
#define SP_E_STRCMP 0x03
|
|
|
|
#define SP_E_CLOSEQUOTE 0x04
|
|
|
|
#define SP_E_STRING 0x05
|
|
|
|
#define SP_E_OPENQUOTE 0x06
|
|
|
|
#define SP_E_INTCMP 0x07
|
|
|
|
#define SP_E_NUMBER 0x08
|
|
|
|
#define SP_E_DATECMP 0x09
|
|
|
|
#define SP_E_BEFOREAFTER 0x0a
|
|
|
|
#define SP_E_TIMEINTERVAL 0x0b
|
|
|
|
#define SP_E_DATE 0x0c
|
2006-03-10 18:41:13 -05:00
|
|
|
#define SP_E_EXPRQUOTE 0x0d
|
2005-10-22 19:05:29 -04:00
|
|
|
|
|
|
|
char *sp_errorstrings[] = {
|
|
|
|
"Success",
|
|
|
|
"Expecting ')'",
|
|
|
|
"Expecting field name",
|
|
|
|
"Expecting string comparison operator (=, includes)",
|
|
|
|
"Expecting '\"' (closing quote)",
|
|
|
|
"Expecting literal string",
|
|
|
|
"Expecting '\"' (opening quote)",
|
2005-12-13 15:42:03 -05:00
|
|
|
"Expecting integer comparison operator (=,<,>, etc)",
|
2005-10-29 17:23:43 -04:00
|
|
|
"Expecting integer",
|
|
|
|
"Expecting date comparison operator (<,<=,>,>=)",
|
|
|
|
"Expecting interval comparison (before, after)",
|
|
|
|
"Expecting time interval (days, weeks, months, years)",
|
2006-03-10 18:41:13 -05:00
|
|
|
"Expecting date",
|
|
|
|
"Expecting ' (single quote)\n"
|
2005-10-22 19:05:29 -04:00
|
|
|
};
|
|
|
|
|
2005-10-17 00:57:06 -04:00
|
|
|
/* Forwards */
|
2005-10-20 03:33:58 -04:00
|
|
|
SP_NODE *sp_parse_phrase(PARSETREE tree);
|
|
|
|
SP_NODE *sp_parse_oexpr(PARSETREE tree);
|
2005-10-22 19:05:29 -04:00
|
|
|
SP_NODE *sp_parse_aexpr(PARSETREE tree);
|
2005-10-20 03:33:58 -04:00
|
|
|
SP_NODE *sp_parse_expr(PARSETREE tree);
|
|
|
|
SP_NODE *sp_parse_criterion(PARSETREE tree);
|
|
|
|
SP_NODE *sp_parse_string_criterion(PARSETREE tree);
|
|
|
|
SP_NODE *sp_parse_int_criterion(PARSETREE tree);
|
|
|
|
SP_NODE *sp_parse_date_criterion(PARSETREE tree);
|
2005-10-29 17:23:43 -04:00
|
|
|
time_t sp_parse_date(PARSETREE tree);
|
|
|
|
time_t sp_parse_date_interval(PARSETREE tree);
|
2005-10-22 19:05:29 -04:00
|
|
|
void sp_free_node(SP_NODE *node);
|
|
|
|
int sp_node_size(SP_NODE *node);
|
|
|
|
void sp_set_error(PARSETREE tree,int error);
|
|
|
|
|
2005-10-20 03:33:58 -04:00
|
|
|
|
2005-10-22 19:05:29 -04:00
|
|
|
/**
|
|
|
|
* simple logging funcitons
|
|
|
|
*
|
|
|
|
* @param tree tree ew are parsing
|
|
|
|
* @param function funtion entering/exiting
|
|
|
|
* @param result result of param (if exiting)
|
|
|
|
*/
|
|
|
|
void sp_enter_exit(PARSETREE tree, char *function, int enter, void *result) {
|
|
|
|
char *str_result = result ? "success" : "failure";
|
|
|
|
|
|
|
|
if(enter) {
|
|
|
|
tree->level++;
|
|
|
|
DPRINTF(E_DBG,L_PARSE,"%*s Entering %s\n",tree->level," ",function);
|
|
|
|
} else {
|
|
|
|
DPRINTF(E_DBG,L_PARSE,"%*s Exiting %s (%s)\n",tree->level," ",
|
|
|
|
function, str_result);
|
|
|
|
tree->level--;
|
|
|
|
}
|
|
|
|
}
|
2005-12-13 15:42:03 -05:00
|
|
|
|
|
|
|
|
2005-10-20 03:33:58 -04:00
|
|
|
/**
|
|
|
|
* see if a string is actually a number
|
|
|
|
*
|
|
|
|
* @param string string to check
|
|
|
|
* @returns 1 if the string is numeric, 0 otherwise
|
|
|
|
*/
|
|
|
|
int sp_isnumber(char *string) {
|
|
|
|
char *current=string;
|
2005-12-13 15:42:03 -05:00
|
|
|
|
|
|
|
while(*current && (*current >= '0') && (*current <= '9')) {
|
2005-10-20 03:33:58 -04:00
|
|
|
current++;
|
|
|
|
}
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-10-20 03:33:58 -04:00
|
|
|
return *current ? 0 : 1;
|
|
|
|
}
|
|
|
|
|
2005-11-06 16:06:07 -05:00
|
|
|
/**
|
|
|
|
* see if a string is actually a date in the date format
|
|
|
|
* YYYY-MM-DD
|
|
|
|
*
|
|
|
|
* @param string string to check
|
|
|
|
* @returns time_t of date, or 0 if not a date
|
|
|
|
*/
|
|
|
|
time_t sp_isdate(char *string) {
|
|
|
|
struct tm date_time;
|
|
|
|
time_t seconds=0;
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-11-06 16:06:07 -05:00
|
|
|
memset((void*)&date_time,0,sizeof(date_time));
|
|
|
|
if(strptime(string,"%Y-%m-%d",&date_time)) {
|
|
|
|
seconds=timegm(&date_time);
|
|
|
|
}
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-11-06 16:06:07 -05:00
|
|
|
return seconds;
|
|
|
|
}
|
2005-10-17 00:57:06 -04:00
|
|
|
|
2005-10-13 03:38:22 -04:00
|
|
|
/**
|
2006-03-10 18:41:13 -05:00
|
|
|
* scan the input, returning the next available token. This is
|
|
|
|
* kind of a mess, and looking at it with new eyes would probably
|
|
|
|
* yield a better way of tokenizing the stream, but this seems to
|
|
|
|
* work.
|
2005-10-13 03:38:22 -04:00
|
|
|
*
|
|
|
|
* @param tree current working parse tree.
|
|
|
|
* @returns next token (token, not the value)
|
|
|
|
*/
|
2006-03-09 18:54:00 -05:00
|
|
|
int sp_scan(PARSETREE tree, int hint) {
|
2005-10-17 00:57:06 -04:00
|
|
|
char *terminator=NULL;
|
2005-10-06 00:48:04 -04:00
|
|
|
char *tail;
|
2006-03-07 18:37:42 -05:00
|
|
|
FIELDLOOKUP *pfield=sp_fields[tree->token_list];
|
2005-10-13 03:38:22 -04:00
|
|
|
int len;
|
2005-10-17 00:57:06 -04:00
|
|
|
int found;
|
2005-10-20 03:33:58 -04:00
|
|
|
int numval;
|
2006-03-09 18:54:00 -05:00
|
|
|
int is_qstr;
|
2005-11-06 16:06:07 -05:00
|
|
|
time_t tval;
|
2006-03-09 18:54:00 -05:00
|
|
|
char *qstr;
|
2006-03-10 18:41:13 -05:00
|
|
|
char *token_string;
|
2006-03-12 06:30:58 -05:00
|
|
|
char *dst, *src;
|
2005-10-13 03:38:22 -04:00
|
|
|
|
|
|
|
if(tree->token.token_id & 0x2000) {
|
2005-10-17 00:57:06 -04:00
|
|
|
if(tree->token.data.cvalue)
|
|
|
|
free(tree->token.data.cvalue);
|
2005-10-13 03:38:22 -04:00
|
|
|
}
|
2005-10-06 00:48:04 -04:00
|
|
|
|
2005-10-17 00:57:06 -04:00
|
|
|
if(tree->token.token_id == T_EOF) {
|
2005-10-22 19:05:29 -04:00
|
|
|
DPRINTF(E_DBG,L_PARSE,"%*s Returning token T_EOF\n",tree->level," ");
|
2005-10-13 03:38:22 -04:00
|
|
|
return T_EOF;
|
2005-10-17 00:57:06 -04:00
|
|
|
}
|
2005-10-06 00:48:04 -04:00
|
|
|
|
|
|
|
/* keep advancing until we have a token */
|
2005-10-14 00:11:06 -04:00
|
|
|
while(*(tree->current) && strchr(" \t\n\r",*(tree->current)))
|
2005-10-13 03:38:22 -04:00
|
|
|
tree->current++;
|
2005-10-18 18:35:10 -04:00
|
|
|
|
2006-02-26 03:46:24 -05:00
|
|
|
tree->token_pos = (int) (tree->current - tree->term);
|
2005-10-22 19:05:29 -04:00
|
|
|
|
2005-10-13 03:38:22 -04:00
|
|
|
if(!*(tree->current)) {
|
2005-10-22 19:05:29 -04:00
|
|
|
tree->token.token_id = T_EOF;
|
|
|
|
DPRINTF(E_DBG,L_PARSE,"%*s Returning token %04x\n",tree->level," ",
|
|
|
|
tree->token.token_id);
|
2005-10-13 03:38:22 -04:00
|
|
|
return tree->token.token_id;
|
2005-10-06 00:48:04 -04:00
|
|
|
}
|
2005-10-18 18:35:10 -04:00
|
|
|
|
2005-10-14 00:11:06 -04:00
|
|
|
DPRINTF(E_SPAM,L_PARSE,"Current offset: %d, char: %c\n",
|
2005-10-22 19:05:29 -04:00
|
|
|
tree->token_pos, *(tree->current));
|
2005-10-06 00:48:04 -04:00
|
|
|
|
2006-03-09 18:54:00 -05:00
|
|
|
if(hint == SP_HINT_STRING) {
|
|
|
|
terminator=sp_terminators[tree->token_list][1];
|
|
|
|
tree->in_string = 1;
|
|
|
|
} else {
|
|
|
|
terminator = sp_terminators[tree->token_list][0];
|
|
|
|
tree->in_string = 0;
|
|
|
|
}
|
2005-10-17 00:57:06 -04:00
|
|
|
|
2006-03-09 23:00:38 -05:00
|
|
|
|
|
|
|
DPRINTF(E_SPAM,L_PARSE,"Starting scan - in_string: %d, hint: %d\n",
|
2006-03-10 18:41:13 -05:00
|
|
|
tree->in_string, hint);
|
2006-03-09 23:00:38 -05:00
|
|
|
|
2006-03-09 18:54:00 -05:00
|
|
|
/* check symbols */
|
2006-03-07 18:37:42 -05:00
|
|
|
if(!tree->in_string) {
|
|
|
|
pfield=sp_symbols[tree->token_list];
|
2006-03-09 18:54:00 -05:00
|
|
|
while(pfield->name) {
|
2006-03-07 18:37:42 -05:00
|
|
|
if(!strncmp(pfield->name,tree->current,strlen(pfield->name))) {
|
|
|
|
/* that's a match */
|
|
|
|
tree->current += strlen(pfield->name);
|
|
|
|
tree->token.token_id = pfield->type;
|
2006-03-09 18:54:00 -05:00
|
|
|
return pfield->type;
|
2005-10-22 19:05:29 -04:00
|
|
|
}
|
2006-03-07 18:37:42 -05:00
|
|
|
pfield++;
|
2005-10-22 19:05:29 -04:00
|
|
|
}
|
2005-12-13 15:42:03 -05:00
|
|
|
}
|
2005-10-13 03:38:22 -04:00
|
|
|
|
2006-03-09 18:54:00 -05:00
|
|
|
qstr = sp_terminators[tree->token_list][3];
|
2006-03-09 23:00:38 -05:00
|
|
|
is_qstr = (strchr(qstr,*(tree->current)) != NULL);
|
2006-03-10 18:41:13 -05:00
|
|
|
|
|
|
|
DPRINTF(E_SPAM,L_PARSE,"qstr: %s -- is_quoted: %d\n",qstr,is_qstr);
|
|
|
|
|
2006-03-09 18:54:00 -05:00
|
|
|
if(strlen(qstr)) { /* strings ARE quoted */
|
|
|
|
if(hint == SP_HINT_STRING) { /* MUST be a quote */
|
2006-03-09 23:00:38 -05:00
|
|
|
if(!is_qstr) {
|
2006-03-10 18:41:13 -05:00
|
|
|
tree->token.token_id = T_ERROR;
|
2006-03-09 18:54:00 -05:00
|
|
|
return T_ERROR;
|
2006-03-10 18:41:13 -05:00
|
|
|
}
|
2006-03-12 16:25:29 -05:00
|
|
|
tree->current++; /* absorb it*/
|
2006-03-09 18:54:00 -05:00
|
|
|
} else {
|
2006-03-10 18:41:13 -05:00
|
|
|
if(is_qstr) {
|
|
|
|
tree->in_string = 1; /* guess we're in a string */
|
|
|
|
terminator=sp_terminators[tree->token_list][1];
|
|
|
|
tree->current++;
|
|
|
|
}
|
2006-03-09 18:54:00 -05:00
|
|
|
}
|
2005-10-13 03:38:22 -04:00
|
|
|
}
|
|
|
|
|
2006-03-09 18:54:00 -05:00
|
|
|
DPRINTF(E_SPAM,L_PARSE,"keyword or string!\n");
|
2005-10-18 18:35:10 -04:00
|
|
|
|
2006-03-09 18:54:00 -05:00
|
|
|
/* walk to a terminator */
|
|
|
|
tail = tree->current;
|
2005-10-18 18:35:10 -04:00
|
|
|
|
2006-03-09 18:54:00 -05:00
|
|
|
while((*tail) && (!strchr(terminator,*tail))) {
|
2006-03-12 06:30:58 -05:00
|
|
|
/* skip escaped characters -- will be unescaped later */
|
|
|
|
if((*tail == '\\')&&(*(tail+1) != '\0'))
|
|
|
|
tail++;
|
2006-03-09 18:54:00 -05:00
|
|
|
tail++;
|
|
|
|
}
|
2005-10-18 18:35:10 -04:00
|
|
|
|
2006-03-09 18:54:00 -05:00
|
|
|
found=0;
|
|
|
|
len = (int) (tail - tree->current);
|
2005-10-13 03:38:22 -04:00
|
|
|
|
2006-03-09 23:00:38 -05:00
|
|
|
DPRINTF(E_SPAM,L_PARSE,"Len: %d, in_string: %d\n",len,tree->in_string);
|
|
|
|
|
2006-03-09 18:54:00 -05:00
|
|
|
if(!tree->in_string) {
|
|
|
|
/* find it in the token list */
|
|
|
|
pfield=sp_fields[tree->token_list];
|
|
|
|
while(pfield->name) {
|
2006-03-10 18:41:13 -05:00
|
|
|
DPRINTF(E_SPAM,L_PARSE,"Comparing to %s\n",pfield->name);
|
2006-03-09 18:54:00 -05:00
|
|
|
if(strlen(pfield->name) == len) {
|
|
|
|
if(strncasecmp(pfield->name,tree->current,len) == 0) {
|
|
|
|
found=1;
|
|
|
|
break;
|
2005-10-17 00:57:06 -04:00
|
|
|
}
|
2005-10-13 03:38:22 -04:00
|
|
|
}
|
2006-03-09 18:54:00 -05:00
|
|
|
pfield++;
|
2005-10-13 03:38:22 -04:00
|
|
|
}
|
2006-03-09 18:54:00 -05:00
|
|
|
}
|
2005-10-18 18:35:10 -04:00
|
|
|
|
2006-03-09 18:54:00 -05:00
|
|
|
if(found) {
|
|
|
|
tree->token.token_id = pfield->type;
|
|
|
|
} else {
|
|
|
|
tree->token.token_id = T_STRING;
|
|
|
|
}
|
2005-10-13 03:38:22 -04:00
|
|
|
|
2006-03-09 18:54:00 -05:00
|
|
|
if(tree->token.token_id & 0x2000) {
|
2006-03-10 18:41:13 -05:00
|
|
|
token_string=tree->current;
|
|
|
|
if(found) {
|
|
|
|
if(pfield->xlat) {
|
2006-03-13 19:45:33 -05:00
|
|
|
len = (int)strlen(pfield->xlat);
|
2006-03-10 18:41:13 -05:00
|
|
|
token_string = pfield->xlat;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2006-03-09 18:54:00 -05:00
|
|
|
tree->token.data.cvalue = malloc(len + 1);
|
|
|
|
if(!tree->token.data.cvalue) {
|
|
|
|
/* fail on malloc error */
|
|
|
|
DPRINTF(E_FATAL,L_PARSE,"Malloc error.\n");
|
2005-10-17 00:57:06 -04:00
|
|
|
}
|
2006-03-10 18:41:13 -05:00
|
|
|
strncpy(tree->token.data.cvalue,token_string,len);
|
2006-03-09 18:54:00 -05:00
|
|
|
tree->token.data.cvalue[len] = '\x0';
|
|
|
|
}
|
2005-10-18 18:35:10 -04:00
|
|
|
|
2006-03-09 18:54:00 -05:00
|
|
|
if((hint == SP_HINT_NONE) || (hint == SP_HINT_INT)) {
|
|
|
|
/* check for numeric? */
|
2005-12-13 15:42:03 -05:00
|
|
|
if(tree->token.token_id == T_STRING &&
|
|
|
|
(!tree->in_string) &&
|
2006-03-09 18:54:00 -05:00
|
|
|
sp_isnumber(tree->token.data.cvalue)) {
|
2005-10-20 03:33:58 -04:00
|
|
|
/* woops! */
|
2005-10-22 19:05:29 -04:00
|
|
|
numval = atoi(tree->token.data.cvalue);
|
|
|
|
free(tree->token.data.cvalue);
|
|
|
|
tree->token.data.ivalue = numval;
|
|
|
|
tree->token.token_id = T_NUMBER;
|
2005-10-20 03:33:58 -04:00
|
|
|
}
|
2006-03-09 18:54:00 -05:00
|
|
|
}
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2006-03-09 18:54:00 -05:00
|
|
|
if((hint == SP_HINT_NONE) || (hint == SP_HINT_DATE)) {
|
2005-11-06 16:06:07 -05:00
|
|
|
if(tree->token.token_id == T_STRING &&
|
2005-12-13 15:42:03 -05:00
|
|
|
(!tree->in_string) &&
|
2006-03-09 18:54:00 -05:00
|
|
|
(tval=sp_isdate(tree->token.data.cvalue))) {
|
2005-11-06 16:06:07 -05:00
|
|
|
free(tree->token.data.cvalue);
|
|
|
|
tree->token.data.tvalue = tval;
|
|
|
|
tree->token.token_id = T_DATE;
|
|
|
|
}
|
2006-03-09 18:54:00 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
tree->current=tail;
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2006-03-09 18:54:00 -05:00
|
|
|
/* if we are in_string, and we have quoted strings, ensure we
|
|
|
|
* have a quote */
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2006-03-09 23:00:38 -05:00
|
|
|
is_qstr = (strchr(qstr,*tree->current) != NULL);
|
|
|
|
if((!found) && strlen(qstr) && (tree->in_string)) {
|
2006-03-10 18:41:13 -05:00
|
|
|
if(is_qstr) {
|
|
|
|
tree->current++; /* absorb it */
|
|
|
|
} else {
|
|
|
|
DPRINTF(E_INF,L_PARSE,"Missing closing quotes\n");
|
|
|
|
if(tree->token.token_id & 0x2000) {
|
|
|
|
free(tree->token.data.cvalue);
|
|
|
|
}
|
|
|
|
tree->token.token_id = T_ERROR;
|
|
|
|
}
|
2005-10-13 03:38:22 -04:00
|
|
|
}
|
2005-10-18 18:35:10 -04:00
|
|
|
|
2006-03-12 06:30:58 -05:00
|
|
|
/* escape string */
|
|
|
|
if(tree->token.token_id == T_STRING) {
|
|
|
|
src = dst = tree->token.data.cvalue;
|
|
|
|
while(*src) {
|
|
|
|
if(*src != '\\') {
|
|
|
|
*dst++ = *src++;
|
|
|
|
} else {
|
|
|
|
src++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
*dst = '\0';
|
|
|
|
}
|
|
|
|
|
2005-10-22 19:05:29 -04:00
|
|
|
DPRINTF(E_DBG,L_PARSE,"%*s Returning token %04x\n",tree->level," ",
|
|
|
|
tree->token.token_id);
|
2005-10-17 00:57:06 -04:00
|
|
|
if(tree->token.token_id & 0x2000)
|
|
|
|
DPRINTF(E_SPAM,L_PARSE,"String val: %s\n",tree->token.data.cvalue);
|
|
|
|
if(tree->token.token_id & 0x1000)
|
|
|
|
DPRINTF(E_SPAM,L_PARSE,"Int val: %d\n",tree->token.data.ivalue);
|
2005-10-18 18:35:10 -04:00
|
|
|
|
2005-10-13 03:38:22 -04:00
|
|
|
return tree->token.token_id;
|
2005-08-14 23:16:36 -04:00
|
|
|
}
|
2005-08-01 23:17:22 -04:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* set up the initial parse tree
|
2005-10-18 18:35:10 -04:00
|
|
|
*
|
2005-08-01 23:17:22 -04:00
|
|
|
* @returns opaque parsetree struct
|
|
|
|
*/
|
|
|
|
PARSETREE sp_init(void) {
|
|
|
|
PARSETREE ptree;
|
|
|
|
|
|
|
|
ptree = (PARSETREE)malloc(sizeof(PARSESTRUCT));
|
2005-10-18 18:35:10 -04:00
|
|
|
if(!ptree)
|
2005-10-13 03:38:22 -04:00
|
|
|
DPRINTF(E_FATAL,L_PARSE,"Alloc error\n");
|
2005-08-01 23:17:22 -04:00
|
|
|
|
|
|
|
memset(ptree,0,sizeof(PARSESTRUCT));
|
2006-03-07 18:37:42 -05:00
|
|
|
|
|
|
|
/* this sets the default token list as well (0) */
|
|
|
|
|
2005-08-01 23:17:22 -04:00
|
|
|
return ptree;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* parse a term or phrase into a tree.
|
|
|
|
*
|
2005-10-17 00:57:06 -04:00
|
|
|
* I'm not a language expert, so I'd welcome suggestions on the
|
2005-10-16 02:55:42 -04:00
|
|
|
* following production rules:
|
|
|
|
*
|
2005-10-22 19:05:29 -04:00
|
|
|
* phrase -> oexpr T_EOF
|
|
|
|
* oexpr -> aexpr { T_AND aexpr }
|
|
|
|
* aexpr -> expr { T_OR expr }
|
|
|
|
* expr -> T_OPENPAREN oexpr T_CLOSEPAREN | criterion
|
2005-10-17 00:57:06 -04:00
|
|
|
* criterion -> field op value
|
2005-10-16 02:55:42 -04:00
|
|
|
*
|
|
|
|
* field -> T_STRINGFIELD, T_INTFIELD, T_DATEFIELD
|
|
|
|
* op -> T_EQUAL, T_GREATEREQUAL, etc
|
|
|
|
* value -> T_NUMBER, T_STRING, or T_DATE, as appropriate
|
|
|
|
*
|
2005-08-01 23:17:22 -04:00
|
|
|
* @param tree parsetree previously created with sp_init
|
|
|
|
* @param term term or phrase to parse
|
|
|
|
* @returns 1 if successful, 0 if not
|
|
|
|
*/
|
2006-03-10 18:41:13 -05:00
|
|
|
int sp_parse(PARSETREE tree, char *term, int type) {
|
2005-08-01 23:17:22 -04:00
|
|
|
tree->term = strdup(term); /* will be destroyed by parsing */
|
2005-10-06 00:48:04 -04:00
|
|
|
tree->current=tree->term;
|
2005-10-13 03:38:22 -04:00
|
|
|
tree->token.token_id=T_BOF;
|
2006-03-10 18:41:13 -05:00
|
|
|
tree->token_list = type;
|
2005-10-18 18:35:10 -04:00
|
|
|
|
2005-12-13 15:42:03 -05:00
|
|
|
if(tree->tree)
|
2005-10-20 03:33:58 -04:00
|
|
|
sp_free_node(tree->tree);
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2006-03-09 18:54:00 -05:00
|
|
|
sp_scan(tree,SP_HINT_NONE);
|
2005-10-20 03:33:58 -04:00
|
|
|
tree->tree = sp_parse_phrase(tree);
|
|
|
|
|
|
|
|
if(tree->tree) {
|
2005-10-17 00:57:06 -04:00
|
|
|
DPRINTF(E_SPAM,L_PARSE,"Parsed successfully\n");
|
|
|
|
} else {
|
|
|
|
DPRINTF(E_SPAM,L_PARSE,"Parsing error\n");
|
2005-08-01 23:17:22 -04:00
|
|
|
}
|
2005-08-14 23:16:36 -04:00
|
|
|
|
2005-10-20 03:33:58 -04:00
|
|
|
return tree->tree ? 1 : 0;
|
2005-08-01 23:17:22 -04:00
|
|
|
}
|
|
|
|
|
2005-10-16 02:55:42 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* parse for a phrase
|
|
|
|
*
|
|
|
|
* phrase -> aexpr T_EOF
|
|
|
|
*
|
|
|
|
* @param tree tree we are parsing (and building)
|
2005-10-20 03:33:58 -04:00
|
|
|
* @returns new SP_NODE * if successful, NULL otherwise
|
2005-10-16 02:55:42 -04:00
|
|
|
*/
|
2005-10-18 18:35:10 -04:00
|
|
|
|
2005-10-20 03:33:58 -04:00
|
|
|
SP_NODE *sp_parse_phrase(PARSETREE tree) {
|
|
|
|
SP_NODE *expr;
|
2005-10-18 18:35:10 -04:00
|
|
|
|
2005-10-22 19:05:29 -04:00
|
|
|
sp_enter_exit(tree,"sp_parse_phrase",1,NULL);
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-10-22 19:05:29 -04:00
|
|
|
DPRINTF(E_SPAM,L_PARSE,"%*s Entering sp_parse_phrase\n",tree->level," ");
|
|
|
|
tree->level++;
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-10-22 19:05:29 -04:00
|
|
|
expr = sp_parse_oexpr(tree);
|
2005-10-20 03:33:58 -04:00
|
|
|
if((!expr) || (tree->token.token_id != T_EOF)) {
|
|
|
|
sp_free_node(expr);
|
|
|
|
expr = NULL;
|
|
|
|
}
|
2005-10-18 18:35:10 -04:00
|
|
|
|
2005-10-22 19:05:29 -04:00
|
|
|
sp_enter_exit(tree,"sp_parse_phrase",0,expr);
|
2005-10-20 03:33:58 -04:00
|
|
|
return expr;
|
2005-10-16 02:55:42 -04:00
|
|
|
}
|
2005-10-18 18:35:10 -04:00
|
|
|
|
2005-10-16 02:55:42 -04:00
|
|
|
/**
|
|
|
|
* parse for an ANDed expression
|
|
|
|
*
|
2005-10-22 19:05:29 -04:00
|
|
|
* aexpr -> expr { T_AND expr }
|
2005-10-16 02:55:42 -04:00
|
|
|
*
|
|
|
|
* @param tree tree we are building
|
2005-10-20 03:33:58 -04:00
|
|
|
* @returns new SP_NODE pointer if successful, NULL otherwise
|
2005-10-16 02:55:42 -04:00
|
|
|
*/
|
2005-10-20 03:33:58 -04:00
|
|
|
SP_NODE *sp_parse_aexpr(PARSETREE tree) {
|
|
|
|
SP_NODE *expr;
|
|
|
|
SP_NODE *pnew;
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-10-22 19:05:29 -04:00
|
|
|
sp_enter_exit(tree,"sp_parse_aexpr",1,NULL);
|
2005-10-18 18:35:10 -04:00
|
|
|
|
2005-10-20 03:33:58 -04:00
|
|
|
expr = sp_parse_expr(tree);
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2006-03-10 18:41:13 -05:00
|
|
|
while(expr && ((tree->token.token_id == T_AND) ||
|
|
|
|
(tree->token.token_id == T_GREATERAND))) {
|
2005-10-20 03:33:58 -04:00
|
|
|
pnew = (SP_NODE*)malloc(sizeof(SP_NODE));
|
|
|
|
if(!pnew) {
|
|
|
|
DPRINTF(E_FATAL,L_PARSE,"Malloc error\n");
|
|
|
|
}
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-10-20 03:33:58 -04:00
|
|
|
memset(pnew,0x00,sizeof(SP_NODE));
|
|
|
|
pnew->op=T_AND;
|
|
|
|
pnew->op_type = SP_OPTYPE_ANDOR;
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-10-20 03:33:58 -04:00
|
|
|
pnew->left.node = expr;
|
2006-03-09 18:54:00 -05:00
|
|
|
sp_scan(tree,SP_HINT_NONE);
|
2005-10-20 03:33:58 -04:00
|
|
|
pnew->right.node = sp_parse_expr(tree);
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-10-20 03:33:58 -04:00
|
|
|
if(!pnew->right.node) {
|
|
|
|
sp_free_node(pnew);
|
|
|
|
pnew=NULL;
|
|
|
|
}
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-10-20 03:33:58 -04:00
|
|
|
expr=pnew;
|
2005-10-16 02:55:42 -04:00
|
|
|
}
|
|
|
|
|
2005-10-29 17:23:43 -04:00
|
|
|
sp_enter_exit(tree,"sp_parse_aexpr",0,expr);
|
2005-10-20 03:33:58 -04:00
|
|
|
return expr;
|
2005-10-16 02:55:42 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* parse for an ORed expression
|
|
|
|
*
|
2005-10-22 19:05:29 -04:00
|
|
|
* oexpr -> aexpr { T_OR aexpr }
|
2005-10-16 02:55:42 -04:00
|
|
|
*
|
|
|
|
* @param tree tree we are building
|
2005-10-20 03:33:58 -04:00
|
|
|
* @returns new SP_NODE pointer if successful, NULL otherwise
|
2005-10-16 02:55:42 -04:00
|
|
|
*/
|
2005-10-20 03:33:58 -04:00
|
|
|
SP_NODE *sp_parse_oexpr(PARSETREE tree) {
|
|
|
|
SP_NODE *expr;
|
|
|
|
SP_NODE *pnew;
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-10-22 19:05:29 -04:00
|
|
|
sp_enter_exit(tree,"sp_parse_oexpr",1,NULL);
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-10-22 19:05:29 -04:00
|
|
|
expr = sp_parse_aexpr(tree);
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-10-20 03:33:58 -04:00
|
|
|
while(expr && (tree->token.token_id == T_OR)) {
|
|
|
|
pnew = (SP_NODE*)malloc(sizeof(SP_NODE));
|
|
|
|
if(!pnew) {
|
|
|
|
DPRINTF(E_FATAL,L_PARSE,"Malloc error\n");
|
|
|
|
}
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-10-20 03:33:58 -04:00
|
|
|
memset(pnew,0x00,sizeof(SP_NODE));
|
|
|
|
pnew->op=T_OR;
|
|
|
|
pnew->op_type = SP_OPTYPE_ANDOR;
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-10-20 03:33:58 -04:00
|
|
|
pnew->left.node = expr;
|
2006-03-09 18:54:00 -05:00
|
|
|
sp_scan(tree,SP_HINT_NONE);
|
2005-10-22 19:05:29 -04:00
|
|
|
pnew->right.node = sp_parse_aexpr(tree);
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-10-20 03:33:58 -04:00
|
|
|
if(!pnew->right.node) {
|
|
|
|
sp_free_node(pnew);
|
|
|
|
pnew=NULL;
|
|
|
|
}
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-10-20 03:33:58 -04:00
|
|
|
expr=pnew;
|
2005-10-16 02:55:42 -04:00
|
|
|
}
|
|
|
|
|
2005-10-23 03:33:24 -04:00
|
|
|
sp_enter_exit(tree,"sp_parse_oexpr",0,expr);
|
2005-10-20 03:33:58 -04:00
|
|
|
return expr;
|
2005-10-16 02:55:42 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* parse for an expression
|
|
|
|
*
|
2006-03-11 19:29:48 -05:00
|
|
|
* expr -> T_OPENPAREN phrase T_CLOSEPAREN | criteria
|
2005-10-18 18:35:10 -04:00
|
|
|
*
|
2005-10-16 02:55:42 -04:00
|
|
|
* @param tree tree we are building
|
2005-10-20 03:33:58 -04:00
|
|
|
* @returns pointer to new SP_NODE if successful, NULL otherwise
|
2005-10-16 02:55:42 -04:00
|
|
|
*/
|
2005-10-20 03:33:58 -04:00
|
|
|
SP_NODE *sp_parse_expr(PARSETREE tree) {
|
|
|
|
SP_NODE *expr;
|
2005-10-18 18:35:10 -04:00
|
|
|
|
2005-10-22 19:05:29 -04:00
|
|
|
sp_enter_exit(tree,"sp_parse_expr",1,NULL);
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-10-17 00:57:06 -04:00
|
|
|
if(tree->token.token_id == T_OPENPAREN) {
|
2006-03-09 18:54:00 -05:00
|
|
|
sp_scan(tree,SP_HINT_NONE);
|
2005-10-22 19:05:29 -04:00
|
|
|
expr = sp_parse_oexpr(tree);
|
2005-10-20 03:33:58 -04:00
|
|
|
if((expr) && (tree->token.token_id == T_CLOSEPAREN)) {
|
2006-03-09 18:54:00 -05:00
|
|
|
sp_scan(tree,SP_HINT_NONE);
|
2005-10-17 00:57:06 -04:00
|
|
|
} else {
|
|
|
|
/* Error: expecting close paren */
|
2005-10-22 19:05:29 -04:00
|
|
|
sp_set_error(tree,SP_E_CLOSE);
|
2005-10-20 03:33:58 -04:00
|
|
|
sp_free_node(expr);
|
|
|
|
expr=NULL;
|
2005-10-17 00:57:06 -04:00
|
|
|
}
|
|
|
|
} else {
|
2005-10-20 03:33:58 -04:00
|
|
|
expr = sp_parse_criterion(tree);
|
2005-10-17 00:57:06 -04:00
|
|
|
}
|
2005-10-18 18:35:10 -04:00
|
|
|
|
2005-10-22 19:05:29 -04:00
|
|
|
sp_enter_exit(tree,"sp_parse_expr",0,expr);
|
2005-10-20 03:33:58 -04:00
|
|
|
return expr;
|
2005-10-17 00:57:06 -04:00
|
|
|
}
|
2005-10-18 18:35:10 -04:00
|
|
|
|
2005-10-17 00:57:06 -04:00
|
|
|
/**
|
|
|
|
* parse for a criterion
|
|
|
|
*
|
|
|
|
* criterion -> field op value
|
|
|
|
*
|
|
|
|
* @param tree tree we are building
|
2005-10-20 03:33:58 -04:00
|
|
|
* @returns pointer to new SP_NODE if successful, NULL otherwise.
|
2005-10-17 00:57:06 -04:00
|
|
|
*/
|
2005-10-20 03:33:58 -04:00
|
|
|
SP_NODE *sp_parse_criterion(PARSETREE tree) {
|
|
|
|
SP_NODE *expr=NULL;
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-10-22 19:05:29 -04:00
|
|
|
sp_enter_exit(tree,"sp_parse_criterion",1,expr);
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2006-03-10 18:41:13 -05:00
|
|
|
if(tree->token_list == 1) {
|
|
|
|
if(tree->token.token_id != T_EXPRQUOTE) {
|
|
|
|
sp_set_error(tree,SP_E_EXPRQUOTE);
|
|
|
|
return NULL;
|
|
|
|
} else {
|
|
|
|
sp_scan(tree,SP_HINT_NONE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2005-10-17 00:57:06 -04:00
|
|
|
switch(tree->token.token_id) {
|
|
|
|
case T_STRING_FIELD:
|
2005-10-20 03:33:58 -04:00
|
|
|
expr = sp_parse_string_criterion(tree);
|
2005-10-17 00:57:06 -04:00
|
|
|
break;
|
2005-10-18 18:35:10 -04:00
|
|
|
|
2005-10-17 00:57:06 -04:00
|
|
|
case T_INT_FIELD:
|
2005-10-20 03:33:58 -04:00
|
|
|
expr = sp_parse_int_criterion(tree);
|
2005-10-17 00:57:06 -04:00
|
|
|
break;
|
2005-10-18 18:35:10 -04:00
|
|
|
|
2005-10-17 00:57:06 -04:00
|
|
|
case T_DATE_FIELD:
|
2005-10-20 03:33:58 -04:00
|
|
|
expr = sp_parse_date_criterion(tree);
|
2005-10-17 00:57:06 -04:00
|
|
|
break;
|
2005-10-18 18:35:10 -04:00
|
|
|
|
2005-10-17 00:57:06 -04:00
|
|
|
default:
|
|
|
|
/* Error: expecting field */
|
2005-10-22 19:05:29 -04:00
|
|
|
sp_set_error(tree,SP_E_FIELD);
|
2005-10-20 03:33:58 -04:00
|
|
|
expr = NULL;
|
2005-10-17 00:57:06 -04:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2006-03-10 18:41:13 -05:00
|
|
|
if(tree->token_list == 1) {
|
|
|
|
if(tree->token.token_id != T_EXPRQUOTE) {
|
|
|
|
sp_set_error(tree,SP_E_EXPRQUOTE);
|
|
|
|
sp_free_node(expr);
|
|
|
|
return NULL;
|
|
|
|
} else {
|
|
|
|
sp_scan(tree,SP_HINT_NONE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2005-10-22 19:05:29 -04:00
|
|
|
sp_enter_exit(tree,"sp_parse_criterion",0,expr);
|
2005-10-20 03:33:58 -04:00
|
|
|
return expr;
|
2005-10-16 02:55:42 -04:00
|
|
|
}
|
2005-10-17 00:57:06 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* parse for a string criterion
|
|
|
|
*
|
|
|
|
* @param tree tree we are building
|
2005-10-20 03:33:58 -04:00
|
|
|
* @returns pointer to new SP_NODE if successful, NULL otherwise
|
2005-10-17 00:57:06 -04:00
|
|
|
*/
|
2005-10-20 03:33:58 -04:00
|
|
|
SP_NODE *sp_parse_string_criterion(PARSETREE tree) {
|
2005-10-17 00:57:06 -04:00
|
|
|
int result=0;
|
2005-10-20 03:33:58 -04:00
|
|
|
SP_NODE *pnew = NULL;
|
2005-10-17 00:57:06 -04:00
|
|
|
|
2005-10-22 19:05:29 -04:00
|
|
|
sp_enter_exit(tree,"sp_parse_string_criterion",1,NULL);
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-10-20 03:33:58 -04:00
|
|
|
pnew = malloc(sizeof(SP_NODE));
|
|
|
|
if(!pnew) {
|
|
|
|
DPRINTF(E_FATAL,L_PARSE,"Malloc Error\n");
|
|
|
|
}
|
|
|
|
memset(pnew,0x00,sizeof(SP_NODE));
|
|
|
|
pnew->left.field = strdup(tree->token.data.cvalue);
|
|
|
|
|
2006-03-10 18:41:13 -05:00
|
|
|
sp_scan(tree,SP_HINT_NONE); /* scan past the string field we know is there */
|
2005-10-17 00:57:06 -04:00
|
|
|
|
2005-11-06 16:06:07 -05:00
|
|
|
if(tree->token.token_id == T_NOT) {
|
|
|
|
pnew->not_flag=1;
|
2006-03-09 18:54:00 -05:00
|
|
|
sp_scan(tree,SP_HINT_NONE);
|
2005-11-06 16:06:07 -05:00
|
|
|
}
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-10-17 00:57:06 -04:00
|
|
|
switch(tree->token.token_id) {
|
|
|
|
case T_EQUAL:
|
2005-10-23 20:18:08 -04:00
|
|
|
case T_INCLUDES:
|
2005-12-13 15:42:03 -05:00
|
|
|
case T_STARTSWITH:
|
|
|
|
case T_ENDSWITH:
|
2005-10-20 03:33:58 -04:00
|
|
|
pnew->op=tree->token.token_id;
|
|
|
|
pnew->op_type = SP_OPTYPE_STRING;
|
2005-10-17 00:57:06 -04:00
|
|
|
result = 1;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
/* Error: expecting legal string comparison operator */
|
2005-10-22 19:05:29 -04:00
|
|
|
sp_set_error(tree,SP_E_STRCMP);
|
2005-10-17 00:57:06 -04:00
|
|
|
break;
|
|
|
|
}
|
2005-10-18 18:35:10 -04:00
|
|
|
|
2005-10-17 00:57:06 -04:00
|
|
|
if(result) {
|
2006-03-10 18:41:13 -05:00
|
|
|
sp_scan(tree,SP_HINT_STRING);
|
2006-03-09 18:54:00 -05:00
|
|
|
/* should be sitting on string literal */
|
|
|
|
if(tree->token.token_id == T_STRING) {
|
|
|
|
result = 1;
|
|
|
|
pnew->right.cvalue=strdup(tree->token.data.cvalue);
|
2006-03-10 18:41:13 -05:00
|
|
|
if(tree->token_list == 1) {
|
|
|
|
if(pnew->right.cvalue[0]=='*') {
|
|
|
|
pnew->op = T_ENDSWITH;
|
|
|
|
memcpy(pnew->right.cvalue,&pnew->right.cvalue[1],
|
|
|
|
(int)strlen(pnew->right.cvalue)); /* with zt */
|
|
|
|
}
|
|
|
|
if(pnew->right.cvalue[strlen(pnew->right.cvalue)-1] == '*') {
|
|
|
|
pnew->op = (pnew->op==T_ENDSWITH)?T_INCLUDES:T_STARTSWITH;
|
|
|
|
pnew->right.cvalue[strlen(pnew->right.cvalue)-1] = '\0';
|
|
|
|
}
|
|
|
|
}
|
2006-03-09 18:54:00 -05:00
|
|
|
sp_scan(tree,SP_HINT_NONE);
|
2005-10-17 00:57:06 -04:00
|
|
|
} else {
|
2005-10-22 19:05:29 -04:00
|
|
|
sp_set_error(tree,SP_E_OPENQUOTE);
|
2006-03-09 18:54:00 -05:00
|
|
|
DPRINTF(E_SPAM,L_PARSE,"Expecting string, got %04X\n",
|
|
|
|
tree->token.token_id);
|
|
|
|
result = 0;
|
2005-10-17 00:57:06 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2005-10-20 03:33:58 -04:00
|
|
|
|
|
|
|
if(!result) {
|
|
|
|
sp_free_node(pnew);
|
2005-12-13 15:42:03 -05:00
|
|
|
pnew=NULL;
|
2005-10-20 03:33:58 -04:00
|
|
|
}
|
|
|
|
|
2005-10-22 19:05:29 -04:00
|
|
|
sp_enter_exit(tree,"sp_parse_string_criterion",0,pnew);
|
2005-10-20 03:33:58 -04:00
|
|
|
return pnew;
|
2005-10-17 00:57:06 -04:00
|
|
|
}
|
2005-10-18 18:35:10 -04:00
|
|
|
|
2005-10-17 00:57:06 -04:00
|
|
|
/**
|
|
|
|
* parse for an int criterion
|
|
|
|
*
|
|
|
|
* @param tree tree we are building
|
2005-10-20 03:33:58 -04:00
|
|
|
* @returns address of new SP_NODE if successful, NULL otherwise
|
2005-10-17 00:57:06 -04:00
|
|
|
*/
|
2005-10-20 03:33:58 -04:00
|
|
|
SP_NODE *sp_parse_int_criterion(PARSETREE tree) {
|
2005-10-17 00:57:06 -04:00
|
|
|
int result=0;
|
2005-10-20 03:33:58 -04:00
|
|
|
SP_NODE *pnew = NULL;
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-10-22 19:05:29 -04:00
|
|
|
sp_enter_exit(tree,"sp_parse_int_criterion",1,pnew);
|
2005-10-20 03:33:58 -04:00
|
|
|
pnew = malloc(sizeof(SP_NODE));
|
|
|
|
if(!pnew) {
|
|
|
|
DPRINTF(E_FATAL,L_PARSE,"Malloc Error\n");
|
|
|
|
}
|
|
|
|
memset(pnew,0x00,sizeof(SP_NODE));
|
|
|
|
pnew->left.field = strdup(tree->token.data.cvalue);
|
2005-10-17 00:57:06 -04:00
|
|
|
|
2006-03-09 18:54:00 -05:00
|
|
|
sp_scan(tree,SP_HINT_NONE); /* scan past the int field we know is there */
|
2005-10-17 00:57:06 -04:00
|
|
|
|
2005-11-06 16:06:07 -05:00
|
|
|
if(tree->token.token_id == T_NOT) {
|
|
|
|
pnew->not_flag=1;
|
2006-03-09 18:54:00 -05:00
|
|
|
sp_scan(tree,SP_HINT_NONE);
|
2005-11-06 16:06:07 -05:00
|
|
|
}
|
|
|
|
|
2005-10-17 00:57:06 -04:00
|
|
|
switch(tree->token.token_id) {
|
|
|
|
case T_LESSEQUAL:
|
|
|
|
case T_LESS:
|
|
|
|
case T_GREATEREQUAL:
|
|
|
|
case T_GREATER:
|
|
|
|
case T_EQUAL:
|
|
|
|
result = 1;
|
2005-10-20 03:33:58 -04:00
|
|
|
pnew->op=tree->token.token_id;
|
|
|
|
pnew->op_type = SP_OPTYPE_INT;
|
2005-10-17 00:57:06 -04:00
|
|
|
break;
|
2006-03-10 18:41:13 -05:00
|
|
|
case T_GREATERAND:
|
|
|
|
result = 1;
|
|
|
|
pnew->op = T_GREATER;
|
|
|
|
pnew->op_type = SP_OPTYPE_INT;
|
|
|
|
break;
|
2005-10-17 00:57:06 -04:00
|
|
|
default:
|
2005-10-22 19:05:29 -04:00
|
|
|
/* Error: expecting legal int comparison operator */
|
|
|
|
sp_set_error(tree,SP_E_INTCMP);
|
|
|
|
DPRINTF(E_LOG,L_PARSE,"Expecting int comparison op, got %04X\n",
|
2005-10-17 00:57:06 -04:00
|
|
|
tree->token.token_id);
|
|
|
|
break;
|
|
|
|
}
|
2005-10-18 18:35:10 -04:00
|
|
|
|
2005-10-17 00:57:06 -04:00
|
|
|
if(result) {
|
2006-03-10 18:41:13 -05:00
|
|
|
sp_scan(tree,SP_HINT_INT);
|
2005-10-17 00:57:06 -04:00
|
|
|
/* should be sitting on a literal string */
|
|
|
|
if(tree->token.token_id == T_NUMBER) {
|
|
|
|
result = 1;
|
2005-10-20 03:33:58 -04:00
|
|
|
pnew->right.ivalue=tree->token.data.ivalue;
|
2006-03-09 18:54:00 -05:00
|
|
|
sp_scan(tree,SP_HINT_NONE);
|
2005-10-17 00:57:06 -04:00
|
|
|
} else {
|
2005-10-20 03:33:58 -04:00
|
|
|
/* Error: Expecting number */
|
2005-10-22 19:05:29 -04:00
|
|
|
sp_set_error(tree,SP_E_NUMBER);
|
2005-10-20 03:33:58 -04:00
|
|
|
DPRINTF(E_LOG,L_PARSE,"Expecting number, got %04X\n",
|
2005-10-17 00:57:06 -04:00
|
|
|
tree->token.token_id);
|
|
|
|
result = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2005-10-20 03:33:58 -04:00
|
|
|
if(!result) {
|
|
|
|
sp_free_node(pnew);
|
|
|
|
pnew=NULL;
|
|
|
|
}
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-10-22 19:05:29 -04:00
|
|
|
sp_enter_exit(tree,"sp_parse_int_criterion",0,pnew);
|
2005-10-18 18:35:10 -04:00
|
|
|
|
2005-10-20 03:33:58 -04:00
|
|
|
return pnew;
|
2005-10-17 00:57:06 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* parse for a date criterion
|
|
|
|
*
|
|
|
|
* @param tree tree we are building
|
2005-10-20 03:33:58 -04:00
|
|
|
* @returns pointer to new SP_NODE if successful, NULL otherwise
|
2005-10-17 00:57:06 -04:00
|
|
|
*/
|
2005-10-29 17:23:43 -04:00
|
|
|
SP_NODE *sp_parse_date_criterion(PARSETREE tree) {
|
2005-10-20 03:33:58 -04:00
|
|
|
SP_NODE *pnew=NULL;
|
2005-10-29 17:23:43 -04:00
|
|
|
int result=0;
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-10-22 19:05:29 -04:00
|
|
|
sp_enter_exit(tree,"sp_parse_date_criterion",1,pnew);
|
2005-10-29 17:23:43 -04:00
|
|
|
pnew = malloc(sizeof(SP_NODE));
|
|
|
|
if(!pnew) {
|
|
|
|
DPRINTF(E_FATAL,L_PARSE,"Malloc Error\n");
|
|
|
|
}
|
|
|
|
memset(pnew,0x00,sizeof(SP_NODE));
|
|
|
|
pnew->left.field = strdup(tree->token.data.cvalue);
|
|
|
|
|
2006-03-09 18:54:00 -05:00
|
|
|
sp_scan(tree,SP_HINT_NONE); /* scan past the date field we know is there */
|
2005-10-29 17:23:43 -04:00
|
|
|
|
2005-11-06 16:06:07 -05:00
|
|
|
if(tree->token.token_id == T_NOT) {
|
|
|
|
pnew->not_flag=1;
|
2006-03-09 18:54:00 -05:00
|
|
|
sp_scan(tree,SP_HINT_NONE);
|
2005-11-06 16:06:07 -05:00
|
|
|
}
|
|
|
|
|
2005-10-29 17:23:43 -04:00
|
|
|
switch(tree->token.token_id) {
|
|
|
|
case T_LESSEQUAL:
|
|
|
|
case T_LESS:
|
|
|
|
case T_GREATEREQUAL:
|
|
|
|
case T_GREATER:
|
|
|
|
result = 1;
|
|
|
|
pnew->op=tree->token.token_id;
|
|
|
|
pnew->op_type = SP_OPTYPE_DATE;
|
|
|
|
break;
|
2006-03-10 18:41:13 -05:00
|
|
|
case T_GREATERAND:
|
|
|
|
result = 1;
|
|
|
|
pnew->op=T_GREATER;
|
|
|
|
pnew->op_type = SP_OPTYPE_DATE;
|
|
|
|
break;
|
2005-10-29 17:23:43 -04:00
|
|
|
case T_BEFORE:
|
|
|
|
result = 1;
|
|
|
|
pnew->op_type = SP_OPTYPE_DATE;
|
|
|
|
pnew->op=T_LESS;
|
|
|
|
break;
|
|
|
|
case T_AFTER:
|
|
|
|
result = 1;
|
|
|
|
pnew->op_type = SP_OPTYPE_DATE;
|
|
|
|
pnew->op=T_GREATER;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
/* Error: expecting legal int comparison operator */
|
|
|
|
sp_set_error(tree,SP_E_DATECMP);
|
|
|
|
DPRINTF(E_LOG,L_PARSE,"Expecting int comparison op, got %04X\n",
|
|
|
|
tree->token.token_id);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(result) {
|
2006-03-19 01:43:32 -05:00
|
|
|
sp_scan(tree,SP_HINT_NONE);
|
2005-10-29 17:23:43 -04:00
|
|
|
/* should be sitting on a date */
|
|
|
|
if((pnew->right.tvalue = sp_parse_date(tree))) {
|
|
|
|
result=1;
|
|
|
|
} else {
|
|
|
|
sp_set_error(tree,SP_E_DATE);
|
|
|
|
result=0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!result) {
|
|
|
|
sp_free_node(pnew);
|
|
|
|
pnew=NULL;
|
|
|
|
}
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-10-17 00:57:06 -04:00
|
|
|
|
2005-10-22 19:05:29 -04:00
|
|
|
sp_enter_exit(tree,"sp_parse_date_criterion",0,pnew);
|
2005-10-18 18:35:10 -04:00
|
|
|
|
2005-10-20 03:33:58 -04:00
|
|
|
return pnew;
|
2005-10-17 00:57:06 -04:00
|
|
|
}
|
2005-10-18 18:35:10 -04:00
|
|
|
|
2005-10-29 17:23:43 -04:00
|
|
|
/**
|
|
|
|
* parse a date value for a date field comparison
|
|
|
|
*
|
2005-12-13 15:42:03 -05:00
|
|
|
* date -> T_TODAY | T_DATE |
|
2005-10-29 17:23:43 -04:00
|
|
|
* date_interval T_BEFORE date | date_interval T_AFTER date
|
|
|
|
* date_interval -> T_NUMBER ( T_DAY | T_WEEK | T_MONTH | T_YEAR)
|
|
|
|
* (T_BEFORE | T_AFTER) date
|
|
|
|
*
|
|
|
|
* @param tree tree we are building/parsing
|
|
|
|
* @returns time_t of date, or 0 if failure
|
|
|
|
*/
|
|
|
|
time_t sp_parse_date(PARSETREE tree) {
|
|
|
|
time_t result=0;
|
|
|
|
time_t interval;
|
|
|
|
int before;
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-10-29 17:23:43 -04:00
|
|
|
sp_enter_exit(tree,"sp_parse_date",1,(void*)result);
|
|
|
|
|
|
|
|
switch(tree->token.token_id) {
|
|
|
|
case T_TODAY:
|
|
|
|
result = time(NULL);
|
2006-03-09 18:54:00 -05:00
|
|
|
sp_scan(tree,SP_HINT_NONE);
|
2005-10-29 17:23:43 -04:00
|
|
|
break;
|
|
|
|
case T_DATE:
|
|
|
|
result = tree->token.data.tvalue;
|
2006-03-09 18:54:00 -05:00
|
|
|
sp_scan(tree,SP_HINT_NONE);
|
2005-10-29 17:23:43 -04:00
|
|
|
break;
|
|
|
|
}
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-10-29 17:23:43 -04:00
|
|
|
if(!result) {
|
|
|
|
/* must be an interval */
|
|
|
|
interval = sp_parse_date_interval(tree);
|
|
|
|
if(!interval) {
|
|
|
|
result = 0;
|
|
|
|
} else if((tree->token.token_id != T_BEFORE) &&
|
|
|
|
(tree->token.token_id != T_AFTER)) {
|
|
|
|
sp_set_error(tree,SP_E_BEFOREAFTER);
|
|
|
|
result=0;
|
|
|
|
} else {
|
|
|
|
before = 1;
|
|
|
|
if(tree->token.token_id == T_AFTER)
|
|
|
|
before = 0;
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2006-03-09 18:54:00 -05:00
|
|
|
sp_scan(tree,SP_HINT_NONE);
|
2005-10-29 17:23:43 -04:00
|
|
|
result = sp_parse_date(tree);
|
|
|
|
if(result) {
|
|
|
|
if(before) {
|
|
|
|
result -= interval;
|
|
|
|
} else {
|
|
|
|
result += interval;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-10-29 17:23:43 -04:00
|
|
|
sp_enter_exit(tree,"sp_parse_date_criterion",0,(void*)result);
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* parse a date inteval
|
|
|
|
*
|
|
|
|
* date_interval -> T_NUMBER (T_DAY | T_WEEK | T_MONTH | T_YEAR)
|
|
|
|
* @param tree tree we are parsing/building
|
|
|
|
* @returns time_t seconds in interval, or 0 if error
|
|
|
|
*/
|
|
|
|
time_t sp_parse_date_interval(PARSETREE tree) {
|
|
|
|
time_t result=0;
|
|
|
|
int count;
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-10-29 17:23:43 -04:00
|
|
|
sp_enter_exit(tree,"sp_parse_date_interval",1,(void*)result);
|
|
|
|
|
|
|
|
if(tree->token.token_id != T_NUMBER) {
|
|
|
|
result=0;
|
2005-12-13 15:42:03 -05:00
|
|
|
sp_set_error(tree,SP_E_NUMBER);
|
2005-10-29 17:23:43 -04:00
|
|
|
} else {
|
|
|
|
count = tree->token.data.ivalue;
|
2006-03-09 18:54:00 -05:00
|
|
|
sp_scan(tree,SP_HINT_NONE);
|
2005-10-29 17:23:43 -04:00
|
|
|
switch(tree->token.token_id) {
|
|
|
|
case T_DAY:
|
|
|
|
result = count * 3600 * 24;
|
2006-03-09 18:54:00 -05:00
|
|
|
sp_scan(tree,SP_HINT_NONE);
|
2005-10-29 17:23:43 -04:00
|
|
|
break;
|
|
|
|
case T_WEEK:
|
|
|
|
result = count * 3600 * 24 * 7;
|
2006-03-09 18:54:00 -05:00
|
|
|
sp_scan(tree,SP_HINT_NONE);
|
2005-10-29 17:23:43 -04:00
|
|
|
break;
|
|
|
|
case T_MONTH:
|
|
|
|
result = count * 3600 * 24 * 30;
|
2006-03-09 18:54:00 -05:00
|
|
|
sp_scan(tree,SP_HINT_NONE);
|
2005-10-29 17:23:43 -04:00
|
|
|
break;
|
|
|
|
case T_YEAR:
|
|
|
|
result = count * 3600 * 24 * 365;
|
2006-03-09 18:54:00 -05:00
|
|
|
sp_scan(tree,SP_HINT_NONE);
|
2005-10-29 17:23:43 -04:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
result=0;
|
|
|
|
sp_set_error(tree, SP_E_TIMEINTERVAL);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-10-29 17:23:43 -04:00
|
|
|
sp_enter_exit(tree,"sp_parse_date_interval",0,(void*)result);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-10-29 17:23:43 -04:00
|
|
|
|
2005-10-20 03:33:58 -04:00
|
|
|
/**
|
|
|
|
* free a node, and all left/right subnodes
|
|
|
|
*
|
|
|
|
* @param node node to free
|
|
|
|
*/
|
|
|
|
void sp_free_node(SP_NODE *node) {
|
|
|
|
if(!node)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if(node->op_type == SP_OPTYPE_ANDOR) {
|
|
|
|
if(node->left.node) {
|
|
|
|
sp_free_node(node->left.node);
|
|
|
|
node->left.node = NULL;
|
2005-12-13 15:42:03 -05:00
|
|
|
}
|
|
|
|
|
2005-10-20 03:33:58 -04:00
|
|
|
if(node->right.node) {
|
|
|
|
sp_free_node(node->right.node);
|
|
|
|
node->right.node = NULL;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if(node->left.field) {
|
|
|
|
free(node->left.field);
|
|
|
|
node->left.field = NULL;
|
|
|
|
}
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-10-20 03:33:58 -04:00
|
|
|
if(node->op_type == SP_OPTYPE_STRING) {
|
|
|
|
if(node->right.cvalue) {
|
|
|
|
free(node->right.cvalue);
|
|
|
|
node->right.cvalue = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
free(node);
|
|
|
|
}
|
|
|
|
|
2005-10-18 18:35:10 -04:00
|
|
|
|
2005-08-01 23:17:22 -04:00
|
|
|
/**
|
|
|
|
* dispose of an initialized tree
|
|
|
|
*
|
|
|
|
* @param tree tree to dispose
|
|
|
|
* @returns 1
|
|
|
|
*/
|
|
|
|
int sp_dispose(PARSETREE tree) {
|
|
|
|
if(tree->term)
|
2005-10-13 03:38:22 -04:00
|
|
|
free(tree->term);
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-10-20 03:33:58 -04:00
|
|
|
if(tree->token.token_id & 0x2000)
|
|
|
|
free(tree->token.data.cvalue);
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-10-22 19:05:29 -04:00
|
|
|
if(tree->error)
|
|
|
|
free(tree->error);
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-08-01 23:17:22 -04:00
|
|
|
free(tree);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2006-03-12 06:30:58 -05:00
|
|
|
#ifdef HAVE_SQL
|
2005-10-21 03:48:07 -04:00
|
|
|
/**
|
2005-12-13 15:42:03 -05:00
|
|
|
* calculate the size required to render the tree as a
|
2005-10-21 03:48:07 -04:00
|
|
|
* sql query.
|
|
|
|
*
|
|
|
|
* @parameter node node/tree to calculate
|
|
|
|
* @returns size in bytes of sql "where" clause
|
|
|
|
*/
|
|
|
|
int sp_node_size(SP_NODE *node) {
|
|
|
|
int size;
|
2006-03-12 06:30:58 -05:00
|
|
|
int string_size;
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-10-21 03:48:07 -04:00
|
|
|
if(node->op_type == SP_OPTYPE_ANDOR) {
|
|
|
|
size = sp_node_size(node->left.node);
|
|
|
|
size += sp_node_size(node->right.node);
|
|
|
|
size += 7; /* (xxx AND xxx) */
|
|
|
|
} else {
|
|
|
|
size = 4; /* parens, plus spaces around op */
|
2006-02-26 03:46:24 -05:00
|
|
|
size += (int) strlen(node->left.field);
|
2005-10-21 03:48:07 -04:00
|
|
|
if((node->op & 0x0FFF) > T_LAST) {
|
|
|
|
DPRINTF(E_FATAL,L_PARSE,"Can't determine node size: op %04x\n",
|
|
|
|
node->op);
|
|
|
|
} else {
|
2006-02-26 03:46:24 -05:00
|
|
|
size += (int) strlen(sp_token_descr[node->op & 0x0FFF]);
|
2005-10-21 03:48:07 -04:00
|
|
|
}
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-10-23 20:18:08 -04:00
|
|
|
if(node->op_type == SP_OPTYPE_STRING) {
|
2006-03-12 06:30:58 -05:00
|
|
|
string_size = 0;
|
2006-03-24 17:29:24 -05:00
|
|
|
if((node->right.cvalue) && (strlen(node->right.cvalue)))
|
|
|
|
db_sql_escape(NULL,&string_size,"%q",node->right.cvalue);
|
|
|
|
|
2006-03-12 06:30:58 -05:00
|
|
|
size += (2 + string_size);
|
2005-10-23 20:18:08 -04:00
|
|
|
if(node->op == T_INCLUDES) {
|
|
|
|
size += 2; /* extra %'s */
|
|
|
|
}
|
2005-12-13 15:42:03 -05:00
|
|
|
if((node->op == T_STARTSWITH)||(node->op == T_ENDSWITH)) {
|
|
|
|
size += 1;
|
|
|
|
}
|
2005-10-23 20:18:08 -04:00
|
|
|
}
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-10-29 17:23:43 -04:00
|
|
|
if((node->op_type == SP_OPTYPE_INT) || (node->op_type == SP_OPTYPE_DATE)) {
|
|
|
|
size += 40; /* what *is* the max size of int64? */
|
|
|
|
}
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-11-06 16:06:07 -05:00
|
|
|
if(node->not_flag) {
|
|
|
|
size += 5; /* " not " */
|
|
|
|
}
|
2005-10-21 03:48:07 -04:00
|
|
|
}
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-10-21 03:48:07 -04:00
|
|
|
return size;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* serialize node into pre-allocated string
|
|
|
|
*
|
|
|
|
* @param node node to serialize
|
|
|
|
* @param string string to generate
|
|
|
|
*/
|
|
|
|
void sp_serialize_sql(SP_NODE *node, char *string) {
|
|
|
|
char buffer[40];
|
2006-03-12 06:30:58 -05:00
|
|
|
int size;
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-10-21 03:48:07 -04:00
|
|
|
if(node->op_type == SP_OPTYPE_ANDOR) {
|
|
|
|
strcat(string,"(");
|
|
|
|
sp_serialize_sql(node->left.node,string);
|
|
|
|
if(node->op == T_AND) strcat(string," and ");
|
|
|
|
if(node->op == T_OR) strcat(string," or ");
|
|
|
|
sp_serialize_sql(node->right.node,string);
|
|
|
|
strcat(string,")");
|
|
|
|
} else {
|
|
|
|
strcat(string,"(");
|
2005-11-06 16:06:07 -05:00
|
|
|
if(node->not_flag) {
|
|
|
|
strcat(string,"not ");
|
|
|
|
}
|
2005-12-13 15:42:03 -05:00
|
|
|
strcat(string,node->left.field);
|
|
|
|
strcat(string," ");
|
2005-10-21 03:48:07 -04:00
|
|
|
strcat(string,sp_token_descr[node->op & 0x0FFF]);
|
|
|
|
strcat(string," ");
|
|
|
|
if(node->op_type == SP_OPTYPE_STRING) {
|
|
|
|
strcat(string,"'");
|
2005-12-13 15:42:03 -05:00
|
|
|
if((node->op == T_INCLUDES) || (node->op == T_ENDSWITH))
|
2005-10-23 20:18:08 -04:00
|
|
|
strcat(string,"%");
|
2006-03-12 06:30:58 -05:00
|
|
|
size = 0;
|
2006-03-24 17:29:24 -05:00
|
|
|
if((node->right.cvalue) && (strlen(node->right.cvalue)))
|
|
|
|
db_sql_escape(NULL,&size,"%q",node->right.cvalue);
|
|
|
|
|
|
|
|
/* we don't have a way to verify we have that much
|
2006-03-12 06:30:58 -05:00
|
|
|
* room, but we must... we allocated it earlier.
|
|
|
|
*/
|
2006-03-24 17:29:24 -05:00
|
|
|
if((node->right.cvalue) && (strlen(node->right.cvalue)))
|
|
|
|
db_sql_escape(&string[strlen(string)],&size,"%q",
|
|
|
|
node->right.cvalue);
|
|
|
|
|
2006-03-12 06:30:58 -05:00
|
|
|
// strcat(string,node->right.cvalue);
|
2005-12-13 15:42:03 -05:00
|
|
|
if((node->op == T_INCLUDES) || (node->op == T_STARTSWITH))
|
2005-10-23 20:18:08 -04:00
|
|
|
strcat(string,"%");
|
2005-10-21 03:48:07 -04:00
|
|
|
strcat(string,"'");
|
|
|
|
}
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-10-21 03:48:07 -04:00
|
|
|
if(node->op_type == SP_OPTYPE_INT) {
|
|
|
|
sprintf(buffer,"%d",node->right.ivalue);
|
|
|
|
strcat(string,buffer);
|
|
|
|
}
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-10-29 17:23:43 -04:00
|
|
|
if(node->op_type == SP_OPTYPE_DATE) {
|
|
|
|
sprintf(buffer,"%d",(int)node->right.tvalue);
|
|
|
|
strcat(string,buffer);
|
|
|
|
}
|
2005-10-21 03:48:07 -04:00
|
|
|
strcat(string,")");
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-10-21 03:48:07 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* generate sql "where" clause
|
|
|
|
*
|
|
|
|
* @param node node/tree to calculate
|
|
|
|
* @returns sql string. Must be freed by caller
|
|
|
|
*/
|
|
|
|
char *sp_sql_clause(PARSETREE tree) {
|
|
|
|
int size;
|
|
|
|
char *sql;
|
|
|
|
|
2006-03-12 06:30:58 -05:00
|
|
|
DPRINTF(E_DBG,L_PARSE,"Fetching sql statement size\n");
|
2005-10-21 03:48:07 -04:00
|
|
|
size = sp_node_size(tree->tree);
|
2006-03-12 06:30:58 -05:00
|
|
|
DPRINTF(E_DBG,L_PARSE,"Size: %d\n",size);
|
|
|
|
|
2005-10-21 03:48:07 -04:00
|
|
|
sql = (char*)malloc(size+1);
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-10-21 03:48:07 -04:00
|
|
|
memset(sql,0x00,size+1);
|
|
|
|
sp_serialize_sql(tree->tree,sql);
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2006-03-12 06:30:58 -05:00
|
|
|
DPRINTF(E_DBG,L_PARSE,"Serialized to : %s\n",sql);
|
|
|
|
|
2005-10-21 03:48:07 -04:00
|
|
|
return sql;
|
|
|
|
}
|
2006-03-12 06:30:58 -05:00
|
|
|
#endif /* HAVE_SQL */
|
2005-10-21 03:48:07 -04:00
|
|
|
|
2005-08-01 23:17:22 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* if there was an error in a previous action (parsing?)
|
2005-10-18 18:35:10 -04:00
|
|
|
* then return that error to the client. This does not
|
2005-08-01 23:17:22 -04:00
|
|
|
* clear the error condition -- multiple calls to sp_geterror
|
2005-10-20 03:33:58 -04:00
|
|
|
* will return the same value. Also, if you want to keep an error,
|
|
|
|
* you must strdup it before it disposing the parse tree...
|
2005-08-01 23:17:22 -04:00
|
|
|
*
|
|
|
|
* memory handling is done on the smart-parser side.
|
|
|
|
*
|
|
|
|
* @param tree tree that generated the last error
|
|
|
|
* @returns text of the last error
|
|
|
|
*/
|
2005-10-22 19:05:29 -04:00
|
|
|
char *sp_get_error(PARSETREE tree) {
|
|
|
|
return tree->error;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* set the parse tree error for retrieval above
|
|
|
|
*
|
|
|
|
* @param tree tree we are setting error for
|
|
|
|
* @param error error code
|
|
|
|
*/
|
|
|
|
void sp_set_error(PARSETREE tree, int error) {
|
|
|
|
int len;
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-10-22 19:05:29 -04:00
|
|
|
if(tree->error)
|
|
|
|
free(tree->error);
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2006-02-26 03:46:24 -05:00
|
|
|
len = 10 + (tree->token_pos / 10) + 1 + (int) strlen(sp_errorstrings[error]) + 1;
|
2005-10-22 19:05:29 -04:00
|
|
|
tree->error = (char*)malloc(len);
|
|
|
|
if(!tree->error) {
|
|
|
|
DPRINTF(E_FATAL,L_PARSE,"Malloc error");
|
|
|
|
return;
|
|
|
|
}
|
2005-12-13 15:42:03 -05:00
|
|
|
|
2005-10-22 19:05:29 -04:00
|
|
|
sprintf(tree->error,"Offset %d: %s",tree->token_pos + 1,sp_errorstrings[error]);
|
|
|
|
return;
|
2005-08-01 23:17:22 -04:00
|
|
|
}
|
|
|
|
|