diff --git a/.gitignore b/.gitignore index 06f0b4d8c..faaf9ad71 100644 --- a/.gitignore +++ b/.gitignore @@ -65,3 +65,4 @@ perf.data.old .idea/ .vscode/ centrallix-os/tmp/* +centrallix-os/datasets/ diff --git a/centrallix-doc/Widgets/onepage.html b/centrallix-doc/Widgets/onepage.html index 9a449b6a5..d2e733884 100644 --- a/centrallix-doc/Widgets/onepage.html +++ b/centrallix-doc/Widgets/onepage.html @@ -5976,7 +5976,7 @@

Child Properties:

type string -The type of the column: "text", "check", or "image". "text" is a normal column, and displays the textual value of the data element. "check" displays a checkmark if the data is non-zero (integers) or for strings if the value is non-empty and not "N" or "No". "image" displays the image referred to by the pathname contained in the data value. +The type of the column: "text", "check", "image", or "progress". "text" is a normal column, and displays the textual value of the data element. "check" displays a checkmark if the data is non-zero (integers) or for strings if the value is non-empty and not "N" or "No". "image" displays the image referred to by the pathname contained in the data value. "progress" displays a progress bar, with additional fields such as bar_color, bar_textcollor, and bar_padding. width diff --git a/centrallix-doc/Widgets/table.html b/centrallix-doc/Widgets/table.html index d0538544b..a3361828d 100644 --- a/centrallix-doc/Widgets/table.html +++ b/centrallix-doc/Widgets/table.html @@ -329,7 +329,7 @@

Child Properties:

type string -The type of the column: "text", "check", or "image". "text" is a normal column, and displays the textual value of the data element. "check" displays a checkmark if the data is non-zero (integers) or for strings if the value is non-empty and not "N" or "No". "image" displays the image referred to by the pathname contained in the data value. +The type of the column: "text", "check", "image", or "progress". "text" is a normal column, and displays the textual value of the data element. "check" displays a checkmark if the data is non-zero (integers) or for strings if the value is non-empty and not "N" or "No". "image" displays the image referred to by the pathname contained in the data value. "progress" displays a progress bar, with additional fields such as bar_color, bar_textcollor, and bar_padding. width diff --git a/centrallix-doc/Widgets/widgets.xml b/centrallix-doc/Widgets/widgets.xml index b6b50afde..1eca6712b 100644 --- a/centrallix-doc/Widgets/widgets.xml +++ b/centrallix-doc/Widgets/widgets.xml @@ -629,7 +629,7 @@ checkbox_test "widget/page" Opens the window. If the parameter IsModal is set to 1, then the window becomes modal (only the window's contents are accessible to the user until the window is closed). If the parameter NoClose is set to 1, then the close button in the upper right corner of the window becomes inactive and the window will only close via the Close, SetVisibility, and ToggleVisibility actions. - Makes the window relocate to a side using a triangle (pop over). + Creates a triangular pointer on the edge of the window to point at a given (X,Y) coordinate. Opens a window like a pop-up. @@ -2323,7 +2323,7 @@ MyButton "widget/imagebutton" The color (named or #numeric) of the text in the label when the user hovers the mouse over the label. - (e.g. bold). + 'bold' for bold text, 'italic' for italic text. The text that the label is to display. @@ -2575,7 +2575,7 @@ myMenu "widget/menu" ** This feature currently disabled in Centrallix 0.9.1 ** Default "no". If set to "yes", the objectsource will ask the server to send it updates on any changes that occur on the server side (i.e., if the changes were made by another objectsource or by another user, they would be automatically refreshed into this objectsource in near real-time). - The time between the data refreshing, if set to 0 it does not automatically refresh. + The time in milliseconds between the data refreshing, if set to 0 it does not automatically refresh. Represents the number of records to store in its replica. This value should be larger than the maximum number of records that will be displayed at any one time. At times, Centrallix may increase the number of records cached on the client beyond this number. @@ -2818,13 +2818,17 @@ osrc1 "widget/osrc" - Sends an alert widget. + Sends an alert widget. Set the 'Message' to specify a text string that should appear in the alert. Closes the page. Starts a new app in a new window. + Logs data to the console (using console.log()), for testing and debugging. Set the 'Message' to specify a text string that should appear in the log. Might be useful for logging Easter Egg #8. + Loads the page. + + Reloads the page in the user's browser. Note: This event forces a reload, even if the original content could be loaded without one. @@ -3087,7 +3091,9 @@ my_cmp "widget/component-decl" A color, RGB or named, for the panel background. If neither bgcolor nor background transparent. Height, in pixels, of the panel. - + + The maximum height (in pixels) of space allowed between radio buttons on the panel (default: 10px). + An image to be used for the rectangular border drawn around the radio buttons. The color, RGB or named, of the text within the panel. Default: "black". @@ -3390,21 +3396,31 @@ $Version=2$ - Scrolls to a specific location determined by the scroll bar. + Scrolls to a specific location determined by the scroll bar. Specify the 'Percent' attribute to indicate how far to scroll in decimal representation (so 1.00 is 100%, aka. the bottom of the page). Specify 'Offset' how many pixels the content should be offset from the top (specify 100 to scroll the first 100 px of content off the top of the scroll pane). Specify 'RangeStart' and 'RangeEnd' to scroll to within the pixel range (using the same units as offset). Keep in mind that this action will trigger a scroll event to occur. + + This event occurs any time the user scrolls the scroll pane. This includes scrolling by clicking the scroll buttons, clicking on the scroll bar, dragging the scroll thumb, turning the scroll wheel, or when the ScrollTo action is used. This event does not occur when the scroll pane moves because the contained content changed in length, or when the scroll pane is forced to scroll because the available visible area was resized. This event will never occur if the content within the scroll pane is shorter than the available visible area because then the content cannot be scrolled. + This event provides the :Percent attribute, a number from 0 to 100 (the same as the ScrollTo action above) representing the percentage that the user has now scrolled down the page as of the event occuring. This event also provides :Change, representing how much the user's scroll location has changed in the same unit as above (although this value will be negative if the user scrolled up). + + This event occurs when the user moves the mouse pointer while it is over the widget. The event will repeatedly fire each time the pointer moves. + + This event occurs when the user moves the scroll wheel while it is over the widget (or content inside the widget). The event will repeatedly fire each time the pointer moves. + This event occurs when the user presses the mouse button on the widget. This differs from the 'Click' event in that the user must actually press and release the mouse button on the widget for a Click event to fire, whereas simply pressing the mouse button down will cause the MouseDown event to fire. - This event occurs when the user moves the mouse pointer while it is over the widget. The event will repeatedly fire each time the pointer moves. + This event occurs when the user moves the mouse pointer while it is over the widget (or content inside the widget). The event will repeatedly fire each time the pointer moves. This event occurs when the user moves the mouse pointer off of the widget. This event occurs when the user first moves the mouse pointer over the widget. It will not occur again until the user moves the mouse off of the widget and then back over it again. This event occurs when the user releases the mouse button on the widget. + + Note: The Click, Wheel, MouseDown, and MouseUp events provide several pieces of useful information, including :shiftKey, :ctrlKey, :altKey, and :metaKey, which are 1 if the respective key is held down and 0 otherwise. These events also provide :button, a number representing the button number that the user used to execute the event (which appears to always be 0 for wheel). @@ -3467,7 +3483,7 @@ MyScrollPane "widget/scrollpane"

The tab pages are containers, and as such, controls of various kinds, including other tab controls, can be placed inside the tab pages.

-

Tab pages are added to a tab control by including widgets of type "widget/tabpage" within the "widget/tab" widget in the structure file that defines the application. Any controls to appear inside a particular tab page should be placed inside their respective "widget/tabpage" widgets in the structure file.Only widgets of type "widget/tabpage" should be placed inside a "widget/tab", with the exception of nonvisuals such as connectors.

+

Tab pages are added to a tab control by including widgets of type "widget/tabpage" within the "widget/tab" widget in the structure file that defines the application. Any controls to appear inside a particular tab page should be placed inside their respective "widget/tabpage" widgets in the structure file.Only widgets of type "widget/tabpage" should be placed inside a "widget/tab", except nonvisuals such as connectors.

Tab pages also have a 'visible' property which allows them to be hidden and revealed. This is used if the type is set to dynamic, but can be used manually as well.

@@ -3502,6 +3518,8 @@ MyScrollPane "widget/scrollpane" The location of the tabs: "top" (default), "bottom", "left", "right", or "none". The width of the tabs in pixels. This is optional for tab_locations of "top", "bottom", and "none". + + The height of the tabs in pixels. This is optional for all tab_locations. Defaults to 24px. The color of the text to be used on the tabs to identify them. @@ -3511,7 +3529,15 @@ MyScrollPane "widget/scrollpane" X-coordinate of the upper left corner of the tab control, relative to the container. - Y-coordinate of the upper left corner of the control, relative to its container. + 'client-side' or 'server-side'. This property is intended for developers (although it can give a very small performance boost). A value of "server-side" turns off JS rendering on the client. This does not work for dynamic width tabs (aka. top or bottom tabs with no 'tab_width' property). Defaults to "client-side". + + The amount to translate a selected tab along the side of the tab control. Defaults to 0px. + + The amount to translate a selected tab out and away from the side of the tab control. Defaults to 2px. + + The amount to translate a selected tab in the x direction. If set, overrides the value derived from select_translate_along and/or select_translate_out. + + The amount to translate a selected tab in the y direction. If set, overrides the value derived from select_translate_along and/or select_translate_out. @@ -3595,9 +3621,9 @@ myTabControl "widget/tab" -

A table widget is used to display data in a tabular format. It consists of a header row with column labels, followed by any number of rows containing data.The header may have a different color or image scheme than the rows, and the rows may or may not be configured to alternate between two colors or background images.

+

A table widget is used to display data in a tabular format. It consists of a header row with column labels, followed by any number of rows containing data. The header may have a different color or image scheme than the rows, and the rows may or may not be configured to alternate between two colors or background images.

- Table widgets come in three different flavors: static, dynamicpage, and dynamicrow.Static table widgets are built on the server and write their data directly into the container in which they reside, which is usually a scrollpane widget. Dynamicpage table widgets load their data once they initialize on the client, by activating a query through an ObjectSource nonvisual widget.Dynamicpage table widgets do not support modification, but can be reloaded through an ObjectSource at will.Dynamicrow table widgets, on the other hand, display each row as an individual layer, and thus are modifiable on the client. Dynamicrow table widgets also load their contents through an ObjectSource widget query.As of the time of writing of this document, only static mode and dynamicrow mode were supported. + Table widgets come in three different flavors: static, dynamicpage, and dynamicrow. Static table widgets are built on the server and write their data directly into the container in which they reside, which is usually a scrollpane widget. Dynamicpage table widgets load their data once they initialize on the client, by activating a query through an ObjectSource nonvisual widget. Dynamicpage table widgets do not support modification, but can be reloaded through an ObjectSource at will. Dynamicrow table widgets, on the other hand, display each row as an individual layer, and thus are modifiable on the client. Dynamicrow table widgets also load their contents through an ObjectSource widget query. As of the time of writing of this document, only static mode and dynamicrow mode were supported.

Table widgets allow the selection (keyboard, mouse, and data focus) of individual rows.

@@ -3622,6 +3648,8 @@ myTabControl "widget/tab" The vertical spacing between cells in the table, in pixels. Default is 1. The width of the column separation lines in pixels. Default is 1. + + Either 'full' or 'header'. Default is 'full'. Either "rows" (default) or "properties". In "properties" mode, the table displays one row per attribute, and so only displays the current record in the objectsource. In "rows" mode, the table displays one row per record in the objectsource. @@ -3731,7 +3759,7 @@ myTabControl "widget/tab" The title of the column to be displayed in the header row. - The type of the column: "text", "check", or "image". "text" is a normal column, and displays the textual value of the data element. "check" displays a checkmark if the data is non-zero (integers) or for strings if the value is non-empty and not "N" or "No". "image" displays the image referred to by the pathname contained in the data value. + The type of the column: "text", "check", "image", or "progress". "text" is a normal column, and displays the textual value of the data element. "check" displays a checkmark if the data is non-zero (integers) or for strings if the value is non-empty and not "N" or "No". "image" displays the image referred to by the pathname contained in the data value. "progress" displays a progress bar, with additional fields such as bar_color, bar_textcollor, and bar_padding. width of the column. diff --git a/centrallix-lib/Makefile.in b/centrallix-lib/Makefile.in index a7197622b..91b670e83 100644 --- a/centrallix-lib/Makefile.in +++ b/centrallix-lib/Makefile.in @@ -59,14 +59,14 @@ LIBS = @LIBS@ ## PROFILE=@PROFILE@ COVERAGE=@COVERAGE@ -CFLAGS=@CFLAGS@ @DEFS@ -Iinclude -DCXLIB_INTERNAL -DNM_USE_SYSMALLOC -Wall $(PROFILE) $(COVERAGE) -g +CFLAGS=@CFLAGS@ @DEFS@ -Iinclude -DCXLIB_INTERNAL -DNM_USE_SYSMALLOC -Wall $(PROFILE) $(COVERAGE) -g -lm MTCFLAGS=@CFLAGS@ @DEFS@ -Iinclude -DCXLIB_INTERNAL -DNM_USE_SYSMALLOC -Wall $(PROFILE) $(COVERAGE) -g -O0 TCFLAGS=$(patsubst -DNDEBUG,,$(CFLAGS)) -XSTATICFILES=mtask.o mtlexer.o memstr.o xarray.o xhash.o xstring.o mtsession.o newmalloc.o xhashqueue.o bdqs_transport.o xhandle.o xringqueue.o cxsec.o smmalloc.o qprintf.o strtcpy.o util.o +XSTATICFILES=mtask.o mtlexer.o memstr.o xarray.o xhash.o xstring.o mtsession.o newmalloc.o xhashqueue.o bdqs_transport.o xhandle.o xringqueue.o cxsec.o smmalloc.o clusters.o qprintf.o strtcpy.o util.o STATICFILES=$(patsubst %,src/%,$(XSTATICFILES)) -XDYNAMICFILES=mtask.lo mtlexer.lo memstr.lo xarray.lo xhash.lo xstring.lo mtsession.lo newmalloc.lo xhashqueue.lo bdqs_transport.lo xhandle.lo xringqueue.lo cxsec.lo smmalloc.lo qprintf.lo strtcpy.lo util.lo +XDYNAMICFILES=mtask.lo mtlexer.lo memstr.lo xarray.lo xhash.lo xstring.lo mtsession.lo newmalloc.lo xhashqueue.lo bdqs_transport.lo xhandle.lo xringqueue.lo cxsec.lo smmalloc.lo clusters.lo qprintf.lo strtcpy.lo util.lo DYNAMICFILES=$(patsubst %,src/%,$(XDYNAMICFILES)) INCLUDEFILES:=$(wildcard include/*.h) diff --git a/centrallix-lib/aclocal.m4 b/centrallix-lib/aclocal.m4 index cd74b27b2..279b7291d 100644 --- a/centrallix-lib/aclocal.m4 +++ b/centrallix-lib/aclocal.m4 @@ -47,7 +47,7 @@ AC_DEFUN(CHECK_BUILTIN_EXPECT, AC_DEFINE([HAVE_BUILTIN_EXPECT], [1], [Define if __builtin_expect is available]) AC_MSG_RESULT([yes]) ], - [AC_MSG_RESULT([no])], + [AC_MSG_RESULT([no])] ) ] ) diff --git a/centrallix-lib/configure b/centrallix-lib/configure index 4995f514f..c8c03668c 100755 --- a/centrallix-lib/configure +++ b/centrallix-lib/configure @@ -4104,6 +4104,7 @@ $as_echo "yes" >&6; } else { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } + fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext diff --git a/centrallix-lib/include/clusters.h b/centrallix-lib/include/clusters.h new file mode 100644 index 000000000..c813e2169 --- /dev/null +++ b/centrallix-lib/include/clusters.h @@ -0,0 +1,161 @@ +#ifndef CLUSTERS_H +#define CLUSTERS_H + +/************************************************************************/ +/* Centrallix Application Server System */ +/* Centrallix Core */ +/* */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ +/* */ +/* This program is free software; you can redistribute it and/or modify */ +/* it under the terms of the GNU General Public License as published by */ +/* the Free Software Foundation; either version 2 of the License, or */ +/* (at your option) any later version. */ +/* */ +/* This program is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU General Public License for more details. */ +/* */ +/* You should have received a copy of the GNU General Public License */ +/* along with this program; if not, write to the Free Software */ +/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA */ +/* 02111-1307 USA */ +/* */ +/* A copy of the GNU General Public License has been included in this */ +/* distribution in the file "COPYING". */ +/* */ +/* Module: lib_cluster.c, lib_cluster.h */ +/* Author: Israel Fuller */ +/* Creation: September 29, 2025 */ +/* Description Clustering library used to cluster and search data with */ +/* cosine or Levenshtein (aka. edit distance) similarity */ +/* measures. Used by the "clustering driver". */ +/* For more information on how to use this library, see */ +/* string-similarity.md in the centrallix-sysdoc folder. */ +/************************************************************************/ + +#include +#include + +#ifdef CXLIB_INTERNAL +#include "xarray.h" +#else +#include "cxlib/xarray.h" +#endif + +/** This file has additional documentation in string_similarity.md. **/ + + +/*** This value defines the number of dimensions used for a sparse + *** vector. The higher the number, the fewer collisions will be + *** encountered when using these vectors for cosine comparisons. + *** This is also called the vector table size, if viewing the + *** vector as a hash table of character pairs. + *** + *** 2147483647 is the signed int max, and is also a prime number. + *** Using this value ensures that the longest run of 0s will not + *** cause an int underflow with the current encoding scheme. + *** + *** Unfortunately, we can't use a number this large yet because + *** kmeans algorithm creates densely allocated centroids with + *** `CA_NUM_DIMS` dimensions, so a large number causes it to fail. + *** This, we use 251 as the largest prime number less than 256, + *** giving us a decent balance between collision reduction and + *** kmeans centroid performance/memory overhead. + ***/ +#define CA_NUM_DIMS 251 + +/*** The character used to create a pair with the first and last characters + *** of a string. Currently set to 96, the character just before 'a' (97) + *** in the ASCII table. + ***/ +#define CA_BOUNDARY_CHAR ((unsigned char)('a' - 1)) + +/** Types. **/ +typedef int* pVector; /* Sparse vector. */ +typedef double* pCentroid; /* Dense centroid. */ +#define CENTROID_SIZE (CA_NUM_DIMS * sizeof(double)) + +/*** Information about detected matching pairs. + *** + *** @param i The index into the provided data for the first element of the pair. + *** @param j The index into the provided data for the second element of the pair. + *** @param similarity A number from 0 to 1, from a similarity function, showing + *** how similar the pairs are. + ***/ +typedef struct + { + unsigned int i, j; + double similarity; + } + Pair, *pPair; + + +/** Edit distance function. **/ +int ca_edit_dist(const char* str1, const char* str2, const size_t str1_length, const size_t str2_length); + +/** Vector functions. **/ +pVector ca_build_vector(const char* str); +unsigned int ca_sparse_len(const pVector vector); +void ca_print_vector(const pVector vector); +void ca_free_vector(pVector sparse_vector); + +/** k-means function. **/ +int ca_kmeans( + pVector* vectors, + const unsigned int num_vectors, + const unsigned int num_clusters, + const unsigned int max_iter, + const double min_improvement, + unsigned int* labels, + double* vector_sims, + bool auto_seed); + +/** Vector helper macros. **/ +#define ca_is_empty(vector) (vector[0] == -CA_NUM_DIMS) +/*** Note: Given that CA_NUM_DIMS == 251, ca_build_vector("") will give the + *** vector we check for in the ca_has_no_pairs() macro, [-172, 11, -78], + *** which has a single pair of boundary characters. + *** If CA_NUM_DIMS is modified, this macro will need to be updated, hence the + *** compiler directive causing it to be undefined in this case, likely leading + *** to a lot of compiler or linker issues to remind the developer about this. + ***/ +#if CA_NUM_DIMS == 251 +#define ca_has_no_pairs(vector) \ + ({ \ + __typeof__ (vector) _v = (vector); \ + _v[0] == -172 && _v[1] == 11 && _v[2] == -78; \ + }) +#endif + +/** Comparison functions (see ca_search()). **/ +double ca_cos_compare(void* v1, void* v2); +double ca_lev_compare(void* str1, void* str2); +bool ca_eql(pVector v1, pVector v2); + +/** Similarity search functions. **/ +void* ca_most_similar( + void* target, + void** data, + const unsigned int num_data, + const double (*similarity)(void*, void*), + const double threshold); +pXArray ca_sliding_search( + void** data, + const unsigned int num_data, + const unsigned int window_size, + const double (*similarity)(void*, void*), + const double threshold, + pXArray maybe_pairs); +pXArray ca_complete_search( + void** data, + const unsigned int num_data, + const double (*similarity)(void*, void*), + const double threshold, + pXArray maybe_pairs); + +/** Module management functions. **/ +void ca_init(void); + +#endif /* End of .h file. */ diff --git a/centrallix-lib/include/cxlibconfig-internal.h.in b/centrallix-lib/include/cxlibconfig-internal.h.in index 6649e4478..7074db4ee 100644 --- a/centrallix-lib/include/cxlibconfig-internal.h.in +++ b/centrallix-lib/include/cxlibconfig-internal.h.in @@ -1,5 +1,10 @@ /* include/cxlibconfig-internal.h.in. Generated from configure.ac by autoheader. */ +/*** cxlibconfig-internal.h.in lists defines that are used by centrallix-lib + *** internally and will not be needed in other modules. For external + *** usecases, see cxlibconfig.h.in. + ***/ + /* Define to the address where bug reports for this package should be sent. */ #undef PACKAGE_BUGREPORT @@ -34,4 +39,3 @@ /* defined to 1 if SIOCOUTQ is available */ #undef HAVE_SIOCOUTQ - diff --git a/centrallix-lib/include/cxlibconfig.h.in b/centrallix-lib/include/cxlibconfig.h.in index dc6b3ad29..d1fc408fa 100644 --- a/centrallix-lib/include/cxlibconfig.h.in +++ b/centrallix-lib/include/cxlibconfig.h.in @@ -1,5 +1,11 @@ /* include/cxlibconfig.h.in. Generated from configure.ac by autoheader. */ +/*** cxlibconfig-h.in lists defines that should be availabe to anyone + *** using centrallix-lib. For example, a define used in an external .h + *** file should be written here so that the .h file can include it even + *** if it is used in another project. + ***/ + /* Define to 1 if you have the `endservent' function. */ #undef HAVE_ENDSERVENT @@ -33,6 +39,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_STDLIB_H +/* Define to 1 if you have the `__builtin_expect' function. */ +#undef HAVE_BUILTIN_EXPECT + /* Define to 1 if you have the `strdup' function. */ #undef HAVE_STRDUP diff --git a/centrallix-lib/include/expect.h b/centrallix-lib/include/expect.h index 7f859213b..03db37ab3 100644 --- a/centrallix-lib/include/expect.h +++ b/centrallix-lib/include/expect.h @@ -11,7 +11,7 @@ /* GNU Lesser General Public License, Version 2.1, contained in the */ /* included file "COPYING". */ /* */ -/* Module: Expect Branch Optimization Module (mtask.c, mtask.h) */ +/* Module: Expect Branch Optimization Module (expect.h) */ /* Author: Israel Fuller */ /* Date: February 27th, 2026 */ /* */ @@ -23,19 +23,26 @@ /* true, UNLIKELY() indicates it is likely to be false. */ /************************************************************************/ +#ifdef HAVE_CONFIG_H + #ifdef CXLIB_INTERNAL + #include "cxlibconfig.h" + #else + #include "cxlib/cxlibconfig.h" + #endif +#endif /** Define macros for signalling the more likely branch to the compiler. **/ #ifdef HAVE_BUILTIN_EXPECT -/** Use the GCC __builtin_expect() function for optimization. **/ -/** Note: We use !!(x) which normalizes to 0 or 1 to help the compiler. **/ -#define LIKELY(x) (__builtin_expect(!!(x), 1)) -#define UNLIKELY(x) (__builtin_expect(!!(x), 0)) + /** Use the GCC __builtin_expect() function for optimization. **/ + /** Note: We use !!(x) which normalizes to 0 or 1 to help the compiler. **/ + #define LIKELY(x) (__builtin_expect(!!(x), 1)) + #define UNLIKELY(x) (__builtin_expect(!!(x), 0)) #else -/*** Fallback: Define pass through functions to support compilers that don't - *** have this feature. - ***/ -#define LIKELY(x) (x) -#define UNLIKELY(x) (x) + /*** Fallback: Define pass through functions to support compilers that don't + *** have this feature. + ***/ + #define LIKELY(x) (x) + #define UNLIKELY(x) (x) #endif #endif diff --git a/centrallix-lib/include/glyph.h b/centrallix-lib/include/glyph.h new file mode 100644 index 000000000..6a5b0a74e --- /dev/null +++ b/centrallix-lib/include/glyph.h @@ -0,0 +1,79 @@ +#ifndef GLYPH_H +#define GLYPH_H + +/************************************************************************/ +/* Centrallix Application Server System */ +/* Centrallix Core */ +/* */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ +/* */ +/* This program is free software; you can redistribute it and/or modify */ +/* it under the terms of the GNU General Public License as published by */ +/* the Free Software Foundation; either version 2 of the License, or */ +/* (at your option) any later version. */ +/* */ +/* This program is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU General Public License for more details. */ +/* */ +/* You should have received a copy of the GNU General Public License */ +/* along with this program; if not, write to the Free Software */ +/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA */ +/* 02111-1307 USA */ +/* */ +/* A copy of the GNU General Public License has been included in this */ +/* distribution in the file "COPYING". */ +/* */ +/* Module: glyph.h */ +/* Author: Israel Fuller */ +/* Creation: October 27, 2025 */ +/* Description: A simple debug visualizer to make pretty patterns in */ +/* developer's terminal which can be surprisingly useful */ +/* for debugging algorithms. */ +/************************************************************************/ + +#include + +/** Uncomment to activate glyphs. **/ +/** Should not be enabled in production code on the master branch. */ +// #define ENABLE_GLYPHS + +#ifdef ENABLE_GLYPHS +#define glyph_print(s) printf("%s", s); + +/*** Initialize a simple debug visualizer to make pretty patterns in the + *** developer's terminal. Great for when you need to run a long task and + *** want a super simple way to make sure it's still working. + *** + *** @attention - Relies on storing data in variables in scope, so calling + *** glyph() requires a call to glyph_init() previously in the same scope. + *** + *** @param name The symbol name of the visualizer. + *** @param str The string printed for the visualization. + *** @param interval The number of invocations of glyph() required to print. + *** @param flush Whether to flush on output. + ***/ +#define glyph_init(name, str, interval, flush) \ + const char* vis_##name##_str = str; \ + const unsigned int vis_##name##_interval = interval; \ + const bool vis_##name##_flush = flush; \ + unsigned int vis_##name##_i = 0u; + +/*** Invoke a visualizer. + *** + *** @param name The name of the visualizer to invoke. + ***/ +#define glyph(name) \ + if (++vis_##name##_i % vis_##name##_interval == 0) \ + { \ + glyph_print(vis_##name##_str); \ + if (vis_##name##_flush) fflush(stdout); \ + } +#else +#define glyph_print(str) +#define glyph_init(name, str, interval, flush) +#define glyph(name) +#endif + +#endif /* End of .h file. */ diff --git a/centrallix-lib/include/magic.h b/centrallix-lib/include/magic.h index 812483090..92679e9c3 100644 --- a/centrallix-lib/include/magic.h +++ b/centrallix-lib/include/magic.h @@ -5,7 +5,7 @@ /* Centrallix Application Server System */ /* Centrallix Base Library */ /* */ -/* Copyright (C) 1999-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1999-2026 LightSys Technology Services, Inc. */ /* */ /* You may use these files and this library under the terms of the */ /* GNU Lesser General Public License, Version 2.1, contained in the */ @@ -27,9 +27,40 @@ #ifdef DBMAGIC - -#define ASSERTMAGIC(x,y) ((!(x) || (((pMagicHdr)(x))->Magic == (y)))?0:(printf("LS-PANIC: Magic number assertion failed, unexpected %X != %X for %8.8lX\n",(x)?(((pMagicHdr)(x))->Magic):(0xEE1EE100),(y),(long)(x)),(*((int*)(8)) = *((int*)(0))))) -#define ASSERTNOTMAGIC(x,y) ((!(x) || (((pMagicHdr)(x))->Magic != (y)))?0:(printf("LS-PANIC: Magic number assertion failed, unexpected %X\n",(y)),(*((int*)(8)) = *((int*)(0))))) +#include +#include "expect.h" + +#define ASSERTMAGIC(data, expect) \ + ({ \ + const pMagicHdr _data = (pMagicHdr)(data); \ + const Magic_t _expect = (expect); \ + const Magic_t _actual = (_data == NULL) ? 0xEE1EE100 : _data->Magic; \ + if (UNLIKELY(_data != NULL && _actual != _expect)) \ + { \ + fprintf(stderr, \ + "%s:%d: Magic assertion failed, unexpected %u != %d for %8.8lX.\n", \ + __FILE__, __LINE__, _actual, _expect, (long)_data \ + ); \ + abort(); \ + } \ + 0; \ + }) + +#define ASSERTNOTMAGIC(data, expect) \ + ({ \ + const pMagicHdr _data = (pMagicHdr)(data); \ + const Magic_t _expect = (expect); \ + const Magic_t _actual = (_data == NULL) ? 0xEE1EE100 : _data->Magic; \ + if (UNLIKELY(_data != NULL && _actual == _expect)) \ + { \ + fprintf(stderr, \ + "%s:%d: Magic assertion failed, unexpected %d.\n", \ + __FILE__, __LINE__, _expect \ + ); \ + abort(); \ + } \ + 0; \ + }) #else /* defined DBMAGIC */ @@ -38,9 +69,9 @@ #endif /* defined DBMAGIC */ -#define ISMAGIC(x,y) (((pMagicHdr)(x))->Magic == (y)) -#define ISNTMAGIC(x,y) (((pMagicHdr)(x))->Magic != (y)) -#define SETMAGIC(x,y) (((pMagicHdr)(x))->Magic = (y)) +#define ISMAGIC(data, expect) (((pMagicHdr)(data))->Magic == (expect)) +#define ISNTMAGIC(data, expect) (((pMagicHdr)(data))->Magic != (expect)) +#define SETMAGIC(data, expect) (((pMagicHdr)(data))->Magic = (expect)) typedef int Magic_t; @@ -81,4 +112,12 @@ typedef struct #define MGK_SMREGION 0x1200345c /* smmalloc.h::SmRegion */ #define MGK_SMBLOCK 0x1200349a /* smmalloc_private.h::SmBlock */ +#define MGK_CL_SOURCE_DATA 0x12340c19 /* objdrv_cluster.c::SourceData */ +#define MGK_CL_CLUSTER 0x12340c28 /* objdrv_cluster.c::Cluster */ +#define MGK_CL_CLUSTER_DATA 0x12340c37 /* objdrv_cluster.c::ClusterData */ +#define MGK_CL_SEARCH_DATA 0x12340c46 /* objdrv_cluster.c::SearchData */ +#define MGK_CL_NODE_DATA 0x12340c55 /* objdrv_cluster.c::NodeData */ +#define MGK_CL_DRIVER_DATA 0x12340c64 /* objdrv_cluster.c::DriverData */ +#define MGK_CL_QUERY_DATA 0x12340c73 /* objdrv_cluster.c::QueryData */ + #endif /* not defined _MAGIC_H */ diff --git a/centrallix-lib/include/mtsession.h b/centrallix-lib/include/mtsession.h index 29adaff59..cff7101d5 100644 --- a/centrallix-lib/include/mtsession.h +++ b/centrallix-lib/include/mtsession.h @@ -81,7 +81,9 @@ void* mssGetParam(char* paramname); /** Error handling functions **/ int mssLog(int level, char* msg); -int mssError(int clr, char* module, char* message, ...); +void mssError_internal(int clr, char* module, char* file, int line, char* message, ...); +#define mssError(clear, module, message, ...) \ + mssError_internal(clear, module, __FILE__, __LINE__, message, ##__VA_ARGS__) int mssErrorErrno(int clr, char* module, char* message, ...); int mssClearError(); int mssPrintError(pFile fd); diff --git a/centrallix-lib/include/qprintf.h b/centrallix-lib/include/qprintf.h index d638ef684..725b6aec5 100644 --- a/centrallix-lib/include/qprintf.h +++ b/centrallix-lib/include/qprintf.h @@ -1,19 +1,11 @@ #ifndef _QPRINTF_H #define _QPRINTF_H -#ifdef CXLIB_INTERNAL -#include "cxsec.h" -#include "magic.h" -#else -#include "cxlib/cxsec.h" -#include "cxlib/magic.h" -#endif - /************************************************************************/ /* Centrallix Application Server System */ /* Centrallix Base Library */ /* */ -/* Copyright (C) 1998-2006 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* You may use these files and this library under the terms of the */ /* GNU Lesser General Public License, Version 2.1, contained in the */ @@ -22,48 +14,80 @@ /* Module: qprintf.c, qprintf.h */ /* Author: Greg Beeley (GRB) */ /* Creation: January 31, 2006 */ -/* Description: Quoting Printf routine, used to make sure that */ -/* injection type attacks don't occur when building */ -/* strings. These functions do not support some of the */ -/* more advanced (and dangerous) features of the normal */ -/* printf() library calls. */ +/* Description: Quoting Printf routine that helps to prevent injection */ +/* attacks when building strings. These functions also do */ +/* not support some of the more advanced (and dangerous) */ +/* features found in the standard printf() library. */ +/* See centrallix-sysdoc/QPrintf.md for more information. */ /************************************************************************/ - #include -typedef int (*qpf_grow_fn_t)(char**, size_t*, size_t, void*, size_t); +#ifdef CXLIB_INTERNAL + #include "cxsec.h" + #include "magic.h" +#else + #include "cxlib/cxsec.h" + #include "cxlib/magic.h" +#endif + +#define QPF_ERR_T_NO_ERRORS (0) /* a default error buffer value with no errors */ +#define QPF_ERR_T_NOTIMPL (1<<0) /* unimplemented feature */ +#define QPF_ERR_T_BUFOVERFLOW (1<<1) /* dest buffer too small */ +#define QPF_ERR_T_INSOVERFLOW (1<<2) /* NLEN or *LEN restriction occurred */ +#define QPF_ERR_T_NOTPOSITIVE (1<<3) /* %POS conversion but number was neg */ +#define QPF_ERR_T_BADSYMBOL (1<<4) /* &SYM filter did not match the data. */ +#define QPF_ERR_T_MEMORY (1<<5) /* Memory allocation failed (internal). */ +#define QPF_ERR_T_BADLENGTH (1<<6) /* Length for NLEN or *LEN was invalid */ +#define QPF_ERR_T_BADFORMAT (1<<7) /* Format string was invalid */ +#define QPF_ERR_T_RESOURCE (1<<8) /* Internal resource limit hit */ +#define QPF_ERR_T_NULL (1<<9) /* NULL pointer passed (e.g. as a string) */ +#define QPF_ERR_T_INTERNAL (1<<10) /* Unrecoverable internal error. */ +#define QPF_ERR_T_BADFILE (1<<11) /* Bad filename for &FILE filter */ +#define QPF_ERR_T_BADPATH (1<<12) /* Bad pathname for &PATH filter */ +#define QPF_ERR_T_BADCHAR (1<<13) /* Bad character for filter (e.g. an octothorpe for &DB64) */ +#define QPF_ERR_COUNT (14) /* The number of errors listed above. */ +/*** A function to grow a string buffer. + *** + *** @param str The string buffer being grown. + *** @param size A pointer to the current size of the string buffer. + *** @param offset An offset up to which data must be preserved. + *** @param args Arguments for growing the buffer. + *** @param req The requested size. + *** @returns True (1) if successful, and false (0) if an error occurs. + ***/ +typedef int (*qpf_grow_fn_t)(char** str, size_t* size, size_t offset, void* args, size_t req); + +/*** Stores information about a qprint session, including details about errors + *** such as parsing and formatting issues that have occurred. + *** + *** @param Errors An error mask indicating any errors that have occurred, or + *** 0 (aka. `QPF_ERR_T_NO_ERRORS`) if no errors have occurred. + *** @param ErrorLines An array where each index represents a type of error in + *** order (e.g. 0 represents `QPF_ERR_T_NOTIMPL`) and each value represents + *** the line number where the error occurred, or 0 if this error type has + *** not occurred during the session. + ***/ typedef struct _QPS { unsigned int Errors; /* QPF_ERR_T_xxx */ + unsigned short ErrorLines[QPF_ERR_COUNT]; } QPSession, *pQPSession; -#define QPF_ERR_T_NOTIMPL 1 /* unimplemented feature */ -#define QPF_ERR_T_BUFOVERFLOW 2 /* dest buffer too small */ -#define QPF_ERR_T_INSOVERFLOW 4 /* NLEN or *LEN restriction occurred */ -#define QPF_ERR_T_NOTPOSITIVE 8 /* %POS conversion but number was neg */ -#define QPF_ERR_T_BADSYMBOL 16 /* &SYM filter did not match the data. */ -#define QPF_ERR_T_MEMORY 32 /* Memory allocation failed (internal). */ -#define QPF_ERR_T_BADLENGTH 64 /* Length for NLEN or *LEN was invalid */ -#define QPF_ERR_T_BADFORMAT 128 /* Format string was invalid */ -#define QPF_ERR_T_RESOURCE 256 /* Internal resource limit hit */ -#define QPF_ERR_T_NULL 512 /* NULL pointer passed (e.g. as a string) */ -#define QPF_ERR_T_INTERNAL 1024 /* Uncorrectable internal error. */ -#define QPF_ERR_T_BADFILE 2048 /* Bad filename for &FILE filter */ -#define QPF_ERR_T_BADPATH 4096 /* Bad pathname for &PATH filter */ -#define QPF_ERR_T_BADCHAR 8192 /* Bad character for filter (e.g. an octothorpe for &DB64) */ - -#define QPERR(x) (s->Errors |= (x)) - /*** QPrintf methods ***/ -pQPSession qpfOpenSession(); +pQPSession qpfOpenSession(void); int qpfCloseSession(pQPSession s); int qpfClearErrors(pQPSession s); unsigned int qpfErrors(pQPSession s); +void qpfLogErrors(pQPSession s); +int qpfNoGrow(char** str, size_t* size, size_t offs, void* arg, size_t req_size); +int qpfSysMallocGrow(char** str, size_t* size, size_t offset, void* args, size_t req_size); int qpfPrintf(pQPSession s, char* str, size_t size, const char* format, ...); int qpfPrintf_va(pQPSession s, char* str, size_t size, const char* format, va_list ap); +int qpfPrintf_g(pQPSession s, char** str, size_t* size, qpf_grow_fn_t grow_fn, void* grow_arg, const char* format, ...); +int qpfPrintf_gva(pQPSession s, char** str, size_t* size, qpf_grow_fn_t grow_fn, void* grow_arg, const char* format, va_list ap); void qpfRegisterExt(char* ext_spec, int (*ext_fn)(), int is_source); /*** Raw interface - should only be used internally by cxlib **/ diff --git a/centrallix-lib/include/util.h b/centrallix-lib/include/util.h index df4ba0d58..c184caecf 100644 --- a/centrallix-lib/include/util.h +++ b/centrallix-lib/include/util.h @@ -2,33 +2,163 @@ #define UTILITY_H /************************************************************************/ -/* Centrallix Application Server System */ +/* Centrallix Application Server System */ /* Centrallix Base Library */ /* */ -/* Copyright (C) 1998-2011 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* You may use these files and this library under the terms of the */ /* GNU Lesser General Public License, Version 2.1, contained in the */ /* included file "COPYING". */ /* */ -/* Module: (util.c,.h) */ -/* Author: Micah Shennum */ -/* Date: May 26, 2011 */ -/* Description: Collection of utilities */ +/* Module: util.c, util.h */ +/* Author: Micah Shennum and Israel Fuller */ +/* Date: May 26, 2011 */ +/* Description: Collection of utilities including: */ +/* - Utilities for parsing numbers. */ +/* - The timer utility for benchmarking code. */ +/* - snprint_bytes() for formatting a byte count. */ +/* - snprint_commas_llu() for formatting large numbers. */ +/* - fprint_mem() for printing memory stats. */ +/* - min() and max() for handling numbers. */ +/* - The check functions for reliably printing debug data. */ /************************************************************************/ #ifdef __cplusplus extern "C" { #endif - int strtoi(const char *nptr, char **endptr, int base); unsigned int strtoui(const char *nptr, char **endptr, int base); + char* snprint_bytes(char* buf, const size_t buf_size, unsigned int bytes); + char* snprint_commas_llu(char* buf, size_t buf_size, unsigned long long value); + void fprint_mem(FILE* out); + + typedef struct + { + double start, total; + } + Timer, *pTimer; + + pTimer timer_init(pTimer timer); + pTimer timer_new(void); + pTimer timer_start(pTimer timer); + pTimer timer_stop(pTimer timer); + double timer_get(pTimer timer); + pTimer timer_reset(pTimer timer); + void timer_de_init(pTimer timer); + void timer_free(pTimer timer); #ifdef __cplusplus } #endif -#endif /* UTILITY_H */ +#ifndef __cplusplus +#include + +#include "expect.h" + +/*** @brief Returns the smaller of two values. + *** + *** @param a The first value. + *** @param b The second value. + *** @return The smaller of the two values. + *** + *** @note This macro uses GCC extensions to ensure type safety. + ***/ +#define min(a, b) \ + ({ \ + __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + (_a < _b) ? _a : _b; \ + }) + +/*** @brief Returns the larger of two values. + *** + *** @param a The first value. + *** @param b The second value. + *** @return The larger of the two values. + *** + *** @note This macro uses GCC extensions to ensure type safety. + ***/ +#define max(a, b) \ + ({ \ + __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + (_a > _b) ? _a : _b; \ + }) + +/** File name macro, expanding functionality like __FILE__ and __LINE__. **/ +#define __FILENAME__ \ + ({ \ + const char* last_directory = strrchr(__FILE__, '/'); \ + ((last_directory != NULL) ? last_directory + 1 : __FILE__); \ + }) + +/** Error Handling. **/ +void print_err(int code, const char* function_name, const char* file_name, const int line_number); +/*** Ensures that developer diagnostics are printed if the result of the + *** passed function call is not zero. Not intended for user errors. + *** + *** @param result The expression to check. The text of this expression is + *** included in the error message if an error occurs. + *** @returns The result of the checked expression. + ***/ +#define check(result) \ + ({ \ + errno = 0; /* Reset errno to prevent confusion. */ \ + int _r = (result); \ + if (UNLIKELY(_r != 0)) print_err(_r, #result, __FILE__, __LINE__); \ + _r; \ + }) + +/*** Ensures that developer diagnostics are printed if the result of the + *** passed function call is negative. Not intended for user errors. + *** + *** @param result The expression to check. The text of this expression is + *** included in the error message if an error occurs. + *** @returns The result of the checked expression. + ***/ +#define check_neg(result) \ + ({ \ + errno = 0; /* Reset errno to prevent confusion. */ \ + int _r = (result); \ + if (UNLIKELY(_r < 0)) print_err(_r, #result, __FILE__, __LINE__); \ + _r; \ + }) + +/*** Ensures that developer diagnostics are printed if the result of the + *** passed function call is a NAN double. Not intended for user errors. + *** + *** @param result The expression to check. The text of this expression is + *** included in the error message if an error occurs. + *** @returns The result of the checked expression. + ***/ +#define check_double(result) \ + ({ \ + errno = 0; /* Reset errno to prevent confusion. */ \ + double _r = (result); \ + if (UNLIKELY(isnan(_r))) print_err(0, #result, __FILE__, __LINE__); \ + _r; \ + }) + +/*** Ensures that developer diagnostics are printed if the result of the + *** passed function call is a NULL pointer. Not intended for user errors. + *** + *** @param result The expression to check. The text of this expression is + *** included in the error message if an error occurs. + *** @returns The result of the checked expression. + ***/ +#define check_ptr(result) \ + ({ \ + errno = 0; /* Reset errno to prevent confusion. */ \ + void* _r = (result); \ + if (UNLIKELY(_r == NULL)) print_err(0, #result, __FILE__, __LINE__); \ + _r; \ + }) + +#endif /* __cplusplus */ + +#endif /* UTILITY_H */ diff --git a/centrallix-lib/include/xarray.h b/centrallix-lib/include/xarray.h index bcd107e79..9773767fa 100644 --- a/centrallix-lib/include/xarray.h +++ b/centrallix-lib/include/xarray.h @@ -50,6 +50,8 @@ int xaCount(pXArray this); int xaSetItem(pXArray this, int index, void* item); int xaInsertBefore(pXArray this, int index, void* item); int xaInsertAfter(pXArray this, int index, void* item); +int xaTrim(pXArray this); +void** xaToArray(pXArray this); #define CLD(x,y,z) ((x)((y)->Children.Items[(z)])) diff --git a/centrallix-lib/include/xhash.h b/centrallix-lib/include/xhash.h index 1b5d8459a..53cc8d892 100644 --- a/centrallix-lib/include/xhash.h +++ b/centrallix-lib/include/xhash.h @@ -1,12 +1,11 @@ #ifndef _XHASH_H #define _XHASH_H - /************************************************************************/ /* Centrallix Application Server System */ /* Centrallix Base Library */ /* */ -/* Copyright (C) 1998-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* You may use these files and this library under the terms of the */ /* GNU Lesser General Public License, Version 2.1, contained in the */ @@ -55,6 +54,7 @@ int xhAdd(pXHashTable this, char* key, char* data); int xhRemove(pXHashTable this, char* key); char* xhLookup(pXHashTable this, char* key); int xhClear(pXHashTable this, int (*free_fn)(), void* free_arg); +int xhForEach(pXHashTable this, int (*callback_fn)(pXHashEntry, void*), void* each_arg); +int xhClearKeySafe(pXHashTable this, void (*free_fn)(pXHashEntry, void*), void* free_arg); #endif /* _XHASH_H */ - diff --git a/centrallix-lib/src/clusters.c b/centrallix-lib/src/clusters.c new file mode 100644 index 000000000..6e4ec1a3f --- /dev/null +++ b/centrallix-lib/src/clusters.c @@ -0,0 +1,1122 @@ +/************************************************************************/ +/* Centrallix Application Server System */ +/* Centrallix Core */ +/* */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ +/* */ +/* This program is free software; you can redistribute it and/or modify */ +/* it under the terms of the GNU General Public License as published by */ +/* the Free Software Foundation; either version 2 of the License, or */ +/* (at your option) any later version. */ +/* */ +/* This program is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU General Public License for more details. */ +/* */ +/* You should have received a copy of the GNU General Public License */ +/* along with this program; if not, write to the Free Software */ +/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA */ +/* 02111-1307 USA */ +/* */ +/* A copy of the GNU General Public License has been included in this */ +/* distribution in the file "COPYING". */ +/* */ +/* Module: lib_cluster.c, lib_cluster.h */ +/* Author: Israel Fuller */ +/* Creation: September 29, 2025 */ +/* Description Clustering library used to cluster and search data with */ +/* cosine or Levenshtein (aka. edit distance) similarity */ +/* measures. Used by the "clustering driver". */ +/* For more information on how to use this library, see */ +/* string-similarity.md in the centrallix-sysdoc folder. */ +/************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "clusters.h" +#include "expect.h" +#include "newmalloc.h" +#include "util.h" +#include "xarray.h" + +/** This file has additional documentation in string_similarity.md. **/ + + +/*** Gets the hash for a pair of ASCII characters, represented by unsigned ints. + *** Thank you professor John Delano for this hashing algorithm. + *** + *** @param c1 The first character in the pair. + *** @param c2 The second character in the pair. + *** @returns The resulting hash. + ***/ +static unsigned int +ca_i_hash_char_pair(const unsigned char c1, const unsigned char c2) + { + const double sum = (c1 * c1 * c1) + (c2 * c2 * c2); + const double scale = ((double)c1 + 1.0) / ((double)c2 + 1.0); + const unsigned int hash = (unsigned int)round(sum * scale) - 1u; + return hash % CA_NUM_DIMS; + } + +/*** An internal struct for temporarily storing character pairs while building + *** sparse vectors. + *** + *** @param c1 The first character in the character pair. + *** @param c2 The second character in the character pair. + *** @param hash The hash for the two characters, calculated by calling the + *** ca_i_hash_char_pair() function (above). + **/ +typedef struct + { + unsigned char c1, c2; + unsigned int hash; + } + CharPair, *pCharPair; + +/*** Internal function to compare two character pairs, allowing us to sort them + *** by hash (ascending). + *** + *** @param p1 The first pCharPair. + *** @param p2 The second pCharPair. + *** @returns An int > 0 if p1's hash is larger. + *** An int < 0 if p2's hash is larger. + *** 0 if p1 and p2 have identical hashes. + ***/ +static int +ca_i_char_pair_cmp(const void *p1, const void *p2) + { + const CharPair *a = p1, *b = p2; + return a->hash - b->hash; + } + +/*** Builds a sparse vector for cosine compare using the provided string. + *** + *** Vectors are based on the frequencies of character pairs in the string. + *** Insignificant characters like spaces or punctuation are ignored (see + *** code for more detail) and all characters are converted to lowercase. + *** The boundary character (see `CA_BOUNDARY_CHAR`) is used to make pairs + *** on the start and end of strings. + *** + *** The only supported characters for the passed `char*` are whitespace, + *** punctuation, uppercase and lowercase letters, and numbers. Passing + *** other characters results in undefined behavior. + *** + *** The function uses the following modified ASCII table: + *** ```csv + *** #, char, #, char, #, char + *** 97, 'a', 109, 'm', 121, 'y' + *** 98, 'b', 110, 'n', 122, 'z' + *** 99, 'c', 111, 'o', 123, '0' + *** 100, 'd', 112, 'p', 124, '1' + *** 101, 'e', 113, 'q', 125, '2' + *** 102, 'f', 114, 'r', 126, '3' + *** 103, 'g', 115, 's', 127, '4' + *** 104, 'h', 116, 't', 128, '5' + *** 105, 'i', 117, 'u', 129, '6' + *** 106, 'j', 118, 'v', 130, '7' + *** 107, 'k', 119, 'w', 131, '8' + *** 108, 'l', 120, 'x', 132, '9' + *** ``` + *** + *** After hashing each character pair, we add some number from 1 to 13 to the + *** corresponding dimension based on the characters to improve performance in + *** cases where collisions occur. However, for most names, this process gives + *** a lot of zeros and a FEW positive numbers. Thus, we represent this vector + *** using a sparse abstraction where an entry of `-n` represents a run of `n` + *** consecutive zeros. Consider the following example: + *** + *** Dense Vector: `[1,0,0,0,3,0]` + *** Sparse Representation: `[1,-3,3,-1]` + *** + *** Using these sparse vectors greatly reduces the required memory and gives + *** approximately an x5 boost to performance on a test dataset with short + *** strings that rarely approached 32 characters (using 251 dimensions). The + *** only cost is more algorithmically complex code. + *** + *** @param str The string to be divided into pairs and hashed to make the vector. + *** @returns The sparse vector built using the hashed character pairs. + ***/ +pVector +ca_build_vector(const char* str) + { + unsigned char* chars = NULL; + CharPair* char_pairs = NULL; + pVector sparse_vector = NULL; + pVector trimmed_sparse_vector = NULL; + + /** Allocate memory to store the characters. **/ + unsigned int num_chars = 0u; + chars = check_ptr(nmSysMalloc((strlen(str) + 2u) * sizeof(unsigned char))); + if (UNLIKELY(chars == NULL)) goto err_free; + + /** Store characters. **/ + chars[num_chars++] = CA_BOUNDARY_CHAR; /* Starting boundary character. */ + for (const char* char_ptr = str; *char_ptr != '\0'; char_ptr++) + { + char maybe_char = *char_ptr; + if (maybe_char < 0) fprintf(stderr, "Warning: Unexpected negative char '%c' in string: \"%s\"\n", maybe_char, str); + unsigned char c = (unsigned char)maybe_char; + + /** Ignore insignificant characters. **/ + /** isspace(): space, \n, \v, \f, \r **/ + /** ispunct(): !"#$%&'()*+,-./:;<=>?@[\]^_{|}~ **/ + if (c != CA_BOUNDARY_CHAR && (isspace(c) || ispunct(c))) continue; + + /** Shift numbers to the end of the lowercase letters. **/ + if ('0' <= c && c <= '9') c += 75u; + + /** Store the character. **/ + chars[num_chars++] = tolower(c); + } + chars[num_chars++] = CA_BOUNDARY_CHAR; /* Ending boundary character. */ + + + /** Compute character pair hashes. **/ + char_pairs = check_ptr(nmSysMalloc(num_chars * sizeof(CharPair))); + if (UNLIKELY(char_pairs == NULL)) goto err_free; + const unsigned int num_pairs = num_chars - 1u; + for (unsigned int i = 0u; i < num_pairs; i++) + { + /** Store characters. **/ + char_pairs[i].c1 = chars[i]; + char_pairs[i].c2 = chars[i + 1]; + + /** Hash the character pair into an index (dimension). **/ + /** Note that the passed value should always be between 97 ('a') and 132 ('9'). **/ + char_pairs[i].hash = ca_i_hash_char_pair(chars[i], chars[i + 1]); + } + + /** Free unused memory. **/ + nmSysFree(chars); + chars = NULL; + + + /** Sort char_pairs by hash value. **/ + qsort(char_pairs, num_pairs, sizeof(CharPair), ca_i_char_pair_cmp); + + + /** Allocate space for the sparse vector. **/ + sparse_vector = check_ptr(nmSysMalloc((num_pairs * 2u + 1u) * sizeof(int))); + if (sparse_vector == NULL) goto err_free; + + /** Build the sparse vector from the character pairs. **/ + unsigned int cur = 0u, dim = 0u; + for (unsigned int i = 0u; i < num_pairs;) + { + unsigned int hash = char_pairs[i].hash; + + /** Proceed through the pairs until we find a unique hash. **/ + /** Dividing value by 2 each time reduces the impact of repeated pairs. **/ + int value = 0; + for (; i < num_pairs && char_pairs[i].hash == hash; i++) + { + value /= 2; /* Reduce impact of repeated pairs. */ + value += ((unsigned int)char_pairs[i].c1 + (unsigned int)char_pairs[i].c2) % 13u + 1u; + } + + /** Skip zeros to reach the dimension index specified by the hash. **/ + unsigned int num_zeros = hash - dim; + if (num_zeros > 0u) + { + sparse_vector[cur++] = (int)-num_zeros; + dim = hash; + } + + /** Add the value to the sparse vector. **/ + sparse_vector[cur++] = value; + dim++; + } + if (dim != CA_NUM_DIMS) sparse_vector[cur++] = -(CA_NUM_DIMS - dim); + + /** Free unused memory. **/ + nmSysFree(char_pairs); + char_pairs = NULL; + + + /** Trim extra space wasted by identical hashes. **/ + trimmed_sparse_vector = check_ptr(nmSysRealloc(sparse_vector, cur * sizeof(int))); + if (trimmed_sparse_vector == NULL) goto err_free; + sparse_vector = NULL; /* Mark memory freed by nmSysRealloc() no longer valid. */ + + /** Return the result. **/ + return trimmed_sparse_vector; + + err_free: + /** Cleanup. **/ + if (trimmed_sparse_vector != NULL) nmSysFree(trimmed_sparse_vector); + if (sparse_vector != NULL) nmSysFree(sparse_vector); + if (char_pairs != NULL) nmSysFree(char_pairs); + if (chars != NULL) nmSysFree(chars); + return NULL; + } + +/*** Free memory allocated to store a sparse vector. + *** + *** @param sparse_vector The sparse vector being freed. + ***/ +void +ca_free_vector(pVector sparse_vector) + { + nmSysFree(sparse_vector); + + return; + } + +/*** Parse a token from a sparsely allocated vector and write its value to + *** `token_value`. The number of dimensions consumed in the process is + *** written `dims_consumed`. + *** + *** @param token The sparse vector token to be parsed. + *** @param dims_consumed The location to store the number of dimensions + *** consumed, aka. how many indexes we would need to advance if this + *** were a densely allocated vector. + *** @param token_value The location to save the `token_value`. + ***/ +static void +ca_parse_vector_token(const int token, unsigned int* dims_consumed, unsigned int* token_value) + { + if (token < 0) + { + /** This run contains -token zeros. **/ + *dims_consumed = (unsigned)(-token); + *token_value = 0u; + } + else + { + /** This run contains one token_value. **/ + *dims_consumed = 1u; + *token_value = (unsigned)(token); + } + + return; + } + +/*** Compute the actual number of ints stored in memory to hold the given + *** sparsely allocated vector. + *** + *** @param vector The vector. + *** @returns The computed length. + ***/ +unsigned int +ca_sparse_len(const pVector vector) + { + unsigned int i = 0u; + + /*** TODO: Israel - Add code here to use nmSysGetSize(), if it is + *** available, once the newmalloc branch is merged. This would + *** allow us to know the size immediately without needing to + *** traverse the array. + ***/ + + for (unsigned int dim = 0u; dim < CA_NUM_DIMS;) + { + /** Parse the vector token. **/ + unsigned int dims_consumed = 0u, val = 0u; + ca_parse_vector_token(vector[i++], &dims_consumed, &val); + + /** Move ahead the requested number of dimensions. **/ + dim += dims_consumed; + } + + return i; + } + +/*** Print the underlying implementation-level values of a sparsely allocated + *** vector (for debugging). + *** + *** @param vector The vector to print. + ***/ +void +ca_print_vector(const pVector vector) + { + if (vector == NULL) + { + printf("Vector: NULL"); + return; + } + + const unsigned int len = ca_sparse_len(vector); + printf("Vector: [%d", vector[0]); + for (unsigned int i = 1u; i < len; i++) + printf(", %d", vector[i]); + printf("]"); + + return; + } + +/*** Compute the magnitude of a sparsely allocated vector. + *** + *** @param vector The vector. + *** @returns The computed magnitude. + ***/ +static double +ca_magnitude_sparse(const pVector vector) + { + unsigned int magnitude = 0u; + + for (unsigned int i = 0u, dim = 0u; dim < CA_NUM_DIMS;) + { + /** Parse the vector token. **/ + unsigned int dims_consumed = 0u, val = 0u; + ca_parse_vector_token(vector[i++], &dims_consumed, &val); + + /** Increase magnitude. **/ + magnitude += val * val; + + /** Move ahead the requested number of dimensions. **/ + dim += dims_consumed; + } + + return sqrt((double)magnitude); + } + +/*** Compute the magnitude of a densely allocated centroid. + *** + *** @param centroid The centroid. + *** @returns The computed magnitude. + ***/ +static double +ca_magnitude_dense(const pCentroid centroid) + { + double magnitude = 0.0; + + for (unsigned int i = 0u; i < CA_NUM_DIMS; i++) + magnitude += centroid[i] * centroid[i]; + + return sqrt(magnitude); + } + +/*** Calculate the similarity between two sparsely allocated vectors by taking + *** their dot product. + *** + *** @param v1 Sparse vector #1. + *** @param v2 Sparse vector #2. + *** @returns Similarity between 0 and 1 where + *** 1 indicates identical and + *** 0 indicates completely different. + ***/ +static double +ca_sparse_similarity(const pVector v1, const pVector v2) + { + /** Calculate dot product. **/ + unsigned int vec1_remaining = 0u, vec2_remaining = 0u; + unsigned int dim = 0u, i1 = 0u, i2 = 0u, dot_product = 0u; + while (dim < CA_NUM_DIMS) + { + unsigned int val1 = 0u, val2 = 0u; + if (vec1_remaining == 0u) ca_parse_vector_token(v1[i1++], &vec1_remaining, &val1); + if (vec2_remaining == 0u) ca_parse_vector_token(v2[i2++], &vec2_remaining, &val2); + + /*** Accumulate the dot_product. If either vector is 0 here, + *** the total is 0 and this statement does nothing. + ***/ + dot_product += val1 * val2; + + /** Consume overlap from both runs. **/ + unsigned int overlap = min(vec1_remaining, vec2_remaining); + vec1_remaining -= overlap; + vec2_remaining -= overlap; + dim += overlap; + } + + /** Optimization: Skip computing magnitudes for completely different vectors. **/ + if (dot_product == 0u) return 0.0; + + /** Return the difference score. **/ + return (double)dot_product / (ca_magnitude_sparse(v1) * ca_magnitude_sparse(v2)); + } + +/*** Calculate the difference between two sparsely allocated vectors by + *** subtracting their dot product from 1.0. + *** + *** @param v1 Sparse vector #1. + *** @param v2 Sparse vector #2. + *** @returns Similarity between 0 and 1 where + *** 1 indicates completely different and + *** 0 indicates identical. + ***/ +#define ca_sparse_dif(v1, v2) (1.0 - ca_sparse_similarity(v1, v2)) + +/*** Calculate the similarity between a sparsely allocated vector and a densely + *** allocated centroid by taking their dot product. + *** + *** @param v1 Sparse vector #1. + *** @param c1 Dense centroid #2. + *** @returns Similarity between 0 and 1 where + *** 1 indicates identical and + *** 0 indicates completely different. + ***/ +static double +ca_sparse_similarity_to_centroid(const pVector v1, const pCentroid c2) + { + double dot_product = 0.0; + + for (unsigned int i = 0u, dim = 0u; dim < CA_NUM_DIMS;) + { + /** Parse the vector token. **/ + unsigned int dims_consumed = 0u, val = 0u; + ca_parse_vector_token(v1[i++], &dims_consumed, &val); + + /** Increase dot product (skipped for zero-values). **/ + if (val > 0u) dot_product += (double)val * c2[dim]; + + /** Move ahead the requested number of dimensions. **/ + dim += dims_consumed; + } + + /** Return the difference score. **/ + return dot_product / (ca_magnitude_sparse(v1) * ca_magnitude_dense(c2)); + } + +/*** Calculate the difference between a sparsely allocated vector and a densely + *** allocated centroid by subtracting their dot product from 1.0. + *** + *** @param v1 Sparse vector #1. + *** @param c1 Dense centroid #2. + *** @returns Difference between 0 and 1 where + *** 1 indicates completely different and + *** 0 indicates identical. + ***/ +#define ca_sparse_dif_to_centroid(v1, c2) (1.0 - ca_sparse_similarity_to_centroid(v1, c2)) + +/*** Computes Levenshtein distance between two strings. + *** + *** @param str1 The first string. + *** @param str2 The second string. + *** @param str1_length The length of the first string. + *** @param str2_length The length of the second string. + *** @returns The edit distance between the two strings, or a negative value on error. + *** + *** @attention - `Tip`: Pass 0 for the length of either string to infer it + *** using the null terminating character. Conversely, character arrays + *** with no null terminator are allowed if an explicit length is passed. + ***/ +int +ca_edit_dist(const char* str1, const char* str2, const size_t str1_length, const size_t str2_length) + { + int result = -1; + unsigned int** lev_matrix = NULL; + + /*** lev_matrix: + *** For all i and j, d[i][j] will hold the Levenshtein distance between + *** the first i characters of s and the first j characters of t. + *** + *** As they say, no dynamic programming algorithm is complete without a + *** matrix that you fill out and it has the answer in the final location. + ***/ + const size_t str1_len = (str1_length == 0u) ? strlen(str1) : str1_length; + const size_t str2_len = (str2_length == 0u) ? strlen(str2) : str2_length; + lev_matrix = check_ptr(nmSysMalloc((str1_len + 1) * sizeof(unsigned int*))); + if (lev_matrix == NULL) goto end; + for (unsigned int i = 0u; i < str1_len + 1u; i++) + { + lev_matrix[i] = check_ptr(nmSysMalloc((str2_len + 1) * sizeof(unsigned int))); + if (lev_matrix[i] == NULL) goto end; + } + + /*** Base case #0: + *** Transforming an empty string into an empty string has 0 cost. + ***/ + lev_matrix[0][0] = 0u; + + /*** Base case #1: + *** Any source prefix can be transformed into an empty string by + *** dropping each character. + ***/ + for (unsigned int i = 1u; i <= str1_len; i++) + lev_matrix[i][0] = i; + + /*** Base case #2: + *** Any target prefixes can be transformed into an empty string by + *** inserting each character. + ***/ + for (unsigned int j = 1u; j <= str2_len; j++) + lev_matrix[0][j] = j; + + /** General Case. **/ + for (unsigned int i = 1u; i <= str1_len; i++) + { + for (unsigned int j = 1u; j <= str2_len; j++) + { + /** If the characters are equal, no change is needed. **/ + if (str1[i - 1] == str2[j - 1]) + lev_matrix[i][j] = lev_matrix[i - 1][j - 1]; + + /*** We need to make a change, so use the operation with the + *** lowest cost out of delete, insert, replace, or swap. + ***/ + else + { + unsigned int cost_delete = lev_matrix[i - 1][j] + 1u; + unsigned int cost_insert = lev_matrix[i][j - 1] + 1u; + unsigned int cost_replace = lev_matrix[i-1][j-1] + 1u; + + /** If a swap is possible, calculate the cost. **/ + bool can_swap = ( + i > 1 && j > 1 && + str1[i - 1] == str2[j - 2] && + str1[i - 2] == str2[j - 1] + ); + unsigned int cost_swap = (can_swap) ? lev_matrix[i - 2][j - 2] + 1 : UINT_MAX; + + /** Assign the best operation. **/ + lev_matrix[i][j] = min(min(min(cost_delete, cost_insert), cost_replace), cost_swap); + } + } + } + + /** Store result. **/ + unsigned int unsigned_result = lev_matrix[str1_len][str2_len]; + if (unsigned_result > INT_MAX) + { + fprintf(stderr, + "Warning: Integer overflow detected in ca_edit_dist(\"%s\", \"%s\", %lu, %lu) = %u > %d\n", + str1, str2, str1_length, str2_length, unsigned_result, INT_MAX + ); + } + result = (int)unsigned_result; + + /** Cleanup. **/ + end: + if (lev_matrix != NULL) + { + for (unsigned int i = 0u; i < str1_len + 1u; i++) + { + if (lev_matrix[i] == NULL) break; + else nmSysFree(lev_matrix[i]); + } + nmSysFree(lev_matrix); + } + + /** Done. **/ + return result; + } + +/*** Compares two strings using their cosine similarity, returning a value + *** between `0.0` (completely different) and `1.0` (identical). If either + *** OR BOTH strings are NULL, this function returns `0.0`. + *** + *** @attention - Note: Punctuation, whitespace, etc. are ignored due to how + *** cosine vector hashing is implemented. + *** + *** @attention - This function takes `void*` instead of `pVector` so that it + *** can be used as the similarity function in the ca_search() function + *** family without needing a messy typecast to avoid the compiler warning. + *** However, behavior is undefined if `v1` and `v2` do are not `pVector`s. + *** + *** @param v1 A `pVector` to the first string to compare. + *** @param v2 A `pVector` to the second string to compare. + *** @returns The cosine similarity between the two strings. + ***/ +double +ca_cos_compare(void* v1, void* v2) + { + /** Input validation checks. **/ + if (v1 == NULL || v2 == NULL) return 0.0; + if (v1 == v2) return 1.0; + + /** Input validation checks. **/ + const pVector vec1 = v1, vec2 = v2; + const bool v1_empty = (vec1 == NULL || ca_is_empty(vec1) || ca_has_no_pairs(vec1)); + const bool v2_empty = (vec2 == NULL || ca_is_empty(vec2) || ca_has_no_pairs(vec2)); + if (v1_empty && v2_empty) return 1.0; + if (v1_empty && !v2_empty) return 0.0; + if (!v1_empty && v2_empty) return 0.0; + + /** Apply rounding to avoid annoying floating point issues before returning. **/ + return round(ca_sparse_similarity(vec1, vec2) * 1000000.0) / 1000000.0; + } + +/*** Compares two strings using their Levenshtein edit distance to compute a + *** similarity between `0.0` (completely different) and `1.0` (identical). + *** If both strings are empty, this function returns `1.0` (identical). If + *** either OR BOTH strings are NULL, this function returns `0.0`. + *** + *** @attention - Note: Unlike `ca_cos_compare()`, punctuation, whitespace, + *** etc. are NOT ignored. In fact, this functions supports strings that + *** contain ANY valid ASCII characters other than the NULL-terminator, + *** which is used to terminate the string. + *** + *** @attention - This function takes `void*` instead of `char*` so that it + *** can be used as the similarity function in the ca_search() function + *** family without needing a messy typecast to avoid the compiler warning. + *** However, behavior is undefined if `v1` and `v2` do are not `char*`s. + *** + *** @param str1 A `char*` to the first string to compare. + *** @param str2 A `char*` to the second string to compare. + *** @returns The Levenshtein similarity between the two strings, + *** or NAN on failure. + ***/ +double +ca_lev_compare(void* str1, void* str2) + { + /** Input validation checks. **/ + if (str1 == NULL || str2 == NULL) return 0.0; + if (str1 == str2) return 1.0; + + /** Handle string length. **/ + const size_t len1 = strlen(str1); + const size_t len2 = strlen(str2); + if (len1 == 0lu && len2 == 0lu) return 1.0; + if (len1 != 0lu && len2 == 0lu) return 0.0; + if (len1 == 0lu && len2 != 0lu) return 0.0; + + /** Compute levenshtein edit distance. **/ + const int edit_dist = check_neg(ca_edit_dist((const char*)str1, (const char*)str2, len1, len2)); + if (edit_dist < 0) return NAN; + + /** Normalize edit distance into a similarity measure. **/ + const double normalized_similarity = 1.0 - (double)edit_dist / (double)max(len1, len2); + + /** Apply rounding to avoid annoying floating point issues before returning. **/ + return round(normalized_similarity * 1000000.0) / 1000000.0; + } + +/*** Check if two sparse vectors are identical, typically used for debugging + *** or testing. True if both vectors are `NULL`, but false if one is and the + *** other is not. + *** + *** @param v1 The first vector. + *** @param v2 The second vector. + *** @returns true if they are equal, + *** false if any element is different. + ***/ +bool +ca_eql(pVector v1, pVector v2) + { + /** Edge cases. **/ + if (v1 == v2) return true; + if (v1 == NULL || v2 == NULL) return false; + + const unsigned int len = ca_sparse_len(v1); + if (len != ca_sparse_len(v2)) return false; + for (unsigned int i = 0u; i < len; i++) + if (v1[i] != v2[i]) return false; + + return true; + } + +/*** Calculate the average size of all clusters in a set of vectors. + *** + *** @param vectors The vectors of the dataset (allocated sparsely). + *** @param num_vectors The number of vectors in the dataset. + *** @param labels The clusters to which vectors are assigned. + *** @param centroids The locations of the centroids (allocated densely). + *** @param num_clusters The number of centroids (k). + *** @returns The average cluster size. + ***/ +static double +get_cluster_size( + pVector* vectors, + const unsigned int num_vectors, + unsigned int* labels, + pCentroid* centroids, + const unsigned int num_clusters) + { + double result = NAN; + double* cluster_sums = NULL; + unsigned int* cluster_counts = NULL; + + /** Allocate space to store clusters as averages are computed. **/ + /*** We use nmMalloc() here because this function is usually called + *** repeatedly with the same number of clusters at the end of the + *** loop in ca_kmeans(). Also, ca_kmeans() may be called multiple + *** times with the same k value, increasing this benefit. + ***/ + cluster_sums = check_ptr(nmMalloc(num_clusters * sizeof(double))); + cluster_counts = check_ptr(nmMalloc(num_clusters * sizeof(unsigned int))); + if (cluster_sums == NULL || cluster_counts == NULL) goto end; + for (unsigned int i = 0u; i < num_clusters; i++) + { + cluster_sums[i] = 0.0; + cluster_counts[i] = 0u; + } + + /** Sum the difference from each vector to its cluster centroid. **/ + for (unsigned int i = 0u; i < num_vectors; i++) + { + const unsigned int label = labels[i]; + cluster_sums[label] += ca_sparse_dif_to_centroid(vectors[i], centroids[label]); + cluster_counts[label]++; + } + + /** Add up the average cluster size. **/ + double cluster_total = 0.0; + unsigned int num_valid_clusters = 0u; + for (unsigned int label = 0u; label < num_clusters; label++) + { + const unsigned int cluster_count = cluster_counts[label]; + if (cluster_count == 0u) continue; + + cluster_total += cluster_sums[label] / cluster_count; + num_valid_clusters++; + } + + /** Calculate average sizes. **/ + result = cluster_total / num_valid_clusters; + + end: + /** Clean up. **/ + if (cluster_sums != NULL) nmFree(cluster_sums, num_clusters * sizeof(double)); + if (cluster_counts != NULL) nmFree(cluster_counts, num_clusters * sizeof(unsigned int)); + + return result; + } + +/*** Executes the k-means clustering algorithm. Selects `num_clusters` random + *** vectors as initial centroids, using `rand()` (to set a seed, call srand() + *** pass false for `auto_seed`). Each iteration, points are assigned to the + *** nearest centroid using cosine similarity on the provided sparse vectors. + *** After this, centroids are moved to the center of their points. This + *** process repeats until the `min_improvement` threshold is not met, or + *** `max_iter` is reached (whichever happens first). + *** + *** @attention - `num_vectors` must be the length of `vectors`. + *** @attention - `num_clusters` must be the length of `labels`. + *** + *** @param vectors The sparse cosine similarity vectors representing the data + *** to cluster. + *** @param num_vectors The number of vectors (in `vectors`) to cluster. + *** @param num_clusters The number of clusters to generate (also called k). + *** @param max_iter A hard cutoff for the max number of iterations, applied + *** even if the `min_improvement` threshold is still met on each iteration. + *** @param min_improvement The minimum improvement threshold that must be met + *** each clustering iteration. If this is not met, the iterations stop. + *** Pass -1.0 to disable this and iterate for as long as the centroids keep + *** changing (or until `max_iter` is reached). + *** @param labels Initialized by this function to stores the final cluster + *** identities of the vectors after clustering is completed. Each value + *** will be `0 <= n < num_clusters`. This buffer can be uninitialized. + *** @param vector_sims An array of num_vectors elements, allocated by the + *** caller, where index i stores the similarity of vector i to its assigned + *** cluster. Passing NULL skips evaluation of these values. + *** @param auto_seed If true, the function will set its own seed. Otherwise, + *** it will use rand() without setting a seed, so the caller should use + *** srand() to set a seed before calling. + *** @returns 0 if successful, or -1 if an error occurs. + ***/ +int +ca_kmeans( + pVector* vectors, + const unsigned int num_vectors, + const unsigned int num_clusters, + const unsigned int max_iter, + const double min_improvement, + unsigned int* labels, + double* vector_sims, + bool auto_seed) + { + pCentroid* centroids = NULL; + pCentroid* new_centroids = NULL; + + /** Setup variables. **/ + bool successful = false; + unsigned int cluster_counts[num_clusters]; + memset(labels, 0u, num_vectors * sizeof(unsigned int)); + + /** Allocate space to store centroids and new_centroids. **/ + /** Dynamic allocation is required because these densely allocated arrays might be up to 500KB! **/ + const size_t centroids_size = num_clusters * sizeof(pCentroid); + centroids = check_ptr(nmMalloc(centroids_size)); + new_centroids = check_ptr(nmMalloc(centroids_size)); + if (centroids == NULL || new_centroids == NULL) goto end; + memset(centroids, 0, centroids_size); + memset(new_centroids, 0, centroids_size); + for (unsigned int i = 0u; i < num_clusters; i++) + { + centroids[i] = check_ptr(nmMalloc(CENTROID_SIZE)); + new_centroids[i] = check_ptr(nmMalloc(CENTROID_SIZE)); + if (centroids[i] == NULL || new_centroids[i] == NULL) goto end; + memset(centroids[i], 0, CENTROID_SIZE); + memset(new_centroids[i], 0, CENTROID_SIZE); + } + + /** Select random vectors to use as the initial centroids. **/ + if (auto_seed) srand(time(NULL)); + for (unsigned int i = 0u; i < num_clusters; i++) + { + /** Pick a random vector. **/ + const pVector vector = vectors[rand() % num_vectors]; + + /** Sparse copy the vector to expand it into a densely allocated centroid. **/ + pCentroid centroid = centroids[i]; + for (unsigned int i = 0u, dim = 0u; dim < CA_NUM_DIMS;) + { + const int token = vector[i++]; + if (token > 0) centroid[dim++] = (double)token; + else for (unsigned int j = 0u; j < (unsigned)-token; j++) centroid[dim++] = 0.0; + } + } + + /** Main kmeans loop. **/ + double old_average_cluster_size = 1.0; + for (unsigned int iter = 0u; iter < max_iter; iter++) + { + bool changed = false; + + /** Reset new centroids. **/ + for (unsigned int i = 0u; i < num_clusters; i++) + { + cluster_counts[i] = 0u; + for (unsigned int dim = 0; dim < CA_NUM_DIMS; dim++) + new_centroids[i][dim] = 0.0; + } + + /** Assign each point to the nearest centroid. **/ + for (unsigned int i = 0u; i < num_vectors; i++) + { + const pVector vector = vectors[i]; + double min_dist = DBL_MAX; + unsigned int best_centroid_label = 0u; + + /** Find nearest centroid. **/ + for (unsigned int j = 0u; j < num_clusters; j++) + { + const double dist = ca_sparse_dif_to_centroid(vector, centroids[j]); + if (dist < min_dist) + { + min_dist = dist; + best_centroid_label = j; + } + } + + /** Update label to new centroid, if necessary. **/ + if (labels[i] != best_centroid_label) + { + labels[i] = best_centroid_label; + changed = true; + } + + /** Accumulate values for new centroid calculation. **/ + pCentroid best_centroid = new_centroids[best_centroid_label]; + for (unsigned int i = 0u, dim = 0u; dim < CA_NUM_DIMS;) + { + const int val = vector[i++]; + if (val < 0) dim += (unsigned)(-val); + else best_centroid[dim++] += (double)val; + } + cluster_counts[best_centroid_label]++; + } + + /** Stop if centroids didn't change. **/ + if (!changed) break; + + /** Update centroids. **/ + for (unsigned int i = 0u; i < num_clusters; i++) + { + if (cluster_counts[i] == 0u) continue; + pCentroid centroid = centroids[i]; + const pCentroid new_centroid = new_centroids[i]; + const unsigned int cluster_count = cluster_counts[i]; + for (unsigned int dim = 0u; dim < CA_NUM_DIMS; dim++) + centroid[dim] = new_centroid[dim] / cluster_count; + } + + /** Is there enough improvement? **/ + if (min_improvement <= -1.0) continue; /** Skip check if it will never end the loop. **/ + const double average_cluster_size = check_double(get_cluster_size(vectors, num_vectors, labels, centroids, num_clusters)); + if (isnan(average_cluster_size)) goto end; + const double improvement = old_average_cluster_size - average_cluster_size; + if (improvement < min_improvement) break; + old_average_cluster_size = average_cluster_size; + } + + /** Compute vector similarities, if requested. **/ + if (vector_sims != NULL) + { + for (unsigned int i = 0u; i < num_vectors; i++) + vector_sims[i] = ca_sparse_similarity_to_centroid(vectors[i], centroids[labels[i]]); + } + + /** Success. **/ + successful = true; + + end: + /** Clean up. **/ + if (centroids != NULL) + { + for (unsigned int i = 0u; i < num_clusters; i++) + { + if (centroids[i] != NULL) nmFree(centroids[i], CENTROID_SIZE); + else break; + } + nmFree(centroids, num_clusters * sizeof(pCentroid)); + } + if (new_centroids != NULL) + { + for (unsigned int i = 0u; i < num_clusters; i++) + { + if (new_centroids[i] != NULL) nmFree(new_centroids[i], CENTROID_SIZE); + else break; + } + nmFree(new_centroids, num_clusters * sizeof(pCentroid)); + } + + /** Return the function result code. **/ + return (successful) ? 0 : -1; + } + +/*** Finds the data that is the most similar to the target and returns + *** it if the similarity meets the threshold. + *** + *** @param target The target data to compare to the rest of the data. + *** @param data The rest of the data, compared against the target. + *** @param num_data The number of elements in data. Specify 0 to detect + *** length on a null terminated array of data. + *** @param similarity A function that takes two data items (aka. pointers + *** in the array pointed to by `data`) and returns their similarity. + *** @param threshold The minimum similarity threshold. If the most similar + *** data does not meet this threshold, the function returns NULL. + *** @returns A pointer to the most similar piece of data found in the data + *** array, or NULL if the most similar data did not meet the threshold. + ***/ +void* +ca_most_similar( + void* target, + void** data, + const unsigned int num_data, + const double (*similarity)(void*, void*), + const double threshold) + { + void* most_similar = NULL; + double best_sim = -INFINITY; + + /** Iterate over all data options to find the one with the highest similarity. **/ + for (unsigned int i = 0u; (num_data == 0u && data[i] != NULL) || (i < num_data); i++) + { + const double sim = check_double(similarity(target, data[i])); + if (isnan(sim)) continue; /* Skip failed comparison. */ + if (sim > best_sim && sim > threshold) + { + most_similar = data[i]; + best_sim = sim; + } + } + + return most_similar; + } + + +/*** Runs a sliding search over the provided data, comparing each element to + *** the next `window_size` elements. If any comparison yields a similarity + *** greater than the threshold, it is stored in the returned xArray. + *** + *** @param data The data to be searched. + *** @param num_data The number of data items in `data`. + *** @param window_size The size of the sliding window used for the search. + *** @param similarity A function which takes two data items of the type of + *** the `data` param and returns their similarity. + *** @param threshold The minimum threshold required for each pair. + *** @param maybe_pairs A pointer to an xArray to which pairs should be added. + *** Pass NULL to allocate a new one. + *** @returns An xArray of the pairs found. If `maybe_pairs` is not NULL, this + *** will be that xArray, to allow for chaining. + ***/ +pXArray +ca_sliding_search( + void** data, + const unsigned int num_data, + const unsigned int window_size, + const double (*similarity)(void*, void*), + const double threshold, + pXArray maybe_pairs) + { + pXArray pairs = maybe_pairs; + + /** Allocate space for pairs (if it is not provided). **/ + if (pairs == NULL) + { + /** Guess that we will need space for two pairs per data point. **/ + const int guess_size = num_data * 2; + pairs = check_ptr(xaNew(guess_size)); + if (pairs == NULL) goto err; + } + const int num_starting_pairs = pairs->nItems; + + /** Search for pairs. **/ + for (unsigned int i = 0u; i < num_data; i++) + { + const unsigned int window_start = i + 1u; + const unsigned int window_end = min(i + window_size, num_data); + for (unsigned int j = window_start; j < window_end; j++) + { + const double sim = check_double(similarity(data[i], data[j])); + if (isnan(sim) || sim < 0.0 || 1.0 < sim) + { + fprintf(stderr, "Invalid similarity %g %lf.\n", sim, sim); + goto err_free; + } + if (sim > threshold) /* Pair found! */ + { + pPair pair = (pPair)check_ptr(nmMalloc(sizeof(Pair))); + if (pair == NULL) goto err_free; + pair->i = i; + pair->j = j; + pair->similarity = sim; + if (check_neg(xaAddItem(pairs, (void*)pair)) < 0) goto err_free; + } + } + } + + /** Success. **/ + return pairs; + + err_free: + /** Error cleanup: Free the pairs that we added to the XArray. **/ + while (pairs->nItems > num_starting_pairs) + nmFree(pairs->Items[--pairs->nItems], sizeof(Pair)); + if (maybe_pairs == NULL) check(xaDeInit(pairs)); /* Failure ignored. */ + + err: + return NULL; + } + +/*** Runs a complete search over the provided data, comparing each element to + *** each other element. If any comparison yields a similarity greater than the + *** threshold, it is stored in the returned xArray. + *** + *** @param data The data to be searched. + *** @param num_data The number of data items in `data`. + *** @param similarity A function which takes two data items of the type of + *** the `data` param and returns their similarity. + *** @param threshold The minimum threshold required for each pair. + *** @param maybe_pairs A pointer to an xArray to which pairs should be added. + *** Pass NULL to allocate a new one. + *** @returns An xArray of the pairs found. If `maybe_pairs` is not NULL, this + *** will be that xArray, to allow for chaining. + ***/ +pXArray +ca_complete_search( + void** data, + const unsigned int num_data, + const double (*similarity)(void*, void*), + const double threshold, + pXArray maybe_pairs) + { + return ca_sliding_search(data, num_data, num_data, similarity, threshold, maybe_pairs); + } + +/** Initialize the module. **/ +void +ca_init(void) + { + nmRegister(sizeof(pVector), "pVector"); + nmRegister(sizeof(pCentroid), "pCentroid"); + nmRegister(CENTROID_SIZE, "Centroid"); + nmRegister(sizeof(Pair), "Pair"); + + return; + } + +/** Scope cleanup. **/ +#undef ca_sparse_dif +#undef ca_sparse_dif_to_centroid diff --git a/centrallix-lib/src/mtask.c b/centrallix-lib/src/mtask.c index 9a167d724..2c6c80e43 100644 --- a/centrallix-lib/src/mtask.c +++ b/centrallix-lib/src/mtask.c @@ -41,7 +41,7 @@ /* Centrallix Application Server System */ /* Centrallix Base Library */ /* */ -/* Copyright (C) 1998-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* You may use these files and this library under the terms of the */ /* GNU Lesser General Public License, Version 2.1, contained in the */ @@ -54,13 +54,14 @@ /* Description: */ /* */ /* The MTASK Multithreading Tasking Module provides non-preemptive */ -/* threading services for Centrallix. It has been shown to be useable */ +/* threading services for Centrallix. It has been shown to be usable */ /* on a variety of platforms, although the values for MT_TASKSEP may */ /* sometimes need to be adjusted. This module does NOT provide for */ /* kernel threads or for preemptive threading. */ /************************************************************************/ +#include "expect.h" #include "newmalloc.h" #include "mtask.h" #include "xstring.h" @@ -694,7 +695,10 @@ int r_mtRun_PokeStack() { char buf[MT_MAX_STACK - MT_TASKSEP*2]; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wuninitialized" buf[0] = buf[0]; +#pragma GCC diagnostic pop return 0; } @@ -705,9 +709,15 @@ r_mtRun_Spacer() ** it needs to be in order for MTASK to work. DO NOT OPTIMIZE ** THIS MODULE!!!! The bogus assignment is added to keep gcc -Wall ** happy (and silent).... + ** + ** Edit (Israel): It wasn't very happy, so I added #pragma gcc directives + ** to silence this warning when compiling. **/ char buf[MT_TASKSEP]; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wuninitialized" buf[MT_TASKSEP-1] = buf[MT_TASKSEP-1]; +#pragma GCC diagnostic pop /*mprotect((char*)((int)(buf-MT_MAX_STACK+MT_TASKSEP*2+4095) & ~4095), MT_TASKSEP/2, PROT_NONE);*/ MTASK.CurrentThread->Stack = (unsigned char*)buf; @@ -730,9 +740,15 @@ r_mtRunStartFn() ** it needs to be in order for MTASK to work. DO NOT OPTIMIZE ** THIS MODULE!!!! The bogus assignment is added to keep gcc -Wall ** happy. + ** + ** Edit (Israel): It wasn't very happy, so I added #pragma gcc directives + ** to silence this warning when compiling. **/ char buf[MT_MAX_STACK]; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wuninitialized" buf[MT_MAX_STACK-1] = buf[MT_MAX_STACK-1]; +#pragma GCC diagnostic pop /*if (r_newidx < 0) return 0;*/ if (--r_newidx) r_mtRunStartFn(); @@ -2208,8 +2224,8 @@ thClearFlags(pThread thr, int flags) int thExcessiveRecursion() { - unsigned char buf[1]; - return (MTASK.CurrentThread->Stack - buf > MT_STACK_HIGHWATER); + const unsigned char stack_ptr[1]; + return UNLIKELY(MTASK.CurrentThread->Stack - stack_ptr > MT_STACK_HIGHWATER); } @@ -3407,7 +3423,7 @@ netGetRemotePort(pFile net_filedesc) } -/*** NETCONNECTTCP creats a client socket and connects it to a +/*** NETCONNECTTCP creates a client socket and connects it to a *** server on a given TCP service/port and host name. The flag *** NET_U_NOBLOCK causes the request to return immediately even *** if the connection is still trying to establish. Further @@ -4265,4 +4281,3 @@ syGetSem(pSemaphore sem, int cnt, int flags) return code; } - diff --git a/centrallix-lib/src/mtlexer.c b/centrallix-lib/src/mtlexer.c index 4c955993e..50f9d6b3b 100644 --- a/centrallix-lib/src/mtlexer.c +++ b/centrallix-lib/src/mtlexer.c @@ -7,6 +7,7 @@ #include #include #include + #include "newmalloc.h" #include "mtask.h" #include "mtlexer.h" @@ -904,7 +905,7 @@ mlxNextToken(pLxSession this) } else { - mssError(1,"MLX","Unexpected character encountered"); + mssError(1, "MLX", "Unexpected character encountered: '%c'", ch); this->TokType = MLX_TOK_ERROR; break; } diff --git a/centrallix-lib/src/mtsession.c b/centrallix-lib/src/mtsession.c index 015a00193..dc516e9f2 100644 --- a/centrallix-lib/src/mtsession.c +++ b/centrallix-lib/src/mtsession.c @@ -3,6 +3,7 @@ #include "cxlibconfig-internal.h" #endif #include +#include #include #include #include @@ -23,6 +24,7 @@ #include "xstring.h" #include "xhash.h" #include "strtcpy.h" +#include "util.h" /************************************************************************/ /* Centrallix Application Server System */ @@ -452,99 +454,87 @@ mssEndSession(pMtSession s) } -/*** mssError - Add an error message to the error stack, optionally - *** clearing the existing contents thereof. +/*** Displays error text to the user (but no stack trace). Does not exit the + *** program, allowing the calling function to fail, generating a cascade of + *** error messages which may provide useful info. + *** + *** @param clr Whether to clear the current error stack. As a rule of thumb, + *** if you are the first one to detect the error, clear the stack so that + *** other unrelated messages are not shown. If you are detecting an error + *** from another function that may also call an mssError() function, do + *** not clear the stack. + *** @param module The name or abbreviation of the module in which this + *** function is being called, to help developers narrow down the location + *** of the error. + *** @param format The format text for the error, which accepts any format + *** specifier that would be accepted by printf(). + *** @param ... Variables matching format specifiers in the format. ***/ -int -mssError(int clr, char* module, char* message, ...) +void +mssError_internal(int clr, char* module, char* file, int line, char* message, ...) { - va_list vl; - char* msg; - pMtSession s; - XString xs; - char* ptr; - char* cur_pos; - char* str; - int i; - char nbuf[16]; - char ch; - - /** Build the real error msg. **/ - xsInit(&xs); - cur_pos = message; - va_start(vl, message); - while((ptr = strchr(cur_pos, '%'))) - { - xsConcatenate(&xs, cur_pos, ptr - cur_pos); - switch(ptr[1]) - { - case '\0': - xsConcatenate(&xs, "%", 1); - cur_pos = ptr+1; - break; - case '%': - xsConcatenate(&xs, "%", 1); - cur_pos = ptr+2; - break; - case 's': - str = va_arg(vl, char*); - xsConcatenate(&xs, str?str:"(NULL)", -1); - cur_pos = ptr + 2; - break; - case 'c': - ch = va_arg(vl, int); - xsConcatenate(&xs, &ch, 1); - cur_pos = ptr + 2; - break; - case 'd': - i = va_arg(vl, int); - sprintf(nbuf,"%d",i); - xsConcatenate(&xs, nbuf, -1); - cur_pos = ptr + 2; - break; - default: - cur_pos = ptr + 2; - break; - } - } - va_end(vl); - if (*cur_pos) xsConcatenate(&xs, cur_pos, -1); - + char err_msg[BUFSIZ]; + unsigned int i = 0u; + + /** Prevent issues from interlacing this function with prints to stdout. **/ + check(fflush(stdout)); /* Failure ignored. */ + + /** Add line number to error message. **/ + i += snprintf(err_msg + i, sizeof(err_msg) - i, "%s:%d: ", file, line); + + /** Write the module to the start of the error message. */ + i += snprintf(err_msg + i, sizeof(err_msg) - i, "%s: ", module); + + /** Process the message format with all the same rules as printf(). **/ + va_list args; + va_start(args, message); + i += vsnprintf(err_msg + i, sizeof(err_msg) - i, message, args); + va_end(args); + /** Get current session **/ - s = (pMtSession)thGetParam(NULL,"mss"); - if (!s || MSS.LogAllErrors) + pMtSession s = thGetParam(NULL, "mss"); + const bool log_error = (s == NULL || MSS.LogAllErrors); + + /** Use standard logging without a session context, if needed. **/ + if (log_error) { - /*printf("mssError: Error occurred outside of session context.\n");*/ - if (!strcmp(MSS.LogMethod,"syslog")) + /** Use the requested logging method. **/ + if (strcmp(MSS.LogMethod, "syslog") == 0) { - if (!s) - syslog(LOG_ERR, "System: %s: %.256s\n", module, xs.String); + if (s == NULL) + syslog(LOG_ERR, "System: %.256s\n", err_msg); else - syslog(LOG_WARNING, "User '%s': %s: %.256s\n", s->UserName, module, xs.String); + syslog(LOG_WARNING, "User '%s': %.256s\n", s->UserName, err_msg); } - else if (!strcmp(MSS.LogMethod, "stdout")) + else if (strcmp(MSS.LogMethod, "stdout") == 0) { - printf("%s: %s: %s\n",MSS.AppName[0]?MSS.AppName:"error",module,xs.String); + printf("%s: %s\n", (MSS.AppName[0]) ? MSS.AppName : "error", err_msg); } - if (!s) return -1; } - - /** Need to clear? **/ - if (clr) mssClearError(); - - /** Allocate space and construct the error text. **/ - msg = (char*)nmSysMalloc(strlen(module)+strlen(xs.String)+3); - if (!msg) + + /** If a session is available, try to add the error to the error list. **/ + if (s != NULL) { - perror("mssError: Could not allocate error"); - printf("mssError: %s: %s\n",module,xs.String); - return -1; + /** Clear the error context, if requested. **/ + if (clr) check(mssClearError()); /* Failure ignored. */ + + /** Allocate space and construct the error text. **/ + const char* allocated_err_msg = check_ptr(nmSysStrdup(err_msg)); + if (allocated_err_msg == NULL) + { + fprintf(stderr, "Failed to store error message: %s\n", err_msg); + return; /* Give up. */ + } + + /** Store the error. **/ + if (check_neg(xaAddItem(&(s->ErrList), (void*)allocated_err_msg)) < 0) + { + fprintf(stderr, "Failed to add error message to session error list: %s\n", err_msg); + return; /* Give up. */ + } } - sprintf(msg,"%s: %s",module,xs.String); - xaAddItem(&(s->ErrList),(void*)msg); - xsDeInit(&xs); - - return 0; + + return; } @@ -836,4 +826,3 @@ mssGetParam(char* paramname) return p->Value; } - diff --git a/centrallix-lib/src/qprintf.c b/centrallix-lib/src/qprintf.c index 6827cc358..af9a435fb 100644 --- a/centrallix-lib/src/qprintf.c +++ b/centrallix-lib/src/qprintf.c @@ -1,22 +1,3 @@ -#ifdef HAVE_CONFIG_H -#include "cxlibconfig-internal.h" -#endif -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "qprintf.h" -#include "mtask.h" -#include "newmalloc.h" -#include "cxsec.h" -#include "util.h" -#include "expect.h" - /************************************************************************/ /* Centrallix Application Server System */ /* Centrallix Base Library */ @@ -30,14 +11,36 @@ /* Module: qprintf.c, qprintf.h */ /* Author: Greg Beeley (GRB) */ /* Creation: January 31, 2006 */ -/* Description: Quoting Printf routine, used to make sure that */ -/* injection type attacks don't occur when building */ -/* strings. These functions do not support some of the */ -/* more advanced (and dangerous) features of the normal */ -/* printf() library calls. */ -/* See centrallix-sysdoc/QPrintf.md for more information. */ +/* Description: Quoting Printf routine that helps to prevent injection */ +/* attacks when building strings. These functions also do */ +/* not support some of the more advanced (and dangerous) */ +/* features found in the standard printf() library. */ +/* See centrallix-sysdoc/QPrintf.md for more information. */ /************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_CONFIG_H + #include "cxlibconfig-internal.h" +#endif + +#include "cxsec.h" +#include "expect.h" +#include "mtask.h" +#include "newmalloc.h" +#include "qprintf.h" +#include "util.h" + /*** maximum # of externally-defined specifiers ***/ #define QPF_MAX_EXTS (64) @@ -56,7 +59,7 @@ #define QPF_SPEC_T_DBL (4) #define QPF_SPEC_T_NSTR (5) #define QPF_SPEC_T_CHR (6) -#define QPF_SPEC_T_LL (7) +#define QPF_SPEC_T_LL (7) #define QPF_SPEC_T_ENDSRC (7) /*** builtin filtering specifiers ***/ @@ -104,43 +107,47 @@ const char* qpf_spec_names[] = { NULL, /* 0 */ + + /** Source specifiers. **/ "INT", /* 1 */ - "STR", - "POS", - "DBL", - "nSTR", - "CHR", - "LL", - "QUOT", - "DQUOT", - "SYM", - "JSSTR", - "nLEN", - "WS", - "ESCWS", - "ESCSP", - "UNESC", - "SSYB", - "DSYB", - "FILE", - "PATH", - "HEX", - "DHEX", - "B64", - "DB64", - "RF", - "RR", - "HTENLBR", - "DHTE", - "URL", - "DURL", - "nLSET", - "nRSET", - "nZRSET", - "SQLARG", - "SQLSYM", + "STR", /* 2 */ + "POS", /* 3 */ + "DBL", /* 4 */ + "nSTR", /* 5 */ + "CHR", /* 6 */ + "LL", /* 7 */ + + /** Filter specifiers. **/ + "QUOT", /* 8 */ + "DQUOT", /* 9 */ + "SYM", /* 10 */ + "JSSTR", /* 11 */ + "nLEN", /* 12 */ + "WS", /* 13 */ + "ESCWS", /* 14 */ + "ESCSP", /* 15 */ + "UNESC", /* 16 */ + "SSYB", /* 17 */ + "DSYB", /* 18 */ + "FILE", /* 19 */ + "PATH", /* 20 */ + "HEX", /* 21 */ + "DHEX", /* 22 */ + "B64", /* 23 */ + "DB64", /* 24 */ + "RF", /* 25 */ + "RR", /* 26 */ + "HTENLBR", /* 27 */ + "DHTE", /* 28 */ + "URL", /* 29 */ + "DURL", /* 30 */ + "nLSET", /* 31 */ + "nRSET", /* 32 */ + "nZRSET", /* 33 */ + "SQLARG", /* 34 */ + "SQLSYM", /* 35 */ "HTDATA", /* 36 */ - "HTE", + "HTE", /* 37 */ "ESCQWS", /* 38 */ "ESCQ", /* 39 */ "CSSVAL", /* 40 */ @@ -151,6 +158,19 @@ qpf_spec_names[] = int qpf_spec_len[QPF_SPEC_T_MAXSPEC+1]; +/*** A QPConvTable expresses a way to translate characters. For example, if a + *** QPConvTable expresses how to escape quotes in a string, it might contain + *** mappings that turn `'` into `\'` and `"` into `\"`. + *** + *** @param Matrix A matrix of mappings where `Matrix[c]` is the character(s) + *** that character `c` should map to. Leave this NULL for characters that + *** do not need to be translated. Thus, in our example, `Matrix['"']` + *** should equal the string `"\""`, but `Matrix['a']` should be NULL. + *** @param MatrixLen The length of each string pointed to in `Matrix`. Thus, + *** in our example, `MatrixLen['"']` equal be 2. + *** @param MaxExpand The maximum number of characters that can result from + *** translating a single character. In our example, this would be 2. + ***/ typedef struct { char* Matrix[QPF_MATRIX_SIZE]; @@ -166,31 +186,82 @@ typedef struct char* ext_specs[QPF_MAX_EXTS]; int (*ext_fns[QPF_MAX_EXTS])(); char is_source[QPF_MAX_EXTS]; - QPConvTable quote_matrix; - QPConvTable quote_ws_matrix; - QPConvTable ws_matrix; - QPConvTable hte_matrix; - QPConvTable htenlbr_matrix; - QPConvTable hex_matrix; - QPConvTable url_matrix; - QPConvTable jsstr_matrix; - QPConvTable jsonstr_matrix; - QPConvTable cssval_matrix; - QPConvTable cssurl_matrix; - QPConvTable dsyb_matrix; - QPConvTable ssyb_matrix; + + /** Conversion matrices for various tasks. **/ + QPConvTable quote_matrix; /* Escapes quotes. */ + QPConvTable quote_ws_matrix; /* Escapes quotes & whitespace. */ + QPConvTable ws_matrix; /* Escapes whitespace. */ + QPConvTable hte_matrix; /* Escapes characters for HTML. */ + QPConvTable htenlbr_matrix; /* Escapes characters and line breaks for HTML. */ + QPConvTable cssval_matrix; /* Escapes CSS values. */ + QPConvTable cssurl_matrix; /* Escapes CSS URLs. */ + QPConvTable jsstr_matrix; /* Escapes JavaScript strings. */ + QPConvTable jsonstr_matrix; /* Escapes JSON strings. */ + QPConvTable hex_matrix; /* Encodes strings into HEX. */ + QPConvTable url_matrix; /* Encodes URLs. */ + QPConvTable dsyb_matrix; /* Escapes double quotes sybase-style: " -> "" */ + QPConvTable ssyb_matrix; /* Escapes single quotes sybase-style: ' -> '' */ } QPF_t; static QPF_t QPF = { n_ext:0, is_init:0 }; +/** TODO: Israel - Move to util.h after dups branch is merged. **/ +/*** Count the number of 0s until the first 1. If we pass 16 (aka. `1<<4`), + *** for example, the function returns 4. Useful for converting bitmask + *** values to array indices. + *** + *** @param n The number to be queried. + *** @returns The trailing zero count. + ***/ +static unsigned int +qpf_internal_count_zeros(int n) + { + int shift = 0; + + if (UNLIKELY(n == 0)) return 0; + while ((n & 1) == 0) + { + n >>= 1; + shift++; + } + + return shift; + } + +#define QPERR(err) ({ \ + const unsigned int _err = (err); \ + const unsigned int _err_i = qpf_internal_count_zeros(_err); \ + assert(_err_i < QPF_ERR_COUNT); \ + if (__LINE__ > USHRT_MAX) \ + fprintf(stderr, "Failed to save code location %s:%d: File too long!!\n", __FILE__, __LINE__); \ + s->Errors |= _err; \ + s->ErrorLines[_err_i] = __LINE__; \ + }) + +/*** Searches for a substring within a buffer. + *** Uses an optimized two-step approach: first locate the first character + *** using `memchr()`, then verify the full match with `memcmp()`. + *** + *** @param haystack The buffer to search. + *** @param haystacklen The length of the haystack buffer (in bytes). + *** @param needle The substring for which to search. + *** @param needlelen The length of the needle substring (in bytes). + *** @return The byte offset to the first instance of `needle` in `haystack`, + *** or -1 if not found (including when needlelen exceeds haystacklen). + *** Returns 0 if needlelen is 0 (empty needle matches at haystack). + ***/ int qpf_internal_FindStr(const char* haystack, size_t haystacklen, const char* needle, size_t needlelen) { int pos; char* ptr; - if (needlelen > haystacklen) return -1; - if (needlelen == 0) return 0; + + /** Edge cases. **/ + if (UNLIKELY(needlelen > haystacklen)) return -1; + if (UNLIKELY(needlelen == 0)) return 0; + + /** Search for the needle. **/ pos = 0; while(pos <= haystacklen - needlelen) { @@ -200,25 +271,34 @@ qpf_internal_FindStr(const char* haystack, size_t haystacklen, const char* needl return (ptr - haystack); pos = (ptr - haystack) + 1; } + + /** Not found. **/ return -1; } - +/*** Set up data in a conversion matrix (aka. table) data structure, called + *** after the matrix values have been set. + *** + *** @param table The conversion matrix table data structure to set up. + *** @returns 0 if successful, or -1 if an error occurs. + ***/ int qpf_internal_SetupTable(pQPConvTable table) { - int i; - int mx = 1; - int n; + size_t mx = 1; - for(i=0;iMatrix[i]) - { - n = strlen(table->Matrix[i]); - if (n > mx) mx = n; - table->MatrixLen[i] = n; - } + /*** Skip characters that don't map to different characters. + *** LIKELY because most translation tables do not include + *** most characters. + ***/ + if (LIKELY(table->Matrix[i] == NULL)) continue; + + /** Compute the length of the string that this char maps to. **/ + const size_t n = strlen(table->Matrix[i]); + if (n > mx) mx = n; + table->MatrixLen[i] = n; } table->MaxExpand = mx; @@ -226,21 +306,25 @@ qpf_internal_SetupTable(pQPConvTable table) } -/*** qpfInitialize() - inits the QPF suite. +/*** Initialize internal data structures for the QPF module. + *** + *** @returns 0 if successful, or -1 if an error occurs. ***/ int -qpfInitialize() +qpfInitialize(void) { int i; char buf[4]; char hex[] = "0123456789abcdef"; + /** Initialize matrix that: Escapes quotes. **/ memset(&QPF.quote_matrix, 0, sizeof(QPF.quote_matrix)); QPF.quote_matrix.Matrix['\''] = "\\'"; QPF.quote_matrix.Matrix['"'] = "\\\""; QPF.quote_matrix.Matrix['\\'] = "\\\\"; qpf_internal_SetupTable(&QPF.quote_matrix); + /** Initialize matrix that: Escapes quotes & whitespace. **/ memset(&QPF.quote_ws_matrix, 0, sizeof(QPF.quote_ws_matrix)); QPF.quote_ws_matrix.Matrix['\''] = "\\'"; QPF.quote_ws_matrix.Matrix['"'] = "\\\""; @@ -250,58 +334,14 @@ qpfInitialize() QPF.quote_ws_matrix.Matrix['\r'] = "\\r"; qpf_internal_SetupTable(&QPF.quote_ws_matrix); - memset(&QPF.jsstr_matrix, 0, sizeof(QPF.jsstr_matrix)); - QPF.jsstr_matrix.Matrix['\''] = "\\'"; - QPF.jsstr_matrix.Matrix['"'] = "\\\""; - QPF.jsstr_matrix.Matrix['\\'] = "\\\\"; - QPF.jsstr_matrix.Matrix['/'] = "\\/"; - QPF.jsstr_matrix.Matrix['\n'] = "\\n"; - QPF.jsstr_matrix.Matrix['\t'] = "\\t"; - QPF.jsstr_matrix.Matrix['\r'] = "\\r"; - QPF.jsstr_matrix.Matrix['\b'] = "\\b"; - QPF.jsstr_matrix.Matrix['\f'] = "\\f"; - for(i=0;i<=31;i++) - { - if (!QPF.jsstr_matrix.Matrix[i]) - { - QPF.jsstr_matrix.Matrix[i] = nmSysMalloc(7); - snprintf(QPF.jsstr_matrix.Matrix[i], 7, "\\u%4.4X", i); - } - } - qpf_internal_SetupTable(&QPF.jsstr_matrix); - - memset(&QPF.jsonstr_matrix, 0, sizeof(QPF.jsonstr_matrix)); - QPF.jsonstr_matrix.Matrix['"'] = "\\\""; - QPF.jsonstr_matrix.Matrix['\\'] = "\\\\"; - QPF.jsonstr_matrix.Matrix['\n'] = "\\n"; - QPF.jsonstr_matrix.Matrix['\t'] = "\\t"; - QPF.jsonstr_matrix.Matrix['\r'] = "\\r"; - QPF.jsonstr_matrix.Matrix['\b'] = "\\b"; - QPF.jsonstr_matrix.Matrix['\f'] = "\\f"; - for(i=0;i<=31;i++) - { - if (!QPF.jsonstr_matrix.Matrix[i]) - { - QPF.jsonstr_matrix.Matrix[i] = nmSysMalloc(7); - snprintf(QPF.jsonstr_matrix.Matrix[i], 7, "\\u%4.4X", i); - } - } - qpf_internal_SetupTable(&QPF.jsonstr_matrix); - + /** Initialize matrix that: Escapes whitespace. **/ memset(&QPF.ws_matrix, 0, sizeof(QPF.ws_matrix)); QPF.ws_matrix.Matrix['\n'] = "\\n"; QPF.ws_matrix.Matrix['\t'] = "\\t"; QPF.ws_matrix.Matrix['\r'] = "\\r"; qpf_internal_SetupTable(&QPF.ws_matrix); - memset(&QPF.dsyb_matrix, 0, sizeof(QPF.dsyb_matrix)); - QPF.dsyb_matrix.Matrix['"'] = "\"\""; - qpf_internal_SetupTable(&QPF.dsyb_matrix); - - memset(&QPF.ssyb_matrix, 0, sizeof(QPF.ssyb_matrix)); - QPF.ssyb_matrix.Matrix['\''] = "''"; - qpf_internal_SetupTable(&QPF.ssyb_matrix); - + /** Initialize matrix that: Escapes characters for HTML. **/ memset(&QPF.hte_matrix, 0, sizeof(QPF.hte_matrix)); QPF.hte_matrix.Matrix['<'] = "<"; QPF.hte_matrix.Matrix['>'] = ">"; @@ -313,6 +353,7 @@ qpfInitialize() QPF.hte_matrix.Matrix['\0'] = "�"; qpf_internal_SetupTable(&QPF.hte_matrix); + /** Initialize matrix that: Escapes characters and line breaks for HTML. **/ memset(&QPF.htenlbr_matrix, 0, sizeof(QPF.htenlbr_matrix)); QPF.htenlbr_matrix.Matrix['<'] = "<"; QPF.htenlbr_matrix.Matrix['>'] = ">"; @@ -325,6 +366,7 @@ qpfInitialize() QPF.htenlbr_matrix.Matrix['\n'] = "
"; qpf_internal_SetupTable(&QPF.htenlbr_matrix); + /** Initialize matrix that: Escapes CSS values. **/ memset(&QPF.cssval_matrix, 0, sizeof(QPF.cssval_matrix)); QPF.cssval_matrix.Matrix[';'] = "\\;"; QPF.cssval_matrix.Matrix['}'] = "\\}"; @@ -337,6 +379,7 @@ qpfInitialize() QPF.cssval_matrix.Matrix['\''] = "\\'"; qpf_internal_SetupTable(&QPF.cssval_matrix); + /** Initialize matrix that: Escapes CSS URLs. **/ memset(&QPF.cssurl_matrix, 0, sizeof(QPF.cssurl_matrix)); QPF.cssurl_matrix.Matrix[';'] = "\\;"; QPF.cssurl_matrix.Matrix['}'] = "\\}"; @@ -356,6 +399,47 @@ qpfInitialize() QPF.cssurl_matrix.Matrix['\r'] = "\\\r"; qpf_internal_SetupTable(&QPF.cssurl_matrix); + /** Initialize matrix that: Escapes JavaScript strings. **/ + memset(&QPF.jsstr_matrix, 0, sizeof(QPF.jsstr_matrix)); + QPF.jsstr_matrix.Matrix['\''] = "\\'"; + QPF.jsstr_matrix.Matrix['"'] = "\\\""; + QPF.jsstr_matrix.Matrix['\\'] = "\\\\"; + QPF.jsstr_matrix.Matrix['/'] = "\\/"; + QPF.jsstr_matrix.Matrix['\n'] = "\\n"; + QPF.jsstr_matrix.Matrix['\t'] = "\\t"; + QPF.jsstr_matrix.Matrix['\r'] = "\\r"; + QPF.jsstr_matrix.Matrix['\b'] = "\\b"; + QPF.jsstr_matrix.Matrix['\f'] = "\\f"; + for(i=0;i<=31;i++) + { + if (!QPF.jsstr_matrix.Matrix[i]) + { + QPF.jsstr_matrix.Matrix[i] = nmSysMalloc(7); + snprintf(QPF.jsstr_matrix.Matrix[i], 7, "\\u%4.4X", i); + } + } + qpf_internal_SetupTable(&QPF.jsstr_matrix); + + /** Initialize matrix that: Escapes JSON strings. **/ + memset(&QPF.jsonstr_matrix, 0, sizeof(QPF.jsonstr_matrix)); + QPF.jsonstr_matrix.Matrix['"'] = "\\\""; + QPF.jsonstr_matrix.Matrix['\\'] = "\\\\"; + QPF.jsonstr_matrix.Matrix['\n'] = "\\n"; + QPF.jsonstr_matrix.Matrix['\t'] = "\\t"; + QPF.jsonstr_matrix.Matrix['\r'] = "\\r"; + QPF.jsonstr_matrix.Matrix['\b'] = "\\b"; + QPF.jsonstr_matrix.Matrix['\f'] = "\\f"; + for(i=0;i<=31;i++) + { + if (!QPF.jsonstr_matrix.Matrix[i]) + { + QPF.jsonstr_matrix.Matrix[i] = nmSysMalloc(7); + snprintf(QPF.jsonstr_matrix.Matrix[i], 7, "\\u%4.4X", i); + } + } + qpf_internal_SetupTable(&QPF.jsonstr_matrix); + + /** Initialize matrix that: Encodes strings into HEX. **/ for(i=0;i>4)&0x0F]; @@ -365,7 +449,7 @@ qpfInitialize() } qpf_internal_SetupTable(&QPF.hex_matrix); - /* set up table for url encoding everything except 0-9, A-Z, and a-z */ + /** Initialize matrix that: Encodes URLs (everything except 0-9, A-Z, and a-z). **/ memset(&QPF.url_matrix, 0, sizeof(QPF.url_matrix)); for(i=0;i<48;i++) /* escape until 0-9 */ { @@ -401,42 +485,60 @@ qpfInitialize() } qpf_internal_SetupTable(&QPF.url_matrix); - + /** Initialize matrix that: Escapes double quotes sybase-style: " -> "". **/ + memset(&QPF.dsyb_matrix, 0, sizeof(QPF.dsyb_matrix)); + QPF.dsyb_matrix.Matrix['"'] = "\"\""; + qpf_internal_SetupTable(&QPF.dsyb_matrix); + + /** Initialize matrix that: Escapes single quotes sybase-style: ' -> ''. **/ + memset(&QPF.ssyb_matrix, 0, sizeof(QPF.ssyb_matrix)); + QPF.ssyb_matrix.Matrix['\''] = "''"; + qpf_internal_SetupTable(&QPF.ssyb_matrix); + + /** Initialize the qpf_spec_len array. **/ for(i=0;i<=QPF_SPEC_T_MAXSPEC;i++) { if (qpf_spec_names[i]) qpf_spec_len[i] = strlen(qpf_spec_names[i]); } + /** Done. **/ QPF.is_init = 1; return 0; } -/*** qpfOpenSession() - open a new qprintf session. The session is used for - *** storing cumulative error information, to make error handling much cleaner - *** for any caller that wants to do so. +/*** Open a new qprintf session. + *** The session is used for storing cumulative error information which makes + *** error handling much cleaner for callers that use this feature. + *** + *** @returns A new, initialized pQPSession object, or NULL if an error occurs. ***/ pQPSession -qpfOpenSession() +qpfOpenSession(void) { pQPSession s = NULL; - if (!QPF.is_init) - { - if (qpfInitialize() < 0) return NULL; - } + /** Ensure initialization. **/ + if (UNLIKELY(!QPF.is_init) && UNLIKELY(qpfInitialize() < 0)) return NULL; + /** Allocate and initialize the new session. **/ s = (pQPSession)nmMalloc(sizeof(QPSession)); - if (!s) return NULL; + if (UNLIKELY(!s)) return NULL; s->Errors = 0; + memset(s->ErrorLines, 0, sizeof(s->ErrorLines)); return s; } -/*** qpfErrors() - return the current error mask. +/*** Queries the current error mask, indicating errors that have occurred since + *** when the session was initialized or the last time that `qpfClearErrors()` + *** was called. + *** + *** @param s The session to be queried. + *** @returns The current error mask (does not fail). ***/ unsigned int qpfErrors(pQPSession s) @@ -444,18 +546,75 @@ qpfErrors(pQPSession s) return s->Errors; } +/*** Gives the error name for a qprintf error. + *** + *** @param error A bitmask for a single error. + *** @return A string with the name for that error. + ***/ +static const char* +qpf_internal_getErrorName(unsigned int error) + { + switch (error) + { + case QPF_ERR_T_NOTIMPL: return "Not Implemented"; + case QPF_ERR_T_BUFOVERFLOW: return "Buffer Overflow"; + case QPF_ERR_T_INSOVERFLOW: return "Limit Overflow"; + case QPF_ERR_T_NOTPOSITIVE: return "Not Positive"; + case QPF_ERR_T_BADSYMBOL: return "Bad Symbol"; + case QPF_ERR_T_MEMORY: return "Out of Memory"; + case QPF_ERR_T_BADLENGTH: return "Bad Length"; + case QPF_ERR_T_BADFORMAT: return "Bad Format"; + case QPF_ERR_T_RESOURCE: return "Resource Exhaustion"; + case QPF_ERR_T_NULL: return "Null Parameter"; + case QPF_ERR_T_INTERNAL: return "Internal Error"; + case QPF_ERR_T_BADFILE: return "Bad File Name"; + case QPF_ERR_T_BADPATH: return "Bad File Path"; + case QPF_ERR_T_BADCHAR: return "Bad Character"; + default: return "Unknown or mixed error"; + } + } + +/*** Prints a message to stderr containing all the errors that occurred in the + *** specified session. If no errors have occurred, print nothing. + ***/ +void +qpfLogErrors(pQPSession s) + { + if (s == NULL) return; + unsigned int errors = s->Errors; + if (errors == 0) return; + + fprintf(stderr, "qprintf() errors:\n"); + for (unsigned int i = 0u; i < QPF_ERR_COUNT; i++) + { + const unsigned int err = (1 << i); + if (errors & err) + { + const char* error_name = qpf_internal_getErrorName(err); + const unsigned int line_number = s->ErrorLines[i]; + fprintf(stderr, "- %d: %s (%s:%d)\n", err, error_name, __FILE__, line_number); + } + } + } -/*** qpfClearErrors() - reset the errors mask. +/*** Reset the errors mask, clearing all errors that have occurred. + *** + *** @param s The session holding the error mask to be cleared. + *** @returns 0 if successful, or -1 if an error occurs. ***/ int qpfClearErrors(pQPSession s) { s->Errors = 0; + memset(&s->ErrorLines, 0, sizeof(s->ErrorLines)); return 0; } -/*** qpfCloseSession() - close a qprintf session. +/*** Closes a qprintf session and deallocates associated resources. + *** + *** @param s The session to close. + *** @returns 0 if successful, or -1 if an error occurs. ***/ int qpfCloseSession(pQPSession s) @@ -467,19 +626,23 @@ qpfCloseSession(pQPSession s) } -/*** qpf_internal_itoa() - convert an integer into a string representation. - *** this seems to perform better than snprintf(%d), even without - *** optimization enabled. +/*** Convert an integer into a string representation. Seems to perform better + *** than `snprintf("%d")`, even without optimization enabled. + *** + *** @param dst The destination string buffer. + *** @param dstlen The allocated length of the string buffer, used to avoid + *** buffer overflows. + *** @param i The value to be written. + *** @returns The number of characters written to the buffer. ***/ -static inline int +static inline size_t qpf_internal_itoa(char* dst, size_t dstlen, int i) { char ibuf[sizeof(int)*8*3/10+4]; char* iptr = ibuf; int r; int i2 = i; - int rval; - if (i2 == 0) + if (UNLIKELY(i2 == 0)) { *(iptr++) = '0'; } @@ -493,7 +656,7 @@ qpf_internal_itoa(char* dst, size_t dstlen, int i) } if (i < 0) *(iptr++) = '-'; } - rval = iptr - ibuf; + const size_t rval = iptr - ibuf; while(iptr > ibuf && dstlen > 1) { *(dst++) = *(--iptr); @@ -504,7 +667,21 @@ qpf_internal_itoa(char* dst, size_t dstlen, int i) } -/*** qpf_internal_base64encode() - convert string to base 64 representation +/*** Convert string to its base 64 representation. + *** This function is the reverse of `qpf_internal_base64decode()`. + *** + *** @param s The qprintf session in use. + *** @param src The source string to convert. + *** @param src_size The length of `src` in bytes. + *** @param dst A pointer to a location where the resulting string pointer + *** should be saved. + *** @param dst_size The size of the currently allocated string at `dst`. + *** @param dst_offset The number of bytes to skip at the start of `dst` + *** before writing the result. + *** @param grow_fn An optional grow function, used to grow the dst string if + *** more space is needed. + *** @param grow_arg The argument to be passed to the grow function. + *** @returns The number of bytes written. ***/ int qpf_internal_base64encode(pQPSession s, const char* src, size_t src_size, char** dst, size_t* dst_size, size_t* dst_offset, qpf_grow_fn_t grow_fn, void* grow_arg) @@ -570,7 +747,22 @@ qpf_internal_base64encode(pQPSession s, const char* src, size_t src_size, char** } -/*** qpf_internal_base64decode() - convert base 64 to a string representation +/*** Convert the base 64 representation of a string back to the original + *** string. + *** This function is the reverse of `qpf_internal_base64encode()`. + *** + *** @param s The qprintf session in use. + *** @param src The source string representation to convert. + *** @param src_size The length of `src` in bytes. + *** @param dst A pointer to a location where the resulting string pointer + *** should be saved. + *** @param dst_size The size of the currently allocated string at `dst`. + *** @param dst_offset The number of bytes to skip at the start of `dst` + *** before writing the result. + *** @param grow_fn An optional grow function, used to grow the dst string if + *** more space is needed. + *** @param grow_arg The argument to be passed to the grow function. + *** @returns The number of bytes written. ***/ static inline int qpf_internal_base64decode(pQPSession s, const char* src, size_t src_size, char** dst, size_t* dst_size, size_t* dst_offset, qpf_grow_fn_t grow_fn, void* grow_arg) @@ -582,7 +774,7 @@ qpf_internal_base64decode(pQPSession s, const char* src, size_t src_size, char** int req_size = (.75 * src_size) + *dst_offset + 1; /** fmul could truncate when cast to int hence +1 **/ /** Verify source data is correct length for base 64 **/ - if (src_size % 4 != 0) + if (UNLIKELY(src_size % 4 != 0)) { QPERR(QPF_ERR_T_BADCHAR); return -1; @@ -605,7 +797,7 @@ qpf_internal_base64decode(pQPSession s, const char* src, size_t src_size, char** { /** First 6 bits. **/ ptr = strchr(b64,src[0]); - if (!ptr || !*ptr) + if (UNLIKELY(!ptr || !*ptr)) { QPERR(QPF_ERR_T_BADCHAR); return -1; @@ -615,7 +807,7 @@ qpf_internal_base64decode(pQPSession s, const char* src, size_t src_size, char** /** Second six bits are split between cursor[0] and cursor[1] **/ ptr = strchr(b64,src[1]); - if (!ptr || !*ptr) + if (UNLIKELY(!ptr || !*ptr)) { QPERR(QPF_ERR_T_BADCHAR); return -1; @@ -631,7 +823,7 @@ qpf_internal_base64decode(pQPSession s, const char* src, size_t src_size, char** break; } ptr = strchr(b64,src[2]); - if (!ptr || !*ptr) + if (UNLIKELY(!ptr || !*ptr)) { QPERR(QPF_ERR_T_BADCHAR); return -1; @@ -647,7 +839,7 @@ qpf_internal_base64decode(pQPSession s, const char* src, size_t src_size, char** break; } ptr = strchr(b64,src[3]); - if (!ptr || !*ptr) + if (UNLIKELY(!ptr || !*ptr)) { QPERR(QPF_ERR_T_BADCHAR); return -1; @@ -664,7 +856,21 @@ qpf_internal_base64decode(pQPSession s, const char* src, size_t src_size, char** } -/*** qpf_internal_hexdecode() - convert base 64 to a string representation +/*** Convert the base 16 (hex) representation of a string back to the + *** original string. + *** + *** @param s The qprintf session in use. + *** @param src The source string representation to convert. + *** @param src_size The length of `src` in bytes. + *** @param dst A pointer to a location where the resulting string pointer + *** should be saved. + *** @param dst_size The size of the currently allocated string at `dst`. + *** @param dst_offset The number of bytes to skip at the start of `dst` + *** before writing the result. + *** @param grow_fn An optional grow function, used to grow the dst string if + *** more space is needed. + *** @param grow_arg An argument, passed to`grow_fn`() when it is called. + *** @returns The number of bytes written. ***/ static inline int qpf_internal_hexdecode(pQPSession s, const char* src, size_t src_size, char** dst, size_t* dst_size, size_t* dst_offset, qpf_grow_fn_t grow_fn, void* grow_arg) @@ -675,10 +881,10 @@ qpf_internal_hexdecode(pQPSession s, const char* src, size_t src_size, char** ds char* cursor; int ix; int req_size; - char* orig_src = src; + const char* orig_src = src; /** Required size **/ - if (src_size%2 == 1) + if (UNLIKELY(src_size%2 == 1)) { QPERR(QPF_ERR_T_BADLENGTH); return -1; @@ -702,7 +908,7 @@ qpf_internal_hexdecode(pQPSession s, const char* src, size_t src_size, char** ds { /** First 4 bits. **/ ptr = strchr(hex, src[0]); - if (!ptr) + if (UNLIKELY(!ptr)) { QPERR(QPF_ERR_T_BADCHAR); return -1; @@ -712,7 +918,7 @@ qpf_internal_hexdecode(pQPSession s, const char* src, size_t src_size, char** ds /** Second four bits **/ ptr = strchr(hex, src[1]); - if (!ptr) + if (UNLIKELY(!ptr)) { QPERR(QPF_ERR_T_BADCHAR); return -1; @@ -730,8 +936,55 @@ qpf_internal_hexdecode(pQPSession s, const char* src, size_t src_size, char** ds } -/*** qpfPrintf() - do the quoting printf operation, given a standard vararg - *** function call. +/*** Returns the amount of additional size that will fit, but does not + *** reallocate the string to grow it to a larger size. + *** + *** Useful for passing to qpfPrintf_g*() functions. + ***/ +int +qpfNoGrow(char** str, size_t* size, size_t offs, void* arg, size_t req_size) + { + return (*size) >= req_size; + } + + +/*** Assumes the string buffer was allocated using nmSysMalloc() and grows the + *** buffer as needed to reach the requested size. Does not use grow args. + *** + *** Useful for passing to qpfPrintf_g*() functions. + ***/ +int +qpfSysMallocGrow(char** str, size_t* size, size_t offset, void* args, size_t req_size) + { + /** Handle edge cases. **/ + if (UNLIKELY(str == NULL || *str == NULL || size == NULL)) return 0; + if (*size >= req_size) return 1; + if (UNLIKELY(*size == 0)) *size = 1; + + /** Determine new size. **/ + size_t new_size = *size; + do new_size *= 2; + while (new_size < req_size); + + /** Reallocate. **/ + char* new_str = nmSysRealloc(*str, new_size); + if (UNLIKELY(new_str == NULL)) return 0; + *str = new_str; + *size = new_size; + + /** Success. **/ + return 1; + } + + +/*** Print formatted text using the qprintf formatting. + *** + *** @param s The qprintf session in use. + *** @param str The destination string buffer for printed data. + *** @param strsize The length of `str` in bytes. + *** @param format The qprintf format to follow when printing data. + *** @param ... A variable arguments list used to populate the format. + *** @returns The number of chars written. ***/ int qpfPrintf(pQPSession s, char* str, size_t size, const char* format, ...) @@ -748,696 +1001,810 @@ qpfPrintf(pQPSession s, char* str, size_t size, const char* format, ...) } -/*** qpfPrintf_grow() - returns whether the additional size will fit. + +/*** Print formatted text using the qprintf formatting. + *** + *** @param s The qprintf session in use. + *** @param str The destination string buffer for printed data. + *** @param strsize The length of `str` in bytes. + *** @param format The qprintf format to follow when printing data. + *** @param ap A variable arguments list used to populate the format. + *** @returns The number of chars written. + ***/ +int +qpfPrintf_va(pQPSession s, char* str, size_t size, const char* format, va_list ap) + { + return qpfPrintf_gva(s, &str, &size, qpfNoGrow, NULL, format, ap); + } + + +/*** Print formatted text using the qprintf formatting, using provided the + *** grow function to reallocate the provided string as needed. + *** + *** @param s The qprintf session in use. + *** @param str A pointer to the destination string buffer for printed data, + *** which might be reallocated by `grow_fn`, if it is called. + *** @param strsize A pointer to the length value of `str` in bytes, which + *** might be updated by `grow_fn` if it reallocates `str` to a new size. + *** @param grow_fn The grow function called if `str` is not large enough. + *** @param grow_arg The void* argument passed to `grow_fn`. + *** @param format The qprintf format to follow when printing data. + *** @param ... A variable arguments list used to populate the format. + *** @returns The number of chars written. ***/ int -qpfPrintf_grow(char** str, size_t* size, size_t offs, void* arg, size_t req_size) +qpfPrintf_g(pQPSession s, char** str, size_t* size, qpf_grow_fn_t grow_fn, void* grow_arg, const char* format, ...) { - return (*size) >= req_size; + va_list va; + int rval; + + /** Grab the va ptr and call the _va version **/ + va_start(va, format); + rval = qpfPrintf_gva(s, str, size, grow_fn, grow_arg, format, va); + va_end(va); + + return rval; } -/*** qpfPrintf_va() - same as qpfPrintf(), but takes a va_list instead of - *** a list of arguments. +/*** Print formatted text using the qprintf formatting, using provided the + *** grow function to reallocate the provided string as needed. + *** + *** @param s The qprintf session in use. + *** @param str A pointer to the destination string buffer for printed data, + *** which might be reallocated by `grow_fn`, if it is called. + *** @param strsize A pointer to the length value of `str` in bytes, which + *** might be updated by `grow_fn` if it reallocates `str` to a new size. + *** @param grow_fn The grow function called if `str` is not large enough. + *** @param grow_arg The void* argument passed to `grow_fn`. + *** @param format The qprintf format to follow when printing data. + *** @param ap A variable arguments list used to populate the format. + *** @returns The number of chars written. ***/ -int -qpfPrintf_va(pQPSession s, char* str, size_t size, const char* format, va_list ap) +int +qpfPrintf_gva(pQPSession s, char** str, size_t* size, qpf_grow_fn_t grow_fn, void* grow_arg, const char* format, va_list ap) { - return qpfPrintf_va_internal(s, &str, &size, qpfPrintf_grow, NULL, format, ap); + return qpfPrintf_va_internal(s, str, size, grow_fn, grow_arg, format, ap); } -/*** qpf_internal_Translate() - do a translation copy from one buffer to - *** another, respecting a soft and hard limit, and using a given char - *** translation table. Returns the number of chars that would have been - *** placed in dstbuf had there been enough room (or, if there was enough - *** room, the actual # chars placed in dstbuf); NOTE - does NOT return - *** the number of chars pulled from the srcbuf!!! +/*** Copy characters to the new buffer, translating each character using the + *** given pQPConvTable. Respects a soft and hard limit. + *** + *** I'm not sure what this stuff about the "soft and hard limit" means, but + *** I think it has something do do with the `limit` parameter. I'm also not + *** sure why that parameter is necessary. (Israel, 2026) *** - *** min_room applies to 'dstsize' only -- we must leave at least that much - *** room in dstbuf once we are done. Often this is "1", to leave room for - *** a null terminator, or "2" to leave room for a closing quote mark and a - *** null terminator, for instance. + *** @param s The qprintf session in use. + *** @param srcbuf The source string representation to translate and copy. + *** @param srcsize The length of `srcbuf` in bytes. + *** @param dstbuf A pointer to a location where the resulting string pointer + *** should be saved. + *** @param dstoffs The number of bytes to skip at the start of `dstbuf` + *** before writing the result. + *** @param dstsize The size of the currently allocated string at `dstbuf`. + *** @param limit The maximum amount that the destination string can grow past + *** the size of the source string. Causes an `QPF_ERR_T_INSOVERFLOW` + *** error if this limit is exceeded. + *** @param table The translation table to apply when copying each character. + *** @param grow_fn An optional grow function, used to grow the dst string if + *** more space is needed. + *** @param grow_arg An argument, passed to`grow_fn`() when it is called. + *** @param min_room A required amount of space that must be available at the + *** end of the destination buffer when the function completes. Often this is + *** `1`, to leave room for a null terminator, or `2` to leave room for a + *** closing quote mark followed by a null terminator. + *** @returns The number of chars placed in dstbuf (or the number that would + *** have been placed if there was enough room), or -1 if an error occurs. + *** Note: Does NOT return the number of chars pulled from the srcbuf!!! ***/ static inline int -qpf_internal_Translate(pQPSession s, const char* srcbuf, size_t srcsize, char** dstbuf, size_t* dstoffs, size_t* dstsize, size_t limit, pQPConvTable table, qpf_grow_fn_t grow_fn, void* grow_arg, size_t min_room) - { - int rval = 0; - unsigned int tlen; - int i; - char* trans; - int nogrow = (grow_fn == NULL); - - if (srcsize >= SIZE_MAX/2/table->MaxExpand) +qpf_internal_Translate( + pQPSession s, + const char* srcbuf, + size_t srcsize, + char** dstbuf, + size_t* dstoffs, + size_t* dstsize, + size_t limit, + pQPConvTable table, + qpf_grow_fn_t grow_fn, + void* grow_arg, + size_t min_room +) { + int n_chars_written = 0; + bool no_grow = (grow_fn == NULL); + + /** Check for sources that are FAR too large for us to handle. **/ + if (UNLIKELY(srcsize >= (SIZE_MAX / 2) / table->MaxExpand)) return -1; - - if (srcsize) + + if (LIKELY(srcsize != 0)) { - rval += srcsize; - if ((srcsize*table->MaxExpand) <= limit && (srcsize*table->MaxExpand + min_room) <= (*dstsize - *dstoffs)) + n_chars_written += srcsize; + const size_t max_chars_to_write = srcsize * table->MaxExpand; + const size_t max_chars_needed = max_chars_to_write + min_room; + const size_t chars_available = *dstsize - *dstoffs; + if (max_chars_to_write <= limit && max_chars_needed <= chars_available) { /** Easy route - definitely enough space! **/ - for(i=0;iMatrix[(unsigned char)(srcbuf[i])]) != NULL))) - { - tlen = table->MatrixLen[(unsigned char)(srcbuf[i])]; - while(*trans) (*dstbuf)[(*dstoffs)++] = *(trans++); - rval += (tlen-1); - } - else + const unsigned char c = (unsigned char)(srcbuf[i]); + const char* translated_chars = table->Matrix[c]; + + /*** Check if the translation table specifies to do nothing to this character. + *** LIKELY because most translation tables do not include most characters. + ***/ + if (LIKELY(translated_chars == NULL)) { (*dstbuf)[(*dstoffs)++] = srcbuf[i]; + continue; } + + /** Write the translated characters to the destination buffer. **/ + const unsigned int translated_length = table->MatrixLen[c]; + while(*translated_chars) (*dstbuf)[(*dstoffs)++] = *(translated_chars++); + n_chars_written += (translated_length-1); } } else { /** Hard route - may or may not be enough space! **/ - for(i=0;iMatrix[(unsigned char)(srcbuf[i])]) != NULL))) + const unsigned char c = (unsigned char)(srcbuf[i]); + const char* translated_chars = table->Matrix[c]; + const unsigned int translated_length = (LIKELY(translated_chars == NULL)) ? 1 : table->MatrixLen[c]; + + if (UNLIKELY(translated_length > limit)) { - tlen = table->MatrixLen[(unsigned char)(srcbuf[i])]; - if (LIKELY(limit >= tlen)) - { - rval += (tlen-1); - if (LIKELY(!nogrow) && (LIKELY((*dstoffs)+tlen+min_room <= (*dstsize)) || - (grow_fn(dstbuf, dstsize, *dstoffs, grow_arg, (*dstoffs)+tlen+min_room)))) - { - while(*trans) (*dstbuf)[(*dstoffs)++] = *(trans++); - limit -= tlen; - } - else - { - QPERR(QPF_ERR_T_BUFOVERFLOW); - nogrow = 1; - } - } - else - { - QPERR(QPF_ERR_T_INSOVERFLOW); - rval--; - } + QPERR(QPF_ERR_T_INSOVERFLOW); + n_chars_written--; + continue; } - else + + /** Check the available space in the destination buffer. **/ + n_chars_written += (translated_length-1); + const size_t chars_needed = *dstoffs + translated_length + min_room; + if (no_grow || (chars_needed > *dstsize && !grow_fn(dstbuf, dstsize, *dstoffs, grow_arg, chars_needed))) { - if (LIKELY(limit > 0)) - { - if (LIKELY(!nogrow) && (LIKELY((*dstoffs)+1+min_room <= (*dstsize)) || - (grow_fn(dstbuf, dstsize, *dstoffs, grow_arg, (*dstoffs)+1+min_room)))) - { - (*dstbuf)[(*dstoffs)++] = srcbuf[i]; - limit--; - } - else - { - QPERR(QPF_ERR_T_BUFOVERFLOW); - nogrow = 1; - } - } - else - { - QPERR(QPF_ERR_T_INSOVERFLOW); - rval--; - } + QPERR(QPF_ERR_T_BUFOVERFLOW); + no_grow = true; + continue; } + + /** Write the translated characters to the destination buffer. **/ + if (LIKELY(translated_chars == NULL)) (*dstbuf)[(*dstoffs)++] = srcbuf[i]; + else while(*translated_chars) (*dstbuf)[(*dstoffs)++] = *(translated_chars++); + limit -= translated_length; } } } - - return rval; + + return n_chars_written; } -/*** qpfPrintf_va_internal() - does all of the guts work of the qpfPrintf - *** family of functions. - *** - *** A warning to those who would modify this: the 'str' parameter may - *** change out from under this function to a new buffer if a realloc is - *** done by the grow_fn function. Do not store pointers to 'str'. Go - *** solely by offsets. +/*** Parse the provided format, apply all of the qprintf() format specifier + *** rules, and write the result to a string buffer. + *** + *** Warning: The 'dest' parameter may change during the execution of this + *** function if `grow_fn` reallocates it. Do not store pointers to 'dest'! + *** Instead, use offsets. + *** + *** @param s Optional session struct. + *** @param dest A pointer to a string buffer where data will be written. + *** @param dest_size A pointer to the current size of the string buffer. + *** @param grow_fn A function to grow the string buffer. + *** @param grow_fn An optional grow function, used to grow `dest` if more + *** space is needed. + *** @param grow_arg An argument, passed to`grow_fn`() when it is called. + *** @param format The format of data which should be written. + *** @param ap The arguments list to fulfill the provided format. + *** @returns The number of characters copied, or a negative number if an + *** error occurs. + *** + *** NULL, &(s->Tmpbuf), &(s->TmpbufSize), htr_internal_GrowFn, (void*)s, fmt, va + *** @param s Optional session struct. + *** @param str Pointer to a string buffer where data will be written. + *** @param size Pointer to the current size of the string buffer. + *** @param grow_fn A function to grow the string buffer. + *** @param format The format of data which should be written. + *** @param ap The arguments list to fulfill the provided format. ***/ int -qpfPrintf_va_internal(pQPSession s, char** str, size_t* size, qpf_grow_fn_t grow_fn, void* grow_arg, const char* format, va_list ap) - { - const char* specptr; - const char* endptr; - size_t copied = 0; - int rval; - size_t cplen; - ptrdiff_t ptrdiff_cplen; - char specchain[QPF_MAX_SPECS]; - int specchain_n[QPF_MAX_SPECS]; - int n_specs; - int i; - int n; - int found; - int intval; - long long llval; - const char* strval; - double dblval; - char tmpbuf[64]; - char chrval; - size_t cpoffset = 0; - size_t oldcpoffset; - int nogrow = 0; - int startspec; - int endspec; - size_t maxdst; - int ignore = 0; - pQPConvTable table; - QPSession null_session; - size_t min_room; - char quote; - - if (!QPF.is_init) +qpfPrintf_va_internal( + pQPSession s, + char** dest, + size_t* dest_size, + qpf_grow_fn_t grow_fn, + void* grow_arg, + const char* format, + va_list ap +) { + size_t copied = 0lu; + size_t dest_offset = 0lu; + int rval = -1; + + /** Ensure initialization. **/ + if (UNLIKELY(!QPF.is_init) && UNLIKELY(qpfInitialize() < 0)) return -ENOMEM; + + /** Use a null session (that does nothing with errors) if no session is provided. **/ + QPSession null_session; + if (s == NULL) { - if (qpfInitialize() < 0) return -ENOMEM; + memset(&null_session, 0, sizeof(QPSession)); + s = &null_session; } - - if (!s) + + /** Ensure that there is at least enough room for the a null terminator. **/ + if (UNLIKELY((!*dest || *dest_size < 1) && !grow_fn(dest, dest_size, dest_offset, grow_arg, 1))) { - null_session.Errors = 0; - s=&null_session; + rval = -EINVAL; + QPERR(QPF_ERR_T_BUFOVERFLOW); + goto error; } - - /** this all falls apart if there isn't at least room for the - ** null terminator! - **/ - if ((!*str || *size < 1) && !grow_fn(str, size, cpoffset, grow_arg, 1)) - { rval = -EINVAL; QPERR(QPF_ERR_T_BUFOVERFLOW); goto error; } - - /** search for %this-and-that (specifiers), copy everything else **/ - do { - /** Find the end of the non-specifier string segment **/ - specptr = strchr(format, '%'); - endptr = specptr?specptr:(format+strlen(format)); - - /** Copy the plain section of string **/ - if (!ignore) + + /** Search for format specifiers (e.g. %STR, %INT, etc.) and copy all other characters. **/ + bool no_grow = false, ignore = false; + while (1) + { + /** Find the end of the non-specifier string segment. **/ + const char* spec_ptr = strchr(format, '%'); + const bool spec_found = LIKELY(spec_ptr != NULL); + const ptrdiff_t plain_str_len = (spec_found) ? (spec_ptr - format) : strlen(format); + + /** Copy the plain section of string. **/ + if (LIKELY(!ignore)) { - ptrdiff_cplen = (endptr - format); - if (UNLIKELY(ptrdiff_cplen < 0 || ptrdiff_cplen >= SIZE_MAX/4)) + if (UNLIKELY(plain_str_len < 0 || SIZE_MAX/4 <= plain_str_len)) { QPERR(QPF_ERR_T_BUFOVERFLOW); rval = -EINVAL; goto error; } - cplen = ptrdiff_cplen; - if (UNLIKELY(nogrow)) cplen = 0; - if (UNLIKELY(cpoffset+cplen+1 > SIZE_MAX/2)) + + /** Compute the length that we need to copy. **/ + size_t copy_len = (UNLIKELY(no_grow)) ? 0 : plain_str_len; + const size_t space_needed = dest_offset + copy_len + 1; + if (UNLIKELY(space_needed > SIZE_MAX/2)) { QPERR(QPF_ERR_T_BUFOVERFLOW); rval = -EINVAL; goto error; } - if (UNLIKELY(cpoffset+cplen+1 > *size) && (nogrow || !grow_fn(str, size, cpoffset, grow_arg, cpoffset+cplen+1))) + + /** Ensure that we have enough space for the copy. **/ + if (UNLIKELY(space_needed > *dest_size) && (no_grow || !grow_fn(dest, dest_size, dest_offset, grow_arg, space_needed))) { QPERR(QPF_ERR_T_BUFOVERFLOW); - cplen = (*size)-cpoffset-1; - nogrow = 1; + no_grow = true; + + /** Reduce the copy length to the maximum we're able to copy. **/ + copy_len = (*dest_size) - dest_offset - 1; } - if (cplen) memcpy((*str) + cpoffset, format, cplen); - cpoffset += cplen; - copied += ptrdiff_cplen; + + /** Copy the plain string to the destination. **/ + if (LIKELY(copy_len > 0)) memcpy(*dest + dest_offset, format, copy_len); + dest_offset += copy_len; + copied += plain_str_len; } - format = endptr; - - /** Handle specifiers **/ - if (format[0] == '%') + format += plain_str_len; + + /** Check if there are no more format specifiers, we're done. **/ + if (!spec_found) break; + format++; /* Consume the % character. */ + + /** Parse simple, 1-character format specifiers. **/ + switch (format[0]) { - format++; - - /** Simple specifiers **/ - if (UNLIKELY(format[0] == '%')) + case '%': { - if (LIKELY(!nogrow) && (LIKELY(cpoffset+2 <= *size) || (grow_fn(str, size, cpoffset, grow_arg, cpoffset+2)))) - (*str)[cpoffset++] = '%'; - else + /** Consume this character. **/ + format++; + if (ignore) continue; + + /** Ensure that we have enough space. **/ + const size_t space_needed = dest_offset + 2lu; + if (no_grow || (space_needed > *dest_size && !grow_fn(dest, dest_size, dest_offset, grow_arg, space_needed))) { QPERR(QPF_ERR_T_BUFOVERFLOW); - nogrow = 1; + no_grow = true; + continue; } + + /** Add the character to the output string. **/ + (*dest)[dest_offset++] = '%'; copied++; - format++; + continue; } - else if (UNLIKELY(format[0] == '&')) + case '&': { - if (LIKELY(!nogrow) && (LIKELY(cpoffset+2 <= *size) || (grow_fn(str, size, cpoffset, grow_arg, cpoffset+2)))) - (*str)[cpoffset++] = '&'; - else + /** Consume this character. **/ + format++; + if (ignore) continue; + + /** Ensure that we have enough space. **/ + const size_t space_needed = dest_offset + 2lu; + if (no_grow || (space_needed > *dest_size && !grow_fn(dest, dest_size, dest_offset, grow_arg, space_needed))) { QPERR(QPF_ERR_T_BUFOVERFLOW); - nogrow = 1; + no_grow = true; + continue; } + + /** Add the character to the output string. **/ + (*dest)[dest_offset++] = '&'; copied++; + continue; + } + case '[': + { format++; + const int val = va_arg(ap, int); + if (!val) ignore = true; + continue; } - else if (UNLIKELY(format[0] == ']')) + case ']': { format++; - ignore = 0; + ignore = false; + continue; + } + } + + /** Source data specifier. Build the specifier chain **/ + unsigned int n_specs = 0u; + char specchain[QPF_MAX_SPECS]; + int specchain_n[QPF_MAX_SPECS]; + + /** Only support source format specifiers for the first iteration. **/ + unsigned int startspec = QPF_SPEC_T_STARTSRC; + unsigned int endspec = QPF_SPEC_T_ENDSRC; + do { + /** Check if we've reached the maximum number of specifiers in the chain. **/ + if (UNLIKELY(n_specs == QPF_MAX_SPECS)) + { + rval = -ENOMEM; + QPERR(QPF_ERR_T_RESOURCE); + goto error; } - else if (UNLIKELY(format[0] == '[')) + + /** Is this a numerically-constrained spec? (e.g. %nSTR, %nLSET, etc.) **/ + int n = -1; + if ('0' <= format[0] && format[0] <= '9') + { + n = strtoi(format, (char**)&format, 10); /* cast is needed because format is const char* */ + if (n < 0) n = 0; + } + else if (format[0] == '*') { format++; - intval = va_arg(ap, int); - if (!intval) + n = va_arg(ap, int); + if (n < 0) n = 0; + } + const bool has_n = (n != -1); + + /** Find this spec using qpf_spec_names. **/ + bool found = 0; + for (unsigned int i = startspec; i <= endspec; i++) + { + /** Skip specs if their numerical constraint status does not match what we parsed. **/ + const int spec_uses_n = (qpf_spec_names[i][0] == 'n'); + if (has_n != spec_uses_n) continue; + + /** Check that the spec name (without any inital "n" constraint marker) matches. **/ + const char* spec_name = qpf_spec_names[i] + ((spec_uses_n) ? 1 : 0); + const int spec_len = qpf_spec_len[i] - ((spec_uses_n) ? 1 : 0); + if (strncmp(format, spec_name, spec_len) == 0) { - /*while(*format && format[0] != '%' && format[1] != ']') - format++; - if (*format) - format += 2;*/ - ignore = 1; + /** Found it. **/ + format += spec_len; + specchain_n[n_specs] = n; + specchain[n_specs++] = i; + found = true; + break; } } - else + + /** Did we find the format specifier? **/ + if (UNLIKELY(!found)) { - /** Source data specifier. Build the specifier chain **/ - n_specs = 0; - startspec = QPF_SPEC_T_STARTSRC; - endspec = QPF_SPEC_T_ENDSRC; - while(1) + if (UNLIKELY(n_specs == 0u)) { - n = -1; - found = 0; - - /** Is this a numerically-constrained spec? **/ - if (*format >= '0' && *format <= '9') + /** need at least one spec **/ + rval = -EINVAL; + QPERR(QPF_ERR_T_BADFORMAT); + goto error; + } + + /** Invalid spec: Skip to printing. **/ + format--; + break; + } + + /** Later items in the specifier chain must be filter specifiers. **/ + startspec = QPF_SPEC_T_STARTFILT; + endspec = QPF_SPEC_T_ENDFILT; + } + while (format[0] == '&' && format++); /* Loop as long as there are '&' chars to consume. */ + + /** Get the data using the source spec. **/ + char tmp_buf[318]; /* 318 characters are needed to print DBL_MAX. */ + const char* strval = NULL; + const char format_specifier = specchain[0]; + size_t copy_len = 0lu; + switch (format_specifier) + { + case QPF_SPEC_T_INT: + { + const int int_val = va_arg(ap, int); + copy_len = qpf_internal_itoa(tmp_buf, sizeof(tmp_buf), int_val); + strval = tmp_buf; + break; + } + + case QPF_SPEC_T_STR: + { + strval = va_arg(ap, const char*); + if (UNLIKELY(strval == NULL && !ignore)) + { + rval = -EINVAL; + QPERR(QPF_ERR_T_NULL); + goto error; + } + copy_len = (strval) ? strlen(strval) : 0; + break; + } + + case QPF_SPEC_T_POS: + { + const int int_val = va_arg(ap, int); + if (UNLIKELY(int_val < 0 && !ignore)) + { + rval = -EINVAL; + QPERR(QPF_ERR_T_NOTPOSITIVE); + goto error; + } + copy_len = qpf_internal_itoa(tmp_buf, sizeof(tmp_buf), int_val); + strval = tmp_buf; + break; + } + + case QPF_SPEC_T_LL: + { + const long long ll_val = va_arg(ap, long long); + copy_len = (size_t)snprintf(tmp_buf, sizeof(tmp_buf), "%lld", ll_val); + strval = tmp_buf; + break; + } + + case QPF_SPEC_T_DBL: + { + const double double_val = va_arg(ap, double); + copy_len = (size_t)snprintf(tmp_buf, sizeof(tmp_buf), "%lf", double_val); + strval = tmp_buf; + break; + } + + case QPF_SPEC_T_NSTR: + { + strval = va_arg(ap, const char*); + if (UNLIKELY(strval == NULL && !ignore)) + { + rval = -EINVAL; + QPERR(QPF_ERR_T_NULL); + goto error; + } + copy_len = specchain_n[0]; + break; + } + + case QPF_SPEC_T_CHR: + { + tmp_buf[0] = va_arg(ap, int); + copy_len = 1; + strval = &tmp_buf[0]; + break; + } + + default: + { + rval = -EINVAL; + QPERR(QPF_ERR_T_BADFORMAT); + goto error; + } + } + + /** If this specifier is ignored, we're done. Skip all filtering/writing logic. **/ + if (UNLIKELY(ignore)) continue; + + /** Handle filters. **/ + pQPConvTable table; + size_t min_room = 1; + char quote = 0; + for (unsigned int i = 1; i < n_specs; i++) + { + const char filter_specifier = specchain[i]; + switch (filter_specifier) + { + case QPF_SPEC_T_NLEN: + { + if (UNLIKELY(copy_len > specchain_n[i])) { - n = strtoi(format, (char**)&endptr, 10); /* cast is needed because endptr is const char* */ - format = endptr; - if (n < 0) n = 0; + QPERR(QPF_ERR_T_INSOVERFLOW); + copy_len = specchain_n[i]; } - else if (*format == '*') + break; + } + + case QPF_SPEC_T_SYM: + { + /** TODO: Fix code the duplication below. **/ + if (n_specs-i == 2 && specchain[i + 1] == QPF_SPEC_T_NLEN && copy_len > specchain_n[i + 1]) + copy_len = specchain_n[i + 1]; + if (UNLIKELY(cxsecVerifySymbol_n(strval, copy_len) < 0)) { - format++; - n = va_arg(ap, int); - if (n < 0) n = 0; + rval = -EINVAL; + QPERR(QPF_ERR_T_BADSYMBOL); + goto error; } - - /** Look for which spec this is **/ - for(i=startspec; i<=endspec; i++) + break; + } + + case QPF_SPEC_T_FILE: + { + if (n_specs-i == 2 && specchain[i + 1] == QPF_SPEC_T_NLEN && copy_len > specchain_n[i + 1]) + copy_len = specchain_n[i + 1]; + if (UNLIKELY( + copy_len == 0 || + (copy_len == 1 && strval[0] == '.') || + (copy_len == 2 && strval[0] == '.' && strval[1] == '.') || + memchr(strval, '/', copy_len) != NULL || + memchr(strval, '\0', copy_len) != NULL + )) { - if ((n == -1 && - qpf_spec_names[i][0] != 'n' && - !strncmp(format, qpf_spec_names[i], qpf_spec_len[i])) || - (n >= 0 && - qpf_spec_names[i][0] == 'n' && - !strncmp(format, qpf_spec_names[i]+1, qpf_spec_len[i]-1))) - { - /** Found it. **/ - if (n == -1) format += qpf_spec_len[i]; - else format += (qpf_spec_len[i]-1); - specchain_n[n_specs] = n; - specchain[n_specs++] = i; - found = 1; - break; - } - } - startspec = QPF_SPEC_T_STARTFILT; - endspec = QPF_SPEC_T_ENDFILT; - - /** Did we find it? **/ - if (UNLIKELY(!found)) - { - if (n_specs == 0) - { - /** need at least one spec **/ - rval = -EINVAL; - QPERR(QPF_ERR_T_BADFORMAT); - goto error; - } - /** invalid spec, ignore and print **/ - format--; - break; + rval = -EINVAL; + QPERR(QPF_ERR_T_BADFILE); + goto error; } - - /** More? **/ - if (*format == '&') + break; + } + + case QPF_SPEC_T_PATH: + { + if (n_specs-i == 2 && specchain[i + 1] == QPF_SPEC_T_NLEN && copy_len > specchain_n[i + 1]) + copy_len = specchain_n[i + 1]; + if (UNLIKELY( + copy_len == 0 || + (copy_len == 2 && strval[0] == '.' && strval[1] == '.') || + (copy_len > 2 && strval[0] == '.' && strval[1] == '.' && strval[2] == '/') || + (copy_len > 2 && strval[copy_len-1] == '.' && strval[copy_len-2] == '.' && strval[copy_len-3] == '/') || + qpf_internal_FindStr(strval, copy_len, "/../", 4) >= 0 || + memchr(strval, '\0', copy_len) + )) { - if (UNLIKELY(n_specs == QPF_MAX_SPECS)) - { rval = -ENOMEM; QPERR(QPF_ERR_T_RESOURCE); goto error; } - format++; + rval = -EINVAL; + QPERR(QPF_ERR_T_BADPATH); + goto error; } - else + break; + } + + case QPF_SPEC_T_B64: + { + const int num_bytes = qpf_internal_base64encode(s, strval, copy_len, dest, dest_size, &dest_offset, grow_fn, grow_arg); + if (UNLIKELY(num_bytes < 0)) { - break; + rval = -EINVAL; + goto error; } + copied += num_bytes; + copy_len = 0; + break; } - - /** Get source **/ - switch(specchain[0]) + + case QPF_SPEC_T_DB64: { - case QPF_SPEC_T_INT: - intval = va_arg(ap, int); - cplen = qpf_internal_itoa(tmpbuf, sizeof(tmpbuf), intval); - strval = tmpbuf; - break; - - case QPF_SPEC_T_STR: - strval = va_arg(ap, const char*); - if (UNLIKELY(strval == NULL && !ignore)) - { rval = -EINVAL; QPERR(QPF_ERR_T_NULL); goto error; } - cplen = strval?strlen(strval):0; - break; - - case QPF_SPEC_T_POS: - intval = va_arg(ap, int); - if (UNLIKELY(intval < 0 && !ignore)) - { rval = -EINVAL; QPERR(QPF_ERR_T_NOTPOSITIVE); goto error; } - cplen = qpf_internal_itoa(tmpbuf, sizeof(tmpbuf), intval); - strval = tmpbuf; - break; - - case QPF_SPEC_T_LL: - llval = va_arg(ap, long long); - cplen = snprintf(tmpbuf, sizeof(tmpbuf), "%lld", llval); - strval = tmpbuf; - break; - - case QPF_SPEC_T_DBL: - dblval = va_arg(ap, double); - cplen = snprintf(tmpbuf, sizeof(tmpbuf), "%lf", dblval); - strval = tmpbuf; - break; - - case QPF_SPEC_T_NSTR: - strval = va_arg(ap, const char*); - if (UNLIKELY(strval == NULL && !ignore)) - { rval = -EINVAL; QPERR(QPF_ERR_T_NULL); goto error; } - cplen = specchain_n[0]; - break; - - case QPF_SPEC_T_CHR: - chrval = va_arg(ap, int); - cplen = 1; - strval = &chrval; - break; - - default: + const int num_bytes = qpf_internal_base64decode(s, strval, copy_len, dest, dest_size, &dest_offset, grow_fn, grow_arg); + if (UNLIKELY(num_bytes < 0)) + { rval = -EINVAL; - QPERR(QPF_ERR_T_BADFORMAT); goto error; + } + copied += num_bytes; + copy_len = 0; + break; } - - if (!ignore) + + case QPF_SPEC_T_DHEX: { - /** Length problem? **/ - if (UNLIKELY(cplen < 0)) - { rval = -EINVAL; QPERR(QPF_ERR_T_BADLENGTH); goto error; } - - /** Filters? **/ - for (i=1;i maxdst)) { - case QPF_SPEC_T_NLEN: - if (cplen > specchain_n[i]) - { - QPERR(QPF_ERR_T_INSOVERFLOW); - cplen = specchain_n[i]; - } - break; - - case QPF_SPEC_T_SYM: - if (n_specs-i == 2 && specchain[i+1] == QPF_SPEC_T_NLEN && cplen > specchain_n[i+1]) - cplen = specchain_n[i+1]; - if (cxsecVerifySymbol_n(strval, cplen) < 0) - { rval = -EINVAL; QPERR(QPF_ERR_T_BADSYMBOL); goto error; } - break; - - case QPF_SPEC_T_FILE: - if (n_specs-i == 2 && specchain[i+1] == QPF_SPEC_T_NLEN && cplen > specchain_n[i+1]) - cplen = specchain_n[i+1]; - if ((cplen == 1 && strval[0] == '.') || - (cplen == 2 && strval[0] == '.' && strval[1] == '.') || - memchr(strval, '/', cplen) || - memchr(strval, '\0', cplen) || - cplen == 0) - { rval = -EINVAL; QPERR(QPF_ERR_T_BADFILE); goto error; } - break; - - case QPF_SPEC_T_PATH: - if (n_specs-i == 2 && specchain[i+1] == QPF_SPEC_T_NLEN && cplen > specchain_n[i+1]) - cplen = specchain_n[i+1]; - if ((cplen == 2 && strval[0] == '.' && strval[1] == '.') || - (cplen > 2 && strval[0] == '.' && strval[1] == '.' && strval[2] == '/') || - memchr(strval, '\0', cplen) || - cplen == 0 || - (cplen > 2 && strval[cplen-1] == '.' && strval[cplen-2] == '.' && strval[cplen-3] == '/') || - qpf_internal_FindStr(strval, cplen, "/../", 4) >= 0) - { rval = -EINVAL; QPERR(QPF_ERR_T_BADPATH); goto error; } - break; - - case QPF_SPEC_T_B64: - if((n=qpf_internal_base64encode(s, strval, cplen, str, size, &cpoffset, grow_fn, grow_arg))<0) - { rval = -EINVAL; goto error; } - else - { - copied+=n; - cplen=0; - } - break; - - case QPF_SPEC_T_DB64: - if((n=qpf_internal_base64decode(s, strval, cplen, str, size, &cpoffset, grow_fn, grow_arg))<0) - { rval = -EINVAL; goto error; } - else - { - copied+=n; - cplen=0; - } - break; - - case QPF_SPEC_T_DHEX: - if((n=qpf_internal_hexdecode(s, strval, cplen, str, size, &cpoffset, grow_fn, grow_arg))<0) - { rval = -EINVAL; goto error; } - else - { - copied+=n; - cplen=0; - } - break; - - case QPF_SPEC_T_ESCQ: - case QPF_SPEC_T_ESCQWS: - case QPF_SPEC_T_JSSTR: - case QPF_SPEC_T_JSONSTR: - case QPF_SPEC_T_DSYB: - case QPF_SPEC_T_CSSVAL: - case QPF_SPEC_T_CSSURL: - case QPF_SPEC_T_ESCWS: - case QPF_SPEC_T_QUOT: - case QPF_SPEC_T_DQUOT: - case QPF_SPEC_T_HTE: - case QPF_SPEC_T_HTENLBR: - case QPF_SPEC_T_HEX: - case QPF_SPEC_T_URL: - if (n_specs-i == 1 || (n_specs-i == 2 && specchain[i+1] == QPF_SPEC_T_NLEN)) - { - if (n_specs-i == 2) - maxdst = specchain_n[i+1]; - else - maxdst = INT_MAX; - switch(specchain[i]) - { - case QPF_SPEC_T_ESCQ: table = &QPF.quote_matrix; - min_room = 1; - quote = 0; - break; - case QPF_SPEC_T_ESCQWS: table = &QPF.quote_ws_matrix; - min_room = 1; - quote = 0; - break; - case QPF_SPEC_T_JSSTR: table = &QPF.jsstr_matrix; - min_room = 1; - quote = 0; - break; - case QPF_SPEC_T_JSONSTR: table = &QPF.jsonstr_matrix; - min_room = 1; - quote = 0; - break; - case QPF_SPEC_T_DSYB: table = &QPF.dsyb_matrix; - min_room = 1; - quote = 0; - break; - case QPF_SPEC_T_SSYB: table = &QPF.ssyb_matrix; - min_room = 1; - quote = 0; - break; - case QPF_SPEC_T_CSSVAL: table = &QPF.cssval_matrix; - min_room = 1; - quote = 0; - break; - case QPF_SPEC_T_CSSURL: table = &QPF.cssurl_matrix; - min_room = 1; - quote = 0; - break; - case QPF_SPEC_T_ESCWS: table = &QPF.ws_matrix; - min_room = 1; - quote = 0; - break; - case QPF_SPEC_T_QUOT: table = &QPF.quote_matrix; - min_room = 2; - quote = '\''; - break; - case QPF_SPEC_T_DQUOT: table = &QPF.quote_matrix; - min_room = 2; - quote = '"'; - break; - case QPF_SPEC_T_HTE: table = &QPF.hte_matrix; - min_room = 1; - quote = 0; - break; - case QPF_SPEC_T_HTENLBR: table = &QPF.htenlbr_matrix; - min_room = 1; - quote = 0; - break; - case QPF_SPEC_T_HEX: table = &QPF.hex_matrix; - min_room = 1; - quote = 0; - break; - case QPF_SPEC_T_URL: table = &QPF.url_matrix; - min_room = 1; - quote = 0; - break; - default: table = NULL; - quote = 0; - min_room = 1; - QPERR(QPF_ERR_T_INTERNAL); - break; - } - if (quote && specchain[0] != QPF_SPEC_T_STR) - { - /** don't quote things other than strings **/ - if (cplen > maxdst) - { - QPERR(QPF_ERR_T_INSOVERFLOW); - cplen = maxdst; - } - break; - } - if (quote) - { - if (maxdst < 2) - { - QPERR(QPF_ERR_T_BADFORMAT); - rval = -EINVAL; - goto error; - } - maxdst -= 2; - } - if (quote) - { - if (LIKELY(!nogrow) && (LIKELY(cpoffset+1+1 <= *size) || (grow_fn(str, size, cpoffset, grow_arg, cpoffset+1+1)))) - { - (*str)[cpoffset++] = quote; - } - else - { - QPERR(QPF_ERR_T_BUFOVERFLOW); - nogrow = 1; - } - copied++; - } - oldcpoffset = cpoffset; - n = qpf_internal_Translate(s, strval, cplen, str, &cpoffset, size, maxdst, table, nogrow?NULL:grow_fn, grow_arg, min_room); - if (n < 0) - { - QPERR(QPF_ERR_T_INTERNAL); - rval = n; - goto error; - } - if (n != cpoffset - oldcpoffset) nogrow = 1; - if (quote) - { - if ((LIKELY(cpoffset+1+1 <= *size) || (grow_fn(str, size, cpoffset, grow_arg, cpoffset+1+1)))) - { - (*str)[cpoffset++] = quote; - } - else - { - QPERR(QPF_ERR_T_BUFOVERFLOW); - nogrow = 1; - } - copied++; - } - copied += n; - cplen = 0; - } - else - { - QPERR(QPF_ERR_T_NOTIMPL); - rval = -ENOSYS; - goto error; - } - break; - - default: - /** Unimplemented filter **/ - QPERR(QPF_ERR_T_NOTIMPL); - rval = -ENOSYS; - goto error; + QPERR(QPF_ERR_T_INSOVERFLOW); + copy_len = maxdst; } + break; } - - /** Copy it. **/ - if (LIKELY(cplen != 0)) + + /** Add opening quote (if requested). **/ + if (quote) { - copied += cplen; - if (UNLIKELY(cpoffset+cplen+1 > SIZE_MAX/2)) + if (UNLIKELY(maxdst < 2)) { - QPERR(QPF_ERR_T_BUFOVERFLOW); + QPERR(QPF_ERR_T_BADFORMAT); rval = -EINVAL; goto error; } - if (UNLIKELY(nogrow)) cplen = 0; - if (UNLIKELY(cpoffset+cplen+1 > *size) && (!grow_fn(str, size, cpoffset, grow_arg, cpoffset+cplen+1))) + maxdst -= 2; + + const size_t space_needed = dest_offset + 2lu; + if (UNLIKELY(no_grow || (space_needed > *dest_size && !grow_fn(dest, dest_size, dest_offset, grow_arg, space_needed)))) { QPERR(QPF_ERR_T_BUFOVERFLOW); - cplen = (*size) - cpoffset - 1; - nogrow = 1; + no_grow = true; } - memcpy((*str)+cpoffset, strval, cplen); + else (*dest)[dest_offset++] = quote; + copied++; } - - /** Update string counters **/ - cpoffset += cplen; + + /** Translate the string content using the table selected above. **/ + const qpf_grow_fn_t gf = (no_grow) ? NULL : grow_fn; + const int n_chars = qpf_internal_Translate(s, strval, copy_len, dest, &dest_offset, dest_size, maxdst, table, gf, grow_arg, min_room); + if (UNLIKELY(n_chars < 0)) + { + /** Probably unreachable. **/ + QPERR(QPF_ERR_T_INTERNAL); + rval = n_chars; + goto error; + } + if (UNLIKELY(s->Errors & QPF_ERR_T_BUFOVERFLOW)) no_grow = true; + + /** Add closing quote (if requested). **/ + if (quote) + { + const size_t space_needed = dest_offset + 2lu; + if (UNLIKELY(no_grow || (space_needed > *dest_size && !grow_fn(dest, dest_size, dest_offset, grow_arg, space_needed)))) + { + QPERR(QPF_ERR_T_BUFOVERFLOW); + no_grow = true; + } + + /*** Write the closing quote with space for the + *** null-terminator if at all possible, even if + *** a buffer overflow has already occurred, by + *** moving the offset back and overwriting some + *** of the end of the provided string, if needed. + ***/ + if (LIKELY(*dest_size >= 2)) + { + if (UNLIKELY(space_needed > *dest_size)) + dest_offset = *dest_size - 2; + (*dest)[dest_offset++] = quote; + } + copied++; + } + copied += n_chars; + copy_len = 0; + + break; } + + default: + /** Unimplemented filter **/ + QPERR(QPF_ERR_T_NOTIMPL); + rval = -ENOSYS; + goto error; } } + + /** Copy the data into the buffer. **/ + if (UNLIKELY(copy_len == 0)) continue; + copied += copy_len; + const size_t space_needed = dest_offset + copy_len + 1lu; + if (UNLIKELY(space_needed > SIZE_MAX/2)) + { + QPERR(QPF_ERR_T_BUFOVERFLOW); + rval = -EINVAL; + goto error; + } + if (UNLIKELY(no_grow)) copy_len = 0; + if (UNLIKELY(no_grow || (space_needed > *dest_size && !grow_fn(dest, dest_size, dest_offset, grow_arg, space_needed)))) + { + QPERR(QPF_ERR_T_BUFOVERFLOW); + copy_len = *dest_size - dest_offset - 1; + no_grow = true; + } + memcpy(*dest + dest_offset, strval, copy_len); + + /** Update string counters **/ + dest_offset += copy_len; } - while (specptr); - + + /** Success. **/ rval = copied; - + error: /** Null terminate. Only case where this does not happen is - ** if size == 0 on the initial call. Terminator is not counted + ** if dest_size == 0 on the initial call. Terminator is not counted ** in the return value. **/ - if ((*size) > cpoffset) (*str)[cpoffset] = '\0'; + if ((*dest_size) > dest_offset) (*dest)[dest_offset] = '\0'; + return rval; } -/*** qpfRegisterExt() - registers an extension with the QPF module, allowing - *** outside modules to provide extra specifiers for processing data or for - *** data sources. If is_source is nonzero, the extension is treated as a - *** source specifier, otherwise it is a filtering specifier. +/*** Registers a new extension with the QPF module. This allows outside + *** modules to provide extra specifiers for processing data or for data + *** sources. + *** + *** Warning: It appears that extensions are not currently implemented. + *** TODO: This comment should document the expected signature for ext_fn() + *** more clearly once it is used somewhere. + *** + *** @param ext_spec The name of the extension (e.g. "STR" for `%str`). + *** @param ext_fn The function to call on data that meets the extension. + *** @param is_source If nonzero, the extension is treated as a source + *** specifier (also known as a format specifier, e.g. `%STR`), otherwise + *** it is a treated as a filtering specifier (e.g. `"`). ***/ void qpfRegisterExt(char* ext_spec, int (*ext_fn)(), int is_source) { - /** Add to list of extensions **/ - if (QPF.n_ext >= QPF_MAX_EXTS) + /** Check if extension max has been reached. **/ + if (UNLIKELY(QPF.n_ext >= QPF_MAX_EXTS)) { fprintf(stderr, "warning: qpfRegisterExt: QPF_MAX_EXTS exceeded\n"); return; } + + /** Add to list of extensions **/ QPF.ext_specs[QPF.n_ext] = nmSysStrdup(ext_spec); QPF.ext_fns[QPF.n_ext] = ext_fn; QPF.is_source[QPF.n_ext] = is_source?1:0; @@ -1445,3 +1812,6 @@ qpfRegisterExt(char* ext_spec, int (*ext_fn)(), int is_source) return; } + +/** Scope cleanup. **/ +#undef QPERR diff --git a/centrallix-lib/src/strtcpy.c b/centrallix-lib/src/strtcpy.c index 8b4366716..15b471458 100644 --- a/centrallix-lib/src/strtcpy.c +++ b/centrallix-lib/src/strtcpy.c @@ -22,11 +22,18 @@ /************************************************************************/ -/*** strtcat() - truncating string concatenation - *** - *** Appends to dst, being sure to not overflow the given dstlen size. - *** Returns number of bytes actually copied, including null terminator. - *** If truncated, returns -(bytes copied). +/*** Truncating string concatenation. + *** + *** Appends the source string to the destination string, truncating the data + *** if needed to avoid overflowing the destination buffer. + *** + *** @param dst A pointer to the destination buffer. + *** @param src A pointer to the source buffer to read from. + *** @param dstlen The total allocated memory of the destination buffer (not + *** the length of available space), used to avoid writing off the end of + *** the buffer. + *** @returns number of bytes actually copied, including null terminator. If + *** truncated, returns `-(bytes copied)`. ***/ int strtcat(char* dst, const char* src, size_t dstlen) @@ -46,10 +53,18 @@ strtcat(char* dst, const char* src, size_t dstlen) } -/*** strtcpy() - truncating string copy - *** - *** returns number of bytes actually copied (including null terminator) - *** if truncated, returns -(bytes copied), which is the same as -(dstlen). +/*** Truncating string copy. + *** + *** Truncates written data if the source data is too long to fit into the + *** allocated destination buffer. + *** + *** @param dst A pointer to the destination buffer. + *** @param src A pointer to the source buffer to read from. + *** @param dstlen The length of the destination buffer, used to avoid writing + *** off the end of the allocated memory. + *** @returns The number of bytes actually copied (including null terminator). + *** If the data is truncated, returns `-(bytes copied)`, which is the same + *** as `-(dstlen)`. ***/ int strtcpy(char* dst, const char* src, size_t dstlen) diff --git a/centrallix-lib/src/util.c b/centrallix-lib/src/util.c index 629b59c79..988f000db 100644 --- a/centrallix-lib/src/util.c +++ b/centrallix-lib/src/util.c @@ -1,24 +1,36 @@ /************************************************************************/ -/* Centrallix Application Server System */ +/* Centrallix Application Server System */ /* Centrallix Base Library */ /* */ -/* Copyright (C) 1998-2011 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* You may use these files and this library under the terms of the */ /* GNU Lesser General Public License, Version 2.1, contained in the */ /* included file "COPYING". */ /* */ -/* Module: (util.c,.h) */ -/* Author: Micah Shennum */ -/* Date: May 26, 2011 */ -/* Description: Collection of utilities */ +/* Module: util.c, util.h */ +/* Author: Micah Shennum and Israel Fuller */ +/* Date: May 26, 2011 and October 13, 2025 (respectively) */ +/* Description: Collection of utilities including: */ +/* - Utilities for parsing numbers. */ +/* - The timer utility for benchmarking code. */ +/* - snprint_bytes() for formatting a byte count. */ +/* - snprint_commas_llu() for formatting large numbers. */ +/* - fprint_mem() for printing memory stats. */ +/* - min() and max() for handling numbers. */ +/* - The check functions for reliably printing debug data. */ /************************************************************************/ +#include +#include +#include +#include #include - #include -#include -#include +#include +#include + +#include "newmalloc.h" #include "util.h" /** @@ -77,3 +89,264 @@ unsigned int strtoui(const char *nptr, char **endptr, int base){ //return as tmp; return (unsigned int)tmp; } + +/*** snprint_bytes() allows one to pick between CS units, where the kibibyte + *** (KiB) is 1024 bytes, and metric units where the kilobyte (KB) is 1000 bytes. + *** Fun Fact: Windows uses kibibytes, but displays them as KB. + ***/ +#define USE_METRIC false +static char* units_cs[] = {"bytes", "KiB", "MiB", "GiB"}; +static char* units_metric[] = {"bytes", "KB", "MB", "GB"}; +#define N_UNITS ((unsigned int)(sizeof(units_cs) / sizeof(units_cs[0]))) + +/*** Displays a size in bytes using the largest unit where the result would be + *** at least 1.0. Note that units larger than GB and GiB are not supported + *** because the largest possible unsigned int is 4,294,967,295, which is + *** exactly 4 GiB (or approximately 4.29 GB). + *** + *** @param buf The buffer to which new text will be written, using snprintf(). + *** @param buf_size The amount of space in the buffer, passed to snprintf(). + *** It is recommended to have at least 12 characters available. + *** @param bytes The number of bytes, which will be formatted and written + *** to the buffer.. + *** @returns buf, for chaining. + ***/ +char* +snprint_bytes(char* buf, const size_t buf_size, unsigned int bytes) + { + char** units = (USE_METRIC) ? units_metric : units_cs; + const double unit_size = (USE_METRIC) ? 1000.0 : 1024.0; + + /** Search for the largest unit where the value would be at least 1. **/ + const double size = (double)bytes; + for (unsigned char i = N_UNITS - 1; i >= 1u; i--) + { + const double denominator = pow(unit_size, i); + if (size >= denominator) + { + const double converted_size = size / denominator; + if (converted_size >= 100.0) + snprintf(buf, buf_size, "%.5g %s", converted_size, units[i]); + else if (converted_size >= 10.0) + snprintf(buf, buf_size, "%.4g %s", converted_size, units[i]); + else /* if (converted_size >= 1.0) - Always true. */ + snprintf(buf, buf_size, "%.3g %s", converted_size, units[i]); + return buf; + } + } + + /** None of the larger units work, so we just use bytes. **/ + snprintf(buf, buf_size, "%u %s", bytes, units[0]); + + return buf; + } +#undef N_UNITS + +/*** Print a large number formatted with comas to a buffer. + *** + *** @param buf The buffer to print the number into. + *** @param buf_size The maximum number of characters to add to the buffer. + *** @param value The value to write into the buffer. + *** @returns `buf`, or NULL if `buf_size` is 0. + */ +char* +snprint_commas_llu(char* buf, size_t buf_size, unsigned long long value) + { + if (buf_size == 0) return NULL; + if (value == 0) + { + if (buf_size > 1) { buf[0] = '0'; buf[1] = '\0'; } + else buf[0] = '\0'; + return buf; + } + + /*** Write the number to the string in reverse order, adding commas as + *** they are needed. + ***/ + char tmp[32]; + unsigned int ti = 0; + while (value > 0 && ti < sizeof(tmp) - 1) + { + if (ti % 4 == 3) tmp[ti++] = ','; + tmp[ti++] = '0' + (value % 10); + value /= 10; + } + tmp[ti] = '\0'; + + unsigned int outlen = min(ti, buf_size - 1u); + for (unsigned int i = 0u; i < outlen; i++) buf[i] = tmp[ti - i - 1]; + buf[outlen] = '\0'; + + return buf; + } + +/** Print summary the current memory in use to the file pointer. **/ +void +fprint_mem(FILE* out) + { + FILE* fp = fopen("/proc/self/statm", "r"); + if (fp == NULL) { perror("fopen()"); return; } + + /** Get page counts. **/ + long size, resident, share, text, lib, data, dt; + if (fscanf(fp, "%ld %ld %ld %ld %ld %ld %ld", + &size, &resident, &share, &text, &lib, &data, &dt) != 7) + { + fprintf(stderr, "Failed to read memory info\n"); + check(fclose(fp)); /* Failure ignored. */ + return; + } + check(fclose(fp)); /* Failure ignored. */ + + /** Get page size. **/ + const long page_size = sysconf(_SC_PAGESIZE); /* in bytes */ + if (page_size == -1) + { + fprintf(stderr, "Failed to get page size.\n"); + return; + } + + /** Get the number of resident bytes used. **/ + const long resident_bytes = resident * page_size; + char buf[16]; + snprint_bytes(buf, sizeof(buf), (unsigned int)(resident_bytes)); + + /** fprintf() out data. **/ + fprintf(out, "Memory used: %ld bytes (%s)\n", resident_bytes, buf); + fprintf(out, + "Share %ldb, Text %ldb, Lib %ldb, Data %ldb\n", + share * page_size, text * page_size, lib * page_size, data * page_size + ); + + return; + } + +/*** Get the current monotonic time in seconds. + *** + *** @returns The current monotonic time as a fractional number of seconds. + ***/ +static double +get_time(void) + { + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + + return (double)ts.tv_sec + (double)ts.tv_nsec / 1.0e9f; + } + +/*** Initialize a timer struct. + *** + *** @param timer The timer to initialize. + *** @returns `timer`, for chaining. + ***/ +pTimer +timer_init(pTimer timer) + { + if (UNLIKELY(timer == NULL)) return NULL; + timer->start = NAN; + timer->total = 0.0; + + return timer; + } + +/*** Allocate and initialize a new timer. + *** + *** @returns A newly allocated timer, or NULL if allocation fails. + ***/ +pTimer +timer_new(void) + { + return timer_init(check_ptr(nmMalloc(sizeof(Timer)))); + } + +/*** Start timing. + *** + *** @param timer The timer to start. + *** @returns `timer`, for chaining. + ***/ +pTimer +timer_start(pTimer timer) + { + if (UNLIKELY(timer == NULL)) return NULL; + timer->start = get_time(); + + return timer; + } + +/*** Stop timing and add the elapsed time to the timer total. + *** + *** @param timer The timer to stop. + *** @returns `timer`, for chaining. + ***/ +pTimer +timer_stop(pTimer timer) + { + if (UNLIKELY(timer == NULL)) return NULL; + timer->total += get_time() - timer->start; + + return timer; + } + +/*** Get the total accumulated time for a timer. + *** + *** @param timer The timer to read. + *** @returns The total accumulated time in seconds, or NAN if `timer` + *** is NULL. + ***/ +double +timer_get(pTimer timer) + { + if (UNLIKELY(timer == NULL)) return NAN; + + return timer->total; + } + +/*** Reset a timer to its initial state so that it can be reused to time + *** something else. + *** + *** @param timer The timer to reset. + *** @returns `timer`, for chaining. + ***/ +pTimer +timer_reset(pTimer timer) + { + return timer_init(timer); + } + +/*** De-initialize a timer before it is freed. + *** + *** @param timer The timer to de-initialize. + ***/ +void +timer_de_init(pTimer timer) {} + +/*** De-initialize and free a timer allocated by timer_new(). + *** + *** @param timer The timer to free. + ***/ +void +timer_free(pTimer timer) + { + timer_de_init(timer); + nmFree(timer, sizeof(Timer)); + + return; + } + +/*** Function for failing on error, assuming the error came from a library or + *** system function call, so that the error buffer is set to a valid value. + ***/ +void print_err(int code, const char* function_name, const char* file_name, const int line_number) + { + /** Create a descriptive error message. **/ + char error_buf[BUFSIZ]; + snprintf(error_buf, sizeof(error_buf), "%s:%d: %s failed", file_name, line_number, function_name); + + /** Print it with as much info as we can reasonably find. **/ + if (errno != 0) perror(error_buf); + else if (code != 0) fprintf(stderr, "%s (error code %d).\n", error_buf, code); + else fprintf(stderr, "%s.\n", error_buf); + + return; + } diff --git a/centrallix-lib/src/xarray.c b/centrallix-lib/src/xarray.c index 477e2fe24..3a7fa3429 100644 --- a/centrallix-lib/src/xarray.c +++ b/centrallix-lib/src/xarray.c @@ -6,6 +6,7 @@ #include #include #include "xarray.h" +#include "util.h" #include "newmalloc.h" /************************************************************************/ @@ -408,4 +409,41 @@ int xaInsertAfter(pXArray this, int index, void* item) return index+1; } +/*** Trims an xArray so that the allocated space matches the number of items + *** in the array. + *** + *** @param this The array to be trimmed. + *** @returns 0 if successful, or -1 if an error occurs. + ***/ +int +xaTrim(pXArray this) + { + /** Allocate the new internal items array. **/ + const size_t new_size = this->nItems * sizeof(void*); + void* new_items = check_ptr(nmSysRealloc(this->Items, new_size)); + if (new_items == NULL) return -1; + + /** Update the struct. **/ + this->Items = new_items; + this->nAlloc = this->nItems; + + return 0; + } +/*** Returns a new array with a shallow copy of the data of the xArray. This + *** new array is allocated with `nmSysMalloc()` and is the exact length that + *** is needed to store the items in the xArray. + *** + *** @param this The array to be read. + *** @returns The new array, or NULL if an error occurs. + ***/ +void** +xaToArray(pXArray this) + { + const size_t size = this->nItems * sizeof(void*); + void** result = check_ptr(nmSysMalloc(size)); + if (result == NULL) return NULL; + memcpy(result, this->Items, size); + + return result; + } diff --git a/centrallix-lib/src/xhash.c b/centrallix-lib/src/xhash.c index afeb432b5..58adc7e18 100644 --- a/centrallix-lib/src/xhash.c +++ b/centrallix-lib/src/xhash.c @@ -13,7 +13,7 @@ /* Centrallix Application Server System */ /* Centrallix Base Library */ /* */ -/* Copyright (C) 1998-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* You may use these files and this library under the terms of the */ /* GNU Lesser General Public License, Version 2.1, contained in the */ @@ -290,4 +290,103 @@ xhClear(pXHashTable this, int (*free_fn)(), void* free_arg) return 0; } +/*** Executes an operation on each entry of the hash table entry. + *** + *** @param this The affected hash table (passing NULL causes undefined + *** behavior). + *** @param callback_fn A callback function to be called on each hash table + *** entry. It takes 2 parameters: the current hash table entry and a void* + *** argument specified using each_arg. If any invocation of the callback + *** function returns a value other than 0, xhForEach() will immediately + *** fail, returning that value as the error code. + *** @param each_arg An additional argument which will be passed to each + *** invocation of the callback function. + *** @returns 0 if the function executes successfully. + *** 1 if the callback function is NULL. + *** n (where n != 0) if the callback function returns n. + ***/ +int +xhForEach(pXHashTable this, int (*callback_fn)(pXHashEntry, void*), void* each_arg) + { + if (callback_fn == NULL) return 1; + + for (int row = 0; row < this->nRows; row++) + { + pXHashEntry entry = (pXHashEntry)(this->Rows.Items[row]); + while (entry != NULL) + { + pXHashEntry next = entry->Next; + const int ret = callback_fn(entry, each_arg); + if (ret != 0) return ret; + entry = next; + } + } + + return 0; + } + +/*** A helper function for `xhClearKeySafe()`. Deallocates a hash table entry + *** after calling the appropriate free function with the provided free arg. + *** + *** @param entry A pointer to the hash table entry to be freed (passing NULL + *** causes undefined behavior). + *** @param arg A pointer to a void* array with 2 elements: The first element + *** is a function pointer to the free function, which we invoke using the + *** provided entry and the free_arg, specified as the second element of + *** this array. + *** @returns 0, success. + ****/ +static int +xh_i_FreeEntry(pXHashEntry entry, void* arg) + { + /*** The passed void* actually points to a void* array with 2 elements. + *** + *** The first element is a function pointer to the free function, which + *** we invoke using the provided entry and the free_arg, specified as the + *** second element of the array. + *** + *** Interestingly, you can write this code in one line like this: + *** ((void (*)(pXHashEntry, void*))((void**)arg)[0])(entry, ((void**)arg)[1]); + *** But I value code readability, so fortunately, I can't be THAT cleaver... + ***/ + void** args = (void**)arg; + void (*free_fn)(pXHashEntry, void*) = args[0]; + free_fn(entry, args[1]); + + /** Free the entry. **/ + nmFree(entry, sizeof(XHashEntry)); + + return 0; + } +/*** Clears all contents from a hash table. The free function is passed each + *** hash entry struct, allowing it to free both the value and key, if needed. + *** + *** @param this The affected hash table (passing NULL causes undefined + *** behavior). + *** @param free_fn A pointer to a free function which will be called with a + *** pointer to each `XHashEntry` before they are deallocated. It is also + *** passed a `void*`, which will be `free_arg` (the third argument). + *** @param free_arg The void pointer value passed to the free function. + *** @returns 0 if successful, or + *** -1 if `free_fn()` is `NULL`. + ***/ +int +xhClearKeySafe(pXHashTable this, void (*free_fn)(pXHashEntry, void*), void* free_arg) + { + if (free_fn == NULL) return -1; + + /** Free each row. **/ + void* args[2] = {free_fn, free_arg}; + const int ret = xhForEach(this, xh_i_FreeEntry, args); + + /** Mark all rows as empty. **/ + for (int i = 0; i < this->nRows; i++) + this->Rows.Items[i] = NULL; + this->nItems = 0; + + /*** We are successful only if the free function didn't fail (and it should + *** not be able to fail). + ***/ + return ret; + } diff --git a/centrallix-lib/tests/test_00baseline.c b/centrallix-lib/tests/test_00baseline.c index 5101e6030..b2012c6b1 100644 --- a/centrallix-lib/tests/test_00baseline.c +++ b/centrallix-lib/tests/test_00baseline.c @@ -9,7 +9,7 @@ test(char** tname) { int i; int iter; - int array[2]; + int array[2] = {0}; *tname = "BASELINE - should pass"; iter = 1000*1000*300; @@ -17,4 +17,3 @@ test(char** tname) return iter; } - diff --git a/centrallix-lib/tests/test_02baseline.c b/centrallix-lib/tests/test_02baseline.c index 76618e1c0..ed0acf643 100644 --- a/centrallix-lib/tests/test_02baseline.c +++ b/centrallix-lib/tests/test_02baseline.c @@ -8,11 +8,7 @@ long long test(char** tname) { - int iter; - *tname = "BASELINE failed, return value - should fail"; - iter = 1000*1000*1000; return -1; } - diff --git a/centrallix-lib/tests/test_03baseline.c b/centrallix-lib/tests/test_03baseline.c index 766c8807e..c3fe06c87 100644 --- a/centrallix-lib/tests/test_03baseline.c +++ b/centrallix-lib/tests/test_03baseline.c @@ -9,7 +9,7 @@ long long test(char** tname) { int iter; - int array[2]; + int array[2] = {0}; *tname = "BASELINE sigsegv (11) caused - should crash"; iter = 1000*1000*1000; @@ -17,4 +17,3 @@ test(char** tname) return iter; } - diff --git a/centrallix-lib/tests/test_mtlexer_07.c b/centrallix-lib/tests/test_mtlexer_07.c index 278c2d2a9..b716c7135 100644 --- a/centrallix-lib/tests/test_mtlexer_07.c +++ b/centrallix-lib/tests/test_mtlexer_07.c @@ -15,7 +15,6 @@ test(char** tname) int j; int k; int t; - int n; int iter; int flags; pLxSession lxs; @@ -63,4 +62,3 @@ test(char** tname) return iter * n_tokens * n_iter; } - diff --git a/centrallix-lib/tests/test_mtlexer_15.c b/centrallix-lib/tests/test_mtlexer_15.c index a9410bc63..7baac7ab7 100644 --- a/centrallix-lib/tests/test_mtlexer_15.c +++ b/centrallix-lib/tests/test_mtlexer_15.c @@ -19,7 +19,6 @@ test(char** tname) int flagtypes[5] = { MLX_F_CPPCOMM, MLX_F_POUNDCOMM, MLX_F_SEMICOMM, MLX_F_DASHCOMM, MLX_F_CCOMM }; int n_flagtypes = 5; int flags; - char* commnames[5] = { "cppcomm", "poundcomm", "semicomm", "dashcomm", "ccomm" }; pLxSession lxs; pFile fd; @@ -54,4 +53,3 @@ test(char** tname) return iter * 10; } - diff --git a/centrallix-lib/tests/test_mtlexer_16.c b/centrallix-lib/tests/test_mtlexer_16.c index 644537137..e6cde7768 100644 --- a/centrallix-lib/tests/test_mtlexer_16.c +++ b/centrallix-lib/tests/test_mtlexer_16.c @@ -19,7 +19,6 @@ test(char** tname) int flagtypes[5] = { MLX_F_CPPCOMM, MLX_F_POUNDCOMM, MLX_F_SEMICOMM, MLX_F_DASHCOMM, MLX_F_CCOMM }; int n_flagtypes = 5; int flags; - char* commnames[5] = { "cppcomm", "poundcomm", "semicomm", "dashcomm", "ccomm" }; pLxSession lxs; pFile fd; @@ -54,4 +53,3 @@ test(char** tname) return iter * 10; } - diff --git a/centrallix-lib/tests/test_qprintf_00.c b/centrallix-lib/tests/test_qprintf_00.c index 939a6e9fb..f8ad85dbc 100644 --- a/centrallix-lib/tests/test_qprintf_00.c +++ b/centrallix-lib/tests/test_qprintf_00.c @@ -26,11 +26,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - snprintf(buf+4, 36, "this is a string non-overflow test."); - snprintf(buf+4, 36, "this is a string non-overflow test."); - snprintf(buf+4, 36, "this is a string non-overflow test."); - snprintf(buf+4, 36, "this is a string non-overflow test."); - assert(!strcmp(buf+4,"this is a string non-overflow test.")); + snprintf((char*)buf+4, 36, "this is a string non-overflow test."); + snprintf((char*)buf+4, 36, "this is a string non-overflow test."); + snprintf((char*)buf+4, 36, "this is a string non-overflow test."); + snprintf((char*)buf+4, 36, "this is a string non-overflow test."); + assert(!strcmp((char*)buf+4,"this is a string non-overflow test.")); assert(buf[43] == '\n'); assert(buf[42] == '\0'); assert(buf[41] == 0xff); @@ -44,4 +44,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_01.c b/centrallix-lib/tests/test_qprintf_01.c index d97e1aeed..e4299f241 100644 --- a/centrallix-lib/tests/test_qprintf_01.c +++ b/centrallix-lib/tests/test_qprintf_01.c @@ -26,11 +26,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 36, "this is a string non-overflow test."); - qpfPrintf(NULL, buf+4, 36, "this is a string non-overflow test."); - qpfPrintf(NULL, buf+4, 36, "this is a string non-overflow test."); - qpfPrintf(NULL, buf+4, 36, "this is a string non-overflow test."); - assert(!strcmp(buf+4,"this is a string non-overflow test.")); + qpfPrintf(NULL, (char*)buf+4, 36, "this is a string non-overflow test."); + qpfPrintf(NULL, (char*)buf+4, 36, "this is a string non-overflow test."); + qpfPrintf(NULL, (char*)buf+4, 36, "this is a string non-overflow test."); + qpfPrintf(NULL, (char*)buf+4, 36, "this is a string non-overflow test."); + assert(!strcmp((char*)buf+4,"this is a string non-overflow test.")); assert(buf[43] == '\n'); assert(buf[42] == '\0'); assert(buf[41] == 0xff); @@ -44,4 +44,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_02.c b/centrallix-lib/tests/test_qprintf_02.c index 03408c99f..bb59c7792 100644 --- a/centrallix-lib/tests/test_qprintf_02.c +++ b/centrallix-lib/tests/test_qprintf_02.c @@ -26,11 +26,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 36, "this is a string non-overflow test.... NOT!"); - qpfPrintf(NULL, buf+4, 36, "this is a string non-overflow test.... NOT!"); - qpfPrintf(NULL, buf+4, 36, "this is a string non-overflow test.... NOT!"); - qpfPrintf(NULL, buf+4, 36, "this is a string non-overflow test.... NOT!"); - assert(!strcmp(buf+4,"this is a string non-overflow test.")); + qpfPrintf(NULL, (char*)buf+4, 36, "this is a string non-overflow test.... NOT!"); + qpfPrintf(NULL, (char*)buf+4, 36, "this is a string non-overflow test.... NOT!"); + qpfPrintf(NULL, (char*)buf+4, 36, "this is a string non-overflow test.... NOT!"); + qpfPrintf(NULL, (char*)buf+4, 36, "this is a string non-overflow test.... NOT!"); + assert(!strcmp((char*)buf+4,"this is a string non-overflow test.")); assert(buf[43] == '\n'); assert(buf[42] == '\0'); assert(buf[41] == 0xff); @@ -44,4 +44,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_03.c b/centrallix-lib/tests/test_qprintf_03.c index dc6e5611b..e0660555e 100644 --- a/centrallix-lib/tests/test_qprintf_03.c +++ b/centrallix-lib/tests/test_qprintf_03.c @@ -26,11 +26,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 36, "this is a string non-overflow test.?"); - qpfPrintf(NULL, buf+4, 36, "this is a string non-overflow test.?"); - qpfPrintf(NULL, buf+4, 36, "this is a string non-overflow test.?"); - qpfPrintf(NULL, buf+4, 36, "this is a string non-overflow test.?"); - assert(!strcmp(buf+4,"this is a string non-overflow test.")); + qpfPrintf(NULL, (char*)buf+4, 36, "this is a string non-overflow test.?"); + qpfPrintf(NULL, (char*)buf+4, 36, "this is a string non-overflow test.?"); + qpfPrintf(NULL, (char*)buf+4, 36, "this is a string non-overflow test.?"); + qpfPrintf(NULL, (char*)buf+4, 36, "this is a string non-overflow test.?"); + assert(!strcmp((char*)buf+4,"this is a string non-overflow test.")); assert(buf[43] == '\n'); assert(buf[42] == '\0'); assert(buf[41] == 0xff); @@ -44,4 +44,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_04.c b/centrallix-lib/tests/test_qprintf_04.c index 08ad5f8df..afba05ba8 100644 --- a/centrallix-lib/tests/test_qprintf_04.c +++ b/centrallix-lib/tests/test_qprintf_04.c @@ -27,11 +27,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 36, ""); - qpfPrintf(NULL, buf+4, 36, ""); - qpfPrintf(NULL, buf+4, 36, ""); - qpfPrintf(NULL, buf+4, 36, ""); - assert(!strcmp(buf+4,"")); + qpfPrintf(NULL, (char*)buf+4, 36, ""); + qpfPrintf(NULL, (char*)buf+4, 36, ""); + qpfPrintf(NULL, (char*)buf+4, 36, ""); + qpfPrintf(NULL, (char*)buf+4, 36, ""); + assert(!strcmp((char*)buf+4,"")); assert(buf[43] == '\n'); assert(buf[42] == '\0'); assert(buf[41] == 0xff); @@ -46,4 +46,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_05.c b/centrallix-lib/tests/test_qprintf_05.c index 66be194c3..809aa6637 100644 --- a/centrallix-lib/tests/test_qprintf_05.c +++ b/centrallix-lib/tests/test_qprintf_05.c @@ -28,10 +28,10 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 0, "this is a string overflow test."); - qpfPrintf(NULL, buf+4, 0, "this is a string overflow test."); - qpfPrintf(NULL, buf+4, 0, "this is a string overflow test."); - qpfPrintf(NULL, buf+4, 0, "this is a string overflow test."); + qpfPrintf(NULL, (char*)buf+4, 0, "this is a string overflow test."); + qpfPrintf(NULL, (char*)buf+4, 0, "this is a string overflow test."); + qpfPrintf(NULL, (char*)buf+4, 0, "this is a string overflow test."); + qpfPrintf(NULL, (char*)buf+4, 0, "this is a string overflow test."); assert(buf[43] == '\n'); assert(buf[42] == '\0'); assert(buf[41] == 0xff); @@ -47,4 +47,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_06.c b/centrallix-lib/tests/test_qprintf_06.c index ee23a414b..150c16128 100644 --- a/centrallix-lib/tests/test_qprintf_06.c +++ b/centrallix-lib/tests/test_qprintf_06.c @@ -28,11 +28,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 1, "this is a string overflow test."); - qpfPrintf(NULL, buf+4, 1, "this is a string overflow test."); - qpfPrintf(NULL, buf+4, 1, "this is a string overflow test."); - qpfPrintf(NULL, buf+4, 1, "this is a string overflow test."); - assert(!strcmp(buf+4, "")); + qpfPrintf(NULL, (char*)buf+4, 1, "this is a string overflow test."); + qpfPrintf(NULL, (char*)buf+4, 1, "this is a string overflow test."); + qpfPrintf(NULL, (char*)buf+4, 1, "this is a string overflow test."); + qpfPrintf(NULL, (char*)buf+4, 1, "this is a string overflow test."); + assert(!strcmp((char*)buf+4, "")); assert(buf[43] == '\n'); assert(buf[42] == '\0'); assert(buf[41] == 0xff); @@ -48,4 +48,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_07.c b/centrallix-lib/tests/test_qprintf_07.c index 8cceeb97c..6dbf0b302 100644 --- a/centrallix-lib/tests/test_qprintf_07.c +++ b/centrallix-lib/tests/test_qprintf_07.c @@ -25,11 +25,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 36, "The word %STR is our data.", "STRING"); - qpfPrintf(NULL, buf+4, 36, "The word %STR is our data.", "STRING"); - qpfPrintf(NULL, buf+4, 36, "The word %STR is our data.", "STRING"); - rval = qpfPrintf(NULL, buf+4, 36, "The word %STR is our data.", "STRING"); - assert(!strcmp(buf+4, "The word STRING is our data.")); + qpfPrintf(NULL, (char*)buf+4, 36, "The word %STR is our data.", "STRING"); + qpfPrintf(NULL, (char*)buf+4, 36, "The word %STR is our data.", "STRING"); + qpfPrintf(NULL, (char*)buf+4, 36, "The word %STR is our data.", "STRING"); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "The word %STR is our data.", "STRING"); + assert(!strcmp((char*)buf+4, "The word STRING is our data.")); assert(rval == 28); assert(buf[43] == '\n'); assert(buf[42] == '\0'); @@ -43,4 +43,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_08.c b/centrallix-lib/tests/test_qprintf_08.c index efb242665..cbaf8236f 100644 --- a/centrallix-lib/tests/test_qprintf_08.c +++ b/centrallix-lib/tests/test_qprintf_08.c @@ -25,11 +25,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 36, "This is our data: %STR", "STRING"); - qpfPrintf(NULL, buf+4, 36, "This is our data: %STR", "STRING"); - qpfPrintf(NULL, buf+4, 36, "This is our data: %STR", "STRING"); - rval = qpfPrintf(NULL, buf+4, 36, "This is our data: %STR", "STRING"); - assert(!strcmp(buf+4, "This is our data: STRING")); + qpfPrintf(NULL, (char*)buf+4, 36, "This is our data: %STR", "STRING"); + qpfPrintf(NULL, (char*)buf+4, 36, "This is our data: %STR", "STRING"); + qpfPrintf(NULL, (char*)buf+4, 36, "This is our data: %STR", "STRING"); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "This is our data: %STR", "STRING"); + assert(!strcmp((char*)buf+4, "This is our data: STRING")); assert(rval == 24); assert(buf[43] == '\n'); assert(buf[42] == '\0'); @@ -43,4 +43,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_09.c b/centrallix-lib/tests/test_qprintf_09.c index 303f23325..f217856c0 100644 --- a/centrallix-lib/tests/test_qprintf_09.c +++ b/centrallix-lib/tests/test_qprintf_09.c @@ -25,11 +25,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 36, "%STR is our data today.", "STRING"); - qpfPrintf(NULL, buf+4, 36, "%STR is our data today.", "STRING"); - qpfPrintf(NULL, buf+4, 36, "%STR is our data today.", "STRING"); - rval = qpfPrintf(NULL, buf+4, 36, "%STR is our data today.", "STRING"); - assert(!strcmp(buf+4, "STRING is our data today.")); + qpfPrintf(NULL, (char*)buf+4, 36, "%STR is our data today.", "STRING"); + qpfPrintf(NULL, (char*)buf+4, 36, "%STR is our data today.", "STRING"); + qpfPrintf(NULL, (char*)buf+4, 36, "%STR is our data today.", "STRING"); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "%STR is our data today.", "STRING"); + assert(!strcmp((char*)buf+4, "STRING is our data today.")); assert(rval == 25); assert(buf[43] == '\n'); assert(buf[42] == '\0'); @@ -43,4 +43,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_10.c b/centrallix-lib/tests/test_qprintf_10.c index 0a010f6e8..8651fc7cd 100644 --- a/centrallix-lib/tests/test_qprintf_10.c +++ b/centrallix-lib/tests/test_qprintf_10.c @@ -25,11 +25,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - snprintf(buf+4, 36, "The word %s is our data.", "STRING"); - snprintf(buf+4, 36, "The word %s is our data.", "STRING"); - snprintf(buf+4, 36, "The word %s is our data.", "STRING"); - rval = snprintf(buf+4, 36, "The word %s is our data.", "STRING"); - assert(!strcmp(buf+4,"The word STRING is our data.")); + snprintf((char*)buf+4, 36, "The word %s is our data.", "STRING"); + snprintf((char*)buf+4, 36, "The word %s is our data.", "STRING"); + snprintf((char*)buf+4, 36, "The word %s is our data.", "STRING"); + rval = snprintf((char*)buf+4, 36, "The word %s is our data.", "STRING"); + assert(!strcmp((char*)buf+4,"The word STRING is our data.")); assert(rval == 28); assert(buf[43] == '\n'); assert(buf[42] == '\0'); @@ -43,4 +43,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_11.c b/centrallix-lib/tests/test_qprintf_11.c index fcec20d46..01e3f0b00 100644 --- a/centrallix-lib/tests/test_qprintf_11.c +++ b/centrallix-lib/tests/test_qprintf_11.c @@ -25,11 +25,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 36, "The word %STR is our data... this is an overflow", "STRING"); - qpfPrintf(NULL, buf+4, 36, "The word %STR is our data... this is an overflow", "STRING"); - qpfPrintf(NULL, buf+4, 36, "The word %STR is our data... this is an overflow", "STRING"); - rval = qpfPrintf(NULL, buf+4, 36, "The word %STR is our data... this is an overflow", "STRING"); - assert(!strcmp(buf+4, "The word STRING is our data... this")); + qpfPrintf(NULL, (char*)buf+4, 36, "The word %STR is our data... this is an overflow", "STRING"); + qpfPrintf(NULL, (char*)buf+4, 36, "The word %STR is our data... this is an overflow", "STRING"); + qpfPrintf(NULL, (char*)buf+4, 36, "The word %STR is our data... this is an overflow", "STRING"); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "The word %STR is our data... this is an overflow", "STRING"); + assert(!strcmp((char*)buf+4, "The word STRING is our data... this")); assert(rval == 50); assert(buf[43] == '\n'); assert(buf[42] == '\0'); @@ -43,4 +43,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_12.c b/centrallix-lib/tests/test_qprintf_12.c index db4be2ddc..8df3763b7 100644 --- a/centrallix-lib/tests/test_qprintf_12.c +++ b/centrallix-lib/tests/test_qprintf_12.c @@ -25,11 +25,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 36, "The overflow this is data word %STR is our......", "STRING"); - qpfPrintf(NULL, buf+4, 36, "The overflow this is data word %STR is our......", "STRING"); - qpfPrintf(NULL, buf+4, 36, "The overflow this is data word %STR is our......", "STRING"); - rval = qpfPrintf(NULL, buf+4, 36, "The overflow this is data word %STR is our......", "STRING"); - assert(!strcmp(buf+4, "The overflow this is data word STRI")); + qpfPrintf(NULL, (char*)buf+4, 36, "The overflow this is data word %STR is our......", "STRING"); + qpfPrintf(NULL, (char*)buf+4, 36, "The overflow this is data word %STR is our......", "STRING"); + qpfPrintf(NULL, (char*)buf+4, 36, "The overflow this is data word %STR is our......", "STRING"); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "The overflow this is data word %STR is our......", "STRING"); + assert(!strcmp((char*)buf+4, "The overflow this is data word STRI")); assert(rval == 50); assert(buf[43] == '\n'); assert(buf[42] == '\0'); @@ -43,4 +43,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_13.c b/centrallix-lib/tests/test_qprintf_13.c index d7f091a1a..218d23c06 100644 --- a/centrallix-lib/tests/test_qprintf_13.c +++ b/centrallix-lib/tests/test_qprintf_13.c @@ -25,11 +25,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 36, "The overflow this is...... data word %STR is our", "STRING"); - qpfPrintf(NULL, buf+4, 36, "The overflow this is...... data word %STR is our", "STRING"); - qpfPrintf(NULL, buf+4, 36, "The overflow this is...... data word %STR is our", "STRING"); - rval = qpfPrintf(NULL, buf+4, 36, "The overflow this is...... data word %STR is our", "STRING"); - assert(!strcmp(buf+4, "The overflow this is...... data wor")); + qpfPrintf(NULL, (char*)buf+4, 36, "The overflow this is...... data word %STR is our", "STRING"); + qpfPrintf(NULL, (char*)buf+4, 36, "The overflow this is...... data word %STR is our", "STRING"); + qpfPrintf(NULL, (char*)buf+4, 36, "The overflow this is...... data word %STR is our", "STRING"); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "The overflow this is...... data word %STR is our", "STRING"); + assert(!strcmp((char*)buf+4, "The overflow this is...... data wor")); assert(rval == 50); assert(buf[43] == '\n'); assert(buf[42] == '\0'); @@ -43,4 +43,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_14.c b/centrallix-lib/tests/test_qprintf_14.c index 4e973cf8b..2ba906042 100644 --- a/centrallix-lib/tests/test_qprintf_14.c +++ b/centrallix-lib/tests/test_qprintf_14.c @@ -25,11 +25,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 36, "Here is the integer: %INT...", 12345); - qpfPrintf(NULL, buf+4, 36, "Here is the integer: %INT...", 12345); - qpfPrintf(NULL, buf+4, 36, "Here is the integer: %INT...", 12345); - rval = qpfPrintf(NULL, buf+4, 36, "Here is the integer: %INT...", 12345); - assert(!strcmp(buf+4, "Here is the integer: 12345...")); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the integer: %INT...", 12345); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the integer: %INT...", 12345); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the integer: %INT...", 12345); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "Here is the integer: %INT...", 12345); + assert(!strcmp((char*)buf+4, "Here is the integer: 12345...")); assert(rval == 29); assert(buf[43] == '\n'); assert(buf[42] == '\0'); @@ -43,4 +43,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_15.c b/centrallix-lib/tests/test_qprintf_15.c index 49328270f..ecd55c896 100644 --- a/centrallix-lib/tests/test_qprintf_15.c +++ b/centrallix-lib/tests/test_qprintf_15.c @@ -25,11 +25,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 36, "Here is the integer: %POS...", 12345); - qpfPrintf(NULL, buf+4, 36, "Here is the integer: %POS...", 12345); - qpfPrintf(NULL, buf+4, 36, "Here is the integer: %POS...", 12345); - rval = qpfPrintf(NULL, buf+4, 36, "Here is the integer: %POS...", 12345); - assert(!strcmp(buf+4, "Here is the integer: 12345...")); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the integer: %POS...", 12345); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the integer: %POS...", 12345); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the integer: %POS...", 12345); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "Here is the integer: %POS...", 12345); + assert(!strcmp((char*)buf+4, "Here is the integer: 12345...")); assert(rval == 29); assert(buf[43] == '\n'); assert(buf[42] == '\0'); @@ -43,4 +43,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_16.c b/centrallix-lib/tests/test_qprintf_16.c index 8b596b621..84a5c1eda 100644 --- a/centrallix-lib/tests/test_qprintf_16.c +++ b/centrallix-lib/tests/test_qprintf_16.c @@ -26,11 +26,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 36, "Here is the integer: %POS...", -12345); - qpfPrintf(NULL, buf+4, 36, "Here is the integer: %POS...", -12345); - qpfPrintf(NULL, buf+4, 36, "Here is the integer: %POS...", -12345); - rval = qpfPrintf(NULL, buf+4, 36, "Here is the integer: %POS...", -12345); - assert(strlen(buf+4) <= 30); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the integer: %POS...", -12345); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the integer: %POS...", -12345); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the integer: %POS...", -12345); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "Here is the integer: %POS...", -12345); + assert(strlen((char*)buf+4) <= 30); assert(rval == -EINVAL); assert(buf[43] == '\n'); assert(buf[42] == '\0'); @@ -44,4 +44,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_17.c b/centrallix-lib/tests/test_qprintf_17.c index ae2f71248..bbd470743 100644 --- a/centrallix-lib/tests/test_qprintf_17.c +++ b/centrallix-lib/tests/test_qprintf_17.c @@ -25,11 +25,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 36, "Here is the string: %6STR...", "STRING"); - qpfPrintf(NULL, buf+4, 36, "Here is the string: %6STR...", "STRING"); - qpfPrintf(NULL, buf+4, 36, "Here is the string: %6STR...", "STRING"); - rval = qpfPrintf(NULL, buf+4, 36, "Here is the string: %6STR...", "STRING"); - assert(!strcmp(buf+4, "Here is the string: STRING...")); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the string: %6STR...", "STRING"); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the string: %6STR...", "STRING"); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the string: %6STR...", "STRING"); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "Here is the string: %6STR...", "STRING"); + assert(!strcmp((char*)buf+4, "Here is the string: STRING...")); assert(rval == 29); assert(buf[43] == '\n'); assert(buf[42] == '\0'); @@ -43,4 +43,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_18.c b/centrallix-lib/tests/test_qprintf_18.c index 3011375e2..d42b970a5 100644 --- a/centrallix-lib/tests/test_qprintf_18.c +++ b/centrallix-lib/tests/test_qprintf_18.c @@ -25,11 +25,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 36, "Here is the string: %8STR...", "STRINGSTR"); - qpfPrintf(NULL, buf+4, 36, "Here is the string: %8STR...", "STRINGSTR"); - qpfPrintf(NULL, buf+4, 36, "Here is the string: %8STR...", "STRINGSTR"); - rval = qpfPrintf(NULL, buf+4, 36, "Here is the string: %8STR...", "STRINGSTR"); - assert(!strcmp(buf+4, "Here is the string: STRINGST...")); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the string: %8STR...", "STRINGSTR"); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the string: %8STR...", "STRINGSTR"); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the string: %8STR...", "STRINGSTR"); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "Here is the string: %8STR...", "STRINGSTR"); + assert(!strcmp((char*)buf+4, "Here is the string: STRINGST...")); assert(rval == 31); assert(buf[43] == '\n'); assert(buf[42] == '\0'); @@ -43,4 +43,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_19.c b/centrallix-lib/tests/test_qprintf_19.c index 00d26dbe6..028c622d1 100644 --- a/centrallix-lib/tests/test_qprintf_19.c +++ b/centrallix-lib/tests/test_qprintf_19.c @@ -25,11 +25,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 36, "Here is the string: %*STR...", 8, "STRINGSTR"); - qpfPrintf(NULL, buf+4, 36, "Here is the string: %*STR...", 8, "STRINGSTR"); - qpfPrintf(NULL, buf+4, 36, "Here is the string: %*STR...", 8, "STRINGSTR"); - rval = qpfPrintf(NULL, buf+4, 36, "Here is the string: %*STR...", 8, "STRINGSTR"); - assert(!strcmp(buf+4, "Here is the string: STRINGST...")); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the string: %*STR...", 8, "STRINGSTR"); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the string: %*STR...", 8, "STRINGSTR"); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the string: %*STR...", 8, "STRINGSTR"); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "Here is the string: %*STR...", 8, "STRINGSTR"); + assert(!strcmp((char*)buf+4, "Here is the string: STRINGST...")); assert(rval == 31); assert(buf[43] == '\n'); assert(buf[42] == '\0'); @@ -43,4 +43,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_20.c b/centrallix-lib/tests/test_qprintf_20.c index bb3ed05e4..72d44aa8b 100644 --- a/centrallix-lib/tests/test_qprintf_20.c +++ b/centrallix-lib/tests/test_qprintf_20.c @@ -25,11 +25,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 36, "Here is the char: %CHR...", 'c'); - qpfPrintf(NULL, buf+4, 36, "Here is the char: %CHR...", 'c'); - qpfPrintf(NULL, buf+4, 36, "Here is the char: %CHR...", 'c'); - rval = qpfPrintf(NULL, buf+4, 36, "Here is the char: %CHR...", 'c'); - assert(!strcmp(buf+4, "Here is the char: c...")); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the char: %CHR...", 'c'); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the char: %CHR...", 'c'); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the char: %CHR...", 'c'); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "Here is the char: %CHR...", 'c'); + assert(!strcmp((char*)buf+4, "Here is the char: c...")); assert(rval == 22); assert(buf[43] == '\n'); assert(buf[42] == '\0'); @@ -43,4 +43,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_21.c b/centrallix-lib/tests/test_qprintf_21.c index d559f7c58..62afbe8a3 100644 --- a/centrallix-lib/tests/test_qprintf_21.c +++ b/centrallix-lib/tests/test_qprintf_21.c @@ -25,11 +25,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 36, "Here is the value: %DBL...", 3.14159); - qpfPrintf(NULL, buf+4, 36, "Here is the value: %DBL...", 3.14159); - qpfPrintf(NULL, buf+4, 36, "Here is the value: %DBL...", 3.14159); - rval = qpfPrintf(NULL, buf+4, 36, "Here is the value: %DBL...", 3.14159); - assert(!strcmp(buf+4, "Here is the value: 3.141590...")); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the value: %DBL...", 3.14159); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the value: %DBL...", 3.14159); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the value: %DBL...", 3.14159); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "Here is the value: %DBL...", 3.14159); + assert(!strcmp((char*)buf+4, "Here is the value: 3.141590...")); assert(rval == 30); assert(buf[43] == '\n'); assert(buf[42] == '\0'); @@ -43,4 +43,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_22.c b/centrallix-lib/tests/test_qprintf_22.c index 9572ff44f..9c1d5fb2a 100644 --- a/centrallix-lib/tests/test_qprintf_22.c +++ b/centrallix-lib/tests/test_qprintf_22.c @@ -25,11 +25,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 36, "Here is the str: %STR&6LEN...", "STRINGSTR"); - qpfPrintf(NULL, buf+4, 36, "Here is the str: %STR&6LEN...", "STRINGSTR"); - qpfPrintf(NULL, buf+4, 36, "Here is the str: %STR&6LEN...", "STRINGSTR"); - rval = qpfPrintf(NULL, buf+4, 36, "Here is the str: %STR&6LEN...", "STRINGSTR"); - assert(!strcmp(buf+4, "Here is the str: STRING...")); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: %STR&6LEN...", "STRINGSTR"); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: %STR&6LEN...", "STRINGSTR"); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: %STR&6LEN...", "STRINGSTR"); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: %STR&6LEN...", "STRINGSTR"); + assert(!strcmp((char*)buf+4, "Here is the str: STRING...")); assert(rval == 26); assert(buf[43] == '\n'); assert(buf[42] == '\0'); @@ -43,4 +43,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_23.c b/centrallix-lib/tests/test_qprintf_23.c index 456b700c8..360f9fb98 100644 --- a/centrallix-lib/tests/test_qprintf_23.c +++ b/centrallix-lib/tests/test_qprintf_23.c @@ -25,11 +25,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 36, "Here is the str: %STR&6LEN...", "STR"); - qpfPrintf(NULL, buf+4, 36, "Here is the str: %STR&6LEN...", "STR"); - qpfPrintf(NULL, buf+4, 36, "Here is the str: %STR&6LEN...", "STR"); - rval = qpfPrintf(NULL, buf+4, 36, "Here is the str: %STR&6LEN...", "STR"); - assert(!strcmp(buf+4, "Here is the str: STR...")); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: %STR&6LEN...", "STR"); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: %STR&6LEN...", "STR"); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: %STR&6LEN...", "STR"); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: %STR&6LEN...", "STR"); + assert(!strcmp((char*)buf+4, "Here is the str: STR...")); assert(rval == 23); assert(buf[43] == '\n'); assert(buf[42] == '\0'); @@ -43,4 +43,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_24.c b/centrallix-lib/tests/test_qprintf_24.c index 1e4ab30ad..d93c4a9f7 100644 --- a/centrallix-lib/tests/test_qprintf_24.c +++ b/centrallix-lib/tests/test_qprintf_24.c @@ -25,11 +25,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 36, "Here is the str: %STR&*LEN...", 8, "STRING"); - qpfPrintf(NULL, buf+4, 36, "Here is the str: %STR&*LEN...", 8, "STRING"); - qpfPrintf(NULL, buf+4, 36, "Here is the str: %STR&*LEN...", 8, "STRING"); - rval = qpfPrintf(NULL, buf+4, 36, "Here is the str: %STR&*LEN...", 8, "STRING"); - assert(!strcmp(buf+4, "Here is the str: STRING...")); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: %STR&*LEN...", 8, "STRING"); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: %STR&*LEN...", 8, "STRING"); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: %STR&*LEN...", 8, "STRING"); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: %STR&*LEN...", 8, "STRING"); + assert(!strcmp((char*)buf+4, "Here is the str: STRING...")); assert(rval == 26); assert(buf[43] == '\n'); assert(buf[42] == '\0'); @@ -43,4 +43,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_25.c b/centrallix-lib/tests/test_qprintf_25.c index 140104c47..47534b791 100644 --- a/centrallix-lib/tests/test_qprintf_25.c +++ b/centrallix-lib/tests/test_qprintf_25.c @@ -25,11 +25,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 36, "Here is the str: %STR&SYM...", "identifier"); - qpfPrintf(NULL, buf+4, 36, "Here is the str: %STR&SYM...", "identifier"); - qpfPrintf(NULL, buf+4, 36, "Here is the str: %STR&SYM...", "identifier"); - rval = qpfPrintf(NULL, buf+4, 36, "Here is the str: %STR&SYM...", "identifier"); - assert(!strcmp(buf+4, "Here is the str: identifier...")); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: %STR&SYM...", "identifier"); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: %STR&SYM...", "identifier"); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: %STR&SYM...", "identifier"); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: %STR&SYM...", "identifier"); + assert(!strcmp((char*)buf+4, "Here is the str: identifier...")); assert(rval == 30); assert(buf[43] == '\n'); assert(buf[42] == '\0'); @@ -43,4 +43,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_26.c b/centrallix-lib/tests/test_qprintf_26.c index 265ae41ea..37a62fc4f 100644 --- a/centrallix-lib/tests/test_qprintf_26.c +++ b/centrallix-lib/tests/test_qprintf_26.c @@ -25,10 +25,10 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 36, "Here is the str: %STR&SYM...", "00identifier"); - qpfPrintf(NULL, buf+4, 36, "Here is the str: %STR&SYM...", "00identifier"); - qpfPrintf(NULL, buf+4, 36, "Here is the str: %STR&SYM...", "00identifier"); - rval = qpfPrintf(NULL, buf+4, 36, "Here is the str: %STR&SYM...", "00identifier"); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: %STR&SYM...", "00identifier"); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: %STR&SYM...", "00identifier"); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: %STR&SYM...", "00identifier"); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: %STR&SYM...", "00identifier"); assert(rval < 0); assert(buf[43] == '\n'); assert(buf[42] == '\0'); @@ -42,4 +42,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_27.c b/centrallix-lib/tests/test_qprintf_27.c index df22c5cfd..7a81df39b 100644 --- a/centrallix-lib/tests/test_qprintf_27.c +++ b/centrallix-lib/tests/test_qprintf_27.c @@ -25,11 +25,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 36, "Here is the str: '%STR&ESCQ'...", "\"ain't\""); - qpfPrintf(NULL, buf+4, 36, "Here is the str: '%STR&ESCQ'...", "\"ain't\""); - qpfPrintf(NULL, buf+4, 36, "Here is the str: '%STR&ESCQ'...", "\"ain't\""); - rval = qpfPrintf(NULL, buf+4, 36, "Here is the str: '%STR&ESCQ'...", "\"ain't\""); - assert(!strcmp(buf+4, "Here is the str: '\\\"ain\\'t\\\"'...")); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: '%STR&ESCQ'...", "\"ain't\""); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: '%STR&ESCQ'...", "\"ain't\""); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: '%STR&ESCQ'...", "\"ain't\""); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: '%STR&ESCQ'...", "\"ain't\""); + assert(!strcmp((char*)buf+4, "Here is the str: '\\\"ain\\'t\\\"'...")); assert(rval == 32); assert(buf[43] == '\n'); assert(buf[42] == '\0'); @@ -43,4 +43,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_28.c b/centrallix-lib/tests/test_qprintf_28.c index 319576d5e..3b203d51e 100644 --- a/centrallix-lib/tests/test_qprintf_28.c +++ b/centrallix-lib/tests/test_qprintf_28.c @@ -25,11 +25,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 36, "Here is the str: '%STR&ESCQ'...", "\"ain't\""); - qpfPrintf(NULL, buf+4, 36, "Here is the str: '%STR&ESCQ'...", "\"ain't\""); - qpfPrintf(NULL, buf+4, 36, "Here is the str: '%STR&ESCQ'...", "\"ain't\""); - rval = qpfPrintf(NULL, buf+4, 36, "Here is the str: '%STR&ESCQ'...", "\"ain't\""); - assert(!strcmp(buf+4, "Here is the str: '\\\"ain\\'t\\\"")); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: '%STR&ESCQ'...", "\"ain't\""); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: '%STR&ESCQ'...", "\"ain't\""); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: '%STR&ESCQ'...", "\"ain't\""); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: '%STR&ESCQ'...", "\"ain't\""); + assert(!strcmp((char*)buf+4, "Here is the str: '\\\"ain\\'t\\\"")); assert(rval == 39); assert(buf[43] == '\n'); assert(buf[42] == '\0'); @@ -43,4 +43,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_29.c b/centrallix-lib/tests/test_qprintf_29.c index cf74cbcae..162233286 100644 --- a/centrallix-lib/tests/test_qprintf_29.c +++ b/centrallix-lib/tests/test_qprintf_29.c @@ -25,11 +25,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 36, "Here is the str: '%STR&ESCQ'...", "\"ain't\""); - qpfPrintf(NULL, buf+4, 36, "Here is the str: '%STR&ESCQ'...", "\"ain't\""); - qpfPrintf(NULL, buf+4, 36, "Here is the str: '%STR&ESCQ'...", "\"ain't\""); - rval = qpfPrintf(NULL, buf+4, 36, "Here is the str: '%STR&ESCQ'...", "\"ain't\""); - assert(!strcmp(buf+4, "Here is the str: '\\\"ain\\'t")); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: '%STR&ESCQ'...", "\"ain't\""); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: '%STR&ESCQ'...", "\"ain't\""); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: '%STR&ESCQ'...", "\"ain't\""); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: '%STR&ESCQ'...", "\"ain't\""); + assert(!strcmp((char*)buf+4, "Here is the str: '\\\"ain\\'t")); assert(rval == 40); assert(buf[43] == '\n'); assert(buf[42] == '\0'); @@ -43,4 +43,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_30.c b/centrallix-lib/tests/test_qprintf_30.c index 963092b18..efdbfce96 100644 --- a/centrallix-lib/tests/test_qprintf_30.c +++ b/centrallix-lib/tests/test_qprintf_30.c @@ -25,11 +25,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 36, "Here is the str: '%STR&ESCQ'...", "\"ain't\""); - qpfPrintf(NULL, buf+4, 36, "Here is the str: '%STR&ESCQ'...", "\"ain't\""); - qpfPrintf(NULL, buf+4, 36, "Here is the str: '%STR&ESCQ'...", "\"ain't\""); - rval = qpfPrintf(NULL, buf+4, 36, "Here is the str: '%STR&ESCQ'...", "\"ain't\""); - assert(!strcmp(buf+4, "Here is the str: '\\\"ain\\'t")); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: '%STR&ESCQ'...", "\"ain't\""); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: '%STR&ESCQ'...", "\"ain't\""); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: '%STR&ESCQ'...", "\"ain't\""); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: '%STR&ESCQ'...", "\"ain't\""); + assert(!strcmp((char*)buf+4, "Here is the str: '\\\"ain\\'t")); assert(rval == 41); assert(buf[43] == '\n'); assert(buf[42] == '\0'); @@ -43,4 +43,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_31.c b/centrallix-lib/tests/test_qprintf_31.c index fc8535fbe..cd03951b4 100644 --- a/centrallix-lib/tests/test_qprintf_31.c +++ b/centrallix-lib/tests/test_qprintf_31.c @@ -25,11 +25,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 36, "Here is the str: '%STR&ESCQ&11LEN'...", "\"ain't\""); - qpfPrintf(NULL, buf+4, 36, "Here is the str: '%STR&ESCQ&11LEN'...", "\"ain't\""); - qpfPrintf(NULL, buf+4, 36, "Here is the str: '%STR&ESCQ&11LEN'...", "\"ain't\""); - rval = qpfPrintf(NULL, buf+4, 36, "Here is the str: '%STR&ESCQ&11LEN'...", "\"ain't\""); - assert(!strcmp(buf+4, "Here is the str: '\\\"ain\\'t\\\"'...")); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: '%STR&ESCQ&11LEN'...", "\"ain't\""); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: '%STR&ESCQ&11LEN'...", "\"ain't\""); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: '%STR&ESCQ&11LEN'...", "\"ain't\""); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: '%STR&ESCQ&11LEN'...", "\"ain't\""); + assert(!strcmp((char*)buf+4, "Here is the str: '\\\"ain\\'t\\\"'...")); assert(rval == 32); assert(buf[43] == '\n'); assert(buf[42] == '\0'); @@ -43,4 +43,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_32.c b/centrallix-lib/tests/test_qprintf_32.c index 8781ab38a..356fb29fc 100644 --- a/centrallix-lib/tests/test_qprintf_32.c +++ b/centrallix-lib/tests/test_qprintf_32.c @@ -25,11 +25,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 36, "Here is the str: '%STR&ESCQ&10LEN'...", "\"ain't\""); - qpfPrintf(NULL, buf+4, 36, "Here is the str: '%STR&ESCQ&10LEN'...", "\"ain't\""); - qpfPrintf(NULL, buf+4, 36, "Here is the str: '%STR&ESCQ&10LEN'...", "\"ain't\""); - rval = qpfPrintf(NULL, buf+4, 36, "Here is the str: '%STR&ESCQ&10LEN'...", "\"ain't\""); - assert(!strcmp(buf+4, "Here is the str: '\\\"ain\\'t\\\"'...")); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: '%STR&ESCQ&10LEN'...", "\"ain't\""); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: '%STR&ESCQ&10LEN'...", "\"ain't\""); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: '%STR&ESCQ&10LEN'...", "\"ain't\""); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: '%STR&ESCQ&10LEN'...", "\"ain't\""); + assert(!strcmp((char*)buf+4, "Here is the str: '\\\"ain\\'t\\\"'...")); assert(rval == 32); assert(buf[43] == '\n'); assert(buf[42] == '\0'); @@ -43,4 +43,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_33.c b/centrallix-lib/tests/test_qprintf_33.c index 2c4714e59..39fe591be 100644 --- a/centrallix-lib/tests/test_qprintf_33.c +++ b/centrallix-lib/tests/test_qprintf_33.c @@ -25,11 +25,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 36, "Here is the str: '%STR&ESCQ&9LEN'...", "\"ain't\""); - qpfPrintf(NULL, buf+4, 36, "Here is the str: '%STR&ESCQ&9LEN'...", "\"ain't\""); - qpfPrintf(NULL, buf+4, 36, "Here is the str: '%STR&ESCQ&9LEN'...", "\"ain't\""); - rval = qpfPrintf(NULL, buf+4, 36, "Here is the str: '%STR&ESCQ&9LEN'...", "\"ain't\""); - assert(!strcmp(buf+4, "Here is the str: '\\\"ain\\'t'...")); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: '%STR&ESCQ&9LEN'...", "\"ain't\""); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: '%STR&ESCQ&9LEN'...", "\"ain't\""); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: '%STR&ESCQ&9LEN'...", "\"ain't\""); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: '%STR&ESCQ&9LEN'...", "\"ain't\""); + assert(!strcmp((char*)buf+4, "Here is the str: '\\\"ain\\'t'...")); assert(rval == 30); assert(buf[43] == '\n'); assert(buf[42] == '\0'); @@ -43,4 +43,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_34.c b/centrallix-lib/tests/test_qprintf_34.c index 468575cfa..60ca51cd9 100644 --- a/centrallix-lib/tests/test_qprintf_34.c +++ b/centrallix-lib/tests/test_qprintf_34.c @@ -25,11 +25,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 36, "Here is the str: '%STR&ESCQ&8LEN'...", "\"ain't\""); - qpfPrintf(NULL, buf+4, 36, "Here is the str: '%STR&ESCQ&8LEN'...", "\"ain't\""); - qpfPrintf(NULL, buf+4, 36, "Here is the str: '%STR&ESCQ&8LEN'...", "\"ain't\""); - rval = qpfPrintf(NULL, buf+4, 36, "Here is the str: '%STR&ESCQ&8LEN'...", "\"ain't\""); - assert(!strcmp(buf+4, "Here is the str: '\\\"ain\\'t'...")); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: '%STR&ESCQ&8LEN'...", "\"ain't\""); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: '%STR&ESCQ&8LEN'...", "\"ain't\""); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: '%STR&ESCQ&8LEN'...", "\"ain't\""); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: '%STR&ESCQ&8LEN'...", "\"ain't\""); + assert(!strcmp((char*)buf+4, "Here is the str: '\\\"ain\\'t'...")); assert(rval == 30); assert(buf[43] == '\n'); assert(buf[42] == '\0'); @@ -43,4 +43,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_35.c b/centrallix-lib/tests/test_qprintf_35.c index fe2c06d08..34d33b785 100644 --- a/centrallix-lib/tests/test_qprintf_35.c +++ b/centrallix-lib/tests/test_qprintf_35.c @@ -25,11 +25,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 36, "HTML: '%STR&HTE'.", ""); - qpfPrintf(NULL, buf+4, 36, "HTML: '%STR&HTE'.", ""); - qpfPrintf(NULL, buf+4, 36, "HTML: '%STR&HTE'.", ""); - rval = qpfPrintf(NULL, buf+4, 36, "HTML: '%STR&HTE'.", ""); - assert(!strcmp(buf+4, "HTML: '<b c="w">'.")); + qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE'.", ""); + qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE'.", ""); + qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE'.", ""); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE'.", ""); + assert(!strcmp((char*)buf+4, "HTML: '<b c="w">'.")); assert(rval == 34); assert(buf[43] == '\n'); assert(buf[42] == '\0'); @@ -43,4 +43,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_36.c b/centrallix-lib/tests/test_qprintf_36.c index 918605b62..cd41edc45 100644 --- a/centrallix-lib/tests/test_qprintf_36.c +++ b/centrallix-lib/tests/test_qprintf_36.c @@ -26,11 +26,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 36, "The HTML: '%STR&HTE'.", ""); - qpfPrintf(NULL, buf+4, 36, "The HTML: '%STR&HTE'.", ""); - qpfPrintf(NULL, buf+4, 36, "The HTML: '%STR&HTE'.", ""); - rval = qpfPrintf(NULL, buf+4, 36, "The HTML: '%STR&HTE'.", ""); - assert(!strcmp(buf+4, "The HTML: '<b c="w"")); + qpfPrintf(NULL, (char*)buf+4, 36, "The HTML: '%STR&HTE'.", ""); + qpfPrintf(NULL, (char*)buf+4, 36, "The HTML: '%STR&HTE'.", ""); + qpfPrintf(NULL, (char*)buf+4, 36, "The HTML: '%STR&HTE'.", ""); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "The HTML: '%STR&HTE'.", ""); + assert(!strcmp((char*)buf+4, "The HTML: '<b c="w"")); assert(rval == 38); assert(buf[39] == '\n'); assert(buf[38] == '\0'); @@ -45,4 +45,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_37.c b/centrallix-lib/tests/test_qprintf_37.c index 9ed221c07..8dad336b2 100644 --- a/centrallix-lib/tests/test_qprintf_37.c +++ b/centrallix-lib/tests/test_qprintf_37.c @@ -25,11 +25,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 36, "HTML: '%STR&HTE&26LEN'.", ""); - qpfPrintf(NULL, buf+4, 36, "HTML: '%STR&HTE&26LEN'.", ""); - qpfPrintf(NULL, buf+4, 36, "HTML: '%STR&HTE&26LEN'.", ""); - rval = qpfPrintf(NULL, buf+4, 36, "HTML: '%STR&HTE&26LEN'.", ""); - assert(!strcmp(buf+4, "HTML: '<b c="w">'.")); + qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE&26LEN'.", ""); + qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE&26LEN'.", ""); + qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE&26LEN'.", ""); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE&26LEN'.", ""); + assert(!strcmp((char*)buf+4, "HTML: '<b c="w">'.")); assert(rval == 34); assert(buf[43] == '\n'); assert(buf[42] == '\0'); @@ -43,4 +43,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_38.c b/centrallix-lib/tests/test_qprintf_38.c index dcd9a679a..98815af42 100644 --- a/centrallix-lib/tests/test_qprintf_38.c +++ b/centrallix-lib/tests/test_qprintf_38.c @@ -25,11 +25,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 36, "HTML: '%STR&HTE&25LEN'.", ""); - qpfPrintf(NULL, buf+4, 36, "HTML: '%STR&HTE&25LEN'.", ""); - qpfPrintf(NULL, buf+4, 36, "HTML: '%STR&HTE&25LEN'.", ""); - rval = qpfPrintf(NULL, buf+4, 36, "HTML: '%STR&HTE&25LEN'.", ""); - assert(!strcmp(buf+4, "HTML: '<b c="w">'.")); + qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE&25LEN'.", ""); + qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE&25LEN'.", ""); + qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE&25LEN'.", ""); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE&25LEN'.", ""); + assert(!strcmp((char*)buf+4, "HTML: '<b c="w">'.")); assert(rval == 34); assert(buf[43] == '\n'); assert(buf[42] == '\0'); @@ -43,4 +43,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_39.c b/centrallix-lib/tests/test_qprintf_39.c index 796833f38..426b1f7af 100644 --- a/centrallix-lib/tests/test_qprintf_39.c +++ b/centrallix-lib/tests/test_qprintf_39.c @@ -25,11 +25,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 36, "HTML: '%STR&HTE&24LEN'.", ""); - qpfPrintf(NULL, buf+4, 36, "HTML: '%STR&HTE&24LEN'.", ""); - qpfPrintf(NULL, buf+4, 36, "HTML: '%STR&HTE&24LEN'.", ""); - rval = qpfPrintf(NULL, buf+4, 36, "HTML: '%STR&HTE&24LEN'.", ""); - assert(!strcmp(buf+4, "HTML: '<b c="w"'.")); + qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE&24LEN'.", ""); + qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE&24LEN'.", ""); + qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE&24LEN'.", ""); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE&24LEN'.", ""); + assert(!strcmp((char*)buf+4, "HTML: '<b c="w"'.")); assert(rval == 30); assert(buf[43] == '\n'); assert(buf[42] == '\0'); @@ -43,4 +43,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_40.c b/centrallix-lib/tests/test_qprintf_40.c index 8e5d47906..bbc67eab7 100644 --- a/centrallix-lib/tests/test_qprintf_40.c +++ b/centrallix-lib/tests/test_qprintf_40.c @@ -25,11 +25,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 36, "HTML: '%STR&HTE&23LEN'.", ""); - qpfPrintf(NULL, buf+4, 36, "HTML: '%STR&HTE&23LEN'.", ""); - qpfPrintf(NULL, buf+4, 36, "HTML: '%STR&HTE&23LEN'.", ""); - rval = qpfPrintf(NULL, buf+4, 36, "HTML: '%STR&HTE&23LEN'.", ""); - assert(!strcmp(buf+4, "HTML: '<b c="w"'.")); + qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE&23LEN'.", ""); + qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE&23LEN'.", ""); + qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE&23LEN'.", ""); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "HTML: '%STR&HTE&23LEN'.", ""); + assert(!strcmp((char*)buf+4, "HTML: '<b c="w"'.")); assert(rval == 30); assert(buf[43] == '\n'); assert(buf[42] == '\0'); @@ -43,4 +43,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_41.c b/centrallix-lib/tests/test_qprintf_41.c index 4725bad1a..4c127739d 100644 --- a/centrallix-lib/tests/test_qprintf_41.c +++ b/centrallix-lib/tests/test_qprintf_41.c @@ -25,11 +25,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 36, "Encode: '%STR&HEX'.", ""); - qpfPrintf(NULL, buf+4, 36, "Encode: '%STR&HEX'.", ""); - qpfPrintf(NULL, buf+4, 36, "Encode: '%STR&HEX'.", ""); - rval = qpfPrintf(NULL, buf+4, 36, "Encode: '%STR&HEX'.", ""); - assert(!strcmp(buf+4, "Encode: '3c6220633d2277223e'.")); + qpfPrintf(NULL, (char*)buf+4, 36, "Encode: '%STR&HEX'.", ""); + qpfPrintf(NULL, (char*)buf+4, 36, "Encode: '%STR&HEX'.", ""); + qpfPrintf(NULL, (char*)buf+4, 36, "Encode: '%STR&HEX'.", ""); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "Encode: '%STR&HEX'.", ""); + assert(!strcmp((char*)buf+4, "Encode: '3c6220633d2277223e'.")); assert(rval == 29); assert(buf[36] == '\n'); assert(buf[35] == '\0'); @@ -43,4 +43,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_42.c b/centrallix-lib/tests/test_qprintf_42.c index 8fd6528f5..eb8646f68 100644 --- a/centrallix-lib/tests/test_qprintf_42.c +++ b/centrallix-lib/tests/test_qprintf_42.c @@ -27,11 +27,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 26, "Encode: %STR&HEX", ""); - qpfPrintf(NULL, buf+4, 26, "Encode: %STR&HEX", ""); - qpfPrintf(NULL, buf+4, 26, "Encode: %STR&HEX", ""); - rval = qpfPrintf(NULL, buf+4, 26, "Encode: %STR&HEX", ""); - assert(!strcmp(buf+4, "Encode: 3c6220633d227722")); + qpfPrintf(NULL, (char*)buf+4, 26, "Encode: %STR&HEX", ""); + qpfPrintf(NULL, (char*)buf+4, 26, "Encode: %STR&HEX", ""); + qpfPrintf(NULL, (char*)buf+4, 26, "Encode: %STR&HEX", ""); + rval = qpfPrintf(NULL, (char*)buf+4, 26, "Encode: %STR&HEX", ""); + assert(!strcmp((char*)buf+4, "Encode: 3c6220633d227722")); assert(rval == 26); assert(buf[32] == '\n'); assert(buf[31] == '\0'); @@ -47,4 +47,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_43.c b/centrallix-lib/tests/test_qprintf_43.c index 5446ea559..a7a2f597a 100644 --- a/centrallix-lib/tests/test_qprintf_43.c +++ b/centrallix-lib/tests/test_qprintf_43.c @@ -27,11 +27,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 25, "Encode: %STR&HEX", ""); - qpfPrintf(NULL, buf+4, 25, "Encode: %STR&HEX", ""); - qpfPrintf(NULL, buf+4, 25, "Encode: %STR&HEX", ""); - rval = qpfPrintf(NULL, buf+4, 25, "Encode: %STR&HEX", ""); - assert(!strcmp(buf+4, "Encode: 3c6220633d227722")); + qpfPrintf(NULL, (char*)buf+4, 25, "Encode: %STR&HEX", ""); + qpfPrintf(NULL, (char*)buf+4, 25, "Encode: %STR&HEX", ""); + qpfPrintf(NULL, (char*)buf+4, 25, "Encode: %STR&HEX", ""); + rval = qpfPrintf(NULL, (char*)buf+4, 25, "Encode: %STR&HEX", ""); + assert(!strcmp((char*)buf+4, "Encode: 3c6220633d227722")); assert(rval == 26); assert(buf[32] == '\n'); assert(buf[31] == '\0'); @@ -47,4 +47,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_44.c b/centrallix-lib/tests/test_qprintf_44.c index e5170f58e..4288ae108 100644 --- a/centrallix-lib/tests/test_qprintf_44.c +++ b/centrallix-lib/tests/test_qprintf_44.c @@ -27,11 +27,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 26, "Enc: %STR&HEX...", ""); - qpfPrintf(NULL, buf+4, 26, "Enc: %STR&HEX...", ""); - qpfPrintf(NULL, buf+4, 26, "Enc: %STR&HEX...", ""); - rval = qpfPrintf(NULL, buf+4, 26, "Enc: %STR&HEX...", ""); - assert(!strcmp(buf+4, "Enc: 3c6220633d2277223e..")); + qpfPrintf(NULL, (char*)buf+4, 26, "Enc: %STR&HEX...", ""); + qpfPrintf(NULL, (char*)buf+4, 26, "Enc: %STR&HEX...", ""); + qpfPrintf(NULL, (char*)buf+4, 26, "Enc: %STR&HEX...", ""); + rval = qpfPrintf(NULL, (char*)buf+4, 26, "Enc: %STR&HEX...", ""); + assert(!strcmp((char*)buf+4, "Enc: 3c6220633d2277223e..")); assert(rval == 26); assert(buf[32] == '\n'); assert(buf[31] == '\0'); @@ -47,4 +47,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_45.c b/centrallix-lib/tests/test_qprintf_45.c index f9ee5fa85..8f53f8c32 100644 --- a/centrallix-lib/tests/test_qprintf_45.c +++ b/centrallix-lib/tests/test_qprintf_45.c @@ -27,11 +27,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 27, "Enc: %STR&HEX&18LEN...", ""); - qpfPrintf(NULL, buf+4, 27, "Enc: %STR&HEX&18LEN...", ""); - qpfPrintf(NULL, buf+4, 27, "Enc: %STR&HEX&18LEN...", ""); - rval = qpfPrintf(NULL, buf+4, 27, "Enc: %STR&HEX&18LEN...", ""); - assert(!strcmp(buf+4, "Enc: 3c6220633d2277223e...")); + qpfPrintf(NULL, (char*)buf+4, 27, "Enc: %STR&HEX&18LEN...", ""); + qpfPrintf(NULL, (char*)buf+4, 27, "Enc: %STR&HEX&18LEN...", ""); + qpfPrintf(NULL, (char*)buf+4, 27, "Enc: %STR&HEX&18LEN...", ""); + rval = qpfPrintf(NULL, (char*)buf+4, 27, "Enc: %STR&HEX&18LEN...", ""); + assert(!strcmp((char*)buf+4, "Enc: 3c6220633d2277223e...")); assert(rval == 26); assert(buf[33] == '\n'); assert(buf[32] == '\0'); @@ -47,4 +47,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_46.c b/centrallix-lib/tests/test_qprintf_46.c index 4d4739ca6..eade4eb5f 100644 --- a/centrallix-lib/tests/test_qprintf_46.c +++ b/centrallix-lib/tests/test_qprintf_46.c @@ -27,11 +27,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 27, "Enc: %STR&HEX&17LEN...", ""); - qpfPrintf(NULL, buf+4, 27, "Enc: %STR&HEX&17LEN...", ""); - qpfPrintf(NULL, buf+4, 27, "Enc: %STR&HEX&17LEN...", ""); - rval = qpfPrintf(NULL, buf+4, 27, "Enc: %STR&HEX&17LEN...", ""); - assert(!strcmp(buf+4, "Enc: 3c6220633d227722...")); + qpfPrintf(NULL, (char*)buf+4, 27, "Enc: %STR&HEX&17LEN...", ""); + qpfPrintf(NULL, (char*)buf+4, 27, "Enc: %STR&HEX&17LEN...", ""); + qpfPrintf(NULL, (char*)buf+4, 27, "Enc: %STR&HEX&17LEN...", ""); + rval = qpfPrintf(NULL, (char*)buf+4, 27, "Enc: %STR&HEX&17LEN...", ""); + assert(!strcmp((char*)buf+4, "Enc: 3c6220633d227722...")); assert(rval == 24); assert(buf[31] == '\n'); assert(buf[30] == '\0'); @@ -47,4 +47,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_47.c b/centrallix-lib/tests/test_qprintf_47.c index 21e4b5278..714aac51b 100644 --- a/centrallix-lib/tests/test_qprintf_47.c +++ b/centrallix-lib/tests/test_qprintf_47.c @@ -27,11 +27,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 27, "Enc: %STR&HEX&16LEN...", ""); - qpfPrintf(NULL, buf+4, 27, "Enc: %STR&HEX&16LEN...", ""); - qpfPrintf(NULL, buf+4, 27, "Enc: %STR&HEX&16LEN...", ""); - rval = qpfPrintf(NULL, buf+4, 27, "Enc: %STR&HEX&16LEN...", ""); - assert(!strcmp(buf+4, "Enc: 3c6220633d227722...")); + qpfPrintf(NULL, (char*)buf+4, 27, "Enc: %STR&HEX&16LEN...", ""); + qpfPrintf(NULL, (char*)buf+4, 27, "Enc: %STR&HEX&16LEN...", ""); + qpfPrintf(NULL, (char*)buf+4, 27, "Enc: %STR&HEX&16LEN...", ""); + rval = qpfPrintf(NULL, (char*)buf+4, 27, "Enc: %STR&HEX&16LEN...", ""); + assert(!strcmp((char*)buf+4, "Enc: 3c6220633d227722...")); assert(rval == 24); assert(buf[31] == '\n'); assert(buf[30] == '\0'); @@ -47,4 +47,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_48.c b/centrallix-lib/tests/test_qprintf_48.c index 93c55728a..ba83728d5 100644 --- a/centrallix-lib/tests/test_qprintf_48.c +++ b/centrallix-lib/tests/test_qprintf_48.c @@ -25,11 +25,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 36, "Here is the str: %STR"...", "\"ain't\""); - qpfPrintf(NULL, buf+4, 36, "Here is the str: %STR"...", "\"ain't\""); - qpfPrintf(NULL, buf+4, 36, "Here is the str: %STR"...", "\"ain't\""); - rval = qpfPrintf(NULL, buf+4, 36, "Here is the str: %STR"...", "\"ain't\""); - assert(!strcmp(buf+4, "Here is the str: '\\\"ain\\'t\\\"'...")); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: %STR"...", "\"ain't\""); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: %STR"...", "\"ain't\""); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: %STR"...", "\"ain't\""); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: %STR"...", "\"ain't\""); + assert(!strcmp((char*)buf+4, "Here is the str: '\\\"ain\\'t\\\"'...")); assert(rval == 32); assert(buf[43] == '\n'); assert(buf[42] == '\0'); @@ -43,4 +43,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_49.c b/centrallix-lib/tests/test_qprintf_49.c index b368f01ba..106ecdbf6 100644 --- a/centrallix-lib/tests/test_qprintf_49.c +++ b/centrallix-lib/tests/test_qprintf_49.c @@ -25,11 +25,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 36, "Here is the str: %STR&DQUOT...", "\"ain't\""); - qpfPrintf(NULL, buf+4, 36, "Here is the str: %STR&DQUOT...", "\"ain't\""); - qpfPrintf(NULL, buf+4, 36, "Here is the str: %STR&DQUOT...", "\"ain't\""); - rval = qpfPrintf(NULL, buf+4, 36, "Here is the str: %STR&DQUOT...", "\"ain't\""); - assert(!strcmp(buf+4, "Here is the str: \"\\\"ain\\'t\\\"\"...")); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: %STR&DQUOT...", "\"ain't\""); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: %STR&DQUOT...", "\"ain't\""); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: %STR&DQUOT...", "\"ain't\""); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "Here is the str: %STR&DQUOT...", "\"ain't\""); + assert(!strcmp((char*)buf+4, "Here is the str: \"\\\"ain\\'t\\\"\"...")); assert(rval == 32); assert(buf[43] == '\n'); assert(buf[42] == '\0'); @@ -43,4 +43,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_50.c b/centrallix-lib/tests/test_qprintf_50.c index 104d82396..ae75e075d 100644 --- a/centrallix-lib/tests/test_qprintf_50.c +++ b/centrallix-lib/tests/test_qprintf_50.c @@ -25,11 +25,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 33, "Here is the str...: %STR"", "\"ain't\""); - qpfPrintf(NULL, buf+4, 33, "Here is the str...: %STR"", "\"ain't\""); - qpfPrintf(NULL, buf+4, 33, "Here is the str...: %STR"", "\"ain't\""); - rval = qpfPrintf(NULL, buf+4, 33, "Here is the str...: %STR"", "\"ain't\""); - assert(!strcmp(buf+4, "Here is the str...: '\\\"ain\\'t\\\"'")); + qpfPrintf(NULL, (char*)buf+4, 33, "Here is the str...: %STR"", "\"ain't\""); + qpfPrintf(NULL, (char*)buf+4, 33, "Here is the str...: %STR"", "\"ain't\""); + qpfPrintf(NULL, (char*)buf+4, 33, "Here is the str...: %STR"", "\"ain't\""); + rval = qpfPrintf(NULL, (char*)buf+4, 33, "Here is the str...: %STR"", "\"ain't\""); + assert(!strcmp((char*)buf+4, "Here is the str...: '\\\"ain\\'t\\\"'")); assert(rval == 32); assert(buf[39] == '\n'); assert(buf[38] == '\0'); @@ -43,4 +43,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_51.c b/centrallix-lib/tests/test_qprintf_51.c index 936417dd4..0b8225f97 100644 --- a/centrallix-lib/tests/test_qprintf_51.c +++ b/centrallix-lib/tests/test_qprintf_51.c @@ -27,11 +27,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 32, "Here is the str...: %STR"", "\"ain't\""); - qpfPrintf(NULL, buf+4, 32, "Here is the str...: %STR"", "\"ain't\""); - qpfPrintf(NULL, buf+4, 32, "Here is the str...: %STR"", "\"ain't\""); - rval = qpfPrintf(NULL, buf+4, 32, "Here is the str...: %STR"", "\"ain't\""); - assert(!strcmp(buf+4, "Here is the str...: '\\\"ain\\'t'")); + qpfPrintf(NULL, (char*)buf+4, 32, "Here is the str...: %STR"", "\"ain't\""); + qpfPrintf(NULL, (char*)buf+4, 32, "Here is the str...: %STR"", "\"ain't\""); + qpfPrintf(NULL, (char*)buf+4, 32, "Here is the str...: %STR"", "\"ain't\""); + rval = qpfPrintf(NULL, (char*)buf+4, 32, "Here is the str...: %STR"", "\"ain't\""); + assert(!strcmp((char*)buf+4, "Here is the str...: '\\\"ain\\'t'")); assert(rval == 32); assert(buf[39] == '\n'); assert(buf[38] == '\0'); @@ -47,4 +47,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_52.c b/centrallix-lib/tests/test_qprintf_52.c index 16cbbd6bc..a933a30e0 100644 --- a/centrallix-lib/tests/test_qprintf_52.c +++ b/centrallix-lib/tests/test_qprintf_52.c @@ -27,11 +27,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 31, "Here is the str...: %STR"", "\"ain't\""); - qpfPrintf(NULL, buf+4, 31, "Here is the str...: %STR"", "\"ain't\""); - qpfPrintf(NULL, buf+4, 31, "Here is the str...: %STR"", "\"ain't\""); - rval = qpfPrintf(NULL, buf+4, 31, "Here is the str...: %STR"", "\"ain't\""); - assert(!strcmp(buf+4, "Here is the str...: '\\\"ain\\'t'")); + qpfPrintf(NULL, (char*)buf+4, 31, "Here is the str...: %STR"", "\"ain't\""); + qpfPrintf(NULL, (char*)buf+4, 31, "Here is the str...: %STR"", "\"ain't\""); + qpfPrintf(NULL, (char*)buf+4, 31, "Here is the str...: %STR"", "\"ain't\""); + rval = qpfPrintf(NULL, (char*)buf+4, 31, "Here is the str...: %STR"", "\"ain't\""); + assert(!strcmp((char*)buf+4, "Here is the str...: '\\\"ain\\'t'")); assert(rval == 32); assert(buf[39] == '\n'); assert(buf[38] == '\0'); @@ -47,4 +47,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_53.c b/centrallix-lib/tests/test_qprintf_53.c index d94c0b110..ec2a4dedb 100644 --- a/centrallix-lib/tests/test_qprintf_53.c +++ b/centrallix-lib/tests/test_qprintf_53.c @@ -25,11 +25,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 35, "Here is the str: %STR&HTE ", ""); - qpfPrintf(NULL, buf+4, 35, "Here is the str: %STR&HTE ", ""); - qpfPrintf(NULL, buf+4, 35, "Here is the str: %STR&HTE ", ""); - rval = qpfPrintf(NULL, buf+4, 35, "Here is the str: %STR&HTE ", ""); - assert(!strcmp(buf+4, "Here is the str: <tag> ")); + qpfPrintf(NULL, (char*)buf+4, 35, "Here is the str: %STR&HTE ", ""); + qpfPrintf(NULL, (char*)buf+4, 35, "Here is the str: %STR&HTE ", ""); + qpfPrintf(NULL, (char*)buf+4, 35, "Here is the str: %STR&HTE ", ""); + rval = qpfPrintf(NULL, (char*)buf+4, 35, "Here is the str: %STR&HTE ", ""); + assert(!strcmp((char*)buf+4, "Here is the str: <tag> ")); assert(rval == 34); assert(buf[41] == '\n'); assert(buf[40] == '\0'); @@ -43,4 +43,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_54.c b/centrallix-lib/tests/test_qprintf_54.c index 3e6cc7e79..1b9ebd1f1 100644 --- a/centrallix-lib/tests/test_qprintf_54.c +++ b/centrallix-lib/tests/test_qprintf_54.c @@ -25,11 +25,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 31, "Conditional: yes=%[yes%STR%] no=%[no%STR%]%STR", 1, "!YES!", 0, "!NO!", "."); - qpfPrintf(NULL, buf+4, 31, "Conditional: yes=%[yes%STR%] no=%[no%STR%]%STR", 1, "!YES!", 0, "!NO!", "."); - qpfPrintf(NULL, buf+4, 31, "Conditional: yes=%[yes%STR%] no=%[no%STR%]%STR", 1, "!YES!", 0, "!NO!", "."); - rval = qpfPrintf(NULL, buf+4, 31, "Conditional: yes=%[yes%STR%] no=%[no%STR%]%STR", 1, "!YES!", 0, "!NO!", "."); - assert(!strcmp(buf+4, "Conditional: yes=yes!YES! no=.")); + qpfPrintf(NULL, (char*)buf+4, 31, "Conditional: yes=%[yes%STR%] no=%[no%STR%]%STR", 1, "!YES!", 0, "!NO!", "."); + qpfPrintf(NULL, (char*)buf+4, 31, "Conditional: yes=%[yes%STR%] no=%[no%STR%]%STR", 1, "!YES!", 0, "!NO!", "."); + qpfPrintf(NULL, (char*)buf+4, 31, "Conditional: yes=%[yes%STR%] no=%[no%STR%]%STR", 1, "!YES!", 0, "!NO!", "."); + rval = qpfPrintf(NULL, (char*)buf+4, 31, "Conditional: yes=%[yes%STR%] no=%[no%STR%]%STR", 1, "!YES!", 0, "!NO!", "."); + assert(!strcmp((char*)buf+4, "Conditional: yes=yes!YES! no=.")); assert(rval == 30); assert(buf[38] == '\n'); assert(buf[37] == '\0'); @@ -43,4 +43,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_55.c b/centrallix-lib/tests/test_qprintf_55.c index f2ccfb69f..ac5d7da8d 100644 --- a/centrallix-lib/tests/test_qprintf_55.c +++ b/centrallix-lib/tests/test_qprintf_55.c @@ -25,11 +25,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 31, "/path/to/%STR&FILE/file", "myfilename.sbd"); - qpfPrintf(NULL, buf+4, 31, "/path/to/%STR&FILE/file", "myfilename.sbd"); - qpfPrintf(NULL, buf+4, 31, "/path/to/%STR&FILE/file", "myfilename.sbd"); - rval = qpfPrintf(NULL, buf+4, 31, "/path/to/%STR&FILE/file", "myfilename.sbd"); - assert(!strcmp(buf+4, "/path/to/myfilename.sbd/file")); + qpfPrintf(NULL, (char*)buf+4, 31, "/path/to/%STR&FILE/file", "myfilename.sbd"); + qpfPrintf(NULL, (char*)buf+4, 31, "/path/to/%STR&FILE/file", "myfilename.sbd"); + qpfPrintf(NULL, (char*)buf+4, 31, "/path/to/%STR&FILE/file", "myfilename.sbd"); + rval = qpfPrintf(NULL, (char*)buf+4, 31, "/path/to/%STR&FILE/file", "myfilename.sbd"); + assert(!strcmp((char*)buf+4, "/path/to/myfilename.sbd/file")); assert(rval == 28); assert(buf[35] == '\n'); assert(buf[34] == '\0'); @@ -43,4 +43,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_56.c b/centrallix-lib/tests/test_qprintf_56.c index 581d945f0..1826588c1 100644 --- a/centrallix-lib/tests/test_qprintf_56.c +++ b/centrallix-lib/tests/test_qprintf_56.c @@ -25,19 +25,19 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - rval = qpfPrintf(NULL, buf+4, 31, "/path/to/%STR&FILE/file", ".."); + rval = qpfPrintf(NULL, (char*)buf+4, 31, "/path/to/%STR&FILE/file", ".."); assert(rval < 0); - rval = qpfPrintf(NULL, buf+4, 31, "/path/to/%STR&FILE/file", "../otherdir"); + rval = qpfPrintf(NULL, (char*)buf+4, 31, "/path/to/%STR&FILE/file", "../otherdir"); assert(rval < 0); - rval = qpfPrintf(NULL, buf+4, 31, "/path/to/%STR&FILE/file", "dir/.."); + rval = qpfPrintf(NULL, (char*)buf+4, 31, "/path/to/%STR&FILE/file", "dir/.."); assert(rval < 0); - rval = qpfPrintf(NULL, buf+4, 31, "/path/to/%STR&FILE/file", "file/subfile"); + rval = qpfPrintf(NULL, (char*)buf+4, 31, "/path/to/%STR&FILE/file", "file/subfile"); assert(rval < 0); - rval = qpfPrintf(NULL, buf+4, 31, "/path/to/%STR&FILE/file", "a/../b"); + rval = qpfPrintf(NULL, (char*)buf+4, 31, "/path/to/%STR&FILE/file", "a/../b"); assert(rval < 0); - rval = qpfPrintf(NULL, buf+4, 31, "/path/to/%STR&FILE/file", "."); + rval = qpfPrintf(NULL, (char*)buf+4, 31, "/path/to/%STR&FILE/file", "."); assert(rval < 0); - rval = qpfPrintf(NULL, buf+4, 31, "/path/to/%STR&FILE/file", ""); + rval = qpfPrintf(NULL, (char*)buf+4, 31, "/path/to/%STR&FILE/file", ""); assert(rval < 0); assert(buf[35] == '\n'); assert(buf[34] == '\0'); @@ -51,4 +51,3 @@ test(char** tname) return iter*7; } - diff --git a/centrallix-lib/tests/test_qprintf_57.c b/centrallix-lib/tests/test_qprintf_57.c index bf57ff87b..d95fe460e 100644 --- a/centrallix-lib/tests/test_qprintf_57.c +++ b/centrallix-lib/tests/test_qprintf_57.c @@ -25,15 +25,15 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - rval = qpfPrintf(NULL, buf+4, 31, "/path/to/%8STR&FILE/file", "file\0ame"); + rval = qpfPrintf(NULL, (char*)buf+4, 31, "/path/to/%8STR&FILE/file", "file\0ame"); assert(rval < 0); - rval = qpfPrintf(NULL, buf+4, 31, "/path/to/%8STR&FILE/file", "filenam\0"); + rval = qpfPrintf(NULL, (char*)buf+4, 31, "/path/to/%8STR&FILE/file", "filenam\0"); assert(rval < 0); - rval = qpfPrintf(NULL, buf+4, 31, "/path/to/%8STR&FILE/file", "\0ilename"); + rval = qpfPrintf(NULL, (char*)buf+4, 31, "/path/to/%8STR&FILE/file", "\0ilename"); assert(rval < 0); - rval = qpfPrintf(NULL, buf+4, 31, "/path/to/%2STR&FILE/file", "..."); + rval = qpfPrintf(NULL, (char*)buf+4, 31, "/path/to/%2STR&FILE/file", "..."); assert(rval < 0); - rval = qpfPrintf(NULL, buf+4, 31, "/path/to/%8STR&FILE/file", "filename"); + rval = qpfPrintf(NULL, (char*)buf+4, 31, "/path/to/%8STR&FILE/file", "filename"); assert(rval == 22); assert(buf[35] == '\n'); assert(buf[34] == '\0'); @@ -47,4 +47,3 @@ test(char** tname) return iter*5; } - diff --git a/centrallix-lib/tests/test_qprintf_58.c b/centrallix-lib/tests/test_qprintf_58.c index 5200ba118..25430ef27 100644 --- a/centrallix-lib/tests/test_qprintf_58.c +++ b/centrallix-lib/tests/test_qprintf_58.c @@ -25,11 +25,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - qpfPrintf(NULL, buf+4, 31, "/path/%STR&PATH/name", "one/two"); - qpfPrintf(NULL, buf+4, 31, "/path/%STR&PATH/name", "one/two"); - qpfPrintf(NULL, buf+4, 31, "/path/%STR&PATH/name", "one/two"); - rval = qpfPrintf(NULL, buf+4, 31, "/path/%STR&PATH/name", "one/two"); - assert(strcmp(buf+4,"/path/one/two/name") == 0); + qpfPrintf(NULL, (char*)buf+4, 31, "/path/%STR&PATH/name", "one/two"); + qpfPrintf(NULL, (char*)buf+4, 31, "/path/%STR&PATH/name", "one/two"); + qpfPrintf(NULL, (char*)buf+4, 31, "/path/%STR&PATH/name", "one/two"); + rval = qpfPrintf(NULL, (char*)buf+4, 31, "/path/%STR&PATH/name", "one/two"); + assert(strcmp((char*)buf+4,"/path/one/two/name") == 0); assert(rval == 18); assert(buf[25] == '\n'); assert(buf[24] == '\0'); @@ -43,4 +43,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_qprintf_59.c b/centrallix-lib/tests/test_qprintf_59.c index b0a3c02ab..818260ba3 100644 --- a/centrallix-lib/tests/test_qprintf_59.c +++ b/centrallix-lib/tests/test_qprintf_59.c @@ -25,19 +25,19 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - rval = qpfPrintf(NULL, buf+4, 31, "/path/%STR&PATH/name", "one/../two"); + rval = qpfPrintf(NULL, (char*)buf+4, 31, "/path/%STR&PATH/name", "one/../two"); assert(rval < 0); - rval = qpfPrintf(NULL, buf+4, 31, "/path/%STR&PATH/name", ".."); + rval = qpfPrintf(NULL, (char*)buf+4, 31, "/path/%STR&PATH/name", ".."); assert(rval < 0); - rval = qpfPrintf(NULL, buf+4, 31, "/path/%STR&PATH/name", "../one"); + rval = qpfPrintf(NULL, (char*)buf+4, 31, "/path/%STR&PATH/name", "../one"); assert(rval < 0); - rval = qpfPrintf(NULL, buf+4, 31, "/path/%STR&PATH/name", "one/.."); + rval = qpfPrintf(NULL, (char*)buf+4, 31, "/path/%STR&PATH/name", "one/.."); assert(rval < 0); - rval = qpfPrintf(NULL, buf+4, 31, "/path/%STR&PATH/name", "/.."); + rval = qpfPrintf(NULL, (char*)buf+4, 31, "/path/%STR&PATH/name", "/.."); assert(rval < 0); - rval = qpfPrintf(NULL, buf+4, 31, "/path/%STR&PATH/name", "../"); + rval = qpfPrintf(NULL, (char*)buf+4, 31, "/path/%STR&PATH/name", "../"); assert(rval < 0); - rval = qpfPrintf(NULL, buf+4, 31, "/path/%STR&PATH/name", "/../"); + rval = qpfPrintf(NULL, (char*)buf+4, 31, "/path/%STR&PATH/name", "/../"); assert(rval < 0); assert(buf[25] == '\n'); assert(buf[24] == '\0'); @@ -51,4 +51,3 @@ test(char** tname) return iter*7; } - diff --git a/centrallix-lib/tests/test_qprintf_60.c b/centrallix-lib/tests/test_qprintf_60.c index 392007145..08ef696af 100644 --- a/centrallix-lib/tests/test_qprintf_60.c +++ b/centrallix-lib/tests/test_qprintf_60.c @@ -25,16 +25,16 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - rval = qpfPrintf(NULL, buf+4, 31, "/path/%8STR&PATH/name", "file/..\0"); + rval = qpfPrintf(NULL, (char*)buf+4, 31, "/path/%8STR&PATH/name", "file/..\0"); assert(rval < 0); - rval = qpfPrintf(NULL, buf+4, 31, "/path/%8STR&PATH/name", "one/t/.\0"); + rval = qpfPrintf(NULL, (char*)buf+4, 31, "/path/%8STR&PATH/name", "one/t/.\0"); assert(rval < 0); - rval = qpfPrintf(NULL, buf+4, 31, "/path/%8STR&PATH/name", "one/t/.."); + rval = qpfPrintf(NULL, (char*)buf+4, 31, "/path/%8STR&PATH/name", "one/t/.."); assert(rval < 0); - rval = qpfPrintf(NULL, buf+4, 31, "/path/%8STR&PATH/name", "one/t/..."); + rval = qpfPrintf(NULL, (char*)buf+4, 31, "/path/%8STR&PATH/name", "one/t/..."); assert(rval < 0); - rval = qpfPrintf(NULL, buf+4, 31, "/path/%8STR&PATH/name", "one/two/"); - assert(strcmp(buf+4,"/path/one/two//name") == 0); + rval = qpfPrintf(NULL, (char*)buf+4, 31, "/path/%8STR&PATH/name", "one/two/"); + assert(strcmp((char*)buf+4,"/path/one/two//name") == 0); assert(rval == 19); assert(buf[26] == '\n'); assert(buf[25] == '\0'); @@ -48,4 +48,3 @@ test(char** tname) return iter*5; } - diff --git a/centrallix-lib/tests/test_qprintf_61.c b/centrallix-lib/tests/test_qprintf_61.c index b20a78656..f8b29e8b9 100644 --- a/centrallix-lib/tests/test_qprintf_61.c +++ b/centrallix-lib/tests/test_qprintf_61.c @@ -25,19 +25,19 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - rval = qpfPrintf(NULL, buf+4, 31, "/path/%STR&PATH&8LEN/name", "file/..\0"); + rval = qpfPrintf(NULL, (char*)buf+4, 31, "/path/%STR&PATH&8LEN/name", "file/..\0"); assert(rval < 0); - rval = qpfPrintf(NULL, buf+4, 31, "/path/%STR&PATH&8LEN/name", "files/../"); + rval = qpfPrintf(NULL, (char*)buf+4, 31, "/path/%STR&PATH&8LEN/name", "files/../"); assert(rval < 0); - rval = qpfPrintf(NULL, buf+4, 31, "/path/%STR&PATH&8LEN/name", "one/t/.."); + rval = qpfPrintf(NULL, (char*)buf+4, 31, "/path/%STR&PATH&8LEN/name", "one/t/.."); assert(rval < 0); - rval = qpfPrintf(NULL, buf+4, 31, "/path/%STR&PATH&8LEN/name", "one/t/..."); + rval = qpfPrintf(NULL, (char*)buf+4, 31, "/path/%STR&PATH&8LEN/name", "one/t/..."); assert(rval < 0); - rval = qpfPrintf(NULL, buf+4, 31, "/path/%STR&PATH&8LEN/name", "one/two/"); - assert(strcmp(buf+4,"/path/one/two//name") == 0); + rval = qpfPrintf(NULL, (char*)buf+4, 31, "/path/%STR&PATH&8LEN/name", "one/two/"); + assert(strcmp((char*)buf+4,"/path/one/two//name") == 0); assert(rval == 19); - rval = qpfPrintf(NULL, buf+4, 31, "/path/%STR&PATH&8LEN/name", "one/two//"); - assert(strcmp(buf+4,"/path/one/two//name") == 0); + rval = qpfPrintf(NULL, (char*)buf+4, 31, "/path/%STR&PATH&8LEN/name", "one/two//"); + assert(strcmp((char*)buf+4,"/path/one/two//name") == 0); assert(rval == 19); assert(buf[26] == '\n'); assert(buf[25] == '\0'); @@ -51,4 +51,3 @@ test(char** tname) return iter*6; } - diff --git a/centrallix-lib/tests/test_qprintf_62.c b/centrallix-lib/tests/test_qprintf_62.c index f3903297f..7433cd264 100644 --- a/centrallix-lib/tests/test_qprintf_62.c +++ b/centrallix-lib/tests/test_qprintf_62.c @@ -25,16 +25,16 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - rval = qpfPrintf(NULL, buf+4, 36, "%STR&DB64", "dGVzdC#BkYXRh"); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "%STR&DB64", "dGVzdC#BkYXRh"); assert(rval < 0); - rval = qpfPrintf(NULL, buf+4, 36, "%STR&DB64", "#dGVzdCBkYXRh"); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "%STR&DB64", "#dGVzdCBkYXRh"); assert(rval < 0); - rval = qpfPrintf(NULL, buf+4, 36, "%STR&DB64", "dGVzdCBkYXRh#"); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "%STR&DB64", "dGVzdCBkYXRh#"); assert(rval < 0); - rval = qpfPrintf(NULL, buf+4, 36, "%STR&DB64", "dGVzdCBkY#XRh"); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "%STR&DB64", "dGVzdCBkY#XRh"); assert(rval < 0); - rval = qpfPrintf(NULL, buf+4, 36, "%STR&DB64", "dGVzdCBkYXRh"); - assert(strcmp(buf+4,"test data") == 0); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "%STR&DB64", "dGVzdCBkYXRh"); + assert(strcmp((char*)buf+4,"test data") == 0); assert(rval == 9); assert(buf[43] == '\n'); assert(buf[42] == '\0'); @@ -48,4 +48,3 @@ test(char** tname) return iter*6; } - diff --git a/centrallix-lib/tests/test_qprintf_63.c b/centrallix-lib/tests/test_qprintf_63.c index d860d8b42..f3db7fbaf 100644 --- a/centrallix-lib/tests/test_qprintf_63.c +++ b/centrallix-lib/tests/test_qprintf_63.c @@ -25,9 +25,9 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - rval = qpfPrintf(NULL, buf+4, 36, "%STR&DB64", "dGVzdCBkYXRh"); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "%STR&DB64", "dGVzdCBkYXRh"); assert(rval == 9); - rval = qpfPrintf(NULL, buf+4, 36, "%STR&DB64", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5eg"); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "%STR&DB64", "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5eg"); assert(rval < 0); assert(buf[43] == '\n'); assert(buf[42] == '\0'); @@ -41,4 +41,3 @@ test(char** tname) return iter*2; } - diff --git a/centrallix-lib/tests/test_qprintf_64.c b/centrallix-lib/tests/test_qprintf_64.c index 7c6cea8a9..42d670239 100644 --- a/centrallix-lib/tests/test_qprintf_64.c +++ b/centrallix-lib/tests/test_qprintf_64.c @@ -25,9 +25,9 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - rval = qpfPrintf(NULL, (char*)(buf+4), 36, "%STR&B64", "test data"); + rval = qpfPrintf(NULL, (char*)((char*)buf+4), 36, "%STR&B64", "test data"); assert(rval == strlen("dGVzdCBkYXRh")); - assert(strcmp(buf+4, "dGVzdCBkYXRh") == 0); + assert(strcmp((char*)buf+4, "dGVzdCBkYXRh") == 0); assert(buf[43] == '\n'); assert(buf[42] == '\0'); assert(buf[41] == 0xff); @@ -40,4 +40,3 @@ test(char** tname) return iter; } - diff --git a/centrallix-lib/tests/test_qprintf_65.c b/centrallix-lib/tests/test_qprintf_65.c index 6d8dd5409..ae66dc73f 100644 --- a/centrallix-lib/tests/test_qprintf_65.c +++ b/centrallix-lib/tests/test_qprintf_65.c @@ -25,9 +25,9 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - rval = qpfPrintf(NULL, (char*)(buf+4), 36, "%STR&B64", "test data"); + rval = qpfPrintf(NULL, (char*)((char*)buf+4), 36, "%STR&B64", "test data"); assert(rval == strlen("dGVzdCBkYXRh")); - rval = qpfPrintf(NULL, (char*)(buf+4), 36, "%STR&B64", "the quick brown fox jumps ov"); + rval = qpfPrintf(NULL, (char*)((char*)buf+4), 36, "%STR&B64", "the quick brown fox jumps ov"); assert(rval < 0); assert(buf[43] == '\n'); assert(buf[42] == '\0'); @@ -41,4 +41,3 @@ test(char** tname) return iter*2; } - diff --git a/centrallix-lib/tests/test_qprintf_66.c b/centrallix-lib/tests/test_qprintf_66.c index b2659dc79..48e1157f8 100644 --- a/centrallix-lib/tests/test_qprintf_66.c +++ b/centrallix-lib/tests/test_qprintf_66.c @@ -30,12 +30,12 @@ test(char** tname) //Test value > INT_MAX long long testNum = 2200000000ll; - qpfPrintf(NULL, buf + 4, 36, "Here is the ll: %LL...", testNum); - qpfPrintf(NULL, buf+4, 36, "Here is the ll: %LL...", testNum); - qpfPrintf(NULL, buf+4, 36, "Here is the ll: %LL...", testNum); - rval = qpfPrintf(NULL, buf+4, 36, "Here is the ll: %LL...", testNum); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the ll: %LL...", testNum); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the ll: %LL...", testNum); + qpfPrintf(NULL, (char*)buf+4, 36, "Here is the ll: %LL...", testNum); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "Here is the ll: %LL...", testNum); - assert(!strcmp(buf+4, "Here is the ll: 2200000000...")); + assert(!strcmp((char*)buf+4, "Here is the ll: 2200000000...")); //For the long long case, rval will be set to the return value of snprintf, i.e. length of string assert(rval == 29); assert(buf[34] == 0xff); @@ -50,4 +50,4 @@ test(char** tname) } return iter*4; -} \ No newline at end of file +} diff --git a/centrallix-lib/tests/test_qprintf_67.c b/centrallix-lib/tests/test_qprintf_67.c index 46100b3c0..38fd50235 100644 --- a/centrallix-lib/tests/test_qprintf_67.c +++ b/centrallix-lib/tests/test_qprintf_67.c @@ -25,21 +25,21 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - rval = qpfPrintf(NULL, buf+4, 36, "%STR&DHEX", "4142434D4E4F#"); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "%STR&DHEX", "4142434D4E4F#"); assert(rval < 0); - rval = qpfPrintf(NULL, buf+4, 36, "%STR&DHEX", "414243#4D4E4F"); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "%STR&DHEX", "414243#4D4E4F"); assert(rval < 0); - rval = qpfPrintf(NULL, buf+4, 36, "%STR&DHEX", "41424#34D4E4F"); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "%STR&DHEX", "41424#34D4E4F"); assert(rval < 0); - rval = qpfPrintf(NULL, buf+4, 36, "%STR&DHEX", "#4142434D4E4F"); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "%STR&DHEX", "#4142434D4E4F"); assert(rval < 0); - rval = qpfPrintf(NULL, buf+4, 36, "%STR&DHEX", "4142434D4E4"); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "%STR&DHEX", "4142434D4E4"); assert(rval < 0); - rval = qpfPrintf(NULL, buf+4, 36, "%STR&DHEX", "4142434D4E4F"); - assert(strcmp(buf+4,"ABCMNO") == 0); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "%STR&DHEX", "4142434D4E4F"); + assert(strcmp((char*)buf+4,"ABCMNO") == 0); assert(rval == 6); - rval = qpfPrintf(NULL, buf+4, 36, "%STR&DHEX", "4142434d4e4f"); - assert(strcmp(buf+4,"ABCMNO") == 0); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "%STR&DHEX", "4142434d4e4f"); + assert(strcmp((char*)buf+4,"ABCMNO") == 0); assert(rval == 6); assert(buf[43] == '\n'); assert(buf[42] == '\0'); @@ -53,4 +53,3 @@ test(char** tname) return iter*7; } - diff --git a/centrallix-lib/tests/test_qprintf_68.c b/centrallix-lib/tests/test_qprintf_68.c index b6b7cd573..16c770ae4 100644 --- a/centrallix-lib/tests/test_qprintf_68.c +++ b/centrallix-lib/tests/test_qprintf_68.c @@ -25,9 +25,9 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - rval = qpfPrintf(NULL, buf+4, 36, "%STR&DHEX", "414243444546"); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "%STR&DHEX", "414243444546"); assert(rval == 6); - rval = qpfPrintf(NULL, buf+4, 36, "%STR&DHEX", "4142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F60616263646566676869"); + rval = qpfPrintf(NULL, (char*)buf+4, 36, "%STR&DHEX", "4142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F60616263646566676869"); assert(rval < 0); assert(buf[43] == '\n'); assert(buf[42] == '\0'); @@ -41,4 +41,3 @@ test(char** tname) return iter*2; } - diff --git a/centrallix-lib/tests/test_qprintf_69.c b/centrallix-lib/tests/test_qprintf_69.c new file mode 100644 index 000000000..84c4b5cc4 --- /dev/null +++ b/centrallix-lib/tests/test_qprintf_69.c @@ -0,0 +1,58 @@ +#include +#include +#include + +#include "qprintf.h" + +long long +test(char** tname) + { + int i, rval; + int iter; + unsigned char buf[16]; + + /*** This test verifies that, when printing quoted text to a buffer that + *** is too small using %STR", the string quoting is handled properly + *** and the string is still null-terminated, even if the buffer is not + *** large enough for any of the text. + ***/ + + *tname = "qprintf-69 %STR" small buffer quote & null termination"; + iter = 100000; + for(i=0;i +#include +#include + +#include "qprintf.h" + +long long +test(char** tname) + { + int i, rval; + int iter; + unsigned char buf[16]; + + /*** This test verifies that a bug was successfully fixed: When + *** printing quoted text to a buffer that was only one character + *** long using %STR", the function would underflow the buffer, + *** clobbering the byte before the buffer with 0x27, aka. the ' + *** character, intended to be the closing quote. + ***/ + + *tname = "qprintf-70 %STR" buffer size underflow"; + iter = 100000; + for(i=0;i #include #include +#include long long test(char** tname) @@ -83,4 +84,3 @@ test(char** tname) return iter; } - diff --git a/centrallix-lib/tests/test_strtcpy_00.c b/centrallix-lib/tests/test_strtcpy_00.c index fc919b180..090c57d94 100644 --- a/centrallix-lib/tests/test_strtcpy_00.c +++ b/centrallix-lib/tests/test_strtcpy_00.c @@ -26,11 +26,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - strcpy(buf+4, "this is a string non-overflow test."); - strcpy(buf+4, "this is a string non-overflow test."); - strcpy(buf+4, "this is a string non-overflow test."); - strcpy(buf+4, "this is a string non-overflow test."); - assert(!strcmp(buf+4,"this is a string non-overflow test.")); + strcpy((char*)buf+4, "this is a string non-overflow test."); + strcpy((char*)buf+4, "this is a string non-overflow test."); + strcpy((char*)buf+4, "this is a string non-overflow test."); + strcpy((char*)buf+4, "this is a string non-overflow test."); + assert(!strcmp((char*)buf+4,"this is a string non-overflow test.")); assert(buf[43] == '\n'); assert(buf[42] == '\0'); assert(buf[41] == 0xff); @@ -44,4 +44,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_strtcpy_01.c b/centrallix-lib/tests/test_strtcpy_01.c index 93cfdc78f..c6dbc548a 100644 --- a/centrallix-lib/tests/test_strtcpy_01.c +++ b/centrallix-lib/tests/test_strtcpy_01.c @@ -26,15 +26,15 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - memccpy(buf+4, "this is a string non-overflow test.", '\0', 36-1); - (buf+4)[36-1] = '\0'; - memccpy(buf+4, "this is a string non-overflow test.", '\0', 36-1); - (buf+4)[36-1] = '\0'; - memccpy(buf+4, "this is a string non-overflow test.", '\0', 36-1); - (buf+4)[36-1] = '\0'; - memccpy(buf+4, "this is a string non-overflow test.", '\0', 36-1); - (buf+4)[36-1] = '\0'; - assert(!strcmp(buf+4,"this is a string non-overflow test.")); + memccpy((char*)buf+4, "this is a string non-overflow test.", '\0', 36-1); + ((char*)buf+4)[36-1] = '\0'; + memccpy((char*)buf+4, "this is a string non-overflow test.", '\0', 36-1); + ((char*)buf+4)[36-1] = '\0'; + memccpy((char*)buf+4, "this is a string non-overflow test.", '\0', 36-1); + ((char*)buf+4)[36-1] = '\0'; + memccpy((char*)buf+4, "this is a string non-overflow test.", '\0', 36-1); + ((char*)buf+4)[36-1] = '\0'; + assert(!strcmp((char*)buf+4,"this is a string non-overflow test.")); assert(buf[43] == '\n'); assert(buf[42] == '\0'); assert(buf[41] == 0xff); @@ -48,4 +48,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_strtcpy_02.c b/centrallix-lib/tests/test_strtcpy_02.c index 6a3c140ba..d64451482 100644 --- a/centrallix-lib/tests/test_strtcpy_02.c +++ b/centrallix-lib/tests/test_strtcpy_02.c @@ -26,11 +26,11 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - snprintf(buf+4, 36, "this is a string non-overflow test."); - snprintf(buf+4, 36, "this is a string non-overflow test."); - snprintf(buf+4, 36, "this is a string non-overflow test."); - snprintf(buf+4, 36, "this is a string non-overflow test."); - assert(!strcmp(buf+4,"this is a string non-overflow test.")); + snprintf((char*)buf+4, 36, "this is a string non-overflow test."); + snprintf((char*)buf+4, 36, "this is a string non-overflow test."); + snprintf((char*)buf+4, 36, "this is a string non-overflow test."); + snprintf((char*)buf+4, 36, "this is a string non-overflow test."); + assert(!strcmp((char*)buf+4,"this is a string non-overflow test.")); assert(buf[43] == '\n'); assert(buf[42] == '\0'); assert(buf[41] == 0xff); @@ -44,4 +44,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_strtcpy_03.c b/centrallix-lib/tests/test_strtcpy_03.c index 47a792a90..7dd6bd025 100644 --- a/centrallix-lib/tests/test_strtcpy_03.c +++ b/centrallix-lib/tests/test_strtcpy_03.c @@ -26,12 +26,12 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - strtcpy(buf+4, "this is a string non-overflow test.", 36); - strtcpy(buf+4, "this is a string non-overflow test.", 36); - strtcpy(buf+4, "this is a string non-overflow test.", 36); - rval = strtcpy(buf+4, "this is a string non-overflow test.", 36); + strtcpy((char*)buf+4, "this is a string non-overflow test.", 36); + strtcpy((char*)buf+4, "this is a string non-overflow test.", 36); + strtcpy((char*)buf+4, "this is a string non-overflow test.", 36); + rval = strtcpy((char*)buf+4, "this is a string non-overflow test.", 36); assert(rval == 36); - assert(!strcmp(buf+4,"this is a string non-overflow test.")); + assert(!strcmp((char*)buf+4,"this is a string non-overflow test.")); assert(buf[43] == '\n'); assert(buf[42] == '\0'); assert(buf[41] == 0xff); @@ -45,4 +45,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_strtcpy_04.c b/centrallix-lib/tests/test_strtcpy_04.c index c142b85ea..dbc81f268 100644 --- a/centrallix-lib/tests/test_strtcpy_04.c +++ b/centrallix-lib/tests/test_strtcpy_04.c @@ -26,12 +26,12 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - strtcpy(buf+4, "this is a string non-overflow test.?", 36); - strtcpy(buf+4, "this is a string non-overflow test.?", 36); - strtcpy(buf+4, "this is a string non-overflow test.?", 36); - rval = strtcpy(buf+4, "this is a string non-overflow test.?", 36); + strtcpy((char*)buf+4, "this is a string non-overflow test.?", 36); + strtcpy((char*)buf+4, "this is a string non-overflow test.?", 36); + strtcpy((char*)buf+4, "this is a string non-overflow test.?", 36); + rval = strtcpy((char*)buf+4, "this is a string non-overflow test.?", 36); assert(rval == -36); - assert(!strcmp(buf+4,"this is a string non-overflow test.")); + assert(!strcmp((char*)buf+4,"this is a string non-overflow test.")); assert(buf[43] == '\n'); assert(buf[42] == '\0'); assert(buf[41] == 0xff); @@ -45,4 +45,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_strtcpy_05.c b/centrallix-lib/tests/test_strtcpy_05.c index 2c20471df..d1098c76d 100644 --- a/centrallix-lib/tests/test_strtcpy_05.c +++ b/centrallix-lib/tests/test_strtcpy_05.c @@ -26,12 +26,12 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - strtcpy(buf+4, "this is a string non-overflow test... NOT!!", 36); - strtcpy(buf+4, "this is a string non-overflow test... NOT!!", 36); - strtcpy(buf+4, "this is a string non-overflow test... NOT!!", 36); - rval = strtcpy(buf+4, "this is a string non-overflow test... NOT!!", 36); + strtcpy((char*)buf+4, "this is a string non-overflow test... NOT!!", 36); + strtcpy((char*)buf+4, "this is a string non-overflow test... NOT!!", 36); + strtcpy((char*)buf+4, "this is a string non-overflow test... NOT!!", 36); + rval = strtcpy((char*)buf+4, "this is a string non-overflow test... NOT!!", 36); assert(rval == -36); - assert(!strcmp(buf+4,"this is a string non-overflow test.")); + assert(!strcmp((char*)buf+4,"this is a string non-overflow test.")); assert(buf[43] == '\n'); assert(buf[42] == '\0'); assert(buf[41] == 0xff); @@ -45,4 +45,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_strtcpy_06.c b/centrallix-lib/tests/test_strtcpy_06.c index 567d9f851..c19f3df82 100644 --- a/centrallix-lib/tests/test_strtcpy_06.c +++ b/centrallix-lib/tests/test_strtcpy_06.c @@ -28,10 +28,10 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - strtcpy(buf+4, "this is a string non-overflow test.?", 0); - strtcpy(buf+4, "this is a string non-overflow test.?", 0); - strtcpy(buf+4, "this is a string non-overflow test.?", 0); - rval = strtcpy(buf+4, "this is a string non-overflow test.?", 0); + strtcpy((char*)buf+4, "this is a string non-overflow test.?", 0); + strtcpy((char*)buf+4, "this is a string non-overflow test.?", 0); + strtcpy((char*)buf+4, "this is a string non-overflow test.?", 0); + rval = strtcpy((char*)buf+4, "this is a string non-overflow test.?", 0); assert(rval == 0); assert(buf[43] == '\n'); assert(buf[42] == '\0'); @@ -48,4 +48,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_strtcpy_07.c b/centrallix-lib/tests/test_strtcpy_07.c index 3e87297dc..63d0457cf 100644 --- a/centrallix-lib/tests/test_strtcpy_07.c +++ b/centrallix-lib/tests/test_strtcpy_07.c @@ -29,10 +29,10 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - strtcpy(buf+4, "this is a string non-overflow test.?", 1); - strtcpy(buf+4, "this is a string non-overflow test.?", 1); - strtcpy(buf+4, "this is a string non-overflow test.?", 1); - rval = strtcpy(buf+4, "this is a string non-overflow test.?", 1); + strtcpy((char*)buf+4, "this is a string non-overflow test.?", 1); + strtcpy((char*)buf+4, "this is a string non-overflow test.?", 1); + strtcpy((char*)buf+4, "this is a string non-overflow test.?", 1); + rval = strtcpy((char*)buf+4, "this is a string non-overflow test.?", 1); assert(rval == -1); assert(buf[43] == '\n'); assert(buf[42] == '\0'); @@ -50,4 +50,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_strtcpy_08.c b/centrallix-lib/tests/test_strtcpy_08.c index 30415b048..ebfa9e852 100644 --- a/centrallix-lib/tests/test_strtcpy_08.c +++ b/centrallix-lib/tests/test_strtcpy_08.c @@ -35,10 +35,10 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - strtcpy(buf+4, "this is a string non-overflow test.?", 2); - strtcpy(buf+4, "this is a string non-overflow test.?", 2); - strtcpy(buf+4, "this is a string non-overflow test.?", 2); - rval = strtcpy(buf+4, "this is a string non-overflow test.?", 2); + strtcpy((char*)buf+4, "this is a string non-overflow test.?", 2); + strtcpy((char*)buf+4, "this is a string non-overflow test.?", 2); + strtcpy((char*)buf+4, "this is a string non-overflow test.?", 2); + rval = strtcpy((char*)buf+4, "this is a string non-overflow test.?", 2); assert(rval == -2); assert(buf[43] == '\n'); assert(buf[42] == '\0'); @@ -56,4 +56,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_strtcpy_09.c b/centrallix-lib/tests/test_strtcpy_09.c index 929c1e109..50ca3eccc 100644 --- a/centrallix-lib/tests/test_strtcpy_09.c +++ b/centrallix-lib/tests/test_strtcpy_09.c @@ -27,12 +27,12 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - strtcpy(buf+4, "this is a non-overflow test.", 36); - strtcpy(buf+4, "this is a non-overflow test.", 36); - strtcpy(buf+4, "this is a non-overflow test.", 36); - rval = strtcpy(buf+4, "this is a non-overflow test.", 36); + strtcpy((char*)buf+4, "this is a non-overflow test.", 36); + strtcpy((char*)buf+4, "this is a non-overflow test.", 36); + strtcpy((char*)buf+4, "this is a non-overflow test.", 36); + rval = strtcpy((char*)buf+4, "this is a non-overflow test.", 36); assert(rval == 29); - assert(!strcmp(buf+4,"this is a non-overflow test.")); + assert(!strcmp((char*)buf+4,"this is a non-overflow test.")); assert(buf[43] == '\n'); assert(buf[42] == '\0'); assert(buf[41] == 0xff); @@ -47,4 +47,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-lib/tests/test_strtcpy_10.c b/centrallix-lib/tests/test_strtcpy_10.c index 75e5e0465..95e4835c1 100644 --- a/centrallix-lib/tests/test_strtcpy_10.c +++ b/centrallix-lib/tests/test_strtcpy_10.c @@ -27,12 +27,12 @@ test(char** tname) buf[2] = '\0'; buf[1] = 0xff; buf[0] = '\0'; - strtcpy(buf+4, "", 36); - strtcpy(buf+4, "", 36); - strtcpy(buf+4, "", 36); - rval = strtcpy(buf+4, "", 36); + strtcpy((char*)buf+4, "", 36); + strtcpy((char*)buf+4, "", 36); + strtcpy((char*)buf+4, "", 36); + rval = strtcpy((char*)buf+4, "", 36); assert(rval == 1); - assert(!strcmp(buf+4,"")); + assert(!strcmp((char*)buf+4,"")); assert(buf[43] == '\n'); assert(buf[42] == '\0'); assert(buf[41] == 0xff); @@ -47,4 +47,3 @@ test(char** tname) return iter*4; } - diff --git a/centrallix-os/apps/nav/default.tpl b/centrallix-os/apps/nav/default.tpl index c0d73ca13..de958eafd 100644 --- a/centrallix-os/apps/nav/default.tpl +++ b/centrallix-os/apps/nav/default.tpl @@ -5,7 +5,7 @@ default "widget/template" { bgcolor="#e0e0e0"; linkcolor="#0000ff"; - font_name = "Arial"; + font_name = "Arial, Helvetica, sans-serif"; font_size = 12; icon = "/favicon.ico"; } diff --git a/centrallix-os/cluster-schema.cluster b/centrallix-os/cluster-schema.cluster new file mode 100644 index 000000000..af69b23d5 --- /dev/null +++ b/centrallix-os/cluster-schema.cluster @@ -0,0 +1,66 @@ +// Input schema +$Version=2$ +file_name "system/cluster" + { + name "cluster/parameter" + { + type : DATA_T // See datatypes.h + ?default : type // Default value for the variable. + ?name : String // Overrides the name above. + ?style : StyleObj // idk where to find docs for this. + } + // Access with :parameters:name. Accessing dynamic data (e.g. parameters) + // should be done within a runserver() call. + ... + + source : DataSourcePath + key_attr : string ⊂ DataSourcePath/columns + data_attr : string ⊂ DataSourcePath/columns + + cluster_name "cluster/cluster" + { + algorithm : "none" | "sliding-window" | "k-means" // Implemented + | "k-means++" | "k-medoids" | "db-scan" // Not implemented + similarity_measure : "cosine" | "levenshtein" // levenshtein not implemented. + num_clusters : uint > 1 // (probably a parameter) + ?min_improvement : double && -1.0 <= x <= 1.0 // default: 0.0001 + ?max_iterations : uint // default: 64 + ?window_size : uint > 0 // required for algorithm = sliding_window. + ?overlap_size : double && 0.0 <= x <= 1.0 // default: 0.0, only allowed for algorithm = k-means | k-means++ | k-medoids, not implemented + ?seed : uint > 0 // default: Dynamic seed. + + // Note: Use min_improvement=-1.0; to effectively disable it. This will + // run as many iterations as possible until the clusters stop moving or + // `max_iterations` is reached. + + // Not implemented + sub_cluster_name "cluster/cluster" + { + // Same as above. + } + } + ... + + search_name "system/search" + { + source : string ⊂ [cluster_name, ...] + similarity_measure : "cosine" | "levenshtein" + threshold : double && 0.0 < x < 1.0 // optimization. + } + ... + } + +// Output schema + +- /cluster_name + ? /sub_cluster_name + ? ... + - /{query} + - /items : StringVec // The data points in the cluster. + ... +/search_name +- /{query} + - /key1 : string // The key of the first data point. + - /key2 : string // The key of the second data point. + - /sim : double && 0.0 < x <= threshold // The similarity of the two data points. +... diff --git a/centrallix-os/datasets/README.md b/centrallix-os/datasets/README.md new file mode 100644 index 000000000..7633d84a4 --- /dev/null +++ b/centrallix-os/datasets/README.md @@ -0,0 +1,6 @@ +# Datasets Folder + +The datasets folder is intended for storing datasets used in testing that must not be committed to GitHub. Datasets in this folder may contain sensitive data, including production data or patterns from production data. This data _must_ be treated with care, even if it is obfuscated. Thus, this directory should be "git-ignored". If you ever see files in this directory listed as changes when you run `git status` or use similar commands or tools, imedately remove these changes and investigate. Data in this directory must _never_ be committed or pushed! + +## Modifying this README.md +If you need to modify this readme file, you may need to pass `-f` when adding it because the directory is part of the `.gitignore` (e.g. `git add -f README.md`). Obviously, be careful _not to `git add` any other files_ in this directory. diff --git a/centrallix-os/samples/autoscale_test.app b/centrallix-os/samples/autoscale_test.app new file mode 100644 index 000000000..9099aa857 --- /dev/null +++ b/centrallix-os/samples/autoscale_test.app @@ -0,0 +1,78 @@ +$Version=2$ +MyPage "widget/page" + { + title = "Responsive Testing Page"; + bgcolor = "black"; + textcolor = "#00f8ff"; + width = 1000; + height = 1000; + + auto "widget/hbox" + { + x=100; y=50; width=900; height=750; + spacing=20; row_height=300; + fl_width=100; fl_height=100; + + pane0 "widget/pane" { fl_width=100; fl_height=100; width=190; height=180; bgcolor = "#9cf"; } // x=100; y=50; + pane1 "widget/pane" { fl_width=100; fl_height=100; width=130; height=180; bgcolor = "#ccc"; } // x=305; y=50; + pane2 "widget/pane" { fl_width=100; fl_height=100; width=80; height=320; bgcolor = "#f99"; } // x=455; y=50; + pane3 "widget/pane" { fl_width=100; fl_height=100; width=150; height=140; bgcolor = "#9f9"; } // x=555; y=50; + pane4 "widget/pane" { fl_width=100; fl_height=100; width=90; height=240; bgcolor = "#99f"; } // x=725; y=50; + pane5 "widget/pane" { fl_width=100; fl_height=100; width=230; height=100; bgcolor = "#ff9"; } // x=80; y=390; + pane6 "widget/pane" { fl_width=100; fl_height=100; width=80; height=200; bgcolor = "#f9f"; } // x=325; y=390; + pane7 "widget/pane" { fl_width=100; fl_height=100; width=170; height=220; bgcolor = "#9ff"; } // x=425; y=390; + pane8 "widget/pane" { fl_width=100; fl_height=100; width=110; height=200; bgcolor = "#fc9"; } // x=615; y=390; + pane9 "widget/pane" { fl_width=100; fl_height=100; width=130; height=120; bgcolor = "#cf9"; } // x=745; y=390; + } + + // pane0 "widget/pane" { x=100; y=50; width=190; height=180; bgcolor = "#9cf"; } + // pane1 "widget/pane" { x=305; y=50; width=130; height=180; bgcolor = "#ccc"; } + // pane2 "widget/pane" { x=455; y=50; width=80; height=320; bgcolor = "#f99"; } + // pane3 "widget/pane" { x=555; y=50; width=150; height=140; bgcolor = "#9f9"; } + // pane4 "widget/pane" { x=725; y=50; width=90; height=240; bgcolor = "#99f"; } + // pane5 "widget/pane" { x=80; y=390; width=230; height=100; bgcolor = "#ff9"; } + // pane6 "widget/pane" { x=325; y=390; width=80; height=200; bgcolor = "#f9f"; } + // pane7 "widget/pane" { x=425; y=390; width=170; height=220; bgcolor = "#9ff"; } + // pane8 "widget/pane" { x=615; y=390; width=110; height=200; bgcolor = "#fc9"; } + // pane9 "widget/pane" { x=745; y=390; width=130; height=120; bgcolor = "#cf9"; } + + paneA "widget/pane" { x=40; y=680; width=890; height=220; bgcolor = "#620"; } + + // Outline the visible area. + top_left0 "widget/pane" { x=0; y=0; width=10; height=10; bgcolor = "#f00"; } + top_right0 "widget/pane" { x=990; y=0; width=10; height=10; bgcolor = "#ff0"; } + bottom_left0 "widget/pane" { x=0; y=990; width=10; height=10; bgcolor = "#0f0"; } + bottom_right0 "widget/pane" { x=990; y=990; width=10; height=10; bgcolor = "#00f"; } + + // Advance markers. + top_left1 "widget/pane" { x=100; y=100; width=10; height=10; fl_x=25; fl_y=25; fl_width=25; fl_height=25; bgcolor = "#a00"; } + top_right1 "widget/pane" { x=890; y=100; width=10; height=10; fl_x=25; fl_y=25; fl_width=25; fl_height=25; bgcolor = "#aa0"; } + bottom_left1 "widget/pane" { x=100; y=890; width=10; height=10; fl_x=25; fl_y=25; fl_width=25; fl_height=25; bgcolor = "#0a0"; } + bottom_right1 "widget/pane" { x=890; y=890; width=10; height=10; fl_x=25; fl_y=25; fl_width=25; fl_height=25; bgcolor = "#00a"; } + + // Interior markers. + top_left2 "widget/pane" { x=250; y=250; width=10; height=10; fl_x=100; fl_y=100; fl_width=25; fl_height=25; bgcolor = "#700"; } + top_right2 "widget/pane" { x=740; y=250; width=10; height=10; fl_x=100; fl_y=100; fl_width=25; fl_height=25; bgcolor = "#770"; } + bottom_left2 "widget/pane" { x=250; y=740; width=10; height=10; fl_x=100; fl_y=100; fl_width=25; fl_height=25; bgcolor = "#070"; } + bottom_right2 "widget/pane" { x=740; y=740; width=10; height=10; fl_x=100; fl_y=100; fl_width=25; fl_height=25; bgcolor = "#007"; } + + // Deep interior markers. + top_left3 "widget/pane" { x=400; y=400; width=10; height=10; fl_x=25; fl_y=25; fl_width=100; fl_height=100; bgcolor = "#500"; } + top_right3 "widget/pane" { x=590; y=400; width=10; height=10; fl_x=25; fl_y=25; fl_width=100; fl_height=100; bgcolor = "#550"; } + bottom_left3 "widget/pane" { x=400; y=590; width=10; height=10; fl_x=25; fl_y=25; fl_width=100; fl_height=100; bgcolor = "#050"; } + bottom_right3 "widget/pane" { x=590; y=590; width=10; height=10; fl_x=25; fl_y=25; fl_width=100; fl_height=100; bgcolor = "#005"; } + + // Center marker. + center "widget/pane" + { + x=450; y=450; width=100; height=100; bgcolor = "orange"; + centerer "widget/pane" { x=25; y=25; width=50; height=50; bgcolor = "purple"; } // Debug + } + + button "widget/textbutton" + { + x = 450; y = 380; width = 100; height = 30; font_size=16; bgcolor="#0c0447ff"; + text = "Resize"; + connector "widget/connector" { event=Click; target=center; action=Resize; } + } + } \ No newline at end of file diff --git a/centrallix-os/samples/autoscale_test2.app b/centrallix-os/samples/autoscale_test2.app new file mode 100644 index 000000000..10b7a4cff --- /dev/null +++ b/centrallix-os/samples/autoscale_test2.app @@ -0,0 +1,19 @@ +$Version=2$ +MyPage "widget/page" + { + title = "Flex Testing Page"; + bgcolor = "black"; + textcolor = "#00f8ff"; + width = 1000; + height = 1000; + + box "widget/pane" + { + x=25; y=25; width=975; height=975; bgcolor = "#111"; + + // Note: fl_x and fl_y seem to be ignored. + standard "widget/pane" { x=100; y=100; width=100; height=100; fl_x=100; fl_y=100; fl_width=100; fl_height=100; bgcolor = "orange"; } + big "widget/pane" { x=250; y=250; width=200; height=200; fl_x=10; fl_y=50; fl_width=10; fl_height=10; bgcolor = "purple"; } + double "widget/pane" { x=500; y=500; width=200; height=200; fl_x=50; fl_y=10; fl_width=50; fl_height=50; bgcolor = "green"; } + } + } \ No newline at end of file diff --git a/centrallix-os/samples/clock.app b/centrallix-os/samples/clock.app index 1a2567ab3..a17ceb2af 100644 --- a/centrallix-os/samples/clock.app +++ b/centrallix-os/samples/clock.app @@ -14,7 +14,6 @@ main "widget/page" shadowx = 2; shadowy = 2; size=1; - moveable="true"; bold="true"; } clock2 "widget/clock" @@ -22,7 +21,6 @@ main "widget/page" background="/sys/images/fade_pixelate_01.gif"; x=15; y=55; width=80; height=20; fgcolor1="white"; - moveable="true"; bold="true"; } clock3 "widget/clock" @@ -33,7 +31,6 @@ main "widget/page" fgcolor1="orange"; fgcolor2="#666666"; // size=3; - moveable="true"; bold="true"; } } diff --git a/centrallix-os/samples/scrollpane_test.app b/centrallix-os/samples/scrollpane_test.app new file mode 100644 index 000000000..31db45c6d --- /dev/null +++ b/centrallix-os/samples/scrollpane_test.app @@ -0,0 +1,262 @@ +$Version=2$ +test "widget/page" + { + title = "Test App"; + bgcolor = "#ffffff"; + + x = 0; y = 0; + width = 500; height = 500; + + // Description: + // This page is intended for testing scroll pane functionality, including + // the associated events, actions, etc. It specifically tests the scroll + // pane when used on an HTML widget, allowing us to load long HTML pages + // to test scrolling large amounts of content. + + pane "widget/pane" + { + x = 10; y = 10; + width = 480; height = 480; + + scroll "widget/scrollpane" + { + x = 0; y = 20; + width = 300; height = 300; + bgcolor = "#c0f1ba"; + + // Content. + // html "widget/html" + // { + // x = 0; y = 0; + // width = 300; height = 300; + + // mode = "dynamic"; + // source = "/samples/html_example2_long.html"; + // } + + // Vertical content. + spacer "widget/pane" + { + x = 0; y = 0; + width = 0; height = 2000; + fl_height = 0; + style = flat; + } + a1 "widget/pane" + { + x = 10; y = 0; + width = 250; height = 500; + + bgcolor = "#e6b2c4"; + } + a2 "widget/pane" + { + x = 20; y = 500; + width = 200; height = 500; + + bgcolor = "#e6d2c4"; + } + a3 "widget/pane" + { + x = 30; y = 1000; + width = 150; height = 500; + + bgcolor = "#e6b2c4"; + } + a4 "widget/pane" + { + x = 40; y = 1500; + width = 100; height = 500; + + bgcolor = "#e6d2c4"; + } + + // Horizontal Content. + // Unused because there is no horizontal scrollpane. + // spacer "widget/pane" + // { + // x = 0; y = 0; + // width = 2000; height = 0; + // fl_height = 0; + // style = flat; + // } + // a1 "widget/pane" + // { + // x = 0; y = 10; + // width = 500; height = 250; + + // bgcolor = "#e6b2c4"; + // } + // a2 "widget/pane" + // { + // x = 500; y = 20; + // width = 500; height = 200; + + // bgcolor = "#e6d2c4"; + // } + // a3 "widget/pane" + // { + // x = 1000; y = 30; + // width = 500; height = 150; + + // bgcolor = "#e6b2c4"; + // } + // a4 "widget/pane" + // { + // x = 1500; y = 40; + // width = 500; height = 100; + + // bgcolor = "#e6d2c4"; + // } + + // Ads, testing the Scroll event. + adv "widget/variable" { type = integer; value = runclient(0); } + ad1v "widget/variable" { type = integer; value = runclient(:adv:value); } + ad1c "widget/connector" + { + event = Scroll; + target = test; + action = Alert; + event_condition = runclient(:ad1v:value == 0 and :Percent > 30 and :Change > 0); + Message = runclient("Advertisement!!"); + } + ad1d "widget/connector" + { + event = Scroll; + target = ad1v; + action = SetValue; + event_condition = runclient(:ad1v:value == 0 and :Percent > 30 and :Change > 0); + Value = runclient(1); + } + + ad2v "widget/variable" { type = integer; value = runclient(:adv:value); } + ad2c "widget/connector" + { + event = Scroll; + target = test; + action = Alert; + event_condition = runclient(:ad2v:value == 0 and :Percent > 40 and :Change > 0); + Message = runclient("Advertisement 2!!"); + } + ad2d "widget/connector" + { + event = Scroll; + target = ad2v; + action = SetValue; + event_condition = runclient(:ad2v:value == 0 and :Percent > 40 and :Change > 0); + Value = runclient(1); + } + + ad3v "widget/variable" { type = integer; value = runclient(:adv:value); } + ad3c "widget/connector" + { + event = Scroll; + target = test; + action = Alert; + event_condition = runclient(:ad3v:value == 0 and :Percent > 50 and :Change > 0); + Message = runclient("Advertisement 3!!"); + } + ad3d "widget/connector" + { + event = Scroll; + target = ad3v; + action = SetValue; + event_condition = runclient(:ad3v:value == 0 and :Percent > 50 and :Change > 0); + Value = runclient(1); + } + + // Log events for debugging. + debug_Scroll "widget/connector" + { + event = Scroll; + target = test; + action = Log; + Message = runclient("Scroll " + :Percent + " " + :Change); + } + debug_Wheel "widget/connector" + { + event = Wheel; + target = test; + action = Log; + Message = runclient("Wheel: ctrlKey=" + :ctrlKey + " shiftKey=" + :shiftKey + " altKey=" + :altKey + " metaKey=" + :metaKey + " button=" + :button); + } + debug_Click "widget/connector" + { + event = Click; + target = test; + action = Log; + Message = runclient("Click: ctrlKey=" + :ctrlKey + " shiftKey=" + :shiftKey + " altKey=" + :altKey + " metaKey=" + :metaKey + " button=" + :button); + } + debug_MouseDown "widget/connector" + { + event = MouseDown; + target = test; + action = Log; + Message = runclient("MouseDown: ctrlKey=" + :ctrlKey + " shiftKey=" + :shiftKey + " altKey=" + :altKey + " metaKey=" + :metaKey + " button=" + :button); + } + debug_MouseUp "widget/connector" + { + event = MouseUp; + target = test; + action = Log; + Message = runclient("MouseUp: ctrlKey=" + :ctrlKey + " shiftKey=" + :shiftKey + " altKey=" + :altKey + " metaKey=" + :metaKey + " button=" + :button); + } + debug_MouseOver "widget/connector" + { + event = MouseOver; + target = test; + action = Log; + Message = runclient("MouseOver"); + } + debug_MouseOut "widget/connector" + { + event = MouseOut; + target = test; + action = Log; + Message = runclient("MouseOut"); + } + debug_MouseMove "widget/connector" + { + event = MouseMove; + target = test; + action = Log; + Message = runclient("MouseMove"); + } + } + + button1 "widget/textbutton" + { + x = 5; y = 5; + width = 75; height = 30; + font_size = 18; + + bgcolor="#0c0447"; + text = "To 45%"; + + button1c "widget/connector" + { + event = Click; + target = scroll; + action = ScrollTo; + Percent = 45; + } + } + button2 "widget/textbutton" + { + x = 5; y = 40; + width = 75; height = 30; + font_size = 18; + + bgcolor="#241672"; + text = "To 100px"; + + button2c "widget/connector" + { + event = Click; + target = scroll; + action = ScrollTo; + Offset = 100; + } + } + } + } \ No newline at end of file diff --git a/centrallix-os/samples/tab_adventure.app b/centrallix-os/samples/tab_adventure.app new file mode 100644 index 000000000..a05481949 --- /dev/null +++ b/centrallix-os/samples/tab_adventure.app @@ -0,0 +1,413 @@ +$Version=2$ +FourTabs "widget/page" { + title = "Tab Adventure"; + bgcolor = "#c0c0c0"; + x=0; y=0; width=250; height=300; + + MainTab "widget/tab" { + x = 0; y = 0; width=250; height=300; + tab_location = none; + + bgcolor = "#05091dff"; + inactive_bgcolor = "#01010aff"; + selected_index = 1; + + Scene1 "widget/tabpage" { + height=300; + fl_width = 100; + + Scene1Title "widget/label" { x=10; y=10; width=250; height=32; font_size=32; fgcolor="#fff1df"; text="The Beginning"; } + Scene1Text "widget/label" { + x=10; y=50; width=250; height=80; + font_size=18; fgcolor="#fff1df"; + text=" + You wake up surounded by the ruins of a village. Houses are burned, walls are colapsed, and roves are caved in. + The house you're 'in' doesn't even have a roof, and barely half a wall is still standing. + "; + } + Scene1Ask "widget/label" { x=10; y=130; width=250; height=32; font_size=18; fgcolor="#ffc67cff"; text="What do you do?"; } + + Scene1Option1 "widget/textbutton" { + x = 10; y = 180; width = 50; height = 30; font_size=18; bgcolor="#0c0447ff"; + text = "Get up and investigate the village"; + Scene1Option1C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=2; } + } + + Scene1Option2 "widget/textbutton" { + x = 70; y = 180; width = 50; height = 30; font_size=18; bgcolor="#0c0447ff"; + text = "Look around the 'house'"; + Scene1Option2C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=3; } + } + } + + Scene2 "widget/tabpage" { + height=300; + + Scene2Title "widget/label" { x=10; y=10; width=250; height=32; font_size=32; fgcolor="#fff1df"; text="Exploring"; } + Scene2Text "widget/label" { + x=10; y=50; width=250; height=80; + font_size=18; fgcolor="#fff1df"; + text=" + You get up and begin to walk around the village. Everything is in pretty bad shape, but a lot of brick fireplaces + were able to survive decently well. + "; + } + Scene2Ask "widget/label" { x=10; y=130; width=250; height=32; font_size=18; fgcolor="#ffc67cff"; text="What do you do?"; } + + Scene2Option1 "widget/textbutton" { + x = 10; y = 180; width = 50; height = 30; font_size=18; bgcolor="#0c0447ff"; + text = "Leave the village"; + Scene2Option1C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=6; } + } + + Scene2Option2 "widget/textbutton" { + x = 70; y = 180; width = 50; height = 30; font_size=18; bgcolor="#0c0447ff"; + text = "Investigate a fireplace"; + Scene2Option2C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=7; } + } + + Scene2Option3 "widget/textbutton" { + x = 130; y = 180; width = 50; height = 30; font_size=18; bgcolor="#0c0447ff"; + text = "Go back to the first house where you woke up."; + Scene2Option3C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=3; } + } + } + + Scene3 "widget/tabpage" { + height=300; + + Scene3Title "widget/label" { x=10; y=10; width=250; height=32; font_size=32; fgcolor="#fff1df"; text="Look around the house"; } + Scene3Text "widget/label" { + x=10; y=50; width=250; height=80; + font_size=18; fgcolor="#fff1df"; + text=" + You look around the house where you woke up. + "; + } + Scene3Ask "widget/label" { x=10; y=130; width=250; height=32; font_size=18; fgcolor="#ffc67cff"; text=""; } + + Scene3Option1 "widget/textbutton" { + x = 10; y = 180; width = 50; height = 30; font_size=18; bgcolor="#0c0447ff"; + text = "..."; + Scene3Option1C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=4; } + } + } + + Scene4 "widget/tabpage" { + height=300; + + Scene4Title "widget/label" { x=10; y=10; width=250; height=32; font_size=32; fgcolor="#fff1df"; text="Food!"; } + Scene4Text "widget/label" { + x=10; y=50; width=250; height=80; + font_size=18; fgcolor="#fff1df"; + text=" + You find some scraps of food and realize that you're famished! + The food looks pretty old, though. Maybe it's not a good idea... + "; + } + Scene4Ask "widget/label" { x=10; y=130; width=250; height=32; font_size=18; fgcolor="#ffc67cff"; text="What do you do?"; } + + Scene4Option1 "widget/textbutton" { + x = 10; y = 180; width = 50; height = 30; font_size=18; bgcolor="#0c0447ff"; + text = "Eat the food"; + Scene4Option1C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=5; } + } + + Scene4Option2 "widget/textbutton" { + x = 70; y = 180; width = 50; height = 30; font_size=18; bgcolor="#0c0447ff"; + text = "Leave it and check out the village"; + Scene4Option2C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=2; } + } + } + + Scene5 "widget/tabpage" { + height=300; + + Scene5Title "widget/label" { x=10; y=10; width=250; height=32; font_size=32; fgcolor="#fff1df"; text="Bad food..."; } + Scene5Text1 "widget/label" { + x=10; y=50; width=250; height=80; + font_size=18; fgcolor="#fff1df"; + text="You eat the food."; + } + Scene5Text2 "widget/label" { + x=10; y=90; width=250; height=80; + font_size=18; fgcolor="#da2d2dff"; + text="OH NO!!"; + } + Scene5Text3 "widget/label" { + x=10; y=120; width=250; height=80; + font_size=18; fgcolor="#fff1df"; + text="You should not have done that. You feel very sick."; + } + Scene5Ask "widget/label" { x=10; y=150; width=250; height=32; font_size=18; fgcolor="#ffc67cff"; text=". . ."; } + + Scene5Option1 "widget/textbutton" { + x = 10; y = 190; width = 50; height = 30; font_size=18; bgcolor="#0c0447ff"; + text = ". . ."; + Scene5Option1C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=15; } + } + } + + Scene6 "widget/tabpage" { + height=300; + + Scene6Title "widget/label" { x=10; y=10; width=250; height=32; font_size=32; fgcolor="#fff1df"; text="FREEDOM"; } + Scene6Text "widget/label" { + x=10; y=50; width=250; height=80; + font_size=18; fgcolor="#fff1df"; + text=" + You leave the village. As you get further away from the depressing place, you begin to run. + You sprint across planes and through vallies, never looking back or missing the acursed ruins you left behind. + After a while, though, you suddenly fall off some kind of edge and plumit until everything goes black... + "; + } + + Scene6Option1 "widget/textbutton" { + x = 10; y = 150; width = 75; height = 30; font_size=18; bgcolor="#1a1066ff"; + text = "WHAT?! The writer made the world THAT small?"; + Scene6Option1C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=15; } + } + } + + Scene7 "widget/tabpage" { + height=300; + + Scene7Title "widget/label" { x=10; y=10; width=250; height=32; font_size=32; fgcolor="#fff1df"; text="Fireplaces"; } + Scene7Text "widget/label" { + x=10; y=50; width=250; height=80; + font_size=18; fgcolor="#fff1df"; + text=" + There's several firepalces around, so you have a few options. + "; + } + + Scene7Option1 "widget/textbutton" { + x = 10; y = 120; width = 50; height = 24; font_size=18; bgcolor="#0c0447ff"; + text = "Sturdy Fireplace"; + Scene7Option1C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=8; } + } + + Scene7Option2 "widget/textbutton" { + x = 10; y = 150; width = 50; height = 24; font_size=18; bgcolor="#0c0447ff"; + text = "Leaning Fireplace"; + Scene7Option2C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=10; } + } + + Scene7Option3 "widget/textbutton" { + x = 10; y = 180; width = 50; height = 24; font_size=18; bgcolor="#0c0447ff"; + text = "Crumbling Fireplace"; + Scene7Option3C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=9; } + } + } + + Scene8 "widget/tabpage" { + height=300; + + Scene8Title "widget/label" { x=10; y=10; width=250; height=32; font_size=32; fgcolor="#fff1df"; text="'Sturdy' Fireplace"; } + Scene8Text "widget/label" { + x=10; y=50; width=250; height=80; + font_size=18; fgcolor="#fff1df"; + text=" + You approach the sturdy fireplace and begin to investigate it. Then, you find a loose brick! + However, as you slowly pull it out, the entire fireplace suddenly topples over onto you. + Guess it wasn't so sturdy after all!! + "; + } + + Scene8Option1 "widget/textbutton" { + x = 10; y = 150; width = 75; height = 30; font_size=18; bgcolor="#660009ff"; + text = "The end"; + Scene8Option1C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=15; } + } + } + + Scene9 "widget/tabpage" { + height=300; + + Scene9Title "widget/label" { x=10; y=10; width=250; height=32; font_size=32; fgcolor="#fff1df"; text="Crumbling Fireplace"; } + Scene9Text "widget/label" { + x=10; y=50; width=250; height=80; + font_size=18; fgcolor="#fff1df"; + text=" + You approach the crumbling fireplace and realize it's the one in the house where you woke up! + "; + } + + Scene9Option1 "widget/textbutton" { + x = 10; y = 150; width = 75; height = 30; font_size=18; bgcolor="#0c0447ff"; + text = "Neat!"; + Scene9Option1C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=3; } + } + } + + Scene10 "widget/tabpage" { + height=300; + + Scene10Title "widget/label" { x=10; y=10; width=250; height=32; font_size=32; fgcolor="#fff1df"; text="Leaning Fireplace"; } + Scene10Text "widget/label" { + x=10; y=50; width=250; height=80; + font_size=18; fgcolor="#fff1df"; + text=" + As you investigate the leaning fireplace, you find a loose brick. You carefully slide + the brick out, and behind it you pull out a golden scroll that seems to call to you. + "; + } + Scene10Ask "widget/label" { x=10; y=130; width=250; height=32; font_size=18; fgcolor="#ffc67cff"; text="What do you do?"; } + + Scene10Option1 "widget/textbutton" { + x = 10; y = 180; width = 50; height = 30; font_size=18; bgcolor="#0c0447ff"; + text = "Read it"; + Scene10Option1C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=11; } + } + + Scene10Option2 "widget/textbutton" { + x = 70; y = 180; width = 50; height = 30; font_size=18; bgcolor="#0c0447ff"; + text = "Yikes! Probably cursed. Put it down."; + Scene10Option2C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=2; } + } + } + + Scene11 "widget/tabpage" { + height=300; + + Scene11Title "widget/label" { x=10; y=10; width=250; height=32; font_size=32; fgcolor="#fff1df"; text="Leaning Fireplace: Last Chance"; } + Scene11Text "widget/label" { + x=10; y=50; width=250; height=80; + font_size=18; fgcolor="#fff1df"; + text=" + You know, the scroll probably is cursed. + Reading it might not be a good idea! + "; + } + Scene11Ask "widget/label" { x=10; y=130; width=250; height=32; font_size=18; fgcolor="#ffc67cff"; text="What do you do?"; } + + Scene11Option7 "widget/textbutton" { + x = 82; y = 230; width = 25; height = 15; font_size=6; fgcolor="#fcc885"; bgcolor="#580251"; + text = "Read it anyway!"; + Scene11Option7C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=12; } + } + + Scene11Option1 "widget/textbutton" { + x = 10; y = 180; width = 50; height = 25; font_size=18; bgcolor="#0c0447ff"; + text = "Ok, I'll go investigate the village"; + Scene11Option1C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=2; } + } + + Scene11Option2 "widget/textbutton" { + x = 70; y = 180; width = 50; height = 25; font_size=18; bgcolor="#0c0447ff"; + text = "Ok, I'll look around the house where I woke up."; + Scene11Option2C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=3; } + } + + Scene11Option3 "widget/textbutton" { + x = 130; y = 180; width = 50; height = 25; font_size=18; bgcolor="#0c0447ff"; + text = "Ok, I'll go to the sturdy fireplace."; + Scene11Option3C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=8; } + } + + Scene11Option4 "widget/textbutton" { + x = 10; y = 220; width = 50; height = 25; font_size=18; bgcolor="#0c0447ff"; + text = "Ok, I'll leave the village."; + Scene11Option4C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=6; } + } + + Scene11Option5 "widget/textbutton" { + x = 70; y = 220; width = 50; height = 25; font_size=18; bgcolor="#1f0757bb"; + text = "Ok, knock myself out."; + Scene11Option5C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=1; } + } + + Scene11Option6 "widget/textbutton" { + x = 130; y = 220; width = 50; height = 25; font_size=18; bgcolor="#3e0655ff"; + text = "Ok, I'll die."; + Scene11Option6C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=15; } + } + } + + Scene12 "widget/tabpage" { + height=300; + + Scene12Title "widget/label" { x=10; y=10; width=250; height=32; font_size=32; fgcolor="#fff1df"; text="You really shouldn't read that!"; } + Scene12Text "widget/label" { + x=10; y=50; width=250; height=80; + font_size=18; fgcolor="#fff1df"; + text=" + What? How did you do that! You're not supposed to do that! + "; + } + + Scene12Option1 "widget/textbutton" { + x = 10; y = 120; width = 50; height = 30; font_size=18; bgcolor="#0c0447ff"; + text = "JUST LET ME READ THE SCROLL!!!"; + Scene12Option1C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=13; } + } + } + + + Scene13 "widget/tabpage" { + height=300; + + Scene13Title "widget/label" { x=10; y=10; width=250; height=32; font_size=32; fgcolor="#fff1df"; text="VICTORY"; } + Scene13Text "widget/label" { + x=10; y=50; width=250; height=80; + font_size=18; fgcolor="#fff1df"; + text=" + Ok... fine. You found the golden scroll and won the game. + Look, I had to make it at least a little bit difficult for you! + "; + } + + Scene13Option1 "widget/textbutton" { + x = 10; y = 120; width = 50; height = 30; font_size=18; bgcolor="#0c0447ff"; + text = "Yay!"; + Scene13Option1C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=14; } + } + } + + + Scene14 "widget/tabpage" { + height=300; + + Scene14Title "widget/label" { x=10; y=10; width=250; height=32; font_size=32; fgcolor="#fff1df"; text="The End"; } + Scene14Text "widget/label" { + x=10; y=50; width=250; height=80; + font_size=18; fgcolor="#fff1df"; + text=" + Ok... fine. You found the golden scroll and won the game. + Just thought I'd see if I could trick you there hahahah. + "; + } + + Scene14Text2 "widget/label" { + x=10; y=120; width=250; height=80; + font_size=18; fgcolor="#d9e97dff"; + text=" + Thank you for playing! + "; + } + } + + Scene15 "widget/tabpage" { + height=300; + + Scene15Title "widget/label" { x=10; y=10; width=250; height=32; font_size=32; fgcolor="#ff001c"; text="DEATH"; } + Scene15Text "widget/label" { + x=10; y=50; width=250; height=80; + font_size=18; fgcolor="#fff1df"; + text=" + You died... Let's call this a learning experience. + Better luck next time. + "; + } + Scene15Ask "widget/label" { x=10; y=130; width=250; height=32; font_size=18; fgcolor="#ffc67cff"; text="What do you do?"; } + + Scene15Option1 "widget/textbutton" { + x = 10; y = 180; width = 50; height = 30; font_size=18; bgcolor="#0c0447ff"; + text = "Try again?"; + Scene15Option1C "widget/connector" { event=Click; target=MainTab; action=SetTab; TabIndex=1; } + } + + Scene15Option2 "widget/textbutton" { x = 70; y = 180; width = 50; height = 30; font_size=18; bgcolor="#470404ff"; text = "Give up"; } + } + } +} diff --git a/centrallix-os/samples/tab_features.app b/centrallix-os/samples/tab_features.app new file mode 100644 index 000000000..23f4c448c --- /dev/null +++ b/centrallix-os/samples/tab_features.app @@ -0,0 +1,698 @@ +$Version=2$ +tab_features "widget/page" + { + x = 0; y = 0; + width = 500; height = 500; + + title = "Tab Features Demonstrated"; + bgcolor = "#b0b0b0"; + + // Render values (used by the buttons below) + tloc "widget/parameter" { type = "string"; default = "top"; } + w "widget/parameter" { type = "integer"; default = 220; } + h "widget/parameter" { type = "integer"; default = 70; } + tab_w "widget/parameter" { type = "integer"; default = null; } + tab_h "widget/parameter" { type = "integer"; default = null; } + + tloc_label "widget/label" + { + x = 2; y = 1; + width = 50; height = 20; + font_size = 16; + + text = "Tab Location:"; + } + + button_top "widget/textbutton" + { + x = 55; y = 1; + width = 27; height = 18; + font_size = 10; + + text = "Top"; + bgcolor="#0c0447ff"; + + button_top_c "widget/connector" + { + event = Click; + target = tab_features; + action = LoadPage; + Source = "tab_features.app?tloc=top&w=220&h=70"; + } + } + + button_bottom "widget/textbutton" + { + x = 85; y = 1; + width = 27; height = 18; + font_size = 10; + + text = "Bottom"; + bgcolor="#0c0447ff"; + + button_bottom_c "widget/connector" + { + event = Click; + target = tab_features; + action = LoadPage; + Source = "tab_features.app?tloc=bottom&w=220&h=70"; + } + } + + button_left "widget/textbutton" + { + x = 115; y = 1; + width = 27; height = 18; + font_size = 10; + + text = "Left"; + bgcolor="#0c0447ff"; + + button_left_c "widget/connector" + { + event = Click; + target = tab_features; + action = LoadPage; + Source = "tab_features.app?tloc=left&w=140&h=90&tab_w=80"; + } + } + + button_right "widget/textbutton" + { + x = 145; y = 1; + width = 27; height = 18; + font_size = 10; + + text = "Right"; + bgcolor="#0c0447ff"; + + button_right_c "widget/connector" + { + event = Click; + target = tab_features; + action = LoadPage; + Source = "tab_features.app?tloc=right&w=140&h=90&tab_w=80"; + } + } + + t1 "widget/tab" + { + x = 0; y = 20; + height = 500; width = 500; + + bgcolor = "#c0c0c0"; + inactive_bgcolor = "#a8a8a8"; + selected_index = 2; + tab_location = top; + select_translate_out = 1; + + t11 "widget/tabpage" + { + title = "Spacing Rant"; + + t11l0 "widget/label" { x=20; y=20; width=200; height=40; font_size=24; text="Spacing Rant"; } + + t11t1 "widget/tab" + { + x = 20; y = 60; + width = runserver(:this:w); height = 100; + tab_location = runserver(:this:tloc); + tab_width = runserver(:this:tab_w); + tab_height = runserver(:this:tab_h); + + background = "/sys/images/slate2.gif"; // "/sys/images/4Color.png" + inactive_background = "/sys/images/slate2_dark.gif"; + selected_index = 2; + + t11t11 "widget/tabpage" { title = "Tab 1"; t11t11l "widget/label" { x=10; y=10; width=100; height=32; style=bold; text="10px X 10px"; } } + t11t12 "widget/tabpage" { title = "Tab 2"; t11t12l "widget/label" { x=20; y=30; width=100; height=32; style=bold; text="20px X 30px"; } } + t11t13 "widget/tabpage" { title = "Tab 3"; t11t13l "widget/label" { x=30; y=50; width=100; height=32; style=bold; text="30px X 50px"; } } + t11t14 "widget/tabpage" { title = "Tab 4"; t11t14l "widget/label" { x=40; y=70; width=100; height=32; style=bold; text="40px X 70px"; } } + + t11t1c "widget/connector" + { + event = TabChanged; + action = SetTab; + target = t11t2; + TabIndex = runclient(:t11t1:selected_index); + } + } + + t11t2 "widget/tab" + { + x = 260; y = 60; + width = runserver(:this:w); height = 100; + tab_location = runserver(:this:tloc); + tab_width = runserver(:this:tab_w); + tab_height = runserver(:this:tab_h); + + bgcolor = "#c0c0c0"; + inactive_bgcolor = "#b8b8b8"; + selected = t11t22; + + t11t21 "widget/tabpage" { title = "Tab 1"; t11t21l "widget/label" { x=10; y=10; width=100; height=32; style=bold; text="10px X 10px"; } } + t11t22 "widget/tabpage" { title = "Tab 2"; t11t22l "widget/label" { x=20; y=30; width=100; height=32; style=bold; text="20px X 30px"; } } + t11t23 "widget/tabpage" { title = "Tab 3"; t11t23l "widget/label" { x=30; y=50; width=100; height=32; style=bold; text="30px X 50px"; } } + t11t24 "widget/tabpage" { title = "Tab 4"; t11t24l "widget/label" { x=40; y=70; width=100; height=32; style=bold; text="40px X 70px"; } } + + t11t2c "widget/connector" + { + event = TabChanged; + action = SetTab; + target = t11t1; + TabIndex = runclient(:t11t2:selected_index); + } + } + + t11p "widget/pane" + { + x = 20; y = 180; width=runserver(:this:w); height=100; + + t11p1l "widget/label" { x=10; y=10; width=100; height=32; style=bold; text="10px X 10px"; } + t11p2l "widget/label" { x=20; y=30; width=100; height=32; style=bold; text="20px X 30px"; } + t11p3l "widget/label" { x=30; y=50; width=100; height=32; style=bold; text="30px X 50px"; } + t11p4l "widget/label" { x=40; y=70; width=100; height=32; style=bold; text="40px X 70px"; } + } + } + + t12 "widget/tabpage" + { + title = "Tab Spacing"; + + t12l0 "widget/label" { x=20; y=20; width=400; height=40; font_size=24; text="Tab Spacing"; } + + t12l1 "widget/label" { x=30; y=75; width=runserver(:this:w-20); height=40; font_size=16; style="bold"; text="Default (2px)"; } + t12t1 "widget/tab" + { + x = 20; y = 100; + width = runserver(:this:w); height = runserver(:this:h); + tab_location = runserver(:this:tloc); + tab_width = runserver(:this:tab_w); + tab_height = runserver(:this:tab_h); + + background = "/sys/images/slate2.gif"; + inactive_background = "/sys/images/slate2_dark.gif"; + selected_index = 2; + + t12t11 "widget/tabpage" { title = "First"; t12t11l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab One"; } } + t12t12 "widget/tabpage" { title = "Looong Tab"; t12t12l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Two"; } } + t12t13 "widget/tabpage" { title = "S"; t12t13l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Three"; } } + t12t14 "widget/tabpage" { title = "Last Tab"; t12t14l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Four"; } } + + t12t1c "widget/connector" + { + event = TabChanged; + action = SetTab; + target = t12t2; + TabIndex = runclient(:t12t1:selected_index); + } + } + + t12l2 "widget/label" { x=270; y=75; width=runserver(:this:w-20); height=40; font_size=16; style="bold"; text="No Tab Spacing"; } + t12t2 "widget/tab" + { + x = 260; y = 100; + width = runserver(:this:w); height = runserver(:this:h); + tab_location = runserver(:this:tloc); + tab_width = runserver(:this:tab_w); + tab_height = runserver(:this:tab_h); + + background = "/sys/images/slate2.gif"; + inactive_background = "/sys/images/slate2_dark.gif"; + selected = t12t22; + tab_spacing = 0; + + t12t21 "widget/tabpage" { title = "First"; t12t21l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label One"; } } + t12t22 "widget/tabpage" { title = "Looong Tab"; t12t22l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label Two"; } } + t12t23 "widget/tabpage" { title = "S"; t12t23l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label Three"; } } + t12t24 "widget/tabpage" { title = "Last Tab"; t12t24l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label Four"; } } + + t12t2c "widget/connector" + { + event = TabChanged; + action = SetTab; + target = t12t3; + TabIndex = runclient(:t12t2:selected_index); + } + } + + t12l3 "widget/label" { x=30; y=215; width=runserver(:this:w-20); height=40; font_size=16; style="bold"; text="Tab Spacing 8px"; } + t12t3 "widget/tab" + { + x = 20; y = 240; + width = runserver(:this:w); height = runserver(:this:h); + tab_location = runserver(:this:tloc); + tab_width = runserver(:this:tab_w); + tab_height = runserver(:this:tab_h); + + background = "/sys/images/slate2.gif"; + inactive_background = "/sys/images/slate2_dark.gif"; + selected_index = 2; + tab_spacing = 8; + + t12t31 "widget/tabpage" { title = "First"; t12t31l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab One"; } } + t12t32 "widget/tabpage" { title = "Looong Tab"; t12t32l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Two"; } } + t12t33 "widget/tabpage" { title = "S"; t12t33l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Three"; } } + t12t34 "widget/tabpage" { title = "Last Tab"; t12t34l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Four"; } } + + t12t3c "widget/connector" + { + event = TabChanged; + action = SetTab; + target = t12t4; + TabIndex = runclient(:t12t3:selected_index); + } + } + + t12l4 "widget/label" { x=270; y=215; width=runserver(:this:w-20); height=40; font_size=16; style="bold"; text="Tab Spacing 16px"; } + t12t4 "widget/tab" + { + x = 260; y = 240; + width = runserver(:this:w); height = runserver(:this:h); + tab_location = runserver(:this:tloc); + tab_width = runserver(:this:tab_w); + tab_height = runserver(:this:tab_h); + + background = "/sys/images/slate2.gif"; + inactive_background = "/sys/images/slate2_dark.gif"; + selected = t12t42; + tab_spacing = 16; + + t12t41 "widget/tabpage" { title = "First"; t12t41l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label One"; } } + t12t42 "widget/tabpage" { title = "Looong Tab"; t12t42l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label Two"; } } + t12t43 "widget/tabpage" { title = "S"; t12t43l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label Three"; } } + t12t44 "widget/tabpage" { title = "Last Tab"; t12t44l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label Four"; } } + + t12t4c "widget/connector" + { + event = TabChanged; + action = SetTab; + target = t12t5; + TabIndex = runclient(:t12t4:selected_index); + } + } + + t12l5 "widget/label" { x=30; y=355; width=60; height=20; fl_width = 1; font_size=16; style="bold"; text="Tab Spacing -8px"; } + t12l5a "widget/label" { x=90; y=357; width=runserver(:this:w-80); height=15; font_size=12; style="bold"; fgcolor="red"; text="(Negative values not recomended)"; } + t12t5 "widget/tab" + { + x = 20; y = 380; + width = runserver(:this:w); height = runserver(:this:h); + tab_location = runserver(:this:tloc); + tab_width = runserver(:this:tab_w); + tab_height = runserver(:this:tab_h); + + background = "/sys/images/slate2.gif"; + inactive_background = "/sys/images/slate2_dark.gif"; + selected_index = 2; + tab_spacing = -8; + + t12t51 "widget/tabpage" { title = "First"; t12t51l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab One"; } } + t12t52 "widget/tabpage" { title = "Looong Tab"; t12t52l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Two"; } } + t12t53 "widget/tabpage" { title = "S"; t12t53l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Three"; } } + t12t54 "widget/tabpage" { title = "Last Tab"; t12t54l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Four"; } } + + t12t5c "widget/connector" + { + event = TabChanged; + action = SetTab; + target = t12t6; + TabIndex = runclient(:t12t5:selected_index); + } + } + + t12l6 "widget/label" { x=270; y=355; width=70; height=20; fl_width = 1; font_size=16; style="bold"; text="Tab Spacing -16px"; } + t12l6a "widget/label" { x=345; y=357; width=runserver(:this:w-70); height=15; font_size=12; style="bold"; fgcolor="red"; text="(Negative values not recomended)"; } + t12t6 "widget/tab" + { + x = 260; y = 380; + width = runserver(:this:w); height = runserver(:this:h); + tab_location = runserver(:this:tloc); + tab_width = runserver(:this:tab_w); + tab_height = runserver(:this:tab_h); + + background = "/sys/images/slate2.gif"; + inactive_background = "/sys/images/slate2_dark.gif"; + selected = t12t62; + tab_spacing = -16; + + t12t61 "widget/tabpage" { title = "First"; t12t61l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label One"; } } + t12t62 "widget/tabpage" { title = "Looong Tab"; t12t62l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label Two"; } } + t12t63 "widget/tabpage" { title = "S"; t12t63l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label Three"; } } + t12t64 "widget/tabpage" { title = "Last Tab"; t12t64l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label Four"; } } + + t12t6c "widget/connector" + { + event = TabChanged; + action = SetTab; + target = t12t1; + TabIndex = runclient(:t12t6:selected_index); + } + } + } + + t13 "widget/tabpage" + { + title = "Tab Height"; + + t13l0 "widget/label" { x=20; y=20; width=400; height=40; font_size=24; text="Tab Height"; } + + t13l1 "widget/label" { x=30; y=75; width=runserver(:this:w-20); height=40; font_size=16; style="bold"; text="Default (24px)"; } + t13t1 "widget/tab" + { + x = 20; y = 100; + width = runserver(:this:w); height = runserver(:this:h); + tab_location = runserver(:this:tloc); + tab_width = runserver(:this:tab_w); + + background = "/sys/images/slate2.gif"; + inactive_background = "/sys/images/slate2_dark.gif"; + selected_index = 2; + + t13t11 "widget/tabpage" { title = "First"; t13t11l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab One"; } } + t13t12 "widget/tabpage" { title = "Looong Tab"; t13t12l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Two"; } } + t13t13 "widget/tabpage" { title = "S"; t13t13l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Three"; } } + t13t14 "widget/tabpage" { title = "Last Tab"; t13t14l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Four"; } } + + t13t1c "widget/connector" + { + event = TabChanged; + action = SetTab; + target = t13t3; + TabIndex = runclient(:t13t1:selected_index); + } + } + + t13l2 "widget/label" { x=270; y=75; width=runserver(:this:w-20); height=40; font_size=16; style="bold"; text="No Tab Height"; } + t13l2a "widget/label" + { + x = 270; y = 110; + width = runserver(:this:w-20); height = runserver(:this:h - 20); + font_size = 24; style = bold; fgcolor = "red"; + text = "Not allowed"; + } + + t13l2b "widget/label" + { + x = 270; y = 140; + width = runserver(:this:w-20); height = 30; + font_size = 18; style = italic; fgcolor = "red"; + text = "Because I hate fun."; + } + + t13l3 "widget/label" { x=30; y=215; width=runserver(:this:w-20); height=40; font_size=16; style="bold"; text="Tab Height 32px"; } + t13t3 "widget/tab" + { + x = 20; y = 240; + width = runserver(:this:w); height = runserver(:this:h - 12); + tab_location = runserver(:this:tloc); + tab_width = runserver(:this:tab_w); + tab_height = 32; + + background = "/sys/images/slate2.gif"; + inactive_background = "/sys/images/slate2_dark.gif"; + selected_index = 2; + tab_height = 32; + + t13t31 "widget/tabpage" { title = "First"; t13t31l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab One"; } } + t13t32 "widget/tabpage" { title = "Looong Tab"; t13t32l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Two"; } } + t13t33 "widget/tabpage" { title = "S"; t13t33l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Three"; } } + t13t34 "widget/tabpage" { title = "Last Tab"; t13t34l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Four"; } } + + t13t3c "widget/connector" + { + event = TabChanged; + action = SetTab; + target = t13t4; + TabIndex = runclient(:t13t3:selected_index); + } + } + + t13l4 "widget/label" { x=270; y=215; width=runserver(:this:w-20); height=40; font_size=16; style="bold"; text="Tab Height 48px"; } + t13t4 "widget/tab" + { + x = 260; y = 240; + width = runserver(:this:w); height = runserver(:this:h - 12); + tab_location = runserver(:this:tloc); + tab_width = runserver(:this:tab_w); + tab_height = 48; + + background = "/sys/images/slate2.gif"; + inactive_background = "/sys/images/slate2_dark.gif"; + selected = t13t42; + tab_height = 48; + + t13t41 "widget/tabpage" { title = "First"; t13t41l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label One"; } } + t13t42 "widget/tabpage" { title = "Looong Tab"; t13t42l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label Two"; } } + t13t43 "widget/tabpage" { title = "S"; t13t43l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label Three"; } } + t13t44 "widget/tabpage" { title = "Last Tab"; t13t44l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label Four"; } } + + t13t4c "widget/connector" + { + event = TabChanged; + action = SetTab; + target = t13t5; + TabIndex = runclient(:t13t4:selected_index); + } + } + + t13l5 "widget/label" { x=30; y=355; width=60; height=20; font_size=16; style="bold"; text="Tab Height 16px"; } + t13t5 "widget/tab" + { + x = 20; y = 380; + width = runserver(:this:w); height = runserver(:this:h); + tab_location = runserver(:this:tloc); + tab_width = runserver(:this:tab_w); + tab_height = 16; + + background = "/sys/images/slate2.gif"; + inactive_background = "/sys/images/slate2_dark.gif"; + selected_index = 2; + tab_height = 16; + + t13t51 "widget/tabpage" { title = "First"; t13t51l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab One"; } } + t13t52 "widget/tabpage" { title = "Looong Tab"; t13t52l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Two"; } } + t13t53 "widget/tabpage" { title = "S"; t13t53l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Three"; } } + t13t54 "widget/tabpage" { title = "Last Tab"; t13t54l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Four"; } } + + t13t5c "widget/connector" + { + event = TabChanged; + action = SetTab; + target = t13t6; + TabIndex = runclient(:t13t5:selected_index); + } + } + + t13l6 "widget/label" { x=270; y=355; width=60; height=20; font_size=16; style="bold"; text="Tab Spacing 8px"; } + t13t6 "widget/tab" + { + x = 260; y = 380; + width = runserver(:this:w); height = runserver(:this:h); + tab_location = runserver(:this:tloc); + tab_width = runserver(:this:tab_w); + tab_height = 8; + + background = "/sys/images/slate2.gif"; + inactive_background = "/sys/images/slate2_dark.gif"; + selected = t13t62; + tab_height = 8; + + t13t61 "widget/tabpage" { title = "First"; t13t61l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label One"; } } + t13t62 "widget/tabpage" { title = "Looong Tab"; t13t62l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label Two"; } } + t13t63 "widget/tabpage" { title = "S"; t13t63l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label Three"; } } + t13t64 "widget/tabpage" { title = "Last Tab"; t13t64l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label Four"; } } + + t13t6c "widget/connector" + { + event = TabChanged; + action = SetTab; + target = t13t1; + TabIndex = runclient(:t13t6:selected_index); + } + } + } + + + t14 "widget/tabpage" + { + title = "Selection Offsets"; + + t14l0 "widget/label" { x=20; y=20; width=400; height=40; font_size=24; text="Selection Offsets"; } + + t14l1 "widget/label" { x=30; y=75; width=220; height=40; font_size=16; style="bold"; text="Default"; } + t14t1 "widget/tab" + { + x = 20; y = 100; + width = runserver(:this:w); height = runserver(:this:h); + tab_location = runserver(:this:tloc); + tab_width = runserver(:this:tab_w); + tab_height = runserver(:this:tab_h); + + background = "/sys/images/slate2.gif"; + inactive_background = "/sys/images/slate2_dark.gif"; + selected_index = 2; + + t14t11 "widget/tabpage" { title = "First"; t14t11l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab One"; } } + t14t12 "widget/tabpage" { title = "Looong Tab"; t14t12l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Two"; } } + t14t13 "widget/tabpage" { title = "S"; t14t13l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Three"; } } + t14t14 "widget/tabpage" { title = "Last Tab"; t14t14l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Four"; } } + + t14t1c "widget/connector" + { + event = TabChanged; + action = SetTab; + target = t14t2; + TabIndex = runclient(:t14t1:selected_index); + } + } + + t14l2 "widget/label" { x=270; y=75; width=220; height=40; font_size=16; style="bold"; text="No Translation"; } + t14t2 "widget/tab" + { + x = 260; y = 100; + width = runserver(:this:w); height = runserver(:this:h); + tab_location = runserver(:this:tloc); + tab_width = runserver(:this:tab_w); + tab_height = runserver(:this:tab_h); + + background = "/sys/images/slate2.gif"; + inactive_background = "/sys/images/slate2_dark.gif"; + selected = t14t22; + select_translate_along = 0; + select_translate_out = 0; + + t14t21 "widget/tabpage" { title = "First"; t14t21l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label One"; } } + t14t22 "widget/tabpage" { title = "Looong Tab"; t14t22l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label Two"; } } + t14t23 "widget/tabpage" { title = "S"; t14t23l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label Three"; } } + t14t24 "widget/tabpage" { title = "Last Tab"; t14t24l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label Four"; } } + + t14t2c "widget/connector" + { + event = TabChanged; + action = SetTab; + target = t14t3; + TabIndex = runclient(:t14t2:selected_index); + } + } + + t14l3 "widget/label" { x=30; y=215; width=220; height=40; font_size=16; style="bold"; text="Along 8"; } + t14t3 "widget/tab" + { + x = 20; y = 240; + width = runserver(:this:w); height = runserver(:this:h); + tab_location = runserver(:this:tloc); + tab_width = runserver(:this:tab_w); + tab_height = runserver(:this:tab_h); + + background = "/sys/images/slate2.gif"; + inactive_background = "/sys/images/slate2_dark.gif"; + selected_index = 2; + select_translate_along = 8; + select_translate_out = 0; + + t14t31 "widget/tabpage" { title = "First"; t14t31l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab One"; } } + t14t32 "widget/tabpage" { title = "Looong Tab"; t14t32l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Two"; } } + t14t33 "widget/tabpage" { title = "S"; t14t33l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Three"; } } + t14t34 "widget/tabpage" { title = "Last Tab"; t14t34l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Four"; } } + + t14t3c "widget/connector" + { + event = TabChanged; + action = SetTab; + target = t14t4; + TabIndex = runclient(:t14t3:selected_index); + } + } + + t14l4 "widget/label" { x=270; y=215; width=220; height=40; font_size=16; style="bold"; text="Out 8"; } + t14t4 "widget/tab" + { + x = 260; y = 240; + width = runserver(:this:w); height = runserver(:this:h); + tab_location = runserver(:this:tloc); + tab_width = runserver(:this:tab_w); + tab_height = runserver(:this:tab_h); + + background = "/sys/images/slate2.gif"; + inactive_background = "/sys/images/slate2_dark.gif"; + selected = t14t42; + select_translate_along = 0; + select_translate_out = 8; + + t14t41 "widget/tabpage" { title = "First"; t14t41l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label One"; } } + t14t42 "widget/tabpage" { title = "Looong Tab"; t14t42l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label Two"; } } + t14t43 "widget/tabpage" { title = "S"; t14t43l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label Three"; } } + t14t44 "widget/tabpage" { title = "Last Tab"; t14t44l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label Four"; } } + + t14t4c "widget/connector" + { + event = TabChanged; + action = SetTab; + target = t14t5; + TabIndex = runclient(:t14t4:selected_index); + } + } + + t14l5 "widget/label" { x=30; y=355; width=30; height=40; font_size=16; style="bold"; text="Along -8"; } + t14l5a "widget/label" { x=67; y=357; width=100; height=40; font_size=12; style="bold"; fgcolor="red"; text="(Negative values not recomended)"; } + t14t5 "widget/tab" + { + x = 20; y = 380; + width = runserver(:this:w); height = runserver(:this:h); + tab_location = runserver(:this:tloc); + tab_width = runserver(:this:tab_w); + tab_height = runserver(:this:tab_h); + + background = "/sys/images/slate2.gif"; + inactive_background = "/sys/images/slate2_dark.gif"; + selected_index = 2; + select_translate_along = -8; + select_translate_out = 0; + + t14t51 "widget/tabpage" { title = "First"; t14t51l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab One"; } } + t14t52 "widget/tabpage" { title = "Looong Tab"; t14t52l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Two"; } } + t14t53 "widget/tabpage" { title = "S"; t14t53l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Three"; } } + t14t54 "widget/tabpage" { title = "Last Tab"; t14t54l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Tab Four"; } } + + t14t5c "widget/connector" + { + event = TabChanged; + action = SetTab; + target = t14t6; + TabIndex = runclient(:t14t5:selected_index); + } + } + + t14l6 "widget/label" { x=270; y=355; width=30; height=40; font_size=16; style="bold"; text="Out -8"; } + t14l6a "widget/label" { x=307; y=357; width=100; height=40; font_size=12; style="bold"; fgcolor="red"; text="(Negative values not recomended)"; } + t14t6 "widget/tab" + { + x = 260; y = 380; + width = runserver(:this:w); height = runserver(:this:h); + tab_location = runserver(:this:tloc); + tab_width = runserver(:this:tab_w); + tab_height = runserver(:this:tab_h); + + background = "/sys/images/slate2.gif"; + inactive_background = "/sys/images/slate2_dark.gif"; + selected = t14t62; + select_translate_along = 0; + select_translate_out = -8; + + t14t61 "widget/tabpage" { title = "First"; t14t61l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label One"; } } + t14t62 "widget/tabpage" { title = "Looong Tab"; t14t62l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label Two"; } } + t14t63 "widget/tabpage" { title = "S"; t14t63l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label Three"; } } + t14t64 "widget/tabpage" { title = "Last Tab"; t14t64l "widget/label" { x=10; y=10; width=100; height=32; font_size=18; fgcolor="#00065f"; text="Label Four"; } } + + t14t6c "widget/connector" + { + event = TabChanged; + action = SetTab; + target = t14t1; + TabIndex = runclient(:t14t6:selected_index); + } + } + } + } + } diff --git a/centrallix-os/sys/js/ht_geom_dom1html.js b/centrallix-os/sys/js/ht_geom_dom1html.js index b8c17ab7d..7c76dd875 100644 --- a/centrallix-os/sys/js/ht_geom_dom1html.js +++ b/centrallix-os/sys/js/ht_geom_dom1html.js @@ -1,4 +1,4 @@ -// Copyright (C) 1998-2006 LightSys Technology Services, Inc. +// Copyright (C) 1998-2026 LightSys Technology Services, Inc. // // You may use these files and this library under the terms of the // GNU Lesser General Public License, Version 2.1, contained in the @@ -11,6 +11,39 @@ // Cross browser Geometry DOM1HTML +// Add useful Math functions. +Math.clamp = (min, val, max) => Math.min(Math.max(min, val), max); +Math.isBetween = (lowerBound, num, upperBound) => (lowerBound < num && num < upperBound); + +/*** Whether to enable noclip (which disables generation of clipping CSS) by + *** default. This requires code to explicitly call enableClippingCSS() to + *** generate clipping CSS. + ***/ +const default_noclip_value = true; + +/*** Experimental system for turning off clipping CSS. + *** The clip values are still stored and can be queried + *** for legacy compatibility, but they will not output + *** any clip rectangles or clip paths in the CSS or HTML. + ***/ +/** Ensure clipping is disabled for a layer / HTML node. **/ +function disableClippingCSS(l) + { + l.clip.noclip = true; + updateClippingCSS(l); + } +/** Ensure clipping is enabled for a layer / HTML node. **/ +function enableClippingCSS(l) + { + l.clip.noclip = false; + updateClippingCSS(l); + } +/** Update clipping without changing any specific values. **/ +function updateClippingCSS(l) + { + setClipTop(l, getClipTop(l)); + } + // Clip Width function getClipWidth(l) { @@ -18,13 +51,13 @@ function getClipWidth(l) } function setClipWidth(l, value) - { + { l.clip.width = value; } // Clip Height function getClipHeight(l) - { + { return l.clip.height; } @@ -34,7 +67,7 @@ function getRuntimeClipHeight(l) } function setClipHeight(l, value) - { + { l.clip.height = value; } @@ -207,43 +240,174 @@ function getpageYOffset() return window.pageYOffset; } -function getRelativeX(l) +/*** Get the size of a DOM node's parent container. + *** + *** @param l The DOM node. + *** @returns The width and height of the parent container. + ***/ +function getParentSize(l) { - if (l.__pg_left != null) return l.__pg_left; - var left = parseInt(pg_get_style(l,'left')); - l.__pg_left = isNaN(left)?0:left; - return l.__pg_left; + const parentRect = l.parentNode.getBoundingClientRect(); + return { width: parentRect.width, height: parentRect.height }; } -function setRelativeX(l, value) +/*** Get the width of a DOM node's parent container. + *** + *** @param l The DOM node. + *** @returns The width of the parent container. + ***/ +function getParentW(l) { - pg_set_style(l,'left',(l.__pg_left = parseInt(value))); - return l.__pg_left; + return getParentSize(l).width; } -function getRelativeY(l) +/*** Get the height of a DOM node's parent container. + *** + *** @param l The DOM node. + *** @returns The height of the parent container. + ***/ +function getParentH(l) { - if (l.__pg_top != null) return l.__pg_top; - return (l.__pg_top = parseInt(pg_get_style(l,'top'))); + return getParentSize(l).height; } -function setRelativeY(l, value) + +/*** We ignore the current value of __pg_left in the following functions even + *** though it might be correct and faster than querying the DOM. However, the + *** layout may have changed since last time, so we always requery the DOM. + ***/ +function getRelative(l, d) { - pg_set_style(l,'top',(l.__pg_top = parseInt(value))); - return l.__pg_top; - } + if (!l) + { + console.error(`Call to getRelative${d.toUpperCase()}(`, l, ')'); + return 0; + } + + const val = parseInt(pg_get_style(l, d, NaN), 10); + return l['__pg_' + d] = (isNaN(val)) ? 0 : val; + } + +function getRelativeX(l) { return getRelative(l, 'left'); } +function getRelativeY(l) { return getRelative(l, 'top'); } +function getRelativeW(l) { return getRelative(l, 'width'); } +function getRelativeH(l) { return getRelative(l, 'height'); } + +/*** Sets the location of a DOM node relative to its parent container. + *** + *** @param l The DOM node being set. (Assumed to be defined.) + *** @param value The new location. This can be a CSS string. + *** @param {'left'|'top'|'width'|'height'} d The dimension being set. + ***/ +function setRelative(l, value, d) + { + /** Convert the value to a number, if possible. **/ + const parsedValue = parseInt(value); + if (!isNaN(parsedValue)) value = parsedValue; + + pg_set_style(l, d, value); + l['__pg_' + d + '_style'] = value; + return l['__pg_' + d] = parseInt(pg_get_style(l, d)); + } + +function setRelativeX(l, value) { return setRelative(l, value, 'left'); } +function setRelativeY(l, value) { return setRelative(l, value, 'top'); } +function setRelativeW(l, value) { return setRelative(l, value, 'width'); } +function setRelativeH(l, value) { return setRelative(l, value, 'height'); } + +/*** Sets a dimension of a DOM element using coordinates in the server + *** generated adaptive layout. It is RECOMMENDED to call a specific sub- + *** function (aka. setResponsiveX(), setResponsiveY(), etc.) instead of + *** calling this function directly to avoid passing dimension directly. + *** + *** WARNING: Ensure that any value passed is calculated ENTIRELY using + *** values from the server (e.g. widget properties) and no values from + *** real page dimensions are used, as these change when the page is + *** resized after being loaded for the first time. + *** + *** @param l The DOM node being set. (Assumed to be defined.) + *** @param value The new location in server-side px. This value must be + *** parseable as a number. + *** @param {'x'|'y'|'w'|'h'} d The letter for the dimension being set. + ***/ +function setResponsive(l, value, d) { + /** Convert the value to a number, if possible. **/ + const parsedValue = parseInt(value); + if (!isNaN(parsedValue)) value = parsedValue; + + /** Server-layout values are always numbers. **/ + if (typeof(value) !== 'number') + { + console.warn(`setResponsive(${l.id}, ?, '${d}'): Expected a parseable number but got:`, value); + return value; + } + + /** The flexibility specified by the server. **/ + var fl_scale = l['__fl_scale_' + d] ?? wgtrGetServerProperty(l, 'fl_scale_' + d); + if (fl_scale == undefined || fl_scale == null) + { + /** The server did not specify a flexibility, even though one was expected. **/ + const warningMsg = 'setResponsive() - FAIL: Missing ' + ((wgtrIsNode(l)) ? 'wgtr.' : '__') + 'fl_scale_' + d; + console.warn(warningMsg, l); + fl_scale = 0; + } + + /** Inflexible elements don't need to be responsive. **/ + if (fl_scale <= 0) return setRelative(l, value, d); + + /** The parent width expected by the server in the adaptive layout. **/ + var d2 = d; + if (d2 == 'x') d2 = 'w'; + if (d2 == 'y') d2 = 'h'; + + var fl_parent = l['__fl_parent_' + d2] ?? wgtrGetServerProperty(l, 'fl_parent_' + d2); + if (fl_parent == undefined || fl_parent == null) + { + /** I wonder if anyone reviewers will see this: Easter egg #7. **/ + const warningMsg = 'setResponsive() - FAIL: Missing ' + ((wgtrIsNode(l)) ? 'wgtr.' : '__') + 'fl_parent_' + d2; + console.warn(warningMsg, l); + } + /** Generate and set the CSS. **/ + const css = `calc(${value}px + (100% - ${fl_parent}px) * ${fl_scale})`; + const prop = { x:'left', y:'top', w:'width', h:'height' }[d]; + return setRelative(l, css, prop); +} + +/** Call these functions instead of calling setResponsive() directly, which leads to less readable code. **/ +function setResponsiveX(l, value) { return setResponsive(l, value, 'x'); } +function setResponsiveY(l, value) { return setResponsive(l, value, 'y'); } +function setResponsiveW(l, value) { return setResponsive(l, value, 'w'); } +function setResponsiveH(l, value) { return setResponsive(l, value, 'h'); } + +/** Moves a DOM node to a location within the window. **/ function moveToAbsolute(l, x, y) { setPageX(l,x); setPageY(l,y); } -function moveTo(l, x, y) +/*** Moves a DOM node to a location inside it's parent container. + *** + *** @param l The DOM node being moved. + *** @param x The new x coordinate. Can be a CSS string (if responsive is false). + *** @param y The new y coordinate. Can be a CSS string (if responsive is false). + *** @param responsive Whether the given coordinates should be treated as + *** adaptive, 'server-side', coordinates where setResponsive() + *** should be invoked to give them responsive design. + ***/ +function moveTo(l, x, y, responsive = false) { - //pg_set_style_string(this,'position','absolute'); - setRelativeX(l,x); - setRelativeY(l,y); + if (responsive) + { + setResponsiveX(l, x); + setResponsiveY(l, y); + } + else + { + setRelativeX(l, x); + setRelativeY(l, y); + } } @@ -310,7 +474,7 @@ function getHeight(l) return getClipHeight(l); } -// Copyright (C) 1998-2001 LightSys Technology Services, Inc. +// Copyright (C) 1998-2026 LightSys Technology Services, Inc. // // You may use these files and this library under the terms of the // GNU Lesser General Public License, Version 2.1, contained in the @@ -329,7 +493,8 @@ function ClipObject_SetAll(top,right,bottom,left) + bottom + "px, " + left + "px)"; this.arr = {1:top,2:right,3:bottom,4:left}; - this.obj.style.setProperty('clip',str,""); + if (!this.hasOwnProperty('noclip')) this.noclip = default_noclip_value; + this.obj.style.setProperty('clip', (this.noclip) ? "" : str); } var ClipRegexp = /rect\((.*), (.*), (.*), (.*)\)/; @@ -367,92 +532,55 @@ function ClipObject_GetPart(n) function ClipObject(o) { this.obj = o; - + this.setall = ClipObject_SetAll; this.getpart = ClipObject_GetPart; } -ClipObject.prototype.__defineGetter__("top", function () - { - return this.getpart(1); - } - ); - -ClipObject.prototype.__defineGetter__("right", function () - { - return this.getpart(2); - } - ); - -ClipObject.prototype.__defineGetter__("width", function () - { - return this.right - this.left; - } - ); - -ClipObject.prototype.__defineGetter__("bottom", function () - { - return this.getpart(3); - } - ); - -ClipObject.prototype.__defineGetter__("height", function () - { - return this.bottom - this.top; - } - ); - -ClipObject.prototype.__defineGetter__("left", function () - { - return this.getpart(4); - } - ); - -ClipObject.prototype.__defineSetter__("top", function (val) - { - this.setall(val,this.right,this.bottom,this.left); - } - ); - -ClipObject.prototype.__defineSetter__("right", function (val) - { - this.setall(this.top,val,this.bottom,this.left); - } - ); - -ClipObject.prototype.__defineSetter__("width", function (val) - { - this.right = this.left + val; - } - ); - -ClipObject.prototype.__defineSetter__("bottom", function (val) - { - this.setall(this.top,this.right,val,this.left); - } - ); - -ClipObject.prototype.__defineSetter__("height", function (val) - { - this.bottom = this.top + val; - } - ); - -ClipObject.prototype.__defineSetter__("left", function (val) - { - this.setall(this.top,this.right,this.bottom,val); - } - ); - -HTMLElement.prototype.__defineGetter__("clip", function () - { - /** keep the same ClipObject around -- that way we can use watches on it **/ - if(this.cx__clip) - return this.cx__clip; - else - return this.cx__clip = new ClipObject(this); - } - ); +Object.defineProperties(ClipObject.prototype, { + top: { + get() { return this.getpart(1); }, + set(val) { this.setall(val, this.right, this.bottom, this.left); }, + configurable: true, + enumerable: true, + }, + right: { + get() { return this.getpart(2); }, + set(val) { this.setall(this.top, val, this.bottom, this.left); }, + configurable: true, + enumerable: true, + }, + bottom: { + get() { return this.getpart(3); }, + set(val) { this.setall(this.top, this.right, val, this.left); }, + configurable: true, + enumerable: true, + }, + left: { + get() { return this.getpart(4); }, + set(val) { this.setall(this.top, this.right, this.bottom, val); }, + configurable: true, + enumerable: true, + }, + width: { + get() { return this.right - this.left; }, + set(val) { this.right = this.left + val; }, + configurable: true, + enumerable: true, + }, + height: { + get() { return this.bottom - this.top; }, + set(val) { this.bottom = this.top + val; }, + configurable: true, + enumerable: true, + }, +}); + +Object.defineProperty(HTMLElement.prototype, "clip", { + get() { return (this.cx__clip) ? this.cx__clip : (this.cx__clip = new ClipObject(this));}, + configurable: true, + enumerable: true, +}); // Load indication if (window.pg_scripts) pg_scripts['ht_geom_dom1html.js'] = true; diff --git a/centrallix-os/sys/js/ht_render.js b/centrallix-os/sys/js/ht_render.js index cc93e167a..b1397b46a 100644 --- a/centrallix-os/sys/js/ht_render.js +++ b/centrallix-os/sys/js/ht_render.js @@ -1,4 +1,4 @@ -// Copyright (C) 1998-2015 LightSys Technology Services, Inc. +// Copyright (C) 1998-2026 LightSys Technology Services, Inc. // // You may use these files and this library under the terms of the // GNU Lesser General Public License, Version 2.1, contained in the @@ -819,6 +819,70 @@ function htr_stylize_element(element, widget, prefix, defaults) ); } + +/** An array storing all points so they can be updated on resize. **/ +const htr_point_targets = []; +window.addEventListener('resize', (e) => htr_point_targets.forEach(htr_update_point)); + +/*** Updates a point when the page has been resized. Fails if the target does + *** not have a point element. (Call htr_action_point() to create a point, + *** this function is only intended to update existing points.) + *** + *** @param point_target The target of the point being updated (necessary + *** because it stores the resize data for the point). + ***/ +function htr_update_point(point_target) + { + const resize_param = point_target?.resize?.param; + if (!resize_param) + { + // Skip: No available resize data. Maybe this DOM node doesn't have an associated point? + console.warn('Failed to get resize data to update point on', point_target); + return; + } + + /** Update the point with data from the resize object. **/ + const { X, Y, AtWidget, BorderColor, FillColor } = resize_param; + const { p1, p2 } = htutil_point(point_target, + X, Y, AtWidget, BorderColor, FillColor, + point_target.point1, point_target.point2 + ); + point_target.point1 = p1; + point_target.point2 = p2; + } + +/*** This function updates the pointing UI element that points from the point_target, + *** or creates one if it does not exist, and handles resize updates for it. + *** Often invoked when the point action is used. + *** + *** @param point_target The point_target DOM node to be pointed at. + *** @param param Pointing parameters. + *** @param param.X The x value for where to point. + *** @param param.Y The y value for where to point. + *** @param param.AtWidget True to point at a widget, false to point at the + *** coordinates specified above. + *** @param param.BorderColor The border color of the point element. + *** @param param.FillColor The fill color of the point element. + ***/ +function htr_action_point(point_target, param) + { + // Get the resize data from the point_target (or create it, if needed). + if (!point_target.resize) point_target.resize = {}; + const { resize } = point_target; + + // Update the saved param. + resize.param = param; + + // Update the entry in htr_point_targets (or create it, if needed). + if (typeof(resize.index) === 'undefined') + resize.index = htr_point_targets.push(point_target) - 1; + else + htr_point_targets[resize.index] = point_target; + + // (Re)render the new point. + htr_update_point(point_target); + } + function htr_alert(obj,maxlevels) { alert(htr_obj_to_text(obj,0,maxlevels)); @@ -1117,12 +1181,20 @@ function htr_subel(l, id) function htr_extract_bgcolor(s) { - if (s.substr(0,17) == "background-color:") + // Handle edge cases. + if (typeof(s) !== 'string') return null; + s = s.toLocaleLowerCase(); + + // Check for CSS rule. + if (s.startsWith("background-color:")) { - var cp = s.indexOf(":"); - return s.substr(cp+2,s.length-cp-3); + const color_start = s.indexOf(":") + 1; + const color_end = (s.endsWith(';')) ? -1 : undefined; + return s.slice(color_start, color_end).trim(); } - else if (s.substr(0,8) == "bgcolor=" || s.substr(0,8) == "bgColor=") + + // Check for HTML attribute (deprecated). + if (s.startsWith("bgcolor=")) { var qp = s.indexOf("'"); if (qp < 1) @@ -1130,6 +1202,8 @@ function htr_extract_bgcolor(s) else return s.substr(qp+1,s.length-qp-2); } + + // Fail: Color not found. return null; } @@ -1150,6 +1224,8 @@ function htr_extract_bgimage(s) function htr_getvisibility(l) { + if (!l) return ''; + var v = null; if (cx__capabilities.Dom0NS) { @@ -1489,7 +1565,7 @@ function htr_addeventlistener(eventType,obj,handler) if (typeof pg_capturedevents[eventType] == 'undefined') { pg_capturedevents[eventType] = handler; - obj.addEventListener(eventType, handler, true); + obj.addEventListener(eventType, handler, { capture: true, passive: false }); } } else diff --git a/centrallix-os/sys/js/ht_utils_layers.js b/centrallix-os/sys/js/ht_utils_layers.js index 096754eb5..2f5904eca 100644 --- a/centrallix-os/sys/js/ht_utils_layers.js +++ b/centrallix-os/sys/js/ht_utils_layers.js @@ -1,4 +1,4 @@ -// Copyright (C) 1998-2001 LightSys Technology Services, Inc. +// Copyright (C) 1998-2026 LightSys Technology Services, Inc. // // You may use these files and this library under the terms of the // GNU Lesser General Public License, Version 2.1, contained in the @@ -20,6 +20,19 @@ function htutil_tag_images(d,t,l,ml) } } +/*** Writes DOM nodes for a pointing UI element. + *** + *** @param wthis The widget targetted by the point. + *** @param x The x value for where to point. + *** @param y The y value for where to point. + *** @param at True to point at a widget, false to point at the coordinates + *** specified above. + *** @param bc The border color of the point element. + *** @param fc The fill color of the point element. + *** @param p1 Point DOM node 1 (optional). + *** @param p2 Point DOM node 2 (optional). + *** @returns { p1, p2 } Pointers to the two possibly new DOM nodes. + ***/ function htutil_point(wthis, x, y, at, bc, fc, p1, p2) { // Determine x/y to point at @@ -97,14 +110,12 @@ function htutil_point(wthis, x, y, at, bc, fc, p1, p2) if (side == 'top') top -= 2; } - // Create the "point divs" if needed - if (!p1) - { - p1 = htr_new_layer(size*2, document); - p2 = htr_new_layer(size*2, document); - } + // Create point DOM nodes, if needed. + if (!p1) p1 = htr_new_layer(size*2, document); + if (!p2) p2 = htr_new_layer(size*2, document); // Set the CSS to enable the point divs + const { top: wtop, left: wleft } = $(wthis).offset(); $(p1).css ({ "position": "absolute", @@ -114,8 +125,8 @@ function htutil_point(wthis, x, y, at, bc, fc, p1, p2) "border-style": "solid", "box-sizing": "border-box", "content": "", - "top": (top + $(wthis).offset().top) + "px", - "left": (left + $(wthis).offset().left) + "px", + "top": (top + wtop) + "px", + "left": (left + wleft) + "px", "border-color": c1, "visibility": "inherit", "z-index": htr_getzindex(wthis) + 1 @@ -129,8 +140,8 @@ function htutil_point(wthis, x, y, at, bc, fc, p1, p2) "border-style": "solid", "box-sizing": "border-box", "content": "", - "top": (top + doffs.y + $(wthis).offset().top) + "px", - "left": (left + doffs.x + $(wthis).offset().left) + "px", + "top": (top + doffs.y + wtop) + "px", + "left": (left + doffs.x + wleft) + "px", "border-color": c2, "visibility": "inherit", "z-index": htr_getzindex(wthis) + 2 diff --git a/centrallix-os/sys/js/ht_utils_wgtr.js b/centrallix-os/sys/js/ht_utils_wgtr.js index 1379bacf9..66f371629 100644 --- a/centrallix-os/sys/js/ht_utils_wgtr.js +++ b/centrallix-os/sys/js/ht_utils_wgtr.js @@ -1,6 +1,6 @@ // // Widget Tree module -// (c) 2004-2014 LightSys Technology Services, Inc. +// (c) 2004-2026 LightSys Technology Services, Inc. // // You may use these files and this library under the terms of the // GNU Lesser General Public License, Version 2.1, contained in the @@ -363,7 +363,7 @@ function wgtrIsUndefined(prop) // wgtrGetServerProperty() - return a server-supplied property value function wgtrGetServerProperty(node, prop_name, def) { - var val = node.__WgtrParams[prop_name]; + var val = node?.__WgtrParams?.[prop_name]; if (typeof val == 'undefined') return def; else if (typeof val == 'object' && val && val.exp) diff --git a/centrallix-os/sys/js/htdrv_autolayout.js b/centrallix-os/sys/js/htdrv_autolayout.js index fabace1ca..9fce22522 100644 --- a/centrallix-os/sys/js/htdrv_autolayout.js +++ b/centrallix-os/sys/js/htdrv_autolayout.js @@ -1,4 +1,4 @@ -// Copyright (C) 1998-2007 LightSys Technology Services, Inc. +// Copyright (C) 1998-2026 LightSys Technology Services, Inc. // // You may use these files and this library under the terms of the // GNU Lesser General Public License, Version 2.1, contained in the @@ -9,6 +9,15 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. +/*** This function works entirely using server-side dimensions. + *** Responsive dimensions are handled elsewhere. + *** + *** @param child The child to be resized. + *** @param oldw The previous width in server-side adaptive coordinates. + *** @param oldh The previous height in server-side adaptive coordinates. + *** @param neww The new width in server-side adaptive coordinates. + *** @param newh The new height in server-side adaptive coordinates. + ***/ function al_childresize(child, oldw, oldh, neww, newh) { if (oldw != neww || oldh != newh) @@ -37,29 +46,25 @@ function al_reflow_buildlist(node, children) } } +/*** Note: + *** This function gets all its values from server properties, so its generated + *** positions are in server layout. Thus, we convert them right before they + *** assigned to allow for responsive values. + ***/ function al_reflow() { // Get configuration var width = wgtrGetServerProperty(this,"width"); var height = wgtrGetServerProperty(this,"height"); - var spacing = wgtrGetServerProperty(this,"spacing"); - if (!spacing) spacing = 0; - var cellsize = wgtrGetServerProperty(this,"cellsize"); - if (!cellsize) cellsize = -1; - var align = wgtrGetServerProperty(this,"align"); - if (!align) align = "left"; - var justify_mode = wgtrGetServerProperty(this,"justify"); - if (!justify_mode) justify_mode = "none"; + var spacing = wgtrGetServerProperty(this,"spacing",0); + var align = wgtrGetServerProperty(this,"align","left"); var type = "vbox"; if (wgtrGetServerProperty(this,"style") == "hbox" || wgtrGetType(this) == "widget/hbox") type = "hbox"; - var column_width; - if (type == "vbox") - column_width = wgtrGetServerProperty(this,"column_width"); + var column_width, row_height; + if (type == "vbox") column_width = wgtrGetServerProperty(this,"column_width"); + else row_height = wgtrGetServerProperty(this,"row_height"); if (!column_width) column_width = width; - var row_height; - if (type == "hbox") - row_height = wgtrGetServerProperty(this,"row_height"); if (!row_height) row_height = height; // Build the child list @@ -87,10 +92,9 @@ function al_reflow() for(var i=0; i width) { if (xo > 0 && row_height > 0 && row_offset + row_height*2 + spacing <= height) @@ -108,6 +112,7 @@ function al_reflow() } else if (type == 'vbox') { + const cheight = wgtrGetServerProperty(child, "height"); if (yo + cheight > height) { if (yo > 0 && column_width > 0 && column_offset + column_width*2 + spacing <= width) @@ -148,10 +153,9 @@ function al_reflow() for(var i=0; i width) { if (xo > 0 && row_height > 0 && row_offset + row_height*2 + spacing <= height) @@ -164,17 +168,19 @@ function al_reflow() } if (child.tagName) { - setRelativeX(child, xo + xalign); - if (wgtrGetServerProperty(child,"r_y") == -1) - setRelativeY(child, row_offset); + setResponsiveX(child, xo + xalign); + const r_y = wgtrGetServerProperty(child, "r_y"); + if (r_y == -1) + setResponsiveY(child, row_offset); else - setRelativeY(child, row_offset + wgtrGetServerProperty(child,"r_y")); + setResponsiveY(child, row_offset + r_y); } xo += cwidth; xo += spacing; } else if (type == 'vbox') { + const cheight = wgtrGetServerProperty(child, "height"); if (yo + cheight > height) { if (yo > 0 && column_width > 0 && column_offset + column_width*2 + spacing <= width) @@ -187,11 +193,12 @@ function al_reflow() } if (child.tagName) { - setRelativeY(child, yo + yalign); - if (wgtrGetServerProperty(child,"r_x") == -1) - setRelativeX(child, column_offset); + setResponsiveY(child, yo + yalign); + const r_x = wgtrGetServerProperty(child, "r_x"); + if (r_x == -1) + setResponsiveX(child, column_offset); else - setRelativeX(child, column_offset + wgtrGetServerProperty(child,"r_x")); + setResponsiveX(child, column_offset + r_x); } yo += cheight; yo += spacing; diff --git a/centrallix-os/sys/js/htdrv_button.js b/centrallix-os/sys/js/htdrv_button.js index 6be26b436..c1504226b 100644 --- a/centrallix-os/sys/js/htdrv_button.js +++ b/centrallix-os/sys/js/htdrv_button.js @@ -1,4 +1,4 @@ -// Copyright (C) 1998-2001 LightSys Technology Services, Inc. +// Copyright (C) 1998-2026 LightSys Technology Services, Inc. // // You may use these files and this library under the terms of the // GNU Lesser General Public License, Version 2.1, contained in the @@ -262,7 +262,6 @@ function gb_setmode(layer,mode) pg_set(layer.img, 'src', newsrc); if(type=='image' || type=='textoverimage') return; } - moveTo(layer,layer.orig_x,layer.orig_y); if(cx__capabilities.Dom2CSS) { layer.style.setProperty('border-width','0px',null); @@ -294,7 +293,6 @@ function gb_setmode(layer,mode) pg_set(layer.img, 'src', newsrc); if(type=='image' || type=='textoverimage' ) return; } - moveTo(layer,layer.orig_x,layer.orig_y); if(cx__capabilities.Dom2CSS) { layer.style.setProperty('border-width','1px',null); @@ -337,7 +335,6 @@ function gb_setmode(layer,mode) pg_set(layer.img, 'src', newsrc); if(type=='image' || type=='textoverimage') return; } - moveTo(layer,layer.orig_x+1,layer.orig_y+1); if(cx__capabilities.Dom2CSS) { layer.style.setProperty('border-width','1px',null); diff --git a/centrallix-os/sys/js/htdrv_calendar.js b/centrallix-os/sys/js/htdrv_calendar.js index 65d6000f4..5c889fc3b 100644 --- a/centrallix-os/sys/js/htdrv_calendar.js +++ b/centrallix-os/sys/js/htdrv_calendar.js @@ -1,4 +1,4 @@ -// Copyright (C) 1998-2003 LightSys Technology Services, Inc. +// Copyright (C) 1998-2026 LightSys Technology Services, Inc. // // You may use these files and this library under the terms of the // GNU Lesser General Public License, Version 2.1, contained in the @@ -359,7 +359,20 @@ function ca_mousemove(e) } // Widget initialization. -function ca_init(l,main_bg,cell_bg,textcolor,dispmode,eventdatefield,eventdescfield,eventnamefield,eventpriofield,minprio,w,h) +function ca_init({ + l, + main_bg, + cell_bg, + textcolor, + dispmode, + eventdatefield, + eventdescfield, + eventnamefield, + eventpriofield, + minprio, + w, + h +}) { l.kind = 'ca'; if (cx__capabilities.Dom0NS) @@ -400,7 +413,6 @@ function ca_init(l,main_bg,cell_bg,textcolor,dispmode,eventdatefield,eventdescfi l.keyhandler = ca_keyhandler; l.getfocushandler = ca_getfocus; l.losefocushandler = ca_losefocus; - //pg_addarea(l, -1,-1,l.clip.width+1,l.clip.height+1, 'ebox', 'ebox', is_readonly?0:3); // Internal functions l.Redraw = ca_redraw; diff --git a/centrallix-os/sys/js/htdrv_chart.js b/centrallix-os/sys/js/htdrv_chart.js index 3b6d0abe3..7c52a0653 100755 --- a/centrallix-os/sys/js/htdrv_chart.js +++ b/centrallix-os/sys/js/htdrv_chart.js @@ -1,4 +1,4 @@ -// Copyright (C) 1998-2014 LightSys Technology Services, Inc. +// Copyright (C) 1998-2026 LightSys Technology Services, Inc. // // You may use these files and this library under the terms of the // GNU Lesser General Public License, Version 2.1, contained in the @@ -387,6 +387,12 @@ function cht_init(params) { this.update_soon = false; //see cht_object_available chart_wgt.ChartJsInit(); + + // Set ChartJS options. + const { options } = chart_wgt.chart; + options.responsive = false; + options.maintainAspectRatio = false; + options.resizeDelay = 10; } // Load indication diff --git a/centrallix-os/sys/js/htdrv_clock.js b/centrallix-os/sys/js/htdrv_clock.js index d7bec6027..e811af541 100644 --- a/centrallix-os/sys/js/htdrv_clock.js +++ b/centrallix-os/sys/js/htdrv_clock.js @@ -1,4 +1,4 @@ -// Copyright (C) 1998-2001 LightSys Technology Services, Inc. +// Copyright (C) 1998-2026 LightSys Technology Services, Inc. // // You may use these files and this library under the terms of the // GNU Lesser General Public License, Version 2.1, contained in the @@ -24,7 +24,6 @@ function cl_init(param){ l.contentLayer = c1; l.hiddenLayer = c2; l.shadowed = param.shadowed; - l.moveable = param.moveable; l.bold = param.bold; l.fgColor1 = param.foreground1; l.fgColor2 = param.foreground2; @@ -57,13 +56,14 @@ function cl_init(param){ } function cl_get_time(l) { - var t = new Date(); - var time = new Object(); - time.hrs = t.getHours(); - time.mins = t.getMinutes(); - time.secs = t.getSeconds(); - time.msecs = t.getMilliseconds(); - time.formated = cl_format_time(l,time); + const t = new Date(); + const time = { + hrs: t.getHours(), + mins: t.getMinutes(), + secs: t.getSeconds(), + msecs: t.getMilliseconds(), + }; + time.formated = cl_format_time(l, time); return time; } @@ -110,46 +110,16 @@ function cl_format_time(l,t) { return timef; } -function cl_mouseup(e) { - if (e.kind == 'cl') { - cn_activate(e.mainlayer, 'MouseUp'); - if (e.mainlayer.moveable) cl_move = false; - } - return EVENT_CONTINUE | EVENT_ALLOW_DEFAULT_ACTION; -} - -function cl_mousedown(e) { - if (e.kind == 'cl') { - cn_activate(e.mainlayer, 'MouseDown'); - if (e.mainlayer.moveable) { - cl_move = true; - cl_xOffset = e.pageX - getPageX(e.mainlayer); - cl_yOffset = e.pageY - getPageY(e.mainlayer); - } - } - return EVENT_CONTINUE | EVENT_ALLOW_DEFAULT_ACTION; -} - -function cl_mouseover(e) { - if (e.kind == 'cl') cn_activate(e.mainlayer, 'MouseOver'); - return EVENT_CONTINUE | EVENT_ALLOW_DEFAULT_ACTION; -} - -function cl_mouseout(e) { - if (e.kind == 'cl') cn_activate(e.mainlayer, 'MouseOut'); - return EVENT_CONTINUE | EVENT_ALLOW_DEFAULT_ACTION; -} - -function cl_mousemove(e) { - if (e.kind == 'cl') { - cn_activate(e.mainlayer, 'MouseMove'); - if (e.mainlayer.moveable && cl_move) - moveToAbsolute(e.mainlayer, - e.pageX - cl_xOffset, - e.pageY - cl_yOffset); - } +// Handle events by passing them on to Centrallix. +function cl_event(e, name) { + if (e && e.kind === 'cl') cn_activate(e.mainlayer, name); return EVENT_CONTINUE | EVENT_ALLOW_DEFAULT_ACTION; } +function cl_mouseup(e) { return cl_event(e, 'MouseUp'); } +function cl_mousedown(e) { return cl_event(e, 'MouseDown'); } +function cl_mouseover(e) { return cl_event(e, 'MouseOver'); } +function cl_mouseout(e) { return cl_event(e, 'MouseOut'); } +function cl_mousemove(e) { return cl_event(e, 'MouseMove'); } // Load indication if (window.pg_scripts) pg_scripts['htdrv_clock.js'] = true; diff --git a/centrallix-os/sys/js/htdrv_component.js b/centrallix-os/sys/js/htdrv_component.js index 9aad2837d..5ce903d0e 100644 --- a/centrallix-os/sys/js/htdrv_component.js +++ b/centrallix-os/sys/js/htdrv_component.js @@ -1,4 +1,4 @@ -// Copyright (C) 1998-2006 LightSys Technology Services, Inc. +// Copyright (C) 1998-2026 LightSys Technology Services, Inc. // // You may use these files and this library under the terms of the // GNU Lesser General Public License, Version 2.1, contained in the @@ -228,10 +228,6 @@ function cmp_instantiate(aparam) else var path = this.path; var url = path + "?cx__geom=" + escape(geom) + "&cx__graft=" + escape(graft) + "&cx__akey=" + escape(akey); - if (this.orig_x != 0 || this.orig_y != 0) - { - url += "&cx__xoffset=" + escape(this.orig_x) + "&cx__yoffset=" + escape(this.orig_y); - } if (this.templates.length > 0) { diff --git a/centrallix-os/sys/js/htdrv_datetime.js b/centrallix-os/sys/js/htdrv_datetime.js index c333f6446..68050a8d1 100644 --- a/centrallix-os/sys/js/htdrv_datetime.js +++ b/centrallix-os/sys/js/htdrv_datetime.js @@ -1,4 +1,4 @@ -// Copyright (C) 1998-2001 LightSys Technology Services, Inc. +// Copyright (C) 1998-2026 LightSys Technology Services, Inc. // // You may use these files and this library under the terms of the // GNU Lesser General Public License, Version 2.1, contained in the @@ -9,6 +9,18 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. + +// A resize observer to update datetime dropdowns when they are resized. +const dt_resize_observer = new ResizeObserver(e => e.forEach(({ target }) => { + // Ignore widgets that don't have a visible panelayer in need of updating. + if (htr_getvisibility(target.PaneLayer) !== 'inherit') return; + + // Reopen the datetime dropdown to rerender it. + dt_collapse(target); + dt_expand(target); +})); + + function dt_getvalue() { if(this.form && this.form.mode == 'Query' && this.sbr && this.DateObj) return new Array('>= ' + dt_formatdate(this, this.DateObj, 3),'<= ' + dt_formatdate(this, this.DateObj2, 3)); @@ -163,7 +175,6 @@ function dt_init(param){ htr_init_layer(c2,l,'dt'); //dt_tag_images(l.document, 'dt', l); htutil_tag_images(l,'dt',l,l); - l.w = w; l.h = h; l.bg = htr_extract_bgcolor(bg); l.ubg = bg; l.fg = param.foreground; @@ -200,7 +211,26 @@ function dt_init(param){ else l.form = wgtrFindContainer(l,"widget/form"); if (l.form) l.form.Register(l); - pg_addarea(l, -1, -1, getClipWidth(l)+3, getClipHeight(l)+3, 'dt', 'dt', 3); + + // Setup getters for widths and heights. + Object.defineProperties(l, { + w: { + get() { return getRelativeW(l); }, + configurable: true, + enumerable: true, + }, + h: { + get() { return getRelativeH(l); }, + configurable: true, + enumerable: true, + }, + }); + + // Setup the hover area and set getters to allow responsive resizing. + l.area = pg_addarea(l, -1, -1, () => l.w + 3, () => l.h + 3, 'dt', 'dt', 3); + + // Resize date selection dropdown automatically. + dt_resize_observer.observe(l); // Events ifc_init_widget(l); @@ -251,6 +281,7 @@ function dt_prepare(l) { l.PaneLayer.HidLayer.getfocushandler = dt_getfocus_day; l.PaneLayer.VisLayer.losefocushandler = dt_losefocus_day; l.PaneLayer.HidLayer.losefocushandler = dt_losefocus_day; + l.PaneLayer.classList.add('dt_dropdown'); } if (!l.PaneLayer2) { l.PaneLayer2 = dt_create_pane(l,l.ubg,l.w2,l.h2,l.h,"End"); @@ -261,7 +292,6 @@ function dt_prepare(l) { l.PaneLayer2.HidLayer.getfocushandler = dt_getfocus_day; l.PaneLayer2.VisLayer.losefocushandler = dt_losefocus_day; l.PaneLayer2.HidLayer.losefocushandler = dt_losefocus_day; - } // redraw the month & time. if(l.form && l.form.mode == 'Query' && l.sbr){ @@ -391,11 +421,12 @@ function dt_drawmonth(l, d) { moveTo(l.TimeVisLayer,0,rows*20+56); } } + enableClippingCSS(l); var v=''; for (var i=0; i<7*rows; i++) { if (i!=0 && i%7==0) v+=''; if (i%7==0) {v+='\n';r++} - v+='' + + ''; l.HidLayer.NullArea = pg_addarea(l.HidLayer, 1, 1+(r+1)*18, 100, 16, 'dt', 'dt_null', 3); l.HidLayer.NullArea.DateVal = null; l.HidLayer.NullArea.parentPaneId = l.myid; @@ -451,7 +484,12 @@ function dt_inittime(l) { // build the structure for the time v = "
'; + v += ''; if (i>=col && dy(No Date)(Today)
"; - v += "
  (24h)
\n"; + v += "" + + "" + + "" + + "" + + "" + + "
  (24h)
\n"; htr_write_content(l.TimeHidLayer, v); htr_setvisibility(l.TimeHidLayer, 'inherit'); htr_setvisibility(l.TimeVisLayer, 'hidden'); @@ -866,11 +904,11 @@ function dt_create_pane(ml,bg,w,h,h2,name) { h+=20; } str += " "; - str += " "; - str += " "; - str += " "; - str += " "; - str += " "; + str += " "; + str += " "; + str += " "; + str += " "; + str += " "; str += "
"; str += " "; str += " "; @@ -893,6 +931,7 @@ function dt_create_pane(ml,bg,w,h,h2,name) { pg_stackpopup(l,ml); setClipHeight(l,h); setClipWidth(l,w); + enableClippingCSS(l); //l.HidLayer = new Layer(1024, l); l.HidLayer = htr_new_layer(1024,l); diff --git a/centrallix-os/sys/js/htdrv_dropdown.js b/centrallix-os/sys/js/htdrv_dropdown.js index b3a23a8db..71ae88770 100644 --- a/centrallix-os/sys/js/htdrv_dropdown.js +++ b/centrallix-os/sys/js/htdrv_dropdown.js @@ -1,4 +1,4 @@ -// Copyright (C) 1998-2001 LightSys Technology Services, Inc. +// Copyright (C) 1998-2026 LightSys Technology Services, Inc. // // You may use these files and this library under the terms of the // GNU Lesser General Public License, Version 2.1, contained in the @@ -9,6 +9,18 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. + +// A resize observer to update dropdowns when they are resized. +const dd_resize_observer = new ResizeObserver(e => e.forEach(({ target }) => { + // Ignore widgets that don't have a visible panelayer in need of updating. + if (htr_getvisibility(target.PaneLayer) !== 'inherit') return; + + // Reopen the dropdown to rerender it. + dd_collapse(target); + dd_expand(target); +})); + + // Form manipulation function dd_getvalue() @@ -390,6 +402,9 @@ function dd_collapse(l) //pg_resize_area(l.area,getClipWidth(l)+1,getClipHeight(l)+1, -1, -1); htr_setvisibility(l.PaneLayer, 'hidden'); dd_current = null; + + /** Remove the pane to avoid leaking DOM nodes. **/ + dd_remove_pane(l); } } @@ -407,7 +422,10 @@ function dd_expand(l) l.Values.splice(0,0,nullitem); } if (l && !l.PaneLayer) + { l.PaneLayer = dd_create_pane(l); + l.PaneLayer.style.cursor = 'pointer'; + } if (l && htr_getvisibility(l.PaneLayer) != 'inherit') { pg_stackpopup(l.PaneLayer, l); @@ -702,9 +720,9 @@ function dd_create_pane(l) moveTo(p.BarLayer, l.popup_width-20, 2); htr_setvisibility(p.BarLayer, 'inherit'); c = '
S
'; - c += ''; - c += ''; - c += ''; + c += ''; + c += ''; + c += ''; c += '
up
trans
down
'; htr_write_content(p.BarLayer, c); //pg_serialized_write(p.BarLayer, c, null); @@ -775,6 +793,41 @@ function dd_create_pane(l) return p; } +/** Quick and dirty function to remove panes without leaking DOM nodes. **/ +function dd_remove_pane(l) + { + if (!l || !l.PaneLayer) return; + + const p = l.PaneLayer; + + // Function for removing elements. + const remove_node = (n) => { if (n && n.parentNode) n.parentNode.removeChild(n); } + + // Remove item DOM nodes if present. + if (l.Items && l.Items.length) + { + for (let i = 0; i < l.Items.length; i++) + { + remove_node(l.Items[i]); + l.Items[i] = null; + } + l.Items.length = 0; + l.Items = null; + } + + // Remove scrollbar/thumb layers if present. + remove_node(p.ScrLayer); + remove_node(p.BarLayer); + remove_node(p.TmbLayer); + + // Remove the pane root. + remove_node(p); + + // Clear references. + p.ScrLayer = p.BarLayer = p.TmbLayer = p.Items = p.mainlayer = null; + l.PaneLayer = null; + } + /// REPLACE ITEMS IN DROPDOWN function dd_add_items(l,ary) @@ -839,14 +892,14 @@ function dd_add_items(l,ary) function dd_mouseout(e) { var ti=dd_target_img; - if (ti && ti.name == 't' && dd_current) + if (ti && ti.dataset.type === 'trans' && dd_current) return EVENT_HALT | EVENT_PREVENT_DEFAULT_ACTION; } function dd_mousemove(e) { var ti=dd_target_img; - if (ti != null && ti.name == 't' && dd_current && dd_current.enabled!='disabled') + if (ti != null && ti.dataset.type === 'trans' && dd_current && dd_current.enabled !== 'disabled') { var pl=ti.mainlayer.PaneLayer; var v=getClipHeight(pl)-(3*18)-4; @@ -899,8 +952,11 @@ function dd_mouseup(e) } if (dd_target_img != null) { - if (dd_target_img.kind && dd_target_img.kind.substr(0,2) == 'dd' && (dd_target_img.name == 'u' || dd_target_img.name == 'd')) - pg_set(dd_target_img,'src',htutil_subst_last(dd_target_img.src,"b.gif")); + if (dd_target_img.kind && dd_target_img.kind.slice(0, 2) === 'dd' + && (dd_target_img.dataset.type === 'up' || dd_target_img.dataset.type === 'down')) + { + pg_set(dd_target_img, 'src', htutil_subst_last(dd_target_img.src, "b.gif")); + } dd_target_img = null; } if ((e.kind == 'dd' || e.kind == 'ddtxt') && e.mainlayer.enabled != 'disabled') @@ -954,31 +1010,26 @@ function dd_mousedown(e) } else if (e.kind == 'dd_sc') { - switch(e.layer.name) + switch(e.layer.dataset.type) { - case 'u': + case 'up': pg_set(e.layer,'src','/sys/images/ico13c.gif'); dd_incr = 8; dd_scroll(); dd_timeout = setTimeout(dd_scroll_tm,300); break; - case 'd': + case 'down': pg_set(e.layer, 'src', '/sys/images/ico12c.gif'); dd_incr = -8; dd_scroll(); dd_timeout = setTimeout(dd_scroll_tm,300); break; - case 'b': + case 'thumb': dd_incr = dd_target_img.height+36; if (e.pageY > getPageY(dd_target_img.thum)+9) dd_incr = -dd_incr; dd_scroll(); dd_timeout = setTimeout(dd_scroll_tm,300); break; - case 't': - dd_click_x = e.pageX; - dd_click_y = e.pageY; - dd_thum_y = getPageY(dd_target_img.thum); - break; } } else if ((e.kind == 'dd' || e.kind == 'ddtxt') && e.mainlayer.enabled != 'disabled') @@ -1194,7 +1245,6 @@ function dd_init(param) l.NumDisplay = param.numDisplay; l.Mode = param.mode; l.SQL = param.sql; - l.popup_width = param.popup_width?param.popup_width:param.width; l.VisLayer = param.c1; l.HidLayer = param.c2; htr_init_layer(l.VisLayer, l, 'ddtxt'); @@ -1244,7 +1294,6 @@ function dd_init(param) l.getfocushandler = dd_getfocus; l.bg = param.background; l.hl = param.highlight; - l.w = param.width; l.h = param.height; l.fieldname = param.fieldname; l.enabled = 'full'; if (l.Mode != 3) @@ -1268,11 +1317,35 @@ function dd_init(param) else if (imgs[i].src.substr(-13,5) == 'white') imgs[i].upimg = true; } - l.area = pg_addarea(l, -1, -1, getClipWidth(l)+3, - getClipHeight(l)+3, 'dd', 'dd', 3); if (l.form) l.form.Register(l); l.init_items = false; + // Setup getters for widths and heights. + const width_ratio = param.popup_width / param.width; + Object.defineProperties(l, { + w: { + get() { return getRelativeW(l); }, + configurable: true, + enumerable: true, + }, + h: { + get() { return getRelativeH(l); }, + configurable: true, + enumerable: true, + }, + popup_width: { + get() { return l.w * width_ratio; }, + configurable: true, + enumerable: true, + }, + }); + + // Setup the hover area. + l.area = pg_addarea(l, -1, -1, () => l.w + 3, () => l.h + 3, 'dd', 'dd', 3); + + // Resize dropdown automatically. + dd_resize_observer.observe(l); + // Events var ie = l.ifcProbeAdd(ifEvent); ie.Add("MouseDown"); diff --git a/centrallix-os/sys/js/htdrv_editbox.js b/centrallix-os/sys/js/htdrv_editbox.js index 160ce0bdf..b75e38855 100644 --- a/centrallix-os/sys/js/htdrv_editbox.js +++ b/centrallix-os/sys/js/htdrv_editbox.js @@ -1,4 +1,4 @@ -// Copyright (C) 1998-2004 LightSys Technology Services, Inc. +// Copyright (C) 1998-2026 LightSys Technology Services, Inc. // // You may use these files and this library under the terms of the // GNU Lesser General Public License, Version 2.1, contained in the @@ -162,8 +162,10 @@ function eb_setdesc(txt) ({ "z-index":"-1", "color":this.desc_fgcolor?this.desc_fgcolor:"#808080", - "top":($(this).height() - $(this.DescLayer).height())/2 + "px", + "top":"0px", "left":(this.input_width() + ((this.content || this.has_focus)?4:0) + 5) + "px", + "height":"100%", + "align-content":"center", "visibility":"inherit", "white-space":"nowrap", }); @@ -647,8 +649,9 @@ function eb_init(param) var imgs = pg_images(l); for(var i=0;i $(l).width() + area_adj, () => $(l).height() + area_adj, 'ebox', 'ebox', (param.isReadOnly) ? 0 : 3); + if (param.form) l.form = wgtrGetNode(l, param.form); else diff --git a/centrallix-os/sys/js/htdrv_html.js b/centrallix-os/sys/js/htdrv_html.js index b40264bec..91cf53a65 100644 --- a/centrallix-os/sys/js/htdrv_html.js +++ b/centrallix-os/sys/js/htdrv_html.js @@ -1,4 +1,4 @@ -// Copyright (C) 1998-2001 LightSys Technology Services, Inc. +// Copyright (C) 1998-2026 LightSys Technology Services, Inc. // // You may use these files and this library under the terms of the // GNU Lesser General Public License, Version 2.1, contained in the @@ -240,7 +240,6 @@ function ht_init(param) { setClipHeight(l, getdocHeight(l)); } - pg_set_style(l, 'height', getdocHeight(l)); if (param.width != -1) { setClipWidth(l, param.width); @@ -250,7 +249,11 @@ function ht_init(param) { setClipWidth(l, getdocWidth(l)); } - pg_set_style(l, 'width', getdocWidth(l)); + + /** Clipping breaks responsive pages and is not required in modern browsers. **/ + disableClippingCSS(l); + disableClippingCSS(l2); + if (source.substr(0,5) == 'http:') { //pg_serialized_load(l, source, ht_reloaded); @@ -289,6 +292,8 @@ function ht_init(param) //l.watch('source', ht_sourcechanged); pg_resize(l.parentLayer); + disableClippingCSS(l.parentLayer); + return l; } diff --git a/centrallix-os/sys/js/htdrv_image.js b/centrallix-os/sys/js/htdrv_image.js index a4a608022..55e53c666 100644 --- a/centrallix-os/sys/js/htdrv_image.js +++ b/centrallix-os/sys/js/htdrv_image.js @@ -1,6 +1,6 @@ // htdrv_image.js // -// Copyright (C) 2004 LightSys Technology Services, Inc. +// Copyright (C) 2004-2026 LightSys Technology Services, Inc. // // You may use these files and this library under the terms of the // GNU Lesser General Public License, Version 2.1, contained in the @@ -146,13 +146,27 @@ function im_set_scale(a, v) function im_action_offset(aparam) { - im_set_x.call(this, "xoffset", aparam.X); - im_set_y.call(this, "yoffset", aparam.Y); + const { X, Y } = aparam; + + // Log warning. + console.warn('Offset action used: This breaks page responsiveness.'); + console.log(`Action Info: X=${X} Y=${Y} Target=`, this); + + // Update the offset values. + im_set_x.call(this, "xoffset", X); + im_set_y.call(this, "yoffset", Y); } function im_action_scale(aparam) { - im_set_scale.call(this, "scale", aparam.Scale); + const { Scale } = aparam; + + // Log warning. + console.warn('Scale action used: This breaks page responsiveness.'); + console.log(`Action Info: Scale=${Scale} Target=`, this); + + // Update the scale value. + im_set_scale.call(this, "scale", Scale); } function im_init(l) diff --git a/centrallix-os/sys/js/htdrv_imagebutton.js b/centrallix-os/sys/js/htdrv_imagebutton.js index c3aae6b2a..a78a4f7bd 100644 --- a/centrallix-os/sys/js/htdrv_imagebutton.js +++ b/centrallix-os/sys/js/htdrv_imagebutton.js @@ -1,4 +1,4 @@ -// Copyright (C) 1998-2001 LightSys Technology Services, Inc. +// Copyright (C) 1998-2026 LightSys Technology Services, Inc. // // You may use these files and this library under the terms of the // GNU Lesser General Public License, Version 2.1, contained in the @@ -211,6 +211,7 @@ function ib_init(param) l.img.kind = 'ib'; l.cursrc = param.n; setClipWidth(l, w); + disableClippingCSS(l); l.trigger = ib_trigger; diff --git a/centrallix-os/sys/js/htdrv_label.js b/centrallix-os/sys/js/htdrv_label.js index a0226f1cb..6bd89dd92 100644 --- a/centrallix-os/sys/js/htdrv_label.js +++ b/centrallix-os/sys/js/htdrv_label.js @@ -1,4 +1,4 @@ -// Copyright (C) 1998-2001 LightSys Technology Services, Inc. +// Copyright (C) 1998-2026 LightSys Technology Services, Inc. // // You may use these files and this library under the terms of the // GNU Lesser General Public License, Version 2.1, contained in the @@ -103,7 +103,6 @@ function lb_disable() function lb_update() { var v = htutil_nlbr(htutil_encode(htutil_obscure(this.content), true)); - //var txt = this.stylestr + (v?v:"") + ""; var txt = v?v:""; $(this).find("span").html(txt).attr("style", htutil_getstyle(this, null, {})); /*if (cx__capabilities.Dom0NS) // only serialize this for NS4 @@ -176,7 +175,6 @@ function lbl_init(l, wparam) l.content = wparam.text; l.orig_content = wparam.text; l.fieldname = wparam.field; - l.stylestr = wparam.style; l.tooltip = wparam.tooltip; l.tipid = null; l.pointcolor = wparam.pfg; diff --git a/centrallix-os/sys/js/htdrv_menu.js b/centrallix-os/sys/js/htdrv_menu.js index f517e3948..6759b556b 100644 --- a/centrallix-os/sys/js/htdrv_menu.js +++ b/centrallix-os/sys/js/htdrv_menu.js @@ -1,4 +1,4 @@ -// Copyright (C) 1998-2001 LightSys Technology Services, Inc. +// Copyright (C) 1998-2026 LightSys Technology Services, Inc. // // You may use these files and this library under the terms of the // GNU Lesser General Public License, Version 2.1, contained in the @@ -395,19 +395,21 @@ function mn_init(param) menu.objname = param.name; menu.cur_highlight = null; + // Set up sizing. if (cx__capabilities.CSS2) { if (menu.scrollHeight == 0) { - pg_set_style(menu,'height',menu.childNodes[0].scrollHeight); - pg_set_style(menu,'width',menu.childNodes[0].scrollWidth); + if (param.h === -1) pg_set_style(menu, 'height', menu.childNodes[0].scrollHeight); + if (param.w === -1) pg_set_style(menu, 'width', menu.childNodes[0].scrollWidth); } else { - pg_set_style(menu,'height',menu.scrollHeight); - pg_set_style(menu,'width',menu.scrollWidth); + if (param.h === -1) pg_set_style(menu, 'height', menu.scrollHeight); + if (param.w === -1) pg_set_style(menu, 'width', menu.scrollWidth); } } + disableClippingCSS(menu); menu.act_w = getClipWidth(menu.clayer); menu.act_h = getClipHeight(menu.clayer); if ($(menu).css('visibility') == 'hidden' && (!menu.__WgtrParent.style || $(menu.__WgtrParent).css('visibility') == 'inherit')) @@ -423,25 +425,19 @@ function mn_init(param) var search; for(var i=0; i Object.values(pg_area_resize_handlers).forEach(f => f(e))); + + //START SECTION: DOM/CSS helper functions ----------------------------------- /** returns an attribute of the element in pixels **/ @@ -728,88 +733,61 @@ function pg_isinlayer(outer,inner) return false; } -/** Function to make four layers into a box //SETH: ?? what's a 'box'? -* pl - a layer -* x,y - x,y-cord -* w,h - widht, height -* s - ? -* tl - top layer -* bl - bottom layer -* rl - right layer -* ll - left layer -* c1 - color1, for tl and ll -* c2 - color2, for bl and rl -* z - zIndex -**/ -function pg_mkbox(pl, x,y,w,h, s, tl,bl,rl,ll, c1,c2, z) - { +/*** Helper function for pg_init_box() to initialize a side of a box. + *** + *** @param layer The target layer (aka. dom node) for the side of the box. + *** @param parent_layer The target layer for which the box has been created. + *** @param x The x for this side of the box. + *** @param y The y for this side of the box. + *** @param w The width for this side of the box. + *** @param h The height for this side of the box. + *** @param color The color of this side of the box. + *** @param z The z-index for this side of the box. + ***/ +function pg_init_box_side(layer, parent_layer, x, y, w, h, color, z) + { + resizeTo(layer, w, h); + setClipWidth(layer, w); + setClipHeight(layer, h); + moveAbove(layer, parent_layer); + $(layer).offset({ left:x, top:y }); + htr_setbgcolor(layer, color); + htr_setzindex(layer, z); + htr_setvisibility(layer, 'inherit'); - htr_setvisibility(tl, 'hidden'); - htr_setvisibility(bl, 'hidden'); - htr_setvisibility(rl, 'hidden'); - htr_setvisibility(ll, 'hidden'); - //abc(); - if (cx__capabilities.Dom0NS || cx__capabilities.Dom1HTML) -/* { - tl.bgColor = c1; - ll.bgColor = c1; - bl.bgColor = c2; - rl.bgColor = c2; - } - else if (cx__capabilities.Dom1HTML) */ - { - htr_setbgcolor(tl,c1); - htr_setbgcolor(ll,c1); - htr_setbgcolor(bl,c2); - htr_setbgcolor(rl,c2); - } - //alert("x, y --" + x + " " + y); - - resizeTo(tl,w,1); - setClipWidth(tl,w); - setClipHeight(tl,1); - //if (cx__capabilities.Dom1HTML && pl) - // pl.parentLayer.appendChild(tl); - moveAbove(tl,pl); - //moveToAbsolute(tl,x,y); - $(tl).offset({left:x, top:y}); - htr_setzindex(tl,z); - - resizeTo(bl,w+s-1,1); - setClipWidth(bl,w+s-1); - setClipHeight(bl,1); - //if (cx__capabilities.Dom1HTML && pl) - // pl.parentLayer.appendChild(bl); - moveAbove(bl,pl); - //moveToAbsolute(bl,x,y+h-s+1); - $(bl).offset({left:x, top:y+h-s+1}); - htr_setzindex(bl,z); - - resizeTo(ll,1,h); - setClipHeight(ll,h); - setClipWidth(ll,1); - //if (cx__capabilities.Dom1HTML && pl) - // pl.parentLayer.appendChild(ll); - moveAbove(ll,pl); - //moveToAbsolute(ll,x,y); - $(ll).offset({left:x, top:y}); - htr_setzindex(ll,z); - - resizeTo(rl,1,h+1); - setClipHeight(rl,h+1); - setClipWidth(rl,1); - //if (cx__capabilities.Dom1HTML && pl) - // pl.parentLayer.appendChild(rl); - moveAbove(rl,pl); - //moveToAbsolute(rl,x+w-s+1,y); - $(rl).offset({left:x+w-s+1, top:y}); - htr_setzindex(rl,z); + return; + } + +/*** Initialize the four layers (dom nodes) of a box UI element. These are + *** commonly used when a user hovers over or selects certain UI elements, + *** such as dropdown widgets. + *** + *** @param parent_layer The target layer for which the box has been created. + *** @param x The x coordinate of the top left corner of the box (in px). + *** @param y The y coordinate of the top left corner of the box (in px). + *** @param w The width of the box (in px). + *** @param h The height of the box (in px). + *** @param s The thickness of the box (in px)? Maybe? + *** @param top_layer The layer to be the line across the top of the box. + *** @param bottom_layer The layer to be the line across the bottom of the box. + *** @param right_layer The layer to be the line across the right side of the box. + *** @param left_layer The layer to be the line across the left side of the box. + *** @param color1 The "lit" color of the box (used for the top and left lines). + *** @param color2 The "unlit" color of the box (used for the bottom and right lines). + *** @param z The z-index for the sides of the box. + ***/ +function pg_init_box({ + parent_layer, + x, y, w, h, s, + top_layer, bottom_layer, right_layer, left_layer, + color1, color2, z +}) + { + pg_init_box_side(top_layer, parent_layer, x, y, w, 1, color1, z); + pg_init_box_side(bottom_layer, parent_layer, x, y+h-s+1, w+s-1, 1, color2, z); + pg_init_box_side(right_layer, parent_layer, x+w-s+1, y, 1, h+1, color2, z); + pg_init_box_side(left_layer, parent_layer, x, y, 1, h, color1, z); - htr_setvisibility(tl, 'inherit'); - htr_setvisibility(bl, 'inherit'); - htr_setvisibility(rl, 'inherit'); - htr_setvisibility(ll, 'inherit'); - //alert(rl.style.cssText); return; } @@ -839,17 +817,62 @@ function pg_hidebox(tl,bl,rl,ll) return; } -/** Function to make a new clickable "area" **INTERNAL** **/ -function pg_area(pl,x,y,w,h,cls,nm,f) //SETH: ?? what's an 'area'? - { - this.layer = pl; - this.x = x; - this.y = y; - this.width = w; - this.height = h; - this.name = nm; +/*** Internal constructor function to create a new clickable/hoverable "area". + *** This function has additional documentation in `HTFormInterface.md`. + *** + *** Note: Many functions specify (x,y) as (0,0), or as (-1,-1) if the focus + *** area should appear 1px outside the parent layer. + *** + *** Note: The x, y, w, and h params can all be specified as either a single + *** value or a function. The latter is useful because areas are redawn + *** when the page resizes, so a function can provide an updated value + *** for the new layout. + *** + *** @param parent The associated parent layer for which this area is rendered. + *** This object should implement the `keyhandler()`, `getfocushandler()`, + *** and `losefocushandler()`, as described in `HTFormInterface.md`. + *** @param x The x coordinate of the area, relative to the parent layer. + *** @param y The y coordinate of the area, relative to the parent layer. + *** @param width The width of the area. + *** @param height The height of the area. + *** @param cls A mostly unused value for the "context" of an area, used for + *** the callback functions described in `HTFormatInterface.md`. (As for how + *** "context" can be abbreviated to `cls`, your guess is as good as mine.) + *** @param name The name of the area, also used for callback functions. + *** @param f A bitmask representing zero or more of the following flags: + *** 1: Allow the area to receive keyboard focus. + *** 2: Allow the area to receive data focus. + ***/ +function pg_area(parent, x, y, width, height, cls, name, f) + { + // Function to handle params that might be functions. + const handle_param = (name, value) => { + if (typeof(value) === 'function') + { + // Set a base value and define a getter that calls the provided function. + this[name] = value(); + Object.defineProperty(this, name, { + get() { return value.call(this); }, + configurable: true, + enumerable: true + }); + } + + // If just a value is provided, simply set that. + else this[name] = value; + } + + // Handle each parameter for the class. + this.layer = parent; + handle_param('x', x); + handle_param('y', y); + handle_param('width', width); + handle_param('height', height); + this.name = name; this.cls = cls; this.flags = f; + + // Return the newly instantiated object. return this; } @@ -924,9 +947,12 @@ function pg_resize_area(a,w,h,xo,yo) } } -/** Function to add a new area to the arealist **/ +/*** Function to add a new area to the area list. + *** Note that x, y, w, & h can all be provided as + *** function for responsive resizing. + ***/ function pg_addarea(pl,x,y,w,h,cls,nm,f) - { + { var a = new pg_area(pl,x,y,w,h,cls,nm,f); //pg_arealist.splice(0,0,a); pg_arealist.push(a); @@ -1354,6 +1380,8 @@ function pg_init(l,a,gs,ct) //SETH: ?? ia.Add("Launch", pg_launch); ia.Add("Close", pg_close); ia.Add("Alert", pg_alert); + ia.Add("Log", pg_log); + ia.Add("ReloadPage", pg_reload_page); // Events var ie = window.ifcProbeAdd(ifEvent); @@ -1435,6 +1463,16 @@ function pg_alert(aparam) alert(aparam.Message); } +function pg_log({ Message }) + { + console.log(Message); + } + +function pg_reload_page() + { + window.location.reload(); + } + function pg_reveal_cb(e) { if (e.eventName == 'ObscureOK') @@ -1917,7 +1955,9 @@ function pg_findfocusarea(l, xo, yo) function pg_setmousefocus(l, xo, yo) { - var a = pg_findfocusarea(l, xo, yo); + if (!l) return false; + + const a = pg_findfocusarea(l, xo, yo); if (a && a != pg_curarea) { pg_curarea = a; @@ -1925,26 +1965,55 @@ function pg_setmousefocus(l, xo, yo) { if (!pg_curarea.layer.getmousefocushandler || pg_curarea.layer.getmousefocushandler(xo, yo, a.layer, a.cls, a.name, a)) { - // wants mouse focus - var offs = $(pg_curarea.layer).offset(); - //var x = getPageX(pg_curarea.layer)+pg_curarea.x; - //var y = getPageY(pg_curarea.layer)+pg_curarea.y; - var x = offs.left+pg_curarea.x; - var y = offs.top+pg_curarea.y; - - var w = pg_curarea.width; - var h = pg_curarea.height; - if (cx__capabilities.Dom0NS) - { - pg_mkbox(l, x,y,w,h, 1, document.layers.pgtop,document.layers.pgbtm,document.layers.pgrgt,document.layers.pglft, page.mscolor1, page.mscolor2, document.layers.pgktop.zIndex-1); - } - else if (cx__capabilities.Dom1HTML) + // Create a function to handle all box updates with this focus. + const update_box = (area) => { - pg_mkbox(l, x,y,w,h, 1, document.getElementById("pgtop"),document.getElementById("pgbtm"),document.getElementById("pgrgt"),document.getElementById("pglft"), page.mscolor1, page.mscolor2, htr_getzindex(document.getElementById("pgktop"))-1); - } + // Compute layout data. + const offs = $(area.layer).offset(); + const x = area.x + offs.left; + const y = area.y + offs.top; + const w = area.width; + const h = area.height; + + if (cx__capabilities.Dom0NS) + { + pg_init_box({ + parent_layer: l, + x, y, w, h, s: 1, + top_layer: document.layers.pgtop, + bottom_layer: document.layers.pgbtm, + right_layer: document.layers.pgrgt, + left_layer: document.layers.pglft, + color1: page.mscolor1, + color2: page.mscolor2, + z: document.layers.pgktop.zIndex - 1, + }); + } + else if (cx__capabilities.Dom1HTML) + { + pg_init_box({ + parent_layer: l, + x, y, w, h, s: 1, + top_layer: document.getElementById("pgtop"), + bottom_layer: document.getElementById("pgbtm"), + right_layer: document.getElementById("pgrgt"), + left_layer: document.getElementById("pglft"), + color1: page.mscolor1, color2: page.mscolor2, + z: htr_getzindex(document.getElementById("pgktop")) - 1, + }); + } + }; + + // Initial update. + update_box(pg_curarea); + + // Responsive updates. + const area = pg_curarea; // Save value so we can create a closure below. + pg_area_resize_handlers.mouse_focus = () => update_box(area); } } } + if (!a) delete pg_area_resize_handlers.mouse_focus; } function pg_removekbdfocus(p) @@ -1956,23 +2025,55 @@ function pg_removekbdfocus(p) pg_curkbdarea = null; if (cx__capabilities.Dom0NS) { - pg_mkbox(null,0,0,0,0, 1, document.layers.pgktop,document.layers.pgkbtm,document.layers.pgkrgt,document.layers.pgklft, page.kbcolor1, page.kbcolor2, document.layers.pgtop.zIndex+100); + pg_init_box({ + parent_layer: null, + x: 0, y: 0, w: 0, h: 0, s: 1, + top_layer: document.layers.pgktop, + bottom_layer: document.layers.pgkbtm, + right_layer: document.layers.pgkrgt, + left_layer: document.layers.pgklft, + color1: page.kbcolor1, color2: page.kbcolor2, + z: document.layers.pgtop.zIndex + 100, + }); } else if (cx__capabilities.Dom1HTML) { - pg_mkbox(null,0,0,0,0, 1, document.getElementById("pgktop"),document.getElementById("pgkbtm"),document.getElementById("pgkrgt"),document.getElementById("pgklft"), page.kbcolor1, page.kbcolor2, pg_get_style(document.getElementById("pgtop"), 'zIndex')+100); + pg_init_box({ + parent_layer: null, + x: 0, y: 0, w: 0, h: 0, s: 1, + top_layer: document.getElementById("pgktop"), + bottom_layer: document.getElementById("pgkbtm"), + right_layer: document.getElementById("pgkrgt"), + left_layer: document.getElementById("pgklft"), + color1: page.kbcolor1, color2: page.kbcolor2, + z: pg_get_style(document.getElementById("pgtop"), 'zIndex') + 100, + }); } } + + // Clear resize handling. + delete pg_area_resize_handlers.mouse_focus; + delete pg_area_resize_handlers.data_focus; + delete pg_area_resize_handlers.kbd_focus; + return true; } function pg_setdatafocus(a) { + if (!a) return false; + var x = getPageX(a.layer)+a.x; var y = getPageY(a.layer)+a.y; var w = a.width; var h = a.height; var l = a.layer; + + // Setup resize handling. + pg_area_resize_handlers.data_focus = () => { + // Recall function to update values. + pg_setdatafocus(a); + }; // hide old data focus box if (l.pg_dttop != null) @@ -2015,16 +2116,36 @@ function pg_setdatafocus(a) // draw new data focus box if (cx__capabilities.Dom0NS) { - pg_mkbox(l,x-1,y-1,w+2,h+2, 1, l.pg_dttop,l.pg_dtbtm,l.pg_dtrgt,l.pg_dtlft, page.dtcolor1, page.dtcolor2, document.layers.pgtop.zIndex+100); + pg_init_box({ + parent_layer: l, + x: x - 1, y: y - 1, w: w + 2, h: h + 2, s: 1, + top_layer: l.pg_dttop, + bottom_layer: l.pg_dtbtm, + right_layer: l.pg_dtrgt, + left_layer: l.pg_dtlft, + color1: page.dtcolor1, color2: page.dtcolor2, + z: document.layers.pgtop.zIndex + 100, + }); } else if (cx__capabilities.Dom1HTML) { - pg_mkbox(l,x-1,y-1,w+2,h+2, 1, l.pg_dttop,l.pg_dtbtm,l.pg_dtrgt,l.pg_dtlft, page.dtcolor1, page.dtcolor2, pg_get_style(document.getElementById("pgtop"),'zIndex')+100); + pg_init_box({ + parent_layer: l, + x: x - 1, y: y - 1, w: w + 2, h: h + 2, s: 1, + top_layer: l.pg_dttop, + bottom_layer: l.pg_dtbtm, + right_layer: l.pg_dtrgt, + left_layer: l.pg_dtlft, + color1: page.dtcolor1, color2: page.dtcolor2, + z: pg_get_style(document.getElementById("pgtop"),'zIndex') + 100, + }); } } function pg_setkbdfocus(l, a, xo, yo) { + if (!l) return false; + var from_kbd = false; if (xo == null && yo == null) { @@ -2056,23 +2177,27 @@ function pg_setkbdfocus(l, a, xo, yo) pg_curkbdarea = a; pg_curkbdlayer = l; + // Setup resize handling. + pg_area_resize_handlers.kbd_focus = () => { + // Recall function to update values. + pg_setkbdfocus(l, a, xo, yo); + }; + if (pg_curkbdlayer && pg_curkbdlayer.getfocushandler) { v=pg_curkbdlayer.getfocushandler(xo,yo,a.layer,a.cls,a.name,a,from_kbd); if (v & 1) { - // mk box for kbd focus - //if (prevArea != a) - // { - // if (cx__capabilities.Dom0NS) - // { - // pg_mkbox(l ,x,y,w,h, 1, document.layers.pgktop,document.layers.pgkbtm,document.layers.pgkrgt,document.layers.pgklft, page.kbcolor1, page.kbcolor2, document.layers.pgtop.zIndex+100); - // } - // else if (cx__capabilities.Dom1HTML) - // { - pg_mkbox(l ,x,y,w,h, 1, document.getElementById("pgktop"),document.getElementById("pgkbtm"),document.getElementById("pgkrgt"),document.getElementById("pgklft"), page.kbcolor1, page.kbcolor2, htr_getzindex(document.getElementById("pgtop"))+100); - // } - // } + pg_init_box({ + parent_layer: l, + x, y, w, h, s: 1, + top_layer: document.getElementById("pgktop"), + bottom_layer: document.getElementById("pgkbtm"), + right_layer: document.getElementById("pgkrgt"), + left_layer: document.getElementById("pgklft"), + color1: page.kbcolor1, color2: page.kbcolor2, + z: htr_getzindex(document.getElementById("pgtop")) + 100, + }); } if (v & 2) { @@ -3028,9 +3153,12 @@ function pg_check_resize(l) { if (wgtrGetServerProperty(l, "height") != $(l).height()) { - if (wgtrGetParent(l).childresize) + const parent = wgtrGetParent(l); + if (parent.childresize) { - var geom = wgtrGetParent(l).childresize(l, wgtrGetServerProperty(l, "width"), wgtrGetServerProperty(l, "height"), $(l).width(), $(l).height()); + const width = wgtrGetServerProperty(l, "width"); + const height = wgtrGetServerProperty(l, "height"); + const geom = parent.childresize(l, width, height, $(l).width(), $(l).height()); if (geom) { wgtrSetServerProperty(l, "height", geom.height); diff --git a/centrallix-os/sys/js/htdrv_page_moz.js b/centrallix-os/sys/js/htdrv_page_moz.js deleted file mode 100644 index bf84f15e8..000000000 --- a/centrallix-os/sys/js/htdrv_page_moz.js +++ /dev/null @@ -1,268 +0,0 @@ -// Copyright (C) 1998-2001 LightSys Technology Services, Inc. -// -// You may use these files and this library under the terms of the -// GNU Lesser General Public License, Version 2.1, contained in the -// included file "COPYING" or http://www.gnu.org/licenses/lgpl.txt. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. - - -function pg_ping_init(param) - { - var l = param.layer; - var i = param.i; - l.tid=setInterval(pg_ping_send,i,l); - } - - -function pg_ping_recieve() - { - this.removeEventListener('load',pg_ping_recieve,false); - if(this.contentDocument.getElementsByTagName('a')[0].target!=='OK') - { - clearInterval(this.tid); - confirm('you have been disconnected from the server'); - } - } - - -function pg_ping_send(p) - { - if(p.addEventListener) - p.addEventListener('load',pg_ping_recieve,false); - //else - // p.onload=pg_ping_recieve; - p.src='/INTERNAL/ping'; - } - -/** Function to set modal mode to a layer. **/ -function pg_setmodal(l) - { - pg_modallayer = l; - } - -/** Function to find out whether image or layer is in a layer **/ -function pg_isinlayer(outer,inner) - { - if (inner == outer) return true; - if(!outer) return true; - if(!inner) return false; - var i = 0; - var olist = outer.getElementsByTagName('iframe'); - for(i=0;i maxheight) - maxheight = cl.y + cl.clip.height; - if ((cl.visibility == 'show' || cl.visibility == 'inherit') && cl.x + cl.clip.width > maxwidth) - maxwidth = cl.x + cl.clip.width; - } - if (l.maxheight && maxheight > l.maxheight) maxheight = l.maxheight; - if (l.minheight && maxheight < l.minheight) maxheight = l.minheight; - if (l!=window) l.clip.height = maxheight; - else l.document.height = maxheight; - if (l.maxwidth && maxwidth > l.maxwidth) maxwidth = l.maxwidth; - if (l.minwidth && maxwidth < l.minwidth) maxwidth = l.minwidth; - if (l!=window) l.clip.width = maxwidth; - else l.document.width = maxwidth; - } - -/** Add a universal "is visible" function that handles inherited visibility. **/ -function pg_isvisible(l) - { - if (l.visibility == 'show') return 1; - else if (l.visibility == 'hidden') return 0; - else if (l == window || l.parentLayer == null) return 1; - else return pg_isvisible(l.parentLayer); - } - -/** Cursor flash **/ -function pg_togglecursor() - { - if (pg_curkbdlayer != null && pg_curkbdlayer.cursorlayer != null) - { - if (pg_curkbdlayer.cursorlayer.visibility != 'inherit') - pg_curkbdlayer.cursorlayer.visibility = 'inherit'; - else - pg_curkbdlayer.cursorlayer.visibility = 'hidden'; - } - setTimeout(pg_togglecursor,333); - } - - - -/** Keyboard input handling **/ -function pg_addkey(s,e,mod,modmask,mlayer,klayer,tgt,action,aparam) - { - kd = new Object(); - kd.startcode = s; - kd.endcode = e; - kd.mod = mod; - kd.modmask = modmask; - kd.mouselayer = mlayer; - kd.kbdlayer = klayer; - kd.target_obj = tgt; - kd.fnname = 'Action' + action; - kd.aparam = aparam; - pg_keylist.splice(0,0,kd); - pg_keylist.sort(pg_cmpkey); - return kd; - } - - -function pg_cmpkey(k1,k2) - { - return (k1.endcode-k1.startcode) - (k2.endcode-k2.startcode); - } - -function pg_removekey(kd) - { - for(i=0;i= pg_keylist[i].startcode && k <= pg_keylist[i].endcode && (pg_keylist[i].kbdlayer == null || pg_keylist[i].kbdlayer == pg_curkbdlayer) && (pg_keylist[i].mouselayer == null || pg_keylist[i].mouselayer == pg_curlayer) && (m & pg_keylist[i].modmask) == pg_keylist[i].mod) - { - pg_keylist[i].aparam.KeyCode = k; - pg_keylist[i].target_obj[pg_keylist[i].fnname](pg_keylist[i].aparam); - return false; - } - } - return false; - } - - -// Load indication -if (window.pg_scripts) pg_scripts['htdrv_page_moz.js'] = true; diff --git a/centrallix-os/sys/js/htdrv_pane.js b/centrallix-os/sys/js/htdrv_pane.js index dfdb597ad..7e9ce94fb 100644 --- a/centrallix-os/sys/js/htdrv_pane.js +++ b/centrallix-os/sys/js/htdrv_pane.js @@ -1,4 +1,4 @@ -// Copyright (C) 1998-2001 LightSys Technology Services, Inc. +// Copyright (C) 1998-2026 LightSys Technology Services, Inc. // // You may use these files and this library under the terms of the // GNU Lesser General Public License, Version 2.1, contained in the @@ -79,16 +79,21 @@ function pn_setbackground(aparam) function pn_action_resize(aparam) { - var w = aparam.Width?aparam.Width:pg_get_style(this, 'width'); - var h = aparam.Height?aparam.Height:pg_get_style(this, 'height'); + const { Width, Height } = aparam; + + // Log warning. + console.warn('Resize action used: This breaks page responsiveness.'); + console.log(`Action Info: Width=${Width} Height=${Height} Target=`, this); + + // Resize the pane. + const w = Width ?? pg_get_style(this, 'width'); + const h = Height ?? pg_get_style(this, 'height'); resizeTo(this, w, h); } function pn_action_point(aparam) { - var divs = htutil_point(this, aparam.X, aparam.Y, aparam.AtWidget, aparam.BorderColor, aparam.FillColor, this.point1, this.point2); - this.point1 = divs.p1; - this.point2 = divs.p2; + htr_action_point(this, aparam); } function pn_init(param) diff --git a/centrallix-os/sys/js/htdrv_radiobutton.js b/centrallix-os/sys/js/htdrv_radiobutton.js index 5b106deb9..e019c09d2 100644 --- a/centrallix-os/sys/js/htdrv_radiobutton.js +++ b/centrallix-os/sys/js/htdrv_radiobutton.js @@ -1,4 +1,4 @@ -// Copyright (C) 1998-2001 LightSys Technology Services, Inc. +// Copyright (C) 1998-2026 LightSys Technology Services, Inc. // // You may use these files and this library under the terms of the // GNU Lesser General Public License, Version 2.1, contained in the @@ -37,7 +37,6 @@ function rb_setvalue(v) { } } this.clearvalue(); - //alert('Warning: "'+v+'" is not in the radio button list.'); } function rb_cb_getvalue(p) { @@ -104,12 +103,8 @@ function add_radiobutton(optionPane, param) { var rb = wgtrGetParent(optionPane); rb.rbCount++; optionPane.valueIndex = rb.rbCount; - /*optionPane.kind = 'radiobutton'; - optionPane.document.layer = optionPane;*/ htr_init_layer(optionPane, rb, 'radiobutton'); - //optionPane.mainlayer = rb; optionPane.optionPane = optionPane; - optionPane.isSelected = param.selected; optionPane.valueStr = param.valuestr; optionPane.labelStr = param.labelstr; @@ -151,8 +146,14 @@ function add_radiobutton(optionPane, param) { htr_setvisibility(optionPane.unsetPane, 'inherit'); } - optionPane.yOffset = getRelativeY(optionPane)+getRelativeY(rb.coverPane)+getRelativeY(rb.borderPane); - pg_addarea(rb, getRelativeX(optionPane), optionPane.yOffset, getClipWidth(optionPane), pg_parah+4, optionPane, 'rb', 3); + const yOffset = getRelativeY(rb.coverPane) + getRelativeY(rb.borderPane) - 1; + optionPane.area = pg_addarea(rb, + () => getRelativeX(optionPane) + 2, + () => getRelativeY(optionPane) + yOffset, + () => getRelativeW(optionPane) + 2, + () => getRelativeH(optionPane) + 3, + optionPane, 'rb', 3 + ); } function rb_getfocus(xo,yo,l,c,n,a,from_kbd) @@ -347,8 +348,6 @@ function rb_changemode(){ for (var i=0;i e.forEach(({ target }) => { + target.UpdateThumb(); +})); + + +function sp_init({ layer: pane, area_name, thumb_name }) { - var l = param.layer; - var alayer=null; - var tlayer=null; - var ml; - var img; - var i; - if(cx__capabilities.Dom0NS) - { - var layers = pg_layers(l); - for(i=0;i 100) aparam.Percent = 100; - setRelativeY(this.area, -d*aparam.Percent/100); + const { bottom } = child.getBoundingClientRect(); + if (bottom > maxBottom) maxBottom = bottom; } - else if (typeof aparam.Offset != 'undefined') + area.content_height = Math.max(0, Math.round(maxBottom - top)); + + /** Watch for changes in content height. **/ + if (cx__capabilities.Dom0IE) { - if (aparam.Offset < 0) aparam.Offset = 0; - else if (aparam.Offset > d) aparam.Offset = d; - setRelativeY(this.area, -aparam.Offset); + area.runtimeStyle.clip.pane = pane; + // how to watch this in IE? + area.runtimeStyle.clip.onpropertychange = sp_WatchHeight; } - else if (typeof aparam.RangeStart != 'undefined' && typeof aparam.RangeEnd != 'undefined') + else { - var ny = -getRelativeY(this.area); - if (ny + ch < aparam.RangeEnd) ny = aparam.RangeEnd - ch; - if (ny > aparam.RangeStart) ny = aparam.RangeStart; - if (ny < 0) ny = 0; - if (ny > d) ny = d; - setRelativeY(this.area, -ny); + area.clip.pane = pane; + area.clip.watch("height", sp_WatchHeight); } - this.UpdateThumb(h); } -function sp_WatchHeight(property, oldvalue, newvalue) +/** ========== Getter functions ========== **/ +/** Functions to compute common values needed often in this code. **/ + +/** @returns The height of content inside the scrollpane (even if not all of it is visible). **/ +function sp_get_content_height(area) + { + return area.content_height; + } + +/** @returns The height of visible area available to the scrollpane. **/ +function sp_get_available_height(pane) + { + return parseInt(getComputedStyle(pane).height); + } + +/** @returns The height of the content outside the available visible area of the scrollpane. **/ +function sp_get_nonvisible_height(pane) + { + return sp_get_content_height(pane.area) - sp_get_available_height(pane); + } + +/** @returns The height of visible area available to the scroll bar. **/ +function sp_get_scrollbar_height(pane) + { + /** The up and down buttons and thumb are each 18px. **/ + return sp_get_available_height(pane) - (3*18); + } + +/** @returns The distance down that the scrollpane has been scrolled. **/ +function sp_get_scroll_dist(area) + { + return -getRelativeY(area); + } + + +/*** Update the scrollpane to handle the height of its contained content + *** (aka. its child widgets) changing. + *** + *** @param property Unused + *** @param old_value The old height of the child content (unused). + *** @param new_value The new height of the child content. + ***/ +function sp_WatchHeight(property, old_value, new_value) { + const { pane } = this; + const { area } = pane; + + /** Handle legacy Internet Explorer behavior. **/ if (cx__capabilities.Dom0IE) - { - newvalue = htr_get_watch_newval(window.event); - } - - // make sure region not offscreen now - newvalue += getClipTop(this.pane.area); - if (getRelativeY(this.pane.area) + newvalue < getClipHeight(this.pane)) setRelativeY(this.pane.area, getClipHeight(this.pane) - newvalue); - if (newvalue < getClipHeight(this.pane)) setRelativeY(this.pane.area, 0); - this.pane.UpdateThumb(newvalue); - newvalue -= getClipTop(this.pane.area); - this.bottom = this.top + newvalue; /* ns seems to unlink bottom = top + height if you modify clip obj */ - return newvalue; + new_value = htr_get_watch_newval(window.event); + + /** Update the internal content height value. **/ + area.content_height = new_value; + + /** Get the available height of the visible area. **/ + const available_height = sp_get_available_height(pane); + + /** Scroll to the top if the content is now smaller than the visible area. **/ + if (getRelativeY(area) + new_value < available_height) + setRelativeY(area, available_height - new_value); + if (new_value < available_height) setRelativeY(area, 0); + + /** Update the scroll thumb. **/ + pane.UpdateThumb(); + + return new_value; } -function sp_UpdateThumb(h) +/** Called when the ScrollTo action is used. **/ +function sp_action_ScrollTo({ Percent, Offset, RangeStart, RangeEnd }) { - /** 'this' is a spXpane **/ - if(!h) - { /** if h is supplied, it is the soon-to-be clip.height of the spXarea **/ - h=getClipHeight(this.area)+getClipTop(this.area); // height of content - } - var d=h-getClipHeight(this); // height of non-visible content (max scrollable distance) - var v=getClipHeight(this)-(3*18); - if(d<=0) - setRelativeY(this.thum, 18); - else - setRelativeY(this.thum, 18+v*(-getRelativeY(this.area)/d)); + const pane = this; + const available = sp_get_available_height(pane); + const nonvisible_height = sp_get_nonvisible_height(pane); // Height of non-visible content. + + /** Ignore the action if all content is visible. **/ + if (nonvisible_height <= 0) return; + + /** Calculate the new location to scroll to. **/ + const new_scroll_height = + (Offset !== undefined) ? Offset : + (Percent !== undefined) ? Math.clamp(0, Percent / 100, 1) * nonvisible_height : + (RangeStart !== undefined && RangeEnd !== undefined) ? + Math.clamp(RangeEnd - available, sp_get_scroll_dist(pane.area), RangeStart) : + 0; /* Fallback default value. */ + + /** Scroll to the new location. **/ + sp_scroll_to(pane, new_scroll_height); } -function do_mv() +/** Recalculate and update the location of the scroll thumb. **/ +function sp_update_thumb() { + const pane = this; + const { area, thumb } = pane; - var ti=sp_target_img; - /** not sure why, but it's getting called with a null sp_target_img sometimes... **/ - if(!ti) + /** Get the height of nonvisible content. **/ + const nonvisible_height = sp_get_nonvisible_height(pane); + + /** Handle the case where all content is visible. **/ + if (nonvisible_height <= 0) { + /** All we need to do is to move the scroll thumb to the top. **/ + setRelativeY(thumb, 18); return; } - var h=getClipHeight(ti.area)+getClipTop(ti.area); // height of content - var d=h-getClipHeight(ti.pane); // height of non-visible content (max scrollable distance) - var incr=sp_mv_incr; - if(d<0) - incr=0; - if (ti.kind=='sp') + + /** Calculate where the scroll thumb should be based on the scroll progress. **/ + let scroll_dist = sp_get_scroll_dist(area); + if (scroll_dist > nonvisible_height) { - var scrolled = -getRelativeY(ti.area); // distance scrolled already - if(incr > 0 && scrolled+incr>d) - incr=d-scrolled; + /** Scroll down to fill the new space at the bottom of the scrollpane. **/ + setRelativeY(area, -nonvisible_height); + scroll_dist = nonvisible_height; + } + const progress = scroll_dist / nonvisible_height; + const progress_scaled = 18 + sp_get_scrollbar_height(pane) * progress; + + /** Set the scroll thumb to the calculated location. **/ + setRelativeY(thumb, progress_scaled); + } - /** if we've scrolled down less than we want to go up, go up the distance we went down **/ - if(incr < 0 && scrolled<-incr) - incr=-scrolled; +/*** Scroll the scrollpane to the specified `scroll_height`. + *** + *** @param pane The affected scrollpane DOM node. + *** @param scroll_height The new height, in pixels, that the content should + *** be scrolled to as a result of this scroll. + ***/ +function sp_scroll_to(pane, scroll_height) + { + /** Ignore undefined target pane. **/ + if (!pane) return; + + /** Don't scroll if there's no content to scroll. **/ + const nonvisible_height = sp_get_nonvisible_height(pane); + if (nonvisible_height < 0) return; + + /** Save the current scroll amount for later. **/ + const scroll_height_old = sp_get_scroll_dist(pane.area); + + /** Clamp the scroll height within the bounds of the scroll bar. **/ + const scroll_height_new = Math.clamp(0, scroll_height, nonvisible_height); + + /** Update the content. **/ + setRelativeY(pane.area, -scroll_height_new); + pane.UpdateThumb(); + + /** Construct the param for the centrallix 'Scroll' event. **/ + const percent_old = (scroll_height_old / nonvisible_height) * 100; + const percent_new = (scroll_height_new / nonvisible_height) * 100; + const param = { Percent: percent_new, Change: percent_new - percent_old }; + + /** Schedule the scroll event to allow the page to repaint first. **/ + setTimeout(() => cn_activate(pane, 'Scroll', param), 0); + } - /*var layers = pg_layers(ti.pane); - for(var i=0;i getPageY(ti.pane)+18+v) new_y=getPageY(ti.pane)+18+v; - if (new_y < getPageY(ti.pane)+18) new_y=getPageY(ti.pane)+18; - setPageY(ti.thum,new_y); - var h=getClipHeight(ti.area)+getClipTop(ti.area); - var d=h-getClipHeight(ti.pane); - if (d<0) d=0; - var yincr = (((getRelativeY(ti.thum)-18)/v)*-d) - getRelativeY(ti.area); - moveBy(ti.area, 0, yincr); - return EVENT_HALT | EVENT_PREVENT_DEFAULT_ACTION; - } + /** Trigger Centrallix events. **/ + const pane = sp_get_pane(e); + if (pane) cn_activate(pane, 'MouseMove'); + + /** Monitor events on other DOM nodes to detect MouseOut. **/ + if (!pane && sp_target_mainlayer) + { + /** Mouse out has occurred, ending the mouse over. **/ + cn_activate(sp_target_mainlayer, 'MouseOut'); + sp_target_mainlayer = undefined; + } + + /** Check if a drag is in progress (aka. the drag target exists and is a scroll thumb). **/ + const target_img = sp_drag_img; + if (target_img && target_img.kind === 'sp' && target_img.dataset.type === 'thumb') + { + const { pane } = target_img; + + /** Get drag_dist: the distance that the scroll bar should move. **/ + const page_y = getPageY(target_img.thumb); + const drag_dist = e.pageY - page_y; + + /** Scale drag_dist to the distance that the content should move. **/ + const scrollbar_height = sp_get_scrollbar_height(pane); + const nonvisible_height = sp_get_nonvisible_height(pane); + const content_drag_dist = (drag_dist / scrollbar_height) * nonvisible_height; + + /** Scroll the content by the required amount to reach the new mouse location. **/ + sp_scroll(target_img.pane, content_drag_dist); + + /** Event handled. **/ + return EVENT_HALT | EVENT_PREVENT_DEFAULT_ACTION; + } + + /** Continue the event. **/ return EVENT_CONTINUE | EVENT_ALLOW_DEFAULT_ACTION; } +/*** Handles mouse up events anywhere on the page. + *** + *** @param e The event that has occurred. + *** @returns An event result (see ht_render.js). + ***/ function sp_mouseup(e) { - if (sp_mv_timeout != null) - { - clearTimeout(sp_mv_timeout); - sp_mv_timeout = null; - sp_mv_incr = 0; - } - if (sp_target_img != null) - { - if (sp_target_img.name != 'b') - pg_set(sp_target_img,'src',htutil_subst_last(sp_target_img.src,"b.gif")); - sp_target_img = null; - } - if (e.kind == 'sp') cn_activate(e.mainlayer, 'MouseUp'); + /** Trigger Centrallix events. **/ + if (e.kind === 'sp') + { + const params = sp_get_event_params(e); + cn_activate(e.mainlayer, 'MouseUp', params); + if (sp_click_in_progress) + cn_activate(e.mainlayer, 'Click', params); + } + + /** A click is no longer in progress. **/ + sp_click_in_progress = false; + + /** Check for an active drag. **/ + if (sp_drag_img) + { + /** End the drag. **/ + sp_drag_img = undefined; + } + + /** Check for a pressed button. **/ + if (sp_button_img) + { + /** Reset the pressed button. **/ + pg_set(sp_button_img, 'src', htutil_subst_last(sp_button_img.src, "b.gif")); + sp_button_img = undefined; + } + + /** Continue the event. **/ return EVENT_CONTINUE | EVENT_ALLOW_DEFAULT_ACTION; } +/*** Handles mouse over events anywhere on the page. + *** + *** @param e The event that has occurred. + *** @returns An event result (see ht_render.js). + ***/ function sp_mouseover(e) { - if (e.kind == 'sp') - { - if (!sp_cur_mainlayer) - { - cn_activate(e.mainlayer, 'MouseOver'); - sp_cur_mainlayer = e.mainlayer; - } - } + /** Check for mouse over on an sp element. **/ + if (sp_target_mainlayer && e.kind === 'sp') + { + /** Begin a mouse over. **/ + cn_activate(e.mainlayer, 'MouseOver'); + sp_target_mainlayer = e.mainlayer; + } + + /** Continue the event. **/ return EVENT_CONTINUE | EVENT_ALLOW_DEFAULT_ACTION; } -// Load indication +/** Indicate loading is complete. **/ if (window.pg_scripts) pg_scripts['htdrv_scrollpane.js'] = true; diff --git a/centrallix-os/sys/js/htdrv_sys_osml.js b/centrallix-os/sys/js/htdrv_sys_osml.js deleted file mode 100644 index 6b722f140..000000000 --- a/centrallix-os/sys/js/htdrv_sys_osml.js +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (C) 1998-2001 LightSys Technology Services, Inc. -// -// You may use these files and this library under the terms of the -// GNU Lesser General Public License, Version 2.1, contained in the -// included file "COPYING" or http://www.gnu.org/licenses/lgpl.txt. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. - - -/** Add function to instantiate objects **/ -function cn_init(p) - { - this.type = 'cn'; - this.LSParent = p; - } - - -// Load indication -if (window.pg_scripts) pg_scripts['htdrv_sys_osml.js'] = true; diff --git a/centrallix-os/sys/js/htdrv_tab.js b/centrallix-os/sys/js/htdrv_tab.js index ebab8b6b6..f043853bc 100644 --- a/centrallix-os/sys/js/htdrv_tab.js +++ b/centrallix-os/sys/js/htdrv_tab.js @@ -1,4 +1,4 @@ -// Copyright (C) 1998-2004 LightSys Technology Services, Inc. +// Copyright (C) 1998-2026 LightSys Technology Services, Inc. // // You may use these files and this library under the terms of the // GNU Lesser General Public License, Version 2.1, contained in the @@ -9,7 +9,6 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. - // Sets the value of the current tab (but not the appearance), without // triggering on-change events. function tc_set_tab_unwatched() @@ -27,189 +26,219 @@ function tc_set_tab_unwatched() // Makes the given tab current. function tc_makecurrent() { - var t; - if (this.tabctl.tloc != 4 && htr_getzindex(this.tab) > htr_getzindex(this.tabctl)) return 0; - for(var i=0;i htr_getzindex(t)) return 0; + for(let i = 0; i < tabs.length; i++) { - t = this.tabctl.tabs[i]; - if (t != this && (t.tabctl.tloc == 4 || htr_getzindex(t.tab) > htr_getzindex(this.tab))) + const cur = tabs[i]; + if (cur !== this && (cur.tabctl.tloc === 'None' || htr_getzindex(cur.tab) > htr_getzindex(tab))) { - htr_setzindex(t, htr_getzindex(this.tabctl) - 1); - htr_setvisibility(t, 'hidden'); - if (t.tabctl.tloc != 4) + htr_setzindex(cur, htr_getzindex(t) - 1); + htr_setvisibility(cur, 'hidden'); + if (cur.tabctl.tloc !== 'None') { - htr_setzindex(t.tab, htr_getzindex(this.tabctl) - 1); - t.tab.marker_image.src = '/sys/images/tab_lft3.gif'; - moveBy(t.tab, this.tabctl.xo, this.tabctl.yo); - //setClipItem(t.tab, t.tabctl.cl, getClipItem(t.tab, t.tabctl.cl) + t.tabctl.ci); - if (this.tabctl.inactive_bgColor) htr_setbgcolor(t.tab, this.tabctl.inactive_bgColor); - if (this.tabctl.inactive_bgnd) htr_setbgimage(t.tab, this.tabctl.inactive_bgnd); + htr_setzindex(cur.tab, htr_getzindex(t) - 1); + cur.tab.marker_image.src = '/sys/images/tab_lft3.gif'; + cur.tab.classList.remove('tab_selected'); + if (t.inactive_bgColor) htr_setbgcolor(cur.tab, t.inactive_bgColor); + if (t.inactive_bgnd) htr_setbgimage(cur.tab, t.inactive_bgnd); } } } - htr_setzindex(this, htr_getzindex(this.tabctl) + 1); + htr_setzindex(this, htr_getzindex(t) + 1); htr_setvisibility(this,'inherit'); - if (this.tabctl.tloc != 4) + if (tloc !== 'None') { - if (this.tabctl.main_bgColor) htr_setbgcolor(this.tab, this.tabctl.main_bgColor); - if (this.tabctl.main_bgnd) htr_setbgimage(this.tab, this.tabctl.main_bgnd); - htr_setzindex(this.tab, htr_getzindex(this.tabctl) + 1); - this.tab.marker_image.src = '/sys/images/tab_lft2.gif'; - moveBy(this.tab, -this.tabctl.xo, -this.tabctl.yo); - //setClipItem(this.tab, this.tabctl.cl, getClipItem(this.tab, this.tabctl.cl) - this.tabctl.ci); + if (t.main_bgColor) htr_setbgcolor(tab, t.main_bgColor); + if (t.main_bgnd) htr_setbgimage(tab, t.main_bgnd); + htr_setzindex(tab, htr_getzindex(t) + 1); + tab.marker_image.src = '/sys/images/tab_lft2.gif'; + tab.classList.add('tab_selected'); } this.setTabUnwatched(); - this.tabctl.ifcProbe(ifEvent).Activate("TabChanged", {Selected:this.tabctl.selected, SelectedIndex:this.tabctl.selected_index}); + + // Activate the Centrallix TabChanged event. + t.ifcProbe(ifEvent).Activate("TabChanged", { + Selected:t.selected, + SelectedIndex:t.selected_index, + }); } -function tc_makenotcurrent(t) +function tc_makenotcurrent(page) { - htr_setzindex(t,htr_getzindex(t.tabctl) - 1); - htr_setvisibility(t,'hidden'); - if (t.tabctl.tloc != 4) + const { tabctl, tab } = page; + + htr_setzindex(page, htr_getzindex(tabctl) - 1); + htr_setvisibility(page, 'hidden'); + + if (tabctl.tloc !== 'None') { - htr_setzindex(t.tab,htr_getzindex(t.tabctl) - 1); - t.tab.marker_image.src = '/sys/images/tab_lft3.gif'; - moveBy(t.tab, t.tabctl.xo, t.tabctl.yo); - //setClipItem(t.tab, t.tabctl.cl, getClipItem(t.tab, t.tabctl.cl) + t.tabctl.ci); - if (t.tabctl.inactive_bgColor) htr_setbgcolor(t.tab, t.tabctl.inactive_bgColor); - if (t.tabctl.inactive_bgnd) htr_setbgimage(t.tab, t.tabctl.inactive_bgnd); + htr_setzindex(page.tab,htr_getzindex(page.tabctl) - 1); + tab.marker_image.src = '/sys/images/tab_lft3.gif'; + tab.classList.remove('tab_selected'); + if (tabctl.inactive_bgColor) htr_setbgcolor(tab, tabctl.inactive_bgColor); + if (tabctl.inactive_bgnd) htr_setbgimage(tab, tabctl.inactive_bgnd); } } - -// Adds a new tab to the tab control -function tc_addtab(l_tab, l_page, l, nm, type,fieldname) + +/*** Adds a new tab to the tab control. This function deals with whether or + *** not that tab is selected as LITTLE AS POSSIBLE since that piece of state + *** should be handled elsewhere with functions like tc_makenotcurrent() or + *** tc_makecurrent(). + *** + *** @param param The object containing parameters for the function. + *** @param param.l_tab The tab to be added. + *** @param param.l_page The page to which the tab shall be added. + *** @param param.name The name of the tab. + *** @param param.type The type of the tab. + *** @param param.fieldname The fieldname of the tab. + ***/ +function tc_add_tab(param) { - var newx; - var newy; - if (!l_tab) l_tab = new Object(); - l_page.tabname = nm; + const { tab, page, name, type, fieldname } = param; + const tabctl = this, { tloc, tab_h, tab_spacing, tabs } = tabctl; + const l_tab = tab ?? {}, l_page = page; + + let x, y; + l_page.tabname = name; l_page.type = type; l_page.fieldname = fieldname; - l_page.tabindex = this.tabs.length+1; - htr_init_layer(l_page,l,'tc_pn'); + l_page.tabindex = tabs.length + 1; + htr_init_layer(l_page, tabctl, 'tc_pn'); ifc_init_widget(l_page); - if (l.tloc != 4) + + // Calculate the location and flexibility to render the tab. + if (tloc === 'None') + { + x = 0; + y = 0; + } + else { - htr_init_layer(l_tab,l,'tc'); - if (l.tloc == 0 || l.tloc == 1) // top or bottom + htr_init_layer(l_tab, tabctl, 'tc'); + + /** Calculate x coordinate. **/ + if (tloc === 'Top' || tloc === 'Bottom') { - if (this.tabs.length > 0) + if (tabs.length > 0) + { + const previous_tab = tabs[tabs.length - 1].tab; + x = getRelativeX(previous_tab) + $(previous_tab).outerWidth() + tab_spacing; + } + else if (tabctl.tab_fl_x) { - //alert(htr_getphyswidth(this.tabs[this.tabs.length-1])); - newx = getPageX(this.tabs[this.tabs.length-1].tab) + $(this.tabs[this.tabs.length-1].tab).outerWidth() + 1; - if (htr_getvisibility(this.tabs[this.tabs.length-1]) == 'inherit') newx += l.xo; + // Copy tabctl.style.left to avoid small but noticeable inconsistencies. + setRelativeX(l_tab, tabctl.style.left); } else - newx = getPageX(this); + { + // Math for inflexible tabs do not suffer from the inconsistencies handled above. + x = getRelativeX(tabctl); + } } - else if (l.tloc == 2) // left - newx = getPageX(this)- htr_getviswidth(l_tab) + 0; - else if (l.tloc == 3) // right - newx = getPageX(this) + htr_getviswidth(this) + 1; + else if (tloc === 'Left') + x = getRelativeX(tabctl) - htr_getviswidth(l_tab); + else // Right + x = getRelativeX(tabctl); // Included in xtoffset (see below) - if (l.tloc == 2 || l.tloc == 3) // left or right + /** Calculate y coordinate. **/ + if (tloc === 'Left' || tloc === 'Right') { - if (this.tabs.length > 0) + if (tabs.length > 0) { - newy = getPageY(this.tabs[this.tabs.length-1].tab) + 26; - if (htr_getvisibility(this.tabs[this.tabs.length-1]) == 'inherit') newy += l.yo; + const previous_tab = tabs[tabs.length - 1].tab; + y = getRelativeY(previous_tab) + tab_h + tab_spacing; } + else if (tabctl.tab_fl_y) + /** Copy tabctl.style.top to avoid small but noticeable inconsistencies. **/ + setRelativeY(l_tab, tabctl.style.top); else - newy = getPageY(this); + /** Math for inflexible tabs do not suffer from inconsistencies. * */ + y = getRelativeY(tabctl); } - else if (l.tloc == 1) // bottom - newy = getPageY(this)+ htr_getvisheight(this) + 1; - else // top - newy = getPageY(this) - 24; - - // Clipping - switch(l.tloc) + else if (tloc === 'Bottom') + y = getRelativeY(tabctl); // Included in ytoffset (see below) + else // Top + y = getRelativeY(tabctl) - tab_h; + + /** Apply the same tab offsets used on the server. **/ + x += tabctl.xtoffset; + y += tabctl.ytoffset; + + // Space out tab away from previous tab to account for borders. + if (tabs.length > 0) { - case 0: // top - $(l_tab).css('clip', 'rect(-10px, ' + ($(l_tab).outerWidth()+10) + 'px, 25px, -10px)'); - break; - case 1: // bottom - $(l_tab).css('clip', 'rect(0px, ' + ($(l_tab).outerWidth()+10) + 'px, 35px, -10px)'); - break; - case 2: // left - $(l_tab).css('clip', 'rect(-10px, ' + ($(l_tab).outerWidth()) + 'px, 35px, -10px)'); - break; - case 3: // right - $(l_tab).css('clip', 'rect(-10px, ' + ($(l_tab).outerWidth()+10) + 'px, 35px, 0px)'); - break; + switch (tloc) + { + case 'Top': case 'Bottom': x += 2; break; + case 'Left': case 'Right': y += 2; break; + } } } - else - { - newx = 0; - newy = 0; - } - if (htr_getvisibility(l_page) != 'inherit') - { - if (l.tloc != 4) - { - newx += l.xo; - newy += l.yo; - //setClipItem(l_tab, l.cl, getClipItem(l_tab, l.cl) + l.ci); - if (l.inactive_bgColor) htr_setbgcolor(l_tab, l.inactive_bgColor); - else if (l.main_bgColor) htr_setbgcolor(l_tab, l.main_bgColor); - if (l.inactive_bgnd) htr_setbgimage(l_tab, l.inactive_bgnd); - else if (l.main_bgnd) htr_setbgimage(l_tab, l.main_bgnd); - } - } - else + // Handle visibility. + if (htr_getvisibility(l_page) === 'inherit') { - htr_unwatch(l,"selected","tc_selection_changed"); - htr_unwatch(l,"selected_index","tc_selection_changed"); - l.selected = l_page.tabname; - l.selected_index = l_page.tabindex; - l.current_tab = l_page; - l.init_tab = l_page; - pg_addsched_fn(window,"pg_reveal_event",new Array(l_page,l_page,'Reveal'), 0); - htr_watch(l,"selected", "tc_selection_changed"); - htr_watch(l,"selected_index", "tc_selection_changed"); - if (l.tloc != 4) + htr_unwatch(tabctl, "selected", "tc_selection_changed"); + htr_unwatch(tabctl, "selected_index", "tc_selection_changed"); + tabctl.selected = l_page.tabname; + tabctl.selected_index = l_page.tabindex; + tabctl.current_tab = l_page; + tabctl.init_tab = l_page; + pg_addsched_fn(window, "pg_reveal_event", [l_page, l_page, 'Reveal'], 0); + htr_watch(tabctl, "selected", "tc_selection_changed"); + htr_watch(tabctl, "selected_index", "tc_selection_changed"); + if (tloc !== 'None') { - if (l.main_bgColor) htr_setbgcolor(l_tab, l.main_bgColor); - if (l.main_bgnd) htr_setbgimage(l_tab, l.main_bgnd); + if (tabctl.main_bgColor) htr_setbgcolor(l_tab, tabctl.main_bgColor); + if (tabctl.main_bgnd) htr_setbgimage(l_tab, tabctl.main_bgnd); } } - if (l.tloc != 4) + + // Handle images. + if (tloc !== 'None') { - var images = pg_images(l_tab); - for(var i=0;i this.tabs.length) return o; + if (tabindex < 1 || tabindex > tabs.length) return o; // okay to change tab. - //this.tabs[tabindex-1].makeCurrent(); + //tabs[tabindex-1].makeCurrent(); if (this.selchange_schedid) pg_delsched(this.selchange_schedid); - this.selchange_schedid = pg_addsched_fn(this,"ChangeSelection1", new Array(this.tabs[tabindex-1]), 0); + this.selchange_schedid = pg_addsched_fn(this, "ChangeSelection1", new Array(tabs[tabindex - 1]), 0); return n; } -function tc_action_set_tab(aparam) +function tc_action_set_tab({ Tab, TabIndex }) { - if (aparam.Tab) this.selected = aparam.Tab; - else if (aparam.TabIndex) this.selected_index = parseInt(aparam.TabIndex); + if (Tab) this.selected = Tab; + else if (TabIndex) this.selected_index = parseInt(TabIndex); } function tc_showcontainer() @@ -263,13 +294,13 @@ function tc_showcontainer() function tc_clear_tabs(tabs) { - for(var i=0;i "+vals[j]+" \n"; else - content = "\n
 "+vals[j]+" 
\n"; - tabparent = tc_direct_parent(tabs[i]); + content = + '\n\t' + + '' + + '' + + '
 ' + vals[j] + ' 
\n'; + + tabparent = tc_direct_parent(cur_tab); if(this.tc_layer_cache && this.tc_layer_cache.length >0) newtab = this.tc_layer_cache.pop(); else newtab = htr_new_layer(null,tabparent); - pageparent = tc_direct_parent(tabs[i].tabpage) + pageparent = tc_direct_parent(cur_tab.tabpage) newpage = htr_new_layer(null,pageparent); - newtab.marker_image = tabs[i].marker_image; + newtab.marker_image = cur_tab.marker_image; newtab.marker_image.src = '/sys/images/tab_lft3.gif'; $(newtab).find('span').text(' ' + htutil_encode(vals[j]) + ' '); //htr_write_content(newtab,content); @@ -343,8 +392,6 @@ function tc_updated(p1) htr_setvisibility(newpage,'inherit'); htr_setzindex(newtab,14); this.addTab(newtab,newpage,this,vals[j],'generated',''); - //setClipWidth(newtab,htr_getphyswidth(newtab)); - //setClipHeight(newtab,26); newpage.osrcdata = vals[j]; newpage.recordnumber = j; @@ -354,8 +401,8 @@ function tc_updated(p1) if(targettab) { - this.tabs[targettab].makeCurrent(); - this.tabs[targettab].tc_visible_changed('visible','hidden','inherit'); + tabs[targettab].makeCurrent(); + tabs[targettab].tc_visible_changed('visible','hidden','inherit'); } } } @@ -367,13 +414,20 @@ function tc_init(param) var l = param.layer; htr_init_layer(l,l,'tc'); ifc_init_widget(l); - l.tabs = new Array(); - l.addTab = tc_addtab; + l.tabs = []; + l.addTab = tc_add_tab; l.current_tab = null; l.init_tab = null; - l.tloc = param.tloc; - if (tc_tabs == null) tc_tabs = new Array(); + l.do_rendering = param.do_client_rendering; + l.select_x_offset = param.select_x_offset; + l.select_y_offset = param.select_y_offset; + l.xtoffset = param.xtoffset; + l.ytoffset = param.ytoffset; + l.tab_spacing = param.tab_spacing; + l.tab_h = param.tab_h; + if (tc_tabs == null) tc_tabs = []; tc_tabs[tc_tabs.length++] = l; + l.tloc = param.tloc; // Background color/image selection... l.main_bgColor = htr_extract_bgcolor(param.mainBackground); @@ -422,144 +476,99 @@ function tc_init(param) l.ChangeSelection2 = tc_changeselection_2; l.ChangeSelection3 = tc_changeselection_3; - // Movement geometries and clipping for tabs - switch(l.tloc) - { - case 0: // top - l.xo = +1; - l.yo = +2; - l.cl = "bottom"; - l.ci = -2; - break; - case 1: // bottom - l.xo = +1; - l.yo = -2; - l.cl = "top"; - l.ci = +2; - break; - case 2: // left - l.xo = +2; - l.yo = +1; - l.cl = "right"; - l.ci = -2; - break; - case 3: // right - l.xo = -2; - l.yo = +1; - l.cl = "left"; - l.ci = +2; - break; - case 4: // none - l.xo = 0; - l.yo = 0; - l.cl = "bottom"; - l.ci = 0; - break; - } return l; } -function tc_visible_changed(prop,o,n) +/*** Idk what this does... + *** + *** @param prop Unused, for some reason. + *** @param o Unused again... + *** @param n If this is true, it makes this.tab inherit visibility. Otherwise, hidden. + *** @returns nothing... idk what this is doing. + */ +function tc_visible_changed(prop, o, n) { - var t = this.tabctl; - var xo = t.xo; - var yo = t.yo; + const { tabctl: t } = this; + const { tabs, tloc } = t; + + if (tloc === 'None') console.warn("tc_visible_changed() called on tab contol with tab_location = none."); + if(n) htr_setvisibility(this.tab, 'inherit'); else htr_setvisibility(this.tab, 'hidden'); - // which tab should be selected? - if(htr_getvisibility(t.tabs[t.selected_index-1].tab)!='inherit') + + /** This nonsense is why we need goto in js. **/ + const pickSelectedTab = () => { - //try default tab - if(htr_getvisibility(t.init_tab.tab)=='inherit') + // If a visible tab is already selected, we're done. + const selected = tabs[t.selected_index-1]; + if (htr_getvisibility(selected.tab) === 'inherit') return; + + // Try to select the initial tab. + const initial = t.init_tab; + if (htr_getvisibility(initial.tab) === 'inherit') { - // This is forced, so we skip the obscure/reveal checks - t.ChangeSelection3(t.tabs[t.init_tab.tabindex-1]); - //t.tabs[t.init_tab.tabindex-1].makeCurrent(); + // This is forced, so we skip the obscure/reveal checks. + t.ChangeSelection3(tabs[initial.tabindex - 1]); + return; } - else //otherwise find first tab not hidden + + // Otherwise, pick the first visible tab. + for (let i = 0; i < tabs.length; i++) { - for(var i=0; i entries.forEach(({ + contentRect: { width, height }, + target: table +}) => + { + // Set the new size. + table.param_width = width; + table.param_height = height; + + // Update update the scrollbar, no data message, and reflow the columns. + if (table.rows.first !== null) table.Scroll(table.scroll_y, false); + else table.UpdateThumb(false); + table.UpdateNDM($(table).children('#ndm')); + table.ReflowWidth(width); + + // Resize all rows. + const { rows } = table; + for (let i = (table.has_header) ? 0 : rows.first; i <= rows.last; i++) + { + const row = rows[i]; + if (!row) continue; + $(row).css({ width: (row.w = width) }); + } + } +)); + function tbld_log_status() { var rowstr = ''; @@ -60,27 +87,33 @@ function tbld_format_cell(cell, color) var bartext = wgtrGetServerProperty(wgtrFindDescendent(this, this.cols[cell.colnum].name, this.cols[cell.colnum].ns), 'bar_textcolor'); if (!bartext) bartext = 'black'; bartext = String(bartext).replace(/[^a-z0-9A-Z#]/g, ""); - var actpct = '' + (100 * ((val < 0)?0:((val > 1)?1:val))) + '%'; - actpct = String(actpct).replace(/[^0-9.%]/g, ""); - var pct = '' + (Math.round(val * 100 / roundto) * roundto) + '%'; + const width_percent = ('' + (100 * Math.clamp(0, val, 1)) + '%').replace(/[^0-9.%]/g, ""); + const percent = '' + (Math.round(val * 100 / roundto) * roundto) + '%'; if (val >= 0.5) { - innertxt = pct + ' '; + innertxt = percent + ' '; outertxt = ''; } else { innertxt = ' '; - outertxt = ' ' + pct; + outertxt = ' ' + percent; } - txt = '
' + - '
' + - htutil_encode(innertxt) + - '
' + - (outertxt?('' + - htutil_encode(outertxt) + - ''):'') + - '
'; + txt = + '
' + + '
' + + htutil_encode(innertxt) + + '
' + + ((outertxt) ? ('' + htutil_encode(outertxt) + '') : '') + + '
'; } else txt = ''; @@ -117,20 +150,20 @@ function tbld_format_cell(cell, color) else { // Text - txt = '' + str + ''; + txt = '' + str + ''; } style += htutil_getstyle(wgtrFindDescendent(this,colinfo.name,colinfo.ns), null, {textcolor: color}); if (cell.capdata) { // Caption (added to any of the above types) - captxt = '' + htutil_encode(htutil_obscure(cell.capdata), colinfo.wrap != 'no') + ''; + captxt = '' + htutil_encode(htutil_obscure(cell.capdata), colinfo.wrap != 'no') + ''; if (colinfo.wrap != 'no') captxt = htutil_nlbr(captxt); capstyle += htutil_getstyle(wgtrFindDescendent(this,colinfo.name,colinfo.ns), "caption", {textcolor: color}); } if (cell.titledata) { - titletxt = '' + htutil_encode(htutil_obscure(cell.titledata), colinfo.wrap != 'no') + ''; + titletxt = '' + htutil_encode(htutil_obscure(cell.titledata), colinfo.wrap != 'no') + ''; if (colinfo.wrap != 'no') titletxt = htutil_nlbr(titletxt); titlestyle += htutil_getstyle(wgtrFindDescendent(this,colinfo.name,colinfo.ns), "title", {textcolor: color}); @@ -194,10 +227,9 @@ function tbld_format_cell(cell, color) // If an image, then test for final image loading, and readjust row // height once the image is loaded. - // - if (cell.firstChild && cell.firstChild.firstChild && cell.firstChild.firstChild.tagName == 'IMG') + const img = cell?.firstChild?.firstChild; + if (img?.tagName === 'IMG') { - var img = cell.firstChild.firstChild; img.layer = cell; $(img).one("load", function() { @@ -301,18 +333,10 @@ function tbld_redraw_all(dataobj, force_datafetch) var max = this.osrc.LastRecord + this.is_new; } - // "no data" message? - var ndm = $(this).children('#ndm'); - if (max >= min) - { - ndm.hide(); - } - else - { - ndm.show(); - ndm.text(wgtrGetServerProperty(this,"nodata_message")); - ndm.css({"top":((this.param_height - ndm.height())/2) + "px", "color":wgtrGetServerProperty(this,"nodata_message_textcolor") }); - } + // Redraw the no data message. + var $ndm = $(this).children('#ndm'); + if (max >= min) $ndm.hide(); + else this.UpdateNDM($ndm); // (re)draw the loaded records var selected_position_changed = false; @@ -566,19 +590,12 @@ function tbld_get_selected_geom() return { x:$(obj).offset().left, y:$(obj).offset().top, width:$(obj).width(), height:$(obj).height() }; } - -function tbld_css_height(element, seth) - { - if (seth == null) - { - return parseFloat($(element).css("height")); - } - else - { - $(element).css("height",seth + "px"); - } - } - +function tbld_get_height(node) { + return parseFloat($(node).css("height")); +} +function tbld_set_height(node, new_height) { + $(node).css("height", new_height + "px"); +} function tbld_update_height(row) { @@ -597,8 +614,8 @@ function tbld_update_height(row) h = this.max_rowheight - this.cellvspacing*2; if (h < this.min_rowheight - this.cellvspacing*2) h = this.min_rowheight - this.cellvspacing*2; - if (tbld_css_height(col) != h) - tbld_css_height(col,h); + if (tbld_get_height(col) != h) + tbld_set_height(col, h); if (h > maxheight) maxheight = h; } @@ -617,10 +634,11 @@ function tbld_update_height(row) } // No change? - if (tbld_css_height(row) == maxheight + this.innerpadding*2) + const new_height = maxheight + this.innerpadding*2; + if (tbld_get_height(row) === new_height) return false; - tbld_css_height(row,maxheight + this.innerpadding*2); + tbld_set_height(row, new_height); return true; } @@ -686,7 +704,6 @@ function tbld_format_row(id, selected, do_new) function tbld_bring_into_view(rownum) { - //this.log.push("tbld_bring_into_view(" + rownum + ")"); this.bring_into_view = null; // Clamp the requested row to the available range @@ -939,10 +956,11 @@ function tbld_detail_showcontainer() } } - +/** @param dw The detail widget DOM node. **/ function tbld_update_detail(dw) { - if (dw.display_for && (this.table.initselect !== 2 || (this.table.initselect == 2 && dw.on_new)) /* 2 = noexpand */ && (!dw.on_new || wgtrGetServerProperty(dw, 'show_on_new', 0))) + const expand = (this.table.initselect !== 2); + if (dw.display_for && ((dw.on_new) ? wgtrGetServerProperty(dw, 'show_on_new', 0) : expand)) { var found=false; for(var j=0; j scroll_end - this.vis_height) y = 0 - (scroll_end - this.vis_height); @@ -1307,86 +1331,80 @@ function tbld_bar_click(e) function tbld_change_width(move, compensate) { - var l=this; - var t=l.row.table; - var rw = $(l.resizebdr).width(); - var colinfo = t.cols[l.colnum]; + const { colnum, resizebdr, table: t } = this; + const { colcount, cols, rows } = t; + const rw = $(resizebdr).width(); + const { width: col_info_width, xoffset: col_info_xoffset } = cols[colnum]; // Sanity checks on column resizing... - //if(colinfo.xoffset+colinfo.width+move+rw>l.row.w) - // move = l.row.w - rw - colinfo.xoffset - colinfo.width; - if(colinfo.xoffset+colinfo.width+rw+move<0) - move=0-colinfo.xoffset-rw; - if (colinfo.width + move < 3) - move = 3-colinfo.width; - if(l.resizebdr.xoffset+move<0) - move=0-l.resizebdr.xoffset; - //if(getPageX(l.resizebdr) + t.colsep + t.bdr_width*2 + move >= getPageX(t) + t.param_width) - // move = getPageX(t) + t.param_width - getPageX(l.resizebdr) - t.colsep - t.bdr_width*2; + if (col_info_xoffset + col_info_width + rw + move < 0) + move = 0 - col_info_xoffset - rw; + if (col_info_width + move < 3) + move = 3 - col_info_width; + if (resizebdr && resizebdr.xoffset + move < 0) + move = 0 - resizebdr.xoffset; // Figure how much space on the right of this resize handle we're adjusting, too... - var cols_right = t.colcount - l.colnum - 1; - var adj = []; - var total_right_width = 0; - for(var j=l.colnum+1; j= t.rows.first) - { - if (t.ApplyRowGeom(t.rows[i], l.colnum) && t.min_rowheight != t.max_rowheight) + const updated_rows = []; + const { first, last } = rows; + const inflexible_row_height = (t.min_rowheight === t.max_rowheight); + for (let i = 0; i <= last; i++) + { + if (i < first && i !== 0) continue; + + const rowi = rows[i]; + if (!t.ApplyRowGeom(rowi, colnum) || inflexible_row_height) continue; + + // Need to update height of row? + if (!t.UpdateHeight(rowi)) continue; + + for (let j = i + 1; j <= last; j++) + { + const rowj = rows[j]; + if (rowj.positioned) { - // Need to update height of row? - if (t.UpdateHeight(t.rows[i])) - { - for(var j=i+1; j<=t.rows.last; j++) - { - if (t.rows[j].positioned) - { - t.rows[j].positioned = false; - upd_rows.push(t.rows[j]); - } - } - } + rowj.positioned = false; + updated_rows.push(rowj); } } } - if (upd_rows.length) + + if (updated_rows.length) { - t.PositionRows(upd_rows); + t.PositionRows(updated_rows); t.CheckBottom(); } @@ -1397,40 +1415,34 @@ function tbld_change_width(move, compensate) } -function tbld_reflow_width() +function tbld_reflow_width(new_width) { - if (this.hdrrow) - { - var logstr = 'Before reflow widths:'; - var ttl = 0; - for(var i=0; i 0 || this.dragcols) - new_w -= (this.bdr_width*2 + this.colsep); - $(c).width(new_w); - setRelativeX(c, this.cols[j].xoffset); - if (this.cols[j].wrap != 'no') - change_wrapped_cell = true; + const target_col = target_cols[i], this_col = this_cols[i]; + let new_w = this_col.width; // - this.innerpadding*2; + + if (colsep > 0 || dragcols) new_w -= (bdr_width*2 + colsep); + + $(target_col).width(new_w); + setRelativeX(target_col, this_col.xoffset); + + change_wrapped_cell |= (this_col.wrap != 'no'); } return change_wrapped_cell; } @@ -1604,7 +1620,6 @@ function tbld_remove_row(rowobj) } if (this.rows.firstvis > this.rows.lastvis) { - console.log('TABLE ' + this.__WgtrName + ': resetting firstvis/lastvis to null (firstvis > lastvis)'); this.rows.firstvis = null; this.rows.lastvis = null; } @@ -1687,12 +1702,14 @@ function tbld_display_row(rowobj, rowslot) if (!this.rows.lastvis || this.rows.lastvis < rowslot) this.rows.lastvis = rowslot; } - if (getRelativeY(rowobj) < this.scroll_minheight || this.scroll_minheight == null) - this.scroll_minheight = getRelativeY(rowobj); + const rowY = getRelativeY(rowobj); + if (rowY < this.scroll_minheight || this.scroll_minheight == null) + this.scroll_minheight = rowY; if (rowslot < this.scroll_minrec || this.scroll_minrec == null) this.scroll_minrec = rowslot; - if (rowslot == this.rows.lastosrc || (getRelativeY(rowobj) + $(rowobj).height() + this.cellvspacing*2 > this.scroll_maxheight)) - this.scroll_maxheight = getRelativeY(rowobj) + $(rowobj).height() + this.cellvspacing*2; + const rowHeight = $(rowobj).height(); + if (rowslot == this.rows.lastosrc || (rowY + rowHeight + this.cellvspacing*2 > this.scroll_maxheight)) + this.scroll_maxheight = rowY + rowHeight + this.cellvspacing*2; if (rowslot > this.scroll_maxrec) this.scroll_maxrec = rowslot; } @@ -1843,16 +1860,12 @@ function tbld_osrc_dispatch() case 'ScrollTo': this.osrc_busy = true; this.osrc_last_op = item.type; - //this.log.push("Calling ScrollTo(" + item.start + "," + item.end + ") on osrc, stat=" + (this.osrc.pending?'pending':'not-pending')); - //console.log("Calling ScrollTo(" + item.start + "," + item.end + ") on osrc, stat=" + (this.osrc.pending?'pending':'not-pending')); this.osrc.ScrollTo(item.start, item.end); break; case 'MoveToRecord': this.osrc_busy = true; this.osrc_last_op = item.type; - //this.log.push("Calling MoveToRecord(" + item.rownum + ") on osrc, stat=" + (this.osrc.pending?'pending':'not-pending')); - //console.log("Calling MoveToRecord(" + item.rownum + ") on osrc, stat=" + (this.osrc.pending?'pending':'not-pending')); this.osrc.MoveToRecord(item.rownum, this); break; @@ -1934,8 +1947,11 @@ function tbld_init(param) { var t = param.table; var scroll = param.scroll; + + // Initialize table. ifc_init_widget(t); t.table = t; + t.name = param.name; // Debug value. t.param_width = param.width; t.param_height = param.height; t.dragcols = param.dragcols; @@ -1955,12 +1971,15 @@ function tbld_init(param) t.cr = 0; t.is_new = 0; t.rowdivcache = []; - t.followcurrent = param.followcurrent>0?true:false; t.hdr_bgnd = param.hdrbgnd; htr_init_layer(t, t, "tabledynamic"); + + // Initialize scrollbar. t.scrollbar = scroll; htr_init_layer(t.scrollbar, t, "tabledynamic"); t.scrollbar.Click = tbld_bar_click; + + // Initialize scrollbar images. var imgs = pg_images(t.scrollbar); for(var img in imgs) { @@ -1971,29 +1990,23 @@ function tbld_init(param) else if (imgs[img].name == 'd') t.down = imgs[img]; } - t.box=htr_subel(scroll,param.boxname); - htr_init_layer(t.box, t, "tabledynamic"); - t.scrollbar.b=t.box; + + // Initialize scroll thumb. + t.thumb = htr_subel(scroll, param.thumb_name); + htr_init_layer(t.thumb, t, "tabledynamic"); + t.scrollbar.b = t.thumb; + + // Initialize events for scrolling. t.up.Click=tbld_up_click; t.down.Click=tbld_down_click; - t.box.Click = new Function( ); - t.scrollbar.table = t.up.table = t.down.table = t.box.table = t; + t.thumb.Click = new Function(); + t.scrollbar.table = t.up.table = t.down.table = t.thumb.table = t; t.up.subkind='up'; t.down.subkind='down'; - t.box.subkind='box'; + t.thumb.subkind='thumb'; t.scrollbar.subkind='bar'; - /*t.dispatch_queue = {}; - t.dispatch_parallel_max = 1; - t.Dispatch = tbld_dispatch; - t.Request = tbld_request;*/ - t.osrc_request_queue = []; - t.osrc_busy = false; - t.osrc_last_op = null; - //t.log = []; - t.ttf_string = ''; - t.selected_row = null; - t.selected = null; + // Initialize layout data. t.rowheight=param.min_rowheight>0?param.min_rowheight:15; t.min_rowheight = param.min_rowheight; t.max_rowheight = param.max_rowheight; @@ -2004,9 +2017,6 @@ function tbld_init(param) t.textcolorhighlight=param.textcolorhighlight?param.textcolorhighlight:param.textcolor; t.textcolornew=param.newrow_textcolor; t.titlecolor=param.titlecolor; - t.row_bgnd1=param.rowbgnd1?param.rowbgnd1:"bgcolor='white'"; - t.row_bgnd2=param.rowbgnd2?param.rowbgnd2:t.row_bgnd1; - t.row_bgndhigh=param.rowbgndhigh?param.rowbgndhigh:"bgcolor='black'"; t.row_bgndnew=param.newrow_bgnd; t.cols=param.cols; t.colcount=0; @@ -2017,17 +2027,29 @@ function tbld_init(param) else delete t.cols[i]; } - if (param.osrc) - t.osrc = wgtrGetNode(t, param.osrc, "widget/osrc"); - else - t.osrc = wgtrFindContainer(t, "widget/osrc"); - if(!t.osrc || !(t.colcount>0)) + if (t.colcount <= 0) { - alert('table widget requires an objectsource and at least one column'); + alert('The table widget requires at least one column'); return t; } - - // Main table widget methods + + // Initialize ObjectSource values. + t.osrc_request_queue = []; + t.osrc_busy = false; + t.osrc_last_op = null; + t.ttf_string = ''; + t.selected_row = null; + t.selected = null; + t.osrc = (param.osrc) + ? wgtrGetNode(t, param.osrc, "widget/osrc") + : wgtrFindContainer(t, "widget/osrc"); + if (!t.osrc) + { + alert('The table widget requires an ObjectSource'); + return t; + } + + // Bind table widget functions. t.RedrawAll = tbld_redraw_all; t.InstantiateRow = tbld_instantiate_row; t.DisplayRow = tbld_display_row; @@ -2050,7 +2072,7 @@ function tbld_init(param) t.SchedScroll = tbld_sched_scroll; t.CheckBottom = tbld_check_bottom; t.ApplyRowGeom = tbld_apply_row_geom; - t.InitBH = tbld_init_bh; + t.UpdateNDM = tbld_update_ndm; t.OsrcDispatch = tbld_osrc_dispatch; t.OsrcRequest = tbld_osrc_request; t.EndTTF = tbld_end_ttf; @@ -2071,6 +2093,7 @@ function tbld_init(param) t.ObjectModified = tbld_object_modified; t.osrc.Register(t); + // Set the number or records visible in the table at one time. if (param.windowsize > 0) { t.windowsize = param.windowsize; @@ -2090,12 +2113,15 @@ function tbld_init(param) if (t.datamode != 1 && t.windowsize > t.osrc.replicasize) t.windowsize = t.osrc.replicasize; + // Handle header row. t.totalwindowsize = t.windowsize + 1; if (!t.has_header) t.windowsize = t.totalwindowsize; t.firstdatarow = t.has_header?1:0; - // Handle column resizing and columns without widths + /*** Handle columns without widths by assigning a default and resizing other + *** columns proportionally. + ***/ var total_w = 0; for (var i in t.cols) { @@ -2125,11 +2151,10 @@ function tbld_init(param) t.grpby = i; } + // Set some other values. t.maxwindowsize = t.windowsize; t.maxtotalwindowsize = t.totalwindowsize; t.rows = {first:null, last:null, firstvis:null, lastvis:null, lastosrc:null}; - setClipWidth(t, param.width); - setClipHeight(t, param.height); t.subkind='table'; t.bdr_width = (t.colsep > 0)?3:0; t.target_y = null; @@ -2153,7 +2178,7 @@ function tbld_init(param) l.resizebdr = htr_new_layer(t.bdr_width*2 + t.colsep, t); $(l.resizebdr).css ({ - "cursor": "move", + "cursor": "col-resize", "height": (t.colsepmode == 0)?(((t.gridinemptyrows)?(t.param_height):t.rowheight) + "px"):(t.rowheight + "px"), "visibility": "inherit", "width": t.colsep + t.bdr_width*2 + "px", @@ -2208,6 +2233,7 @@ function tbld_init(param) } } + // Initialize scrollbar values. t.scroll_maxheight = null; t.scroll_maxrec = null; t.scroll_minheight = null; @@ -2220,6 +2246,7 @@ function tbld_init(param) // set working area height and scrollbar size t.UpdateGeom(); + // Initialize the scrollbar. t.scrolldiv = htr_new_layer(t.param_width, t.scrollctr); htr_init_layer(t.scrolldiv, t, "tabledynamic"); t.scrolldiv.subkind = "scrolldiv"; @@ -2238,11 +2265,23 @@ function tbld_init(param) if (window.tbld_mcurrent == undefined) window.tbld_mcurrent = null; + // Handle resizing. + tbld_resize_observer.observe(t); + // No data message - var ndm = document.createElement("div"); - $(ndm).css({"position":"absolute", "width":"100%", "text-align":"center", "left":"0px"}); - $(ndm).attr({"id":"ndm"}); - $(t).append(ndm); + var $ndm = $('
'); + $ndm[0].table = t; + $ndm.text(wgtrGetServerProperty(t, "nodata_message")); + $ndm.css({ + position: 'absolute', + width: '100%', + left: '0px', + textAlign: 'center', + color: wgtrGetServerProperty(t, "nodata_message_textcolor"), + }); + $ndm.show(); + $(t).append($ndm); + t.UpdateNDM($ndm); // Events var ie = t.ifcProbeAdd(ifEvent); @@ -2266,21 +2305,27 @@ function tbld_init(param) // Locate any row detail subwidgets t.detail_widgets = wgtrFindMatchingDescendents(t, 'widget/table-row-detail'); - for(var i=0; i $(t.scrollbar).height() - 18 - 3) - incr = $(t.scrollbar).height() - 18 - 3 - tbldx_tstart - $(t.box).height(); - setRelativeY(t.box, tbldx_tstart + incr); + if (tbldx_tstart + incr + $(t.thumb).height() > $(t.scrollbar).height() - 18 - 3) + incr = $(t.scrollbar).height() - 18 - 3 - tbldx_tstart - $(t.thumb).height(); + setRelativeY(t.thumb, tbldx_tstart + incr); if (t.thumb_avail > t.thumb_height) { t.SchedScroll((-t.scroll_minheight) - Math.floor((tbldx_tstart + incr - 18)*t.thumb_sh/(t.thumb_avail - t.thumb_height))); @@ -2915,7 +2962,7 @@ function tbld_mouseup(e) if (t.colsep > 0 || t.dragcols) maxw += (t.bdr_width*2 + t.colsep); l.ChangeWidth(maxw-t.cols[l.colnum].width, true); - t.ReflowWidth(); + t.ReflowWidth(t.hdrrow.w); } else { diff --git a/centrallix-os/sys/js/htdrv_textarea.js b/centrallix-os/sys/js/htdrv_textarea.js index 9229c3d86..f33f6e88d 100644 --- a/centrallix-os/sys/js/htdrv_textarea.js +++ b/centrallix-os/sys/js/htdrv_textarea.js @@ -1,4 +1,4 @@ -// Copyright (C) 1998-2001 LightSys Technology Services, Inc. +// Copyright (C) 1998-2026 LightSys Technology Services, Inc. // // You may use these files and this library under the terms of the // GNU Lesser General Public License, Version 2.1, contained in the @@ -318,10 +318,11 @@ function tx_init(param) } l.mode = param.mode; // 0=text, 1=html, 2=wiki l.isFormStatusWidget = false; - if (cx__capabilities.CSSBox) - pg_addarea(l, -1, -1, $(l).width()+3, $(l).height()+3, 'tbox', 'tbox', param.isReadonly?0:3); - else - pg_addarea(l, -1, -1, $(l).width()+1, $(l).height()+1, 'tbox', 'tbox', param.isReadonly?0:3); + + // Add the hover area. + const area_adj = (cx__capabilities.CSSBox) ? 3 : 1; + pg_addarea(l, -1, -1, () => $(l).width() + area_adj, () => $(l).height() + area_adj, 'ebox', 'ebox', (param.isReadOnly) ? 0 : 3); + if (param.form) l.form = wgtrGetNode(l, param.form); else diff --git a/centrallix-os/sys/js/htdrv_textbutton.js b/centrallix-os/sys/js/htdrv_textbutton.js index 77b968e75..0beab1f5f 100644 --- a/centrallix-os/sys/js/htdrv_textbutton.js +++ b/centrallix-os/sys/js/htdrv_textbutton.js @@ -1,4 +1,4 @@ -// Copyright (C) 1998-2001 LightSys Technology Services, Inc. +// Copyright (C) 1998-2026 LightSys Technology Services, Inc. // // You may use these files and this library under the terms of the // GNU Lesser General Public License, Version 2.1, contained in the @@ -205,7 +205,6 @@ function tb_setmode(layer,mode) switch(mode) { case 0: /* no point no click */ - moveTo(layer,layer.orig_x,layer.orig_y); $(layer).find(".cell").css({'border-style':'solid', 'border-color':'transparent'}); /*if(cx__capabilities.Dom2CSS) { @@ -226,7 +225,6 @@ function tb_setmode(layer,mode) break; case 1: /* point, but no click */ - moveTo(layer,layer.orig_x,layer.orig_y); $(layer).find(".cell").css({'border-style':wgtrGetServerProperty(layer, 'border_style', 'outset'), 'border-color':wgtrGetServerProperty(layer, 'border_color', '#c0c0c0')}); /*if(cx__capabilities.Dom2CSS) { @@ -259,7 +257,6 @@ function tb_setmode(layer,mode) break; case 2: /* point and click */ - moveTo(layer,layer.orig_x+1,layer.orig_y+1); var bstyle = wgtrGetServerProperty(layer, 'border_style', 'outset'); if (bstyle == 'outset') bstyle = 'inset'; diff --git a/centrallix-os/sys/js/htdrv_treeview.js b/centrallix-os/sys/js/htdrv_treeview.js index b3d4f1873..e7f9b40f0 100644 --- a/centrallix-os/sys/js/htdrv_treeview.js +++ b/centrallix-os/sys/js/htdrv_treeview.js @@ -1,4 +1,4 @@ -// Copyright (C) 1998-2004 LightSys Technology Services, Inc. +// Copyright (C) 1998-2026 LightSys Technology Services, Inc. // // You may use these files and this library under the terms of the // GNU Lesser General Public License, Version 2.1, contained in the @@ -38,7 +38,6 @@ function tv_new_layer(width,pdoc,l) else if(cx__capabilities.Dom1HTML) { nl = document.createElement('DIV'); - if (width) nl.style.width = width + 'px'; nl.className = l.divclass; //setClip(0, width, 0, 0); pg_set_style(nl, 'position','absolute'); @@ -1297,7 +1296,7 @@ function tv_mouseover(e) if (e.kind == 'tv') { cn_activate(e.mainlayer, 'MouseOver'); - if (getClipWidth(e.layer) <= getdocWidth(e.layer)+2 && e.layer.link_txt) + if (e.layer.link_txt) e.layer.tipid = pg_tooltip(e.layer.link_txt, e.pageX, e.pageY); } return EVENT_CONTINUE | EVENT_ALLOW_DEFAULT_ACTION; diff --git a/centrallix-os/sys/js/htdrv_window.js b/centrallix-os/sys/js/htdrv_window.js index b551430f4..6f8459f79 100644 --- a/centrallix-os/sys/js/htdrv_window.js +++ b/centrallix-os/sys/js/htdrv_window.js @@ -1,4 +1,4 @@ -// Copyright (C) 1998-2004 LightSys Technology Services, Inc. +// Copyright (C) 1998-2026 LightSys Technology Services, Inc. // // You may use these files and this library under the terms of the // GNU Lesser General Public License, Version 2.1, contained in the @@ -9,7 +9,13 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. -//$(".wn") + +// Resize listener that updates all windows so they remain on the page by +// re-calling wn_do_move_internal() with whatever values were last used. +window.addEventListener('resize', () => wn_list.forEach((wn) => { + const { pg_attract, wn_new_x, wn_new_y } = wn.resize_data; + wn_do_move_internal(wn, pg_attract, wn_new_x, wn_new_y); +})); var wn_popped = {}; @@ -41,6 +47,7 @@ function wn_init(param) ifc_init_widget(l); l.destroy_widget = wn_deinit; + // Determine titlebar. if (titlebar) { htr_init_layer(titlebar,l,"wn"); @@ -48,6 +55,7 @@ function wn_init(param) } else titlebar = l; + l.titlebar = titlebar; l.keep_kbd_focus = true; l.ContentLayer = param.clayer; @@ -136,6 +144,13 @@ function wn_init(param) pg_addsched_fn(window, "pg_reveal_event", [l,l,'Reveal'], 0); } + // Setup responsive movement. + l.resize_data = { + pg_attract: 0, + wn_new_x: getPageX(l), + wn_new_y: getPageY(l), + }; + // force on page... if (getPageY(l) + l.orig_height > getInnerHeight()) { @@ -152,9 +167,7 @@ function wn_init(param) function wn_action_point(aparam) { - var divs = htutil_point(this, aparam.X, aparam.Y, aparam.AtWidget, aparam.BorderColor, aparam.FillColor, this.point1, this.point2); - this.point1 = divs.p1; - this.point2 = divs.p2; + htr_action_point(this, aparam); } // Popup - pops up a window in the way that a menu might pop up. @@ -530,7 +543,6 @@ function wn_close(l) { if (l.is_modal) pg_setmodal(l, false); if (wn_popped[l.id]) delete wn_popped[l.id]; - //l.is_modal = false; l.no_close = false; l.extended_region = null; if (l.popped_above) @@ -542,7 +554,7 @@ function wn_close(l) { wn_close(l.has_popup); } - if (l.closetype == 0 || !cx__capabilities.Dom0NS) + if (l.closetype == 0) { htr_setvisibility(l,'hidden'); $(l).css({display:"none"}); @@ -551,30 +563,8 @@ function wn_close(l) l.is_visible = 0; } else - { - if(cx__capabilities.Dom0NS) - { - st = new Date(); - var speed = 20; - var duration = 150; - var sizeX = 0; - var sizeY = 0; - if (l.closetype & 1) - { - var toX = Math.ceil(getClipWidth(l)/2); - sizeX = Math.ceil(toX*speed/duration); - } - if (l.closetype & 2) - { - var toY = Math.ceil(getClipHeight(l)/2); - sizeY = Math.ceil(toY*speed/duration); - } - wn_graphical_close(l,speed,sizeX,sizeY); - } - else - { - alert("close type " + l.closetype + " is not implimented for this browser"); - } + { + alert("close type " + l.closetype + " is not implemented for this browser"); } } @@ -660,33 +650,71 @@ function wn_setvisibility(aparam) } } -function wn_domove2() - { +/*** This function does movement without worrying about global variables, + *** resize observers, etc. It just takes params and does movement. + *** + *** This function does handle snapping to edges and preventing windows from + *** being moved too far outside the viewport. + *** + *** @param wn_current The window to affect. + *** @param pg_attract The number of pixels from the edge of the viewport at + *** which windows snap to the edge. (Seems to always be 0.) + *** @param wn_new_x The new x coordinate for moving the window. + *** @param wn_new_y The new y coordinate for moving the window. + ***/ +function wn_do_move_internal(wn_current, pg_attract, wn_new_x, wn_new_y) + { + /** Get useful values. **/ + const { innerWidth, innerHeight } = window; + const window_width = getClipWidth(wn_current); + const window_height = getClipHeight(wn_current); + + /** Calculate available width and height, taking the sizes of scrollbars into account. **/ + const available_width = innerWidth - ((document.height - innerHeight - 2 >= 0) ? 15 : 0); + const available_height = innerHeight - ((document.width - innerWidth - 2 >= 0) ? 15 : 0); + let new_x, new_y; + + /** X: Handle snapping to edges. **/ + if (Math.isBetween(-pg_attract, wn_new_x, pg_attract)) new_x = 0; + else if (Math.isBetween(available_width - pg_attract, wn_new_x + window_width, available_width + pg_attract)) + new_x = available_width - window_width; + + /** X: Prevent windows getting lost off the left side of the page. **/ + else if (wn_new_x + window_width < 24) new_x = 24 - window_width; + else if (wn_new_x > available_width - 32) new_x = available_width - 32; + /** X: Default case, no movement needed. **/ + else new_x = wn_new_x; + + /** Y: Handle snapping to edges. **/ + if (Math.isBetween(-pg_attract, wn_new_y, pg_attract)) new_y = 0; + else if (Math.isBetween(available_height - pg_attract, wn_new_y + window_height, available_height + pg_attract)) + new_y = available_height - window_height; + + /** Y: Prevent windows from going too far off the screen. **/ + else new_y = Math.clamp(0, wn_new_y, available_height - 24); + + /** Move the window to the new location. **/ + moveToAbsolute(wn_current, new_x, new_y); + + /** Clicking and dragging a window is not a click. **/ + wn_current.clicked = 0; } -function wn_domove() +function wn_do_move() { - if (wn_current != null) - { - var ha=(document.height-window.innerHeight-2)>=0?15:0; - var va=(document.width-window.innerWidth-2)>=0?15:0; - var newx,newy; - if (wn_newx < pg_attract && wn_newx > -pg_attract) newx = 0; - else if (wn_newx+getClipWidth(wn_current) > window.innerWidth-ha-pg_attract && wn_newx+ getClipWidth(wn_current) < window.innerWidth-ha+pg_attract) - newx = window.innerWidth-ha-pg_get_style(wn_current,'clip.width'); - else if (wn_newx+getClipWidth(wn_current) < 25) newx = 25-pg_get_style(wn_current,'clip.width'); - else if (wn_newx > window.innerWidth-35-ha) newx = window.innerWidth-35-ha; - else newx = wn_newx; - if (wn_newy<0) newy = 0; - else if (wn_newy > window.innerHeight-12-va) newy = window.innerHeight-12-va; - else if (wn_newy < pg_attract) newy = 0; - else if (wn_newy+getClipHeight(wn_current) > window.innerHeight-va-pg_attract && wn_newy+getClipHeight(wn_current) < window.innerHeight-va+pg_attract) - newy = window.innerHeight-va-pg_get_style(wn_current,'clip.height'); - else newy = wn_newy; - moveToAbsolute(wn_current,newx,newy); - wn_current.clicked = 0; - } + /** Dereference globals once for performance. **/ + const { wn_current, pg_attract, wn_new_x, wn_new_y } = window; + + /** No window is selected, so we don't have to move anything. **/ + if (wn_current === null) return true; + + /** Call the unresponsive version. **/ + wn_do_move_internal(wn_current, pg_attract, wn_new_x, wn_new_y); + + /** Update params for future resize calls. **/ + wn_current.resize_data = { pg_attract, wn_new_x, wn_new_y }; + return true; } @@ -735,12 +763,14 @@ function wn_mousedown(e) else if ((e.mainlayer.has_titlebar && cx__capabilities.Dom0NS && e.pageY < e.mainlayer.pageY + 24) || (cx__capabilities.Dom1HTML && e.layer.subkind == 'titlebar' )) { + /** Initiate a window drag. **/ wn_current = e.mainlayer; wn_msx = e.pageX; wn_msy = e.pageY; - wn_newx = null; - wn_newy = null; + wn_new_x = null; + wn_new_y = null; wn_moved = 0; + e.layer.style.cursor = 'grabbing'; if (!cx__capabilities.Dom0IE) wn_windowshade_ns_moz(e.mainlayer); return EVENT_CONTINUE | EVENT_PREVENT_DEFAULT_ACTION; } @@ -775,9 +805,13 @@ function wn_mouseup(e) if (wn_current != null) { if (wn_moved == 0) wn_bring_top(wn_current); + wn_current.titlebar.style.cursor = 'grab'; } if (e.kind == 'wn') cn_activate(e.mainlayer, 'MouseUp'); + + /** End the active window drag (if one exists). **/ wn_current = null; + return EVENT_CONTINUE | EVENT_ALLOW_DEFAULT_ACTION; } @@ -789,17 +823,17 @@ function wn_mousemove(e) wn_current.clicked = 0; if (wn_current.tid) clearTimeout(wn_current.tid); wn_current.tid = null; - if (wn_newx == null) + if (wn_new_x == null) { - wn_newx = getPageX(wn_current) + e.pageX-wn_msx; - wn_newy = getPageY(wn_current) + e.pageY-wn_msy; + wn_new_x = getPageX(wn_current) + e.pageX-wn_msx; + wn_new_y = getPageY(wn_current) + e.pageY-wn_msy; } else { - wn_newx += (e.pageX - wn_msx); - wn_newy += (e.pageY - wn_msy); + wn_new_x += (e.pageX - wn_msx); + wn_new_y += (e.pageY - wn_msy); } - setTimeout(wn_domove,60); + wn_do_move(); wn_moved = 1; wn_msx = e.pageX; wn_msy = e.pageY; diff --git a/centrallix-os/sys/js/startup.js b/centrallix-os/sys/js/startup.js index 7d645f044..a9526f8d6 100644 --- a/centrallix-os/sys/js/startup.js +++ b/centrallix-os/sys/js/startup.js @@ -1,4 +1,4 @@ -// Copyright (C) 1998-2006 LightSys Technology Services, Inc. +// Copyright (C) 1998-2026 LightSys Technology Services, Inc. // // You may use these files and this library under the terms of the // GNU Lesser General Public License, Version 2.1, contained in the @@ -30,10 +30,9 @@ function startup() // are we mobile? var is_mobile = (window.navigator.userAgent.indexOf('Mobile') >= 0); - loc = loc.replace(new RegExp('([?&])cx__geom[^&]*([&]?)'), - function (str,p1,p2) { return p2?p1:''; }); - loc = loc.replace(new RegExp('([?&])cx__lkey[^&]*([&]?)'), - function (str,p1,p2) { return p2?p1:''; }); + const op = (str, p1, p2) => (p2) ? p1 : ''; + loc = loc.replace(new RegExp('([?&])cx__geom[^&]*(&?)'), op); + loc = loc.replace(new RegExp('([?&])cx__lkey[^&]*(&?)'), op); if (loc.indexOf('?') >= 0) loc += '&'; else diff --git a/centrallix-os/tests/test_objdrv_cluster_00.cluster b/centrallix-os/tests/test_objdrv_cluster_00.cluster new file mode 100644 index 000000000..496376982 --- /dev/null +++ b/centrallix-os/tests/test_objdrv_cluster_00.cluster @@ -0,0 +1,33 @@ +$Version=2$ +test_objdrv_cluster_00 "system/cluster" + { + // Declare parameters. + algorithm "cluster/parameter" { type = string; } + k "cluster/parameter" { type = integer; } + threshold "cluster/parameter" { type = double; } + sim "cluster/parameter" { type = string; } + + // Declare data source. + source = "/tests/test_objdrv_cluster_00.csv/rows"; + key_attr = "key"; + data_attr = "data"; + + // A cluster for searching with clustering. + cluster "cluster/cluster" + { + algorithm = runserver(:parameters:algorithm); + similarity_measure = "cosine"; + num_clusters = runserver(:parameters:k); + min_improvement = 0.0001; + max_iterations = 16; + window_size = 3; + seed = 1; + } + + search "cluster/search" + { + source = cluster; + similarity_measure = runserver(:parameters:sim); + threshold = runserver(:parameters:threshold); + } + } diff --git a/centrallix-os/tests/test_objdrv_cluster_00.csv b/centrallix-os/tests/test_objdrv_cluster_00.csv new file mode 100644 index 000000000..2b1412fc4 --- /dev/null +++ b/centrallix-os/tests/test_objdrv_cluster_00.csv @@ -0,0 +1,8 @@ +key,data +"0: john","john" +"1: johnny","johnny" +"2: johns","johns" +"3: mathew","mathew" +"4: MatTheW","MatTheW" +"5: mathews","mathews" +"6: math","math" diff --git a/centrallix-os/tests/test_objdrv_cluster_00.spec b/centrallix-os/tests/test_objdrv_cluster_00.spec new file mode 100644 index 000000000..ea69334ab --- /dev/null +++ b/centrallix-os/tests/test_objdrv_cluster_00.spec @@ -0,0 +1,14 @@ +$Version=2$ +test_objdrv_cluster_00 "application/filespec" + { + // General parameters. + filetype = csv; + header_row = yes; + header_has_titles = no; + annotation = "test_objdrv_cluster_00"; + key_is_rowid = yes; + + // Column specifications. + key "filespec/column" { type=string; id=1; } + data "filespec/column" { type=string; id=2; } + } diff --git a/centrallix-sysdoc/ClusterDriverRequirements-old.md b/centrallix-sysdoc/ClusterDriverRequirements-old.md new file mode 100644 index 000000000..601f41703 --- /dev/null +++ b/centrallix-sysdoc/ClusterDriverRequirements-old.md @@ -0,0 +1,186 @@ + +## Cluster Driver Specifications +### Cluster Open +```c +void* clusterOpen(pObject obj, int mask, pContentType sys_type, char* usr_type, pObjTrxTree* oxt); +``` +`clusterOpen()` shall... +- Create or read a node, as indicated by passed flags. + - Read flags from `obj->Mode`. + - If `O_EXCL` is specified, `O_CREAT` is specified, and there are no other elements in the path, create a new node. + - Otherwise attempt to read the previous object (in `obj->Prev`). + - If this fails and `O_CREAT` is specified, create a new node. + - If there is still no node, fail. +- Parse the provided path. + - Use `obj_internal_PathPart()` with the pathname in `obj->Pathname`. + - Not parse previous parts of the path already parsed by other drivers. + - Start at the `obj->SubPtr`-th path element (skipping `obj->SubPtr - 1` elements). + - Consume elements in the path until `obj_internal_PathPart()` returns `NULL`. + - Store the number of elements consumed in `obj->SubCnt`. +- Determine what data is being targeted from the parsed path. + - If the relevant part of the path contains only the name of the file, the driver shall set the target to root. + - If it contains the name of a valid (sub)cluster or search, the driver shall set the target to that (sub)cluster or search. + - Otherwise, the driver shall produce a descriptive error. +- Parse the provided structure file. + - Follow the spec given in `cluster-schema.cluster`. + - Produce descriptive errors when issues are detected. +- Return a new struct containing necessary information, including: + - The name, source path, and attribute name. + - All parameters (and a param list for scope), clusters, and searches. + - Each parameter shall be represented by a `pParam` object (see `params.h`). + - Each cluster shall be represented by a struct with information including: + - Its name, clustering algorithm, and similarity measure. + - The number of clusters to generate. + - If a k-means algorithm is specified, the improvement threshold. + - The maximum number of iterations to run. + - A list of subclusters with at least this information for each. + - Each search shall be represented by a struct with information including: + - Its name, threshold, and similarity measure. + - Its source, which is a valid cluster name of a cluster in the clusters list. + - Information about targets, derived from the path. + +### Cluster Close +```c +int clusterClose(void* inf_v, pObjTrxTree* oxt); +``` +`clusterClose()` shall... +- Free all allocated data in the driver struct. +- Close any open files or the like in the driver struct. +- Return 0. + +### Cluster Open Query +```c +void* clusterOpenQuery(void* inf_v, pObjQuery query, pObjTrxTree* oxt); +``` +`clusterOpenQuery()` shall... +- Return a query struct that can be passed to `clusterQueryFetch()`. + - This struct shall contain an index to the last row accessed (starting at 0). + - This struct shall contain a pointer to the driver data. + +### Cluster Query Fetch +```c +void* clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) +``` +`clusterQueryFetch()` shall... +- If the driver struct targets the root node, this function shall produce an error. +- If the driver struct targets an entry, this function shall produce a different error. +- If the driver targets a cluster or search, this function shall return a driver struct targetting the cluster or search *entry* (respectively) indicated by the query struct's row pointer, and increment the pointer. + - Exception: If no data remains, this function shall return `NULL` instead. + - This request shall cause clustering / searching to execute, if it has not executed already. + +### Cluster Query Close +```c +int clusterQueryClose(void* qy_v, pObjTrxTree* oxt); +``` +`clusterQueryClose()` shall... +- Free all allocated data in the query struct. +- Close any open files or the like in the query struct. +- Return 0. + +### Cluster Get Attribute Type +```c +int clusterGetAttrType(void* qy_v, pObjTrxTree* oxt); +``` +`clusterGetAttrType()` shall... +- Return the `DATA_T_...` type of the requested attribute, or `DATA_T_UNAVAILABLE` if the attribute does not exist. +- The name, content_type, inner_type, and outer_type attributes shall be of type `DATA_T_STRING`. +- The last_modification attribute shall be of type `DATA_T_DATETIME`. +- If the target is root... + - The source and attr_name attributes shall be of type `DATA_T_STRING`. +- If the target is a cluster... + - The algorithm and similarity_measure attributes shall be of type `DATA_T_STRING`. + - The num_clusters and max_iterations attributes shall be of type `DATA_T_INTEGER`. + - The improvement_threshold and average_similarity attributes shall be of type `DATA_T_DOUBLE`. +- If the target is a search... + - The source and similarity_measure attribute shall be of type `DATA_T_STRING`. + - The threshold attribute shall be of type `DATA_T_DOUBLE`. +- If the target is a cluster entry... + - The val attribute shall be of type `DATA_T_INTEGER`. + - The sim attribute shall be of type `DATA_T_DOUBLE`. +- If the target is a search entry... + - The val1 and val2 attribute shall be of type `DATA_T_INTEGER`. + - The sim attribute shall be of type `DATA_T_DOUBLE`. + +### Cluster Get Attribute Value +```c +int clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pObjTrxTree* _); +``` +`clusterGetAttrValue()` shall... +- If the given datatype does not match that returned from `clusterGetAttrType()`, the function shall produce an error. +- Requesting the name attribute shall produce the following values, depending on the target: + - If the target is root, the name in the driver struct (aka. the one specified in the .cluster file) shall be produced. + - If the target is a cluster or cluster entry, the name of the cluster shall be produced. + - If the target is a search or search entry, the name of the search shall be produced. +- Requesting the annotation shall produce some string describing the driver. +- Requesting the outer_type shall produce "system/row". +- Requesting the inner_type or content_type shall produce "system/void". (All path elements are consumed.) +- If the target is root... + - Requesting source shall produce the source path. + - Requesting attr_name shall produce the attribute name. +- If the target is a cluster... + - Requesting algorithm shall produce the name of the clustering algorithm. + - Requesting similarity_measure shall produce the name of the similarity measure. + - Requesting num_clusters shall produce the number of clusters. + - Requesting max_iterations shall produce the maximum number of iterations. + - Requesting improvement_threshold shall produce the minimum improvement threshold. + - Requesting average_similarity shall produce the average size of clusters, running clustering / searching algorithms, if necessary. +- If the target is a search... + - Requesting source shall produce the name of the source cluster for the search. + - Requesting similarity_measure shall produce the name of the similarity measure. + - Requesting threshold shall produce the filtering threshold. +- If the target is a cluster entry... + - Requesting val shall produce the value of the data point in this cluster. + - Requesting sim shall produce the similarity of the data point to the center of the cluster. +- If the target is a cluster entry... + - Requesting val1 or val2 shall produce the first and second value (respectively)detected in this search. + - Requesting sim shall produce the similarity of these two data points. + + +### Cluster Get First Attribute +```c +char* clusterGetFirstAttr(void* inf_v, pObjTrxTree oxt); +``` +`clusterGetFirstAttr()` shall... +- Reset the current attribute index on the driver struct to 0. +- Return the value of invoking `clusterGetNextAttr()`. + +### Cluster Get Next Attribute +```c +char* clusterGetNextAttr(void* inf_v, pObjTrxTree oxt); +``` +`clusterGetNextAttr()` shall... +- Return the attribute name at the attribute index given by the driver struct in the list of attributes based on the target type. +- Return `NULL` if the end of the list has been reached. +- Increase the attribute index on the driver struct by 1. + +- The attribute name list for a targetting root shall include "source" and "attr_name". +- The attribute name list for a targetting a cluster shall include "algorithm", "similarity_measure", "num_clusters", "improvement_threshold", and "max_iterations". +- The attribute name list for a targetting a search shall include "source", "threshold", and "similarity_measure". +- The attribute name list for a targetting a cluster entry shall include "val" and "sim". +- The attribute name list for a targetting a search entry shall include "val1", "val2", and "sim". + +### Cluster Get Next Attribute +```c +int clusterInfo(void* inf_v, pObjectInfo info); +``` +`clusterInfo()` shall... +- Provide the OBJ_INFO_F_CANT_ADD_ATTR flag. +- Provide the OBJ_INFO_F_CANT_HAVE_CONTENT flag. +- Provide the OBJ_INFO_F_NO_CONTENT flag. +- If the target is a root... + - Provide the OBJ_INFO_F_CAN_HAVE_SUBOBJ flag. + - Provide the OBJ_INFO_F_SUBOBJ_CNT_KNOWN flag. + - Provide the OBJ_INFO_F_HAS_SUBOBJ flag if there is at least one cluster or search. + - Provide the OBJ_INFO_F_NO_SUBOBJ flag otherwise. + - Provide the total number of clusters and searches as the number of subobjects. +- If the target is a cluster... + - Provide the OBJ_INFO_F_CAN_HAVE_SUBOBJ flag. + - Provide the OBJ_INFO_F_HAS_SUBOBJ flag. + - If the algorithm has been run, provide OBJ_INFO_F_SUBOBJ_CNT_KNOWN flag and the number of data points clustered as the number of subobjects. +- If the target is a search... + - Provide the OBJ_INFO_F_CAN_HAVE_SUBOBJ flag. + - If the algorithm has been run... + - Provide OBJ_INFO_F_SUBOBJ_CNT_KNOWN flag and the number of elements found by the search as the number of subobjects. + - Provide the OBJ_INFO_F_HAS_SUBOBJ flag if at least one element was found. +- If the target is a cluster entry or a search entry... + - Provide the OBJ_INFO_F_CANT_HAVE_SUBOBJ flag. \ No newline at end of file diff --git a/centrallix-sysdoc/CommentAnchorsExtension.md b/centrallix-sysdoc/CommentAnchorsExtension.md new file mode 100644 index 000000000..756eb463a --- /dev/null +++ b/centrallix-sysdoc/CommentAnchorsExtension.md @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# The Comment Anchors Extension +A couple of files in Centrallix use the [Comment Anchors VSCode extension](https://marketplace.visualstudio.com/items?itemName=ExodiusStudios.comment-anchors) from Starlane Studios. This extension allows developers to use certain text in comments to create links to other files, or other locations within the same file. This extension is _optional_ and developing in Centrallix without it is fine. If you don't intend to use it, you can safely ignore `LINK`, `ANCHOR`, and similar comments intended for it, although please try not to break them. + + +## Table of Contents +- [The Comment Anchors Extension](#the-comment-anchors-extension) + - [Table of Contents](#table-of-contents) + - [Basic Features](#basic-features) + - [Links](#links) + - [Anchors](#anchors) + - [Quick Warning](#quick-warning) + - [Informing other developers](#informing-other-developers) + + +## Basic Features +Below is a quick summary of the primary features from the extension that are used in Centrallix. This is intended to help get developers up to speed easily without needing to sift through other features that aren't currently in use. + +_For more information, visit the [extension page](https://marketplace.visualstudio.com/items?itemName=ExodiusStudios.comment-anchors)._ + + +### Links +Create a link by writing `LINK ` at the start of a line in a documentation comment. Control click the link to jump to the destination in the editor. A link might look like: +```c +/*** To read the license, click the link below: + *** LINK LICENSE + ***/ +``` +- Use `:line-number` to link to a specific line number. +- Use `#` to link to an [anchor](#anchors) with that id. +- Intra-file links don't need to specify a file (e.g. `LINK #example`). + +Note: Invalid or broken links to a location in a file will sometimes default to the first line of the file. + + +### Anchors +Create an anchor by writing `ANCHOR[id=]` at the start of a line in a documentation comment, like this: +```c +/*** ANCHOR[id=intro] + *** This file will teach you how to never write a memory error in C ever again... + ***/ +``` +Add `#ID` to the end of a link to target the anchor with that ID (useful for navigating long files, such as `objdrv_cluster.c`). + + +## Quick Warning +The comment anchors extension is a little less robust than I would like. Sometimes, placing characters before or after links or anchors on the same line will cause them to break. The extension is sometimes a little finicky. + + +## Informing other developers +When using the comment anchors extension, include something closely resembling the following snippet at the top of the file, just below the copywrite notice and include statements: + +**.c or .h file** +```c +/*** This file uses the optional Comment Anchors VSCode extension, documented + *** with CommentAnchorsExtension.md in centrallix-sysdoc. + ***/ +``` + +**.md file** +```md + +``` \ No newline at end of file diff --git a/centrallix-sysdoc/GCC_Dependencies.md b/centrallix-sysdoc/GCC_Dependencies.md new file mode 100644 index 000000000..5467ee13b --- /dev/null +++ b/centrallix-sysdoc/GCC_Dependencies.md @@ -0,0 +1,20 @@ +# GCC Dependencies + +Author: Israel Fuller + +Date: Descember 4, 2025 + +## Table of Contents +- [GCC Dependencies](#gcc-dependencies) + - [Table of Contents](#table-of-contents) + - [Introduction](#intoduction) + - [List of Dependencies](#list-of-dependencies) + +## Intoduction +This document tracks dependencies on the GCC toolchain in the centrallix codebase. As code is added which relies on GCC specific behavior, such additions should be noted here to make possible use of a different toolchain (e.g. LLVM) in the future less painful. + +## List of Dependencies +- `util.h` & `magic.h`: Use `__typeof__` and `({ ... })` in macros to avoid double-computation. + +## Notes +`__FILE__` and `__LINE__` are not dependencies as they were added in C90. See [this page](https://gcc.gnu.org/onlinedocs/cpp/Standard-Predefined-Macros.html) for information about predefined macros. diff --git a/centrallix-sysdoc/Libraries/mtask.md b/centrallix-sysdoc/Libraries/mtask.md new file mode 100644 index 000000000..26982ff34 --- /dev/null +++ b/centrallix-sysdoc/Libraries/mtask.md @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# Handling Network Connection + +**Author**: Greg Beeley + +**Date**: January 13, 1999 + +**Updated**: December 11, 2025 + +**License**: Copyright (C) 2001-2025 LightSys Technology Services. See LICENSE.txt for more information. + + +## Table of Contents +- [Handling Network Connection](#the-mtsession-library) + - [Introduction](#introduction) + - [netConnectTCP()](#netconnecttcp) + - [netCloseTCP()](#netclosetcp) + - [fdWrite()](#fdwrite) + - [fdRead()](#fdread) + + +## Introduction +The `MTASK` module provides simple and easy TCP/IP connectivity. It includes many functions, only a few of which are documented below: + +- ⚠️ **Warning**: This documentation is incomplete, as many relevant functions are not explained here. You can help by expanding it. + + +## netConnectTCP() +```c +pFile netConnectTCP(char* host_name, char* service_name, int flags); +``` +This function creates a client socket and connects it to a server on a given TCP service/port and host name. It takes the following three parameters: +- `host_name`: The host name or ascii string for the host's ip address. +- `service_name`: The name of the service (from `/etc/services`) or its numeric representation as a string. +- `flags`: Normally left 0. + +- 📖 **Note**: The `NET_U_NOBLOCK` flag causes the function to return immediately even if the connection is still being established. Further reads and writes will block until the connection either establishes or fails. + +This function returns the connection file descriptor if successful, or `NULL` if an error occurs. + + +## netCloseTCP() +```c +int netCloseTCP(pFile net_filedesc, int linger_msec, int flags); +``` +This function closes a network connection (either a TCP listening, server, or client socket). It will also optionally waits up to `linger_msec` milliseconds (1/1000 seconds) for any data written to the connection to make it to the other end before performing the close. If `linger_msec` is set to 0, the connection is aborted (reset). The linger time can be set to 1000 msec or so if no writes were performed on the connection prior to the close. If a large amount of writes were performed immediately prior to the close, offering to linger for a few more seconds (perhaps 5 or 10 by specifying 5000 or 10000 msec) can be a good idea. + + +## fdWrite() +```c +int fdWrite(pFile filedesc, char* buffer, int length, int offset, int flags); +``` +This function writes data to an open file descriptor, from a given `buffer` and `length` of data to write. It also takes an optional seek `offset` and and `flags`, which can be zero or more of: +- `FD_U_NOBLOCK` - If the write can't be performed immediately, don't perform it at all. +- `FD_U_SEEK` - The `offset` value is valid. Seek to it before writing. Not allowed for network connections. +- `FD_U_PACKET` - *ALL* of the data specified by `length` in `buffer` must be written. Normal `write()` semantics in UNIX state that not all data has to be written, and the number of bytes actually written is returned. Setting this flag makes sure all data is really written before returning. + + +## fdRead() +```c +int fdRead(pFile filedesc, char* buffer, int maxlen, int offset, int flags); +``` +This function works the same as [`fdWrite()`](#fdwrite) except that it reads data instead of writing it. It takes the same flags as above, except that `FD_U_PACKET` now requires that all of `maxlen` bytes must be read before returning. This is good for reading a packet of a known length that might be broken up into fragments by the network (TCP/IP has a maximum frame transmission size of about 1450 bytes). diff --git a/centrallix-sysdoc/Libraries/mtsession.md b/centrallix-sysdoc/Libraries/mtsession.md new file mode 100644 index 000000000..84990ab32 --- /dev/null +++ b/centrallix-sysdoc/Libraries/mtsession.md @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# The MTSession Library + +**Author**: Greg Beeley + +**Date**: January 13, 1999 + +**Updated**: December 11, 2025 + +**License**: Copyright (C) 2001-2025 LightSys Technology Services. See LICENSE.txt for more information. + + +## Table of Contents +- [The MTSession Library](#the-mtsession-library) + - [Introduction](#introduction) + - [mssUserName()](#mssusername) + - [mssPassword()](#msspassword) + - [mssSetParam()](#msssetparam) + - [mssGetParam()](#mssgetparam) + - [mssError()](#msserror) + - [mssErrorErrno()](#msserrorerrno) + + +## Introduction +The mtsession (MSS) module is used for session authentication, error reporting, and for storing session-wide variables such as the current date format, username, and password (used when issuing a login request to a remote server). Care should be taken in the use of Centrallix that its coredump files are NOT in a world-readable location, as the password will be visible in the coredump file (or just ulimit the core file size to 0). + + +- ⚠️ **Warning**: This documentation is incomplete, as several relevant functions are not explained here. You can help by expanding it. + + +## mssInitialize() +```c +int mssInitialize(char* authmethod, char* authfile, char* logmethod, int logall, char* log_progname); +``` +This function initializes the session manager and sets global variables used in this module. It returns 0 if successful and -1 if an error occurs. + + +## mssUserName() +```c +char* mssUserName(); +``` +This function returns the current user name, or `NULL` an error occurs. + + +## mssPassword() +```c +char* mssPassword(); +``` +This function returns the current user's password that they used to log into Centrallix, or `NULL` an error occurs. + + +## mssSetParam() +```c +int mssSetParam(char* paramname, char* param); +``` +This function sets the session parameter of the provided name (`paramname`) to the provided value (`param`). The parameter MUST be a string value. This function returns 0 if successful, or -1 an error occurs. + + +## mssGetParam() +```c +char* mssGetParam(char* paramname); +``` +Returns the value of a session parameter of the provided name (`paramname`), or `NULL` if an error occurs. Common session parameters include: +- `dfmt`: The current date format. +- `mfmt`: The current money format. +- `textsize`: The current max text size from a read of an object's content via `objGetAttrValue(obj, "objcontent", POD(&str))` + + +## mssError() +```c +int mssError(int clr, char* module, char* message, ...); +``` +Formats and caches an error message for return to the user. This function returns 0 if successful, or -1 if an error occurred. + +| Parameter | Type | Description +| --------- | ------------- | ------------ +| crl | int | If set to 1, all previous error messages are cleared. Set this when the error is initially discovered and no other module is likely to have made a relevant `mssError()` call for the current error. +| module | char* | A two-to-five letter abbreviation of the module reporting the error. This is typically the module or driver's abbreviation prefix in full uppercase letters (although that is not required). This is intended to help the developer find the source of the error faster. +| message | char* | A string error message, accepting format specifiers like `%d` and `%s` which are supplied by the argument list, similar to `printf()`. +| ... | ... | Parameters for the formatting. + +Errors that occur inside a session context are normally stored up and not printed until other MSS module routines are called to fetch the errors. Errors occurring outside a session context (such as in Centrallix's network listener) are printed to Centrallix's standard output immediately. + +The `mssError()` function is not required to be called at every function nesting level when an error occurs. For example, if the expression compiler returns -1 indicating that a compilation error occurred, it has probably already added one or more error messages to the error list. The calling function should only call `mssError()` if doing so would provide additional context or other useful information (e.g. _What_ expression failed compilation? _Why_ as an expression being compiled? etc.). However, it is far easier to give too little information that too much, so it can often be best to air on the side of calling `mssError()` with information that might be irrelevant, rather than skipping it and leaving the developer confused. + +- 📖 **Note**: The `mssError()` routines do not cause the calling function to return or exit. The function must still clean up after itself and return an appropriate value (such as `-1` or `NULL`) to indicate failure. + +- ⚠️ **Warning**: Even if `-1` is returned, the error message may still be sent to the user in some scenarios. This is not guaranteed, though. + +- ⚠️ **Warning**: `%d` and `%s` are the ONLY supported format specifier for this function. **DO NOT** use any other format specifiers like `%lf`, `%u`, `%lu`, `%c` etc. **DO NOT** attempt to include `%%` for a percent symbol in your error message, as misplaced percent symbols often break this function. If you wish to use these features of printf, it is recommended to print the error message to a buffer and pass that buffer to `mssError()`, as follows: + ```c + char err_buf[256]; + snprintf(err_buf, sizeof(err_buf), + "Incorrect values detected: %u, %g (%lf), '%c'", + unsigned_int_value, double_value, char_value + ); + if (mssError(1, "EXMPL", "%s", err_buf) != 0) + { + fprintf(stderr, "ERROR! %s\n", err_buf); + } + return -1; + ``` + + + +## mssErrorErrno() +```c +int mssErrorErrno(int clr, char* module, char* message, ...); +``` +This function works the same way as [`mssError`](#mssError), except checks the current value of `errno` and includes a description of any error stored there. This is useful if a system call or other library function is responsible for this error. diff --git a/centrallix-sysdoc/Libraries/newmalloc.md b/centrallix-sysdoc/Libraries/newmalloc.md new file mode 100644 index 000000000..cba631154 --- /dev/null +++ b/centrallix-sysdoc/Libraries/newmalloc.md @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# Memory Management in Centrallix + +**Author**: Greg Beeley + +**Date**: January 13, 1999 + +**Updated**: December 11, 2025 + +**License**: Copyright (C) 2001-2025 LightSys Technology Services. See LICENSE.txt for more information. + + +## Table of Contents +- [Memory Management in Centrallix](#objectsystem-driver-interface) + - [Introduction](#introduction) + - [nmMalloc()](#nmmalloc) + - [nmFree()](#nmfree) + - [nmStats()](#nmstats) + - [nmRegister()](#nmregister) + - [nmDebug()](#nmdebug) + - [nmDeltas()](#nmdeltas) + - [nmSysMalloc()](#nmsysmalloc) + - [nmSysRealloc()](#nmsysrealloc) + - [nmSysStrdup()](#nmsysstrdup) + - [nmSysFree()](#nmsysfree) + + +## Introduction +Centrallix has its own memory management wrapper that caches deallocated blocks of memory by size for faster reuse. This wrapper also detects double-freeing of blocks (sometimes), making debugging of memory problems just a little bit easier. + +In addition, the memory manager provides statistics on the hit ratio of allocated blocks coming from the lists vs. `malloc()`, and on how many blocks of each size/type are `malloc()`ed and cached. This information can be helpful for tracking down memory leaks. Empirical testing has shown an increase of performance of around 50% or more in programs that use newmalloc. + +One caveat is that this memory manager does not provide `nmRealloc()` function, only `nmMalloc()` and `nmFree()`. Thus, either `malloc()`, `free()`, and `realloc()` or [`nmSysMalloc()`](#nmsysmalloc), [`nmSysFree()`](#nmsysfree), and [`nmSysRealloc()`](#nmsysrealloc) should be used for blocks of memory that might vary in size. + +The newmalloc module can be accessed by adding `#include "cxlib/newmalloc.h"` to the include section of a .c file in centrallix, or `#include "newmalloc.h"` in centrallix-lib. + +- 📖 **Note**: This memory manager is usually the wrong choice for blocks of memory of arbitrary, inconsistent sizes. It is intended for allocating structures quickly that are of a specific size. For example, allocated space for a struct that is always the same size. + +- 🥱 **tl;dr**: Use `nmMalloc()` for structs, not for strings. + +- ⚠️ **Warning**: Do not mix and match, even though calling `free()` on a block obtained from `nmMalloc()` or calling `nmFree()` on a block obtained from `malloc()` might not crash the program immediately. However, it may result in either inefficient use of the memory manager, or a significant memory leak, respectively. These practices will also lead to incorrect results from the statistics and block count mechanisms. + +The newmalloc module provides the following functions: + + +## nmMalloc() +```c +void* nmMalloc(int size); +``` +This function allocates a block of the given `size`. It returns `NULL` if the memory could not be allocated. + + +## nmFree() +```c +void nmFree(void* ptr, int size); +``` +This function frees the block of memory. + +- ⚠️ **Warning**: The caller **must know the size of the block.** Getting this wrong is very bad!! For structures, this is trivial, simply use `sizeof()`, exactly the same as with `nmMalloc()`. + + +## nmStats() +```c +void nmStats(void); +``` +Prints statistics about the memory manager, for debugging and optimizing. + +For example: +``` +NewMalloc subsystem statistics: + nmMalloc: 20244967 calls, 19908369 hits (98.337%) + nmFree: 20233966 calls + bigblks: 49370 too big, 32768 largest size +``` + +- ⚠️ **Warning**: Centrallix-lib must be built with the configure option `--enable-debugging` for this function to work. Otherwise, all the stats will be zeros. + + +## nmRegister() +```c +void nmRegister(int size, char* name); +``` +Registers an inteligent name for block of the specified size. This allows the memory manager to give more information when reporting block allocation counts. A given size can have more than one name. This function is optional and not required for any production usecases, but using it can make tracking down memory leaks easier. + +This function is usually called in a module's `Initialize()` function on each of the structures the module uses internally. + + +## nmDebug() +```c +void nmDebug(void); +``` +Prints a listing of block allocation counts, giving (by size): +- The number of blocks allocated but not yet freed. +- The number of blocks in the cache. +- The total allocations for this block size. +- A list of names (from [`nmRegister()`](#nmregister)) for that block size. + + +## nmDeltas() +```c +void nmDeltas(void); +``` +Prints a listing of all blocks whose allocation count has changed, and by how much, since the last `nmDeltas()` call. This function is VERY USEFUL FOR MEMORY LEAK DETECTIVE WORK. + + +## nmSysMalloc() +```c +void* nmSysMalloc(int size); +``` +Allocates memory without using the block-caching algorithm. This is roughly equivalent to `malloc()`, but pointers returned by malloc and this function are not compatible - i.e., you cannot `free()` something that was [`nmSysMalloc()`](#nmsysmalloc)'ed, nor can you [`nmSysFree()`](#nmsysfree) something that was `malloc()`'ed. + +- 📖 **Note**: This function is much better to use on variable-sized blocks of memory. `nmMalloc()` is better for fixed-size blocks, such as for structs. + + +## nmSysRealloc() +```c +void* nmSysRealloc(void* ptr, int newsize); +``` +Changes the size of an allocated block of memory that was obtained from [`nmSysMalloc()`](#nmsysmalloc), [`nmSysRealloc()`](#nmsysrealloc), or [`nmSysStrdup()`](#nmsysstrdup). The new pointer may be different if the block needs to be moved. This is the rough equivalent of `realloc()`. + +- 📖 **Note**: If you are `realloc()`'ing a block of memory and need to store pointers to data somewhere inside the block, it is often better to store an offset rather than a full pointer. This is because a full pointer becomes invalid if a [`nmSysRealloc()`](#nmsysrealloc) causes the block to move. + + +## nmSysStrdup() +```c +char* nmSysStrdup(const char* str); +``` +Allocates memory using the [`nmSysMalloc()`](#nmsysmalloc) function and copies the string `str` into this memory. It is a rough equivalent of `strdup()`. The resulting pointer can be free'd using [`nmSysFree()`](#nmsysfree). + + +## nmSysFree() +```c +void nmSysFree(void* ptr); +``` +Frees a block of memory allocated by [`nmSysMalloc()`](#nmsysmalloc), [`nmSysRealloc()`](#nmsysrealloc), or [`nmSysStrdup()`](#nmsysstrdup). diff --git a/centrallix-sysdoc/Libraries/xarray.md b/centrallix-sysdoc/Libraries/xarray.md new file mode 100644 index 000000000..4e67be437 --- /dev/null +++ b/centrallix-sysdoc/Libraries/xarray.md @@ -0,0 +1,219 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# The XArray Library + +**Author**: Greg Beeley + +**Date**: January 13, 1999 + +**Updated**: December 11, 2025 + +**License**: Copyright (C) 2001-2025 LightSys Technology Services. See LICENSE.txt for more information. + + +## Table of Contents +- [The XArray Library](#the-xarray-library) + - [Introduction](#introduction) + - [xaNew()](#xanew) + - [xaFree()](#xafree) + - [xaInit()](#xainit) + - [xaDeInit()](#xadeinit) + - [xaAddItem()](#xaadditem) + - [xaAddItemSorted()](#xaadditemsorted) + - [xaAddItemSortedInt32()](#xaadditemsortedint32) + - [xaGetItem()](#xagetitem) + - [xaFindItem()](#xafinditem) + - [xaFindItemR()](#xafinditemr) + - [xaRemoveItem()](#xaremoveitem) + - [xaClear()](#xaclear) + - [xaClearR()](#xaclearr) + - [xaCount()](#xacount) + - [xaInsertBefore()](#xainsertbefore) + - [xaInsertAfter()](#xainsertafter) + + +## Introduction +The xarray (xa) module is intended to manage sized growable arrays, similar to a light-weight arraylist implementation. It includes the `XArray`, which has the following fields: +- `nItems : int`: The number of items in the array. +- `nAlloc : int`: Internal variable to store the size of the allocated memory. +- `Items : void**`: The allocated array of items. + +- 📖 **Note**: Some code occasionally sets `nAlloc` to 0 after an XArray struct has been deinitialized to indicate that the relevant data is no longer allocated. Other than this, it is only used internally by the library. + +- ⚠️ **Warning**: Do not mix calls to [`xaNew()`](#xanew)/[`xaFree()`](#xafree) with calls to [`xaInit()`](#xainit)/[`xaDeInit()`](#xadeinit). Every struct allocated using new must be freed, and ever struct allocated using init must be deinitted. Mixing these calls can lead to memory leaks, bad frees, and crashes. + + +## xaNew() +```c +pXArray xaNew(int init_size); +``` +Allocates a new `XArray` struct on the heap (using [`nmMalloc()`](#nmmalloc) for caching) and returns a pointer to it, or returns `NULL` if an error occurs. + + +## xaFree() +```c +int xaFree(pXArray this); +``` +Frees a `pXArray` allocated using [`xaNew`](#xanew), returning 0 if successful or -1 if an error occurs. + + +## xaInit() +```c +int xaInit(pXArray this, int init_size); +``` +This function initializes an allocated (but uninitialized) xarray. It makes room for `init_size` items initially, but this is only an optimization. A typical value for `init_size` is 16. Remember to [`xaDeInit`](#xadeinit) this xarray, do **not** [`xaFree`](#xafree) it. + +This function returns 0 on success, or -1 if an error occurs. + + +## xaDeInit() +```c +int xaDeInit(pXArray this); +``` +This function de-initializes an xarray, but does not free the XArray structure itself. This is useful if the structure is a local variable allocated using [`xaInit()`](#xainit). + +This function returns 0 on success, or -1 if an error occurs. + +For example: +```c +XArray arr; +if (xaInit(&arr, 16) != 0) goto handle_error; + +/** Use the xarray. **/ + +if (arr.nAlloc != 0 && xaDeInit(&arr) != 0) goto handle_error; +arr.nAlloc = 0; +``` + + +## xaAddItem() +```c +int xaAddItem(pXArray this, void* item); +``` +This function adds an item to the end of the xarray. The item is assumed to be a `void*`, but this function will _not_ follow pointeres stored in the array. Thus, other types can be typecast and stored into that location (such as an `int`). + +This function returns 0 on success, or -1 if an error occurs. + + +## xaAddItemSorted() +```c +int xaAddItemSorted(pXArray this, void* item, int keyoffset, int keylen); +``` +This function adds an item to a sorted xarray while maintaining the sorted property. The value for sorting is expected to begin at the offset given by `keyoffset` and continue for `keylen` bytes. This function _will_ follow pointers are stored in the array so casting other types to store them is not allowed (as it is with [`xaAddItem()`](#xaadditem)). + + +## xaAddItemSortedInt32() +```c +int xaAddItemSortedInt32(pXArray this, void* item, int keyoffset) +``` + + + +## xaGetItem() +```c +void* xaGetItem(pXArray this, int index) +``` +This function returns an item given a specific index into the xarray, or `NULL` if the index is out of bounds. If the bounds check needs to be omitted for performance and the caller can otherwise verify that no out of bounds read is possible (e.g. because they are iterating from 0 to `xarray->nItems`), the caller should access `xarray->Items` directly. Either way, the result may need to be typecasted or stored in a variable of a specific type for it to be useable, and error checking for `NULL` values should be used. + + +## xaFindItem() +```c +int xaFindItem(pXArray this, void* item); +``` +This function returns array index for the provided item in the array, or -1 if the item could not be found. Requires an exact match, so two `void*` pointing to different memory with identical contents are not considered equal by this function. If the data is actually another datatype typecasted as a `void*`, all 8 bytes must be identical for a match. + +For example: +```c +void* data = &some_data; + +XArray xa; +xaInit(&xa, 16); + +... + +xaAddItem(&xa, data); + +... + +int item_id = xaFindItem(&xa, data); +assert(data == xa.Items[item_id]); +``` + + +## xaFindItemR() +```c +int xaFindItemR(pXArray this, void* item); +``` +This function works the same as [`xaFindItem()`](#xafinditem), however it iterates in reverse, giving a slight performance boost, especially for finding items near the end of the array. + + +## xaRemoveItem() +```c +int xaRemoveItem(pXArray this, int index) +``` +This function removes an item from the xarray at the given the index, then shifts all following items back to fill the gap created by the removal. XArray is not optimized for removing multiple items efficiently. This function returns 0 on success, or -1 if an error occurs. + + +## xaClear() +```c +int xaClear(pXArray this, int (*free_fn)(), void* free_arg); +``` +This function removes all elements from the xarray, leaving it empty. `free_fn()` is invoked on each element with a `void*` to the element to be freed as the first argument and `free_arg` as the second argument (the return value of `free_fn()` is always ignored). This function returns 0 on success (even if the `free_fn()` returns an error), or -1 if an error is detected. + + +## xaClearR() +```c +int xaClearR(pXArray this, int (*free_fn)(), void* free_arg); +``` +This function works the same as [`xaClear()`](#xaclear), except that it is slightly faster because the free function is evaluated on items in reverse order. + + +## xaCount() +```c +int xaCount(pXArray this); +``` +This function returns the number of items in the xarray, or -1 on error. It is equivalent to accessing `xarray->nItems` (although the latter expression will not return an error). + + +## xaInsertBefore() +```c +int xaInsertBefore(pXArray this, int index, void* item) +``` +This function inserts an item before the specified index, moving all following items forward to make space. The new item cannot be inserted past the end of the array. This function returns the index on success, or -1 if an error occurs. + + +## xaInsertAfter() +```c +int xaInsertAfter(pXArray this, int index, void* item) +``` +This function inserts an item after the specified index, moving all following items forward to make space. The new item cannot be inserted past the end of the array. This function returns the index on success, or -1 if an error occurs. diff --git a/centrallix-sysdoc/Libraries/xhash.md b/centrallix-sysdoc/Libraries/xhash.md new file mode 100644 index 000000000..daf16d186 --- /dev/null +++ b/centrallix-sysdoc/Libraries/xhash.md @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# The XHash Library + +**Author**: Greg Beeley + +**Date**: January 13, 1999 + +**Updated**: December 11, 2025 + +**License**: Copyright (C) 2001-2025 LightSys Technology Services. See LICENSE.txt for more information. + + +## Table of Contents +- [The XHash Library](#the-xhash-library) + - [Introduction](#introduction) + - [xhInitialize()](#xhinitialize) + - [xhInit()](#xhinit) + - [xhDeInit()](#xhdeinit) + - [xhAdd()](#xhadd) + - [xhRemove()](#xhremove) + - [xhLookup()](#xhlookup) + - [xhClear()](#xhclear) + - [xhForEach()](#xhforeach) + - [xhClearKeySafe()](#xhclearkeysafe) + + +## Introduction +The xhash (xh) module provides an extensible hash table interface. The hash table is a table of linked lists of items, so collisions and overflows are handled by this data structure (although excessive collisions still cause a performance loss). This implementation also supports variable-length keys for more flexible usecases. + +- ⚠️ **Warning**: All `xhXYZ()` function calls assume that the `pXHashTable this` arg points to a valid hashtable struct. All non-init functions assume that this struct has been validly initialized and has not yet been freed. If these conditions are not met, the resulting behavior is undefined. + + +## xhInitialize() +```c +int xhInitialize(); +``` +Initialize the random number table for hash computation, returning 0 on success or -1 if an error occurs. Normally, you can assume someone else has already called this during program startup. + + +## xhInit() +```c +int xhInit(pXHashTable this, int rows, int keylen); +``` +This function initializes a hash table, setting the number of rows and the key length. Specify a `keylen` of 0 for for variable length keys (aka. null-terminated strings). The `rows` should be an odd number, preferably prime (although that isn't required). `rows` **SHOULD NOT** be a power of 2. Providing this value allows the caller to optimize it based on how much data they expect to be stored in the hash table. If this value is set to 1, the hash search degenerates to a linear array search with extra overhead. Thus, the value should be large enough to comfortably accommodate the elements with minimal collisions. Typical values include 31, 251, or 255 (though 255 is not prime). + + +## xhDeInit() +```c +int xhDeInit(pXHashTable this); +``` +This function deinitializes a hash table struct, freeing all rows. Note that the stored data is not freed and neither are the keys as this data is assumed to be the responsibility of the caller. Returns 0 on success, or -1 if an error occurs. + + +## xhAdd() +```c +int xhAdd(pXHashTable this, char* key, char* data); +``` +Adds an item to the hash table, with a given key value and data pointer. Both data and key pointers must have a lifetime that exceeds the time that they item is hashed, as they are assumed to be the responsibility of the caller. This function returns 0 on success, or -1 if an error occurs. + + +## xhRemove() +```c +int xhRemove(pXHashTable this, char* key); +``` +This function removes an item with the given key value from the hash table. It returns 0 if the item was successfully removed, or -1 if an error occurs (including failing to find the item). + + +## xhLookup() +```c +char* xhLookup(pXHashTable this, char* key); +``` +This function returns a pointer to the data associated with the given key, or `NULL` if an error occurs (including failing to find the key). + + +## xhClear() +```c +int xhClear(pXHashTable this, int (*free_fn)(), void* free_arg); +``` +Clears all items from a hash table. If a `free_fn()` is provided, it will be invoked with each data pointer as the first argument and `free_arg` as the second argument as items are removed. The return value of the `free_fn()` is ignored. This function returns 0 on success (even if the `free_fn()` returns an error), or -1 if an error is detected. + + +## xhForEach() +```c +int xhForEach(pXHashTable this, int (*callback_fn)(pXHashEntry, void*), void* each_arg); +``` +This function executes an operation on each entry of the hash table entry. The provided callback function will be called with each entry (in an arbitrary order). This function is provided 2 parameters: the current hash table entry, and a `void*` argument specified using `each_arg`. If any invocation of the callback function returns a value other than 0, the `xhForEach()` will immediately fail, returning that value as the error code. + +This function returns 0 if the function executes successfully, 1 if the callback function is `NULL`, or n (where n != 0) if the callback function returns n. It does not return any error code other than 1 or any error codes returned by `callback_fn()`. + + +## xhClearKeySafe() +```c +int xhClearKeySafe(pXHashTable this, void (*free_fn)(pXHashEntry, void*), void* free_arg); +``` +This function clears all contents from the hash table. The free function is passed each hash entry struct and `free_arg`, allowing it to free both the value and key, if needed, and the free function is not allowed to return an error code. This function returns 0 for success as long as `free_fn()` is nonnull, otherwise it returns -1. diff --git a/centrallix-sysdoc/Libraries/xstring.md b/centrallix-sysdoc/Libraries/xstring.md new file mode 100644 index 000000000..4924a0803 --- /dev/null +++ b/centrallix-sysdoc/Libraries/xstring.md @@ -0,0 +1,298 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# The XString Library + +**Author**: Greg Beeley + +**Date**: January 13, 1999 + +**Updated**: December 11, 2025 + +**License**: Copyright (C) 2001-2025 LightSys Technology Services. See LICENSE.txt for more information. + + +## Table of Contents +- [The XString Library](#the-xstring-library) + - [Introduction](#introduction) + - [xsNew()](#xsnew) + - [xsFree()](#xsfree) + - [xsInit()](#xsinit) + - [xsDeInit()](#xsdeinit) + - [xsCheckAlloc()](#xscheckalloc) + - [xsConcatenate()](#xsconcatenate) + - [xsCopy()](#xscopy) + - [xsStringEnd()](#xsstringend) + - [xsConcatPrintf()](#xsconcatprintf) + - [xsPrintf()](#xsprintf) + - [xsWrite()](#xswrite) + - [xsRTrim()](#xsrtrim) + - [xsLTrim()](#xsltrim) + - [xsTrim()](#xstrim) + - [xsFind()](#xsfind) + - [xsFindRev()](#xsfindrev) + - [xsSubst()](#xssubst) + - [xsReplace()](#xsreplace) + - [xsInsertAfter()](#xsinsertafter) + - [xsGenPrintf_va()](#xsgenprintf_va) + - [xsGenPrintf()](#xsgenprintf) + - [xsString()](#xsstring) + - [xsLength()](#xslength) + - [xsQPrintf_va(), xsQPrintf(), & xsConcatQPrintf()](#xsqprintf_va-xsqprintf--xsconcatqprintf) + + +## Introduction +The xstring (xs) module is used for managing growable strings. It is based on a structure containing a small initial string buffer to avoid string allocations for small strings. However, it can also perform `realloc()` operations to extend the string space for storing incrementally larger strings. This module allows for strings to contain arbitrary data, even NULL (`'\0'`) characters mid-string. Thus, it can also be used as an extensible buffer for arbitrary binary data. + +- 📖 **Note**: The contents of the XString can be easily referenced with the `xstring->String` field in the xstring struct. + +- ⚠️ **Warning**: Do not mix calls to [`xsNew()`](#xsnew)/[`xsFree()`](#xsfree) with calls to [`xsInit()`](#xsinit)/[`xsDeInit()`](#xsdeinit). Every struct allocated using new must be freed, and ever struct allocated using init must be deinitted. Mixing these calls can lead to memory leaks, bad frees, and crashes. + + +## xsNew() +```c +pXString xsNew() +``` +This function allocates a new XString structure to contain a new, empty string. It uses [`nmMalloc()`](#nmmalloc) because the XString struct is always a consistant size. This function returns a pointer to the new string if successful, or `NULL` if an error occurs. + + +## xsFree() +```c +void xsFree(pXString this); +``` +This function frees an XString structure allocated with [`xsNew()`](#xsnew), freeing all associated memory. + + +## xsInit() +```c +int xsInit(pXString this); +``` +This function initializes an XString structure to contain a new, empty string. This function returns 0 if successful, or -1 if an error occurs. + + +## xsDeInit() +```c +int xsDeInit(pXString this); +``` +This function deinitializes an XString structure allocated with [`xsInit()`](#xsinit), freeing all associated memory. This function returns 0 if successful, or -1 if an error occurs. + + +## xsCheckAlloc() +```c +int xsCheckAlloc(pXString this, int addl_needed); +``` +This function will optionally allocate more memory, if needed, given the currently occupied data area and the additional space required (specified with `addl_needed`). This function returns 0 if successful, or -1 if an error occurs. + + +## xsConcatenate() +```c +int xsConcatenate(pXString this, char* text, int len); +``` +This function concatenates the `text` string onto the end of the XString's value. If `len` is set, that number of characters are copied, including possible null characters (`'\0'`). If `len` is -1, all data up to the null-terminater is copied. This function returns 0 if successful, or -1 if an error occurs. + +- ⚠️ **Warning**: Do not store pointers to values within the string while adding text to the end of the string. The string may be reallocated to increase space, causing such pointers to break. Instead, use offset indexes into the string and calculate pointers on demand with `xs->String + offset`. + + For example, **DO NOT**: + ```c + XString xs; + if (xsInit(&xs) != 0) goto handle_error; + + if (xsConcatenate(&xs, "This is the first sentence. ", -1) != 0) goto handle_error; + char* ptr = xsStringEnd(&xs); /* Stores string pointer! */ + if (xsConcatenate(&xs, "This is the second sentence.", -1) != 0) goto handle_error; + + /** Print will probably read invalid memory. **/ + printf("A pointer to the second sentence is '%s'\n", ptr); + + ... + + if (xsDeInit(&xs) != 0) goto handle_error; + ``` + + Instead, use indexes and pointer arithmetic like this: + ```c + XString xs; + if (xsInit(&xs) != 0) goto handle_error; + + if (xsConcatenate(&xs, "This is the first sentence. ", -1) != 0) goto handle_error; + int offset = xsStringEnd(&xs) - xs->String; /* Stores index offset. */ + if (xsConcatenate(&xs, "This is the second sentence.", -1) != 0) goto handle_error; + + /** Print will probably work fine. **/ + printf("A pointer to the second sentence is '%s'\n", xs->String + offset); + + ... + + if (xsDeInit(&xs) != 0) goto handle_error; + ``` + + +## xsCopy() +```c +int xsCopy(pXString this, char* text, int len); +``` +This function copies the string `text` into the XString, overwriting any previous contents. This function returns 0 if successful, or -1 if an error occurs. + + +## xsStringEnd() +```c +char* xsStringEnd(pXString this); +``` +This function returns a pointer to the end of the string. This function is more efficient than searching for a null-terminator using `strlen()` because the xs module already knows the string length. Furthermore, since some string may contain nulls, using `strlen()` may produce an incorrect result. + + +## xsConcatPrintf() +```c +int xsConcatPrintf(pXString this, char* fmt, ...); +``` +This function prints additional data onto the end of the string. It is similar to printf, however, only the following features are supported: +- `%s`: Add a string (`char*`). +- `%d`: Add a number (`int`). +- `%X`: Add something? +- `%%`: Add a `'%'` character. +Attempting to use other features of printf (such as `%lf`, `%c`, `%u`, etc.) will cause unexpected results. + +This function returns 0 if successful, or -1 if an error occurs. + + +## xsPrintf() +```c +int xsPrintf(pXString this, char* fmt, ...); +``` +This function works the same as [`xsConcatPrintf()`](#xsconcatprintf), except that it overwrites the previous string instead of appending to it. This function returns 0 if successful, or -1 if an error occurs. + + +## xsWrite() +```c +int xsWrite(pXString this, char* buf, int len, int offset, int flags); +``` +This function writes data into the xstring, similar to using the standard fdWrite or objWrite API. This function can thus be used as a value for `write_fn`, for those functions that require this (such as the `expGenerateText()` function). This function returns `len` if successful, or -1 if an error occurs. + + +## xsRTrim() +```c +int xsRTrim(pXString this); +``` +This function trims whitespace characters (spaces, tabs, newlines, and line feeds) from the right side of the xstring. This function returns 0 if successful, or -1 if an error occurs. + + +## xsLTrim() +```c +int xsLTrim(pXString this); +``` +This function trims whitespace characters (spaces, tabs, newlines, and line feeds) from the left side of the xstring. This function returns 0 if successful, or -1 if an error occurs. + + +## xsTrim() +```c +int xsTrim(pXString this); +``` +This function trims whitespace characters (spaces, tabs, newlines, and line feeds) from both sides of the xstring. This function returns 0 if successful, or -1 if an error occurs. + + +## xsFind() +```c +int xsFind(pXString this, char* find, int findlen, int offset) +``` +This function searches for a specific string (`find`) in the xstring, starting at the provided `offset`. `findlen` is the length of the provided string, allowing it to include null characters (pass -1 to have the length calculated using `strlen(find)`). This function returns the index where the string was found if successful, or -1 if an error occurs (including the string not being found). + + +## xsFind() +```c +int xsFindRev(pXString this, char* find, int findlen, int offset) +``` +This function works the same as [`xsFind()`](#xsfind) except that it searches from the end of the string, resulting in better performance if the value is closer to the end of the string. This function returns the index where the string was found if successful, or -1 if an error occurs (including the string not being found). + + +## xsSubst() +```c +int xsSubst(pXString this, int offset, int len, char* rep, int replen) +``` +This function substitutes a string into a given position in an xstring. This does not search for matches as with [`xsReplace()`](#xsrepalce), instead the position (`offset`) and length (`len`) must be specified. Additionally, the length of the replacement string (`replen`) can be specified handle null characters. Both `len` and `replen` can be left blank to generate them using `strlen()`. This function returns 0 if successful, or -1 if an error occurs. + + +## xsReplace() +```c +int xsReplace(pXString this, char* find, int findlen, int offset, char* rep, int replen); +``` +This function searches an xString for the specified string (`find`) and replaces that string with another specified string (`rep`). Both strings can have their length specified (`findlen` and `replen` respectively), or left as -1 to generate it using `strlen()`. This function returns the starting offset of the replace if successful, or -1 if an error occurs (including the string not being found). + + +## xsInsertAfter() +```c +int xsInsertAfter(pXString this, char* ins, int inslen, int offset); +``` +This function inserts the specified string (`ins`) at offset (`offset`). The length of the string can be specified (`inslen`), or left as -1 to generate it using `strlen()`. This function returns the new offset after the insertion (i.e. `offset + inslen`), or -1 if an error occurs. + + +## xsGenPrintf_va() +```c +int xsGenPrintf_va(int (*write_fn)(), void* write_arg, char** buf, int* buf_size, const char* fmt, va_list va); +``` +This function performs a `printf()` operation to an `xxxWrite()` style function. + +In the wise words of Greg Beeley from 2002: +> This routine isn't really all that closely tied to the XString module, but this seemed to be the best place for it. If a `buf` and `buf_size` are supplied (`NULL` otherwise), then `buf` MUST be allocated with the `nmSysMalloc()` routine. Otherwise, **kaboom!** This routine will grow `buf` if it is too small, and will update `buf_size` accordingly. + +This function returns the printed length (>= 0) on success, or -(errno) if an error occurs. + + +## xsGenPrintf() +```c +int xsGenPrintf(int (*write_fn)(), void* write_arg, char** buf, int* buf_size, const char* fmt, ...); +``` +This function works the same as [`xsGenPrintf_va()`](#xsgenprintf_va), but with a more convenient signature for the developer. + + +## xsString() +```c +char* xsString(pXString this); +``` +This function returns the stored string after checking for various errors, or returns `NULL` if an error occurs. + + +## xsLength() +```c +xsLength(pXString this); +``` +This function returns the length of the string in constant time (since this value is stored in `this->Length`) checking for various errors, or returns `NULL` if an error occurs. + + + + +## xsQPrintf_va(), xsQPrintf(), & xsConcatQPrintf() +```c +int xsQPrintf_va(pXString this, char* fmt, va_list va); +int xsQPrintf(pXString this, char* fmt, ...); +int xsConcatQPrintf(pXString this, char* fmt, ...); +``` +These functions use the `QPrintf` to add data to an xstring. They return 0 on success, or some other value on failure. diff --git a/centrallix-sysdoc/OSDriver_Authoring.md b/centrallix-sysdoc/OSDriver_Authoring.md index c167fce26..ce117966b 100644 --- a/centrallix-sysdoc/OSDriver_Authoring.md +++ b/centrallix-sysdoc/OSDriver_Authoring.md @@ -1,794 +1,1243 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + # ObjectSystem Driver Interface -Author: Greg Beeley -Date: January 13, 1999 +**Author**: Greg Beeley + +**Date**: January 13, 1999 + +**Imported**: August 13, 2001 + +**Updated**: December 11, 2025 + +**License**: Copyright (C) 2001-2025 LightSys Technology Services. See LICENSE.txt for more information. -Updated: March 9, 2011 -License: Copyright (C) 2001-2011 LightSys Technology Services. See LICENSE.txt for more information. ## Table of Contents - [ObjectSystem Driver Interface](#objectsystem-driver-interface) - [Table of Contents](#table-of-contents) - [I Introduction](#i-introduction) - [II Interface](#ii-interface) - - [A. Initialization](#a--initialization) - - [B. Opening And Closing Objects](#b--opening-and-closing-objects) - - [C. Creating and Deleting Objects.](#c--creating-and-deleting-objects) - - [D. Reading and Writing Object Content.](#d--reading-and-writing-object-content) - - [E. Querying for Child Objects.](#e--querying-for-child-objects) - - [F. Managing Object Attributes](#f--managing-object-attributes) - - [G. Managing Object Methods](#g--managing-object-methods) + - [Abbreviation Prefix](#abbreviation-prefix) + - [Internal Functions](#internal-functions) + - [Function: Initialize()](#function-initialize) + - [Function: Open()](#function-open) + - [Function: OpenChild()](#function-openchild) + - [Function: Close()](#function-close) + - [Function: Create()](#function-create) + - [Function: Delete()](#function-delete) + - [Function: DeleteObj()](#function-deleteobj) + - [Function: Read()](#function-read) + - [Function: Write()](#function-write) + - [Function: OpenQuery()](#function-openquery) + - [Function: QueryDelete()](#function-querydelete) + - [Function: QueryFetch()](#function-queryfetch) + - [Function: QueryCreate()](#function-querycreate) + - [Function: QueryClose()](#function-queryclose) + - [Function: GetAttrType()](#function-getattrtype) + - [Function: GetAttrValue()](#function-getattrvalue) + - [Function: GetFirstAttr()](#function-getfirstattr--getnextattr) + - [Function: GetNextAttr()](#function-getfirstattr--getnextattr) + - [Function: SetAttrValue()](#function-setattrvalue) + - [Function: AddAttr()](#function-addattr) + - [Function: OpenAttr()](#function-openattr) + - [Function: GetFirstMethod()](#function-getfirstmethod--getnextmethod) + - [Function: GetNextMethod()](#function-getfirstmethod--getnextmethod) + - [Function: ExecuteMethod()](#function-executemethod) + - [Function: PresentationHints()](#function-presentationhints) + - [Function: Info()](#function-info) + - [Function: Commit()](#function-commit) + - [Function: GetQueryCoverageMask()](#function-getquerycoveragemask) + - [Function: GetQueryIdentityPath()](#function-getqueryidentitypath) - [III Reading the Node Object](#iii-reading-the-node-object) - - [pSnNode snReadNode(pObject obj)](#psnnode-snreadnodepobject-obj) - - [pSnNode snNewNode(pObject obj, char* content_type)](#psnnode-snnewnodepobject-obj-char-content_type) - - [int snWriteNode(pSnNode node)](#int-snwritenodepsnnode-node) - - [int snDeleteNode(pSnNode node)](#int-sndeletenodepsnnode-node) - - [int snGetSerial(pSnNode node)](#int-sngetserialpsnnode-node) - - [pStructInf stParseMsg(pFile inp_fd, int flags)](#pstructinf-stparsemsgpfile-inp_fd-int-flags) - - [pStructInf stParseMsgGeneric(void* src, int (*read_fn)(), int flags)](#pstructinf-stparsemsggenericvoid-src-int-read_fn-int-flags) - - [int stGenerateMsg(pFile out_fd, pStructInf info, int flags)](#int-stgeneratemsgpfile-out_fd-pstructinf-info-int-flags) - - [int stGenerateMsgGeneric(void* dst, int (*write_fn)(), pStructInf info, int flags)](#int-stgeneratemsggenericvoid-dst-int-write_fn-pstructinf-info-int-flags) - - [pStructInf stCreateStruct(char* name, char* type)](#pstructinf-stcreatestructchar-name-char-type) - - [pStructInf stAddAttr(pStructInf inf, char* name)](#pstructinf-staddattrpstructinf-inf-char-name) - - [pStructInf stAddGroup(pStructInf inf, char* name, char* type)](#pstructinf-staddgrouppstructinf-inf-char-name-char-type) - - [int stAddValue(pStructInf inf, char* strval, int intval)](#int-staddvaluepstructinf-inf-char-strval-int-intval) - - [pStructInf stLookup(pStructInf inf, char* name)](#pstructinf-stlookuppstructinf-inf-char-name) - - [int stAttrValue(pStructInf inf, int* intval, char** strval, int nval)](#int-stattrvaluepstructinf-inf-int-intval-char-strval-int-nval) - - [int stFreeInf(pStructInf this)](#int-stfreeinfpstructinf-this) - - [IV Memory Management in Centrallix](#iv-memory-management-in-centrallix) - - [void* nmMalloc(int size)](#void-nmmallocint-size) - - [void nmFree(void* ptr, int size)](#void-nmfreevoid-ptr-int-size) - - [void nmStats()](#void-nmstats) - - [void nmRegister(int size, char* name)](#void-nmregisterint-size-char-name) - - [void nmDebug()](#void-nmdebug) - - [void nmDeltas()](#void-nmdeltas) - - [void* nmSysMalloc(int size)](#void-nmsysmallocint-size) - - [void nmSysFree(void* ptr)](#void-nmsysfreevoid-ptr) - - [void* nmSysRealloc(void* ptr, int newsize)](#void-nmsysreallocvoid-ptr-int-newsize) - - [char* nmSysStrdup(const char* str)](#char-nmsysstrdupconst-char-str) - - [V Other Utility Modules](#v-other-utility-modules) - - [A. XArray (XA) - Arrays](#axarray-xa---arrays) - - [xaInit(pXArray this, int init_size)](#xainitpxarray-this-int-init_size) - - [xaDeInit(pXArray this)](#xadeinitpxarray-this) - - [xaAddItem(pXArray this, void* item)](#xaadditempxarray-this-void-item) - - [xaAddItemSorted(pXArray this, void* item, int keyoffset, int keylen)](#xaadditemsortedpxarray-this-void-item-int-keyoffset-int-keylen) - - [xaFindItem(pXArray this, void* item)](#xafinditempxarray-this-void-item) - - [xaRemoveItem(pXArray this, int index)](#xaremoveitempxarray-this-int-index) - - [B. XHash (XH) - Hash Tables](#bxhash-xh---hash-tables) - - [int xhInit(pXHashTable this, int rows, int keylen)](#int-xhinitpxhashtable-this-int-rows-int-keylen) - - [int xhDeInit(pXHashTable this)](#int-xhdeinitpxhashtable-this) - - [int xhAdd(pXHashTable this, char* key, char* data)](#int-xhaddpxhashtable-this-char-key-char-data) - - [int xhRemove(pXHashTable this, char* key)](#int-xhremovepxhashtable-this-char-key) - - [char* xhLookup(pXHashTable this, char* key)](#char-xhlookuppxhashtable-this-char-key) - - [int xhClear(pXHashTable this, int free_blk)](#int-xhclearpxhashtable-this-int-free_blk) - - [C. XString (XS) - Strings](#cxstring-xs---strings) - - [int xsInit(pXString this)](#int-xsinitpxstring-this) - - [int xsDeInit(pXString this)](#int-xsdeinitpxstring-this) - - [int xsConcatenate(pXString this, char* text, int len)](#int-xsconcatenatepxstring-this-char-text-int-len) - - [int xsCopy(pXString this, char* text, int len)](#int-xscopypxstring-this-char-text-int-len) - - [char* xsStringEnd(pXString this)](#char-xsstringendpxstring-this) - - [D. Expression (EXP) - Expression Trees](#dexpression-exp---expression-trees) - - [pExpression expCompileExpression(char* text, pParamObjects objlist, int lxflags, int cmpflags)](#pexpression-expcompileexpressionchar-text-pparamobjects-objlist-int-lxflags-int-cmpflags) - - [expFreeExpression(pExpression this)](#expfreeexpressionpexpression-this) - - [int expEvalTree(pExpression this, pParamObjects objlist)](#int-expevaltreepexpression-this-pparamobjects-objlist) - - [pParamObjects expCreateParamList()](#pparamobjects-expcreateparamlist) - - [int expFreeParamList(pParamObjects this)](#int-expfreeparamlistpparamobjects-this) - - [int expAddParamToList(pParamObjects this, char* name, pObject obj, int flags)](#int-expaddparamtolistpparamobjects-this-char-name-pobject-obj-int-flags) - - [int expModifyParam(pParamObjects this, char* name, pObject replace_obj)](#int-expmodifyparampparamobjects-this-char-name-pobject-replace_obj) - - [int expRemoveParamFromList(pParamObjects this, char* name)](#int-expremoveparamfromlistpparamobjects-this-char-name) - - [int expReverseEvalTree(pExpression tree, pParamObjects objlist)](#int-expreverseevaltreepexpression-tree-pparamobjects-objlist) - - [E. MTSession (MSS) - Basic Session Management](#emtsession-mss---basic-session-management) - - [char* mssUserName()](#char-mssusername) - - [char* mssPassword()](#char-msspassword) - - [int mssSetParam(char* paramname, char* param)](#int-msssetparamchar-paramname-char-param) - - [char* mssGetParam(char* paramname)](#char-mssgetparamchar-paramname) - - [int mssError(int clr, char* module, char* message, ...)](#int-msserrorint-clr-char-module-char-message-) - - [int mssErrorErrno(int clr, char* module, char* message, ...)](#int-msserrorerrnoint-clr-char-module-char-message-) - - [F. OSML Utility Functions](#fosml-utility-functions) - - [char* obj_internal_PathPart(pPathname path, int start, int length)](#char-obj_internal_pathpartppathname-path-int-start-int-length) - - [int obj_internal_AddToPath(pPathname path, char* new_element)](#int-obj_internal_addtopathppathname-path-char-new_element) - - [int obj_internal_CopyPath(pPathname dest, pPathname src)](#int-obj_internal_copypathppathname-dest-ppathname-src) - - [void obj_internal_FreePathStruct(pPathname path)](#void-obj_internal_freepathstructppathname-path) - - [VI Network Connection Functionality](#vi-network-connection-functionality) - - [pFile netConnectTCP(char* host_name, char* service_name, int flags)](#pfile-netconnecttcpchar-host_name-char-service_name-int-flags) - - [int netCloseTCP(pFile net_filedesc, int linger_msec, int flags)](#int-netclosetcppfile-net_filedesc-int-linger_msec-int-flags) - - [int fdWrite(pFile filedesc, char* buffer, int length, int offset, int flags)](#int-fdwritepfile-filedesc-char-buffer-int-length-int-offset-int-flags) - - [int fdRead(pFile filedesc, char* buffer, int maxlen, int offset, int flags)](#int-fdreadpfile-filedesc-char-buffer-int-maxlen-int-offset-int-flags) - - [VII Parsing Data](#vii-parsing-data) - - [pLxSession mlxOpenSession(pFile fd, int flags)](#plxsession-mlxopensessionpfile-fd-int-flags) - - [pLxSession mlxStringSession(char* str, int flags)](#plxsession-mlxstringsessionchar-str-int-flags) - - [int mlxCloseSession(pLxSession this)](#int-mlxclosesessionplxsession-this) - - [int mlxNextToken(pLxSession this)](#int-mlxnexttokenplxsession-this) - - [char* mlxStringVal(pLxSession this, int* alloc)](#char-mlxstringvalplxsession-this-int-alloc) - - [int mlxIntVal(pLxSession this)](#int-mlxintvalplxsession-this) - - [double mlxDoubleVal(pLxSession this)](#double-mlxdoublevalplxsession-this) - - [int mlxCopyToken(pLxSession this, char* buffer, int maxlen)](#int-mlxcopytokenplxsession-this-char-buffer-int-maxlen) - - [int mlxHoldToken(pLxSession this)](#int-mlxholdtokenplxsession-this) - - [int mlxSetOptions(pLxSession this, int options)](#int-mlxsetoptionsplxsession-this-int-options) - - [int mlxUnsetOptions(pLxSession this, int options)](#int-mlxunsetoptionsplxsession-this-int-options) - - [int mlxSetReservedWords(pLxSession this, char** res_words)](#int-mlxsetreservedwordsplxsession-this-char-res_words) - - [int mlxNoteError(pLxSession this)](#int-mlxnoteerrorplxsession-this) - - [int mlxNotePosition(pLxSession this)](#int-mlxnotepositionplxsession-this) - - [VIII Objectsystem Driver Testing](#viii-objectsystem-driver-testing) - - [A. Object opening, closing, creation, and deletion](#aobject-opening-closing-creation-and-deletion) - - [B. Object attribute enumeration, getting, and setting.](#bobject-attribute-enumeration-getting-and-setting) - - [C. Object querying (for subobjects)](#cobject-querying-for-subobjects) + - [Module: st_node](#module-st_node) + - [st_node: snReadNode()](#st_node-snreadnode) + - [st_node: snNewNode()](#st_node-snnewnode) + - [st_node: snWriteNode()](#st_node-snwritenode) + - [st_node: snDelete()](#st_node-sndelete) + - [st_node: snGetSerial()](#st_node-sngetserial) + - [st_node: snGetLastModification()](#st_node-sngetlastmodification) + - [Module: stparse](#module-stparse) + - [stparse: stStructType()](#stparse-ststructtype) + - [stparse: stLookup()](#stparse-stlookup) + - [stparse: stAttrValue()](#stparse-stattrvalue) + - [stparse: stGetExpression()](#stparse-stgetexpression) + - [stparse: stCreateStruct()](#stparse-stcreatestruct) + - [stparse: stAddAttr()](#stparse-staddattr) + - [stparse: stAddGroup()](#stparse-staddgroup) + - [stparse: stAddValue()](#stparse-staddvalue) + - [stparse: stFreeInf()](#stparse-stfreeinf) + - [stparse: Using Fields Directly](#stparse-using-fields-directly) + - [IV Module: Expression](#iv-module-expression) + - [expAllocExpression())](#expallocexpression) + - [expFreeExpression()](#expfreeexpression) + - [expCompileExpression()](#expcompileexpression) + - [expCompileExpressionFromLxs()](#expcompileexpressionfromlxs) + - [expPodToExpression()](#exppodtoexpression) + - [expExpressionToPod()](#expexpressiontopod) + - [expDuplicateExpression()](#expduplicateexpression) + - [expIsConstant()](#expisconstant) + - [expEvalTree()](#expevaltree) + - [expCreateParamList()](#expcreateparamlist) + - [expFreeParamList()](#expfreeparamlist) + - [expAddParamToList()](#expaddparamtolist) + - [expModifyParam()](#expmodifyparam) + - [expRemoveParamFromList()](#expremoveparamfromlist) + - [expSetParamFunctions()](#expsetparamfunctions) + - [expReverseEvalTree()](#expreverseevaltree) + - [V Path Handling Functions](#v-path-handling-functions) + - [obj_internal_PathPart()](#obj_internal_pathpart) + - [obj_internal_AddToPath()](#obj_internal_addtopath) + - [obj_internal_CopyPath](#obj_internal_copypath) + - [obj_internal_FreePathStruct()](#obj_internal_freepathstruct) + - [VI Parsing Data](#vi-parsing-data) + - [mlxOpenSession()](#mlxopensession) + - [mlxStringSession()](#mlxstringsession) + - [mlxCloseSession()](#mlxclosesession) + - [mlxNextToken()](#mlxnexttoken) + - [mlxStringVal()](#mlxstringval) + - [mlxIntVal()](#mlxintval) + - [mlxDoubleVal()](#mlxdoubleval) + - [mlxCopyToken()](#mlxcopytoken) + - [mlxHoldToken()](#mlxholdtoken) + - [mlxSetOptions()](#mlxsetoptions) + - [mlxUnsetOptions()](#mlxunsetoptions) + - [mlxSetReservedWords()](#mlxsetreservedwords) + - [mlxNoteError()](#mlxnoteerror) + - [mlxNotePosition()](#mlxnoteposition) + - [VII Driver Testing](#vii-driver-testing) + - [A. Opening, closing, creating, and deleting](#a-opening-closing-creating-and-deleting) + - [B. Attributes](#b-attributes) + - [C. Querying Subobjects](#c-querying-subobjects) + + ## I Introduction -An objectsystem driver's purpose is to provide access to a particular type of local or network data/resource, and to organize that data in a tree- structured heirarchy that can be integrated into the Centrallix's ObjectSystem. This tree structure will vary based on the data being presented, but will fit the basic ObjectSystem model of a heirarchy of objects, each having attributes, perhaps some methods, and possibly content. +An objectsystem driver's purpose is to provide access to a particular type of local or network data/resource. Specific information about the resource to be accessed (such as credentials for a database, queries for selecting data, the auth token for an API, etc.) is stored in a file that is opened by the relevant driver. For example, the query driver (defined in `objdrv_query.c`) opens `.qy` files, which store one or more ObjectSQL queries used to fetch data. -Each objectsystem driver will implement this subtree structure rooted at what is called the "node" object. The node has a specifically recognizable object type which the ObjectSystem Management Layer uses to determine which OS Driver to pass control to. Normally, the 'node' object is a UNIX file either with a particular extension registered with the OSML, or a UNIX file residing in a directory containing a '.type' file, which contains the explicit object type for all objects in that directory without recognizable extensions. +When the object system starts up, each driver registers one or more type names that it supports (e.g. `"system/query"` for the query driver). When a file is opened, the object system uses the file's type name to select which driver to use. It finds this type name with one of two strategies. If the file has an extension (e.g. `example.qy`), that extension can be mapped to a type name using `types.cfg` (e.g. `.qy` maps to `"system/query"`). Alternatively, the file may reside in a directory containing a `.type` file which explicitly specifies the type name for all files in that directory without recognizable extensions. -Normally, objectsystem drivers will be able to manage any number of 'node' objects and the subtrees rooted at them. Each 'node' object will normally relate to a particular instance of a network resource, or in some cases, a group of resources that are easily enumerated. For example, a POP3 server would be a network resource that an OS driver could be written for. If the network had multiple POP3 servers, then that one OS driver would be able to access each of them using different node objects. However, if somehow the OS driver were able to easily enumerate the various POP3 servers on the network (i.e., they responded to some kind of hypothetical broadcast query), then the OS driver author could optionally design the driver to list the POP3 servers under a single node for the whole network. +Once a file is opened, the driver should organize provided data into a tree-structured hierarchy, which becomes part of the path used by Centrallix's ObjectSystem. For example, when opening `example.qy` in the ObjectSystem, the driver makes `/rows` and `/columns` available, allowing for paths such as `/apps/data/example.qy/rows`. The root of a driver's tree (`example.qy`) is called the driver's "node" object, and most paths traverse the node objects of multiple drivers. The root of the entire tree is a special driver called the root node which is used to begin traversal. Within its tree, a driver author is free to define any manner of hierarchical structures for representing available data. However, the structure should fit the basic ObjectSystem model of a hierarchy of objects, each having attributes, and optionally some methods and/or content. -The structure of the subtree beneath the node object is entirely up to the drivers' author to determine; the OSML does not impose any structural restrictions on such subtrees. +A driver can be opened multiple times, leading one driver to have multiple "node" objects, also called instances. Typically, each "node" object relates to a particular instance of a resource. For example, say you are designing a driver to access MySQL databases. You could design the driver file to describe a MySQL instance. Thus, the node object for this driver could have children for each database in that instance (e.g. `Kardia_DB`, `mysql`, and even the system databases used by MySQL to manage the database internals). Another design would be for each driver file to describe one MySQL database. Thus, you could make a `Kardia_DB` file to access that database, and the children of that node object would be each table in the database. A third design option would be for each driver file to describe a MySQL table. Thus, you make a `p_partner` file to access members of the partner table, a `p_contact_info` file to access contact info for partners, etc. with each node object having children for the rows in the table. This last option would require the developer to create a _lot_ of files (and would probably also make joins hard to implement), so in this case, it's probably not the best. Ultimately, though, these design choices are up to the driver author. -Here is one example of an OS Driver's node object and subtree (this is for the Sybase OS Driver, objdrv_sybase.c): +an instance of a POP3 driver might represent a POP3 server on the network. If the network had multiple POP3 servers, this driver could be used to access each of them through different node objects (e.g. `dev.pop3`, `prod.pop3`, etc.). However, if somehow the OS driver were able to easily enumerate the various POP3 servers on the network (i.e., they responded to some kind of hypothetical broadcast query), then the OS driver author could also design the driver to list the POP3 servers under a single node for the whole network. -``` -OMSS_DB (type = application/sybase) +The structure of the subtree beneath the node object is entirely up to the drivers' author to determine; the OSML does not impose any structural restrictions on such subtrees. Each object within this structure (e.g. `/example.qy`) can have three types of readable data: +- Child objects (e.g. `/rows`) which can have their own data. +- Content, which can be read similar to reading a file. +- Query data, allowing the object to be queried for information. + +Thus, parent objects with child objects behave similarly to a directory, although they can still have separate readable data _and_ queryable data. This may seem foreign in the standard file system paradigm, however, it is common for web servers, where opening a directory often returns `index.html` file in that directory, or some other form of information to allow further navigation. Querying an object was originally intended as a way to quickly traversal of its child objects, although queries are not required to be implemented this way. + +Below is an example of the Sybase driver's node object and its subtrees of child objects (defined in `objdrv_sybase.c`): + +```sh +Kardia_DB (type = "application/mysql") | - +--- JNetHelp (type = system/table) - | | - | +--- columns (type = system/table-columns) - | | | - | | +--- document_id (type = system/column) - | | | - | | +--- parent_id (type = system/column) - | | | - | | +--- title (type = system/column) - | | | - | | +--- content (type = system/column) - | | - | +--- rows (type = system/table-rows) - | | - | +--- 1 (type = system/row) - | | - | +--- 2 (type = system/row) + +----- p_partner (type = "system/table") + | | + | +----- columns (type = "system/table-columns") + | | | + | | +----- p_partner_key (type = "system/column") + | | | + | | +----- p_given_name (type = "system/column") + | | | + | | +----- p_surname (type = "system/column") + | | | + | | ... + | | + | +----- rows (type = "system/table-rows") + | | | + | | +----- 1 (type = "system/row") + | | | + | | +----- 2 (type = "system/row") + | | | + | | ... + | | + | ... | - +--- Partner (type = system/table) + +----- p_contact_info (type = "system/table") + | | + | ... + ... ``` (... and so forth) -In this case the node object would contain the information necessary to access the database, such as server name, database name, max connections to pool, and so forth. More about the node object and managing its parameters will be discussed later in this document. +In this case, the `Kardia_DB` file becomes the driver's node object. This file on disk contains the information necessary to access the database (such as server name, database name, max connections to pool, etc.). + +OS Drivers support several primary areas of functionality: +- Opening and closing objects. +- Creating and deleting node objects (optional). +- Reading and writing object content (optional). +- Getting and (optionally) setting object attributes. +- Executing object methods (optional). +- Querying data attributes (optional). + +Using the example above, we can query from the database using a statement like `select :title from /Kardia_DB/p_partner/rows`, which will open a mysql driver instance, then open a query and repeatedly fetch rows, getting the `title` attribute from each row. + -OS Drivers support several primary areas of functionality: opening and closing objects, reading and writing object content (if the object has content), setting and viewing object attributes, executing object methods, and querying an object's child objects based on name and/or attribute values. Drivers will also support the creation and deletion of objects and/or a set of child objects. ## II Interface -This section describes the standard interface between the OSML and the ObjectSystem driver itself. +This section describes the standard interface between the OSML and the ObjectSystem driver itself. Every driver should implement certain required functions. (**Note**: Many drivers "implement" some required functions to simply fail with a not implemented or not supported error. For example, most database drivers "implement" `Read()` and `Write()` this way because database content should be queried, not read). Various optional functions are also available, which a driver is not required to implement. + +The driver should implement an `Initialize()` function, as well as the following (* indicates required functions): + +| Function Name | Description +| --------------------------------------------------------- | ------------ +| [Open](#function-open)* | Opens a new driver instance object on a given node object. +| [OpenChild](#function-openchild) | Opens a single child object of the provided object by name. +| [Close](#function-close)* | Close an open object created by either `Open()` or `QueryFetch()`. +| [Create](#function-create) | Create a new driver node object. (Not currently used because the OSML calls the driver Open with the `O_WRONLY \| O_CREAT \| O_EXCL` options instead. See [Open()](#function-open) below for more info.) +| [Delete](#function-delete) | Used for general object deletion. Drivers can implement `DeleteObj()` instead. +| [DeleteObj](#function-deleteobj)* | Replacement for `Delete()` which operates on an already-open object. +| [OpenQuery](#function-openquery)** | Start a new query for child objects of a given object. +| [QueryDelete](#function-querydelete) | Delete specific objects from a query's result set. +| [QueryFetch](#function-queryfetch)** | Open the next child object in the query's result set. +| [QueryCreate](#function-querycreate) | Currently just a stub function that is not fully implemented. +| [QueryClose](#function-queryclose)** | Close an open query. +| [Read](#function-read)* | Read content from the object. +| [Write](#function-write)* | Write content to the object. +| [GetAttrType](#function-getattrtype)* | Get the type of a given object's attribute. +| [GetAttrValue](#function-getattrvalue)* | Get the value of a given object's attribute. +| [GetFirstAttr](#function-getfirstattr--getnextattr)* | Get the name of the object's first attribute. +| [GetNextAttr](#function-getfirstattr--getnextattr)* | Get the name of the object's next attribute. +| [SetAttrValue](#function-setattrvalue)* | Set the value of an object's attribute. +| [AddAttr](#function-addattr) | Add a new attribute to an object. +| [OpenAttr](#function-openattr) | Open an attribute as if it were an object with content. +| [GetFirstMethod](#function-getfirstmethod--getnextmethod) | Get the name of an object's first method. +| [GetNextMethod](#function-getfirstmethod--getnextmethod) | Get the name of an object's next method. +| [ExecuteMethod](#function-executemethod) | Execute a method with a given name and optional parameter string. +| [PresentationHints](#function-presentationhints) | Get info about an object's attributes. +| [Info](#function-info)* | Get info about an object instance. +| [Commit](#function-commit) | Commit changes made to an object, ensuring that all modifications in the current transaction are completed and the transaction is closed before returning. +| [GetQueryCoverageMask](#function-getquerycoveragemask) | Should be left `NULL` outside the MultiQuery module. +| [GetQueryIdentityPath](#function-getqueryidentitypath) | Should be left `NULL` outside the MultiQuery module. + +_*Function is always required._ + +_**Function is always required, but can always return NULL if queries are not supported._ + + +--- +### Abbreviation Prefix +Each OS Driver will have an abbreviation prefix, such as `qy` for the query driver or `rpt` for the report driver. This prefix should be prepended to the start of every public function name within the OS driver for consistency and scope management (e.g. `qyInitialize()`, `rptOpen()`, etc.). Normally, a driver's abbreviation prefix is two to four characters, all lowercase and may be the same as a file extension the driver supports. However, this is not an absolute requirement (see the cluster driver in `objdrv_cluster.c` which supports `.cluster` files using an abbreviation prefix of `cluster`). + +This document uses `xxx` to refer to an unspecified abbreviation prefix. + +- 📖 **Note**: Once an abbreviation prefix has been selected, the driver author should add it to the [Prefixes.md](Prefixes.md) file. It is advised never to pick a prefix longer than 7 characters, as doing so will this file's table style. + + +### Internal Functions +It is highly likely that driver authors will find shared functionality in the following functions, or wish to abstract out functionality from any of them for a variety of reasons. When creating additional internal functions in this way, they should be named using the convention of `xxx_internal_FunctionName()`, or possibly `xxxi_FunctionName()` for short. + +--- +### Function: Initialize() +```c +/*** @returns 0 if successful, or + *** -1 if an error occurred. + ***/ +int xxxInitialize(void) +``` +- ⚠️ **Warning**: For compiled drivers, the success/failure of this function is ignored by the caller. However, for drivers loaded as modules, the return value is checked in order to determine whether to keep the module loaded. In either case, `mssError()` should be called for any failure (other than memory allocation failures). +- 📖 **Note**: Unlike other functions defined in the driver, each driver author must manually add this call to the start up code, found in the `cxDriverInit()` function in `centrallix.c`. -### A. Initialization -Each OS Driver will have an initialization function, normally named xxxInitialize() where 'xxx' is the driver's abbreviative prefix. This prefix should be attached to each and every function within the OS driver for consistency and project management. Normally 'xxx' is two to four characters, all lowercase. This initialization function is called when the Centrallix starts up, and at least at the present time, this initial call to the OS driver must be manually added to the appropriate startup code, currently found in 'centrallix.c'. +The initialization function is called when the Centrallix starts up, and should register the driver with the OSML and initialize necessary global variables. It is recommended to place global variables in a single global 'struct' that is named with the driver's prefix in all uppercase. Global variables should **NOT** be accessed from outside the driver. Instead, the driver should define functions to access them, allowing it to abstract details away from other drivers. -Within the initialization function, the driver should initialize all necessary global variables and register itself with the OSML. Global variables should all be placed inside a single global 'struct', which is normally named similarly to the driver's prefix, except normally in all uppercase. Under no circumstances should global variables be accessed outside of the module, except via the module's functions. +To register itself with the OSML, the driver should first allocate an ObjDriver structure and initialize its contents: -To register with the OSML, the driver must first allocate an ObjDriver structure and fill in its contents. +```c +pObjDriver drv = (pObjDriver)nmMalloc(sizeof(ObjDriver)); +if (drv == NULL) goto error_handling; +memset(drv, 0, sizeof(ObjDriver)); +... +``` - pObjDriver drv; +To initialize this struct, the driver must: +- Provide a name (in `drv->Name`). +- Provide an array of supported root types (in `drv->RootContentTypes`). +- Provide capability flags (in `drv->Capabilities`). +- Provide function pointers to implemented functions (see [II Interface](#ii-interface) for a list). - drv = (pObjDriver)nmMalloc(sizeof(ObjDriver)); +#### Name +The `name` field is a 64 character buffer (allowing names up to 63 characters, with a null terminator). It usually follows the format of the driver abbreviation prefix (in all uppercase), followed by a dash, followed by a descriptive name for the driver. -This involves setting a large number of fields to the appropriate entry points within the OS Driver, as well as telling the OSML what object type(s) are handled by the driver and giving the OSML a description of the driver. A list of the required entry point functions / fields follows: +For example: +```c +if (strcpy(drv->Name, "SYBD - Sybase Database Driver") == NULL) goto error_handling; +``` -| Function/Field | Description -| -------------------- | ------------ -| Open | Function that the OSML calls when the user opens an object managed by this driver. -| Close | Close an open object. -| Create | Create a new object. -| Delete | Delete an existing object. -| OpenQuery | Start a query for child objects. -| QueryDelete | Delete all objects in the query result set. -| QueryFetch | Open the next child object in the query's result set. -| QueryClose | Close an open query. -| Read | Read content from the object. -| Write | Write content to the object. -| GetAttrType | Get the type of an object's attribute. -| GetAttrValue | Get the value of an object's attribute. -| GetFirstAttr | Get the first attribute associated with the object. -| GetNextAttr | Get the next attribute associated with the object. -| SetAttrValue | Set the value of an attribute. -| AddAttr | Add a new attribute to an object. -| OpenAttr | Open an attribute as if it were an object with content. -| GetFirstMethod | Get the first method of the object. -| GetNextMethod | Get the next method of an object. -| ExecuteMethod | Execute a method with an optional string parameter. +#### RootContentTypes +The `RootContentTypes` field is an XArray containing a list of strings, representing the type names that the driver can open. This should only include types the driver will open as node objects at the root of its tree, not other objects created by the driver within that tree. Thus, the sybase driver would include `"application/sybase"`, but not `"system/table"`. -The only method that can be set to NULL is the QueryDelete method, in which case the OSML will call QueryFetch() and Delete() in succession. However, if the underlying network resource has the capability of intelligently deleting objects matching the query's criteria, this method should be implemented (as with a database server). +For example: +```c +if (xaInit(&(drv->RootContentTypes), 2) != 0) goto error_handling; +if (xaAddItem(&(drv->RootContentTypes), "application/sybase") < 0) goto error_handling; +if (xaAddItem(&(drv->RootContentTypes), "system/query") < 0) goto error_handling; +``` -Another field in the driver structure is the Capabilities field. This field is a bitmask, and can currently contain zero or more of the following options: +- 📖 **Note**: To make a specific file extension (like `.qy`) open in a driver, edit `types.cfg` to map that file extension to an available root content type supported by the driver (such as `"system/query"`). -- OBJDRV_C_FULLQUERY: Indicates that this objectsystem driver will intelligently process the query's expression tree specified in the OpenQuery call, and will only return objects that match that expression. If this flag is missing, the OSML will filter objects returned by QueryFetch so that the calling user does not get objects that do not match the query. Typically this is set by database server drivers. +#### Capabilities +The capabilities field is a bitmask which can contain zero or more of the following flags: - THE ABOVE IS OUT-OF-DATE. From now on, a driver can determine whether to handle the Where and OrderBy on a per-query basis, by setting values in the ObjQuery structure used when opening a new query. This is because a driver may be able to handle Where and OrderBy for some object listings but not for others. +- `OBJDRV_C_FULLQUERY`: Indicates that this objectsystem driver will intelligently process the query's expression tree specified in the `OpenQuery()` call, and will only return objects that match that expression. If this flag is missing, the OSML will filter objects returned by `QueryFetch()` so that the calling user does not get objects that do not match the query. Typically this is set by database server drivers. + - > **THE ABOVE IS OUT-OF-DATE** (May 16th, 2022): A driver can now determine whether to handle the `Where` and `OrderBy` on a per-query basis, by setting values in the ObjQuery structure used when opening a new query. This allows a driver to handle `Where` and `OrderBy` selectively for some object listings but not others. -- OBJDRV_C_TRANS: Indicates that this objectsystem driver requires transaction management by the OSML's transaction layer (the OXT layer). OS drivers that require this normally are those that for some reason cannot complete operations in independence from one another. For example, with a database driver, the creation of a new row object and the setting of its attributes must be done as one operation, although the operation requires several calls from the end user's process. The OXT allows for the grouping of objectsystem calls so that the os driver does not have to complete them independently, but instead can wait until several calls have been made before actually completing the operation. +- `OBJDRV_C_TRANS`: Indicates that this objectsystem driver requires transaction management by the OSML's transaction layer (the OXT layer). OS drivers that require this normally are those that for some reason cannot complete operations in independence from one another. For example, with a database driver, the creation of a new row object and the setting of its attributes must be done as one operation, although the operation requires several calls from the end user's process. The OXT allows for the grouping of objectsystem calls so that the os driver does not have to complete them independently, but instead can wait until several calls have been made before actually completing the operation. -The 'Name' field should be filled in with a description of the OS driver, with a maximum length of 63 characters (plus the string null terminator). Normally, the 2-4 letter prefix of the driver is included at the beginning of 'Name', such as "UXD - UNIX filesystem driver". +#### Registering the Driver Struct +When all values within the structure have been initialized, the driver should call the OSML to register itself, using the `objRegisterDriver()` function: -Finally, the 'RootContentTypes' field is an XArray containing a list of strings, each of which specifies the node object types that the driver will handle. Such types are added to this XArray using the normal XArray utility functions, such as: +```c +if (objRegisterDriver(drv) != 0) goto error_handling; +``` - xaInit(&drv->RootContentTypes, 16); - xaAddItem(&drv->RootContentTypes, "system/file"); - xaAddItem(&drv->RootContentTypes, "system/directory"); -When the structure has been filled out, the os driver should call the OSML to register itself, using the objRegisterDriver function: +--- +### Function: Open() +```c +void* xxxOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pObjTrxTree* oxt); +``` - objRegisterDriver(drv); +The `Open()` function opens a given file to create a new driver instance. This procedure normally includes the following steps: -The initialization function should return 0 to indicate success, or -1 on failure. Currently, initialization success/failure is not verified by lsmain.c. +1. Access or create the node object, depending on specified flags and whether or not it already exists. +2. Parse additional contents of the path after the driver node object. +3. Allocate a structure that will represent the open object, including a pointer to the node object. +4. Perform other opening operations (such as reading database table information, etc., when a db table's row is being accessed). +5. Return a pointer to the node instance as a void pointer. This pointer will be passed as `void* inf_v` to the driver in subsequent calls involving this object (except the Query functions, discussed below). -The driver should NOT nmFree() the allocated driver structure unless the objRegisterDriver() routine fails (returns -1). +- 📖 **Note - Transactions**: If the os driver specified the `OBJDRV_C_TRANS` capability, it must respect the current state of the user's transaction. If a new object is being created, an object is being deleted, or other modifications/additions are being performed, and if the OXT layer indicates a transaction is in process, the driver must either complete the current transaction and then complete the current call, or else add the current delete/create/modify call to the transaction tree (in which case the tree item is pre-allocated; all the driver needs to do is fill it in). This is handled using the transaction tree parameter (`oxt : pObjTrxTree*`). -Note that the RootContentTypes handled by the driver should only include the types of the objects this driver will handle as node objects. For instance, the Sybase database access driver uses "application/sybase" as its top level type. It won't register such things as "system/table". +#### Accessing the Node Object +If `O_CREAT` and `O_EXCL` are both specified in `parent->Mode`, the driver should **only** create a new file and fail if the file already exists (refusing to open and read it). Otherwise, the driver should read an existing file, or create one if it does not exist and `O_CREAT` is specified, failing if no file can be read or created. -### B. Opening And Closing Objects -As an overview, the normal procedure for the open routine to follow is this: +#### Parsing Path Contents +The task of parsing the provided path into the subtree beneath its node object is one of the more complex operations for a driver. For example, the path to a driver's node object might be `/datasources/Kardia_DB` and the user opens an object called `/datasources/Kardia_DB/p_partner/rows/1`. In this case, the OS driver must parse the meaning of the subtree path `p_partner/rows/1`, storing the data targeted by the user into the driver instance to allow later method calls to access the correct data. -1. Access the node object, or create it, depending on whether the object already exists as well as the open mode flags indicated by the end-user. -2. Upon successful node object access, determine what additional components of the pathname are to be handled by this driver, and verify that they can be opened, depending on the object's open mode (CREAT, EXCL, etc.) -3. If it hasn't been already, allocate a structure that will represent this open object and contain information about it and how we're to handle it. It should include a pointer to the node object. -4. Perform any operations inherent in the open process that have not already been performed (such as reading database table information, etc., when a db table's row is being accessed). -5. Return a pointer to the structure allocated in (3) as a void pointer. The OSML will pass this pointer back to the driver on subsequent calls that involve this object. +#### Parameters +The `Open()` routine is called with five parameters: -The first basic part of the OS driver consists of the Open and Close routines, normally named 'xxxOpen' and 'xxxClose' within the driver, where 'xxx' is the driver's prefix. The Close routine is normally fairly simple, but the Open routine is one of the most complicated routines in a typical OS driver, for the Open routine must parse the subtree pathname beneath the node object. For example, if the node object had a pathname like: +- `parent : pObject`: A pointer to the Object structure maintained by the OSML. This structure includes some useful fields: + + - `parent->Mode : int`: A bitmask of the OBJ_O_* flags, which include: `OBJ_O_RDONLY` (read only), `OBJ_O_WRONLY` (write only), `OBJ_O_RDWR` (read/write), `OBJ_O_CREAT` (create), `OBJ_O_TRUNC` (truncate), and `OBJ_O_EXCL` (exclusive, see above). + + - `parent->Pathname : pPathname`: A pointer to a Pathname struct (defined in `include/obj.h`) which contains the complete parsed pathname for the object. This provides a buffer for the pathname as well as an array of pointers to the pathname's components. The function `obj_internal_PathPart()` can be used to obtain at will any component or series of components of the pathname. - /datasources/OMSS_DB + - `parent->Pathname->OpenCtl : pStruct[]`: Parameters for the open() operation, as defined by the driver author. These are specified in the path in a similar way to URLs (`example.qy?param1=value¶m2=other_value`). Drivers typically only use `parent->Pathname->OpenCtl[parent->SubPtr]` (see SubPtr below) to retrieve their own parameters, ignoring parameters passed to other drivers in the path. -and the user opened an object called: + - `parent->SubPtr : short`: The number of components in the path that are a part of the path to the driver's node object, including the `.` for the top level directory and the driver's node object. For example, in the path of `/data/file.csv`, the path would be internally represented as `./ data/ file.csv`, so SubPtr is 3. + + - For example, use `obj_internal_PathPart(parent->Pathname, parent->SubPtr - 1, 1)` to get the name of the file being opened, and use `obj_internal_PathPart(parent->Pathname, 0, parent->SubPtr)` to get the path. - /datasources/OMSS_DB/JNetHelp/rows/1 + - `parent->SubCnt : short`: _The driver should set this value_ to show the number of components it controls. This includes the driver's node object, so `SubCnt` will always be at least 1. For example, when opening `/data/file.csv/rows/1`, the CSV driver will read the `SubPtr` of 3 (see above), representing `./ data/ file.csv`. It will then set a `SubCnt` of 3, representing that it controls `file.csv /rows /1`. (The driver only sets `SubCnt`; `SubPtr` is provided.) -the OS driver would have to determine what the subtree pathname 'JNetHelp/rows/1' means, since this path will mean different things to different os drivers. + - `parent->Prev : pObject`: The underlying object as opened by the next-lower-level driver. The file can be accessed and parsed by calling functions and passing this pointer to them (such as the st_parse functions, see below). **DO NOT attempt to open the file directly with a call like `fopen()`,** as this would require hard coding the path to the root directory of the object system, which *will* break if the code runs on another machine. -The Open routine also must determine whether the object already exists or not, and if not, whether to create a new object. This logic is largely dependent on the obj->Mode flags, as if O_CREAT is included, the driver must attempt to create the object if it does not already exist, and if O_EXCL is included, the driver must refuse to open the object if it already exists, as with the UNIX open() system call semantics. + - `parent->Prev->Flags : short`: Contains some useful flags about the underlying object, such as: + - `OBJ_F_CREATED`: The underlying object was just created by this open() operation. In that case, this driver is expected to create the node with `snNewNode()` (see later in this document) as long as `parent->Mode` contains `O_CREAT`. -Finally, if the os driver specified a capability of OBJDRV_C_TRANS, it must pay attention to the current state of the end-user's trans- action. If a new object is being created, an object is being deleted, or other modifications/additions are being performed, and if the OXT layer indicates a transaction is in process, the driver must either complete the current transaction and then complete the current call, or else add the current delete/create/modify call to the transaction tree (in which case the tree item is preallocated; all the driver needs to do is fill it in). The transaction layer will be discussed in depth later in this document. +- `mask : int`: The permission mask to be given to the object, if it is being created. Typically, this will only apply to files and directories, so most drivers can ignore it. The values are the same as the UNIX [octal digit permissions](https://en.wikipedia.org/wiki/Chmod#:~:text=Octal%20digit%20permission) used for the `chmod()` command. -As a part of the Open process, the OS driver will normally allocate an internal structure to represent the current open object, and will return that structure as a void* data type in the return value. This pointer will be then passed to each of the other driver entry point functions, with the exception of QueryFetch, QueryDelete, and Query- Close, which will be discussed later. +- `sys_type : pContentType`: Indicates the content type of the node object as determined by the OSML. The ContentType structure is defined in `include/obj.h`. `sys_type->Name` lists the name of the content type (e.g. `"system/query"` for the query driver). This is also the type used to select which driver should open the node object, so it will be one of the types registered in the `Initialize()` function. -The Open() routine is called with five parameters: +- `usr_type : char*`: The object type requested by the user. This is normally used when creating a new object, though some drivers also use it when opening an existing object. For example, the reporting driver generates HTML report text or plaintext reports if `usr_type` is `"text/html"` or `"text/plain"` (respectively). -- obj (pObject) - This is a pointer to the Object sturcture maintained by the OSML. This structure will contain some important fields for processing the open() request. +- `oxt : pObjTrxTree*`: The transaction tree, used when the driver specifies the `OBJDRV_C_TRANS` capability. More on this field later. Non-transaction-aware drivers can safely ignore this field. - obj->Mode is a bitmask of the O_* flags, which include O_RDONLY, O_WRONLY, O_RDWR, O_CREAT, O_TRUNC, and O_EXCL. + - 📖 **Note**: Yes, this param *is* a pointer to a pointer. Essentially, a pointer passed by reference. This allows the driver to create a new transaction tree even if none is in progress. - obj->Pathname is a Pathname structure which contains the complete parsed pathname for the object. This structure is defined in the file include/obj.h, and has a buffer for the pathname as well as an array of pointers to the pathname's components. The function obj_internal_PathPart() can be used to obtain at will any component or series of components of the pathname. - obj->Pathname->OpenCtl[] contains parameters to the open() operation. Frequently these params provide additional information on how to open the object. The use of these parameters is determined by the author of the objectsystem driver. The parameters are those passed in normal URL fasion (?param=value, etc.). Typically, the only OpenCtl of interest is going to be obj->Pathname->OpenCtl[obj->SubPtr] (see below for SubPtr meaning). +The `Open()` routine should return a pointer to an internal driver structure on success, or `NULL` on failure. It is normal to allocate one such structure per `Open()` call, and for one of the structure fields to point to shared data describing the node object. Accessing the node object is described later in this document. - obj->SubPtr is the number of components in the path that are a part of the node object's path. For example, in the above path of '/datasources/OMSS_DB', the path would be internally represented as './datasources/ OMSS_DB', and the SubPtr would be 3. +While driver instance structures may vary, some fields are common in most drivers (`inf` is the pointer to the structure here): - obj->SubCnt reflects the number of components of the path which are under the control of the current driver. This includes the node object, so SubCnt will always be at least 1. For example, when opening '/data/file.csv/rows/1', and the driver in question is the CSV driver, SubPtr would be 3 (includes an "invisible" first component), from '/data/file.csv', and SubCnt would be 3, from 'file.csv/rows/1'. The driver will need to SET THE SUBCNT value in its Open function. SubPtr is already set. +| Field | Type | Description | +|-----------|---------|-------------------------------------------------| +| inf->Obj | pObject | A copy of the `obj` pointer passed to `Open()`. | +| inf->Mask | int | The `mask` argument passed to `Open()`. | +| inf->Node | pSnNode | A pointer to the node object. | - obj->Prev is the underlying object as opened by the next-lower-level driver. It is the duty of this driver to parse the content of that object and do something meaningful with it. +The driver's node pointer typically comes from `snNewNode()` or `snReadNode()` (for structure files), but it can also be other node struct information. - obj->Prev->Flags contains some critical infor- mation about the underlying object. If it contains the flag OBJ_F_CREATED, then the underlying object was just created by this open() operation. In that case, this driver is expected to create the node with snNewNode() (see later in this document) as long as obj->Mode contains O_CREAT. +--- +### Function: OpenChild() +*(Optional)* +```c +void* xxxOpenChild(void* inf_v, pObject obj, char* child_name, int mask, pContentType sys_type, char* usr_type, pObjTrxTree* oxt); +``` +Opens a single child object of the provided object by name. Conceptually, this is similar to querying the object for all children where the name attribute equals the passed `child_name` parameter and fetching only the first result. This function is used to open children of a driver that do not map well into the driver's node object tree. For example, the query file driver uses this function to allow the caller to open a temporary collection declared in that query file. -- mask (int) - Indicates the security mask to be given to the object if it is being created. Typically, this will only apply to files and directories. The values are the same as UNIX chmod() type values. +The `OpenChild()` function is called with seven parameters: -- systype (pContentType) - This param indicates the content type of the node object as determined by the OSML. The ContentType structure is defined in include/ obj.h, and includes among other things the name of the content type. For example, for the reporting driver, this type would be "system/report". +| Param | Type | Description | +|------------|--------------|---------------------------------------------------------------------------| +| inf_v | void* | A driver instance pointer (returned from `Open()` or `QueryFetch()`). | +| obj | pObject | The child object provided by the object system. | +| child_name | char* | The value for the name attribute of the child object to be opened. | +| mask | int | The permission mask to be given to the object (if created).* | +| sys_type | pContentType | Indicates the content type of the node object as determined by the OSML.* | +| usr_type | char* | The object type requested by the user.* | +| oxt | pObjTrxTree* | The transaction tree pointer for the `OBJDRV_C_TRANS` capability. | -- usrtype (char*) - This param is the requested object type by the user and is normally used when creating a new object, though under some circumstances it may change the way the open operates on an existing object. For example, the reporting driver can change whether it generates HTML report text or plaintext reports based on usrtype being either "text/html" or "text/plain". +*See [`Open()`](#function-open) above for more info. -- oxt (pObjTrxTree*) - This param is only used by object drivers that specified a capability of OBJDRV_C_TRANS. More on this field later. For non-transaction-aware drivers, this field can be safely ignored. +The `OpenChild()` function should return a pointer to the node object for the newly opened child on success or `NULL` on failure. - Yes, this param *is* a pointer to a pointer. Essentially, a pointer passed by reference. +- 📖 **Note**: The object system stores the value returned from this function into `obj->Data`. -The Open routine should return its internal structure pointer on success, or NULL on failure. It is normal to allocate one such structure per Open call, and for the structure to point, among other things, to shared data describing the node object. Accessing the node object is described later in this document. +--- +### Function: Close() +```c +int xxxClose(void* inf_v, pObjTrxTree* oxt); +``` +The close function closes a driver instance, freeing all allocated data and releasing all shared memory such as open connections, files, or other driver instances. The driver must ensure that all memory allocated by originally opening the object (or allocated by other functions that may be called on an open object) is properly deallocated. This includes the internal structure returned by `Open()`, or by `QueryFetch()`, which is passed in as `inf_v`. The driver may also need to decrement the Open Count (`node->OpenCnt--`) if it had to increment this value during `Open()`. Before doing so, it should also perform a `snWriteNode()` to write any modified node information to the node object. -It is important to know what kinds of fields normally are placed in the allocated data structure returned by Open. These fields are all determined by the driver author, but here are a few typical ones that are helpful to have ("inf" is the pointer to the structure here): +- 📖 **Note**: Remember that the passed driver instance may originally be from a call to `Open()` or a call to `QueryFetch()`. -| Field | Type | Description -| ---------- | --------- | ------------ -| inf->Obj | pObject | This is a copy of the 'obj' pointer passed to the Open routine. -| inf->Mask | int | The 'mask' argument passed to Open. -| inf->Node | pSnNode | A pointer to the node object, as returned from snNewNode() or snReadNode(), or if structure files aren't being used as the node content type, a pointer to whatever structure contains information about the node object. +- 📖 **Note**: Even if close fails, the object should still be closed in whatever way is possible. The end-user should deal with the resulting situation by reviewing the `mssError()` messages left by the driver. -The Close() routine is called with two parameters: +- 📖 **Note**: Information may be left unfreed if it is stored in a cache for later use. -| Param | Type | Description -| ------ | ------------ | ------------ -| inf_v | void* | This param is the pointer that the Open routine returned. Normally the driver will cast the void* parameter to some other structure pointer to access the object's information. -| oxt | pObjTrxTree* | The transaction tree pointer. +The `Close()` function is called with two parameters: -The Close routine should return 0 on success or -1 on failure. The os driver must make sure it properly deallocates the memory used by originally opening the object, such as the internal structure returned by open and passed in as inf_v. +| Param | Type | Description | +|-------|--------------|-----------------------------------------------------------------------| +| inf_v | void* | A driver instance pointer (returned from `Open()` or `QueryFetch()`). | +| oxt | pObjTrxTree* | The transaction tree pointer for the `OBJDRV_C_TRANS` capability. | -Note the semantics of a Close failure - the object should still be closed in whatever way is still meaningful. The end-user must deal with the situation by reviewing the returned mssError messages. +The `Close()` function should return 0 on success or -1 on failure. -Before exiting, the Close routine should make sure it decrements the Open Count (node->OpenCnt--). Before doing so, it should also perform a snWriteNode() to write any modified node information back to the node object. -### C. Creating and Deleting Objects. -The Create and Delete functions are used for creating and deleting objects. Normally, the os driver will process the Pathname in the same manner for Create and Delete as for Open, thus such functionality could be placed in another function. +### Function: Create() +```c +int xxxCreate(pObject obj, int mask, pContentType sys_type, char* usr_type, pObjTrxTree* oxt); +``` +The `Create()` function is used to create a new object, and uses the same parameters and return value as `Open()` (documented in detail above). This often means adding a new file to the file system to represent the object. Many drivers do not implement this and recommend that driver end-users create files using a standard text editor or programmatically using more general means, such as general structure file generation. If implemented, this function frequently requires very similar path parsing functionality to `Open()`. -As a side note, within Centrallix, the standard function naming convention is to use xxx_internal_FunctionName for functions that are more or less internal to the module and not a part of any standard interface. +- 📖 **Note**: For many drivers, the `Create()` function calls the driver's `Open()` function with `O_CREAT`, then calls its `Close()` function, although some drivers may manage this differently. -The Create routine has parameters identical to the Open routine. It should return 0 on success and -1 on error. -The Delete routine is passed the following parameters: +### Function: Delete() +```c +int clusterDelete(pObject obj, pObjTrxTree* oxt); +``` +The `Delete()` function is used to delete an object, which often means removing a file from the file system. The Delete routine is passed the following parameters: -| Param | Type | Description -| ------ | ------------- | ------------ -| obj | pObject | The Object structure pointer, used in the same way as in Open and Delete. -| oxt | pObjTrxTree* | The transaction tree pointer. +| Param | Type | Description | +|-------|--------------|---------------------------------------------------------------------------| +| obj | pObject | The Object structure pointer, used in the same way as in Open and Delete. | +| oxt | pObjTrxTree* | The transaction tree pointer for the `OBJDRV_C_TRANS` capability. | -Delete should return 0 on success and -1 on failure. +`Delete()` should return 0 on success and -1 on failure. -For many objectsystem drivers, the Create function simply calls the driver's internal Open() with O_CREAT and then its internal Close, although some drivers could manage Create differently from Open. -### D. Reading and Writing Object Content. -Some, but not all, objects will have content. If the object does or can have content, the driver should handle these functions as is appropriate. Otherwise, the driver should return a failure code (-1) from these functions. +### Function: DeleteObj() +```c +int xxxDeleteObj(void* inf_v, pObjTrxTree* oxt); +``` +**No documentation provided.** -The Read routine reads content from the object, as if reading from a file. The parameters passed are almost identical to those used in the fdRead command in MTASK: -| Parameter | Type | Description -| --------- | ------------- | ------------ -| inf_v | void* | The generic pointer to the structure returned from Open(). -| buffer | char* | The destination buffer for the data being read in. -| maxcnt | int | The maximum number of bytes to read into the buffer. -| flags | int | Either 0 or FD_U_SEEK, in which case the user is specifying the seek offset for the read in the 5th argument. Of course, not all objects will be seekable, and furthermore, some of the objects handled by the driver may have full or limited seek functionality, even though others may not. -| arg | int | Extra argument, currently only used to specify an optional seek offset. -| oxt | pObjTrxTree* | The transaction tree pointer. +### Function: Read() +```c +int xxxRead(void* inf_v, char* buffer, int max_cnt, int offset, int flags, pObjTrxTree* oxt); +``` -The Write routine is very similar, except that instead of 'maxcnt', the third argument is 'cnt', and specifies how much data is in the buffer waiting to be written. +The `Read()` function reads content from objects that have content, similar to reading content from a file. If the object does or can have content, the driver should handle these functions as is appropriate. Otherwise, the driver should return a failure code (-1) and call `mssError()` in these functions. -Each of these routines should return -1 on failure and return the number of bytes read/written on success. At end of file or on device hangup, 0 should be returned once, and then subsequent calls should return -1. +The parameters passed are intentionally similar to the `fdRead()` function in `mtask.c`: -### E. Querying for Child Objects. -Many objects will have the capability of having sub-objects beneath them, called child objects. In such a case, the parent object becomes a directory of sorts, even though the parent object may also have content, something which is somewhat foreign in the standard filesystem world, but is common for web servers, where opening a directory returns the file 'index.html' on many occasions. +| Parameter | Type | Description | +|-----------|--------------|------------------------------------------------------------------------------------------------------------------------------| +| inf_v | void* | A driver instance pointer (returned from `Open()` or `QueryFetch()`). | +| buffer | char* | The buffer where read data should be stored. | +| max_cnt | int | The maximum number of bytes to read into the buffer. | +| offset | int | An optional seek offset. | +| flags | int | Either `0` or `FD_U_SEEK`. If `FD_U_SEEK` is specified, the caller should specify a seek offset in the 4th argument (`arg`). | +| oxt | pObjTrxTree* | The transaction tree pointer for the `OBJDRV_C_TRANS` capability. | -To enumerate a parent object's child objects, the query functions are used. A query may have a specific criteria so that only objects having certain attributes will be listed. As mentioned earlier in this document, a driver may or may not choose to intelligently handle those criteria. The driver has the option of always enumerating all child objects via its query functions, and allowing the OSML filter them and only return to the user the objects that match the criteria. But it also can do the filtering itself or, more typically, pass the filtering on to the source of the data the driver manages, as with a database server. +- 📖 **Note**: Not all objects can be seekable and some of the objects handled by the driver may have limited seek functionality, even if others do not. -The query mechanism can also be used to delete a set of child objects, optionally matching a certain criteria. The QueryDelete method may be left NULL in the ObjDriver structure if the driver does not implement full query support, in which case the OSML will iterate through the query results and delete the objects one by one. +Each of these routines should return -1 on failure and return the number of bytes read/written on success. At end of file or on device hangup, 0 should be returned once, and then subsequent calls should return -1. -The first main function for handling queries is OpenQuery. This function is passed three arguments: +- 📖 **Note**: There is no separate seek command to help mitigate [Time-of-check to time-of-use attacks](https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use). To seek without reading data, specify a buffer size of zero. -- inf_v (void*) The value returned from Open for this object. -- query (pObjQuery) The query structure setup by the OSML. It will contain several key fields: +### Function: Write() +```c +int xxxWrite(void* inf_v, char* buffer, int cnt, int offset, int flags, pObjTrxTree* oxt); +``` +The `Write()` function is very similar to the `Read()` function above, allowing the caller to write data to objects of supporting drivers with content. However, the third argument (`max_cnt`) is replaced with `cnt`, specifying the number of bytes of data in the buffer that should be written. - query->QyText: the text of the criteria (i.e., the WHERE clause, in Centrallix SQL syntax) - query->Tree: the compiled expression tree, which evaluates to nonzero for true or zero for false as the WHERE clause condition. +### Function: OpenQuery() +```c +void* xxxOpenQuery(void* inf_v, pObjQuery query, pObjTrxTree* oxt); +``` +The `OpenQuery()` function opens a new query instance struct for fetching query results from a specific driver instance. Queries are often used to enumerate an object's child objects, although this is not a requirement. Queries may include specific criteria, and the driver may decide to intelligently handle them (either manually or, more often, by passing them on to a lower level driver or database) or simply to enumerating all results with its query functions. In the latter case, the OSML layer will filter results and only return objects that match the criteria to the user. - query->SortBy[]: an array of expressions giving the various components of the sorting criteria. +`OpenQuery()` is passed three parameters: - query->Flags: the driver should set and/or clear the flags OBJ_QY_F_FULLQUERY and OBJ_QY_F_FULLSORT if need be. The former indicates that the driver is willing to handle the full WHERE clause (the query->Tree). The latter indicates that the driver is willing to handle the sorting of the data as well (in query->SortBy[]). If the driver can easily have the sorting/selection done (as when querying an RDBMS), it should set these flags. Otherwise, it should let the OSML take care of the ORDER BY and WHERE conditions. +| Parameter | Type | Description | +|-----------|--------------|-----------------------------------------------------------------------| +| inf_v | void* | A driver instance pointer (returned from `Open()` or `QueryFetch()`). | +| query | pObjQuery | A query structure created by the object system. | +| oxt | pObjTrxTree* | The transaction tree pointer for the `OBJDRV_C_TRANS` capability. | -- oxt (pObjTrxTree*) The transaction tree pointer. +The `query : pObjQuery` parameter contains several useful fields: +| Parameter | Type | Description +| --------------- | ----------------------- | ------------ +| query->QyText | char* | The text specifying the criteria (i.e., the WHERE clause, in Centrallix SQL syntax). +| query->Tree | void* (pExpression) | The compiled expression tree. This expression evaluates to a nonzero value for `true` if the where clause is satisfied, or zero for `false` if it is not. +| query->SortBy[] | void*[] (pExpression[]) | An array of expressions giving the various components of the sorting criteria. +| query->Flags | int | The driver should set and/or clear the `OBJ_QY_F_FULLQUERY` and `OBJ_QY_F_FULLSORT` flags, if needed. -The OpenQuery function should return a void* value, which will within the driver point to a structure used for managing the query. This structure will normally have a pointer to the inf_v value returned by Open as well, since inf_v is never passed to QueryFetch, QueryDelete or QueryClose. OpenQuery should return NULL if the object does not support queries or if some other error condition occurs that will prevent the execution of the query. +The `OBJ_QY_F_FULLQUERY` flag indicates that the driver will handle the full `where` clause specified in `query->Tree`. Even if this flag is not specified, the driver is still free to use the provided `where` clause to pre-filter data, which improves performance when the Object System does its final filtering. However, setting this flag disables the Object System filtering because it promises that the driver will _always_ handle _all_ filtering for _every_ valid queries. -Once the query is underway with OpenQuery, the user will either start fetching the results with QueryFetch, or will issue a delete operation with QueryDelete. +The `OBJ_QY_F_FULLSORT` flag indicates that the driver will handle all sorting for the data specified in `query->SortBy[]`. -The QueryFetch routine should return an inf_v pointer to the child object, or NULL if no more child objects are to be returned by the query. Some drivers may be able to use their internal Open function to generate the newly opened object, although others will directly allocate the inf_v structure and fill it in based on the current queried child object. QueryFetch will be passed these parameters: +If the driver can easily handle sorting/selection (as when querying a database), it should set these flags. Otherwise, it should let the OSML handle the ORDER BY and WHERE conditions to avoid unnecessary work for the driver author. -| Parameter | Type | Description -| ---------- | -------------- | ------------ -| qy_v | void* | The value returned by OpenQuery. -| obj | pObject | The newly-created object structure that the OSML is using to track the newly queried child object. -| mode | int | The open mode for the new object, as with obj->Mode in Open(). -| oxt | pObjTrxTree* | The transaction tree pointer. +The `OpenQuery()` function returns a `void*` for the query instance struct, which will be passed to the other query functions (`QueryDelete()`, `QueryFetch()`, and `QueryClose()`). This structure normally points to the driver instance struct to allow easy access to queried data. `OpenQuery()` returns `NULL` if the object does not support queries or if an error occurs, in which case `mssError()` should be called before returning. -All object drivers will need to add an element to the obj->Pathname structure to indicate the path to the child object being returned. This will involve a process somewhat like this: (given that new_name is the new object's name, qy is the current query structure, which contains a field 'Parent' that points to the inf_v originally returned by Open, and where the inf_v contains a field Obj that points to the Object structure containing a Pathname structure) - int cnt; - pObject obj; - char* new_name; - pMyDriversQueryInf qy; +### Function: QueryDelete() +*(Optional)* +```c +int xxxQueryDelete(void* qy_v, pObjTrxTree* oxt); +``` +Deletes results in the query result set, optionally matching a certain criteria. `QueryDelete()` is passed two parameters: - /** Build the filename. **/ - cnt = snprintf(obj->Pathname->Pathbuf, 256, "%s/%s", - qy->Parent->Obj->Pathname->Pathbuf,new_name); - if (cnt < 0 || cnt >= 256) return NULL; - obj->Pathname->Elements[obj->Pathname->nElements++] = - strrchr(obj->Pathname->Pathbuf,'/')+1; +| Parameter | Type | Description +| --------- | ------------- | ------------ +| qy_v | void* | A query instance pointer (returned from `QueryOpen()`). +| oxt | pObjTrxTree* | The transaction tree pointer for the `OBJDRV_C_TRANS` capability. -QueryDelete is passed the qy_v void* parameter, and an oxt parameter. It should return 0 on successful deletion, and -1 on failure. +`QueryDelete()` returns 0 to indicate a successful deletion, or -1 to indicate failure, in which case `mssError()` should be called before returning. -QueryClose is also passed qy_v and oxt. It should close the query, whether or not QueryFetch has been called enough times to enumerate all of the query results. +If a delete is needed and this method is not implemented, the OSML will iterate through the query results and delete the objects one by one. -### F. Managing Object Attributes -All objects will have at least some attributes. Five attributes are mandatory: 'name', 'content_type', 'inner_type', 'outer_type', and 'annotation'. All compliant drivers must implement these five attributes, all of which have a data type of DATA_T_STRING. -Currently, the OS specification includes support for the following data types: +### Function: QueryFetch() +```c +void* xxxQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt); +``` +The `QueryFetch()` function fetches a driver instance pointer (aka. an `inf_v` pointer) to a child object, or `NULL` if there are no more child objects. It may be helpful to think of `QueryFetch()` as similar to an alternate form of `Open()`, even if your driver does not implement the functionality to `Open()` every object that can be found with `QueryFetch()`. In fact, some drivers may use an internal `Open()` function to generate the opened objects. + +`QueryFetch()` takes four parameters: + +| Parameter | Type | Description +| ---------- | ------------- | ------------ +| qy_v | void* | A query instance struct (returned by `OpenQuery()`). +| obj | pObject | An object structure that the OSML uses to track the newly queried child object. +| mode | int | The open mode for the new object, the same as `obj->Mode` in `Open()`. +| oxt | pObjTrxTree* | The transaction tree pointer for the `OBJDRV_C_TRANS` capability. + +The driver should add an element to the `obj->Pathname` structure to indicate the path of the returned child object. This will involve a process somewhat like this, where: +- `new_name : char*` is the new object's name. +- `qy : pMyDriversQueryInf` is the current query structure. +- `qy->Parent->Obj->Pathname : pPathname` points to the affected Pathname struct. + +```c +int count; +pObject obj; +char* new_name; +pMyDriversQueryInf qy; + +/** Build the new filename. **/ +count = snprintf(obj->Pathname->Pathbuf, 256, "%s/%s", qy->Parent->Obj->Pathname->Pathbuf, new_name); +if (count < 0 || 256 <= count) goto error_handling; +obj->Pathname->Elements[obj->Pathname->nElements++] = strrchr(obj->Pathname->Pathbuf, '/') + 1; +``` -- DATA_T_INTEGER - 32-bit signed integer. -- DATA_T_STRING - Zero-terminated ASCII string. -- DATA_T_DOUBLE - Double-precision floating point. -- DATA_T_DATETIME - date/time structure. -- DATA_T_MONEY - money data type. +### Function: QueryCreate() +```c +void* xxxQueryCreate(void* qy_v, pObject new_obj, char* name, int mode, int permission_mask, pObjTrxTree *oxt); +``` +The `QueryCreate()` function is just a stub function that is not fully implemented yet. Simply not providing it (aka. setting the location in the driver initialization struct to `NULL`) is fine. -True/false or on/off attributes should be treated as DATA_T_INTEGER for the time being with values of 0 and 1. -Here is a description of the functionality of the five mandatory attributes: +### Function: QueryClose() +```c +int xxxQueryClose(void* qy_v, pObjTrxTree* oxt); +``` +The `QueryClose()` function closes a query instance, freeing all allocated data and releasing all shared memory such as open connections, files, or other driver instances. This function operates very similarly to `Close()`, documented in detail above. The query should be closed, whether or not `QueryFetch()` has been called enough times to enumerate all of the query results. -| Attribute | Description -| -------------- | ------------ -| 'name' | This attribute indicates the name of the object, just as it should appear in any directory listing. The name of the object must be unique for the directory it is in. -| 'content_type' | This is the type of the object's content, given as a MIME-type. -| 'annotation' | This is an annotation for the object. While users may not assign annotations to all objects, each object should be able to have an annotation. Normally the annotation is a short description of what the object is. For the Sybase driver, annotations for rows are created by assigning an 'expression' to the table in question, such as 'first_name + last_name' for a people table. -| 'inner_type' | An alias for 'content_type'. Both should be supported. -| 'outer_type' | This is the type of the object itself (the container). -A sixth attribute is not mandatory, but is useful if the object might have content that could in turn be a node object (be interpreted by another driver). This attribute is 'last_modification', of type DATA_T_DATETIME, and should indicate when the object's content was last updated or modified. +### Object Attributes +All objects can have attributes, and there are five required attributes that all drivers must implement (explained below). -The first function to be aware of is the GetAttrType function. This routine takes the inf_v pointer, the name of the attribute in question, and the oxt* pointer. It should return the DATA_T_xxx value for the data type of the attribute. +Currently, the OS specification includes support for the following data types: -Next is the GetAttrValue function, which takes four parameters: the inf_v pointer, the name of the attribute, a void pointer pointing to where the attribute's value will be put, and the oxt* pointer. The way the value pointer is handled depends on the data type. For DATA_T_INTEGER types, the value pointer is assumed to be pointing to a 32-bit integer where the integer value can be written. For DATA_T_ STRING types, the value pointer is assumed to be pointing to an empty pointer location where a pointer to the string can be stored. For DATA_T_DATETIME types, the value pointer is assumed to be pointing to an empty pointer where a pointer to a date time structure (from obj.h) can be stored. And for double values, the value pointer points to a double value where the double will be stored. In this way, integer and double values are returned from GetAttrValue by value, and string or datetime values are returned from GetAttrValue by reference. Items returned by reference must be guaranteed to be valid until the object is closed, or another GetAttrValue or SetAttrValue call is made. This function should return -1 on a non-existent attribute, 0 on success, and 1 if the value is NULL or unset. +| Name | Description +| ----------------- | ------------ +| `DATA_T_INTEGER` | 32-bit signed integer. +| `DATA_T_STRING` | Null-terminated ASCII string. +| `DATA_T_DOUBLE` | Double-precision floating point number. +| `DATA_T_DATETIME` | Date/time structure. +| `DATA_T_MONEY` | Money structure. -UPDATE ON GETATTR/SETATTR: These functions now, instead of taking a void* pointer for the value, take a pObjData pointer, which points to an ObjData structure. The POD(x) macro can be used to typecast appropriate pointers to a pObjData pointer. The ObjData structure is a UNION type of structure, allowing easy manipulation of data of various types. See 'datatypes.h'. Note that this is binary compatible with the old way of using a typecasted void pointer. +See `datatypes.h` for more information. -The SetAttrValue function works much the same way as GetAttrValue, just with the information moving in the opposite direction. The third parameter, void* value, is treated in the same manner. +For `true`/`false` or `on`/`off` attributes, use `DATA_T_INTEGER` where 0 indicates `false` and 1 indicates `true`. -The GetFirstAttr and GetNextAttr functions each take two parameters, the inf_v pointer and the oxt* pointer, and are used to iterate through the non-mandatory attributes for the object. GetFirstAttr should return a string naming the first attribute, and GetNextAttr should iterate through subsequent attributes. When the attributes are exhausted, these functions should return NULL. The attributes 'name', 'annotation', and 'content_type' should not be returned. If the object has no other attributes, GetFirstAttr should return NULL. +The following five attributes are required (all are of type `DATA_T_STRING`): -AddAttr is used to add a new attribute to an existing object. Not all objects support this, and many will refuse the operation. The parameters are as follows: void* inf_v, char* attrname, int type, void* value, and pObjTrxTree* oxt. +| Attribute | Description +| ------------ | ------------ +| name | The name of the object, just as it appears in any directory listing. The name of the object must always be unique for its level in the tree (e.g. a unique file name in a directory, the primary key of a database row, etc.). +| annotation | A short description of the object. While users may not assign annotations to all objects, each object should be able to have an annotation. For example, in the Sybase driver, annotations for rows are created by assigning an 'expression' to the table in question, such as `first_name + last_name` for a people table. This attribute should _never_ be null, however, it can be an empty string (`""`) if the driver has no meaningful way to provide an annotation. +| content_type | The type of the object's content, given as a MIME-type. Specify `"system/void"` if the object does not have content. +| inner_type | An alias for 'content_type'. Both should be supported. +| outer_type | This is the type of the object itself (the container). -OpenAttr is used to open an attribute for objRead/objWrite as if it were an object with content. Not all object drivers will support this; this routine should return an inf_v pointer for the new descriptor, and takes four parameters: void* inf_v, char* attrname, int mode, and pObjTrxTree* oxt. The mode is used in the same manner as the Open function. +The `last_modification : DATA_T_DATETIME` attribute is a sixth, optional attribute that may be useful in some situations. This attribute should indicate the last time that the object's content was modified or updated. -### G. Managing Object Methods -Objects may optionally have methods associated with them. Each method is given a unique name within the object, and can take a single string parameter. Three functions exist for managing methods. -The first two functions, GetFirstMethod and GetNextMethod, work identically to their counterparts dealing with attributes. The third function, ExecuteMethod, starts a method executing. This function takes four parameters: the inf_v pointer, the name of the method, the optional string parameter, and the oxt* pointer. +### Function: GetAttrType() +```c +int xxxGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt); +``` +The `GetAttrType()` function returns DATA_T_xxx value for the datatype of the requested. It takes three parameters: -## III Reading the Node Object -The Node object has content which controls what resource(s) this driver will actually access, so it is important for the driver to access the node object's content. If the driver's node objects are structure files (which is normally the case when dealing with a remote network resource), then the SN module can make opening the node object much more painless. It also performs caching automatically to improve performance. +| Parameter | Type | Description +| --------- | ------------- | ------------ +| inf_v | void* | A driver instance pointer (returned from `Open()` or `QueryFetch()`). +| attr_name | char* | The name of the attribute to be queried. +| oxt | pObjTrxTree* | The transaction tree pointer for the `OBJDRV_C_TRANS` capability. -Note that the Node object will technically ALREADY BE OPEN as an object in the objectsystem. The OSML does that for you. If your driver will not use the SN/ST modules, then it should read the node object via the normal objRead() function, and write it via objWrite(). Your driver should NEVER objClose() the node object! The OSML does that for you. +This function should return `DATA_T_UNAVAILABLE` if the requested attribute does not exist on the driver instance. It should return -1 to indicate an error, in which case `mssError()` should be called before returning. + +For example, calling the following on any driver should return `DATA_T_STRING`. +```c +int datatype = driver->GetAttrType(inf_v, 'name', oxt); +``` -An objectsystem driver will commonly configure itself by reading a text file at the root of its object subtree. There are two main modules available for making this easier. -The normal way to manage object parameters is to use a structure file. Structure files are a little more complicated, but allow for arrays of values for a given attribute name, as well as allowing for tree- structured hierarchies of attributes and values. Structure files are accessed via the stparse and st_node modules. The stparse module provides access to the individual attributes and groups of attributes, and the st_node module loads and saves the structure file heirarchies as a whole. The st_node module also provides node caching to reduce disk activity and eliminate repeated parsing of one file. +### Function: GetAttrValue() +```c +int xxxGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pObjTrxTree* oxt); +``` +The `GetAttrValue()` function takes four parameters: -For example, if two sessions open two files, '/test1.rpt' and '/test2.rpt' the st_node (SN) module will cache the internal representations of these node object files, and for successive uses of these node objects, the physical file will not be re-parsed. The file will be re-parsed if its timestamp changes. +| Parameter | Type | Description +| --------- | ------------- | ------------ +| inf_v | void* | A driver instance pointer (returned from `Open()` or `QueryFetch()`). +| attr_name | char* | The name of the attribute to be queried. +| datatype | int | The expected datatype for the requested value. +| val | pObjData | A pointer to a location where the value of the attribute should be stored. +| oxt | pObjTrxTree* | The transaction tree pointer for the `OBJDRV_C_TRANS` capability. + +The value pointer points to a union struct which can hold one of several types of data in the same memory location. Which type of data is expected depends on the value of the `datatype` parameter. +| Field | Datatype | Description +| ----------- | ------------------ | ----------- +| `Integer` | `DATA_T_INTEGER` | Store an `int` in the `val->Integer` field. +| `String` | `DATA_T_STRING` | Store a `char*` in the `val->String` field. +| `Double` | `DATA_T_DOUBLE` | Store a `double` in the `val->Double` field. +| `DateTime` | `DATA_T_DATETIME` | Store a `pDateTime`* in the `val->DateTime` field. +| `IntVec` | `DATA_T_INTVEC` | Store a `pIntVec`* in the `val->IntVec` field. +| `StringVec` | `DATA_T_STRINGVEC` | Store a `pStringVec`* in the `val->StringVec` field. +| `Money` | `DATA_T_MONEY` | Store a `pMoneyType`* in the `val->Money` field. +| `Generic` | ? | Store a `void*` in the `val->Generic` field (target data is usually implementation dependant). + +\*_See [`datatypes.h`](../centrallix-lib/include/datatypes.h) for more info about this datatype._ + +In this way, `int`s and `double`s can be returned by value while other types are returned by reference. Items returned by reference must be guaranteed to be valid until either the object is closed, or another `GetAttrValue()` or `SetAttrValue()` call is made on the same driver (which ever happens first). + +This function should return 0 on success, 1 if the value is `NULL` or undefined / unset, or -1 on a non-existent attribute or other error. + +- 📖 **Note**: The caller can use the `POD(x)` macro to typecast appropriate pointers to the `pObjData` pointer. For example: + ```c + char* name; + if (xxxGetAttrValue(obj, "name", DATA_T_STRING, POD(&name)) != 0) + goto error_handling; + printf("Object name: \"%s\"\n", name); + ``` + +- 📖 **Note**: In legacy code, a type cast `void*` was used instead of a `pObjData` pointer used today. This method was binary compatible the current solution because of the union struct implementation (See [`datatypes.h`](../centrallix/include/datatypes.h) for more information). + + +### Function: SetAttrValue() +```c +int xxxSetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pObjTrxTree* oxt); +``` +The `SetAttrValue()` function is the same as `GetAttrValue()`, however it sets the value by reading it from the `val` parameter instead of getting the value by writing it to the `val` parameter. The return value is also identical, and `mssError()` should be invoked on failure, or if setting attributes programmatically is not implemented. -If the underlying object does not support the attribute "last_modification" (assumed to be the timestamp), then SN prints a warning. In essence, this warning indicates that changes to the underlying object will not trigger the SN module to re-read the structure file defining the node object. Otherwise, the SN module keeps track of the timestamp, and if it changes, the node object is re-read and re-parsed. -The driver's first course of action to obtain node object data is to open the node object with the SN module. The SN module's functions are listed below: +### Function: GetFirstAttr() & GetNextAttr() +```c +char* xxxGetFirstAttr(void* inf_v, pObjTrxTree* oxt); +char* xxxGetNextAttr(void* inf_v, pObjTrxTree* oxt); +``` +These functions return the names of attributes that can be queried on an object. They both take the same two parameters. -### pSnNode snReadNode(pObject obj) -This function reads a Structure File from the already-open node object which is passed in the "obj" parameter in the xxxOpen() routine. The "obj" parameter has an element, obj->Prev, which is a link to the node object as opened by the previous driver in the OSML's chain of drivers for handling this open(). All you need to know to get the parsed node object is the following: +| Parameter | Type | Description +| --------- | ------------- | ------------ +| inf_v | void* | A driver instance pointer (returned from `Open()` or `QueryFetch()`). +| oxt | pObjTrxTree* | The transaction tree pointer for the `OBJDRV_C_TRANS` capability. - pSnNode node; +These functions should only return the names of significant values, so `name`, `annotation`, etc. should not be returned from these functions, even though they are required to be valid values for any object. Typically, this is implemented by `GetFirstAttr()` resetting some internal value in the driver `inf_v`, then returning the result of `GetNextAttr()`. `GetNextAttr()` extracts a string from an array or other list of valid attribute names for the object and increments the internal counter. Once the attributes are exhausted, `GetNextAttr()` returns `NULL` and `GetFirstAttr()` can be used to restart and begin querying elements from the start of the list again. If an object has no significant attributes, `GetFirstAttr()` and `GetNextAttr()` both return NULL. - node = snReadNode(obj->Prev); -The returned node structure is managed by the SN module and need not be nmFree()ed. The only thing that must be done is that the driver should increment the node structure's link count like this: +### Function: AddAttr() +```c +int clusterAddAttr(void* inf_v, char* attr_name, int type, pObjData val, pObjTrxTree* oxt); +``` +The `AddAttr()` function adds a new attribute to an existing object. Not all objects support this, and many will refuse the operation. The parameters are the same as those of `GetAttrValue()` and `SetAttrValue()`, documented in detail above. - node->OpenCnt++; -When closing an object (and thus releasing a reference to the Node structure), the driver should decrement the link count. +### Function: OpenAttr() +```c +void* clusterOpenAttr(void* inf_v, char* attr_name, int mode, pObjTrxTree* oxt); +``` +The `OpenAttr()` function is used to open an attribute for `objRead()`/`objWrite()` as if it were an object with content. Not all object drivers will support this, and many will refuse the operation. -### pSnNode snNewNode(pObject obj, char* content_type) -This function creates a new node object with a given content type. The open link count should be incremented as appropriate, as before with snReadNode(). +This function takes 4 parameters. `inf_v`, `attr_name`, and `oxt` are the same as they are for `GetAttrValue()` and `SetAttrValue()`. `mode` is the same as it is for `Open()`. This function should return an `inf_v` pointer for the new descriptor (similar to `Open()` and `QueryFetch()` above). - pSnNode node; - node = snNewNode(obj->Prev, "system/structure"); +### Function: ExecuteMethod() +```c +int clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree* oxt); +``` +The `ExecuteMethod()` function is used to execute a method on an object. This feature is rarely used, but some drivers have created methods for actions like dropping their cache or printing debug information. Each method has a unique name within that object, and can take a single string parameter. -The "system/structure" argument is the type that will be assigned to the newly created node object. Note that the underlying object must already exist in order for this to create a node object as that object's content. Normally the OSML does this for you by commanding the previous driver (handling obj->Prev) to create the underlying object in question. +The `ExecuteMethod()` function takes four parameters: -### int snWriteNode(pSnNode node) -This function writes a node's internal representation back out to the node file. The node's status (node->Status) should be set to SN_NS_DIRTY in order for the write to actually occur. Otherwise, snWriteNode() does nothing. +| Parameter | Type | Description +| ----------- | ------------- | ------------ +| inf_v | void* | A driver instance pointer (returned from `Open()` or `QueryFetch()`). +| method_name | char* | The name of the method to be executed. +| param | pObjData | A pointer to a location where the string value of the param is stored. +| oxt | pObjTrxTree* | The transaction tree pointer for the `OBJDRV_C_TRANS` capability. -### int snDeleteNode(pSnNode node) -This function deletes a node file. At this point, does not actually delete the file but instead just removes the node's data structures from the internal node cache. +- 📖 **Note**: The `pObjData` type of the `param` parameter makes it possible that other types of parameters could be supported in the future, however, this is not currently implemented. -### int snGetSerial(pSnNode node) -This function returns the serial number of the node. Each time the node is re-read because of modifications to the file or is written via snWriteNode because of modifications to the internal structure, the serial number is increased. This is a good way for a driver to refresh internal information that it caches should it determine a node object has changed. +The function returns 0 on success, and -1 to indicate an error, in which case `mssError()` should be called before returning. -The stparse module is used to examine the parsed contents of the node file. A node file using the stparse module (and thus st_node module) has a structure file format; see StructureFile.txt. The file format is a tree structure with objects, subobjects, and attributes. The internal parsed representation is a tree, with each tree node being an object in the structure file, and each node having attributes, each of which is also a tree node. Thus, there are three different node types in the tree representation: the top-level ST_T_STRUCT element, which can contain subgroups and attributes; a mid-level ST_T_SUBGROUP tree node, which has a content type, name, and can contain attributes and other subgroups, and lastly a ST_T_ATTRIB node which contains an attribute name and attribute values, either integer or string, and optional lists of such up to 64 items in length. To use this module, include the file stparse.h. -The following functions are used to manage a parsed structure file: +### Function: GetFirstMethod() & GetNextMethod() +```c +char* xxxGetFirstMethod(void* inf_v, pObjTrxTree* oxt); +char* xxxGetNextMethod(void* inf_v, pObjTrxTree* oxt); +``` +These functions work the same as `GetFirstAttr()` and `GetNextAttr()` (respectively), except that they return the method names instead of the attribute names. -### pStructInf stParseMsg(pFile inp_fd, int flags) -This function is internal-use-only and is used by the st_node module to parse a structure file. -### pStructInf stParseMsgGeneric(void* src, int (*read_fn)(), int flags) -This function is also internal-use-only (unless you want to parse the file manually without st_node's help) and is used to parse the structure file when the structure file isn't being read from an MTASK pFile descriptor. This is always the case, as the structure file data is being read from a pObject pointer. In such a case, src is the pObject pointer and read_fn is objRead(). +### Function: PresentationHints() +```c +pObjPresentationHints xxxPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt); +``` +The `PresentationHints()` function allows the caller to request extra information about a specific attribute on a specific driver instance object. Most of this information is intended to be used for displaying the attribute in a user interface, although it can also be useful for general data validation. As such, many drivers may not implement this function. -### int stGenerateMsg(pFile out_fd, pStructInf info, int flags) -This function, also internal-use only, is used by the st_node module to write a structure file whose internal representation is given in the 'info' parameter. +The `PresentationHints()` function takes three parameters: -### int stGenerateMsgGeneric(void* dst, int (*write_fn)(), pStructInf info, int flags) -This function is stParseMsgGeneric's converse. +| Parameter | Type | Description +| --------- | ------------- | ------------ +| inf_v | void* | A driver instance pointer (returned from `Open()` or `QueryFetch()`). +| attr_name | char* | The name of the requested attribute. +| oxt | pObjTrxTree* | The transaction tree pointer for the `OBJDRV_C_TRANS` capability. + +The returns a new pObjPresentationHints struct on success, or NULL to indicate an error, in which case `mssError()` should be called before returning. This struct should be allocated using `nmMalloc()`, and memset to zero, like this: +```c +pObjPresentationHints hints = nmMalloc(sizeof(ObjPresentationHints)); +if (hints == NULL) goto error_handling; +memset(hints, 0, sizeof(ObjPresentationHints)); +``` -### pStructInf stCreateStruct(char* name, char* type) -This function creates a new top-level tree item of type ST_T_STRUCT, with a given name and content-type. +The return value, `hints : ObjPresentationHints`, contains the following useful fields which the function should set to give various useful information about the attribute. +- `hints->Constraint : void*`: An expression for determining if a value is valid. +- `hints->DefaultExpr : void*`: An expression defining the default value. +- `hints->MinValue : void*`: An expression defining the minimum valid value. +- `hints->MaxValue : void*`: An expression defining the maximum valid value. +- `hints->EnumList : XArray`: If the attribute is a string enum, this XArray lists the valid string values. +- `hints->EnumQuery : char*`: A query string which enumerates the valid values a string enum attribute. +- `hints->Format : char*`: A presentation format for datetime or money types, such as `"dd MMM yyyy HH:mm"` or `"$0.00"`. See `obj_datatypes.c` (near line 100) for more information creating a presentation format. +- `hints->AllowChars : char*`: An array of all valid characters for a string attribute, NULL to allow all characters. +- `hints->BadChars : char*`: An array of all invalid characters for a string attribute. If a character appears in both `hints->BadChars` and `hints->AllowChars`, the character should be rejected. +- `hints->Length : int`: The maximum length of data that can be included in a string attribute. +- `hints->VisualLength : int`: The length that the attribute should be displayed if it is show to the user. +- `hints->VisualLength2 : int`: The number of lines to use in a multi-line edit box for the attribute. +- `hints->BitmaskRO : unsigned int`: If the value is an integer that represents a bit mask, _this_ bit mask shows which bits of that bitmask are read-only. +- `hints->Style : int`: Style flags, documented below. +- `hints->StyleMask : int`: A mask for which style flags were set and which were left unset / undefined. +- `hints->GroupID : int`: Used to assign attributes to groups. Use -1 if the attribute is not in a group. +- `hints->GroupName : char*`: The name of the group to which this attribute belongs, or NULL if it is ungrouped or if the group is named elsewhere. +- `hints->OrderID : int`: Used to specify an attribute order. +- `hints->FriendlyName : char*`: Used to specify a "display name" for an attribute (e.g. `n_rows` might have a friendly name of `"Number of Rows"`). Should be [`nmSysMalloc()`](#nmsysmalloc)ed, often using [`nmSysStrdup()`](#nmsysstrdup). + +- ⚠️ **Warning**: Behavior is undefined if: + - The data is longer than length. + +The `hints->Style` field can be set with several useful flags. To specify that a flag is not set (e.g. to specify explicitly that a field does allow `NULL`s), set the corresponding bit in the `hints->StyleMask` field while leaving the the bit in the `hints->Style` field set to 0. + +The following macros are provided for setting style flags: +- `OBJ_PH_STYLE_BITMASK`: The items in `hints->EnumList` or `hints->EnumQuery` are bit masked. +- `OBJ_PH_STYLE_LIST`: List-style presentation should be used for the values of an enum attribute. +- `OBJ_PH_STYLE_BUTTONS`: Radio buttons or check boxes should be used for the presentation of enum attribute values. +- `OBJ_PH_STYLE_NOTNULL`: The attribute does not allow `NULL` values. +- `OBJ_PH_STYLE_STRNULL`: An empty string (`""`) should be treated as a `NULL` value. +- `OBJ_PH_STYLE_GROUPED`: The GroupID should be checked and so that fields can be grouped together. +- `OBJ_PH_STYLE_READONLY`: The user is not allowed to modify this attribute. +- `OBJ_PH_STYLE_HIDDEN`: This attribute should be hidden and not presented to the user. +- `OBJ_PH_STYLE_PASSWORD`: Values in this attribute should be hidden, such as for passwords. +- `OBJ_PH_STYLE_MULTILINE`: String values should allow multiline editing. +- `OBJ_PH_STYLE_HIGHLIGHT`: This attribute should be highlighted when presented to the user. +- `OBJ_PH_STYLE_LOWERCASE`: This attribute only allows lowercase characters. +- `OBJ_PH_STYLE_UPPERCASE`: This attribute only allows uppercase characters. +- `OBJ_PH_STYLE_TABPAGE`: Prefer the tab-page layout for grouped fields. +- `OBJ_PH_STYLE_SEPWINDOW`: Prefer separate windows for grouped fields. +- `OBJ_PH_STYLE_ALWAYSDEF`: Always reset the default value when this attribute is modified. +- `OBJ_PH_STYLE_CREATEONLY`: This attribute is writeable only when created, after that it is read only. +- `OBJ_PH_STYLE_MULTISEL`: This enum attribute can accept more than one value from the list of valid values. Think of using checkboxes instead of radio buttons (although the flag does not make any UI requirements). +- `OBJ_PH_STYLE_KEY`: This attribute is a primary key. +- `OBJ_PH_STYLE_APPLYCHG`: Presentation hints should be applied on DataChange instead of on DataModify. + + +### Function: Info() +```c +int xxxInfo(void* inf_v, pObjectInfo info); +``` +The `Info()` function allows the caller to request extra information about a specific driver instance object. It takes two parameters: -### pStructInf stAddAttr(pStructInf inf, char* name) -This function adds a node of type ST_T_ATTRIB to either a ST_T_STRUCT or ST_T_SUBGROUP type of node, with a given name and no values associated with that name (see AddValue, below). The new attribute tree node is linked under the 'inf' node passed, and is returned. +| Parameter | Type | Description +| --------- | ------------- | ------------ +| inf_v | void* | A driver instance pointer (returned from `Open()` or `QueryFetch()`). +| info | pObjectInfo | A driver info struct allocated by the caller which the driver sets with information. + +The `pObjectInfo` struct has two fields: `Flags` and `nSubobjects`. This function should set `info->Flags` to 0 (to ensure no uninitialized noise gets into the data), then bitwise-or (`|`) it with all the following flags that apply to that object: +- `OBJ_INFO_F_CAN_HAVE_SUBOBJ` / `OBJ_INFO_F_CANT_HAVE_SUBOBJ`: Indicates that the object can or cannot have subobjects. +- `OBJ_INFO_F_HAS_SUBOBJ` / `OBJ_INFO_F_NO_SUBOBJ`: Indicates that the object has or does not have subobjects. +- `OBJ_INFO_F_SUBOBJ_CNT_KNOWN`: Indicates that we know the number of subobjects. If set, the count should be stored in `info->nSubobjects`. +- `OBJ_INFO_F_CAN_HAVE_CONTENT` / `OBJ_INFO_F_CANT_HAVE_CONTENT`: Indicates that the object can or cannot have content (see `Read()` / `Write()`). +- `OBJ_INFO_F_HAS_CONTENT` / `OBJ_INFO_F_NO_CONTENT`: Indicates that this object does or does not have content (see `Read()` / `Write()`). +- `OBJ_INFO_F_CAN_SEEK_FULL`: Seeking is fully supported (both forwards and backwards) on the object. +- `OBJ_INFO_F_CAN_SEEK_REWIND`: Seeking is only supported with an offset of `0`. +- `OBJ_INFO_F_CANT_SEEK`: Seeking is not supported at all. +- `OBJ_INFO_F_CAN_ADD_ATTR` / `OBJ_INFO_F_CANT_ADD_ATTR`: Indicates that the object does or does not allow attributes to be added with the [AddAttr()](#function-addattr) function. +- `OBJ_INFO_F_SUPPORTS_INHERITANCE`: Indicates that the object supports inheritance through attributes such as `cx__inherit`. +- `OBJ_INFO_F_FORCED_LEAF`: Indicates that the object is forced to be a 'leaf' unless ls__type used. +- `OBJ_INFO_F_TEMPORARY`: Indicates that this is a temporary object without a valid pathname. + +The function returns 0 on success, and -1 to indicate an error, in which case `mssError()` should be called before returning. + + +### Function: Commit() +```c +int xxxCommit(void* inf_v, pObjTrxTree *oxt); +``` +The `Commit()` function immediately completes the current transaction, ensuring that all writes are applied to the affected data before returning. For example, if the current transaction involves creating a database row, this call will ensure that the row is created and the transaction is closed before returning. This allows the caller to ensure that actions in a transaction have been completed without needing to close the object, which they may wish to continue using. -### pStructInf stAddGroup(pStructInf inf, char* name, char* type) -This function adds a node of type ST_T_SUBGROUP to either a ST_T_SUBGROUP or ST_T_STRUCT tree node, with a given name and content type (content type such as 'report/query'). +The `Commit()` function takes two parameters: -### int stAddValue(pStructInf inf, char* strval, int intval) -This function adds a value to an attribute, and can be called multiple times on an attribute to add a list of values. If 'strval' is not null, a string value is added, otherwise an integer value is added. The string is NOT copied, but is simply pointed-to. If the string is non-static, and has a lifetime less than the ST_T_ATTRIB tree node, then the following procedure must be used: +| Parameter | Type | Description +| --------- | ------------- | ------------ +| inf_v | void* | A driver instance pointer (returned from `Open()` or `QueryFetch()`). +| oxt | pObjTrxTree* | The transaction tree pointer for the `OBJDRV_C_TRANS` capability. - char* ptr; - char* nptr; - pStructInf attr_inf; +The function returns 0 on success, and -1 to indicate an error, in which case `mssError()` should be called before returning. - attr_inf = stAddAttr(my_parent_inf, "myattr"); - nptr = (char*)malloc(strlen(ptr)+1); - if (!nptr) go_report_the_error_and_return; - strcpy(nptr, ptr); - stAddValue(attr_inf, nptr, 0); - attr_inf->StrAlloc[0] = 1; -By following this method (making a copy of the string and then setting the StrAlloc value for that string), when the StructInf tree node is freed by the stparse module, the string will auto- matically be freed as well. +### Function: GetQueryCoverageMask() +```c +int xxxGetQueryCoverageMask(pObjQuery this); +``` +This function is only intended to be used by the MultiQuery module. Any other driver should not provide this function by setting the appropriate struct field to `NULL`. -### pStructInf stLookup(pStructInf inf, char* name) -This routine examines all sub-tree-nodes, both group and attribute nodes, for a group or attribute with the given name. If it finds one, it returns a pointer to the sub-node, otherwise NULL. -### int stAttrValue(pStructInf inf, int* intval, char** strval, int nval) -This function returns the value of the given attribute in an ST_T_ATTRIB tree node. If a string value is being returned, pass a pointer to the string pointer. If an integer value is being returned, pass a pointer to an integer. The pointer not being used must be left NULL. 'nval' can normally be 0, but if the attribute has several values, setting nval to 1,2,3, etc., returns the 2nd, 3rd, 4th item, respectively. This routing returns -1 if the attribute value did not exist or if the wrong type was requested. It also returns -1 if 'inf' was NULL. +### Function: GetQueryIdentityPath() +```c +int xxxGetQueryIdentityPath(pObjQuery this, char* pathbuf, int maxlen); +``` +This function is only intended to be used by the MultiQuery module. Any other driver should not provide this function by setting the appropriate struct field to `NULL`. -It is common practice to use the stLookup and stAttrValue functions together to retrieve values, and search for an attribute StructInf and retrieve its value in one operation: - pStructInf inf; - char* ptr; - if (stAttrValue(stLookup(inf, "myattr"),NULL,&ptr,0) == 0) - { - printf("%s is the value\n", ptr); - } +## III Reading the Node Object +A driver will commonly configure itself by reading text content from its node object file, at the root of its object subtree. This content may define what resource(s) a driver should provide, how it should access or compute them, and other similar information. Most drivers use the structure file format for their node objects because SN module makes parsing, reading, and writing these files easier. It also performs caching automatically to improve performance. -### int stFreeInf(pStructInf this) -This function is used to free a StructInf tree node. It will free any sub-nodes first, so if that is not desired, be sure to disconnect them by removing them from the SubInf array and appropriately adjusting the nSubInf counter, and setting the SubInf array position to NULL. This function also disconnects the tree node from its parent, if any, so if the parent is already free()'d, be sure to set the node's Parent pointer to NULL. Any strings marked allocated with the StrAlloc flags will be free()'d. +- 📖 **Note**: The node object will **already be open** as an object in the ObjectSystem: The OSML does this for each driver. If a driver does not use the SN/ST modules, then it should read and write the node object directly with `objRead()` and `objWrite()`. A driver should **NEVER** `objClose()` the node object! The OSML handles that. -It is also common practice to bypass the stXxx() functions entirely and access the elements of the StructInf structures themselves. This is not forbidden, and may be done. See the file stparse.h for a description of the structure. For example, +Although using the structure file format may be complex, it allows significant flexibility, as well as greater consistency across drivers. The use of this shared syntax across different drivers makes learning to use a new driver far easier than it would be if they all used unique, custom syntax for specifying properties. In the structure file syntax, data is structured in hierarchies where each sub-object can have named attributes as well as sub-objects. Centrallix has many examples of this, including any `.qy`, `.app`, `.cmp`, or `.cluster` file. - pStructInf inf; - int i; +Structure files are accessed via the st_node (SN) and stparse (SP) modules. The st_node module loads and saves the structure file hierarchies as a whole. It also manages caching to reduce disk activity and eliminate repeated parsing of the same file. The stparse module provides access to the individual attributes and groups of attributes within a node structure file. - for(i=0;inSubInf;i++) - { - if (inf->SubInf[i]->Type == ST_T_ATTRIB) - { - /** do stuff with attribute... **/ - } - } +For example, if two sessions open two files, `/test1.rpt` and `/test2.rpt` the st_node module will cache the internal representations of these node object files, and for successive uses of these node objects, the physical file will not be reparsed. The file will be reparsed if its timestamp changes. -## IV Memory Management in Centrallix -Centrallix has its own memory manager that caches freshly-deallocated blocks of memory in lists according to size so that they can be quickly reallocated. This memory manager also catches double-freeing of blocks, making debugging of memory problems a little easier. +If the underlying object does not support the attribute "last_modification" (assumed to be the timestamp), then st_node prints a warning. In essence, this warning indicates that changes to the underlying object will not trigger the st_node module to re-read the structure file defining the node object. Otherwise, the st_node module keeps track of the timestamp, and if it changes, the node object is re-read and reparsed. -In addition the memory manager provides statistics on the hit ratio of allocated blocks coming from the lists vs. malloc(), and information on how many blocks of each size/type are allocated out and cached. This information can be invaluable in tracking down memory leaks. +### Module: st_node +To obtain node object data, the driver should first open the node object with the st_node module. To use this module, include the file `st_node.h`, which provides the following functions (read `st_node.c` for more functions and additional information): -One caveat is that this memory manager does not provide a realloc() function, so the standard malloc(), free(), and realloc() must be used for blocks of memory that might grow in size. This memory manager is also perhaps not the best to use for blocks of memory of arbitrary sizes, but rather is best for allocating structures quickly that are of a specific size and belong to specific objects, such as the StructInf structure or the SnNode structure, and others. In short, use it for structures, but not for strings. -Empirical testing has shown an increase of performance of around 50% or more in programs with the newmalloc module in use. +### st_node: snReadNode() +```c +pSnNode snReadNode(pObject obj); +``` +The `snReadNode()` function reads a Structure File from the `obj` parameter, which should be a previously opened object. In a driver's `Open()` function, this is `obj->Prev` (the node object as opened by the previous driver in the OSML's chain of drivers). -The following are the functions for the newmalloc module: +**Usage:** +```c +pSnNode node = snReadNode(obj->Prev); +if (node == NULL) goto error_handling; +``` -### void* nmMalloc(int size) -This function allocates a block of the given 'size'. It returns NULL if the memory could not be allocated. +The returned node structure is managed by the SN module and does not need to be `nmFree()`ed. Instead, the driver should increment the node structure's link count for as long as it intends to use this structure, using `node->OpenCnt++;`. When the structure is no longer needed (e.g. when the driver instance is closed), the driver should decrement the link count. -### void nmFree(void* ptr, int size) -This function frees the block of memory. NOTE THAT THE CALLING FUNCTION MUST KNOW THE SIZE OF THE BLOCK. Getting this wrong is very bad. For structures, this is trivial, just use sizeof() just like with nmMalloc(). -### void nmStats() -Prints out statistics on how well the memory manager is doing. +### st_node: snNewNode() +```c +pSnNode snNewNode(pObject obj, char* content_type); +``` +The `snNewNode()` function creates a new node object of the given content type. The open link count should be incremented and decremented when appropriate, as with `snReadNode()`. -### void nmRegister(int size, char* name) -Registers a name with a block size. This allows the memory manager to be intelligent when reporting block allocation counts. The first argument is the size of the block, the second, an intelligent name for that size of block. A size can have more than one name. This function is optional and need not be used except when tracking down memory leaks, but can be used freely. +**Usage:** +```c +pSnNode node = snNewNode(obj->Prev, "system/structure"); +if (node == NULL) goto error_handling; +``` -Typically this function is called in a module's Initialize() function on each of the structures the module uses internally. +In this case, the new structure file will have the type: `"system/structure"`. -### void nmDebug() -Prints out a listing of block allocation counts, giving (by size): 1) number of blocks allocated but not yet freed, 2) number of blocks in the cache, 3) total allocations for this block size, and a list of names (from nmRegister()) for that block size. +- 📖 **Note**: This function only creates node object content, so the underlying object file must already exist. The OSML should do this for you because the previous driver (`obj->Prev`) creates the underlying object. -### void nmDeltas() -Prints a listing of all blocks whose allocation count has changed, and by how much, since the last nmDeltas() call. This function is VERY USEFUL FOR MEMORY LEAK DETECTIVE WORK. -### void* nmSysMalloc(int size) -Allocates memory without using the block-caching algorithm. This is roughly equivalent to malloc(), but pointers returned by malloc and this function are not compatible with each other - i.e., you cannot free() something that was nmSysMalloc'ed, nor can you nmSysFree() something that was malloc'ed. +### st_node: snWriteNode() +```c +int snWriteNode(pSnNode node); +``` +The `snWriteNode()` function writes a node's internal data back out to the node file, if the node's status (`node->Status`) is set to `SN_NS_DIRTY`. Otherwise, `snWriteNode()` does nothing. -This function is much better to use on variable-sized blocks of memory. nmMalloc is better for fixed-size blocks, such as for data structures. -### void nmSysFree(void* ptr) -Frees a block of memory allocated by nmSysMalloc, nmSysStrdup, or nmSysRealloc. +### st_node: snDelete() +```c +int snDelete(pSnNode node); +``` +The `snDelete()` function deletes a node by removing the node's data from the internal node cache. -### void* nmSysRealloc(void* ptr, int newsize) -Changes the size of an allocated block of memory that was obtained via nmSysMalloc or nmSysRealloc or nmSysStrdup. The new pointer may be different if the block had to be moved. This is the rough equivalent of realloc(). Usage Note: If you are realloc'ing a block of memory, and need to store pointers to data somewhere inside the block, it is often better to store the offset rather than a full pointer, as a pointer would become invalid if a nmSysRealloc caused the block to move. +- 📖 **Note**: This does not actually delete the node file. -### char* nmSysStrdup(const char* str) -Allocates memory for a copy of the string str by using the nmSysMalloc function, and then makes a copy of the string str. It is a rough equivalent of strdup(). The resulting pointer can be free'd using nmSysFree(). -Calling free() on a block obtained from nmMalloc() or calling nmFree() on a block obtained from malloc() will not crash the program. Instead, it will result in either inefficient use of the memory manager, or a huge memory leak, respectively. These practices will also render the statistics and block count mechanisms useless. +### st_node: snGetSerial() +```c +int snGetSerial(pSnNode node); +``` +The `snGetSerial()` function returns the serial number of the node. -## V Other Utility Modules -There are many other utility modules useful in Centrallix. These include the xarray module, used for managing growable arrays; the xhash module, used for managing hash tables with no overflow problems and variable-length keys, the xstring module used for managing growable strings; the expression module used for compiling and evaluating expressions; and the mtsession module, used for managing session-level variables and reporting errors. +Each time the node is re-read because of modifications to the node file or is written with because `snWriteNode()` was called after modifications to the internal structure, the serial number is increased. This is a good way for a driver to determine if the node file has changed so it can refresh internal cached data. -### A. XArray (XA) - Arrays -The first is the xarray (XA) module. -#### xaInit(pXArray this, int init_size) -This function initializes an allocated-but-uninitialized xarray. It makes room for 'init_size' items initially, but this is only an optimization. A typical value for init_size is 16. +### st_node: snGetLastModification() +```c +pDateTime snGetLastModification(pSnNode node); +``` +The `snGetLastModification()` function returns the date and time that a file was last modified. This pointer will remain valid as long as the passed `pSnNode` struct remains valid. It is managed by the `st_node` module, so the caller should not free the returned pointer. This function promises not to fail and return `NULL`. -#### xaDeInit(pXArray this) -This de-initializes an xarray, but does not free the XArray structure itself. -#### xaAddItem(pXArray this, void* item) -This adds an item to the array. The item can be a pointer or an integer (but ints will need a typecast on the function call). +### Module: stparse +The stparse module is used to examine the parsed contents of the node file using the structure file format; see [StructureFile.txt](../centrallix-doc/StructureFile.txt). This format is a tree structure with node objects that can each have sub-objects and named attributes. Thus, stparse uses three distinct node types: +- `ST_T_STRUCT`: The top-level node, containing the subtrees and attributes in the file. +- `ST_T_SUBGROUP`: A mid-level type for subobjects within the top-level node. Each subgroup has a content type, name, and may contain attributes and other subgroups. +- `ST_T_ATTRIB`: A bottom-level type for each named attribute. Each attribute has a name and values, either of type integer or string, and optional lists of such up to 64 items in length. -#### xaAddItemSorted(pXArray this, void* item, int keyoffset, int keylen) -This adds an item to the xarray, and keeps the array sorted. The value for sorting is expected to begin at offset 'keyoffset' and continue for 'keylen' bytes. This only works when pointers are stored in the array, not integers. +To use this module, include the file `stparse.h`, which includes the following functions (read `stparse.c` for more functions and additional information): -#### xaFindItem(pXArray this, void* item) -This returns the offset into the array's items of the given value. An exact match is required. The array's items are given below: - XArray xa; - pStructInf inf; - int item_id; +### stparse: stStructType() +```c +int stStructType(pStructInf this); +``` +The `stStructType()` function returns the struct type of the past `pStructInf` parameter, which is either `ST_T_ATTRIB` or `ST_T_SUBGROUP` (see above). - xaInit(&xa, 16); +- ⚠️ **Warning**: The node object root of type `ST_T_STRUCT` will return `ST_T_SUBGROUP` from this function. In most cases, treating this node as ust another subgroup simplifies logic for the caller. However, if you wish to avoid this behavior, read `inf->Type` (see [stparse: Using Fields Directly](#stparse-using-fields-directly) for more info). - [...] - xaAddItem(&xa, inf); +### stparse: stLookup() +```c +pStructInf stLookup(pStructInf inf, char* name); +``` +The `stLookup()` function searches all sub-tree nodes for a group or attribute of the given name and returns a pointer to it or returns `NULL` if no group or attribute was found. - [...] - item_id = xaFindItem(&xa, inf); - inf == xa.Items[item_id]; +### stparse: stAttrValue() +```c +int stAttrValue(pStructInf inf, int* intval, char** strval, int nval); +``` +This function gets the value of the given attribute in an `ST_T_ATTRIB` node. If the value is an integer, the caller should pass a pointer to an integer where it can be stored. If the value is a string, the caller should pass a pointer to string (aka. a `char*`) where char* for the string can be stored. The unused alternate pointer must be left `NULL`. `nval` can normally be 0, but if the attribute has several values, setting nval to 1, 2, 3, etc., returns the 2nd, 3rd, 4th item, respectively. -#### xaRemoveItem(pXArray this, int index) -This function removes an item from the xarray at the given index. +This function returns -1 if the attribute value did not exist, if the wrong type was requested, or if 'inf' was `NULL`. -### B. XHash (XH) - Hash Tables -The xhash module provides an extensible hashing table interface. The hash table is a table of linked lists of items, so collisions and overflows are not a problem as in conventional hash tables. +It is common practice to use `stLookup()` and `stAttrValue()` or `stGetExpression()` (see below) together to retrieve values, for example (where `inf` is a `pStructInfo` variable from somewhere): -### int xhInit(pXHashTable this, int rows, int keylen) -This initializes a hash table, giving it the given number of rows, and setting the key length. For variable length keys (null- terminated strings), use a key length of 0 (zero). The 'rows' should be an odd number, preferably prime, but does not need to be. It SHOULD NOT be a power of 2. It's value is an optimization depending on how much data you expect to be in the hash table. If its value is set to 1, the hash search degenerates to a linear array search. The value should be large enough to comfortably accomodate the elements. Typical values might be 31 or 255 (though 255 is not prime). +```c +char* ptr; +if (stAttrValue(stLookup(inf, "my_attr"), NULL, &ptr, 0) != 0) + goto error_handling; +printf("The value is: %s\n", ptr); +``` -#### int xhDeInit(pXHashTable this) -De-initializes a hash table. -#### int xhAdd(pXHashTable this, char* key, char* data) -Adds an item to the hash table, with a given key value and data pointer. Both data and key pointers must have a lifetime that exceeds the time that they item is hashed. +### stparse: stGetExpression() +```c +pExpression stGetExpression(pStructInf this, int nval); +``` +Returns a pointer to an expression that represents the value of the nval-th element of the given struct. -#### int xhRemove(pXHashTable this, char* key) -Removes an item with the given key value from the hash table. -#### char* xhLookup(pXHashTable this, char* key) -Returns the data pointer for a given key, or NULL if the item is not found. +### stparse: stCreateStruct() +```c +pStructInf stCreateStruct(char* name, char* type); +``` +This function creates a new top-level tree item of type `ST_T_STRUCT`, with a given name and content-type. -#### int xhClear(pXHashTable this, int free_blk) -Clears all items from a hash table. If free_blk is set to 1, the items are free()'d as they are removed. -### C. XString (XS) - Strings -The xstring (XS) module is used for managing growable strings. It is based on a structure containing a small initial string buffer to avoid string allocations for small strings, but with the capability of performing realloc() operations to extend the string space for storing incrementally larger strings. The interface to this module allows for strings to contain arbitrary data, even null '\0' characters mid-string. Thus it is useful as an extensible buffer module as well. +### stparse: stAddAttr() +```c +pStructInf stAddAttr(pStructInf inf, char* name); +``` +This function adds a node of type `ST_T_ATTRIB` to either an `ST_T_STRUCT` or an `ST_T_SUBGROUP` type of node, with a given name and no values (see AddValue, below). The new attribute tree node is linked under the `inf` node passed, and is returned. -#### int xsInit(pXString this) -Initializes an XString structure, to an empty string. -#### int xsDeInit(pXString this) -Deinitializes an XString structure. +### stparse: stAddGroup() +```c +pStructInf stAddGroup(pStructInf inf, char* name, char* type); +``` +This function adds a node of type `ST_T_SUBGROUP` to either an `ST_T_SUBGROUP` or an `ST_T_STRUCT` tree node, with a given name and content type (content type such as `"report/query"`). -#### int xsConcatenate(pXString this, char* text, int len) -Concatenates the string 'text' onto the end of the XString's value. If len is -1, all data up to the null terminater is copied. If len is set, all data up to length 'len' is copied, including possible '\0' characters. -#### int xsCopy(pXString this, char* text, int len) -Copies the string 'text' into the XString. Like xsConcatenate, except that the previous string contents are overwritten. +### stparse: stAddValue() +```c +int stAddValue(pStructInf inf, char* strval, int intval); +``` +This function adds a value to an attribute. If there is already one value, it creates an `EXPR_N_LIST`, and if there's already a list it simply adds the new value to the list. Thus, this function can be called multiple times on an attribute to add a list of multiple values. If `strval` is not null, a string value is added, otherwise an integer value is added using `intval`. The string is NOT copied, but is simply pointed-to. This function returns the number of values stored on the attribute, including the new one. No errors can occur, so it never returns -1. -#### char* xsStringEnd(pXString this) -Returns a pointer to the end of the string. Useful for finding the end of the string without performing: +### stparse: stFreeInf() +```c +int stFreeInf(pStructInf this); +``` +This function is used to free a `StructInf` tree node. This also recursively frees sub-tree nodes, so these should be disconnected before calling if they are still needed. To do this, remove them from the SubInf array by appropriately adjusting the nSubInf counter and setting the SubInf array position to `NULL`. This function also disconnects the tree node from its parent, if any, so if the parent is already `free()`'d, prevent this behavior by setting the node's Parent pointer to `NULL` before calling this function. Any strings marked allocated with the StrAlloc flags will also be `free()`'d by this function, so update that flag if necessary. This function returns 0. + + +### stparse: Using Fields Directly +It is also common practice to bypass the stparse functions entirely and access the elements of the `StructInf` struct directly, which is allowed. (See `stparse.h` for more information about this structure.) + +For example (assuming `inf` is a `pStructInfo` variable in scope): +```c +for (unsigned int i = 0u; i < inf->nSubInf; i++) + { + switch (inf->SubInf[i]->Type) + { + case ST_T_ATTRIB: + /** Do stuff with attribute... **/ + break; + + case ST_T_SUBGROUP: + /** Do stuff with group... **/ + break; + + ... + } + } +``` - pXString xs; - xs->String + strlen(xs->String) -since the xs module already knows the string length and does not have to search for the null terminator. Furthermore, since the string can contain nulls, the above statement could produce incorrect results in those situations. +## IV Module: Expression +The expression (EXP) module is used for compiling, evaluating, reverse-evaluating, and managing parameters for expression strings. The expression strings are compiled and stored in an expression tree structure. -The contents of the XString can be easily referenced via: +Expressions can be stand-alone expression trees, or they can take parameter objects. A parameter object is an open object (from `objOpen()`) whose values (attributes) are referenced within the expression string. By using such parameter objects, one expression can be compiled and then evaluated for many different objects with diverse attribute values. - pXString xs; +Expression evaluation results in the top-level expression tree node having the final value of the expression, which may be `NULL`, and may be an integer, string, datetime, money, or double data type. For example, the final value of `:my_object:oneattribute == 'yes'` is the integer 1, `true`, if the attribute's value is indeed `'yes'` (and the integer 0, `false`, otherwise). - printf("This string is %s\n", xs->String); +Expression reverse-evaluation takes a given final value and attempts to assign values to the parameter object attributes based on the structure of the expression tree. It is akin to 'solving for X' in algebraic work, but isn't nearly that 'smart'. For example, with the previous expression, if the final value was set to 1 (`true`), then an `objSetAttrValue()` function would be called to set my_object's `oneattribute` to `yes`. Trying this with a final value of 0 (`false`) would result in no assignment to the attribute, since there would be no way of determining the proper value for that attribute (anything other than `yes` would work). -IMPORTANT NOTE: Do not store pointers to values within the string while you are still adding text to the end of the string. If the string ends up realloc()ing, your pointers will be incorrect. Instead, if data in the middle of the string needs to be pointed to, store offsets from the beginning of the string, not pointers to the string. +Reverse evaluation is typically very useful in updateable joins and views. -For example, this is WRONG: +The expression module includes the following functions: - pXString xs; - char* ptr; +### expAllocExpression() +```c +pExpression expAllocExpression(); +``` +This function allocates space to store a new expression tree, returning a pointer to the allocated memory or `NULL` if an error occurs. - xsInit(&xs); - xsConcatenate(&xs, "This is the first sentence. ", -1); - ptr = xsStringEnd(&xs); - xsConcatenate(&xs, "This is the second sentence.", -1); - printf("A pointer to the second sentence is '%s'\n", ptr); +### expFreeExpression() +```c +int expFreeExpression(pExpression this); +``` +This function frees an expression tree allocated using `expAllocExpression()`, returning 0 if successful or -1 if an error occurs. -Instead, use pointer aritmetic and do this: +### expCompileExpression() +```c +pExpression expCompileExpression(char* text, pParamObjects objlist, int lxflags, int cmpflags); +``` +This function compiles a textual expression into an expression tree. The `objlist` lists the parameter objects that are allowed in the expression (see below for param objects maintenance functions). - pXString xs; - int offset; +The `lxflags` parameter is a bitmask that provides flags which will be passed to the lexer. These flags alter the manner in which the input string is tokenized. For information about these flags, see [`mlxOpenSession()`](#mlxopensession). - xsInit(&xs); - xsConcatenate(&xs, "This is the first sentence. ", -1); - offset = xsStringEnd(&xs) - xs->String; - xsConcatenate(&xs, "This is the second sentence.", -1); - printf("A pointer to the second sentence is '%s'\n",xs->String+offset); +The `cmpflags` parameter is a bitmask that provides flags which will be passed to the expression compiler. It can contain the following values: +| Value | Description +| -------------------- | ------------ +| `EXPR_CMP_ASCDESC` | Recognize `asc`/`desc` following a value as flags to indicate sort order. +| `EXPR_CMP_OUTERJOIN` | Recognize the `*=` and `=*` syntax for left and right outer joins. +| `EXPR_CMP_WATCHLIST` | A list (`"value,value,value"`) is expected first in the expression. +| `EXPR_CMP_LATEBIND` | Allow late object-name binding. +| `EXPR_CMP_RUNSERVER` | Compile as a `runserver` expression (for dynamic binding). +| `EXPR_CMP_RUNCLIENT` | Compile as a `runclient` expression (for client-side binding). +| `EXPR_CMP_REVERSE` | Lookup names in the reverse order. + +### expCompileExpressionFromLxs() +```c +pExpression expCompileExpressionFromLxs(pLxSession s, pParamObjects objlist, int cmpflags); +``` +This function is similar to [`expCompileExpression()`](#expcompileexpression), excpet that it compiles from a provided lexer session instead of from a string. -### D. Expression (EXP) - Expression Trees -The expression (EXP) module is used for compiling, evaluating, reverse- evaluating, and passing parameters to expression strings. The expression strings are compiled and stored in an expression tree structure. +### expPodToExpression() +```c +pExpression expPodToExpression(pObjData pod, int type, pExpression provided_exp) +``` +This function builds an expression node from a single piece of data, passed using the `pObjData` of the given datatype. This function can be used to initialize a provided expression (`provided_exp`), or it will allocate a new one if none is provided (aka. `provided_exp` is `NULL`). -Expressions can be stand-alone expression trees, or they can take parameter objects. A parameter object is an open object (from objOpen()) whose values (attributes) are referenced within the expression string. By using such parameter objects, one expression can be compiled and then evaluated for many different objects with diverse attribute values. +For example, the following code creates an expression representing the integer 1. +```c +int value = 1; +pExpression exp = expPodToExpression(POD(value), DATA_T_INTEGER, NULL); +``` -Expression evaluation results in the top-level expression tree node having the final value of the expression, which may be NULL, and may be an integer, string, datetime, money, or double data type. For example, the final value of +This function returns a pointer to the expression if successful, or `NULL` if an error occurs. - :myobject:oneattribute == 'yes' +- 📖 **Note**: There is also a `expPtodToExpression()` function for working with the `Ptod` (pointer to object data) struct. -would be integer 1 (true) if the attribute's value is indeed 'yes'. +### expExpressionToPod() +```c +int expExpressionToPod(pExpression this, int type, pObjData pod); +``` +This function reverses the functionality of [`expPodToExpression()`](#exppodtoexpression) to instead read data from an evaluated expression. Be careful, this does not evaluate the expression if it is not already evaluated. This function returns 0 if successful, 1 if the expression is NULL, or -1 if an error occurs. -Reverse expression evaluation takes a given final value and attempts to assign values to the parameter object attributes based on the structure of the expression tree. It is akin to 'solving for X' in algebraic work, but isn't nearly that 'smart'. For example, with the previous expression, if the final value was set to 1 (true), then an objSetAttrValue() function would be issued to set myobject's 'oneattribute' to 'yes'. Trying this with a final value of 0 (false) would result in no assignment to the attribute, since there would be no way of determining the proper value for that attribute (anything other than 'yes' would work). +- 📖 **Note**: The source code for this function can be a useful reference when interacting with expression structures, such as when implementing the c code for an exp_function. -Reverse evaluation is typically very useful in updateable joins and views. +- 📖 **Note**: There is also a `expExpressionToPtod()` function for working with the `Ptod` (pointer to object data) struct. -Here are the basic expression functions: +### expDuplicateExpression() +```c +pExpression expDuplicateExpression(pExpression this); +``` +This function creates a recursive deep copy of the expression and associated expression tree, returning a pointer to this new copy if successful and `NULL` if an error occurs. -#### pExpression expCompileExpression(char* text, pParamObjects objlist, int lxflags, int cmpflags) -This function compiles a textual expression into an expression tree. The 'objlist' lists the parameter objects that are allowed in the expression (see below for param objects maintenance functions). +### expIsConstant() +```c +int expIsConstant(pExpression this); +``` +This function returns a truthy value if the provided expression is of a type that is always the same, such as an integer, string, double, etc. Otherwise, it returns a falsy value. -The 'lxflags' parameter gives a set of lexical analyzer flags for the compilation. These flags alter the manner in which the input string is tokenized. A bitmask; possible values are: +### expEvalTree() +```c +int expEvalTree(pExpression this, pParamObjects objlist); +``` +This function evaluates the expression using the provided list of parameter objects. It returns 0 if successful or 1 if the result is `NULL`, and -1 if an error occurs. -| Value | Description -| ---------------- | ------------ -| MLX_F_ICASEK | automatically convert all keywords (non-quoted strings) to lowercase. -| MLX_F_POUNDCOMM | allow comment lines that begin with a # sign. -| MLX_F_CCOMM | allow c-style comments /* */ -| MLX_F_CPPCOMM | allow c-plus-plus comments // -| MLX_F_SEMICOMM | allow semicolon comments ;this is a comment -| MLX_F_DASHCOMM | allow double-dash comments --this is a comment -| MLX_F_DASHKW | keywords can include the dash '-'. Otherwise, the keyword is treated as two keywords with a minus sign between them. -| MLX_F_FILENAMES | Treat a non-quoted string beginning with a slash '/' or dot-slash './' as a filename, and allow slashes and dots in the string without quotes needed. -| MLX_F_ICASER | automatically convert all reserved words to lowercase. The use of this flag is highly recommended, and in some cases, required. -| MLX_F_ICASE | same as MLX_F_ICASER | MLX_F_ICASEK. +### expCreateParamList() +```c +pParamObjects expCreateParamList(); +``` +This function allocates and returns a new parameter object list containing no parameters, or returns `NULL` if an error occurs. -The 'cmpflags' is a bitmask parameter controlling the compilation of the expression. It can contain the following values: +### expFreeParamList() +```c +int expFreeParamList(pParamObjects this); +``` +This function frees a parameter object list, returning 0 if successful and -1 if an error occurs. -| Value | Description -| ------------------- | ------------ -| EXPR_CMP_WATCHLIST | A list "value,value,value" is expected first in the expression. -| EXPR_CMP_ASCDESC | Recognize 'asc' and 'desc' following a value as flags to indicate sort order. -| EXPR_CMP_OUTERJOIN | Recognize the *= and =* syntax as outer joins. +### expAddParamToList() +```c +int expAddParamToList(pParamObjects this, char* name, pObject obj, int flags); +``` +This function adds a parameter to the parameter object list. The `obj` pointer may be left `NULL` during the expCompileExpression state of operation but must be set to a value before expEvalTree is called. Otherwise the attributes that reference that parameter object will result in `NULL` values in the expression. (Although this _technically_ is not an error, it's usually not intended behavior). Flags can be `EXPR_O_CURRENT` if the object is to be marked as the current one, or `EXPR_O_PARENT` if it is to be marked as the parent object. Current and Parent objects can be referenced in an expression like this: -#### expFreeExpression(pExpression this) -Frees an expression tree. +``` +:currentobjattr +::parentobjattr +``` -#### int expEvalTree(pExpression this, pParamObjects objlist) -Evaluates an expression against a list of parameter objects. If the evaluation is successful, returns 0 or 1, otherwise -1. +### expModifyParam() +```c +int expModifyParam(pParamObjects this, char* name, pObject replace_obj); +``` +This function is used to update a parameter object with a new open pObject, possibly one returned from `objOpen()` or `objQueryFetch()`. This function returns 0 if successful and -1 if an error occurs. -#### pParamObjects expCreateParamList() -Allocates a new parameter object list, with no parameters. +### expRemoveParamFromList() +```c +int expRemoveParamFromList(pParamObjects this, char* name); +``` +This function removes a parameter object from the list, returning 0 if successful and -1 if an error occurs. -#### int expFreeParamList(pParamObjects this) -Frees a parameter object list. +- 📖 **Note**: There is also a `expRemoveParamFromListById()` function. -#### int expAddParamToList(pParamObjects this, char* name, pObject obj, int flags) -Adds a parameter to the parameter object list. The 'obj' pointer may be left NULL during the expCompileExpression state of operation but must be set to a value before expEvalTree is called. Otherwise the attributes that reference that parameter object will result in NULL values in the expression (it's technically not an error). Flags can be EXPR_O_CURRENT if the object is to be marked as the current one, or EXPR_O_PARENT if it is to be marked as the parent object. Current and Parent objects can be referenced in an expression like this: +### expSetParamFunctions() +```c +int expSetParamFunctions(pParamObjects this, char* name, int (*type_fn)(), int (*get_fn)(), int (*set_fn)()); +``` +This function sets the param accessor functions used to access params on a specific name. Some example function signatures for the `type_fn()`, `get_fn()`, and `set_fn()` are provided below: - :currentobjattr - ::parentobjattr +```c +static int ci_GetParamType(void* v, char* attr_name); +static int ci_GetParamValue(void* v, char* attr_name, int datatype, pObjData val); +static int ci_SetParamValue(void* v, char* attr_name, int datatype, pObjData val); +``` -and is thus a shortcut to typing the full object name. +The functions use the following parameters: +- `v : void*` is the object provided in `expAddParamToList()` (or a similar function). +- `attr_name : char*` is the string name for the requested attribute. +- `datatype : int` is the data type for the requested attribute. +- `val : pObjectData` is either a buffer in which to store the requested data (`ci_GetParamValue()`) or a buffer containing data that will be copied to the parameter `ci_SetParamValue()`. -#### int expModifyParam(pParamObjects this, char* name, pObject replace_obj) -This function is used to update a parameter object with a new open pObject returned from objOpen or objQueryFetch. +The functions return the following values: +- The `ci_GetParamType()` function returns the datatype on success (e.g. `DATA_T_INTEGER`), or -1 if an error occurs. +- The `ci_GetParamValue()` function returns 0 for success, 1 if the attribute is `NULL`, or -1 if an error occurs. +- The `ci_SetParamValue()` function returns 0 for success (even if the value was set to `NULL`), or -1 if an error occurs. -#### int expRemoveParamFromList(pParamObjects this, char* name) -This function removes a parameter object from the list. +The `expSetParamFunctions()` function returns 0 if the functions were set successfully, or -1 if an error occurs. -#### int expReverseEvalTree(pExpression tree, pParamObjects objlist) +### expReverseEvalTree() +```c +int expReverseEvalTree(pExpression tree, pParamObjects objlist)l +``` This function reverse-evaluates a tree. The results of an expression evaluation can be accessed by examining the @@ -807,228 +1256,261 @@ top-level tree node. The following properties are useful: There are several other EXP functions used to deal with aggregates and a few other obscure features as well. Aggregates are mostly handled internally by Centrallix so further explanation should not be necessary here. -### E. MTSession (MSS) - Basic Session Management -The next utility module to be described here is the mtsession module (MSS). This module is used for session authentication, error reporting, and for storing session-wide variables such as the currently used date format, current username, and current password (for issuing a login request to a remote server). Care should be taken in the use of Centrallix that its coredump files are NOT in a world-readable location, as the password will be visible in the core file (or just ulimit the core file size to 0). - -#### char* mssUserName() -This function returns the current user name. - -#### char* mssPassword() -This function returns the password used to login to the Centrallix - -#### int mssSetParam(char* paramname, char* param) -This function sets a session parameter. The parameter MUST be a string value. - -#### char* mssGetParam(char* paramname) -Returns the value of a session parameter. Common ones are: - -- dfmt - current date format. -- mfmt - current money format. -- textsize - current max text size from a read of an object's content via objGetAttrValue(obj, "objcontent", POD(&str)) - -#### int mssError(int clr, char* module, char* message, ...) -Formats and caches an error message for return to the user. If 'clr' is set to 1, the assumption is that the error was JUST discovered and no other module has had reason to do an mssError on the current problem. Setting 'clr' to 1 clears all error messages from the current error message list and adds the current message. - -'module' is a two-to-five letter abbreviation of the module reporting the error. Typically it is all upper-case. - -'message' is a string for the error message. As this function will accept a variable-length argument list, the strings '%d' and '%s' can be included in 'message', and will be substituted with the appropriate integer or string arguments, in a similar way to how printf() works. -#### int mssErrorErrno(int clr, char* module, char* message, ...) -Works much the same way as mssError, except checks the current value of 'errno' and includes a description of any error stored there. Used primarily when a system call was at fault for an error occurring. +## V Path Handling Functions +The OSML provides a set of utility functions that make it easier to handle path structs when writing drivers. Most of them are named `obj_internal_XxxYyy()` or similar. -Errors that occur inside a session context are normally stored up and not printed until other MSS module routines are called to fetch those errors. Errors occurring outside a session context (such as in Centrallix's network listener) are printed to Centrallix's standard output immediately. - -These mssError routines need not be called at every function nesting level when an error happens. For example, if the expression compiler returns -1 indicating that a compilation error occurred, it probably has set one or more error messages in the error list. The calling function only needs to provide context information (e.g. _what_ expression failed compilation?) so that the user has enough information to locate the error. And once the user is told the full context of the expression compilation error, no more information need be returned. - -Another example of this is the memory manager, which sets an error message indicating when an nmMalloc() failed. The user probably does not care what kind of structure failed allocation -- he/she only needs to know that the hardware ran out of resources. Thus, upon receiving a NULL from nmMalloc, in most cases another mssError need not be issued. - -The mssError() routines do not cause the calling function to return. The function must still clean up after itself and return an appropriate value (like -1 or NULL) to indicate failure. - -### F. OSML Utility Functions -The OSML provides a set of utility functions that make it easier to write -drivers. Most of them are named obj_internal_XxxYyy or similar. - -#### char* obj_internal_PathPart(pPathname path, int start, int length) -The Pathname structure breaks down a pathname into path elements, which are text strings separated by the directory separator '/'. This function takes the given Pathname structure, and returns the number of path elements requested. For instance, if you have a path: - - /apps/kardia/data/Kardia_DB/p_partner/rows/1 - -that path would be stored internally in Centrallix as: - - ./apps/kardia/data/Kardia_DB/p_partner/rows/1 - -To just return "Kardia_DB/p_partner", you could call: +### obj_internal_PathPart() +```c +char* obj_internal_PathPart(pPathname path, int start, int length); +``` +The Pathname structure breaks down a pathname into path elements, which are text strings separated by the directory separator `'/'`. This function takes the given Pathname structure and returns the number of path elements requested (using `length`) after skipping to the `start`th element (where element 0 is the starting `.` that begins any Centrallix path). - obj_internal_PathPart(pathstruct, 4, 2); +For example, given the path: +```bash +/apps/kardia/data/Kardia_DB/p_partner/rows/1 +``` +Centrallix stores the path internally as the following (see [Parsing Path Contents](#parsing-path-contents) and [Parameters](#parameters) above): +```bash +./apps/kardia/data/Kardia_DB/p_partner/rows/1 +``` +Thus, calling `obj_internal_PathPart(pathstruct, 4, 2);` will return `"Kardia_DB/p_partner"` because the `.` is the 0th element, making `Kardia_DB` the 4th element, and we have requested two elements. -Note that return values from obj_internal_PathPart are only valid until the next call to PathPart on the given pathname structure. +- 📖 **Note**: The values returned from `obj_internal_PathPart()` use an internal buffer, so they are only valid until the next call to a PathPart function on the given pathname structure. -#### int obj_internal_AddToPath(pPathname path, char* new_element) +### obj_internal_AddToPath() +```c +int obj_internal_AddToPath(pPathname path, char* new_element); +``` This function lengthens the path by one element, adding new_element on to the end of the path. This function is frequently useful for drivers in the QueryFetch routine where the new child object needs to be appended onto the end of the given path. -This function returns < 0 on failure, or the index of the new element in the path on success. - -#### int obj_internal_CopyPath(pPathname dest, pPathname src) -Copies a pathname structure. - -#### void obj_internal_FreePathStruct(pPathname path) -Frees a pathname structure. +This function returns the index of the new element in the path on success, or a value less than 0 on failure. -## VI Network Connection Functionality -Sometimes a driver will need to initiate a network connection. This can be done via the MTASK module, which provides simple and easy TCP/IP connectivity. - -### pFile netConnectTCP(char* host_name, char* service_name, int flags) -This function connects to a server. The host name or ascii string for its ip address is in 'host_name'. The name of the service (from /etc/services) or its numeric representation in a string is the 'service_name'. Flags can normally be left 0. - -### int netCloseTCP(pFile net_filedesc, int linger_msec, int flags) -This function closes a network connection, and optionally waits up to 'linger_msec' milliseconds (1/1000 seconds) for any data written to the connection to make it to the other end before performing the close. If linger_msec is set to 0, the connection is aborted (reset). The linger time can be set to 1000 msec or so if no writes were performed on the connection prior to the close. If a large amount of writes were performed immediately perior to the close, offering to linger for a few more seconds (perhaps 5 or 10, 5000 or 10000 msec), might be a good idea. - -### int fdWrite(pFile filedesc, char* buffer, int length, int offset, int flags) -This function writes data to a file descriptor, from a given buffer and length, and to an optional seek offset and with some optional flags. Flags can be the following: +### obj_internal_CopyPath() +```c +int obj_internal_CopyPath(pPathname dest, pPathname src); +``` +This function copies a pathname structure from the `src` to the `dest`, returning 0 if successful or -1 if an error occurs. -- FD_U_NOBLOCK - If the write can't be performed immediately, don't perform it at all. -- FD_U_SEEK - The 'offset' value is valid. Seek to it before writing. Not valid for network connections. -- FD_U_PACKET - ALL of the data of 'length' in 'buffer' must be written. Normal write() semantics in UNIX state that not all data has to be written, and the number of bytes actually written is returned. Setting this flag makes sure all data is really written before returning. +### obj_internal_FreePathStruct() +```c +void obj_internal_FreePathStruct(pPathname path); +``` +This function frees a pathname structure. -#### int fdRead(pFile filedesc, char* buffer, int maxlen, int offset, int flags) -The complement to the above routine. Takes the same flags as the above routine, except FD_U_PACKET means that all of 'maxlen' bytes must be read before returning. This is good for reading a packet that is known to be exactly 'maxlen' bytes long, but which might be broken up into fragments by the network (TCP/IP has a maximum frame transmission size of about 1450 bytes). -## VII Parsing Data -Centrallix provides a lexical analyzer library that can be used for parsing many types of data. This module, mtlexer (MLX) can either parse data from a pFile descriptor or from a string value. This lexical analyzer is used by the expression compiler as well. It is basically a very fancy string tokenizer. -### pLxSession mlxOpenSession(pFile fd, int flags) -This function opens a lexer session from a file source. See the 'expression' module description previous in this document for more information on the flags. Some flags of use here but not mentioned in that section are: +## VI Parsing Data +The mtlexer (MLX) module is a lexical analyzer library provided by Centrallix for parsing many types of data. It can parse data from either a `pFile` descriptor or from a string value. This lexical analyzer is also used by the [expression compiler](#iv-module-expression). In simple terms, it's a very fancy string tokenizer. -| Flag | Description -| ------------------- | ------------ -| MLX_F_EOL | Return end-of-line as a token. Otherwise, the end of a line is just considered whitespace. -| MLX_F_EOF | Return end-of-file as a token. Otherwise, if end of file is reached it is an error. -| MLX_F_IFSONLY | Only return string values separated by tabs, spaces, newlines, and carriage returns. For example, normally the brace in "this{brace" is a token and that string will result in three tokens, but in IFSONLY mode it is just one token. -| MLX_F_NODISCARD | This flag indicates to the lexer that the calling function expects to be able to read data normally using fdRead() or another lexer session after the last token is read and the session is closed. The lexer will then attempt to "unread" bytes that it buffered during the lexical analysis process (it does fdRead() operations in 2k or so chunks). If this flag is not specified, up to 2k of information after the last token will be discarded and further fdRead()s on the file descriptor will start at an undefined place in the file. -| MLX_F_ALLOWNUL | Allow NUL characters ('\0') in the input stream. If this flag is not set, then NUL characters result in an error condition. This prevents unwary callers from mis-reading a token returned by mlxStringVal if the token contains a NUL. If ALLOWNUL is turned on, then the caller must ensure that it is safely handling values with NULs. +### mlxOpenSession() +```c +pLxSession mlxOpenSession(pFile fd, int flags); +``` +This function opens a lexer session, using a file descripter as its source. Some of the more useful values for `flags` include: + +| Value | Description +| ----------------- | ------------ +| `MLX_F_ICASEK` | Automatically convert all keywords (non-quoted strings) to lowercase. +| `MLX_F_ICASER` | Automatically convert all reserved words to lowercase. This flag is highly recommended, and in some cases, required. +| `MLX_F_ICASE` | Same as `MLX_F_ICASER` \| `MLX_F_ICASEK`. +| `MLX_F_POUNDCOMM` | Respect # comment at the start of the line (`#comment`). +| `MLX_F_CCOMM` | Respect c-style comments (`/*comment*/`). +| `MLX_F_CPPCOMM` | Respect c-plus-plus comments (`//comment`). +| `MLX_F_SEMICOMM` | Respect semicolon comments (`;comment`). +| `MLX_F_DASHCOMM` | Respect double-dash comments (`--comment`). +| `MLX_F_EOL` | Return end-of-line as a token. Otherwise, this is considered whitespace. +| `MLX_F_EOF` | Return end-of-file as a token. Otherwise, reaching end of file is an error. +| `MLX_F_ALLOWNUL` | Allow null characters (`'\0'`) in the input stream, which otherwise cause an error. If this flag is set, the caller must ensure that null characters are handled safely. +| `MLX_F_IFSONLY` | Only return string values separated by tabs, spaces, newlines, and carriage returns. For example, normally the brace in `"this{brace"` is a token and that string will result in three tokens, but in `IFSONLY` mode it is just one token. +| `MLX_F_DASHKW` | Keywords can include the dash (`-`). Otherwise, the keyword is treated as two keywords with a minus sign between them. +| `MLX_F_FILENAMES` | Treat a non-quoted string beginning with a slash (`/`) or dot-slash (`./`) as a filename, and allow slashes and dots in the string without requiring quotes. +| `MLX_F_NODISCARD` | Attempt to unread unused buffered data rather than discarding it, allowing the calling function to continue reading with `fdRead()` or another lexer session after the last token is read and the session is closed. The lexer `fdRead()`s in 2k or so chunks for performance, and normally discards this data when done, causing future file decriptors to start at an undefined file location. +| `MLX_F_DBLBRACE` | Treat `{{` and `}}` as double brace tokens, not two single brace tokens. +| `MLX_F_NOUNESC` | Do not remove escapes in strings. +| `MLX_F_SSTRING` | Differentiate between strings values using `""` and `''`. + +This function returns a pointer to the new lexer session if successful, or `NULL` if an error occurs. + +### mlxStringSession() +```c +pLxSession mlxStringSession(char* str, int flags); +``` +This function opens a lexer session, using a text string as its source. The flags are the same as [`mlxOpenSession()`](#mlxopensession) above, except that `MLX_F_NODISCARD` has no effect. -### pLxSession mlxStringSession(char* str, int flags) -This function opens a lexer session from a text string. Same as the above function except that the flag MLX_F_NODISCARD makes no sense for the string. +This function returns a pointer to the new lexer session if successful, or `NULL` if an error occurs. -### int mlxCloseSession(pLxSession this) -Closes a lexer session. +### mlxCloseSession() +```c +int mlxCloseSession(pLxSession this); +``` +This function closes a lexer session, freeing all associated data. This does not also close the file descriptor used to open the lexer session, as this is assumed to be managed by the caller. This function returns 0 if successful, and -1 if an error occurs. -### int mlxNextToken(pLxSession this) +### mlxNextToken() +```c +int mlxNextToken(pLxSession this); +``` Returns the type of the next token in the token stream. Valid token types are: -| Token | Meaning | -|----------------------|---------------------------------------| -| MLX_TOK_STRING | String value, as in a "string". | -| MLX_TOK_INTEGER | Integer value. | -| MLX_TOK_EQUALS | = | -| MLX_TOK_OPENBRACE | { | -| MLX_TOK_CLOSEBRACE | } | -| MLX_TOK_ERROR | An error has occurred. | -| MLX_TOK_KEYWORD | An unquoted string. | -| MLX_TOK_COMMA | , | -| MLX_TOK_EOL | End-of-line. | -| MLX_TOK_EOF | End-of-file reached. | -| MLX_TOK_COMPARE | <> != < > >= <= == | -| MLX_TOK_COLON | : | -| MLX_TOK_OPENPAREN | ( | -| MLX_TOK_CLOSEPAREN | ) | -| MLX_TOK_SLASH | / | -| MLX_TOK_PERIOD | . | -| MLX_TOK_PLUS | + | -| MLX_TOK_ASTERISK | * | -| MLX_TOK_RESERVEDWD | Reserved word (special keyword). | -| MLX_TOK_FILENAME | Unquoted string starting with / or ./ | -| MLX_TOK_DOUBLE | Double precision floating point. | -| MLX_TOK_DOLLAR | $ | -| MLX_TOK_MINUS | - | - -### char* mlxStringVal(pLxSession this, int* alloc) -Gets the string value of the current token. If 'alloc' is NULL, only the first 255 bytes of the string will be returned, and the rest will be discarded. If 'alloc' is non-null and set to 0, the routine will set 'alloc' to 1 if it needed to allocate memory for a very long string, otherwise leave it at 0. If 'alloc' is non- null and set to 1, this routine will ALWAYS allocate memory for the string, whether long or short. - -This routine works no matter what the token type, and returns a string representation of the token if not MLX_TOK_STRING. +| Token | Required Flag | Meaning | +|-------------------------|-------------------|---------------------------------------------| +| `MLX_TOK_BEGIN` | - | Beginning of the input stream. | +| `MLX_TOK_STRING` | - | String value, e.g. `"string"`. | +| `MLX_TOK_INTEGER` | - | Integer value, e.g. `42`. | +| `MLX_TOK_EQUALS` | - | `=` | +| `MLX_TOK_OPENBRACE` | - | `{` | +| `MLX_TOK_CLOSEBRACE` | - | `}` | +| `MLX_TOK_ERROR` | - | An error has occurred. | +| `MLX_TOK_KEYWORD` | - | A keyword (unquoted string). | +| `MLX_TOK_COMMA` | - | `,` | +| `MLX_TOK_EOL` | `MLX_F_EOL` | End-of-line. | +| `MLX_TOK_EOF` | `MLX_F_EOF` | End-of-file reached. | +| `MLX_TOK_COMPARE` | - | `<>` `!=` `<` `>` `>=` `<=` `==` | +| `MLX_TOK_COLON` | - | `:` | +| `MLX_TOK_OPENPAREN` | - | `(` | +| `MLX_TOK_CLOSEPAREN` | - | `)` | +| `MLX_TOK_SLASH` | - | `/` | +| `MLX_TOK_PERIOD` | - | `.` | +| `MLX_TOK_PLUS` | - | `+` | +| `MLX_TOK_ASTERISK` | - | `*` | +| `MLX_TOK_RESERVEDWD` | - | Reserved word (special keyword). | +| `MLX_TOK_FILENAME` | `MLX_F_FILENAMES` | Unquoted string starting with / or ./ | +| `MLX_TOK_DOUBLE` | - | Double precision floating point. | +| `MLX_TOK_DOLLAR` | - | `$` | +| `MLX_TOK_MINUS` | - | `-` | +| `MLX_TOK_DBLOPENBRACE` | `MLX_F_DBLBRACE` | `{{` | +| `MLX_TOK_DBLCLOSEBRACE` | `MLX_F_DBLBRACE` | `}}` | +| `MLX_TOK_SYMBOL` | - | `+-=.,<>` etc. | +| `MLX_TOK_SEMICOLON` | - | `;` | +| `MLX_TOK_SSTRING` | `MLX_F_SSTRING` | Single quote string value, e.g. `'string'`. | +| `MLX_TOK_POUND` | - | `#` | +| `MLX_TOK_MAX` | - | Max token value (internal). | + +### mlxStringVal() +```c +char* mlxStringVal(pLxSession this, int* alloc); +``` +This function gets the string value of the current token. If `alloc` is `NULL`, an internal buffer is returned (which the caller _should not free_). This may also cause an error if such a buffer is not available. If `alloc` is non-null and set to 0, the routine will set `alloc` to 1 if it needed to allocate memory for a very long string, otherwise leave it as 0. If `alloc` is non-null and set to 1, this routine will _always_ allocate memory for the string, whether long or short. + +This routine works no matter what the token type, and returns a string representation of the token if not `MLX_TOK_STRING`. This routine MAY NOT be called twice for the same token. -Note that if MLX_F_ALLOWNUL is enabled, there is no way to tell from the return value of mlxStringVal() whether a NUL in the returned string is the end-of-string terminator, or whether it existed in the input data stream. Thus, this function should not be called when MLX_F_ALLOWNUL is being used. Use mlxCopyToken instead on MLX_TOK_STRING's, as it gives a definitive answer on the token length. (mlxStringVal can still be used on keywords since those will never contain a NUL, by definition). +- ⚠️ **Warning**: This function should not be called when `MLX_F_ALLOWNUL` is being used because it may return a null character, giving the caller no way to know whether it is the null-terminator or it simply existed in the input data stream. In this case, `mlxCopyToken()` should be used instead, as it gives a definitive answer on the token length. (`mlxStringVal()` can still be used on keywords, though, since they never contain a null, by definition). -### int mlxIntVal(pLxSession this) -Returns the integer value of MLX_TOK_INTEGER tokens, or returns the compare type for MLX_TOK_COMPARE tokens. The compare type is a bitmask of the following flags: +- ⚠️ **Warning**: This documentation might be slightly out of date from what the function currently does. Double check `mtlexer.c`, if needed. -- MLX_CMP_EQUALS -- MLX_CMP_GREATER -- MLX_CMP_LESS +### mlxIntVal() +```c +int mlxIntVal(pLxSession this); +``` +This function returns the integer value of `MLX_TOK_INTEGER` tokens, or returns the compare type for `MLX_TOK_COMPARE` tokens. The compare type is a bitmask of the `MLX_CMP_EQUALS`, `MLX_CMP_GREATER`, and `MLX_CMP_LESS` flags. For `MLX_TOK_DOUBLE` tokens, this function returns the whole part. -For MLX_TOK_DOUBLE tokens, returns the whole part. +### mlxDoubleVal() +```c +double mlxDoubleVal(pLxSession this); +``` +This function returns a double precision floating point number for either `MLX_TOK_INTEGER` or `MLX_TOK_DOUBLE` values. -### double mlxDoubleVal(pLxSession this) -Returns a double precision floating point number for either MLX_TOK_INTEGER or MLX_TOK_DOUBLE values. +### mlxCopyToken() +```c +int mlxCopyToken(pLxSession this, char* buffer, int maxlen); +``` +This function copies the contents of the current token to a string buffer, up to `maxlen` characters. It should be used instead of `mlxStringVal()`, _especially_ where null characters may be involved. This function returns the number of characters copied on success, or -1 on failure, and it can be called multiple times if more data needs to be read from the same token. -### int mlxCopyToken(pLxSession this, char* buffer, int maxlen) -For use instead of mlxStringVal, copies the contents of the current token to a string buffer, up to 'maxlen' characters. Returns the number of characters copied. This function can be called multiple times if more data needs to be read from the token. +### mlxHoldToken() +```c +int mlxHoldToken(pLxSession this); +``` +This function "puts back" a token, causing the next `mlxNextToken()` to return the current token again. This is useful when a function realizes after `mlxNextToken()` that it has read one-too-many. This function returns 0 on success, or -1 if an error occurs. -### int mlxHoldToken(pLxSession this) -Basically causes the next mlxNextToken() to do nothing but return the current token again. Used for when a routine realizes after mlxNextToken() that it has read one-too-many tokens and needs to 'put a token back'. +### mlxSetOptions() +```c +int mlxSetOptions(pLxSession this, int options); +``` +This function sets the options (`MLX_F_xxx`) for an active lexer session. The options that are valid here are `MLX_F_ICASE` and `MLX_F_IFSONLY`. This function returns 0 if successful, or -1 if an error occurs. -### int mlxSetOptions(pLxSession this, int options) -Sets options (MLX_F_xxx) in the middle of a lexer session. The options that are valid here are MLX_F_ICASE and MLX_F_IFSONLY. +### mlxUnsetOptions() +```c +int mlxUnsetOptions(pLxSession this, int options); +``` +This function clears the same set of flags used by [`mlxSetOptions()`](#mlxsetoptions). This function returns 0 if successful, or -1 if an error occurs. -### int mlxUnsetOptions(pLxSession this, int options) -Clears options (see above). +### mlxSetReservedWords() +```c +int mlxSetReservedWords(pLxSession this, char** res_words); +``` +This function sets the lexer to return the list of `res_words` as `MLX_TOK_RESERVEDWD` tokens instead of `MLX_TOK_KEYWORD` tokens. The list of words should be an array of character strings, with the last string in the list being `NULL`. This function returns 0 if successful, or -1 if an error occurs. -### int mlxSetReservedWords(pLxSession this, char** res_words) -Informs the lexer that a certain list of words are to be returned as MLX_TOK_RESERVEDWD instead of MLX_TOK_KEYWORD. The list of words should be an array of character strings, with the last string in the list NULL. mtlexer does not copy this list, so it must be static or have a lifetime greater than that of the lexer session. +- ⚠️ **Warning**: `mtlexer` does not copy this list! Ensure that it has a lifetime longer than that of the lexer session. -### int mlxNoteError(pLxSession this) -Generates an mssError() message of this form: +### mlxNoteError() +```c +int mlxNoteError(pLxSession this); +``` +This function generates an `mssError()` message of the form: +```bash +MLX: Error near '' +``` - MLX: Error near '' +- 📖 **Note**: The calling routine may have detected the error long after the actual place where it occurred. The MLX module just tries to come close :) -NOTE: the calling routine may have detected the error long after the actual place where it occurred. The MLX module just tries to come close :) +### mlxNotePosition() +```c +int mlxNotePosition(pLxSession this); +``` +This function generates an mssError() message of this form: +```bash +MLX: Error at line ## +``` -### int mlxNotePosition(pLxSession this) -Generates an mssError() message of this form: +- 📖 **Note**: If using a `StringSession` instead of a `pFile` session, this may not be accurate, as the string may have come from the middle of a file somewhere. Use with care. - MLX: Error at line ## -NOTE: If using a StringSession instead of a pFile session, this may not be accurate, as the string may have come from the middle of a file somewhere. Use with care. -## VIII Objectsystem Driver Testing -This section contains a list of things that can be done to test an objectsystem driver, to make sure that it is performing all basic operations normally. We will use the test_obj command line interface for testing here. For more information on test_obj commands, see the online Centrallix documentation at: http://www.centrallix.net/docs/docs.php +## VII Driver Testing +This section contains a list of things that can be done to test an objectsystem driver and ensure that it preforms all basic operations correctly, using the [test_obj command line interface](https://www.centrallix.net/docs/docs.php?t=1.3.1%20test_obj%20Command-Line). -Testing for memory leaks for each of these items is strongly encouraged, by watching memory utilization using nmDeltas() during repetitive operations (e.g., nmDeltas(), open, close, nmDeltas(), open, close, and then nmDeltas() again). +It is strongly recommended to test for invalid reads, writes, frees, and memory leaks during each of these by watching memory utilization using nmDeltas() during repetitive operations (e.g., nmDeltas(), open, close, nmDeltas(), open, close, and then nmDeltas() again). -Testing for more general bugs using the "valgrind" tool is also strongly encouraged, via running these various tests in test_obj while test_obj is running under valgrind. +Testing for more general memory bugs using the "valgrind" tool is also strongly encouraged, via running these various tests in test_obj while test_obj is running under valgrind. To properly test under Valgrind, centrallix-lib must be compiled with the configure flag `--enable-valgrind-integration` turned on. This disables `nmMalloc()` block caching (so that valgrind can properly detect memory leaks and free memory reuse), and it provides better information to valgrind's analyzer regarding MTASK threads. -Magic number checking on data structures is encouraged. To use magic number checking, determine a magic number value for each of your structures, and code that as a constant #define in your code. The magic number should be a 32-bit integer, possibly with 0x00 in either the 2nd or 3rd byte of the integer. Many existing magic number values can be found in the file "magic.h" in centrallix-lib. The 32-bit integer is placed as the first element of the structure, and set using the macro SETMAGIC(), and then tested using the macros ASSERTMAGIC(), and less commonly, ASSERTNOTMAGIC(). ASSERTMAGIC() should be used any time a pointer to the structure crosses an interface boundary. It also may be used at the entry to internal methods/functions, or when traversing linked lists of data structures, or when retrieving data structures from an array. +Magic number checking on data structures is encouraged. To use magic number checking, determine a magic number value for each of your structures, and add a #define for that constant in your code. The magic number should be a 32-bit integer, possibly with 0x00 in either the 2nd or 3rd byte of the integer. Many existing magic number values can be found in [magic.h](../centrallix-lib/include/magic.h). The 32-bit integer is placed as the first element of the structure, and set using the `SETMAGIC()` macro, then tested using the macros `ASSERTMAGIC()` macro or, less commonly, `ASSERTNOTMAGIC()`. Common times to `ASSERTMAGIC()` include: +- Any time a pointer to the structure crosses an interface boundary. +- At the entry to internal methods/functions. +- When traversing linked lists of data structures. +- When retrieving data structures from an array. +- etc. -When used in conjunction with nmMalloc() and nmFree(), ASSERTMAGIC also helps to detect the reuse of already-freed memory, since nmFree() tags the first four bytes of the memory block with the constant MGK_FREEMEM. nmFree() also looks for the constant MGK_FREEMEM in the magic number slot to detect already-freed memory (so do not use that same constant for your own magic numbers). +When used in conjunction with `nmMalloc()` and `nmFree()`, `ASSERTMAGIC` also helps to detect the reuse of already-freed memory, since `nmFree()` tags the first four bytes of the memory block with the constant `MGK_FREEMEM`. `nmFree()` also looks for the constant `MGK_FREEMEM` in the magic number slot to detect already-freed memory. (**DO NOT** use that constant for your own magic numbers!) -To properly test under Valgrind, centrallix-lib must be compiled with the configure flag --enable-valgrind-integration turned on. This disables nmMalloc block caching (so that valgrind can properly detect memory leaks and free memory reuse), and it provides better information to valgrind's analyzer regarding MTASK threads. +The term "**MUST**", as used here, means that the driver will likely cause problems if the functionality is not present. -The term "MUST", as used here, means that the driver will likely cause problems if the functionality is not present. +The term "**SHOULD**" indicates behavior which is desirable, but that might not cause immediate problems if not fully implemented. -The term "SHOULD" indicates behavior which is desirable, but may not cause problems if not fully implemented. +The term "**MAY**" refers to optional, but permissible, behavior. -The term "MAY" refers to optional, but permissible, behavior. -### A. Object opening, closing, creation, and deletion +### A. Opening, closing, creating, and deleting -1. Any object in the driver's subtree, including the node object itself, MUST be able to be opened using objOpen() and then closed using objClose(). Although it does more than just open and close, the "show" command in test_obj can be useful for testing this. +1. Any object in the driver's subtree, including the node object itself, MUST be able to be opened using `xxxOpen()` and then closed using `xxxClose()`. Although it does more than just open and close, the "show" command in test_obj can be useful for testing this. 2. Objects MUST be able to be opened regardless of the location of the node object in the ObjectSystem. For example, don't just test the driver with the node object in the top level directory of the ObjectSystem - also try it in other subdirectories. -3. New objects within the driver's subtree SHOULD be able to be created using objOpen with OBJ_O_CREAT, or using objCreate(). The flags OBJ_O_EXCL and OBJ_O_TRUNC should also be supported, where meaningful. +3. New objects within the driver's subtree SHOULD be able to be created using `xxxOpen()` with `OBJ_O_CREAT`, or using `objCreate()`. The flags `OBJ_O_EXCL` and `OBJ_O_TRUNC` should also be supported, where meaningful. + +4. Where possible, `OBJ_O_AUTONAME` should be supported on object creation. With this, the name of the object will be set to `*` in the pathname structure, and `OBJ_O_CREAT` will also be set. The driver should automatically determine a suitable "name" for the object, and subsequent calls to objGetAttrValue on "name" should return the determined name. A driver MAY choose to return NULL for "name" until after certain object properties have been set and an `xxxCommit()` operation performed. A driver MUST NOT return `*` for the object name unless `*` is truly the name chosen for the object. -4. Where possible, OBJ_O_AUTONAME should be supported on object creation. With this, the name of the object will be set to `*` in the pathname structure, and OBJ_O_CREAT will also be set. The driver should automatically determine a suitable "name" for the object, and subsequent calls to objGetAttrValue on "name" should return the determined name. A driver MAY choose to return NULL for "name" until after certain object properties have been set and an objCommit operation performed. A driver MUST NOT return `*` for the object name unless `*` is truly the name chosen for the object. +5. A driver SHOULD support deletion of any object in its subtree with the exception of the node object itself. Deletion may be done directly with `xxxDelete()`, or on an already-open object using `xxxDeleteObj()`. A driver MAY refuse to delete an object if the object still contains deletable sub-objects. Some objects in the subtree might inherently not be deletable apart from the parent objects of said objects. In those cases, deletion should not succeed. -5. A driver SHOULD support deletion of any object in its subtree with the exception of the node object itself. Deletion may be done directly with objDelete(), or on an already-open object using objDeleteObj(). A driver MAY refuse to delete an object if the object still contains deletable sub-objects. Some objects in the subtree might inherently not be deletable apart from the parent objects of said objects. In those cases, deletion should not succeed. -### B. Object attribute enumeration, getting, and setting. -1. The driver MUST NOT return system attributes (name, inner_type, and so forth) when enumerating with objGetFirst/NextAttr. +### B. Attributes -2. The driver does not need to handle objGetAttrType on the system attributes. The OSML does this. +1. The driver MUST NOT return system attributes (name, inner_type, etc) when enumerating with `xxxGetFirst()`/`xxxNextAttr()`. -3. The driver SHOULD support the attribute last_modification if at all reasonable. Not all objects can have this property however. +2. The driver MAY choose not to handle `xxxGetAttrType` on the system attributes. The OSML handles this. + +3. The driver SHOULD support the attribute `last_modification` if at all reasonable. Not all objects can have this property however. 4. The driver SHOULD support the attribute "annotation" if reasonable to do so. Database drivers should have a configurable "row annotation expression" to auto-generate annotations from existing row content, where reasonable. The driver MAY permit the user to directly set annotation values. The driver MUST return an empty string ("") for any annotation values that are unavailable. @@ -1038,43 +1520,44 @@ The term "MAY" refers to optional, but permissible, behavior. 7. The "show" command in test_obj is a good way to display a list of attributes for an object. -8. Attribute enumeration, retrieval, and modification MUST work equally well on objects returned by objOpen() and objects returned by objQueryFetch(). +8. Attribute enumeration, retrieval, and modification MUST work equally well on objects returned by `xxxOpen()` and objects returned by `xxxQueryFetch()`. -9. If a driver returns an attribute during attribute enumeration, then that attribute MUST return a valid type via objGetAttrType. +9. If a driver returns an attribute during attribute enumeration, then that attribute MUST return a valid type via `xxxGetAttrType`. -10. A driver MUST return -1 and error with a "type mismatch" type of error from objGet/SetAttrValue, if the data type is inappropriate. +10. A driver MUST return -1 and error with a "type mismatch" type of error from `xxxGetAttrValue()`/`xxxSetAttrValue()`, if the data type is inappropriate. 11. A driver MAY choose to perform auto-conversion of data types on certain attributes, but SHOULD NOT perform such auto conversion on a widespread wholesale basis. -12. A driver MAY support the DATA_T_CODE attribute data type. +12. A driver MAY support the `DATA_T_CODE` attribute data type. + +13. Drivers MAY support `DATA_T_INTVEC` and `DATA_T_STRINGVEC`. -13. Drivers MAY support DATA_T_INTVEC and DATA_T_STRINGVEC. +14. Drivers MAY support `xxxAddAttr()` and `xxxOpenAttr()`. -14. Drivers MAY support objAddAttr and objOpenAttr. +15. Drivers MAY support methods on objects. Objects without any methods should be indicated by a `NULL` return value from the method enumeration functions. -15. Drivers MAY support methods on objects. Objects without any methods should be indicated by a NULL return value from the method enumeration functions. +16. When returning attribute values, the value MUST remain valid at least until the next call to `xxxGetAttrValue()`, `xxxSetAttrValue()`, or `xxxGetAttrType()`, or until the object is closed, whichever occurs first. Drivers MUST NOT require the caller to free attribute memory. -16. When returning attribute values, the value MUST remain valid at least until the next call to objGetAttrValue, objSetAttrValue, or objGetAttrType, or until the object is closed, whichever occurs first. Drivers MUST NOT require the caller to free attribute memory. +17. When `xxxSetAttrValue()` is used, drivers MUST NOT depend on the referenced value (in the POD) being valid past the end of the call to `xxxSetAttrValue()`. -17. When objSetAttrValue is used, drivers MUST NOT depend on the referenced value (in the POD) being valid past the end of the call to objSetAttrValue(). -### C. Object querying (for subobjects) +### C. Querying Subobjects -1. If an object cannot support queries for subobjects, the OpenQuery call SHOULD fail. +1. If an object cannot support queries for subobjects, `xxxOpenQuery()` call SHOULD fail. -2. If an object can support the existence of subobjects, but has no subobjects, the OpenQuery should succeed, but calls to QueryFetch MUST return NULL. +2. If an object can support the existence of subobjects, but has no subobjects, the `xxxOpenQuery()` should succeed, but calls to `xxxQueryFetch()` MUST return `NULL`. -3. Objects returned by QueryFetch MUST remain valid even after the query is closed using QueryClose. +3. Objects returned by `xxxQueryFetch()` MUST remain valid even after the query is closed using `xxxQueryClose()`. -4. Objects returned by QueryFetch MUST also be able to be passed to OpenQuery to check for the existence of further subobjects, though the OpenQuery call is permitted to fail as in (C)(1) above. +4. Objects returned by `xxxQueryFetch()` MUST also be able to be passed to `xxxOpenQuery()` to check for the existence of further subobjects, though the `xxxOpenQuery()` call is permitted to fail as in (C)(1) above. -5. Any name returned by objGetAttrValue(name) on a queried subobject MUST be able to be used to open the same object using objOpen(). +5. Any name returned by `xxxGetAttrValue(name)` on a queried sub-object MUST be usable to open the same object using `xxxOpen()`. -6. Drivers which connect to resources which are able to perform sorting and/or selection (filtering) of records or objects SHOULD use the OBJ_QY_F_FULLSORT and OBJ_QY_F_FULLQUERY flags (see previous discussion) as well as pass on the sorting and filtering expressions to the remote resource so that resource can do the filtering and/or sorting. +6. Drivers which connect to resources which are able to perform sorting and/or selection (filtering) of records or objects SHOULD use the [`OBJ_QY_F_FULLSORT`](#function-openquery) and [`OBJ_QY_F_FULLQUERY`](#function-openquery) flags. Further, they SHOULD pass on the sorting and filtering expressions to the remote resource so that resource can optimize sorting and/or filtering as needed. -7. If the driver's remote resource can filter and/or sort, but can only do so imperfectly (e.g., the resource cannot handle the potential complexity of all sorting/selection expressions, but can handle parts of them), then OBJ_QY_F_FULLSORT and/or OBJ_QY_F_FULL- QUERY MUST NOT be used. However the remote resource MAY still provide partial sorting and/or selection of data. +7. If the driver's remote resource can filter and/or sort, but can only do so imperfectly (e.g., the resource cannot handle the potential complexity of all sorting/selection expressions, but can handle parts of them), then `OBJ_QY_F_FULLSORT` and/or `OBJ_QY_F_FULL`- QUERY MUST NOT be used. However, the remote resource MAY still provide partial sorting and/or selection of data. -8. Drivers SHOULD NOT use OBJ_QY_F_FULLSORT and OBJ_QY_F_FULLQUERY if there is no advantage to letting the resource perform these operations (usually, however, if the resource provides such functionality, there is advantage to letting the resource perform those operations. However, the coding burden to provide the filtering and sorting expressions to the resource, and in the correct format for the resource, may be not worth the work). +8. Drivers SHOULD NOT use `OBJ_QY_F_FULLSORT` and `OBJ_QY_F_FULLQUERY` if there is no advantage to letting the resource perform these operations (usually, however, if the resource provides such functionality, there is advantage to letting the resource perform those operations. However, the coding burden to provide the filtering and sorting expressions to the resource, and in the correct format for the resource, may be not worth the work). 9. Testing of query functionality can be done via test_obj's "query", "csv", and "ls" (or "list") commands. To test for nested querying of objects returned from QueryFetch, a SUBTREE select can be used with the "query" or "csv" commands. diff --git a/centrallix-sysdoc/Prefixes.md b/centrallix-sysdoc/Prefixes.md index b0e12c6b0..30e536324 100644 --- a/centrallix-sysdoc/Prefixes.md +++ b/centrallix-sysdoc/Prefixes.md @@ -4,6 +4,7 @@ |---------|--------------------------------------------------------------------- | aud | OSDriver - Linux OSS /dev/dsp audio (plays WAV files on ExecMethod) | bar | BarCode generator module (for prt mgmt) +| cluster | OSDriver - Cluster & search file | dat | OSDriver - Flat data file (CSV/etc) | ev | MTASK internal - event handling | exp | Expression compiler/parser/evaluator diff --git a/centrallix-sysdoc/QPrintf.md b/centrallix-sysdoc/QPrintf.md index ff572cfa2..533d1f9d0 100644 --- a/centrallix-sysdoc/QPrintf.md +++ b/centrallix-sysdoc/QPrintf.md @@ -3,6 +3,8 @@ Date: January 31, 2006 Author: Greg Beeley (GRB) +License: Copyright (C) 2001-2026 LightSys Technology Services. See LICENSE.txt for more information. + ## Overview Centrallix is a modern application server environment that must work with a lot of untrusted and semi-trusted data, frequently building commands and structures which contain data encapsulated within control structures, such as SQL, HTML, JavaScript, and more. @@ -11,82 +13,87 @@ The goal of this module is to provide an easy, seamless way to safely format suc ## Functions Two functions will initially be available: -- qpfPrintf() - quoted formatted printing into a fixed-length buffer. +- `qpfPrintf()` - quoted formatted printing into a fixed-length buffer. +- `qpfPrintf_va()` - quoted formatted printing into a fixed-length buffer, where the vararg pointer is explicitly provided in lieu of passing the arguments directly to the function. -- qpfPrintf_va() - quoted formatted printing into a fixed-length buffer, where the vararg pointer is explicitly provided in lieu of passing the arguments directly to the function. +The calling syntax of these functions is similar to that of the `snprintf()` and `vsnprintf()` respectively. However, the formatting specifiers are different, and an initial "session" parameter can be provided to track any errors that occur (this can be left NULL if cumulative error handling is not needed). -The calling syntax of these functions is similar to that of the corresponding snprintf() and vsnprintf() calls, but the formatting specifiers are different, and an initial "session" parameter can be provided - but can be left NULL in most cases where cumulative error handling is not needed. +The following functions for handling error sessions are also provided: -## Formatting -Formatting specifiers will begin with a % sign, as in normal printf(). However, the specifiers will be very different and can be combined together to obtain the desired result. Formatting specifiers can affect a parameter's length, position, padding, content, quoting, and more. +- `qpfOpenSession()` - Open a new error session. +- `qpfCloseSession()` - Close an error session, freeing all allocated resources. +- `qpfClearErrors()` - Clear errors on an error session so that it can be reused. +- `qpfErrors()` - Get the error mask for the errors that have occurred in an error session. +- `qpfLogErrors()` - Log a helpful message to the console that errors that have occurred in an error session. -To combine specifiers, simply chain them with the ampersand &. The first one will be applied first to the parameter, and then the second, and so forth. +## Formatting +Format specifiers begin with the percent character (`%`), similarly to the `printf()` functions. However, the `qprintf()` module uses a new set of format specifers. The module includes several source format specifiers (e.g. `%STR`, `%INT`, etc.) which indicate what type of data the function should expect so that it can format that data into the output string. Filter format specifiers (e.g. `%QUOT`, `%PATH`, etc.) can be appended to source format specifiers with the ampersand (`&`) to modify or process the string data, granting control over properties such as its length, position, padding, quoting, and more. Multiple format specifiers may be added and their effects will be applied in order from left to right. The module also provides control format specifiers which control the formatted data in other ways. -For non-string values, once the INT, POS, DBL, etc., conversion has been performed, further specifiers can be added to further process the string. +Some specifiers begin with an `n`, which should be replaced with a number or a wildcard (`*`), in which case the function will expect an int to be provided. This number usually controlls the behavior of the format specifier in some way. For example, when using `%nSTR`, the developer may use `%4STR` to indicate a string exactly 4 characters long. -Please check the end of this document for information on which of these specifiers are currently implemented. +**Warning**: Not all format specifiers below have been implemented yet! Please check the end of this document for information on which specifiers are implemented so far. -### Simple specifiers: +### Control Format Specifiers | Specifier | Description | --------- | ------------ -| %% | A percent sign. -| %& | An ampersand sign. -| %[ | Beginning of conditional printing (an integer argument is expected, 0 = noprint, nonzero = print) -| %] | End of conditional printing +| `%%` | A percent sign. +| `%&` | An ampersand sign. +| `%[` | Beginning of conditional printing (expects an integer: 0 = noprint, nonzero = print). +| `%]` | End of conditional printing. -### Source data specifiers: +### Source Format Specifiers | Specifier | Description | --------- | ------------ -| %INT | An integer value, with range of the normal 'int' value in the C language. Can be positive, negative, or zero. -| %LL | A 64-bit integer value, with range of 'long long' value in the C language. Can be positive, negative, or zero. -| %POS | A non-negative integer value (zero allowed). -| %DBL | Double-precision floating point value. -| %STR | A normal zero-terminated string. -| %nSTR | A string of exactly N length (binary safe), where N is an integer supplied in the format string, or if `*`, supplied as an argument immediately preceding the string pointer. Warning: this does *not* honor null terminators. Be careful. -| %CHR | A single character. -| %XSTR | An XString. -| %EXP | An Expression tree node. -| %POD | A Pointer-to-object-data. The type of the POD is specified as an argument immediately preceding the string pointer. -| %PTOD | A typed pointer-to-object-data. - -### Specifiers used for filtering or processing the data: +| `%INT` | An integer value, with range of the normal 'int' value in the C language. Can be positive, negative, or zero. +| `%LL` | A 64-bit integer value, with range of 'long long' value in the C language. Can be positive, negative, or zero. +| `%POS` | A non-negative integer value (zero allowed). Expects a positive int, and the function errors if the int is negative. +| `%DBL` | A double-precision floating point value. +| `%STR` | A normal null-terminated string. +| `%nSTR` | A string of exactly `n` length (binary safe), where `n` is the integer supplied in the format string, or if `*`, supplied as an argument immediately preceding the string pointer. Warning: This does *not* honor null-terminators. Be careful. +| `%CHR` | A single character. +| `%XSTR` | An XString. +| `%EXP` | An Expression tree node. +| `%POD` | A Pointer-to-object-data. The type of the POD is specified as an argument immediately preceding the string pointer. +| `%PTOD` | A typed pointer-to-object-data. + +### Filter Format Specifiers: | Specifier | Description | ----------------- | ------------ -| " | Adds single quotes around the string value if its source type was a string or date/time value (esp. useful for expressions and pods). -| &DQUOT | Adds double quotes around the string value if its source type was a string or date/time value. -| &ESCQ | Causes quotes in the string to be escaped, with single quotes, double quotes, and backslashes quoted with a leading backslash. -| &WS | Causes whitespace in the string to be processed into its native values (\n -> newline, \r -> carriage return, and \t -> horizontal tab). -| &ESCWS | Causes newlines, carriage returns, and tab characters to be processed back into escaped representations \n, \r, and \t. -| &ESCSP | Causes spaces to be escaped with a backslash. -| &UNESC | Causes string to be unescaped (backslashes removed and escaped values converted to their normal characters). -| &SSYB | Causes single quotes in the string to be doubled, sybase quote style ' -> '' -| &DSYB | Causes double quotes in the string to be doubled " -> "" -| &FILE | Presumes that the string is a filename, and thus results in an error if the string contains '/' or if the string is solely '.' or '..'. -| &PATH | Presumes that the string is a pathname, and so cannot contain '..' at the beginning, end, or between two '/' characters. -| &SYM | Treats the string as a 'symbol', beginning with [_a-zA-Z] and then containing [_a-zA-Z0-9]. Results in an error if the string does not match. -| &HEX | Hex-encodes the entire string. -| &DHEX | Hex-decodes the entire string. -| &B64 | Base64-encodes the entire string. -| &DB64 | Base64-decodes the entire string. -| &RF/reg/ | Filters string value through regular expression, and if it does not match in its entirety, causes an error. -| &RR/reg/rep/ | Filters string value through regular expression and replaces occurrences of 'reg' with 'rep'. It is not an error if the regular expression does not match at all. -| &HTE | Converts special HTML characters to HTML entities (includes &, <, >, ', and ". -| &DHTE | Converts the above HTML entities back to normal characters. -| &URL | Converts any special characters other than [A-Za-z0-9] into %nn where nn is the hex value of the character. -| &DURL | Converts any %nn back to normal characters. -| &nLSET | Space-pads the string on the right (left-align) until there are at least n characters in the string. -| &nRSET | Space-pads the string on the left (right-align) until there are at least n characters in the string. -| &nZRSET | Zero-pads the string on the left (right-align) until there are at least n characters in the string. -| &nLEN | Truncates the string to at most n characters. -| &SQLARG | Makes the argument safe for inclusion in a SQL command as a data value. -| &SQLSYM | Makes the argument safe for inclusion in a SQL command as a symbol (for example, table or column name). -| &HTDATA | Makes the argument safe for inclusion in an HTML document as data, for example between tags or as an attribute of a tag. +| `"` | Adds single quotes around the string value if its source type was a string or DateTime value (esp. useful for expressions and pods). +| `&DQUOT` | Adds double quotes around the string value if its source type was a string or DateTime value. +| `&ESCQ` | Escapes quotes (`'"`) and backslashes (`\`) with a leading backslash. +| `&WS` | Processes whitespace notations into the actual characters (\n -> newline, \r -> carriage return, and \t -> horizontal tab). +| `&ESCWS` | Escapes newlines, carriage returns, and tab characters into their notations: `\n`, `\r`, and `\t`. +| `&ESCSP` | Escapes spaces with a leading backslash. +| `&UNESC` | Unescapes whitespace (backslashes removed and escaped values converted to their normal characters). +| `&SSYB` | Doubles single quotes, sybase quote style `'` -> `''` +| `&DSYB` | Doubles double quotes, sybase quote style `"` -> `""` +| `&FILE` | Ensures the string is a valid filename, giving an error if it contains `'/'` or is only `"."` or `".."`. +| `&PATH` | Ensures the string is a pathname, giving an error if it has `'..'` at the start, end, or between two `'/'` characters. +| `&SYM` | Ensures the string is a symbol (starts with `[_a-zA-Z]`, followed by `[_a-zA-Z0-9]`), giving an error if is does not. +| `&HEX` | **Hex-Encode**s the string (e.g. `"Example"` -> `"4578616d706c65"`). +| `&DHEX` | **Hex-Decode**s the string (e.g. `"4578616d706c65"` -> `"Example"`). +| `&B64` | **Base64-Encode**s the entire string (e.g. `"Example"` -> `"RXhhbXBsZQ=="`). +| `&DB64` | **Base64-Decode**s the entire string (e.g. `"RXhhbXBsZQ=="` -> `"Example"`). +| `&HTE` | **HTML-Encode**: Escapes special HTML characters into HTML entities (including &, <, >, ', and ") to prevent script injection. +| `&DHTE` | **HTML-Dencode**: Unescapes HTML entities back to normal characters. +| `&URL` | **URL-Encode**: Escapes any special characters other than `[A-Za-z0-9]` into `%nn` where `nn` is the character's hex value. +| `&DURL` | **URL-Decode**: Converts any `%nn` encodings back to normal characters. +| `&RF/reg/` | Ensures the string matches a regular expression, giving an error if it does not. +| `&RR/reg/rep/` | Replaces occurrences of the `reg` regular expression with `"rep"`. Does not give an error if no matches occur. +| `&nLSET` | Right-pads the string (left-align) with spaces until it has at least `n` characters. +| `&nRSET` | Left-pads the string (right-align) with spaces until it has at least `n` characters. +| `&nZRSET` | Left-pads the string (right-align) with zeros until it has at least `n` characters. +| `&nLEN` | Truncates the string to at most `n` characters. +| `&SQLARG` | Ensures the string is safe as an SQL data value. +| `&SQLSYM` | Ensures the string is safe as an SQL symbol (for example, table or column name). +| `&HTDATA` | Ensures the string is safe as an HTML document as data, for example between tags or as an attribute of a tag. ## Implemented -Here are the currently implemented specifier chains: +Below is a list of all implemented specifier chains: - %INT - %LL @@ -111,4 +118,4 @@ Here are the currently implemented specifier chains: - %STR&PATH - %STR&PATH&nLEN -All others are unimplemented and will result in a return value of -ENOSYS. +All others are unimplemented and may result in a return value of `-ENOSYS` with the `QPF_ERR_T_NOTIMPL` error set. diff --git a/centrallix-sysdoc/string_comparison.md b/centrallix-sysdoc/string_comparison.md deleted file mode 100644 index 222e3e6d9..000000000 --- a/centrallix-sysdoc/string_comparison.md +++ /dev/null @@ -1,99 +0,0 @@ -# String Comparison -The following sections discuss the two approaches to calculating similarity between two strings. Both approaches use a SQL function to calculate a similarity metric (on a scale of 0 to 1) for two string parameters. - -## Table of Contents -- [String Comparison](#string-comparison) - - [Table of Contents](#table-of-contents) - - [Levenshtein Similarity](#levenshtein-similarity) - - [Levenshtein](#levenshtein) - - [Cosine Similarity](#cosine-similarity) - - [CHAR_SET](#char_set) - - [Frequency Table](#frequency-table) - - [Relative Frequency Table](#relative-frequency-table) - - [TF-IDF](#tf-idf) - - [Dot Product](#dot-product) - - [Magnitude](#magnitude) - - [Similarity](#similarity) - - [Future Implementation](#future-implementation) - - [Inverse Document Frequency (IDF)](#inverse-document-frequency-idf) - -## Levenshtein Similarity -The [Levenshtein](https://en.wikipedia.org/wiki/Levenshtein_distance) distance is defined as the number of insertions, deletions, or substitutions required to make one string exactly like another string. - -### Levenshtein -```c -int exp_fn_levenshtein(pExpression tree, pParamObjects objlist, pExpression i0, pExpression i1, pExpression i2) -``` -Returns the levenshtein edit distance between two strings. - -```c -int exp_fn_fuzzy_compare(pExpression tree, pParamObjects objlist, pExpression i0, pExpression i1, pExpression i2) -``` -Returns a value between 0.0 (complete match) and 1.0 (complete difference) between strings a and b, based on the (levenshtein distance) / (max len of input strings). -Some alterations to the calculation are as follows: -- matching an empty string against anything returns 0.5. -- a string that only required insertions to become the other string has its (lev_dist)/(strlen) value halved before returning -The parameter max_field_width is required, but not used. - -## Cosine Similarity - -The [Cosine Similarity](https://en.wikipedia.org/wiki/Cosine_similarity) function is defined as the dot product of two vectors divided by the product of the magnitude of the two vectors. We use the relative frequency of the individual characters within each term as the vectors in the calculation. The following functions are used to calculate cosine similarity. - -### CHAR_SET -```c -const char *CHAR_SET ... -``` -`CHAR_SET` represents all of the characters that should be considered during the calculation of similarity. `CHAR_SET` can be extended to include additional characters, as necessary. - -### Frequency Table -```c -int exp_fn_i_frequency_table(double *table, char *term) -``` -Helper function for similarity(). Creates a frequency table containing indices corresponding to all characters in `CHAR_SET` (all other characters are ignored). The values in the frequency table will contain the number of times each character appers in `term`. - -The `table` parameter must be allocated prior to calling the function with `nmMalloc()` using `sizeof(x * sizeof(double))`, where `x` is the length of `CHAR_SET`. The function will initialize all `table` values to 0, before calculating the frequency values. - -### Relative Frequency Table -```c -int exp_fn_i_relative_frequency_table(double *frequency_table) -``` -Helper function for similarity(). Converts a frequency table into a relative frequency table, where each value in the `frequency_table` is converted to the percent of occurrence (i.e., frequency divided by the sum of total occurrences). - -The `frequency_table` parameter must have been created using the `exp_fn_i_frequency_table` function above. - -### TF-IDF -```c -int exp_fn_i_tf_idf_table(double *frequency_table) -``` -Helper function for similarity(). Creates a TF x IDF vector from a frequency table, where each value in the resulting table is created by multiplying the relative frequency of each letter by the corresponding coefficient in the IDF array. - -The `frequency_table` parameter must have been created using the `exp_fn_i_frequency_table` function above. - -### Dot Product -```c -int exp_fn_i_dot_product(double *dot_product, double *r_freq_table1, double *r_freq_table2) -``` -Helper function for similarity(). Calculates the dot product of two relative frequency tables (sum of the squared values from each relative frequency table). - -The `dot_product` parameter should be initialized to 0 before calling the function. The table parameters must contain relative frequency tables that are generated from the `exp_fn_i_relative_frequency_table` function. The lengths of both tables must equal the length of `CHAR_SET`. - -### Magnitude -```c -int exp_fn_i_magnitude(double *magnitude, double *r_freq_table) -``` -Helper function for similarity(). Calculates the magnitude of a relative frequency table (square root of the sum of the squared relative frequencies). - -The `magnitude` parameter should be initialized to 0 before calling the function. The table parameter must contain a relative frequency table that was generated from the `exp_fn_i_relative_frequency_table` function. The length of the frequency table must equal the length of `CHAR_SET`. - -### Similarity -```c -int exp_fn_similarity(pExpression tree, pParamObjects objlist, pExpression i0, pExpression i1, pExpression i2) -``` -Returns a value between 0.0 (completely different) and 1.0 (complete match) reflecting the similarity between the value passed in to i0 and the value passed in to i1. The first two parameters should contain strings that need to be compared. If the value 1 is passed in the third parameter, then the similarity function will rely on TF x IDF scores to determine similarity. If no third parameter is passed, then the function will rely only on relative frequency scores. - -## Future Implementation - -### Inverse Document Frequency (IDF) -In text mining, the most common metric to use in the cosine similarity function is the [TF x IDF](https://en.wikipedia.org/wiki/Tf%E2%80%93idf) metric. Our approach uses only TF (term frequency). Inverse document frequency calculates a weighting factor for each character. This could increase precision a small amount by weighting characters that appear on many records as less important in distinguishing matches, and weighting characters that appear on only certain records as more important. IDF could be calculated by iterating through the entire partner dataset each time. The current approach uses the relative frequency of each letter used in the English language on [Wikipedia](https://en.wikipedia.org/wiki/Letter_frequency), which may not be consistent with the data in the partner database. - - diff --git a/centrallix-sysdoc/string_similarity.md b/centrallix-sysdoc/string_similarity.md new file mode 100644 index 000000000..2685edf98 --- /dev/null +++ b/centrallix-sysdoc/string_similarity.md @@ -0,0 +1,164 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# String Similarity +The following sections discuss the approaches to calculating similarity between two strings using the `clusters.c` library. This library can be included using `#include "cxlib/clusters.h"` in the centrallix codebase (use `#include "clusters.h"` in other libaries in centrallix-lib). + +## Table of Contents +- [String Comparison](#string-comparison) + - [Table of Contents](#table-of-contents) + - [Cosine Similarity](#cosine-similarity) + - [Character Sets](#character-sets) + - [Character Pair Hashing](#character-pair-hashing) + - [String Vectors](#string-vectors) + - [Sparse Vectors](#sparse-vectors) + - [Computing Similarity](#computing-similarity) + - [Levenshtein Similarity](#levenshtein-similarity) + - [Clustering](#clustering) + - [K-means Clustering](#k-means-clustering) + - [K-means++ Clustering](#k-means-clustering-1) + - [K-medoids Clustering](#k-medoids-clustering) + - [DBScan Clustering](#db-scan) + - [Sliding Clusters](#sliding-clusters) + - [Future Implementation](#future-implementation) + - [K-means Fuzzy Clustering](#k-means-fuzzy-clustering) + - [Implement Missing Algorithms](#implement-missing-algorithms) + - [Upgrade Other Duplicate Detection Systems](#upgrade-other-duplicate-detection-systems) + - [Fix Known Issues](#fix-known-issues) + + +## Cosine Similarity +The [Cosine Similarity](https://en.wikipedia.org/wiki/Cosine_similarity) function is defined as the dot product of two vectors divided by the product of the magnitude of the two vectors. Conceptually, it's like finding the _angle_ between two vectors. To get these vectors, we use the relative frequency of character pairs within each string. To reduce memory cost and speed up computation, we store them in a special sparsely allocated form, described below. + +### Character Sets +Cosine compare currently uses the following character sets. These can be extended or modified later, if necessary. +```c +const char ALLOW_SET[] = " \n\v\f\r!#$%&\"'()*+,-./:;<=>?@[]^_{|}~ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; +const char CHAR_SET[] = "`abcdefghijklmnopqrstuvwxyz0123456789"; +const char SIGNIFICANT_SET[] = "`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; +const char IGNORE_SET[] = " \n\v\f\r!#$%&\"'()*+,-./:;<=>?@[]^_{|}"; +const char BOUNDARY_CHAR = ('a' - 1); // aka. '`' +``` +- `ALLOW_SET` represents all characters which can be passed to a similarity detection algorithm. Passing other characters may cause warnings and errors, undefined or unintended behavior, and even security concerns. +- `CHAR_SET` represents all of the characters that will be uniquely considered during the calculation of similarity. Currently, this is all lowercase letters and numbers. +- `SIGNIFICANT_SET` represents all of the characters that are significant for the purposes of similarity. For example, the uppercase letters are significant because they are considered identical to lowercase letters. Thus, they are included in the `SIGNIFICANT_SET`, but not in the `CHAR_SET`. +- `IGNORE_SET` represents characters which, while allowed to be passed to a similarity algorithm, will be ignored. For example, the strings "Ya!!" and "Ya..." will be considered identical. +- The `BOUNDARY_CHAR` is a special character which is conceptually added to the start and end of any string to be checked. + - This allows for pairs that functionally include only the first and last character. + - This character appears to have been selected to be one before the first character in `CHAR_SET` (thus convention dictates that it be written `'a' - 1` to indicate this), although it's unknown if that's the main or only reason. + - If `clusters.h` is included, it can be accessed using the `CA_BOUNDARY_CHAR` macro. + +### Character Pair Hashing +Even with a small set of ASCII characters (say 36), there are still `36^2 = 1296` possible character pairs. If the number of characters in the `CHAR_SET` ever needed to be expanded - for example, to include all UTF-8 characters - this number would quickly explode exponentially to utterly infeasible proportions. Thus, a hashing algorithm is employed to hash each character pair down to a more reasonable number of dimensions (which can be accessed with the `CA_NUM_DIMS` macro). + +### String Vectors +Any string of characters in the `ALLOW_SET` can be represented by a vector. For simplicity, imagine this vector has only `5` dimensions. To find this vector, we hash each character pair in the string. As each character pair is hashed (for example, that the pair "ab" happens to hash to `3`), the corresponding dimension is increased by some amount. This amount varies to based on the characters in the pair, helping to mitigate the impact of collisions where different character pairs hash to identical numbers (a larger number of dimensions also helps to mitigate this). + +Remember that the first and last characters form a pair with the `BOUNDARY_CHAR`, so the string "ab" has three pairs: "a", "ab", and "b". If these each hash to `2`, `3`, and `0`. Thus, the vector generated by the string "ab" might be: `[7, 0, 4, 3, 0]`. Notice that dimensions #1 and #4 are both `0` because no character pairs generated a hash of `1` or `4`. In real usecases, the vast majority of elements are `0`s because the number of dimensions used is much larger than the number of character pairs in a typical string. + +### Sparse Vectors +As noted above, the vast majority of elements in a vector generated by a typical string are `0`s. This would lead to a large waste of memory and computation if every `0` was stored separately, so instead, vectors are stored sparsely. Because all hashes are positive integers, we represent `n` `0`s with a value of ` -n`. Thus, the vector `[0, 1, 0, 0, 0]` (representing an empty string in `5` dimensions) would be represented sparsely as `[-1, 1, -3]`. + +**Note**: A value of `0` in a sparse vector is undefined, so no element should be equal to `0`. + +**Note**: Sparse arrays can vary greatly in length. To find their size, one needs to traverse the array until the total number of values found adds up to `CA_NUM_DIMS`. The `ca_sparse_len()` function can be used to do this. Also, the `ca_build_vector()` and `ca_free_vector()` use the `nmSys` functions from `newmalloc.h` to avoid conflicts over the size of the allocated data. + +### Computing Similarity +Finally, to find the cosine similarity between two strings, we can simply take the [dot product](https://en.wikipedia.org/wiki/Dot_product) of their coresponding vectors. Then, we normalize the dot product by dividing by the magnitudes of both vectors multiplied together. Two strings can be compared this way using the `ca_cos_compare()` function. + + +## Levenshtein Similarity +The [Levenshtein](https://en.wikipedia.org/wiki/Levenshtein_distance) distance is defined as the number of insertions, deletions, or substitutions required to make one string exactly like another string. The version implemented in `clusters.c` additionally allows a new operation called a "swap" in which two adjacent characters change places. Transpositions of larger pieces of text are, unfortunately, not handled as well, which is a potential downfall of using Levenshtein edit distance. + +The Levenshtein similarity of two strings can be compared using the `ca_lev_compare()` function. + + +## Clustering +When searching for similar strings in a large amount of data (for example, `1,000,000` strings), comparing every string to every other string can be very computationally expensive. To speed up this process, it is helpful to _cluster_ similar strings together, then only compare strings within similar clusters. This sacrifices some accuracy to allow large amounts of data to be searched and compared in a feasible amount of time. + +### K-means Clustering +When clustering data using the [k-means](https://en.wikipedia.org/wiki/K-means_clustering) algorithm, data is divided into a predefined number of clusters with the goal of maximizing the average similarity of data points within any given cluster. To quickly summarize the algorithm: +1. Randomly select `k` data points to be the initial centroids of each cluster. +2. For each data point, find the centroid it is most similar to, and assign it to that cluster. +3. For each cluster, find the new centroid by averaging all data points in the cluster. +4. Repeat steps 2 and 3 until the clusters stabilize (i.e. no data point changes clusters). + +The implementation used in `clusters.c` also allows the programmer to specify a maximum number of iterations (called `max_iter` in the code) to prevent this process from running forever. Additionally, successive iterations can give diminishing results or even produce clusters that are slightly worse. To improve performance, the programmer can also specify a minimum improvement threshold (called `min_improvement`). Clusters must become more similar by at least this amount each iteration, otherwise the algorithm ends, even if the maximum number of iterations has not yet been reached. + +The `ca_kmeans()` function can be invoked using [the cosine comparison string vectors](#string-vectors) (see above) to cluster them into similar clusters. + +### K-means++ Clustering +**Not yet implemented** +This method is largely identical to k-means, except that [k-means++](https://en.wikipedia.org/wiki/K-means%2B%2B) assigns the initial centroids using an approximate algorithm designed to avoid some of the poor clustering possible with random assignment. + +### K-medoids Clustering +**Not yet implemented** +This method is also very similar to k-means, except that [k-medoids](https://en.wikipedia.org/wiki/K-medoids) places an additional requirement that all centroids be points in the data. This would theoretically allow for other similarity measures (such as Levenshtein edit distance) to be used for clustering instead of only cosine compare. + +### DB-Scan +**Proposed, not yet implemented or documented** + +### Sliding Clusters +A far more basic method of "clustering" is to simply sort all data alphabetically, then, instead of comparing each string to all other strings, it can be compared to only the next `n` strings. Of course, differences near the start of a string (for example, "fox" vs. "box") will cause those strings to sort far away from each other, leading them to be completely missed. + +Sorting using a similarity measure, such as `ca_cos_compare()` or `ca_lev_compare()` would resolve this issue. However, these comparison functions do not meet the transitivity requirement for sorting, which is that `(A < B) & (B < C) -> (A < C)`. For example, "car" is similar to "boxcar", which is also similar to "box". However, "car" and "box" are not similar at all. + +Additionally, sorting by the cosine vectors (similarly to how we cluster by them when using k-means) was proposed, but further investigation showed that this was also not possible. + +For problems where a sorting algorithm exists which can mitigate the above issues, this solution may prove very promising. However, so far we have not found such a problem, so the other clustering algorithms tend to outperform Sliding Clusters. + + +## Future Implementation + +### K-means Fuzzy Clustering +One of the biggest downsides with k-means is that it creates very arbitrary boundaries between clusters. Elements on either side of these boundaries may be highly similar, but if comparisons only occur within a cluster, these similar entries will be missed. The problem becomes more extreme as a higher k value (more clusters) is used, creating more arbitrary boundaries. This drawback is probably the main reason that clustering sacrifices some accuracy over searching every element. + +Running the entire search multiple types may allow some of these to be found because the initial cluster locations are random. This approach is partially implemented for duplicate searching because the algorithm runs nightly anyway, so a simple upsert (**UP**date existing entries; in**SERT** new entries) slightly reduces this problem. However, this solution is obviously far from ideal. + +If the clustering could be expanded with an additional step that makes clusters larger, adding elements from other clusters to them, this might effectively mitigate the issue. It may also allow developers to use larger numbers of clusters, improving performance as well as accuracy. Further research is needed to verify the effectiveness of this approach before an implementation is written. + +### Implement Missing Algorithms +Several algorithms (such as [k-means++](#k-means-clustering-1), [k-medoids](#k-medoids-clustering), and [DBScan](#db-scan)) above are proposed but lack an implementation. They may be effective and useful, however, to reduce development time, they have not yet been implemented. + +### Upgrade Other Duplicate Detection Systems +When a new record is entered, a quick scan is run to check if it might be a duplicate. There is also a button in the UI for a record that lets you run a duplicate check. These systems could also be upgraded using the new algorithms and strategies developed for general duplicate detection. + +### Fix Known Issues +- The cluster driver often fails to open the structure file if it was modified since the last time the path was opened. Opening a different path (including the root path, even though it does not support queries) fixes this issue. This is either a bug in the st_node caching or in the cluster driver's usage of stparse. +- The cluster does not invalidate caches if the underlying data source changes. This bug exists because I wasn't sure how to do this, but I'm pretty sure it's possible. Workaround: Developers should use `exec "cache" "drop_all"` to manually invalidate caches when data is changed, or use a fresh object system instance. +- It is not possible to open cluster or search entries, they can only be retrieved by querying. diff --git a/centrallix/Makefile.in b/centrallix/Makefile.in index 7c7407f8d..93b4292fc 100644 --- a/centrallix/Makefile.in +++ b/centrallix/Makefile.in @@ -1,6 +1,6 @@ #### Centrallix Makefile #### -#### Copyright (C) 1998-2014 by LightSys Technology Services, Inc. +#### Copyright (C) 1998-2026 by LightSys Technology Services, Inc. #### #### FREE SOFTWARE, NO WARRANTY: This program is Free Software, licensed #### under version 2 of the GNU General Public License, or any later @@ -80,6 +80,7 @@ XSUPPORT=stparse.o \ endorsement_utils.o \ obfuscate.o \ json_util.o \ + double_metaphone.o \ double.o SUPPORT=$(patsubst %,utility/%,$(XSUPPORT)) @@ -115,6 +116,7 @@ XOBJDRIVERS=objdrv_ux.o \ objdrv_uxprint.o \ objdrv_qytree.o \ objdrv_qypivot.o \ + objdrv_cluster.o \ objdrv_datafile.o \ objdrv_audio.o \ objdrv_link.o \ @@ -133,6 +135,7 @@ XV3OBJDRIVERS= \ objdrv_uxprint_v3.o \ objdrv_qytree.o \ objdrv_qypivot.o \ + objdrv_cluster.o \ objdrv_query.o \ objdrv_datafile.o \ objdrv_audio.o \ @@ -180,7 +183,6 @@ XWGTRDRIVERS=wgtdrv_alerter.o \ wgtdrv_scrollbar.o \ wgtdrv_scrollpane.o \ wgtdrv_spinner.o \ - wgtdrv_sys_osml.o \ wgtdrv_tab.o \ wgtdrv_table.o \ wgtdrv_template.o \ diff --git a/centrallix/aclocal.m4 b/centrallix/aclocal.m4 index 3b8716131..9f0905c91 100644 --- a/centrallix/aclocal.m4 +++ b/centrallix/aclocal.m4 @@ -1173,21 +1173,6 @@ AC_DEFUN(CHECK_COVERAGE, ] ) -dnl check if gcc allows __builtin_expect() -AC_DEFUN(CHECK_BUILTIN_EXPECT, - [ - AC_MSG_CHECKING(if __builtin_expect is available) - AC_COMPILE_IFELSE( - [AC_LANG_PROGRAM([], [__builtin_expect(0, 0)])], - [ - AC_DEFINE([HAVE_BUILTIN_EXPECT], [1], [Define if __builtin_expect is available]) - AC_MSG_RESULT([yes]) - ], - [AC_MSG_RESULT([no])], - ) - ] -) - dnl check if gcc allows -fPIC and -pg at the same time AC_DEFUN(CHECK_PROFILE, [ diff --git a/centrallix/centrallix.c b/centrallix/centrallix.c index 887fc9c84..27dc45d66 100644 --- a/centrallix/centrallix.c +++ b/centrallix/centrallix.c @@ -29,7 +29,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 2002 LightSys Technology Services, Inc. */ +/* Copyright (C) 2002-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -461,6 +461,7 @@ cxDriverInit() stxInitialize(); /* Structure file driver */ qytInitialize(); /* Query Tree driver */ qypInitialize(); /* Query Pivot driver */ + clusterInitialize(); /* Cluster driver */ qyInitialize(); /* stored query (aka view) driver */ rptInitialize(); /* report writer driver */ uxpInitialize(); /* UNIX printer access driver */ @@ -784,4 +785,3 @@ cxLinkSigningSetup(pStructInf my_config) return 0; } - diff --git a/centrallix/config.h.in b/centrallix/config.h.in index a40a4e6a0..8abbefb32 100644 --- a/centrallix/config.h.in +++ b/centrallix/config.h.in @@ -12,9 +12,6 @@ /* Define to 1 if you have the `basename' function. */ #undef HAVE_BASENAME -/* Define if __builtin_expect is available */ -#undef HAVE_BUILTIN_EXPECT - /* Whether __ctype_b is present and usable */ #undef HAVE_CTYPE_B diff --git a/centrallix/configure b/centrallix/configure index adf0045a8..d8c30ba8f 100755 --- a/centrallix/configure +++ b/centrallix/configure @@ -5807,36 +5807,6 @@ $as_echo "#define HAVE_NCURSES 1" >>confdefs.h - { $as_echo "$as_me:${as_lineno-$LINENO}: checking if __builtin_expect is available" >&5 -$as_echo_n "checking if __builtin_expect is available... " >&6; } - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - -int -main () -{ -__builtin_expect(0, 0) - ; - return 0; -} -_ACEOF -if ac_fn_c_try_compile "$LINENO"; then : - - -$as_echo "#define HAVE_BUILTIN_EXPECT 1" >>confdefs.h - - { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 -$as_echo "yes" >&6; } - -else - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; } -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext - - - - has_ctype_b="test" { $as_echo "$as_me:${as_lineno-$LINENO}: checking if __ctype_b is present and usable" >&5 diff --git a/centrallix/configure.ac b/centrallix/configure.ac index c96525983..fe58222e3 100644 --- a/centrallix/configure.ac +++ b/centrallix/configure.ac @@ -92,9 +92,6 @@ CENTRALLIX_CHECK_SQLITE dnl Check for ncurses CENTRALLIX_CHECK_NCURSES -dnl Check for __builtin_expect() -CHECK_BUILTIN_EXPECT - dnl Check for __ctype_b AH_TEMPLATE([HAVE_CTYPE_B], [Whether __ctype_b is present and usable]) CHECK_CTYPE_B diff --git a/centrallix/cxss/cxss_credentials_db.c b/centrallix/cxss/cxss_credentials_db.c index 5963d5d48..fb3d9629a 100644 --- a/centrallix/cxss/cxss_credentials_db.c +++ b/centrallix/cxss/cxss_credentials_db.c @@ -6,6 +6,7 @@ #include #include "cxss/credentials_db.h" #include "cxss/util.h" +#include "cxlib/mtsession.h" /* Private functions (credentials_db) */ static int cxss_i_SetupCredentialsDatabase(CXSS_DB_Context_t dbcontext); @@ -1043,4 +1044,3 @@ cxss_i_FinalizeSqliteStatements(CXSS_DB_Context_t dbcontext) sqlite3_finalize(dbcontext->update_resc_stmt); sqlite3_finalize(dbcontext->delete_resc_stmt); } - diff --git a/centrallix/cxss/cxss_crypto.c b/centrallix/cxss/cxss_crypto.c index 2c6373766..3edae6198 100644 --- a/centrallix/cxss/cxss_crypto.c +++ b/centrallix/cxss/cxss_crypto.c @@ -10,6 +10,7 @@ #include #include "cxss/crypto.h" #include "cxss/credentials_db.h" +#include "cxlib/mtsession.h" static bool CSPRNG_Initialized = false; @@ -502,4 +503,3 @@ cxssDestroyKey(char *key, size_t keylength) free(key); } } - diff --git a/centrallix/cxss/cxss_util.c b/centrallix/cxss/cxss_util.c index c32ef3adb..b0d71a3be 100644 --- a/centrallix/cxss/cxss_util.c +++ b/centrallix/cxss/cxss_util.c @@ -4,6 +4,7 @@ #include #include #include "cxss/util.h" +#include "cxlib/mtsession.h" /** @brief Duplicate a string * @@ -84,4 +85,3 @@ cxssGetTimestamp(void) } return timestamp; } - diff --git a/centrallix/etc/types.cfg b/centrallix/etc/types.cfg index 11ebc3e3e..53d874669 100644 --- a/centrallix/etc/types.cfg +++ b/centrallix/etc/types.cfg @@ -1,5 +1,5 @@ ## See LICENSE.txt for licensing information. -## Copyright (C) 1998-2024 LightSys Technology Services, Inc. +## Copyright (C) 1998-2026 LightSys Technology Services, Inc. ## ## types.cfg - file and object type information database. ## @@ -51,6 +51,7 @@ "system/symbolic-link" "Symbolic Link" lnk "" "text/plain" "text/css" "CSS File" css "" "text/plain" "system/querypivot" "Query Pivot Object" qyp "" "system/structure" +"system/cluster" "Clustering Object" cluster "" "system/structure" "application/json" "JSON data" json "" "text/plain" "text/json" "JSON data" "" "" "application/json" "text/x-json" "JSON data" "" "" "application/json" diff --git a/centrallix/expression/exp_compiler.c b/centrallix/expression/exp_compiler.c index 48f975928..84753e5b8 100644 --- a/centrallix/expression/exp_compiler.c +++ b/centrallix/expression/exp_compiler.c @@ -15,7 +15,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1999-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1999-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -1043,8 +1043,8 @@ expCompileExpression(char* text, pParamObjects objlist, int lxflags, int cmpflag /*** expBindExpression - do late binding of an expression tree to an *** object list. 'domain' specifies the requested bind domain, whether - *** runstatic (EXP_F_RUNSTATIC), runserver (EXP_F_RUNSERVER), or runclient - *** (EXP_F_RUNCLIENT). 'domain' can also be -0-, in which case we rebind + *** runstatic (EXPR_F_RUNSTATIC), runserver (EXPR_F_RUNSERVER), or runclient + *** (EXPR_F_RUNCLIENT). 'domain' can also be -0-, in which case we rebind *** a domainless expression. ***/ int @@ -1072,20 +1072,11 @@ expBindExpression(pExpression exp, pParamObjects objlist, int flags) break; } } - if (exp->ObjID == -1) - { - cm |= EXPR_MASK_EXTREF; - } - } - else if (exp->ObjID == -2 || exp->ObjID == -3) - { - if (exp->ObjID == -2) cm |= (1<<(objlist->CurrentID)); - if (exp->ObjID == -3) cm |= (1<<(objlist->ParentID)); - } - else if (exp->ObjID >= 0) - { - cm |= (1<<(exp->ObjID)); + cm |= EXPR_MASK_EXTREF; } + else if (exp->ObjID == EXPR_OBJID_CURRENT) cm |= (1<<(objlist->CurrentID)); + else if (exp->ObjID == EXPR_OBJID_PARENT) cm |= (1<<(objlist->ParentID)); + else if (exp->ObjID >= 0) cm |= (1<<(exp->ObjID)); } /** Check for absolute references in functions **/ @@ -1105,4 +1096,3 @@ expBindExpression(pExpression exp, pParamObjects objlist, int flags) return cm; } - diff --git a/centrallix/expression/exp_evaluate.c b/centrallix/expression/exp_evaluate.c index 1984494a2..2413a0d00 100644 --- a/centrallix/expression/exp_evaluate.c +++ b/centrallix/expression/exp_evaluate.c @@ -16,7 +16,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1999-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1999-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -1460,6 +1460,25 @@ expEvalProperty(pExpression tree, pParamObjects objlist) memcpy(&(tree->Types.Date),vptr,sizeof(DateTime)); break; + /*** FIX: Shallow copy violates contract, no known drivers use + *** INTVEC yet so it's fine. + ***/ + case DATA_T_INTVEC: + v = getfn(obj, tree->Name, DATA_T_INTVEC, &vptr); + if (v != 0) break; + memcpy(&(tree->Types.StrVec), vptr, sizeof(IntVec)); + break; + + /*** FIX: Shallow copy violates contract, but objdrv_cluster.c is + *** the only one location that StringVec is used and I think it + *** won't cause issues. + ***/ + case DATA_T_STRINGVEC: + v = getfn(obj, tree->Name, DATA_T_STRINGVEC, &vptr); + if (v != 0) break; + memcpy(&(tree->Types.IntVec), vptr, sizeof(StringVec)); + break; + case DATA_T_MONEY: v = getfn(obj,tree->Name,DATA_T_MONEY,&vptr); if (v != 0) break; diff --git a/centrallix/expression/exp_functions.c b/centrallix/expression/exp_functions.c index ae207fc89..be01c7cb1 100644 --- a/centrallix/expression/exp_functions.c +++ b/centrallix/expression/exp_functions.c @@ -1,32 +1,8 @@ -#define _GNU_SOURCE -#include -#include -#include -#include -#include -#include -#include -#include -#include "obj.h" -#include "cxlib/mtask.h" -#include "cxlib/xarray.h" -#include "cxlib/xhash.h" -#include "cxlib/mtlexer.h" -#include "expression.h" -#include "cxlib/mtsession.h" -#include "cxss/cxss.h" -#include -#include -#include -#include -#include - - /************************************************************************/ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1999-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1999-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -65,6 +41,495 @@ /* that issue in exp_evaluate.c */ /************************************************************************/ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cxlib/clusters.h" +#include "cxlib/expect.h" +#include "cxlib/mtask.h" +#include "cxlib/mtlexer.h" +#include "cxlib/mtsession.h" +#include "cxlib/newmalloc.h" +#include "cxlib/util.h" +#include "cxlib/xarray.h" +#include "cxlib/xhash.h" +#include "cxss/cxss.h" +#include "double_metaphone.h" +#include "expression.h" +#include "obj.h" + + +/*** Specifies expectations about an argument. + *** + *** @param Datatypes An array of datatypes (terminated with a -1). Set to NULL + *** to accept any datatype as valid for this argument. + *** @param Flags Flags to require other properties about an argument. If the + *** flag a required behavior for specific types, the requirement will be + *** skipped for other types. + *** + *** Valid Flags: + *** - `EXP_ARG_OPTIONAL`: The arg is optional. It is not valid a required + *** argument after an optional one. + *** - `EXP_ARG_NOT_NULL`: Expect the arg to not be null. + *** - `EXP_ARG_FORCE_TYPE`: Run type check on null args (not recommended). + *** - `EXP_ARG_NON_EMPTY`: Expect string to be non-empty. Expect a + *** stringvec or intvec to have elements (does not check them). + *** - `EXP_ARG_POSITIVE`: Expect a positive or zero value for int, double, + *** or money. (Includes NON_NAN: NAN is not positive). + *** - `EXP_ARG_NEGATIVE`: Expect a negative or zero value for int, double, + *** or money. (Includes NON_NAN: NAN is not negative). + *** - `EXP_ARG_NON_NAN`: Expect a double to be a number, not NAN. + *** + *** @attention - Checks like `EXP_ARG_NON_EMPTY`, `EXP_ARG_NON_NAN`, etc. also + *** succeed for `NULL` values. To avoid this, specify `EXP_ARG_NOT_NULL`. + ***/ +typedef struct + { + int* Datatypes; + int Flags; + } + ArgExpect, *pArgExpect; + +#define EXP_ARG_END (ArgExpect){NULL, -1} +#define EXP_ARG_NO_FLAGS (0) +#define EXP_ARG_OPTIONAL (1 << 0) +#define EXP_ARG_NOT_NULL (1 << 1) +#define EXP_ARG_FORCE_TYPE (1 << 2) +#define EXP_ARG_NON_EMPTY (1 << 3) +#define EXP_ARG_NEGATIVE (1 << 4) +#define EXP_ARG_POSITIVE (1 << 5) +#define EXP_ARG_NON_NAN (1 << 6) + +/*** An internal function used by the schema verifier (below) to verify each + *** argument of the provided schema. + *** + *** @param fn_name The name of the expression function to be verified. + *** @param arg The argument to be verified. + *** @param arg_expect The expectation struct which specifies the requirements + *** for this argument. + *** @returns 0 if the expectations are successfully met, + *** -1 if an expectation is violated (and mssError() is called). + ***/ +static int +exp_fn_i_verify_arg(const char* fn_name, pExpression arg, const ArgExpect* arg_expect) + { + /** The expectation struct cannot be NULL. **/ + if (UNLIKELY(arg_expect == NULL)) + { + mssError(1, "EXP", + "%s(...): Expectation struct cannot be NULL", + fn_name + ); + return -1; + } + + /** Extract values. **/ + ASSERTMAGIC(arg, MGK_EXPRESSION); + const int actual_datatype = arg->DataType; + + /** Check for a provided NULL value. **/ + if (arg->Flags & EXPR_F_NULL) + { + if (arg_expect->Flags & EXP_ARG_NOT_NULL) + { + mssError(1, "EXP", + "%s(...): Expects a non-null value, but got NULL : %s (%d).", + fn_name, objTypeToStr(actual_datatype), actual_datatype + ); + return -1; + } + + /** Skip type checks for NULL values (unless they are forced). **/ + if (!(arg_expect->Flags & EXP_ARG_FORCE_TYPE)) goto skip_type_checks; + } + + /** Skip type checks if none are requested. **/ + if (arg_expect->Datatypes == NULL) goto skip_type_checks; + + /** Type checks requested, but no valid types given: Likely a mistake. **/ + if (arg_expect->Datatypes[0] == -1) + { + mssError(1, "EXP", "%s(...): Invalid Schema! Empty array of allowed datatypes.", fn_name); + fprintf(stderr, "Hint: To skip type checks, pass NULL for the array of data types.\n"); + return -1; + } + + /** Verify datatypes. **/ + bool found = false; + for (int j = 0; arg_expect->Datatypes[j] != -1; j++) + { + const int expected_datatype = arg_expect->Datatypes[j]; + if (expected_datatype == actual_datatype) + { + found = true; + break; + } + } + + /** Handle failure. **/ + if (!found) + { + /** Accumulate additional valid types. **/ + char buf[256] = {'\0'}; + int cur = 0, j = 1; + while (true) + { + int datatype = arg_expect->Datatypes[j++]; + if (datatype == -1) break; + + cur += snprintf( + buf + cur, 256 - cur, + " or %s (%d)", + objTypeToStr(datatype), datatype + ); + } + + /** Print error. **/ + int first_datatype = arg_expect->Datatypes[0]; + mssError(1, "EXP", + "%s(...): Expects type %s (%d)%s but got type %s (%d).", + fn_name, objTypeToStr(first_datatype), first_datatype, buf, objTypeToStr(actual_datatype), actual_datatype + ); + return -1; + } + + skip_type_checks: + /** All flag checks not implemented above should pass on NULL values. **/ + if (arg->Flags & EXPR_F_NULL) return 0; + + /** Verify other Flags by type, if specified. **/ + switch (actual_datatype) + { + case DATA_T_INTEGER: + { + int value = arg->Integer; + if (arg_expect->Flags & EXP_ARG_POSITIVE && value < 0) + { + mssError(1, "EXP", + "%s(...): Expects positive int but got %d.", + fn_name, value + ); + return -1; + } + if (arg_expect->Flags & EXP_ARG_NEGATIVE && value > 0) + { + mssError(1, "EXP", + "%s(...): Expects negative int but got %d.", + fn_name, value + ); + return -1; + } + break; + } + + case DATA_T_DOUBLE: + { + double value = arg->Types.Double; + if (arg_expect->Flags & EXP_ARG_NON_NAN && isnan(value)) + { + mssError(1, "EXP", + "%s(...): Expects non-nan double but got %g.", + fn_name, value + ); + return -1; + } + if (arg_expect->Flags & EXP_ARG_POSITIVE && value < 0) + { + mssError(1, "EXP", + "%s(...): Expects positive double but got %g.", + fn_name, value + ); + return -1; + } + if (arg_expect->Flags & EXP_ARG_NEGATIVE && value > 0) + { + mssError(1, "EXP", + "%s(...): Expects negative double but got %g.", + fn_name, value + ); + return -1; + } + break; + } + + case DATA_T_STRING: + { + char* str = arg->String; + if (arg_expect->Flags & EXP_ARG_NON_EMPTY && str[0] == '\0') + { + mssError(1, "EXP", + "%s(...): Expects string to contain characters, but got \"\".", + fn_name + ); + return -1; + } + break; + } + + case DATA_T_MONEY: + { + pMoneyType value = &arg->Types.Money; + if (arg_expect->Flags & EXP_ARG_POSITIVE && value->WholePart < 0) + { + mssError(1, "EXP", + "%s(...): Expects positive money value but got $%d.%g.", + fn_name, value->WholePart, (double)value->FractionPart / 100.0 + ); + return -1; + } + if (arg_expect->Flags & EXP_ARG_NEGATIVE && value->WholePart > 0) + { + mssError(1, "EXP", + "%s(...): Expects negative money value but got $%d.%g.", + fn_name, value->WholePart, (double)value->FractionPart / 100.0 + ); + return -1; + } + break; + } + + case DATA_T_STRINGVEC: + { + pStringVec str_vec = &arg->Types.StrVec; + if (arg_expect->Flags & EXP_ARG_NON_EMPTY && str_vec->nStrings == 0) + { + mssError(1, "EXP", + "%s(...): Expects StringVec to contain strings, but got [].", + fn_name + ); + return -1; + } + break; + } + + case DATA_T_INTVEC: + { + pIntVec int_vec = &arg->Types.IntVec; + if (arg_expect->Flags & EXP_ARG_NON_EMPTY && int_vec->nIntegers == 0) + { + mssError(1, "EXP", + "%s(...): Expects IntVec to contain strings, but got [].", + fn_name + ); + return -1; + } + break; + } + } + + return 0; + } + +/*** Verify that arguments passed to a function match some expected values. + *** + *** @param arg_expects A pointer to an array of ArgExpect structs, each + *** representing expectations for a single argument, in the order they + *** are passed to the function. + *** @param num_args The number of arguments to expect to be passed to the + *** function (and the length of arg_expects). + *** @param tree The tree containing the actual arguments passed. + *** @param obj_list The object list scope which was passed to the function. + *** @returns 0 if verification passes, or + *** -1 if an error occurs or arguments are incorrect. + *** + *** @attention - Promises that an error message will be printed with a call + *** to mssError() if an error occurs. + *** + *** Example: + *** ```c + *** if (exp_fn_i_verify_schema( + *** (ArgExpect[]){ + *** {(int[]){DATA_T_INTEGER, DATA_T_DOUBLE, DATA_T_DATETIME, -1}, EXP_ARG_NOT_NULL}, + *** {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS}, + *** {(int[]){DATA_T_STRING, -1}, EXP_ARG_OPTIONAL}, + *** EXP_ARG_END + *** }, tree + *** ) != 0) + *** { + *** mssError(0, "EXP", "%s(?): Call does not match function schema.", tree->Name); + *** return -1; + *** } + *** ``` + ***/ +static int +exp_fn_i_verify_schema(const ArgExpect* arg_expects, pExpression tree) + { + /** Verify expression tree. **/ + ASSERTMAGIC(tree, MGK_EXPRESSION); + + /** Count arguments. **/ + unsigned int req_args = 0u, opt_args = 0u; + for (unsigned int i = 0; arg_expects[i].Flags != EXP_ARG_END.Flags; i++) + { + if (arg_expects[i].Flags & EXP_ARG_OPTIONAL) + opt_args++; + else if (UNLIKELY(opt_args > 0)) + { + /** Required argument follows optional argument (not allowed). **/ + mssError(1, "EXP", "%s(?): Invalid Schema! Required argument #%u after optional argument.", tree->Name, i); + return -1; + } + else + req_args++; + } + const unsigned int total_args = req_args + opt_args; + + /** Verify argument count. **/ + const int actual_args = tree->Children.nItems; + if (opt_args == 0) + { + if (UNLIKELY(actual_args != req_args)) + { + mssError(1, "EXP", + "%s(?): Expects %u argument%s, got %d argument%s.", + tree->Name, req_args, (req_args == 1) ? "" : "s", actual_args, (actual_args == 1) ? "" : "s" + ); + return -1; + } + } + else + { + if (UNLIKELY(actual_args < req_args || total_args < actual_args)) + { + mssError(1, "EXP", + "%s(?): Expects between %u and %u arguments, got %d argument%s.", + tree->Name, req_args, total_args, actual_args, (actual_args == 1) ? "" : "s" + ); + return -1; + } + } + + /** Verify arguments. **/ + for (int i = 0; i < actual_args; i++) + { + if (UNLIKELY(exp_fn_i_verify_arg(tree->Name, tree->Children.Items[i], &arg_expects[i]) != 0)) + { + mssError(0, "EXP", + "%s(...): Error while reading arg #%d/%d.", + tree->Name, i + 1, max(i + 1, req_args) + ); + return -1; + } + } + + /** Pass. **/ + return 0; + } + +/*** Extract a number from a numeric expression. + *** + *** @param numeric_expr The numeric expression to be extracted. + *** @param result_ptr A pointer to a double where the result is stored. + *** @returns 0 on success, + *** -1 on failure, + *** 1 if the expression is NULL. + ***/ +static int +exp_fn_i_get_number(pExpression numeric_expr, double* result_ptr) + { + /** Check for null values. **/ + if (numeric_expr == NULL || numeric_expr->Flags & EXPR_F_NULL) return 1; + + /** Check for null destination. **/ + if (UNLIKELY(result_ptr == NULL)) + { + mssError(1, "EXP", "Null location provided to store numeric result."); + return -1; + } + + /** Get the numeric value. **/ + double n; + switch(numeric_expr->DataType) + { + case DATA_T_INTEGER: n = numeric_expr->Integer; break; + case DATA_T_DOUBLE: n = numeric_expr->Types.Double; break; + case DATA_T_MONEY: n = objDataToDouble(DATA_T_MONEY, &(numeric_expr->Types.Money)); break; + default: + mssError(1, "EXP", + "%s (%d) is not a numeric type.", + objTypeToStr(numeric_expr->DataType), numeric_expr->DataType + ); + return -1; + } + + /** Store the result. **/ + *result_ptr = n; + + return 0; + } + +/*** Free the given tree's result string, if it has one. + *** + *** @param tree The affected tree. + ***/ +static void +exp_fn_i_free_result_string(pExpression tree) + { + /** If no string is allocated, no work is needed. **/ + if (tree->Alloc == 0) return; + + /** Free the string, if it exists. **/ + if (tree->String != NULL) nmSysFree(tree->String); + + /** No string is allocated anymore. **/ + tree->String = tree->Types.StringBuf; + tree->Alloc = 0; + + return; + } + +/*** Ensure that the allocated result string is long enough to store a given + *** amount of required data. This function promises that `tree->String` will + *** point to at least `required_space` bytes after it returns 0. + *** + *** @param tree The affected tree. + *** @param required_space The number of bytes required. + *** @returns 0 if successful, or + *** -1 if an error occurs. + ***/ +static int +exp_fn_i_alloc_result_string(pExpression tree, const size_t required_space) + { + /** Free the previous string (if needed) so we can store a new one. **/ + exp_fn_i_free_result_string(tree); + + /** Decide how to allocate space. **/ + if (required_space <= sizeof(tree->Types.StringBuf)) + { + /** We can use the preallocated buffer. **/ + tree->String = tree->Types.StringBuf; + tree->Alloc = 0; + } + else + { + /** We need to allocate new memory. **/ + char* result = check_ptr(nmSysMalloc(required_space * sizeof(char))); + if (result == NULL) return -1; + tree->String = result; + tree->Alloc = 1; + } + + return 0; + } + /****** Evaluator functions follow for expEvalFunction ******/ @@ -1111,111 +1576,169 @@ int exp_fn_reverse(pExpression tree, pParamObjects objlist, pExpression i0, pExp return 0; } - -int exp_fn_lztrim(pExpression tree, pParamObjects objlist, pExpression i0, pExpression i1, pExpression i2) +/** Leading zero trim. */ +int +exp_fn_lztrim(pExpression tree) { - char* ptr; - - if (!i0 || i0->Flags & EXPR_F_NULL) - { - tree->Flags |= EXPR_F_NULL; + /** Expect one nullable string parameter. **/ + if (UNLIKELY(exp_fn_i_verify_schema((ArgExpect[]){ + {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS}, + EXP_ARG_END, + }, tree) != 0)) + { + mssError(0, "EXP", "%s(?): Call does not match function schema.", tree->Name); + return -1; + } + + /** Extract the arg string. **/ + pExpression maybe_str = check_ptr(tree->Children.Items[0]); + if (UNLIKELY(maybe_str == NULL)) return -1; + if (maybe_str->Flags & EXPR_F_NULL) + { + /** Propagate null values. **/ + tree->Flags |= EXPR_F_NULL; + tree->DataType = DATA_T_STRING; + return 0; + } + char* str = check_ptr(maybe_str->String); + if (UNLIKELY(str == NULL)) return -1; + + /*** We don't need to allocate new memory or copy anything because we + *** can simply point to the first character in the previous string + *** that isn't trimmed. + ***/ + + /** Iterate over all the characters that need to be removed. **/ + while (*str == '0' && (str[1] >= '0' && str[1] <= '9')) str++; + + /** Return the results using the tree. **/ + exp_fn_i_free_result_string(tree); tree->DataType = DATA_T_STRING; - return 0; - } - if (i0->DataType != DATA_T_STRING) - { - mssError(1,"EXP","lztrim() only works on STRING data types"); - return -1; - } - if (tree->Alloc && tree->String) - { - nmSysFree(tree->String); - } - tree->DataType = DATA_T_STRING; - ptr = i0->String; - while(*ptr == '0' && (ptr[1] >= '0' && ptr[1] <= '9')) ptr++; - tree->String = ptr; - tree->Alloc = 0; + tree->String = str; + tree->Alloc = 0; + return 0; } -int exp_fn_ltrim(pExpression tree, pParamObjects objlist, pExpression i0, pExpression i1, pExpression i2) +/** Left trim spaces. **/ +int +exp_fn_ltrim(pExpression tree) { - char* ptr; - - if (!i0 || i0->Flags & EXPR_F_NULL) - { - tree->Flags |= EXPR_F_NULL; + /** Expect one nullable string parameter. **/ + if (UNLIKELY(exp_fn_i_verify_schema((ArgExpect[]){ + {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS}, + EXP_ARG_END, + }, tree) != 0)) + { + mssError(0, "EXP", "%s(?): Call does not match function schema.", tree->Name); + return -1; + } + + /** Extract the arg string. **/ + pExpression maybe_str = check_ptr(tree->Children.Items[0]); + if (UNLIKELY(maybe_str == NULL)) return -1; + if (maybe_str->Flags & EXPR_F_NULL) + { + /** Propagate null values. **/ + tree->Flags |= EXPR_F_NULL; + tree->DataType = DATA_T_STRING; + return 0; + } + char* str = check_ptr(maybe_str->String); + if (UNLIKELY(str == NULL)) return -1; + + /*** We don't need to allocate new memory or copy anything because we + *** can simply point to the first character in the previous string + *** that isn't trimmed. + ***/ + + /** Iterate until we find the a character that isn't a space. **/ + /** Note: Only spaces are trimmed, as with similar trim functions in most SQL languages. **/ + while (*str == ' ') str++; + + /** Return the results using the tree. **/ + exp_fn_i_free_result_string(tree); tree->DataType = DATA_T_STRING; - return 0; - } - if (i0->DataType != DATA_T_STRING) - { - mssError(1,"EXP","ltrim() only works on STRING data types"); - return -1; - } - if (tree->Alloc && tree->String) - { - nmSysFree(tree->String); - } - tree->DataType = DATA_T_STRING; - ptr = i0->String; - while(*ptr == ' ') ptr++; - tree->String = ptr; - tree->Alloc = 0; + tree->String = str; + tree->Alloc = 0; + return 0; } -int exp_fn_rtrim(pExpression tree, pParamObjects objlist, pExpression i0, pExpression i1, pExpression i2) +/** Right trim spaces. **/ +int +exp_fn_rtrim(pExpression tree) { - char* ptr; - int n; - - if (!i0 || i0->Flags & EXPR_F_NULL) - { - tree->Flags |= EXPR_F_NULL; - tree->DataType = DATA_T_STRING; - return 0; - } - if (i0->DataType != DATA_T_STRING) - { - mssError(1,"EXP","rtrim() only works on STRING data types"); - return -1; - } - if (tree->Alloc && tree->String) - { - nmSysFree(tree->String); - } - tree->Alloc = 0; - tree->DataType = DATA_T_STRING; - ptr = i0->String + strlen(i0->String); - while(ptr > i0->String && ptr[-1] == ' ') ptr--; - if (ptr == i0->String + strlen(i0->String)) - { - /** optimization for strings are still the same **/ - tree->String = i0->String; - } - else - { - /** have to copy because we removed spaces **/ - n = ptr - i0->String; - if (n < 63) + /** Expect one nullable string parameter. **/ + if (UNLIKELY(exp_fn_i_verify_schema((ArgExpect[]){ + {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS}, + EXP_ARG_END, + }, tree) != 0)) { - tree->String = tree->Types.StringBuf; - memcpy(tree->String, i0->String, n); - tree->String[n] = '\0'; - tree->Alloc = 0; + mssError(0, "EXP", "%s(?): Call does not match function schema.", tree->Name); + return -1; } - else + + /** Extract the arg string. **/ + pExpression maybe_str = check_ptr(tree->Children.Items[0]); + if (UNLIKELY(maybe_str == NULL)) return -1; + if (maybe_str->Flags & EXPR_F_NULL) { - tree->String = (char*)nmSysMalloc(n+1); - memcpy(tree->String, i0->String, n); - tree->String[n] = '\0'; - tree->Alloc = 1; + /** Propagate null values. **/ + tree->Flags |= EXPR_F_NULL; + tree->DataType = DATA_T_STRING; + return 0; } - } + char* str = check_ptr(maybe_str->String); + if (UNLIKELY(str == NULL)) return -1; + + /** Trim spaces from the end of the string. **/ + /** Note: Only spaces are trimmed, as with similar trim functions in most SQL languages. **/ + const int len = strlen(str); + int n = len; + while (n > 0 && str[n - 1] == ' ') n--; + + /** Shortcut for trimming nothing. **/ + if (n == len) + { + tree->String = str; + goto end; + } + + /** We need to copy to remove spaces (str is owned by a child expression). **/ + if (check(exp_fn_i_alloc_result_string(tree, n + 1)) != 0) return -1; + memcpy(tree->String, str, n); + tree->String[n] = '\0'; + + end: + /** Return the results in the tree. **/ + tree->DataType = DATA_T_STRING; + + return 0; + } + + +/** Left and right trim spaces. **/ +int +exp_fn_trim(pExpression tree) + { + /** Left trim the expression. **/ + exp_fn_ltrim(tree); + + /** Temporarily override the arg0 str pointer with the result from ltrim(). **/ + pExpression arg0 = tree->Children.Items[0]; + char* arg1_str = arg0->String; + arg0->String = tree->String; + tree->Alloc = 0; + + /** Right trim the expression, which will use the overridden string above. **/ + exp_fn_rtrim(tree); + + /** Restore the arg0 tree. **/ + arg0->String = arg1_str; + return 0; } @@ -2356,17 +2879,37 @@ int exp_fn_truncate(pExpression tree, pParamObjects objlist, pExpression i0, pEx /*** constrain(value, min, max) ***/ int exp_fn_constrain(pExpression tree, pParamObjects objlist, pExpression i0, pExpression i1, pExpression i2) { - if (!i0 || !i1 || !i2 || (i0->DataType != i1->DataType) || i0->DataType != i2->DataType || !(i0->DataType == DATA_T_INTEGER || i0->DataType == DATA_T_MONEY || i0->DataType == DATA_T_DOUBLE)) - { - mssError(1,"EXP","constrain() requires three numeric parameters of the same data type"); - return -1; - } + /** Skip null value. **/ tree->DataType = i0->DataType; if ((i0->Flags & EXPR_F_NULL)) { tree->Flags |= EXPR_F_NULL; return 0; } + + /** Verify parameters. **/ + if (i0 == NULL || i1 == NULL || i2 == NULL) + { + mssError(1, "EXP", "constrain() expects three parameters."); + return -1; + } + if (i0->DataType != DATA_T_INTEGER && i0->DataType != DATA_T_DOUBLE && i0->DataType != DATA_T_MONEY) + { + mssError(1, "EXP", + "constrain() expects three numeric parameters: %s is not numeric.", + objTypeToStr(i0->DataType) + ); + if (i0->DataType == DATA_T_STRING) printf("Value: '%s'\n", i0->String); + return -1; + } + if (i0->DataType != i1->DataType || i1->DataType != i2->DataType) + { + mssError(1, "EXP", + "constrain() expects three numeric parameters of the same data type but got types %s, %s, and %s.", + objTypeToStr(i0->DataType), objTypeToStr(i1->DataType), objTypeToStr(i2->DataType) + ); + return -1; + } /* check min */ if (!(i1->Flags & EXPR_F_NULL)) @@ -3224,99 +3767,111 @@ int exp_fn_from_base64(pExpression tree, pParamObjects objlist, pExpression i0, return -1; } - -int exp_fn_log10(pExpression tree, pParamObjects objlist, pExpression i0, pExpression i1, pExpression i2) +static int +exp_fn_i_do_math(pExpression tree, double (*math)(), int arg_num) { - double n; - - if (!i0) + /** Verify function schema: expect arg_num numeric values. **/ + ArgExpect expects[arg_num + 1]; + for (int i = 0; i < arg_num; i++) + expects[i] = (ArgExpect){(int[]){DATA_T_INTEGER, DATA_T_DOUBLE, DATA_T_MONEY, -1}, EXP_ARG_NO_FLAGS}; + expects[arg_num] = EXP_ARG_END; + if (exp_fn_i_verify_schema(expects, tree) != 0) { - mssError(1, "EXP", "log10() requires a number as its first parameter"); - goto error; + mssError(0, "EXP", "%s(?): Call does not match function schema.", tree->Name); + return -1; } - if (i0->Flags & EXPR_F_NULL) + + /** Null checks. **/ + for (int i = 0; i < arg_num; i++) { - tree->DataType = DATA_T_DOUBLE; - tree->Flags |= EXPR_F_NULL; - return 0; + pExpression arg = tree->Children.Items[i]; + if (arg->Flags & EXPR_F_NULL) + { + tree->DataType = DATA_T_DOUBLE; + tree->Flags |= EXPR_F_NULL; + return 0; + } } - switch(i0->DataType) + + /** Maximum supported args. **/ + if (arg_num > 4) { - case DATA_T_INTEGER: - n = i0->Integer; - break; - case DATA_T_DOUBLE: - n = i0->Types.Double; - break; - case DATA_T_MONEY: - n = objDataToDouble(DATA_T_MONEY, &(i0->Types.Money)); - break; - default: - mssError(1, "EXP", "log10() requires a number as its first parameter"); - goto error; + mssError(1, "EXP", "%s(...): exp_fn_i_do_math() does not support functions with more than 4 arguments. If this is an issue, please increase the number of arguments here: %s:%d", tree->Name, __FILE__, __LINE__); + return -1; } - if (n < 0) + + /** Get the numbers for the args. **/ + double n[4] = {0.0, 0.0, 0.0, 0.0}; + for (int i = 0; i < arg_num; i++) { - mssError(1, "EXP", "log10(): cannot compute the logarithm of a negative number"); - goto error; + if (check(exp_fn_i_get_number(tree->Children.Items[i], &(n[i]))) != 0) + { + mssError(0, "EXP", "%s(...): Failed to get arg%d.", tree->Name, i); + return -1; + } } + + /** Return results. **/ tree->DataType = DATA_T_DOUBLE; - tree->Types.Double = log10(n); - return 0; + tree->Types.Double = math(n[0], n[1], n[2], n[3]); /* Call function with all supported args. */ + + return 0; + } - error: - return -1; +int +exp_fn_power(pExpression tree) + { + return exp_fn_i_do_math(tree, pow, 2); } +int +exp_fn_ln(pExpression tree) + { + return exp_fn_i_do_math(tree, log, 1); + } -int exp_fn_power(pExpression tree, pParamObjects objlist, pExpression i0, pExpression i1, pExpression i2) +int +exp_fn_log10(pExpression tree) { - double n, p; + return exp_fn_i_do_math(tree, log10, 1); + } - if (!i0 || !i1) - { - mssError(1, "EXP", "power() requires numbers as its first and second parameters"); - goto error; - } - if ((i0->Flags & EXPR_F_NULL) || (i1->Flags & EXPR_F_NULL)) +int +exp_fn_log(pExpression tree) + { + /** Verify function schema: A number and an optional base. **/ + if (exp_fn_i_verify_schema((ArgExpect[]){ + {(int[]){DATA_T_INTEGER, DATA_T_DOUBLE, DATA_T_MONEY, -1}, EXP_ARG_NO_FLAGS}, + {(int[]){DATA_T_INTEGER, DATA_T_DOUBLE, DATA_T_MONEY, -1}, EXP_ARG_OPTIONAL}, + EXP_ARG_END, + }, tree) != 0) { - tree->DataType = DATA_T_DOUBLE; - tree->Flags |= EXPR_F_NULL; - return 0; + mssError(0, "EXP", "%s(?): Call does not match function schema.", tree->Name); + return -1; } - switch(i0->DataType) + + /** Extract args. **/ + double number, base; + if (check(exp_fn_i_get_number(check_ptr(tree->Children.Items[0]), &number)) != 0) { - case DATA_T_INTEGER: - n = i0->Integer; - break; - case DATA_T_DOUBLE: - n = i0->Types.Double; - break; - case DATA_T_MONEY: - n = objDataToDouble(DATA_T_MONEY, &(i0->Types.Money)); - break; - default: - mssError(1, "EXP", "power() requires a number as its first parameter"); - goto error; + mssError(0, "EXP", "%s(...): Failed to get arg1 (number).", tree->Name); + return -1; } - switch(i1->DataType) + if (tree->Children.nItems > 1) { - case DATA_T_INTEGER: - p = i1->Integer; - break; - case DATA_T_DOUBLE: - p = i1->Types.Double; - break; - default: - mssError(1, "EXP", "power() requires an integer or double as its second parameter"); - goto error; + if (check(exp_fn_i_get_number(check_ptr(tree->Children.Items[1]), &base)) != 0) + { + mssError(0, "EXP", "%s(...): Failed to get arg2 (base).", tree->Name); + return -1; + } } + else base = M_E; + + /** Return the results in the tree. **/ tree->DataType = DATA_T_DOUBLE; - tree->Types.Double = pow(n, p); - return 0; + tree->Types.Double = log(number) / log(base); - error: - return -1; + return 0; } @@ -3978,368 +4533,205 @@ int exp_fn_nth(pExpression tree, pParamObjects objlist, pExpression i0, pExpress return 0; } -/* See centrallix-sysdoc/string_comparison.md for more information. */ -int exp_fn_levenshtein(pExpression tree, pParamObjects objlist, pExpression i0, pExpression i1, pExpression i2) - { - - if (!i0 || !i1) - { - mssError(1,"EXP","levenshtein() requires two parameters"); - return -1; - } - - if ((i0->Flags & EXPR_F_NULL) || (i1->Flags & EXPR_F_NULL)) - { - tree->DataType = DATA_T_INTEGER; - tree->Flags |= EXPR_F_NULL; - return 0; - } - - if ((i0->DataType != DATA_T_STRING) || (i1->DataType != DATA_T_STRING)) - { - mssError(1,"EXP","levenshtein() requires two string parameters"); - return -1; - } - // for all i and j, d[i,j] will hold the Levenshtein distance between - // the first i characters of s and the first j characters of t - int length1 = strlen(i0->String); - int length2 = strlen(i1->String); - //int levMatrix[length1+1][length2+1]; - int (*levMatrix)[length1+1][length2+1] = nmSysMalloc(sizeof(*levMatrix)); - int i; - int j; - //set each element in d to zero - for (i = 0; i < length1; i++) +int +exp_fn_metaphone(pExpression tree) { - for (j = 0; j < length2; j++) - { - (*levMatrix)[i][j] = 0; - } - } + int ret = -1; + bool free_strs = true; - // source prefixes can be transformed into empty string by - // dropping all characters - for (i = 0; i <= length1; i++) - { - (*levMatrix)[i][0] = i; - } - - // target prefixes can be reached from empty source prefix - // by inserting every character - for (j = 0; j <= length2; j++) - { - (*levMatrix)[0][j] = j; - } - - for (i = 1; i <= length1; i++) - { - for (j = 1; j <= length2; j++) - { - if (i0->String[i-1] == i1->String[j-1]) - { - (*levMatrix)[i][j] = (*levMatrix)[i-1][j-1]; - } - else - { - int value1 = (*levMatrix)[i - 1][j] + 1; - int value2 = (*levMatrix)[i][j-1] + 1; - int value3 = (*levMatrix)[i-1][j-1] + 1; - (*levMatrix)[i][j] = (value1 < value2) ? - ((value1 < value3) ? value1 : value3) : - (value2 < value3) ? value2 : value3; - } - } - } - tree->DataType = DATA_T_INTEGER; - tree->Integer = (*levMatrix)[length1][length2]; - nmSysFree(levMatrix); - return 0; - } - -/* See centrallix-sysdoc/string_comparison.md for more information. */ -int exp_fn_lev_compare(pExpression tree, pParamObjects objlist, pExpression i0, pExpression i1, pExpression i2) - { - - if (!i0 || !i1) - { - mssError(1,"EXP","lev_compare() requires two or three parameters"); - return -1; - } - - if ((i0->Flags & EXPR_F_NULL) || (i1->Flags & EXPR_F_NULL) || (i2 && (i2->Flags & EXPR_F_NULL))) - { - tree->DataType = DATA_T_DOUBLE; - tree->Flags |= EXPR_F_NULL; - return 0; - } - - if ((i0->DataType != DATA_T_STRING) || (i1->DataType != DATA_T_STRING) || (i2 && i2->DataType != DATA_T_INTEGER)) - { - mssError(1,"EXP","lev_compare() requires two string and one optional integer parameters"); - return -1; - } + /** Verify function schema. **/ + if (UNLIKELY(exp_fn_i_verify_schema((ArgExpect[]){ + {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS}, + EXP_ARG_END, + }, tree) != 0)) + { + mssError(0, "EXP", "%s(?): Call does not match function schema.", tree->Name); + goto end_free; + } - exp_fn_levenshtein(tree, objlist, i0, i1, i2); - //!!! I am not checking for errors here, because IN THEORY we have two strings... if we don't, big uh-oh. - int lev_dist = tree->Integer; + /** Allocate space to store metaphone pointers. **/ + char* primary = NULL; + char* secondary = NULL; - int length1 = strlen(i0->String); - int length2 = strlen(i1->String); - - double clamped_dist = 1.0; - - if (length1 == 0 || length2 == 0) //empty string - { - clamped_dist = 0.5; - } - else //normal case - { - int max_len = (length1 > length2) ? length1 : length2; - clamped_dist = ((double) lev_dist) / max_len; - - if (abs(length1-length2) == lev_dist) //only inserts. Maybe substring. - { - clamped_dist /= 2; - } - - //use max_field_width if it was provided as a sensible value. If not, don't use it. - double max_field_width = i2?(i2->Integer):0; - if (max_field_width && max_field_width >= max_len) { - double mod = (lev_dist + max_field_width * 3/4) / max_field_width; - if (mod < 1) { //don't make clamped_dist bigger - clamped_dist *= mod; - } - } - } + /** Extract string param. **/ + pExpression maybe_str = check_ptr(tree->Children.Items[0]); + if (UNLIKELY(maybe_str == NULL)) goto end_free; + if (maybe_str->Flags & EXPR_F_NULL) + { + tree->Flags |= EXPR_F_NULL; + tree->DataType = DATA_T_STRING; + ret = 0; + goto end_free; + } + const char* str = check_ptr(maybe_str->String); + if (UNLIKELY(str == NULL)) goto end_free; + const size_t str_len = strlen(str); + if (UNLIKELY(str_len == 0u)) + { + primary = ""; + secondary = ""; + free_strs = false; + goto store_data; + } + /** Compute Double Metaphone. **/ + ret = meta_double_metaphone(str, &primary, &secondary); + if (UNLIKELY(ret != 0)) + { + mssError(1, "EXP", "Double metaphone computation failed (error code %d).", ret); + goto end_free; + } - tree->DataType = DATA_T_DOUBLE; - tree->Types.Double = 1.0 - clamped_dist; - return 0; -} - -// This is the size of the vector table. It is also used in calculating the table indices. -const int EXP_VECTOR_TABLE_SIZE = 251; - -/* - * hash_char_pair - * This method creates an vector table index based a given character pair. The characters are represented - * as their ASCII code points. - * - * Parameters: - * num1 : first ASCII code point (double) - * num2 : second ASCII code point (double) - * - * Returns: - * vector table index (integer) - */ -int exp_fn_i_hash_char_pair(double num1, double num2) - { - int func_result = round(((num1 * num1 * num1) + (num2 * num2 * num2)) * ((num1+1)/(num2+1))) -1; - return func_result % EXP_VECTOR_TABLE_SIZE; + /** Store the results. **/ + store_data:; + const size_t length = strlen(primary) + 1lu + strlen(secondary) + 1lu; + if (check(exp_fn_i_alloc_result_string(tree, length)) != 0) goto end_free; + sprintf(tree->String, "%s%c%s", primary, CA_BOUNDARY_CHAR, secondary); + tree->DataType = DATA_T_STRING; + ret = 0; + + end_free: + if (LIKELY(free_strs && primary != NULL)) nmSysFree(primary); + if (LIKELY(free_strs && secondary != NULL)) nmSysFree(secondary); + if (UNLIKELY(ret != 0)) mssError(0, "EXP", "%s(): Failed to execute function.", tree->Name); + + return ret; } -/* - * exp_fn_i_frequency_table - * This method creates a vector frequency table based on a string of characters. - * - * Parameters: - * table : integer pointer to vector frequency table (unsigned short) - * term : the string of characters (char*) - * - * Returns: - * 0 - */ -int exp_fn_i_frequency_table(unsigned short *table, char *term) +/*** Computes cosine or Levenshtein similarity between two strings. These two + *** tasks have a large amount of overlapping logic (mostly error checking), + *** so doing them with one function greatly reduces code duplication. + *** + *** Assumes that the function which caused this code to run was either + *** "cos_compare" or "lev_compare" (this value is read from `tree->Name`). + *** + *** @param tree The tree resulting from this function. + *** @returns 0 for success, -1 for failure. + ***/ +static int +exp_fn_compare(pExpression tree) { - int i; - // Initialize hash table with 0 values - for (i = 0; i < EXP_VECTOR_TABLE_SIZE; i++) - { - table[i] = 0; - } - - int j = -1; - for(i = 0; i < strlen(term) + 1; i++) - { - // If latter character is punctuation or whitespace, skip it - if (ispunct(term[i]) || isspace(term[i])) + /** Verify function schema. **/ + if (UNLIKELY(exp_fn_i_verify_schema((ArgExpect[]){ + {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS}, + {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS}, + EXP_ARG_END, + }, tree) != 0)) { - continue; + mssError(0, "EXP", "%s(?): Call does not match function schema.", tree->Name); + goto err; } - - double temp1 = 0.0; - double temp2 = 0.0; - - // If previous character is null - if (j == -1) + + /** Extract strings. **/ + pExpression maybe_str1 = check_ptr(tree->Children.Items[0]); + pExpression maybe_str2 = check_ptr(tree->Children.Items[1]); + if (UNLIKELY(maybe_str1 == NULL || maybe_str2 == NULL)) goto err; + if (maybe_str1->Flags & EXPR_F_NULL || maybe_str2->Flags & EXPR_F_NULL) { - temp1 = 96; + tree->Flags |= EXPR_F_NULL; + tree->DataType = DATA_T_DOUBLE; + return 0; } - - // Else character is not null - else - { - temp1 = (int)tolower(term[j]); + char* str1 = check_ptr(maybe_str1->String); + char* str2 = check_ptr(maybe_str2->String); + if (UNLIKELY(str1 == NULL || str2 == NULL)) goto err; + + /** Handle either cos_compare() or lev_compare(). **/ + if (tree->Name[0] == 'c') + { /* cos_compare() */ + int ret; + + /** Build vectors. **/ + const pVector v1 = check_ptr(ca_build_vector(str1)); + const pVector v2 = check_ptr(ca_build_vector(str2)); + if (UNLIKELY(v1 == NULL || v2 == NULL)) + { + mssError(1, "EXP", + "%s(\"%s\", \"%s\"): Failed to build vectors.", + tree->Name, str1, str2 + ); + ret = -1; + } + else + { + /** Compute the similarity. **/ + tree->Types.Double = ca_cos_compare(v1, v2); + tree->DataType = DATA_T_DOUBLE; + ret = 0; + } + + /** Clean up. **/ + if (LIKELY(v1 != NULL)) ca_free_vector(v1); + if (LIKELY(v2 != NULL)) ca_free_vector(v2); + if (UNLIKELY(ret == -1)) goto err; + else return 0; } - - // If latter character is null - if (i == strlen(term)) - { - temp2 = 96; + else + { /* lev_compare() */ + const double lev_sim = check_double(ca_lev_compare(str1, str2)); + if (UNLIKELY(isnan(lev_sim))) + { + mssError(1, "EXP", + "%s(\"%s\", \"%s\"): Failed to compute Levenshtein edit distance.", + tree->Name, str1, str2 + ); + goto err; + } + + /** Return the computed result. **/ + tree->Types.Double = lev_sim; + tree->DataType = DATA_T_DOUBLE; + return 0; } + + err: + mssError(0, "EXP", "%s(): Failed to compute Levenshtein edit distance.", tree->Name); + return -1; + } - // Else character is not null - else +int +exp_fn_levenshtein(pExpression tree) + { + /** Verify function schema. **/ + if (UNLIKELY(exp_fn_i_verify_schema((ArgExpect[]){ + {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS}, + {(int[]){DATA_T_STRING, -1}, EXP_ARG_NO_FLAGS}, + EXP_ARG_END, + }, tree) != 0)) { - temp2 = (int)tolower(term[i]); + mssError(0, "EXP", "%s(?): Call does not match function schema.", tree->Name); + return -1; } - - // Else character is not null // If either character is a number, reassign the code point - if (temp1 >= 48 && temp1 <= 57) + + /** Extract strings. **/ + pExpression maybe_str1 = check_ptr(tree->Children.Items[0]); + pExpression maybe_str2 = check_ptr(tree->Children.Items[1]); + if (UNLIKELY(maybe_str1 == NULL || maybe_str2 == NULL)) return -1; + if (maybe_str1->Flags & EXPR_F_NULL || maybe_str2->Flags & EXPR_F_NULL) { - temp1 += 75; + tree->Flags |= EXPR_F_NULL; + tree->DataType = DATA_T_INTEGER; + return 0; } - - if (temp2 >= 48 && temp2 <= 57) + char* str1 = check_ptr(maybe_str1->String); + char* str2 = check_ptr(maybe_str2->String); + if (UNLIKELY(str1 == NULL || str2 == NULL)) return -1; + + /** Compute edit distance. **/ + /** Length 0 is provided for both strings so that the function will compute it for us. **/ + const int edit_dist = check_neg(ca_edit_dist(str1, str2, 0lu, 0lu)); + if (UNLIKELY(edit_dist < 0)) { - temp2 += 75; + mssError(1, "EXP", "%s(\"%s\", \"%s\"): Failed to compute edit distance.\n", tree->Name, str1, str2); + return -1; } - - // Hash the character pair into an index - int index = exp_fn_i_hash_char_pair(temp1, temp2); - - // Increment Frequency Table value by number from 0 to 13 - table[index] += ((unsigned short)temp1 + (unsigned short)temp2) % 13 + 1; - - // Move j up to latter character before incrementing i - j = i; - - } - - return 0; - - } - -/* - * exp_fn_i_dot_product - * This method calculautes the dot product of two vectors. - * - * Parameters: - * dot_product : the place where the result is stored (double) - * r_freq_table1 : the first vector (unsigned short) - * r_freq_table2 : the second vector (unsigned short) - * - * Returns: - * 0 - */ -int exp_fn_i_dot_product(double *dot_product, unsigned short *r_freq_table1, unsigned short *r_freq_table2) - { - int i; - for (i = 0; i < EXP_VECTOR_TABLE_SIZE; i++) - { - *dot_product = *dot_product + ((double)r_freq_table1[i] * (double)r_freq_table2[i]); - } - return 0; - } - -/* - * exp_fn_i_magnitude - * This method calculates the magnitude of a vector - * - * Parameters: - * magnitude : the place where the result is stored (double) - * r_freq_table : the vector (unsigned short) - */ -int exp_fn_i_magnitude(double *magnitude, unsigned short *r_freq_table) - { - int i; - for (i = 0; i < EXP_VECTOR_TABLE_SIZE; i++) - { - *magnitude = *magnitude + ((double)r_freq_table[i] * (double)r_freq_table[i]); - } - *magnitude = sqrt(*magnitude); - return 0; - } - -/* - * exp_fn_cos_compare - * This method calculates the cosine similarity of two vector frequency tables - * See centrallix-sysdoc/string_comparison.md for more information. - * - * Parameters: - * tree : structure where output is stored - * objlist: - * i0 : first data entry (pExpression) - * i1 : second data entry (pExpression) - * i2 : - * - * Returns: - * 0 - */ -int exp_fn_cos_compare(pExpression tree, pParamObjects objlist, pExpression i0, pExpression i1, pExpression i2) - { - // Ensure function receives two non-null parameters - if (!i0 || !i1) - { - mssError(1,"EXP","cos_compare() requires two parameter."); - return -1; - } - - // Ensure value passed in both parameters is not null - if ((i0->Flags & EXPR_F_NULL) || (i1->Flags & EXPR_F_NULL)) - { - tree->DataType = DATA_T_DOUBLE; - tree->Flags |= EXPR_F_NULL; - return 0; - } - - // Ensure both parameters contain string values - if ((i0->DataType != DATA_T_STRING) || (i1->DataType != DATA_T_STRING)) - { - mssError(1,"EXP","cos_compare() requires two string parameters."); - return -1; - } - - //If the two strings are identical, don't bother running cosine compare - if (strcmp(i0->String, i1->String) == 0) - { - tree->DataType = DATA_T_DOUBLE; - tree->Types.Double = 1.0; - return 0; - } - - // Allocate frequency tables (arrays of integers) for each term - unsigned short *table1 = nmMalloc(EXP_VECTOR_TABLE_SIZE * sizeof(unsigned short)); - unsigned short *table2 = nmMalloc(EXP_VECTOR_TABLE_SIZE * sizeof(unsigned short)); - - if (table1 == NULL || table2 == NULL) - { - mssError(1,"EXP","Memory allocation failed."); - return -1; - } - - // Calculate frequency tables for each term - exp_fn_i_frequency_table(table1, i0->String); - exp_fn_i_frequency_table(table2, i1->String); - // Calculate dot product - double dot_product = 0; - exp_fn_i_dot_product(&dot_product, table1, table2); - - // Calculate magnitudes of each relative frequency vector - double magnitude1 = 0; - double magnitude2 = 0; - exp_fn_i_magnitude(&magnitude1, table1); - exp_fn_i_magnitude(&magnitude2, table2); + /** Return the computed distance. **/ + tree->Integer = edit_dist; + tree->DataType = DATA_T_INTEGER; - tree->DataType = DATA_T_DOUBLE; - tree->Types.Double = dot_product / (magnitude1 * magnitude2); - nmFree(table1, EXP_VECTOR_TABLE_SIZE * sizeof(unsigned short)); - nmFree(table2, EXP_VECTOR_TABLE_SIZE * sizeof(unsigned short)); - return 0; } @@ -4351,7 +4743,7 @@ int exp_fn_cos_compare(pExpression tree, pParamObjects objlist, pExpression i0, * Parameters: * pExpression tree: * pParamObjects: - * pExpression passowrd: The password, passed as a pExpression + * pExpression password: The password, passed as a pExpression * pExpression salt: The salt, passed as a pExpression * * returns: @@ -4466,7 +4858,9 @@ int exp_fn_argon2id(pExpression tree, pParamObjects objlist, pExpression passwor int exp_internal_DefineFunctions() { - + /** Initialize clustering library. **/ + ca_init(); + /** Function list for EXPR_N_FUNCTION nodes **/ xhAdd(&EXP.Functions, "getdate", (char*)exp_fn_getdate); xhAdd(&EXP.Functions, "user_name", (char*)exp_fn_user_name); @@ -4485,6 +4879,7 @@ int exp_internal_DefineFunctions() xhAdd(&EXP.Functions, "ltrim", (char*)exp_fn_ltrim); xhAdd(&EXP.Functions, "lztrim", (char*)exp_fn_lztrim); xhAdd(&EXP.Functions, "rtrim", (char*)exp_fn_rtrim); + xhAdd(&EXP.Functions, "trim", (char*)exp_fn_trim); xhAdd(&EXP.Functions, "substring", (char*)exp_fn_substring); xhAdd(&EXP.Functions, "right", (char*)exp_fn_right); xhAdd(&EXP.Functions, "ralign", (char*)exp_fn_ralign); @@ -4519,23 +4914,26 @@ int exp_internal_DefineFunctions() xhAdd(&EXP.Functions, "hash", (char*)exp_fn_hash); xhAdd(&EXP.Functions, "hmac", (char*)exp_fn_hmac); xhAdd(&EXP.Functions, "log10", (char*)exp_fn_log10); + xhAdd(&EXP.Functions, "ln", (char*)exp_fn_ln); + xhAdd(&EXP.Functions, "log", (char*)exp_fn_log); xhAdd(&EXP.Functions, "power", (char*)exp_fn_power); xhAdd(&EXP.Functions, "pbkdf2", (char*)exp_fn_pbkdf2); + xhAdd(&EXP.Functions, "metaphone", (char*)exp_fn_metaphone); xhAdd(&EXP.Functions, "levenshtein", (char*)exp_fn_levenshtein); - xhAdd(&EXP.Functions, "lev_compare", (char*)exp_fn_lev_compare); - xhAdd(&EXP.Functions, "cos_compare", (char*)exp_fn_cos_compare); + xhAdd(&EXP.Functions, "lev_compare", (char*)exp_fn_compare); + xhAdd(&EXP.Functions, "cos_compare", (char*)exp_fn_compare); xhAdd(&EXP.Functions, "to_base64", (char*)exp_fn_to_base64); xhAdd(&EXP.Functions, "from_base64", (char*)exp_fn_from_base64); xhAdd(&EXP.Functions, "to_hex", (char*)exp_fn_to_hex); xhAdd(&EXP.Functions, "from_hex", (char*)exp_fn_from_hex); xhAdd(&EXP.Functions, "octet_length", (char*)exp_fn_octet_length); xhAdd(&EXP.Functions, "argon2id",(char*)exp_fn_argon2id); - + /** Windowing **/ xhAdd(&EXP.Functions, "row_number", (char*)exp_fn_row_number); xhAdd(&EXP.Functions, "dense_rank", (char*)exp_fn_dense_rank); xhAdd(&EXP.Functions, "lag", (char*)exp_fn_lag); - + /** Aggregate **/ xhAdd(&EXP.Functions, "count", (char*)exp_fn_count); xhAdd(&EXP.Functions, "avg", (char*)exp_fn_avg); @@ -4545,9 +4943,9 @@ int exp_internal_DefineFunctions() xhAdd(&EXP.Functions, "first", (char*)exp_fn_first); xhAdd(&EXP.Functions, "last", (char*)exp_fn_last); xhAdd(&EXP.Functions, "nth", (char*)exp_fn_nth); - + /** Reverse functions **/ xhAdd(&EXP.ReverseFunctions, "isnull", (char*)exp_fn_reverse_isnull); - + return 0; } diff --git a/centrallix/htmlgen/ht_render.c b/centrallix/htmlgen/ht_render.c index 285a1c0da..ebdeae0b2 100644 --- a/centrallix/htmlgen/ht_render.c +++ b/centrallix/htmlgen/ht_render.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -12,10 +13,12 @@ #endif #include "ht_render.h" #include "obj.h" +#include "cxlib/expect.h" #include "cxlib/mtask.h" #include "cxlib/xarray.h" #include "cxlib/xhash.h" #include "cxlib/mtsession.h" +#include "cxlib/util.h" #include "centrallix.h" #include "expression.h" #include "cxlib/qprintf.h" @@ -25,7 +28,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -197,9 +200,9 @@ htr_internal_ProcessUserAgent(const pStructInf node, const pHtCapabilities paren void htr_internal_writeCxCapabilities(pHtSession s) { - htrWrite(s," cx__capabilities = {};\n",27); + htrWrite(s,"\tcx__capabilities = {};\n",24); #define PROCESS_CAP_OUT(attr) \ - htrWrite(s," cx__capabilities.",21); \ + htrWrite(s,"\tcx__capabilities.",18); \ htrWrite(s, # attr ,strlen( # attr )); \ htrWrite(s," = ",3); \ htrWrite(s,(s->Capabilities.attr?"1;\n":"0;\n"),3); @@ -366,15 +369,20 @@ htr_internal_AddTextToArray(pXArray arr, char* txt) { int l,n,cnt; char* ptr; + const size_t block_size = 2048lu; /** Need new block? **/ if (arr->nItems == 0) { - ptr = (char*)nmMalloc(2048); - if (!ptr) return -1; + ptr = (char*)check_ptr(nmMalloc(block_size)); + if (ptr == NULL) goto err; *(int*)ptr = 0; l = 0; - xaAddItem(arr,ptr); + if (check_neg(xaAddItem(arr, ptr)) < 0) + { + nmFree(ptr, block_size); + goto err; + } } else { @@ -395,15 +403,26 @@ htr_internal_AddTextToArray(pXArray arr, char* txt) *(int*)ptr = l; if (n) { - ptr = (char*)nmMalloc(2048); - if (!ptr) return -1; + ptr = (char*)check_ptr(nmMalloc(block_size)); + if (ptr == NULL) goto err; *(int*)ptr = 0; l = 0; - xaAddItem(arr,ptr); + if (check_neg(xaAddItem(arr, ptr)) < 0) + { + nmFree(ptr, block_size); + goto err; + } } } - return 0; + return 0; + + err: + mssError(1, "HTR", + "Failed to add text to array with %d items: \"%s\"", + xaCount(arr), txt + ); + return -1; } @@ -415,98 +434,140 @@ htrRenderWidget(pHtSession session, pWgtrNode widget, int z) { pHtDriver drv; pXHashTable widget_drivers = NULL; - int rval; + int rval = -1; + + /** Guard segfault. **/ + if (UNLIKELY(widget == NULL)) + { + mssError(1, "HTR", "Failed to render NULL widget."); + return -1; + } /** Check recursion **/ - if (thExcessiveRecursion()) + if (UNLIKELY(thExcessiveRecursion())) { mssError(1,"HTR","Could not render application: resource exhaustion occurred"); - return -1; + goto end; } /** Find the hashtable keyed with widget names for this combination of ** user-agent:style that contains pointers to the drivers to use. **/ - if(!session->Class) - { - printf("Class not defined %s:%i\n",__FILE__,__LINE__); - return -1; - } - widget_drivers = &( session->Class->WidgetDrivers); - if (!widget_drivers) + if (UNLIKELY(session->Class == NULL)) { - htrAddBodyItem_va(session, "No widgets have been defined for your browser type and requested class combination."); - mssError(1, "HTR", "Invalid UserAgent:class combination"); - return -1; + mssError(1, "HTR", "Class not defined\n"); + goto end; } + widget_drivers = &session->Class->WidgetDrivers; /** Get the name of the widget.. **/ - if (strncmp(widget->Type,"widget/",7)) + if (UNLIKELY(strncmp(widget->Type, "widget/", 7) != 0)) { mssError(1,"HTR","Invalid content type for widget - must be widget/xxx"); - return -1; + goto end; } /** Lookup the driver. These are what are defined in the various htdrv_* functions **/ drv = (pHtDriver)xhLookup(widget_drivers,widget->Type+7); - if (!drv) + if (UNLIKELY(drv == NULL)) { mssError(1,"HTR","Unknown widget object type '%s'", widget->Type); - return -1; + goto end; } /** Has this driver been used this session yet? **/ if (xhLookup(&session->UsedDrivers, drv->WidgetName) == NULL) { - xhAdd(&session->UsedDrivers, drv->WidgetName, (void*)drv); - if (drv->Setup) - { - if (drv->Setup(session) < 0) - return -1; - } + if (check_neg(xhAdd(&session->UsedDrivers, drv->WidgetName, (void*)drv)) != 0) goto end; + if (drv->Setup != NULL && check_neg(drv->Setup(session)) < 0) goto end; } /** Crossing a namespace boundary? **/ - htrCheckNSTransition(session, widget->Parent, widget); + if (UNLIKELY(htrCheckNSTransition(session, widget->Parent, widget) != 0)) goto end; /** will be a *Render function found in a htmlgen/htdrv_*.c file (eg htpageRender) **/ rval = drv->Render(session, widget, z); + if (UNLIKELY(rval != 0)) + { + char error_code[32] = {'\0'}; + if (rval != -1) snprintf(error_code, sizeof(error_code), " (error code: %d)", rval); + mssError(0, "HTR", "\"%s\" render failed%s.", drv->Name, error_code); + } - /** Going back to previous namespace? **/ - htrCheckNSTransitionReturn(session, widget->Parent, widget); + /** Going back to previous namespace? (Even if rendering failed.) **/ + if (UNLIKELY(htrCheckNSTransitionReturn(session, widget->Parent, widget) != 0)) + { + rval = -1; + goto end; + } - return rval; + end: + if (UNLIKELY(rval != 0)) + { + mssError(0, "HTR", + "Failed to render \"%s\":\"%s\".", + widget->Name, widget->Type + ); + } + + return rval; } -/*** htrCheckNSTransition -- check to see if we are transitioning between - *** namespaces, and if so, emit the code to switch the context. +/*** Check to see if we are transitioning when we move from the parent's + *** namespace to the child's namespace. If so, write code to switch the + *** namespace context. + *** + *** @param s The current session. + *** @param parent The parent we are moving away from. + *** @param parent The child of the parent we are moving to. + *** @returns 0 if successful, and -1 if an error occurs. ***/ int htrCheckNSTransition(pHtSession s, pWgtrNode parent, pWgtrNode child) { - /** Crossing a namespace boundary? **/ - if (child && parent && strcmp(child->Namespace, parent->Namespace) != 0) + if (child != NULL && parent != NULL && + strcmp(child->Namespace, parent->Namespace) != 0) { - htrAddNamespace(s, NULL, child->Namespace, 1); + if (UNLIKELY(htrAddNamespace(s, NULL, child->Namespace, 1) != 0)) + { + mssError(0, "HTR", + "Transition failed: \"%s\":\"%s\" (parent) --> \"%s\":\"%s\" (child).", + parent->Name, parent->Type, child->Name, child->Type + ); + return -1; + } } return 0; } -/*** htrCheckNSTransitionReturn -- check to see if we are transitioning between - *** namespaces, and if so, emit the code to switch the context. +/*** Check to see if we are transitioning when we move from the child's + *** namespace back up to the parent's namespace. If so, write code to + *** switch the namespace context. + *** + *** @param s The current session. + *** @param parent The parent we are returning to. + *** @param parent The child we are moving away from. + *** @returns 0 if successful, and -1 if an error occurs. ***/ int htrCheckNSTransitionReturn(pHtSession s, pWgtrNode parent, pWgtrNode child) { - /** Crossing a namespace boundary? **/ - if (child && parent && strcmp(child->Namespace, parent->Namespace) != 0) + if (child != NULL && parent != NULL && + strcmp(child->Namespace, parent->Namespace) != 0) { - htrLeaveNamespace(s); + if (UNLIKELY(htrLeaveNamespace(s) != 0)) + { + mssError(0, "HTR", + "Transition return failed: \"%s\":\"%s\" (parent) <-- \"%s\":\"%s\" (child).", + parent->Name, parent->Type, child->Name, child->Type + ); + return -1; + } } return 0; @@ -519,7 +580,13 @@ htrCheckNSTransitionReturn(pHtSession s, pWgtrNode parent, pWgtrNode child) int htrAddStylesheetItem(pHtSession s, char* html_text) { - return htr_internal_AddTextToArray(&(s->Page.HtmlStylesheet), html_text); + if (UNLIKELY(htr_internal_AddTextToArray(&(s->Page.HtmlStylesheet), html_text) != 0)) + { + mssError(0, "HTR", "Failed to add style sheet item."); + return -1; + } + + return 0; } @@ -529,7 +596,13 @@ htrAddStylesheetItem(pHtSession s, char* html_text) int htrAddHeaderItem(pHtSession s, char* html_text) { - return htr_internal_AddTextToArray(&(s->Page.HtmlHeader), html_text); + if (UNLIKELY(htr_internal_AddTextToArray(&(s->Page.HtmlHeader), html_text) != 0)) + { + mssError(0, "HTR", "Failed to add header item."); + return -1; + } + + return 0; } @@ -539,7 +612,13 @@ htrAddHeaderItem(pHtSession s, char* html_text) int htrAddBodyItem(pHtSession s, char* html_text) { - return htr_internal_AddTextToArray(&(s->Page.HtmlBody), html_text); + if (UNLIKELY(htr_internal_AddTextToArray(&(s->Page.HtmlBody), html_text) != 0)) + { + mssError(0, "HTR", "Failed to add body item."); + return -1; + } + + return 0; } @@ -549,7 +628,13 @@ htrAddBodyItem(pHtSession s, char* html_text) int htrAddExpressionItem(pHtSession s, char* html_text) { - return htr_internal_AddTextToArray(&(s->Page.HtmlExpressionInit), html_text); + if (UNLIKELY(htr_internal_AddTextToArray(&(s->Page.HtmlExpressionInit), html_text) != 0)) + { + mssError(0, "HTR", "Failed to add expression item."); + return -1; + } + + return 0; } @@ -560,7 +645,13 @@ htrAddExpressionItem(pHtSession s, char* html_text) int htrAddBodyParam(pHtSession s, char* html_param) { - return htr_internal_AddTextToArray(&(s->Page.HtmlBodyParams), html_param); + if (UNLIKELY(htr_internal_AddTextToArray(&(s->Page.HtmlBodyParams), html_param) != 0)) + { + mssError(0, "HTR", "Failed to add body parameter."); + return -1; + } + + return 0; } @@ -577,19 +668,18 @@ htr_internal_GrowFn(char** str, size_t* size, size_t offs, void* arg, size_t req char* new_buf; size_t new_buf_size; - if (*size >= req_size) return 1; + if (UNLIKELY(*size >= req_size)) return 1; assert(*str == s->Tmpbuf); assert(*size == s->TmpbufSize); new_buf_size = s->TmpbufSize * 2; while(new_buf_size < req_size) new_buf_size *= 2; - new_buf = nmSysRealloc(s->Tmpbuf, new_buf_size); - if (!new_buf) - return 0; + new_buf = check_ptr(nmSysRealloc(s->Tmpbuf, new_buf_size)); + if (new_buf == NULL) return false; /* Grow failed. */ *str = s->Tmpbuf = new_buf; *size = s->TmpbufSize = new_buf_size; - return 1; /* OK */ + return true; /* Success. */ } @@ -599,7 +689,8 @@ htr_internal_GrowFn(char** str, size_t* size, size_t offs, void* arg, size_t req int htr_internal_QPAddText(pHtSession s, int (*fn)(), char* fmt, va_list va) { - int rval; + int rval = -1, tmp; + pQPSession error_session = NULL; /** Print a warning if we think the format string isn't a constant. ** We'll need to upgrade this once htdrivers start being loaded as @@ -613,18 +704,43 @@ htr_internal_QPAddText(pHtSession s, int (*fn)(), char* fmt, va_list va) } #endif - rval = qpfPrintf_va_internal(NULL, &(s->Tmpbuf), &(s->TmpbufSize), htr_internal_GrowFn, (void*)s, fmt, va); - if (rval < 0) + /** Create a pQPSession to track errors from qpfPrintf. **/ + error_session = check_ptr(qpfOpenSession()); + if (error_session == NULL) goto end; + + /** Print text using qpfPrintf(). **/ + tmp = qpfPrintf_va_internal(error_session, &(s->Tmpbuf), &(s->TmpbufSize), htr_internal_GrowFn, (void*)s, fmt, va); + if (UNLIKELY(tmp < 0)) { - printf("WARNING: QPAddText() failed for format: %s\n", fmt); + mssError(1, "HTR", "QPAddText() failed to format: \"%s\"", fmt); + qpfLogErrors(error_session); + goto end; + } + if (UNLIKELY(s->TmpbufSize - 1 < tmp)) + { + mssError(1, "HTR", "qpfPrintf() failed to write all the required text."); + qpfLogErrors(error_session); + goto end; } - if (rval < 0 || rval > (s->TmpbufSize - 1)) - return -1; - /** Ok, now add the tmpbuf normally. **/ - fn(s, s->Tmpbuf); + /** Add the text using the callback function. **/ + tmp = fn(s, s->Tmpbuf); + if (UNLIKELY(tmp != 0)) + { + char error_code[32] = {'\0'}; + if (UNLIKELY(tmp != -1)) snprintf(error_code, sizeof(error_code), " (error code: %d)", tmp); + mssError(0, "HTR", "Provided callback function failed%s.", error_code); + goto end; + } + + /** Success. **/ + rval = 0; + + end: + /** Clean up. **/ + if (LIKELY(error_session != NULL)) check(qpfCloseSession(error_session)); /* Failure ignored. */ - return 0; + return rval; } @@ -634,10 +750,7 @@ htr_internal_QPAddText(pHtSession s, int (*fn)(), char* fmt, va_list va) int htr_internal_AddText(pHtSession s, int (*fn)(), char* fmt, va_list va) { - va_list orig_va; - int rval; - char* new_buf; - int new_buf_size; + int tmp; /** Print a warning if we think the format string isn't a constant. ** We'll need to upgrade this once htdrivers start being loaded as @@ -651,40 +764,55 @@ htr_internal_AddText(pHtSession s, int (*fn)(), char* fmt, va_list va) } #endif - /** Save the current va_list state so we can retry it. **/ + /** Save a copy of the va_list so we can retry. **/ + va_list orig_va; va_copy(orig_va, va); + retry: /** Attempt to print the thing to the tmpbuf. **/ - while(1) - { - rval = vsnprintf(s->Tmpbuf, s->TmpbufSize, fmt, va); - - /** Sigh. Some libc's return -1 and some return # bytes that would be written. **/ - if (rval < 0 || rval > (s->TmpbufSize - 1)) + tmp = check_neg(vsnprintf(s->Tmpbuf, s->TmpbufSize, fmt, va)); + if (tmp < 0) goto err; + if (UNLIKELY(tmp > s->TmpbufSize - 1)) + { + /** Increase buffer size. **/ + size_t new_buf_size = s->TmpbufSize * 2lu; + while (new_buf_size < tmp) new_buf_size *= 2lu; + + /** Allocate the new buffer. **/ + void* new_buf = check_ptr(nmSysMalloc(new_buf_size)); + if (new_buf == NULL) { - /** I think I need a bigger box. Fix it and try again. **/ - new_buf_size = s->TmpbufSize * 2; - while(new_buf_size < rval) new_buf_size *= 2; - new_buf = nmSysMalloc(new_buf_size); - if (!new_buf) - { - return -1; - } - nmSysFree(s->Tmpbuf); - s->Tmpbuf = new_buf; - s->TmpbufSize = new_buf_size; - va = orig_va; - } - else - { - break; + /** Start a new error chain. **/ + mssClearError(); + goto err; } + nmSysFree(s->Tmpbuf); /* Clean up. */ + + /** Set new buffer. **/ + s->TmpbufSize = new_buf_size; + s->Tmpbuf = new_buf; + + /** Try the write again. **/ + va = orig_va; + goto retry; + } + + /** Add the text using the callback function. **/ + tmp = fn(s, s->Tmpbuf); + if (UNLIKELY(tmp != 0)) + { + char error_code[32] = {'\0'}; + if (UNLIKELY(tmp != -1)) snprintf(error_code, sizeof(error_code), " (error code: %d)", tmp); + mssError(0, "HTR", "Provided callback function failed%s.", error_code); + goto err; } - - /** Ok, now add the tmpbuf normally. **/ - fn(s, s->Tmpbuf); - - return 0; + + /** Success. **/ + return 0; + + err: + mssError(0, "HTR", "Failed to add text using format: \"%s\"", fmt); + return -1; } @@ -697,10 +825,11 @@ htrAddBodyItem_va(pHtSession s, char* fmt, ... ) va_list va; va_start(va, fmt); - htr_internal_QPAddText(s, htrAddBodyItem, fmt, va); + const int ret = htr_internal_QPAddText(s, htrAddBodyItem, fmt, va); va_end(va); + if (UNLIKELY(ret != 0)) mssError(0, "HTR", "Failed to add HTML body item."); - return 0; + return ret; } @@ -713,10 +842,11 @@ htrAddExpressionItem_va(pHtSession s, char* fmt, ... ) va_list va; va_start(va, fmt); - htr_internal_QPAddText(s, htrAddExpressionItem, fmt, va); + const int ret = htr_internal_QPAddText(s, htrAddExpressionItem, fmt, va); va_end(va); + if (UNLIKELY(ret != 0)) mssError(0, "HTR", "Failed to add expression item."); - return 0; + return ret; } @@ -729,10 +859,11 @@ htrAddStylesheetItem_va(pHtSession s, char* fmt, ... ) va_list va; va_start(va, fmt); - htr_internal_QPAddText(s, htrAddStylesheetItem, fmt, va); + const int ret = htr_internal_QPAddText(s, htrAddStylesheetItem, fmt, va); va_end(va); + if (UNLIKELY(ret != 0)) mssError(0, "HTR", "Failed to add CSS stylesheet item."); - return 0; + return ret; } @@ -745,10 +876,11 @@ htrAddHeaderItem_va(pHtSession s, char* fmt, ... ) va_list va; va_start(va, fmt); - htr_internal_QPAddText(s, htrAddHeaderItem, fmt, va); + const int ret = htr_internal_QPAddText(s, htrAddHeaderItem, fmt, va); va_end(va); + if (UNLIKELY(ret != 0)) mssError(0, "HTR", "Failed to add HTML document header item."); - return 0; + return ret; } @@ -761,10 +893,11 @@ htrAddBodyParam_va(pHtSession s, char* fmt, ... ) va_list va; va_start(va, fmt); - htr_internal_QPAddText(s, htrAddBodyParam, fmt, va); + const int ret = htr_internal_QPAddText(s, htrAddBodyParam, fmt, va); va_end(va); + if (UNLIKELY(ret != 0)) mssError(0, "HTR", "Failed to add body parameter."); - return 0; + return ret; } @@ -777,10 +910,11 @@ htrAddScriptWgtr_va(pHtSession s, char* fmt, ...) va_list va; va_start(va, fmt); - htr_internal_QPAddText(s, htrAddScriptWgtr, fmt, va); + const int ret = htr_internal_QPAddText(s, htrAddScriptWgtr, fmt, va); va_end(va); + if (UNLIKELY(ret != 0)) mssError(0, "HTR", "Failed to add JS code."); - return 0; + return ret; } @@ -793,6 +927,10 @@ htrAddScriptInit_va(pHtSession s, char* fmt, ... ) { va_list va; + /*** TODO: Greg - Can we remove this code? It seems like this is the + *** concern of htrRender(), and if it's not, should we remove the + *** line from htrRender() that also does the same thing? + ***/ if (!s->Namespace->HasScriptInits) { /** No script inits for this namespace yet? Issue the context @@ -800,14 +938,15 @@ htrAddScriptInit_va(pHtSession s, char* fmt, ... ) **/ s->Namespace->HasScriptInits = 1; /*htrAddScriptInit_va(s, "\n nodes = wgtrNodeList(%STR&SYM);\n", s->Namespace->DName);*/ - htrAddScriptInit_va(s, "\n ns = \"%STR&SYM\";\n", s->Namespace->DName); + htrAddScriptInit_va(s, "\tns = \"%STR&SYM\";\n", s->Namespace->DName); } va_start(va, fmt); - htr_internal_QPAddText(s, htrAddScriptInit, fmt, va); + const int ret = htr_internal_QPAddText(s, htrAddScriptInit, fmt, va); va_end(va); + if (UNLIKELY(ret != 0)) mssError(0, "HTR", "Failed to add JS init call."); - return 0; + return ret; } @@ -820,10 +959,11 @@ htrAddScriptCleanup_va(pHtSession s, char* fmt, ... ) va_list va; va_start(va, fmt); - htr_internal_QPAddText(s, htrAddScriptCleanup, fmt, va); + const int ret = htr_internal_QPAddText(s, htrAddScriptCleanup, fmt, va); va_end(va); + if (UNLIKELY(ret != 0)) mssError(0, "HTR", "Failed to add JS clean up code."); - return 0; + return ret; } @@ -835,30 +975,33 @@ int htrAddScriptInclude(pHtSession s, char* filename, int flags) { pStrValue sv; - char* scripts_already; - /** What scripts has the client already loaded? If the - ** requested script is in the list, ignore it since the - ** client already has it. - **/ - scripts_already = htrParamValue(s, "cx__scripts"); - if (scripts_already && strstr(scripts_already, filename)) + /** Ignore this call if the script is already included. **/ + char* scripts_loaded = htrParamValue(s, "cx__scripts"); + if (scripts_loaded != NULL && strstr(scripts_loaded, filename)) return 0; + + /** Cache check. **/ + if (xhLookup(&(s->Page.NameIncludes), filename) != NULL) return 0; /** Alloc the string val. **/ - if (xhLookup(&(s->Page.NameIncludes), filename)) return 0; - sv = (pStrValue)nmMalloc(sizeof(StrValue)); - if (!sv) return -1; + sv = (pStrValue)check_ptr(nmMalloc(sizeof(StrValue))); + if (sv == NULL) goto err; sv->Name = filename; if (flags & HTR_F_NAMEALLOC) sv->NameSize = strlen(filename)+1; sv->Value = ""; sv->Alloc = (flags & HTR_F_NAMEALLOC); /** Add to the hash table and array **/ - xhAdd(&(s->Page.NameIncludes), filename, (char*)sv); - xaAddItem(&(s->Page.Includes), (char*)sv); + if (check(xhAdd(&(s->Page.NameIncludes), filename, (char*)sv)) != 0) goto err; + if (check_neg(xaAddItem(&(s->Page.Includes), (char*)sv)) < 0) goto err; - return 0; + /** Success. **/ + return 0; + + err: + mssError(1, "HTR", "Failed to include JS file: \"%s\"", filename); + return -1; } @@ -873,8 +1016,8 @@ htrAddScriptFunction(pHtSession s, char* fn_name, char* fn_text, int flags) /** Alloc the string val. **/ if (xhLookup(&(s->Page.NameFunctions), fn_name)) return 0; - sv = (pStrValue)nmMalloc(sizeof(StrValue)); - if (!sv) return -1; + sv = (pStrValue)check_ptr(nmMalloc(sizeof(StrValue))); + if (sv == NULL) goto err; sv->Name = fn_name; if (flags & HTR_F_NAMEALLOC) sv->NameSize = strlen(fn_name)+1; sv->Value = fn_text; @@ -882,10 +1025,15 @@ htrAddScriptFunction(pHtSession s, char* fn_name, char* fn_text, int flags) sv->Alloc = flags; /** Add to the hash table and array **/ - xhAdd(&(s->Page.NameFunctions), fn_name, (char*)sv); - xaAddItem(&(s->Page.Functions), (char*)sv); + if (check(xhAdd(&(s->Page.NameFunctions), fn_name, (char*)sv)) != 0) goto err; + if (check_neg(xaAddItem(&(s->Page.Functions), (char*)sv)) < 0) goto err; - return 0; + /** Success. **/ + return 0; + + err: + mssError(1, "HTR", "Failed to add JS function: %s()", fn_name); + return -1; } @@ -897,10 +1045,12 @@ htrAddScriptGlobal(pHtSession s, char* var_name, char* initialization, int flags { pStrValue sv; - /** Alloc the string val. **/ - if (xhLookup(&(s->Page.NameGlobals), var_name)) return 0; - sv = (pStrValue)nmMalloc(sizeof(StrValue)); - if (!sv) return -1; + /** Cache check. **/ + if (xhLookup(&(s->Page.NameGlobals), var_name) != NULL) return 0; + + /** Alloc a new string val. **/ + sv = (pStrValue)check_ptr(nmMalloc(sizeof(StrValue))); + if (sv == NULL) goto err; sv->Name = var_name; sv->NameSize = strlen(var_name)+1; sv->Value = initialization; @@ -908,10 +1058,20 @@ htrAddScriptGlobal(pHtSession s, char* var_name, char* initialization, int flags sv->Alloc = flags; /** Add to the hash table and array **/ - xhAdd(&(s->Page.NameGlobals), var_name, (char*)sv); - xaAddItem(&(s->Page.Globals), (char*)sv); + if (check(xhAdd(&(s->Page.NameGlobals), var_name, (char*)sv)) != 0) goto err; + if (check_neg(xaAddItem(&(s->Page.Globals), (char*)sv)) < 0) goto err; - return 0; + /** Success. **/ + return 0; + + err:; + char flags_buf[20] = {'\0'}; + if (flags != 0) snprintf(flags_buf, sizeof(flags_buf), " (flags %d)", flags); + mssError(1, "HTR", + "Failed to add global variable %s = %s%s.", + var_name, initialization, flags_buf + ); + return -1; } @@ -921,7 +1081,13 @@ htrAddScriptGlobal(pHtSession s, char* var_name, char* initialization, int flags int htrAddScriptInit(pHtSession s, char* init_text) { - return htr_internal_AddTextToArray(&(s->Page.Inits), init_text); + if (UNLIKELY(htr_internal_AddTextToArray(&(s->Page.Inits), init_text) != 0)) + { + mssError(0, "HTR", "Failed to add script init."); + return -1; + } + + return 0; } @@ -930,7 +1096,13 @@ htrAddScriptInit(pHtSession s, char* init_text) int htrAddScriptWgtr(pHtSession s, char* wgtr_text) { - return htr_internal_AddTextToArray(&(s->Page.Wgtr), wgtr_text); + if (UNLIKELY(htr_internal_AddTextToArray(&(s->Page.Wgtr), wgtr_text) != 0)) + { + mssError(0, "HTR", "Failed to add to script in widget tree."); + return -1; + } + + return 0; } @@ -940,7 +1112,13 @@ htrAddScriptWgtr(pHtSession s, char* wgtr_text) int htrAddScriptCleanup(pHtSession s, char* init_text) { - return htr_internal_AddTextToArray(&(s->Page.Cleanups), init_text); + if (UNLIKELY(htr_internal_AddTextToArray(&(s->Page.Cleanups), init_text) != 0)) + { + mssError(0, "HTR", "Failed to add script clean up."); + return -1; + } + + return 0; } @@ -951,46 +1129,57 @@ htrAddScriptCleanup(pHtSession s, char* init_text) *** -- drvname is no longer used, just ignored. ***/ int -htrAddEventHandlerFunction(pHtSession s, char* event_src, char* event, char* drvname, char* function) +htrAddEventHandlerFunction(pHtSession s, char* event_src, char* event, char* drv_name, char* function) { pHtDomEvent e = NULL; - pHtDomEvent e_srch; - int i,cnt; /** Look it up? ** (GRB note -- is this slow enough to need an XHashTable?) + ** (Israel note -- idk, but it hasn't been a problem over the + ** past 19 years, so it's probably fine.) **/ - cnt = xaCount(&s->Page.EventHandlers); - for(i=0;iPage.EventHandlers); + for (unsigned int i = 0u; i < n_event_handlers; i++) { - e_srch = (pHtDomEvent)xaGetItem(&s->Page.EventHandlers,i); - if (!strcmp(event, e_srch->DomEvent)) + pHtDomEvent event_handler = check_ptr(xaGetItem(&s->Page.EventHandlers, i)); + if (event_handler == NULL) goto err; + if (strcmp(event, event_handler->DomEvent) == 0) { - e = e_srch; + e = event_handler; break; } } - /** Make a new one? **/ - if (!e) + /** Make a new event handler, if needed. **/ + if (e == NULL) { - e = (pHtDomEvent)nmMalloc(sizeof(HtDomEvent)); - if (!e) return -1; + e = (pHtDomEvent)check_ptr(nmMalloc(sizeof(HtDomEvent))); + if (e == NULL) goto err; strtcpy(e->DomEvent, event, sizeof(e->DomEvent)); - xaInit(&e->Handlers,64); - xaAddItem(&s->Page.EventHandlers, e); + if (check(xaInit(&e->Handlers,64)) != 0) goto err; + if (check_neg(xaAddItem(&s->Page.EventHandlers, e)) < 0) goto err; } /** Add our handler **/ - cnt = xaCount(&e->Handlers); - for(i=0;iHandlers); + for (unsigned int i = 0u; i < n_handlers; i++) { - if (!strcmp(function, (char*)xaGetItem(&e->Handlers,i))) + char* handler = check_ptr(xaGetItem(&e->Handlers, i)); + if (handler == NULL) goto err; + if (strcmp(function, handler) == 0) return 0; } - xaAddItem(&e->Handlers, function); + if (check_neg(xaAddItem(&e->Handlers, function) < 0)) goto err; - return 0; + /** Success. **/ + return 0; + + err: + mssError(1, "HTR", + "Failed to add %s event handler function \'%s()\' to %d for driver %s.", + event, function, event_src, drv_name + ); + return -1; } @@ -1010,17 +1199,30 @@ htrDisableBody(pHtSession s) int htrAddEvent(pHtDriver drv, char* event_name) { - pHtEventAction event; + pHtEventAction event = NULL; - /** Create the action **/ - event = (pHtEventAction)nmSysMalloc(sizeof(HtEventAction)); - if (!event) return -1; - memccpy(event->Name, event_name, 0, 31); - event->Name[31] = '\0'; - xaInit(&(event->Parameters),16); - xaAddItem(&drv->Events, (void*)event); + /** Allocate the event struct. **/ + event = (pHtEventAction)check_ptr(nmSysMalloc(sizeof(HtEventAction))); + if (event == NULL) goto err; + + /** Write the event name with a null terminator. **/ + memccpy(event->Name, event_name, 0, sizeof(event->Name) - 1lu); + event->Name[sizeof(event->Name) - 1lu] = '\0'; + + /** Register the event. **/ + if (check(xaInit(&(event->Parameters), 16)) != 0) goto err; + if (check_neg(xaAddItem(&drv->Events, (void*)event)) < 0) goto err; - return 0; + /** Success. **/ + return 0; + + err: + mssError(1, "HTR", "Failed to add event: \"%s\".", event_name); + + /** Clean up. **/ + if (LIKELY(event != NULL)) nmSysFree(event); + + return -1; } @@ -1029,17 +1231,30 @@ htrAddEvent(pHtDriver drv, char* event_name) int htrAddAction(pHtDriver drv, char* action_name) { - pHtEventAction action; + pHtEventAction action = NULL; - /** Create the action **/ - action = (pHtEventAction)nmSysMalloc(sizeof(HtEventAction)); - if (!action) return -1; - memccpy(action->Name, action_name, 0, 31); - action->Name[31] = '\0'; - xaInit(&(action->Parameters),16); - xaAddItem(&drv->Actions, (void*)action); + /** Allocate the action struct. **/ + action = (pHtEventAction)check_ptr(nmSysMalloc(sizeof(HtEventAction))); + if (action == NULL) goto err; + + /** Write the action name with a null terminator. **/ + memccpy(action->Name, action_name, 0, sizeof(action->Name) - 1lu); + action->Name[sizeof(action->Name) - 1lu] = '\0'; + + /** Register the action. **/ + if (check(xaInit(&(action->Parameters), 16)) != 0) goto err; + if (check_neg(xaAddItem(&drv->Actions, (void*)action)) < 0) goto err; - return 0; + /** Success. **/ + return 0; + + err: + mssError(1, "HTR", "Failed to add action: \"%s\".", action_name); + + /** Clean up. **/ + if (LIKELY(action != NULL)) nmSysFree(action); + + return -1; } @@ -1049,11 +1264,10 @@ int htrAddParam(pHtDriver drv, char* eventaction, char* param_name, int datatype) { pHtEventAction ea = NULL; - int i; pHtParam p; /** Look for a matching event/action **/ - for(i=0;iActions.nItems;i++) + for (unsigned int i = 0u; i < drv->Actions.nItems; i++) { if (!strcmp(((pHtEventAction)(drv->Actions.Items[i]))->Name, eventaction)) { @@ -1061,7 +1275,7 @@ htrAddParam(pHtDriver drv, char* eventaction, char* param_name, int datatype) break; } } - if (!ea) for(i=0;iEvents.nItems;i++) + if (ea == NULL) for (unsigned int i = 0u; i < drv->Events.nItems; i++) { if (!strcmp(((pHtEventAction)(drv->Events.Items[i]))->Name, eventaction)) { @@ -1069,17 +1283,30 @@ htrAddParam(pHtDriver drv, char* eventaction, char* param_name, int datatype) break; } } - if (!ea) return -1; + if (UNLIKELY(ea == NULL)) goto err; - /** Add the parameter **/ - p = nmSysMalloc(sizeof(HtParam)); - if (!p) return -1; - memccpy(p->ParamName, param_name, 0, 31); - p->ParamName[31] = '\0'; + /** Allocate the parameter struct. **/ + p = check_ptr(nmSysMalloc(sizeof(HtParam))); + if (p == NULL) goto err; + + /** Initialize the parameter struct. **/ + memccpy(p->ParamName, param_name, 0, sizeof(p->ParamName) - 1lu); + p->ParamName[sizeof(p->ParamName) - 1lu] = '\0'; p->DataType = datatype; - xaAddItem(&(ea->Parameters), (void*)p); + + /** Register the parameter. **/ + if (check_neg(xaAddItem(&(ea->Parameters), (void*)p)) < 0) goto err; - return 0; + /** Success. **/ + return 0; + + err: + mssError(1, "HTR", "Failed to add parameter: \"%s\" of type %d.", param_name, datatype); + + /** Clean up. **/ + if (LIKELY(p != NULL)) nmSysFree(p); + + return -1; } @@ -1093,17 +1320,23 @@ htrAddBodyItemLayer_va(pHtSession s, int flags, char* id, int cnt, char* cls, co va_list va; /** Add the opening tag **/ - htrAddBodyItemLayerStart(s, flags, id, cnt, cls); + if (UNLIKELY(htrAddBodyItemLayerStart(s, flags, id, cnt, cls) != 0)) goto err; /** Add the content **/ va_start(va, fmt); - htr_internal_QPAddText(s, htrAddBodyItem, (char*)fmt, va); + const int rval = htr_internal_QPAddText(s, htrAddBodyItem, (char*)fmt, va); va_end(va); + if (UNLIKELY(rval != 0)) goto err; /** Add the closing tag **/ - htrAddBodyItemLayerEnd(s, flags); + if (UNLIKELY(htrAddBodyItemLayerEnd(s, flags) != 0)) goto err; - return 0; + /** Success. **/ + return 0; + + err: + mssError(0, "HTR", "Failed to add body item layer of format: \"%s\".", fmt); + return -1; } @@ -1115,28 +1348,47 @@ htrAddBodyItemLayer_va(pHtSession s, int flags, char* id, int cnt, char* cls, co *** PARAMETER WHICH IS A FORMAT STRING FOR THE LAYER'S ID!!! ***/ int -htrAddBodyItemLayerStart(pHtSession s, int flags, char* id, int cnt, char* cls) +htrAddBodyItemLayerStart(pHtSession s, int flags, char* id, int cnt, char* class) { - char* starttag; char id_sbuf[64]; + int rval = -1; + pQPSession error_session = NULL; + + /** Create a pQPSession to track errors from qpfPrintf. **/ + error_session = check_ptr(qpfOpenSession()); + if (error_session == NULL) goto end; + + /** Pick a starting tag. **/ + const int use_iframe = (s->Capabilities.HTML40 && (flags & HTR_LAYER_F_DYNAMIC)); + const char* start_tag = (use_iframe) ? "iframe style='border:0'" : "div"; - if(s->Capabilities.HTML40) + /** Add the starting tag. **/ + if (UNLIKELY(qpfPrintf(error_session, id_sbuf, sizeof(id_sbuf), id, cnt) < 0)) { - if (flags & HTR_LAYER_F_DYNAMIC) - starttag = "IFRAME frameBorder=\"0\""; - else - starttag = "DIV"; + mssError(1, "HTR", "Failed to format \"%s\" with %d", id, cnt); + qpfLogErrors(error_session); + goto end; } - else + if (UNLIKELY(htrAddBodyItem_va(s, + "<%STR %[class='%STR&HTE' %]id='%STR&HTE'>", + start_tag, (class != NULL), class, id_sbuf + ) != 0)) { - starttag = "DIV"; + mssError(0, "HTR", "Failed to write HTML tag to start body item."); + goto end; } - /** Add it. **/ - qpfPrintf(NULL, id_sbuf,sizeof(id_sbuf),id,cnt); - htrAddBodyItem_va(s, "<%STR %[class=\"%STR&HTE\" %]id=\"%STR&HTE\">", starttag, cls != NULL, cls, id_sbuf); + /** Success. **/ + rval = 0; - return 0; + end: + if (UNLIKELY(rval != 0)) mssError(0, "HTR", "Failed to add starting body item with id: %d.", id); + + /** Clean up. **/ + if (LIKELY(error_session != NULL)) check(qpfCloseSession(error_session)); /* Failure ignored. */ + + /** Done. **/ + return rval; } @@ -1146,23 +1398,18 @@ htrAddBodyItemLayerStart(pHtSession s, int flags, char* id, int cnt, char* cls) int htrAddBodyItemLayerEnd(pHtSession s, int flags) { - char* endtag; - - if(s->Capabilities.HTML40) - { - if (flags & HTR_LAYER_F_DYNAMIC) - endtag = "IFRAME"; - else - endtag = "DIV"; - } - else + /** Pick a starting tag. **/ + const int use_iframe = (s->Capabilities.HTML40 && (flags & HTR_LAYER_F_DYNAMIC)); + const char* end_tag = (use_iframe) ? "iframe" : "div"; + + /** Add it. **/ + if (UNLIKELY(htrAddBodyItem_va(s, "", end_tag) != 0)) { - endtag = "DIV"; + mssError(0, "HTR", "Failed to write HTML tag to start body item."); + return -1; } - /** Add it. **/ - htrAddBodyItem_va(s, "", endtag); - + /** Done. **/ return 0; } @@ -1173,43 +1420,53 @@ htrAddBodyItemLayerEnd(pHtSession s, int flags) int htrGetExpParams(pExpression exp, pXString xs) { - int i, first; - XArray objs, props; - char* obj; - char* prop; + int rval = -1; + XArray objs = { nAlloc: 0 }, props = { nAlloc: 0 }; /** setup **/ - xaInit(&objs, 16); - xaInit(&props, 16); + if (check(xaInit(&objs, 16)) != 0) goto end; + if (check(xaInit(&props, 16)) != 0) goto end; /** Find the properties accessed by the expression **/ - expGetPropList(exp, &objs, &props); + if (check_neg(expGetPropList(exp, &objs, &props) < 0)) goto end; /** Build the list **/ - xsCopy(xs,"[",-1); - first=1; - for(i=0;iName); + + /** Clean up. **/ + for (unsigned int i = 0u; i < objs.nItems; i++) + if (LIKELY(objs.Items[i] != NULL)) nmSysFree(objs.Items[i]); + if (LIKELY(objs.nAlloc != 0)) check(xaDeInit(&objs)); /* Failure ignored. */ + for (unsigned int i = 0u; i < props.nItems; i++) + if (LIKELY(props.Items[i] != NULL)) nmSysFree(props.Items[i]); + if (LIKELY(props.nAlloc != 0)) check(xaDeInit(&props)); /* Failure ignored. */ + + /** Done. **/ + return rval; } @@ -1223,45 +1480,70 @@ htrGetExpParams(pExpression exp, pXString xs) int htrAddExpression(pHtSession s, char* objname, char* property, pExpression exp) { - int i,first; - XArray objs, props; - XString xs,exptxt; - char* obj; - char* prop; - - xaInit(&objs, 16); - xaInit(&props, 16); - xsInit(&xs); - xsInit(&exptxt); - expGetPropList(exp, &objs, &props); - - xsCopy(&xs,"[",-1); - first=1; - for(i=0;iNamespace->DName); - - for(i=0;iNamespace->DName + ) != 0)) + { + mssError(0, "HTR", "Failed to write the expresion item."); + cls = 0; + goto end; } - xaDeInit(&objs); - xaDeInit(&props); - xsDeInit(&xs); - xsDeInit(&exptxt); - return 0; + /** Success. **/ + rval = 0; + + end: + if (UNLIKELY(rval != 0)) mssError(cls, "HTR", "Failed to add expression \"%s\".", objname); + + /** Clean up. **/ + for (unsigned int i = 0u; i < objs.nItems; i++) + if (LIKELY(objs.Items[i] != NULL)) nmSysFree(objs.Items[i]); + if (LIKELY(objs.nAlloc != 0)) check(xaDeInit(&objs)); /* Failure ignored. */ + for (unsigned int i = 0u; i < props.nItems; i++) + if (LIKELY(props.Items[i] != NULL)) nmSysFree(props.Items[i]); + if (LIKELY(props.nAlloc != 0)) check(xaDeInit(&props)); /* Failure ignored. */ + if (LIKELY(xs.AllocLen != 0)) check(xsDeInit(&xs)); /* Failure ignored. */ + if (LIKELY(exptxt.AllocLen != 0)) check(xsDeInit(&exptxt)); /* Failure ignored. */ + + /** Done. **/ + return rval; } @@ -1271,16 +1553,27 @@ htrAddExpression(pHtSession s, char* objname, char* property, pExpression exp) int htrCheckAddExpression(pHtSession s, pWgtrNode tree, char* w_name, char* property) { - pExpression code; - if (wgtrGetPropertyType(tree,property) == DATA_T_CODE) { - wgtrGetPropertyValue(tree,property,DATA_T_CODE,POD(&code)); - htrAddExpression(s, w_name, property, code); + pExpression code; + if (UNLIKELY(wgtrGetPropertyValue(tree, property, DATA_T_CODE, POD(&code)) < 0)) + { + mssError(1, "HTR", "Failed to get property: '%s'", property); + goto err; + } + if (UNLIKELY(htrAddExpression(s, w_name, property, code) != 0)) goto err; return 1; } - return 0; + /** Success. **/ + return 0; + + err: + mssError(0, "HTR", + "Failed to add expression \"%s\" to widget \"%s\":\"%s\".", + property, tree->Name, tree->Type + ); + return -1; } @@ -1291,28 +1584,24 @@ htrCheckAddExpression(pHtSession s, pWgtrNode tree, char* w_name, char* property *** as panes and tab pages. ***/ int -htrRenderSubwidgets(pHtSession s, pWgtrNode widget, int zlevel) +htrRenderSubwidgets(pHtSession s, pWgtrNode widget, int z_level) { -// pObjQuery qy; -// pObject sub_widget_obj; - int i, count; - - /** Open the query for subwidgets **/ - /* - qy = objOpenQuery(widget_obj, "", NULL, NULL, NULL, 0); - if (qy) + /** Iterate through each sub_widget and call its render function. **/ + const unsigned int n_sub_widgets = widget->Children.nItems; + for (unsigned int i = 0u; i < n_sub_widgets; i++) { - while((sub_widget_obj = objQueryFetch(qy, O_RDONLY))) - { - htrRenderWidget(s, sub_widget_obj, zlevel, docname, layername); - objClose(sub_widget_obj); + pWgtrNode sub_widget = widget->Children.Items[i]; + if (UNLIKELY(htrRenderWidget(s, sub_widget, z_level) != 0)) + { + mssError(0, "HTR", + "Failed to render \"%s\":\"%s\", " + "child #%d/%d of \"%s\":\"%s\" (z: %d).", + sub_widget->Name, sub_widget->Type, + i + 1, n_sub_widgets, widget->Name, widget->Type, z_level + ); + return -1; } - objQueryClose(qy); } - **/ - count = xaCount(&(widget->Children)); - for (i=0;iChildren), i), zlevel); return 0; } @@ -1341,7 +1630,7 @@ htr_internal_GenInclude(pHtSession s, char* filename) include_file = objOpen(s->ObjSession, filename, O_RDONLY, 0600, "application/x-javascript"); if (include_file) { - htrQPrintf(s, "\n", path, buf[0], buf, slash+1); + htrQPrintf(s, "\t\n", path, buf[0], buf, slash+1); } return 0; @@ -1380,50 +1669,121 @@ htr_internal_GenInclude(pHtSession s, char* filename) ***/ int htr_internal_WriteWgtrProperty(pHtSession s, pWgtrNode tree, char* propname) - { - int t; - ObjData od; - int rval; - pExpression code; - XString exptxt; - XString proptxt; - - t = wgtrGetPropertyType(tree, propname); - if (t > 0) + { + /** Get type. **/ + const int type = wgtrGetPropertyType(tree, propname); + if (UNLIKELY(type < 0)) { - rval = wgtrGetPropertyValue(tree, propname, t, &od); - if (rval == 1) - { - /** null **/ - htrAddScriptWgtr_va(s, "%STR&SYM:null, ", propname); - } - else if (rval == 0) + mssError(1, "HTR", "Failed to get property type for property '%s'.", propname); + goto err; + } + + /** Get value. **/ + ObjData od; + const int result = wgtrGetPropertyValue(tree, propname, type, &od); + if (UNLIKELY(result < 0)) + { + mssError(1, "HTR", + "Failed to get property value for property '%s' of type %d.", + propname, type + ); + goto err; + } + + /** Value is null. **/ + if (result == 1) + { + if (UNLIKELY(htrAddScriptWgtr_va(s, "%STR&SYM:null, ", propname) != 0)) goto err; + return 0; /* success */ + } + + /** Write values by type. **/ + switch (type) + { + case DATA_T_INTEGER: + if (UNLIKELY(htrAddScriptWgtr_va(s, "%STR&SYM:%INT, ", propname, od.Integer) != 0)) goto err; + break; + + case DATA_T_STRING: + if (UNLIKELY(htrAddScriptWgtr_va(s, "%STR&SYM:'%STR&JSSTR', ", propname, od.String) != 0)) goto err; + break; + + case DATA_T_DOUBLE: + if (UNLIKELY(htrAddScriptWgtr_va(s, "%STR&SYM:%DBL, ", propname, od.Double) != 0)) goto err; + break; + + case DATA_T_DATETIME: + if (UNLIKELY(htrAddScriptWgtr_va(s, + "%STR&SYM:new Date(%LL, %LL, %LL, %LL, %LL, %LL), ", + propname, + (long long)od.DateTime->Part.Year + 1900, + (long long)od.DateTime->Part.Month, + (long long)od.DateTime->Part.Day + 1, + (long long)od.DateTime->Part.Hour, + (long long)od.DateTime->Part.Minute, + (long long)od.DateTime->Part.Second + ) != 0)) goto err; + break; + + case DATA_T_INTVEC: + if (UNLIKELY(htrAddScriptWgtr_va(s, "%STR&SYM:[", propname) != 0)) goto err; + for (unsigned int i = 0; i < od.IntVec->nIntegers; i++) + if (UNLIKELY(htrAddScriptWgtr_va(s, "%[, %]%INT", (i != 0), od.IntVec->Integers[i]) != 0)) goto err; + if (UNLIKELY(htrAddScriptWgtr(s, "], ") != 0)) goto err; + break; + + case DATA_T_STRINGVEC: + if (UNLIKELY(htrAddScriptWgtr_va(s, "%STR&SYM:[", propname) != 0)) goto err; + for (unsigned int i = 0; i < od.StringVec->nStrings; i++) + if (UNLIKELY(htrAddScriptWgtr_va(s, "%[, %]'%STR&JSSTR'", (i != 0), od.StringVec->Strings[i]) != 0)) goto err; + if (UNLIKELY(htrAddScriptWgtr(s, "], ") != 0)) goto err; + break; + + case DATA_T_CODE: { - switch(t) + pExpression code; + XString exptxt = { AllocLen: 0 }; + XString proptxt = { AllocLen: 0 }; + + if (UNLIKELY(wgtrGetPropertyValue(tree, propname, DATA_T_CODE, POD(&code)) < 0)) + { + mssError(1, "HTR", "Failed to get value for property '%s'", propname); + goto err_free; + } + if (check(xsInit(&exptxt)) != 0) goto err_free; + if (check(xsInit(&proptxt)) != 0) goto err_free; + if (UNLIKELY(htrGetExpParams(code, &proptxt) != 0)) goto err_free; + if (UNLIKELY(expGenerateText(code, NULL, xsWrite, &exptxt, '\0', "javascript", EXPR_F_RUNCLIENT) != 0)) { - case DATA_T_INTEGER: - htrAddScriptWgtr_va(s, "%STR&SYM:%INT, ", propname, od.Integer); - break; - - case DATA_T_STRING: - htrAddScriptWgtr_va(s, "%STR&SYM:'%STR&JSSTR', ", propname, od.String); - break; - - case DATA_T_CODE: - wgtrGetPropertyValue(tree,propname,DATA_T_CODE,POD(&code)); - xsInit(&exptxt); - xsInit(&proptxt); - htrGetExpParams(code, &proptxt); - expGenerateText(code, NULL, xsWrite, &exptxt, '\0', "javascript", EXPR_F_RUNCLIENT); - htrAddScriptWgtr_va(s, "%STR&SYM:{val:null, exp:function(_this,_context){return ( %STR );}, props:%STR, revexp:null}, ", propname, exptxt.String, proptxt.String); - xsDeInit(&proptxt); - xsDeInit(&exptxt); - break; + mssError(0, "HTR", "Failed to generate expression text."); + goto err_free; } + if (UNLIKELY(htrAddScriptWgtr_va(s, + "%STR&SYM:{ val:null, exp:(_this, _context) => { return ( %STR ); }, props:%STR, revexp:null }, ", + propname, exptxt.String, proptxt.String + ) != 0)) goto err_free; + break; + + err_free: /** Clean up. **/ + if (proptxt.AllocLen != 0) check_neg(xsDeInit(&proptxt)); /* Failure ignored. */ + if (exptxt.AllocLen != 0) check_neg(xsDeInit(&exptxt)); /* Failure ignored. */ + goto err; } + + default: + mssError(1, "HTR", "Unknown datatype %d.\n", type); + goto err; } - - return 0; + + /** Success. **/ + return 0; + + err: + mssError(0, "HTR", + "Failed to write widget property \"%s\" to widget \"%s\":\"%s\".", + propname, tree->Name, tree->Type + ); + return -1; } @@ -1433,22 +1793,18 @@ htr_internal_WriteWgtrProperty(pHtSession s, pWgtrNode tree, char* propname) int htr_internal_BuildClientWgtr_r(pHtSession s, pWgtrNode tree, int indent) { - int i; int childcnt = xaCount(&tree->Children); - char* objinit; - char* ctrinit; pHtDMPrivateData inf = wgtrGetDMPrivateData(tree); pWgtrNode child; - int rendercnt; char* scope = NULL; char* scopename = NULL; char* propname; /** Check recursion **/ - if (thExcessiveRecursion()) + if (UNLIKELY(thExcessiveRecursion())) { mssError(1,"HTR","Could not render application: resource exhaustion occurred"); - return -1; + goto err; } /** Widget name scope **/ @@ -1460,9 +1816,9 @@ htr_internal_BuildClientWgtr_r(pHtSession s, pWgtrNode tree, int indent) wgtrGetPropertyValue(tree,"scope_name",DATA_T_STRING,POD(&scopename)); /** Deploy the widget **/ - objinit = inf?(inf->ObjectLinkage):NULL; - ctrinit = inf?(inf->ContainerLinkage):NULL; - htrAddScriptWgtr_va(s, + const char* objinit = (inf != NULL) ? (inf->ObjectLinkage) : NULL; + const char* ctrinit = (inf != NULL) ? (inf->ContainerLinkage) : NULL; + const int result = htrAddScriptWgtr_va(s, " %STR&*LEN{name:'%STR&SYM'%[, obj:%STR%]%[, cobj:%STR%]%[, scope:'%STR&JSSTR'%]%[, sn:'%STR&JSSTR'%], type:'%STR&JSSTR', vis:%STR, ctl:%STR%[, namespace:'%STR&SYM'%]", indent*4, " ", tree->Name, @@ -1475,48 +1831,67 @@ htr_internal_BuildClientWgtr_r(pHtSession s, pWgtrNode tree, int indent) (tree->Flags & WGTR_F_CONTROL)?"true":"false", (!tree->Parent || strcmp(tree->Parent->Namespace, tree->Namespace)), tree->Namespace); + if (UNLIKELY(result != 0)) + { + mssError(0, "HTR", "Failed to write JS data."); + goto err; + } /** Parameters **/ - htrAddScriptWgtr_va(s, ", param:{"); + if (UNLIKELY(htrAddScriptWgtr(s, ", param:{") != 0)) goto err; if (!(tree->Flags & WGTR_F_NONVISUAL)) { - htr_internal_WriteWgtrProperty(s, tree, "x"); - htr_internal_WriteWgtrProperty(s, tree, "y"); - htr_internal_WriteWgtrProperty(s, tree, "width"); - htr_internal_WriteWgtrProperty(s, tree, "height"); - htr_internal_WriteWgtrProperty(s, tree, "r_x"); - htr_internal_WriteWgtrProperty(s, tree, "r_y"); - htr_internal_WriteWgtrProperty(s, tree, "r_width"); - htr_internal_WriteWgtrProperty(s, tree, "r_height"); + if (UNLIKELY(htr_internal_WriteWgtrProperty(s, tree, "x") != 0)) goto err; + if (UNLIKELY(htr_internal_WriteWgtrProperty(s, tree, "y") != 0)) goto err; + if (UNLIKELY(htr_internal_WriteWgtrProperty(s, tree, "width") != 0)) goto err; + if (UNLIKELY(htr_internal_WriteWgtrProperty(s, tree, "height") != 0)) goto err; + if (UNLIKELY(htr_internal_WriteWgtrProperty(s, tree, "r_x") != 0)) goto err; + if (UNLIKELY(htr_internal_WriteWgtrProperty(s, tree, "r_y") != 0)) goto err; + if (UNLIKELY(htr_internal_WriteWgtrProperty(s, tree, "r_width") != 0)) goto err; + if (UNLIKELY(htr_internal_WriteWgtrProperty(s, tree, "r_height") != 0)) goto err; + if (UNLIKELY(htr_internal_WriteWgtrProperty(s, tree, "fl_x") != 0)) goto err; + if (UNLIKELY(htr_internal_WriteWgtrProperty(s, tree, "fl_y") != 0)) goto err; + if (UNLIKELY(htr_internal_WriteWgtrProperty(s, tree, "fl_width") != 0)) goto err; + if (UNLIKELY(htr_internal_WriteWgtrProperty(s, tree, "fl_height") != 0)) goto err; + if (UNLIKELY(htr_internal_WriteWgtrProperty(s, tree, "fl_scale_x") != 0)) goto err; + if (UNLIKELY(htr_internal_WriteWgtrProperty(s, tree, "fl_scale_y") != 0)) goto err; + if (UNLIKELY(htr_internal_WriteWgtrProperty(s, tree, "fl_scale_w") != 0)) goto err; + if (UNLIKELY(htr_internal_WriteWgtrProperty(s, tree, "fl_scale_h") != 0)) goto err; + if (UNLIKELY(htr_internal_WriteWgtrProperty(s, tree, "fl_parent_w") != 0)) goto err; + if (UNLIKELY(htr_internal_WriteWgtrProperty(s, tree, "fl_parent_h") != 0)) goto err; } propname = wgtrFirstPropertyName(tree); - while(propname) + while (propname != NULL) { - htr_internal_WriteWgtrProperty(s, tree, propname); + if (UNLIKELY(htr_internal_WriteWgtrProperty(s, tree, propname) != 0)) goto err; propname = wgtrNextPropertyName(tree); } - htrAddScriptWgtr_va(s, "}"); + if (UNLIKELY(htrAddScriptWgtr(s, "}") != 0)) goto err; /** ... and any subwidgets **/ //TODO: there's a glitch in this section in which a comma is placed after the last element of an array. - for(rendercnt=i=0;iChildren, i); if (child->RenderFlags & HT_WGTF_NORENDER) continue; rendercnt++; } - if (rendercnt) + if (LIKELY(rendercnt > 0)) { - htrAddScriptWgtr_va(s, ", sub:\n %STR&*LEN [\n", indent*4, " "); + if (htrAddScriptWgtr_va(s, + ", sub:\n %STR&*LEN [\n", + indent*4, " " + ) != 0) goto err; rendercnt = 0; - for(i=0;iChildren, i); if (child->RenderFlags & HT_WGTF_NORENDER) continue; rendercnt--; if (htr_internal_BuildClientWgtr_r(s, child, indent+1) < 0) - return -1; + goto err; if (rendercnt == 0) /* last one - no comma */ htrAddScriptWgtr(s, "\n"); else @@ -1526,22 +1901,39 @@ htr_internal_BuildClientWgtr_r(pHtSession s, pWgtrNode tree, int indent) } else { - htrAddScriptWgtr(s, "}"); + if (UNLIKELY(htrAddScriptWgtr(s, "}") != 0)) goto err; } - return 0; + /** Success. **/ + return 0; + + err: + mssError(0, "HTR", + "Failed to build client widget tree for widget \"%s\":\"%s\" (indent: %d).", + tree->Name, tree->Type, indent + ); + + return -1; } int htrBuildClientWgtr(pHtSession s, pWgtrNode tree) { + /** Build the JS for the tree. **/ + if (htrAddScriptInclude(s, "/sys/js/ht_utils_wgtr.js", 0) != 0) goto err; + if (htrAddScriptWgtr_va(s, " pre_%STR&SYM =\n", tree->DName) != 0) goto err; + if (htr_internal_BuildClientWgtr_r(s, tree, 0) != 0) goto err; + if (htrAddScriptWgtr(s, ";\n") != 0) goto err; - htrAddScriptInclude(s, "/sys/js/ht_utils_wgtr.js", 0); - htrAddScriptWgtr_va(s, " pre_%STR&SYM =\n", tree->DName); - htr_internal_BuildClientWgtr_r(s, tree, 0); - htrAddScriptWgtr(s, ";\n"); + /** Success. **/ + return 0; - return 0; + err: + mssError(0, "HTR", + "Failed to build client widget tree for widget \"%s\":\"%s\".", + tree->Name, tree->Type + ); + return -1; } @@ -1627,14 +2019,176 @@ htrQPrintf(pHtSession s, char* fmt, ...) if (!xs) return -1; va_start(va, fmt); - xsQPrintf_va(xs, fmt, va); + rval = xsQPrintf_va(xs, fmt, va); va_end(va); + if (rval < 0) return rval; rval = htrWrite(s, xsString(xs), -1); xsFree(xs); return rval; } +/*** Generates HTML for an error page. Used as a fallback if an error occurs + *** during normal rendering. + *** + *** @param title The title of the error page. e.g. "An error occurred!" + *** @returns A pointer to a new string buffer allocated with nmSysMalloc(), + *** containing data that can be sent to a browser as HTML, or NULL in the + *** rare case that an error page absolutely could not be generated. + ***/ +char* +htrGetErrorHTML(char* title) + { + char* err_str = "Failed to fetch error."; + char* page_buf = NULL; + pXString err_xs = NULL; + unsigned int n_lines = 1u; + pQPSession error_session = NULL; + + /** Default title. **/ + if (title == NULL) title = "An error occurred!"; + + /** Get the error string. **/ + err_xs = check_ptr(xsNew()); + if (err_xs == NULL) goto write_err; + if (check(mssStringError(err_xs)) != 0) goto write_err; + if (check(xsTrim(err_xs)) != 0) goto write_err; + char* tmp_err_str = check_ptr(xsString(err_xs)); + if (tmp_err_str == NULL) goto write_err; + err_str = tmp_err_str; + + /** Count the number of lines in the error message. **/ + for (unsigned int i = 0u; err_str[i] != '\0'; i++) + { + if (err_str[i] == '\n') n_lines++; + } + + write_err:; + const char* page_format = "" + "" + "" + "Error" + "" + "" + "" + "" + "" + "" + "

Uh oh!

" + "

%STR&HTE

" + "

" + "Sorry for the inconvenience. If this issue persists, please contact " + "your organization's tech support and provide the error below." + "

" + "

Error message:

" + "
" + "
Copy
" + "
%STR&HTE
" + "
" + "" + ""; + + /** Allocate space for page. **/ + size_t page_buf_size = 0lu + + strlen(page_format) + + 10lu /* %POS (n_lines) */ + + strlen(title) + + strlen(err_str) + + 1lu; /* Null terminator. */ + page_buf = check_ptr(nmSysMalloc(page_buf_size)); + if (page_buf == NULL) goto clean_up; + + /** Write an error HTML for the user. **/ + error_session = check_ptr(qpfOpenSession()); /* Failure ignored. */ + // nmSysFree(check_ptr(nmSysMalloc(8))); + if (check_neg(qpfPrintf_g( + error_session, &page_buf, &page_buf_size, &qpfSysMallocGrow, NULL, page_format, + n_lines, title, err_str + )) < 0) + { + if (LIKELY(error_session != NULL)) qpfLogErrors(error_session); + goto fail; + } + // nmSysFree(check_ptr(nmSysMalloc(8))); + + /** Success. **/ + goto clean_up; + + fail: + fprintf(stderr, "Failed to print error page. (Continuing...)"); + if (LIKELY(page_buf != NULL)) nmSysFree(page_buf); + page_buf = NULL; + + clean_up: + /** Clean up. **/ + if (LIKELY(err_xs != NULL)) xsFree(err_xs); + if (LIKELY(error_session != NULL)) check(qpfCloseSession(error_session)); /* Failure ignored. */ + + /** Final fallback chain if we STILL couldn't create an error page. **/ + if (UNLIKELY(page_buf == NULL)) page_buf = check_ptr(nmSysStrdup("See server logs")); + if (UNLIKELY(page_buf == NULL)) page_buf = check_ptr(nmSysStrdup("err")); + if (UNLIKELY(page_buf == NULL)) page_buf = check_ptr(nmSysStrdup("!")); + + return page_buf; + } + /*** htrRender - generate an HTML document given the app structure subtree *** as an open ObjectSystem object. @@ -1658,19 +2212,18 @@ htrRender(void* stream, int (*stream_write)(void*, char*, int, int, int), pObjSe char* agent = NULL; char* classname = NULL; int rval; - pXString err_xs; /** What UA is on the other end of the connection? **/ agent = (char*)mssGetParam("User-Agent"); - if (!agent) + if (UNLIKELY(agent == NULL || agent[0] == '\0')) { mssError(1, "HTR", "User-Agent undefined in the session parameters"); return -1; } /** Initialize the session **/ - s = (pHtSession)nmMalloc(sizeof(HtSession)); - if (!s) return -1; + s = (pHtSession)check_ptr(nmMalloc(sizeof(HtSession))); + if (s == NULL) return -1; memset(s,0,sizeof(HtSession)); s->Params = params; s->ObjSession = obj_s; @@ -1786,13 +2339,8 @@ htrRender(void* stream, int (*stream_write)(void*, char*, int, int, int), pObjSe s->DisableBody = 0; /** first thing in the startup() function should be calling build_wgtr **/ - htrAddScriptInit_va(s, " build_wgtr_%STR&SYM();\n", - s->Namespace->DName); - /*htrAddScriptInit_va(s, "\n var nodes = wgtrNodeList(%STR&SYM);\n",*/ - htrAddScriptInit_va(s, "\n var ns = \"%STR&SYM\";\n", - /*" var rootname = \"%STR&SYM\";\n", */ - s->Namespace->DName /*, s->Namespace->DName */); - /*htrAddStylesheetItem(s, "\tdiv {position:absolute; visibility:inherit; overflow:hidden; }\n");*/ + htrAddScriptInit_va(s, "\tvar ns = '%STR&SYM';\n", s->Namespace->DName); + htrAddScriptInit_va(s, "\tbuild_wgtr_%STR&SYM();\n", s->Namespace->DName); /** Render the top-level widget -- the function that's run * underneath will be dependent upon what the widget @@ -1812,7 +2360,7 @@ htrRender(void* stream, int (*stream_write)(void*, char*, int, int, int), pObjSe #ifdef WGTR_DBG_WINDOW htrAddScriptWgtr_va(s, " wgtrWalk(%STR&SYM);\n", tree->Name); htrAddScriptWgtr(s, " ifcLoadDef(\"net/centrallix/button.ifc\");\n"); - htrAddStylesheetItem(s, "\t#dbgwnd {position: absolute; top: 400; left: 50;}\n"); + htrAddStylesheetItem(s, "\t\t#dbgwnd {position: absolute; top: 400; left: 50;}\n"); htrAddBodyItem(s, "
" "" "
\n"); @@ -1820,26 +2368,25 @@ htrRender(void* stream, int (*stream_write)(void*, char*, int, int, int), pObjSe /** Could not render? **/ - if (rval < 0) + if (UNLIKELY(rval < 0)) { - err_xs = xsNew(); - if (err_xs) - { - mssStringError(err_xs); - htrQPrintf(s, "Error

An Error occured while attempting to render this document


%STR&HTE
\r\n", xsString(err_xs)); - xsFree(err_xs); - } + /** Render an error page to gracefully recover from the error. **/ + char* error_title = "An error occurred while rendering the page."; + char* error_html = check_ptr(htrGetErrorHTML(error_title)); + if (UNLIKELY(error_html == NULL)) error_html = error_title; + check_neg(htrQPrintf(s, "%STR", error_html)); /* Failure ignored. */ + if (LIKELY(error_html != error_title)) nmSysFree(error_html); + check(mssClearError()); /* Failure ignored. */ + goto end_free; } - /** Output the DOCTYPE for browsers supporting HTML 4.0 -- this will make them use HTML 4.0 Strict **/ - /** FIXME: should probably specify the DTD.... **/ - if(s->Capabilities.HTML40 && !s->Capabilities.Dom0IE) - htrWrite(s, "\n\n", -1); + /** Write the DOCTYPE header, enabling HTML 4.0 Strict mode. **/ + htrWriteConst(s, "\n\n"); - /** Write the HTML out... **/ + /** Write HTML header comment & license. **/ htrQPrintf(s, "\n\n" , cx__version); - htrQPrintf(s, "\n" + /** Write the start of the HTML document. **/ + htrQPrintf(s, "\n" "\n" - " \n" - " \n" - " \n" + "\t\n" + "\t\n" + "\t\n" + "\t\n" , cx__version); - htrWrite(s, " \n", -1); + htrWriteConst(s, "\t\n"); /** Write the HTML header items. **/ for(i=0;iPage.HtmlHeader.nItems;i++) { @@ -1872,19 +2421,26 @@ htrRender(void* stream, int (*stream_write)(void*, char*, int, int, int), pObjSe } /** Write the script globals **/ - htrWrite(s, "\n\n", -1); + htrWriteConst(s, "\t\n\n"); /** include ht_render.js **/ htr_internal_GenInclude(s, "/sys/js/ht_render.js"); @@ -1912,7 +2468,7 @@ htrRender(void* stream, int (*stream_write)(void*, char*, int, int, int), pObjSe sv = (pStrValue)(s->Page.Includes.Items[i]); htr_internal_GenInclude(s, sv->Name); } - htrWrite(s, "\n",-1); - htrWrite(s, "\n\n"); + htrWriteConst(s, "\nPage.HtmlBodyParams.nItems;i++) { ptr = (char*)(s->Page.HtmlBodyParams.Items[i]); @@ -2019,13 +2594,13 @@ htrRender(void* stream, int (*stream_write)(void*, char*, int, int, int), pObjSe /** work around the Netscape 4.x bug regarding page resizing **/ if(s->Capabilities.Dom0NS && !s->Capabilities.Dom1HTML) { - htrWrite(s, " onResize=\"location.reload()\"",-1); + htrWriteConst(s, " onResize=\"location.reload()\""); } - htrWrite(s, ">\n", -1); + htrWriteConst(s, ">\n"); } else { - htrWrite(s, "\n\n",-1); + htrWriteConst(s, "\n\n"); } /** Write the HTML body. **/ @@ -2038,13 +2613,14 @@ htrRender(void* stream, int (*stream_write)(void*, char*, int, int, int), pObjSe if (s->DisableBody == 0) { - htrWrite(s, "\n\n",-1); + htrWriteConst(s, "\n\n"); } else { - htrWrite(s, "\n\n",-1); + htrWriteConst(s, "\n\n"); } + end_free: /** Deinitialize the session and page structures **/ for(i=0;iPage.Functions.nItems;i++) { @@ -2124,18 +2700,23 @@ htrAllocDriver() pHtDriver drv; /** Allocate the driver structure **/ - drv = (pHtDriver)nmMalloc(sizeof(HtDriver)); - if (!drv) return NULL; + drv = (pHtDriver)check_ptr(nmMalloc(sizeof(HtDriver))); + if (drv == NULL) goto err; memset(drv, 0, sizeof(HtDriver)); /** Init some of the basic array structures **/ - xaInit(&(drv->PosParams),16); - xaInit(&(drv->Properties),16); - xaInit(&(drv->Events),16); - xaInit(&(drv->Actions),16); - xaInit(&(drv->PseudoTypes), 4); + if (check(xaInit(&(drv->PosParams), 16)) != 0) goto err; + if (check(xaInit(&(drv->Properties), 16)) != 0) goto err; + if (check(xaInit(&(drv->Events), 16)) != 0) goto err; + if (check(xaInit(&(drv->Actions), 16)) != 0) goto err; + if (check(xaInit(&(drv->PseudoTypes), 4)) != 0) goto err; + + /** Success. **/ + return drv; - return drv; + err: + mssError(0, "HTR", "Failed to allocate driver."); + return NULL; } @@ -2238,44 +2819,110 @@ htrGetBackground(pWgtrNode tree, char* prefix, int as_style, char* buf, int bufl char* bgcolor = "bgcolor"; char* background = "background"; char* ptr; + pQPSession error_session = NULL; + int rval = -1; - /** init buf **/ - if (buflen < 1) return -1; + /** Initialize the buffer. **/ + if (UNLIKELY(buflen < 1)) + { + mssError(1, "HTR", "Failed! buflen (%d) < 1", buflen); + goto end; + } buf[0] = '\0'; + + /** Create a pQPSession to track errors from qpfPrintf. **/ + error_session = check_ptr(qpfOpenSession()); + if (error_session == NULL) goto end; /** Prefix supplied? **/ - if (prefix && *prefix) + if (prefix != NULL && prefix[0] != '\0') { - qpfPrintf(NULL, bgcolor_name,sizeof(bgcolor_name),"%STR&SYM_bgcolor",prefix); - qpfPrintf(NULL, background_name,sizeof(background_name),"%STR&SYM_background",prefix); + /** Initialize buffers with prefixed attribute names. **/ + if (qpfPrintf(error_session, bgcolor_name, sizeof(bgcolor_name), "%STR&SYM_bgcolor", prefix) < 0) + { + mssError(1, "HTR", "Failed to write background color attribute name."); + qpfLogErrors(error_session); + goto end; + } + else qpfClearErrors(error_session); + if (qpfPrintf(error_session, background_name, sizeof(background_name), "%STR&SYM_background", prefix) < 0) + { + mssError(1, "HTR", "Failed to write background color attribute name."); + qpfLogErrors(error_session); + goto end; + } + else qpfClearErrors(error_session); + + /** Update attribute name pointers. **/ bgcolor = bgcolor_name; background = background_name; } - /** Image? **/ + /** Search for different background types. **/ if (wgtrGetPropertyValue(tree, background, DATA_T_STRING, POD(&ptr)) == 0) - { - if (strpbrk(ptr,"\"'\n\r\t")) return -1; - if (as_style) - qpfPrintf(NULL, buf,buflen,"background-image: URL('%STR&CSSURL');",ptr); - else - qpfPrintf(NULL, buf,buflen,"background='%STR&HTE'",ptr); + { /* Background image. */ + /** Check for invalid characters in the string. **/ + if (strpbrk(ptr, "\"'\n\r\t") != NULL) + { + mssError(1, "HTR", + "Value for attribute '%s' contains invalid characters: \"%s\"", + background, ptr + ); + goto end; + } + + /** Write background image. **/ + const char* format = (as_style) ? "background-image:URL('%STR&CSSURL');" : "background='%STR&HTE'"; + if (qpfPrintf(error_session, buf, buflen, format, ptr) < 0) + { + mssError(1, "HTR", "Failed to write background image using format: \"%s\"", format); + qpfLogErrors(error_session); + goto end; + } + else qpfClearErrors(error_session); } else if (wgtrGetPropertyValue(tree, bgcolor, DATA_T_STRING, POD(&ptr)) == 0) - { - /** Background color **/ - if (strpbrk(ptr,"\"'\n\r\t;}<>&")) return -1; - if (as_style) - qpfPrintf(NULL, buf,buflen,"background-color: %STR&CSSVAL;",ptr); - else - qpfPrintf(NULL, buf,buflen,"bgColor='%STR&HTE'",ptr); + { /* Background color. */ + /** Check for invalid characters in the string. **/ + if (strpbrk(ptr, "\"'\n\r\t;}<>&") != NULL) + { + mssError(1, "HTR", + "Value for attribute '%s' contains invalid characters: \"%s\"", + background, ptr + ); + goto end; + } + + /** Write background color. **/ + const char* format = (as_style) ? "background-color:%STR&CSSVAL;" : "bgColor='%STR&HTE"; + if (qpfPrintf(error_session, buf, buflen, format, ptr) < 0) + { + mssError(1, "HTR", "Failed to write background color using format: \"%s\"", format); + qpfLogErrors(error_session); + goto end; + } + else qpfClearErrors(error_session); } - else + /** Fail quietly, as this may be intended behavior. **/ + else goto free; + + /** Success. **/ + rval = 0; + + end: + if (UNLIKELY(rval != 0)) { - return -1; + mssError(0, "HTR", + "Failed write \"%s\"-background for \"%s\":\"%s\" into buffer %p of length %d.", + prefix, tree->Name, tree->Type, buf, buflen + ); } - return 0; + free: + /** Clean up. **/ + if (LIKELY(error_session != NULL)) qpfCloseSession(error_session); + + return rval; } @@ -2296,7 +2943,7 @@ htrGetBoolean(pWgtrNode wgt, char* attrname, int default_value) /** type of attr (need to check number if 1/0) **/ t = wgtrGetPropertyType(wgt,attrname); - if (t < 0) return default_value; + if (t < 0) goto end; /** integer? **/ if (t == DATA_T_INTEGER) @@ -2323,10 +2970,12 @@ htrGetBoolean(pWgtrNode wgt, char* attrname, int default_value) } else { - mssError(1,"HT","Invalid data type for attribute '%s'", attrname); + mssError(1,"HTR","Invalid datatype %d for attribute '%s'", t, attrname); rval = -1; + goto end; } + end: return rval; } @@ -2360,7 +3009,8 @@ htr_internal_CheckDMPrivateData(pWgtrNode widget) if (!inf) { - inf = (pHtDMPrivateData)nmMalloc(sizeof(HtDMPrivateData)); + inf = (pHtDMPrivateData)check_ptr(nmMalloc(sizeof(HtDMPrivateData))); + if (inf == NULL) return NULL; memset(inf, 0, sizeof(HtDMPrivateData)); wgtrSetDMPrivateData(widget, inf); } @@ -2402,11 +3052,29 @@ htr_internal_FreeDMPrivateData(pWgtrNode widget) int htrAddWgtrObjLinkage(pHtSession s, pWgtrNode widget, char* linkage) { - pHtDMPrivateData inf = htr_internal_CheckDMPrivateData(widget); - - inf->ObjectLinkage = nmSysStrdup(objDataToStringTmp(DATA_T_STRING, linkage, DATA_F_QUOTED)); - - return 0; + /** Get private data. **/ + pHtDMPrivateData inf = check_ptr(htr_internal_CheckDMPrivateData(widget)); + if (inf == NULL) goto err; + + /** Get temporary string data. **/ + char* str_tmp = check_ptr(objDataToStringTmp(DATA_T_STRING, linkage, DATA_F_QUOTED)); + if (str_tmp == NULL) goto err; + + /** Dup string data. **/ + char* str = check_ptr(nmSysStrdup(str_tmp)); + if (str == NULL) goto err; + + /** Set string data. **/ + inf->ObjectLinkage = str; + + return 0; + + err: + mssError(1, "HTR", + "Failed to add object linkage \"%s\" to widget \"%s\":\"%s\".", + linkage, widget->Name, widget->Type + ); + return -1; } @@ -2417,12 +3085,45 @@ htrAddWgtrObjLinkage_va(pHtSession s, pWgtrNode widget, char* fmt, ...) { va_list va; char buf[256]; + int rval = -1, tmp; + pQPSession error_session = NULL; + + /** Create a pQPSession to track errors from qpfPrintf. **/ + error_session = check_ptr(qpfOpenSession()); + if (error_session == NULL) goto end; + /** Process the provided format. **/ va_start(va, fmt); - qpfPrintf_va(NULL, buf, sizeof(buf), fmt, va); + tmp = qpfPrintf_va(error_session, buf, sizeof(buf), fmt, va); va_end(va); - - return htrAddWgtrObjLinkage(s, widget, buf); + if (UNLIKELY(tmp < 0)) + { + mssError(1, "HTR", "qpfPrintf_va() failed to format: \"%s\"", fmt); + qpfLogErrors(error_session); + rval = tmp; + goto end; + } + + /** Add the linkage. **/ + tmp = htrAddWgtrObjLinkage(s, widget, buf); + if (UNLIKELY(tmp < 0)) goto end; + + /** Success. **/ + rval = 0; + + end: + if (rval != 0) + { + mssError(0, "HTR", + "Failed to add object linkage of format \"%s\" to widget \"%s\":\"%s\".", + fmt, widget->Name, widget->Type + ); + } + + /** Clean up. **/ + if (LIKELY(error_session != NULL)) qpfCloseSession(error_session); + + return rval; } @@ -2433,11 +3134,29 @@ htrAddWgtrObjLinkage_va(pHtSession s, pWgtrNode widget, char* fmt, ...) int htrAddWgtrCtrLinkage(pHtSession s, pWgtrNode widget, char* linkage) { - pHtDMPrivateData inf = htr_internal_CheckDMPrivateData(widget); - - inf->ContainerLinkage = nmSysStrdup(objDataToStringTmp(DATA_T_STRING, linkage, DATA_F_QUOTED)); + /** Get private data. **/ + pHtDMPrivateData inf = check_ptr(htr_internal_CheckDMPrivateData(widget)); + if (inf == NULL) goto err; + + /** Get temporary string data. **/ + char* str_tmp = check_ptr(objDataToStringTmp(DATA_T_STRING, linkage, DATA_F_QUOTED)); + if (str_tmp == NULL) goto err; + + /** Dup string data. **/ + char* str = check_ptr(nmSysStrdup(str_tmp)); + if (str == NULL) goto err; + + /** Set string data. **/ + inf->ContainerLinkage = str; + + return 0; - return 0; + err: + mssError(1, "HTR", + "Failed to add container linkage \"%s\" to widget \"%s\":\"%s\".", + linkage, widget->Name, widget->Type + ); + return -1; } @@ -2448,12 +3167,45 @@ htrAddWgtrCtrLinkage_va(pHtSession s, pWgtrNode widget, char* fmt, ...) { va_list va; char buf[256]; - + int rval = -1, tmp; + pQPSession error_session = NULL; + + /** Create a pQPSession to track errors from qpfPrintf. **/ + error_session = check_ptr(qpfOpenSession()); + if (error_session == NULL) goto end; + + /** Process the provided format. **/ va_start(va, fmt); - qpfPrintf_va(NULL, buf, sizeof(buf), fmt, va); + tmp = qpfPrintf_va(error_session, buf, sizeof(buf), fmt, va); va_end(va); - - return htrAddWgtrCtrLinkage(s, widget, buf); + if (UNLIKELY(tmp < 0)) + { + mssError(1, "HTR", "qpfPrintf_va() failed to format: \"%s\"", fmt); + qpfLogErrors(error_session); + rval = tmp; + goto end; + } + + /** Add the linkage. **/ + rval = htrAddWgtrCtrLinkage(s, widget, buf); + if (tmp < 0) goto end; + + /** Success. **/ + rval = 0; + + end: + if (rval != 0) + { + mssError(1, "HTR", + "Failed to add object linkage of format \"%s\" to widget \"%s\":\"%s\".", + fmt, widget->Name, widget->Type + ); + } + + /** Clean up. **/ + if (LIKELY(error_session != NULL)) qpfCloseSession(error_session); + + return rval; } @@ -2464,15 +3216,31 @@ htrAddWgtrInit(pHtSession s, pWgtrNode widget, char* func, char* paramfmt, ...) { va_list va; char buf[256]; - pHtDMPrivateData inf = htr_internal_CheckDMPrivateData(widget); + pHtDMPrivateData inf = check_ptr(htr_internal_CheckDMPrivateData(widget)); + if (UNLIKELY(inf == NULL)) goto err; + + /** Process format. **/ inf->InitFunc = func; va_start(va, paramfmt); vsnprintf(buf, sizeof(buf), paramfmt, va); va_end(va); - inf->Param = nmSysStrdup(objDataToStringTmp(DATA_T_STRING, buf, DATA_F_QUOTED)); - - return 0; + + /** Process string data. **/ + char* str_tmp = check_ptr(objDataToStringTmp(DATA_T_STRING, buf, DATA_F_QUOTED)); + if (str_tmp == NULL) goto err; + char* str = check_ptr(nmSysStrdup(str_tmp)); + if (str == NULL) goto err; + inf->Param = str; + + return 0; + + err: + mssError(1, "HTR", + "Failed to set widget initialization function to %s(%s) for widget \"%s\":\"%s\".", + func, paramfmt, widget->Name, widget->Type + ); + return -1; } @@ -2486,8 +3254,8 @@ htrAddNamespace(pHtSession s, pWgtrNode container, char* nspace, int is_subns) char* ptr; /** Allocate a new namespace **/ - new_ns = (pHtNamespace)nmMalloc(sizeof(HtNamespace)); - if (!new_ns) return -1; + new_ns = (pHtNamespace)check_ptr(nmMalloc(sizeof(HtNamespace))); + if (UNLIKELY(new_ns == NULL)) goto err; new_ns->Parent = s->Namespace; strtcpy(new_ns->DName, nspace, sizeof(new_ns->DName)); @@ -2512,7 +3280,20 @@ htrAddNamespace(pHtSession s, pWgtrNode container, char* nspace, int is_subns) nspace /*, nspace */); #endif - return 0; + return 0; + + err:; + char context[152]; + if (container != NULL) + snprintf(context, sizeof(context), + "to container widget \"%s\":\"%s\"", + container->Name, container->Type + ); + mssError(1, "HTR", + "Failed to add namespace \"%s\"%s.", + nspace, context + ); + return -1; } @@ -2680,22 +3461,139 @@ htrFormatElement(pHtSession s, pWgtrNode node, char* id, int flags, int x, int y shadow_angle = strtod(strval, NULL); /** Generate the style CSS **/ - htrAddStylesheetItem_va(s, "\t%STR { left:%POSpx; top:%POSpx; %[width:%POSpx; %]%[height:%POSpx; %]%[z-index:%POS; %]%[color:%STR&CSSVAL; %]%[font-weight:bold; %]%[text-decoration:underline; %]%[font-style:italic; %]%[font:%STR&CSSVAL; %]%[font-size:%DBLpx; %]%[background-color:%STR&CSSVAL; %]%[background-image:url('%STR&CSSURL'); %]%[padding:%DBLpx; %]%[border:1px %STR&CSSVAL %STR&CSSVAL; %]%[border-radius:%DBLpx; %]%[text-align:%STR&CSSVAL; %]%[white-space:nowrap; %]%[box-shadow:%DBLpx %DBLpx %DBLpx %STR&CSSVAL%STR&CSSVAL; %]%[%STR %]}\n", - id, - x, y, w > 0, w, h > 0, h, z > 0, z, - *textcolor, textcolor, - !strcmp(style, "bold"), !strcmp(style, "underline"), !strcmp(style, "italic"), - *font, font, font_size > 0, font_size, - *bgcolor, bgcolor, *background, background, - padding > 0, padding, - *border_color, (*border_style)?border_style:"solid", border_color, border_radius > 0, border_radius, - *align, align, - !wrap, - (*shadow_color && shadow_radius > 0), sin(shadow_angle*M_PI/180)*shadow_offset, cos(shadow_angle*M_PI/180)*(-shadow_offset), shadow_radius, shadow_color, (!strcasecmp(shadow_location,"inside"))?" inset":"", - addl && *addl, addl - ); + const int tmp = htrAddStylesheetItem_va(s, + "\t\t%STR { " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "%[width:"ht_flex_format"; %]" + "%[height:"ht_flex_format"; %]" + "%[z-index:%POS; %]" + "%[color:%STR&CSSVAL; %]" + "%[font-weight:bold; %]" + "%[text-decoration:underline; %]" + "%[font-style:italic; %]" + "%[font:%STR&CSSVAL; %]" + "%[font-size:%DBLpx; %]" + "%[background-color:%STR&CSSVAL; %]" + "%[background-image:url('%STR&CSSURL'); %]" + "%[padding:%DBLpx; %]" + "%[border:1px %STR&CSSVAL %STR&CSSVAL; %]" + "%[border-radius:%DBLpx; %]" + "%[text-align:%STR&CSSVAL; %]" + "%[white-space:nowrap; %]" + "%[box-shadow:" + "%DBLpx " + "%DBLpx " + "%DBLpx " + "%STR&CSSVAL%STR&CSSVAL; " + "%]" + "%[%STR %]" + "}\n", + id, + ht_flex_x(x, node), + ht_flex_y(y, node), + (w > 0), ht_flex_w(w, node), + (h > 0), ht_flex_h(h, node), + (z > 0), z, + (*textcolor), textcolor, + (strcmp(style, "bold") == 0), + (strcmp(style, "underline") == 0), + (strcmp(style, "italic") == 0), + (*font), font, + (font_size > 0), font_size, + (*bgcolor), bgcolor, + (*background), background, + (padding > 0), padding, + (*border_color), (*border_style) ? border_style : "solid", border_color, + (border_radius > 0), border_radius, + (*align), align, + (!wrap), + (*shadow_color && shadow_radius > 0), + sin(shadow_angle * M_PI/180) * shadow_offset, + cos(shadow_angle * M_PI/180) *(-shadow_offset), + shadow_radius, + shadow_color, (strcasecmp(shadow_location, "inside") == 0) ? " inset" : "", + (addl != NULL && addl[0] != '\0'), addl + ); + if (UNLIKELY(tmp != 0)) + { + mssError(0, "HTR", "Failed to write CSS while formatting element.\n"); + return -1; + } return 0; } +int +ht_get_parent_w__INTERNAL(pWgtrNode widget) + { + /** Guard segfault. **/ + if (UNLIKELY(widget == NULL)) + { + mssError(1, "HTR", "Failed to get width on NULL widget.\n"); + return -1; + } + + /** Check for a cached value. **/ + const int cached_value = widget->fl_parent_w; + if (LIKELY(cached_value != -1)) return cached_value; + + /** Recursion check. **/ + if (UNLIKELY(thExcessiveRecursion())) + { + mssError(1, "HTR", "Resource exhaustion in ht_get_parent_w__INTERNAL()"); + return 0; + } + + /** Check for a width value on the parent. **/ + const pWgtrNode parent = widget->Parent; + if (UNLIKELY(parent == NULL)) + { + mssError(1, "HTR", + "Failed to get parent width on widget \"%s\":\"%s\" because parent is NULL.\n", + widget->Name, widget->Type + ); + return -1; + } + const int parentWidth = parent->width; + return widget->fl_parent_w = (parentWidth >= 0) + ? parentWidth - (parent->left + parent->right) /* Width found! */ + : ht_get_parent_w__INTERNAL(parent); /* Width not found: search recursively. */ + } +int +ht_get_parent_h__INTERNAL(pWgtrNode widget) + { + /** Guard segfault. **/ + if (UNLIKELY(widget == NULL)) + { + mssError(1, "HTR", "Failed to get height on NULL widget.\n"); + return -1; + } + + /** Check for a cached value. **/ + const int cached_value = widget->fl_parent_h; + if (LIKELY(cached_value != -1)) return cached_value; + + /** Recursion check. **/ + if (UNLIKELY(thExcessiveRecursion())) + { + mssError(1, "HTR", "Resource exhaustion in ht_get_parent_h__INTERNAL()"); + return 0; + } + + /** Check for a height value on the parent. **/ + const pWgtrNode parent = widget->Parent; + if (UNLIKELY(parent == NULL)) + { + mssError(1, "HTR", + "Failed to get parent height on widget \"%s\":\"%s\" because parent is NULL.\n", + widget->Name, widget->Type + ); + return -1; + } + const int parentHeight = parent->height; + return widget->fl_parent_h = (parentHeight >= 0) + ? parentHeight - (parent->top + parent->bottom) /* Height found! */ + : ht_get_parent_h__INTERNAL(parent); /* Height not found: search recursively. */ + } diff --git a/centrallix/htmlgen/htdrv_alerter.c b/centrallix/htmlgen/htdrv_alerter.c index 216b2b979..873194058 100644 --- a/centrallix/htmlgen/htdrv_alerter.c +++ b/centrallix/htmlgen/htdrv_alerter.c @@ -14,7 +14,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -42,26 +42,14 @@ /************************************************************************/ -/** globals **/ -static struct - { - int idcnt; - } - HTALRT; - - /*** htalrtRender - generate the HTML code for the alert -- not much.. ***/ int htalrtRender(pHtSession s, pWgtrNode tree, int z) { - int id; - - /** Get an id for this. **/ - id = (HTALRT.idcnt++); /** Get name **/ - htrAddScriptInit_va(s," alrt_init(wgtrGetNodeRef(ns,\"%STR&SYM\"));\n", wgtrGetName(tree)); + htrAddScriptInit_va(s,"\talrt_init(wgtrGetNodeRef(ns, '%STR&SYM'));\n", wgtrGetName(tree)); htrAddScriptInclude(s,"/sys/js/htdrv_alerter.js",0); @@ -76,8 +64,6 @@ htalrtInitialize() { pHtDriver drv; - HTALRT.idcnt = 0; - /** Allocate the driver **/ drv = htrAllocDriver(); if (!drv) return -1; diff --git a/centrallix/htmlgen/htdrv_autolayout.c b/centrallix/htmlgen/htdrv_autolayout.c index 0dee2198b..7e3ead6db 100644 --- a/centrallix/htmlgen/htdrv_autolayout.c +++ b/centrallix/htmlgen/htdrv_autolayout.c @@ -14,7 +14,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2007 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -65,19 +65,17 @@ htalRender(pHtSession s, pWgtrNode tree, int z) { char name[64]; int x=-1,y=-1,w,h; - int id; - pWgtrNode subtree; - int i; - if(!s->Capabilities.Dom0NS && !(s->Capabilities.Dom1HTML && s->Capabilities.CSS1)) + /** Get an id for this. **/ + const int id = (HTAL.idcnt++); + + /** Verify browser capabilities. **/ + if (!s->Capabilities.Dom1HTML || !s->Capabilities.Dom2CSS) { - mssError(1,"HTAL","Netscape DOM or W3C DOM1 HTML and CSS support required"); - return -1; + mssError(1, "HTAL", "Unsupported browser: W3C DOM1 HTML and DOM2 CSS support required."); + goto err; } - /** Get an id for this. **/ - id = (HTAL.idcnt++); - /** Get x,y,w,h of this object. X and Y can be assumed to be zero if unset, ** but the wgtr Verify routine should have taken care of width/height for us ** if those were unspecified. Bark! @@ -87,63 +85,90 @@ htalRender(pHtSession s, pWgtrNode tree, int z) if (wgtrGetPropertyValue(tree,"width",DATA_T_INTEGER,POD(&w)) != 0) { mssError(1,"HTAL","Bark! Autolayout widget must have a 'width' property"); - return -1; + goto err; } if (wgtrGetPropertyValue(tree,"height",DATA_T_INTEGER,POD(&h)) != 0) { mssError(1,"HTAL","Bark! Autolayout widget must have a 'height' property"); - return -1; + goto err; } /** Get name **/ strtcpy(name,wgtrGetName(tree),sizeof(name)); /** Add the stylesheet for the layer **/ - htrAddStylesheetItem_va(s,"\t#al%POSbase { POSITION:absolute; VISIBILITY:inherit; OVERFLOW:visible; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; Z-INDEX:%POS; }\n", - //htrAddStylesheetItem_va(s,"\t#al%POSbase { POSITION:absolute; VISIBILITY:inherit; OVERFLOW:visible; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; CLIP:rect(%INTpx,%INTpx,%INTpx,%INTpx); Z-INDEX:%POS; }\n", - id,x,y,w,h, - //-1, w+1, h+1, -1, - z); + if (htrAddStylesheetItem_va(s, + "\t\t#al%POSbase { " + "position:absolute; " + "visibility:inherit; " + "overflow:visible; " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "width:"ht_flex_format"; " + "height:"ht_flex_format"; " + "z-index:%POS; " + "}\n ", + id, + ht_flex_x(x, tree), + ht_flex_y(y, tree), + ht_flex_w(w, tree), + ht_flex_h(h, tree), + z + ) != 0) + { + mssError(0, "HTAL", "Failed to write CSS."); + goto err; + } - /** Linkage **/ - htrAddWgtrObjLinkage_va(s, tree, "al%POSbase",id); + /** Link the widget to the DOM node. **/ + if (htrAddWgtrObjLinkage_va(s, tree, "al%POSbase", id) != 0) + { + mssError(0, "HTAL", "Failed to add object linkage."); + goto err; + } /** Script include call **/ - htrAddScriptInclude(s, "/sys/js/htdrv_autolayout.js", 0); + if (htrAddScriptInclude(s, "/sys/js/htdrv_autolayout.js", 0) != 0) goto err; /** Script initialization call. **/ - htrAddScriptInit_va(s, " al_init(wgtrGetNodeRef(ns,'%STR&SYM'), {});\n", name); + if (htrAddScriptInit_va(s, "\tal_init(wgtrGetNodeRef(ns, '%STR&SYM'), {});\n", name) != 0) + { + mssError(0, "HTAL", "Failed to write JS init call."); + goto err; + } /** Start of container **/ - htrAddBodyItemLayerStart(s, 0, "al%POSbase", id, NULL); + if (htrAddBodyItemLayerStart(s, 0, "al%POSbase", id, NULL) != 0) + { + mssError(0, "HTAL", "Failed to start body container."); + goto err; + } /** Check for objects within this autolayout widget. **/ - for (i=0;iChildren));i++) + const int n_children = xaCount(&(tree->Children)); + for (unsigned int i = 0u; i < n_children; i++) { - subtree = xaGetItem(&(tree->Children), i); + pWgtrNode subtree = xaGetItem(&(tree->Children), i); if (!strcmp(subtree->Type, "widget/autolayoutspacer")) subtree->RenderFlags |= HT_WGTF_NOOBJECT; - /* - else if(!strcmp(subtree->Type, "widget/repeat")) - { - for(rpti=0;rptiChildren));rpti++) - { - rptsubtree = xaGetItem(&(subtree->Children),rpti); - htrRenderWidget(s,rptsubtree, z+1); - //mssError(1,"HTAL","Found a subwidget to a repeat"); - } - htrRenderWidget(s, subtree, z+1); - }*/ - else - { - htrRenderWidget(s, subtree, z+1); - //mssError(1,"HTAL","Found a subwidget"); - } + else if (htrRenderWidget(s, subtree, z + 1) != 0) goto err; } + /** End of container **/ - htrAddBodyItemLayerEnd(s, 0); + if (htrAddBodyItemLayerEnd(s, 0) != 0) + { + mssError(0, "HTAL", "Failed to end body container."); + goto err; + } - return 0; + return 0; + + err: + mssError(0, "HTAL", + "Failed to render \"%s\":\"%s\" (id: %d).", + tree->Name, tree->Type, id + ); + return -1; } diff --git a/centrallix/htmlgen/htdrv_button.c b/centrallix/htmlgen/htdrv_button.c index 67439566e..d60f25255 100644 --- a/centrallix/htmlgen/htdrv_button.c +++ b/centrallix/htmlgen/htdrv_button.c @@ -1,3 +1,34 @@ +/************************************************************************/ +/* Centrallix Application Server System */ +/* Centrallix Core */ +/* */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ +/* */ +/* This program is free software; you can redistribute it and/or modify */ +/* it under the terms of the GNU General Public License as published by */ +/* the Free Software Foundation; either version 2 of the License, or */ +/* (at your option) any later version. */ +/* */ +/* This program is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU General Public License for more details. */ +/* */ +/* You should have received a copy of the GNU General Public License */ +/* along with this program; if not, write to the Free Software */ +/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA */ +/* 02111-1307 USA */ +/* */ +/* A copy of the GNU General Public License has been included in this */ +/* distribution in the file "COPYING". */ +/* */ +/* Module: htdrv_button.c */ +/* Author: dkasper */ +/* Creation: June 21, 2007 */ +/* Description: HTML Widget driver for a 'generic' button widget based */ +/* off of the imagebutton and textbutton widgets button. */ +/************************************************************************/ + #include #include #include @@ -38,38 +69,36 @@ htbtnRender(pHtSession s, pWgtrNode tree, int z) char c_img[128]; char d_img[128]; int x,y,w,h,spacing; - int id, i; int is_ts = 1; char* dptr; int is_enabled = 1; pExpression code; - int box_offset; - int clip_offset; - if(!s->Capabilities.Dom0NS && !s->Capabilities.Dom0IE && !(s->Capabilities.Dom1HTML && s->Capabilities.Dom2CSS)) + /** Get an id for this. **/ + const int id = (HTBTN.idcnt++); + + /** Verify browser capabilities. **/ + if (!s->Capabilities.Dom1HTML || !s->Capabilities.Dom2CSS) { - mssError(1,"HTBTN","Netscape DOM or (W3C DOM1 HTML and W3C DOM2 CSS) support required"); - return -1; + mssError(1, "HTBTN", "Unsupported browser: W3C DOM1 HTML and DOM2 CSS support required."); + goto err; } - /** Get an id for this. **/ - id = (HTBTN.idcnt++); - /** Get x,y,w,h of this object **/ if (wgtrGetPropertyValue(tree,"x",DATA_T_INTEGER,POD(&x)) != 0) { mssError(1,"HTBTN","Button widget must have an 'x' property"); - return -1; + goto err; } if (wgtrGetPropertyValue(tree,"y",DATA_T_INTEGER,POD(&y)) != 0) { mssError(1,"HTBTN","Button widget must have a 'y' property"); - return -1; + goto err; } if (wgtrGetPropertyValue(tree,"width",DATA_T_INTEGER,POD(&w)) != 0) { mssError(1,"HTBTN","Button widget must have a 'width' property"); - return -1; + goto err; } if (wgtrGetPropertyValue(tree,"height",DATA_T_INTEGER,POD(&h)) != 0) h = -1; @@ -79,16 +108,16 @@ htbtnRender(pHtSession s, pWgtrNode tree, int z) } /** Get name **/ - if (wgtrGetPropertyValue(tree,"name",DATA_T_STRING,POD(&ptr)) != 0) return -1; + if (wgtrGetPropertyValue(tree,"name",DATA_T_STRING,POD(&ptr)) != 0) goto err; strtcpy(name,ptr,sizeof(name)); - if (wgtrGetPropertyValue(tree,"type",DATA_T_STRING,POD(&ptr)) != 0) return -1; + if (wgtrGetPropertyValue(tree,"type",DATA_T_STRING,POD(&ptr)) != 0) goto err; strtcpy(type,ptr,sizeof(type)); /* if not a text only button */ if(strcmp(type,"text")) { - if (wgtrGetPropertyValue(tree,"image",DATA_T_STRING,POD(&ptr)) != 0) return -1; + if (wgtrGetPropertyValue(tree,"image",DATA_T_STRING,POD(&ptr)) != 0) goto err; strtcpy(n_img,ptr,sizeof(n_img)); } @@ -114,7 +143,7 @@ htbtnRender(pHtSession s, pWgtrNode tree, int z) if (wgtrGetPropertyValue(tree,"text",DATA_T_STRING,POD(&ptr)) != 0) { mssError(1,"HTBTN","Button widget must have a 'text' property"); - return -1; + goto err; } strtcpy(text,ptr,sizeof(text)); @@ -145,259 +174,384 @@ htbtnRender(pHtSession s, pWgtrNode tree, int z) is_enabled = 0; htrAddExpression(s, name, "enabled", code); } - - /* image only button - based on imagebutton */ - if (!strcmp(type,"image") || !strcmp(type,"textoverimage")) + /** Include the JS for the button. **/ + if (htrAddScriptGlobal(s, "gb_cur_img", "null", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "gb_current", "null", 0) != 0) goto err; + if (htrAddScriptInclude(s, "/sys/js/ht_utils_layers.js", 0) != 0) goto err; + if (htrAddScriptInclude(s, "/sys/js/htdrv_button.js", 0) != 0) goto err; + if (htrAddWgtrObjLinkage_va(s, tree, "gb%POSpane", id) != 0) + { + mssError(0, "HTBTN", "Failed to add object linkage."); + goto err; + } + dptr = wgtrGetDName(tree); + if (htrAddScriptInit_va(s, "\t%STR&SYM = wgtrGetNodeRef(ns, '%STR&SYM');\n", dptr, name) != 0) + { + mssError(0, "HTBTN", "Failed to write JS."); + goto err; + } + + /** Write the button container. **/ + if (htrAddBodyItem_va(s, "
\n", id) != 0) + { + mssError(0, "HTBTN", "Failed to write HTML to open button pane."); + goto err; + } + + /* image only button - based on imagebutton */ + if (strcmp(type, "image") == 0 || strcmp(type, "textoverimage") == 0) + { + /** Button styles. **/ + if (htrAddStylesheetItem_va(s, + "\t\t#gb%POSpane { " + "position:absolute; " + "visibility:inherit; " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "width:"ht_flex_format"; " + "z-index:%POS; " + "}\n", + id, + ht_flex_x(x, tree), + ht_flex_y(y, tree), + ht_flex_w(w, tree), + z + ) != 0) { - htrAddStylesheetItem_va(s,"\t#gb%POSpane { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; Z-INDEX:%POS; }\n",id,x,y,w,z); - - htrAddScriptGlobal(s, "gb_cur_img", "null", 0); - htrAddScriptGlobal(s, "gb_current", "null", 0); - htrAddWgtrObjLinkage_va(s, tree, "gb%POSpane", id); - - /** User requesting expression for enabled? **/ - if (wgtrGetPropertyType(tree,"enabled") == DATA_T_CODE) + mssError(0, "HTBTN", "Failed to write CSS."); + goto err; + } + + /** Button click animation. **/ + if (is_enabled) + { + if (htrAddStylesheetItem_va(s, + "\t\t#tb%POSpane:active { " + "transform:translate(1px, 1px); " + "}\n", + id + ) != 0) { - wgtrGetPropertyValue(tree,"enabled",DATA_T_CODE,POD(&code)); - is_enabled = 0; - htrAddExpression(s, name, "enabled", code); + mssError(0, "HTBTN", "Failed to write CSS click animation."); + goto err; } - /** Widget Tree Stuff **/ - dptr = wgtrGetDName(tree); - htrAddScriptInit_va(s, " %STR&SYM = wgtrGetNodeRef(ns,'%STR&SYM');\n", dptr, name); - - if(!strcmp(type,"image")) htrAddScriptInit_va(s," gb_init({layer:%STR&SYM, n:'%STR&JSSTR', p:'%STR&JSSTR', c:'%STR&JSSTR', d:'%STR&JSSTR', width:%INT, height:%INT, name:'%STR&SYM', enable:%INT, type:'%STR&JSSTR', text:'%STR&JSSTR'});\n", dptr, n_img, p_img, c_img, d_img, w, h, name,is_enabled,type,text); - /* text over image needs second layer */ - else htrAddScriptInit_va(s," gb_init({layer:\"%STR&SYM\", layer2:htr_subel(%STR&SYM, \"gb%POSpane2\"), n:'%STR&JSSTR', p:'%STR&JSSTR', c:'%STR&JSSTR', d:'%STR&JSSTR', width:%INT, height:%INT, name:'%STR&SYM', enable:%INT, type:'%STR&JSSTR', text:'%STR&JSSTR'});\n", dptr, dptr, id, n_img, p_img, c_img, d_img, w, h, name,is_enabled,type,text); - - /** Include the javascript code for the button **/ - htrAddScriptInclude(s, "/sys/js/ht_utils_layers.js", 0); - htrAddScriptInclude(s, "/sys/js/htdrv_button.js", 0); - - /** HTML body
elements for the layers. **/ - if(!strcmp(type,"image")||!strcmp(type,"textoverimage")) + } + + /** HTML body
elements for the layers. **/ + if (htrAddBodyItem_va(s, + "\n", + (is_enabled) ? n_img : d_img, (h >= 0), w, h + ) != 0) + { + mssError(0, "HTBTN", + "Failed to write HTML for %sabled image button.", + (is_enabled) ? "en" : "dis" + ); + goto err; + } + + /* text over image */ + if(!strcmp(type,"textoverimage")) + { + if (htrAddStylesheetItem_va(s, + "\t\t#gb%POSpane2 { " + "position:absolute; " + "visibility:inherit; " + "left:0px; " + "top:0px; " + "width:"ht_flex_format"; " + "z-index:%POS; " + "}\n", + id, + ht_flex_w(w, tree), + z + 1 + ) != 0) { - if (h < 0) - if(is_enabled) - htrAddBodyItem_va(s,"
\n",id,n_img); - else - htrAddBodyItem_va(s,"
\n",id,d_img); - else - if(is_enabled) - htrAddBodyItem_va(s,"
\n",id,n_img,w,h); - else - htrAddBodyItem_va(s,"
\n",id,d_img,w,h); + mssError(0, "HTBTN", "Failed to write CSS for text over image button."); + goto err; + } + + if (htrAddBodyItem_va(s, + "
%STR&HTE
\n", + id, fgcolor1, text + ) != 0) + { + mssError(0, "HTBTN", "Failed to write pane HTML."); + goto err; } - /* text over image */ - if(!strcmp(type,"textoverimage")) - { - htrAddStylesheetItem_va(s,"\t#gb%POSpane2 { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; Z-INDEX:%POS; }\n",id,0,0,w,z+1); - htrAddBodyItem_va(s,"
%STR&HTE
\n",id,h,fgcolor1,text); - } - else - htrAddBodyItem_va(s,"
"); } - - /* other types of buttons - based on textbutton */ - - else - - { - /** box adjustment... arrgh **/ - if (s->Capabilities.CSSBox) - box_offset = 1; - else - box_offset = 0; - clip_offset = s->Capabilities.CSSClip?1:0; - - htrAddScriptGlobal(s, "gb_current", "null", 0); - - /** DOM Linkages **/ - htrAddWgtrObjLinkage_va(s, tree, "gb%POSpane",id); - /** Include the javascript code for the button **/ - htrAddScriptInclude(s, "/sys/js/ht_utils_layers.js", 0); - htrAddScriptInclude(s, "/sys/js/htdrv_button.js", 0); - - dptr = wgtrGetDName(tree); - htrAddScriptInit_va(s, " %STR&SYM = wgtrGetNodeRef(ns,'%STR&SYM');\n", dptr, name); - - if(s->Capabilities.Dom0NS) + /** Write the init script. **/ + /** Note: We only need to specify layer2 for text-over-image. **/ + if (htrAddScriptInit_va(s, + "\tgb_init({ " + "layer:%STR&SYM, " + "%[layer2:htr_subel(%STR&SYM, 'gb%POSpane2'), %]" + "n:'%STR&JSSTR', " + "p:'%STR&JSSTR', " + "c:'%STR&JSSTR', " + "d:'%STR&JSSTR', " + "width:%INT, " + "height:%INT, " + "name:'%STR&SYM', " + "enable:%INT, " + "type:'%STR&JSSTR', " + "text:'%STR&JSSTR', " + "});\n", + dptr, + (strcmp(type, "image") != 0), dptr, id, + n_img, p_img, c_img, d_img, + w, h, name, is_enabled, type, text + ) != 0) + { + mssError(0, "HTBTN", "Failed to write JS init call."); + goto err; + } + } + else /* other types of buttons - based on textbutton */ + { + /** Write common CSS for all three panes. **/ + if (htrAddStylesheetItem_va(s, + "\t\t#gb%POSpane, #gb%POSpane1, #gb%POSpane2, #gb%POSpane3 { " + "position:absolute; " + "%[cursor:pointer; %]" + "text-align:center; " + "display:flex; " + "justify-content:center; " + "font-weight:700; " + "}\n", + id, id, id, id, + is_enabled + ) != 0) + { + mssError(0, "HTBTN", "Failed to write CSS."); + goto err; + } + + /** Write unique CSS each pane individually. **/ + if (htrAddStylesheetItem_va(s, + "\t\t#gb%POSpane { " + "visibility:inherit; " + "overflow:hidden; " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "width:"ht_flex_format"; " + "z-index:%POS; " + "border-width:1px; " + "border-style:solid; " + "border-color:white gray gray white; " + "%STR " + "}\n", + id, + ht_flex_x(x, tree), + ht_flex_y(y, tree), + ht_flex_w(w - 3, tree), + z, + bgstyle + ) != 0) + { + mssError(0, "HTBTN", "Failed to write CSS."); + goto err; + } + if (htrAddStylesheetItem_va(s, + "\t\t#gb%POSpane1 { " + "color:%STR&HTE; " + "}\n", + id, + fgcolor2 + ) != 0) + { + mssError(0, "HTBTN", "Failed to write CSS."); + goto err; + } + if (htrAddStylesheetItem_va(s, + "\t\t#gb%POSpane2 { " + "visibility:%STR; " + "left:-1px; " + "top:-1px; " + "width:"ht_flex_format"; " + "color:%STR&HTE; " + "z-index:%INT; " + "}\n", + id, + (is_enabled) ? "inherit" : "hidden", + ht_flex_w(w - 3, tree), + fgcolor1, + z + 1 + ) != 0) + { + mssError(0, "HTBTN", "Failed to write CSS."); + goto err; + } + if (htrAddStylesheetItem_va(s, + "\t\t#gb%POSpane3 { " + "visibility:%STR; " + "left:0px; " + "top:0px; " + "width:"ht_flex_format"; " + "color:%STR&HTE; " + "z-index:%INT; " + "}\n", + id, + (is_enabled) ? "hidden" : "inherit", + ht_flex_w(w - 3, tree), + disable_color, + z + 1 + ) != 0) + { + mssError(0, "HTBTN", "Failed to write CSS."); + goto err; + } + + /** Write CSS heights, if specified. **/ + if (h >= 0 && htrAddStylesheetItem_va(s, + "\t\t#gb%POSpane #gb%POSpane1, #gb%POSpane2, #gb%POSpane3 { height: "ht_flex_format"; }\n", + id, id, id, id, + ht_flex_h(h - 3, tree) + ) != 0) + { + mssError(0, "HTBTN", "Failed to write CSS."); + goto err; + } + + if (strcmp(type, "text") == 0) + { + if (htrAddBodyItem_va(s, + "
%STR&HTE
\n" + "
%STR&HTE
\n" + "
%STR&HTE
\n", + id, text, + id, text, + id, text + ) != 0) { - /** Ok, write the style header items. **/ - htrAddStylesheetItem_va(s,"\t#gb%POSpane { POSITION:absolute; VISIBILITY:inherit; LEFT:%INT; TOP:%INT; WIDTH:%POS; Z-INDEX:%POS; }\n",id,x,y,w,z); - htrAddStylesheetItem_va(s,"\t#gb%POSpane2 { POSITION:absolute; VISIBILITY:%STR; LEFT:-1; TOP:-1; WIDTH:%POS; Z-INDEX:%POS; }\n",id,is_enabled?"inherit":"hidden",w-1,z+1); - htrAddStylesheetItem_va(s,"\t#gb%POSpane3 { POSITION:absolute; VISIBILITY:%STR; LEFT:0; TOP:0; WIDTH:%POS; Z-INDEX:%POS; }\n",id,is_enabled?"hidden":"inherit",w-1,z+1); - htrAddStylesheetItem_va(s,"\t#gb%POStop { POSITION:absolute; VISIBILITY:%STR; LEFT:0; TOP:0; HEIGHT:1; WIDTH:%POS; Z-INDEX:%POS; }\n",id,is_ts?"hidden":"inherit",w,z+2); - htrAddStylesheetItem_va(s,"\t#gb%POSbtm { POSITION:absolute; VISIBILITY:%STR; LEFT:0; TOP:0; HEIGHT:1; WIDTH:%POS; Z-INDEX:%POS; }\n",id,is_ts?"hidden":"inherit",w,z+2); - htrAddStylesheetItem_va(s,"\t#gb%POSrgt { POSITION:absolute; VISIBILITY:%STR; LEFT:0; TOP:0; HEIGHT:1; WIDTH:1; Z-INDEX:%POS; }\n",id,is_ts?"hidden":"inherit",z+2); - htrAddStylesheetItem_va(s,"\t#gb%POSlft { POSITION:absolute; VISIBILITY:%STR; LEFT:0; TOP:0; HEIGHT:1; WIDTH:1; Z-INDEX:%POS; }\n",id,is_ts?"hidden":"inherit",z+2); - - /** Script initialization call. **/ - htrAddScriptInit_va(s, " gb_init({layer:%STR&SYM, layer2:htr_subel(%STR&SYM, \"gb%POSpane2\"), layer3:htr_subel(%STR&SYM, \"gb%POSpane3\"), top:htr_subel(%STR&SYM, \"gb%POStop\"), bottom:htr_subel(%STR&SYM, \"gb%POSbtm\"), right:htr_subel(%STR&SYM, \"gb%POSrgt\"), left:htr_subel(%STR&SYM, \"gb%POSlft\"), width:%INT, height:%INT, tristate:%INT, name:\"%STR&SYM\", text:'%STR&JSSTR', n:\"%STR&JSSTR\", p:\"%STR&JSSTR\", c:\"%STR&JSSTR\", d:\"%STR&JSSTR\", type:\"%STR&JSSTR\"});\n", - dptr, dptr, id, dptr, id, dptr, id, dptr, id, dptr, id, dptr, id, w, h, is_ts, name, text,n_img,p_img,c_img,d_img,type); - - /** HTML body
elements for the layers. **/ - if (h >= 0) - { - if(!strcmp(type,"text")) - { - htrAddBodyItem_va(s,"
%STR&HTE
\n",id,bgcolor,w,fgcolor2,text,h); - htrAddBodyItem_va(s, "
%STR&HTE
\n
",id,w,fgcolor1,text,h); - htrAddBodyItem_va(s, "
%STR&HTE
\n
",id,w,disable_color,text,h); - } - else if(!strcmp(type,"image")) - { - htrAddBodyItem_va(s,"
\n",id,bgcolor,w,n_img,w,h,h); - htrAddBodyItem_va(s, "
\n
",id,w,n_img,w,h,h); - htrAddBodyItem_va(s, "
\n
",id,w,n_img,w,h,h); - } - else if(!strcmp(type,"topimage")) - { - htrAddBodyItem_va(s,"

%STR&HTE
\n",id,bgcolor,w,n_img,fgcolor2,text,h); - htrAddBodyItem_va(s, "

%STR&HTE
\n
",id,w,n_img,fgcolor1,text,h); - htrAddBodyItem_va(s, "

%STR&HTE
\n
",id,w,n_img,disable_color,text,h); - } - else if(!strcmp(type,"bottomimage")) - { - htrAddBodyItem_va(s,"
%STR&HTE
\n",id,bgcolor,w,fgcolor2,text,n_img,h); - htrAddBodyItem_va(s, "
%STR&HTE
\n
",id,w,fgcolor1,text,n_img,h); - htrAddBodyItem_va(s, "
%STR&HTE
\n
",id,w,disable_color,text,n_img,h); - } - else if(!strcmp(type,"leftimage")) - { - htrAddBodyItem_va(s,"
%STR&HTE
\n",id,spacing,bgcolor,w,n_img,fgcolor2,text,h); - htrAddBodyItem_va(s, "
%STR&HTE
\n
",id,spacing,w,n_img,fgcolor1,text,h); - htrAddBodyItem_va(s, "
%STR&HTE
\n
",id,spacing,w,n_img,disable_color,text,h); - } - else if(!strcmp(type,"rightimage")) - { - htrAddBodyItem_va(s,"
%STR&HTE
\n",id,spacing,bgcolor,w,fgcolor2,text,n_img,h); - htrAddBodyItem_va(s, "
%STR&HTE
\n
",id,spacing,w,fgcolor1,text,n_img,h); - htrAddBodyItem_va(s, "
%STR&HTE
\n
",id,spacing,w,disable_color,text,n_img,h); - } - else if(!strcmp(type,"textoverimage")) - { - htrAddBodyItem_va(s,"
\n",id,bgcolor,w,n_img,w,h,h); - htrAddBodyItem_va(s, "
\n
",id,w,n_img,w,h,h); - htrAddBodyItem_va(s, "
\n
",id,w,n_img,w,h,h); - htrAddBodyItem_va(s, "

%STR&HTE

",id,fgcolor1,text); - } - else - { - htrAddBodyItem_va(s,"
%STR&HTE
\n",id,bgcolor,w,fgcolor2,text,h); - htrAddBodyItem_va(s, "
%STR&HTE
\n
",id,w,fgcolor1,text,h); - htrAddBodyItem_va(s, "
%STR&HTE
\n
",id,w,disable_color,text,h); - } - } - else - { - htrAddBodyItem_va(s,"
%STR&HTE
\n",id,bgcolor,w,fgcolor2,text); - htrAddBodyItem_va(s,"
%STR&HTE
\n
",id,w,fgcolor1,text); - htrAddBodyItem_va(s,"
%STR&HTE
\n
",id,w,disable_color,text); - } - htrAddBodyItem_va(s,"
\n",id,w); - htrAddBodyItem_va(s,"
\n",id,w); - htrAddBodyItem_va(s,"
\n",id,(h<0)?1:h); - htrAddBodyItem_va(s,"
\n",id,(h<0)?1:h); - htrAddBodyItem(s, "
\n"); + mssError(0, "HTBTN", "Failed to write pane HTML."); + goto err; } - else if(s->Capabilities.CSS2) + } + else if (strcmp(type, "topimage") == 0) + { + if (htrAddBodyItem_va(s, + "
top img%STR&HTE
\n" + "
top img%STR&HTE
\n" + "
top img%STR&HTE
\n", + id, n_img, text, + id, n_img, text, + id, n_img, text + ) != 0) { - if(h >=0 ) - { - htrAddStylesheetItem_va(s,"\t#gb%POSpane { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; Z-INDEX:%POS; OVERFLOW:hidden; clip:rect(%INTpx %INTpx %INTpx %INTpx)}\n",id,x,y,w-1-2*box_offset,z,0,w-1-2*box_offset+2*clip_offset,h-1-2*box_offset+2*clip_offset,0); - htrAddStylesheetItem_va(s,"\t#gb%POSpane2, #gb%POSpane3 { height: %POSpx;}\n",id,id,h-3); - htrAddStylesheetItem_va(s,"\t#gb%POSpane { height: %POSpx;}\n",id,h-1-2*box_offset); - } - else - { - htrAddStylesheetItem_va(s,"\t#gb%POSpane { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; Z-INDEX:%POS; OVERFLOW:hidden; clip:rect(%INTpx %INTpx auto %INTpx)}\n",id,x,y,w-1-2*box_offset,z,0,w-1-2*box_offset+2*clip_offset,0); - } - htrAddStylesheetItem_va(s,"\t#gb%POSpane, #gb%POSpane2, #gb%POSpane3 { cursor:default; text-align: center; }\n",id,id,id); - htrAddStylesheetItem_va(s,"\t#gb%POSpane { %STR border-width: 1px; border-style: solid; border-color: white gray gray white; }\n",id,bgstyle); - /*htrAddStylesheetItem_va(s,"\t#gb%dpane { color: %s; }\n",id,fgcolor2);*/ - htrAddStylesheetItem_va(s,"\t#gb%POSpane2 { VISIBILITY: %STR; Z-INDEX: %INT; position: absolute; left:-1px; top: -1px; width:%POSpx; }\n",id,is_enabled?"inherit":"hidden",z+1,w-3); - htrAddStylesheetItem_va(s,"\t#gb%POSpane3 { VISIBILITY: %STR; Z-INDEX: %INT; position: absolute; left:0px; top: 0px; width:%POSpx; }\n",id,is_enabled?"hidden":"inherit",z+1,w-3); - - if(!strcmp(type,"text")) - { - htrAddBodyItem_va(s,"
%STR&HTE
\n",id,h-3,fgcolor2,text); - htrAddBodyItem_va(s,"
%STR&HTE
\n",id,h-3,fgcolor1,text); - htrAddBodyItem_va(s,"
%STR&HTE
\n",id,h-3,disable_color,text); - } - else if(!strcmp(type,"image")) - { - /* working image htrAddBodyItem_va(s,"
\n",id,h-3,n_img,w,h-3); - htrAddBodyItem_va(s,"
\n",id,h-3,n_img,w,h-3); - htrAddBodyItem_va(s,"
\n",id,h-3,n_img,w,h-3); */ - } - else if(!strcmp(type,"topimage")) - { - htrAddBodyItem_va(s,"
%STR&HTE
\n",id,h-3,n_img,fgcolor2,text); - htrAddBodyItem_va(s,"
%STR&HTE
\n",id,h-3,n_img,fgcolor1,text); - htrAddBodyItem_va(s,"
%STR&HTE
\n",id,h-3,n_img,disable_color,text); - } - else if(!strcmp(type,"bottomimage")) - { - htrAddBodyItem_va(s,"
%STR&HTE
\n",id,h-3,fgcolor2,text,n_img); - htrAddBodyItem_va(s,"
%STR&HTE
\n",id,h-3,fgcolor1,text,n_img); - htrAddBodyItem_va(s,"
%STR&HTE
\n",id,h-3,disable_color,text,n_img); - } - else if(!strcmp(type,"leftimage")) - { - htrAddBodyItem_va(s,"
%STR&HTE
\n",id,spacing,h-3,n_img,h-3,fgcolor2,text); - htrAddBodyItem_va(s,"
%STR&HTE
\n",id,spacing,h-3,n_img,h-3,fgcolor1,text); - htrAddBodyItem_va(s,"
%STR&HTE
\n",id,spacing,h-3,n_img,h-3,disable_color,text); - } - else if(!strcmp(type,"rightimage")) - { - htrAddBodyItem_va(s,"
%STR&HTE
\n",id,spacing,h-3,fgcolor2,text,h-3,n_img); - htrAddBodyItem_va(s,"
%STR&HTE
\n",id,spacing,h-3,fgcolor1,text,h-3,n_img); - htrAddBodyItem_va(s,"
%STR&HTE
\n",id,spacing,h-3,disable_color,text,h-3,n_img); - } - else if(!strcmp(type,"textoverimage")) - { - //htrAddBodyItem_va(s,"
%STR&HTE
\n",id,fgcolor2,text,h-3,n_img,w,h-3); - //htrAddBodyItem_va(s,"
%STR&HTE
\n",id,fgcolor2,text,h-3,n_img,w,h-3); - //htrAddBodyItem_va(s,"
%STR&HTE
\n",id,fgcolor2,text,h-3,n_img,w,h-3); - } - else - { - htrAddBodyItem_va(s,"
default text
\n",id,h-3,fgcolor2); - htrAddBodyItem_va(s,"
default text
\n",id,h-3,fgcolor1); - htrAddBodyItem_va(s,"
default text
\n",id,h-3,disable_color); - } - htrAddBodyItem(s, "
"); - - /** Script initialization call. **/ - htrAddScriptInit_va(s, " gb_init({layer:%STR&SYM, layer2:htr_subel(%STR&SYM, \"gb%POSpane2\"), layer3:htr_subel(%STR&SYM, \"gb%POSpane3\"), top:null, bottom:null, right:null, left:null, width:%INT, height:%INT, tristate:%INT, name:\"%STR&SYM\", text:'%STR&JSSTR', n:'%STR&JSSTR', p:'%STR&JSSTR', c:'%STR&JSSTR', d:'%STR&JSSTR', type:'%STR&JSSTR'});\n", - dptr, dptr, id, dptr, id, w, h, is_ts, name, text, n_img, p_img, c_img, d_img,type); + mssError(0, "HTBTN", "Failed to write pane HTML."); + goto err; } - else + } + else if (strcmp(type, "bottomimage") == 0) + { + if (htrAddBodyItem_va(s, + "
%STR&HTEbottom img
\n" + "
%STR&HTEbottom img
\n" + "
%STR&HTEbottom img
\n", + id, text, n_img, + id, text, n_img, + id, text, n_img + ) != 0) + { + mssError(0, "HTBTN", "Failed to write pane HTML."); + goto err; + } + } + else if (strcmp(type, "leftimage") == 0) + { + if (htrAddBodyItem_va(s, + "
top img%STR&HTE
\n" + "
top img%STR&HTE
\n" + "
top img%STR&HTE
\n", + id, n_img, text, + id, n_img, text, + id, n_img, text + ) != 0) + { + mssError(0, "HTBTN", "Failed to write pane HTML."); + goto err; + } + } + else if (strcmp(type, "rightimage") == 0) + { + if (htrAddBodyItem_va(s, + "
%STR&HTEbottom img
\n" + "
%STR&HTEbottom img
\n" + "
%STR&HTEbottom img
\n", + id, text, n_img, + id, text, n_img, + id, text, n_img + ) != 0) { - mssError(0,"HTBTN","Unable to render for this browser"); - return -1; + mssError(0, "HTBTN", "Failed to write pane HTML."); + goto err; } } + else + { + mssError(1, "HTBTN", "Unknown button type \"%s\".", type); + goto err; + } + + /** Script initialization call. **/ + if (htrAddScriptInit_va(s, + "\tgb_init({ " + "layer:%STR&SYM, " + "layer2:htr_subel(%STR&SYM, 'gb%POSpane2'), " + "layer3:htr_subel(%STR&SYM, 'gb%POSpane3'), " + "top:null, " + "bottom:null, " + "right:null, " + "left:null, " + "width:%INT, " + "height:%INT, " + "tristate:%INT, " + "name:'%STR&SYM', " + "text:'%STR&JSSTR', " + "n:'%STR&JSSTR', " + "p:'%STR&JSSTR', " + "c:'%STR&JSSTR', " + "d:'%STR&JSSTR', " + "type:'%STR&JSSTR', " + "});\n", + dptr, dptr, id, dptr, id, + w, h, is_ts, name, text, + n_img, p_img, c_img, d_img, type + ) != 0) + { + mssError(0, "HTBTN", "Failed to write JS init call."); + goto err; + } + } + + /** Close the button container. **/ + if (htrAddBodyItem(s, "
") != 0) + { + mssError(0, "HTBTN", "Failed to write HTML closing tag."); + goto err; + } /** Add the event handling scripts **/ - htrAddEventHandlerFunction(s, "document", "MOUSEDOWN", "gb", "gb_mousedown"); - htrAddEventHandlerFunction(s, "document", "MOUSEUP", "gb", "gb_mouseup"); - htrAddEventHandlerFunction(s, "document", "MOUSEOVER", "gb", "gb_mouseover"); - htrAddEventHandlerFunction(s, "document", "MOUSEOUT", "gb", "gb_mouseout"); - htrAddEventHandlerFunction(s, "document", "MOUSEMOVE", "gb", "gb_mousemove"); - - /** IE handles dblclick strangely **/ - if (s->Capabilities.Dom0IE) - htrAddEventHandlerFunction(s, "document", "DBLCLICK", "gb", "gb_dblclick"); - - /** Check for more sub-widgets within the button. **/ - for (i=0;iChildren));i++) - htrRenderWidget(s, xaGetItem(&(tree->Children), i), z+3); - - return 0; + if (htrAddEventHandlerFunction(s, "document", "MOUSEDOWN", "gb", "gb_mousedown") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEMOVE", "gb", "gb_mousemove") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEOUT", "gb", "gb_mouseout") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEOVER", "gb", "gb_mouseover") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEUP", "gb", "gb_mouseup") != 0) goto err; + + /** Render children. **/ + if (htrRenderSubwidgets(s, tree, z + 3) != 0) goto err; + + return 0; + + err: + mssError(0, "HTBTN", + "Failed to render \"%s\":\"%s\" (id: %d).", + tree->Name, tree->Type, id + ); + return -1; } diff --git a/centrallix/htmlgen/htdrv_calendar.c b/centrallix/htmlgen/htdrv_calendar.c index 3605135fd..f84e0d13a 100644 --- a/centrallix/htmlgen/htdrv_calendar.c +++ b/centrallix/htmlgen/htdrv_calendar.c @@ -14,7 +14,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2003 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -71,25 +71,24 @@ htcaRender(pHtSession s, pWgtrNode tree, int z) char eventpriofield[32] = ""; int minpriority=0; int x=-1,y=-1,w,h; - int id, i; - /** Verify user-agent's capabilities allow us to continue... **/ - if(!s->Capabilities.Dom0NS && !(s->Capabilities.Dom1HTML && s->Capabilities.CSS1)) + /** Get an id for this. **/ + const int id = (HTCA.idcnt++); + + /** Verify browser capabilities. **/ + if (!s->Capabilities.Dom1HTML || !s->Capabilities.Dom2CSS) { - mssError(1,"HTCA","Netscape 4.x DOM support or W3C HTML DOM1/CSS1 support required"); - return -1; + mssError(1, "HTCA", "Unsupported browser: W3C DOM1 HTML and DOM2 CSS support required."); + goto err; } - /** Get an id for this. **/ - id = (HTCA.idcnt++); - /** Get x,y,w,h of this object **/ if (wgtrGetPropertyValue(tree,"x",DATA_T_INTEGER,POD(&x)) != 0) x=0; if (wgtrGetPropertyValue(tree,"y",DATA_T_INTEGER,POD(&y)) != 0) y=0; if (wgtrGetPropertyValue(tree,"width",DATA_T_INTEGER,POD(&w)) != 0) { mssError(1,"HTCA","Calendar widget must have a 'width' property"); - return -1; + goto err; } if (wgtrGetPropertyValue(tree,"height",DATA_T_INTEGER,POD(&h)) != 0) h = 0; @@ -141,36 +140,88 @@ htcaRender(pHtSession s, pWgtrNode tree, int z) strtcpy(name,ptr,sizeof(name)); /** Ok, write the style header items. **/ - htrAddStylesheetItem_va(s,"\t#ca%POSbase { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; Z-INDEX:%POS; }\n",id,x,y,w,h,z); + if (htrAddStylesheetItem_va(s, + "\t\t#ca%POSbase { " + "position:absolute; " + "visibility:inherit; " + "left:%INTpx; " + "top:%INTpx; " + "width:%POSpx; " + "height:%POSpx; " + "z-index:%POS; " + "}\n", + id, x, y, w, h, z + ) != 0) + { + mssError(0, "HTCA", "Failed to write CSS init call."); + goto err; + } /** Script include to get functions **/ - htrAddScriptInclude(s, "/sys/js/htdrv_calendar.js", 0); - htrAddScriptInclude(s, "/sys/js/ht_utils_string.js", 0); + if (htrAddScriptInclude(s, "/sys/js/ht_utils_string.js", 0) != 0) goto err; + if (htrAddScriptInclude(s, "/sys/js/htdrv_calendar.js", 0) != 0) goto err; - htrAddEventHandlerFunction(s, "document","MOUSEUP", "ca", "ca_mouseup"); - htrAddEventHandlerFunction(s, "document","MOUSEDOWN", "ca", "ca_mousedown"); - htrAddEventHandlerFunction(s, "document","MOUSEOVER", "ca", "ca_mouseover"); - htrAddEventHandlerFunction(s, "document","MOUSEOUT", "ca", "ca_mouseout"); - htrAddEventHandlerFunction(s, "document","MOUSEMOVE", "ca", "ca_mousemove"); + /** Register events. **/ + if (htrAddEventHandlerFunction(s, "document","MOUSEDOWN", "ca", "ca_mousedown") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document","MOUSEMOVE", "ca", "ca_mousemove") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document","MOUSEOUT", "ca", "ca_mouseout") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document","MOUSEOVER", "ca", "ca_mouseover") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document","MOUSEUP", "ca", "ca_mouseup") != 0) goto err; /** Script initialization call. **/ - htrAddScriptInit_va(s, " ca_init(wgtrGetNodeRef(ns,\"%STR&SYM\"), \"%STR&JSSTR\", \"%STR&JSSTR\", \"%STR&JSSTR\", \"%STR&JSSTR\", \"%STR&SYM\", \"%STR&SYM\", \"%STR&SYM\", \"%STR&SYM\", %INT, %INT, %INT);\n", + if (htrAddScriptInit_va(s, + "\tca_init({" + "l:wgtrGetNodeRef(ns, '%STR&SYM'), " + "main_bg:'%STR&JSSTR', " + "cell_bg:'%STR&JSSTR', " + "textcolor:'%STR&JSSTR', " + "dispmode:'%STR&JSSTR', " + "eventdatefield:'%STR&SYM', " + "eventdescfield:'%STR&SYM', " + "eventnamefield:'%STR&SYM', " + "eventpriofield:'%STR&SYM', " + "minprio:%INT, " + "w:%INT, " + "h:%INT, " + "});\n", name, main_bg, cell_bg, textcolor, dispmode, eventdatefield, eventdescfield, eventnamefield, eventpriofield, - minpriority, w, h); + minpriority, w, h + ) != 0) + { + mssError(0, "HTCA", "Failed to write JS init call."); + goto err; + } /** HTML body
element for the base layer. **/ - htrAddBodyItem_va(s, "
\n",id, main_bg, textcolor); + if (htrAddBodyItem_va(s, + "
\n", + id, main_bg, textcolor + ) != 0) + { + mssError(0, "HTCA", "Failed to write HTML opening tags."); + goto err; + } - /** Check for more sub-widgets **/ - for (i=0;iChildren));i++) - htrRenderWidget(s, xaGetItem(&(tree->Children), i), z+1); + /** Render children. **/ + if (htrRenderSubwidgets(s, tree, z + 1) != 0) goto err; /** End the containing layer. **/ - htrAddBodyItem(s, "
\n"); + if (htrAddBodyItem(s, "
\n") != 0) + { + mssError(0, "HTCA", "Failed to write HTML closing tags."); + goto err; + } - return 0; + return 0; + + err: + mssError(0, "HTCA", + "Failed to render \"%s\":\"%s\" (id: %d).", + tree->Name, tree->Type, id + ); + return -1; } diff --git a/centrallix/htmlgen/htdrv_chart.c b/centrallix/htmlgen/htdrv_chart.c index 073627a20..804e45e9e 100644 --- a/centrallix/htmlgen/htdrv_chart.c +++ b/centrallix/htmlgen/htdrv_chart.c @@ -16,7 +16,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1999-2007 LightSys Technology Services, Inc. */ +/* Copyright (C) 1999-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -205,13 +205,22 @@ htchtAddSeriesProperties(pHtSession session, pWgtrNode tree) htchtGetStrValue(sub_tree, "y_column", "", y_column, sizeof(y_column)); htchtGetStrValue(sub_tree, "chart_type", "", chart_type, sizeof(chart_type)); - htrAddScriptInit_va(session, " chartobj.series.push({ label: \"%STR&JSSTR\", color: \"%STR&JSSTR\", fill: %INT, x_column: \"%STR&JSSTR\", y_column: \"%STR&JSSTR\", chart_type: \"%STR&JSSTR\" });\n", - label, - color, - htrGetBoolean(tree, "fill", 1), - x_column, - y_column, - chart_type); + htrAddScriptInit_va(session, + "\t\tchart_obj.series.push({ " + "label:'%STR&JSSTR', " + "color:'%STR&JSSTR', " + "fill:%INT, " + "x_column:'%STR&JSSTR', " + "y_column:'%STR&JSSTR', " + "chart_type:'%STR&JSSTR' " + "});\n", + label, + color, + htrGetBoolean(tree, "fill", 1), + x_column, + y_column, + chart_type + ); htrCheckNSTransitionReturn(session, tree, sub_tree); } @@ -241,7 +250,13 @@ htchtAddAxesProperties(pHtSession session, pWgtrNode tree) htchtGetStrValue(sub_tree, "label", "", label, sizeof(label)); htchtGetStrValue(sub_tree, "axis", "x", axis, sizeof(axis)); - htrAddScriptInit_va(session, " chartobj.axes.push({ label: \"%STR&JSSTR\", axis: \"%STR&JSSTR\", });\n", label, axis); + htrAddScriptInit_va(session, + "\t\tchart_obj.axes.push({ " + "label:'%STR&JSSTR', " + "axis:'%STR&JSSTR', " + "});\n", + label, axis + ); htrCheckNSTransitionReturn(session, tree, sub_tree); } @@ -261,6 +276,7 @@ htchtInitCall(pHtSession session, pWgtrNode tree) char title_color[32]; char legend_position[32]; + /** Get data used for the initialization call. **/ htchtGetObjectSource(tree, object_source, sizeof(object_source)); htchtGetName(tree, name, sizeof(name)); htchtGetTitle(tree, title, sizeof(title)); @@ -269,43 +285,48 @@ htchtInitCall(pHtSession session, pWgtrNode tree) htchtGetTitleColor(tree, title_color, sizeof(title_color)); htchtGetLegendPosition(tree, legend_position, sizeof(legend_position)); - htrAddScriptInit_va(session, " var chartobj = {" - "x_pos: %INT, " - "y_pos: %INT, " - "width: %INT, " - "height: %INT, " - "title_size: %INT, " - "start_at_zero: %INT," - "stacked: %INT," - "chart: wgtrGetNodeRef(ns,\"%STR&SYM\"), " - "chart_type: '%STR&JSSTR', " - "canvas_id: '%STR&SYM', " - "osrc: '%STR&JSSTR', " - "title: '%STR&JSSTR', " - "title_color: '%STR&JSSTR', " - "legend_position: '%STR&JSSTR', " - "axes: [], " - "series: [], " - "};\n", - htchtGetX(tree), - htchtGetY(tree), - htchtGetWidth(tree), - htchtGetHeight(tree), - htchtGetIntValue(tree, "title_size", 12), - htrGetBoolean(tree, "start_at_zero", 1), - htrGetBoolean(tree, "stacked", 0), - name, - chart_type, - canvas_id, - object_source, - title, - title_color, - legend_position - ); + /** Write a JS object for the script initialization in a new scope. **/ + htrAddScriptInit_va(session, "\t\t{\n" + "\t\tconst chart_obj = { " + "x_pos: %INT, " + "y_pos: %INT, " + "width: %INT, " + "height: %INT, " + "title_size: %INT, " + "start_at_zero: %INT," + "stacked: %INT," + "chart: wgtrGetNodeRef(ns,\"%STR&SYM\"), " + "chart_type: '%STR&JSSTR', " + "canvas_id: '%STR&SYM', " + "osrc: '%STR&JSSTR', " + "title: '%STR&JSSTR', " + "title_color: '%STR&JSSTR', " + "legend_position: '%STR&JSSTR', " + "axes: [], " + "series: [], " + "};\n", + htchtGetX(tree), + htchtGetY(tree), + htchtGetWidth(tree), + htchtGetHeight(tree), + htchtGetIntValue(tree, "title_size", 12), + htrGetBoolean(tree, "start_at_zero", 1), + htrGetBoolean(tree, "stacked", 0), + name, + chart_type, + canvas_id, + object_source, + title, + title_color, + legend_position + ); + /** Write code to add values to the initialization object. **/ htchtAddAxesProperties(session, tree); htchtAddSeriesProperties(session, tree); - htrAddScriptInit_va(session, " cht_init(chartobj);\n"); + + /** Write the initialization call, using the object, and close the scope. **/ + htrAddScriptInit(session, "\t\tcht_init(chart_obj);\n\t\t}\n"); return 0; } @@ -323,27 +344,54 @@ htchtScriptInclude(pHtSession session) void htchtGenHTML(pHtSession session, pWgtrNode tree, int z) { - char buf[32]; - - htchtGetCanvasId(tree, buf, sizeof(buf)); - - htrAddBodyItem_va(session,"
\n", - buf, - buf, - htchtGetWidth(tree), - htchtGetHeight(tree) + /** Get id. **/ + char id[32]; + htchtGetCanvasId(tree, id, sizeof(id)); + + /** Get layout data. **/ + const int x = htchtGetX(tree); + const int y = htchtGetY(tree); + const int w = htchtGetWidth(tree); + const int h = htchtGetHeight(tree); + + /** Write style rules for the container div. **/ + htrAddStylesheetItem_va(session, + "\t\t#%STR&SYMdiv { " + "position:absolute; " + "visibility:inherit; " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "width:"ht_flex_format"; " + "height:"ht_flex_format"; " + "z-index:%POS; " + "}\n", + id, + ht_flex_x(x, tree), + ht_flex_y(y, tree), + ht_flex_w(w, tree), + ht_flex_h(h, tree), + z ); - - htrAddBodyItem(session,"

CHART HERE

\n"); - htrAddBodyItem(session,"
\n"); - - htrAddStylesheetItem_va(session, "\t#%STR&SYMdiv { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; Z-INDEX:%POS; } \n", - buf, - htchtGetX(tree), - htchtGetY(tree), - htchtGetWidth(tree), - htchtGetHeight(tree), - z + + /** Write the canvas HTML. **/ + /*** Israel: Dark magic and sorcery beyond my comprehension somehow + *** cause "CHART HERE" to render as the label for the chart. + ***/ + htrAddBodyItem_va(session, + "
" + "" + "

CHART HERE

" + "
" + "
", + id, + id, + w, + h ); } @@ -405,4 +453,3 @@ htchtInitialize() return 0; } - diff --git a/centrallix/htmlgen/htdrv_checkbox.c b/centrallix/htmlgen/htdrv_checkbox.c index c2f699e91..953ee4e82 100644 --- a/centrallix/htmlgen/htdrv_checkbox.c +++ b/centrallix/htmlgen/htdrv_checkbox.c @@ -14,7 +14,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 2000-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 2000-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -47,23 +47,24 @@ static struct { } HTCB; -int htcbRender(pHtSession s, pWgtrNode tree, int z) { +int htcbRender(pHtSession s, pWgtrNode tree, int z) + { char fieldname[HT_FIELDNAME_SIZE]; int x=-1,y=-1,checked=0; - int id, i; char *ptr; char name[64]; char form[64]; int enabled = 0; - if(!(s->Capabilities.Dom0NS || s->Capabilities.Dom1HTML)) - { - mssError(1,"HTCB","Netscape DOM support or W3C DOM Level 1 HTML required"); - return -1; - } - /** Get an id for this. **/ - id = (HTCB.idcnt++); + const int id = (HTCB.idcnt++); + + /** Verify browser capabilities. **/ + if (!s->Capabilities.Dom1HTML || !s->Capabilities.Dom2CSS) + { + mssError(1, "HTCB", "Unsupported browser: W3C DOM1 HTML and DOM2 CSS support required."); + goto err; + } /** Get name **/ if (wgtrGetPropertyValue(tree,"name",DATA_T_STRING,POD(&ptr)) != 0) return -1; @@ -88,46 +89,104 @@ int htcbRender(pHtSession s, pWgtrNode tree, int z) { /** Is it enabled? **/ enabled = htrGetBoolean(tree, "enabled", 1); - /** Write named global **/ - htrAddWgtrObjLinkage_va(s, tree, "cb%INTmain", id); - - /** Ok, write the style header items. **/ - htrAddStylesheetItem_va(s,"\t#cb%POSmain { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; HEIGHT:13px; WIDTH:13px; Z-INDEX:%POS; }\n",id,x,y,z); - htrAddScriptInclude(s,"/sys/js/htdrv_checkbox.js",0); - htrAddScriptInclude(s,"/sys/js/ht_utils_hints.js",0); - - htrAddEventHandlerFunction(s, "document","MOUSEDOWN", "checkbox", "checkbox_mousedown"); - htrAddEventHandlerFunction(s, "document","MOUSEUP", "checkbox", "checkbox_mouseup"); - htrAddEventHandlerFunction(s, "document","MOUSEOVER", "checkbox", "checkbox_mouseover"); - htrAddEventHandlerFunction(s, "document","MOUSEOUT", "checkbox", "checkbox_mouseout"); - htrAddEventHandlerFunction(s, "document","MOUSEMOVE", "checkbox", "checkbox_mousemove"); + /** Link the widget to the DOM node. **/ + if (htrAddWgtrObjLinkage_va(s, tree, "cb%INTmain", id) != 0) + { + mssError(0, "HTCB", "Failed to add object linkage."); + goto err; + } + + /** Write style header. **/ + if (htrAddStylesheetItem_va(s, + "\t\t#cb%POSmain { " + "position:absolute; " + "visibility:inherit; " + "cursor:pointer; " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "height:13px; " + "width:13px; " + "z-index:%POS; " + "}\n", + id, + ht_flex_x(x, tree), + ht_flex_y(y, tree), + z + ) != 0) + { + mssError(0, "HTCB", "Failed to write CSS."); + goto err; + } - /** Script initialization call. **/ - htrAddScriptInit_va(s," checkbox_init({layer:wgtrGetNodeRef(ns,\"%STR&SYM\"), fieldname:\"%STR&JSSTR\", checked:%INT, enabled:%INT, form:\"%STR&JSSTR\"});\n", name, fieldname,checked,enabled,form); + /** Include scripts. **/ + if (htrAddScriptInclude(s,"/sys/js/ht_utils_hints.js", 0) != 0) goto err; + if (htrAddScriptInclude(s,"/sys/js/htdrv_checkbox.js", 0) != 0) goto err; + + /** Register event handlers. **/ + if (htrAddEventHandlerFunction(s, "document", "MOUSEDOWN", "checkbox", "checkbox_mousedown") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEMOVE", "checkbox", "checkbox_mousemove") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEOUT", "checkbox", "checkbox_mouseout") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEOVER", "checkbox", "checkbox_mouseover") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEUP", "checkbox", "checkbox_mouseup") != 0) goto err; + + /** Script initialization call. **/ + if (htrAddScriptInit_va(s, + "\tcheckbox_init({ " + "layer:wgtrGetNodeRef(ns, '%STR&SYM'), " + "fieldname:'%STR&JSSTR', " + "checked:%INT, " + "enabled:%INT, " + "form:'%STR&JSSTR', " + "});\n", + name, fieldname, checked, enabled, form + ) != 0) + { + mssError(0, "HTCB", "Failed to write JS init call."); + goto err; + } - /** HTML body
element for the layers. **/ - htrAddBodyItemLayerStart(s, 0, "cb%POSmain", id, NULL); - switch(checked) + /** Write HTML. **/ + if (htrAddBodyItemLayerStart(s, 0, "cb%POSmain", id, NULL) != 0) + { + mssError(0, "HTCB", "Failed to write HTML layer start."); + goto err; + } + char* state_name; + switch (checked) + { + case 1: state_name = "checked"; break; + case 0: state_name = "unchecked"; break; + case -1: state_name = "null"; break; + default: + mssError(0, "HTCB", "Unexpected value %d for 'checked'.", checked); + goto err; + } + char src_path[48]; + snprintf(src_path, sizeof(src_path), "/sys/images/checkbox_%s%s.gif", state_name, (enabled) ? "" : "_dis"); + if (htrAddBodyItem_va(s, " \n", src_path) != 0) + { + mssError(0, "HTCB", "Failed to write HTML tag."); + goto err; + } + if (htrAddBodyItemLayerEnd(s, 0) != 0) { - case 1: - htrAddBodyItem_va(s," \n",!enabled); - break; - case 0: - htrAddBodyItem_va(s," \n",!enabled); - break; - case -1: /* null */ - htrAddBodyItem_va(s," \n",!enabled); - break; + mssError(0, "HTCB", "Failed to write HTML layer start."); + goto err; } - htrAddBodyItemLayerEnd(s, 0); + /** Render children. **/ + if (htrRenderSubwidgets(s, tree, z + 1) != 0) goto err; - /** Check for more sub-widgets **/ - for (i=0;iChildren));i++) - htrRenderWidget(s, xaGetItem(&(tree->Children), i), z+1); + /** Success. **/ + return 0; - return 0; -} + err: + mssError(0, "HTCB", + "Failed to render \"%s\":\"%s\" (id: %d).", + tree->Name, tree->Type, id + ); + return -1; + } /* diff --git a/centrallix/htmlgen/htdrv_clock.c b/centrallix/htmlgen/htdrv_clock.c index 26d070321..98cb45857 100644 --- a/centrallix/htmlgen/htdrv_clock.c +++ b/centrallix/htmlgen/htdrv_clock.c @@ -14,7 +14,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -64,24 +64,23 @@ htclRender(pHtSession s, pWgtrNode tree, int z) int shadowx = 0; int shadowy = 0; int size = 0; - int moveable = 0; int bold = 0; int showsecs = 1; int showampm = 1; int miltime = 0; int x=-1,y=-1,w,h; - int id, i; char fieldname[HT_FIELDNAME_SIZE]; - if(!s->Capabilities.Dom0NS && !s->Capabilities.Dom1HTML) + /** Get an id for this. **/ + const int id = (HTCL.idcnt++); + + /** Verify browser capabilities. **/ + if (!s->Capabilities.Dom1HTML || !s->Capabilities.Dom2CSS) { - mssError(1,"HTCL","Netscape 4 or W3C DOM support required"); - return -1; + mssError(1, "HTCL", "Unsupported browser: W3C DOM1 HTML and DOM2 CSS support required."); + goto err; } - /** Get an id for this. **/ - id = (HTCL.idcnt++); - /** Get x,y,w,h of this object **/ if (wgtrGetPropertyValue(tree,"x",DATA_T_INTEGER,POD(&x)) != 0) x=0; if (wgtrGetPropertyValue(tree,"y",DATA_T_INTEGER,POD(&y)) != 0) y=0; @@ -142,10 +141,6 @@ htclRender(pHtSession s, pWgtrNode tree, int z) if (wgtrGetPropertyValue(tree,"size",DATA_T_INTEGER,POD(&ptr)) == 0) size = (intptr_t)ptr; - /** Movable? **/ - if (wgtrGetPropertyValue(tree,"moveable",DATA_T_STRING,POD(&ptr)) == 0 && !strcmp(ptr,"true")) - moveable = 1; - /** Show Seconds **/ if (wgtrGetPropertyValue(tree,"seconds",DATA_T_STRING,POD(&ptr)) == 0 && (!strcasecmp(ptr,"false") || !strcasecmp(ptr,"no"))) showsecs = 0; @@ -160,53 +155,110 @@ htclRender(pHtSession s, pWgtrNode tree, int z) else fieldname[0]='\0'; - /** Write Style header items. **/ - htrAddStylesheetItem_va(s,"\t#cl%POSbase { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; Z-INDEX:%POS; }\n",id,x,y,w,z); - htrAddStylesheetItem_va(s,"\t#cl%POScon1 { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; Z-INDEX:%POS; }\n",id,0,0,w,z+2); - htrAddStylesheetItem_va(s,"\t#cl%POScon2 { POSITION:absolute; VISIBILITY:hidden; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; Z-INDEX:%POS; }\n",id,0,0,w,z+2); - - /** Write named global **/ - htrAddWgtrObjLinkage_va(s, tree, "cl%POSbase",id); + /** Write style headers. **/ + if (htrAddStylesheetItem_va(s, + "\t\t#cl%POSbase { " + "position:absolute; " + "visibility:inherit; " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "width:"ht_flex_format"; " + "z-index:%POS; " + "}\n", + id, + ht_flex_x(x, tree), + ht_flex_y(y, tree), + ht_flex_w(w, tree), + z + ) != 0) + { + mssError(0, "HTCL", "Failed to write base CSS."); + goto err; + } + if (htrAddStylesheetItem_va(s, + "\t\t.cl%POScon { " + "position:absolute; " + "left:0px; " + "top:0px; " + "width:100%%; " + "z-index:%POS; " + "}\n", + id, + z + 2 + ) != 0) + { + mssError(0, "HTCL", "Failed to write con CSS."); + goto err; + } - /** Other global variables **/ - htrAddScriptGlobal(s, "cl_move", "false", 0); - htrAddScriptGlobal(s, "cl_xOffset", "null", 0); - htrAddScriptGlobal(s, "cl_yOffset", "null", 0); + /** Link the widget to the DOM node. **/ + if (htrAddWgtrObjLinkage_va(s, tree, "cl%POSbase", id) != 0) + { + mssError(0, "HTCL", "Failed to add object linkage."); + goto err; + } /** Javascript include files **/ - htrAddScriptInclude(s, "/sys/js/htdrv_clock.js", 0); - htrAddScriptInclude(s, "/sys/js/ht_utils_string.js", 0); + if (htrAddScriptInclude(s, "/sys/js/ht_utils_string.js", 0)) goto err; + if (htrAddScriptInclude(s, "/sys/js/htdrv_clock.js", 0)) goto err; /** Event Handlers **/ - htrAddEventHandlerFunction(s, "document","MOUSEUP", "cl", "cl_mouseup"); - htrAddEventHandlerFunction(s, "document","MOUSEDOWN", "cl", "cl_mousedown"); - htrAddEventHandlerFunction(s, "document","MOUSEOVER", "cl", "cl_mouseover"); - htrAddEventHandlerFunction(s, "document","MOUSEOUT", "cl", "cl_mouseout"); - htrAddEventHandlerFunction(s, "document","MOUSEMOVE", "cl", "cl_mousemove"); + if (htrAddEventHandlerFunction(s, "document", "MOUSEDOWN", "cl", "cl_mousedown")) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEMOVE", "cl", "cl_mousemove")) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEOUT", "cl", "cl_mouseout")) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEOVER", "cl", "cl_mouseover")) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEUP", "cl", "cl_mouseup")) goto err; /** Script initialization call. **/ - htrAddScriptInit_va(s, " cl_init({layer:wgtrGetNodeRef(ns,\"%STR&SYM\"), c1:htr_subel(wgtrGetNodeRef(ns,\"%STR&SYM\"),\"cl%POScon1\"), c2:htr_subel(wgtrGetNodeRef(ns,\"%STR&SYM\"),\"cl%POScon2\"), fieldname:\"%STR&JSSTR\", background:\"%STR&JSSTR\", shadowed:%POS, foreground1:\"%STR&JSSTR\", foreground2:\"%STR&JSSTR\", fontsize:%INT, moveable:%INT, bold:%INT, sox:%INT, soy:%INT, showSecs:%INT, showAmPm:%INT, milTime:%INT});\n", - name, - name, id, - name, id, - fieldname, main_bg, shadowed, - fgcolor1, fgcolor2, - size, moveable, bold, - shadowx, shadowy, - showsecs, showampm, miltime); + if (htrAddScriptInit_va(s, "\t{ " + "const layer = wgtrGetNodeRef(ns, '%STR&SYM'); " + "cl_init({ " + "layer, " + "c1:htr_subel(layer, 'cl%POScon1'), " + "c2:htr_subel(layer, 'cl%POScon2'), " + "fieldname:'%STR&JSSTR', " + "fontsize:%INT, " + "bold:%INT, " + "background:'%STR&JSSTR', " + "foreground1:'%STR&JSSTR', " + "foreground2:'%STR&JSSTR', " + "shadowed:%POS, " + "sox:%INT, " + "soy:%INT, " + "showSecs:%INT, " + "showAmPm:%INT, " + "milTime:%INT, " + "}); }\n", + name, id, id, + fieldname, size, bold, + main_bg, fgcolor1, fgcolor2, + shadowed, shadowx, shadowy, + showsecs, showampm, miltime + ) != 0) + { + mssError(0, "HTCL", "Failed to add JS init call."); + goto err; + } /** HTML body
element for the base layer. **/ htrAddBodyItem_va(s, "
\n",id); htrAddBodyItem_va(s, "
\n",main_bg,w,h); - htrAddBodyItem_va(s, "
\n",id); - htrAddBodyItem_va(s, "
\n",id); + htrAddBodyItem_va(s, "
\n", id, id); + htrAddBodyItem_va(s, " \n", id, id); htrAddBodyItem(s, "
\n"); - /** Check for more sub-widgets **/ - for (i=0;iChildren));i++) - htrRenderWidget(s, xaGetItem(&(tree->Children), i), z+1); + /** Render children. **/ + if (htrRenderSubwidgets(s, tree, z + 1) != 0) goto err; - return 0; + /** Success. **/ + return 0; + + err: + mssError(0, "HTCL", + "Failed to render \"%s\":\"%s\" (id: %d).", + tree->Name, tree->Type, id + ); + return -1; } diff --git a/centrallix/htmlgen/htdrv_component.c b/centrallix/htmlgen/htdrv_component.c index 93c5533a5..e55488c84 100644 --- a/centrallix/htmlgen/htdrv_component.c +++ b/centrallix/htmlgen/htdrv_component.c @@ -4,6 +4,7 @@ #include #include "ht_render.h" #include "obj.h" +#include "cxlib/util.h" #include "cxlib/mtask.h" #include "cxlib/xarray.h" #include "cxlib/xhash.h" @@ -18,7 +19,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2006 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -172,7 +173,6 @@ htcmpRender(pHtSession s, pWgtrNode tree, int z) { char* ptr; char name[64]; - int id; char cmp_path[256]; pObject cmp_obj = NULL; pWgtrNode cmp_tree = NULL; @@ -185,7 +185,6 @@ htcmpRender(pHtSession s, pWgtrNode tree, int z) char sbuf[128]; pStruct params = NULL; pStruct old_params = NULL; - int i; char* path; int is_toplevel; int old_is_dynamic = 0; @@ -194,16 +193,16 @@ htcmpRender(pHtSession s, pWgtrNode tree, int z) char* slashptr; int new_sec_context = 0; - /** Verify capabilities **/ - if(!s->Capabilities.Dom0NS && !(s->Capabilities.Dom1HTML && s->Capabilities.CSS1)) + /** Get an id for this. **/ + const int id = (HTCMP.idcnt++); + + /** Verify browser capabilities. **/ + if (!s->Capabilities.Dom1HTML || !s->Capabilities.Dom2CSS) { - mssError(1,"HTCMP","Either Netscape DOM or W3C DOM1 HTML and W3C CSS support required"); - return -1; + mssError(1, "HTCMP", "Unsupported browser: W3C DOM1 HTML and DOM2 CSS support required."); + goto end_free; } - /** Get an id for this. **/ - id = (HTCMP.idcnt++); - /** Is this a toplevel component? **/ is_toplevel = htrGetBoolean(tree, "toplevel", 0); @@ -212,10 +211,6 @@ htcmpRender(pHtSession s, pWgtrNode tree, int z) if (wgtrGetPropertyValue(tree,"y",DATA_T_INTEGER,POD(&y)) != 0) y = 0; if (is_toplevel) { -/* if (wgtrGetPropertyValue(wgtrGetRoot(tree),"width",DATA_T_INTEGER,POD(&w)) != 0) - w = wgtrGetContainerWidth(tree) - x; - if (wgtrGetPropertyValue(wgtrGetRoot(tree),"height",DATA_T_INTEGER,POD(&h)) != 0) - h = wgtrGetContainerHeight(tree) - y;*/ if (wgtrGetPropertyValue(tree,"width",DATA_T_INTEGER,POD(&w)) != 0) w = s->ClientInfo->AppMaxWidth - x; if (wgtrGetPropertyValue(tree,"height",DATA_T_INTEGER,POD(&h)) != 0) @@ -275,15 +270,67 @@ htcmpRender(pHtSession s, pWgtrNode tree, int z) params = htcmp_internal_CreateParams(s, tree); /** Any params have expressions? **/ - if (params) + if (params != NULL) { - for(i=0;inSubInf;i++) + for (int i = 0; i < params->nSubInf; i++) { - htrCheckAddExpression(s, tree, name, params->SubInf[i]->Name); + if (htrCheckAddExpression(s, tree, name, params->SubInf[i]->Name) < 0) goto end_free; } } - /** If static mode, load the component **/ + + /** Write styles for the enclosing div. **/ + if (htrAddStylesheetItem_va(s, + "\t\t#cmp%POSbase { " + "position:absolute; " + "visibility:inherit; " + "overflow:visible; " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "width:"ht_flex_format"; " + "height:"ht_flex_format"; " + "z-index:%POS; " + "}\n", + id, + ht_flex_x(x, tree), + ht_flex_y(y, tree), + ht_flex_w(w, tree), + ht_flex_h(h, tree), + z + ) != 0) + { + mssError(0, "HTCMP", "Failed to write base CSS."); + goto end_free; + } + + /** Write enclosing div event CSS. **/ + if (htrAddStylesheetItem_va(s, + "\t\t#cmp%POSbase { " + "pointer-events:none; " + "}\n" + "\t\t#cmp%POSbase > * { " + "pointer-events:auto; " + "}\n", + id, id + ) != 0) + { + mssError(0, "HTCMP", "Failed to write pointer CSS."); + goto end_free; + } + + /** Write linked container div to isolate children. **/ + if (htrAddWgtrObjLinkage_va(s, tree, "cmp%POSbase", id) != 0) + { + mssError(0, "HTCMP", "Failed to add object linkage."); + goto end_free; + } + if (htrAddBodyItem_va(s, "
\n", id) != 0) + { + mssError(0, "HTCMP", "Failed to write container HTML."); + goto end_free; + } + + /** If static mode, render the component now. **/ if (is_static) { /** Copy in cx__scripts value **/ @@ -304,14 +351,29 @@ htcmpRender(pHtSession s, pWgtrNode tree, int z) s->IsDynamic = 0; /** Init component **/ - htrAddScriptInit_va(s, - " cmp_init({node:wgtrGetNodeRef(ns,\"%STR&SYM\"), is_static:true, allow_multi:false, auto_destroy:false, width:%INT, height:%INT, xpos:%INT, ypos:%INT});\n", - name, w,h,x,y); + if (htrAddScriptInit_va(s, + "\tcmp_init({ " + "node:wgtrGetNodeRef(ns, '%STR&SYM'), " + "is_static:true, " + "allow_multi:false, " + "auto_destroy:false, " + "width:%INT, " + "height:%INT, " + "xpos:%INT, " + "ypos:%INT, " + "});\n", + name, + w, h, x, y + ) != 0) + { + mssError(0, "HTCMP", "Failed to write JS init call."); + goto end_free; + } /** Are there any templates we should use **/ memcpy(&wgtr_params, s->ClientInfo, sizeof(wgtr_params)); memset(wgtr_params.Templates, 0, sizeof(wgtr_params.Templates)); - for(i=0;iNamespace->DName); /** Switch namespaces **/ - htrAddNamespace(s, tree, wgtrGetRootDName(cmp_tree), 0); + char* namespace_name = wgtrGetRootDName(cmp_tree); + if (htrAddNamespace(s, tree, namespace_name, 0) != 0) + { + mssError(0, "HTCMP", "Failed to add namespace."); + goto end_free; + } - /** Generate the component **/ - htrAddWgtrCtrLinkage(s, tree, "_parentctr"); - htrRenderWidget(s, cmp_tree, z+10); - htrBuildClientWgtr(s, cmp_tree); + /** Render the component-decl widget. **/ + if (htrAddWgtrCtrLinkage(s, tree, "_parentctr") != 0) goto end_free; + if (htrRenderWidget(s, cmp_tree, z + 10) != 0) + { + mssError(0, "HTCMP", "Failed to render component tree."); + goto end_free; + } + if (htrBuildClientWgtr(s, cmp_tree) != 0) + { + mssError(0, "HTCMP", "Failed to build client widget tree."); + goto end_free; + } /** Switch the namespace back **/ - htrLeaveNamespace(s); + if (htrLeaveNamespace(s) != 0) + { + mssError(0, "HTCMP", "Failed to leave namespace."); + goto end_free; + } /** End Init component **/ - htrAddScriptInit_va(s, " cmp_endinit(wgtrGetNodeRef(ns,\"%STR&SYM\"));\n", name); + if (htrAddScriptInit_va(s, "\tcmp_endinit(wgtrGetNodeRef(ns, '%STR&SYM'));\n", name) != 0) + { + mssError(0, "HTCMP", "Failed to write JS init call."); + goto end_free; + } /** Return to previous security context **/ if (new_sec_context) cxssPopContext(); @@ -385,49 +478,136 @@ htcmpRender(pHtSession s, pWgtrNode tree, int z) s->IsDynamic = old_is_dynamic; old_is_dynamic = 0; } + + /** Dynamic mode, component is loaded later by the client. **/ else { - /** Init component **/ - htrAddScriptInit_va(s, - " cmp_init({node:wgtrGetNodeRef(ns,\"%STR&SYM\"), is_top:%POS, is_static:false, allow_multi:%POS, auto_destroy:%POS, path:\"%STR&JSSTR\", loader:htr_subel(wgtrGetParentContainer(wgtrGetNodeRef(ns,\"%STR&SYM\")), \"cmp%POS\"), width:%INT, height:%INT, xpos:%INT, ypos:%INT});\n", - name, is_toplevel, allow_multi, auto_destroy, cmp_path, - name, id, - w, h, x, y); - - /** Add template paths **/ - for(i=0;inSubInf;i++) + path = wgtrGetTemplatePath(tree, i); + if (path == NULL) continue; + + if (htrAddScriptInit_va(s, "\t\tnode.templates.push('%STR&JSSTR');\n", path) != 0) { - htrAddScriptInit_va(s, " wgtrGetNodeRef(ns,\"%STR&SYM\").AddParam(\"%STR&SYM\",%[null%]%[\"%STR&HEX\"%]);\n", - name, params->SubInf[i]->Name, !params->SubInf[i]->StrVal, params->SubInf[i]->StrVal, - params->SubInf[i]->StrVal); + mssError(0, "HTCMP", "Failed to write JS template #%d: \"%s\".", i + 1, path); + goto end_free; } } + /** Write params. **/ + if (params != NULL) + { + for (int i = 0; i < params->nSubInf; i++) + { + pStruct param = params->SubInf[i]; + const int is_empty_str = (param->StrVal == NULL || param->StrVal[0] == '\0'); + if (htrAddScriptInit_va(s, + "\t\tnode.AddParam('%STR&SYM', %[null%]%['%STR&HEX'%]);\n", + param->Name, (is_empty_str), (!is_empty_str), param->StrVal + ) != 0) + { + mssError(0, "HTCMP", "Failed to write JS."); + goto end_free; + } + } + } + + /** Write data to close the scope above. **/ + if (htrAddScriptInit(s, "\t\t}\n") != 0) + { + mssError(0, "HTCMP", "Failed to write end of JS code block."); + goto end_free; + } + /** Dynamic mode -- load from client **/ - htrAddWgtrCtrLinkage(s, tree, "_parentctr"); - htrAddBodyItemLayer_va(s, HTR_LAYER_F_DYNAMIC, "cmp%POS", id, NULL, ""); - htrAddStylesheetItem_va(s,"\t#cmp%POS { POSITION:absolute; VISIBILITY:hidden; LEFT:0px; TOP:0px; WIDTH:0px; HEIGHT:0px; Z-INDEX:0;}\n", id); + if (htrAddWgtrCtrLinkage(s, tree, "_parentctr") != 0) + { + mssError(0, "HTCMP", "Failed to add dynamic component container linkage."); + goto end_free; + } + if (htrAddBodyItemLayer_va(s, HTR_LAYER_F_DYNAMIC, "cmp%POS", id, NULL, "") != 0) + { + mssError(0, "HTCMP", "Failed to write dynamic component HTML container."); + goto end_free; + } + if (htrAddStylesheetItem_va(s, + "\t\t#cmp%POS { " + "position:absolute; " + "visibility:hidden; " + "left:0px; " + "top:0px; " + "width:0px; " + "height:0px; " + "z-index:0; " + "}\n", + id + ) != 0) + { + mssError(0, "HTCMP", "Failed to write dynamic component CSS."); + goto end_free; + } } - htrRenderSubwidgets(s, tree, z+1); + if (htrRenderSubwidgets(s, tree, z + 1) != 0) goto end_free; + + /** End the enclosing div. **/ + if (htrAddBodyItem(s, "
") != 0) + { + mssError(0, "HTCMP", "Failed to write HTML closing tag."); + goto end_free; + } + /** Success. **/ rval = 0; - out: + end_free: + if (rval != 0) + { + mssError(0, "HTCMP", + "Failed to render \"%s\":\"%s\" (id: %d).", + tree->Name, tree->Type, id + ); + } + /** Clean up **/ - if (new_sec_context) cxssPopContext(); - if (params) - stFreeInf_ne(params); + if (new_sec_context) check(cxssPopContext()); /* Failure ignored. */ + if (params) check(stFreeInf_ne(params)); /* Failure ignored. */ if (s->IsDynamic == 0 && old_is_dynamic) s->IsDynamic = 1; if (s->GraftPoint && old_graft) @@ -441,10 +621,10 @@ htcmpRender(pHtSession s, pWgtrNode tree, int z) s->Params = old_params; old_params = NULL; } - if (cmp_obj) objClose(cmp_obj); - if (cmp_tree) wgtrFree(cmp_tree); + if (cmp_obj != NULL) check(objClose(cmp_obj)); /* Failure ignored. */ + if (cmp_tree != NULL) wgtrFree(cmp_tree); - return rval; + return rval; } @@ -481,4 +661,3 @@ htcmpInitialize() return 0; } - diff --git a/centrallix/htmlgen/htdrv_componentdecl.c b/centrallix/htmlgen/htdrv_componentdecl.c index 546b5cbb3..049f67d36 100644 --- a/centrallix/htmlgen/htdrv_componentdecl.c +++ b/centrallix/htmlgen/htdrv_componentdecl.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -17,7 +18,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2003 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -51,10 +52,9 @@ /** globals **/ static struct { - int idcnt; + unsigned int id_count; } - HTCMPD; - + HTCMPD = {0u}; /** structure defining a param to the component **/ typedef struct @@ -79,45 +79,36 @@ htcmpdRender(pHtSession s, pWgtrNode tree, int z) char expose_actions_for[64] = ""; char expose_props_for[64] = ""; char apply_hints_to[64] = ""; - int id; - /*char* nptr;*/ -// pObject subobj = NULL; pWgtrNode sub_tree = NULL; pWgtrNode conn_tree = NULL; - XArray attrs; - pHTCmpdParam param; - int i, j; - int rval = 0; int is_visual = 1; - char gbuf[256]; + char gbuf[256] = ""; char* gname; char* xptr; char* yptr; int xoffs, yoffs; - /** Verify capabilities **/ - if(!s->Capabilities.Dom0NS && !(s->Capabilities.Dom1HTML && s->Capabilities.CSS1)) + /** Get an id for this. **/ + const unsigned int id = (HTCMPD.id_count++); + + /** Verify browser capabilities. **/ + if (!s->Capabilities.Dom1HTML || !s->Capabilities.Dom2CSS) { - mssError(1,"HTCMPD","Either Netscape DOM or W3C DOM1 HTML and W3C CSS support required"); - return -1; + mssError(1, "HTCMPD", "Unsupported browser: W3C DOM1 HTML and DOM2 CSS support required."); + goto err; } - /** Get an id for this. **/ - id = (HTCMPD.idcnt++); - /** Is this a visual component? **/ - if ((is_visual = htrGetBoolean(tree, "visual", 1)) < 0) - { - return -1; - } + is_visual = htrGetBoolean(tree, "visual", 1); + if (is_visual < 0) goto err; /** Get name **/ - if (wgtrGetPropertyValue(tree,"name",DATA_T_STRING,POD(&ptr)) != 0) return -1; + if (wgtrGetPropertyValue(tree,"name",DATA_T_STRING,POD(&ptr)) != 0) goto err; strtcpy(name, ptr, sizeof(name)); if (cxsecVerifySymbol(name) < 0) { mssError(1,"HTCMPD","Invalid name '%s' for component", name); - return -1; + goto err; } /** Need to relocate the widgets via xoffset/yoffset? **/ @@ -147,169 +138,116 @@ htcmpdRender(pHtSession s, pWgtrNode tree, int z) if (wgtrGetPropertyValue(tree, "apply_hints_to", DATA_T_STRING, POD(&ptr)) == 0) strtcpy(apply_hints_to, ptr, sizeof apply_hints_to); - /** Write named global **/ - /*nptr = (char*)nmMalloc(strlen(name)+1); - strcpy(nptr,name);*/ - /*htrAddScriptGlobal(s, nptr, "null", HTR_F_NAMEALLOC);*/ - /** Include the js module **/ - htrAddScriptInclude(s, "/sys/js/htdrv_componentdecl.js", 0); - - /** parameters for this component **/ - xaInit(&attrs, 16); + if (htrAddScriptInclude(s, "/sys/js/htdrv_componentdecl.js", 0) != 0) goto err; /** DOM Linkages **/ if (s->GraftPoint) { + bool graft_successful = false; strtcpy(gbuf, s->GraftPoint, sizeof(gbuf)); gname = strchr(gbuf,':'); if (!gname) { mssError(1,"HTCMPD", "Invalid graft point"); - goto htcmpd_cleanup; + goto graft_done; } *(gname++) = '\0'; if (strpbrk(gname,"'\"\\<>\r\n\t ") || strspn(gbuf,"w0123456789abcdef_") != strlen(gbuf)) { mssError(1,"HTCMPD", "Invalid graft point"); - goto htcmpd_cleanup; + goto graft_done; + } + + /** Add linkage. **/ + if (htrAddWgtrCtrLinkage_va(s, tree, + "wgtrGetContainer(wgtrGetNode('%STR&SYM', '%STR&SYM'))", + gbuf, gname + ) != 0) + { + mssError(0, "HTCMPD", "Failed to add container linkage."); + goto graft_done; + } + + /** Add HTML DOM node. **/ + if (htrAddBodyItem_va(s, + "", + s->Namespace->DName + ) != 0) + { + mssError(0, "HTCMPD", "Failed to write HTML linkage node."); + goto graft_done; + } + + /** Success. **/ + graft_successful = true; + + graft_done: + if (!graft_successful) + { + mssError(0, "HTCMPD", "Failed to mount graft point: \"%s\"", gbuf); + goto err; } - htrAddWgtrCtrLinkage_va(s, tree, - "wgtrGetContainer(wgtrGetNode(\"%STR&SYM\",\"%STR&SYM\"))", gbuf, gname); - htrAddBodyItem_va(s, "", s->Namespace->DName); } else { strcpy(gbuf,""); gname=""; - htrAddWgtrCtrLinkage(s, tree, "_parentctr"); + if (htrAddWgtrCtrLinkage(s, tree, "_parentctr") != 0) + { + mssError(0, "HTCMPD", "Failed to add container linkage."); + goto err; + } } - /** Init component **/ - htrAddScriptInit_va(s, " cmpd_init(wgtrGetNodeRef(ns,\"%STR&SYM\"), {vis:%POS, gns:%[\"%STR&SYM\"%]%[null%], gname:'%STR&SYM'%[, expe:'%STR&SYM'%]%[, expa:'%STR&SYM'%]%[, expp:'%STR&SYM'%]%[, applyhint:'%STR&SYM'%]});\n", - name, is_visual, *gbuf, gbuf, !*gbuf, gname, *expose_events_for, expose_events_for, *expose_actions_for, expose_actions_for, - *expose_props_for, expose_props_for, *apply_hints_to, apply_hints_to); -#if 0 - for (i=0;iChildren));i++) + /** Warning message. **/ + if (gname[0] == '\0') { - /** Loop through each param we get **/ - sub_tree = xaGetItem(&(tree->Children), i); - if (!strcmp(sub_tree->Type, "system/parameter")) - { - param = (pHTCmpdParam)nmMalloc(sizeof(HTCmpdParam)); - if (!param) break; - xaAddItem(&attrs, param); - - /** Get component parameter name **/ - wgtrGetPropertyValue(sub_tree, "name", DATA_T_STRING, POD(&ptr)); - param->Name = nmSysStrdup(ptr); - if (cxsecVerifySymbol(param->Name) < 0) - { - mssError(1,"HTCMPD","Invalid name '%s' for parameter in component '%s'", param->Name, name); - rval = -1; - goto htcmpd_cleanup; - } - - /** Does the param have a value? **/ - param->StrVal = htrParamValue(s, param->Name); - - /** Get component type **/ - if (wgtrGetPropertyValue(sub_tree, "type", DATA_T_STRING, POD(&ptr)) == 0) - { - t = objTypeID(ptr); - if (t < 0) - { - mssError(1,"HTCMPD","Component '%s' parameter '%s' has unknown/invalid data type '%s'", name, param->Name, ptr); - rval = -1; - goto htcmpd_cleanup; - } - param->TypedObjData.DataType = t; - } - else - { - /** default type is string **/ - param->TypedObjData.DataType = DATA_T_STRING; - } - - /** Get hints **/ - param->Hints = wgtrWgtToHints(sub_tree); - if (!param->Hints) - { - rval = -1; - goto htcmpd_cleanup; - } - - /** Close the object **/ - sub_tree = NULL; - } - subobj_qy = NULL; + fprintf(stderr, + "Warning: No value specified for gname, which is required to " + "be a valid symbol. Expect a printing failure.\n" + ); } -#endif - /** Build the typed pod values for each data value **/ - for(i=0;iStrVal) - { - param->TypedObjData.Flags = DATA_TF_NULL; - } - else - { - param->TypedObjData.Flags = 0; - switch(param->TypedObjData.DataType) - { - case DATA_T_STRING: - param->TypedObjData.Data.String = param->StrVal; - break; - case DATA_T_INTEGER: - if (!param->StrVal[0]) - { - mssError(1,"HTCMPD","Failed to convert empty string for param '%s' to integer", param->Name); - rval = -1; - goto htcmpd_cleanup; - } - param->TypedObjData.Data.Integer = strtoi(param->StrVal,&ptr,10); - if (*ptr) - { - mssError(1,"HTCMPD","Failed to convert value '%s' for param '%s' to integer", param->StrVal, param->Name); - rval = -1; - goto htcmpd_cleanup; - } - break; - case DATA_T_DOUBLE: - if (!param->StrVal[0]) - { - mssError(1,"HTCMPD","Failed to convert empty string for param '%s' to double", param->Name); - rval = -1; - goto htcmpd_cleanup; - } - param->TypedObjData.Data.Double = strtod(param->StrVal,&ptr); - if (*ptr) - { - mssError(1,"HTCMPD","Failed to convert value '%s' for param '%s' to double", param->StrVal, param->Name); - rval = -1; - goto htcmpd_cleanup; - } - break; - default: - mssError(1,"HTCMPD","Unsupported type for param '%s'", param->Name); - rval= -1; - goto htcmpd_cleanup; - } - } + mssError(0, "HTCMPD", "Failed to write JS init call."); + goto err; + } - /** Verify the thing **/ - if (hntVerifyHints(param->Hints, &(param->TypedObjData), &ptr, NULL, s->ObjSession) < 0) - { - mssError(1,"HTCMPD","Invalid value '%s' for component '%s' param '%s': %s", param->StrVal, name, param->Name, ptr); - rval = -1; - goto htcmpd_cleanup; - } + /** Write the declNAME variable. **/ + if (htrAddScriptInit_va(s, "\n\t// Start of %STR component.\n" + "\tconst decl%POS = wgtrGetNodeRef(ns, '%STR&SYM');\n", + name, id, name + ) != 0) + { + mssError(0, "HTCMPD", "Failed to write the start of the JS init call."); + goto err; } - /** Add actions, events, and client properties **/ - for (i=0;iChildren));i++) + /** Render sub-widgets. **/ + const unsigned int n_sub_trees = xaCount(&(tree->Children)); + for (unsigned int i = 0u; i < n_sub_trees; i++) { sub_tree = xaGetItem(&(tree->Children), i); @@ -319,78 +257,72 @@ htcmpdRender(pHtSession s, pWgtrNode tree, int z) if (cxsecVerifySymbol(subobj_name) < 0) { mssError(1,"HTCMPD","Invalid name '%s' for action/event/cprop in component '%s'", subobj_name, name); - rval = -1; - goto htcmpd_cleanup; + goto err; } - /** Get type **/ + /** Handle sub-widgets based ont he outer type. **/ wgtrGetPropertyValue(sub_tree, "outer_type", DATA_T_STRING, POD(&ptr)); - if (!strcmp(ptr,"widget/component-decl-action")) - { - htrAddScriptInit_va(s, " wgtrGetNodeRef(ns,\"%STR&SYM\").addAction('%STR&SYM');\n", name, subobj_name); - sub_tree->RenderFlags |= HT_WGTF_NOOBJECT; - } - else if (!strcmp(ptr,"widget/component-decl-event")) - { - htrAddScriptInit_va(s, " wgtrGetNodeRef(ns,\"%STR&SYM\").addEvent('%STR&SYM');\n", name, subobj_name); - sub_tree->RenderFlags |= HT_WGTF_NOOBJECT; - } - else if (!strcmp(ptr,"widget/component-decl-cprop")) - { - htrAddScriptInit_va(s, " wgtrGetNodeRef(ns,\"%STR&SYM\").addProp('%STR&SYM');\n", name, subobj_name); - sub_tree->RenderFlags |= HT_WGTF_NOOBJECT; + if (strncmp(ptr, "widget/component-decl-", 22) != 0) + { + /** Widget is not a component-decl-TYPE, render normally. **/ + if (htrRenderWidget(s, sub_tree, z + 2) != 0) + { + mssError(0, "HTCMPD", "Failed to render widget in declared component."); + goto err; + } + continue; } + char* decl_type = ptr + 22; - sub_tree = NULL; - } - - /** Do subwidgets **/ - /*htrRenderSubwidgets(s, tree, z+2);*/ - for (i=0;iChildren));i++) - { - sub_tree = xaGetItem(&(tree->Children), i); - wgtrGetPropertyValue(sub_tree, "outer_type", DATA_T_STRING, POD(&ptr)); - if (strcmp(ptr,"widget/component-decl-action") && - strcmp(ptr,"widget/component-decl-event") && - strcmp(ptr,"widget/component-decl-cprop")) + /** Pick the function to call to handle this. **/ + char* fn_name; + if (strcmp(decl_type, "event") == 0) fn_name = "addEvent"; + else if (strcmp(decl_type, "cprop") == 0) fn_name = "addProp"; + else if (strcmp(decl_type, "action") == 0) { - htrRenderWidget(s, sub_tree, z+2); - } - else if (strcmp(ptr,"widget/component-decl-action") == 0) - { - /** allow connectors inside component-decl-action **/ - for (j=0;jChildren));j++) + fn_name = "addAction"; + const unsigned int n_con_trees = xaCount(&(sub_tree->Children)); + for (unsigned int j = 0u; j < n_con_trees; j++) { conn_tree = xaGetItem(&(sub_tree->Children), j); wgtrGetPropertyValue(conn_tree, "outer_type", DATA_T_STRING, POD(&ptr)); if (strcmp(ptr,"widget/connector") == 0) { - htrRenderWidget(s, conn_tree, z+2); + if (htrRenderWidget(s, conn_tree, z + 2) != 0) + { + mssError(0, "HTCMPD", "Failed to render connector in component-decl action."); + goto err; + } } } } - sub_tree = NULL; - } - - /** End init for component **/ - htrAddScriptInit_va(s, " cmpd_endinit(wgtrGetNodeRef(ns,\"%STR&SYM\"));\n", name); + else continue; - htcmpd_cleanup: -// if (subobj) objClose(subobj); -// if (subobj_qy) objQueryClose(subobj_qy); - for(i=0;iHints) objFreeHints(param->Hints); - if (param->Name) nmSysFree(param->Name); - nmFree(param, sizeof(HTCmpdParam)); + mssError(0, "HTCMPD", "Failed to write JS function init call."); + goto err; } + sub_tree->RenderFlags |= HT_WGTF_NOOBJECT; + } + + /** Write JS init call. **/ + if (htrAddScriptInit_va(s, "\tcmpd_endinit(decl%POS);\n", id) != 0) + { + mssError(0, "HTCMPD", "Failed to write the err of the JS init call."); + goto err; } - xaDeInit(&attrs); - return rval; + /** Success. **/ + return 0; + + err: + mssError(0, "HTCMPD", + "Failed to render \"%s\":\"%s\" (id: %d).", + tree->Name, tree->Type, id + ); + return -1; } @@ -423,8 +355,5 @@ htcmpdInitialize() /** Declare support for DHTML user interface class **/ htrAddSupport(drv, "dhtml"); - HTCMPD.idcnt = 0; - return 0; } - diff --git a/centrallix/htmlgen/htdrv_connector.c b/centrallix/htmlgen/htdrv_connector.c index bbb24d503..daca80cd6 100644 --- a/centrallix/htmlgen/htdrv_connector.c +++ b/centrallix/htmlgen/htdrv_connector.c @@ -1,9 +1,11 @@ +#include #include #include #include #include #include "ht_render.h" #include "obj.h" +#include "cxlib/util.h" #include "cxlib/mtask.h" #include "cxlib/xarray.h" #include "cxlib/xhash.h" @@ -16,7 +18,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -45,48 +47,32 @@ /************************************************************************/ -/** globals **/ -static struct - { - int idcnt; - } - HTCONN; - - /*** htconnRender - generate the HTML code for the page. ***/ int htconnRender(pHtSession s, pWgtrNode tree, int z) { char* ptr; - char* vstr; - int vint; - double vdbl; char name[64]; char event[32] = ""; char target[128]; char source[128]; char action[32] = ""; - int id, i; - XString xs; - pExpression code; - int first; - int inside_action = 0; - /*char rpt_context[128];*/ + XString xs = { AllocLen: 0 }; + int rval = -1; - if(!s->Capabilities.Dom0NS && !s->Capabilities.Dom1HTML ) + /** Verify browser capabilities. **/ + if (!s->Capabilities.Dom1HTML || !s->Capabilities.Dom2CSS) { - mssError(1,"HTCONN","Netscape DOM or W3C DOM1HTML support required"); - return -1; + mssError(1, "HTCONN", "Unsupported browser: W3C DOM1 HTML and DOM2 CSS support required."); + goto end; } - /** Get an id for this. **/ - id = (HTCONN.idcnt++); - /** Inside a component-decl-action? **/ + bool inside_action = false; if (tree->Parent && wgtrGetPropertyValue(tree->Parent, "outer_type", DATA_T_STRING, POD(&ptr)) == 0 && !strcmp(ptr, "widget/component-decl-action")) { - inside_action = 1; + inside_action = true; wgtrGetPropertyValue(tree->Parent, "name", DATA_T_STRING, POD(&ptr)); strtcpy(event, ptr, sizeof(event)); } @@ -97,14 +83,14 @@ htconnRender(pHtSession s, pWgtrNode tree, int z) if (inside_action) { mssError(1,"HTCONN","Connector inside componentdecl-action cannot have an 'event' property"); - return -1; + goto end; } strtcpy(event,ptr,sizeof(event)); } if (!*event) { mssError(1,"HTCONN","Connector must have an 'event' property"); - return -1; + goto end; } if (wgtrGetPropertyValue(tree,"action",DATA_T_STRING,POD(&ptr)) == 0) @@ -114,25 +100,13 @@ htconnRender(pHtSession s, pWgtrNode tree, int z) if (!*action) { mssError(1,"HTCONN","Connector must have an 'action' property"); - return -1; + goto end; } /** Get name **/ - if (wgtrGetPropertyValue(tree,"name",DATA_T_STRING,POD(&ptr)) != 0) return -1; + if (wgtrGetPropertyValue(tree,"name",DATA_T_STRING,POD(&ptr)) != 0) goto end; strtcpy(name,ptr,sizeof(name)); - /** Are we inside a widget/repeat context? **/ - /*rpt_context[0] = '\0'; - if (!strncmp(name, WGTR_REPEAT_PREFIX, strlen(WGTR_REPEAT_PREFIX))) - { - ptr = strchr(name+strlen(WGTR_REPEAT_PREFIX),'_'); - if (ptr) - { - strtcpy(rpt_context, name, sizeof(rpt_context)); - rpt_context[ptr - name] = '\0'; - } - }*/ - /** Source/target **/ if (wgtrGetPropertyValue(tree,"target",DATA_T_STRING,POD(&ptr)) != 0) target[0] = '\0'; @@ -146,106 +120,149 @@ htconnRender(pHtSession s, pWgtrNode tree, int z) if (inside_action) { mssError(1,"HTCONN","Connector inside componentdecl-action cannot have a 'source' property"); - return -1; + goto end; } strtcpy(source, ptr, sizeof(source)); } /** Build the param list **/ - xsInit(&xs); - first = 1; + if (check(xsInit(&xs)) != 0) goto end; + bool first = true; for(ptr = wgtrFirstPropertyName(tree); ptr; ptr = wgtrNextPropertyName(tree)) { if (!strcmp(ptr, "event") || !strcmp(ptr, "target") || !strcmp(ptr, "action") || !strcmp(ptr, "source") || !strcmp(ptr, "condition")) continue; - if (!first) xsConcatenate(&xs, ",", 1); - first = 0; - switch(wgtrGetPropertyType(tree, ptr)) - { + const int property_type = wgtrGetPropertyType(tree, ptr); + if (!first && check(xsConcatenate(&xs, ",", 1)) != 0) goto err_param; + first = false; + switch (property_type) + { case DATA_T_CODE: + { + pExpression code; wgtrGetPropertyValue(tree, ptr, DATA_T_CODE, POD(&code)); xsConcatQPrintf(&xs,"%STR&SYM:{type:'exp', value:function(_context,_this,ep) { with(ep) { return ", ptr); expGenerateText(code, NULL, xsWrite, &xs, '\0', "javascript", EXPR_F_RUNCLIENT); xsConcatenate(&xs,"; } } }",7); break; + } + case DATA_T_INTEGER: - wgtrGetPropertyValue(tree, ptr, DATA_T_INTEGER,POD(&vint)); - xsConcatQPrintf(&xs,"%STR&SYM:{type:'int', value:%INT}",ptr,vint); + { + int vint; + wgtrGetPropertyValue(tree, ptr, DATA_T_INTEGER, POD(&vint)); + if (xsConcatQPrintf(&xs, "%STR&SYM:{type:'int', value:%INT}", ptr, vint) < 0) + { + mssError(1, "HTCONN", "Failed to write int JSON."); + goto err_param; + } break; + } + case DATA_T_DOUBLE: + { + double vdbl; wgtrGetPropertyValue(tree, ptr, DATA_T_DOUBLE,POD(&vdbl)); - xsConcatQPrintf(&xs,"%STR&SYM:{type:'dbl', value:%DBL}", ptr, vdbl); + if (xsConcatQPrintf(&xs, "%STR&SYM:{type:'dbl', value:%DBL}", ptr, vdbl) < 0) + { + mssError(1, "HTCONN", "Failed to write int JSON."); + goto err_param; + } break; + } + case DATA_T_STRING: - wgtrGetPropertyValue(tree, ptr, DATA_T_STRING,POD(&vstr)); - if (!strpbrk(vstr," !@#$%^&*()-=+`~;:,.<>/?'\"[]{}\\|")) - { - xsConcatQPrintf(&xs, "%STR&SYM:{type:'sym', value:'%STR&SYM', namespace:ns}", ptr, vstr); + { + char* vstr; + wgtrGetPropertyValue(tree, ptr, DATA_T_STRING, POD(&vstr)); + if (strpbrk(vstr, " !@#$%^&*()-=+`~;:,.<>/?'\"[]{}\\|") == NULL) + { + if (xsConcatQPrintf(&xs, "%STR&SYM:{type:'sym', value:'%STR&SYM', namespace:ns}", ptr, vstr) < 0) + { + mssError(1, "HTCONN", "Failed to write string symbol parameter JSON."); + goto err_param; + } } else - { - xsConcatQPrintf(&xs, "%STR&SYM:{type:'str', value:'%STR&JSSTR'}", ptr, vstr); + { + if (xsConcatQPrintf(&xs, "%STR&SYM:{type:'str', value:'%STR&JSSTR'}", ptr, vstr) < 0) + { + mssError(1, "HTCONN", "Failed to write string parameter JSON."); + goto err_param; + } } break; + } + + default: + mssError(1, "HTCONN", "Unsupported type %d.", property_type); + goto err_param; } - } - /** Add a script init to install the connector **/ -#if 00 - if (*rpt_context && *source) - { - /** Try repeat-specific node first, then normally named node **/ - htrAddScriptInit_va(s, " var src=nodes[\"%STR&SYM_%STR&SYM\"];\n", - rpt_context, - source); - htrAddScriptInit_va(s, " if(!src) src=nodes[\"%STR&SYM\"];\n", - source); - } - else - { - htrAddScriptInit_va(s, " var src=%[wgtrGetParent(nodes[\"%STR&SYM\"])%]%[nodes[\"%STR&SYM\"]%];\n", -#endif - /*htrAddScriptInit_va(s, " var src=%[wgtrGetParent(wgtrGetNodeRef(ns,\"%STR&SYM\"))%]%[wgtrGetNodeRef(ns, \"%STR&SYM\")%];\n", - !*source, name, - *source, source);*/ -#if 00 + /** Success. **/ + continue; + + err_param: + mssError(0, "HTCONN", + "Failed to write JSON for connector parameter %s of type %d.", + ptr, property_type + ); + goto end; } - if (*rpt_context && *target) + + /** Determine the target in a new scope. **/ + const int no_source = (source == NULL || source[0] == '\0'); + if (htrAddScriptInit_va(s, "\t{ " + "let target = wgtrGetNodeRef(ns, '%STR&SYM'); " + "%[target = wgtrGetParent(target); %]" + "%[target = wgtrGetParent(target); %]", + (no_source) ? name : source, + (no_source), + (inside_action) // only true when no_source is true. + ) != 0) { - htrAddScriptInit_va(s, " var tgt=\"%STR&SYM_%STR&SYM\";\n", - rpt_context, - target); - htrAddScriptInit_va(s, " if(!nodes[tgt]) tgt='%STR&SYM';\n", - target); + mssError(0, "HTCONN", "Failed to write JS target."); + goto end; } - else + + /** Call the functions on the target, and close the scope. **/ + const int no_target = (target == NULL || target[0] == '\0'); + if (htrAddScriptInit_va(s, "target" + ".ifcProbe(ifEvent)" + ".Connect('%STR&SYM', '%STR&SYM', '%STR&SYM', {%STR}, ns); " + "}\n", + event, (no_target) ? tree->Parent->Name : target, action, xs.String + ) != 0) { -#endif - /*htrAddScriptInit_va(s, " var tgt=%['%STR&SYM'%]%[wgtrGetName(wgtrGetParent(wgtrGetNodeRef(ns,\"%STR&SYM\")))%];\n", - *target, target, - !*target, name);*/ -#if 00 + mssError(0, "HTCONN", "Failed to write JS event connect."); + goto end; } -#endif - //htrAddScriptInit_va(s, " src.ifcProbe(ifEvent).Connect('%STR&SYM', tgt, '%STR&SYM', {%STR});\n", - htrAddScriptInit_va(s, " %[wgtrGetParent(wgtrGetParent(wgtrGetNodeRef(ns,\"%STR&SYM\")))%]%[wgtrGetParent(wgtrGetNodeRef(ns,\"%STR&SYM\"))%]%[wgtrGetNodeRef(ns, \"%STR&SYM\")%].ifcProbe(ifEvent).Connect('%STR&SYM', %['%STR&SYM'%]%[wgtrGetName(wgtrGetParent(wgtrGetNodeRef(ns,\"%STR&SYM\")))%], '%STR&SYM', {%STR}, ns);\n", - inside_action, name, - !*source && !inside_action, name, - *source && !inside_action, source, - event, - *target, target, !*target, name, - action, - xs.String); xsDeInit(&xs); + xs.AllocLen = 0; - htrAddScriptInclude(s, "/sys/js/htdrv_connector.js", 0); + /** Include the connector library. **/ + if (htrAddScriptInclude(s, "/sys/js/htdrv_connector.js", 0) != 0) goto end; tree->RenderFlags |= HT_WGTF_NOOBJECT; - /** Check for more sub-widgets within the conn entity. **/ - for (i=0;iChildren));i++) - htrRenderWidget(s, xaGetItem(&(tree->Children), i), z+1); + /** Render children. **/ + if (htrRenderSubwidgets(s, tree, z + 1) != 0) goto end; - return 0; + /** Success. **/ + rval = 0; + + end: + if (rval != 0) + { + mssError(0, "HTCONN", + "Failed to render \"%s\":\"%s\".", + tree->Name, tree->Type + ); + } + + /** Clean up. **/ + if (xs.AllocLen != 0) check(xsDeInit(&xs)); /** Failure ignored. **/ + + return rval; } @@ -272,7 +289,5 @@ htconnInitialize() htrAddSupport(drv, "dhtml"); - HTCONN.idcnt = 0; - return 0; } diff --git a/centrallix/htmlgen/htdrv_datetime.c b/centrallix/htmlgen/htdrv_datetime.c index 703ccbf5f..e5624aca6 100644 --- a/centrallix/htmlgen/htdrv_datetime.c +++ b/centrallix/htmlgen/htdrv_datetime.c @@ -4,6 +4,7 @@ #include #include "ht_render.h" #include "obj.h" +#include "cxlib/util.h" #include "cxlib/mtask.h" #include "cxlib/xarray.h" #include "cxlib/xhash.h" @@ -15,7 +16,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -66,7 +67,6 @@ htdtRender(pHtSession s, pWgtrNode tree, int z) char form[64]; int type; int x,y,w,h,w2=184,h2=190; - int id, i; int rval; int search_by_range; int date_only = 0; @@ -76,15 +76,16 @@ htdtRender(pHtSession s, pWgtrNode tree, int z) pObjQuery qy; pObject qy_obj; - if(!s->Capabilities.Dom0NS && !s->Capabilities.Dom1HTML) + /** Get an id for this. **/ + const int id = (HTDT.idcnt++); + + /** Verify browser capabilities. **/ + if (!s->Capabilities.Dom1HTML || !s->Capabilities.Dom2CSS) { - mssError(1,"HTDT","Netscape or W3C DOM support required"); - return -1; + mssError(1, "HTDT", "Unsupported browser: W3C DOM1 HTML and DOM2 CSS support required."); + goto err; } - /** Get an id for this. **/ - id = (HTDT.idcnt++); - /** Get x,y,w,h of this object **/ if (wgtrGetPropertyValue(tree,"x",DATA_T_INTEGER,POD(&x)) != 0) { @@ -174,94 +175,169 @@ htdtRender(pHtSession s, pWgtrNode tree, int z) } if (strlen(initialdate)) { - objDataToDateTime(DATA_T_STRING, initialdate, &dt, NULL); - qpfPrintf(NULL, initialdate, sizeof(initialdate), "%STR %INT %INT, %INT:%INT:%INT", obj_short_months[dt.Part.Month], - dt.Part.Day+1, - dt.Part.Year+1900, - dt.Part.Hour, - dt.Part.Minute, - dt.Part.Second); + snprintf( + initialdate, sizeof(initialdate), + "%s %d %d, %d:%d%d", + obj_short_months[dt.Part.Month], + dt.Part.Day + 1, + dt.Part.Year + 1900, + dt.Part.Hour, + dt.Part.Minute, + dt.Part.Second + ); } /** Get colors **/ htrGetBackground(tree, NULL, 1, bgcolor, sizeof(bgcolor)); if (!*bgcolor) strcpy(bgcolor,"background-color:white;"); - //else strcpy(bgcolor, "bgcolor=green"); - -/////////////////////////////////////// -// if (wgtrGetPropertyValue(tree,"bgcolor",DATA_T_STRING,POD(&ptr)) == 0) -// sprintf(bgcolor,"%.40s",ptr); -// else { - //mssError(1,"HTDD","Date Time widget must have a 'bgcolor' property"); - //return -1; - //strcpy(bgcolor,"blue"); -// } if (wgtrGetPropertyValue(tree,"fgcolor",DATA_T_STRING,POD(&ptr)) == 0) strtcpy(fgcolor,ptr,sizeof(fgcolor)); else strcpy(fgcolor,"black"); - /** Ok, write the style header items. **/ - htrAddStylesheetItem_va(s,"\t#dt%POSbtn { OVERFLOW:hidden; POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; Z-INDEX:%POS; cursor:default; border:1px outset #e0e0e0; %STR }\n",id,x,y,w,h,z, bgcolor); - htrAddStylesheetItem_va(s,"\t#dt%POScon1 { OVERFLOW:hidden; POSITION:absolute; VISIBILITY:inherit; LEFT:1px; TOP:1px; WIDTH:%POSpx; HEIGHT:%POSpx; Z-INDEX:%POS; }\n",id,w-20,h-2,z+1); - htrAddStylesheetItem_va(s,"\t#dt%POScon2 { OVERFLOW:hidden; POSITION:absolute; VISIBILITY:hidden; LEFT:1px; TOP:1px; WIDTH:%POSpx; HEIGHT:%POSpx; Z-INDEX:%POS; }\n",id,w-20,h-2,z+1); + /** Write style headers. **/ + if (htrAddStylesheetItem_va(s, + "\t\t#dt%POSbtn { " + "position:absolute; " + "visibility:inherit; " + "overflow:hidden; " + "cursor:pointer; " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "width:"ht_flex_format"; " + "height:"ht_flex_format"; " + "z-index:%POS; " + "border:1px outset #e0e0e0; " + "%STR " + "}\n", + id, + ht_flex_x(x, tree), + ht_flex_y(y, tree), + ht_flex_w(w, tree), + ht_flex_h(h, tree), + z, + bgcolor + ) != 0) + { + mssError(0, "HTDT", "Failed to write datetime button CSS."); + goto err; + } + if (htrAddStylesheetItem_va(s, + "\t\t.dt%POScon { " + "position:absolute; " + "overflow:hidden; " + "left:1px; " + "top:1px; " + "width:calc(100%% - 20px); " + "height:calc(100%% - 2px); " + "z-index:%POS; " + "}\n", + id, + z + 1 + ) != 0) + { + mssError(0, "HTDT", "Failed to write datetime con CSS."); + goto err; + } + if (htrAddStylesheetItem_va(s, + "\t\t.dt_dropdown { " + "cursor:default; " + "}\n", + id + ) != 0) + { + mssError(0, "HTDT", "Failed to write datetime dropdown CSS."); + goto err; + } - /** Write named global **/ - htrAddScriptGlobal(s, "dt_current", "null", 0); - htrAddScriptGlobal(s, "dt_timeout", "null", 0); - htrAddScriptGlobal(s, "dt_timeout_fn", "null", 0); - htrAddScriptGlobal(s, "dt_img_y", "0", 0); + /** Link the widget to the DOM node. **/ + if (htrAddWgtrObjLinkage_va(s, tree, "dt%POSbtn", id) != 0) + { + mssError(0, "HTDT", "Failed to add object linkage."); + goto err; + } - htrAddWgtrObjLinkage_va(s, tree, "dt%POSbtn",id); + /** Write named globals. **/ + if (htrAddScriptGlobal(s, "dt_current", "null", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "dt_img_y", "0", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "dt_timeout_fn", "null", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "dt_timeout", "null", 0) != 0) goto err; /** Script includes **/ - htrAddScriptInclude(s, "/sys/js/ht_utils_date.js", 0); - htrAddScriptInclude(s, "/sys/js/ht_utils_string.js", 0); - htrAddScriptInclude(s, "/sys/js/htdrv_datetime.js", 0); - htrAddScriptInclude(s, "/sys/js/ht_utils_layers.js", 0); - - /** Script initialization call. **/ - htrAddScriptInit_va(s, " dt_init({layer:wgtrGetNodeRef(ns,\"%STR&SYM\"),c1:htr_subel(wgtrGetNodeRef(ns,\"%STR&SYM\"),\"dt%POScon1\"),c2:htr_subel(wgtrGetNodeRef(ns,\"%STR&SYM\"),\"dt%POScon2\"),id:\"%STR&JSSTR\", background:\"%STR&JSSTR\", foreground:\"%STR&JSSTR\", fieldname:\"%STR&JSSTR\", form:\"%STR&JSSTR\", width:%INT, height:%INT, width2:%INT, height2:%INT, sbr:%INT, donly:%INT, dtime:\"%STR&JSSTR\"})\n", - name, - name,id, - name,id, - initialdate, bgcolor, fgcolor, fieldname, form, - w-20, h, w2,h2, + if (htrAddScriptInclude(s, "/sys/js/ht_utils_date.js", 0) != 0) goto err; + if (htrAddScriptInclude(s, "/sys/js/ht_utils_layers.js", 0) != 0) goto err; + if (htrAddScriptInclude(s, "/sys/js/ht_utils_string.js", 0) != 0) goto err; + if (htrAddScriptInclude(s, "/sys/js/htdrv_datetime.js", 0) != 0) goto err; + + /** Add the event handling scripts. **/ + if (htrAddEventHandlerFunction(s, "document", "MOUSEDOWN", "dt","dt_mousedown") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEMOVE", "dt","dt_mousemove") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEOUT", "dt","dt_mouseout") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEOVER", "dt","dt_mouseover") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEUP", "dt","dt_mouseup") != 0) goto err; + + /** Write the initialization call in its own scope. **/ + if (htrAddScriptInit_va(s, "\t{ " + "const layer = wgtrGetNodeRef(ns, '%STR&SYM'); " + "dt_init({ " + "layer, " + "c1:htr_subel(layer, 'dt%POScon1'), " + "c2:htr_subel(layer, 'dt%POScon2'), " + "id:'%STR&JSSTR', " + "background:'%STR&JSSTR', " + "foreground:'%STR&JSSTR', " + "fieldname:'%STR&JSSTR', " + "form:'%STR&JSSTR', " + "width:%INT, " + "height:%INT, " + "width2:%INT, " + "height2:%INT, " + "sbr:%INT, " + "donly:%INT, " + "dtime:'%STR&JSSTR', " + "}); }\n", + name, id, id, + initialdate, + bgcolor, fgcolor, fieldname, form, + w - 20, h, w2, h2, search_by_range, - date_only, default_time); - - /** HTML body
elements for the layers. **/ - htrAddBodyItem_va(s,"
\n" - "\n", id); - /*htrAddBodyItem_va(s,"\n",w); - htrAddBodyItem(s, " \n"); - htrAddBodyItem_va(s," \n",w-2); - htrAddBodyItem(s, " \n"); - htrAddBodyItem_va(s," \n",h-2); - htrAddBodyItem(s, " \n"); - htrAddBodyItem_va(s," \n",h-2); - htrAddBodyItem(s, " \n"); - htrAddBodyItem_va(s," \n",w-2); - htrAddBodyItem(s, " \n"); - htrAddBodyItem(s, "
\n");*/ - htrAddBodyItem_va(s,"
\n",id); - htrAddBodyItem_va(s,"
\n",id); - htrAddBodyItem(s, "
\n"); - - /** Add the event handling scripts **/ - htrAddEventHandlerFunction(s, "document","MOUSEDOWN","dt","dt_mousedown"); - htrAddEventHandlerFunction(s, "document","MOUSEUP","dt","dt_mouseup"); - htrAddEventHandlerFunction(s, "document","MOUSEMOVE","dt","dt_mousemove"); - htrAddEventHandlerFunction(s, "document","MOUSEOVER","dt","dt_mouseover"); - htrAddEventHandlerFunction(s, "document","MOUSEOUT","dt","dt_mouseout"); - - /** Check for more sub-widgets within the datetime. **/ - for (i=0;iChildren));i++) - htrRenderWidget(s, xaGetItem(&(tree->Children), i), z+1); + date_only, default_time + ) != 0) + { + mssError(0, "HTDT", "Failed to render child widgets."); + goto err; + } - return 0; + /** Write HTML. **/ + if (htrAddBodyItem_va(s, + "
" + "icon" + "
" + "" + "
\n", + id, + id, id, + id, id + ) != 0) + { + mssError(0, "HTDT", "Failed to write datetime HTML."); + goto err; + } + + /** Render children. **/ + if (htrRenderSubwidgets(s, tree, z + 1) != 0) goto err; + + /** Success. **/ + return 0; + + err: + mssError(0, "HTDT", + "Failed to render \"%s\":\"%s\" (id: %d).", + tree->Name, tree->Type, id + ); + return -1; } @@ -301,4 +377,3 @@ htdtInitialize() return 0; } - diff --git a/centrallix/htmlgen/htdrv_dropdown.c b/centrallix/htmlgen/htdrv_dropdown.c index 335fe7f48..4838693f9 100644 --- a/centrallix/htmlgen/htdrv_dropdown.c +++ b/centrallix/htmlgen/htdrv_dropdown.c @@ -15,7 +15,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 2000-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 2000-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -46,6 +46,13 @@ static struct { int idcnt; } HTDD; +/** Dropdown modes. **/ +#define HTDD_STATIC 0 +#define HTDD_DYNAMIC_SERVER 1 +#define HTDD_DYNAMIC 2 +#define HTDD_DYNAMIC_CLIENT HTDD_DYNAMIC +#define HTDD_OBJECTSOURCE 3 +#define HTDD_EASTER_EGG_4 4 /* htddRender - generate the HTML code for the page. @@ -65,7 +72,6 @@ int htddRender(pHtSession s, pWgtrNode tree, int z) { char *attr; int type, rval, mode, flag=0; int x,y,w,h; - int id, i; int num_disp; int query_multiselect; int invalid_select_default; @@ -76,14 +82,15 @@ int htddRender(pHtSession s, pWgtrNode tree, int z) { pObject qy_obj; pWgtrNode subtree; - if(!s->Capabilities.Dom0NS && !s->Capabilities.Dom1HTML) - { - mssError(1,"HTDD","Netscape or W3C DOM support required"); - return -1; - } + /** Get an id for this. **/ + const int id = (HTDD.idcnt++); - /** Get an id for this. **/ - id = (HTDD.idcnt++); + /** Verify browser capabilities. **/ + if (!s->Capabilities.Dom1HTML || !s->Capabilities.Dom2CSS) + { + mssError(1, "HTDD", "Unsupported browser: W3C DOM1 HTML and DOM2 CSS support required."); + goto err; + } /** Get x,y of this object **/ if (wgtrGetPropertyValue(tree,"x",DATA_T_INTEGER,POD(&x)) != 0) x=0; @@ -144,62 +151,153 @@ int htddRender(pHtSession s, pWgtrNode tree, int z) { if (wgtrGetPropertyValue(tree,"name",DATA_T_STRING,POD(&ptr)) != 0) return -1; strtcpy(name,ptr,sizeof(name)); - /** Ok, write the style header items. **/ - htrAddStylesheetItem_va(s,"\t#dd%POSbtn { OVERFLOW:hidden; POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; HEIGHT:%POSpx; WIDTH:%POSpx; Z-INDEX:%POS; cursor:default; background-color: %STR&CSSVAL; border:1px outset #e0e0e0;}\n",id,x,y,h,w,z,bgstr); - if (*textcolor) { - htrAddStylesheetItem_va(s,"\t#dd%POSbtn { color: %STR&CSSVAL; }\n",id,textcolor); - } - htrAddStylesheetItem_va(s,"\t#dd%POScon1 { OVERFLOW:hidden; POSITION:absolute; VISIBILITY:inherit; LEFT:1px; TOP:1px; WIDTH:1024px; HEIGHT:%POSpx; Z-INDEX:%POS; }\n",id,h-2,z+1); - htrAddStylesheetItem_va(s,"\t#dd%POScon2 { OVERFLOW:hidden; POSITION:absolute; VISIBILITY:hidden; LEFT:1px; TOP:1px; WIDTH:1024px; HEIGHT:%POSpx; Z-INDEX:%POS; }\n",id,h-2,z+1); - - htrAddScriptGlobal(s, "dd_current", "null", 0); - htrAddScriptGlobal(s, "dd_lastkey", "null", 0); - htrAddScriptGlobal(s, "dd_target_img", "null", 0); - htrAddScriptGlobal(s, "dd_thum_y","0",0); - htrAddScriptGlobal(s, "dd_timeout","null",0); - htrAddScriptGlobal(s, "dd_click_x","0",0); - htrAddScriptGlobal(s, "dd_click_y","0",0); - htrAddScriptGlobal(s, "dd_incr","0",0); - htrAddScriptGlobal(s, "dd_cur_mainlayer","null",0); - htrAddWgtrObjLinkage_va(s, tree, "dd%POSbtn", id); - - htrAddScriptInclude(s, "/sys/js/ht_utils_layers.js", 0); - htrAddScriptInclude(s, "/sys/js/ht_utils_string.js", 0); - htrAddScriptInclude(s, "/sys/js/ht_utils_hints.js", 0); - htrAddScriptInclude(s, "/sys/js/htdrv_dropdown.js", 0); - - htrAddEventHandlerFunction(s, "document","MOUSEMOVE", "dd", "dd_mousemove"); - htrAddEventHandlerFunction(s, "document","MOUSEOVER", "dd", "dd_mouseover"); - htrAddEventHandlerFunction(s, "document","MOUSEUP", "dd", "dd_mouseup"); - htrAddEventHandlerFunction(s, "document","MOUSEDOWN", "dd", "dd_mousedown"); - htrAddEventHandlerFunction(s, "document","MOUSEOUT", "dd", "dd_mouseout"); - if (s->Capabilities.Dom1HTML) - htrAddEventHandlerFunction(s, "document", "CONTEXTMENU", "dd", "dd_contextmenu"); + /** Write basic element CSS. **/ + if (htrAddStylesheetItem_va(s, + "\t\t#dd%POSbtn { " + "position:absolute; " + "visibility:inherit; " + "overflow:hidden; " + "cursor:pointer; " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "width:"ht_flex_format"; " + "height:"ht_flex_format"; " + "z-index:%POS; " + "background-color: %STR&CSSVAL; " + "border:1px outset #e0e0e0; " + "}\n", + id, + ht_flex_x(x, tree), + ht_flex_y(y, tree), + ht_flex_w(w, tree), + ht_flex_h(h, tree), + z, + bgstr + ) != 0) + { + mssError(0, "HTDD", "Failed to write base btn CSS."); + goto err; + } + if (*textcolor) + { + if (htrAddStylesheetItem_va(s, + "\t\t#dd%POSbtn { " + "color:%STR&CSSVAL; " + "}\n", + id, + textcolor + ) != 0) + { + mssError(0, "HTDD", "Failed to write btn CSS text color."); + goto err; + } + } + if (htrAddStylesheetItem_va(s, + "\t\t.dd%POScon { " + "position:absolute; " + "overflow:hidden; " + "left:1px; " + "top:1px; " + "width:1024px; " + "height:%POSpx; " + "z-index:%POS; " + "}\n", + id, + h - 2, + z + 1 + ) != 0) + { + mssError(0, "HTDD", "Failed to write con CSS."); + goto err; + } + + /** Link the widget to the DOM node. **/ + if (htrAddWgtrObjLinkage_va(s, tree, "dd%POSbtn", id) != 0) + { + mssError(0, "HTDD", "Failed to add object linkage."); + goto err; + } + + /** Write JS globals. **/ + if (htrAddScriptGlobal(s, "dd_click_x", "0", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "dd_click_y", "0", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "dd_cur_mainlayer", "null", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "dd_current", "null", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "dd_incr", "0", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "dd_lastkey", "null", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "dd_target_img", "null", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "dd_thum_y", "0", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "dd_timeout", "null", 0) != 0) goto err; + + /** Write JS script includes. **/ + if (htrAddScriptInclude(s, "/sys/js/ht_utils_hints.js", 0) != 0) goto err; + if (htrAddScriptInclude(s, "/sys/js/ht_utils_layers.js", 0) != 0) goto err; + if (htrAddScriptInclude(s, "/sys/js/ht_utils_string.js", 0) != 0) goto err; + if (htrAddScriptInclude(s, "/sys/js/htdrv_dropdown.js", 0) != 0) goto err; + + /** Register JS event handlers. **/ + if (htrAddEventHandlerFunction(s, "document", "CONTEXTMENU", "dd", "dd_contextmenu") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEDOWN", "dd", "dd_mousedown") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEMOVE", "dd", "dd_mousemove") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEOUT", "dd", "dd_mouseout") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEOVER", "dd", "dd_mouseover") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEUP", "dd", "dd_mouseup") != 0) goto err; /** Get the mode (default to 1, dynamicpage) **/ - mode = 0; + mode = HTDD_STATIC; if (wgtrGetPropertyValue(tree,"mode",DATA_T_STRING,POD(&ptr)) == 0) { - if (!strcmp(ptr,"static")) mode = 0; - else if (!strcmp(ptr,"dynamic_server")) mode = 1; - else if (!strcmp(ptr,"dynamic")) mode = 2; - else if (!strcmp(ptr,"dynamic_client")) mode = 2; - else if (!strcmp(ptr,"objectsource")) mode = 3; + if (strcmp(ptr, "static") == 0) mode = HTDD_STATIC; + else if (strcmp(ptr, "dynamic_server") == 0) mode = HTDD_DYNAMIC_SERVER; + else if (strcmp(ptr, "dynamic") == 0) mode = HTDD_DYNAMIC; + else if (strcmp(ptr, "dynamic_client") == 0) mode = HTDD_DYNAMIC_CLIENT; + else if (strcmp(ptr, "objectsource") == 0) mode = HTDD_OBJECTSOURCE; else { - mssError(1,"HTDD","Dropdown widget has not specified a valid mode."); + mssError(1, "HTDD", "Invalid dropdown widget 'mode' value: \"%s\"", ptr); return -1; } } - sql = 0; + sql = NULL; if (wgtrGetPropertyValue(tree,"sql",DATA_T_STRING,POD(&sql)) != 0 && mode != 0 && mode != 3) { mssError(1, "HTDD", "SQL parameter was not specified for dropdown widget"); return -1; } htrCheckAddExpression(s,tree,name,"sql"); - /** Script initialization call. **/ - htrAddScriptInit_va(s," dd_init({layer:wgtrGetNodeRef(ns,\"%STR&SYM\"), c1:htr_subel(wgtrGetNodeRef(ns,\"%STR&SYM\"), \"dd%POScon1\"), c2:htr_subel(wgtrGetNodeRef(ns,\"%STR&SYM\"), \"dd%POScon2\"), background:'%STR&JSSTR', highlight:'%STR&JSSTR', fieldname:'%STR&JSSTR', numDisplay:%INT, mode:%INT, sql:'%STR&JSSTR', width:%INT, height:%INT, form:'%STR&JSSTR', osrc:'%STR&JSSTR', qms:%INT, ivs:%INT, popup_width:%INT});\n", name, name, id, name, id, bgstr, hilight, fieldname, num_disp, mode, sql?sql:"", w, h, form, osrc, query_multiselect, invalid_select_default, pop_w); + /** Write the initialization call in its own scope. **/ + if (htrAddScriptInit_va(s, "\t{ " + "const layer = wgtrGetNodeRef(ns, '%STR&SYM'); " + "dd_init({ " + "layer, " + "c1:htr_subel(layer, 'dd%POScon1'), " + "c2:htr_subel(layer, 'dd%POScon2'), " + "background:'%STR&JSSTR', " + "highlight:'%STR&JSSTR', " + "fieldname:'%STR&JSSTR', " + "numDisplay:%INT, " + "mode:%INT, " + "sql:'%STR&JSSTR', " + "form:'%STR&JSSTR', " + "osrc:'%STR&JSSTR', " + "qms:%INT, " + "ivs:%INT, " + "width:%INT, " + "height:%INT, " + "popup_width:%INT, " + "}); }\n", + name, id, id, + bgstr, hilight, + fieldname, num_disp, mode, + (sql != NULL) ? sql : "", + form, osrc, query_multiselect, + invalid_select_default, + w, h, pop_w + ) != 0) + { + mssError(0, "HTDD", "Failed to write JS init call."); + goto err; + } /** HTML body
element for the layers. **/ htrAddBodyItem_va(s,"
\n" @@ -215,12 +313,12 @@ int htddRender(pHtSession s, pWgtrNode tree, int z) { htrAddBodyItem_va(s," \n",w-2); htrAddBodyItem(s, " \n"); htrAddBodyItem(s, "\n");*/ - htrAddBodyItem_va(s,"
\n",id); - htrAddBodyItem_va(s,"
\n",id); + htrAddBodyItem_va(s,"
\n", id, id); + htrAddBodyItem_va(s,"\n", id, id); htrAddBodyItem(s, "
\n"); /* Read and initialize the dropdown items */ - if (mode == 1) { + if (mode == HTDD_DYNAMIC_SERVER) { /** The result set from this SQL query can take two forms: positional or named. ** For Positional, the params are: label, value, selected, group, hidden. ** For Named, the above names can appear in any order. @@ -228,7 +326,7 @@ int htddRender(pHtSession s, pWgtrNode tree, int z) { **/ if ((qy = objMultiQuery(s->ObjSession, sql, NULL, 0))) { flag=0; - htrAddScriptInit_va(s," dd_add_items(wgtrGetNodeRef(ns,\"%STR&SYM\"), [",name); + htrAddScriptInit_va(s, "\tdd_add_items(wgtrGetNodeRef(ns, '%STR&SYM'), [",name); while ((qy_obj = objQueryFetch(qy, O_RDONLY))) { // Label attr = objGetFirstAttr(qy_obj); @@ -312,18 +410,21 @@ int htddRender(pHtSession s, pWgtrNode tree, int z) { objQueryClose(qy); } } - else if(mode==3) { + else if (mode == HTDD_OBJECTSOURCE) { /* get objects from form */ } flag=0; - for (i=0;iChildren));i++) + const int n_children = xaCount(&(tree->Children)); + for (unsigned int i = 0u; i < n_children; i++) { subtree = xaGetItem(&(tree->Children), i); if (!strcmp(subtree->Type, "widget/dropdownitem")) subtree->RenderFlags |= HT_WGTF_NOOBJECT; - if (!strcmp(subtree->Type,"widget/dropdownitem") && mode == 0) + + /** Write JS to render dropdown items in static mode. **/ + if (!strcmp(subtree->Type,"widget/dropdownitem") && mode == HTDD_STATIC) { if (wgtrGetPropertyValue(subtree,"label",DATA_T_STRING,POD(&ptr)) != 0) { @@ -359,7 +460,7 @@ int htddRender(pHtSession s, pWgtrNode tree, int z) { } else { - htrRenderWidget(s, subtree, z+1); + if (htrRenderWidget(s, subtree, z + 1) != 0) goto err; } } if (flag) @@ -371,6 +472,13 @@ int htddRender(pHtSession s, pWgtrNode tree, int z) { return 0; + + err: + mssError(0, "HTDD", + "Failed to render \"%s\":\"%s\" (id: %d).", + tree->Name, tree->Type, id + ); + return -1; } @@ -409,4 +517,3 @@ int htddInitialize() { return 0; } - diff --git a/centrallix/htmlgen/htdrv_editbox.c b/centrallix/htmlgen/htdrv_editbox.c index 0223038f9..5d5bc41f4 100644 --- a/centrallix/htmlgen/htdrv_editbox.c +++ b/centrallix/htmlgen/htdrv_editbox.c @@ -15,7 +15,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -61,24 +61,25 @@ htebRender(pHtSession s, pWgtrNode tree, int z) char descfg[64]; char descr[128]; int x=-1,y=-1,w,h; - int id, i; + int rval = -1; int is_readonly = 0; int is_raised = 0; - char* tooltip; + char* tooltip = NULL; int maxchars; char fieldname[HT_FIELDNAME_SIZE]; char form[64]; int box_offset; - if(!s->Capabilities.Dom0NS && !s->Capabilities.Dom0IE && !s->Capabilities.Dom2Events) + /** Get an id for this. **/ + const int id = (HTEB.idcnt++); + + /** Verify browser capabilities. **/ + if (!s->Capabilities.Dom1HTML || !s->Capabilities.Dom2CSS) { - mssError(1,"HTEB","Netscape, IE, or Dom2Events support required"); - return -1; + mssError(1, "HTEB", "Unsupported browser: W3C DOM1 HTML and DOM2 CSS support required."); + goto end; } - /** Get an id for this. **/ - id = (HTEB.idcnt++); - /** Get x,y,w,h of this object **/ if (wgtrGetPropertyValue(tree,"x",DATA_T_INTEGER,POD(&x)) != 0) x=0; if (wgtrGetPropertyValue(tree,"y",DATA_T_INTEGER,POD(&y)) != 0) y=0; @@ -137,6 +138,11 @@ htebRender(pHtSession s, pWgtrNode tree, int z) tooltip=nmSysStrdup(ptr); else tooltip=nmSysStrdup(""); + if (tooltip == NULL) + { + mssError(1, "HTEB", "Failed to allocate tooltip."); + goto end; + } if (wgtrGetPropertyValue(tree,"form",DATA_T_STRING,POD(&ptr)) == 0) strtcpy(form,ptr,sizeof(form)); @@ -149,60 +155,155 @@ htebRender(pHtSession s, pWgtrNode tree, int z) box_offset = 0; /** Ok, write the style header items. **/ - htrAddStylesheetItem_va(s,"\t#eb%POSbase { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; Z-INDEX:%POS; overflow:hidden; }\n",id,x,y,w-2*box_offset,z); - htrAddStylesheetItem_va(s,"\t#eb%POScon1 { VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; Z-INDEX:%POS; border:none; }\n",id,5,0,w-10,z+1); - - /** Write named global **/ - htrAddWgtrObjLinkage_va(s, tree, "eb%POSbase",id); - - /** Global for ibeam cursor layer **/ - htrAddScriptGlobal(s, "text_metric", "null", 0); - htrAddScriptGlobal(s, "eb_current", "null", 0); - - /** Script include to get functions **/ - htrAddScriptInclude(s, "/sys/js/htdrv_editbox.js", 0); - htrAddScriptInclude(s, "/sys/js/ht_utils_string.js", 0); - htrAddScriptInclude(s, "/sys/js/ht_utils_cursor.js", 0); - htrAddScriptInclude(s, "/sys/js/ht_utils_hints.js", 0); + const int base_w = w - (2 * box_offset); + if (htrAddStylesheetItem_va(s, + "\t\t#eb%POSbase { " + "position:absolute; " + "visibility:inherit; " + "overflow:hidden; " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "width:"ht_flex_format"; " + "z-index:%POS; " + "}\n", + id, + ht_flex(x, ht_get_parent_w(tree), ht_get_fl_x(tree)), + ht_flex(y, ht_get_parent_h(tree), ht_get_fl_y(tree)), + ht_flex(base_w, ht_get_parent_w(tree), ht_get_fl_w(tree)), + z + ) != 0) + { + mssError(0, "HTEB", "Failed to write base CSS."); + goto end; + } + if (htrAddStylesheetItem_va(s, + "\t\t#eb%POScon1 { " + "position:absolute; " + "visibility:inherit; " + "left:5px; " + "top:0px; " + "width:calc(100%% - 10px); " + "height:100%%; " + "border:none; " + "z-index:%POS; " + "}\n", + id, + z + 1 + ) != 0) + { + mssError(0, "HTEB", "Failed to write con1 CSS."); + goto end; + } - htrAddEventHandlerFunction(s, "document","MOUSEUP", "eb", "eb_mouseup"); - htrAddEventHandlerFunction(s, "document","MOUSEDOWN", "eb", "eb_mousedown"); - htrAddEventHandlerFunction(s, "document","MOUSEOVER", "eb", "eb_mouseover"); - htrAddEventHandlerFunction(s, "document","MOUSEOUT", "eb", "eb_mouseout"); - htrAddEventHandlerFunction(s, "document","MOUSEMOVE", "eb", "eb_mousemove"); - htrAddEventHandlerFunction(s, "document","PASTE", "eb", "eb_paste"); + /** Link the widget to the DOM node. **/ + if (htrAddWgtrObjLinkage_va(s, tree, "eb%POSbase", id) != 0) goto end; + + /** Write JS globals. **/ + if (htrAddScriptGlobal(s, "eb_current", "null", 0) != 0) goto end; + if (htrAddScriptGlobal(s, "text_metric", "null", 0) != 0) goto end; + + /** Write JS script includes. **/ + if (htrAddScriptInclude(s, "/sys/js/ht_utils_cursor.js", 0) != 0) goto end; + if (htrAddScriptInclude(s, "/sys/js/ht_utils_hints.js", 0) != 0) goto end; + if (htrAddScriptInclude(s, "/sys/js/ht_utils_string.js", 0) != 0) goto end; + if (htrAddScriptInclude(s, "/sys/js/htdrv_editbox.js", 0) != 0) goto end; + + /** Register JS event handlers. **/ + if (htrAddEventHandlerFunction(s, "document","MOUSEDOWN", "eb", "eb_mousedown") != 0) goto end; + if (htrAddEventHandlerFunction(s, "document","MOUSEMOVE", "eb", "eb_mousemove") != 0) goto end; + if (htrAddEventHandlerFunction(s, "document","MOUSEOUT", "eb", "eb_mouseout") != 0) goto end; + if (htrAddEventHandlerFunction(s, "document","MOUSEOVER", "eb", "eb_mouseover") != 0) goto end; + if (htrAddEventHandlerFunction(s, "document","MOUSEUP", "eb", "eb_mouseup") != 0) goto end; + if (htrAddEventHandlerFunction(s, "document","PASTE", "eb", "eb_paste") != 0) goto end; /** Script initialization call. **/ - htrAddScriptInit_va(s, " eb_init({layer:wgtrGetNodeRef(ns,'%STR&SYM'), c1:document.getElementById(\"eb%POScon1\"), form:\"%STR&JSSTR\", fieldname:\"%STR&JSSTR\", isReadOnly:%INT, mainBackground:\"%STR&JSSTR\", tooltip:\"%STR&JSSTR\", desc_fgcolor:\"%STR&JSSTR\", empty_desc:\"%STR&JSSTR\"});\n", - name, id, - form, fieldname, is_readonly, main_bg, - tooltip, descfg, descr); + if (htrAddScriptInit_va(s, + "\teb_init({ " + "layer:wgtrGetNodeRef(ns,'%STR&SYM'), " + "c1:document.getElementById('eb%POScon1'), " + "form:'%STR&JSSTR', " + "fieldname:'%STR&JSSTR', " + "isReadOnly:%INT, " + "mainBackground:'%STR&JSSTR', " + "tooltip:'%STR&JSSTR', " + "desc_fgcolor:'%STR&JSSTR', " + "empty_desc:'%STR&JSSTR', " + "});\n", + name, id, + form, fieldname, + is_readonly, + main_bg, tooltip, + descfg, descr + ) != 0) + { + mssError(0, "HTEB", "Failed to write JS init call."); + goto end; + } /** HTML body
element for the base layer. **/ - htrAddBodyItem_va(s, "
\n",id); + if (htrAddBodyItem_va(s, "
\n", id) != 0) + { + mssError(0, "HTEB", "Failed to write base HTML."); + goto end; + } /** Use CSS border for drawing **/ - if (is_raised) - htrAddStylesheetItem_va(s,"\t#eb%POSbase { border-style:solid; border-width:1px; border-color: white gray gray white; %STR }\n",id, main_bg); - else - htrAddStylesheetItem_va(s,"\t#eb%POSbase { border-style:solid; border-width:1px; border-color: gray white white gray; %STR }\n",id, main_bg); - if (h >= 0) - htrAddStylesheetItem_va(s,"\t#eb%POSbase { height:%POSpx; }\n\t#eb%POScon1 { height:%POSpx; }\n", id, h-2*box_offset, id, h-2*box_offset-2); + const char* border_colors = (is_raised) ? "white gray gray white" : "gray white white gray"; + if (htrAddStylesheetItem_va(s, + "\t\t#eb%POSbase { " + "border-style:solid; " + "border-width:1px; " + "border-color:%STR; " + "%[height:"ht_flex_format"; %]" + "%STR " + "}\n", + id, + border_colors, + (h >= 0), ht_flex_h(h - (2 * box_offset), tree), + main_bg + ) != 0) + { + mssError(0, "HTEB", "Failed to write base CSS."); + goto end; + } - //htrAddBodyItem_va(s, "
 
\n", w-2, h-2); - //htrAddBodyItem_va(s, "
\n",id); - htrAddBodyItem_va(s, "\n",id); + if (htrAddBodyItem_va(s, + "" + "" + "\n", + id + ) != 0) + { + mssError(0, "HTEB", "Failed to render child widgets."); + goto end; + } - /** Check for more sub-widgets **/ - for (i=0;iChildren));i++) - htrRenderWidget(s, xaGetItem(&(tree->Children), i), z+1); + /** Render children. **/ + if (htrRenderSubwidgets(s, tree, z + 1) != 0) goto end; /** End the containing layer. **/ - htrAddBodyItem(s, "
\n"); + if (htrAddBodyItem(s, "
") != 0) + { + mssError(0, "HTEB", "Failed to write HTML closing tag."); + goto end; + } - nmSysFree(tooltip); + /** Success. **/ + rval = 0; - return 0; + end: + if (rval != 0) + { + mssError(0, "HTEB", + "Failed to render \"%s\":\"%s\" (id: %d).", + tree->Name, tree->Type, id + ); + } + + /** Clean up. **/ + if (tooltip != NULL) nmSysFree(tooltip); + + return rval; } diff --git a/centrallix/htmlgen/htdrv_execmethod.c b/centrallix/htmlgen/htdrv_execmethod.c index ddae450b6..cbc6f4d2d 100644 --- a/centrallix/htmlgen/htdrv_execmethod.c +++ b/centrallix/htmlgen/htdrv_execmethod.c @@ -14,7 +14,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -44,14 +44,6 @@ /************************************************************************/ -/** globals **/ -static struct - { - int idcnt; - } - HTEX; - - /*** htexRender - generate the HTML code for the timer nonvisual widget. ***/ int @@ -59,20 +51,17 @@ htexRender(pHtSession s, pWgtrNode tree, int z) { char* ptr; char name[64]; - int id; char* objname; char* methodname = NULL; char* methodparam = NULL; - if(!s->Capabilities.Dom0NS) + /** Verify browser capabilities. **/ + if (!s->Capabilities.Dom1HTML || !s->Capabilities.Dom2CSS) { - mssError(1,"HTTEX","Netscape DOM support required"); - return -1; + mssError(1, "HTEX", "Unsupported browser: W3C DOM1 HTML and DOM2 CSS support required."); + goto err; } - /** Get an id for this. **/ - id = (HTEX.idcnt++); - /** Get params. **/ if (wgtrGetPropertyValue(tree,"object",DATA_T_STRING,POD(&objname)) != 0) objname=""; if (wgtrGetPropertyValue(tree,"method",DATA_T_STRING,POD(&methodname)) != 0) methodname=""; @@ -83,15 +72,34 @@ htexRender(pHtSession s, pWgtrNode tree, int z) strtcpy(name, ptr, sizeof(name)); /** Script initialization call. **/ - htrAddScriptInit_va(s, " ex_init({node:wgtrGetNodeRef(ns,\"%STR&SYM\"), objname:'%STR&SYM', methname:'%STR&SYM', methparam:'%STR&JSSTR'});\n", - name, objname, methodname, methodparam); + if (htrAddScriptInit_va(s, + "\tex_init({ " + "node:wgtrGetNodeRef(ns, '%STR&SYM'), " + "objname:'%STR&SYM', " + "methname:'%STR&SYM', " + "methparam:'%STR&JSSTR', " + "});\n", + name, objname, methodname, methodparam + ) != 0) + { + mssError(1, "HTTEX", "Failed to allocate tooltip."); + goto err; + } - /** Check for objects within the exec method object. **/ - htrRenderSubwidgets(s, tree, z+2); + /** Render children. **/ + if (htrRenderSubwidgets(s, tree, z + 2) != 0) goto err; - htrAddScriptInclude(s,"/sys/js/htdrv_execmethod.js",0); + if (htrAddScriptInclude(s, "/sys/js/htdrv_execmethod.js", 0) != 0) goto err; - return 0; + /** Success. **/ + return 0; + + err: + mssError(0, "HTTEX", + "Failed to render \"%s\":\"%s\".", + tree->Name, tree->Type + ); + return -1; } @@ -122,7 +130,5 @@ htexInitialize() htrAddSupport(drv, "dhtml"); - HTEX.idcnt = 0; - return 0; } diff --git a/centrallix/htmlgen/htdrv_fileupload.c b/centrallix/htmlgen/htdrv_fileupload.c index a3ed1a5c2..ea663cac2 100755 --- a/centrallix/htmlgen/htdrv_fileupload.c +++ b/centrallix/htmlgen/htdrv_fileupload.c @@ -15,7 +15,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -55,14 +55,13 @@ htfuRender(pHtSession s, pWgtrNode tree, int z) { char* ptr; char name[64]; - int id, i; int multiselect; char target[512]; char fieldname[HT_FIELDNAME_SIZE]; char form[64]; /** Get an id for this. **/ - id = (HTFU.idcnt++); + const int id = (HTFU.idcnt++); if (wgtrGetPropertyValue(tree,"name",DATA_T_STRING,POD(&ptr)) != 0) return -1; strtcpy(name,ptr,sizeof(name)); @@ -82,26 +81,72 @@ htfuRender(pHtSession s, pWgtrNode tree, int z) else fieldname[0]='\0'; - /** Write named global **/ - htrAddWgtrObjLinkage_va(s, tree, "fu%POSbase",id); + /** Link the widget to the DOM node. **/ + if (htrAddWgtrObjLinkage_va(s, tree, "fu%POSbase", id) != 0) + { + mssError(0, "HTFU", "Failed to add object linkage."); + goto err; + } - htrAddEventHandlerFunction(s, "document","CHANGE", "fu", "fu_change"); + if (htrAddEventHandlerFunction(s, "document", "CHANGE", "fu", "fu_change") != 0) goto err; /** Include the javascript code for the file uploader **/ - htrAddScriptInclude(s, "/sys/js/ht_utils_hints.js", 0); - htrAddScriptInclude(s, "/sys/js/htdrv_fileupload.js", 0); + if (htrAddScriptInclude(s, "/sys/js/ht_utils_hints.js", 0) != 0) goto err; + if (htrAddScriptInclude(s, "/sys/js/htdrv_fileupload.js", 0) != 0) goto err; - htrAddScriptInit_va(s, " fu_init({layer:wgtrGetNodeRef(ns,'%STR&SYM'), pane:document.getElementById(\"fu%POSform\"), input:document.getElementById(\"fu%POSinput\"), iframe:document.getElementById(\"fu%POSiframe\"), target:\"%STR&JSSTR\"});\n", name, id, id, id, target); + if (htrAddScriptInit_va(s, + "\tfu_init({ " + "layer:wgtrGetNodeRef(ns, '%STR&SYM'), " + "pane:document.getElementById('fu%POSform'), " + "input:document.getElementById('fu%POSinput'), " + "iframe:document.getElementById('fu%POSiframe'), " + "target:'%STR&JSSTR', " + "});\n", + name, id, id, id, target + ) != 0) + { + mssError(0, "HTFU", "Failed to write JS init call."); + goto err; + } /** style header items **/ - htrAddStylesheetItem_va(s,"#fu%POSbase { POSITION:absolute; VISIBILITY:hidden; }\n", id); - htrAddBodyItem_va(s,"
", id, id, id, id, id, id, id, multiselect?"MULTIPLE":""); + if (htrAddStylesheetItem_va(s, + "\t\t#fu%POSbase { " + "position:absolute; " + "visibility:hidden; " + "}\n", + id + ) != 0) + { + mssError(0, "HTFU", "Failed to write base CSS."); + goto err; + } + if (htrAddBodyItem_va(s, + "
" + "" + "" + "
", + id, id, id, + id, id, + id, id, + (multiselect) + )!= 0) + { + mssError(0, "HTFU", "Failed to write base HTML."); + goto err; + } /** Check for more sub-widgets **/ - for (i=0;iChildren));i++) - htrRenderWidget(s, xaGetItem(&(tree->Children), i), z); + if (htrRenderSubwidgets(s, tree, z) != 0) goto err; return 0; + + err: + mssError(0, "HTFU", + "Failed to render \"%s\":\"%s\" (id: %d).", + tree->Name, tree->Type, id + ); + return -1; }//end htfuRender diff --git a/centrallix/htmlgen/htdrv_form.c b/centrallix/htmlgen/htdrv_form.c index 05e3d1202..fb66681f1 100644 --- a/centrallix/htmlgen/htdrv_form.c +++ b/centrallix/htmlgen/htdrv_form.c @@ -15,7 +15,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -44,14 +44,6 @@ /************************************************************************/ -/** globals **/ -static struct - { - int idcnt; - } - HTFORM; - - /*** htformRender - generate the HTML code for the form 'glue' ***/ int @@ -66,7 +58,7 @@ htformRender(pHtSession s, pWgtrNode tree, int z) char link_prev[64]; char link_prev_within[64]; char interlock_with[256]; - int id, i, t; + int i, t; int allowquery, allownew, allowmodify, allowview, allownodata, multienter, allowdelete, confirmdelete; int confirmdiscard, allowmerge; int allowobscure = 0; @@ -78,9 +70,6 @@ htformRender(pHtSession s, pWgtrNode tree, int z) pStringVec sv; /** form widget should work on any browser **/ - - /** Get an id for this. **/ - id = (HTFORM.idcnt++); /** Get params. **/ allowquery = htrGetBoolean(tree, "allow_query", 1); @@ -221,42 +210,73 @@ htformRender(pHtSession s, pWgtrNode tree, int z) strtcpy(name,ptr,sizeof(name)); /** create our instance variable **/ - htrAddWgtrCtrLinkage(s, tree, "_parentctr"); + if (htrAddWgtrCtrLinkage(s, tree, "_parentctr") != 0) + { + mssError(0, "HTFORM", "Failed to add container linkage."); + goto err; + } /** Script include to add functions **/ - htrAddScriptInclude(s, "/sys/js/htdrv_form.js", 0); - htrAddScriptInclude(s, "/sys/js/ht_utils_hints.js", 0); + if (htrAddScriptInclude(s, "/sys/js/ht_utils_hints.js", 0)) goto err; + if (htrAddScriptInclude(s, "/sys/js/htdrv_form.js", 0)) goto err; /** Write out the init line for this instance of the form ** the name of this instance was defined to be global up above ** and fm_current is defined in htdrv_page.c **/ - htrAddScriptInit_va(s," form_init(wgtrGetNodeRef(ns,\"%STR&SYM\"), {aq:%INT, an:%INT, am:%INT, av:%INT, and:%INT, ad:%INT, cd:%INT, cdis:%INT, amrg:%INT, me:%INT, name:'%STR&SYM', _3b:%[wgtrGetNodeRef(ns,\"%STR&SYM\")%]%[null%], ro:%INT, ao:%INT, af:%INT, osrc:%['%STR&SYM'%]%[null%], tro:%INT, em:%INT, nf:%['%STR&SYM'%]%[null%], nfw:%['%STR&SYM'%]%[null%], pf:%['%STR&SYM'%]%[null%], pfw:%['%STR&SYM'%]%[null%], il:'%STR&JSSTR'});\n", - name,allowquery,allownew,allowmodify,allowview,allownodata,allowdelete,confirmdelete, confirmdiscard, allowmerge, - multienter,name, - strcmp(_3bconfirmwindow,"null") != 0, _3bconfirmwindow, strcmp(_3bconfirmwindow,"null") == 0, - readonly,allowobscure,autofocus, - *osrc != '\0', osrc, *osrc == '\0', - tro, enter_mode, - *link_next != '\0', link_next, *link_next == '\0', - *link_next_within != '\0', link_next_within, *link_next_within == '\0', - *link_prev != '\0', link_prev, *link_prev == '\0', - *link_prev_within != '\0', link_prev_within, *link_prev_within == '\0', - interlock_with - ); - htrAddScriptInit_va(s," wgtrGetNodeRef(ns,\"%STR&SYM\").ChangeMode('NoData');\n",name); - - /** Check for and render all subobjects. **/ - /** non-visual, don't consume a z level **/ - for (i=0;iChildren));i++) + const int null_confirm_window = (strcmp(_3bconfirmwindow, "null") == 0); + const int no_osrc = (osrc == NULL || osrc[0] == '\0'); + const int no_link_next = (link_next == NULL || link_next[0] == '\0'); + const int no_link_next_within = (link_next_within == NULL || link_next_within[0] == '\0'); + const int no_link_prev = (link_prev == NULL || link_prev[0] == '\0'); + const int no_link_prev_within = (link_prev_within == NULL || link_prev_within[0] == '\0'); + if (htrAddScriptInit_va(s, + "\t{ " + "const node = wgtrGetNodeRef(ns, '%STR&SYM'); " + "form_init(node, { " + "aq:%INT, an:%INT, am:%INT, av:%INT, and:%INT, ad:%INT, " + "cd:%INT, cdis:%INT, amrg:%INT, me:%INT, name:'%STR&SYM', " + "_3b:%[wgtrGetNodeRef(ns, '%STR&SYM')%]%[null%], " + "ro:%INT, ao:%INT, af:%INT, " + "osrc:%['%STR&SYM'%]%[null%], " + "tro:%INT, em:%INT, " + "nf:%['%STR&SYM'%]%[null%], " + "nfw:%['%STR&SYM'%]%[null%], " + "pf:%['%STR&SYM'%]%[null%], " + "pfw:%['%STR&SYM'%]%[null%], " + "il:'%STR&JSSTR', " + "});" + "node.ChangeMode('NoData');" + "}\n", + name, + allowquery, allownew, allowmodify, allowview, allownodata, allowdelete, + confirmdelete, confirmdiscard, allowmerge, multienter, name, + (!null_confirm_window), _3bconfirmwindow, (null_confirm_window), + readonly, allowobscure, autofocus, + (!no_osrc), osrc, (no_osrc), + tro, enter_mode, + (!no_link_next), link_next, (no_link_next), + (!no_link_next_within), link_next_within, (no_link_next_within), + (!no_link_prev), link_prev, (no_link_prev), + (!no_link_prev_within), link_prev_within, (no_link_prev_within), + interlock_with + ) != 0) { - if (strcmp(tree->Type, "widget/connector") == 0) - htrRenderWidget(s, xaGetItem(&(tree->Children), i), z); - else - htrRenderWidget(s, xaGetItem(&(tree->Children), i), z); + mssError(0, "HTFORM", "Failed to render child widgets."); + goto err; } - - return 0; + + /** Render children. **/ + if (htrRenderSubwidgets(s, tree, z) != 0) goto err; + + return 0; + + err: + mssError(0, "HTFORM", + "Failed to render \"%s\":\"%s\".", + tree->Name, tree->Type + ); + return -1; } @@ -305,7 +325,5 @@ htformInitialize() htrAddSupport(drv, "dhtml"); - HTFORM.idcnt = 0; - return 0; } diff --git a/centrallix/htmlgen/htdrv_formbar.c b/centrallix/htmlgen/htdrv_formbar.c index 9c326dcbc..95e82e59d 100644 --- a/centrallix/htmlgen/htdrv_formbar.c +++ b/centrallix/htmlgen/htdrv_formbar.c @@ -14,7 +14,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 2000-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 2000-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -46,25 +46,41 @@ /* htfbRender - generate the HTML code for the page. */ -int htfbRender(pHtSession s, pWgtrNode tree, int z) { - int i; - - if(!s->Capabilities.Dom0NS && !(s->Capabilities.Dom1HTML && s->Capabilities.CSS1)) - { - mssError(1,"HTFS","Netscape DOM or W3C DOM1 HTML and CSS1 support required"); - return -1; - } - - htrAddWgtrCtrLinkage(s, tree, "_parentctr"); - - /** mark this node as not being associated with a DHTML object **/ - tree->RenderFlags |= HT_WGTF_NOOBJECT; - - for (i=0;iChildren));i++) - htrRenderWidget(s, xaGetItem(&(tree->Children), i), z+2); - - return 0; -} +int htfbRender(pHtSession s, pWgtrNode tree, int z) + { + /** Verify browser capabilities. **/ + if (!s->Capabilities.Dom0NS && !(s->Capabilities.Dom1HTML && s->Capabilities.CSS1)) + { + mssError(1,"HTFS","Netscape DOM or W3C DOM1 HTML and CSS1 support required"); + goto err; + } + + /** Add container linkage. **/ + if (htrAddWgtrCtrLinkage(s, tree, "_parentctr") != 0) + { + mssError(0, "HTFS", "Failed to add container linkage."); + goto err; + } + + /** Mark this widget as not associated with a DHTML object. **/ + tree->RenderFlags |= HT_WGTF_NOOBJECT; + + /** Render subwidgets. **/ + if (htrRenderSubwidgets(s, tree, z + 2) != 0) + { + mssError(0, "HTFS", "Failed to render child widgets."); + goto err; + } + + return 0; + + err: + mssError(0, "HTFS", + "Failed to render \"%s\":\"%s\".", + tree->Name, tree->Type + ); + return -1; + } /* @@ -72,8 +88,6 @@ int htfbRender(pHtSession s, pWgtrNode tree, int z) { */ int htfbInitialize() { pHtDriver drv; - /*pHtEventAction action; - pHtParam param;*/ /** Allocate the driver **/ drv = htrAllocDriver(); @@ -84,14 +98,6 @@ int htfbInitialize() { strcpy(drv->WidgetName,"formbar"); drv->Render = htfbRender; -/* - htrAddEvent(drv,"Click"); - htrAddEvent(drv,"MouseUp"); - htrAddEvent(drv,"MouseDown"); - htrAddEvent(drv,"MouseOver"); - htrAddEvent(drv,"MouseOut"); - htrAddEvent(drv,"MouseMove"); -*/ /** Register. **/ htrRegisterDriver(drv); diff --git a/centrallix/htmlgen/htdrv_formstatus.c b/centrallix/htmlgen/htdrv_formstatus.c index 33258e3c1..2aa5eccf2 100644 --- a/centrallix/htmlgen/htdrv_formstatus.c +++ b/centrallix/htmlgen/htdrv_formstatus.c @@ -14,7 +14,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 2000-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 2000-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -52,21 +52,21 @@ static struct { */ int htfsRender(pHtSession s, pWgtrNode tree, int z) { int x=-1,y=-1; - int id; char name[64]; char form[64]; char* ptr; char* style; int w; - if(!s->Capabilities.Dom0NS && !(s->Capabilities.Dom1HTML && s->Capabilities.CSS1)) - { - mssError(1,"HTFS","Netscape DOM or W3C DOM1 HTML and CSS1 support required"); - return -1; - } - /** Get an id for this. **/ - id = (HTFS.idcnt++); + const int id = (HTFS.idcnt++); + + /** Verify browser capabilities. **/ + if (!s->Capabilities.Dom1HTML || !s->Capabilities.Dom2CSS) + { + mssError(1, "HTFS", "Unsupported browser: W3C DOM1 HTML and DOM2 CSS support required."); + goto err; + } /** Get x,y of this object **/ if (wgtrGetPropertyValue(tree,"x",DATA_T_INTEGER,POD(&x)) != 0) x=0; @@ -80,40 +80,90 @@ int htfsRender(pHtSession s, pWgtrNode tree, int z) { w = 13; /** Write named global **/ - if (wgtrGetPropertyValue(tree,"name",DATA_T_STRING,POD(&ptr)) != 0) return -1; + if (wgtrGetPropertyValue(tree,"name",DATA_T_STRING,POD(&ptr)) != 0) goto err; strtcpy(name,ptr,sizeof(name)); if (wgtrGetPropertyValue(tree,"form",DATA_T_STRING,POD(&ptr)) != 0) form[0] = '\0'; else strtcpy(form,ptr,sizeof(form)); - htrAddWgtrObjLinkage_va(s, tree, "fs%POSmain", id); - /** Ok, write the style header items. **/ - htrAddStylesheetItem_va(s,"\t#fs%POSmain { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; HEIGHT:13px; WIDTH:%POSpx; Z-INDEX:%POS; }\n",id,x,y,w,z); + /** Link the widget to the DOM node. **/ + if (htrAddWgtrObjLinkage_va(s, tree, "fs%POSmain", id) != 0) + { + mssError(0, "HTFS", "Failed to render object linkage."); + goto err; + } - htrAddScriptInclude(s, "/sys/js/htdrv_formstatus.js", 0); + /** Ok, write the style header items. **/ + if (htrAddStylesheetItem_va(s, + "\t\t#fs%POSmain { " + "position:absolute; " + "visibility:inherit; " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "width:"ht_flex_format"; " + "height:13px; " + "z-index:%POS; " + "}\n", + id, + ht_flex_x(x, tree), + ht_flex_y(y, tree), + ht_flex_w(w, tree), + z + ) != 0) + { + mssError(0, "HTFS", "Failed to write main CSS."); + goto err; + } + + if (htrAddScriptInclude(s, "/sys/js/htdrv_formstatus.js", 0) != 0) goto err; /** Script initialization call. **/ - htrAddScriptInit_va(s," fs_init({layer:wgtrGetNodeRef(ns,\"%STR&SYM\"),form:\"%STR&JSSTR\",style:\"%STR&JSSTR\"});\n", - name, form, style); + if (htrAddScriptInit_va(s, + "\tfs_init({ " + "layer:wgtrGetNodeRef(ns, '%STR&SYM'), " + "form:'%STR&JSSTR', " + "style:'%STR&JSSTR', " + "});\n", + name, form, style + ) != 0) + { + mssError(0, "HTFS", "Failed to write JS init call."); + goto err; + } /** HTML body
element for the layers. **/ - if (!strcmp(style,"large")) - htrAddBodyItem_va(s,"
\n", id); - else if (!strcmp(style,"largeflat")) - htrAddBodyItem_va(s,"
\n", id); - else - htrAddBodyItem_va(s,"
\n", id); - - htrAddEventHandlerFunction(s,"document","MOUSEDOWN","fs","fs_mousedown"); - htrAddEventHandlerFunction(s,"document","MOUSEUP", "fs","fs_mouseup"); - htrAddEventHandlerFunction(s,"document","MOUSEOVER","fs","fs_mouseover"); - htrAddEventHandlerFunction(s,"document","MOUSEOUT", "fs","fs_mouseout"); - htrAddEventHandlerFunction(s,"document","MOUSEMOVE","fs","fs_mousemove"); - - htrRenderSubwidgets(s, tree, z+2); - - return 0; + const char* type = ""; + if (strcmp(style, "large") == 0) type = "L"; + else if (strcmp(style, "largeflat") == 0) type = "LF"; + if (htrAddBodyItem_va(s, + "
\n", + id, type + ) != 0) + { + mssError(0, "HTFS", "Failed to render child widgets."); + goto err; + } + + /** Register JS event handler functions. **/ + if (htrAddEventHandlerFunction(s, "document", "MOUSEDOWN","fs", "fs_mousedown") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEMOVE","fs", "fs_mousemove") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEOUT", "fs", "fs_mouseout") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEOVER","fs", "fs_mouseover") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEUP", "fs", "fs_mouseup") != 0) goto err; + + /** Render children. **/ + if (htrRenderSubwidgets(s, tree, z + 2) != 0) goto err; + + /** Success. **/ + return 0; + + err: + mssError(0, "HTFS", + "Failed to render \"%s\":\"%s\" (id: %d).", + tree->Name, tree->Type, id + ); + return -1; } diff --git a/centrallix/htmlgen/htdrv_frameset.c b/centrallix/htmlgen/htdrv_frameset.c index 7ed9215d3..3e29fe17d 100644 --- a/centrallix/htmlgen/htdrv_frameset.c +++ b/centrallix/htmlgen/htdrv_frameset.c @@ -15,7 +15,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1999-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1999-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -58,7 +58,7 @@ htsetRender(pHtSession s, pWgtrNode tree, int z) /** Check for a title. **/ if (wgtrGetPropertyValue(tree,"title",DATA_T_STRING,POD(&ptr)) == 0) { - htrAddHeaderItem_va(s," %STR&HTE\n",ptr); + htrAddHeaderItem_va(s, "\t%STR&HTE\n", ptr); } /** Loop through the frames (widget/page items) for geometry data **/ diff --git a/centrallix/htmlgen/htdrv_hints.c b/centrallix/htmlgen/htdrv_hints.c index e9ca6bec8..e800256d8 100644 --- a/centrallix/htmlgen/htdrv_hints.c +++ b/centrallix/htmlgen/htdrv_hints.c @@ -14,7 +14,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2004 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -43,26 +43,14 @@ /************************************************************************/ -/** globals **/ -static struct - { - int idcnt; - } - HTHINT; - - /*** hthintRender - generate the HTML code for the page. ***/ int hthintRender(pHtSession s, pWgtrNode tree, int z) { - int id; pObjPresentationHints hints; XString xs; - /** Get an id for this. **/ - id = (HTHINT.idcnt++); - /** Convert the object data into hints data **/ hints = wgtrWgtToHints(tree); if (!hints) @@ -74,8 +62,10 @@ hthintRender(pHtSession s, pWgtrNode tree, int z) /** Serialize the hints data and add the script init for it **/ xsInit(&xs); hntEncodeHints(hints, &xs); - htrAddScriptInit_va(s, " cx_set_hints(wgtrGetParent(wgtrGetNodeRef(ns,\"%STR&SYM\")), '%STR&JSSTR', 'app');\n", - wgtrGetName(tree), xs.String); + htrAddScriptInit_va(s, + "\tcx_set_hints(wgtrGetParent(wgtrGetNodeRef(ns, '%STR&SYM')), '%STR&JSSTR', 'app');\n", + wgtrGetName(tree), xs.String + ); xsDeInit(&xs); /** mark this node as not being associated with a DHTML object **/ @@ -109,7 +99,5 @@ hthintInitialize() htrAddSupport(drv, "dhtml"); - HTHINT.idcnt = 0; - return 0; } diff --git a/centrallix/htmlgen/htdrv_html.c b/centrallix/htmlgen/htdrv_html.c index 9b899802a..51dd527a9 100644 --- a/centrallix/htmlgen/htdrv_html.c +++ b/centrallix/htmlgen/htdrv_html.c @@ -1,9 +1,11 @@ +#include #include #include #include #include #include "ht_render.h" #include "obj.h" +#include "cxlib/util.h" #include "cxlib/mtask.h" #include "cxlib/xarray.h" #include "cxlib/xhash.h" @@ -14,7 +16,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -56,103 +58,151 @@ int hthtmlRender(pHtSession s, pWgtrNode tree, int z) { char* ptr; - char name[64]; - char sbuf[320]; - char src[128] = ""; - int x=-1,y=-1,w,h; - int id,cnt, i; - int mode = 0; - pObject content_obj; - - if(!s->Capabilities.Dom0NS && !(s->Capabilities.Dom1HTML && s->Capabilities.CSS1)) + + /** Get an id for this. **/ + const int id = (HTHTML.idcnt++); + + /** Verify browser capabilities. **/ + if (!s->Capabilities.Dom1HTML || !s->Capabilities.Dom2CSS) { - mssError(1,"HTHTML","NS4 or W3C DOM support required"); - return -1; + mssError(1, "HTHTML", "Unsupported browser: W3C DOM1 HTML and DOM2 CSS support required."); + goto err; } - /** Get an id for this. **/ - id = (HTHTML.idcnt++); - - /** Get x,y,w,h of this object **/ + /** Get x, y, w, & h. **/ + int x = -1, y = -1, w, h; if (wgtrGetPropertyValue(tree,"x",DATA_T_INTEGER,POD(&x)) != 0) x=-1; if (wgtrGetPropertyValue(tree,"y",DATA_T_INTEGER,POD(&y)) != 0) y=-1; if (wgtrGetPropertyValue(tree,"width",DATA_T_INTEGER,POD(&w)) != 0) { mssError(1,"HTHTML","HTML widget must have a 'width' property"); - return -1; + goto err; } if (wgtrGetPropertyValue(tree,"height",DATA_T_INTEGER,POD(&h)) != 0) h = -1; - /** Get source html objectsystem entry. **/ + /** Get the HTML source path. **/ + char src[256] = ""; if (wgtrGetPropertyValue(tree,"source",DATA_T_STRING,POD(&ptr)) == 0) { - strtcpy(src,ptr,sizeof(src)); + if (strtcpy(src, ptr, sizeof(src)) < 0) + { + /** Data truncated: Send warning. **/ + fprintf(stderr, "Warning! Source path truncated."); + } } /** Check for a 'mode' - dynamic or static. Default is static. **/ + int mode = 0; if (wgtrGetPropertyValue(tree,"mode",DATA_T_STRING,POD(&ptr)) == 0 && !strcmp(ptr,"dynamic")) mode = 1; /** Get name **/ - if (wgtrGetPropertyValue(tree,"name",DATA_T_STRING,POD(&ptr)) != 0) return -1; + char name[64]; + if (wgtrGetPropertyValue(tree,"name",DATA_T_STRING,POD(&ptr)) != 0) goto err; strtcpy(name,ptr,sizeof(name)); /** Ok, write the style header items. **/ if (mode == 1) { /** Only give x and y if supplied. **/ - if (x < 0 || y < 0) - { - htrAddStylesheetItem_va(s,"\t#ht%POSpane { POSITION:relative; VISIBILITY:inherit; WIDTH:%POSpx; Z-INDEX:%POS; }\n",id,w,z); - htrAddStylesheetItem_va(s,"\t#ht%POSpane2 { POSITION:relative; VISIBILITY:hidden; WIDTH:%POSpx; Z-INDEX:%POS; }\n",id,w,z); - htrAddStylesheetItem_va(s,"\t#ht%POSfader { POSITION:relative; VISIBILITY:hidden; WIDTH:%POSpx; Z-INDEX:%POS; }\n",id,w,z+1); - } - else - { - htrAddStylesheetItem_va(s,"\t#ht%POSpane { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; Z-INDEX:%POS; }\n",id,x,y,w,z); - htrAddStylesheetItem_va(s,"\t#ht%POSpane2 { POSITION:absolute; VISIBILITY:hidden; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; Z-INDEX:%POS; }\n",id,x,y,w,z); - htrAddStylesheetItem_va(s,"\t#ht%POSfader { POSITION:absolute; VISIBILITY:hidden; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; Z-INDEX:%POS; }\n",id,x,y,w,z+1); - } - - if (s->Capabilities.CSS1) - { - htrAddStylesheetItem_va(s,"\t#ht%POSpane { overflow:hidden; }\n",id); - htrAddStylesheetItem_va(s,"\t#ht%POSpane2 { overflow:hidden; }\n",id); - htrAddStylesheetItem_va(s,"\t#ht%POSfader { overflow:hidden; }\n",id); - htrAddStylesheetItem_va(s,"\t#ht%POSloader { overflow:hidden; visibility:hidden; position:absolute; top:0px; left:0px; width:0px; height:0px; }\n", id); - } - - /** Write named global **/ - htrAddWgtrObjLinkage_va(s, tree, "ht%POSpane",id); - - htrAddScriptGlobal(s, "ht_fadeobj", "null", 0); - - htrAddScriptInclude(s, "/sys/js/htdrv_html.js", 0); - htrAddScriptInclude(s, "/sys/js/ht_utils_string.js", 0); + if (htrAddStylesheetItem_va(s, + "\t\t#ht%POSpane, #ht%POSpane2, #ht%POSfader { " + "position:absolute; " + "visibility:inherit; " + "overflow:hidden; " + "%[left:"ht_flex_format"; " + "top:"ht_flex_format"; %]" + "width:"ht_flex_format"; " + "z-index:%POS; " + "}\n", + id, id, id, + (x < 0 || y < 0), + ht_flex_x(x, tree), + ht_flex_y(y, tree), + ht_flex_w(w, tree), + z + ) != 0) + { + mssError(0, "HTHTML", "Failed to add CSS."); + goto err; + } + if (htrAddStylesheetItem_va(s, + "\t\t#ht%POSloader { " + "position:absolute; " + "visibility:hidden; " + "overflow:hidden; " + "top:0px; " + "left:0px; " + "width:0px; " + "height:0px; " + "}\n", + id + ) != 0) + { + mssError(0, "HTHTML", "Failed to add loader CSS."); + goto err; + } + + /** Link the widget to the DOM node. **/ + if (htrAddWgtrObjLinkage_va(s, tree, "ht%POSpane", id) != 0) goto err; + /** Declare globals. **/ + if (htrAddScriptGlobal(s, "ht_fadeobj", "null", 0) != 0) goto err; + + /** Include JS dependencies. **/ + if (htrAddScriptInclude(s, "/sys/js/ht_utils_string.js", 0) != 0) goto err; + if (htrAddScriptInclude(s, "/sys/js/htdrv_html.js", 0) != 0) goto err; + /** Event handler for click-on-link. **/ - htrAddEventHandlerFunction(s, "document","CLICK","ht","ht_click"); - htrAddEventHandlerFunction(s,"document","MOUSEOVER","ht","ht_mouseover"); - htrAddEventHandlerFunction(s,"document","MOUSEOUT", "ht", "ht_mouseout"); - htrAddEventHandlerFunction(s,"document","MOUSEMOVE","ht", "ht_mousemove"); - htrAddEventHandlerFunction(s,"document","MOUSEDOWN","ht", "ht_mousedown"); - htrAddEventHandlerFunction(s,"document","MOUSEUP", "ht", "ht_mouseup"); - - /** Script initialization call. **/ - if (s->Capabilities.Dom0NS) - htrAddScriptInit_va(s," ht_init({layer:wgtrGetNodeRef(ns,\"%STR&SYM\"), layer2:htr_subel(wgtrGetParentContainer(wgtrGetNodeRef(ns,\"%STR&SYM\")),\"ht%POSpane2\"), faderLayer:htr_subel(wgtrGetParentContainer(wgtrGetNodeRef(ns,\"%STR&SYM\")),\"ht%POSfader\"), source:\"%STR&JSSTR\", width:%INT, height:%INT, loader:null});\n", - name, name, id, name, id, - src, w,h); - else - htrAddScriptInit_va(s," ht_init({layer:wgtrGetNodeRef(ns,\"%STR&SYM\"), layer2:htr_subel(wgtrGetParentContainer(wgtrGetNodeRef(ns,\"%STR&SYM\")),\"ht%POSpane2\"), faderLayer:htr_subel(wgtrGetParentContainer(wgtrGetNodeRef(ns,\"%STR&SYM\")),\"ht%POSfader\"), source:\"%STR&JSSTR\", width:%INT, height:%INT, loader:htr_subel(wgtrGetParentContainer(wgtrGetNodeRef(ns,\"%STR&SYM\")), \"ht%POSloader\")});\n", - name, name, id, name, id, - src, w,h, name, id); + if (htrAddEventHandlerFunction(s, "document", "CLICK", "ht", "ht_click") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEDOWN", "ht", "ht_mousedown") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEMOVE", "ht", "ht_mousemove") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEOUT", "ht", "ht_mouseout") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEOVER", "ht", "ht_mouseover") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEUP", "ht", "ht_mouseup") != 0) goto err; + + /** Write script initialization call in its own scope. **/ + if (htrAddScriptInit_va(s, "\t{ " + "const layer = wgtrGetNodeRef(ns, '%STR&SYM'); " + "const layer_container = wgtrGetParentContainer(layer); " + "ht_init({" + "layer, " + "layer2:htr_subel(layer_container, 'ht%POSpane2'), " + "faderLayer:htr_subel(layer_container, 'ht%POSfader'), " + "source:'%STR&JSSTR', " + "width:%INT, " + "height:%INT, " + "%[loader:htr_subel(layer_container, 'ht%POSloader'), %]" + "}); }\n", + name, id, id, src, w, h, + (s->Capabilities.Dom1HTML), id + ) != 0) + { + mssError(0, "HTHTML", "Failed to write JS init call."); + goto err; + } /** HTML body
element for the layer. **/ - htrAddBodyItem_va(s,"
",id); - htrAddBodyItemLayer_va(s, 0, "ht%POSpane2", id, NULL, ""); - if (!s->Capabilities.Dom0NS) - htrAddBodyItemLayer_va(s, HTR_LAYER_F_DYNAMIC, "ht%POSloader", id, NULL, ""); - htrAddBodyItemLayerStart(s, 0, "ht%POSpane", id, NULL); + if (htrAddBodyItem_va(s, + "
", + id + ) != 0) + { + mssError(0, "HTHTML", "Failed to write HTML background fader."); + goto err; + } + if (htrAddBodyItemLayer_va(s, 0, "ht%POSpane2", id, NULL, "") != 0) + { + mssError(0, "HTHTML", "Failed to write HTML opening tag."); + goto err; + } + if (htrAddBodyItemLayerStart(s, 0, "ht%POSpane", id, NULL) != 0) + { + mssError(0, "HTHTML", "Failed to write HTML start tag."); + goto err; + } } else { @@ -162,44 +212,145 @@ hthtmlRender(pHtSession s, pWgtrNode tree, int z) /** If prefix text given, put it. **/ if (wgtrGetPropertyValue(tree, "prologue", DATA_T_STRING,POD(&ptr)) == 0) { - htrAddBodyItem(s, ptr); + if (htrAddBodyItem(s, ptr) != 0) + { + mssError(0, "HTHTML", "Failed to write prologue HTML."); + goto err; + } } /** If full text given, put it. **/ if (wgtrGetPropertyValue(tree, "content", DATA_T_STRING,POD(&ptr)) == 0) { - htrAddBodyItem(s, ptr); + if (htrAddBodyItem(s, ptr) != 0) + { + mssError(0, "HTHTML", "Failed to write provided HTML content."); + goto err; + } } /** If source is an objectsystem entry... **/ - if (src[0] && strncmp(src,"http:",5) && strncmp(src,"debug:",6)) + if (src[0] && strncmp(src, "http:", 5) != 0 && strncmp(src, "debug:", 6) != 0) { - content_obj = objOpen(s->ObjSession,src,O_RDONLY,0600,"text/html"); - if (content_obj) - { - while((cnt = objRead(content_obj, sbuf, 159,0,0)) > 0) - { - sbuf[cnt]=0; - htrAddBodyItem(s, sbuf); - } - objClose(content_obj); - } + pObject content_obj = NULL; + char* page_buf = NULL; + bool successful = false; + + /** Open the content object. **/ + content_obj = objOpen(s->ObjSession, src, O_RDONLY, 0600, "text/html"); + if (content_obj == NULL) + { + mssError(0, "HTHTML", "Failed to open content object."); + goto end_reading; + } + + /** Get object info. **/ + pObjectInfo obj_info = objInfo(content_obj); + if (obj_info == NULL) + { + mssError(0, "HTHTML", "Failed to get content object info."); + goto end_reading; + } + + /** Inspect object info. **/ + if (obj_info->Flags & OBJ_INFO_F_CANT_HAVE_CONTENT) + { + mssError(1, "HTHTML", "FAIL: Provided object source cannot have content!"); + goto err; + } + if (obj_info->Flags & OBJ_INFO_F_NO_CONTENT) + { + /** Nothing to read. **/ + successful = true; + goto end_reading; + } + + /** Allocate a buffer for reading HTML content. **/ + const size_t page_buf_len = BUFSIZ; + page_buf = check_ptr(nmSysMalloc(page_buf_len * sizeof(char*))); + + /* read content until we run out.*/ + int n_chars_read = 0; + while (1) + { + /** Read some data. **/ + const int max_read = page_buf_len - 1lu; + n_chars_read = objRead(content_obj, page_buf, max_read, 0, 0); + + /** Edge cases. **/ + if (n_chars_read == 0) break; /* Done! */ + if (n_chars_read < 0) + { + char error_code[32] = {'\0'}; + if (n_chars_read != -1) + snprintf(error_code, sizeof(error_code), " (error code: %d)", n_chars_read); + mssError(0, "HTHTML", "Failed to read from content object%s.", error_code); + goto end_reading; + } + if (n_chars_read > max_read) + { + mssError(1, "HTHTML", + "Driver read too many characters (%d/%d).", + n_chars_read, max_read + ); + goto end_reading; + } + + /** Write the data to the page. **/ + page_buf[n_chars_read] = '\0'; + fprintf(stderr, "Got: \"%s\"\n", page_buf); + if (htrAddBodyItem(s, page_buf) != 0) + { + mssError(0, "HTHTML", "Failed to write HTML chunk: \"%s\"", page_buf); + goto end_reading; + } + } + + /** Success. **/ + successful = true; + + end_reading: + /** Clean up. **/ + if (content_obj != NULL) objClose(content_obj); + if (page_buf != NULL) nmSysFree(page_buf); + + /** Handle failure. **/ + if (!successful) + { + mssError(0, "HTHTML", "Failed to write HTML from object source: \"%s\"", src); + goto err; + } } /** If post text given, put it. **/ if (wgtrGetPropertyValue(tree, "epilogue", DATA_T_STRING, POD(&ptr)) == 0) { - htrAddBodyItem(s, ptr); + if (htrAddBodyItem(s, ptr) != 0) + { + mssError(0, "HTHTML", "Failed to write epilogue HTML."); + goto err; + } } - /** render subwidgets **/ - for (i=0;iChildren));i++) - htrRenderWidget(s, xaGetItem(&(tree->Children), i), z+3); + /** Render children. **/ + if (htrRenderSubwidgets(s, tree, z + 3) != 0) goto err; /** End the containing layer. **/ - if (mode == 1) htrAddBodyItemLayerEnd(s, 0); - - return 0; + if (mode == 1 && htrAddBodyItemLayerEnd(s, 0) != 0) + { + mssError(0, "HTHTML", "Failed to add HTML end."); + goto err; + } + + /** Success. **/ + return 0; + + err: + mssError(0, "HTHTML", + "Failed to render \"%s\":\"%s\" (id: %d).", + tree->Name, tree->Type, id + ); + return -1; } diff --git a/centrallix/htmlgen/htdrv_image.c b/centrallix/htmlgen/htdrv_image.c index 7ea3b9d47..3f5ca0e2f 100644 --- a/centrallix/htmlgen/htdrv_image.c +++ b/centrallix/htmlgen/htdrv_image.c @@ -14,7 +14,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2017 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -56,10 +56,23 @@ htimgSetup(pHtSession s) { /** Global style code for all image widget "img" tags **/ - htrAddStylesheetItem(s, " img.wimage { display:block; position:relative; left:0px; top:0px; }\n"); + htrAddStylesheetItem(s, + "\t\timg.wimage { " + "display:block; " + "position:relative; " + "left:0px; " + "top:0px; " + "}\n" + ); /** Global style code for all image widget "div" containers **/ - htrAddStylesheetItem(s, " div.wimage { visibility:inherit; position:absolute; overflow:hidden; }\n"); + htrAddStylesheetItem(s, + "\t\tdiv.wimage { " + "visibility:inherit; " + "position:absolute; " + "overflow:hidden; " + "}\n" + ); return 0; } @@ -74,57 +87,52 @@ htimgRender(pHtSession s, pWgtrNode tree, int z) char name[64]; char src[256]; int x=-1,y=-1,w,h; - int id, i; - char *text; + int rval = -1; char fieldname[HT_FIELDNAME_SIZE]; char form[64]; - char* aspect; + char* alt_text = NULL; + char* aspect = NULL; + char* default_alt_text = ""; + char* default_aspect = "stretch"; - if(!(s->Capabilities.Dom0NS || s->Capabilities.Dom1HTML)) + /** Get an id for this. **/ + const int id = (HTIMG.idcnt++); + + /** Verify browser capabilities. **/ + if (!s->Capabilities.Dom1HTML || !s->Capabilities.Dom2CSS) { - mssError(1,"HTTBL","Netscape DOM support or W3C DOM Level 1 HTML required"); - return -1; + mssError(1, "HTIMG", "Unsupported browser: W3C DOM1 HTML and DOM2 CSS support required."); + goto end; } - /** Get an id for this. **/ - id = (HTIMG.idcnt++); - /** Get x,y,w,h of this object **/ if (wgtrGetPropertyValue(tree,"x",DATA_T_INTEGER,POD(&x)) != 0) x=0; if (wgtrGetPropertyValue(tree,"y",DATA_T_INTEGER,POD(&y)) != 0) y=0; if (wgtrGetPropertyValue(tree,"width",DATA_T_INTEGER,POD(&w)) != 0) { mssError(1,"HTIMG","Image widget must have a 'width' property"); - return -1; + goto end; } if (wgtrGetPropertyValue(tree,"height",DATA_T_INTEGER,POD(&h)) != 0) { mssError(1,"HTIMG","Image widget must have a 'height' property"); - return -1; + goto end; } if(wgtrGetPropertyValue(tree,"text",DATA_T_STRING,POD(&ptr)) == 0) - text=nmSysStrdup(ptr); - else - text=nmSysStrdup(""); + alt_text=nmSysStrdup(ptr); + if (alt_text == NULL) alt_text = default_alt_text; /** Image aspect scaling: stretch or preserve **/ if(wgtrGetPropertyValue(tree,"aspect",DATA_T_STRING,POD(&ptr)) == 0) aspect=nmSysStrdup(ptr); - else - aspect=nmSysStrdup("stretch"); + if (aspect == NULL) aspect = default_aspect; /** Get name **/ - if (wgtrGetPropertyValue(tree,"name",DATA_T_STRING,POD(&ptr)) != 0) return -1; + if (wgtrGetPropertyValue(tree,"name",DATA_T_STRING,POD(&ptr)) != 0) goto end; strtcpy(name,ptr,sizeof(name)); /** image source **/ - /*if (!htrCheckAddExpression(s, tree, name, "source") && wgtrGetPropertyValue(tree,"source",DATA_T_STRING,POD(&ptr)) != 0) - { - mssError(1,"HTIMG","Image widget must have a 'source' property"); - nmSysFree(text); - return -1; - }*/ ptr = ""; htrCheckAddExpression(s, tree, name, "source"); wgtrGetPropertyValue(tree,"source",DATA_T_STRING,POD(&ptr)); @@ -144,43 +152,109 @@ htimgRender(pHtSession s, pWgtrNode tree, int z) else form[0]='\0'; - /** Ok, write the style header items. **/ - htrAddStylesheetItem_va(s,"\t#img%POS { left:%INTpx; top:%INTpx; width:%POSpx; height:%POSpx; z-index:%POS; text-align:center; }\n",id,x,y,w,h,z); - - /** Init image widget (?) **/ - htrAddWgtrObjLinkage_va(s, tree, "img%POS",id); - htrAddScriptInit_va(s, " im_init(wgtrGetNodeRef(ns,'%STR&SYM'), {field:'%STR&JSSTR', form:'%STR&JSSTR'});\n", - name, fieldname, form); - htrAddScriptInclude(s, "/sys/js/htdrv_image.js", 0); + /** Link the widget to the DOM node. **/ + if (htrAddWgtrObjLinkage_va(s, tree, "img%POS", id) != 0) goto end; + + /** Initialize image scripts. **/ + if (htrAddScriptInclude(s, "/sys/js/htdrv_image.js", 0) != 0) goto end; + if (htrAddScriptInit_va(s, + "\tim_init(wgtrGetNodeRef(ns, '%STR&SYM'), { " + "field:'%STR&JSSTR', " + "form:'%STR&JSSTR', " + "});\n", + name, + fieldname, form + ) != 0) + { + mssError(0, "HTDT", "Failed to write JS init call."); + goto end; + } /** Event Handlers **/ - htrAddEventHandlerFunction(s, "document","MOUSEUP", "img", "im_mouseup"); - htrAddEventHandlerFunction(s, "document","MOUSEDOWN", "img", "im_mousedown"); - htrAddEventHandlerFunction(s, "document","MOUSEOVER", "img", "im_mouseover"); - htrAddEventHandlerFunction(s, "document","MOUSEOUT", "img", "im_mouseout"); - htrAddEventHandlerFunction(s, "document","MOUSEMOVE", "img", "im_mousemove"); - - /** HTML body
element for the base layer. **/ - if (!strcmp(aspect, "stretch")) + if (htrAddEventHandlerFunction(s, "document", "MOUSEDOWN", "img", "im_mousedown") != 0) goto end; + if (htrAddEventHandlerFunction(s, "document", "MOUSEMOVE", "img", "im_mousemove") != 0) goto end; + if (htrAddEventHandlerFunction(s, "document", "MOUSEOUT", "img", "im_mouseout") != 0) goto end; + if (htrAddEventHandlerFunction(s, "document", "MOUSEOVER", "img", "im_mouseover") != 0) goto end; + if (htrAddEventHandlerFunction(s, "document", "MOUSEUP", "img", "im_mouseup") != 0) goto end; + + /** Write the style for the image container div. **/ + if (htrAddStylesheetItem_va(s, + "\t\t#img%POS { " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "width:"ht_flex_format"; " + "height:"ht_flex_format"; " + "z-index:%POS; " + "text-align:center; " + "}\n", + id, + ht_flex_x(x, tree), + ht_flex_y(y, tree), + ht_flex_w(w, tree), + ht_flex_h(h, tree), + z + ) != 0) { - htrAddBodyItemLayer_va(s, 0, "img%POS", id, "wimage", - "\n\n", - id, w, h, src); + mssError(0, "HTDT", "Failed to write image CSS."); + goto end; } - else // "preserve" + + /** Use a style that honors the aspect ratio attribute. **/ + char* style = (strcmp(aspect, "stretch") == 0) + ? /* "stretch" */ + "width:100%; " + "height:100%; " + : /* "preserve" */ + "width:100%; " + "height:auto; " + "max-width:fit-content; " + "max-height:fit-content; " + "display:inline; "; + + /** Write image HTML, including the containing div. **/ + if (htrAddBodyItemLayer_va(s, 0, + "img%POS", id, "wimage", + "\n\n", + id, + w, + h, + style, + src, + alt_text + ) != 0) { - htrAddBodyItemLayer_va(s, 0, "img%POS", id, "wimage", - "\n\n", - id, w, h, src); + mssError(0, "HTDT", "Failed to write image HTML."); + goto end; } - /** Check for more sub-widgets **/ - for (i=0;iChildren));i++) - htrRenderWidget(s, xaGetItem(&(tree->Children), i), z+1); + /** Render children. **/ + if (htrRenderSubwidgets(s, tree, z + 1) != 0) goto end; - nmSysFree(text); + /** Success. **/ + rval = 0; - return 0; + end: + if (rval != 0) + { + mssError(0, "HTIMG", + "Failed to render \"%s\":\"%s\" (id: %d).", + tree->Name, tree->Type, id + ); + } + + /** Clean up. **/ + if (alt_text != NULL && alt_text != default_alt_text) nmSysFree(alt_text); + if (aspect != NULL && aspect != default_aspect) nmSysFree(aspect); + + return rval; } diff --git a/centrallix/htmlgen/htdrv_imagebutton.c b/centrallix/htmlgen/htdrv_imagebutton.c index 64cfe92eb..b6bd536c2 100644 --- a/centrallix/htmlgen/htdrv_imagebutton.c +++ b/centrallix/htmlgen/htdrv_imagebutton.c @@ -14,7 +14,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -66,46 +66,46 @@ htibtnRender(pHtSession s, pWgtrNode tree, int z) int is_enabled = 1; int button_repeat = 0; int x,y,w,h; - int id, i; - pExpression code; + int rval = -1; char* tooltip = NULL; - if(!s->Capabilities.Dom0NS && !s->Capabilities.Dom0IE && !(s->Capabilities.Dom1HTML && s->Capabilities.Dom2CSS)) + /** Get an id for this. **/ + const int id = (HTIBTN.idcnt++); + + /** Verify browser capabilities. **/ + if (!s->Capabilities.Dom1HTML || !s->Capabilities.Dom2CSS) { - mssError(1,"HTIBTN","Netscape DOM or W3C DOM1 HTML and DOM2 CSS support required"); - return -1; + mssError(1, "HTIMG", "Unsupported browser: W3C DOM1 HTML and DOM2 CSS support required."); + goto end; } - - /** Get an id for this. **/ - id = (HTIBTN.idcnt++); - + /** Get x,y,w,h of this object **/ if (wgtrGetPropertyValue(tree,"x",DATA_T_INTEGER,POD(&x)) != 0) { mssError(1,"HTIBTN","ImageButton must have an 'x' property"); - return -1; + goto end; } if (wgtrGetPropertyValue(tree,"y",DATA_T_INTEGER,POD(&y)) != 0) { mssError(1,"HTIBTN","ImageButton must have a 'y' property"); - return -1; + goto end; } if (wgtrGetPropertyValue(tree,"width",DATA_T_INTEGER,POD(&w)) != 0) { mssError(1,"HTIBTN","ImageButton must have a 'width' property"); - return -1; + goto end; } if (wgtrGetPropertyValue(tree,"height",DATA_T_INTEGER,POD(&h)) != 0) h = -1; /** Get name **/ - if (wgtrGetPropertyValue(tree,"name",DATA_T_STRING,POD(&ptr)) != 0) return -1; + if (wgtrGetPropertyValue(tree,"name",DATA_T_STRING,POD(&ptr)) != 0) goto end; strtcpy(name,ptr,sizeof(name)); /** Get normal, point, and click images **/ if (wgtrGetPropertyValue(tree,"image",DATA_T_STRING,POD(&ptr)) != 0) { mssError(1,"HTIBTN","ImageButton must have an 'image' property"); - return -1; + goto end; } strtcpy(n_img,ptr,sizeof(n_img)); @@ -137,50 +137,111 @@ htibtnRender(pHtSession s, pWgtrNode tree, int z) button_repeat = htrGetBoolean(tree, "repeat", 0); /** Ok, write the style header items. **/ - htrAddStylesheetItem_va(s,"\t#ib%POSpane { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; Z-INDEX:%POS; cursor:pointer; }\n",id,x,y,w,z); - - htrAddScriptGlobal(s, "ib_cur_img", "null", 0); - htrAddWgtrObjLinkage_va(s, tree, "ib%POSpane", id); + if (htrAddStylesheetItem_va(s, + "\t\t#ib%POSpane { " + "position:absolute; " + "visibility:inherit; " + "overflow:hidden; " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "width:"ht_flex_format"; " + "z-index:%POS; " + "}\n", + id, + ht_flex_x(x, tree), + ht_flex_y(y, tree), + ht_flex_w(w, tree), + z + ) != 0) + { + mssError(0, "HTIBTN", "Failed to write main CSS."); + goto end; + } - htrAddScriptInclude(s, "/sys/js/htdrv_imagebutton.js", 0); + /** Setup JS. **/ + if (htrAddScriptGlobal(s, "ib_cur_img", "null", 0) != 0) goto end; + if (htrAddWgtrObjLinkage_va(s, tree, "ib%POSpane", id) != 0) goto end; + if (htrAddScriptInclude(s, "/sys/js/htdrv_imagebutton.js", 0) != 0) goto end; /** User requesting expression for enabled? **/ if (wgtrGetPropertyType(tree,"enabled") == DATA_T_CODE) { + pExpression code; wgtrGetPropertyValue(tree,"enabled",DATA_T_CODE,POD(&code)); is_enabled = 0; htrAddExpression(s, name, "enabled", code); } - htrAddScriptInit_va(s," ib_init({layer:wgtrGetNodeRef(ns,\"%STR&SYM\"), n:'%STR&JSSTR', p:'%STR&JSSTR', c:'%STR&JSSTR', d:'%STR&JSSTR', width:%INT, height:%INT, name:'%STR&SYM', enable:%INT, tooltip:'%STR&JSSTR', repeat:%INT});\n", - name, n_img, p_img, c_img, d_img, w, h, name,is_enabled, tooltip, button_repeat); - - /** HTML body
elements for the layers. **/ - if (h < 0) - if(is_enabled) - htrAddBodyItem_va(s,"
\n",id,n_img); - else - htrAddBodyItem_va(s,"
\n",id,d_img); - else - if(is_enabled) - htrAddBodyItem_va(s,"
\n",id,n_img,w,h); - else - htrAddBodyItem_va(s,"
\n",id,d_img,w,h); + /** Write script initialization call. **/ + if (htrAddScriptInit_va(s, + "\tib_init({ " + "layer:wgtrGetNodeRef(ns, '%STR&SYM'), " + "name:'%STR&SYM', " + "n:'%STR&JSSTR', " + "p:'%STR&JSSTR', " + "c:'%STR&JSSTR', " + "d:'%STR&JSSTR', " + "width:%INT, " + "height:%INT, " + "enable:%INT, " + "tooltip:'%STR&JSSTR', " + "repeat:%INT, " + "});\n", + name, name, + n_img, p_img, c_img, d_img, + w, h, + is_enabled, tooltip, button_repeat + ) != 0) + { + mssError(0, "HTIBTN", "Failed to write JS init call."); + goto end; + } + + /** Generate alt text. **/ + char alt_text[16]; + snprintf(alt_text, sizeof(alt_text), "img%d", id); + + /** Write HTML. **/ + if (htrAddBodyItem_va(s, + "
" + "%STR&HTE" + "
\n", + id, (is_enabled) ? n_img : d_img, alt_text, (h >= 0), w, h + ) != 0) + { + mssError(0, "HTBTN", + "Failed to write HTML for %sabled image button.", + (is_enabled) ? "en" : "dis" + ); + goto end; + } /** Add the event handling scripts **/ - htrAddEventHandlerFunction(s, "document", "MOUSEDOWN", "ib", "ib_mousedown"); - htrAddEventHandlerFunction(s, "document", "MOUSEUP", "ib", "ib_mouseup"); - htrAddEventHandlerFunction(s, "document", "MOUSEOVER", "ib", "ib_mouseover"); - htrAddEventHandlerFunction(s, "document", "MOUSEOUT", "ib", "ib_mouseout"); - htrAddEventHandlerFunction(s, "document", "MOUSEMOVE", "ib", "ib_mousemove"); + if (htrAddEventHandlerFunction(s, "document", "MOUSEDOWN", "ib", "ib_mousedown") != 0) goto end; + if (htrAddEventHandlerFunction(s, "document", "MOUSEMOVE", "ib", "ib_mousemove") != 0) goto end; + if (htrAddEventHandlerFunction(s, "document", "MOUSEOUT", "ib", "ib_mouseout") != 0) goto end; + if (htrAddEventHandlerFunction(s, "document", "MOUSEOVER", "ib", "ib_mouseover") != 0) goto end; + if (htrAddEventHandlerFunction(s, "document", "MOUSEUP", "ib", "ib_mouseup") != 0) goto end; - /** Check for more sub-widgets within the imagebutton. **/ - for (i=0;iChildren));i++) - htrRenderWidget(s, xaGetItem(&(tree->Children), i), z+1); + /** Render children. **/ + if (htrRenderSubwidgets(s, tree, z + 1) != 0) goto end; - if (tooltip) nmSysFree(tooltip); + /** Success. **/ + rval = 0; - return 0; + end: + if (rval != 0) + { + mssError(0, "HTIBTN", + "Failed to render \"%s\":\"%s\" (id: %d).", + tree->Name, tree->Type, id + ); + } + + /** Clean up. **/ + if (tooltip != NULL) nmSysFree(tooltip); + + return rval; } diff --git a/centrallix/htmlgen/htdrv_label.c b/centrallix/htmlgen/htdrv_label.c index d7140489f..31bd0d874 100644 --- a/centrallix/htmlgen/htdrv_label.c +++ b/centrallix/htmlgen/htdrv_label.c @@ -15,7 +15,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -66,32 +66,29 @@ htlblRender(pHtSession s, pWgtrNode tree, int z) char fieldname[HT_FIELDNAME_SIZE]; char form[64]; int x=-1,y=-1,w,h; - int id; - /*int fontsize;*/ + int rval = -1; int font_size; char *text; char* tooltip; - char stylestr[128]; int is_bold = 0; int is_link = 0; int is_italic = 0; int allow_break = 0; int overflow_ellipsis = 0; - pExpression code; - int n; int auto_height=0; - if(!(s->Capabilities.Dom0NS || s->Capabilities.Dom1HTML)) + /** Get an id for this. **/ + const int id = (HTLBL.idcnt++); + + /** Verify browser capabilities. **/ + if (!s->Capabilities.Dom1HTML || !s->Capabilities.Dom2CSS) { - mssError(1,"HTTBL","Netscape DOM support or W3C DOM Level 1 HTML required"); - return -1; + mssError(1, "HTIMG", "Unsupported browser: W3C DOM1 HTML and DOM2 CSS support required."); + goto end; } - /** Get an id for this. **/ - id = (HTLBL.idcnt++); - /** Get name **/ - if (wgtrGetPropertyValue(tree,"name",DATA_T_STRING,POD(&ptr)) != 0) return -1; + if (wgtrGetPropertyValue(tree,"name",DATA_T_STRING,POD(&ptr)) != 0) goto end; strtcpy(name,ptr,sizeof(name)); /** Get x,y,w,h of this object **/ @@ -100,18 +97,21 @@ htlblRender(pHtSession s, pWgtrNode tree, int z) if (wgtrGetPropertyValue(tree,"width",DATA_T_INTEGER,POD(&w)) != 0) { mssError(1,"HTLBL","Label widget must have a 'width' property"); - return -1; + goto end; } if (wgtrGetPropertyValue(tree,"height",DATA_T_INTEGER,POD(&h)) != 0) { mssError(1,"HTLBL","Label widget must have a 'height' property"); - return -1; + goto end; } /** auto height? **/ + int n; if (wgtrGetPropertyValue(tree,"r_height",DATA_T_INTEGER,POD(&n)) == 0 && n == -1) auto_height = 1; + /** Get label value. **/ + pExpression code; if (wgtrGetPropertyType(tree,"value") == DATA_T_CODE) { wgtrGetPropertyValue(tree,"value",DATA_T_CODE,POD(&code)); @@ -125,6 +125,7 @@ htlblRender(pHtSession s, pWgtrNode tree, int z) else text=nmSysStrdup(""); + /** Get tooltip. **/ if(wgtrGetPropertyValue(tree,"tooltip",DATA_T_STRING,POD(&ptr)) == 0) tooltip=nmSysStrdup(ptr); else @@ -197,43 +198,155 @@ htlblRender(pHtSession s, pWgtrNode tree, int z) form[0]='\0'; /** Ok, write the style header items. **/ - htrAddStylesheetItem_va(s,"\t#lbl%POS { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; %[HEIGHT:%POSpx; %]WIDTH:%POSpx; Z-INDEX:%POS; cursor:default; %[font-weight:bold; %]%[color:%STR&CSSVAL; %]%[font-size:%POSpx; %]text-align:%STR&CSSVAL; vertical-align:%STR&CSSVAL; %[white-space:nowrap; %]%[text-overflow:ellipsis; overflow:hidden; %]%[font-style:italic; %]}\n", - id,x,y, - !auto_height, h, - w,z, - is_bold, *fgcolor, fgcolor, font_size > 0, font_size, align, valign, - !allow_break, overflow_ellipsis, is_italic); + if (htrAddStylesheetItem_va(s, + "\t\t#lbl%POS { " + "position:absolute; " + "visibility:inherit; " + "cursor:pointer; " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "width:"ht_flex_format"; " + "%[height:"ht_flex_format"; %]" + "z-index:%POS; " + "%[font-weight:bold; %]" + "%[color:%STR&CSSVAL; %]" + "%[font-size:%POSpx; %]" + "text-align:%STR&CSSVAL; " + "vertical-align:%STR&CSSVAL; " + "%[white-space:nowrap; %]" + "%[text-overflow:ellipsis; overflow:hidden; %]" + "%[font-style:italic; %]" + "}\n", + id, + ht_flex_x(x, tree), + ht_flex_y(y, tree), + ht_flex_w(w, tree), + (!auto_height), ht_flex_h(h, tree), + z, + (is_bold), + (*fgcolor), fgcolor, + (font_size > 0), font_size, + align, + valign, + (!allow_break), + (overflow_ellipsis), + (is_italic) + ) != 0) + { + mssError(0, "HTLBL", "Failed to write base label CSS."); + goto end; + } + if (is_link) - htrAddStylesheetItem_va(s,"\t#lbl%POS:hover { %[color:%STR&CSSVAL; %]text-decoration:underline; cursor:pointer; }\n", id, *pfgcolor, pfgcolor); + { + if (htrAddStylesheetItem_va(s, + "\t\t#lbl%POS:hover { " + "%[color:%STR&CSSVAL; %]" + "text-decoration:underline; " + "}\n", + id, + (*pfgcolor), pfgcolor + ) != 0) + { + mssError(0, "HTLBL", "Failed to write label hover CSS."); + goto end; + } + } if (is_link && *cfgcolor) - htrAddStylesheetItem_va(s,"\t#lbl%POS:active { color:%STR&CSSVAL; text-decoration:underline; cursor:pointer; }\n", id, cfgcolor); - htrAddStylesheetItem_va(s,"\t#lbl%POS p { text-align:%STR&CSSVAL; %[position:relative; top:50%%; transform:translateY(-50%%); %]padding:0px; margin:0px; border-spacing:0px; width:%POSpx; }\n", id, align, !strcmp(valign, "middle"), w); + { + if (htrAddStylesheetItem_va(s, + "\t\t#lbl%POS:active { " + "color:%STR&CSSVAL; " + "text-decoration:underline; " + "cursor:pointer; " + "}\n", + id, + cfgcolor + ) != 0) + { + mssError(0, "HTLBL", "Failed to write active label (click) CSS."); + goto end; + } + } + + if (htrAddStylesheetItem_va(s, + "\t\t#lbl%POS p { " + "text-align:%STR&CSSVAL; " + "%[position:relative; top:50%%; transform:translateY(-50%%); %]" + "padding:0px; " + "margin:0px; " + "border-spacing:0px; " + "width:100%%; " + "}\n", + id, + align, + (strcmp(valign, "middle") == 0) + ) != 0) + { + mssError(0, "HTLBL", "Failed to write label text CSS."); + goto end; + } - htrAddWgtrObjLinkage_va(s, tree, "lbl%POS",id); - stylestr[0] = '\0'; - htrAddScriptInit_va(s, " lbl_init(wgtrGetNodeRef(ns,'%STR&SYM'), {field:'%STR&JSSTR', form:'%STR&JSSTR', text:'%STR&JSSTR', style:'%STR&JSSTR', tooltip:'%STR&JSSTR', link:%POS, pfg:'%STR&JSSTR'});\n", - name, fieldname, form, text, stylestr, tooltip, is_link, pfgcolor); + /** Link the widget to the DOM node. **/ + if (htrAddWgtrObjLinkage_va(s, tree, "lbl%POS", id) != 0) goto end; + + /** Write the initialization script call. **/ + if (htrAddScriptInit_va(s, + "\tlbl_init(wgtrGetNodeRef(ns,'%STR&SYM'), { " + "field:'%STR&JSSTR', " + "form:'%STR&JSSTR', " + "text:'%STR&JSSTR', " + "style:'', " + "tooltip:'%STR&JSSTR', " + "link:%POS, " + "pfg:'%STR&JSSTR', " + "});\n", + name, + fieldname, form, text, + tooltip, is_link, pfgcolor + ) != 0) + { + mssError(0, "HTLBL", "Failed to write label text CSS."); + goto end; + } /** Script include to get functions **/ - htrAddScriptInclude(s, "/sys/js/htdrv_label.js", 0); + if (htrAddScriptInclude(s, "/sys/js/htdrv_label.js", 0) != 0) goto end; /** Event Handlers **/ - htrAddEventHandlerFunction(s, "document","MOUSEUP", "lbl", "lbl_mouseup"); - htrAddEventHandlerFunction(s, "document","MOUSEDOWN", "lbl", "lbl_mousedown"); - htrAddEventHandlerFunction(s, "document","MOUSEOVER", "lbl", "lbl_mouseover"); - htrAddEventHandlerFunction(s, "document","MOUSEOUT", "lbl", "lbl_mouseout"); - htrAddEventHandlerFunction(s, "document","MOUSEMOVE", "lbl", "lbl_mousemove"); + if (htrAddEventHandlerFunction(s, "document", "MOUSEDOWN", "lbl", "lbl_mousedown") != 0) goto end; + if (htrAddEventHandlerFunction(s, "document", "MOUSEMOVE", "lbl", "lbl_mousemove") != 0) goto end; + if (htrAddEventHandlerFunction(s, "document", "MOUSEOUT", "lbl", "lbl_mouseout") != 0) goto end; + if (htrAddEventHandlerFunction(s, "document", "MOUSEOVER", "lbl", "lbl_mouseover") != 0) goto end; + if (htrAddEventHandlerFunction(s, "document", "MOUSEUP", "lbl", "lbl_mouseup") != 0) goto end; /** HTML body
element for the base layer. **/ - htrAddBodyItemLayer_va(s, 0, "lbl%POS", id, NULL, "

%STR&HTENLBR

", text); + if (htrAddBodyItemLayer_va(s, 0, "lbl%POS", id, NULL, "

%STR&HTENLBR

", text) != 0) + { + mssError(0, "HTIBTN", "Failed to write label HTML."); + goto end; + } - /** Check for more sub-widgets **/ - htrRenderSubwidgets(s, tree, z+1); + /** Render children. **/ + if (htrRenderSubwidgets(s, tree, z + 1) != 0) goto end; - nmSysFree(text); - nmSysFree(tooltip); + /** Success. **/ + rval = 0; - return 0; + end: + if (rval != 0) + { + mssError(0, "HTIBTN", + "Failed to render \"%s\":\"%s\" (id: %d).", + tree->Name, tree->Type, id + ); + } + + /** Clean up. **/ + if (text != NULL) nmSysFree(text); + if (tooltip != NULL) nmSysFree(tooltip); + + return rval; } diff --git a/centrallix/htmlgen/htdrv_map.c b/centrallix/htmlgen/htdrv_map.c index 73f0d5518..9a14920d6 100755 --- a/centrallix/htmlgen/htdrv_map.c +++ b/centrallix/htmlgen/htdrv_map.c @@ -14,7 +14,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 2012 LightSys Technology Services, Inc. */ +/* Copyright (C) 2012-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -55,17 +55,17 @@ int htmapRender(pHtSession s, pWgtrNode map_node, int z) char main_bg[128]; char osrc[64]; int x = -1, y = -1, w, h; - int id; int allow_select, show_select; - if (!s->Capabilities.Dom0NS && !(s->Capabilities.Dom1HTML && s->Capabilities.CSS1)) - { - mssError(1, "HTMAP", "Netscape DOM or W3C DOM1 HTML and CSS support required"); - return -1; - } - /** Get an id for this. **/ - id = (HTMAP.idcnt++); + const int id = (HTMAP.idcnt++); + + /** Verify browser capabilities. **/ + if (!s->Capabilities.Dom1HTML || !s->Capabilities.Dom2CSS) + { + mssError(1, "HTMAP", "Unsupported browser: W3C DOM1 HTML and DOM2 CSS support required."); + goto err; + } /** Get x,y,w,h of this object **/ if (wgtrGetPropertyValue(map_node, "x", DATA_T_INTEGER, POD(&x)) != 0) @@ -104,39 +104,86 @@ int htmapRender(pHtSession s, pWgtrNode map_node, int z) strtcpy(name, ptr, sizeof(name)); /** Add css item for the layer **/ - htrAddStylesheetItem_va(s, "\t#map%POSbase { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; Z-INDEX:%POS; overflow: hidden; %STR}\n", id, x, y, w, h, z, main_bg); - - htrAddWgtrObjLinkage_va(s, map_node, "map%POSbase", id); - - /** Include our necessary supporting js files **/ - htrAddScriptInclude(s, "/sys/js/htdrv_map.js", 0); - htrAddScriptInclude(s, "/sys/js/ht_utils_layers.js", 0); - htrAddScriptInclude(s, "/sys/js/openlayers/build/ol.js", 0); - htrAddScriptInclude(s, "/sys/js/openlayers/build/ol.js.map", 0); - //htrAddScriptInclude(s, "https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js", 0); - //htrAddScriptInclude(s, "https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js", 0); - - /** Event Handlers **/ - htrAddEventHandlerFunction(s, "document", "MOUSEUP", "map", "map_mouseup"); - htrAddEventHandlerFunction(s, "document", "MOUSEDOWN", "map", "map_mousedown"); - htrAddEventHandlerFunction(s, "document", "MOUSEOVER", "map", "map_mouseover"); - htrAddEventHandlerFunction(s, "document", "MOUSEMOVE", "map", "map_mousemove"); - htrAddEventHandlerFunction(s, "document", "MOUSEOUT", "map", "map_mouseout"); + if (htrAddStylesheetItem_va(s, + "\t\t#map%POSbase { " + "position:absolute; " + "visibility:inherit; " + "overflow:hidden; " + "left:%INTpx; " + "top:%INTpx; " + "width:%POSpx; " + "height:%POSpx; " + "z-index:%POS; " + "%STR " + "}\n", + id, x, y, w, h, z, main_bg + ) != 0) + { + mssError(0, "HTMAP", "Failed to write base CSS."); + goto err; + } + + /** Link the widget to the DOM node. **/ + if (htrAddWgtrObjLinkage_va(s, map_node, "map%POSbase", id) != 0) goto err; + + /** Include supporting JS files, **/ + if (htrAddScriptInclude(s, "/sys/js/htdrv_map.js", 0) != 0) goto err; + if (htrAddScriptInclude(s, "/sys/js/ht_utils_layers.js", 0) != 0) goto err; + if (htrAddScriptInclude(s, "/sys/js/openlayers/build/ol.js", 0) != 0) goto err; + if (htrAddScriptInclude(s, "/sys/js/openlayers/build/ol.js.map", 0) != 0) goto err; + //if (htrAddScriptInclude(s, "https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js", 0) != 0) goto err; + //if (htrAddScriptInclude(s, "https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js", 0) != 0) goto err; + + /** Add event handlers. **/ + if (htrAddEventHandlerFunction(s, "document", "MOUSEUP", "map", "map_mouseup") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEDOWN", "map", "map_mousedown") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEOVER", "map", "map_mouseover") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEMOVE", "map", "map_mousemove") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEOUT", "map", "map_mouseout") != 0) goto err; /** Script initialization call. **/ - htrAddScriptInit_va(s, " map_init({layer:wgtrGetNodeRef(ns,\"%STR&SYM\"), osrc:%[wgtrGetNodeRef(ns,\"%STR&SYM\")%]%[null%], allow_select:%INT, show_select:%INT, name:\"%STR&SYM\"});\n", - name, *osrc, osrc, !*osrc, allow_select, show_select, name); + if (htrAddScriptInit_va(s, + "\tmap_init({ " + "layer:wgtrGetNodeRef(ns, '%STR&SYM'), " + "osrc:%[wgtrGetNodeRef(ns, '%STR&SYM')%]%[null%], " + "name:'%STR&SYM', " + "allow_select:%INT, " + "show_select:%INT, " + "});\n", + name, (osrc[0] != '\0'), osrc, (osrc[0] == '\0'), name, + allow_select, show_select + ) != 0) + { + mssError(0, "HTMAP", "Failed to write JS init call."); + goto err; + } /** HTML body
element to be used by the OpenLayers map. **/ - htrAddBodyItem_va(s, "
\n", id); + if (htrAddBodyItem_va(s, "
\n", id) != 0) + { + mssError(0, "HTMAP", "Failed to write HTML for map open tag."); + goto err; + } - /** Check for widgets within the map. **/ - htrRenderSubwidgets(s, map_node, z + 2); + /** Render children. **/ + if (htrRenderSubwidgets(s, map_node, z + 2) != 0) goto err; /** End the containing div. **/ - htrAddBodyItem(s, "
\n"); + if (htrAddBodyItem(s, "
\n") != 0) + { + mssError(0, "HTMAP", "Failed to write HTML for map closing tag."); + goto err; + } + /** Success. **/ return 0; + + err: + mssError(0, "HTMAP", + "Failed to render \"%s\":\"%s\" (id: %d).", + map_node->Name, map_node->Type, id + ); + return -1; } /*** htmapInitialize - register with the ht_render module. diff --git a/centrallix/htmlgen/htdrv_menu.c b/centrallix/htmlgen/htdrv_menu.c index a400c885e..c01fe776f 100644 --- a/centrallix/htmlgen/htdrv_menu.c +++ b/centrallix/htmlgen/htdrv_menu.c @@ -4,6 +4,7 @@ #include #include "ht_render.h" #include "obj.h" +#include "cxlib/util.h" #include "cxlib/mtask.h" #include "cxlib/xarray.h" #include "cxlib/xhash.h" @@ -14,7 +15,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 2000-2005 LightSys Technology Services, Inc. */ +/* Copyright (C) 2000-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -52,7 +53,7 @@ static struct int htmenu_internal_AddDot(pHtSession s, int mcnt, char* nptr, int is_horizontal, int row_height) { - htrAddBodyItem_va(s,"", ((mcnt&1) || !is_horizontal)?"top":"bottom", ((mcnt&1) || !is_horizontal)?"top":"bottom", nptr, mcnt, ((mcnt&1) || !is_horizontal)?(row_height?row_height:1):1); + htrAddBodyItem_va(s,"", ((mcnt&1) || !is_horizontal)?"top":"bottom", ((mcnt&1) || !is_horizontal)?"top":"bottom", nptr, mcnt, ((mcnt&1) || !is_horizontal)?(row_height?row_height:1):1); return 0; } @@ -85,7 +86,7 @@ htmenu_internal_AddItem(pHtSession s, pWgtrNode menu_item, int is_horizontal, in /** checkbox **/ if ( (rval=htrGetBoolean(menu_item, "checked", -1)) >= 0) { - htrAddBodyItem_va(s, "", mcnt, rval?"checked":"unchecked"); + htrAddBodyItem_va(s, "", mcnt, rval?"checked":"unchecked"); xsConcatQPrintf(xs, ", check:%STR", rval?"true":"false"); } else @@ -121,9 +122,19 @@ htmenu_internal_AddItem(pHtSession s, pWgtrNode menu_item, int is_horizontal, in } if (is_submenu) - htrAddScriptInit_va(s, " wgtrGetNodeRef(ns,\"%STR&SYM\").AddItem({%STR});\n", nptr, xs->String); + { + htrAddScriptInit_va(s, + "\twgtrGetNodeRef(ns, '%STR&SYM').AddItem({ %STR });\n", + nptr, xs->String + ); + } else - htrAddScriptInit_va(s, " wgtrReplaceNode(wgtrGetNodeRef(\"%STR&SYM\",\"%STR&SYM\"), wgtrGetNodeRef(ns,\"%STR&SYM\").AddItem({%STR}));\n", wgtrGetNamespace(menu_item), name, nptr, xs->String); + { + htrAddScriptInit_va(s, + "\twgtrReplaceNode(wgtrGetNodeRef('%STR&SYM', '%STR&SYM'), wgtrGetNodeRef(ns, '%STR&SYM').AddItem({ %STR }));\n", + wgtrGetNamespace(menu_item), name, nptr, xs->String + ); + } return 0; } @@ -147,7 +158,7 @@ htmenu_internal_AddSep(pHtSession s, int is_horizontal, int row_h, int mcnt, cha { htrAddBodyItem(s, ""); } - htrAddScriptInit_va(s, " wgtrGetNodeRef(ns,\"%STR&SYM\").AddItem({sep:true});\n", nptr); + htrAddScriptInit_va(s, "\twgtrGetNodeRef(ns, '%STR&SYM').AddItem({ sep:true });\n", nptr); return 0; } @@ -167,7 +178,7 @@ htmenu_internal_AddTitle(pHtSession s, pWgtrNode menu_title, int is_horizontal, ptr = ""; htrAddBodyItem_va(s, "%STR&HTE", ptr); - htrAddScriptInit_va(s, " wgtrGetNodeRef(ns,\"%STR&SYM\").AddItem({sep:true});\n", nptr); + htrAddScriptInit_va(s, "\twgtrGetNodeRef(ns, '%STR&SYM').AddItem({ sep:true });\n", nptr); return 0; } @@ -218,26 +229,26 @@ htmenuRender(pHtSession s, pWgtrNode menu, int z) char *ptr; int x,y,w,h; int col_w, row_h; - int id, i, j, cnt, cntj, mcnt; + int i, j, cnt, cntj, mcnt; int is_horizontal; int is_popup; /*int is_submenu;*/ pWgtrNode sub_tree; pWgtrNode sub_tree_child; - pXString xs; - int bx = 0; + pXString xs = NULL; + int rval = -1; int shadow_offset, shadow_radius; char shadow_color[128]; - if(!s->Capabilities.Dom0NS && !s->Capabilities.CSS2) + /** Get an id for this. **/ + const int id = (HTMN.idcnt++); + + /** Verify browser capabilities. **/ + if (!s->Capabilities.Dom1HTML || !s->Capabilities.Dom2CSS) { - mssError(1,"HTMENU","Netscape 4 DOM or W3C CSS2 support required"); - return -1; + mssError(1, "HTMENU", "Unsupported browser: W3C DOM1 HTML and DOM2 CSS support required."); + goto end; } - if (s->Capabilities.CSS2) bx = 1; - - /** Get an id for this. **/ - id = (HTMN.idcnt++); /** Get x,y,height,& width of this object **/ if (wgtrGetPropertyValue(menu,"x",DATA_T_INTEGER,POD(&x)) != 0) x=0; @@ -284,83 +295,164 @@ htmenuRender(pHtSession s, pWgtrNode menu, int z) is_popup = htrGetBoolean(menu, "popup", 0); if (is_popup < 0) is_popup = 0; - /** Write the main style header item. **/ - htrAddStylesheetItem_va(s,"\t#mn%POSmain { POSITION:absolute; VISIBILITY:%STR; LEFT:%INTpx; TOP:%INTpx; %[HEIGHT:%POSpx; %]%[WIDTH:%POSpx; %]Z-INDEX:%POS; }\n", id,is_popup?"hidden":"inherit", x, y, h != -1, h-2*bx, w != -1, w-2*bx, z); + /** Write styles for the main DOM element. **/ + if (htrAddStylesheetItem_va(s, + "\t\t#mn%POSmain { " + "position:absolute; " + "visibility:%STR; " + "overflow:hidden; " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "%[height:"ht_flex_format"; %]" + "%[width:"ht_flex_format"; %]" + "color:%STR; %STR" + "border-style:solid; " + "border-width:1px; " + "border-color:white gray gray white; " + "z-index:%POS; " + "}\n", + id, + (is_popup) ? "hidden" : "inherit", + ht_flex_x(x, menu), + ht_flex_y(y, menu), + (h != -1), ht_flex_h(h - 2, menu), + (w != -1), ht_flex_w(w - 2, menu), + textcolor, bgstr, + z + ) != 0) + { + mssError(0, "HTMENU", "Failed to write main CSS."); + goto end; + } if (shadow_radius > 0) { - htrAddStylesheetItem_va(s,"\t#mn%POSmain { box-shadow: %POSpx %POSpx %POSpx %STR&CSSVAL; }\n", id, shadow_offset, shadow_offset, shadow_radius, shadow_color); + if (htrAddStylesheetItem_va(s, + "\t\t#mn%POSmain { " + "box-shadow: %POSpx %POSpx %POSpx %STR&CSSVAL; " + "}\n", + id, + shadow_offset, shadow_offset, shadow_radius, shadow_color + ) != 0) + { + mssError(0, "HTMENU", "Failed to write shadow CSS."); + goto end; + } + } + + /** Write styles for the content container. **/ + if (htrAddStylesheetItem_va(s, + "\t\t#mn%POScontent { " + "position:absolute; " + "visibility:inherit; " + "overflow:hidden; " + "cursor:default; " + "left:0px; " + "top:0px; " + "height:100%%;" + "width:100%%;" + "z-index:%POS; " + "}\n", + id, + z + 1 + ) != 0) + { + mssError(0, "HTMENU", "Failed to write content CSS."); + goto end; } - htrAddStylesheetItem_va(s,"\t#mn%POScontent { POSITION:absolute; VISIBILITY: inherit; LEFT:0px; TOP:0px; %[HEIGHT:%POSpx; %]%[WIDTH:%POSpx; %]Z-INDEX:%POS; }\n", id, h != -1, h-2*bx, w != -1, w-2*bx, z+1); - if (s->Capabilities.CSS2) - htrAddStylesheetItem_va(s,"\t#mn%POSmain { overflow:hidden; border-style: solid; border-width: 1px; border-color: white gray gray white; color:%STR; %STR }\n", id, textcolor, bgstr); - - /** content layer **/ - if (s->Capabilities.CSS2) - htrAddStylesheetItem_va(s,"\t#mn%POScontent { overflow:hidden; cursor:default; }\n", id ); - /** highlight bar **/ - htrAddStylesheetItem_va(s, "\t#mn%POShigh { POSITION:absolute; VISIBILITY: hidden; LEFT:0px; TOP:0px; Z-INDEX:%POS; }\n", id, z); - if (s->Capabilities.CSS2) - htrAddStylesheetItem_va(s,"\t#mn%POShigh { overflow:hidden; }\n", id ); + /** Write styles for the highlight bar. **/ + if (htrAddStylesheetItem_va(s, + "\t\t#mn%POShigh { " + "position:absolute; " + "visibility:hidden; " + "overflow:hidden; " + "left:0px; " + "top:0px; " + "z-index:%POS; " + "}\n", + id, + z + ) != 0) + { + mssError(0, "HTMENU", "Failed to write highlight CSS."); + goto end; + } /** Get name **/ - if (wgtrGetPropertyValue(menu,"name",DATA_T_STRING,POD(&ptr)) != 0) return -1; + if (wgtrGetPropertyValue(menu, "name", DATA_T_STRING, POD(&ptr)) != 0) + { + mssError(0, "HTMENU", "Failed to get name!?."); + goto end; + } strtcpy(name,ptr,sizeof(name)); - - /** Globals **/ - htrAddScriptGlobal(s, "mn_active", "new Array()", 0); - htrAddScriptGlobal(s, "mn_current", "null", 0); - htrAddScriptGlobal(s, "mn_deactivate_tmout", "null", 0); - htrAddScriptGlobal(s, "mn_submenu_tmout", "null", 0); - htrAddScriptGlobal(s, "mn_pop_x", "0", 0); - htrAddScriptGlobal(s, "mn_pop_y", "0", 0); - htrAddScriptGlobal(s, "mn_mouseangle", "0", 0); - htrAddWgtrObjLinkage_va(s, menu, "mn%POSmain",id); - htrAddWgtrCtrLinkage_va(s, menu, "htr_subel(_obj, \"mn%POScontent\")",id); - - /** Scripts **/ - htrAddScriptInclude(s, "/sys/js/ht_utils_layers.js", 0); - htrAddScriptInclude(s, "/sys/js/ht_utils_string.js", 0); - htrAddScriptInclude(s, "/sys/js/htdrv_menu.js", 0); - - /** Initialization **/ - htrAddScriptInit_va(s," mn_init({layer:wgtrGetNodeRef(ns,\"%STR&SYM\"), clayer:wgtrGetContainer(wgtrGetNodeRef(ns,\"%STR&SYM\")), hlayer:htr_subel(wgtrGetNodeRef(ns,\"%STR&SYM\"), \"mn%POShigh\"), bgnd:\"%STR&JSSTR\", high:\"%STR&JSSTR\", actv:\"%STR&JSSTR\", txt:\"%STR&JSSTR\", w:%INT, h:%INT, horiz:%INT, pop:%INT, name:\"%STR&SYM\"});\n", - name, name, name, id, - bgstr, highlight, active, textcolor, - w, h, is_horizontal, is_popup, name); - - /** Event handlers **/ - htrAddEventHandlerFunction(s, "document", "MOUSEMOVE", "mn", "mn_mousemove"); - htrAddEventHandlerFunction(s, "document", "MOUSEOUT", "mn", "mn_mouseout"); - htrAddEventHandlerFunction(s, "document", "MOUSEOVER", "mn", "mn_mouseover"); - htrAddEventHandlerFunction(s, "document", "MOUSEDOWN", "mn", "mn_mousedown"); + + /** Write widget object and container linkage. **/ + if (htrAddWgtrObjLinkage_va(s, menu, "mn%POSmain", id) != 0) goto end; + if (htrAddWgtrCtrLinkage_va(s, menu, "htr_subel(_obj, 'mn%POScontent')", id) != 0) goto end; + + /** Write JS globals. **/ + if (htrAddScriptGlobal(s, "mn_active", "[]", 0) != 0) goto end; + if (htrAddScriptGlobal(s, "mn_current", "null", 0) != 0) goto end; + if (htrAddScriptGlobal(s, "mn_deactivate_tmout", "null", 0) != 0) goto end; + if (htrAddScriptGlobal(s, "mn_mouseangle", "0", 0) != 0) goto end; + if (htrAddScriptGlobal(s, "mn_pop_x", "0", 0) != 0) goto end; + if (htrAddScriptGlobal(s, "mn_pop_y", "0", 0) != 0) goto end; + if (htrAddScriptGlobal(s, "mn_submenu_tmout", "null", 0) != 0) goto end; + + /** Write script includes. **/ + if (htrAddScriptInclude(s, "/sys/js/ht_utils_layers.js", 0) != 0) goto end; + if (htrAddScriptInclude(s, "/sys/js/ht_utils_string.js", 0) != 0) goto end; + if (htrAddScriptInclude(s, "/sys/js/htdrv_menu.js", 0) != 0) goto end; + + /** Event handlers. **/ + if (htrAddEventHandlerFunction(s, "document", "MOUSEDOWN", "mn", "mn_mousedown") != 0) goto end; + if (htrAddEventHandlerFunction(s, "document", "MOUSEMOVE", "mn", "mn_mousemove") != 0) goto end; + if (htrAddEventHandlerFunction(s, "document", "MOUSEOUT", "mn", "mn_mouseout") != 0) goto end; + if (htrAddEventHandlerFunction(s, "document", "MOUSEOVER", "mn", "mn_mouseover") != 0) goto end; + + /** Write script initialization. **/ + if (htrAddScriptInit_va(s, "\t{ " + "const layer = wgtrGetNodeRef(ns, '%STR&SYM'); " + "mn_init({ " + "layer, " + "clayer:wgtrGetContainer(layer), " + "hlayer:htr_subel(layer, 'mn%POShigh'), " + "name:'%STR&SYM', " + "bgnd:'%STR&JSSTR', " + "high:'%STR&JSSTR', " + "actv:'%STR&JSSTR', " + "txt:'%STR&JSSTR', " + "w:%INT, " + "h:%INT, " + "horiz:%INT, " + "pop:%INT, " + "}); }\n", + name, id, name, + bgstr, highlight, active, textcolor, + w, h, + is_horizontal, is_popup + ) != 0) + { + mssError(0, "HTMENU", "Failed to write JS init code."); + goto end; + } /** Beginning of code for menu **/ - htrAddBodyItem_va(s,"
", id); - if (s->Capabilities.Dom0NS) - htrAddBodyItem_va(s,"",bgstr); - htrAddBodyItem_va(s,"
\n", id, s->Capabilities.Dom0NS?"":"width=\"100%\" height=\"100%\""); - - /** Only draw border if it is NS4 **/ - if (s->Capabilities.Dom0NS) + if (htrAddBodyItem_va(s, + "
" + "
" + "
\n" + ""); - if (w != -1) - htrAddBodyItem_va(s,"", w-2); - else - htrAddBodyItem(s,""); - htrAddBodyItem(s,"\n"); - if (h != -1) - htrAddBodyItem_va(s,"\n", h-2); - else - htrAddBodyItem(s,"\n"); - htrAddBodyItem(s,""); - if (w != -1) - htrAddBodyItem_va(s,"", w-2); - else - htrAddBodyItem(s,""); - htrAddBodyItem(s,"\n"); - } - else - htrAddBodyItem(s,""); + htrAddBodyItem(s,"
", + id, id + ) != 0) { - htrAddBodyItem(s,"
", h-2); - else - htrAddBodyItem(s,"
"); + mssError(0, "HTMENU", "Failed to write HTML for menu."); + goto end; } - else - htrAddBodyItem(s,"
"); - /** Add 'meat' of menu... **/ - xs = (pXString)nmMalloc(sizeof(XString)); - xsInit(xs); + /** Add menu items and dropdowns. **/ + xs = check_ptr(xsNew()); + if (xs == NULL) goto end; mcnt=0; htrAddBodyItem(s,"\n"); } - htrAddBodyItem(s,"
\n"); htrAddBodyItem_va(s,"%[%]\n", is_horizontal); @@ -447,56 +539,28 @@ htmenuRender(pHtSession s, pWgtrNode menu, int z) mcnt++; htrAddBodyItem(s,"
\n"); - - /** closing border for NS4 **/ - if (s->Capabilities.Dom0NS) - { - if (h != -1) - htrAddBodyItem_va(s,"
\n"); + htrAddBodyItem_va(s,"
\n", id); - /** Ending of layer **/ - if (s->Capabilities.Dom0NS) - htrAddBodyItem_va(s,"
", id); - else - htrAddBodyItem_va(s,"
\n", id); + /** Render children. **/ + if (htrRenderSubwidgets(s, menu, z + 1) != 0) goto end; - xsDeInit(xs); - nmFree(xs, sizeof(XString)); + /** Success. **/ + rval = 0; - /* Read and initialize the menu items */ - /*cnt = xaCount(&(menu->Children)); - for (i=0;iChildren), i); - wgtrGetPropertyValue(sub_tree,"outer_type",DATA_T_STRING,POD(&ptr)); - if (!strcmp(ptr,"widget/menuitem")) - { - htrRenderSubwidgets(s, sub_tree, z+1); - } - else if (!strcmp(ptr,"widget/menusep")) - { - sub_tree->RenderFlags |= HT_WGTF_NOOBJECT; - } - else - { - htrRenderWidget(s, sub_tree, z+1); - } - }*/ - htrRenderSubwidgets(s, menu, z+1); - - return 0; + mssError(0, "HTMENU", + "Failed to render \"%s\":\"%s\" (id: %d).", + menu->Name, menu->Type, id + ); + } + + /** Clean up. **/ + if (xs != NULL) xsFree(xs); + + return rval; } int @@ -596,5 +660,3 @@ htmenuInitialize() return 0; } - - diff --git a/centrallix/htmlgen/htdrv_multiscroll.c b/centrallix/htmlgen/htdrv_multiscroll.c index 2db7a3d66..c9f831dfd 100644 --- a/centrallix/htmlgen/htdrv_multiscroll.c +++ b/centrallix/htmlgen/htdrv_multiscroll.c @@ -14,7 +14,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2007 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -51,6 +51,8 @@ static struct } HTMS; +#define HTMS_MAX_PARTS 32 + /*** htmsRender - generate the HTML code for the page. ***/ @@ -60,24 +62,20 @@ htmsRender(pHtSession s, pWgtrNode tree, int z) char* ptr; char name[64]; char main_bg[128]; - int x=-1,y=-1,w,h,ch,total_h,top_h,total_h_accum,cy; - int id; - int i, cnt; - int box_offset; int always_visible; - pWgtrNode childlist[32]; - pWgtrNode child; - if(!s->Capabilities.Dom0NS && !(s->Capabilities.Dom1HTML && s->Capabilities.CSS1)) + /** Get an id for this. **/ + const int id = (HTMS.idcnt++); + + /** Verify browser capabilities. **/ + if (!s->Capabilities.Dom1HTML || !s->Capabilities.Dom2CSS) { - mssError(1,"HTMS","Netscape DOM or W3C DOM1 HTML and CSS support required"); - return -1; + mssError(1, "HTMS", "Unsupported browser: W3C DOM1 HTML and DOM2 CSS support required."); + goto err; } - /** Get an id for this. **/ - id = (HTMS.idcnt++); - /** Get x,y,w,h of this object **/ + int x = -1, y = -1, w, h; if (wgtrGetPropertyValue(tree,"x",DATA_T_INTEGER,POD(&x)) != 0) x=0; if (wgtrGetPropertyValue(tree,"y",DATA_T_INTEGER,POD(&y)) != 0) y=0; if (wgtrGetPropertyValue(tree,"width",DATA_T_INTEGER,POD(&w)) != 0) @@ -94,97 +92,195 @@ htmsRender(pHtSession s, pWgtrNode tree, int z) /** Background color/image? **/ htrGetBackground(tree,NULL,!s->Capabilities.Dom0NS,main_bg,sizeof(main_bg)); - /** figure out box offset fudge factor... stupid box model... **/ - if (s->Capabilities.CSSBox) - box_offset = 1; - else - box_offset = 0; - /** Get name **/ if (wgtrGetPropertyValue(tree,"name",DATA_T_STRING,POD(&ptr)) != 0) return -1; strtcpy(name,ptr,sizeof(name)); /** Ok, write the style header items. **/ - if(s->Capabilities.Dom0NS) - { - htrAddStylesheetItem_va(s,"\t#ms%POSmain { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; Z-INDEX:%POS; }\n",id,x,y,w,h,z); - } - else if(s->Capabilities.CSS1) - { - htrAddStylesheetItem_va(s,"\t#ms%POSmain { POSITION:absolute; VISIBILITY:inherit; overflow:hidden; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; Z-INDEX:%POS; }\n",id,x,y,w,h,z); - htrAddStylesheetItem_va(s,"\t#ms%POSmain { %STR}\n",id,main_bg); - } - else + if (htrAddStylesheetItem_va(s, + "\t\t#ms%POSmain { " + "position:absolute; " + "visibility:inherit; " + "overflow:hidden; " + "left:%INTpx; " + "top:%INTpx; " + "width:%POSpx; " + "height:%POSpx; " + "z-index:%POS; " + "%STR " + "}\n", + id, x, y, w, h, z, + main_bg + ) != 0) { - mssError(1,"HTMS","Cannot render - environment unsupported"); + mssError(0, "HTMS", "Failed to write main MultiScroll CSS."); + goto err; } - /** DOM linkages **/ - htrAddWgtrObjLinkage_va(s, tree, "ms%POSmain",id); + /** Link the widget to the DOM node. **/ + if (htrAddWgtrObjLinkage_va(s, tree, "ms%POSmain", id)) goto err; - /** Script include call **/ - htrAddScriptInclude(s, "/sys/js/htdrv_multiscroll.js", 0); - htrAddScriptInclude(s, "/sys/js/ht_utils_layers.js", 0); + /** Include scripts. **/ + if (htrAddScriptInclude(s, "/sys/js/htdrv_multiscroll.js", 0) != 0) goto err; + if (htrAddScriptInclude(s, "/sys/js/ht_utils_layers.js", 0) != 0) goto err; - /** Event Handlers **/ - htrAddEventHandlerFunction(s, "document","MOUSEUP", "ms", "ms_mouseup"); - htrAddEventHandlerFunction(s, "document","MOUSEDOWN", "ms", "ms_mousedown"); - htrAddEventHandlerFunction(s, "document","MOUSEOVER", "ms", "ms_mouseover"); - htrAddEventHandlerFunction(s, "document","MOUSEOUT", "ms", "ms_mouseout"); - htrAddEventHandlerFunction(s, "document","MOUSEMOVE", "ms", "ms_mousemove"); + /** Add event handlers. **/ + if (htrAddEventHandlerFunction(s, "document", "MOUSEDOWN", "ms", "ms_mousedown") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEMOVE", "ms", "ms_mousemove") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEOUT", "ms", "ms_mouseout") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEOVER", "ms", "ms_mouseover") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEUP", "ms", "ms_mouseup") != 0) goto err; /** Script init **/ - htrAddScriptInit_va(s, " ms_init(wgtrGetNodeRef(ns,'%STR&SYM'), {});\n", - name); + if (htrAddScriptInit_va(s, + "\tms_init(wgtrGetNodeRef(ns, '%STR&SYM'), {});\n", + name + ) != 0) + { + mssError(0, "HTMS", "Failed to write JS init call."); + goto err; + } /** div for the main body of this widget **/ - htrAddBodyItem_va(s,"
\n",id); + if (htrAddBodyItem_va(s, "
\n", id) != 0) + { + mssError(0, "HTMS", "Failed to write HTML opening tag."); + goto err; + } - /** Check for objects within the widget. **/ - cnt = wgtrGetMatchingChildList(tree, "widget/multiscrollpart", childlist, sizeof(childlist)/sizeof(pWgtrNode)); - top_h = total_h = 0; - for(i=0;i\n",id,i); - always_visible = htrGetBoolean(child, "always_visible", 0); + pWgtrNode child = childlist[i]; + + /** Height computations. **/ + int ch; if (wgtrGetPropertyValue(child,"height",DATA_T_INTEGER,POD(&ch)) != 0 || ch < 0) ch = 0; - if (top_h >= total_h_accum + ch) - cy = total_h_accum; - else - cy = h - total_h + total_h_accum; + const int cy = (top_h >= total_h_accum + ch) ? total_h_accum : h - total_h + total_h_accum; total_h_accum += ch; - htrAddStylesheetItem_va(s,"\t#ms%POSpart%POS { POSITION:absolute; VISIBILITY:inherit; overflow:hidden; LEFT:0px; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; Z-INDEX:%POS; CLIP:rect(0px,%INTpx,%INTpx,0px);}\n",id,i,cy,w,ch,z+1,w,ch); - if (wgtrGetPropertyValue(child,"name",DATA_T_STRING,POD(&ptr)) != 0) return -1; + + /** Property handling. **/ + if (wgtrGetPropertyValue(child, "name", DATA_T_STRING,POD(&ptr)) != 0) + { + mssError(1, "HTMS", "Failed to get part name."); + goto err_part; + } + always_visible = htrGetBoolean(child, "always_visible", 0); if (always_visible && ch == 0) { - mssError(1, "HTMS", "MultiScroll part '%s' of '%s' set to always_visible=true must have a height", ptr, name); - return -1; + mssError(1, "HTMS", "Fail: MultiScroll parts with always_visible=true must have a height."); + goto err_part; + } + + /** Write CSS styles. **/ + if (htrAddStylesheetItem_va(s, + "\t\t#ms%POSpart%POS { " + "position:absolute; " + "visibility:inherit; " + "overflow:hidden; " + "left:0px; " + "top:%INTpx; " + "width:%POSpx; " + "height:%POSpx; " + "z-index:%POS; " + "clip:rect(0px, %INTpx, %INTpx, 0px); " + "}\n", + id, i, cy, w, ch, z + 1, w, ch + ) != 0) + { + mssError(0, "HTMS", "Failed to write CSS."); + goto err_part; + } + + /** Write JS initialization call. **/ + if (htrAddScriptInit_va(s, + "\twgtrGetNodeRef(ns, '%STR&SYM')" + ".addPart(wgtrGetNodeRef('%STR&SYM','%STR&SYM'), {av:%INT, h:%INT, y:%INT});\n", + name, + wgtrGetNamespace(child), ptr, always_visible, ch, cy + ) != 0) + { + mssError(0, "HTMS", "Failed to write JS init call."); + goto err_part; + } + + /** Write linked HTML opening tag. **/ + if (htrAddWgtrObjLinkage_va(s, child, "ms%POSpart%POS", id, i) != 0) goto err_part; + if (htrAddBodyItem_va(s, "
\n", id, i) != 0) + { + mssError(0, "HTMS", "Failed to write HTML closing tag."); + goto err_part; } - htrAddScriptInit_va(s, " wgtrGetNodeRef(ns,'%STR&SYM').addPart(wgtrGetNodeRef('%STR&SYM','%STR&SYM'), {av:%INT, h:%INT, y:%INT});\n", - name, wgtrGetNamespace(child), ptr, always_visible, ch, cy); - htrRenderSubwidgets(s, child, z+2); - htrAddBodyItem(s, "
\n"); + + /** Write subwidgets. **/ + if (htrRenderSubwidgets(s, tree, z + 2) != 0) + { + mssError(0, "HTMS", "Failed to render child widgets of MultiScroll part."); + goto err_part; + } + + /** Write HTML closing tag. **/ + if (htrAddBodyItem(s, "
\n") != 0) + { + mssError(0, "HTMS", "Failed to write HTML closing tag."); + goto err_part; + } + + /** Success, go to next. **/ + continue; + + err_part: /* Error handling. */ + mssError(0, "HTMS", + "Failed to render MultiScroll part #%u, aka. \"%s\":\"%s\".", + i + 1u, child->Name, child->Type + ); + goto err; } - htrRenderSubwidgets(s, tree, z+1); + + /** Render children. **/ + if (htrRenderSubwidgets(s, tree, z + 1) != 0) goto err; /** End the containing layer. **/ - htrAddBodyItem(s, "
\n"); + if (htrAddBodyItem(s, "
\n") != 0) + { + mssError(0, "HTMS", "Failed to write HTML closing tag."); + goto err; + } - return 0; + /** Success. **/ + return 0; + + err: + mssError(0, "HTMS", + "Failed to render \"%s\":\"%s\" (id: %d).", + tree->Name, tree->Type, id + ); + return -1; } -/*** htmsRender_part - renderer for the multiscroll part subwidget. +/*** htmsRender_part - renderer for the MultiScroll part subwidget. ***/ int htmsRender_part(pHtSession s, pWgtrNode tree, int z) @@ -206,7 +302,7 @@ htmsInitialize() /** Fill in the structure. **/ strcpy(drv->Name,"DHTML MultiScroll Driver"); - strcpy(drv->WidgetName,"multiscroll"); + strcpy(drv->WidgetName,"MultiScroll"); drv->Render = htmsRender; htrAddEvent(drv,"Click"); diff --git a/centrallix/htmlgen/htdrv_objcanvas.c b/centrallix/htmlgen/htdrv_objcanvas.c index 6ff6078e8..3eec4db56 100644 --- a/centrallix/htmlgen/htdrv_objcanvas.c +++ b/centrallix/htmlgen/htdrv_objcanvas.c @@ -14,7 +14,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 2004 LightSys Technology Services, Inc. */ +/* Copyright (C) 2004-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -59,30 +59,30 @@ htocRender(pHtSession s, pWgtrNode oc_node, int z) char main_bg[128]; char osrc[64]; int x=-1,y=-1,w,h; - int id; int allow_select, show_select; - if(!s->Capabilities.Dom0NS && !(s->Capabilities.Dom1HTML && s->Capabilities.CSS1)) + /** Get an id for this. **/ + const int id = (HTOC.idcnt++); + + /** Verify browser capabilities. **/ + if (!s->Capabilities.Dom1HTML || !s->Capabilities.Dom2CSS) { - mssError(1,"HTOC","Netscape DOM or W3C DOM1 HTML and CSS support required"); - return -1; + mssError(1, "HTOC", "Unsupported browser: W3C DOM1 HTML and DOM2 CSS support required."); + goto err; } - /** Get an id for this. **/ - id = (HTOC.idcnt++); - /** Get x,y,w,h of this object **/ if (wgtrGetPropertyValue(oc_node,"x",DATA_T_INTEGER,POD(&x)) != 0) x=0; if (wgtrGetPropertyValue(oc_node,"y",DATA_T_INTEGER,POD(&y)) != 0) y=0; if (wgtrGetPropertyValue(oc_node,"width",DATA_T_INTEGER,POD(&w)) != 0) { mssError(1,"HTOC","Pane widget must have a 'width' property"); - return -1; + goto err; } if (wgtrGetPropertyValue(oc_node,"height",DATA_T_INTEGER,POD(&h)) != 0) { mssError(1,"HTOC","Pane widget must have a 'height' property"); - return -1; + goto err; } /** Background color/image? **/ @@ -96,50 +96,99 @@ htocRender(pHtSession s, pWgtrNode oc_node, int z) /** allow selection of objects? **/ allow_select = htrGetBoolean(oc_node, "allow_selection", 0); + if (allow_select < 0) goto err; /** show current selection? **/ show_select = htrGetBoolean(oc_node, "show_selection", 0); + if (show_select < 0) goto err; /** Get name **/ - if (wgtrGetPropertyValue(oc_node,"name",DATA_T_STRING,POD(&ptr)) != 0) return -1; + if (wgtrGetPropertyValue(oc_node,"name",DATA_T_STRING,POD(&ptr)) != 0) goto err; strtcpy(name,ptr,sizeof(name)); /** Add css item for the layer **/ - if (s->Capabilities.CSS2) - htrAddStylesheetItem_va(s,"\t#oc%POSbase { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; Z-INDEX:%POS; overflow: hidden; %STR}\n",id,x,y,w,h,z,main_bg); - else - htrAddStylesheetItem_va(s,"\t#oc%POSbase { POSITION:absolute; VISIBILITY:inherit; LEFT:%INT; TOP:%INT; WIDTH:%POS; HEIGHT:%POS; Z-INDEX:%POS; }\n",id,x,y,w,h,z); + if (htrAddStylesheetItem_va(s, + "\t\t#oc%POSbase { " + "position:absolute; " + "visibility:inherit; " + "overflow:hidden; " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "width:"ht_flex_format"; " + "height:"ht_flex_format"; " + "z-index:%POS; " + "%STR " + "}\n", + id, + ht_flex_x(x, oc_node), + ht_flex_y(y, oc_node), + ht_flex_w(w, oc_node), + ht_flex_h(h, oc_node), + z, + main_bg + ) != 0) + { + mssError(0, "HTOC", "Failed to write base CSS."); + goto err; + } + + /** Link the widget to the DOM node. **/ + if (htrAddWgtrObjLinkage_va(s, oc_node, "oc%POSbase", id)) goto err; - htrAddWgtrObjLinkage_va(s, oc_node, "oc%POSbase",id); + /** Include scripts. **/ + if (htrAddScriptInclude(s, "/sys/js/htdrv_objcanvas.js", 0) != 0) goto err; + if (htrAddScriptInclude(s, "/sys/js/ht_utils_layers.js", 0) != 0) goto err; - /** Include our necessary supporting js files **/ - htrAddScriptInclude(s, "/sys/js/htdrv_objcanvas.js", 0); - htrAddScriptInclude(s, "/sys/js/ht_utils_layers.js", 0); + /** Add event handlers. **/ + if (htrAddEventHandlerFunction(s, "document", "MOUSEDOWN", "oc", "ms_mousedown") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEMOVE", "oc", "ms_mousemove") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEOUT", "oc", "ms_mouseout") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEOVER", "oc", "ms_mouseover") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEUP", "oc", "ms_mouseup") != 0) goto err; - /** Event Handlers **/ - htrAddEventHandlerFunction(s, "document", "MOUSEUP", "oc", "oc_mouseup"); - htrAddEventHandlerFunction(s, "document", "MOUSEDOWN", "oc", "oc_mousedown"); - htrAddEventHandlerFunction(s, "document", "MOUSEOVER", "oc", "oc_mouseover"); - htrAddEventHandlerFunction(s, "document", "MOUSEMOVE", "oc", "oc_mousemove"); - htrAddEventHandlerFunction(s, "document", "MOUSEOUT", "oc", "oc_mouseout"); - /** Script initialization call. **/ - htrAddScriptInit_va(s, " oc_init({layer:wgtrGetNodeRef(ns,\"%STR&SYM\"), osrc:%[wgtrGetNodeRef(ns,\"%STR&SYM\")%]%[null%], allow_select:%INT, show_select:%INT, name:\"%STR&SYM\"});\n", - name, *osrc, osrc, !*osrc, allow_select, show_select, name); + if (htrAddScriptInit_va(s, + "\toc_init({ " + "layer:wgtrGetNodeRef(ns, '%STR&SYM'), " + "osrc:%[wgtrGetNodeRef(ns, '%STR&SYM')%]%[null%], " + "name:'%STR&SYM', " + "allow_select:%INT, " + "show_select:%INT, " + "});\n", + name, (osrc[0] != '\0'), osrc, (osrc[0] == '\0'), name, + allow_select, show_select + ) != 0) + { + mssError(0, "HTOC", "Failed to write JS init call."); + goto err; + } /** HTML body
element for the base layer. **/ - htrAddBodyItem_va(s,"
\n",id); - if (!s->Capabilities.CSS2) - htrAddBodyItem_va(s,"
 
\n",main_bg,w); + if (htrAddBodyItem_va(s,"
\n", id) != 0) + { + mssError(0, "HTOC", "Failed to write HTML opening tag."); + goto err; + } - /** Check for objects within the pane. **/ - htrRenderSubwidgets(s, oc_node, z+2); + /** Render children. **/ + if (htrRenderSubwidgets(s, oc_node, z + 2) != 0) goto err; /** End the containing layer. **/ - if (!s->Capabilities.CSS2) htrAddBodyItem(s, ""); - htrAddBodyItem(s, "
\n"); + if (htrAddBodyItem(s, "
\n") != 0) + { + mssError(0, "HTOC", "Failed to write HTML closing tag."); + goto err; + } - return 0; + /** Success. **/ + return 0; + + err: + mssError(0, "HTOC", + "Failed to render \"%s\":\"%s\" (id: %d).", + oc_node->Name, oc_node->Type, id + ); + return -1; } diff --git a/centrallix/htmlgen/htdrv_osrc.c b/centrallix/htmlgen/htdrv_osrc.c index 9ba4bbfd9..cb9856138 100644 --- a/centrallix/htmlgen/htdrv_osrc.c +++ b/centrallix/htmlgen/htdrv_osrc.c @@ -11,12 +11,13 @@ #include "cxlib/xhash.h" #include "cxlib/mtsession.h" #include "cxlib/strtcpy.h" +#include "cxlib/util.h" /************************************************************************/ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 2000-2007 LightSys Technology Services, Inc. */ +/* Copyright (C) 2000-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -121,35 +122,34 @@ htosrc_internal_AddRule(pHtSession s, pWgtrNode tree, char* treename, pWgtrNode int htosrcRender(pHtSession s, pWgtrNode tree, int z) { - int id; char name[40]; char *ptr; int readahead; int scrollahead; int replicasize; - char *sql; - char *filter; - char *baseobj; - pWgtrNode sub_tree; -// pObjQuery qy; + char* sql = NULL; + char* filter = NULL; + char* baseobj = NULL; enum htosrc_autoquery_types aq; int receive_updates; int send_updates; int refresh_interval; - int count, i; + int rval = -1; int ind_activity; int use_having; int qy_reveal_only; char key_objname[32]; + char* empty_string = ""; - if(!s->Capabilities.Dom0NS && !s->Capabilities.Dom1HTML) - { - mssError(1,"HTOSRC","Netscape DOM or W3C DOM1 HTML support required"); - return -1; - } + /** Get an id for this. **/ + const int id = (HTOSRC.idcnt++); - /** Get an id for this. **/ - id = (HTOSRC.idcnt++); + /** Verify browser capabilities. **/ + if (!s->Capabilities.Dom1HTML || !s->Capabilities.Dom2CSS) + { + mssError(1, "HTOSRC", "Unsupported browser: W3C DOM1 HTML and DOM2 CSS support required."); + goto end; + } /** Get name **/ if (wgtrGetPropertyValue(tree,"name",DATA_T_STRING,POD(&ptr)) != 0) return -1; @@ -213,85 +213,129 @@ htosrcRender(pHtSession s, pWgtrNode tree, int z) /** Get replication updates from server? **/ receive_updates = htrGetBoolean(tree, "receive_updates", 0); + if (receive_updates < 0) goto end; /** Send updates to the server? **/ send_updates = htrGetBoolean(tree, "send_updates", 1); + if (send_updates < 0) goto end; + /** Get osrc strings. **/ if (wgtrGetPropertyValue(tree,"sql",DATA_T_STRING,POD(&ptr)) == 0) { - sql = nmSysStrdup(ptr); + sql = check_ptr(nmSysStrdup(ptr)); } else { if (wgtrGetPropertyType(tree,"sql") != DATA_T_CODE) { - mssError(1,"HTOSRC","'sql' parameter required for objectsource '%s'", name); + mssError(1, "HTOSRC", "'sql' parameter required for object source: \"%s\"", name); return -1; } else - sql = nmSysStrdup(""); + sql = empty_string; } + baseobj = (wgtrGetPropertyValue(tree, "baseobj", DATA_T_STRING, POD(&ptr)) == 0) ? check_ptr(nmSysStrdup(ptr)) : empty_string; + filter = (wgtrGetPropertyValue(tree, "filter", DATA_T_STRING, POD(&ptr)) == 0) ? check_ptr(nmSysStrdup(ptr)) : empty_string; + if (sql == NULL || baseobj == NULL || filter == NULL) goto end; + + /** Link the widget and container to their DOM nodes. **/ + if (htrAddWgtrObjLinkage_va(s, tree, "osrc%POSloader", id) != 0) goto end; + if (htrAddWgtrCtrLinkage(s, tree, "_parentctr") != 0) goto end; + + /** Write JS globals & includes. **/ + if (htrAddScriptGlobal(s, "osrc_relationships", "[]", 0) != 0) goto end; + if (htrAddScriptGlobal(s, "osrc_syncid", "0", 0) != 0) goto end; + if (htrAddScriptInclude(s, "/sys/js/ht_utils_hints.js", 0) != 0) goto end; + if (htrAddScriptInclude(s, "/sys/js/ht_utils_string.js", 0) != 0) goto end; + if (htrAddScriptInclude(s, "/sys/js/htdrv_osrc.js", 0) != 0) goto end; + + /** Write CSS. **/ + if (htrAddStylesheetItem_va(s, + "\t\t#osrc%POSloader { " + "position:absolute; " + "visibility:hidden; " + "overflow:hidden; " + "left:0px; " + "top:1px; " + "width:1px; " + "height:1px; " + "z-index:0; " + "}\n", + id + ) != 0) + { + mssError(0, "HTOSRC", "Failed to write loader CSS."); + goto end; + } - if (wgtrGetPropertyValue(tree,"baseobj",DATA_T_STRING,POD(&ptr)) == 0) - baseobj = nmSysStrdup(ptr); - else - baseobj = NULL; - - if (wgtrGetPropertyValue(tree,"filter",DATA_T_STRING,POD(&ptr)) == 0) - filter = nmSysStrdup(ptr); - else - filter = nmSysStrdup(""); - - /** create our instance variable **/ - htrAddWgtrObjLinkage_va(s, tree, "osrc%POSloader",id); - htrAddWgtrCtrLinkage(s, tree, "_parentctr"); - - htrAddScriptGlobal(s, "osrc_syncid", "0", 0); - htrAddScriptGlobal(s, "osrc_relationships", "[]", 0); - - /** Ok, write the style header items. **/ - htrAddStylesheetItem_va(s," #osrc%POSloader { overflow:hidden; POSITION:absolute; VISIBILITY:hidden; LEFT:0px; TOP:1px; WIDTH:1px; HEIGHT:1px; Z-INDEX:0; }\n",id); + /** Script initialization call. **/ + if (htrAddScriptInit_va(s, + "\tosrc_init({ " + "loader:wgtrGetNodeRef(ns, '%STR&SYM'), " + "name:'%STR&SYM', " + "readahead:%INT, " + "scrollahead:%INT, " + "replicasize:%INT, " + "sql:'%STR&JSSTR', " + "filter:'%STR&JSSTR', " + "baseobj:'%STR&JSSTR', " + "autoquery:%INT, " + "requestupdates:%INT, " + "ind_act:%INT, " + "use_having:%INT, " + "qy_reveal_only:%INT, " + "send_updates:%INT, " + "key_objname:'%STR&JSSTR', " + "refresh:%INT, " + "});\n", + name, name, readahead, scrollahead, replicasize, sql, filter, baseobj, + aq, receive_updates, ind_activity, use_having, qy_reveal_only, + send_updates, key_objname, refresh_interval + ) != 0) + { + mssError(0, "HTOSRC", "Failed to write JS init call."); + goto end; + } - /** Script initialization call. **/ - htrAddScriptInit_va(s," osrc_init({loader:wgtrGetNodeRef(ns,\"%STR&SYM\"), readahead:%INT, scrollahead:%INT, replicasize:%INT, sql:\"%STR&JSSTR\", filter:\"%STR&JSSTR\", baseobj:\"%STR&JSSTR\", name:\"%STR&SYM\", autoquery:%INT, requestupdates:%INT, ind_act:%INT, use_having:%INT, qy_reveal_only:%INT, send_updates:%INT, key_objname:\"%STR&JSSTR\", refresh:%INT});\n", - name,readahead,scrollahead,replicasize,sql,filter, - baseobj?baseobj:"",name,aq,receive_updates, ind_activity, - use_having, qy_reveal_only, send_updates, key_objname, refresh_interval); - //htrAddScriptCleanup_va(s," %s.layers.osrc%dloader.cleanup();\n", parentname, id); + /** HTML body element for the frame **/ + if (htrAddBodyItemLayerStart(s, HTR_LAYER_F_DYNAMIC, "osrc%POSloader", id, NULL) != 0) + { + mssError(0, "HTOSRC", "Failed to write HTML opening tag for osrc loader."); + goto end; + } + if (htrAddBodyItemLayerEnd(s, HTR_LAYER_F_DYNAMIC) != 0) + { + mssError(0, "HTOSRC", "Failed to write HTML closing tag for osrc loader."); + goto end; + } + if (htrAddBodyItem(s, "\n") != 0) + { + mssError(0, "HTOSRC", "Failed to write HTML newline."); + goto end; + } - htrAddScriptInclude(s, "/sys/js/htdrv_osrc.js", 0); - htrAddScriptInclude(s, "/sys/js/ht_utils_string.js", 0); - htrAddScriptInclude(s, "/sys/js/ht_utils_hints.js", 0); + /** Render children. **/ + if (htrRenderSubwidgets(s, tree, z) != 0) goto end; - /** HTML body element for the frame **/ - htrAddBodyItemLayerStart(s,HTR_LAYER_F_DYNAMIC,"osrc%POSloader",id, NULL); - htrAddBodyItemLayerEnd(s,HTR_LAYER_F_DYNAMIC); - htrAddBodyItem(s, "\n"); + /** Success. **/ + rval = 0; - count = xaCount(&(tree->Children)); - for (i=0;iChildren), i); -#if 00 - if (wgtrGetPropertyValue(sub_tree, "outer_type", DATA_T_STRING, POD(&ptr)) == 0 && !strcmp(ptr, "widget/osrc-rule")) - { - htosrc_internal_AddRule(s, tree, name, sub_tree); - } - else - { -#endif - htrRenderWidget(s, sub_tree, z); -#if 00 - } -#endif + mssError(0, "HTOSRC", + "Failed to render \"%s\":\"%s\" (id: %d).", + tree->Name, tree->Type, id + ); } - nmSysFree(filter); - if (baseobj) nmSysFree(baseobj); - nmSysFree(sql); + /** Clean up. **/ + if (filter != NULL && filter != empty_string) nmSysFree(filter); + if (baseobj != NULL && baseobj != empty_string) nmSysFree(baseobj); + if (sql != NULL && sql != empty_string) nmSysFree(sql); - return 0; -} + return rval; + } /* diff --git a/centrallix/htmlgen/htdrv_page.c b/centrallix/htmlgen/htdrv_page.c index e79f7b47e..15580806f 100755 --- a/centrallix/htmlgen/htdrv_page.c +++ b/centrallix/htmlgen/htdrv_page.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -5,6 +6,7 @@ #include #include "ht_render.h" #include "obj.h" +#include "cxlib/util.h" #include "cxlib/mtask.h" #include "cxlib/xarray.h" #include "cxlib/xhash.h" @@ -19,7 +21,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2004 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -55,7 +57,7 @@ htpageRender(pHtSession s, pWgtrNode tree, int z) int attract = 0; int watchdogtimer; char bgstr[128]; - int show, i, count; + int show, i; char kbfocus1[64]; /* kb focus = 3d raised */ char kbfocus2[64]; char msfocus1[64]; /* ms focus = black rectangle */ @@ -68,20 +70,21 @@ htpageRender(pHtSession s, pWgtrNode tree, int z) int dpi_scaling = 0; int w,h; char* path; - pStruct c_param; time_t t; struct tm* timeptr; char timestr[80]; - XArray endorsements; - XArray contexts; + XArray endorsements = { nAlloc: 0 }; + XArray contexts = { nAlloc: 0 }; int max_requests = 1; - if(!((s->Capabilities.Dom0NS || s->Capabilities.Dom0IE || (s->Capabilities.Dom1HTML && s->Capabilities.Dom2Events)) && s->Capabilities.CSS1) ) + /** Verify browser capabilities. **/ + if (!s->Capabilities.Dom1HTML || !s->Capabilities.Dom2CSS) { - mssError(1,"HTPAGE","CSS Level 1 Support and (Netscape DOM support or (W3C Level 1 DOM support and W3C Level 2 Events support required))"); - return -1; + mssError(1, "HTPAGE", "Unsupported browser: W3C DOM1 HTML and DOM2 CSS support required."); + goto err; } + /** Set default focus colors. **/ strcpy(msfocus1,"#ffffff"); /* ms focus = 3d raised */ strcpy(msfocus2,"#7a7a7a"); strcpy(kbfocus1,"#000000"); /* kb focus = black rectangle */ @@ -100,20 +103,29 @@ htpageRender(pHtSession s, pWgtrNode tree, int z) /** Max active server requests at one time **/ wgtrGetPropertyValue(tree,"max_requests",DATA_T_INTEGER,POD(&max_requests)); - /** Page icon? **/ + /** Handle page icon. **/ if (wgtrGetPropertyValue(tree, "icon", DATA_T_STRING, POD(&ptr)) == 0) { - htrAddHeaderItem_va(s, " \n",ptr); + if (htrAddHeaderItem_va(s, "\t\n",ptr) != 0) + { + mssError(0, "HTPAGE", "Failed to write HTML for page icon."); + goto err; + } } - /** Check for a title. **/ - if (wgtrGetPropertyValue(tree,"title",DATA_T_STRING,POD(&ptr)) == 0) + /** Handle page title. **/ + if (wgtrGetPropertyValue(tree, "title", DATA_T_STRING, POD(&ptr)) == 0) { - htrAddHeaderItem_va(s, " %STR&HTE\n",ptr); + if (htrAddHeaderItem_va(s, "\t%STR&HTE\n", ptr) != 0) + { + mssError(0, "HTPAGE", "Failed to write HTML for page title."); + goto err; + } } /** Check for page load status **/ show = htrGetBoolean(tree, "loadstatus", 0); + if (show < 0) goto err; /** Initialize the html-related interface stuff **/ if (ifcHtmlInit(s, tree) < 0) @@ -122,25 +134,50 @@ htpageRender(pHtSession s, pWgtrNode tree, int z) } /** Auto-call startup and cleanup **/ - htrAddBodyParam_va(s, " onLoad=\"startup_%STR&SYM();\" onUnload=\"cleanup();\"", - s->Namespace->DName); + if (htrAddBodyParam_va(s, " onLoad='startup_%STR&SYM();'", s->Namespace->DName) != 0) + { + mssError(0, "HTPAGE", "Failed to register startup function."); + goto err; + } + if (htrAddBodyParam(s, " onUnload='cleanup();'") != 0) + { + mssError(0, "HTPAGE", "Failed to register cleanup function."); + goto err; + } + if (htrAddScriptCleanup(s, "\tpg_cleanup();\n") != 0) + { + mssError(0, "HTPAGE", "Failed to write page cleanup JS."); + goto err; + } /** Check for bgcolor. **/ - if (htrGetBackground(tree, NULL, 0, bgstr, sizeof(bgstr)) == 0) + if (htrGetBackground(tree, NULL, 1, bgstr, sizeof(bgstr)) == 0) { - htrAddBodyParam_va(s, " %STR",bgstr); + if (htrAddStylesheetItem_va(s, "\t\tbody { %STR }", bgstr) != 0) + { + mssError(0, "HTPAGE", "Failed to write page background color."); + goto err; + } } /** Check for text color **/ if (wgtrGetPropertyValue(tree,"textcolor",DATA_T_STRING,POD(&ptr)) == 0) { - htrAddBodyParam_va(s, " TEXT=\"%STR&HTE\"",ptr); + if (htrAddBodyParam_va(s, " text=\"%STR&HTE\"", ptr) != 0) + { + mssError(0, "HTPAGE", "Failed to write page text color."); + goto err; + } } /** Check for link color **/ if (wgtrGetPropertyValue(tree,"linkcolor",DATA_T_STRING,POD(&ptr)) == 0) { - htrAddBodyParam_va(s, " LINK=\"%STR&HTE\"",ptr); + if (htrAddStylesheetItem_va(s, "\t\ta:link { color:%STR&HTE; }\n", ptr) != 0) + { + mssError(0, "HTPAGE", "Failed to write page link color."); + goto err; + } } /** Keyboard Focus Indicator colors 1 and 2 **/ @@ -177,95 +214,96 @@ htpageRender(pHtSession s, pWgtrNode tree, int z) if (wgtrGetPropertyValue(tree,"attract",DATA_T_INTEGER,POD(&ptr)) == 0) attract = (intptr_t)ptr; + /** Get booleans. **/ show_diag = htrGetBoolean(tree, "show_diagnostics", 0); dpi_scaling = htrGetBoolean(tree, "dpi_scaling", 0); + /** Get font data. **/ wgtrGetPropertyValue(tree, "font_size", DATA_T_INTEGER, POD(&font_size)); if (font_size < 5 || font_size > 100) font_size = 12; - if (wgtrGetPropertyValue(tree, "font_name", DATA_T_STRING, POD(&ptr)) == 0 && ptr) strtcpy(font_name, ptr, sizeof(font_name)); else strcpy(font_name, ""); /** Add global for page metadata **/ - htrAddScriptGlobal(s, "page", "{}", 0); + if (htrAddScriptGlobal(s, "page", "{}", 0) != 0) goto err; /** Add a list of highlightable areas **/ /** These are javascript global variables**/ - htrAddScriptGlobal(s, "pg_arealist", "[]", 0); - htrAddScriptGlobal(s, "pg_keylist", "[]", 0); - htrAddScriptGlobal(s, "pg_curarea", "null", 0); - htrAddScriptGlobal(s, "pg_curlayer", "null", 0); - htrAddScriptGlobal(s, "pg_curkbdlayer", "null", 0); - htrAddScriptGlobal(s, "pg_curkbdarea", "null", 0); - htrAddScriptGlobal(s, "pg_lastkey", "-1", 0); - htrAddScriptGlobal(s, "pg_lastmodifiers", "null", 0); - htrAddScriptGlobal(s, "pg_keytimeoutid", "null", 0); - htrAddScriptGlobal(s, "pg_keyschedid", "0", 0); - htrAddScriptGlobal(s, "pg_modallayer", "null", 0); - htrAddScriptGlobal(s, "pg_key_ie_shifted", "false", 0); - htrAddScriptGlobal(s, "pg_attract", "null", 0); - htrAddScriptGlobal(s, "pg_gshade", "null", 0); - htrAddScriptGlobal(s, "pg_closetype", "null", 0); - htrAddScriptGlobal(s, "pg_explist", "[]", 0); - htrAddScriptGlobal(s, "pg_schedtimeout", "null", 0); - htrAddScriptGlobal(s, "pg_schedtimeoutlist", "[]", 0); - htrAddScriptGlobal(s, "pg_schedtimeoutid", "0", 0); - htrAddScriptGlobal(s, "pg_schedtimeoutstamp", "0", 0); - htrAddScriptGlobal(s, "pg_insame", "false", 0); - htrAddScriptGlobal(s, "cn_browser", "null", 0); - htrAddScriptGlobal(s, "ibeam_current", "null", 0); - htrAddScriptGlobal(s, "util_cur_mainlayer", "null", 0); - htrAddScriptGlobal(s, "pg_loadqueue", "[]", 0); - htrAddScriptGlobal(s, "pg_loadqueue_busy", "999999", 0); - htrAddScriptGlobal(s, "pg_debug_log", "null", 0); - htrAddScriptGlobal(s, "pg_isloaded", "false", 0); - htrAddScriptGlobal(s, "pg_username", "null", 0); - htrAddScriptGlobal(s, "pg_msg_handlers", "[]", 0); - htrAddScriptGlobal(s, "pg_msg_layer", "null", 0); - htrAddScriptGlobal(s, "pg_msg_timeout", "null", 0); - htrAddScriptGlobal(s, "pg_diag", show_diag?"true":"false", 0); /* causes pop-up boxes for certain non-fatel warnings */ - htrAddScriptGlobal(s, "pg_dpi_scaling", (dpi_scaling) ? "true" : "false", 0); /* Whether to scale launched windows based on display dots-per-inch */ - htrAddScriptGlobal(s, "pg_width", "0", 0); - htrAddScriptGlobal(s, "pg_height", "0", 0); - htrAddScriptGlobal(s, "pg_charw", "0", 0); - htrAddScriptGlobal(s, "pg_charh", "0", 0); - htrAddScriptGlobal(s, "pg_parah", "0", 0); - htrAddScriptGlobal(s, "pg_namespaces", "{}", 0); - htrAddScriptGlobal(s, "pg_handlertimeout", "null", 0); /* this is used by htr_mousemovehandler */ - htrAddScriptGlobal(s, "pg_mousemoveevents", "[]", 0); - htrAddScriptGlobal(s, "pg_handlers", "[]", 0); /* keeps track of handlers for basic events tied to document (click, mousemove, keypress, etc) */ - htrAddScriptGlobal(s, "pg_capturedevents", "0", 0); /* is the binary OR of all event flags that the document currently has registered */ - htrAddScriptGlobal(s, "pg_tiplayer", "null", 0); - htrAddScriptGlobal(s, "pg_tipindex", "0", 0); - htrAddScriptGlobal(s, "pg_tiptmout", "null", 0); - htrAddScriptGlobal(s, "pg_waitlyr", "null", 0); - htrAddScriptGlobal(s, "pg_appglobals", "[]", 0); - htrAddScriptGlobal(s, "pg_sessglobals", "[]", 0); - htrAddScriptGlobal(s, "pg_scripts", "[]", 0); - htrAddScriptGlobal(s, "pg_endorsements", "[]", 0); - htrAddScriptGlobal(s, "pg_max_requests", "1", 0); - - /** Add script include to get function declarations **/ - if(s->Capabilities.JS15 && s->Capabilities.Dom1HTML) - { - /*htrAddScriptInclude(s, "/sys/js/htdrv_page_js15.js", 0);*/ - } - htrAddScriptInclude(s, "/sys/js/htdrv_page.js", 0); - htrAddScriptInclude(s, "/sys/js/htdrv_connector.js", 0); - htrAddScriptInclude(s, "/sys/js/ht_utils_string.js", 0); - htrAddScriptInclude(s, "/sys/js/jquery/jquery-1.11.1.js", 0); + if (htrAddScriptGlobal(s, "pg_arealist", "[]", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "pg_keylist", "[]", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "pg_curarea", "null", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "pg_curlayer", "null", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "pg_curkbdlayer", "null", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "pg_curkbdarea", "null", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "pg_lastkey", "-1", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "pg_lastmodifiers", "null", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "pg_keytimeoutid", "null", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "pg_keyschedid", "0", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "pg_modallayer", "null", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "pg_key_ie_shifted", "false", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "pg_attract", "null", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "pg_gshade", "null", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "pg_closetype", "null", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "pg_explist", "[]", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "pg_schedtimeout", "null", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "pg_schedtimeoutlist", "[]", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "pg_schedtimeoutid", "0", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "pg_schedtimeoutstamp", "0", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "pg_insame", "false", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "cn_browser", "null", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "ibeam_current", "null", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "util_cur_mainlayer", "null", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "pg_loadqueue", "[]", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "pg_loadqueue_busy", "999999", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "pg_debug_log", "null", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "pg_isloaded", "false", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "pg_username", "null", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "pg_msg_handlers", "[]", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "pg_msg_layer", "null", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "pg_msg_timeout", "null", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "pg_diag", (show_diag) ? "true" : "false", 0) != 0) goto err; /* Use pop-ups for certain non-fatal warnings */ + if (htrAddScriptGlobal(s, "pg_dpi_scaling", (dpi_scaling) ? "true" : "false", 0) != 0) goto err; /* Scale launched windows using display dots-per-inch */ + if (htrAddScriptGlobal(s, "pg_width", "0", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "pg_height", "0", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "pg_charw", "0", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "pg_charh", "0", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "pg_parah", "0", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "pg_namespaces", "{}", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "pg_handlertimeout", "null", 0) != 0) goto err; /* Used by htr_mousemovehandler() in ht_render.js. */ + if (htrAddScriptGlobal(s, "pg_mousemoveevents", "[]", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "pg_handlers", "[]", 0) != 0) goto err; /* List of handlers for basic document events (click, mousemove, keypress, etc.). */ + if (htrAddScriptGlobal(s, "pg_capturedevents", "0", 0) != 0) goto err; /* Binary OR of all event flags currently registered on the document. */ + if (htrAddScriptGlobal(s, "pg_tiplayer", "null", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "pg_tipindex", "0", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "pg_tiptmout", "null", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "pg_waitlyr", "null", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "pg_appglobals", "[]", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "pg_sessglobals", "[]", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "pg_scripts", "[]", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "pg_endorsements", "[]", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "pg_max_requests", "1", 0) != 0) goto err; + + /** Include necessary scripts. **/ + if (htrAddScriptInclude(s, "/sys/js/htdrv_page.js", 0) != 0) goto err; + if (htrAddScriptInclude(s, "/sys/js/htdrv_connector.js", 0) != 0) goto err; + if (htrAddScriptInclude(s, "/sys/js/ht_utils_string.js", 0) != 0) goto err; + if (htrAddScriptInclude(s, "/sys/js/jquery/jquery-1.11.1.js", 0) != 0) goto err; /** Write named global **/ - if (wgtrGetPropertyValue(tree,"name",DATA_T_STRING,POD(&ptr)) != 0) return -1; + if (wgtrGetPropertyValue(tree, "name", DATA_T_STRING, POD(&ptr)) != 0) goto err; strtcpy(name,ptr,sizeof(name)); - htrAddWgtrObjLinkage(s, tree, "window"); - htrAddWgtrCtrLinkage(s, tree, "document"); + /** Link the window and document to widgets. **/ + if (htrAddWgtrObjLinkage(s, tree, "window") != 0) goto err; + if (htrAddWgtrCtrLinkage(s, tree, "document") != 0) goto err; /** Set the application key **/ - htrAddScriptInit_va(s, " if (typeof window.akey == 'undefined') window.akey = '%STR&JSSTR';\n", s->ClientInfo->AKey); + /*** TODO: Greg - This should be a call to htrAddScriptGlobal(), but I + *** do not know how to do that with a the dynamic, formatted value. + ***/ + htrAddScriptInit_va(s, "\tif (typeof window.akey == 'undefined') window.akey = '%STR&JSSTR';\n", s->ClientInfo->AKey); /** Send server's time to the client. **/ t = time(NULL); @@ -275,183 +313,314 @@ htpageRender(pHtSession s, pWgtrNode tree, int z) /** This isn't 100% ideal -- it causes a couple seconds of clock drift **/ if (strftime(timestr, sizeof(timestr), "%Y %m %d %T %Z", timeptr) > 0) { - htrAddScriptInit_va(s, " pg_servertime = new Date(%STR&DQUOT);\n", timestr); + if (htrAddScriptInit_va(s, "\tpg_servertime = new Date(%STR&DQUOT);\n", timestr)) + { + mssError(0, "HTPAGE", "Failed to write server time in JS."); + goto err; + } if (strftime(timestr, sizeof(timestr), "%Y %m %d %T", timeptr) > 0) - htrAddScriptInit_va(s, " pg_servertime_notz = new Date(%STR&DQUOT);\n", timestr); - htrAddScriptInit_va(s, " pg_clienttime = new Date();\n"); + { + if (htrAddScriptInit_va(s, "\tpg_servertime_notz = new Date(%STR&DQUOT);\n", timestr) != 0) + { + mssError(0, "HTPAGE", "Failed to write server time notz in JS."); + goto err; + } + } + if (htrAddScriptInit(s, "\tpg_clienttime = new Date();\n") != 0) + { + mssError(0, "HTPAGE", "Failed to write client time in JS."); + goto err; + } } } - /** Page init **/ - htrAddScriptInit(s, " if(typeof(pg_status_init)=='function')pg_status_init();\n"); - htrAddScriptInit_va(s, " pg_init(wgtrGetNodeRef(ns,'%STR&SYM'),%INT);\n", name, attract); - htrAddScriptInit_va(s, " pg_username = '%STR&JSSTR';\n", mssUserName()); - htrAddScriptInit_va(s, " pg_width = %INT;\n", w); - htrAddScriptInit_va(s, " pg_height = %INT;\n", h); - htrAddScriptInit_va(s, " pg_max_requests = %INT;\n", max_requests); - htrAddScriptInit_va(s, " pg_charw = %INT;\n", s->ClientInfo->CharWidth); - htrAddScriptInit_va(s, " pg_charh = %INT;\n", s->ClientInfo->CharHeight); - htrAddScriptInit_va(s, " pg_parah = %INT;\n", s->ClientInfo->ParagraphHeight); - - c_param = stLookup_ne(s->Params, "cx__obscure"); - if (c_param && !strcasecmp(c_param->StrVal,"yes")) - htrAddScriptInit(s, " obscure_data = true;\n"); - else - htrAddScriptInit(s, " obscure_data = false;\n"); + /** Write JS to initialize page variables. **/ + htrAddScriptInit_va(s, + "\tif (typeof(pg_status_init) === 'function') pg_status_init();\n" + "\tpg_init(wgtrGetNodeRef(ns,'%STR&SYM'),%INT);\n" + "\tpg_username = '%STR&JSSTR';\n" + "\tpg_width = %INT;\n" + "\tpg_height = %INT;\n" + "\tpg_max_requests = %INT;\n" + "\tpg_charw = %INT;\n" + "\tpg_charh = %INT;\n" + "\tpg_parah = %INT;\n", + name, attract, + mssUserName(), + w, h, + max_requests, + s->ClientInfo->CharWidth, + s->ClientInfo->CharHeight, + s->ClientInfo->ParagraphHeight + ); + + /** Write obscore_data. **/ + pStruct c_param = stLookup_ne(s->Params, "cx__obscure"); + if (htrAddScriptInit_va(s, + "\tobscure_data = %STR;\n", + (c_param != NULL && strcasecmp(c_param->StrVal, "yes") == 0) ? "true" : "false" + ) != 0) + { + mssError(0, "HTPAGE", "Failed to write obscure_data variable in JS."); + goto err; + } /** Add template paths **/ for(i=0;iCapabilities.HTML40) - { - /** Add focus box **/ - htrAddStylesheetItem(s,"\ttd img { display: block; }\n"); - htrAddStylesheetItem(s,"\t#pgtop { POSITION:absolute; VISIBILITY:hidden; LEFT:-1000px;TOP:0px;WIDTH:1152px;HEIGHT:1px; clip:rect(0px,0px,0px,0px); Z-INDEX:1000; overflow:hidden;}\n"); - htrAddStylesheetItem(s,"\t#pgbtm { POSITION:absolute; VISIBILITY:hidden; LEFT:-1000px;TOP:0px;WIDTH:1152px;HEIGHT:1px; clip:rect(0px,0px,0px,0px); Z-INDEX:1000; overflow:hidden;}\n"); - htrAddStylesheetItem(s,"\t#pgrgt { POSITION:absolute; VISIBILITY:hidden; LEFT:0px;TOP:-1000px;WIDTH:1px;HEIGHT:864px; clip:rect(0px,0px,0px,0px); Z-INDEX:1000; overflow:hidden;}\n"); - htrAddStylesheetItem(s,"\t#pglft { POSITION:absolute; VISIBILITY:hidden; LEFT:0px;TOP:-1000px;WIDTH:1px;HEIGHT:864px; clip:rect(0px,0px,0px,0px); Z-INDEX:1000; overflow:hidden;}\n"); - htrAddStylesheetItem(s,"\t#pgtvl { POSITION:absolute; VISIBILITY:hidden; LEFT:0px;TOP:0px;WIDTH:1px;HEIGHT:1px; Z-INDEX:0; }\n"); - htrAddStylesheetItem(s,"\t#pgktop { POSITION:absolute; VISIBILITY:hidden; LEFT:-1000px;TOP:0px;WIDTH:1152px;HEIGHT:1px; clip:rect(0px,0px,0px,0px); Z-INDEX:1000; overflow:hidden;}\n"); - htrAddStylesheetItem(s,"\t#pgkbtm { POSITION:absolute; VISIBILITY:hidden; LEFT:-1000px;TOP:0px;WIDTH:1152px;HEIGHT:1px; clip:rect(0px,0px,0px,0px); Z-INDEX:1000; overflow:hidden;}\n"); - htrAddStylesheetItem(s,"\t#pgkrgt { POSITION:absolute; VISIBILITY:hidden; LEFT:0px;TOP:-1000px;WIDTH:1px;HEIGHT:864px; clip:rect(0px,0px,0px,0px); Z-INDEX:1000; overflow:hidden;}\n"); - htrAddStylesheetItem(s,"\t#pgklft { POSITION:absolute; VISIBILITY:hidden; LEFT:0px;TOP:-1000px;WIDTH:1px;HEIGHT:864px; clip:rect(0px,0px,0px,0px); Z-INDEX:1000; overflow:hidden;}\n"); - htrAddStylesheetItem(s,"\t#pginpt { POSITION:absolute; VISIBILITY:hidden; LEFT:0px; TOP:20px; Z-INDEX:20; }\n"); - htrAddStylesheetItem(s,"\t#pgping { POSITION:absolute; VISIBILITY:hidden; LEFT:0px; TOP:0px; WIDTH:0px; HEIGHT:0px; Z-INDEX:0;}\n"); - htrAddStylesheetItem(s,"\t#pgmsg { POSITION:absolute; VISIBILITY:hidden; LEFT:0px; TOP:0px; WIDTH:0px; HEIGHT:0px; Z-INDEX:0;}\n"); + bool error = false; + if (check(xaInit(&endorsements, 16) != 0)) goto err; + if (check(xaInit(&contexts, 16) != 0)) goto err; + if (check_neg(cxssGetEndorsementList(&endorsements, &contexts)) < 0) goto err; + for (unsigned int i = 0; i < endorsements.nItems; i++) + { + char* endorsement = endorsements.Items[i]; + char* context = contexts.Items[i]; + + /** Write endorsement. **/ + if (htrAddScriptInit_va(s, + "\tpg_endorsements.push({ e:'%STR&JSSTR', ctx:'%STR&JSSTR' });\n", + endorsement, context + ) != 0) + { + mssError(0, "HTPAGE", + "Failed to write endorsement #%d: \"%s\" of \"%s\" (continuing...)", + i + 1, endorsement, context + ); + error = true; + } + + /** Clean up. **/ + nmSysFree(endorsement); + nmSysFree(context); } - else + if (check(xaDeInit(&endorsements)) != 0) goto err; + endorsements.nAlloc = 0; + if (check(xaDeInit(&contexts)) != 0) goto err; + contexts.nAlloc = 0; + if (error) { - /** Add focus box **/ - htrAddStylesheetItem(s, - "\t#pgtop { POSITION:absolute; VISIBILITY:hidden; LEFT:0;TOP:0;WIDTH:1152;HEIGHT:1; clip:rect(1,1); Z-INDEX:1000;}\n" - "\t#pgbtm { POSITION:absolute; VISIBILITY:hidden; LEFT:0;TOP:0;WIDTH:1152;HEIGHT:1; clip:rect(1,1); Z-INDEX:1000;}\n" - "\t#pgrgt { POSITION:absolute; VISIBILITY:hidden; LEFT:0;TOP:0;WIDTH:1;HEIGHT:864; clip:rect(1,1); Z-INDEX:1000;}\n" - "\t#pglft { POSITION:absolute; VISIBILITY:hidden; LEFT:0;TOP:0;WIDTH:1;HEIGHT:864; clip:rect(1,1); Z-INDEX:1000;}\n" - "\t#pgtvl { POSITION:absolute; VISIBILITY:hidden; LEFT:0;TOP:0;WIDTH:1;HEIGHT:1; Z-INDEX:0; }\n" - "\t#pgktop { POSITION:absolute; VISIBILITY:hidden; LEFT:0;TOP:0;WIDTH:1152;HEIGHT:1; clip:rect(1,1); Z-INDEX:1000;}\n" - "\t#pgkbtm { POSITION:absolute; VISIBILITY:hidden; LEFT:0;TOP:0;WIDTH:1152;HEIGHT:1; clip:rect(1,1); Z-INDEX:1000;}\n" - "\t#pgkrgt { POSITION:absolute; VISIBILITY:hidden; LEFT:0;TOP:0;WIDTH:1;HEIGHT:864; clip:rect(1,1); Z-INDEX:1000;}\n" - "\t#pgklft { POSITION:absolute; VISIBILITY:hidden; LEFT:0;TOP:0;WIDTH:1;HEIGHT:864; clip:rect(1,1); Z-INDEX:1000;}\n" - "\t#pginpt { POSITION:absolute; VISIBILITY:hidden; LEFT:0; TOP:20; Z-INDEX:20; }\n" - "\t#pgping { POSITION:absolute; VISIBILITY:hidden; LEFT:0; TOP:0; Z-INDEX:0; }\n" - "\t#pgmsg { POSITION:absolute; VISIBILITY:hidden; LEFT:0; TOP:0; Z-INDEX:0; }\n"); + mssError(0, "HTPAGE", "Failed to write one or more endorsement(s) (failing)."); + goto err; } + /** Add focus box. **/ + if (htrAddStylesheetItem(s, + "\t\ttd img { display: block; }\n" + "\t\t.pg { position:absolute; visibility:hidden; }\n" + "\t\t.pgclip { overflow:hidden; z-index:1000; }\n" + "\t\t#pgtop { left:-1000px; top:0px; width:1152px; height:1px; }\n" + "\t\t#pgbtm { left:-1000px; top:0px; width:1152px; height:1px; }\n" + "\t\t#pgrgt { left:0px; top:-1000px; width:1px; height:864px; }\n" + "\t\t#pglft { left:0px; top:-1000px; width:1px; height:864px; }\n" + "\t\t#pgktop { left:-1000px; top:0px; width:1152px; height:1px; }\n" + "\t\t#pgkbtm { left:-1000px; top:0px; width:1152px; height:1px; }\n" + "\t\t#pgkrgt { left:0px; top:-1000px; width:1px; height:864px; }\n" + "\t\t#pgklft { left:0px; top:-1000px; width:1px; height:864px; }\n" + "\t\t#pgtvl { left:0px; top:0px; width:1px; height:1px; z-index:0; }\n" + "\t\t#pginpt { left:0px; top:20px; z-index:20; }\n" + "\t\t#pgping { left:0px; top:0px; width:0px; height:0px; z-index:0; }\n" + "\t\t#pgmsg { left:0px; top:0px; width:0px; height:0px; z-index:0; }\n" + ) != 0) + { + mssError(0, "HTPAGE", "Failed to write focus box CSS."); + goto err; + } + + /** Write pgstat. **/ if (show == 1) { - htrAddStylesheetItem(s, "\t#pgstat { POSITION:absolute; VISIBILITY:visible; LEFT:0;TOP:0;WIDTH:100%;HEIGHT:99%; Z-INDEX:100000;}\n"); + if (htrAddStylesheetItem(s, + "\t\t#pgstat { " + "position:absolute; " + "visibility:visible; " + "left:0; " + "top:0; " + "width:100%; " + "height:99%; " + "z-index:100000; " + "}\n" + ) != 0) + { + mssError(0, "HTPAGE", "Failed to write CSS for pgstat."); + goto err; + + } + + /** Write the pgstat DOM nodes. **/ htrAddBodyItemLayerStart(s,0,"pgstat",0, NULL); - htrAddBodyItem_va(s, "", bgstr); - htrAddBodyItem (s, "
\n"); + htrAddBodyItem_va(s, "", bgstr); + htrAddBodyItem (s, + "" + "" + "
loading...
\n" + ); htrAddBodyItemLayerEnd(s,0); } - htrAddStylesheetItem_va(s, "\thtml { overflow:hidden; }\n"); - htrAddStylesheetItem_va(s, "\tbody { overflow:hidden; %[font-size:%POSpx; %]%[font-family:%STR&CSSVAL; %]}\n", - font_size > 0, font_size, *font_name, font_name); - htrAddStylesheetItem(s, "\tpre { font-size:90%; }\n"); - - if (s->Capabilities.Dom0NS) + if (htrAddStylesheetItem(s, + "\t\thtml { " + "overflow:hidden; " + "}\n" + ) != 0) + { + mssError(0, "HTPAGE", "Failed to write CSS for main tag."); + goto err; + } + if (htrAddStylesheetItem_va(s, + "\t\tbody { " + "overflow:hidden; " + "%[font-size:%POSpx; %]" + "%[font-family:%STR&CSSVAL; %]" + "}\n", + (font_size > 0), font_size, + (*font_name), font_name + ) != 0) { - htrAddStylesheetItem_va(s, "\ttd { %[font-size:%POSpx; %]%[font-family:%STR&CSSVAL; %]}\n", - font_size > 0, font_size, *font_name, font_name); - htrAddStylesheetItem_va(s, "\tfont { %[font-size:%POSpx; %]%[font-family:%STR&CSSVAL; %]}\n", - font_size > 0, font_size, *font_name, font_name); + mssError(0, "HTPAGE", "Failed to write CSS for main tag."); + goto err; + } + if (htrAddStylesheetItem(s, + "\t\tpre {" + "font-size:90%; " + "}\n" + ) != 0) + { + mssError(0, "HTPAGE", "Failed to write CSS for all
 tags.");
+	    goto err;
 	    }
 
-	htrAddBodyItem(s, "
\n"); - htrAddBodyItem(s, "
\n"); - htrAddBodyItem(s, "
\n"); - htrAddBodyItem(s, "
\n"); - htrAddBodyItem(s, "
\n"); - htrAddBodyItem(s, "
\n"); - htrAddBodyItem(s, "
\n"); - htrAddBodyItem(s, "
\n"); - htrAddBodyItem(s, "
\n"); + /** Write trans divs. **/ + bool trans_success = false; + #define HT_IMG_TAGS "src='/sys/images/trans_1.gif' alt=''" + if (htrAddBodyItem(s, "
\n") != 0) goto err_trans; + if (htrAddBodyItem(s, "
\n") != 0) goto err_trans; + if (htrAddBodyItem(s, "
\n") != 0) goto err_trans; + if (htrAddBodyItem(s, "
\n") != 0) goto err_trans; + if (htrAddBodyItem(s, "
\n") != 0) goto err_trans; + if (htrAddBodyItem(s, "
\n") != 0) goto err_trans; + if (htrAddBodyItem(s, "
\n") != 0) goto err_trans; + if (htrAddBodyItem(s, "
\n") != 0) goto err_trans; + if (htrAddBodyItem(s, "
\n") != 0) goto err_trans; + trans_success = true; + #undef HT_IMG_TAGS + + err_trans: + /** Handle errors. **/ + if (!trans_success) + { + mssError(0, "HTPAGE", "Failed to write page trans div."); + goto err; + } - htrAddBodyItemLayerStart(s,HTR_LAYER_F_DYNAMIC,"pgping",0, NULL); - htrAddBodyItemLayerEnd(s,HTR_LAYER_F_DYNAMIC); - htrAddBodyItemLayerStart(s,HTR_LAYER_F_DYNAMIC,"pgmsg",0, NULL); - htrAddBodyItemLayerEnd(s,HTR_LAYER_F_DYNAMIC); - htrAddBodyItem(s, "\n"); + /** Write ping HTML. **/ + if (htrAddBodyItemLayerStart(s, HTR_LAYER_F_DYNAMIC, "pgping", 0, NULL) != 0 + || htrAddBodyItemLayerEnd(s, HTR_LAYER_F_DYNAMIC) != 0 + || htrAddBodyItem(s, "\n") != 0) + { + mssError(0, "HTPAGE", "Failed to write HTML for page ping DOM node."); + goto err; + } + + /** Write message HTML. **/ + if (htrAddBodyItemLayerStart(s,HTR_LAYER_F_DYNAMIC,"pgmsg",0, NULL) != 0 + || htrAddBodyItemLayerEnd(s,HTR_LAYER_F_DYNAMIC) != 0 + || htrAddBodyItem(s, "\n") != 0) + { + mssError(0, "HTPAGE", "Failed to write HTML for page message DOM node."); + goto err; + } + /** Write JS call to initialize the pinging system. **/ stAttrValue(stLookup(stLookup(CxGlobals.ParsedConfig, "net_http"),"session_watchdog_timer"),&watchdogtimer,NULL,0); - htrAddScriptInit_va(s," pg_ping_init(htr_subel(wgtrGetNodeRef(ns,\"%STR&SYM\"),\"pgping\"),%INT);\n",name,watchdogtimer/2*1000); - - /** Add event code to handle mouse in/out of the area.... **/ - htrAddEventHandlerFunction(s, "document", "MOUSEMOVE", "pg", "pg_mousemove"); - htrAddEventHandlerFunction(s, "document", "MOUSEOUT", "pg", "pg_mouseout"); - htrAddEventHandlerFunction(s, "document", "MOUSEOVER", "pg", "pg_mouseover"); - htrAddEventHandlerFunction(s, "document", "MOUSEDOWN", "pg", "pg_mousedown"); - htrAddEventHandlerFunction(s, "document", "MOUSEUP", "pg", "pg_mouseup"); - htrAddEventHandlerFunction(s, "document", "SCROLL", "pg", "pg_scroll"); - if (s->Capabilities.Dom1HTML) - htrAddEventHandlerFunction(s, "document", "CONTEXTMENU", "pg", "pg_contextmenu"); - - /** W3C DOM Level 2 Event model doesn't require a textbox to get keystrokes **/ - if(s->Capabilities.Dom0NS) + if (htrAddScriptInit_va(s, + "\tpg_ping_init(htr_subel(wgtrGetNodeRef(ns, '%STR&SYM'), 'pgping'), %INT);\n", + name, watchdogtimer * 500 + ) != 0) { - htrAddBodyItem(s, "
\n"); - htrAddEventHandlerFunction(s, "document", "MOUSEUP", "pg2", "pg_mouseup_ns4"); + mssError(0, "HTPAGE", "Failed to write JS ping init call."); + goto err; } - /** Set colors for the focus layers **/ - htrAddScriptInit_va(s, " page.kbcolor1 = '%STR&JSSTR';\n page.kbcolor2 = '%STR&JSSTR';\n",kbfocus1,kbfocus2); - htrAddScriptInit_va(s, " page.mscolor1 = '%STR&JSSTR';\n page.mscolor2 = '%STR&JSSTR';\n",msfocus1,msfocus2); - htrAddScriptInit_va(s, " page.dtcolor1 = '%STR&JSSTR';\n page.dtcolor2 = '%STR&JSSTR';\n",dtfocus1,dtfocus2); - /*htrAddScriptInit(s, " document.LSParent = null;\n");*/ - - htrAddScriptInit(s, " pg_togglecursor();\n"); + /** Write event handling functions. **/ + if (htrAddEventHandlerFunction(s, "document", "CONTEXTMENU", "pg", "pg_contextmenu") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "KEYDOWN", "pg", "pg_keydown") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "KEYPRESS", "pg", "pg_keypress") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "KEYUP", "pg", "pg_keyup") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEDOWN", "pg", "pg_mousedown") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEMOVE", "pg", "pg_mousemove") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEOUT", "pg", "pg_mouseout") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEOVER", "pg", "pg_mouseover") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEUP", "pg", "pg_mouseup") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "SCROLL", "pg", "pg_scroll") != 0) goto err; - htrAddEventHandlerFunction(s, "document", "KEYDOWN", "pg", "pg_keydown"); - htrAddEventHandlerFunction(s, "document", "KEYUP", "pg", "pg_keyup"); - htrAddEventHandlerFunction(s, "document", "KEYPRESS", "pg", "pg_keypress"); - - /** create the root node of the wgtr **/ - count = xaCount(&(tree->Children)); - for (i=0;iChildren),i), z+1); + mssError(0, "HTPAGE", "Failed to write JS to set focus box colors."); + goto err; } - /** keyboard input for NS4 **/ - if(s->Capabilities.Dom0NS) + /** Write code to ensure cursor updating starts. **/ + if (htrAddScriptInit(s, "\tpg_togglecursor();\n") != 0) { - htrAddScriptInit(s, - " setTimeout(pg_mvpginpt, 1, document.layers.pginpt);\n" - " document.layers.pginpt.moveTo(window.innerWidth-2, 20);\n" - " document.layers.pginpt.visibility = 'inherit';\n"); - htrAddScriptInit(s," document.layers.pginpt.document.tmpform.x.focus();\n"); + mssError(0, "HTPAGE", "Failed to write JS call to start cursor updating."); + goto err; } - htrAddScriptInit(s, " if(typeof(pg_status_close)=='function')pg_status_close();\n"); - htrAddScriptInit(s, " pg_loadqueue_busy = false;\n"); - htrAddScriptInit(s, " pg_serialized_load_doone();\n"); + /** Render children. **/ + if (htrRenderSubwidgets(s, tree, z) != 0) goto err; + + /** Write JS to signal that the page is done loading. **/ + if (htrAddScriptInit(s, + "\tif(typeof(pg_status_close) === 'function') pg_status_close();\n" + "\tpg_loadqueue_busy = false;\n" + "\tpg_serialized_load_doone();\n" + ) != 0) + { + mssError(0, "HTPAGE", "Failed to write JS to signal that loading is done."); + goto err; + } + /** Success. **/ return 0; + + err: + mssError(0, "HTPAGE", + "Failed to render \"%s\":\"%s\".", + tree->Name, tree->Type + ); + return -1; } /*** htpageInitialize - register with the ht_render module. diff --git a/centrallix/htmlgen/htdrv_pane.c b/centrallix/htmlgen/htdrv_pane.c index 0683cc6e6..7dd415891 100644 --- a/centrallix/htmlgen/htdrv_pane.c +++ b/centrallix/htmlgen/htdrv_pane.c @@ -14,7 +14,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -59,7 +59,6 @@ htpnRender(pHtSession s, pWgtrNode tree, int z) char main_bg[128]; char bdr[64]; int x=-1,y=-1,w,h; - int id; int style = 1; /* 0 = lowered, 1 = raised, 2 = none, 3 = bordered */ char* c1; char* c2; @@ -68,15 +67,16 @@ htpnRender(pHtSession s, pWgtrNode tree, int z) int shadow_offset, shadow_radius; char shadow_color[128]; - if(!s->Capabilities.Dom0NS && !(s->Capabilities.Dom1HTML && s->Capabilities.CSS1)) + /** Get an id for this. **/ + const int id = (HTPN.idcnt++); + + /** Verify browser capabilities. **/ + if (!s->Capabilities.Dom1HTML || !s->Capabilities.Dom2CSS) { - mssError(1,"HTPN","Netscape DOM or W3C DOM1 HTML and CSS support required"); - return -1; + mssError(1, "HTPN", "Unsupported browser: W3C DOM1 HTML and DOM2 CSS support required."); + goto err; } - /** Get an id for this. **/ - id = (HTPN.idcnt++); - /** Get x,y,w,h of this object **/ if (wgtrGetPropertyValue(tree,"x",DATA_T_INTEGER,POD(&x)) != 0) x=0; if (wgtrGetPropertyValue(tree,"y",DATA_T_INTEGER,POD(&y)) != 0) y=0; @@ -123,9 +123,9 @@ htpnRender(pHtSession s, pWgtrNode tree, int z) if (wgtrGetPropertyValue(tree,"name",DATA_T_STRING,POD(&ptr)) != 0) return -1; strtcpy(name,ptr,sizeof(name)); - htrCheckAddExpression(s, tree, name, "enabled"); - htrCheckAddExpression(s, tree, name, "background"); - htrCheckAddExpression(s, tree, name, "bgcolor"); + if (htrCheckAddExpression(s, tree, name, "enabled") < 0) goto err; + if (htrCheckAddExpression(s, tree, name, "background") < 0) goto err; + if (htrCheckAddExpression(s, tree, name, "bgcolor") < 0) goto err; /** Style of pane - raised/lowered **/ if (wgtrGetPropertyValue(tree,"style",DATA_T_STRING,POD(&ptr)) == 0) @@ -135,6 +135,8 @@ htpnRender(pHtSession s, pWgtrNode tree, int z) if (!strcmp(ptr,"flat")) style = 2; if (!strcmp(ptr,"bordered")) style = 3; } + + /** Computes styling colors. **/ if (style == 1) /* raised */ { c1 = "white"; @@ -153,56 +155,147 @@ htpnRender(pHtSession s, pWgtrNode tree, int z) strtcpy(bdr,ptr,sizeof(bdr)); } - /** Ok, write the style header items. **/ - if (style == 2) /* flat */ - { - htrAddStylesheetItem_va(s,"\t#pn%POSmain { POSITION:absolute; VISIBILITY:inherit; overflow:hidden; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; Z-INDEX:%POS; }\n",id,x,y,w,h,z); - htrAddStylesheetItem_va(s,"\t#pn%POSmain { border-radius: %INTpx; %STR}\n",id,border_radius,main_bg); - } + /** Write the CSS for borders on the pane dom node. **/ + int offset = 0; + if (style == 2) { /* flat, the default style, nothing to do */ } else if (style == 0 || style == 1) /* lowered or raised */ { - htrAddStylesheetItem_va(s,"\t#pn%POSmain { POSITION:absolute; VISIBILITY:inherit; overflow: hidden; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; Z-INDEX:%POS; }\n",id,x,y,w-2*box_offset,h-2*box_offset,z); - htrAddStylesheetItem_va(s,"\t#pn%POSmain { border-style: solid; border-width: 1px; border-color: %STR %STR %STR %STR; border-radius: %INTpx; %STR}\n",id,c1,c2,c2,c1,border_radius,main_bg); + offset = -2 * box_offset; + if (htrAddStylesheetItem_va(s, + "\t\t#pn%POSmain {" + "border-style: solid; " + "border-width: 1px; " + "border-color: %STR %STR %STR %STR; " + "}\n", + id, + c1, c2, c2, c1 + ) != 0) + { + mssError(0, "HTPN", + "Failed to write %s pane CSS.", + (style == 0) ? "lowered" : "raised" + ); + goto err; + } } else if (style == 3) /* bordered */ { - htrAddStylesheetItem_va(s,"\t#pn%POSmain { POSITION:absolute; VISIBILITY:inherit; overflow: hidden; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; Z-INDEX:%POS; }\n",id,x,y,w-2*box_offset,h-2*box_offset,z); - htrAddStylesheetItem_va(s,"\t#pn%POSmain { border-style: solid; border-width: 1px; border-color:%STR&CSSVAL; border-radius: %INTpx; %STR}\n",id,bdr,border_radius,main_bg); + offset = -2 * box_offset; + if (htrAddStylesheetItem_va(s, + "\t\t#pn%POSmain {" + "border-style: solid;" + "border-width: 1px; " + "border-color:%STR&CSSVAL; " + "}\n", + id, + bdr + ) != 0) + { + mssError(0, "HTPN", "Failed to write bordered pane CSS."); + goto err; + } + } + + /** Apply the offset to the width and height. **/ + w += offset; + h += offset; + + /** Write the main CSS for the pane DOM node. **/ + if (htrAddStylesheetItem_va(s, + "\t\t#pn%POSmain {" + "position:absolute; " + "visibility:inherit; " + "overflow:hidden; " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "width:"ht_flex_format"; " + "height:"ht_flex_format"; " + "z-index:%POS; " + "border-radius:%INTpx; " + "%STR " + "}\n", + id, + ht_flex_x(x, tree), + ht_flex_y(y, tree), + ht_flex_w(w, tree), + ht_flex_h(h, tree), + z, + border_radius, + main_bg + ) != 0) + { + mssError(0, "HTPN", "Failed to write main CSS."); + goto err; } + if (shadow_radius > 0) { - htrAddStylesheetItem_va(s,"\t#pn%POSmain { box-shadow: %POSpx %POSpx %POSpx %STR&CSSVAL; }\n", id, shadow_offset, shadow_offset, shadow_radius, shadow_color); + if (htrAddStylesheetItem_va(s, + "\t\t#pn%POSmain { " + "box-shadow: %POSpx %POSpx %POSpx %STR&CSSVAL; " + "}\n", + id, + shadow_offset, shadow_offset, shadow_radius, shadow_color + ) != 0) + { + mssError(0, "HTPN", "Failed to write shadow CSS."); + goto err; + } } /** DOM linkages **/ - htrAddWgtrObjLinkage_va(s, tree, "pn%POSmain",id); + if (htrAddWgtrObjLinkage_va(s, tree, "pn%POSmain", id) != 0) goto err; /** Script include call **/ - htrAddScriptInclude(s, "/sys/js/htdrv_pane.js", 0); - htrAddScriptInclude(s, "/sys/js/ht_utils_layers.js", 0); + if (htrAddScriptInclude(s, "/sys/js/ht_utils_layers.js", 0) != 0) goto err; + if (htrAddScriptInclude(s, "/sys/js/htdrv_pane.js", 0) != 0) goto err; /** Event Handlers **/ - htrAddEventHandlerFunction(s, "document","MOUSEUP", "pn", "pn_mouseup"); - htrAddEventHandlerFunction(s, "document","MOUSEDOWN", "pn", "pn_mousedown"); - htrAddEventHandlerFunction(s, "document","MOUSEOVER", "pn", "pn_mouseover"); - htrAddEventHandlerFunction(s, "document","MOUSEOUT", "pn", "pn_mouseout"); - htrAddEventHandlerFunction(s, "document","MOUSEMOVE", "pn", "pn_mousemove"); + if (htrAddEventHandlerFunction(s, "document", "MOUSEDOWN", "pn", "pn_mousedown") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEMOVE", "pn", "pn_mousemove") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEOUT", "pn", "pn_mouseout") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEOVER", "pn", "pn_mouseover") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEUP", "pn", "pn_mouseup") != 0) goto err; /** Script initialization call. **/ - htrAddScriptInit_va(s, " pn_init({mainlayer:wgtrGetNodeRef(ns,\"%STR&SYM\"), layer:wgtrGetNodeRef(ns,\"%STR&SYM\")});\n", - name, name); + if (htrAddScriptInit_va(s, + "\tpn_init({" + "mainlayer:wgtrGetNodeRef(ns, '%STR&SYM'), " + "layer:wgtrGetNodeRef(ns, '%STR&SYM'), " + "});\n", + name, name + ) != 0) + { + mssError(0, "HTPN", "Failed to write JS init call."); + goto err; + } /** HTML body
element for the base layer. **/ - //htrAddBodyItem_va(s,"
\n",id, w-2, h-2); - htrAddBodyItem_va(s,"
\n",id, w-2, h-2); + if (htrAddBodyItem_va(s,"
\n", id) != 0) + { + mssError(0, "HTPN", "Failed to write HTML opening tag."); + goto err; + } - /** Check for objects within the pane. **/ - htrRenderSubwidgets(s, tree, z+2); + /** Render children. **/ + if (htrRenderSubwidgets(s, tree, z + 1) != 0) goto err; - /** End the containing layer. **/ - htrAddBodyItem(s, "
\n"); + /** Write the end of the container. **/ + if (htrAddBodyItem(s, "
\n") != 0) + { + mssError(0, "HTPN", "Failed to write HTML closing tag."); + goto err; + } - return 0; + /** Success. **/ + return 0; + + err: + mssError(0, "HTPN", + "Failed to render \"%s\":\"%s\" (id: %d).", + tree->Name, tree->Type, id + ); + return -1; } diff --git a/centrallix/htmlgen/htdrv_parameter.c b/centrallix/htmlgen/htdrv_parameter.c index 2a8213309..71ef58222 100644 --- a/centrallix/htmlgen/htdrv_parameter.c +++ b/centrallix/htmlgen/htdrv_parameter.c @@ -1,9 +1,11 @@ +#include #include #include #include #include #include "ht_render.h" #include "obj.h" +#include "cxlib/util.h" #include "cxlib/mtask.h" #include "cxlib/xarray.h" #include "cxlib/xhash.h" @@ -17,7 +19,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2007 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -45,14 +47,6 @@ /************************************************************************/ -/** globals **/ -static struct - { - int idcnt; - } - HTPARAM; - - /*** htparamRender - generate the HTML code for the page. ***/ int @@ -63,33 +57,31 @@ htparamRender(pHtSession s, pWgtrNode tree, int z) char paramname[64]; char type[32]; char findcontainer[64]; - int id; - int i; - XString xs; - pObjPresentationHints hints; pStruct find_inf; int deploy_to_client; - if(!s->Capabilities.Dom0NS && !s->Capabilities.Dom1HTML ) + /** Verify browser capabilities. **/ + if (!s->Capabilities.Dom1HTML || !s->Capabilities.Dom2CSS) { - mssError(1,"HTPARAM","Netscape DOM or W3C DOM1HTML support required"); - return -1; + mssError(1, "HTPARAM", "Unsupported browser: W3C DOM1 HTML and DOM2 CSS support required."); + goto err; } - /** Get an id for this. **/ - id = (HTPARAM.idcnt++); - /** Get name and type **/ if (wgtrGetPropertyValue(tree,"name",DATA_T_STRING,POD(&ptr)) != 0) - return -1; + { + mssError(1, "HTPARAM", "Failed to get name."); + goto err; + } strtcpy(name,ptr,sizeof(name)); if (wgtrGetPropertyValue(tree,"type",DATA_T_STRING,POD(&ptr)) != 0) ptr = "string"; strtcpy(type,ptr,sizeof(type)); - if (objTypeID(type) < 0 && strcmp(type,"object")) + const int datatype = objTypeID(type); + if (datatype < 0 && strcmp(type, "object") != 0) { - mssError(1,"HTPARAM","Invalid parameter data type for '%s'", name); - return -1; + mssError(1, "HTPARAM", "Invalid datatype %d.", datatype); + goto err; } /** Specify name of param (in case param widget name isn't the desired param name) **/ @@ -108,8 +100,8 @@ htparamRender(pHtSession s, pWgtrNode tree, int z) findcontainer[0] = '\0'; /** JavaScript include file **/ - htrAddScriptInclude(s, "/sys/js/htdrv_parameter.js", 0); - htrAddScriptInclude(s, "/sys/js/ht_utils_hints.js", 0); + if (htrAddScriptInclude(s, "/sys/js/ht_utils_hints.js", 0) != 0) goto err; + if (htrAddScriptInclude(s, "/sys/js/htdrv_parameter.js", 0) != 0) goto err; /** Value supplied? **/ if (deploy_to_client) @@ -118,39 +110,94 @@ htparamRender(pHtSession s, pWgtrNode tree, int z) find_inf = NULL; /** Parameter has presentation-hints components to it. Set those. **/ + const int has_find_inf = (find_inf != NULL && find_inf->StrVal != NULL && find_inf->StrVal[0] != '\0'); if (deploy_to_client) { + bool successful = false; + XString xs = { AllocLen: 0 }; + pObjPresentationHints hints = NULL; + /** Script init **/ - htrAddScriptInit_va(s, " pa_init(wgtrGetNodeRef(ns,\"%STR&SYM\"), {name:'%STR&JSSTR', type:'%STR&JSSTR', findc:'%STR&JSSTR', val:%[null%]%[\"%STR&HEX\"%]});\n", - name, paramname, type, findcontainer, !find_inf || !find_inf->StrVal, find_inf && find_inf->StrVal, find_inf?find_inf->StrVal:""); + if (htrAddScriptInit_va(s, + "\tpa_init(wgtrGetNodeRef(ns, '%STR&SYM'), { " + "name:'%STR&JSSTR', " + "type:'%STR&JSSTR', " + "findc:'%STR&JSSTR', " + "val:%['%STR&HEX'%]%[null%], " + "});\n", + name, paramname, type, findcontainer, + (has_find_inf), (has_find_inf) ? find_inf->StrVal : "null", (!has_find_inf) + ) != 0) + { + mssError(0, "HTPARAM", "Failed to write JS init call."); + goto err_deploy; + } + /** Get presentation hints. **/ hints = wgtrWgtToHints(tree); - if (!hints) + if (hints == NULL) + { + mssError(0, "HTPARAM", "Failed to get presentation hints."); + goto err_deploy; + } + + /** Write presentation hints. **/ + if (check(xsInit(&xs)) != 0) goto err_deploy; + if (hntEncodeHints(hints, &xs) < 0) { - mssError(0, "HTPARAM", "Error in parameter data"); - return -1; + mssError(0, "HTPARAM", "Failed to encode presentation hints."); + goto err_deploy; } - xsInit(&xs); - hntEncodeHints(hints, &xs); - htrAddScriptInit_va(s, " cx_set_hints(wgtrGetNodeRef(ns,\"%STR&SYM\"), '%STR&JSSTR', 'app');\n", - name, xs.String); - xsDeInit(&xs); - objFreeHints(hints); - - htrAddScriptInit_va(s, " wgtrGetNodeRef(ns,\"%STR&SYM\").Verify();\n", name); + if (htrAddScriptInit_va(s, + "\tcx_set_hints(wgtrGetNodeRef(ns, '%STR&SYM'), '%STR&JSSTR', 'app');\n", + name, xs.String + ) != 0) + { + mssError(0, "HTPARAM", "Failed to write JS for presentation hints."); + goto err_deploy; + } + + if (htrAddScriptInit_va(s, "\twgtrGetNodeRef(ns, '%STR&SYM').Verify();\n", name) != 0) + { + mssError(0, "HTPARAM", "Failed to write JS to verify presentation hints."); + goto err_deploy; + } + + /** Success. **/ + successful = true; + + err_deploy: + /** Clean up. **/ + if (check(xsDeInit(&xs)) != 0) goto err_deploy; + if (objFreeHints(hints) != 0) + { + mssError(0, "HTPARAM", "Failed to free presentation hints."); + goto err_deploy; + } + + /** Handle errors. **/ + if (!successful) goto err; } else { tree->RenderFlags |= HT_WGTF_NORENDER; } + /** Nonvisual widget. **/ tree->RenderFlags |= HT_WGTF_NOOBJECT; - /** Check for more sub-widgets within the param **/ - for (i=0;iChildren));i++) - htrRenderWidget(s, xaGetItem(&(tree->Children), i), z+1); - - return 0; + /** Render children. **/ + if (htrRenderSubwidgets(s, tree, z + 1) != 0) goto err; + + /** Success. **/ + return 0; + + err: + mssError(0, "HTPARAM", + "Failed to render \"%s\":\"%s\".", + tree->Name, tree->Type + ); + return -1; } @@ -177,7 +224,5 @@ htparamInitialize() htrAddSupport(drv, "dhtml"); - HTPARAM.idcnt = 0; - return 0; } diff --git a/centrallix/htmlgen/htdrv_radiobutton.c b/centrallix/htmlgen/htdrv_radiobutton.c index c1d395c06..b84adc5c7 100644 --- a/centrallix/htmlgen/htdrv_radiobutton.c +++ b/centrallix/htmlgen/htdrv_radiobutton.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -9,12 +10,13 @@ #include "cxlib/xhash.h" #include "cxlib/mtsession.h" #include "cxlib/strtcpy.h" +#include "cxlib/util.h" /************************************************************************/ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 2000-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 2000-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -43,273 +45,472 @@ /** globals **/ -static struct { - int idcnt; -} HTRB; +static struct + { + int idcnt; + } + HTRB; /** htrbRender - generate the HTML code for the page. **/ -int htrbRender(pHtSession s, pWgtrNode tree, int z) { - char* ptr; - char name[64]; - char title[64]; - char sbuf2[200]; - //char bigbuf[4096]; - char textcolor[32]; - char main_background[128]; - char outline_background[128]; - char form[64]; - pWgtrNode radiobutton_obj, sub_tree; - int x=-1,y=-1,w,h; - int top_offset; - int cover_height, cover_width; - int item_spacing; - int id, i, j; - int is_selected; - int rb_cnt; - int cover_margin; - char fieldname[32]; - char value[64]; - char label[64]; - - if(!s->Capabilities.Dom0NS && !s->Capabilities.Dom1HTML) - { - mssError(1,"HTRB","Netscape 4.x or W3C DOM support required"); - return -1; - } - - /** Get an id for this. **/ - id = (HTRB.idcnt++); - - /** Get x,y,w,h of this object **/ - if (wgtrGetPropertyValue(tree,"x",DATA_T_INTEGER,POD(&x)) != 0) x=0; - if (wgtrGetPropertyValue(tree,"y",DATA_T_INTEGER,POD(&y)) != 0) y=0; - if (wgtrGetPropertyValue(tree,"width",DATA_T_INTEGER,POD(&w)) != 0) { - mssError(1,"HTRB","RadioButtonPanel widget must have a 'width' property"); - return -1; - } - if (wgtrGetPropertyValue(tree,"height",DATA_T_INTEGER,POD(&h)) != 0) { - mssError(1,"HTRB","RadioButtonPanel widget must have a 'height' property"); - return -1; - } - - /** Background color/image? **/ - htrGetBackground(tree,NULL,!s->Capabilities.Dom0NS,main_background,sizeof(main_background)); - - /** Text color? **/ - if (wgtrGetPropertyValue(tree,"textcolor",DATA_T_STRING,POD(&ptr)) == 0) - strtcpy(textcolor,ptr,sizeof(textcolor)); - else - strcpy(textcolor,"black"); - - /** Outline color? **/ - htrGetBackground(tree,"outline",!s->Capabilities.Dom0NS,outline_background,sizeof(outline_background)); - - /** Get name **/ - if (wgtrGetPropertyValue(tree,"name",DATA_T_STRING,POD(&ptr)) != 0) return -1; - strtcpy(name,ptr,sizeof(name)); - - /** Get title **/ - if (wgtrGetPropertyValue(tree,"title",DATA_T_STRING,POD(&ptr)) != 0) return -1; - strtcpy(title,ptr,sizeof(title)); - - /** User requesting expression for selected tab? **/ - htrCheckAddExpression(s, tree, name, "value"); - - /** User requesting expression for selected tab using integer index value? **/ - htrCheckAddExpression(s, tree, name, "value_index"); - - /** Get fieldname **/ - if (wgtrGetPropertyValue(tree,"fieldname",DATA_T_STRING,POD(&ptr)) == 0) - { - strtcpy(fieldname,ptr,sizeof(fieldname)); - } - else - { - fieldname[0]='\0'; - } - - if (wgtrGetPropertyValue(tree,"form",DATA_T_STRING,POD(&ptr)) == 0) - strtcpy(form,ptr,sizeof(form)); - else - form[0]='\0'; - - htrAddScriptInclude(s, "/sys/js/htdrv_radiobutton.js", 0); - htrAddScriptInclude(s, "/sys/js/ht_utils_layers.js", 0); - - /** Ok, write the style header items. **/ - top_offset = s->ClientInfo->ParagraphHeight*3/4+1; - cover_height = h-(top_offset+3+2); - cover_width = w-(2*3 +2); - htrAddStylesheetItem_va(s,"\t#rb%POSparent { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; Z-INDEX:%POS; CLIP:rect(0px,%POSpx,%POSpx,0px); }\n", - id,x,y,w,h,z,w,h); - htrAddStylesheetItem_va(s,"\t#rb%POSborder { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; Z-INDEX:%POS; CLIP:rect(0px,%POSpx,%POSpx,0px); }\n", - id,3,top_offset,w-(2*3),h-(top_offset+3),z+1,w-(2*3),h-(top_offset+3)); - htrAddStylesheetItem_va(s,"\t#rb%POScover { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; Z-INDEX:%POS; CLIP:rect(0px,%POSpx,%POSpx,0px); }\n", - id,1,1,cover_width,cover_height,z+2,cover_width,cover_height); - htrAddStylesheetItem_va(s,"\t#rb%POStitle { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; Z-INDEX:%POS; }\n", - id,10,1,w/2,s->ClientInfo->ParagraphHeight,z+3); - - htrAddScriptGlobal(s, "radiobutton", "null", 0); - - /** DOM linkages **/ - htrAddWgtrObjLinkage_va(s, tree, "rb%POSparent",id); - htrAddWgtrCtrLinkage_va(s, tree, "htr_subel(htr_subel(_obj,\"rb%POSborder\"),\"rb%POScover\")",id,id); - - /** Loop through each radiobutton and flag it NOOBJECT **/ - rb_cnt = 0; - for (j=0;jChildren));j++) +int htrbRender(pHtSession s, pWgtrNode tree, int z) + { + int rval = -1; + char* ptr; + XArray radio_buttons = { nAlloc: 0}; + + /** Get an id for this widget. **/ + const int id = (HTRB.idcnt++); + + /** Verify browser capabilities. **/ + if (!s->Capabilities.Dom1HTML || !s->Capabilities.Dom2CSS) { - radiobutton_obj = xaGetItem(&(tree->Children), j); - radiobutton_obj->RenderFlags |= HT_WGTF_NOOBJECT; - wgtrGetPropertyValue(radiobutton_obj,"outer_type",DATA_T_STRING,POD(&ptr)); - if (!strcmp(ptr,"widget/radiobutton")) + mssError(1, "HTRB", "Unsupported browser: W3C DOM1 HTML and DOM2 CSS support required."); + goto end_free; + } + + /** Get x,y,w,h of this object. **/ + int x, y, w, h, spacing; + if (wgtrGetPropertyValue(tree, "x", DATA_T_INTEGER, POD(&x)) != 0) x = 0; + if (wgtrGetPropertyValue(tree, "y", DATA_T_INTEGER, POD(&y)) != 0) y = 0; + if (wgtrGetPropertyValue(tree, "width", DATA_T_INTEGER, POD(&w)) != 0) + { + mssError(1,"HTRB","RadioButtonPanel widget must have a 'width' property"); + goto end_free; + } + if (wgtrGetPropertyValue(tree, "height", DATA_T_INTEGER, POD(&h)) != 0) + { + mssError(1,"HTRB","RadioButtonPanel widget must have a 'height' property"); + goto end_free; + } + if (wgtrGetPropertyValue(tree, "spacing", DATA_T_INTEGER, POD(&spacing)) != 0) spacing = 10; + + /** Get the name and title attributes. **/ + char name[64] = "", title[64] = ""; + if (wgtrGetPropertyValue(tree, "name", DATA_T_STRING, POD(&ptr)) != 0) + { + mssError(1, "HTRB", "RadioButtonPanel widget must have a 'name' property"); + goto end_free; + } + strtcpy(name, ptr, sizeof(name)); + if (wgtrGetPropertyValue(tree, "title", DATA_T_STRING, POD(&ptr)) != 0) + { + mssError(1, "HTRB", "RadioButtonPanel widget must have a 'title' property"); + goto end_free; + } + strtcpy(title, ptr, sizeof(title)); + + /** Get text color attribute. **/ + char textcolor[32]; + if (wgtrGetPropertyValue(tree, "textcolor", DATA_T_STRING, POD(&ptr)) == 0) + strtcpy(textcolor, ptr, sizeof(textcolor)); + else + strcpy(textcolor, "black"); + + /** Get background attributes. **/ + char main_background[128] = ""; + char outline_background[128] = ""; + if (htrGetBackground(tree, NULL, true, main_background, sizeof(main_background)) != 0) goto end_free; + if (htrGetBackground(tree, "outline", true, outline_background, sizeof(outline_background)) != 0) goto end_free; + + /** User requesting expression for selected tab? **/ + if (htrCheckAddExpression(s, tree, name, "value") < 0) goto end_free; + + /** User requesting expression for selected tab using integer index value? **/ + if (htrCheckAddExpression(s, tree, name, "value_index") < 0) goto end_free; + + /** Get fieldname and form attributes. **/ + char fieldname[32] = "", form[64] = ""; + if (wgtrGetPropertyValue(tree, "fieldname", DATA_T_STRING, POD(&ptr)) == 0) + strtcpy(fieldname, ptr, sizeof(fieldname)); + if (wgtrGetPropertyValue(tree, "form", DATA_T_STRING, POD(&ptr)) == 0) + strtcpy(form, ptr, sizeof(form)); + + + /** Include scripts. **/ + if (htrAddScriptInclude(s, "/sys/js/ht_utils_layers.js", 0) != 0) goto end_free; + if (htrAddScriptInclude(s, "/sys/js/htdrv_radiobutton.js", 0) != 0) goto end_free; + + /** Link DOM node to widget data. **/ + if (htrAddWgtrObjLinkage_va(s, tree, "rb%POSparent", id) != 0) goto end_free; + if (htrAddWgtrCtrLinkage_va(s, tree, "htr_subel(htr_subel(_obj, 'rb%POSborder'), 'rb%POScover')", id, id) != 0) goto end_free; + + /** Script initialization call. **/ + if (strlen(main_background) > 0) + { + if (htrAddScriptInit_va(s, "\t{ " + "const parentPane = wgtrGetNodeRef(ns, '%STR&SYM'); " + "const borderPane = htr_subel(parentPane, 'rb%POSborder'); " + "const coverPane = htr_subel(borderPane, 'rb%POScover'); " + "const titlePane = htr_subel(parentPane, 'rb%POStitle'); " + "const easterEgg2 = 'Easter Egg #2';" + "radiobuttonpanel_init({ " + "parentPane, borderPane, coverPane, titlePane, " + "fieldname:'%STR&JSSTR', " + "mainBackground:'%STR&JSSTR', " + "outlineBackground:'%STR&JSSTR', " + "form:'%STR&JSSTR', " + "}); }\n", + name, id, id, id, + fieldname, + main_background, + outline_background, + form + ) != 0) { - rb_cnt++; + mssError(0, "HTRB", "Failed to write JS init code."); + goto end_free; } } - /* - Now lets loop through and create a style sheet for each optionpane on the - radiobuttonpanel - */ - item_spacing = 12 + s->ClientInfo->ParagraphHeight; - cover_margin = 10; - if (item_spacing*rb_cnt+2*cover_margin > cover_height) - item_spacing = (cover_height-2*cover_margin)/rb_cnt; - if (item_spacing*rb_cnt+2*cover_margin > cover_height) - cover_margin = (cover_height-(item_spacing*rb_cnt))/2; - if (cover_margin < 2) cover_margin = 2; - i = 1; - for (j=0;jChildren));j++) + else { - radiobutton_obj = xaGetItem(&(tree->Children), j); - wgtrGetPropertyValue(radiobutton_obj,"outer_type",DATA_T_STRING,POD(&ptr)); - if (!strcmp(ptr,"widget/radiobutton")) + if (htrAddScriptInit_va(s, + "\tradiobuttonpanel_init({ " + "parentPane:wgtrGetNodeRef(ns, '%STR&SYM'), " + "fieldname:'%STR&JSSTR', " + "borderPane:0, " + "coverPane:0, " + "titlePane:0, " + "mainBackground:0, " + "outlineBackground:0, " + "form:'%STR&JSSTR', " + "});\n", + name, + fieldname, + form + ) != 0) { - htrAddStylesheetItem_va(s,"\t#rb%POSoption%POS { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; Z-INDEX:%POS; CLIP:rect(0px, %POSpx, %POSpx, 0px); }\n", - id,i,7,cover_margin+((i-1)*item_spacing)+3,cover_width-7,item_spacing,z+2,cover_width-7,item_spacing); - i++; + mssError(0, "HTRB", "Failed to write JS init call."); + goto end_free; } } - - /** Script initialization call. **/ - if (strlen(main_background) > 0) { - htrAddScriptInit_va(s, - " var rb = wgtrGetNodeRef(ns, \"%STR&SYM\");\n" - " radiobuttonpanel_init({\n" - " parentPane:rb, fieldname:\"%STR&JSSTR\",\n" - " borderPane:htr_subel(rb,\"rb%POSborder\"),\n" - " coverPane:htr_subel(htr_subel(rb,\"rb%POSborder\"),\"rb%POScover\"),\n" - " titlePane:htr_subel(rb,\"rb%POStitle\"),\n" - " mainBackground:\"%STR&JSSTR\", outlineBackground:\"%STR&JSSTR\", form:\"%STR&JSSTR\"});\n", - name, fieldname, id, id,id, id, main_background, outline_background, form); - } else { - htrAddScriptInit_va(s," radiobuttonpanel_init({parentPane:wgtrGetNodeRef(ns,\"%STR&SYM\"), fieldname:\"%STR&JSSTR\", borderPane:0, coverPane:0, titlePane:0, mainBackground:0, outlineBackground:0, form:\"%STR&JSSTR\"});\n", name, fieldname, form); - } - - htrAddEventHandlerFunction(s, "document", "MOUSEUP", "radiobutton", "radiobutton_mouseup"); - htrAddEventHandlerFunction(s, "document", "MOUSEDOWN", "radiobutton", "radiobutton_mousedown"); - htrAddEventHandlerFunction(s, "document", "MOUSEOVER", "radiobutton", "radiobutton_mouseover"); - htrAddEventHandlerFunction(s, "document", "MOUSEMOVE", "radiobutton", "radiobutton_mousemove"); - - /* - Now lets loop through and add each radiobutton - */ - i = 1; - for (j=0;jChildren));j++) + + /** Add event listenners. **/ + if (htrAddEventHandlerFunction(s, "document", "MOUSEDOWN", "radiobutton", "radiobutton_mousedown") != 0) goto end_free; + if (htrAddEventHandlerFunction(s, "document", "MOUSEMOVE", "radiobutton", "radiobutton_mousemove") != 0) goto end_free; + if (htrAddEventHandlerFunction(s, "document", "MOUSEOVER", "radiobutton", "radiobutton_mouseover") != 0) goto end_free; + if (htrAddEventHandlerFunction(s, "document", "MOUSEUP", "radiobutton", "radiobutton_mouseup") != 0) goto end_free; + + + /** Write style headers for container DOM nodes. **/ + const int para_height = s->ClientInfo->ParagraphHeight; + const int top_offset = (para_height * 3) / 4 + 1; + if (htrAddStylesheetItem_va(s, + "\t\t.rb%POSall { " + "position:absolute; " + "visibility:inherit; " + "overflow:hidden; " + "}\n", + id, id, id, id + ) != 0) + { + mssError(0, "HTRB", "Failed to write shared CSS."); + goto end_free; + } + if (htrAddStylesheetItem_va(s, + "\t\t#rb%POSparent { " + "cursor:default; " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "width:"ht_flex_format"; " + "height:"ht_flex_format"; " + "z-index:%POS; " + "}\n", + id, + ht_flex_x(x, tree), + ht_flex_y(y, tree), + ht_flex_w(w, tree), + ht_flex_h(h, tree), + z + ) != 0) + { + mssError(0, "HTRB", "Failed to write parent (container) CSS."); + goto end_free; + } + if (htrAddStylesheetItem_va(s, + "\t\t#rb%POSborder { " + "left:3px; " + "top:%POSpx; " + "width:calc(100%% - 6px); " + "height:calc(100%% - %POSpx); " + "z-index:%POS; " + "}\n", + id, + top_offset, + top_offset + 3, + z + 1 + ) != 0) { - sub_tree = xaGetItem(&(tree->Children), j); - wgtrGetPropertyValue(sub_tree,"outer_type",DATA_T_STRING,POD(&ptr)); - if (!strcmp(ptr,"widget/radiobutton")) + mssError(0, "HTRB", "Failed to write border CSS."); + goto end_free; + } + if (htrAddStylesheetItem_va(s, + "\t\t#rb%POScover { " + "left:1px; " + "top:1px; " + "width:calc(100%% - 2px); " + "height:calc(100%% - 2px); " + "z-index:%POS; " + "}\n", + id, + z + 2 + ) != 0) + { + mssError(0, "HTRB", "Failed to write cover CSS."); + goto end_free; + } + if (htrAddStylesheetItem_va(s, + "\t\t#rb%POStitle { " + "left:10px; " + "top:1px; " + "width:50%%; " + "height:%POSpx; " + "z-index:%POS; " + "}\n", + id, + para_height, + z + 3 + ) != 0) + { + mssError(0, "HTRB", "Failed to write title CSS."); + goto end_free; + } + + + /** Write HTML to contain the radio buttons. **/ + if (htrAddBodyItem_va(s, + "
" + "
" + "
\n", + id, id, id, id, id, id + ) != 0) + { + mssError(0, "HTRB", "Failed to write HTML for containers."); + goto end_free; + } + + /** Search child array for radio buttons. **/ + if (check(xaInit(&radio_buttons, tree->Children.nItems)) != 0) goto end_free; + for (int i = 0; i < tree->Children.nItems; i++) + { + pWgtrNode child = check_ptr(tree->Children.Items[i]); + if (child == NULL) { - if (wgtrGetPropertyValue(sub_tree,"value",DATA_T_STRING,POD(&ptr)) == 0) - strtcpy(value, ptr, sizeof(value)); - else - value[0] = '\0'; - if (wgtrGetPropertyValue(sub_tree,"label",DATA_T_STRING,POD(&ptr)) == 0) - strtcpy(label, ptr, sizeof(label)); - else - label[0] = '\0'; - is_selected = htrGetBoolean(sub_tree, "selected", 0); - if (is_selected < 0) is_selected = 0; - htrAddWgtrObjLinkage_va(s,sub_tree,"rb%POSoption%POS",id,i); - wgtrGetPropertyValue(sub_tree,"name",DATA_T_STRING,POD(&ptr)); - htrAddScriptInit_va(s, - " var rbitem = wgtrGetNodeRef('%STR&SYM', '%STR&SYM');\n" - " add_radiobutton(rbitem, {selected:%INT, buttonset:htr_subel(rbitem, \"rb%POSbuttonset%POS\"), buttonunset:htr_subel(rbitem, \"rb%POSbuttonunset%POS\"), value:htr_subel(rbitem, \"rb%POSvalue%POS\"), label:htr_subel(rbitem, \"rb%POSlabel%POS\"), valuestr:\"%STR&JSSTR\", labelstr:\"%STR&JSSTR\"});\n", - wgtrGetNamespace(sub_tree), ptr, - is_selected, - id, i, id, i, - id, i, id, i, - value, label); - i++; + mssError(1, "HTRB", "Child widget #%d/%d is NULL.", i + 1, tree->Children.nItems); + goto end_free; } - else + + /** Mark child as no-object. **/ + child->RenderFlags |= HT_WGTF_NOOBJECT; + + /** Add radio buttons to the array, render other widgets immediately (so we can forget about them). **/ + wgtrGetPropertyValue(child, "outer_type", DATA_T_STRING, POD(&ptr)); + if (strcmp(ptr, "widget/radiobutton") == 0) { - htrRenderWidget(s, sub_tree, z+1); + if (check_neg(xaAddItem(&radio_buttons, child)) < 0) goto end_free; } + else if (htrRenderWidget(s, child, z + 1) != 0) goto end_free; } - - /* - Do the HTML layers - */ - htrAddBodyItem_va(s,"
\n", id); - htrAddBodyItem_va(s,"
\n", id); - htrAddBodyItem_va(s,"
\n", id); - - /* Loop through each radio button and do the option pane and sub layers */ - i = 1; - for (j=0;jChildren));j++) + + /** Write style for radio buttons. **/ + const int top_padding = 12; + const int button_height = para_height + 2; + const int content_height = top_padding + (button_height * radio_buttons.nItems) + 8; + for (int i = 0; i < radio_buttons.nItems; i++) { - radiobutton_obj = xaGetItem(&(tree->Children), j); - wgtrGetPropertyValue(radiobutton_obj,"outer_type",DATA_T_STRING,POD(&ptr)); - if (!strcmp(ptr,"widget/radiobutton")) + pWgtrNode radio_button = radio_buttons.Items[i]; + + /** Store data for the individual radio button. **/ + char value_buf[64] = "", label_buf[64] = ""; + if (wgtrGetPropertyValue(radio_button, "value", DATA_T_STRING, POD(&ptr)) == 0) + strtcpy(value_buf, ptr, sizeof(value_buf)); + if (wgtrGetPropertyValue(radio_button, "label", DATA_T_STRING, POD(&ptr)) == 0) + strtcpy(label_buf, ptr, sizeof(label_buf)); + const int is_selected = (htrGetBoolean(radio_button, "selected", 0) > 0); + wgtrGetPropertyValue(radio_button, "name", DATA_T_STRING, POD(&ptr)); + + /** Create pointers to data. **/ + char* name = ptr; /* Name temporarily stored in the wgtrGetPropertyValue() buffer. */ + char* value = value_buf; + char* label = (label_buf[0] == '\0') ? value_buf : label_buf; + + /** Link the radio button DOM node to widget data. **/ + if (htrAddWgtrObjLinkage_va(s, radio_button, "rb%POSoption%POS", id, i) != 0) goto err_option; + + /** Write the initialization call. **/ + if (htrAddScriptInit_va(s, "\t{ " + "const rbitem = wgtrGetNodeRef('%STR&SYM', '%STR&SYM');" + "add_radiobutton(rbitem, { " + "selected:%POS, " + "buttonset:htr_subel(rbitem, 'rb%POSbuttonset%POS'), " + "buttonunset:htr_subel(rbitem, 'rb%POSbuttonunset%POS'), " + "value:htr_subel(rbitem, 'rb%POSvalue%POS'), " + "label:htr_subel(rbitem, 'rb%POSlabel%POS'), " + "valuestr:'%STR&JSSTR', " + "labelstr:'%STR&JSSTR', " + "}); }\n", + wgtrGetNamespace(radio_button), name, + is_selected, + id, i, id, i, + id, i, id, i, + value, label + ) != 0) { - /** CSS layers **/ - htrAddStylesheetItem_va(s,"\t#rb%POSbuttonset%POS { POSITION:absolute; VISIBILITY:hidden; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; Z-INDEX:%POS; CLIP:rect(0px,%POSpx,%POSpx,0px); CURSOR:pointer; }\n", - id,i,5,2+(s->ClientInfo->ParagraphHeight-12)/2,12,12,z+2,12,12); - htrAddStylesheetItem_va(s,"\t#rb%POSbuttonunset%POS { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; Z-INDEX:%POS; CLIP:rect(0px,%POSpx,%POSpx,0px); CURSOR:pointer; }\n", - id,i,5,2+(s->ClientInfo->ParagraphHeight-12)/2,12,12,z+2,12,12); - htrAddStylesheetItem_va(s,"\t#rb%POSvalue%POS { POSITION:absolute; VISIBILITY:hidden; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; Z-INDEX:%POS; CLIP:rect(0px,%POSpx,%POSpx,0px); }\n", - id,i,5,5,12,12,z+2,12,12); - htrAddStylesheetItem_va(s,"\t#rb%POSlabel%POS { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; Z-INDEX:%POS; CLIP:rect(0px,%POSpx,%POSpx,0px); CURSOR:pointer; }\n", - id,i,27,2,cover_width-(27+1),item_spacing-1,z+2,cover_width-(27+1),item_spacing-1); - - /** Body layers **/ - htrAddBodyItem_va(s,"
\n", id, i); - htrAddBodyItem_va(s,"
\n", id, i); - htrAddBodyItem_va(s,"
\n", id, i); - - wgtrGetPropertyValue(radiobutton_obj,"label",DATA_T_STRING,POD(&ptr)); - strtcpy(sbuf2,ptr,sizeof(sbuf2)); - htrAddBodyItem_va(s,"
%STR&HTE
\n", - id, i, textcolor, sbuf2); - - /* use label (from above) as default value if no value given */ - if(wgtrGetPropertyValue(radiobutton_obj,"value",DATA_T_STRING,POD(&ptr))==0) - { - strtcpy(sbuf2,ptr,sizeof(sbuf2)); - } - - htrAddBodyItem_va(s," \n", - id, i, sbuf2); - htrAddBodyItem(s, "
\n"); - i++; + mssError(0, "HTRB", "Failed to write JS add_radiobutton() call."); + goto err_option; + } + + /** Write CSS for the radio button container. **/ + const int base_top = top_padding + (button_height * i); + const double percent_space_above = (100.0 / radio_buttons.nItems) * i; + const double content_above = ((double)content_height / radio_buttons.nItems) * i; + if (htrAddStylesheetItem_va(s, + "\t\t#rb%POSoption%POS { " + "left:7px; " + "top:calc(0px " + "+ %POSpx " + "+ min(%DBL%% - %DBLpx, %POSpx) " + "); " + "width:calc(100%% - 14px); " + "height:%POSpx; " + "z-index:%POS; " + "}\n", + id, i, + base_top, + percent_space_above, content_above, spacing * i, + button_height, + z + 2 + ) != 0) + { + mssError(0, "HTRB", "Failed to write option CSS."); + goto err_option; + } + + /** Write CSS for the radio button elements. **/ + if (htrAddStylesheetItem_va(s, + "\t\t#rb%POSbuttonset%POS, " + "#rb%POSbuttonunset%POS { " + "position:absolute; " + "overflow:hidden; " + "left:5px; " + "top:%INTpx; " + "width:12px; " + "height:12px; " + "z-index:%POS; " + "cursor:pointer; " + "}\n", + id, i, + id, i, + (para_height / 2) - 3, + z + 2 + ) != 0) + { + mssError(0, "HTRB", "Failed to write option set CSS."); + goto err_option; + } + if (htrAddStylesheetItem_va(s, + "\t\t#rb%POSvalue%POS { " + "position:absolute; " + "visibility:hidden; " + "overflow:hidden; " + "left:5px; " + "top:6px; " + "width:12px; " + "height:12px; " + "z-index:%POS; " + "}\n", + id, i, + z + 2 + ) != 0) + { + mssError(0, "HTRB", "Failed to write option value CSS."); + goto err_option; + } + if (htrAddStylesheetItem_va(s, + "\t\t#rb%POSlabel%POS { " + "position:absolute; " + "visibility:inherit; " + "overflow:hidden; " + "left:27px; " + "top:3px; " + "width:calc(100%% - 27px); " + "height:calc(100%% - 1px); " + "z-index:%POS; " + "cursor:pointer; " + "}\n", + id, i, + z + 2 + ) != 0) + { + mssError(0, "HTRB", "Failed to write option label CSS."); + goto err_option; + } + + /** Write radio button HTML. **/ + if (htrAddBodyItem_va(s, + "
\n" + " \n" + "
\n" + "
%STR&HTE
\n" + " \n" + "
\n", + id, i, id, + id, i, + id, i, + id, i, textcolor, label, + id, i, value + ) != 0) + { + mssError(0, "HTRB", "Failed to write option HTML."); + goto err_option; } + + /** Success. **/ + continue; + + err_option: + mssError(0, "HTRB", "Failed to write option #%d/%d.", i + 1, radio_buttons.nItems); + goto end_free; } - - htrAddBodyItem(s, "
\n"); - htrAddBodyItem(s, "
\n"); - htrAddBodyItem_va(s,"
%STR&HTE
\n", id, textcolor, title); - htrAddBodyItem(s, "
\n"); + + /** Close divs and write title. **/ + if (htrAddBodyItem_va(s, + "
\n" + "
%STR&HTE
\n" + "
\n", + id, textcolor, title + ) != 0) + { + mssError(0, "HTRB", "Failed to write title and closing HTML tags."); + goto end_free; + } + + /** Success. **/ + rval = 0; - return 0; -} + end_free: + if (rval != 0) + { + mssError(0, "HTRB", + "Failed to render \"%s\":\"%s\" (id: %d).", + tree->Name, tree->Type, id + ); + return -1; + } + + /** Clean up. **/ + if (radio_buttons.nAlloc != 0) xaDeInit(&radio_buttons); + + return rval; + } /** htrbInitialize - register with the ht_render module. **/ int htrbInitialize() { pHtDriver drv; + + /** Initialize globals. */ + HTRB.idcnt = 0; /** Allocate the driver **/ drv = htrAllocDriver(); @@ -329,12 +530,9 @@ int htrbInitialize() { htrAddEvent(drv,"MouseMove"); htrAddEvent(drv,"DataChange"); - /** Register. **/ + /** Register with dhtml support. **/ htrRegisterDriver(drv); - htrAddSupport(drv, "dhtml"); - HTRB.idcnt = 0; - return 0; } diff --git a/centrallix/htmlgen/htdrv_repeat.c b/centrallix/htmlgen/htdrv_repeat.c index b4943abc3..8cd62a994 100644 --- a/centrallix/htmlgen/htdrv_repeat.c +++ b/centrallix/htmlgen/htdrv_repeat.c @@ -43,21 +43,22 @@ /************************************************************************/ -/** Globals **/ - -static struct - { - int idcnt; - } HTRPT; - int htrptRender(pHtSession s, pWgtrNode tree, int z) { /** Render Subwidgets **/ - htrAddWgtrCtrLinkage(s, tree, "_parentctr"); - htrRenderSubwidgets(s,tree,z); + if (htrAddWgtrCtrLinkage(s, tree, "_parentctr") != 0) goto err; + if (htrRenderSubwidgets(s, tree, z) != 0) goto err; - return 0; + /** Success. **/ + return 0; + + err: + mssError(0, "HTRPT", + "Failed to render \"%s\":\"%s\".", + tree->Name, tree->Type + ); + return -1; } int @@ -68,8 +69,6 @@ htrptInitialize() /** Allocate the driver **/ drv = htrAllocDriver(); if (!drv) return -1; - - memset(&HTRPT, 0, sizeof(HTRPT)); /** Fill in structure **/ strcpy(drv->Name,"Repeat Object Driver"); diff --git a/centrallix/htmlgen/htdrv_rule.c b/centrallix/htmlgen/htdrv_rule.c index fd245eb96..b0c9340a7 100644 --- a/centrallix/htmlgen/htdrv_rule.c +++ b/centrallix/htmlgen/htdrv_rule.c @@ -17,7 +17,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2007 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -65,7 +65,6 @@ typedef struct _RD /** globals **/ static struct { - int idcnt; pHtRuleDefinition RuletypeList; } HTRULE; @@ -76,7 +75,6 @@ static struct int htruleRender(pHtSession s, pWgtrNode tree, int z) { - int id; char* nptr; char* ptr; pXString xs = NULL; @@ -90,9 +88,6 @@ htruleRender(pHtSession s, pWgtrNode tree, int z) pHtRuleDefinition def; int i, found; - /** Get an id for this. **/ - id = (HTRULE.idcnt++); - xs = (pXString)nmMalloc(sizeof(XString)); if (!xs) goto error; xsInit(xs); @@ -196,8 +191,8 @@ htruleRender(pHtSession s, pWgtrNode tree, int z) htrAddWgtrCtrLinkage(s, tree, "_parentctr"); /** Script Init **/ - htrAddScriptInit_va(s, " rl_init(wgtrGetNodeRef(ns,\"%STR&SYM\"), \"%STR&JSSTR\", %STR);\n", nptr, ruletype, xs->String); - htrAddScriptInit_va(s, " wgtrGetParent(wgtrGetNodeRef(ns,\"%STR&SYM\")).addRule(wgtrGetNodeRef(ns,\"%STR&SYM\"));\n", nptr, nptr); + htrAddScriptInit_va(s, "\trl_init(wgtrGetNodeRef(ns, '%STR&SYM'), '%STR&JSSTR', %STR);\n", nptr, ruletype, xs->String); + htrAddScriptInit_va(s, "\twgtrGetParent(wgtrGetNodeRef(ns, '%STR&SYM')).addRule(wgtrGetNodeRef(ns,'%STR&SYM'));\n", nptr, nptr); /** mark this node as not being associated with a DHTML object **/ tree->RenderFlags |= HT_WGTF_NOOBJECT; @@ -275,7 +270,6 @@ htruleInitialize() htrAddSupport(drv, "dhtml"); - HTRULE.idcnt = 0; HTRULE.RuletypeList = NULL; return 0; diff --git a/centrallix/htmlgen/htdrv_scrollbar.c b/centrallix/htmlgen/htdrv_scrollbar.c index e9b5b65e4..743862275 100644 --- a/centrallix/htmlgen/htdrv_scrollbar.c +++ b/centrallix/htmlgen/htdrv_scrollbar.c @@ -14,7 +14,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2003 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -58,22 +58,22 @@ htsbRender(pHtSession s, pWgtrNode tree, int z) char* ptr; char name[64]; int x,y,w,h,r; - int id, i, t; int visible = 1; char bcolor[64] = ""; char bimage[64] = ""; int is_horizontal = 0; pExpression code; - if(!s->Capabilities.Dom0NS && !s->Capabilities.Dom0IE && !s->Capabilities.Dom1HTML) + /** Get an id for this. **/ + const int id = (HTSB.idcnt++); + + /** Verify browser capabilities. **/ + if (!s->Capabilities.Dom1HTML || !s->Capabilities.Dom2CSS) { - mssError(1,"HTSB","Netscape 4.x, IE, or W3C DOM support required"); - return -1; + mssError(1, "HTSB", "Unsupported browser: W3C DOM1 HTML and DOM2 CSS support required."); + goto err; } - /** Get an id for this. **/ - id = (HTSB.idcnt++); - /** Which direction? **/ if (wgtrGetPropertyValue(tree,"direction",DATA_T_STRING,POD(&ptr)) == 0) { @@ -85,19 +85,19 @@ htsbRender(pHtSession s, pWgtrNode tree, int z) if (wgtrGetPropertyValue(tree,"x",DATA_T_INTEGER,POD(&x)) != 0) { mssError(1,"HTSB","Scrollbar widget must have an 'x' property"); - return -1; + goto err; } if (wgtrGetPropertyValue(tree,"y",DATA_T_INTEGER,POD(&y)) != 0) { mssError(1,"HTSB","Scrollbar widget must have a 'y' property"); - return -1; + goto err; } if (wgtrGetPropertyValue(tree,"width",DATA_T_INTEGER,POD(&w)) != 0) { if (is_horizontal) { mssError(1,"HTSB","Horizontal scrollbar widgets must have a 'width' property"); - return -1; + goto err; } else { @@ -107,14 +107,14 @@ htsbRender(pHtSession s, pWgtrNode tree, int z) if (is_horizontal && w <= 18*3) { mssError(1,"HTSB","Horizontal scrollbar width must be greater than %d", 18*3); - return -1; + goto err; } if (wgtrGetPropertyValue(tree,"height",DATA_T_INTEGER,POD(&h)) != 0) { if (!is_horizontal) { mssError(1,"HTSB","Vertical scrollbar widgets must have a 'height' property"); - return -1; + goto err; } else { @@ -124,11 +124,15 @@ htsbRender(pHtSession s, pWgtrNode tree, int z) if (!is_horizontal && h <= 18*3) { mssError(1,"HTSB","Vertical scrollbar height must be greater than %d", 18*3); - return -1; + goto err; } /** Get name **/ - if (wgtrGetPropertyValue(tree,"name",DATA_T_STRING,POD(&ptr)) != 0) return -1; + if (wgtrGetPropertyValue(tree, "name", DATA_T_STRING, POD(&ptr)) != 0) + { + mssError(1, "HTSB", "Failed to get widget name."); + goto err; + } strtcpy(name,ptr,sizeof(name)); /** Range of scrollbar (static or dynamic property) **/ @@ -136,6 +140,7 @@ htsbRender(pHtSession s, pWgtrNode tree, int z) r = w; else r = h; + int t; if ((t = wgtrGetPropertyType(tree,"range")) == DATA_T_INTEGER) { wgtrGetPropertyValue(tree,"range",DATA_T_INTEGER,POD(&r)); @@ -163,30 +168,72 @@ htsbRender(pHtSession s, pWgtrNode tree, int z) } /** Ok, write the style header items. **/ - htrAddStylesheetItem_va(s,"\t#sb%POSpane { POSITION:absolute; VISIBILITY:%STR; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; clip:rect(0px,%POSpx,%POSpx,0px); Z-INDEX:%POS; }\n",id,visible?"inherit":"hidden",x,y,w,h,w,h, z); - if (is_horizontal) - htrAddStylesheetItem_va(s,"\t#sb%POSthum { POSITION:absolute; VISIBILITY:inherit; LEFT:18px; TOP:0px; WIDTH:18px; Z-INDEX:%POS; }\n",id,z+1); - else - htrAddStylesheetItem_va(s,"\t#sb%POSthum { POSITION:absolute; VISIBILITY:inherit; LEFT:0px; TOP:18px; WIDTH:18px; Z-INDEX:%POS; }\n",id,z+1); - - /** Write globals for internal use **/ - htrAddScriptGlobal(s, "sb_target_img", "null", 0); - htrAddScriptGlobal(s, "sb_click_x","0",0); - htrAddScriptGlobal(s, "sb_click_y","0",0); - htrAddScriptGlobal(s, "sb_thum_x","0",0); - htrAddScriptGlobal(s, "sb_thum_y","0",0); - htrAddScriptGlobal(s, "sb_mv_timeout","null",0); - htrAddScriptGlobal(s, "sb_mv_incr","0",0); - htrAddScriptGlobal(s, "sb_cur_mainlayer","null",0); - - /** DOM Linkage **/ - htrAddWgtrObjLinkage_va(s, tree, "sb%POSpane",id); + if (htrAddStylesheetItem_va(s, + "\t\t#sb%POSpane { " + "position:absolute; " + "visibility:%STR; " + "overflow:hidden; " + "left:%INTpx; " + "top:%INTpx; " + "width:%POSpx; " + "height:%POSpx; " + "z-index:%POS; " + "}\n", + id, + (visible) ? "inherit" : "hidden", + x, y, w, h, + z + ) != 0) + { + mssError(0, "HTSB", "Failed to write scrollbar pane CSS."); + goto err; + } + if (htrAddStylesheetItem_va(s, + "\t\t#sb%POSthum { " + "position:absolute; " + "visibility:inherit; " + "left:%POSpx; " + "top:%POSpx; " + "width:18px; " + "z-index:%POS; " + "}\n", + id, + (is_horizontal) ? 18 : 0, + (is_horizontal) ? 0 : 18, + z + 1 + ) != 0) + { + mssError(0, "HTSB", "Failed to write scrollbar thumb CSS."); + goto err; + } - htrAddScriptInclude(s, "/sys/js/htdrv_scrollbar.js", 0); - htrAddScriptInclude(s, "/sys/js/ht_utils_string.js", 0); + /** Write JS globals, linking, includes, etc. **/ + if (htrAddScriptGlobal(s, "sb_click_x","0", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "sb_click_y","0", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "sb_cur_mainlayer","null", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "sb_mv_incr","0", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "sb_mv_timeout","null", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "sb_target_img", "null", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "sb_thum_x","0", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "sb_thum_y","0", 0) != 0) goto err; + if (htrAddWgtrObjLinkage_va(s, tree, "sb%POSpane", id) != 0) goto err; + if (htrAddScriptInclude(s, "/sys/js/ht_utils_string.js", 0) != 0) goto err; + if (htrAddScriptInclude(s, "/sys/js/htdrv_scrollbar.js", 0) != 0) goto err; /** Script initialization call. **/ - htrAddScriptInit_va(s," sb_init({layer:wgtrGetNodeRef(ns,\"%STR&SYM\"), tname:\"sb%POSthum\", isHorizontal:%INT, range:%INT});\n", name, id, is_horizontal, r); + if (htrAddScriptInit_va(s, + "\tsb_init({ " + "layer:wgtrGetNodeRef(ns, '%STR&SYM'), " + "tname:'sb%POSthum', " + "isHorizontal:%INT, " + "range:%INT, " + "});\n", + name, id, is_horizontal, r + ) != 0) + { + mssError(0, "HTSB", "Failed to write JS init call."); + goto err; + } /** HTML body
elements for the layers. **/ htrAddBodyItem_va(s,"
", id, *bcolor, bcolor, *bimage, bimage, w); @@ -206,19 +253,30 @@ htsbRender(pHtSession s, pWgtrNode tree, int z) /** Add the event handling scripts **/ - htrAddEventHandlerFunction(s, "document","MOUSEDOWN","sb","sb_mousedown"); - htrAddEventHandlerFunction(s, "document","MOUSEMOVE","sb","sb_mousemove"); - htrAddEventHandlerFunction(s, "document","MOUSEUP","sb","sb_mouseup"); - htrAddEventHandlerFunction(s, "document", "MOUSEOVER", "sb","sb_mouseover"); + if (htrAddEventHandlerFunction(s, "document", "MOUSEOVER", "sb", "sb_mouseover") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEDOWN", "sb", "sb_mousedown") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEMOVE", "sb", "sb_mousemove") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEUP", "sb", "sb_mouseup") != 0) goto err; - /** Check for more sub-widgets within the scrollbar (visual ones not allowed). **/ - for (i=0;iChildren));i++) - htrRenderWidget(s, xaGetItem(&(tree->Children), i), z+2); + /** Render children. **/ + if (htrRenderSubwidgets(s, tree, z + 2) != 0) goto err; - /** Finish off the last
**/ - htrAddBodyItem(s,"
\n"); + /** Write the closing HTML
tag. **/ + if (htrAddBodyItem(s, "
\n") != 0) + { + mssError(0, "HTSB", "Failed to write HTML closing tag."); + goto err; + } - return 0; + /** Success. **/ + return 0; + + err: + mssError(0, "HTSB", + "Failed to render \"%s\":\"%s\" (id: %d).", + tree->Name, tree->Type, id + ); + return -1; } diff --git a/centrallix/htmlgen/htdrv_scrollpane.c b/centrallix/htmlgen/htdrv_scrollpane.c index db1a6ec40..4a24b1bc8 100644 --- a/centrallix/htmlgen/htdrv_scrollpane.c +++ b/centrallix/htmlgen/htdrv_scrollpane.c @@ -1,20 +1,8 @@ -#include -#include -#include -#include -#include "ht_render.h" -#include "obj.h" -#include "cxlib/mtask.h" -#include "cxlib/xarray.h" -#include "cxlib/xhash.h" -#include "cxlib/mtsession.h" -#include "cxlib/strtcpy.h" - /************************************************************************/ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -42,6 +30,19 @@ /* layer. Can contain most objects, except for framesets. */ /************************************************************************/ +#include +#include +#include +#include + +#include "cxlib/mtask.h" +#include "cxlib/mtsession.h" +#include "cxlib/strtcpy.h" +#include "cxlib/xarray.h" +#include "cxlib/xhash.h" +#include "ht_render.h" +#include "obj.h" + /** globals **/ static struct @@ -56,145 +57,275 @@ static struct int htspaneRender(pHtSession s, pWgtrNode tree, int z) { - char* ptr; - char name[64]; - int x,y,w,h; - int id, i; - int visible = 1; - char bcolor[64] = ""; - char bimage[64] = ""; - - if(!s->Capabilities.Dom0NS && !s->Capabilities.Dom0IE &&!(s->Capabilities.Dom1HTML && s->Capabilities.Dom2CSS)) + char* str; + + /** Get an id for this scrollpane. **/ + const int id = (HTSPANE.idcnt++); + + /** Verify browser capabilities. **/ + if (!s->Capabilities.Dom1HTML || !s->Capabilities.Dom2CSS) { - mssError(1,"HTSPANE","Netscape DOM or W3C DOM1 HTML and DOM2 CSS support required"); - return -1; + mssError(1, "HTSPANE", "Unsupported browser: W3C DOM1 HTML and DOM2 CSS support required."); + goto err; } - - /** Get an id for this. **/ - id = (HTSPANE.idcnt++); - - /** Get x,y,w,h of this object **/ - if (wgtrGetPropertyValue(tree,"x",DATA_T_INTEGER,POD(&x)) != 0) + + /** Get name. **/ + char name[64]; + if (wgtrGetPropertyValue(tree, "name", DATA_T_STRING, POD(&str)) != 0) { - mssError(1,"HTSPANE","ScrollPane widget must have an 'x' property"); - return -1; + mssError(1, "HTSPANE", "widget/scrollpane must have a 'name' property."); + goto err; } - if (wgtrGetPropertyValue(tree,"y",DATA_T_INTEGER,POD(&y)) != 0) + strtcpy(name, str, sizeof(name)); + + /** Get layout data (required). **/ + int x, y, w, h; + if (wgtrGetPropertyValue(tree, "x", DATA_T_INTEGER, POD(&x)) != 0) { - mssError(1,"HTSPANE","ScrollPane widget must have a 'y' property"); - return -1; + mssError(1, "HTSPANE", "widget/scrollpane must have an 'x' property."); + goto err; } - if (wgtrGetPropertyValue(tree,"width",DATA_T_INTEGER,POD(&w)) != 0) + if (wgtrGetPropertyValue(tree, "y", DATA_T_INTEGER, POD(&y)) != 0) { - mssError(1,"HTSPANE","ScrollPane widget must have a 'width' property"); - return -1; + mssError(1, "HTSPANE", "widget/scrollpane must have a 'y' property."); + goto err; } - if (wgtrGetPropertyValue(tree,"height",DATA_T_INTEGER,POD(&h)) != 0) + if (wgtrGetPropertyValue(tree, "width", DATA_T_INTEGER, POD(&w)) != 0) { - mssError(1,"HTSPANE","ScrollPane widget must have a 'height' property"); - return -1; + mssError(1, "HTSPANE", "widget/scrollpane must have a 'width' property."); + goto err; } - - /** Get name **/ - if (wgtrGetPropertyValue(tree,"name",DATA_T_STRING,POD(&ptr)) != 0) return -1; - strtcpy(name,ptr,sizeof(name)); - - /** Check background color **/ - if (wgtrGetPropertyValue(tree,"bgcolor",DATA_T_STRING,POD(&ptr)) == 0) + if (wgtrGetPropertyValue(tree, "height", DATA_T_INTEGER, POD(&h)) != 0) { - strtcpy(bcolor,ptr,sizeof(bcolor)); + mssError(1, "HTSPANE", "widget/scrollpane must have a 'height' property."); + goto err; } - if (wgtrGetPropertyValue(tree,"background",DATA_T_STRING,POD(&ptr)) == 0) + + /** Get the background color or image. **/ + char background_color[64] = ""; + if (wgtrGetPropertyValue(tree, "bgcolor", DATA_T_STRING, POD(&str)) == 0) { - strtcpy(bimage,ptr,sizeof(bimage)); + strtcpy(background_color, str, sizeof(background_color)); } - - /** Marked not visible? **/ - if (wgtrGetPropertyValue(tree,"visible",DATA_T_STRING,POD(&ptr)) == 0) + char background_image[64] = ""; + if (wgtrGetPropertyValue(tree, "background", DATA_T_STRING, POD(&str)) == 0) { - if (!strcmp(ptr,"false")) visible = 0; + strtcpy(background_image, str, sizeof(background_image)); } - - /** Ok, write the style header items. **/ - if (s->Capabilities.Dom0NS) + + /** Get visibility. **/ + const int visible = htrGetBoolean(tree, "visible", 1); + if (visible == -1) goto err; + + /** Link the widget and container to DOM nodes. **/ + if (htrAddWgtrCtrLinkage_va(s, tree, "htr_subel(_obj, 'sp%POSarea')", id) != 0) { - htrAddStylesheetItem_va(s,"\t#sp%POSpane { POSITION:absolute; VISIBILITY:%STR; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; clip:rect(0px,%POSpx,%POSpx,0px); Z-INDEX:%POS; }\n",id,visible?"inherit":"hidden",x,y,w,h,w,h, z); - htrAddStylesheetItem_va(s,"\t#sp%POSarea { POSITION:absolute; VISIBILITY:inherit; LEFT:0px; TOP:0px; WIDTH:%POSpx; Z-INDEX:%POS; }\n",id,w-18,z+1); - htrAddStylesheetItem_va(s,"\t#sp%POSthum { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:18px; WIDTH:18px; Z-INDEX:%POS; }\n",id,w-18,z+1); + mssError(0, "HTSPANE", "Failed to add container linkage."); + goto err; } - - /** Write globals for internal use **/ - htrAddScriptGlobal(s, "sp_target_img", "null", 0); - htrAddScriptGlobal(s, "sp_click_x","0",0); - htrAddScriptGlobal(s, "sp_click_y","0",0); - htrAddScriptGlobal(s, "sp_thum_y","0",0); - htrAddScriptGlobal(s, "sp_mv_timeout","null",0); - htrAddScriptGlobal(s, "sp_mv_incr","0",0); - htrAddScriptGlobal(s, "sp_cur_mainlayer","null",0); - - /** DOM Linkages **/ - htrAddWgtrObjLinkage_va(s, tree, "sp%POSpane",id); - htrAddWgtrCtrLinkage_va(s, tree, "htr_subel(_obj, \"sp%POSarea\")",id); - - htrAddScriptInclude(s, "/sys/js/htdrv_scrollpane.js", 0); - htrAddScriptInclude(s, "/sys/js/ht_utils_string.js", 0); - - htrAddScriptInit_va(s," sp_init({layer:wgtrGetNodeRef(ns,\"%STR&SYM\"), aname:\"sp%POSarea\", tname:\"sp%POSthum\"});\n", name,id,id); - - /** HTML body
elements for the layers. **/ - if(s->Capabilities.Dom0NS) - { - htrAddBodyItem_va(s,"
", id, *bcolor, bcolor, *bimage, bimage, w); - htrAddBodyItem(s, "
"); - htrAddBodyItem_va(s,"",h-36); - htrAddBodyItem(s, "
\n"); - htrAddBodyItem_va(s,"
\n
",id,id,w-2,h-2); - } - else if(s->Capabilities.Dom1HTML) - { - //htrAddStylesheetItem_va(s,"\t#sp%dpane { POSITION:absolute; VISIBILITY:%s; LEFT:%dpx; TOP:%dpx; WIDTH:%dpx; HEIGHT:%dpx; clip:rect(0px,%dpx,%dpx,0px); Z-INDEX:%d; }\n",id,visible?"inherit":"hidden",x,y,w,h,w,h, z); - //htrAddStylesheetItem_va(s,"\t#sp%darea { HEIGHT: %dpx; WIDTH:%dpx; }\n",id, h, w-18); - //htrAddStylesheetItem_va(s,"\t#sp%dthum { POSITION:absolute; VISIBILITY:inherit; LEFT:%dpx; TOP:18px; WIDTH:18px; Z-INDEX:%dpx; }\n",id,w-18,z+1); - htrAddBodyItem_va(s,"
\n",id,visible?"inherit":"hidden",x,y,w,h,w,h,z); - htrAddBodyItem_va(s,"", id); - htrAddBodyItem_va(s,"", id); - htrAddBodyItem_va(s,"", id); - htrAddStylesheetItem_va(s,"\t#sp%POSup { POSITION: absolute; LEFT: %INTpx; TOP: 0px; }\n",id, w-18); - htrAddStylesheetItem_va(s,"\t#sp%POSbar { POSITION: absolute; LEFT: %INTpx; TOP: 18px; WIDTH: 18px; HEIGHT: %POSpx;}\n",id, w-18, h-36); - htrAddStylesheetItem_va(s,"\t#sp%POSdown { POSITION: absolute; LEFT: %INTpx; TOP: %INTpx; }\n",id, w-18, h-18); - htrAddBodyItem_va(s,"
\n", id,w-18,z+1); - htrAddBodyItem_va(s,"
",id,h,w-18,z+1); - } - else - { - mssError(1,"HTSPNE","Browser not supported"); + if (htrAddWgtrObjLinkage_va(s, tree, "sp%POSpane", id) != 0) + { + mssError(0, "HTSPANE", "Failed to add object linkage."); + goto err; } - + + /** Include scrollpane script and its dependencies. **/ + if (htrAddScriptInclude(s, "/sys/js/ht_utils_string.js", 0) != 0) goto err; + if (htrAddScriptInclude(s, "/sys/js/htdrv_scrollpane.js", 0) != 0) goto err; + /** Add the event handling scripts **/ - htrAddEventHandlerFunction(s, "document","MOUSEDOWN","sp","sp_mousedown"); - htrAddEventHandlerFunction(s, "document","MOUSEMOVE","sp","sp_mousemove"); - htrAddEventHandlerFunction(s, "document","MOUSEUP","sp","sp_mouseup"); - htrAddEventHandlerFunction(s, "document", "MOUSEOVER", "sp","sp_mouseover"); - - /** Do subwidgets **/ - for (i=0;iChildren));i++) - htrRenderWidget(s, xaGetItem(&(tree->Children), i), z+2); - - /** Finish off the last
**/ - if(s->Capabilities.Dom0NS) + if (htrAddEventHandlerFunction(s, "document", "MOUSEDOWN", "sp", "sp_mousedown") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEMOVE", "sp", "sp_mousemove") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEOVER", "sp", "sp_mouseover") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEUP", "sp", "sp_mouseup") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "WHEEL", "sp", "sp_wheel") != 0) goto err; + + /** Init the JS scripts. **/ + if (htrAddScriptInit_va(s, + "\tsp_init({" + "layer: wgtrGetNodeRef(ns, '%STR&SYM'), " + "area_name: 'sp%POSarea', " + "thumb_name: 'sp%POSthumb', " + "});\n", + name, id, id + ) != 0) { - htrAddBodyItem(s,"
\n"); + mssError(0, "HTSPANE", "Failed to write JS init call."); + goto err; } - else if(s->Capabilities.Dom1HTML) + + /** Write the scrollpane. **/ + if (htrAddBodyItem_va(s, + "
\n", + id, + (visible) ? "inherit" : "hidden", + ht_flex_x(x, tree), + ht_flex_y(y, tree), + ht_flex_w(w, tree), + ht_flex_h(h, tree), + z + ) != 0) { - htrAddBodyItem(s,"
\n"); + mssError(0, "HTSPANE", "Failed to write HTML for main scrollpane div."); + goto err; } - else + + /** Write shared CSS for the following UI elements. **/ + if (htrAddStylesheetItem_va(s, + "\t\t.sp%POS_scroll { " + "position:absolute; " + "left:"ht_flex_format"; " + "}\n", + id, + ht_flex(w - 18, tree->width, 1.0) + ) != 0) { - mssError(1,"HTSPNE","browser not supported"); + mssError(0, "HTSPANE", "Failed to write shared CSS ui elements."); + goto err; } - - return 0; + + /** Write the up button. **/ + if (htrAddBodyItem_va(s, + "", + id, + id + ) != 0) + { + mssError(0, "HTSPANE", "Failed to write HTML for the down button."); + goto err; + } + + /** Write the scroll bar. **/ + if (htrAddBodyItem_va(s, + "", + id, + id, + ht_flex(h - 36, tree->height, 1.0) + ) != 0) + { + mssError(0, "HTSPANE", "Failed to write HTML for the scroll bar."); + goto err; + } + + /** Write the down button. **/ + if (htrAddBodyItem_va(s, + "", + id, + id, + ht_flex(h - 18, tree->height, 1.0) + ) != 0) + { + mssError(0, "HTSPANE", "Failed to write HTML for the down button."); + goto err; + } + + /** Write the scroll thumb. **/ + if (htrAddBodyItem_va(s, + "
" + "scroll_thumb" + "
\n", + id, + id, + z + 1 + ) != 0) + { + mssError(0, "HTSPANE", "Failed to write HTML for the scroll thumb."); + goto err; + } + + /** Write the scroll area. **/ + if (htrAddBodyItem_va(s, + "
", + id, + ht_flex(w - 18, ht_get_parent_w(tree), 1.0), + ht_flex(h, ht_get_parent_h(tree), 1.0), + z + 1 + ) != 0) + { + mssError(0, "HTSPANE", "Failed to write HTML for the scroll area."); + goto err; + } + + /** Render children. **/ + if (htrRenderSubwidgets(s, tree, z + 2) != 0) goto err; + + /** Close the final
s. **/ + if (htrAddBodyItem(s,"
\n") != 0) + { + mssError(0, "HTSPANE", "Failed to write closing HTML tags."); + goto err; + } + + return 0; + + err: + mssError(0, "HTTAB", + "Failed to render \"%s\":\"%s\" (id: %d).", + tree->Name, tree->Type, id + ); + return -1; } @@ -204,29 +335,31 @@ int htspaneInitialize() { pHtDriver drv; - - /** Allocate the driver **/ + + /** Allocate the driver **/ drv = htrAllocDriver(); if (!drv) return -1; - + /** Fill in the structure. **/ - strcpy(drv->Name,"HTML ScrollPane Widget Driver"); + strcpy(drv->Name,"HTML Widget/scrollpane Driver"); strcpy(drv->WidgetName,"scrollpane"); drv->Render = htspaneRender; - + /** Events **/ - htrAddEvent(drv,"Click"); - htrAddEvent(drv,"MouseUp"); - htrAddEvent(drv,"MouseDown"); - htrAddEvent(drv,"MouseOver"); - htrAddEvent(drv,"MouseOut"); - htrAddEvent(drv,"MouseMove"); - + htrAddEvent(drv, "Scroll"); + htrAddEvent(drv, "Click"); + htrAddEvent(drv, "Wheel"); + htrAddEvent(drv, "MouseUp"); + htrAddEvent(drv, "MouseDown"); + htrAddEvent(drv, "MouseOver"); + htrAddEvent(drv, "MouseOut"); + htrAddEvent(drv, "MouseMove"); + /** Register. **/ htrRegisterDriver(drv); - + htrAddSupport(drv, "dhtml"); HTSPANE.idcnt = 0; - + return 0; } diff --git a/centrallix/htmlgen/htdrv_spinner.c b/centrallix/htmlgen/htdrv_spinner.c index 7252b86a5..a7afa520c 100644 --- a/centrallix/htmlgen/htdrv_spinner.c +++ b/centrallix/htmlgen/htdrv_spinner.c @@ -14,7 +14,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -114,12 +114,12 @@ htspnrRender(pHtSession s, pWgtrNode tree, int z) } /** Ok, write the style header items. **/ - htrAddStylesheetItem_va(s,"\t#spnr%POSmain { POSITION:absolute; VISIBILITY:inherit; LEFT:%INT; TOP:%INT; WIDTH:%POS; Z-INDEX:%POS; }\n",id,x,y,w,z); - htrAddStylesheetItem_va(s,"\t#spnr%POSbase { POSITION:absolute; VISIBILITY:inherit; LEFT:%INT; TOP:%INT; WIDTH:%POS; Z-INDEX:%POS; }\n",id,1,1,w-12,z); - htrAddStylesheetItem_va(s,"\t#spnr%POScon1 { POSITION:absolute; VISIBILITY:inherit; LEFT:%INT; TOP:%INT; WIDTH:%POS; Z-INDEX:%POS; }\n",id,1,1,w-2-12,z+1); - htrAddStylesheetItem_va(s,"\t#spnr%POScon2 { POSITION:absolute; VISIBILITY:hidden; LEFT:%INT; TOP:%INT; WIDTH:%POS; Z-INDEX:%POS; }\n",id,1,1,w-2-12,z+1); - htrAddStylesheetItem_va(s,"\t#spnr_button_up { POSITION:absolute; VISIBILITY:inherit; LEFT:%INT; TOP:%INT; WIDTH:%POS; Z-INDEX:%POS; }\n",1+w-12,1,w,z); - htrAddStylesheetItem_va(s,"\t#spnr_button_down { POSITION:absolute; VISIBILITY:inherit; LEFT:%INT; TOP:%INT; WIDTH:%POS; Z-INDEX:%POS; }\n",1+w-12,1+9,w,z); + htrAddStylesheetItem_va(s, "\t\t#spnr%POSmain { position:absolute; visibility:inherit; left:%INT; top:%INT; width:%POS; z-index:%POS; }\n", id, x, y, w, z); + htrAddStylesheetItem_va(s, "\t\t#spnr%POSbase { position:absolute; visibility:inherit; left:%INT; top:%INT; width:%POS; z-index:%POS; }\n", id, 1, 1, w-12, z); + htrAddStylesheetItem_va(s, "\t\t#spnr%POScon1 { position:absolute; visibility:inherit; left:%INT; top:%INT; width:%POS; z-index:%POS; }\n", id, 1, 1, w-2-12, z+1); + htrAddStylesheetItem_va(s, "\t\t#spnr%POScon2 { position:absolute; visibility:hidden; left:%INT; top:%INT; width:%POS; z-index:%POS; }\n", id, 1, 1, w-2-12, z+1); + htrAddStylesheetItem_va(s, "\t\t#spnr_button_up { position:absolute; visibility:inherit; left:%INT; top:%INT; width:%POS; z-index:%POS; }\n", 1+w-12, 1, w, z); + htrAddStylesheetItem_va(s, "\t\t#spnr_button_down { position:absolute; visibility:inherit; left:%INT; top:%INT; width:%POS; z-index:%POS; }\n", 1+w-12, 1+9,w, z); /** DOM Linkage **/ htrAddWgtrObjLinkage_va(s, tree, "spnr%POSmain",id); @@ -134,13 +134,16 @@ htspnrRender(pHtSession s, pWgtrNode tree, int z) htrAddEventHandlerFunction(s, "document","MOUSEDOWN", "spnr", "spnr_mousedown"); /** Script initialization call. **/ - htrAddScriptInit_va(s, - " var spnr = wgtrGetNodeRef(ns, \"%STR&SYM\");\n" - " spnr_init({main:spnr, layer:htr_subel(spnr,\"spnr%POSbase\"), c1:htr_subel(htr_subel(spnr,\"spnr%POSbase\"),\"spnr%POScon1\"), c2:htr_subel(htr_subel(spnr,\"spnr%POSbase\"),\"spnr%POScon2\")});\n", - name, - id, - id, id, - id, id); + htrAddScriptInit_va(s, "\t{ " + "const main = wgtrGetNodeRef(ns, '%STR&SYM'); " + "const layer = htr_subel(main, 'spnr%POSbase'); " + "spnr_init({ " + "main, layer, " + "c1:htr_subel(layer,'spnr%POScon1'), " + "c2:htr_subel(layer,'spnr%POScon2'), " + "}); }\n", + name, id, id, id + ); /** HTML body
element for the base layer. **/ htrAddBodyItem_va(s, "
\n",id); diff --git a/centrallix/htmlgen/htdrv_tab.c b/centrallix/htmlgen/htdrv_tab.c index 9b029bf6e..eedb406e4 100644 --- a/centrallix/htmlgen/htdrv_tab.c +++ b/centrallix/htmlgen/htdrv_tab.c @@ -1,21 +1,8 @@ -#include -#include -#include -#include -#include -#include "ht_render.h" -#include "obj.h" -#include "cxlib/mtask.h" -#include "cxlib/xarray.h" -#include "cxlib/xhash.h" -#include "cxlib/mtsession.h" -#include "cxlib/strtcpy.h" - /************************************************************************/ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -41,6 +28,16 @@ /* Description: HTML Widget driver for a tab control. */ /************************************************************************/ +#include +#include +#include + +#include "cxlib/mtsession.h" +#include "cxlib/strtcpy.h" +#include "cxlib/util.h" +#include "ht_render.h" +#include "obj.h" + /** globals **/ static struct @@ -58,315 +55,734 @@ enum httab_locations { Top=0, Bottom=1, Left=2, Right=3, None=4 }; int httabRender(pHtSession s, pWgtrNode tree, int z) { - char* ptr; - char* type,*field; + char* ptr; int tmp = 0; + char* page_type = NULL; + char* field = NULL; char name[64]; - char tab_txt[128]; + char text_color[128]; char main_bg[128]; char inactive_bg[128]; - char page_type[32]; char fieldname[128]; - char sel[128]; - int sel_idx= -1; - pWgtrNode tabpage_obj; - int x=-1,y=-1,w,h; - int id,tabcnt, i, j; - char* subnptr; - enum httab_locations tloc; - int tab_width = 0; - int xoffset,yoffset,xtoffset, ytoffset; - int is_selected; - char* bg; + int sel_idx = -1; + int x = -1, y = -1; + int w, h; /* width & height of the tab control. */ + int tab_w = 0, tab_h = 0; + int is_auto_tab_w = 0; /* 1 if tab_w should be computed client-side. */ + int xoffset, yoffset, xtoffset, ytoffset; char* tabname; pWgtrNode children[32]; int border_radius; char border_style[32]; char border_color[64]; int border_width; - int shadow_offset, shadow_radius, shadow_angle; - char shadow_color[128]; - - if(!s->Capabilities.Dom0NS && !s->Capabilities.Dom0IE &&(!s->Capabilities.Dom1HTML || !s->Capabilities.Dom2CSS)) + + /** Reserve the next tab widget ID. **/ + const int id = (HTTAB.idcnt++); + + /** Verify browser capabilities. **/ + if (!s->Capabilities.Dom1HTML || !s->Capabilities.Dom2CSS) { - mssError(1,"HTTAB","NS4 or W3C DOM Support required"); - return -1; + mssError(1, "HTTAB", "Unsupported browser: W3C DOM1 HTML and DOM2 CSS support required."); + goto err; } - - /** Get an id for this. **/ - id = (HTTAB.idcnt++); - - /** Get name **/ - if (wgtrGetPropertyValue(tree,"name",DATA_T_STRING,POD(&ptr)) != 0) return -1; - strtcpy(name,ptr,sizeof(name)); - - /** Get x,y,w,h of this object **/ - if (wgtrGetPropertyValue(tree,"x",DATA_T_INTEGER,POD(&x)) != 0) x=0; - if (wgtrGetPropertyValue(tree,"y",DATA_T_INTEGER,POD(&y)) != 0) y=0; - if (wgtrGetPropertyValue(tree,"width",DATA_T_INTEGER,POD(&w)) != 0) + + /** Get the tab widget name. **/ + if (wgtrGetPropertyValue(tree, "name", DATA_T_STRING, POD(&ptr)) != 0) { - mssError(0,"HTTAB","Tab widget must have a 'width' property"); - return -1; + mssError(1, "HTTAB", "Tab widget has no name?!"); + goto err; } - if (wgtrGetPropertyValue(tree,"height",DATA_T_INTEGER,POD(&h)) != 0) + strtcpy(name, ptr, sizeof(name)); + + /** Get x, y, w, & h of this object. **/ + if (wgtrGetPropertyValue(tree, "x", DATA_T_INTEGER, POD(&x)) != 0) x = 0; + if (wgtrGetPropertyValue(tree, "y", DATA_T_INTEGER, POD(&y)) != 0) y = 0; + if (wgtrGetPropertyValue(tree, "width", DATA_T_INTEGER, POD(&w)) != 0) { - mssError(0,"HTTAB","Tab widget must have a 'height' property"); - return -1; + mssError(1, "HTTAB", "Tab widget must have a 'width' property"); + goto err; } - - /** Drop shadow **/ - shadow_offset=0; + if (wgtrGetPropertyValue(tree, "height", DATA_T_INTEGER, POD(&h)) != 0) + { + mssError(1, "HTTAB", "Tab widget must have a 'height' property"); + goto err; + } + + /** Get drop shadow data. **/ + int shadow_offset = 0, shadow_radius = 0, shadow_angle = 135; + char shadow_color[128]; if (wgtrGetPropertyValue(tree, "shadow_offset", DATA_T_INTEGER, POD(&shadow_offset)) == 0 && shadow_offset > 0) shadow_radius = shadow_offset+1; - else - shadow_radius = 0; wgtrGetPropertyValue(tree, "shadow_radius", DATA_T_INTEGER, POD(&shadow_radius)); + wgtrGetPropertyValue(tree, "shadow_angle", DATA_T_INTEGER, POD(&shadow_angle)); strcpy(shadow_color, "black"); if (shadow_radius > 0) { if (wgtrGetPropertyValue(tree, "shadow_color", DATA_T_STRING, POD(&ptr)) == 0) strtcpy(shadow_color, ptr, sizeof(shadow_color)); } - if (wgtrGetPropertyValue(tree, "shadow_angle", DATA_T_INTEGER, POD(&shadow_angle)) != 0) - shadow_angle = 135; - - /** Border radius, color, and style. **/ - if (wgtrGetPropertyValue(tree,"border_radius",DATA_T_INTEGER,POD(&border_radius)) != 0) - border_radius=0; - if (wgtrGetPropertyValue(tree,"border_color",DATA_T_STRING,POD(&ptr)) != 0) + const double shadow_x = sin(shadow_angle * M_PI/180) * shadow_offset; + const double shadow_y = cos(shadow_angle * M_PI/180) * -shadow_offset; + + /** Get border info (radius, color, and style). **/ + if (wgtrGetPropertyValue(tree, "border_radius", DATA_T_INTEGER, POD(&border_radius)) != 0) + border_radius = 0; + if (wgtrGetPropertyValue(tree, "border_color", DATA_T_STRING, POD(&ptr)) != 0) strcpy(border_color, "#ffffff"); else strtcpy(border_color, ptr, sizeof(border_color)); - if (wgtrGetPropertyValue(tree,"border_style",DATA_T_STRING,POD(&ptr)) != 0) + if (wgtrGetPropertyValue(tree, "border_style", DATA_T_STRING,POD(&ptr)) != 0) strcpy(border_style, "outset"); else strtcpy(border_style, ptr, sizeof(border_style)); if (!strcmp(border_style, "none") || !strcmp(border_style, "hidden")) - border_width=0; + border_width = 0; else - border_width=1; - - /** Which side are the tabs on? **/ - if (wgtrGetPropertyValue(tree,"tab_location",DATA_T_STRING,POD(&ptr)) == 0) + border_width = 1; + + /** Get tab_location. **/ + enum httab_locations tloc = Top; + if (wgtrGetPropertyValue(tree, "tab_location", DATA_T_STRING, POD(&ptr)) == 0) { - if (!strcasecmp(ptr,"top")) tloc = Top; - else if (!strcasecmp(ptr,"bottom")) tloc = Bottom; - else if (!strcasecmp(ptr,"left")) tloc = Left; - else if (!strcasecmp(ptr,"right")) tloc = Right; - else if (!strcasecmp(ptr,"none")) tloc = None; + if (strcasecmp(ptr, "none") == 0) tloc = None; + else if (strcasecmp(ptr, "top") == 0) tloc = Top; + else if (strcasecmp(ptr, "bottom") == 0) tloc = Bottom; + else if (strcasecmp(ptr, "left") == 0) tloc = Left; + else if (strcasecmp(ptr, "right") == 0) tloc = Right; else { mssError(1,"HTTAB","%s: '%s' is not a valid tab_location",name,ptr); - return -1; + goto err; + } + } + + /** Count the number of tabs. **/ + const int tab_count = wgtrGetMatchingChildList(tree, "widget/tabpage", children, sizeof(children) / sizeof(pWgtrNode)); + + /** Get the selected tab. **/ + if (wgtrGetPropertyType(tree, "selected") == DATA_T_STRING && + wgtrGetPropertyType(tree, "selected_index") == DATA_T_INTEGER) + { + mssError(1,"HTTAB","%s: cannot specify both 'selected' and 'selected_index'", name); + goto err; + } + if (wgtrGetPropertyValue(tree, "selected", DATA_T_STRING, POD(&ptr)) == 0) + { + /** Search for the tab with the indicated name. **/ + for (unsigned int i = 0; i < tab_count; i++) + { + char* tab_name; + wgtrGetPropertyValue(children[i], "name", DATA_T_STRING, POD(&tab_name)); + if (strcmp(ptr, tab_name) == 0) + { + /** sel_idx is 1 based, but i is 0 based. **/ + sel_idx = i + 1; + break; + } + } + if (sel_idx == -1) { + mssError(1, "HTTAB", "Failed to find tab named '%s'", ptr); + + /** Attempt to give hint. **/ + if (tab_count > 0) + { + char* example_tab_name; + wgtrGetPropertyValue(children[0], "name", DATA_T_STRING, POD(&example_tab_name)); + mssError(0, "HTTAB", "Hint: 'selected' should be a tab name, such as \"%s\".", example_tab_name); + } + + /** Fail. **/ + goto err; + } + } + else if (wgtrGetPropertyValue(tree, "selected_index", DATA_T_INTEGER, POD(&sel_idx)) == 0) + { + if (sel_idx <= 0) + { + mssError(1, "HTTAB", "Invalid value for 'selected_index': %d.", sel_idx); + if (sel_idx == 0) mssError(0, "HTTAB", "Hint: 'selected_index' is 1-based."); + goto err; + } + if (sel_idx > tab_count) + { + mssError(1, "HTTAB", + "Invalid value for 'selected_index': %d. Tab control only has %d tab%s.", + sel_idx, tab_count, (tab_count == 1) ? "" : "s" + ); + goto err; } } else { - tloc = Top; + /** No specified selected tab, default to the first one. **/ + sel_idx = 1; } - - /** How wide should left/right tabs be? **/ - if (wgtrGetPropertyValue(tree,"tab_width",DATA_T_INTEGER,POD(&tab_width)) != 0) + + /** Handle user expressions for the selected tab. **/ + if (htrCheckAddExpression(s, tree, name, "selected") < 0) goto err; + if (htrCheckAddExpression(s, tree, name, "selected_index") < 0) goto err; + + /** Get the background colors/images. **/ + htrGetBackground(tree, NULL, s->Capabilities.Dom2CSS, main_bg, sizeof(main_bg)); + if (htrGetBackground(tree, "inactive", s->Capabilities.Dom2CSS, inactive_bg, sizeof(inactive_bg)) != 0) + strcpy(inactive_bg, main_bg); + + /** Get the text color. **/ + if (wgtrGetPropertyValue(tree, "textcolor", DATA_T_STRING, POD(&ptr)) == 0 + && !strpbrk(ptr, "{};&<>\"\'")) + strtcpy(text_color, ptr, sizeof(text_color)); + else + strcpy(text_color,"black"); + + /** Get the tab spacing and tab height. **/ + /** tab_w and tab_h are left as 0 if unset to tell the front end to calculate them dynamically. **/ + int tab_spacing = 2; /* Default to a 2px gap between tabs. */ + if (wgtrGetPropertyValue(tree, "tab_spacing", DATA_T_INTEGER, POD(&tmp)) == 0) tab_spacing = tmp; + if (wgtrGetPropertyValue(tree, "tab_width", DATA_T_INTEGER, POD(&tmp)) == 0) { - if (tloc == Right || tloc == Left) + if (tmp <= 0) { - mssError(1,"HTTAB","%s: tab_width must be specified with tab_location of left or right", name); - return -1; + mssError(1, "HTTAB", "%s: 'tab_width' expected positive nonzero int, got %d.", name, tmp); + goto err; } + tab_w = tmp; + } + else if (tloc == Right || tloc == Left) + { + mssError(1, "HTTAB", "%s: 'tab_width' must be specified for 'tab_location' of left or right", name); + goto err; } else { - if (tab_width < 0) tab_width = 0; + /** Use a default value, updated client side. */ + tab_w = 80; + is_auto_tab_w = 1; } - - /** Which tab is selected? **/ - if (wgtrGetPropertyType(tree,"selected") == DATA_T_STRING && - wgtrGetPropertyValue(tree,"selected",DATA_T_STRING,POD(&ptr)) == 0) + if (wgtrGetPropertyValue(tree, "tab_height", DATA_T_INTEGER, POD(&tmp)) == 0) { - strtcpy(sel,ptr, sizeof(sel)); + if (tmp <= 0) + { + mssError(1, "HTTAB", "%s: 'tab_height' expected positive nonzero int, got %d.", name, tmp); + goto err; + } + tab_h = tmp; } else { - strcpy(sel,""); + /** Use default value, no client side calculation available. **/ + tab_h = 24; } - if (wgtrGetPropertyValue(tree,"selected_index", DATA_T_INTEGER, POD(&sel_idx)) != 0) + + /** Get macro selection translation values. **/ + /** CHANGE: Code previously used 1, but I think 0 is a better looking default. **/ + int along, out; + if (wgtrGetPropertyValue(tree, "select_translate_along", DATA_T_INTEGER, POD(&along)) != 0) along = 0; + if (wgtrGetPropertyValue(tree, "select_translate_out", DATA_T_INTEGER, POD(&out)) != 0) out = 2; + + /** Determine offset to actual tab pages and offsets for selected tabs. **/ + int select_x_offset = 0, select_y_offset = 0; + switch (tloc) { - sel_idx = -1; + /*** Shift to cover boarder line: + *** Top: ytoffset +1 + *** Bottom: ytoffset -2 + *** Left: xtoffset +1 + *** Right: xtoffset -2 + ***/ + case Top: xoffset = 0; yoffset = tab_h; xtoffset = 0; ytoffset = 0; select_x_offset = -along; select_y_offset = -out; break; + case Bottom: xoffset = 0; yoffset = 0; xtoffset = 0; ytoffset = h-1; select_x_offset = -along; select_y_offset = +out; break; + case Left: xoffset = tab_w; yoffset = 0; xtoffset = 0; ytoffset = 0; select_x_offset = -out; select_y_offset = -along; break; + case Right: xoffset = 0; yoffset = 0; xtoffset = w-1; ytoffset = 0; select_x_offset = +out; select_y_offset = -along; break; + case None: xoffset = 0; yoffset = 0; xtoffset = 0; ytoffset = 0; select_x_offset = 0; select_y_offset = 0; break; + default: mssError(1, "HTTAB", "Unexpected tab location value: %d", tloc); goto err; } - if (sel_idx != -1 && *sel != '\0') + + /** Get coordinate-based selection translation values. **/ + if (wgtrGetPropertyValue(tree, "select_translate_x", DATA_T_INTEGER, POD(&tmp)) == 0) select_x_offset = tmp; + if (wgtrGetPropertyValue(tree, "select_translate_y", DATA_T_INTEGER, POD(&tmp)) == 0) select_y_offset = tmp; + + /*** Apply the opposite of the selection offset to all tabs. This + *** prevents the offset from causing the selected tab to appear + *** detached from the tab control. + ***/ + xtoffset -= select_x_offset; + ytoffset -= select_y_offset; + + /** Get the rendering type (debugging feature). **/ + /** Allows the developer to turn off JS client-side widget rendering for testing. **/ + int do_client_rendering = 1; + if (wgtrGetPropertyValue(tree, "rendering", DATA_T_STRING, POD(&ptr)) == 0) { - mssError(1,"HTTAB","%s: cannot specify both 'selected' and 'selected_index'", name); - return -1; + if (strcmp(ptr, "server-side") == 0) do_client_rendering = 0; + else if (strcmp(ptr, "client-side") == 0) do_client_rendering = 1; + else + { + mssError(1, "HTTAB", "Unknown value for 'rendering': %s", ptr); + mssError(0, "HTTAB", "HINT: Should be either 'server-side' or 'client-size'."); + goto err; + } } - - /** User requesting expression for selected tab? **/ - htrCheckAddExpression(s, tree, name, "selected"); - - /** User requesting expression for selected tab using integer index value? **/ - htrCheckAddExpression(s, tree, name, "selected_index"); - - /** Background color/image? **/ - htrGetBackground(tree, NULL, s->Capabilities.Dom2CSS, main_bg, sizeof(main_bg)); - - /** Inactive tab color/image? **/ - htrGetBackground(tree, "inactive", s->Capabilities.Dom2CSS, inactive_bg, sizeof(inactive_bg)); - - /** Text color? **/ - if (wgtrGetPropertyValue(tree,"textcolor",DATA_T_STRING,POD(&ptr)) == 0) - strtcpy(tab_txt, ptr, sizeof(tab_txt)); - else - strcpy(tab_txt,"black"); - if (strpbrk(tab_txt, "{};&<>\"\'")) - strcpy(tab_txt,"black"); - - /** Determine offset to actual tab pages **/ - switch(tloc) + if (!do_client_rendering && tab_w == 0 && (tloc == Top || tloc == Bottom)) { - case Top: xoffset = 0; yoffset = 24; xtoffset = 0; ytoffset = 0; break; - case Bottom: xoffset = 0; yoffset = 0; xtoffset = 0; ytoffset = h; break; - case Right: xoffset = 0; yoffset = 0; xtoffset = w; ytoffset = 0; break; - case Left: xoffset = tab_width; yoffset = 0; xtoffset = 0; ytoffset = 0; break; - case None: xoffset = 0; yoffset = 0; xtoffset = 0; ytoffset = 0; + /*** The widget specifies server-side rendering for Top/Bottom tabs + *** with dynamic width. This will probably look broken. + ***/ + fprintf(stderr, "WARNING: " + "'rendering' value of \"server-side\" will break on tabs with " + "dynamic widths because they cannot be calculated server-side!" + ); + fprintf(stderr, "HINT: Specify the 'tab_width' attribute or use \"client-side\" rendering."); } - - /** Ok, write the style header items. **/ - htrAddStylesheetItem_va(s,"\t#tc%POSbase { background-position: 0px -24px; %STR }\n", id, main_bg); - - /** DOM Linkages **/ - htrAddWgtrObjLinkage_va(s, tree, "tc%POSbase",id); - - /** Script include **/ - htrAddScriptInclude(s, "/sys/js/htdrv_tab.js", 0); - - /** Add a global for the master tabs listing **/ - htrAddScriptGlobal(s, "tc_tabs", "null", 0); - htrAddScriptGlobal(s, "tc_cur_mainlayer", "null", 0); - - /** Event handler for click-on-tab **/ - htrAddEventHandlerFunction(s, "document","MOUSEDOWN","tc","tc_mousedown"); - htrAddEventHandlerFunction(s, "document","MOUSEUP","tc","tc_mouseup"); - htrAddEventHandlerFunction(s, "document","MOUSEMOVE","tc","tc_mousemove"); - htrAddEventHandlerFunction(s, "document","MOUSEOVER","tc","tc_mouseover"); - + + /** Link the widget to the DOM node. **/ + if (htrAddWgtrObjLinkage_va(s, tree, "tc%POSctrl", id) != 0) + { + mssError(0, "HTTAB", "Failed to add object linkage to tab."); + goto err; + } + + /** Include the htdrv_tab.js script. **/ + if (htrAddScriptInclude(s, "/sys/js/htdrv_tab.js", 0) != 0) goto err; + + /** Send globals variables to the client to avoid needing to hard code them. **/ + const int bufsiz = 96; + char* config_buf = nmSysMalloc(bufsiz); + if (config_buf == NULL) + { + mssError(1, "HTTAB", "%s: nmSysMalloc(%d) failed.", name, bufsiz); + goto err; + } + snprintf( + memset(config_buf, 0, bufsiz), bufsiz, + "{ tlocs: { Top:%d, Bottom:%d, Left:%d, Right:%d, None:%d } }", + Top, Bottom, Left, Right, None + ); + if (htrAddScriptGlobal(s, "tc_config", config_buf, HTR_F_VALUEALLOC) != 0) goto err; + /*** TODO: Greg - config_buf is definitely leaked because I can't + *** figure out how long it needs to remain in scope. + ***/ + + /** Add globals for the master tabs listing. **/ + if (htrAddScriptGlobal(s, "tc_cur_mainlayer", "null", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "tc_tabs", "null", 0) != 0) goto err; + + /** Add mouse event handlers. **/ + if (htrAddEventHandlerFunction(s, "document", "MOUSEDOWN", "tc", "tc_mousedown") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEMOVE", "tc", "tc_mousemove") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEOVER", "tc", "tc_mouseover") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEUP", "tc", "tc_mouseup") != 0) goto err; + /** Script initialization call. **/ - htrAddScriptInit_va(s," tc_init({layer:wgtrGetNodeRef(ns,\"%STR&SYM\"), tloc:%INT, mainBackground:\"%STR&JSSTR\", inactiveBackground:\"%STR&JSSTR\"});\n", - name, tloc, main_bg, inactive_bg); - + char tloc_name[8]; + switch (tloc) + { + case Top: strcpy(tloc_name, "Top"); break; + case Bottom: strcpy(tloc_name, "Bottom"); break; + case Left: strcpy(tloc_name, "Left"); break; + case Right: strcpy(tloc_name, "Right"); break; + case None: strcpy(tloc_name, "None"); break; + } + if (htrAddScriptInit_va(s, + "\ttc_init({" + "layer:wgtrGetNodeRef(ns,'%STR&SYM'), " + "tloc:'%STR', " + "mainBackground:'%STR&JSSTR', " + "inactiveBackground:'%STR&JSSTR', " + "select_x_offset:%INT, " + "select_y_offset:%INT, " + "xtoffset:%INT, " + "ytoffset:%INT, " + "tab_spacing:%INT, " + "tab_w:%INT, " + "tab_h:%INT, " + "do_client_rendering:%STR, " + "});\n", + name, tloc_name, + main_bg, inactive_bg, + select_x_offset, select_y_offset, + xtoffset, ytoffset, + tab_spacing, + (is_auto_tab_w) ? 0 : tab_w, /* 0 tells the front end that it should recalculate tab_w. */ + tab_h, + (do_client_rendering) ? "true" : "false" + ) != 0) + { + mssError(0, "HTTAB", "Failed to write JS script call."); + goto err; + } + /** Check for tabpages within the tab control, to do the tabs at the top. **/ - tabcnt = wgtrGetMatchingChildList(tree, "widget/tabpage", children, sizeof(children)/sizeof(pWgtrNode)); if (tloc != None) { - for (i=0;i0, tab_width, - is_selected?(z+2):z, - (tloc==Bottom || tloc==Right)?0:border_radius, (tloc==Bottom || tloc==Left)?0:border_radius, (tloc==Top || tloc==Left)?0:border_radius, (tloc==Top || tloc==Right)?0:border_radius, - //(tloc==Top || tloc==Left)?0:border_radius, (tloc==Right)?0:border_radius, border_radius, (tloc==Bottom)?0:border_radius, - border_style, - (tloc!=Bottom)?1:0, (tloc!=Left)?1:0, (tloc!=Top)?1:0, (tloc!=Right)?1:0, - border_color, - sin(shadow_angle*M_PI/180)*shadow_offset, cos(shadow_angle*M_PI/180)*(-shadow_offset), shadow_radius, shadow_color, - (tloc != Right)?"left":"right", - tab_txt, bg - ); - - htrAddBodyItem_va(s, "

%[ %STR&HTE %]%[ %STR&HTE %]

\n", - id, i+1, - tloc == Right, tabname, - is_selected?2:3, - tloc != Right, tabname - ); + case Top: case Bottom: i_offset_x = full_tab_spacing + tab_w; break; + case Right: case Left: i_offset_y = full_tab_spacing + tab_h; break; + case None:; /* Unreachable, but the compiler doesn't believe me. */ + } + + /** Compute clip area, ensuring that it will not overlap the tab control. **/ + /*** I wasn't sure how to remove edges of any clip path, so I just set + *** -1000 and we'll hope that keeps them out of the way. + ***/ + const int clip_top = (tloc == Bottom) ? 0 : -1000; + const int clip_right = (tloc == Left) ? 0 : -1000; + const int clip_bottom = (tloc == Top) ? 0 : -1000; + const int clip_left = (tloc == Right) ? 0 : -1000; + + /** Compute border radius, only rounding corners that don't touch the tab control. **/ + const int border_radius_top_left = (tloc == Bottom || tloc == Right) ? 0 : border_radius; + const int border_radius_top_right = (tloc == Bottom || tloc == Left) ? 0 : border_radius; + const int border_radius_bottom_right = (tloc == Top || tloc == Left) ? 0 : border_radius; + const int border_radius_bottom_left = (tloc == Top || tloc == Right) ? 0 : border_radius; + + /** Compute border width, skipping borders that would overlap the tab control. **/ + const int border_top = (tloc != Bottom) ? 1 : 0; + const int border_right = (tloc != Left) ? 1 : 0; + const int border_bottom = (tloc != Top) ? 1 : 0; + const int border_left = (tloc != Right) ? 1 : 0; + + /** Compute tab flex information. **/ + /*** fl_x/y is enough flex to line up with the left/top of the tab + *** control. However, if the tab box changes size, tabs on the + *** right/bottom need to flex enough to handle that, too. + ***/ + double tab_fl_x = ht_get_fl_x(tree), tab_fl_y = ht_get_fl_y(tree); + if (tloc == Right) tab_fl_x += ht_get_fl_w(tree); + else if (tloc == Bottom) tab_fl_y += ht_get_fl_h(tree); + const int parent_w = ht_get_parent_w(tree); + const int parent_h = ht_get_parent_h(tree); + + /** Inject tab_fl values for client-side rendering. **/ + if (htrAddScriptInit_va(s, + "{ " + "const node = wgtrGetNodeRef(ns, '%STR&SYM'); " + "node.tab_fl_x = %DBL; " + "node.tab_fl_y = %DBL; " + "}\n", + name, tab_fl_x, tab_fl_y + ) != 0) + { + mssError(0, "HTTAB", "Failed to write JS script call."); + goto err; + } + + /** Loop over each tab. **/ + for (unsigned int i = 0; i < tab_count; i++) + { + const pWgtrNode tab = children[i]; + + /** Get the tab name, preferring to use the title attribute (if specified). **/ + if (wgtrGetPropertyValue(tab, "title", DATA_T_STRING, POD(&tabname)) != 0) + wgtrGetPropertyValue(tab, "name", DATA_T_STRING, POD(&tabname)); + + /** Write tab CSS styles. **/ + const int is_selected = (i == sel_idx - 1); + const int tab_x = (x + xtoffset) + (i_offset_x * i); + const int tab_y = (y + ytoffset) + (i_offset_y * i); + if (htrAddStylesheetItem_va(s, + "\t\t#tc%POStab%POS { " + "position:absolute; " + "visibility:inherit; " + "overflow:hidden; " + "cursor:pointer; " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "%[width:%POSpx; %]" /* Tab width has 0 flexibility. */ + "%[height:%POSpx; %]" /* Tab height has 0 flexibility. */ + "z-index:%POS; " + "clip-path:inset(%INTpx %INTpx %INTpx %INTpx); " + "border-radius:%POSpx %POSpx %POSpx %POSpx; " + "border-width:%POSpx %POSpx %POSpx %POSpx; " + "border-style:%STR&CSSVAL; " + "border-color:%STR&CSSVAL; " + "box-shadow:%DBLpx %DBLpx %POSpx %STR&CSSVAL; " + "text-align:%STR&CSSVAL; " + "color:%STR&CSSVAL; " + "font-weight:bold; /*" + "easter-egg-6:value;*/ " + "background-position: %INTpx %INTpx; " + "%STR " + "}\n", + id, i + 1, + ht_flex(tab_x, parent_w, tab_fl_x), + ht_flex(tab_y, parent_h, tab_fl_y), + (!is_auto_tab_w), tab_w, /* Tab width has 0 flexibility. */ + (tab_h > 0), tab_h, /* Tab height has 0 flexibility. */ + (is_selected) ? (z + 2) : z, + clip_top, clip_right, clip_bottom, clip_left, + border_radius_top_left, border_radius_top_right, border_radius_bottom_right, border_radius_bottom_left, + border_top, border_right, border_bottom, border_left, + border_style, + border_color, + shadow_x, shadow_y, shadow_radius, shadow_color, + (tloc != Right) ? "left" : "right", + text_color, + tab_x + 1, tab_y, + (is_selected) ? main_bg : inactive_bg + ) != 0) + { + mssError(0, "HTTAB", "Failed to write CSS for tab."); + goto err_tab; + } + + if (htrAddStylesheetItem_va(s, + "\t\t#tc%POStab%POS.tab_selected { " + "transform:translate(%INTpx, %INTpx); " + "}\n", + id, i + 1, + select_x_offset, select_y_offset + ) != 0) + { + mssError(0, "HTTAB", "Failed to write CSS for selected tab."); + goto err_tab; + } + + /** Write tab HTML content. **/ + if (htrAddBodyItem_va(s, + "
" + "

" + "%[ %STR&HTE %]" + "" + "%[ %STR&HTE %]" + "

" + "
\n", + id, i + 1, (is_selected), + (tloc == Right), tabname, + (is_selected) ? 2 : 3, tab_h, + (tloc != Right), tabname + ) != 0) + { + mssError(0, "HTTAB", "Failed to write HTML sheet for tab."); + goto err_tab; + } + + /** Tab written successfully. **/ + continue; + + err_tab: /** Handle errors. **/ + mssError(0, "HTTAB", "Failed to write tab #%d.", i + 1); + goto err; } } - - /** h-2 and w-2 because w3c dom borders add to actual width **/ - htrAddBodyItem_va(s,"
\n", - id, h-border_width*2, w-border_width*2, x+xoffset, y+yoffset, z+1, - border_style, border_color, - (tloc==Top || tloc==Left)?0:border_radius, (tloc==Right)?0:border_radius, border_radius, (tloc==Bottom)?0:border_radius, - sin(shadow_angle*M_PI/180)*shadow_offset, cos(shadow_angle*M_PI/180)*(-shadow_offset), shadow_radius, shadow_color - ); - - /** Check for tabpages within the tab control entity, this time to do the pages themselves **/ - for (i=0;i\n", id) != 0) + { + mssError(0, "HTTAB", "Failed to write HTML for tab control."); + goto err; + } + + /** Check for tab pages within the tab control entity, this time to do the pages themselves. **/ + const char* widget_namespace = wgtrGetNamespace(tree); + for (unsigned int i = 0; i < tab_count; i++) + { + const pWgtrNode tab_page_tree = children[i]; + + /** Handle namespace transition. **/ + if (htrCheckNSTransition(s, tree, tab_page_tree) != 0) { - if(wgtrGetPropertyValue(tabpage_obj,"fieldname",DATA_T_STRING,POD(&field)) == 0) - strtcpy(fieldname,field,sizeof(fieldname)); + mssError(0, "HTTAB", "Failed to transition to namespace while writing tab page."); + goto tab_page_err; } - - /** Add script initialization to add a new tabpage **/ - if (tloc == None) - htrAddScriptInit_va(s," wgtrGetNodeRef('%STR&SYM', '%STR&SYM').addTab(null,wgtrGetContainer(wgtrGetNodeRef(\"%STR&SYM\",\"%STR&SYM\")),wgtrGetNodeRef('%STR&SYM','%STR&SYM'),'%STR&JSSTR','%STR&JSSTR','%STR&JSSTR');\n", - wgtrGetNamespace(tree), name, - wgtrGetNamespace(tabpage_obj), ptr, wgtrGetNamespace(tree), name, ptr,page_type,fieldname); + + /** Check if the tab is selected. **/ + int is_selected = (i == sel_idx - 1); + + /** Get page type. **/ + if (wgtrGetPropertyValue(tab_page_tree, "type", DATA_T_STRING, POD(&ptr)) != 0) + page_type = "static"; /* Default: static page_type. */ + else if (ptr == NULL) + { + mssError(0, "HTTAB", "Failed to get attribute 'type'"); + goto tab_page_err; + } + else if (strcmp(ptr, "dynamic") == 0) page_type = "dynamic"; + else if (strcmp(ptr, "static") == 0) page_type = "static"; else - htrAddScriptInit_va(s," wgtrGetNodeRef('%STR&SYM','%STR&SYM').addTab(htr_subel(wgtrGetParentContainer(wgtrGetNodeRef('%STR&SYM','%STR&SYM')),\"tc%POStab%POS\"),wgtrGetContainer(wgtrGetNodeRef(\"%STR&SYM\",\"%STR&SYM\")),wgtrGetNodeRef('%STR&SYM','%STR&SYM'),'%STR&JSSTR','%STR&JSSTR','%STR&JSSTR');\n", - wgtrGetNamespace(tree), name, - wgtrGetNamespace(tree), name, - id, i+1, wgtrGetNamespace(tabpage_obj), ptr, wgtrGetNamespace(tree), name, ptr,page_type,fieldname); - - /** Add named global for the tabpage **/ - subnptr = nmSysStrdup(ptr); - htrAddWgtrObjLinkage_va(s, tabpage_obj, "tc%POSpane%POS", id, i+1); - htrAddWgtrCtrLinkage_va(s, tabpage_obj, "htr_subel(_parentobj, \"tc%POSpane%POS\")", id, i+1); - - /** Add DIV section for the tabpage. **/ - htrAddBodyItem_va(s,"
\n", - id,i+1,is_selected?"inherit":"hidden",w-2,z+2); - - /** Now look for sub-items within the tabpage. **/ - for (j=0;jChildren));j++) - htrRenderWidget(s, xaGetItem(&(tabpage_obj->Children), j), z+3); - - htrAddBodyItem(s, "
\n"); - - nmSysFree(subnptr); - - /** Add the visible property **/ - htrCheckAddExpression(s, tabpage_obj, ptr, "visible"); - - htrCheckNSTransitionReturn(s, tree, tabpage_obj); + { + mssError(1, "HTTAB", "Unknown value \"%s\" for attribute 'type'.", ptr); + goto tab_page_err; + } + + /** Get feildname. **/ + if (page_type[0] == 'd' + && wgtrGetPropertyValue(tab_page_tree, "fieldname", DATA_T_STRING, POD(&field)) == 0) + strtcpy(fieldname, field, sizeof(fieldname)); + else fieldname[0] = '\0'; + + /** Get name. **/ + if (wgtrGetPropertyValue(tab_page_tree, "name", DATA_T_STRING, POD(&ptr)) != 0) + { + mssError(0, "HTTAB", "Failed to get attribute 'name'"); + goto tab_page_err; + } + + /** Write the addTab() initialization call (in a new scope). **/ + const char* tab_page_namespace = wgtrGetNamespace(tab_page_tree); + if (htrAddScriptInit_va(s, "\t{ " + "const tabctrl = wgtrGetNodeRef('%STR&SYM', '%STR&SYM'); " + "tabctrl" + ".addTab({ " + "%[tab:htr_subel(wgtrGetParentContainer(tabctrl), 'tc%POStab%POS'), %]" + "page:wgtrGetContainer(wgtrGetNodeRef('%STR&SYM', '%STR&SYM')), " + "name:'%STR&JSSTR', type:'%STR&JSSTR', fieldname:'%STR&JSSTR', " + "}); " + "}\n", + widget_namespace, name, + (tloc != None), id, i + 1, + tab_page_namespace, ptr, + ptr, page_type, fieldname + ) != 0) + { + mssError(0, "HTTAB", "Failed to write JS to add tab page."); + goto tab_page_err; + } + + /** Add named global for the tabpage. **/ + if (htrAddWgtrObjLinkage_va(s, tab_page_tree, "tc%POSpane%POS", id, i+1) != 0) + { + mssError(0, "HTTAB", "Failed to add object linkage for tab page."); + goto tab_page_err; + } + if (htrAddWgtrCtrLinkage_va(s, tab_page_tree, "htr_subel(_parentobj, \"tc%POSpane%POS\")", id, i + 1) != 0) + { + mssError(0, "HTTAB", "Failed to add container linkage for tab page."); + goto tab_page_err; + } + + /** Add DIV section to contane the tabpage. **/ + if (htrAddBodyItem_va(s, + "
\n", + id, i + 1, + (is_selected) ? "inherit" : "hidden", + z + 2 + ) != 0) + { + mssError(0, "HTTAB", "Failed to write HTML to add tab page."); + goto tab_page_err; + } + + /** Handle sub-items within the tabpage. **/ + if (htrRenderSubwidgets(s, tab_page_tree, z+3) != 0) + { + mssError(0, "HTTAB", "Failed to render subwidgets of tab page."); + goto tab_page_err; + } + + /** Close the tab page container. */ + if (htrAddBodyItem(s, "
\n") != 0) + { + mssError(0, "HTTAB", "Failed to write closing div tag of tab page."); + goto tab_page_err; + } + + /** Add the visible property. **/ + if (htrCheckAddExpression(s, tab_page_tree, ptr, "visible") < 0) goto tab_page_err; + + /** Handle namespace transition. **/ + if (htrCheckNSTransitionReturn(s, tree, tab_page_tree) != 0) + { + mssError(0, "HTTAB", "Failed to return from namespace."); + goto tab_page_err; + } + + continue; + + /** Error cases. **/ + tab_page_err: + mssError(0, "HTTAB", + "Failed to render tab page \"%s\":\"%s\", #%d.", + tree->Name, tree->Type, i + 1 + ); + goto err; } - - /** Need to do other subwidgets (connectors, etc.) now **/ - htrRenderSubwidgets(s, tree, z+1); - + + /** Render children. **/ + if (htrRenderSubwidgets(s, tree, z + 1) != 0) + { + mssError(0, "HTTAB", "Failed to render subwidgets."); + goto err; + } + /** End the containing layer. **/ - htrAddBodyItem(s, "
\n"); - - return 0; + if (htrAddBodyItem(s, "
\n") != 0) + { + mssError(0, "HTTAB", "Failed to write closing div tag."); + goto err; + } + + return 0; + + err: + mssError(0, "HTTAB", + "Failed to render \"%s\":\"%s\" (id: %d).", + tree->Name, tree->Type, id + ); + return -1; } int @@ -385,25 +801,20 @@ httabInitialize() { pHtDriver drv; - /** Allocate the driver **/ + /** Tab Control Driver. **/ drv = htrAllocDriver(); - if (!drv) return -1; - - /** Fill in the structure. **/ - strcpy(drv->Name,"DHTML Tab Control Driver"); - strcpy(drv->WidgetName,"tab"); + if (drv == NULL) return -1; + strcpy(drv->Name, "DHTML Tab Control Driver"); + strcpy(drv->WidgetName, "tab"); drv->Render = httabRender; - /*xaAddItem(&(drv->PseudoTypes), "tabpage");*/ - - /** Register. **/ htrRegisterDriver(drv); - htrAddSupport(drv, "dhtml"); + /** Tab Page Driver. **/ drv = htrAllocDriver(); - if (!drv) return -1; - strcpy(drv->Name,"DHTML Tab Page Driver"); - strcpy(drv->WidgetName,"tabpage"); + if (drv == NULL) return -1; + strcpy(drv->Name, "DHTML Tab Page Driver"); + strcpy(drv->WidgetName, "tabpage"); drv->Render = httabRender_page; htrRegisterDriver(drv); htrAddSupport(drv, "dhtml"); diff --git a/centrallix/htmlgen/htdrv_table.c b/centrallix/htmlgen/htdrv_table.c index 54dc47f41..52410a31b 100644 --- a/centrallix/htmlgen/htdrv_table.c +++ b/centrallix/htmlgen/htdrv_table.c @@ -15,7 +15,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1999-2007 LightSys Technology Services, Inc. */ +/* Copyright (C) 1999-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -35,7 +35,7 @@ /* A copy of the GNU General Public License has been included in this */ /* distribution in the file "COPYING". */ /* */ -/* Module: htdrv_table.c */ +/* Module: htdrv_table.c */ /* Author: Greg Beeley (GRB) */ /* Creation: October 29, 1999 */ /* Description: HTML Widget driver for a data-driven table. Has three */ @@ -58,6 +58,10 @@ /* only. Dynamicrow tables use the most client resources. */ /************************************************************************/ +/*** This file uses the optional Comment Anchors VSCode extension, documented + *** with CommentAnchorsExtension.md in centrallix-sysdoc. + ***/ + #define HTTBL_MAX_COLS (64) @@ -92,12 +96,7 @@ typedef struct typedef struct { char name[64]; - char sbuf[160]; - char tbl_bgnd[128]; char hdr_bgnd[128]; - char row_bgnd1[128]; - char row_bgnd2[128]; - char row_bgndhigh[128]; char colsep_bgnd[128]; char textcolor[64]; char textcolorhighlight[64]; @@ -105,34 +104,25 @@ typedef struct char newrow_bgnd[128]; char newrow_textcolor[64]; char osrc[64]; - char row_border[64]; - char row_shadow_color[64]; - int row_shadow; - int row_shadow_radius; - int row_radius; - int x,y,w,h; + httbl_col* col_infs[HTTBL_MAX_COLS]; + int ncols; int id; + int x,y,w,h; int data_mode; /* 0="rows" or 1="properties" */ - int outer_border; - int inner_border; int inner_padding; - httbl_col* col_infs[HTTBL_MAX_COLS]; - int ncols; int windowsize; int min_rowheight; int max_rowheight; int cellhspacing; int cellvspacing; - int followcurrent; int dragcols; int colsep; int colsep_mode; - int gridinemptyrows; + int grid_in_empty_rows; int allow_selection; int show_selection; int initial_selection; int allow_deselection; - int reverse_order; int overlap_scrollbar; /* scrollbar overlaps with table */ int hide_scrollbar; /* don't show scrollbar at all */ int demand_scrollbar; /* only show scrollbar when needed */ @@ -140,143 +130,418 @@ typedef struct int rowcache_size; /* number of rows the table caches for display */ } httbl_struct; + int httblRenderDynamic(pHtSession s, pWgtrNode tree, int z, httbl_struct* t) { - int colid; - char *ptr; - int i; - pWgtrNode sub_tree; - int subcnt = 0; - char *nptr; - int h; - int first_offset = (t->has_header)?(t->min_rowheight + t->cellvspacing):0; - pWgtrNode children[32]; - int detailcnt; - httbl_col* col; + char* ptr; - if(!s->Capabilities.Dom0NS && !s->Capabilities.Dom1HTML) + /** Verify browser capabilities. **/ + if (!s->Capabilities.Dom1HTML || !s->Capabilities.Dom2CSS) { - mssError(1,"HTTBL","Netscape 4 DOM or W3C DOM support required"); - return -1; + mssError(1, "HTTBL", "Unsupported browser: W3C DOM1 HTML and DOM2 CSS support required."); + goto err; } - /** STYLE for the layer **/ - htrAddStylesheetItem_va(s,"\t#tbld%POSpane { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; Z-INDEX:%POS; } \n",t->id,t->x,t->y,(t->overlap_scrollbar)?(t->w):(t->w-18),z+0); - htrAddStylesheetItem_va(s,"\t#tbld%POSscroll { POSITION:absolute; VISIBILITY:%STR; LEFT:%INTpx; TOP:%INTpx; WIDTH:18px; HEIGHT:%POSpx; Z-INDEX:%POS; }\n",t->id,(t->hide_scrollbar || t->demand_scrollbar)?"hidden":"inherit",t->x+t->w-18,t->y+first_offset,t->h-first_offset,z+0); - htrAddStylesheetItem_va(s,"\t#tbld%POSbox { POSITION:absolute; VISIBILITY:inherit; LEFT:0px; TOP:18px; WIDTH:16px; HEIGHT:16px; Z-INDEX:%POS; BORDER: solid 1px; BORDER-COLOR: white gray gray white; }\n",t->id,z+1); - - htrAddScriptGlobal(s,"tbld_current","null",0); - htrAddScriptGlobal(s,"tbldb_current","null",0); - htrAddScriptGlobal(s,"tbldx_current","null",0); - htrAddScriptGlobal(s,"tbldb_start","null",0); - htrAddScriptGlobal(s,"tbldbdbl_current","null",0); - - htrAddScriptInclude(s, "/sys/js/htdrv_table.js", 0); - htrAddScriptInclude(s, "/sys/js/ht_utils_string.js", 0); - - htrAddWgtrObjLinkage_va(s, tree, "tbld%POSpane",t->id); - - htrAddScriptInit_va(s," tbld_init({tablename:'%STR&SYM', table:wgtrGetNodeRef(ns,\"%STR&SYM\"), scroll:htr_subel(wgtrGetParentContainer(wgtrGetNodeRef(ns,\"%STR&SYM\")),\"tbld%POSscroll\"), boxname:\"tbld%POSbox\", name:\"%STR&SYM\", height:%INT, width:%INT, innerpadding:%INT, innerborder:%INT, windowsize:%INT, min_rowheight:%INT, max_rowheight:%INT, cellhspacing:%INT, cellvspacing:%INT, textcolor:\"%STR&JSSTR\", textcolorhighlight:\"%STR&JSSTR\", titlecolor:\"%STR&JSSTR\", rowbgnd1:\"%STR&JSSTR\", rowbgnd2:\"%STR&JSSTR\", rowbgndhigh:\"%STR&JSSTR\", hdrbgnd:\"%STR&JSSTR\", followcurrent:%INT, dragcols:%INT, colsep:%INT, colsep_mode:%INT, colsep_bgnd:\"%STR&JSSTR\", gridinemptyrows:%INT, reverse_order:%INT, allow_selection:%INT, show_selection:%INT, initial_selection:%INT, allow_deselection:%INT, overlap_sb:%INT, hide_sb:%INT, demand_sb:%INT, osrc:%['%STR&SYM'%]%[null%], dm:%INT, hdr:%INT, newrow_bgnd:\"%STR&JSSTR\", newrow_textcolor:\"%STR&JSSTR\", rcsize:%INT, cols:[", - t->name,t->name,t->name,t->id,t->id,t->name,t->h, - (t->overlap_scrollbar)?t->w:t->w-18, - t->inner_padding,t->inner_border,t->windowsize,t->min_rowheight, t->max_rowheight, - t->cellhspacing, t->cellvspacing,t->textcolor, - t->textcolorhighlight, t->titlecolor,t->row_bgnd1,t->row_bgnd2, - t->row_bgndhigh,t->hdr_bgnd,t->followcurrent,t->dragcols, - t->colsep, t->colsep_mode, t->colsep_bgnd,t->gridinemptyrows, t->reverse_order, - t->allow_selection, t->show_selection, t->initial_selection, t->allow_deselection, - t->overlap_scrollbar, t->hide_scrollbar, t->demand_scrollbar, - *(t->osrc) != '\0', t->osrc, *(t->osrc) == '\0', - t->data_mode, t->has_header, - t->newrow_bgnd, t->newrow_textcolor, - t->rowcache_size); + /** Write CSS for the table base element. **/ + const int content_width = (t->overlap_scrollbar) ? (t->w) : (t->w - 18); + if (htrAddStylesheetItem_va(s, + "\t\t#tbld%POSbase { " + "position:absolute; " + "visibility:inherit; " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "width:"ht_flex_format"; " + "height:"ht_flex_format"; " + "z-index:%POS; " + "}\n", + t->id, + ht_flex_x(t->x, tree), + ht_flex_y(t->y, tree), + ht_flex_w(content_width, tree), + ht_flex_h(t->h, tree), + z + 0 + ) != 0) + { + mssError(0, "HTTBL", "Failed to write base table CSS."); + goto err; + } - for(colid=0;colidncols;colid++) + /** Write CSS for the table scrollbar. **/ + const int row_start_y = (t->has_header) ? (t->min_rowheight + t->cellvspacing) : 0; + if (htrAddStylesheetItem_va(s, + "\t\t#tbld%POSscroll { " + "position:absolute; " + "visibility:%STR; " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "width:18px; " + "height:"ht_flex_format"; " + "z-index:%POS; " + "}\n", + t->id, + (t->hide_scrollbar || t->demand_scrollbar) ? "hidden" : "inherit", /** TODO: Greg - This logic looks fishy. Why does `demand_scrollbar = true` hide the scrollbar?? **/ + ht_flex(t->x + t->w - 18, ht_get_parent_w(tree), ht_get_fl_x(tree) + ht_get_fl_w(tree)), + ht_flex_y(t->y + row_start_y, tree), + ht_flex_h(t->h - row_start_y, tree), + z + 0 + ) != 0) { - col = t->col_infs[colid]; - htrAddScriptInit_va(s,"{name:\"%STR&JSSTR\",ns:\"%STR&JSSTR\",fieldname:\"%STR&JSSTR\",sort_fieldname:\"%STR&JSSTR\",title:\"%STR&JSSTR\",width:%INT,type:\"%STR&JSSTR\",group:%POS,align:\"%STR&JSSTR\",wrap:\"%STR&JSSTR\",caption_fieldname:\"%STR&JSSTR\",caption_textcolor:\"%STR&JSSTR\",image_maxwidth:%POS,image_maxheight:%POS},", - col->wname, - col->wnamespace, - col->fieldname, - col->sort_fieldname, - col->title, - col->width, - col->type, - col->group[0]?1:0, - col->align, - col->wrap, - col->caption_fieldname, - col->caption_textcolor, - col->image_maxwidth, - col->image_maxheight - ); + mssError(0, "HTTBL", "Failed to write table scrollbar CSS."); + goto err; + } + + /** Write CSS for the table scroll thumb. **/ + if (htrAddStylesheetItem_va(s, + "\t\t#tbld%POSthumb { " + "position:absolute; " + "visibility:inherit; " + "left:0px; " + "top:18px; " + "width:16px; " + "height:16px; " + "z-index:%POS; " + "border:solid 1px; " + "border-color:white gray gray white; " + "}\n", + t->id, + z + 1 + ) != 0) + { + mssError(0, "HTTBL", "Failed to write table scroll thumb CSS."); + goto err; + } + + /** Link to the table base object. **/ + if (htrAddWgtrObjLinkage_va(s, tree, "tbld%POSbase", t->id) != 0) goto err; + + /** Add globals for scripts. **/ + if (htrAddScriptGlobal(s, "tbld_current", "null", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "tbldb_current", "null", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "tbldb_start", "null", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "tbldbdbl_current", "null", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "tbldx_current", "null", 0) != 0) goto err; + + /** Include scripts. **/ + if (htrAddScriptInclude(s, "/sys/js/ht_utils_string.js", 0) != 0) goto err; + if (htrAddScriptInclude(s, "/sys/js/htdrv_table.js", 0) != 0) goto err; + + /** Begin writing the js initialization call. **/ + if (htrAddScriptInit_va(s, "\ttbld_init({") != 0) + { + mssError(0, "HTTBL", "Failed to write JS to start table init call."); + goto err; + } + + /** Write identification data. **/ + const int has_osrc = (t->osrc != NULL && t->osrc[0] != '\0'); + if (htrAddScriptInit_va(s, + "name:'%STR&SYM', " + "table:wgtrGetNodeRef(ns, '%STR&SYM'), " + "scroll:htr_subel(" + "wgtrGetParentContainer(wgtrGetNodeRef(ns, '%STR&SYM')), " + "'tbld%POSscroll'" + "), " + "osrc:%['%STR&SYM'%]%[null%], ", + t->name, t->name, t->name, t->id, + (has_osrc), t->osrc, (!has_osrc) + ) != 0) + { + mssError(0, "HTTBL", "Failed to write JS table identification data."); + goto err; + } + + /** Write layout data. **/ + if (htrAddScriptInit_va(s, + "height:%INT, " + "width:%INT, " + "innerpadding:%INT, " + "min_rowheight:%INT, " + "max_rowheight:%INT, " + "cellhspacing:%INT, " + "cellvspacing:%INT, ", + t->h, content_width, + t->inner_padding, + t->min_rowheight, t->max_rowheight, + t->cellhspacing, t->cellvspacing + ) != 0) + { + mssError(0, "HTTBL", "Failed to write JS table layout data."); + goto err; + } + + /** Write selection data. **/ + if (htrAddScriptInit_va(s, + "allow_selection:%INT, " + "show_selection:%INT, " + "initial_selection:%INT, " + "allow_deselection:%INT, ", + t->allow_selection, + t->show_selection, + t->initial_selection, + t->allow_deselection + ) != 0) + { + mssError(0, "HTTBL", "Failed to write JS table selection data."); + goto err; + } + + /** Write scrollbar data. **/ + if (htrAddScriptInit_va(s, + "thumb_name:'tbld%POSthumb', " + "demand_sb:%INT, ", + t->id, + t->demand_scrollbar + ) != 0) + { + mssError(0, "HTTBL", "Failed to write JS table scrollbar data."); + goto err; + } + + /** Write theme data (colors, backgrounds, etc.). **/ + if (htrAddScriptInit_va(s, + "textcolor:'%STR&JSSTR', " + "textcolorhighlight:'%STR&JSSTR', " + "titlecolor:'%STR&JSSTR', " + "colsep_bgnd:'%STR&JSSTR', " + "hdrbgnd:'%STR&JSSTR', " + "newrow_bgnd:'%STR&JSSTR', " + "newrow_textcolor:'%STR&JSSTR', ", + t->textcolor, t->textcolorhighlight, + t->titlecolor, t->colsep_bgnd, t->hdr_bgnd, + t->newrow_bgnd, t->newrow_textcolor + ) != 0) + { + mssError(0, "HTTBL", "Failed to write JS table theme data."); + goto err; + } + + /** Write general row data. **/ + if (htrAddScriptInit_va(s, + "dm:%INT, " + "hdr:%INT, " + "grid_in_empty_rows:%INT, " + "windowsize:%INT, " + "rcsize:%INT, ", + t->data_mode, + t->has_header, + t->grid_in_empty_rows, + t->windowsize, + t->rowcache_size + ) != 0) + { + mssError(0, "HTTBL", "Failed to write JS table general row data."); + goto err; + } + + /** Write general column data. **/ + if (htrAddScriptInit_va(s, + "dragcols:%INT, " + "colsep:%INT, " + "colsep_mode:%INT, ", + t->dragcols, t->colsep, t->colsep_mode + ) != 0) + { + mssError(0, "HTTBL", "Failed to write JS table general column data."); + goto err; + } + + /** Write the cols array with data for each column. **/ + /** ANCHOR[id=table-column] **/ + if (htrAddScriptInit_va(s, "cols:[") != 0) + { + mssError(0, "HTTBL", "Failed to write JS for the start of the table columns list."); + goto err; + } + for (int colid = 0; colid < t->ncols; colid++) + { + httbl_col* col = t->col_infs[colid]; + if (htrAddScriptInit_va(s, + "{ " + "name:'%STR&JSSTR', " + "ns:'%STR&JSSTR', " + "fieldname:'%STR&JSSTR', " + "sort_fieldname:'%STR&JSSTR', " + "title:'%STR&JSSTR', " + "width:%INT, " + "type:'%STR&JSSTR', " + "group:%POS, " + "align:'%STR&JSSTR', " + "wrap:'%STR&JSSTR', " + "caption_fieldname:'%STR&JSSTR', " + "caption_textcolor:'%STR&JSSTR', " + "image_maxwidth:%POS, " + "image_maxheight:%POS, " + " }, ", + col->wname, + col->wnamespace, + col->fieldname, + col->sort_fieldname, + col->title, + col->width, + col->type, + col->group[0] ? 1 : 0, + col->align, + col->wrap, + col->caption_fieldname, + col->caption_textcolor, + col->image_maxwidth, + col->image_maxheight + ) != 0) + { + mssError(0, "HTTBL", + "Failed to write JS for table column #%d/%d, aka. \"%s\".", + colid + 1, t->ncols, col->wname + ); + goto err; + } } - htrAddScriptInit(s,"null]});\n"); - - htrAddBodyItem_va(s,"
\n",t->id); + /** Null terminate the array, and finish writing the init call. **/ + if (htrAddScriptInit(s, "null]});\n") != 0) + { + mssError(0, "HTTBL", "Failed to write JS to finish init call."); + goto err; + } - detailcnt = wgtrGetMatchingChildList(tree, "widget/table-row-detail", children, sizeof(children)/sizeof(pWgtrNode)); - //for (i=0;iChildren));i++) - for (i=0;i\n", t->id) != 0) { - sub_tree = children[i]; - //sub_tree = xaGetItem(&(tree->Children), i); - // - wgtrGetPropertyValue(sub_tree, "outer_type", DATA_T_STRING,POD(&ptr)); - wgtrGetPropertyValue(sub_tree, "name", DATA_T_STRING,POD(&nptr)); + mssError(0, "HTTBL", "Failed to write HTML opening tag for table container."); + goto err; + } - if (strcmp(ptr, "widget/table-row-detail") == 0) + /** Write the table row detail elements. **/ + /** ANCHOR[id=table-row-detail] **/ + int sub_id = 0; + pWgtrNode children[32]; /** Warning: Large local variable in stack. **/ + const int detail_count = wgtrGetMatchingChildList(tree, "widget/table-row-detail", children, sizeof(children)/sizeof(pWgtrNode)); + for (int i = 0; i < detail_count; i++) + { + pWgtrNode row_detail = children[i]; + + /** Only handle table-row-detail widgets. **/ + wgtrGetPropertyValue(row_detail, "outer_type", DATA_T_STRING,POD(&ptr)); + if (strcmp(ptr, "widget/table-row-detail") != 0) continue; + + /** Prepare for the detail widget. **/ + const int detail_id = sub_id++; + if (htrCheckNSTransition(s, tree, row_detail) != 0) goto err_detail; + + /** Write CSS. **/ + int h; + if (wgtrGetPropertyValue(row_detail, "height", DATA_T_INTEGER, POD(&h)) != 0) h = t->min_rowheight; + if (htrAddStylesheetItem_va(s, + "\t\t#tbld%POSsub%POS { " + "position:absolute; " + "visibility:hidden; " + "left:0px; " + "top:0px; " + "width:"ht_flex_format"; " + "height:"ht_flex_format"; " + "z-index:%POS; " + "}\n", + t->id, detail_id, + ht_flex_w(t->w - (t->demand_scrollbar ? 0 : 18), tree), + ht_flex_h(h, tree), + z + 1 + ) != 0) + { + mssError(0, "HTTBL", "Failed to write HTML opening tag for table container."); + goto err_detail; + } + + /** Write HTML for detail content (aka. sub-widgets). **/ + if (htrAddBodyItem_va(s, "
\n", t->id, detail_id) != 0) + { + mssError(0, "HTTBL", "Failed to write HTML opening tag for table row detail."); + goto err_detail; + } + if (htrRenderSubwidgets(s, row_detail, z + 2) != 0) { - htrCheckNSTransition(s, tree, sub_tree); - - if (wgtrGetPropertyValue(sub_tree,"height",DATA_T_INTEGER,POD(&h)) != 0) h = t->min_rowheight; - htrAddStylesheetItem_va(s,"\t#tbld%POSsub%POS { POSITION:absolute; VISIBILITY:hidden; LEFT:0px; TOP:0px; WIDTH:%POSpx; HEIGHT:%POSpx; Z-INDEX:%POS; } \n", - t->id, ++subcnt, t->w-(t->demand_scrollbar?0:18), h, z+1); - htrAddBodyItem_va(s,"
\n", t->id, subcnt); - htrRenderSubwidgets(s, sub_tree, z+2); - htrAddBodyItem(s,"
\n"); - htrAddWgtrObjLinkage_va(s, sub_tree, "tbld%POSsub%POS", t->id, subcnt); - htrCheckAddExpression(s, sub_tree, nptr, "display_for"); - - htrCheckNSTransitionReturn(s, tree, sub_tree); + mssError(0, "HTTBL", "Failed to write widgets in table row detail."); + goto err_detail; } - //else if (strcmp(ptr,"widget/table-column") != 0) //got columns earlier - //{ - //htrRenderWidget(s, sub_tree, z+3); - //} + if (htrAddBodyItem(s, "
\n") != 0) + { + mssError(0, "HTTBL", "Failed to write HTML closing tag for table container."); + goto err_detail; + } + + /** Link detail widget to container DOM node. **/ + if (htrAddWgtrObjLinkage_va(s, row_detail, "tbld%POSsub%POS", t->id, detail_id)) goto err_detail; + + /** Add 'display_for' data. **/ + if (wgtrGetPropertyValue(row_detail, "name", DATA_T_STRING, POD(&ptr)) != 0) + { + mssError(1, "HTTBL", "Failed to get name of table row detail widget."); + goto err_detail; + } + if (htrCheckAddExpression(s, row_detail, ptr, "display_for") < 0) goto err; + + /** Done. **/ + if (htrCheckNSTransitionReturn(s, tree, row_detail) != 0) goto err; + + /** Success. **/ + continue; + + err_detail: + mssError(0, "HTTBL", "Failed to write HTML opening tag for table container."); + goto err; } - htrRenderSubwidgets(s, tree, z+2); - - htrAddBodyItem(s,"
\n"); - - /** HTML body
element for the scrollbar layer. **/ - htrAddBodyItem_va(s,"
\n",t->id); - htrAddBodyItem(s,"\n"); - htrAddBodyItem(s,"\n"); - htrAddBodyItem_va(s,"\n", t->id, t->h-2*18-first_offset); - htrAddBodyItem(s,"\n"); - htrAddBodyItem(s,"
\n"); - /*htrAddBodyItem_va(s,"
\n",t->id);*/ - htrAddBodyItem_va(s,"
\n",t->id); - htrAddBodyItem(s,"
\n"); - - htrAddEventHandlerFunction(s,"document","MOUSEOVER","tbld","tbld_mouseover"); - htrAddEventHandlerFunction(s,"document","MOUSEOUT","tbld","tbld_mouseout"); - htrAddEventHandlerFunction(s,"document","MOUSEDOWN","tbld","tbld_mousedown"); - htrAddEventHandlerFunction(s, "document","MOUSEMOVE","tbld","tbld_mousemove"); - htrAddEventHandlerFunction(s, "document","MOUSEUP","tbld","tbld_mouseup"); - htrAddEventHandlerFunction(s, "document","WHEEL","tbld","tbld_wheel"); - htrAddEventHandlerFunction(s, "document","KEYDOWN","tbld","tbld_keydown"); - htrAddEventHandlerFunction(s, "document","TOUCHSTART","tbld","tbld_touchstart"); - htrAddEventHandlerFunction(s, "document","TOUCHEND","tbld","tbld_touchend"); - htrAddEventHandlerFunction(s, "document","TOUCHMOVE","tbld","tbld_touchmove"); - htrAddEventHandlerFunction(s, "document","TOUCHCANCEL","tbld","tbld_touchcancel"); - if (s->Capabilities.Dom1HTML) - htrAddEventHandlerFunction(s, "document", "CONTEXTMENU", "tbld", "tbld_contextmenu"); + + /** Render other children. **/ + if (htrRenderSubwidgets(s, tree, z + 2) != 0) goto err; - return 0; + /** Close the base container. **/ + if (htrAddBodyItem(s, "
\n") != 0) + { + mssError(0, "HTTBL", "Failed to write HTML closing tag for table container."); + goto err; + } + + /** Write HTML for the scrollbar. **/ + if (htrAddBodyItem_va(s, + "
\n" + "\n" + "\n" + "\n" + "\n" + "
\n" + "
\n" + "
\n", + t->id, + t->id, ht_flex_h(t->h - row_start_y - 2*18, tree), + t->id + ) != 0) + { + mssError(0, "HTTBL", "Failed to write HTML for table scrollbar."); + goto err; + } + + /** Register event handlers. **/ + if (htrAddEventHandlerFunction(s, "document", "CONTEXTMENU", "tbld", "tbld_contextmenu") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "KEYDOWN", "tbld", "tbld_keydown") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEDOWN", "tbld", "tbld_mousedown") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEMOVE", "tbld", "tbld_mousemove") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEOUT", "tbld", "tbld_mouseout") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEOVER", "tbld", "tbld_mouseover") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEUP", "tbld", "tbld_mouseup") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "TOUCHCANCEL", "tbld", "tbld_touchcancel") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "TOUCHEND", "tbld", "tbld_touchend") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "TOUCHMOVE", "tbld", "tbld_touchmove") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "TOUCHSTART", "tbld", "tbld_touchstart") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "WHEEL", "tbld", "tbld_wheel") != 0) goto err; + + /** Success. **/ + return 0; + + err: + mssError(0, "HTTBL", + "Failed to render \"%s\":\"%s\" (id: %d).", + tree->Name, tree->Type, t->id + ); + return -1; } @@ -288,14 +553,18 @@ httblRender(pHtSession s, pWgtrNode tree, int z) { pWgtrNode sub_tree; char* ptr; - char* nptr; int n, i; httbl_struct* t; int rval; pWgtrNode children[HTTBL_MAX_COLS]; httbl_col* col; - /** Don't try to render table-column, etc. We do that elsewhere **/ + /*** Only render "widget/table". Other widgets (e.g. table-column or + *** table-row-detail) are rendered elsewhere (see links below). + *** + *** LINK #table-column + *** LINK #table-row-detail + ***/ wgtrGetPropertyValue(tree,"outer_type",DATA_T_STRING,POD(&ptr)); if (strcmp(ptr, "widget/table") != 0) return 0; @@ -304,154 +573,131 @@ httblRender(pHtSession s, pWgtrNode tree, int z) if (!t) return -1; memset(t, 0, sizeof(httbl_struct)); - t->x=-1; - t->y=-1; - - /** Get an id for thit. **/ + /** Get an id. **/ t->id = (HTTBL.idcnt++); - /** Backwards compat for the time being **/ - wgtrRenameProperty(tree, "row_bgcolor1", "row1_bgcolor"); - wgtrRenameProperty(tree, "row_background1", "row1_background"); - wgtrRenameProperty(tree, "row_bgcolor2", "row2_bgcolor"); - wgtrRenameProperty(tree, "row_background2", "row2_background"); - wgtrRenameProperty(tree, "row_bgcolorhighlight", "rowhighlight_bgcolor"); - wgtrRenameProperty(tree, "row_backgroundhighlight", "rowhighlight_background"); - - /** Get x,y,w,h of this object **/ - if (wgtrGetPropertyValue(tree,"x",DATA_T_INTEGER,POD(&(t->x))) != 0) t->x = -1; - if (wgtrGetPropertyValue(tree,"y",DATA_T_INTEGER,POD(&(t->y))) != 0) t->y = -1; - if (wgtrGetPropertyValue(tree,"width",DATA_T_INTEGER,POD(&(t->w))) != 0) t->w = -1; - if (wgtrGetPropertyValue(tree,"height",DATA_T_INTEGER,POD(&(t->h))) != 0) + /** Get name. **/ + if (wgtrGetPropertyValue(tree, "name", DATA_T_STRING, POD(&ptr)) != 0) { - mssError(1,"HTTBL","'height' property is required"); + nmFree(t, sizeof(httbl_struct)); return -1; } - if (wgtrGetPropertyValue(tree,"windowsize",DATA_T_INTEGER,POD(&(t->windowsize))) != 0) t->windowsize = -1; - if (wgtrGetPropertyValue(tree,"rowheight",DATA_T_INTEGER,POD(&n)) == 0) + strtcpy(t->name, ptr, sizeof(t->name)); + + /** Get object source path. **/ + if (wgtrGetPropertyValue(tree, "objectsource", DATA_T_STRING, POD(&ptr)) != 0) + strcpy(t->osrc, ""); + else + strtcpy(t->osrc, ptr, sizeof(t->osrc)); + + /** Get the location, size, and layout data. **/ + if (wgtrGetPropertyValue(tree, "x", DATA_T_INTEGER, POD(&(t->x))) != 0) t->x = -1; + if (wgtrGetPropertyValue(tree, "y", DATA_T_INTEGER, POD(&(t->y))) != 0) t->y = -1; + if (wgtrGetPropertyValue(tree, "width", DATA_T_INTEGER, POD(&(t->w))) != 0) t->w = -1; + if (wgtrGetPropertyValue(tree, "height", DATA_T_INTEGER, POD(&(t->h))) != 0) { - t->min_rowheight = t->max_rowheight = n; + mssError(1,"HTTBL","'height' property is required"); + return -1; } - else + if (wgtrGetPropertyValue(tree, "rowheight", DATA_T_INTEGER, POD(&n)) != 0) { t->min_rowheight = s->ClientInfo->ParagraphHeight + 2; t->max_rowheight = -1; } - wgtrGetPropertyValue(tree,"min_rowheight",DATA_T_INTEGER,POD(&(t->min_rowheight))); - wgtrGetPropertyValue(tree,"max_rowheight",DATA_T_INTEGER,POD(&(t->max_rowheight))); - if (wgtrGetPropertyValue(tree,"cellhspacing",DATA_T_INTEGER,POD(&(t->cellhspacing))) != 0) t->cellhspacing = 1; - if (wgtrGetPropertyValue(tree,"cellvspacing",DATA_T_INTEGER,POD(&(t->cellvspacing))) != 0) t->cellvspacing = 1; - - if (wgtrGetPropertyValue(tree,"colsep",DATA_T_INTEGER,POD(&(t->colsep))) != 0) t->colsep = 1; - if (wgtrGetPropertyValue(tree,"colsep_mode",DATA_T_STRING,POD(&ptr)) == 0) + else { - if (!strcasecmp(ptr, "full")) - t->colsep_mode = 0; - else if (!strcasecmp(ptr, "header")) - t->colsep_mode = 1; + t->min_rowheight = t->max_rowheight = n; } - - if (wgtrGetPropertyValue(tree,"rowcache_size",DATA_T_INTEGER,POD(&(t->rowcache_size))) != 0) t->rowcache_size = 0; - - t->dragcols = htrGetBoolean(tree, "dragcols", 1); - t->gridinemptyrows = htrGetBoolean(tree, "gridinemptyrows", 1); + if (wgtrGetPropertyValue(tree, "inner_padding", DATA_T_INTEGER, POD(&(t->inner_padding))) != 0) t->inner_padding = 0; + if (wgtrGetPropertyValue(tree, "min_rowheight", DATA_T_INTEGER, POD(&(t->min_rowheight))) != 0); /* Keep value from above. */ + if (wgtrGetPropertyValue(tree, "max_rowheight", DATA_T_INTEGER, POD(&(t->max_rowheight))) != 0); /* Keep value from above. */ + if (wgtrGetPropertyValue(tree, "cellhspacing", DATA_T_INTEGER, POD(&(t->cellhspacing))) != 0) t->cellhspacing = 1; + if (wgtrGetPropertyValue(tree, "cellvspacing", DATA_T_INTEGER, POD(&(t->cellvspacing))) != 0) t->cellvspacing = 1; + + /** Get selection data. **/ t->allow_selection = htrGetBoolean(tree, "allow_selection", 1); t->show_selection = htrGetBoolean(tree, "show_selection", 1); - if (wgtrGetPropertyType(tree, "initial_selection") == DATA_T_STRING && wgtrGetPropertyValue(tree,"initial_selection",DATA_T_STRING,POD(&ptr)) == 0 && !strcasecmp(ptr,"noexpand")) + if (wgtrGetPropertyType(tree, "initial_selection") == DATA_T_STRING + && wgtrGetPropertyValue(tree, "initial_selection", DATA_T_STRING, POD(&ptr)) == 0 + && strcasecmp(ptr, "noexpand") == 0) t->initial_selection = 2; else t->initial_selection = htrGetBoolean(tree, "initial_selection", 1); - t->allow_deselection = htrGetBoolean(tree, "allow_deselection", t->initial_selection?0:1); - t->reverse_order = htrGetBoolean(tree, "reverse_order", 0); + t->allow_deselection = htrGetBoolean(tree, "allow_deselection", (t->initial_selection) ? 0 : 1); + /** Get scrollbar data. **/ t->overlap_scrollbar = htrGetBoolean(tree, "overlap_scrollbar", 0); t->hide_scrollbar = htrGetBoolean(tree, "hide_scrollbar", 0); t->demand_scrollbar = htrGetBoolean(tree, "demand_scrollbar", 0); - t->has_header = htrGetBoolean(tree, "titlebar", 1); - - /** Which data mode to use? **/ - if (wgtrGetPropertyValue(tree,"data_mode", DATA_T_STRING, POD(&ptr)) == 0) + + /** Get theme data (colors, backgrounds, etc.). **/ + if (wgtrGetPropertyValue(tree, "textcolor", DATA_T_STRING, POD(&ptr)) == 0) + strtcpy(t->textcolor, ptr, sizeof(t->textcolor)); + if (wgtrGetPropertyValue(tree, "textcolorhighlight", DATA_T_STRING, POD(&ptr)) == 0) + strtcpy(t->textcolorhighlight, ptr, sizeof(t->textcolorhighlight)); + if (wgtrGetPropertyValue(tree, "titlecolor", DATA_T_STRING, POD(&ptr)) == 0) + strtcpy(t->titlecolor, ptr, sizeof(t->titlecolor)); + if (!*t->titlecolor) strcpy(t->titlecolor, t->textcolor); + htrGetBackground(tree, "colsep", !s->Capabilities.Dom0NS, t->colsep_bgnd, sizeof(t->colsep_bgnd)); + htrGetBackground(tree, "hdr", !s->Capabilities.Dom0NS, t->hdr_bgnd, sizeof(t->hdr_bgnd)); + htrGetBackground(tree, "newrow", !s->Capabilities.Dom0NS, t->newrow_bgnd, sizeof(t->newrow_bgnd)); + if (wgtrGetPropertyValue(tree, "textcolornew", DATA_T_STRING, POD(&ptr)) == 0) + strtcpy(t->newrow_textcolor, ptr, sizeof(t->newrow_textcolor)); + + /** Get general row data. **/ + if (wgtrGetPropertyValue(tree, "data_mode", DATA_T_STRING, POD(&ptr)) != 0) + t->data_mode = 0; + else { - if (!strcmp(ptr, "rows")) - t->data_mode = 0; - else if (!strcmp(ptr, "properties")) - t->data_mode = 1; + if (strcasecmp(ptr, "rows") == 0) t->data_mode = 0; + else if (strcasecmp(ptr, "properties") == 0) t->data_mode = 1; + else + { + mssError(1, "TBL", "Invalid value for attribute 'data_mode': %s", ptr); + return -1; + } } + t->has_header = htrGetBoolean(tree, "titlebar", 1); /* Whether to render a header row. */ + t->grid_in_empty_rows = htrGetBoolean(tree, "gridinemptyrows", 1); /* Whether to show the grid in empty rows. */ + if (wgtrGetPropertyValue(tree, "windowsize", DATA_T_INTEGER, POD(&(t->windowsize))) != 0) t->windowsize = -1; + if (wgtrGetPropertyValue(tree, "rowcache_size", DATA_T_INTEGER, POD(&(t->rowcache_size))) != 0) t->rowcache_size = 0; - /** Should we follow the current record around? **/ - t->followcurrent = htrGetBoolean(tree, "followcurrent", 1); - - /** Get name **/ - if (wgtrGetPropertyValue(tree,"name",DATA_T_STRING,POD(&ptr)) != 0) + /** Get general column data. **/ + t->dragcols = htrGetBoolean(tree, "dragcols", 1); + if (wgtrGetPropertyValue(tree, "colsep", DATA_T_INTEGER, POD(&(t->colsep))) != 0) t->colsep = 1; + if (wgtrGetPropertyValue(tree, "colsep_mode", DATA_T_STRING, POD(&ptr)) != 0) + t->colsep_mode = 0; + else { - nmFree(t, sizeof(httbl_struct)); - return -1; + if (strcasecmp(ptr, "full") == 0) t->colsep_mode = 0; + else if (strcasecmp(ptr, "header") == 0) t->colsep_mode = 1; + else + { + mssError(1, "TBL", "Invalid value for attribute 'colsep_mode': %s", ptr); + return -1; + } } - strtcpy(t->name,ptr,sizeof(t->name)); - - if (wgtrGetPropertyValue(tree,"objectsource",DATA_T_STRING,POD(&ptr)) == 0) - strtcpy(t->osrc,ptr,sizeof(t->osrc)); - else - strcpy(t->osrc,""); - - /** Get background color/image for table header **/ - htrGetBackground(tree, NULL, !s->Capabilities.Dom0NS, t->tbl_bgnd, sizeof(t->tbl_bgnd)); - - /** Get background color/image for header row **/ - htrGetBackground(tree, "hdr", !s->Capabilities.Dom0NS, t->hdr_bgnd, sizeof(t->hdr_bgnd)); - - /** Get background color/image for rows **/ - htrGetBackground(tree, "row1", !s->Capabilities.Dom0NS, t->row_bgnd1, sizeof(t->row_bgnd1)); - htrGetBackground(tree, "row2", !s->Capabilities.Dom0NS, t->row_bgnd2, sizeof(t->row_bgnd2)); - htrGetBackground(tree, "rowhighlight", !s->Capabilities.Dom0NS, t->row_bgndhigh, sizeof(t->row_bgndhigh)); - htrGetBackground(tree, "colsep", !s->Capabilities.Dom0NS, t->colsep_bgnd, sizeof(t->colsep_bgnd)); - htrGetBackground(tree, "newrow", !s->Capabilities.Dom0NS, t->newrow_bgnd, sizeof(t->newrow_bgnd)); - - /** Get borders and padding information **/ - wgtrGetPropertyValue(tree,"outer_border",DATA_T_INTEGER,POD(&(t->outer_border))); - wgtrGetPropertyValue(tree,"inner_border",DATA_T_INTEGER,POD(&(t->inner_border))); - wgtrGetPropertyValue(tree,"inner_padding",DATA_T_INTEGER,POD(&(t->inner_padding))); - /** Row decorations **/ - wgtrGetPropertyValue(tree, "row_border_color", DATA_T_STRING, POD(&t->row_border)); - wgtrGetPropertyValue(tree, "row_shadow_color", DATA_T_STRING, POD(&t->row_shadow_color)); - wgtrGetPropertyValue(tree, "row_shadow_offset", DATA_T_INTEGER, POD(&t->row_shadow)); - wgtrGetPropertyValue(tree, "row_shadow_radius", DATA_T_INTEGER, POD(&t->row_shadow_radius)); - wgtrGetPropertyValue(tree, "row_border_radius", DATA_T_INTEGER, POD(&t->row_radius)); - - /** Text color information **/ - if (wgtrGetPropertyValue(tree,"textcolor",DATA_T_STRING,POD(&ptr)) == 0) - strtcpy(t->textcolor,ptr,sizeof(t->textcolor)); - - /** Text color information **/ - if (wgtrGetPropertyValue(tree,"textcolorhighlight",DATA_T_STRING,POD(&ptr)) == 0) - strtcpy(t->textcolorhighlight,ptr,sizeof(t->textcolorhighlight)); - - /** Text color information for "new row" in process of being created **/ - if (wgtrGetPropertyValue(tree,"textcolornew",DATA_T_STRING,POD(&ptr)) == 0) - strtcpy(t->newrow_textcolor,ptr,sizeof(t->newrow_textcolor)); - - /** Title text color information **/ - if (wgtrGetPropertyValue(tree,"titlecolor",DATA_T_STRING,POD(&ptr)) == 0) - strtcpy(t->titlecolor,ptr,sizeof(t->titlecolor)); - if (!*t->titlecolor) strcpy(t->titlecolor,t->textcolor); - - /** Get column data **/ + /** Get specific column data for each column. **/ t->ncols = wgtrGetMatchingChildList(tree, "widget/table-column", children, sizeof(children)/sizeof(pWgtrNode)); for (i=0;incols;i++) { sub_tree = children[i]; + + /** Only read table-column widgets. **/ wgtrGetPropertyValue(sub_tree, "outer_type", DATA_T_STRING,POD(&ptr)); - wgtrGetPropertyValue(sub_tree, "name", DATA_T_STRING,POD(&nptr)); - if (!strcmp(ptr,"widget/table-column") != 0) + if (strcmp(ptr, "widget/table-column") == 0) { + /** Allocate space to store the column data. **/ col = (httbl_col*)nmMalloc(sizeof(httbl_col)); memset(col, 0, sizeof(*col)); + + /** Basic column info. **/ t->col_infs[i] = col; strtcpy(col->wname, wgtrGetName(sub_tree), sizeof(col->wname)); strtcpy(col->wnamespace, wgtrGetNamespace(sub_tree), sizeof(col->wnamespace)); - /** no layer associated with this guy **/ + /** The object system doesn't need to render this widget (we will do that). **/ sub_tree->RenderFlags |= HT_WGTF_NOOBJECT; /** Get column properties **/ @@ -470,8 +716,9 @@ httblRender(pHtSession s, pWgtrNode tree, int z) strtcpy(col->title, ptr, sizeof(col->title)); else strtcpy(col->title, col->fieldname, sizeof(col->title)); - htrCheckAddExpression(s, sub_tree, nptr, "title"); - htrCheckAddExpression(s, sub_tree, nptr, "visible"); + wgtrGetPropertyValue(sub_tree, "name", DATA_T_STRING, POD(&ptr)); + htrCheckAddExpression(s, sub_tree, ptr, "title"); + htrCheckAddExpression(s, sub_tree, ptr, "visible"); if (wgtrGetPropertyValue(sub_tree, "align", DATA_T_STRING,POD(&ptr)) == 0) strtcpy(col->align, ptr, sizeof(col->align)); else @@ -489,7 +736,10 @@ httblRender(pHtSession s, pWgtrNode tree, int z) } } + /** Render the table. **/ rval = httblRenderDynamic(s, tree, z, t); + + /** Clean up. **/ for(i=0;incols;i++) nmFree(t->col_infs[i], sizeof(httbl_col)); nmFree(t, sizeof(httbl_struct)); @@ -505,25 +755,26 @@ httblInitialize() { pHtDriver drv; - /** Allocate the driver **/ + /** Allocate the driver struct. **/ drv = htrAllocDriver(); if (!drv) return -1; - /** Fill in the structure. **/ + /** Initialize driver values. **/ strcpy(drv->Name,"DHTML DataTable Driver"); strcpy(drv->WidgetName,"table"); drv->Render = httblRender; xaAddItem(&(drv->PseudoTypes), "table-column"); xaAddItem(&(drv->PseudoTypes), "table-row-detail"); + /** Add driver events. **/ htrAddEvent(drv,"Click"); htrAddEvent(drv,"DblClick"); - /** Register. **/ + /** Register the driver, with dhtml support. **/ htrRegisterDriver(drv); - htrAddSupport(drv, "dhtml"); + /** Initialize the ID counter. **/ HTTBL.idcnt = 0; return 0; diff --git a/centrallix/htmlgen/htdrv_terminal.c b/centrallix/htmlgen/htdrv_terminal.c index a4a8ec44a..9434a9367 100644 --- a/centrallix/htmlgen/htdrv_terminal.c +++ b/centrallix/htmlgen/htdrv_terminal.c @@ -15,7 +15,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -58,27 +58,24 @@ httermRender(pHtSession s, pWgtrNode tree, int z) { char* ptr; char name[64]; - int id; - pWgtrNode sub_tree; #define MAX_COLORS 8 #define MAX_COLOR_LEN 32 char colors[MAX_COLORS][MAX_COLOR_LEN]={"black","red","green","yellow","blue","purple","aqua","white"}; - int i; XString source; int rows, cols, fontsize, x, y; - /** our first Mozilla-only widget :) **/ - if(!s->Capabilities.Dom1HTML) + /** Get an id for this. **/ + const unsigned id = (HTTERM.idcnt++); + + /** Verify browser capabilities. **/ + if (!s->Capabilities.Dom1HTML || !s->Capabilities.Dom2CSS) { - mssError(1,"HTTERM","W3C DOM Level 1 support required"); - return -1; + mssError(1, "HTTERM", "Unsupported browser: W3C DOM1 HTML and DOM2 CSS support required."); + goto err; } - /** Get an id for this. **/ - id = (HTTERM.idcnt++); - /** read the color specs, leaving the defaults if they don't exist **/ - for(i=0;i element to use as the base **/ - htrAddBodyItem_va(s,"
\n",id); - htrAddBodyItem_va(s," \n",id); - htrAddBodyItem_va(s," \n",id); - - /** write the stylesheet header element **/ - htrAddStylesheetItem_va(s," #term%POSbase { POSITION:absolute; VISIBILITY:inherit; LEFT:%INT; TOP:%INT; WIDTH:%POS; HEIGHT:%POS; Z-INDEX:%POS; }\n",id,x,y,cols*fontsize,rows*fontsize,z); - htrAddStylesheetItem_va(s," #term%POSreader { POSITION:absolute; VISIBILITY:hidden; LEFT:0; TOP:0; WIDTH:1; HEIGHT:1; Z-INDEX:-20; }\n",id); - htrAddStylesheetItem_va(s," #term%POSwriter { POSITION:absolute; VISIBILITY:hidden; LEFT:0; TOP:0; WIDTH:1; HEIGHT:1; Z-INDEX:-20; }\n",id); - htrAddStylesheetItem_va(s," .fixed%POS {font-family: fixed; }\n",id); + if (htrAddBodyItem_va(s, + "
" + "" + "\n", + id, id, id + ) != 0) + { + mssError(0, "HTTERM", "Failed to write HTML base, reader, and writer tags."); + goto err; + } - /** init line **/ - htrAddScriptInit_va(s," terminal_init({layer:wgtrGetNodeRef(ns,\"%STR&SYM\"), rdr:\"term%POSreader\", wtr:\"term%POSwriter\", fxd:\"fixed%POS\", source:'%STR&JSSTR', rows:%INT, cols:%INT, colors:new Array(", - name,id,id,id,source.String,rows,cols); - for(i=0;iChildren));i++) + /** Write script initialization. **/ + if (htrAddScriptInit_va(s, + "\tterminal_init({ " + "layer:wgtrGetNodeRef(ns, '%STR&SYM'), " + "rdr:'term%POSreader', " + "wtr:'term%POSwriter', " + "fxd:'fixed%POS', " + "source:'%STR&JSSTR', " + "rows:%INT, " + "cols:%INT, " + "colors:new Array(", + name, id, id, id, + source.String, rows, cols + ) != 0) { - sub_tree = xaGetItem(&(tree->Children), i); - htrRenderWidget(s, sub_tree, z+1); + mssError(0, "HTTERM", "Failed to write JS init call start."); + goto err; } - - return 0; + for (unsigned int i = 0u; i < MAX_COLORS; i++) + { + if (htrAddScriptInit_va(s, "'%STR&JSSTR',", colors[i]) != 0) + { + mssError(0, "HTTERM", "Failed to write terminal color #%d/%d: \"%s\"", i + 1, MAX_COLORS, colors[i]); + goto err; + } + } + if (htrAddScriptInit(s, ")});\n") != 0) + { + mssError(0, "HTTERM", "Failed to write JS init call end."); + goto err; + } + + /** Render children. **/ + if (htrRenderSubwidgets(s, tree, z + 1) != 0) goto err; + + /** Success. **/ + return 0; + + err: + mssError(0, "HTTERM", + "Failed to render \"%s\":\"%s\" (id: %d).", + tree->Name, tree->Type, id + ); + return -1; } diff --git a/centrallix/htmlgen/htdrv_textarea.c b/centrallix/htmlgen/htdrv_textarea.c index bd10c2efb..26de0175b 100644 --- a/centrallix/htmlgen/htdrv_textarea.c +++ b/centrallix/htmlgen/htdrv_textarea.c @@ -14,7 +14,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -60,7 +60,6 @@ httxRender(pHtSession s, pWgtrNode tree, int z) char elementid[16]; //char main_bg[128]; int x=-1,y=-1,w,h; - int id, i; int is_readonly = 0; int is_raised = 0; int mode = 0; /* 0=text, 1=html, 2=wiki */ @@ -69,27 +68,28 @@ httxRender(pHtSession s, pWgtrNode tree, int z) char form[64]; int box_offset; - if(!s->Capabilities.Dom0NS && !s->Capabilities.Dom0IE && !s->Capabilities.Dom2Events) + /** Get an id for this. **/ + const int id = (HTTX.idcnt++); + + /** Verify browser capabilities. **/ + if (!s->Capabilities.Dom1HTML || !s->Capabilities.Dom2CSS) { - mssError(1,"HTTX","Netscape, IE, or Dom2Events support required"); - return -1; + mssError(1, "HTTERM", "Unsupported browser: W3C DOM1 HTML and DOM2 CSS support required."); + goto err; } - /** Get an id for this. **/ - id = (HTTX.idcnt++); - /** Get x,y,w,h of this object **/ if (wgtrGetPropertyValue(tree,"x",DATA_T_INTEGER,POD(&x)) != 0) x=0; if (wgtrGetPropertyValue(tree,"y",DATA_T_INTEGER,POD(&y)) != 0) y=0; if (wgtrGetPropertyValue(tree,"width",DATA_T_INTEGER,POD(&w)) != 0) { mssError(1,"HTTX","Textarea widget must have a 'width' property"); - return -1; + goto err; } if (wgtrGetPropertyValue(tree,"height",DATA_T_INTEGER,POD(&h)) != 0) { mssError(1,"HTTX","Textarea widget must have a 'height' property"); - return -1; + goto err; } /** Maximum characters to accept from the user **/ @@ -107,7 +107,7 @@ httxRender(pHtSession s, pWgtrNode tree, int z) else { mssError(1,"HTTX","Textarea widget 'mode' property must be either 'text','html', or 'wiki'"); - return -1; + goto err; } } @@ -115,7 +115,7 @@ httxRender(pHtSession s, pWgtrNode tree, int z) //htrGetBackground(tree, NULL, 1, main_bg, sizeof(main_bg)); /** Get name **/ - if (wgtrGetPropertyValue(tree,"name",DATA_T_STRING,POD(&ptr)) != 0) return -1; + if (wgtrGetPropertyValue(tree,"name",DATA_T_STRING,POD(&ptr)) != 0) goto err; strtcpy(name,ptr,sizeof(name)); /** Style of Textarea - raised/lowered **/ @@ -139,54 +139,96 @@ httxRender(pHtSession s, pWgtrNode tree, int z) /** Write Style header items. **/ snprintf(elementid, sizeof(elementid), "#tx%dbase", id); - htrFormatElement(s, tree, elementid, 0, - x, y, w-2*box_offset, h-2*box_offset, z, "", - (char*[]){"border_color","#e0e0e0", "border_style",(is_raised?"outset":"inset"), NULL}, - "overflow:hidden; position:absolute;"); - //htrAddStylesheetItem_va(s,"\t#tx%POSbase { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; Z-INDEX:%INT; overflow:hidden; }\n",id,x,y,w-2*box_offset,z); - - /** DOM Linkage **/ - htrAddWgtrObjLinkage_va(s, tree, "tx%POSbase",id); - - /** Global for ibeam cursor layer **/ - htrAddScriptGlobal(s, "text_metric", "null", 0); - htrAddScriptGlobal(s, "tx_current", "null", 0); - htrAddScriptGlobal(s, "tx_cur_mainlayer", "null", 0); - - htrAddScriptInclude(s, "/sys/js/htdrv_textarea.js", 0); - htrAddScriptInclude(s, "/sys/js/ht_utils_layers.js", 0); - htrAddScriptInclude(s, "/sys/js/ht_utils_string.js", 0); - htrAddScriptInclude(s, "/sys/js/ht_utils_cursor.js", 0); - - htrAddEventHandlerFunction(s, "document","MOUSEUP", "tx", "tx_mouseup"); - htrAddEventHandlerFunction(s, "document","MOUSEDOWN", "tx","tx_mousedown"); - htrAddEventHandlerFunction(s, "document","MOUSEOVER", "tx", "tx_mouseover"); - htrAddEventHandlerFunction(s, "document","MOUSEMOVE", "tx", "tx_mousemove"); - htrAddEventHandlerFunction(s, "document","PASTE", "tx", "tx_paste"); + const int offset = box_offset * 2; + if (htrFormatElement(s, tree, elementid, 0, + x, y, w - offset, h - offset, z, "", + (char*[]){"border_color","#e0e0e0", "border_style", (is_raised ? "outset" : "inset"), NULL}, + "position:absolute; " + "overflow:hidden; " + ) != 0) + { + mssError(0, "HTTX", "Failed to write styles."); + goto err; + } + + /** Link DOM node to widget data. **/ + if (htrAddWgtrObjLinkage_va(s, tree, "tx%POSbase", id) != 0) goto err; + + /** Write JS globals. **/ + if (htrAddScriptGlobal(s, "text_metric", "null", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "tx_current", "null", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "tx_cur_mainlayer", "null", 0) != 0) goto err; + + /** Write JS script includes. **/ + if (htrAddScriptInclude(s, "/sys/js/ht_utils_cursor.js", 0) != 0) goto err; + if (htrAddScriptInclude(s, "/sys/js/ht_utils_layers.js", 0) != 0) goto err; + if (htrAddScriptInclude(s, "/sys/js/ht_utils_string.js", 0) != 0) goto err; + if (htrAddScriptInclude(s, "/sys/js/htdrv_textarea.js", 0) != 0) goto err; + + /** Register JS event handlers. **/ + if (htrAddEventHandlerFunction(s, "document", "MOUSEDOWN", "tx", "tx_mousedown") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEMOVE", "tx", "tx_mousemove") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEOVER", "tx", "tx_mouseover") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEUP", "tx", "tx_mouseup") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "PASTE", "tx", "tx_paste") != 0) goto err; /** Script initialization call. **/ - htrAddScriptInit_va(s, " tx_init({layer:wgtrGetNodeRef(ns,\"%STR&SYM\"), fieldname:\"%STR&JSSTR\", form:\"%STR&JSSTR\", isReadonly:%INT, mode:%INT});\n", - name, fieldname, form, is_readonly, mode); + if (htrAddScriptInit_va(s, + "\ttx_init({ " + "layer:wgtrGetNodeRef(ns, '%STR&SYM'), " + "fieldname:'%STR&JSSTR', " + "form:'%STR&JSSTR', " + "isReadonly:%INT, " + "mode:%INT, " + "});\n", + name, fieldname, form, is_readonly, mode + ) != 0) + { + mssError(0, "HTTX", "Failed to JS init call."); + goto err; + } - /** HTML body
element for the base layer. **/ - htrAddBodyItem_va(s, "
\n") != 0) + { + mssError(0, "HTTX", "Failed to write HTML closing tags."); + goto err; + } - /** End the containing layer. **/ - htrAddBodyItem(s, "
\n"); + /** Success. **/ + return 0; - return 0; + err: + mssError(0, "HTTX", + "Failed to render \"%s\":\"%s\" (id: %d).", + tree->Name, tree->Type, id + ); + return -1; } diff --git a/centrallix/htmlgen/htdrv_textbutton.c b/centrallix/htmlgen/htdrv_textbutton.c index 34575df52..0f0148fe9 100644 --- a/centrallix/htmlgen/htdrv_textbutton.c +++ b/centrallix/htmlgen/htdrv_textbutton.c @@ -2,6 +2,7 @@ #include #include #include +#include #include "ht_render.h" #include "obj.h" #include "cxlib/mtask.h" @@ -14,7 +15,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2004 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -64,44 +65,44 @@ httbtnRender(pHtSession s, pWgtrNode tree, int z) char bgstyle[128]; char disable_color[64]; int x,y,w,h; - int id, i; int is_ts = 1; int is_enabled = 1; pExpression code; int box_offset; - //int clip_offset; int border_radius; char border_style[32]; char border_color[64]; char image_position[16]; /* top, left, right, bottom */ char image[OBJSYS_MAX_PATH]; + bool has_image; char h_align[16]; int image_width=0, image_height=0, image_margin=0; - if(!s->Capabilities.Dom0NS && !s->Capabilities.Dom0IE && !(s->Capabilities.Dom1HTML && s->Capabilities.Dom2CSS)) + /** Get an id for this. **/ + const int id = (HTTBTN.idcnt++); + + /** Verify browser capabilities. **/ + if (!s->Capabilities.Dom1HTML || !s->Capabilities.Dom2CSS) { - mssError(1,"HTTBTN","Netscape DOM or (W3C DOM1 HTML and W3C DOM2 CSS) support required"); - return -1; + mssError(1, "HTTERM", "Unsupported browser: W3C DOM1 HTML and DOM2 CSS support required."); + goto err; } - /** Get an id for this. **/ - id = (HTTBTN.idcnt++); - /** Get x,y,w,h of this object **/ if (wgtrGetPropertyValue(tree,"x",DATA_T_INTEGER,POD(&x)) != 0) { mssError(1,"HTTBTN","TextButton widget must have an 'x' property"); - return -1; + goto err; } if (wgtrGetPropertyValue(tree,"y",DATA_T_INTEGER,POD(&y)) != 0) { mssError(1,"HTTBTN","TextButton widget must have a 'y' property"); - return -1; + goto err; } if (wgtrGetPropertyValue(tree,"width",DATA_T_INTEGER,POD(&w)) != 0) { mssError(1,"HTTBTN","TextButton widget must have a 'width' property"); - return -1; + goto err; } if (wgtrGetPropertyValue(tree,"height",DATA_T_INTEGER,POD(&h)) != 0) h = -1; if (wgtrGetPropertyType(tree,"enabled") == DATA_T_STRING && wgtrGetPropertyValue(tree,"enabled",DATA_T_STRING,POD(&ptr)) == 0 && ptr) @@ -137,9 +138,15 @@ httbtnRender(pHtSession s, pWgtrNode tree, int z) /** Image source **/ if (wgtrGetPropertyValue(tree,"image",DATA_T_STRING,POD(&ptr)) != 0) + { strcpy(image, ""); + has_image = false; + } else + { strtcpy(image, ptr, sizeof(image)); + has_image = true; + } /** Image sizing **/ if (wgtrGetPropertyValue(tree,"image_width",DATA_T_INTEGER, POD(&image_width)) != 0) @@ -196,90 +203,178 @@ httbtnRender(pHtSession s, pWgtrNode tree, int z) else strcpy(disable_color,"#808080"); - htrAddScriptGlobal(s, "tb_current", "null", 0); /** DOM Linkages **/ - htrAddWgtrObjLinkage_va(s, tree, "tb%POSpane",id); - - /** Include the javascript code for the textbutton **/ - htrAddScriptInclude(s, "/sys/js/htdrv_textbutton.js", 0); - htrAddScriptInclude(s, "/sys/js/ht_utils_layers.js", 0); - - /** Initial CSS styles **/ - htrAddStylesheetItem_va(s,"\t#tb%POSpane { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; %[HEIGHT:%POSpx; %]WIDTH:%POSpx; Z-INDEX:%POS; OVERFLOW:hidden; display:table; }\n", - id, - x, y, h>=0, h-1-2*box_offset, w-1-2*box_offset, z - ); - htrAddStylesheetItem_va(s, "\t#tb%POSpane .cell { height:100%%; width:100%%; vertical-align:middle; display:table-cell; padding:1px; font-weight:bold; cursor:default; text-align:%STR; border-width:1px; border-style:%STR&CSSVAL; border-color:%STR&CSSVAL; border-radius:%INTpx; color:%STR&CSSVAL; %[text-shadow:1px 1px %STR&CSSVAL; %]%STR }\n", - /* clipping no longer needed: 0, w-1-2*box_offset+2*clip_offset, h-1-2*box_offset+2*clip_offset, 0, */ - id, - h_align, - border_style, border_color, border_radius, - is_enabled?fgcolor1:disable_color, is_enabled, fgcolor2, - bgstyle - ); - - /** CSS for image on the button **/ - if (image[0] && (image_width || image_height || image_margin)) + if (htrAddWgtrObjLinkage_va(s, tree, "tb%POSpane",id) != 0) goto err; + + /** Write JS globals and includes for the textbutton. **/ + if (htrAddScriptGlobal(s, "tb_current", "null", 0) != 0) goto err; + if (htrAddScriptInclude(s, "/sys/js/htdrv_textbutton.js", 0) != 0) goto err; + if (htrAddScriptInclude(s, "/sys/js/ht_utils_layers.js", 0) != 0) goto err; + + /** Calculate size adjustment. **/ + const int adj = (2 * box_offset) + 1; + + /** Write CSS for the container that will hold the button. **/ + if (htrAddStylesheetItem_va(s, + "\t\t#tb%POSpane { " + "position:absolute; " + "visibility:inherit; " + "cursor:pointer; " + "display:table; " + "left:"ht_flex_format"; " + "top:"ht_flex_format"; " + "width:"ht_flex_format"; " + "%[height:"ht_flex_format"; %]" + "z-index:%POS; " + "}\n", + id, + ht_flex_x(x, tree), + ht_flex_y(y, tree), + ht_flex_w(w - adj, tree), + (h >= 0), ht_flex_h(h - adj, tree), + z + ) != 0) { - htrAddStylesheetItem_va(s, "\t#tb%POSpane img { %[height:%POSpx; %]%[width:%POSpx; %]%[margin:%POSpx;%] }\n", - id, - image_height, image_height, - image_width, image_width, - image_margin, image_margin); + mssError(0, "HTTBTN", "Failed to write CSS for main button pane."); + goto err; } - -#if 00 - if(h >=0 ) + + /** Button click animation. **/ + if (is_enabled) { + if (htrAddStylesheetItem_va(s, + "\t\t#tb%POSpane:active { transform: translate(1px, 1px); }\n", + id + ) != 0) + { + mssError(0, "HTTBTN", "Failed to write CSS for button click animation."); + goto err; + } + } + + /** Write CSS for the button content, inside the border. **/ + if (htrAddStylesheetItem_va(s, + "\t\t#tb%POSpane .cell { " + "height:100%%; " + "width:100%%; " + "vertical-align:middle; " + "display:table-cell; " + "padding:1px; " + "font-weight:bold; " + "text-align:%STR; " + "border-width:1px; " + "border-style:%STR&CSSVAL; " + "border-color:%STR&CSSVAL; " + "border-radius:%INTpx; " + "color:%STR&CSSVAL; " + "%[text-shadow:1px 1px %STR&CSSVAL; %]" + "%STR " + "}\n", + id, + h_align, + border_style, + border_color, + border_radius, + (is_enabled) ? fgcolor1 : disable_color, + (is_enabled), fgcolor2, + bgstyle + ) != 0) { - htrAddStylesheetItem_va(s,"\t#tb%POSpane { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; HEIGHT:%POSpx; WIDTH:%POSpx; Z-INDEX:%POS; OVERFLOW:hidden; clip:rect(%INTpx %INTpx %INTpx %INTpx)}\n", id, x, y, h-1-2*box_offset, w-1-2*box_offset, z, 0, w-1-2*box_offset+2*clip_offset, h-1-2*box_offset+2*clip_offset, 0); + mssError(0, "HTTBTN", "Failed to write CSS for button content."); + goto err; } - else + + /** Write CSS for image on the button. **/ + if (has_image && (image_width != 0 || image_height != 0 || image_margin != 0)) { - htrAddStylesheetItem_va(s,"\t#tb%POSpane { POSITION:absolute; VISIBILITY:inherit; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; Z-INDEX:%POS; OVERFLOW:hidden; clip:rect(%INTpx %INTpx auto %INTpx)}\n",id,x,y,w-1-2*box_offset,z,0,w-1-2*box_offset+2*clip_offset,0); + if (htrAddStylesheetItem_va(s, + "\t\t#tb%POSpane img { " + "%[height:%POSpx; %]" + "%[width:%POSpx; %]" + "%[margin:%POSpx; %]" + "}\n", + id, + (image_height != 0), image_height, + (image_width != 0), image_width, + (image_margin != 0), image_margin + ) != 0) + { + mssError(0, "HTTBTN", "Failed to write CSS for button image."); + goto err; + } } - htrAddStylesheetItem_va(s,"\t#tb%POSpane { cursor:default; text-align: center; %STR border-width: 1px; border-style: solid; border-color: white gray gray white; border-radius: %INTpx; }\n",id,bgstyle, border_radius); - if (is_enabled) - htrAddStylesheetItem_va(s,"\t#tb%POSspan { color:%STR&CSSVAL; text-shadow:1px 1px %STR&CSSVAL; }\n", id, fgcolor1, fgcolor2); - else - htrAddStylesheetItem_va(s,"\t#tb%POSspan { color:%STR&CSSVAL; }\n", id, disable_color); -#endif - - //htrAddBodyItem_va(s,"
%STR&HTE
\n", id, h-3, id, text); - //htrAddBodyItem(s, "
"); /** We need two DIVs here because of a long-outstanding Firefox bug :( **/ - htrAddBodyItem_va(s,"
%[
%]%[%]%STR&HTE%[%]%[
%]
", - id, - image[0] && !strcmp(image_position, "top"), image, - image[0] && !strcmp(image_position, "left"), image, - text, - image[0] && !strcmp(image_position, "right"), image, - image[0] && !strcmp(image_position, "bottom"), image - ); + if (htrAddBodyItem_va(s, + "
" + "
" + "%[
%]" + "%[%]" + "%STR&HTE" + "%[%]" + "%[
%]" + "
" + "
", + id, + (has_image && strcmp(image_position, "top") == 0), image, + (has_image && strcmp(image_position, "left") == 0), image, + text, + (has_image && strcmp(image_position, "right") == 0), image, + (has_image && strcmp(image_position, "bottom") == 0), image + ) != 0) + { + mssError(0, "HTTBTN", "Failed to write HTML for text button."); + goto err; + } /** Script initialization call. **/ - //htrAddScriptInit_va(s, " tb_init({layer:wgtrGetNodeRef(ns,'%STR&SYM'), span:document.getElementById(\"tb%POSspan\"), ena:%INT, c1:\"%STR&JSSTR\", c2:\"%STR&JSSTR\", dc1:\"%STR&JSSTR\", top:null, bottom:null, right:null, left:null, width:%INT, height:%INT, tristate:%INT, name:\"%STR&SYM\", text:'%STR&JSSTR'});\n", - //name, id, is_enabled, fgcolor1, fgcolor2, disable_color, w, h, is_ts, name, text); - htrAddScriptInit_va(s, " tb_init({layer:wgtrGetNodeRef(ns,'%STR&SYM'), ena:%INT, c1:\"%STR&JSSTR\", c2:\"%STR&JSSTR\", dc1:\"%STR&JSSTR\", top:null, bottom:null, right:null, left:null, width:%INT, height:%INT, tristate:%INT, name:\"%STR&SYM\", text:'%STR&JSSTR'});\n", - name, is_enabled, fgcolor1, fgcolor2, disable_color, w, h, is_ts, name, text); - - /** Add the event handling scripts **/ - htrAddEventHandlerFunction(s, "document", "MOUSEDOWN", "tb", "tb_mousedown"); - htrAddEventHandlerFunction(s, "document", "MOUSEUP", "tb", "tb_mouseup"); - htrAddEventHandlerFunction(s, "document", "MOUSEOVER", "tb", "tb_mouseover"); - htrAddEventHandlerFunction(s, "document", "MOUSEOUT", "tb", "tb_mouseout"); - htrAddEventHandlerFunction(s, "document", "MOUSEMOVE", "tb", "tb_mousemove"); - - /** IE handles dblclick strangely **/ - if (s->Capabilities.Dom0IE) - htrAddEventHandlerFunction(s, "document", "DBLCLICK", "tb", "tb_dblclick"); - - /** Check for more sub-widgets within the textbutton. **/ - for (i=0;iChildren));i++) - htrRenderWidget(s, xaGetItem(&(tree->Children), i), z+3); + if (htrAddScriptInit_va(s, + "\ttb_init({ " + "layer:wgtrGetNodeRef(ns, '%STR&SYM'), " + "name:'%STR&SYM', " + "text:'%STR&JSSTR', " + "ena:%INT, " + "tristate:%INT, " + "c1:'%STR&JSSTR', " + "c2:'%STR&JSSTR', " + "dc1:'%STR&JSSTR', " + "top:null, " + "bottom:null, " + "right:null, " + "left:null, " + "width:%INT, " + "height:%INT, " + "});\n", + name, name, text, + is_enabled, is_ts, + fgcolor1, fgcolor2, + disable_color, + w, h + ) != 0) + { + mssError(0, "HTTBTN", "Failed to write JS init call."); + goto err; + } - return 0; + /** Add event handlers. **/ + if (htrAddEventHandlerFunction(s, "document", "MOUSEDOWN", "tb", "tb_mousedown") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEMOVE", "tb", "tb_mousemove") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEOUT", "tb", "tb_mouseout") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEOVER", "tb", "tb_mouseover") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEUP", "tb", "tb_mouseup") != 0) goto err; + + /** Render children. **/ + if (htrRenderSubwidgets(s, tree, z + 1) != 0) goto err; + + /** Success. **/ + return 0; + + err: + mssError(0, "HTTBTN", + "Failed to render \"%s\":\"%s\" (id: %d).", + tree->Name, tree->Type, id + ); + return -1; } diff --git a/centrallix/htmlgen/htdrv_timer.c b/centrallix/htmlgen/htdrv_timer.c index 9ebb381f6..ce8c64620 100644 --- a/centrallix/htmlgen/htdrv_timer.c +++ b/centrallix/htmlgen/htdrv_timer.c @@ -14,7 +14,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -43,15 +43,6 @@ /************************************************************************/ -/** globals **/ -static struct - { - int idcnt; - } - HTTM; - - - /*** httmRender - generate the HTML code for the timer nonvisual widget. ***/ int @@ -59,25 +50,22 @@ httmRender(pHtSession s, pWgtrNode tree, int z) { char* ptr; char name[64]; - int id; int msec; int auto_reset = 0; int auto_start = 1; - if(!s->Capabilities.Dom0NS && !s->Capabilities.Dom1HTML) + /** Verify browser capabilities. **/ + if (!s->Capabilities.Dom1HTML || !s->Capabilities.Dom2CSS) { - mssError(1,"HTTM","Netscape 4.x or W3C DOM support required"); - return -1; + mssError(1, "HTTERM", "Unsupported browser: W3C DOM1 HTML and DOM2 CSS support required."); + goto err; } - /** Get an id for this. **/ - id = (HTTM.idcnt++); - /** Get msec for timer countdown **/ if (wgtrGetPropertyValue(tree,"msec",DATA_T_INTEGER,POD(&msec)) != 0) { mssError(1,"HTTM","Timer widget must have 'msec' time specified"); - return -1; + goto err; } /** Get auto reset and auto start settings **/ @@ -85,18 +73,39 @@ httmRender(pHtSession s, pWgtrNode tree, int z) if (wgtrGetPropertyValue(tree,"auto_start",DATA_T_INTEGER,POD(&auto_start)) != 0) auto_start = 1; /** Get name **/ - if (wgtrGetPropertyValue(tree,"name",DATA_T_STRING,POD(&ptr)) != 0) return -1; + if (wgtrGetPropertyValue(tree,"name",DATA_T_STRING,POD(&ptr)) != 0) goto err; strtcpy(name,ptr,sizeof(name)); - htrAddScriptInclude(s, "/sys/js/htdrv_timer.js", 0); + /** Write JS include. **/ + if (htrAddScriptInclude(s, "/sys/js/htdrv_timer.js", 0) != 0) goto err; /** Script initialization call. **/ - htrAddScriptInit_va(s, " tm_init({node:wgtrGetNodeRef(ns,\"%STR&SYM\"), time:%INT, autoreset:%INT, autostart:%INT});\n", name, msec, auto_reset, auto_start); + if (htrAddScriptInit_va(s, + "\ttm_init({ " + "node:wgtrGetNodeRef(ns, '%STR&SYM'), " + "time:%INT, " + "autoreset:%INT, " + "autostart:%INT, " + "});\n", + name, msec, auto_reset, auto_start + ) != 0) + { + mssError(0, "HTTM", "Failed to write JS init call."); + goto err; + } - /** Check for objects within the timer. **/ - htrRenderSubwidgets(s, tree, z+2); + /** Render children. **/ + if (htrRenderSubwidgets(s, tree, z + 2) != 0) goto err; - return 0; + /** Success. **/ + return 0; + + err: + mssError(0, "HTTM", + "Failed to render \"%s\":\"%s\".", + tree->Name, tree->Type + ); + return -1; } @@ -132,7 +141,5 @@ httmInitialize() htrAddSupport(drv, "dhtml"); - HTTM.idcnt = 0; - return 0; } diff --git a/centrallix/htmlgen/htdrv_treeview.c b/centrallix/htmlgen/htdrv_treeview.c index 413a2123c..18ad351a9 100644 --- a/centrallix/htmlgen/htdrv_treeview.c +++ b/centrallix/htmlgen/htdrv_treeview.c @@ -14,7 +14,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -69,43 +69,42 @@ httreeRender(pHtSession s, pWgtrNode tree, int z) char hfgcolor[64]; char selected_bg[128]; int x,y,w; - int id, i; int show_root = 1; int show_branches = 1; int show_root_branch = 1; int use_3d_lines; int order_desc = 0; - pWgtrNode sub_tree; - if(!s->Capabilities.Dom0NS && !s->Capabilities.Dom0IE && !(s->Capabilities.Dom1HTML && s->Capabilities.Dom2CSS)) + /** Get an id for this. **/ + const int id = (HTTREE.idcnt++); + + /** Verify browser capabilities. **/ + if (!s->Capabilities.Dom1HTML || !s->Capabilities.Dom2CSS) { - mssError(1,"HTTREE","Netscape DOM or W3C DOM1 HTML and DOM2 CSS support required"); - return -1; + mssError(1, "HTTERM", "Unsupported browser: W3C DOM1 HTML and DOM2 CSS support required."); + goto err; } - /** Get an id for this. **/ - id = (HTTREE.idcnt++); - /** Get x,y,w,h of this object **/ if (wgtrGetPropertyValue(tree,"x",DATA_T_INTEGER,POD(&x)) != 0) { mssError(1,"HTTREE","TreeView widget must have an 'x' property"); - return -1; + goto err; } if (wgtrGetPropertyValue(tree,"y",DATA_T_INTEGER,POD(&y)) != 0) { mssError(1,"HTTREE","TreeView widget must have a 'y' property"); - return -1; + goto err; } if (wgtrGetPropertyValue(tree,"width",DATA_T_INTEGER,POD(&w)) != 0) { mssError(1,"HTTREE","TreeView widget must have a 'width' property"); - return -1; + goto err; } /** Are we showing root of tree or the trunk? **/ show_root = htrGetBoolean(tree, "show_root", 1); - if (show_root < 0) return -1; + if (show_root < 0) goto err; /** How about branches? (branch decorations, etc.) **/ show_branches = htrGetBoolean(tree, "show_branches", 1); @@ -136,7 +135,7 @@ httreeRender(pHtSession s, pWgtrNode tree, int z) } /** Get name **/ - if (wgtrGetPropertyValue(tree,"name",DATA_T_STRING,POD(&ptr)) != 0) return -1; + if (wgtrGetPropertyValue(tree,"name",DATA_T_STRING,POD(&ptr)) != 0) goto err; strtcpy(name,ptr,sizeof(name)); /** Selected item background color **/ @@ -165,76 +164,134 @@ httreeRender(pHtSession s, pWgtrNode tree, int z) if (wgtrGetPropertyValue(tree,"source",DATA_T_STRING,POD(&ptr)) != 0) { mssError(1,"HTTREE","TreeView widget must have a 'source' property"); - return -1; + goto err; } strtcpy(src,ptr,sizeof(src)); - /** Ok, write the style header items. **/ - if (s->Capabilities.Dom0NS) + /** Write CSS. **/ + if (htrAddStylesheetItem_va(s, + "\t\t#tv%POSload { " + "position:absolute; " + "visibility:hidden; " + "overflow:hidden; " + "left:0px; " + "top:0px; " + "width:0px; " + "height:0px; " + "z-index:0; " + "}\n", + id + ) != 0) { - htrAddStylesheetItem_va(s,"\t#tv%POSroot { POSITION:absolute; VISIBILITY:%STR; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; Z-INDEX:%POS; }\n",id,show_root?"inherit":"hidden",x,y,w,z); + mssError(0, "HTTREE", "Failed to write treeview loader CSS."); + goto err; + } + if (htrAddStylesheetItem_va(s, + "\t\tdiv.tv%POS a { %[color:%STR&CSSVAL;%] }\n" + "\t\tdiv.tv%POSh a { %[color:%STR&CSSVAL;%] }\n" + "\t\t.tv%POS { cursor:pointer; }\n", + id, (*fgcolor), fgcolor, + id, (*hfgcolor), hfgcolor, + id + ) != 0) + { + mssError(0, "HTTREE", "Failed to write treeview entry CSS."); + goto err; } - htrAddStylesheetItem_va(s,"\t#tv%POSload { POSITION:absolute; VISIBILITY:hidden; OVERFLOW:hidden; LEFT:0px; TOP:0px; WIDTH:0px; HEIGHT:0px; clip:rect(0px,0px,0px,0px); Z-INDEX:0; }\n",id); - htrAddStylesheetItem_va(s,"\tdiv.tv%POS a { %[color:%STR&CSSVAL;%] }\n", id, *fgcolor, fgcolor); - htrAddStylesheetItem_va(s,"\tdiv.tv%POSh a { %[color:%STR&CSSVAL;%] }\n", id, *hfgcolor, hfgcolor); - /** Write globals for internal use **/ - htrAddScriptGlobal(s, "tv_tgt_layer", "null", 0); - htrAddScriptGlobal(s, "tv_target_img","null",0); - htrAddScriptGlobal(s, "tv_layer_cache","null",0); - htrAddScriptGlobal(s, "tv_alloc_cnt","0",0); - htrAddScriptGlobal(s, "tv_cache_cnt","0",0); + /** Link the widget to the DOM node. **/ + if (htrAddWgtrObjLinkage_va(s, tree, "tv%POSroot", id) != 0) goto err; - /** DOM Linkage on client **/ - htrAddWgtrObjLinkage_va(s, tree, "tv%POSroot",id); + /** Write globals for internal use **/ + if (htrAddScriptGlobal(s, "tv_alloc_cnt", "0", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "tv_cache_cnt", "0", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "tv_layer_cache", "null", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "tv_target_img", "null", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "tv_tgt_layer", "null", 0) != 0) goto err; - /** Script initialization call. **/ - htrAddScriptInit_va(s," tv_init({layer:wgtrGetNodeRef(ns,\"%STR&SYM\"), fname:\"%STR&JSSTR\", loader:htr_subel(wgtrGetParentContainer(wgtrGetNodeRef(ns,\"%STR&SYM\")),\"tv%POSload\"), width:%INT, newroot:null, branches:%INT, use3d:%INT, showrb:%INT, icon:\"%STR&JSSTR\", divclass:\"tv%POS\", sbg:\"%STR&JSSTR\", desc:%INT});\n", - name, src, name, id, w, show_branches, use_3d_lines, show_root_branch, icon, id, selected_bg, order_desc); + /** Write JS script includes. **/ + if (htrAddScriptInclude(s, "/sys/js/ht_utils_info.js", 0) != 0) goto err; + if (htrAddScriptInclude(s, "/sys/js/ht_utils_string.js", 0) != 0) goto err; + if (htrAddScriptInclude(s, "/sys/js/htdrv_treeview.js", 0) != 0) goto err; - /** Script includes **/ - htrAddScriptInclude(s, "/sys/js/htdrv_treeview.js", 0); - htrAddScriptInclude(s, "/sys/js/ht_utils_string.js", 0); - htrAddScriptInclude(s, "/sys/js/ht_utils_info.js", 0); + /** Add the event handling scripts **/ + if (htrAddEventHandlerFunction(s, "document", "CLICK", "tv", "tv_click") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEDOWN", "tv", "tv_mousedown") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEMOVE", "tv", "tv_mousemove") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEOUT", "tv", "tv_mouseout") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEOVER", "tv", "tv_mouseover") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEUP", "tv", "tv_mouseup") != 0) goto err; - /** HTML body
elements for the layers. **/ - if (s->Capabilities.Dom0NS) + /** Script initialization call. **/ + htrAddScriptInit_va(s, "\t{ " + "const layer = wgtrGetNodeRef(ns, '%STR&SYM'); " + "tv_init({ " + "layer, " + "loader:htr_subel(wgtrGetParentContainer(layer),'tv%POSload'), " + "divclass:'tv%POS', " + "fname:'%STR&JSSTR', " + "width:%INT, " + "newroot:null, " + "branches:%INT, " + "use3d:%INT, " + "showrb:%INT, " + "icon:'%STR&JSSTR', " + "sbg:'%STR&JSSTR', " + "desc:%INT, " + "}); }\n", + name, id, id, src, w, + show_branches, use_3d_lines, show_root_branch, + icon, selected_bg, order_desc + ); + + /** Write HTML. **/ + if (htrAddBodyItem_va(s, + "
" + "folder" + " %STR&HTE" + "
\n", + id, /* class */ + id, /* id */ + (show_root) ? "inherit" : "hidden", + ht_flex_x(x, tree), + ht_flex_y(y, tree), + ht_flex_w(w, tree), + z, + (*icon) ? icon : "/sys/images/ico02b.gif", src + ) != 0) { - htrAddBodyItem_va(s, "
 %STR&HTE
\n", id, id, (*icon)?icon:"/sys/images/ico02b.gif", src); - htrAddBodyItem_va(s, "
\n",id); + mssError(0, "HTTREE", "Failed to write HTML for treeview root."); + goto err; } - else + if (htrAddBodyItemLayer_va(s, HTR_LAYER_F_DYNAMIC, "tv%POSload", id, NULL, "") != 0) { - htrAddBodyItem_va(s, "
 %STR&HTE
\n",id,id,show_root?"inherit":"hidden",x,y,w,z,(*icon)?icon:"/sys/images/ico02b.gif", src); - htrAddBodyItemLayer_va(s, HTR_LAYER_F_DYNAMIC, "tv%POSload", id, NULL, ""); - /*htrAddBodyItem_va(s, "
\n",id);*/ + mssError(0, "HTTREE", "Failed to write HTML for treeview loader."); + goto err; } - /** Event handler for click-on-url **/ - htrAddEventHandlerFunction(s, "document","CLICK","tv","tv_click"); + /** Render children. **/ + if (htrRenderSubwidgets(s, tree, z + 2) != 0) goto err; - /** Add the event handling scripts **/ - htrAddEventHandlerFunction(s, "document","MOUSEDOWN","tv","tv_mousedown"); - htrAddEventHandlerFunction(s, "document","MOUSEUP","tv","tv_mouseup"); - htrAddEventHandlerFunction(s,"document","MOUSEOVER","tv","tv_mouseover"); - htrAddEventHandlerFunction(s,"document","MOUSEMOVE","tv","tv_mousemove"); - htrAddEventHandlerFunction(s,"document","MOUSEOUT","tv", "tv_mouseout"); - - /** Check for more sub-widgets within the treeview. **/ - for (i=0;iChildren));i++) - { - sub_tree = xaGetItem(&(tree->Children), i); - /*if (wgtrGetPropertyValue(sub_tree, "outer_type", DATA_T_STRING, POD(&ptr)) == 0 && !strcmp(ptr, "widget/osrc-rule")) - { - httree_internal_AddRule(s, tree, name, sub_tree); - } - else - {*/ - htrRenderWidget(s, sub_tree, z+2); - /*}*/ - } + /** Success. **/ + return 0; - return 0; + err: + mssError(0, "HTTM", + "Failed to render \"%s\":\"%s\".", + tree->Name, tree->Type + ); + return -1; } diff --git a/centrallix/htmlgen/htdrv_uawindow.c b/centrallix/htmlgen/htdrv_uawindow.c index 9f967c1e6..b6cfd90d7 100644 --- a/centrallix/htmlgen/htdrv_uawindow.c +++ b/centrallix/htmlgen/htdrv_uawindow.c @@ -14,7 +14,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1999-2010 LightSys Technology Services, Inc. */ +/* Copyright (C) 1999-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -41,20 +41,11 @@ /************************************************************************/ -/** globals **/ -static struct - { - int idcnt; - } - HTUAWIN; - - /*** htuawinRender - generate the HTML code for the page. ***/ int htuawinRender(pHtSession s, pWgtrNode tree, int z) { - int id; char name[64]; int is_shared = 0; int is_multi = 0; @@ -64,9 +55,6 @@ htuawinRender(pHtSession s, pWgtrNode tree, int z) int height = 480; char* ptr; - /** Get an id for this. **/ - id = (HTUAWIN.idcnt++); - /** Get name **/ if (wgtrGetPropertyValue(tree,"name",DATA_T_STRING,POD(&ptr)) != 0) return -1; strtcpy(name,ptr,sizeof(name)); @@ -81,9 +69,11 @@ htuawinRender(pHtSession s, pWgtrNode tree, int z) /** Share this window with other references in the same session context? (no) **/ is_shared = htrGetBoolean(tree, "shared", 0); + if (is_shared < 0) goto err; /** Allow multiple instances of the window? (no) **/ is_multi = htrGetBoolean(tree, "multiple_instantiation", 0); + if (is_multi < 0) goto err; /** Routing of actions to windows when there are multiple of them **/ if (wgtrGetPropertyValue(tree, "action_routing", DATA_T_STRING, POD(&ptr)) == 0) @@ -93,26 +83,46 @@ htuawinRender(pHtSession s, pWgtrNode tree, int z) else if (!strcmp(ptr, "all")) action_routing = 2; else { - mssError(1, "HTUAWIN", "Invalid action_routing '%s' for widget '%s'", ptr, name); - return -1; + mssError(1, "HTUAWIN", "Invalid action_routing: \"%s\"", ptr); + goto err; } } /** widget init **/ - htrAddScriptInit_va(s, " uw_init(wgtrGetNodeRef(ns,\"%STR&SYM\"), {shared:%INT, multi:%INT, routing:%INT, path:\"%STR&JSSTR\", w:%INT, h:%INT} );\n", - name, is_shared, is_multi, action_routing, path, width, height - ); + if (htrAddScriptInit_va(s, + "\tuw_init(wgtrGetNodeRef(ns, '%STR&SYM'), { " + "shared:%INT, " + "multi:%INT, " + "routing:%INT, " + "path:'%STR&JSSTR', " + "w:%INT, " + "h:%INT, " + "});\n", + name, is_shared, is_multi, action_routing, path, width, height + ) != 0) + { + mssError(1, "HTUAWIN", "Failed to write JS init call."); + goto err; + } /** JavaScript include file **/ - htrAddScriptInclude(s, "/sys/js/htdrv_uawindow.js", 0); + if (htrAddScriptInclude(s, "/sys/js/htdrv_uawindow.js", 0) != 0) goto err; - /** object linkages **/ - htrAddWgtrCtrLinkage(s, tree, "_parentctr"); + /** Link the widget to its container. **/ + if (htrAddWgtrCtrLinkage(s, tree, "_parentctr") != 0) goto err; - /** Check for more sub-widgets within the vbl entity. **/ - htrRenderSubwidgets(s, tree, z+2); + /** Render children. **/ + if (htrRenderSubwidgets(s, tree, z + 2) != 0) goto err; - return 0; + /** Success. **/ + return 0; + + err: + mssError(0, "HTUAWIN", + "Failed to render \"%s\":\"%s\".", + tree->Name, tree->Type + ); + return -1; } @@ -137,7 +147,5 @@ htuawinInitialize() htrAddSupport(drv, "dhtml"); - HTUAWIN.idcnt = 0; - return 0; } diff --git a/centrallix/htmlgen/htdrv_variable.c b/centrallix/htmlgen/htdrv_variable.c index 81e0953dd..0766d3b42 100644 --- a/centrallix/htmlgen/htdrv_variable.c +++ b/centrallix/htmlgen/htdrv_variable.c @@ -14,7 +14,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1999-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1999-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -42,14 +42,6 @@ /************************************************************************/ -/** globals **/ -static struct - { - int idcnt; - } - HTVBL; - - /*** htvblRender - generate the HTML code for the page. ***/ int @@ -59,16 +51,12 @@ htvblRender(pHtSession s, pWgtrNode tree, int z) char name[64]; char fieldname[HT_FIELDNAME_SIZE]; char form[64]; - int t; - int id, i; + int rval = -1; int n = 0; char* vptr = NULL; int is_null = 1; pExpression code; - /** Get an id for this. **/ - id = (HTVBL.idcnt++); - /** Get name **/ if (wgtrGetPropertyValue(tree,"name",DATA_T_STRING,POD(&ptr)) != 0) return -1; strtcpy(name,ptr,sizeof(name)); @@ -84,7 +72,7 @@ htvblRender(pHtSession s, pWgtrNode tree, int z) form[0]='\0'; /** Value of label **/ - t = wgtrGetPropertyType(tree,"value"); + int t = wgtrGetPropertyType(tree,"value"); if (t < 0 || t >= OBJ_TYPE_NAMES_CNT) { t = DATA_T_ANY; @@ -106,29 +94,51 @@ htvblRender(pHtSession s, pWgtrNode tree, int z) } /** widget init **/ - htrAddScriptInit_va(s, " vbl_init(wgtrGetNodeRef(ns,\"%STR&SYM\"), {type:\"%STR&JSSTR\", value:%[null%]%[\"%STR&JSSTR\"%]%[%INT%], field:\"%STR&JSSTR\", form:\"%STR&JSSTR\"} );\n", - name, - obj_type_names[t], - is_null, - (!is_null) && t == DATA_T_STRING, vptr, - (!is_null) && t == DATA_T_INTEGER, n, - fieldname, form); + if (htrAddScriptInit_va(s, + "\tvbl_init(wgtrGetNodeRef(ns, '%STR&SYM'), { " + "type:'%STR&JSSTR', " + "value:%['%STR&JSSTR'%]%[%INT%]%[null%], " + "field:'%STR&JSSTR', " + "form:'%STR&JSSTR', " + "});\n", + name, + obj_type_names[t], + (!is_null && t == DATA_T_STRING), vptr, + (!is_null && t == DATA_T_INTEGER), n, + (is_null), + fieldname, form + ) != 0) + { + mssError(1, "HTVBL", "Failed to write JS init call."); + goto end; + } /** JavaScript include file **/ - htrAddScriptInclude(s, "/sys/js/htdrv_variable.js", 0); - htrAddScriptInclude(s, "/sys/js/ht_utils_hints.js", 0); + if (htrAddScriptInclude(s, "/sys/js/ht_utils_hints.js", 0) != 0) goto end; + if (htrAddScriptInclude(s, "/sys/js/htdrv_variable.js", 0) != 0) goto end; /** object linkages **/ - htrAddWgtrCtrLinkage(s, tree, "_parentctr"); + if (htrAddWgtrCtrLinkage(s, tree, "_parentctr") != 0) goto end; /** Check for more sub-widgets within the vbl entity. **/ - for (i=0;iChildren));i++) - htrRenderWidget(s, xaGetItem(&(tree->Children), i), z+2); + if (htrRenderSubwidgets(s, tree, z + 2) != 0) goto end; - if (vptr) - nmSysFree(vptr); + /** Success. **/ + rval = 0; - return 0; + end: + if (rval != 0) + { + mssError(0, "HTVBL", + "Failed to render \"%s\":\"%s\".", + tree->Name, tree->Type + ); + } + + /** Clean up. **/ + if (vptr != NULL) nmSysFree(vptr); + + return rval; } @@ -155,7 +165,5 @@ htvblInitialize() htrAddSupport(drv, "dhtml"); - HTVBL.idcnt = 0; - return 0; } diff --git a/centrallix/htmlgen/htdrv_window.c b/centrallix/htmlgen/htdrv_window.c index a02da42bb..9dd5ed38d 100644 --- a/centrallix/htmlgen/htdrv_window.c +++ b/centrallix/htmlgen/htdrv_window.c @@ -3,8 +3,10 @@ #include #include #include + #include "ht_render.h" #include "obj.h" +#include "cxlib/util.h" #include "cxlib/mtask.h" #include "cxlib/xarray.h" #include "cxlib/xhash.h" @@ -15,7 +17,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1999-2014 LightSys Technology Services, Inc. */ +/* Copyright (C) 1999-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -59,46 +61,37 @@ htwinRender(pHtSession s, pWgtrNode tree, int z) { char* ptr; char name[64]; - pWgtrNode sub_tree; - int x,y,w,h; - int tbw,tbh,bx,by,bw,bh; - int id, i; int visible = 1; - char bgnd_style[128] = ""; - char hdr_bgnd_style[128] = ""; - char txtcolor[64] = ""; + char background_style[128] = ""; + char header_background_style[128] = ""; + char text_color[64] = ""; int has_titlebar = 1; char title[128]; int is_dialog_style = 0; int gshade = 0; int closetype = 0; - int box_offset = 1; int is_toplevel = 0; int is_modal = 0; char icon[128]; - int shadow_offset,shadow_radius,shadow_angle; + int shadow_offset, shadow_radius, shadow_angle; char shadow_color[128]; int border_radius; char border_color[128]; char border_style[32]; int border_width; - if(!(s->Capabilities.Dom0NS || s->Capabilities.Dom1HTML)) - { - mssError(1,"HTWIN","Netscape DOM support or W3C DOM Level 1 support required"); - return -1; - } + /** Get an id for this. **/ + const int id = (HTWIN.idcnt++); - /** IE puts css box borders inside box width/height **/ - if (!s->Capabilities.CSSBox) + /** Verify browser capabilities. **/ + if (!s->Capabilities.Dom1HTML || !s->Capabilities.Dom2CSS) { - box_offset = 0; + mssError(1, "HTTERM", "Unsupported browser: W3C DOM1 HTML and DOM2 CSS support required."); + goto err; } - /** Get an id for this. **/ - id = (HTWIN.idcnt++); - /** Get x,y,w,h of this object **/ + int x, y, w, h; if (wgtrGetPropertyValue(tree,"x",DATA_T_INTEGER,POD(&x)) != 0) x = 0; if (wgtrGetPropertyValue(tree,"y",DATA_T_INTEGER,POD(&y)) != 0) y = 0; if (wgtrGetPropertyValue(tree,"width",DATA_T_INTEGER,POD(&w)) != 0) @@ -156,17 +149,17 @@ htwinRender(pHtSession s, pWgtrNode tree, int z) is_modal = htrGetBoolean(tree, "modal", 0); /** Check background color **/ - htrGetBackground(tree, NULL, 1, bgnd_style, sizeof(bgnd_style)); + htrGetBackground(tree, NULL, 1, background_style, sizeof(background_style)); /** Check header background color/image **/ - if (htrGetBackground(tree, "hdr", 1, hdr_bgnd_style, sizeof(hdr_bgnd_style)) < 0) - strcpy(hdr_bgnd_style, bgnd_style); + if (htrGetBackground(tree, "hdr", 1, header_background_style, sizeof(header_background_style)) < 0) + strcpy(header_background_style, background_style); /** Check title text color. **/ if (wgtrGetPropertyValue(tree,"textcolor",DATA_T_STRING,POD(&ptr)) == 0) - strtcpy(txtcolor,ptr,sizeof(txtcolor)); + strtcpy(text_color, ptr, sizeof(text_color)); else - strcpy(txtcolor,"black"); + strcpy(text_color, "black"); /** Check window title. **/ if (wgtrGetPropertyValue(tree,"title",DATA_T_STRING,POD(&ptr)) == 0) @@ -208,142 +201,240 @@ htwinRender(pHtSession s, pWgtrNode tree, int z) } /** Compute titlebar width & height - includes edge below titlebar. **/ - if (has_titlebar) - { - tbw = w-2; - if (is_dialog_style || !s->Capabilities.Dom0NS) - tbh = 24; - else - tbh = 23; - } - else - { - tbw = w-2; - tbh = 0; - } + int title_bar_height = (has_titlebar) ? ((is_dialog_style) ? 24 : 23) : 0; - /** Compute window body geometry **/ - if (is_dialog_style) + /** Draw the main window layer and outer edge. **/ + /*** We don't even bother making these styles flex responsively because + *** they will be overwritten by the JS anyway. + ***/ + if (htrAddStylesheetItem_va(s, + "\t\t#wn%POSbase { " + "position:absolute; " + "visibility:%STR; " + "overflow:hidden; " + "left:%INTpx; " + "top:%INTpx; " + "width:%POSpx; " + "height:%POSpx; " + "z-index:%POS; " + "border-style:%STR&CSSVAL; " + "border-width:%INTpx; " + "border-color:%STR&CSSVAL; " + "border-radius:%INTpx; " + "}\n", + id, + (visible) ? "inherit" : "hidden", + x, y, w, h, + z + 100, + border_style, + border_width, + border_color, + border_radius + ) != 0) { - bx = 1; - by = 1+tbh; - bw = w-2; - bh = h-tbh-2; + mssError(0, "HTWIN", "Failed to write CSS for window base."); + goto err; } - else + if (shadow_radius > 0) { - bx = 2; - bw = w-4; - if (has_titlebar) + double shadow_angle_radians = (double)shadow_angle * M_PI/180; + if (htrAddStylesheetItem_va(s, + "\t\t#wn%POSbase { " + "box-shadow: " + "%DBLpx " + "%DBLpx " + "%POSpx " + "%STR&CSSVAL; " + "}\n", id, + sin(shadow_angle_radians) * shadow_offset, + cos(shadow_angle_radians) * (-shadow_offset), + shadow_radius, + shadow_color + ) != 0) { - by = 1+tbh; - bh = h-tbh-3; + mssError(0, "HTWIN", "Failed to write CSS for window shadow."); + goto err; } - else - { - by = 2; - bh = h-4; - } - } - - /** Draw the main window layer and outer edge. **/ - /*htrAddStylesheetItem_va(s,"\t#wn%POSbase { POSITION:absolute; VISIBILITY:%STR; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; overflow: hidden; clip:rect(0px, %INTpx, %INTpx, 0px); Z-INDEX:%POS;}\n", - id,visible?"inherit":"hidden",x,y,w-2*box_offset,h-2*box_offset, w, h, z+100);*/ - htrAddStylesheetItem_va(s,"\t#wn%POSbase { POSITION:absolute; VISIBILITY:%STR; LEFT:%INTpx; TOP:%INTpx; WIDTH:%POSpx; HEIGHT:%POSpx; overflow: hidden; Z-INDEX:%POS;}\n", - id,visible?"inherit":"hidden",x,y,w-2*box_offset,h-2*box_offset, z+100); - htrAddStylesheetItem_va(s,"\t#wn%POSbase { border-style: %STR&CSSVAL; border-width: %INTpx; border-color: %STR&CSSVAL; border-radius: %INTpx; }\n", - id, border_style, border_width, border_color, border_radius); - if (shadow_radius > 0) - { - htrAddStylesheetItem_va(s,"\t#wn%POSbase { box-shadow: %DBLpx %DBLpx %POSpx %STR&CSSVAL; }\n", id, - sin(shadow_angle*M_PI/180)*shadow_offset, cos(shadow_angle*M_PI/180)*(-shadow_offset), shadow_radius, shadow_color); - } - - /** draw titlebar div **/ - if (has_titlebar) - { - htrAddStylesheetItem_va(s,"\t#wn%POStitlebar { POSITION: absolute; VISIBILITY: inherit; LEFT: 0px; TOP: 0px; HEIGHT: %POSpx; WIDTH: 100%%; overflow: hidden; Z-INDEX: %POS; color:%STR&CSSVAL; cursor:default; %STR}\n", id, tbh-1-box_offset, z+1, txtcolor, hdr_bgnd_style); - htrAddStylesheetItem_va(s,"\t#wn%POStitlebar { border-style: solid; border-width: 0px 0px 1px 0px; border-color: gray; }\n", id); } /** inner structure depends on dialog vs. window style **/ + int main_width, main_height, clip_height, dialogue_width; + char* border_color_str; if (is_dialog_style) { /** window inner container -- dialog **/ - htrAddStylesheetItem_va(s,"\t#wn%POSmain { POSITION:absolute; VISIBILITY:inherit; LEFT:0px; TOP:%INTpx; WIDTH: %POSpx; HEIGHT:%POSpx; overflow: hidden; clip:rect(0px, %INTpx, %INTpx, 0px); Z-INDEX:%POS; %STR}\n", - id, tbh?(tbh-1):0, w-2, h-tbh-1, w, h-tbh+1, z+1, bgnd_style); - htrAddStylesheetItem_va(s,"\t#wn%POSmain { border-style: solid; border-width: %POSpx 0px 0px 0px; border-color: white; }\n", id, has_titlebar?1:0); + main_width = w - 2; + main_height = h - title_bar_height - 1; + clip_height = h - title_bar_height + 1; + dialogue_width = 0; + border_color_str = "white"; } else { /** window inner container -- window **/ - htrAddStylesheetItem_va(s,"\t#wn%POSmain { POSITION:absolute; VISIBILITY:inherit; LEFT:0px; TOP:%INTpx; WIDTH: %POSpx; HEIGHT:%POSpx; overflow: hidden; clip:rect(0px, %INTpx, %INTpx, 0px); Z-INDEX:%POS; %STR}\n", - id, tbh?(tbh-1):0, w-2-2*box_offset, h-tbh-(has_titlebar?1:2)-(has_titlebar?1:2)*box_offset, w, h-tbh+(has_titlebar?1:0)-2*box_offset, z+1, bgnd_style); - htrAddStylesheetItem_va(s,"\t#wn%POSmain { border-style: solid; border-width: %POSpx 1px 1px 1px; border-color: gray white white gray; }\n", id, has_titlebar?0:1); + main_width = w - 2; + main_height = h - title_bar_height - 1 * ((has_titlebar) ? 1 : 2); + clip_height = h - title_bar_height + ((has_titlebar) ? 1 : 0); + dialogue_width = 1; + border_color_str = "gray white white gray"; + } + if (htrAddStylesheetItem_va(s, + "\t\t#wn%POSmain { " + "position:absolute; " + "visibility:inherit; " + "overflow:hidden; " + "left:0px; " + "top:%INTpx; " + "width:%POSpx; " + "height:%POSpx; " + "clip:rect(0px, %INTpx, %INTpx, 0px); " + "border-style:solid; " + "border-color:%STR; " + "border-width:%POSpx %POSpx %POSpx %POSpx; " + "z-index:%POS; " + "%STR" + "}\n", + id, + max(title_bar_height - 1, 0), + main_width, + main_height, + w, clip_height, + border_color_str, + (has_titlebar) ? 1 : 0, dialogue_width, dialogue_width, dialogue_width, + z + 1, + background_style + ) != 0) + { + mssError(0, "HTWIN", "Failed to write CSS for window main container."); + goto err; } - /** Write globals for internal use **/ - htrAddScriptGlobal(s, "wn_top_z","10000",0); - htrAddScriptGlobal(s, "wn_list","[]",0); - htrAddScriptGlobal(s, "wn_current","null",0); - htrAddScriptGlobal(s, "wn_newx","null",0); - htrAddScriptGlobal(s, "wn_newy","null",0); - htrAddScriptGlobal(s, "wn_topwin","null",0); - htrAddScriptGlobal(s, "wn_msx","null",0); - htrAddScriptGlobal(s, "wn_msy","null",0); - htrAddScriptGlobal(s, "wn_moved","0",0); - htrAddScriptGlobal(s, "wn_clicked","0",0); + /** Write JS globals and includes. **/ + if (htrAddScriptGlobal(s, "wn_clicked", "0", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "wn_current", "null", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "wn_list", "[]", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "wn_moved", "0", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "wn_msx", "null", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "wn_msy", "null", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "wn_newx", "null", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "wn_newy", "null", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "wn_top_z", "10000", 0) != 0) goto err; + if (htrAddScriptGlobal(s, "wn_topwin", "null", 0) != 0) goto err; + if (htrAddScriptInclude(s, "/sys/js/ht_utils_layers.js", 0) != 0) goto err; + if (htrAddScriptInclude(s, "/sys/js/htdrv_window.js", 0) != 0) goto err; /** DOM Linkages **/ - htrAddWgtrObjLinkage_va(s, tree, "wn%POSbase",id); - htrAddWgtrCtrLinkage_va(s, tree, "htr_subel(_obj, \"wn%POSmain\")",id); - - htrAddScriptInclude(s, "/sys/js/htdrv_window.js", 0); - htrAddScriptInclude(s, "/sys/js/ht_utils_layers.js", 0); + if (htrAddWgtrObjLinkage_va(s, tree, "wn%POSbase", id) != 0) goto err; + if (htrAddWgtrCtrLinkage_va(s, tree, "htr_subel(_obj, 'wn%POSmain')", id) != 0) goto err; /** Event handler for mousedown/up/click/etc **/ - htrAddEventHandlerFunction(s, "document", "MOUSEDOWN", "wn", "wn_mousedown"); - htrAddEventHandlerFunction(s, "document", "MOUSEUP", "wn", "wn_mouseup"); - htrAddEventHandlerFunction(s, "document", "DBLCLICK", "wn", "wn_dblclick"); + if (htrAddEventHandlerFunction(s, "document", "DBLCLICK", "wn", "wn_dblclick") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEDOWN", "wn", "wn_mousedown") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEUP", "wn", "wn_mouseup") != 0) goto err; /** Mouse move event handler -- when user drags the window **/ - htrAddEventHandlerFunction(s, "document", "MOUSEMOVE", "wn", "wn_mousemove"); - htrAddEventHandlerFunction(s, "document", "MOUSEOVER", "wn", "wn_mouseover"); - htrAddEventHandlerFunction(s, "document", "MOUSEOUT", "wn", "wn_mouseout"); + if (htrAddEventHandlerFunction(s, "document", "MOUSEMOVE", "wn", "wn_mousemove") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEOVER", "wn", "wn_mouseover") != 0) goto err; + if (htrAddEventHandlerFunction(s, "document", "MOUSEOUT", "wn", "wn_mouseout") != 0) goto err; /** Script initialization call. **/ - if (has_titlebar) + if (htrAddScriptInit_va(s, + "\twn_init({ " + "mainlayer:wgtrGetNodeRef(ns, '%STR&SYM'), " + "clayer:wgtrGetContainer(wgtrGetNodeRef(ns, '%STR&SYM')), " + "titlebar:%[htr_subel(wgtrGetNodeRef(ns, '%STR&SYM'), 'wn%POStitlebar')%]%[null%], " + "gshade:%INT, " + "closetype:%INT, " + "toplevel:%INT, " + "modal:%INT, " + "});\n", + name, + name, + has_titlebar, name, id, !has_titlebar, + gshade, + closetype, + is_toplevel, + is_modal + ) != 0) { - htrAddScriptInit_va(s," wn_init({mainlayer:wgtrGetNodeRef(ns,'%STR&SYM'), clayer:wgtrGetContainer(wgtrGetNodeRef(ns,'%STR&SYM')), gshade:%INT, closetype:%INT, toplevel:%INT, modal:%INT, titlebar:htr_subel(wgtrGetNodeRef(ns,'%STR&SYM'),'wn%POStitlebar')});\n", - name,name,gshade,closetype, is_toplevel, is_modal, name, id); + mssError(0, "HTWIN", "Failed to write JS init call."); + goto err; } - else + + /** Write HTML for the child window. **/ + if (htrAddBodyItem_va(s, "
\n", id) != 0) { - htrAddScriptInit_va(s," wn_init({mainlayer:wgtrGetNodeRef(ns,'%STR&SYM'), clayer:wgtrGetNodeRef(ns,'%STR&SYM'), gshade:%INT, closetype:%INT, toplevel:%INT, modal:%INT, titlebar:null});\n", - name,name,gshade,closetype, is_toplevel, is_modal); + mssError(0, "HTWIN", "Failed to write HTML for window container."); + goto err; } - - /** HTML body
elements for the layers. **/ - htrAddBodyItem_va(s,"
\n",id); if (has_titlebar) { - htrAddBodyItem_va(s,"
\n",id); - htrAddBodyItem_va(s,"
 %STR&HTE
\n", - tbh-1, tbw-2, icon, txtcolor, title); - htrAddBodyItem(s, "
\n"); + /** Write CSS and HTML for the title bar. **/ + if (htrAddStylesheetItem_va(s, + "\t\t#wn%POStitlebar { " + "position:absolute; " + "visibility:inherit; " + "overflow:hidden; " + "cursor:grab;" + "left:0px; " + "top:0px; " + "height:%POSpx; " + "width:100%%; " + "z-index:%POS; " + "color:%STR&CSSVAL; " + "border-style:solid; " + "border-width:0px 0px 1px 0px; " + "border-color:gray; " + "%STR" + "}\n", + id, + title_bar_height - 1, + z + 1, + text_color, + header_background_style + ) != 0) + { + mssError(0, "HTWIN", "Failed to write styles for window title bar."); + goto err; + } + if (htrAddBodyItem_va(s, + "
" + "" + "
%STR&HTE
" + "" + "
\n", + id, + icon, + text_color, title + ) != 0) + { + mssError(0, "HTWIN", "Failed to write HTML for window title bar."); + goto err; + } } - htrAddBodyItem_va(s,"
\n",id); - /** Check for more sub-widgets within the page. **/ - for (i=0;iChildren));i++) + /** Render child widgets inside window container. **/ + if (htrAddBodyItem_va(s,"
\n",id) != 0) { - sub_tree = xaGetItem(&(tree->Children), i); - htrRenderWidget(s, sub_tree, z+2); + mssError(0, "HTWIN", "Failed to write HTML opening tag for window container."); + goto err; + } + if (htrRenderSubwidgets(s, tree, z + 2) != 0) goto err; + if (htrAddBodyItem(s,"
\n") != 0) + { + mssError(0, "HTWIN", "Failed to write HTML closing tag for window container."); + goto err; } - htrAddBodyItem(s,"
\n"); + /** Success. **/ + return 0; - return 0; + err: + mssError(0, "HTWIN", + "Failed to render \"%s\":\"%s\".", + tree->Name, tree->Type + ); + return -1; } diff --git a/centrallix/include/apos.h b/centrallix/include/apos.h index a40d3d26d..e10461e32 100644 --- a/centrallix/include/apos.h +++ b/centrallix/include/apos.h @@ -5,7 +5,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -51,6 +51,14 @@ struct _APOS_L XArray CWidgets; //widgets that cross the line pAposSection SSection; // section starting with this line pAposSection ESection; // section ending with this line + + /*** Used to find the distance the line should move when the parent + *** container is resized. loc_fl (local flex) is the weight that this + *** line moves relative to the container. my_fl (my flex) is the amount + *** that this line moves relative to the line before (left of) it. + *** Used for generating responsive CSS. + ***/ + float loc_fl, my_fl; }; /**Section Structure (used for both rows and columns)**/ @@ -84,7 +92,7 @@ int aposInit(); /**Registers datastructures used in auto-positioning**/ int aposInitiallizeGrid (pAposGrid); /**Initiallizes the XArrays in the grid object**/ int aposFree(pAposGrid); /**Frees dynamically allocated memory**/ int aposFreeGrids(pWgtrNode); /**Frees dynamically allocated memory**/ -int aposSetOffsetBools(pWgtrNode, int*, int*, int*, int*, int*); /**sets bools used to offset widgets**/ +int aposSetOffsetBools(pWgtrNode, int*, int*, int*, int*, int*, int*); /**sets bools used to offset widgets**/ int aposBuildGrid(pWgtrNode); /** builds the layout grids **/ int aposSetLimits(pWgtrNode); /** enforce min/max sizing **/ @@ -112,27 +120,54 @@ int aposMinimumChildFlex(pAposLine, int); /**Returns minimum flexibility of wid /**Resizing and Repositioning**/ int aposSpaceOutLines(pXArray, pXArray, int); /**Adjusts spaces between lines to expand or contract grid**/ -int aposSnapWidgetsToGrid(pXArray, int); /**Refreshes widget dimensions to match adjusted grid**/ +int aposSnapWidgetsToGrid(pXArray, int, pWgtrClientInfo); /**Refreshes widget dimensions to match adjusted grid**/ int aposProcessWindows(pWgtrNode, pWgtrNode); /**Makes a pass through the tree to process windows**/ -/** # defines **/ + +/** #define names for values to improve readability. **/ + +/** Indicates how a line links to a widget. **/ +#define APOS_NOT_LINKED 0 #define APOS_SWIDGETS 1 #define APOS_EWIDGETS 2 +/** Indicates if a line is vertical. **/ +#define APOS_VERTICAL 1 +#define APOS_HORIZONTAL 0 + +/*** Indicates if a section or line is a row (horizontal) or a column (vertical). + *** A row spans horizontally between two vertical lines, and a column spans + *** vertically between two horizontal lines. + ***/ #define APOS_ROW 1 #define APOS_COL 2 +/** Indicates if a line is a border. **/ +#define APOS_IS_BORDER 1 +#define APOS_NOT_BORDER 0 + +/** Allows rounding when casting floats or doubles to ints. **/ #define APOS_FUDGEFACTOR 0.5 -/** The greatest width between two widgets that still defines them as "adjacent," -*** indicating that we don't want to increase the distance between them **/ +/*** The greatest width between two widgets that still defines them as + *** "adjacent," indicating that we don't want to increase the distance + *** between them. Therefore, a section of this size or less is considered + *** a "spacer" which will not be resized (aka. flex = 0). + ***/ #define APOS_MINSPACE 20 -/**Lowest acceptable width or height for a widget**/ +/** The lowest acceptable width or height for a widget. **/ #define APOS_MINWIDTH 30 -/**Default flexibilities for widgetless gaps in expanding or contracting applications **/ +/** Default flexibilities for widgetless gaps in expanding or contracting applications. **/ +/*** Israel: I don't know the difference between these two values. I'm guessing + *** E stands enlarge and C stands for contract, thus (30, 50) makes it + *** easier for gaps to grown than for them to shrink. + ***/ #define APOS_EGAPFLEX 30 #define APOS_CGAPFLEX 50 +/** Macros for readability and anticipation-of-change. **/ +#define isScrollpane(Parent) (!strcmp((Parent)->Type, "widget/scrollpane")) + #endif diff --git a/centrallix/include/cxss/cxss.h b/centrallix/include/cxss/cxss.h index 98fa11af1..21ff01569 100644 --- a/centrallix/include/cxss/cxss.h +++ b/centrallix/include/cxss/cxss.h @@ -42,11 +42,23 @@ #include "cxlib/xarray.h" #include "cxlib/xstring.h" +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + #define CXSS_ENTROPY_SIZE 1280 -#define CXSS_DEBUG_CONTEXTSTACK 1 #define CXSS_IDENTIFIER_LENGTH 64 #define CXSS_CIPHER_STRENGTH (256 / 8) +#ifdef USING_OPTIMIZATION +/*** Context stack debugging can cause seg-faults if the compiler applies + *** certain optimizations. + ***/ +#define CXSS_DEBUG_CONTEXTSTACK 0 +#else +#define CXSS_DEBUG_CONTEXTSTACK 1 +#endif + #include "cxss/policy.h" /*** CXSS data structures ***/ @@ -170,4 +182,3 @@ int cxssAuthorizeSpec(char* objectspec, int access_type, int log_mode); int cxssAuthorize(char* domain, char* type, char* path, char* attr, int access_type, int log_mode); #endif /* not defined _CXSS_H */ - diff --git a/centrallix/include/cxss/policy.h b/centrallix/include/cxss/policy.h index aeee11ce8..19535b939 100644 --- a/centrallix/include/cxss/policy.h +++ b/centrallix/include/cxss/policy.h @@ -2,12 +2,13 @@ #define _CXSS_POLICY_H #include "cxss/cxss.h" +#include "obj.h" /************************************************************************/ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2007 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -89,4 +90,3 @@ typedef struct _CXSSPOL CxssPolicy, *pCxssPolicy; #endif /* defined _CXSS_POLICY_H */ - diff --git a/centrallix/include/double_metaphone.h b/centrallix/include/double_metaphone.h new file mode 100644 index 000000000..5f07de7f6 --- /dev/null +++ b/centrallix/include/double_metaphone.h @@ -0,0 +1,83 @@ +#ifndef DOUBLE_METAPHONE_H +#define DOUBLE_METAPHONE_H + +/************************************************************************/ +/* Text-DoubleMetaphone */ +/* Centrallix Base Library */ +/* */ +/* Copyright 2000, Maurice Aubrey . */ +/* All rights reserved. */ +/* */ +/* This code is copied for redistribution with modification, from the */ +/* gitpan/Text-DoubleMetaphone implementation on GitHub (1), which is */ +/* under the following license. */ +/* */ +/* This code is based heavily on the C++ implementation by Lawrence */ +/* Philips and incorporates several bug fixes courtesy of Kevin */ +/* Atkinson . */ +/* */ +/* This module is free software; you may redistribute it and/or */ +/* modify it under the same terms as Perl itself. */ +/* */ +/* A summary of the relevant content from https://dev.perl.org/licenses */ +/* has been included below for the convenience of the reader. This */ +/* information was collected and saved on September 5th, 2025 and may */ +/* differ from current information. For the most up to date copy of */ +/* this information, please use the link provided above. */ +/* */ +/* Perl5 is Copyright © 1993 and later, by Larry Wall and others. */ +/* */ +/* It is free software; you can redistribute it and/or modify it */ +/* under the terms of either: */ +/* */ +/* a) the GNU General Public License (2) as published by the Free */ +/* Software Foundation (3); either version 1 (2), or (at your */ +/* option) any later version (4), or */ +/* */ +/* b) the "Artistic License" (5). */ +/* */ +/* Citations: */ +/* 1: https://github.com/gitpan/Text-meta_double_metaphone */ +/* 2: https://dev.perl.org/licenses/gpl1.html */ +/* 3: http://www.fsf.org */ +/* 4: http://www.fsf.org/licenses/licenses.html#GNUGPL */ +/* 5: https://dev.perl.org/licenses/artistic.html */ +/* */ +/* Centrallix is published under the GNU General Public License, */ +/* satisfying the above requirement. A summary of this is included */ +/* below for the convenience of the reader. */ +/* */ +/* This program is free software; you can redistribute it and/or modify */ +/* it under the terms of the GNU General Public License as published by */ +/* the Free Software Foundation; either version 2 of the License, or */ +/* (at your option) any later version. */ +/* */ +/* This program is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU General Public License for more details. */ +/* */ +/* You should have received a copy of the GNU General Public License */ +/* along with this program; if not, write to the Free Software */ +/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA */ +/* 02111-1307 USA */ +/* */ +/* A copy of the GNU General Public License has been included in this */ +/* distribution in the file "COPYING". */ +/* */ +/* Module: double_metaphone.c, double_metaphone.h */ +/* Author: Maurice Aubrey and Israel Fuller */ +/* Description: This module implements a "sounds like" algorithm by */ +/* Lawrence Philips which he published in the June, 2000 */ +/* issue of C/C++ Users Journal. Double Metaphone is an */ +/* improved version of the original Metaphone algorithm */ +/* written by Philips'. This implementation was written by */ +/* Maurice Aubrey for C/C++ with bug fixes provided by */ +/* Kevin Atkinson. It was revised by Israel Fuller to */ +/* better align with the Centrallix coding style and */ +/* standards so that it could be included here. */ +/************************************************************************/ + +int meta_double_metaphone(const char* str, char** primary_code, char** secondary_code); + +#endif /* End of .h file. */ diff --git a/centrallix/include/expression.h b/centrallix/include/expression.h index 8d506f72e..800a90fd5 100644 --- a/centrallix/include/expression.h +++ b/centrallix/include/expression.h @@ -5,7 +5,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1999-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1999-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ diff --git a/centrallix/include/ht_render.h b/centrallix/include/ht_render.h index aeded08fe..7e88aa1e3 100644 --- a/centrallix/include/ht_render.h +++ b/centrallix/include/ht_render.h @@ -5,7 +5,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -347,6 +347,7 @@ int htrAddBodyItemLayerEnd(pHtSession s, int flags); /** Administrative functions **/ int htrRegisterDriver(pHtDriver drv); int htrInitialize(); +char* htrGetErrorHTML(char* title); int htrRender(void* stream, int (*stream_write)(void*, char*, int, int, int), pObjSession s, pWgtrNode tree, pStruct params, pWgtrClientInfo c_info); int htrWrite(pHtSession s, char* buf, int len); int htrQPrintf(pHtSession s, char* fmt, ...); @@ -362,5 +363,95 @@ int htrBuildClientWgtr(pHtSession s, pWgtrNode tree); /** For the rule module... **/ int htruleRegister(char* ruletype, ...); -#endif /* _HT_RENDER_H */ +/*** Write strings known at compile time slightly more efficiently. + *** + *** @param str A string literal known at compile time. This must be a + *** literal value and it may be applied multiple times. + ***/ +#define htrWriteConst(s, str) htrWrite(s, (str), sizeof(str) - 1) + +/** ===================================================== **/ +/** Define macros for implementing responsive dimensions. **/ +/** ===================================================== **/ + +/*** Brief explanation of the responsiveness formula. + *** + *** Responsive dimensions in widgets use the following formula: + *** Original px + (100% - Total px) * Flex + *** Where "Original" is the original size of the object in an adaptive layout, + *** "Total" is the total size of the widget's parent container, and + *** "Flex" is the widget flexibility (where all flexibilities add to 1). + *** All, with respect to the given dimension. + *** + *** The intuition behind this formula is that (100% - Total px) is 0px + *** if the parent container is the size intended by the adaptive design. + *** However, if the user resizes the window, (100% - Total px) is the + *** difference between the size in the adaptive design and the current + *** size, so the widget changes size with respect to that difference. + ***/ + +/** @brief The qprintf format to specify a responsive dimension. **/ +#define ht_flex_format "calc(%INTpx + (100%% - %INTpx) * %DBL)" + +/*** @brief The function which generates the values that should be passed to + *** qprintf in order to satisfy an ht_flex_format. + *** + *** @param size The original size of the ui element. + *** @param total The total size of the ui element's container. + *** @param flex The flexibility of the ui element. It is strongly recommended + *** to generate this with an ht_get_fl function call. + *** @returns Several values to serve as parameters for a qprintf call. + ***/ +#define ht_flex(size, total, flex) (size), (total), (flex) + +/** ====[ Macros for getting total container size ]==== **/ +int ht_get_parent_w__INTERNAL(pWgtrNode widget); +int ht_get_parent_h__INTERNAL(pWgtrNode widget); + +#define ht_get_parent_w(widget) ht_get_parent_w__INTERNAL(widget) +#define ht_get_parent_h(widget) ht_get_parent_h__INTERNAL(widget) + +/** ====[ Macros for getting total flexibilities ]==== **/ + +/*** @param widget The widget to be queried. + *** @returns The flexibility of the widget in the x direction. + ***/ +#define ht_get_fl_x(widget) ((widget)->fl_scale_x) + +/*** @param widget The widget to be queried. + *** @returns The flexibility of the widget in the y direction. + ***/ +#define ht_get_fl_y(widget) ((widget)->fl_scale_y) + +/*** @param widget The widget to be queried. + *** @returns The flexibility of the widget in the width direction. + ***/ +#define ht_get_fl_w(widget) ((widget)->fl_scale_w) + +/*** @param widget The widget to be queried. + *** @returns The flexibility of the widget in the height direction. + ***/ +#define ht_get_fl_h(widget) ((widget)->fl_scale_h) + +/*** @brief A shortcut function to get the flexibility when writing the + *** LEFT CSS attribute. + *** @param widget The widget to be queried. + *** @returns The flexibility of the widget in the left direction. + ***/ +#define ht_get_fl_l ht_get_fl_x + +/*** @brief A shortcut function to get the flexibility when writing the + *** TOP CSS attribute. + *** @param widget The widget to be queried. + *** @returns The flexibility of the widget in the top direction. + ***/ +#define ht_get_fl_t ht_get_fl_y + +/** ====[ Macros for being lazy ]==== **/ + +#define ht_flex_x(x, widget) ht_flex(x, ht_get_parent_w(widget), ht_get_fl_x(widget)) +#define ht_flex_y(y, widget) ht_flex(y, ht_get_parent_h(widget), ht_get_fl_y(widget)) +#define ht_flex_w(w, widget) ht_flex(w, ht_get_parent_w(widget), ht_get_fl_w(widget)) +#define ht_flex_h(h, widget) ht_flex(h, ht_get_parent_h(widget), ht_get_fl_h(widget)) +#endif /* _HT_RENDER_H */ diff --git a/centrallix/include/obj.h b/centrallix/include/obj.h index c781c1d5e..7caf87990 100644 --- a/centrallix/include/obj.h +++ b/centrallix/include/obj.h @@ -307,7 +307,7 @@ typedef struct _OA #define OBJ_INFO_F_NO_CONTENT (1<<13) /* object does not have content, objRead() would fail */ #define OBJ_INFO_F_SUPPORTS_INHERITANCE (1<<14) /* object can support inheritance attr cx__inherit, etc. */ #define OBJ_INFO_F_FORCED_LEAF (1<<15) /* object is forced to be a 'leaf' unless ls__type used. */ -#define OBJ_INFO_F_TEMPORARY (1<<16) /* this is a temporary object without a vaoid pathname. */ +#define OBJ_INFO_F_TEMPORARY (1<<16) /* this is a temporary object without a valid pathname. */ /** object virtual attribute - these are attributes which persist only while @@ -631,6 +631,34 @@ typedef struct #define OBJ_MQ_F_NOUPDATE (1<<1) /* disallow any updates in this query */ #define OBJ_MQ_F_ONEROW (1<<2) /* only need first row from results */ +/** @returns The file name for the provided object. **/ +#define objFileName(obj) \ + ({ \ + __typeof__ (obj) _obj = (obj); \ + obj_internal_PathPart(_obj->Pathname, _obj->SubPtr - 1, 1); \ + }) +/** @returns The file path to the provided object. **/ +#define objFilePath(obj) \ + ({ \ + __typeof__ (obj) _obj = (obj); \ + obj_internal_PathPart(_obj->Pathname, 0, _obj->SubPtr); \ + }) + +/*** An array of the names of the general attributes that must be implemented + *** for every object system driver. See `OSDriver_Authoring.md` for more + *** information. + ***/ +#define DRIVER_ATTRIBUTE_NAMES \ + ((char*[]){ \ + "name", \ + "annotation", \ + "content_type", \ + "inner_type", \ + "outer_type", \ + "last_modification", \ + }) +#define N_DRIVER_ATTRIBUTE_NAMES ((unsigned int)(sizeof(DRIVER_ATTRIBUTE_NAMES) / sizeof(DRIVER_ATTRIBUTE_NAMES[0]))) + /** objectsystem main functions **/ int objInitialize(); @@ -737,6 +765,8 @@ void obj_internal_OpenCtlToString(pPathname pathinfo, int pathstart, int pathend int obj_internal_PathToText(pPathname pathinfo, int pathend, pXString str); /** objectsystem datatype functions **/ +int objTypeFromStr(const char* str); +char* objTypeToStr(const int type); int objDataToString(pXString dest, int data_type, void* data_ptr, int flags); double objDataToDouble(int data_type, void* data_ptr); int objDataToInteger(int data_type, void* data_ptr, char* format); diff --git a/centrallix/include/stparse.h b/centrallix/include/stparse.h index b19a77ccf..a941b6a37 100644 --- a/centrallix/include/stparse.h +++ b/centrallix/include/stparse.h @@ -5,7 +5,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -46,7 +46,7 @@ typedef struct _SI int Magic; int LinkCnt; char* Name; /* name of attrib or group */ - char* UsrType; /* type of group, null if attrib */ + char* UsrType; /* type of group (e.g. "system/object"), null if attrib */ pExpression Value; /* value; EXPR_N_LIST if several listed */ struct _SI* Parent; /* Parent inf, null if toplevel */ struct _SI** SubInf; /* List of attrs/groups included */ diff --git a/centrallix/include/wgtr.h b/centrallix/include/wgtr.h index 99f8c3760..a6d20de83 100644 --- a/centrallix/include/wgtr.h +++ b/centrallix/include/wgtr.h @@ -5,7 +5,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1999-2006 LightSys Technology Services, Inc. */ +/* Copyright (C) 1999-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -69,6 +69,7 @@ typedef struct int CharWidth; int CharHeight; int ParagraphHeight; /* total height of one line of text */ + int IsDesign; /* 1 if the page is rendered with cx__geom=design, 0 otherwise. */ char AKey[256]; char* Templates[WGTR_MAX_TEMPLATE]; char* Overlays[WGTR_MAX_OVERLAY]; @@ -100,14 +101,17 @@ typedef struct _WN char Namespace[64]; /** Namespace this widget and subwidgets are in **/ int r_x, r_y, r_width, r_height; /** Requested geometry **/ int pre_x, pre_y, pre_width, pre_height; /** pre-layout geom. **/ - int fl_x, fl_y, fl_width, fl_height;/** Flexibility **/ - double fx, fy, fw, fh; /** internal flexibility calculations **/ + int fl_x, fl_y, fl_width, fl_height;/** Flexibilities as specified by the designer **/ + double fx, fy, fw, fh; /** internal flexibility calculations **/ + double fl_scale_x, fl_scale_y; /** Scaled x and y flexibilities calculated for this layout. */ + double fl_scale_w, fl_scale_h; /** Scaled w and h flexibilities calculated for this layout. */ + int fl_parent_w, fl_parent_h; /** The expected size of the parent container, used when it flexes. */ int min_width, min_height; /** absolute minimums **/ int x, y, width, height; /** actual geometry **/ int top, bottom, left, right; /** container offsets **/ XArray Properties; /** Array of widget properties **/ XArray Children; /** Array of child widgets **/ - struct _WN* Parent; + struct _WN* Parent; struct _WN* Root; int CurrProperty; /** Property to return on next call to wgtNextProperty **/ int CurrChild; /** Child to return on next call to wgtrNextChild **/ diff --git a/centrallix/multiquery/multiq_orderby.c b/centrallix/multiquery/multiq_orderby.c index 9403df897..739795772 100644 --- a/centrallix/multiquery/multiq_orderby.c +++ b/centrallix/multiquery/multiq_orderby.c @@ -543,7 +543,6 @@ mqobFinish(pQueryElement qe, pQueryStatement stmt) { pMQOData context = (pMQOData)(qe->PrivateData); pMqobOrderable item; - pParamObjects objlist; pQueryElement cld; int i; diff --git a/centrallix/multiquery/multiquery.c b/centrallix/multiquery/multiquery.c index 3fe3abc0b..fcc175a0c 100644 --- a/centrallix/multiquery/multiquery.c +++ b/centrallix/multiquery/multiquery.c @@ -20,7 +20,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1999-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1999-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -1047,11 +1047,14 @@ mq_internal_ParseSelectItem(pQueryStructure item_qs, pLxSession lxs) n_tok = 0; while(1) { + /** Get the next token. **/ t = mlxNextToken(lxs); if (t == MLX_TOK_ERROR || t == MLX_TOK_EOF) break; n_tok++; - if ((t == MLX_TOK_RESERVEDWD || t == MLX_TOK_COMMA || t == MLX_TOK_SEMICOLON) && parenlevel <= 0) + + /** Special handling for certain token types. **/ + if ((t == MLX_TOK_COMMA || t == MLX_TOK_SEMICOLON) && parenlevel <= 0) break; if (t == MLX_TOK_OPENPAREN) parenlevel++; @@ -1062,9 +1065,19 @@ mq_internal_ParseSelectItem(pQueryStructure item_qs, pLxSession lxs) break; } - /** Copy it to the raw data **/ + /** Get the token string. **/ ptr = mlxStringVal(lxs,NULL); if (!ptr) break; + + /** Skip all reserved words except log(). **/ + if (t == MLX_TOK_RESERVEDWD && parenlevel <= 0) + { + /** Treat "log" as a keyword to allow the log function to be handled properly. **/ + if (strcmp(ptr, "log") == 0) t = MLX_TOK_KEYWORD; + else break; + }; + + /** Copy the token string into item_qs->RawData. **/ if (t == MLX_TOK_STRING) xsConcatQPrintf(&item_qs->RawData, "%STR&DQUOT", ptr); else @@ -2087,6 +2100,7 @@ mq_internal_SyntaxParse(pLxSession lxs, pQueryStatement stmt, int allow_empty, p mssError(1,"MQ","Expected equals after EXEC parameter"); mlxNoteError(lxs); xsFree(xs); + xs = NULL; break; } @@ -2099,6 +2113,7 @@ mq_internal_SyntaxParse(pLxSession lxs, pQueryStatement stmt, int allow_empty, p mssError(1,"MQ","Error in EXEC parameter"); mlxNoteError(lxs); xsFree(xs); + xs = NULL; xsFree(param); break; } @@ -2109,6 +2124,7 @@ mq_internal_SyntaxParse(pLxSession lxs, pQueryStatement stmt, int allow_empty, p mssError(1,"MQ","Could not evaluate EXEC parameter"); mlxNoteError(lxs); xsFree(xs); + xs = NULL; xsFree(param); break; } @@ -2121,8 +2137,11 @@ mq_internal_SyntaxParse(pLxSession lxs, pQueryStatement stmt, int allow_empty, p } } - strtcpy(new_qs->Source, xs->String, sizeof(new_qs->Source)); - next_state = LookForClause; + if (xs != NULL) + { + strtcpy(new_qs->Source, xs->String, sizeof(new_qs->Source)); + next_state = LookForClause; + } } else { @@ -4227,6 +4246,8 @@ mqGetAttrValue(void* inf_v, char* attrname, int datatype, void* value, pObjTrxTr case DATA_T_INTEGER: *(int*)value = exp->Integer; break; case DATA_T_STRING: *(char**)value = exp->String; break; case DATA_T_DOUBLE: *(double*)value = exp->Types.Double; break; + case DATA_T_INTVEC: *(pIntVec*)value = &(exp->Types.IntVec); break; + case DATA_T_STRINGVEC: *(pStringVec*)value = &(exp->Types.StrVec); break; case DATA_T_MONEY: *(pMoneyType*)value = &(exp->Types.Money); break; case DATA_T_DATETIME: *(pDateTime*)value = &(exp->Types.Date); break; case DATA_T_BINARY: @@ -4787,5 +4808,3 @@ mqInitialize() return 0; } - - diff --git a/centrallix/netdrivers/net_http.c b/centrallix/netdrivers/net_http.c index c74bc121b..b04f9f072 100755 --- a/centrallix/netdrivers/net_http.c +++ b/centrallix/netdrivers/net_http.c @@ -3,12 +3,13 @@ #include "cxlib/memstr.h" #include "cxlib/strtcpy.h" #include "json/json.h" +#include "ht_render.h" /************************************************************************/ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2004 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -768,25 +769,19 @@ nht_i_QPrintfConn(pNhtConn conn, int is_hdr, char* fmt, ...) void nht_i_ErrorExit(pNhtConn conn, int code, char* text) { - pXString err_message; - /** Write the standard HTTP response **/ nht_i_WriteResponse(conn, code, text, NULL); /** Display error info **/ - err_message = xsNew(); - if (err_message) - { - mssStringError(err_message); - fdQPrintf(conn->ConnFD, "\r\n\r\nError\r\n\r\n

%POS %STR&HTE

\r\n
\r\n
%STR&HTE
\r\n\r\n\r\n", - code, - text, - xsString(err_message) - ); - xsFree(err_message); - } + mssError(0, "NHT", "Net HTTP driver failed (error code: %d): %s", code, text); + char* error_title = "Error!"; + char* error_html = check_ptr(htrGetErrorHTML(error_title)); + if (UNLIKELY(error_html == NULL)) error_html = error_title; + check_neg(nht_i_WriteConn(conn, error_html, -1, 0)); /* Failure ignored. */ + check(mssClearError()); /* Failure ignored. */ /** Shutdown the connection and free memory **/ + if (LIKELY(error_html != error_title)) nmSysFree(error_html); nht_i_FreeConn(conn); thExit(); /* no return */ @@ -1821,9 +1816,7 @@ nht_i_GET(pNhtConn conn, pStruct url_inf, char* if_modified_since) char* slashptr; pNhtApp app = NULL; pNhtAppGroup group = NULL; - int rval; char* kname; - pXString err_xs; acceptencoding=(char*)mssGetParam("Accept-Encoding"); @@ -2096,6 +2089,9 @@ nht_i_GET(pNhtConn conn, pStruct url_inf, char* if_modified_since) wgtr_params.CharWidth = 7; wgtr_params.CharHeight = 16; wgtr_params.ParagraphHeight = 16; + + /** Specify design geometry **/ + wgtr_params.IsDesign = 1; } else { @@ -2220,13 +2216,16 @@ nht_i_GET(pNhtConn conn, pStruct url_inf, char* if_modified_since) if (nhtRenderApp(conn, target_obj->Session, target_obj, url_inf, &wgtr_params, "DHTML", nsess) < 0) { mssError(0, "HTTP", "Unable to render application %s of type %s", url_inf->StrVal, ptr); - err_xs = xsNew(); - if (err_xs) - { - mssStringError(err_xs); - nht_i_QPrintfConn(conn, 0, "

An error occurred while constructing the application:

%STR&HTE\r\n
", xsString(err_xs)); - xsFree(err_xs); - } + + /** Render an error page to gracefully recover from the error. **/ + char* error_title = "An error occurred while constructing the application."; + char* error_html = check_ptr(htrGetErrorHTML(error_title)); + if (UNLIKELY(error_html == NULL)) error_html = error_title; + check_neg(nht_i_WriteConn(conn, error_html, -1, 0)); /* Failure ignored. */ + check(mssClearError()); /* Failure ignored. */ + + /** Clean up. **/ + if (LIKELY(error_html != error_title)) nmSysFree(error_html); objClose(target_obj); if (tptr) nmSysFree(tptr); if (lptr) nmSysFree(lptr); @@ -2329,7 +2328,7 @@ nht_i_GET(pNhtConn conn, pStruct url_inf, char* if_modified_since) else if (!strcmp(find_inf->StrVal,"rest")) { conn->StrictSameSite = 0; - rval = nht_i_RestGet(conn, url_inf, target_obj); + nht_i_RestGet(conn, url_inf, target_obj); } /** Retrieve a new session/group/app key? **/ diff --git a/centrallix/netdrivers/net_http_app.c b/centrallix/netdrivers/net_http_app.c index f903c538d..83acc26db 100644 --- a/centrallix/netdrivers/net_http_app.c +++ b/centrallix/netdrivers/net_http_app.c @@ -4,7 +4,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -24,7 +24,8 @@ /* A copy of the GNU General Public License has been included in this */ /* distribution in the file "COPYING". */ /* */ -/* Module: net_http.h, net_http.c, net_http_conn.c, net_http_sess.c, net_http_osml.c, net_http_app.c */ +/* Module: net_http.h, net_http.c, net_http_conn.c, */ +/* net_http_sess.c, net_http_osml.c, net_http_app.c */ /* Author: Greg Beeley (GRB) */ /* Creation: December 8, 1998 */ /* Description: Network handler providing an HTTP interface to the */ @@ -58,11 +59,11 @@ nht_i_GetGeom(pObject target_obj, pNhtConn output) /** Do we have a bgcolor / background? **/ if (objGetAttrValue(target_obj, "bgcolor", DATA_T_STRING, POD(&ptr)) == 0) { - snprintf(bgnd, sizeof(bgnd), "bgcolor='%.100s'", ptr); + snprintf(bgnd, sizeof(bgnd), "background-color:%.100s", ptr); } else if (objGetAttrValue(target_obj, "background", DATA_T_STRING, POD(&ptr)) == 0) { - snprintf(bgnd, sizeof(bgnd), "background='%.100s'", ptr); + snprintf(bgnd, sizeof(bgnd), "background:url(%.100s)", ptr); } else { @@ -70,22 +71,29 @@ nht_i_GetGeom(pObject target_obj, pNhtConn output) } /** Generate the snippet **/ - nht_i_QPrintfConn(output, 0, "\n" - "\n" - " \n" - " \n" - "\n" - "\n" - "\n" - " \n" - "
x
x
\n" - "
xx
\n" - "\n" - "\n", font_size > 0, font_size, *font_name, font_name, bgnd); + nht_i_QPrintfConn(output, 0, + "\n" + "\n" + " \n" + " Loading...\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " loading...\n" + "
x
x
\n" + "
xx
\n" + " \n" + "\n", + (font_size > 0), font_size, + (font_name != NULL && font_name[0] != '\0'), font_name, + bgnd + ); return 0; } @@ -149,4 +157,3 @@ nhtRenderApp(pNhtConn conn, pObjSession s, pObject obj, pStruct url_inf, pWgtrCl return rval; } - diff --git a/centrallix/netdrivers/net_http_conn.c b/centrallix/netdrivers/net_http_conn.c index fc25ecb32..97c59e2fa 100644 --- a/centrallix/netdrivers/net_http_conn.c +++ b/centrallix/netdrivers/net_http_conn.c @@ -8,7 +8,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -28,7 +28,8 @@ /* A copy of the GNU General Public License has been included in this */ /* distribution in the file "COPYING". */ /* */ -/* Module: net_http.h, net_http.c, net_http_conn.c, net_http_sess.c, net_http_osml.c, net_http_app.c */ +/* Module: net_http.h, net_http.c, net_http_conn.c, */ +/* net_http_sess.c, net_http_osml.c, net_http_app.c */ /* Author: Greg Beeley (GRB) */ /* Creation: December 8, 1998 */ /* Description: Network handler providing an HTTP interface to the */ @@ -306,9 +307,10 @@ nht_i_SendRefreshDocument(pNhtConn conn, char* url) /** This is a simple HTML document that loads the url we give it. **/ nht_i_QPrintfConn(conn, 0, "\r\n" - "\r\n" + "\r\n" " \r\n" - " \r\n" " \r\n" @@ -1192,4 +1194,3 @@ nht_i_Handler(void* v) thExit(); } - diff --git a/centrallix/objectsystem/obj_content.c b/centrallix/objectsystem/obj_content.c index ec7475966..8e3b94eeb 100644 --- a/centrallix/objectsystem/obj_content.c +++ b/centrallix/objectsystem/obj_content.c @@ -4,6 +4,7 @@ #include #include "obj.h" #include "cxlib/mtask.h" +#include "cxlib/mtsession.h" #include "cxlib/xarray.h" #include "cxlib/xhash.h" #include "cxlib/magic.h" @@ -88,4 +89,3 @@ objWrite(pObject this, char* buffer, int cnt, int offset, int flags) } return this->Driver->Write(this->Data, buffer, cnt, offset, flags, &(this->Session->Trx)); } - diff --git a/centrallix/objectsystem/obj_datatypes.c b/centrallix/objectsystem/obj_datatypes.c index c674fe3df..93da47fca 100644 --- a/centrallix/objectsystem/obj_datatypes.c +++ b/centrallix/objectsystem/obj_datatypes.c @@ -22,7 +22,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1999-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1999-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -127,6 +127,93 @@ char* obj_default_money_fmt = "$0.00"; char* obj_default_null_fmt = "NULL"; +/** Should maybe replace current type parsing in the presentation hints. **/ +/*** Parse the given string into a datatype. The case of the first character + *** is ignored, but all other characters must be capitalized correctly. + *** + *** @attention - This function is optimized to prevent performance hits + *** situations where it may need to be called many thousands of times. + *** + *** @param str The string to be parsed to a datatype. + *** @returns The datatype. + *** + *** LINK ../../centrallix-lib/include/datatypes.h:72 + ***/ +int +objTypeFromStr(const char* str) + { + /** All valid types are non-null strings, at least 2 characters long. **/ + if (str == NULL || str[0] == '\0' || str[1] == '\0') return -1; + + /** Check type. **/ + switch (str[0]) + { + case 'A': case 'a': + if (strcmp(str+1, "Array"+1) == 0) return DATA_T_ARRAY; + if (strcmp(str+1, "Any"+1) == 0) return DATA_T_ANY; + break; + + case 'B': case 'b': + if (strcmp(str+1, "Binary"+1) == 0) return DATA_T_BINARY; + break; + + case 'C': case 'c': + if (strcmp(str+1, "Code"+1) == 0) return DATA_T_CODE; + break; + + case 'D': case 'd': + if (strcmp(str+1, "Double"+1) == 0) return DATA_T_DOUBLE; + if (strcmp(str+1, "DateTime"+1) == 0) return DATA_T_DATETIME; + break; + + case 'I': case 'i': + if (strcmp(str+1, "Integer"+1) == 0) return DATA_T_INTEGER; + if (strcmp(str+1, "IntVector"+1) == 0) return DATA_T_INTVEC; + break; + + case 'M': case 'm': + if (strcmp(str+1, "Money"+1) == 0) return DATA_T_MONEY; + break; + + case 'S': case 's': + if (strcmp(str+1, "String"+1) == 0) return DATA_T_STRING; + if (strcmp(str+1, "StringVector"+1) == 0) return DATA_T_STRINGVEC; + break; + + case 'U': case 'u': + if (strcmp(str+1, "Unknown"+1) == 0) return DATA_T_UNAVAILABLE; + if (strcmp(str+1, "Unavailable"+1) == 0) return DATA_T_UNAVAILABLE; + break; + } + + /** Invalid type. **/ + return -1; + } + + +/*** Convert a type to its string name. + *** + *** @param type The type to be converted. + *** @returns A char* to the type name, or + *** "(unknown)" if the type is unknown, or + *** "invalid" if the type number cannot even be a valid type. + ***/ +char* +objTypeToStr(const int type) + { + /** Guard out of bounds reads. **/ + if (type < 0 || OBJ_TYPE_NAMES_CNT <= type) + { + /** Invalid type. **/ + mssError(1, "Cluster", "Invalid type %d.\n", type); + + return "invalid"; /* Shall not parse to a valid type in ci_TypeFromStr(). */ + } + + return obj_type_names[type]; + } + + /*** obj_internal_ParseDateLang - looks up a list of language internationalization *** strings inside the date format. WARNING - modifies the "srcptr" data in *** place. diff --git a/centrallix/objectsystem/obj_query.c b/centrallix/objectsystem/obj_query.c index 05da89a15..b8d99c442 100644 --- a/centrallix/objectsystem/obj_query.c +++ b/centrallix/objectsystem/obj_query.c @@ -421,7 +421,6 @@ objQueryFetch(pObjQuery this, int mode) { pObject obj = NULL; void* obj_data; - char* name; char buf[OBJSYS_MAX_PATH + 32]; pObjQuerySortItem sort_item; int rval; @@ -536,14 +535,6 @@ objQueryFetch(pObjQuery this, int mode) goto error; } obj->Data = obj_data; - - this->Obj->Driver->GetAttrValue(obj_data, "name", DATA_T_STRING, &name, NULL); - if (strlen(name) + strlen(this->Obj->Pathname->Pathbuf) + 2 > OBJSYS_MAX_PATH) - { - mssError(1,"OSML","Filename in query result exceeded internal limits"); - OSMLDEBUG(OBJ_DEBUG_F_APITRACE, " null\n"); - goto error; - } /** If we need to check it, do so now. **/ if (!(this->Flags & OBJ_QY_F_FULLQUERY) && this->Tree) @@ -785,4 +776,3 @@ objGetQueryIdentityPath(pObjQuery this, char* pathbuf, int maxlen) return 0; } - diff --git a/centrallix/objectsystem/obj_session.c b/centrallix/objectsystem/obj_session.c index b4b6a38fa..60a8146f4 100644 --- a/centrallix/objectsystem/obj_session.c +++ b/centrallix/objectsystem/obj_session.c @@ -4,6 +4,7 @@ #include #include "obj.h" #include "cxlib/mtask.h" +#include "cxlib/mtsession.h" #include "cxlib/xarray.h" #include "cxlib/xhash.h" #include "cxlib/magic.h" @@ -257,4 +258,3 @@ objResumeTransaction(pObjSession this, pObjTrxTree trx) return 0; } - diff --git a/centrallix/osdrivers/objdrv_cluster.c b/centrallix/osdrivers/objdrv_cluster.c new file mode 100644 index 000000000..350afd473 --- /dev/null +++ b/centrallix/osdrivers/objdrv_cluster.c @@ -0,0 +1,5023 @@ +/************************************************************************/ +/* Centrallix Application Server System */ +/* Centrallix Core */ +/* */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ +/* */ +/* This program is free software; you can redistribute it and/or modify */ +/* it under the terms of the GNU General Public License as published by */ +/* the Free Software Foundation; either version 2 of the License, or */ +/* (at your option) any later version. */ +/* */ +/* This program is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU General Public License for more details. */ +/* */ +/* You should have received a copy of the GNU General Public License */ +/* along with this program; if not, write to the Free Software */ +/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA */ +/* 02111-1307 USA */ +/* */ +/* A copy of the GNU General Public License has been included in this */ +/* distribution in the file "COPYING". */ +/* */ +/* Module: objdrv_cluster.c */ +/* Author: Israel Fuller */ +/* Creation: September 17, 2025 */ +/* Description: Object driver for Centrallix cluster objects. This */ +/* driver handles cluster configuration and access so */ +/* clustered resources can be opened, searched efficiently */ +/* with caching to prevent unnecessary re-computation, and */ +/* managed through the object system. */ +/************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cxlib/clusters.h" +#include "cxlib/expect.h" +#include "cxlib/magic.h" +#include "cxlib/mtsession.h" +#include "cxlib/newmalloc.h" +#include "cxlib/util.h" +#include "cxlib/xarray.h" +#include "cxlib/xhash.h" +#include "expression.h" +#include "obj.h" +#include "param.h" +#include "st_node.h" +#include "stparse.h" + +/*** This file uses the optional Comment Anchors VSCode extension, documented + *** with CommentAnchorsExtension.md in centrallix-sysdoc. + ***/ + +/** Defaults for unspecified optional attributes. **/ +#define CI_DEFAULT_MIN_IMPROVEMENT 0.0001 +#define CI_DEFAULT_MAX_ITERATIONS 64u +#define CI_NO_SEED 0u + + +/** ================ Enum Declarations ================ **/ +/** ANCHOR[id=enums] **/ + +/** Enum type representing a clustering algorithm. **/ +typedef unsigned char ClusterAlgorithm; +#define ALGORITHM_NULL (ClusterAlgorithm)0u +#define ALGORITHM_NONE (ClusterAlgorithm)1u +#define ALGORITHM_SLIDING_WINDOW (ClusterAlgorithm)2u +#define ALGORITHM_KMEANS (ClusterAlgorithm)3u +#define ALGORITHM_KMEANS_PLUS_PLUS (ClusterAlgorithm)4u +#define ALGORITHM_KMEDOIDS (ClusterAlgorithm)5u +#define ALGORITHM_DB_SCAN (ClusterAlgorithm)6u + +ClusterAlgorithm ALL_CLUSTERING_ALGORITHMS[] = + { + ALGORITHM_NULL, + ALGORITHM_NONE, + ALGORITHM_SLIDING_WINDOW, + ALGORITHM_KMEANS, + ALGORITHM_KMEANS_PLUS_PLUS, + ALGORITHM_KMEDOIDS, + ALGORITHM_DB_SCAN, + }; +#define N_CLUSTERING_ALGORITHMS ((unsigned int)(sizeof(ALL_CLUSTERING_ALGORITHMS) / sizeof(ALL_CLUSTERING_ALGORITHMS[0]))) + +/** Converts a clustering algorithm to its string name. **/ +char* +ci_ClusteringAlgorithmToString(ClusterAlgorithm clustering_algorithm) + { + switch (clustering_algorithm) + { + case ALGORITHM_NULL: return "NULL algorithm"; + case ALGORITHM_NONE: return "none"; + case ALGORITHM_SLIDING_WINDOW: return "sliding-window"; + case ALGORITHM_KMEANS: return "k-means"; + case ALGORITHM_KMEANS_PLUS_PLUS: return "k-means++"; + case ALGORITHM_KMEDOIDS: return "k-medoids"; + case ALGORITHM_DB_SCAN: return "db-scan"; + default: return "Unknown algorithm"; + } + + return NULL; /** Unreachable. **/ + } + +/** Enum type representing a similarity measurement algorithm. **/ +typedef unsigned char SimilarityMeasure; +#define SIMILARITY_NULL (SimilarityMeasure)0u +#define SIMILARITY_COSINE (SimilarityMeasure)1u +#define SIMILARITY_LEVENSHTEIN (SimilarityMeasure)2u + +SimilarityMeasure ALL_SIMILARITY_MEASURES[] = + { + SIMILARITY_NULL, + SIMILARITY_COSINE, + SIMILARITY_LEVENSHTEIN, + }; +#define N_SIMILARITY_MEASURES ((unsigned int)(sizeof(ALL_SIMILARITY_MEASURES) / sizeof(ALL_SIMILARITY_MEASURES[0]))) + +/*** Converts a similarity measure to its string name. + *** + *** @param similarity_measure The similarity measure to convert. + *** @returns The corresponding name. + ***/ +char* +ci_SimilarityMeasureToString(SimilarityMeasure similarity_measure) + { + switch (similarity_measure) + { + case SIMILARITY_NULL: return "NULL similarity measure"; + case SIMILARITY_COSINE: return "cosine"; + case SIMILARITY_LEVENSHTEIN: return "levenshtein"; + default: return "Unknown similarity measure"; + } + + return NULL; /** Unreachable. **/ + } + +/*** Converts a similarity measure to a function pointer that points to the + *** corresponding comparison function. This function can be called directly + *** or passed to functions from `clusters.c`. + *** + *** @param similarity_measure The similarity measure to be converted. + *** @returns A similarity computation function. This function takes two void + *** pointers, representing the data to be compared, and returns a double + *** representing how similar the data is from 0.0 (no similarity) to 1.0 + *** (identical), or NAN if an error occurs. This function may return NULL + *** if an error occurs, in which case it always calls mssError() to give + *** an error message. + ***/ +double (*ci_SimilarityMeasureToFunction(SimilarityMeasure similarity_measure))(void*, void*) + { + switch (similarity_measure) + { + case SIMILARITY_COSINE: return ca_cos_compare; + case SIMILARITY_LEVENSHTEIN: return ca_lev_compare; + default: + mssError(1, "Cluster", + "Unknown similarity measure \"%s\" (%d).", + ci_SimilarityMeasureToString(similarity_measure), similarity_measure + ); + return NULL; + } + } + +/*** Enum representing the type of data targeted by the driver, set based on + *** the path given when the driver is used to open a cluster file. + *** + *** `0u` is reserved for a possible NULL value in the future. However, NULL + *** values for TargetType are not currently allowed. + ***/ +typedef unsigned char TargetType; +#define TARGET_NODE (TargetType)1u +#define TARGET_CLUSTER (TargetType)2u +#define TARGET_SEARCH (TargetType)3u +#define TARGET_CLUSTER_ENTRY (TargetType)4u +#define TARGET_SEARCH_ENTRY (TargetType)5u + +TargetType ALL_TARGET_TYPES[] = + { + TARGET_NODE, + TARGET_CLUSTER, + TARGET_SEARCH, + TARGET_CLUSTER_ENTRY, + TARGET_SEARCH_ENTRY, + }; +#define N_TARGET_TYPES ((unsigned int)(sizeof(ALL_TARGET_TYPES) / sizeof(ALL_TARGET_TYPES[0]))) + +/*** Attribute name lists by TargetType. + *** + *** Promises that input attributes are listed first, before computed attributes. + ***/ +char* ROOT_ATTRS[] = + { + "source", + "key_attr", + "data_attr", + "internal_type", + }; +#define N_ROOT_ATTRS ((unsigned int)(sizeof(ROOT_ATTRS) / sizeof(ROOT_ATTRS[0]))) +#define N_COMPUTED_ROOT_ATTRS 1u +#define N_INPUT_ROOT_ATTRS (N_ROOT_ATTRS - N_COMPUTED_ROOT_ATTRS) +char* CLUSTER_ATTRS[] = + { + "algorithm", + "similarity_measure", + "num_clusters", + "min_improvement", + "max_iterations", + "window_size", + "seed", + "date_created", + "date_computed", + }; +#define N_CLUSTER_ATTRS ((unsigned int)(sizeof(CLUSTER_ATTRS) / sizeof(CLUSTER_ATTRS[0]))) +#define N_COMPUTED_CLUSTER_ATTRS 2u +#define N_INPUT_CLUSTER_ATTRS (N_CLUSTER_ATTRS - N_COMPUTED_CLUSTER_ATTRS) +char* SEARCH_ATTRS[] = + { + "source", + "threshold", + "similarity_measure", + "date_created", + "date_computed", + }; +#define N_SEARCH_ATTRS ((unsigned int)(sizeof(SEARCH_ATTRS) / sizeof(SEARCH_ATTRS[0]))) +#define N_COMPUTED_SEARCH_ATTRS 2u +#define N_INPUT_SEARCH_ATTRS (N_SEARCH_ATTRS - N_COMPUTED_SEARCH_ATTRS) +char* CLUSTER_ENTRY_ATTRS[] = + { + "items", + "date_computed", + "date_created", + }; +#define N_CLUSTER_ENTRY_ATTRS ((unsigned int)(sizeof(CLUSTER_ENTRY_ATTRS) / sizeof(CLUSTER_ENTRY_ATTRS[0]))) +#define N_COMPUTED_CLUSTER_ENTRY_ATTRS 2u +#define N_INPUT_CLUSTER_ENTRY_ATTRS (N_CLUSTER_ENTRY_ATTRS - N_COMPUTED_CLUSTER_ENTRY_ATTRS) +char* SEARCH_ENTRY_ATTRS[] = + { + "key1", + "key2", + "sim", + "date_computed", + "date_created", + }; +#define N_SEARCH_ENTRY_ATTRS ((unsigned int)(sizeof(SEARCH_ENTRY_ATTRS) / sizeof(SEARCH_ENTRY_ATTRS[0]))) +#define N_COMPUTED_SEARCH_ENTRY_ATTRS 2u +#define N_INPUT_SEARCH_ENTRY_ATTRS (N_SEARCH_ENTRY_ATTRS - N_COMPUTED_SEARCH_ENTRY_ATTRS) + +/** Method name list. **/ +char* METHOD_NAMES[] = + { + "cache", + "stat", + }; +#define METHOD_NAMES_COUNT ((unsigned int)(sizeof(METHOD_NAMES) / sizeof(METHOD_NAMES[0]))) + + +/** ================ Struct Declarations ================ **/ +/** ANCHOR[id=structs] **/ + +/*** Represents the data source which may have data already fetched. Only + *** attribute data is checked for caching. + *** + *** Memory Stats: + *** - Padding: 0 bytes + *** - Total size: 80 bytes + *** + *** @skip --> Attribute Data. + *** @param Name The source name, specified in the .cluster file. + *** @param CacheKey The key associated with this object in the SourceDataCache. + *** @param SourcePath The path to the data source from which to retrieve data. + *** @param KeyAttr The name of the attribute to use when getting keys from + *** the driver represented by SourcePath. + *** @param DataAttr The name of the attribute to use when getting data from + *** the driver represented by SourcePath. + *** + *** @skip --> Fetched/Computed Data. + *** @param Keys The keys for each data string received from the database, used + *** when the results are queried, or NULL if the data has not been fetched. + *** @param Strings The data strings to be clustered and searched, or NULL if + *** they have not been fetched from the source. + *** @param Vectors The cosine comparison vectors from the fetched data, or + *** NULL if they haven't been computed yet. + *** @param nDatas The number of keys, data strings, and vectors that have been + *** fetched/computed, or 0 if no data has been fetched/computed yet. + *** + *** @skip --> Time. + *** @param DateCreated The date and time that this object was created/initialized. + *** @param DateComputed The date and time that the fetch/computed attributes + *** were fetched/computed. + *** + *** @param Magic A magic value for detecting memory corruption. + ***/ +typedef struct _SOURCE + { + Magic_t Magic; + unsigned int nDatas; + char* Name; + char* CacheKey; + char* SourcePath; + char* KeyAttr; + char* DataAttr; + char** Keys; + char** Strings; + pVector* Vectors; + DateTime DateCreated; + DateTime DateComputed; + } + SourceData, *pSourceData; + + +/*** Computed data for a single cluster. + *** + *** Memory Stats: + *** - Padding: 0 bytes + *** - Total size: 16 bytes + *** + *** @param Size The number of items in the cluster. + *** @param Indexes The data points in the cluster, as indexes into SourceData. + *** Use these to access the Keys, Strings, or Vectors array fields. This + *** value is NULL if `Size == 0`. + *** @param Magic A magic value for detecting memory corruption. + ***/ +typedef struct + { + Magic_t Magic; + unsigned int Size; + unsigned int* Indexes; + } + Cluster, *pCluster; + + +/*** Data for each cluster object defined in the .cluster file. Only attribute + *** data is checked for caching. + *** + *** Memory Stats: + *** - Padding: 2 bytes + *** - Total size: 104 bytes + *** + *** @skip --> Attribute Data. + *** @param Name The cluster name, specified in the .cluster file. + *** @param CacheKey The key associated with this object in the ClusterDataCache. + *** @param ClusterAlgorithm The clustering algorithm to be used. + *** @param SimilarityMeasure The similarity measure used to compare items. + *** @param nClusters The number of clusters. 1 if `algorithm == none`. + *** @param MinImprovement The minimum amount of improvement that must be met + *** each clustering iteration. -inf represents the "max" value in the + *** .cluster file. + *** @param MaxIterations The maximum number of iterations to run clustering. + *** @param WindowSize The size of the sliding window for sliding window + *** searches. Shares memory with MaxIterations because the sliding window + *** clustering algorithm does not have any concept of "iterations". + *** + *** @skip --> Relational Data. (Note: sub-clusters are not implemented.) + *** @param nSubClusters The number of sub-clusters for this cluster. + *** @param SubClusters A pClusterData array, NULL if `nSubClusters == 0`. + *** @param Parent This cluster's parent. NULL if it is not a sub-cluster. + *** @param SourceData Pointer to the source data that this cluster uses. + *** + *** @skip --> Computed Data. + *** @param Clusters An array of length nClusters, NULL if the clusters have + *** been computed yet. + *** @param Sims An array of nClusters elements, where index i stores the + *** similarity of vector i to its assigned cluster, NULL if the clusters + *** have not been computed yet. + *** + *** @skip --> Time. + *** @param DateCreated The date and time that this object was created/initialized. + *** @param DateComputed The date and time that the computed attributes were computed. + *** + *** @param Magic A magic value for detecting memory corruption. + ***/ +typedef struct _CD + { + Magic_t Magic; + unsigned int nClusters; + char* Name; + char* CacheKey; + ClusterAlgorithm ClusterAlgorithm; + SimilarityMeasure SimilarityMeasure; + /** 2 bytes of auto-padding. **/ + unsigned int Seed; + double MinImprovement; + union { + unsigned int MaxIterations; + unsigned int WindowSize; + }; + unsigned int nSubClusters; + struct _CD** SubClusters; + struct _CD* Parent; + pSourceData SourceData; + Cluster* Clusters; + double* Sims; + DateTime DateCreated; + DateTime DateComputed; + } + ClusterData, *pClusterData; + + +/*** Data for each search. + *** + *** Memory Stats: + *** - Padding: 7 bytes + *** - Total size: 72 bytes + *** + *** @skip --> Attribute Data. + *** @param Name The search name, specified in the .cluster file. + *** @param CacheKey The key associated with this object in the SearchDataCache. + *** @param Source The cluster from which this search is to be derived. + *** @param SimilarityMeasure The similarity measure used to compare items. + *** @param Threshold The minimum similarity threshold for elements to be + *** included in the results of the search. + *** + *** @skip --> Computed data. + *** @param Pairs An array holding the pairs found by the search, or NULL if + *** the search has not been computed yet. The indexes stored in these + *** pairs are indexes into the SourceData data arrays (access with + *** `Source->SourceData->[DATA_ARRAY]`). + *** @param nPairs The number of pairs found, or 0 if the search has not been + *** computed yet. + *** + *** @skip --> Time. + *** @param DateCreated The date and time that this object was created/initialized. + *** @param DateComputed The date and time that the computed attributes were computed. + *** + *** @param Magic A magic value for detecting memory corruption. + ***/ +typedef struct _SEARCH + { + Magic_t Magic; + /** 4 bytes of auto-padding. **/ + char* Name; + char* CacheKey; + pClusterData SourceCluster; + double Threshold; + pPair* Pairs; + unsigned int nPairs; + SimilarityMeasure SimilarityMeasure; + /** 3 bytes of auto-padding. **/ + DateTime DateCreated; + DateTime DateComputed; + } + SearchData, *pSearchData; + + +/*** Node instance data. + *** + *** Memory Stats: + *** - Padding: 4 bytes + *** - Total size: 72 bytes + *** + *** @note When a .cluster file is opened, there will be only one node for + *** that file. However, in the course of the query, many driver instance + *** structs using this one node may be created thanks to functions such as + ***`clusterQueryFetch()`, and closed by with functions like `clusterClose()`. + *** + *** @param SourceData Data from the provided source. + *** @param Params A pParam array storing the params in the .cluster file. + *** @param nParams The number of specified params. + *** @param ParamList A "scope" for resolving parameter values during parsing. + *** @param ClusterDatas A pCluster array for the clusters in the .cluster file, + *** NULL if `nClusters == 0`. + *** @param nClusterDatas The number of specified clusters. + *** @param SearchDatas A SearchData array for the searches in the .cluster file. + *** @param nSearches The number of specified searches. + *** @param nSearchDatas The parent object used to open this NodeData instance. + *** @param OpenCount The number of open driver instances that are using the + *** NodeData struct. When this reaches 0, the struct should be freed. + *** @param Magic A magic value for detecting memory corruption. + ***/ +typedef struct _NODE + { + Magic_t Magic; + /** 4 bytes of auto-padding. **/ + pObject Parent; + pParam* Params; + pParamObjects ParamList; + pSourceData SourceData; + pClusterData* ClusterDatas; + pSearchData* SearchDatas; + unsigned int OpenCount; + unsigned int nParams; + unsigned int nClusterDatas; + unsigned int nSearchDatas; + } + NodeData, *pNodeData; + +/*** Driver instance data. + *** + *** Memory Stats: + *** - Padding: 5 bytes + *** - Total size: 32 bytes + *** + *** Think of this struct like a "pointer" to specific data accessible through + *** the pNodeData field. This struct also tells us whether that data is + *** guaranteed to be computed already. + *** + *** For example, if target type is the root, a cluster, or a search, no data + *** is guaranteed to be computed. These three types can be returned from + *** clusterOpen(), based on the provided path. + *** + *** Alternatively, a cluster entry or search entry can be targeted by calling + *** fetch on a query pointing to a driver instance that targets a cluster or + *** search (respectively). These two entry target types ensure that the data + *** they indicate has been computed. This makes the `clusterGetAttrType()` + *** and `clusterGetAttrValue()` functions faster and simpler because they do + *** not need to check that the data is computed every time they are called. + *** + *** @attention - When allocating this struct, remember to increment the + *** `NodeData->OpenCount` value. When freeing this struct, remember to + *** decrement `NodeData->OpenCount`, freeing the NodeData struct as well + *** if it reaches 0, to prevent memory leaks. + *** + *** @param NodeData The associated node data struct. While many driver + *** struct instances pointing to one NodeData at a time, but each driver + *** instance always points to singular NodeData struct. + *** @param TargetType The type of data targeted (see above). + *** @param TargetData If target type is: + *** ```txt + *** Node: A pointer to the SourceData struct. + *** Cluster or ClusterEntry: A pointer to the targeted cluster. + *** Search or SearchEntry: A pointer to the targeted search. + *** ``` + *** @param TargetAttrIndex An index into an attribute list (for GetNextAttr()). + *** @param TargetMethodIndex An index into an method list (for GetNextMethod()). + *** @param Magic A magic value for detecting memory corruption. + ***/ +typedef struct _DRIVER + { + Magic_t Magic; + /** 4 bytes of auto-padding. **/ + pNodeData NodeData; + void* TargetData; + unsigned int TargetIndex; + unsigned char TargetAttrIndex; + unsigned char TargetMethodIndex; + TargetType TargetType; + /** 1 bytes of auto-padding. **/ + } + DriverData, *pDriverData; + +/*** Query instance data. + *** + *** Memory Stats: + *** - Padding: 0 bytes + *** - Total size: 16 bytes + *** + *** @param DriverData The associated driver instance being queried. + *** @param RowIndex The selected row of the data targeted by the driver. + *** @param Magic A magic value for detecting memory corruption. + ***/ +typedef struct + { + Magic_t Magic; + unsigned int RowIndex; + pDriverData DriverData; + } + ClusterQuery, *pQueryData; + + +/** Global storage for driver caches. **/ +struct + { + XHashTable SourceDataCache; + XHashTable ClusterDataCache; + XHashTable SearchDataCache; + } + ClusterDriverCaches = {0}; + +struct + { + unsigned long long OpenCalls; + unsigned long long OpenQueryCalls; + unsigned long long FetchCalls; + unsigned long long CloseCalls; + unsigned long long GetTypeCalls; + unsigned long long GetValCalls; + unsigned long long GetValCalls_name; + unsigned long long GetValCalls_key1; + unsigned long long GetValCalls_key2; + unsigned long long GetValCalls_sim; + } ClusterStatistics = {0}; + + +/** ================ Function Declarations ================ **/ +/** ANCHOR[id=functions] **/ + +/** Note: ci stands for "cluster_internal". **/ + +/** Parsing Functions. **/ +// LINK #parsing +static void ci_GiveHint(const char* hint); +static bool ci_TryHint(char* value, char** valid_values, const unsigned int n_valid_values); +static void ci_UnknownAttribute(char* attr_name, int target_type); +static int ci_ParseAttribute(pStructInf inf, char* attr_name, int datatype, pObjData data, pParamObjects param_list, bool required, bool print_type_error); +static ClusterAlgorithm ci_ParseClusteringAlgorithm(pStructInf cluster_inf, pParamObjects param_list); +static SimilarityMeasure ci_ParseSimilarityMeasure(pStructInf cluster_inf, pParamObjects param_list); +static pSourceData ci_ParseSourceData(pStructInf inf, pParamObjects param_list, char* path); +static pClusterData ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source_data); +static pSearchData ci_ParseSearchData(pStructInf inf, pNodeData node_data); +static pNodeData ci_ParseNodeData(pStructInf inf, pObject obj); + +/** Freeing Functions. **/ +// LINK #freeing +static void ci_FreeSourceData(pSourceData source_data); +static void ci_FreeClusterData(pClusterData cluster_data, bool recursive); +static void ci_FreeSearchData(pSearchData search_data); +static void ci_FreeNodeData(pNodeData node_data); +static void ci_ClearCaches(void); + +/** Deep Size Computation Functions. **/ +// LINK #sizing +static size_t ci_SizeOfSourceData(pSourceData source_data); +static size_t ci_SizeOfClusterData(pClusterData cluster_data, bool recursive); +static size_t ci_SizeOfSearchData(pSearchData search_data); + +/** Computation Functions. (Ensure data is computed.) **/ +// LINK #computation +static int ci_ComputeSourceData(pSourceData source_data, pObjSession session); +static int ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data); +static int ci_ComputeSearchData(pSearchData search_data, pNodeData node_data); + +/** Parameter Functions. **/ +// LINK #params +static int ci_GetParamType(void* inf_v, const char* attr_name); +static int ci_GetParamValue(void* inf_v, char* attr_name, int datatype, pObjData val); +static int ci_SetParamValue(void* inf_v, char* attr_name, int datatype, pObjData val); + +/** Driver Functions. **/ +// LINK #driver +void* clusterOpen(pObject parent, int mask, pContentType systype, char* usr_type, pObjTrxTree* oxt); +int clusterClose(void* inf_v, pObjTrxTree* oxt); +void* clusterOpenQuery(void* inf_v, pObjQuery query, pObjTrxTree* oxt); +void* clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt); +int clusterQueryClose(void* qy_v, pObjTrxTree* oxt); +int clusterGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt); +int clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pObjTrxTree* oxt); +pObjPresentationHints clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt); +char* clusterGetFirstAttr(void* inf_v, pObjTrxTree* oxt); +char* clusterGetNextAttr(void* inf_v, pObjTrxTree* oxt); +int clusterInfo(void* inf_v, pObjectInfo info); + +/** Method Execution Functions. **/ +// LINK #method +char* clusterGetFirstMethod(void* inf_v, pObjTrxTree* oxt); +char* clusterGetNextMethod(void* inf_v, pObjTrxTree* oxt); +static int ci_PrintEntry(pXHashEntry entry, void* arg); +static void ci_CacheFreeSourceData(pXHashEntry entry, void* path); +static void ci_CacheFreeCluster(pXHashEntry entry, void* path); +static void ci_CacheFreeSearch(pXHashEntry entry, void* path); +int clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree* oxt); + +/** Unimplemented DriverFunctions. **/ +// LINK #unimplemented +int clusterCreate(pObject obj, int mask, pContentType systype, char* usrtype, pObjTrxTree* oxt); +int clusterDelete(pObject obj, pObjTrxTree* oxt); +int clusterDeleteObj(void* inf_v, pObjTrxTree* oxt); +int clusterRead(void* inf_v, char* buffer, int max_cnt, int offset, int flags, pObjTrxTree* oxt); +int clusterWrite(void* inf_v, char* buffer, int cnt, int offset, int flags, pObjTrxTree* oxt); +int clusterSetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pObjTrxTree* oxt); +int clusterAddAttr(void* inf_v, char* attr_name, int type, pObjData val, pObjTrxTree* oxt); +void* clusterOpenAttr(void* inf_v, char* attr_name, int mode, pObjTrxTree* oxt); +int clusterCommit(void* inf_v, pObjTrxTree *oxt); + +/** ================ Parsing Functions ================ **/ +/** ANCHOR[id=parsing] **/ +// LINK #functions + +/** Format a hint to give to the user. **/ +static void ci_GiveHint(const char* hint) + { + fprintf(stderr, " > Hint: Did you mean \"%s\"?\n", hint); + + return; + } + + +/*** Given the user a hint when they specify an invalid string for an attribute + *** where we know the list of valid strings. The hint is only displayed if + *** their string is close enough to a valid string. + *** + *** @param value The value the user gave. + *** @param valid_values The valid values that could be what they meant. + *** @param n_valid_values The number of valid values. Specify 0 to detect + *** length on a null terminated array of values. + *** @returns Whether a hint was given. + ***/ +static bool +ci_TryHint(char* value, char** valid_values, const unsigned int n_valid_values) + { + char* guess = ca_most_similar(value, (void**)valid_values, n_valid_values, ca_lev_compare, 0.25); + if (guess == NULL) return false; /* No hint. */ + + /** Issue hint. **/ + ci_GiveHint(guess); + + return true; + } + + +/*** Display an error message when an unknown attribute is requested, including + *** a hint about which attribute might be intended, if available. + *** + *** @param attr_name The name of the missing attribute. + *** @param target_type The target type, for determining the list of available + *** attributes in this context. + ***/ +static void +ci_UnknownAttribute(char* attr_name, const int target_type) + { + /** Display the error message. **/ + mssError(1, "Cluster", "Unknown attribute '%s'.", attr_name); + + /** Collect specific attributes based on target type. **/ + char** my_attrs = NULL; + unsigned int n_my_attrs = 0u; + switch (target_type) + { + case TARGET_NODE: my_attrs = ROOT_ATTRS; n_my_attrs = N_ROOT_ATTRS; break; + case TARGET_CLUSTER: my_attrs = CLUSTER_ATTRS; n_my_attrs = N_CLUSTER_ATTRS; break; + case TARGET_SEARCH: my_attrs = SEARCH_ATTRS; n_my_attrs = N_SEARCH_ATTRS; break; + case TARGET_CLUSTER_ENTRY: my_attrs = CLUSTER_ENTRY_ATTRS; n_my_attrs = N_CLUSTER_ENTRY_ATTRS; break; + case TARGET_SEARCH_ENTRY: my_attrs = SEARCH_ENTRY_ATTRS; n_my_attrs = N_SEARCH_ENTRY_ATTRS; break; + default: + mssError(0, "Cluster", + "Unknown target type %u detected while attempting to generate hint.", + target_type + ); + return; + } + + /** Attempt to give hints. **/ + if (ci_TryHint(attr_name, my_attrs, n_my_attrs)); + else if (ci_TryHint(attr_name, DRIVER_ATTRIBUTE_NAMES, N_DRIVER_ATTRIBUTE_NAMES)); + + return; + } + + +// LINK #functions +/*** @returns 0 if a value is found, + *** -1 if an error occurs, or + *** 1 if attribute is null, calling mssError() for required attributes. + *** + *** @attention - Promises that a failure invokes mssError() at least once. + *** + *** TODO: Greg - Review carefully. I think this code is the reason that + *** runserver() is sometimes not required for dynamic attributes in the + *** cluster driver, which I'm pretty sure is incorrect. + *** TODO: Israel - After Greg's review, update this doc comment to properly + *** describe the function parameters. + ***/ +static int +ci_ParseAttribute( + pStructInf inf, + char* attr_name, + int datatype, + pObjData data, + pParamObjects param_list, + bool required, + bool print_type_error) + { + int ret; + + /** Get attribute inf. **/ + pStructInf attr_info = stLookup(inf, attr_name); + if (UNLIKELY(attr_info == NULL)) + { + if (required) mssError(1, "Cluster", "'%s' must be specified for clustering.", attr_name); + return 1; + } + ASSERTMAGIC(attr_info, MGK_STRUCTINF); + + /** Allocate expression. **/ + pExpression exp = (pExpression)check_ptr(stGetExpression(attr_info, 0)); + if (UNLIKELY(exp == NULL)) goto err; + + /** Bind parameters. **/ + /** TODO: Greg - What does this return? How do I know if it fails? **/ + expBindExpression(exp, param_list, EXPR_F_RUNSERVER); + + /** Evaluate expression. **/ + ret = expEvalTree(exp, param_list); + if (UNLIKELY(ret != 0)) + { + mssError(0, "Cluster", "Expression evaluation failed (error code %d).", ret); + goto err; + } + + /** Check for data type mismatch. **/ + if (UNLIKELY(datatype != exp->DataType)) + { + mssError(1, "Cluster", + "Expected ['%s' : %s], but got type %s.", + attr_name, objTypeToStr(datatype), objTypeToStr(exp->DataType) + ); + goto err; + } + + /** Get the data out of the expression. **/ + ret = expExpressionToPod(exp, datatype, data); + if (UNLIKELY(ret != 0)) + { + mssError(1, "Cluster", + "Failed to get ['%s' : %s] using expression \"%s\" (error code %d).", + attr_name, objTypeToStr(datatype), exp->Name, ret + ); + goto err; + } + + /** Success. **/ + return 0; + + err: + mssError(0, "Cluster", + "Failed to parse attribute \"%s\" from group \"%s\"", + attr_name, inf->Name + ); + + /** Return error. **/ + return -1; + } + + +// LINK #functions +/*** Parses a ClusteringAlgorithm from the algorithm attribute in the pStructInf. + *** + *** @attention - Promises that a failure invokes mssError() at least once. + *** + *** @param inf A parsed pStructInf. + *** @param param_list The param objects that function as a kind of "scope" for + *** evaluating parameter variables in the structure file. + *** @returns The data algorithm, or ALGORITHM_NULL on failure. + ***/ +static ClusterAlgorithm +ci_ParseClusteringAlgorithm(pStructInf inf, pParamObjects param_list) + { + /** Get the algorithm attribute. **/ + char* algorithm; + if (UNLIKELY(ci_ParseAttribute(inf, "algorithm", DATA_T_STRING, POD(&algorithm), param_list, true, true) != 0)) + { + mssError(0, "Cluster", "Failed to parse attribute 'algorithm' in group \"%s\".", inf->Name); + return ALGORITHM_NULL; + } + + /** Parse known clustering algorithms. **/ + if (strcasecmp(algorithm, "none") == 0) return ALGORITHM_NONE; + if (strcasecmp(algorithm, "sliding-window") == 0) return ALGORITHM_SLIDING_WINDOW; + if (strcasecmp(algorithm, "k-means") == 0) return ALGORITHM_KMEANS; + if (strcasecmp(algorithm, "k-means++") == 0) return ALGORITHM_KMEANS_PLUS_PLUS; + if (strcasecmp(algorithm, "k-medoids") == 0) return ALGORITHM_KMEDOIDS; + if (strcasecmp(algorithm, "db-scan") == 0) return ALGORITHM_DB_SCAN; + + /** Unknown value for clustering algorithm. **/ + mssError(1, "Cluster", "Unknown \"clustering algorithm\": %s", algorithm); + + /** Attempt to give a hint. **/ + char* all_names[N_CLUSTERING_ALGORITHMS] = {NULL}; + for (unsigned int i = 1u; i < N_CLUSTERING_ALGORITHMS; i++) + all_names[i] = ci_ClusteringAlgorithmToString(ALL_CLUSTERING_ALGORITHMS[i]); + if (ci_TryHint(algorithm, all_names, N_CLUSTERING_ALGORITHMS)); + else if (strcasecmp(algorithm, "sliding") == 0) ci_GiveHint(ci_ClusteringAlgorithmToString(ALGORITHM_SLIDING_WINDOW)); + else if (strcasecmp(algorithm, "window") == 0) ci_GiveHint(ci_ClusteringAlgorithmToString(ALGORITHM_SLIDING_WINDOW)); + else if (strcasecmp(algorithm, "null") == 0) ci_GiveHint(ci_ClusteringAlgorithmToString(ALGORITHM_NONE)); + else if (strcasecmp(algorithm, "nothing") == 0) ci_GiveHint(ci_ClusteringAlgorithmToString(ALGORITHM_NONE)); + + /** Fail. **/ + return ALGORITHM_NULL; + } + + +// LINK #functions +/*** Parses a SimilarityMeasure from the similarity_measure attribute in the given + *** pStructInf parameter. + *** + *** @attention - Promises that a failure invokes mssError() at least once. + *** + *** @param inf A parsed pStructInf. + *** @param param_list The param objects that function as a kind of "scope" for + *** evaluating parameter variables in the structure file. + *** @returns The similarity measure, or SIMILARITY_NULL on failure. + ***/ +static SimilarityMeasure +ci_ParseSimilarityMeasure(pStructInf inf, pParamObjects param_list) + { + /** Get the similarity_measure attribute. **/ + char* measure; + if (UNLIKELY(ci_ParseAttribute(inf, "similarity_measure", DATA_T_STRING, POD(&measure), param_list, true, true) != 0)) + { + mssError(0, "Cluster", "Failed to parse attribute 'similarity_measure' in group \"%s\".", inf->Name); + return SIMILARITY_NULL; + } + + /** Parse known clustering algorithms. **/ + if (strcasecmp(measure, "cosine") == 0) return SIMILARITY_COSINE; + if (strcasecmp(measure, "levenshtein") == 0) return SIMILARITY_LEVENSHTEIN; + + /** Unknown similarity measure. **/ + mssError(1, "Cluster", "Unknown \"similarity measure\": %s", measure); + + /** Attempt to give a hint. **/ + char* all_names[N_SIMILARITY_MEASURES] = {NULL}; + for (unsigned int i = 1u; i < N_SIMILARITY_MEASURES; i++) + all_names[i] = ci_SimilarityMeasureToString(ALL_SIMILARITY_MEASURES[i]); + if (ci_TryHint(measure, all_names, N_SIMILARITY_MEASURES)); + else if (strcasecmp(measure, "cos") == 0) ci_GiveHint(ci_SimilarityMeasureToString(SIMILARITY_COSINE)); + else if (strcasecmp(measure, "lev") == 0) ci_GiveHint(ci_SimilarityMeasureToString(SIMILARITY_LEVENSHTEIN)); + else if (strcasecmp(measure, "edit-dist") == 0) ci_GiveHint(ci_SimilarityMeasureToString(SIMILARITY_LEVENSHTEIN)); + else if (strcasecmp(measure, "edit-distance") == 0) ci_GiveHint(ci_SimilarityMeasureToString(SIMILARITY_LEVENSHTEIN)); + + /** Fail. **/ + return SIMILARITY_NULL; + } + + +// LINK #functions +/*** Allocates a new pSourceData struct from a parsed pStructInf representing + *** a .cluster structure file. + *** + *** @attention - Warning: Caching in use. + *** @attention - Promises that a failure invokes mssError() at least once. + *** + *** @param inf A parsed pStructInf for a .cluster structure file. + *** @param param_list The param objects that function as a kind of "scope" for + *** evaluating parameter variables in the structure file. + *** @param path The file path to the parsed structure file, used to generate + *** cache entry keys. + *** @returns A new pSourceData struct on success, or NULL on failure. + ***/ +static pSourceData +ci_ParseSourceData(pStructInf inf, pParamObjects param_list, char* path) + { + char* buf = NULL; + pSourceData source_data = NULL; + + /** Magic checks. **/ + ASSERTMAGIC(inf, MGK_STRUCTINF); + + /** Allocate SourceData. **/ + source_data = (pSourceData)check_ptr(nmMalloc(sizeof(SourceData))); + if (UNLIKELY(source_data == NULL)) goto err_free; + memset(source_data, 0, sizeof(SourceData)); + SETMAGIC(source_data, MGK_CL_SOURCE_DATA); + + /** Initialize obvious values for SourceData. **/ + source_data->Name = (char*)check_ptr(nmSysStrdup(inf->Name)); + if (UNLIKELY(source_data->Name == NULL)) goto err_free; + if (check(objCurrentDate(&source_data->DateCreated)) != 0) goto err_free; + + /** Get source. **/ + if (UNLIKELY(ci_ParseAttribute(inf, "source", DATA_T_STRING, POD(&buf), param_list, true, true) != 0)) goto err_free; + source_data->SourcePath = (char*)check_ptr(nmSysStrdup(buf)); + if (UNLIKELY(source_data->SourcePath == NULL)) goto err_free; + + /** Get the attribute name to use when querying keys from the source. **/ + if (UNLIKELY(ci_ParseAttribute(inf, "key_attr", DATA_T_STRING, POD(&buf), param_list, true, true) != 0)) goto err_free; + source_data->KeyAttr = (char*)check_ptr(nmSysStrdup(buf)); + if (UNLIKELY(source_data->KeyAttr == NULL)) goto err_free; + + /** Get the attribute name to use for querying data from the source. **/ + if (UNLIKELY(ci_ParseAttribute(inf, "data_attr", DATA_T_STRING, POD(&buf), param_list, true, true) != 0)) goto err_free; + source_data->DataAttr = (char*)check_ptr(nmSysStrdup(buf)); + if (UNLIKELY(source_data->DataAttr == NULL)) goto err_free; + + /** Create cache entry key. **/ + const size_t len = strlen(path) + + strlen(source_data->SourcePath) + + strlen(source_data->KeyAttr) + + strlen(source_data->DataAttr) + 5lu; + source_data->CacheKey = (char*)check_ptr(nmSysMalloc(len * sizeof(char))); + if (UNLIKELY(source_data->CacheKey == NULL)) goto err_free; + snprintf(source_data->CacheKey, len, + "%s?%s->%s:%s", + path, source_data->SourcePath, source_data->KeyAttr, source_data->DataAttr + ); + + /** Check for a cached version. **/ + pSourceData source_maybe = (pSourceData)xhLookup(&ClusterDriverCaches.SourceDataCache, source_data->CacheKey); + if (source_maybe != NULL) + { /* Cache hit. */ + ASSERTMAGIC(source_maybe, MGK_CL_SOURCE_DATA); + + /** Free data we don't need. **/ + nmSysFree(source_data->CacheKey); + ci_FreeSourceData(source_data); + + /** Return the cached source data. **/ + return source_maybe; + } + + /** Cache miss: Add the new object to the cache for next time. **/ + if (check(xhAdd(&ClusterDriverCaches.SourceDataCache, source_data->CacheKey, (void*)source_data)) != 0) + goto err_free; + + /** Success. **/ + return source_data; + + /** Error handling. **/ + err_free: + if (source_data != NULL) + { + if (source_data->CacheKey != NULL) nmSysFree(source_data->CacheKey); + ci_FreeSourceData(source_data); + } + + mssError(0, "Cluster", + "Failed to parse source data from group \"%s\" in file: %s", + inf->Name, path + ); + + return NULL; + } + + +// LINK #functions +/*** Allocates a new pClusterData struct from a parsed pStructInf. + *** + *** @attention - Warning: Caching in use. + *** @attention - Promises that mssError() will be invoked on failure, so the + *** caller is not required to specify their own error message. + *** + *** @param inf A parsed pStructInf for a cluster group in a structure file. + *** @param param_list The param objects that function as a kind of "scope" for + *** evaluating parameter variables in the structure file. + *** @param source_data The pSourceData that clusters are to be built from, also + *** used to generate cache entry keys. + *** @returns A new pClusterData struct on success, or NULL on failure. + ***/ +static pClusterData +ci_ParseClusterData(pStructInf inf, pParamObjects param_list, pSourceData source_data) + { + int result; + pClusterData cluster_data = NULL; + XArray sub_clusters = {0}; + char* cache_key = NULL; + + if (thExcessiveRecursion()) + { + mssError(1, "Cluster", "Resource exhaustion occurred while parsing cluster data."); + goto err_free; + } + + /** Verify source_data value. **/ + if (UNLIKELY(source_data == NULL)) goto err_free; + ASSERTMAGIC(source_data, MGK_CL_SOURCE_DATA); + + /** Allocate space for data struct. **/ + cluster_data = check_ptr(nmMalloc(sizeof(ClusterData))); + if (UNLIKELY(cluster_data == NULL)) goto err_free; + memset(cluster_data, 0, sizeof(ClusterData)); + SETMAGIC(cluster_data, MGK_CL_CLUSTER_DATA); + + /** Basic fields. **/ + cluster_data->Name = check_ptr(nmSysStrdup(inf->Name)); + if (UNLIKELY(cluster_data->Name == NULL)) goto err_free; + cluster_data->SourceData = source_data; + if (check(objCurrentDate(&cluster_data->DateCreated)) != 0) goto err_free; + + /** Get algorithm. **/ + cluster_data->ClusterAlgorithm = ci_ParseClusteringAlgorithm(inf, param_list); + if (UNLIKELY(cluster_data->ClusterAlgorithm == ALGORITHM_NULL)) goto err_free; + + /** Handle no clustering case. **/ + if (cluster_data->ClusterAlgorithm == ALGORITHM_NONE) + { + cluster_data->nClusters = 1u; + goto parsing_done; + } + + /** Get similarity_measure. **/ + cluster_data->SimilarityMeasure = ci_ParseSimilarityMeasure(inf, param_list); + if (UNLIKELY(cluster_data->SimilarityMeasure == SIMILARITY_NULL)) goto err_free; + + /** Handle sliding window case. **/ + if (cluster_data->ClusterAlgorithm == ALGORITHM_SLIDING_WINDOW) + { + /** Sliding window doesn't allocate any clusters. **/ + cluster_data->nClusters = 0u; + + /** Get window_size. **/ + int window_size; + if (ci_ParseAttribute(inf, "window_size", DATA_T_INTEGER, POD(&window_size), param_list, true, true) != 0) + goto err_free; + if (window_size < 1) + { + mssError(1, "Cluster", "Invalid value for [window_size : uint > 0]: %d", window_size); + goto err_free; + } + + /** Store value. **/ + cluster_data->WindowSize = (unsigned int)window_size; + goto parsing_done; + } + + /** Get num_clusters. **/ + int num_clusters; + if (UNLIKELY(ci_ParseAttribute(inf, "num_clusters", DATA_T_INTEGER, POD(&num_clusters), param_list, true, true) != 0)) + goto err_free; + if (num_clusters < 2) + { + mssError(1, "Cluster", "Invalid value for [num_clusters : uint > 1]: %d", num_clusters); + if (num_clusters == 1) fprintf(stderr, "HINT: Use algorithm=\"none\" to disable clustering.\n"); + goto err_free; + } + cluster_data->nClusters = (unsigned int)num_clusters; + + /** Get min_improvement. **/ + double improvement; + result = ci_ParseAttribute(inf, "min_improvement", DATA_T_DOUBLE, POD(&improvement), param_list, false, false); + if (UNLIKELY(result == -1)) goto err_free; + else if (result == 1) cluster_data->MinImprovement = CI_DEFAULT_MIN_IMPROVEMENT; + else if (result == 0) + { + if (-1.0 <= improvement && improvement <= 1.0) + cluster_data->MinImprovement = improvement; + else + { + mssError(1, "Cluster", "Invalid value for [min_improvement : -1.0 <= x <= 1.0]: %g", improvement); + goto err_free; + } + } + + /** Get max_iterations. **/ + int max_iterations; + result = ci_ParseAttribute(inf, "max_iterations", DATA_T_INTEGER, POD(&max_iterations), param_list, false, true); + if (UNLIKELY(result == -1)) goto err_free; + if (result == 0) + { + if (max_iterations < 1) + { + mssError(1, "Cluster", "Invalid value for [max_iterations : uint > 0]: %d", max_iterations); + goto err_free; + } + cluster_data->MaxIterations = (unsigned int)max_iterations; + } + else cluster_data->MaxIterations = CI_DEFAULT_MAX_ITERATIONS; + + /** Get seed. **/ + int seed; + result = ci_ParseAttribute(inf, "seed", DATA_T_INTEGER, POD(&seed), param_list, false, true); + if (UNLIKELY(result == -1)) goto err_free; + if (result == 0) + { + if (UNLIKELY(seed < 1)) + { + mssError(1, "Cluster", "Invalid value for [seed : uint > 0]: %d", seed); + goto err_free; + } + cluster_data->Seed = (unsigned int)seed; + } + else cluster_data->Seed = CI_NO_SEED; + + /** Search for sub-clusters. **/ + if (check(xaInit(&sub_clusters, 4u)) != 0) goto err_free; + for (unsigned int i = 0u; i < inf->nSubInf; i++) + { + pStructInf sub_inf = check_ptr(inf->SubInf[i]); + if (UNLIKELY(sub_inf == NULL)) + { + mssError(1, "Cluster", "Failed to get %uth subinf.", i); + goto err_free; + } + ASSERTMAGIC(sub_inf, MGK_STRUCTINF); + char* name = sub_inf->Name; + + /** Handle various struct types. **/ + const int struct_type = check_neg(stStructType(sub_inf)); + switch (struct_type) + { + case ST_T_ATTRIB: + { + /** Ignore valid attribute names. **/ + bool is_valid = false; + for (unsigned int i = 0u; i < N_INPUT_CLUSTER_ATTRS; i++) + { + if (strcmp(name, CLUSTER_ATTRS[i]) == 0) + { + is_valid = true; + break; + } + } + if (is_valid) continue; /* Next inf. */ + + /** Give the user a warning, and attempt to give them a hint. **/ + fprintf(stderr, "Warning: Unknown attribute '%s' in cluster \"%s\".\n", name, inf->Name); + if (ci_TryHint(name, CLUSTER_ATTRS, N_INPUT_CLUSTER_ATTRS)); + else if (strcasecmp(name, "k") == 0) ci_GiveHint("num_clusters"); + else if (strcasecmp(name, "threshold") == 0) ci_GiveHint("min_improvement"); + + break; + } + + case ST_T_SUBGROUP: + { + /** Select array by group type. **/ + char* group_type = check_ptr(sub_inf->UsrType); + if (group_type == NULL) goto err_free; + if (strcmp(group_type, "cluster/cluster") != 0) + { + mssError(1, "Cluster", + "Warning: Unknown group [\"%s\" : \"%s\"] in cluster \"%s\".\n", + name, group_type, inf->Name + ); + ci_GiveHint("cluster/cluster"); + continue; + } + + /** Subcluster found. **/ + pClusterData sub_cluster = ci_ParseClusterData(sub_inf, param_list, source_data); + if (sub_cluster == NULL) goto err_free; + sub_cluster->Parent = cluster_data; + if (check_neg(xaAddItem(&sub_clusters, sub_cluster)) < 0) goto err_free; + + break; + } + + default: + { + mssError(1, "Cluster", + "Warning: Unknown struct type %d in cluster \"%s\".", + struct_type, inf->Name + ); + goto err_free; + } + } + } + cluster_data->nSubClusters = sub_clusters.nItems; + cluster_data->SubClusters = (ClusterData**)check_ptr(xaToArray(&sub_clusters)); + if (UNLIKELY(cluster_data->SubClusters == NULL)) goto err_free; + check(xaDeInit(&sub_clusters)); /* Failure ignored. */ + sub_clusters.nAlloc = 0; + + /** Create the cache_key. **/ + parsing_done:; + switch (cluster_data->ClusterAlgorithm) + { + case ALGORITHM_NONE: + { + const size_t len = strlen(source_data->CacheKey) + strlen(cluster_data->Name) + 8lu; + cache_key = check_ptr(nmSysMalloc(len * sizeof(char))); + if (UNLIKELY(cache_key == NULL)) goto err_free; + snprintf(cache_key, len, "%s/%s?%u", + source_data->CacheKey, + cluster_data->Name, + ALGORITHM_NONE + ); + break; + } + + case ALGORITHM_SLIDING_WINDOW: + { + const size_t len = strlen(source_data->CacheKey) + strlen(cluster_data->Name) + 16lu; + cache_key = check_ptr(nmSysMalloc(len * sizeof(char))); + if (UNLIKELY(cache_key == NULL)) goto err_free; + snprintf(cache_key, len, "%s/%s?%u&%u&%u", + source_data->CacheKey, + cluster_data->Name, + ALGORITHM_SLIDING_WINDOW, + cluster_data->SimilarityMeasure, + cluster_data->WindowSize + ); + break; + } + + default: + { + const size_t len = strlen(source_data->CacheKey) + strlen(cluster_data->Name) + 32lu; + cache_key = check_ptr(nmSysMalloc(len * sizeof(char))); + if (UNLIKELY(cache_key == NULL)) goto err_free; + snprintf(cache_key, len, "%s/%s?%u&%u&%u&%g&%u", + source_data->CacheKey, + cluster_data->Name, + cluster_data->ClusterAlgorithm, + cluster_data->SimilarityMeasure, + cluster_data->nClusters, + cluster_data->MinImprovement, + cluster_data->MaxIterations + ); + break; + } + } + cluster_data->CacheKey = cache_key; + + /** Check for a cached version. **/ + pClusterData cluster_maybe = (pClusterData)xhLookup(&ClusterDriverCaches.ClusterDataCache, cache_key); + if (cluster_maybe != NULL) + { /* Cache hit. */ + ASSERTMAGIC(cluster_maybe, MGK_CL_CLUSTER_DATA); + + /** Free the parsed cluster that we no longer need. */ + if (LIKELY(cluster_data != NULL)) ci_FreeClusterData(cluster_data, false); + if (LIKELY(cache_key != NULL)) nmSysFree(cache_key); + + /** Return the cached cluster. **/ + return cluster_maybe; + } + + /** Cache miss. **/ + if (check(xhAdd(&ClusterDriverCaches.ClusterDataCache, cache_key, (void*)cluster_data)) != 0) goto err_free; + return cluster_data; + + /** Error cleanup. **/ + err_free: + if (cache_key != NULL) nmSysFree(cache_key); + + if (sub_clusters.nAlloc != 0) + { + for (unsigned int i = 0u; i < sub_clusters.nItems; i++) + { + pClusterData cur = sub_clusters.Items[i]; + if (cur == NULL) break; + ci_FreeClusterData(cur, true); + } + check(xaDeInit(&sub_clusters)); /* Failure ignored. */ + } + + if (cluster_data != NULL) ci_FreeClusterData(cluster_data, false); + + mssError(0, "Cluster", "Failed to parse cluster from group \"%s\".", inf->Name); + return NULL; + } + + +// LINK #functions +/*** Allocates a new pSearchData struct from a parsed pStructInf. + *** + *** @attention - Warning: Caching in use. + *** @attention - Promises that mssError() will be invoked on failure, so the + *** caller is not required to specify their own error message. + *** + *** @param inf A parsed pStructInf for a search group in a structure file. + *** @param node_data The pNodeData, used to get the param list and to look up + *** the cluster pointed to by the source attribute. + *** @returns A new pSearchData struct on success, or NULL on failure. + ***/ +static pSearchData +ci_ParseSearchData(pStructInf inf, pNodeData node_data) + { + pSearchData search_data = NULL; + char* key = NULL; + + /** Extract values. **/ + pParamObjects param_list = check_ptr(node_data->ParamList); + if (UNLIKELY(param_list == NULL)) goto err_free; + + /** Allocate space for search struct. **/ + search_data = check_ptr(nmMalloc(sizeof(SearchData))); + if (UNLIKELY(search_data == NULL)) goto err_free; + memset(search_data, 0, sizeof(SearchData)); + SETMAGIC(search_data, MGK_CL_SEARCH_DATA); + + /** Get basic information. **/ + search_data->Name = check_ptr(nmSysStrdup(inf->Name)); + if (UNLIKELY(search_data->Name == NULL)) goto err_free; + if (check(objCurrentDate(&search_data->DateCreated)) != 0) goto err_free; + + /** Search for the source cluster. **/ + char* source_cluster_name; + if (ci_ParseAttribute(inf, "source", DATA_T_STRING, POD(&source_cluster_name), param_list, true, true) != 0) goto err_free; + for (unsigned int i = 0; i < node_data->nClusterDatas; i++) + { + pClusterData cluster_data = node_data->ClusterDatas[i]; + ASSERTMAGIC(cluster_data, MGK_CL_CLUSTER_DATA); + if (strcmp(source_cluster_name, cluster_data->Name) == 0) + { + /** SourceCluster found. **/ + search_data->SourceCluster = cluster_data; + break; + } + + /** Note: Subclusters should probably be parsed here, if they were implemented. **/ + } + + /** Did we find the requested source? **/ + if (UNLIKELY(search_data->SourceCluster == NULL)) + { + /** Print error. **/ + mssError(1, "Cluster", "Could not find cluster \"%s\" for search \"%s\".", source_cluster_name, search_data->Name); + + /** Attempt to give a hint. **/ + char* cluster_names[node_data->nClusterDatas]; + for (unsigned int i = 0; i < node_data->nClusterDatas; i++) + cluster_names[i] = node_data->ClusterDatas[i]->Name; + ci_TryHint(source_cluster_name, cluster_names, node_data->nClusterDatas); + + /** Fail. **/ + goto err_free; + } + + /** Get threshold attribute. **/ + if (UNLIKELY(ci_ParseAttribute(inf, "threshold", DATA_T_DOUBLE, POD(&search_data->Threshold), param_list, true, true) != 0)) goto err_free; + if (UNLIKELY(search_data->Threshold <= 0.0 || 1.0 <= search_data->Threshold)) + { + mssError(1, "Cluster", + "Invalid value for [threshold : 0.0 < x < 1.0 | \"none\"]: %g", + search_data->Threshold + ); + goto err_free; + } + + /** Get similarity measure. **/ + search_data->SimilarityMeasure = ci_ParseSimilarityMeasure(inf, param_list); + if (UNLIKELY(search_data->SimilarityMeasure == SIMILARITY_NULL)) goto err_free; + + /** Check for additional data to warn the user about. **/ + for (unsigned int i = 0u; i < inf->nSubInf; i++) + { + pStructInf sub_inf = check_ptr(inf->SubInf[i]); + if (UNLIKELY(sub_inf == NULL)) + { + mssError(1, "Cluster", "Failed to get %uth subinf.", i); + goto err_free; + } + ASSERTMAGIC(sub_inf, MGK_STRUCTINF); + char* name = check_ptr(sub_inf->Name); + if (UNLIKELY(name == NULL)) goto err_free; + + /** Handle various struct types. **/ + const int struct_type = check_neg(stStructType(sub_inf)); + switch (struct_type) + { + case ST_T_ATTRIB: + { + /** Ignore valid attribute names. **/ + bool is_valid = false; + for (unsigned int i = 0u; i < N_INPUT_SEARCH_ATTRS; i++) + { + if (strcmp(name, SEARCH_ATTRS[i]) == 0) + { + is_valid = true; + break; + } + } + if (is_valid) continue; /* Next inf. */ + + /** Give the user a warning, and attempt to give them a hint. **/ + fprintf(stderr, "Warning: Unknown attribute '%s' in search \"%s\".\n", name, inf->Name); + ci_TryHint(name, SEARCH_ATTRS, N_INPUT_SEARCH_ATTRS); + + break; + } + + case ST_T_SUBGROUP: + { + /** The spec does not specify any valid sub-groups for searches. **/ + char* group_type = check_ptr(sub_inf->UsrType); + if (UNLIKELY(group_type == NULL)) goto err_free; + fprintf(stderr, + "Warning: Unknown group [\"%s\" : \"%s\"] in search \"%s\".\n", + name, group_type, inf->Name + ); + break; + } + + default: + { + mssError(1, "Cluster", + "Warning: Unknown struct type %d in search \"%s\".", + struct_type, inf->Name + ); + goto err_free; + } + } + } + + /** Create cache entry key. **/ + char* source_key = search_data->SourceCluster->CacheKey; + const size_t len = strlen(source_key) + strlen(search_data->Name) + 16lu; + key = check_ptr(nmSysMalloc(len * sizeof(char))); + if (UNLIKELY(key == NULL)) goto err_free; + snprintf(key, len, "%s/%s?%g&%u", + source_key, + search_data->Name, + search_data->Threshold, + search_data->SimilarityMeasure + ); + pXHashTable search_cache = &ClusterDriverCaches.SearchDataCache; + + /** Check for a cached version. **/ + pSearchData search_maybe = (pSearchData)xhLookup(search_cache, key); + if (search_maybe != NULL) + { /* Cache hit. */ + ASSERTMAGIC(search_maybe, MGK_CL_SEARCH_DATA); + + /** Free the parsed search that we no longer need. **/ + if (LIKELY(search_data != NULL)) ci_FreeSearchData(search_data); + if (LIKELY(key != NULL)) nmSysFree(key); + + /** Return the cached search. **/ + return search_maybe; + } + + /** Cache miss. **/ + if (check(xhAdd(search_cache, key, (void*)search_data)) != 0) + goto err_free; + + /** Done. **/ + return search_data; + + /** Error cleanup. **/ + err_free: + if (search_data != NULL) ci_FreeSearchData(search_data); + + mssError(0, "Cluster", "Failed to parse SearchData from group \"%s\".", inf->Name); + + return NULL; + } + + +// LINK #functions +/*** Allocates a new pNodeData struct from a parsed pStructInf. + *** + *** @attention - Does not use caching directly, but uses sub-functions to + *** handle caching of substructures. + *** @attention - Promises that mssError() will be invoked on failure, so the + *** caller is not required to specify their own error message. + *** + *** @param inf A parsed pStructInf for the top level group in a .cluster + *** structure file. + *** @param parent The parent object struct. + *** @returns A new pNodeData struct on success, or NULL on failure. + ***/ +static pNodeData +ci_ParseNodeData(pStructInf inf, pObject parent) + { + int ret = -1; + pNodeData node_data = NULL; + XArray param_infs = {0}; + XArray cluster_infs = {0}; + XArray search_infs = {0}; + + /** Magic. **/ + ASSERTMAGIC(inf, MGK_STRUCTINF); + ASSERTMAGIC(parent, MGK_OBJECT); + + /** Get file path. **/ + char* path = check_ptr(objFilePath(parent)); + if (UNLIKELY(path == NULL)) goto err_free; + + /** Allocate node struct data. **/ + node_data = check_ptr(nmMalloc(sizeof(NodeData))); + if (UNLIKELY(node_data == NULL)) goto err_free; + memset(node_data, 0, sizeof(NodeData)); + SETMAGIC(node_data, MGK_CL_NODE_DATA); + node_data->Parent = parent; + + /** Set up param list. **/ + node_data->ParamList = check_ptr(expCreateParamList()); + if (UNLIKELY(node_data->ParamList == NULL)) goto err_free; + node_data->ParamList->Session = check_ptr(parent->Session); + if (UNLIKELY(node_data->ParamList->Session == NULL)) goto err_free; + ret = expAddParamToList(node_data->ParamList, "parameters", (void*)node_data, 0); + if (ret != 0) + { + mssError(0, "Cluster", "Failed to add parameters to the param list scope (error code %d).", ret); + goto err_free; + } + + /** Set the param functions, defined later in the file. **/ + ret = expSetParamFunctions( + node_data->ParamList, + "parameters", + ci_GetParamType, + ci_GetParamValue, + ci_SetParamValue + ); + if (UNLIKELY(ret != 0)) + { + mssError(0, "Cluster", "Failed to set param functions (error code %d).", ret); + goto err_free; + } + + /** Detect relevant groups. **/ + if (check(xaInit(¶m_infs, 8)) != 0) goto err_free; + if (check(xaInit(&cluster_infs, 8)) != 0) goto err_free; + if (check(xaInit(&search_infs, 8)) != 0) goto err_free; + for (unsigned int i = 0u; i < inf->nSubInf; i++) + { + pStructInf sub_inf = check_ptr(inf->SubInf[i]); + if (UNLIKELY(sub_inf == NULL)) + { + mssError(1, "Cluster", "Failed to get %uth subinf.", i); + goto err_free; + } + ASSERTMAGIC(sub_inf, MGK_STRUCTINF); + char* name = sub_inf->Name; + + /** Handle various struct types. **/ + const int struct_type = check_neg(stStructType(sub_inf)); + switch (struct_type) + { + case ST_T_ATTRIB: + { + /** Ignore valid attribute names. **/ + bool is_valid = false; + for (unsigned int i = 0u; i < N_INPUT_ROOT_ATTRS; i++) + { + if (strcmp(name, ROOT_ATTRS[i]) == 0) + { + is_valid = true; + break; + } + } + if (is_valid) continue; /* Next inf. */ + + /** Give the user a warning, and attempt to give them a hint. **/ + fprintf(stderr, "Warning: Unknown attribute '%s' in cluster driver root node \"%s\".\n", name, inf->Name); + ci_TryHint(name, ROOT_ATTRS, N_INPUT_ROOT_ATTRS); + + break; + } + + case ST_T_SUBGROUP: + { + char* group_type = check_ptr(sub_inf->UsrType); + if (UNLIKELY(group_type == NULL)) goto err_free; + if (strcmp(group_type, "cluster/parameter") == 0) + { + if (check_neg(xaAddItem(¶m_infs, sub_inf) < 0)) + goto err_free; + } + else if (strcmp(group_type, "cluster/cluster") == 0) + { + if (check_neg(xaAddItem(&cluster_infs, sub_inf) < 0)) + goto err_free; + } + else if (strcmp(group_type, "cluster/search") == 0) + { + if (check_neg(xaAddItem(&search_infs, sub_inf) < 0)) + goto err_free; + } + else + { + /** Give the user a warning, and attempt to give them a hint. **/ + fprintf(stderr, + "Warning: Unknown group type \"%s\" on group \"%s\".\n", + group_type, sub_inf->Name + ); + ci_TryHint(group_type, (char*[]){ + "cluster/parameter", + "cluster/cluster", + "cluster/search", + NULL, + }, 0u); + } + break; + } + + default: + { + mssError(1, "Cluster", + "Warning: Unknown struct type %d in search \"%s\".", + struct_type, inf->Name + ); + goto err_free; + } + } + } + + /** Extract OpenCtl for use below. **/ + bool has_provided_params = parent != NULL + && parent->Pathname != NULL + && parent->Pathname->OpenCtl != NULL + && parent->Pathname->OpenCtl[parent->SubPtr - 1] != NULL + && parent->Pathname->OpenCtl[parent->SubPtr - 1]->nSubInf > 0 + && parent->Pathname->OpenCtl[parent->SubPtr - 1]->SubInf != NULL; + int num_provided_params = (has_provided_params) ? parent->Pathname->OpenCtl[parent->SubPtr - 1]->nSubInf : 0; + pStruct* provided_params = (has_provided_params) ? parent->Pathname->OpenCtl[parent->SubPtr - 1]->SubInf : NULL; + + /** Allocate space to store params. **/ + node_data->nParams = param_infs.nItems; + const size_t params_size = node_data->nParams * sizeof(pParam); + node_data->Params = check_ptr(nmSysMalloc(params_size)); + if (node_data->Params == NULL) goto err_free; + memset(node_data->Params, 0, params_size); + + /** Iterate over each param in the structure file. **/ + for (unsigned int i = 0u; i < node_data->nParams; i++) + { + pParam param = paramCreateFromInf(param_infs.Items[i]); + if (UNLIKELY(param == NULL)) + { + mssError(0, "Cluster", + "Failed to create param from inf for param #%u: %s", + i, ((pStructInf)param_infs.Items[i])->Name + ); + goto err_free; + } + node_data->Params[i] = param; + + /** Check each provided param to see if the user provided value. **/ + for (unsigned int j = 0u; j < num_provided_params; j++) + { + pStruct provided_param = check_ptr(provided_params[j]); + if (UNLIKELY(provided_param == NULL)) goto err_free; + + /** If this provided param value isn't for the param, ignore it. **/ + if (UNLIKELY(strcmp(provided_param->Name, param->Name) != 0)) continue; + + /** Matched! The user is providing a value for this param. **/ + ret = paramSetValueFromInfNe(param, provided_param, 0, node_data->ParamList, node_data->ParamList->Session); + if (UNLIKELY(ret != 0)) + { + mssError(0, "Cluster", + "Failed to set param value from struct info.\n" + " > Param #%u: %s\n" + " > Provided Param #%u: %n\n" + " > Error code: %d", + i, param->Name, + j, provided_param->Name, + ret + ); + goto err_free; + } + + /** Provided value successfully handled, we're done. **/ + break; + } + + /** Invoke param hints parsing. **/ + ret = paramEvalHints(param, node_data->ParamList, node_data->ParamList->Session); + if (UNLIKELY(ret != 0)) + { + mssError(0, "Cluster", + "Failed to evaluate parameter hints for parameter \"%s\" (error code %d).", + param->Name, ret + ); + goto err_free; + } + } + check(xaDeInit(¶m_infs)); /* Failure ignored. */ + param_infs.nAlloc = 0; + + /** Iterate over provided parameters and warn the user if they specified a parameter that does not exist. **/ + for (unsigned int i = 0u; i < num_provided_params; i++) + { + pStruct provided_param = check_ptr(provided_params[i]); + if (UNLIKELY(provided_param == NULL)) goto err_free; + char* provided_name = provided_param->Name; + + /** Look to see if this provided param actually exists for this driver instance. **/ + for (unsigned int j = 0u; j < node_data->nParams; j++) + if (strcmp(provided_name, node_data->Params[j]->Name) == 0) + goto next_provided_param; + + /** This param doesn't exist, warn the user and attempt to give them a hint. **/ + fprintf(stderr, "Warning: Unknown provided parameter '%s' for cluster file: %s.\n", provided_name, objFileName(parent)); + char** param_names = check_ptr(nmSysMalloc(node_data->nParams * sizeof(char*))); + if (UNLIKELY(param_names == NULL)) goto err_free; + for (unsigned int j = 0u; j < node_data->nParams; j++) + param_names[j] = node_data->Params[j]->Name; + ci_TryHint(provided_name, param_names, node_data->nParams); + if (LIKELY(param_names != NULL)) nmSysFree(param_names); + + next_provided_param:; + } + + /** Parse source data. **/ + node_data->SourceData = ci_ParseSourceData(inf, node_data->ParamList, path); + if (UNLIKELY(node_data->SourceData == NULL)) goto err_free; + + /** Parse each cluster. **/ + node_data->nClusterDatas = cluster_infs.nItems; + if (LIKELY(node_data->nClusterDatas > 0)) + { + const size_t clusters_size = node_data->nClusterDatas * sizeof(pClusterData); + node_data->ClusterDatas = check_ptr(nmSysMalloc(clusters_size)); + if (UNLIKELY(node_data->ClusterDatas == NULL)) goto err_free; + memset(node_data->ClusterDatas, 0, clusters_size); + for (unsigned int i = 0u; i < node_data->nClusterDatas; i++) + { + node_data->ClusterDatas[i] = ci_ParseClusterData(cluster_infs.Items[i], node_data->ParamList, node_data->SourceData); + if (node_data->ClusterDatas[i] == NULL) goto err_free; + } + } + else node_data->ClusterDatas = NULL; + check(xaDeInit(&cluster_infs)); /* Failure ignored. */ + cluster_infs.nAlloc = 0; + + /** Parse each search. **/ + node_data->nSearchDatas = search_infs.nItems; + if (LIKELY(node_data->nSearchDatas > 0)) + { + const size_t searches_size = node_data->nSearchDatas * sizeof(pSearchData); + node_data->SearchDatas = check_ptr(nmSysMalloc(searches_size)); + if (UNLIKELY(node_data->SearchDatas == NULL)) goto err_free; + memset(node_data->SearchDatas, 0, searches_size); + for (unsigned int i = 0u; i < node_data->nSearchDatas; i++) + { + node_data->SearchDatas[i] = ci_ParseSearchData(search_infs.Items[i], node_data); + if (node_data->SearchDatas[i] == NULL) goto err_free; + } + } + else node_data->SearchDatas = NULL; + check(xaDeInit(&search_infs)); /* Failure ignored. */ + search_infs.nAlloc = 0; + + /** Success. **/ + return node_data; + + err_free: + if (param_infs.nAlloc != 0) check(xaDeInit(¶m_infs)); /* Failure ignored. */ + if (cluster_infs.nAlloc != 0) check(xaDeInit(&cluster_infs)); /* Failure ignored. */ + if (search_infs.nAlloc != 0) check(xaDeInit(&search_infs)); /* Failure ignored. */ + if (node_data != NULL) ci_FreeNodeData(node_data); + mssError(0, "Cluster", "Failed to parse node from group \"%s\" in file: %s", inf->Name, path); + + return NULL; + } + + +/** ================ Freeing Functions ================ **/ +/** ANCHOR[id=freeing] **/ +// LINK #functions + +/** @param source_data A pSourceData struct, freed by this function. **/ +static void +ci_FreeSourceData(pSourceData source_data) + { + /** Guard segfault. **/ + if (UNLIKELY(source_data == NULL)) + { + fprintf(stderr, "Warning: Call to ci_FreeSourceData(NULL);\n"); + return; + } + ASSERTMAGIC(source_data, MGK_CL_SOURCE_DATA); + + /** Free top level attributes, if they exist. **/ + /** Note: The key field is handled by the caching system, so we shouldn't free it here. **/ + if (LIKELY(source_data->Name != NULL)) + { + nmSysFree(source_data->Name); + source_data->Name = NULL; + } + if (LIKELY(source_data->SourcePath != NULL)) + { + nmSysFree(source_data->SourcePath); + source_data->SourcePath = NULL; + } + if (LIKELY(source_data->KeyAttr != NULL)) + { + nmSysFree(source_data->KeyAttr); + source_data->KeyAttr = NULL; + } + if (LIKELY(source_data->DataAttr != NULL)) + { + nmSysFree(source_data->DataAttr); + source_data->DataAttr = NULL; + } + + /** Free fetched keys, if they exist. **/ + if (source_data->Keys != NULL) + { + for (unsigned int i = 0u; i < source_data->nDatas; i++) + { + if (source_data->Keys[i] != NULL) + { + nmSysFree(source_data->Keys[i]); + source_data->Keys[i] = NULL; + } + } + nmSysFree(source_data->Keys); + source_data->Keys = NULL; + } + + /** Free fetched data, if it exists. **/ + if (source_data->Strings != NULL) + { + for (unsigned int i = 0u; i < source_data->nDatas; i++) + { + if (source_data->Strings[i] != NULL) + { + nmSysFree(source_data->Strings[i]); + source_data->Strings[i] = NULL; + } + } + nmSysFree(source_data->Strings); + source_data->Strings = NULL; + } + + /** Free computed vectors, if they exist. **/ + if (source_data->Vectors != NULL) + { + for (unsigned int i = 0u; i < source_data->nDatas; i++) + { + if (source_data->Vectors[i] != NULL) + { + ca_free_vector(source_data->Vectors[i]); + source_data->Vectors[i] = NULL; + } + } + nmSysFree(source_data->Vectors); + source_data->Vectors = NULL; + } + + /** Free the source data struct. **/ + nmFree(source_data, sizeof(SourceData)); + source_data = NULL; + + return; + } + + +// LINK #functions +/*** Free pClusterData struct with an option to recursively free subclusters. + *** + *** @param cluster_data The cluster data struct to free. + *** @param recursive Whether to recursively free subclusters. + ***/ +static void +ci_FreeClusterData(pClusterData cluster_data, bool recursive) + { + if (thExcessiveRecursion()) + { + mssError(1, "Cluster", "Resource exhaustion occurred while freeing cluster datas."); + return; + } + + /** Guard segfault. **/ + if (UNLIKELY(cluster_data == NULL)) + { + fprintf(stderr, "Warning: Call to ci_FreeClusterData(NULL, %s);\n", (recursive) ? "true" : "false"); + return; + } + ASSERTMAGIC(cluster_data, MGK_CL_CLUSTER_DATA); + + /** Free attribute data. **/ + if (LIKELY(cluster_data->Name != NULL)) + { + nmSysFree(cluster_data->Name); + cluster_data->Name = NULL; + } + + /** Free computed data, if it exists. **/ + if (cluster_data->Clusters != NULL) + { + for (unsigned int i = 0u; i < cluster_data->nClusters; i++) + { + pCluster cluster = &cluster_data->Clusters[i]; + if (cluster->Indexes != NULL) + { + nmSysFree(cluster->Indexes); + cluster->Indexes = NULL; + } + } + nmSysFree(cluster_data->Clusters); + cluster_data->Clusters = NULL; + } + if (cluster_data->Sims != NULL) + { + nmSysFree(cluster_data->Sims); + cluster_data->Sims = NULL; + } + + /** Free subclusters recursively. **/ + if (cluster_data->SubClusters != NULL) + { + if (recursive) + { + for (unsigned int i = 0u; i < cluster_data->nSubClusters; i++) + { + if (cluster_data->SubClusters[i] != NULL) + { + ci_FreeClusterData(cluster_data->SubClusters[i], recursive); + cluster_data->SubClusters[i] = NULL; + } + } + } + nmSysFree(cluster_data->SubClusters); + cluster_data->SubClusters = NULL; + } + + /** Free the cluster data struct. **/ + nmFree(cluster_data, sizeof(ClusterData)); + cluster_data = NULL; + + return; + } + + +// LINK #functions +/** @param search_data A pSearchData struct, freed by this function. **/ +static void +ci_FreeSearchData(pSearchData search_data) + { + /** Guard segfault. **/ + if (UNLIKELY(search_data == NULL)) + { + fprintf(stderr, "Warning: Call to ci_FreeSearchData(NULL);\n"); + return; + } + ASSERTMAGIC(search_data, MGK_CL_SEARCH_DATA); + + /** Free attribute data. **/ + if (LIKELY(search_data->Name != NULL)) + { + nmSysFree(search_data->Name); + search_data->Name = NULL; + } + + /** Free computed data. **/ + if (search_data->Pairs != NULL) + { + for (unsigned int i = 0; i < search_data->nPairs; i++) + { + nmFree(search_data->Pairs[i], sizeof(Pair)); + search_data->Pairs[i] = NULL; + } + nmSysFree(search_data->Pairs); + search_data->Pairs = NULL; + } + + /** Free the search data struct. **/ + nmFree(search_data, sizeof(SearchData)); + search_data = NULL; + + return; + } + + +// LINK #functions +/** @param node_data A pNodeData struct, freed by this function. **/ +static void +ci_FreeNodeData(pNodeData node_data) + { + /** Guard segfault. **/ + if (UNLIKELY(node_data == NULL)) + { + fprintf(stderr, "Warning: Call to ci_FreeNodeData(NULL);\n"); + return; + } + ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); + + /** Free parsed params, if they exist. **/ + if (LIKELY(node_data->Params != NULL)) + { + for (unsigned int i = 0u; i < node_data->nParams; i++) + { + if (node_data->Params[i] == NULL) break; + paramFree(node_data->Params[i]); + node_data->Params[i] = NULL; + } + nmSysFree(node_data->Params); + node_data->Params = NULL; + } + if (LIKELY(node_data->ParamList != NULL)) + { + expFreeParamList(node_data->ParamList); + node_data->ParamList = NULL; + } + + /** Free parsed clusters, if they exist. **/ + if (node_data->ClusterDatas != NULL) + { + /*** This data is cached, so we should NOT free it! The caching system + *** is responsible for the memory. We only need to free the array + *** holding our pointers to said cached memory. + ***/ + nmSysFree(node_data->ClusterDatas); + node_data->ClusterDatas = NULL; + } + + /** Free parsed searches, if they exist. **/ + if (node_data->SearchDatas != NULL) + { + /*** This data is cached, so we should NOT free it! The caching system + *** is responsible for the memory. We only need to free the array + *** holding our pointers to said cached memory. + ***/ + nmSysFree(node_data->SearchDatas); + node_data->SearchDatas = NULL; + } + + /** Free data source, if one exists. **/ + /*** Note: SourceData is freed last since other free functions may need to + *** access information from this structure when freeing data. + *** (For example, nVector which is used to determine the size of the + *** label struct in each cluster.) + ***/ + if (node_data->SourceData != NULL) + { + /*** This data is cached, so we should NOT free it! The caching system + *** is responsible for the memory. We only need to free the array + *** holding our pointers to said cached memory. + ***/ + node_data->SourceData = NULL; + } + + /** Free the node data. **/ + nmFree(node_data, sizeof(NodeData)); + node_data = NULL; + + return; + } + +/** Frees all data in caches for all cluster driver instances. **/ +static void +ci_ClearCaches(void) + { + /*** Free caches in reverse of the order they are created in case + *** cached data relies on its source during the freeing process. + ***/ + check(xhClearKeySafe(&ClusterDriverCaches.SearchDataCache, ci_CacheFreeSearch, NULL)); /* Failure ignored. */ + check(xhClearKeySafe(&ClusterDriverCaches.ClusterDataCache, ci_CacheFreeCluster, NULL)); /* Failure ignored. */ + check(xhClearKeySafe(&ClusterDriverCaches.SourceDataCache, ci_CacheFreeSourceData, NULL)); /* Failure ignored. */ + + return; + } + + +/** ================ Deep Size Computation Functions ================ **/ +/** ANCHOR[id=sizing] **/ +// LINK #functions + +/*** Returns the deep size of a SourceData struct, including the size of all + *** allocated substructures. + *** + *** Note: The CacheKey field points to data managed by the caching systems, so it + *** is ignored. + *** + *** @param source_data The source data struct to be queried. + *** @returns The size in bytes of the struct and all internal allocated data. + ***/ +static size_t +ci_SizeOfSourceData(pSourceData source_data) + { + /** Guard segfaults. **/ + if (UNLIKELY(source_data == NULL)) + { + fprintf(stderr, "Warning: Call to ci_SizeOfSourceData(NULL);\n"); + return 0u; + } + ASSERTMAGIC(source_data, MGK_CL_SOURCE_DATA); + + size_t size = 0u; + if (source_data->Name != NULL) size += strlen(source_data->Name) * sizeof(char); + if (source_data->SourcePath != NULL) size += strlen(source_data->SourcePath) * sizeof(char); + if (source_data->KeyAttr != NULL) size += strlen(source_data->KeyAttr) * sizeof(char); + if (source_data->DataAttr != NULL) size += strlen(source_data->DataAttr) * sizeof(char); + if (source_data->Keys != NULL) + { + for (unsigned int i = 0u; i < source_data->nDatas; i++) + size += strlen(source_data->Keys[i]) * sizeof(char); + size += source_data->nDatas * sizeof(char*); + } + if (source_data->Strings != NULL) + { + for (unsigned int i = 0u; i < source_data->nDatas; i++) + size += strlen(source_data->Strings[i]) * sizeof(char); + size += source_data->nDatas * sizeof(char*); + } + if (source_data->Vectors != NULL) + { + for (unsigned int i = 0u; i < source_data->nDatas; i++) + size += ca_sparse_len(source_data->Vectors[i]) * sizeof(int); + size += source_data->nDatas * sizeof(pVector); + } + size += sizeof(SourceData); + + return size; + } + + +// LINK #functions +/*** Returns the deep size of a ClusterData struct, including the size of all + *** allocated substructures. + *** + *** Note: The CacheKey field points to data managed by the caching systems, so it + *** is ignored. + *** + *** @param cluster_data The cluster data struct to be queried. + *** @param recursive Whether to recursively free subclusters. + *** @returns The size in bytes of the struct and all internal allocated data. + ***/ +static size_t +ci_SizeOfClusterData(pClusterData cluster_data, bool recursive) + { + if (thExcessiveRecursion()) + { + mssError(1, "Cluster", "Resource exhaustion occurred while counting size of cluster data structs."); + return 0u; + } + + /** Guard segfaults. **/ + if (UNLIKELY(cluster_data == NULL)) + { + fprintf(stderr, "Warning: Call to ci_SizeOfClusterData(NULL, %s);\n", (recursive) ? "true" : "false"); + return 0u; + } + ASSERTMAGIC(cluster_data, MGK_CL_CLUSTER_DATA); + + size_t size = 0u; + if (cluster_data->Name != NULL) size += strlen(cluster_data->Name) * sizeof(char); + if (cluster_data->Clusters != NULL) + { + for (unsigned int i = 0u; i < cluster_data->nClusters; i++) + size += cluster_data->Clusters[i].Size * (sizeof(int)); + size += cluster_data->nClusters * (sizeof(Cluster) + sizeof(double)); + } + if (cluster_data->SubClusters != NULL) + { + if (recursive) + { + for (unsigned int i = 0u; i < cluster_data->nSubClusters; i++) + size += ci_SizeOfClusterData(cluster_data->SubClusters[i], recursive); + } + size += cluster_data->nSubClusters * sizeof(pClusterData); + } + size += sizeof(ClusterData); + + return size; + } + + +// LINK #functions +/*** Returns the deep size of a SearchData struct, including the size of all + *** allocated substructures. + *** + *** Note: The CacheKey field points to data managed by the caching systems, so it + *** is ignored. + *** + *** @param search_data The search data struct to be queried. + *** @returns The size in bytes of the struct and all internal allocated data. + ***/ +static size_t +ci_SizeOfSearchData(pSearchData search_data) + { + /** Guard segfaults. **/ + if (UNLIKELY(search_data == NULL)) + { + fprintf(stderr, "Warning: Call to ci_SizeOfSearchData(NULL);\n"); + return 0u; + } + ASSERTMAGIC(search_data, MGK_CL_SEARCH_DATA); + + unsigned int size = 0u; + if (search_data->Name != NULL) size += strlen(search_data->Name) * sizeof(char); + if (search_data->Pairs != NULL) size += search_data->nPairs * (sizeof(pPair) + sizeof(Pair)); + size += sizeof(SearchData); + + return size; + } + + +/** ================ Computation Functions ================ **/ +/** ANCHOR[id=computation] **/ +// LINK #functions + +/*** Ensures that the fetched/computed attributes for `source_data` have been + *** computed, fetching from the data source and computing vectors if needed. + *** + *** @attention - Promises that mssError() will be invoked on failure. + *** + *** @param source_data The pSourceData who's attributes should be computed. + *** @param session The current session, used to open the data source. + *** @returns 0 if successful, or + *** -1 other value on failure. + ***/ +static int +ci_ComputeSourceData(pSourceData source_data, pObjSession session) + { + bool successful = false; + int ret; + pObject obj = NULL; + pObjQuery query = NULL; + XArray key_xarray = {0}; + XArray data_xarray = {0}; + XArray vector_xarray = {0}; + + /** Guard segfaults. **/ + if (UNLIKELY(check_ptr(source_data) == NULL)) goto end_free; + ASSERTMAGIC(source_data, MGK_CL_SOURCE_DATA); + + /** If the vectors are already computed, we're done. **/ + if (LIKELY(source_data->Vectors != NULL)) + { + successful = true; + goto end_free; + } + + /** Record the date and time. **/ + if (check(objCurrentDate(&source_data->DateComputed)) != 0) goto end_free; + + /** Open the source path specified by the .cluster file. **/ + obj = objOpen(session, source_data->SourcePath, OBJ_O_RDONLY, 0600, "system/directory"); + if (UNLIKELY(obj == NULL)) + { + mssError(0, "Cluster", "Failed to open object driver."); + goto end_free; + } + + /** Generate a "query" for retrieving data. **/ + query = objOpenQuery(obj, NULL, NULL, NULL, NULL, 0); + if (UNLIKELY(query == NULL)) + { + mssError(0, "Cluster", "Failed to open query."); + goto end_free; + } + + /** Initialize an xarray to store the retrieved data. **/ + if (check(xaInit(&key_xarray, 64)) != 0) goto end_free; + if (check(xaInit(&data_xarray, 64)) != 0) goto end_free; + if (check(xaInit(&vector_xarray, 64)) != 0) goto end_free; + + /** Fetch data and build vectors. **/ + pObject entry; + while ((entry = objQueryFetch(query, O_RDONLY)) != NULL) + { + ASSERTMAGIC(entry, MGK_OBJECT); + + /** Data value: Type checking. **/ + const int data_datatype = objGetAttrType(entry, source_data->DataAttr); + if (UNLIKELY(data_datatype == -1)) + { + mssError(0, "Cluster", + "Failed to get type for data of entry #%u.", + vector_xarray.nItems + ); + goto end_free; + } + if (UNLIKELY(data_datatype != DATA_T_STRING)) + { + mssError(1, "Cluster", + "Type for data of entry #%u was %s instead of String:\n", + vector_xarray.nItems, objTypeToStr(data_datatype) + ); + goto end_free; + } + + /** Data value: Get value from database. **/ + char* data; + ret = objGetAttrValue(entry, source_data->DataAttr, DATA_T_STRING, POD(&data)); + if (UNLIKELY(ret != 0)) + { + mssError(0, "Cluster", + "Failed to get attribute value for data entry #%u (error code: %d).", + vector_xarray.nItems, ret + ); + goto end_free; + } + + /** Skip empty strings. **/ + if (strlen(data) == 0) continue; + + /** Convert the string to a vector. **/ + pVector vector = ca_build_vector(data); + if (UNLIKELY(vector == NULL)) + { + mssError(1, "Cluster", "Failed to build vectors for string \"%s\".", data); + goto end_free; + } + if (UNLIKELY(ca_is_empty(vector))) + { + mssError(1, "Cluster", "Vector building for string \"%s\" produced no character pairs.", data); + goto end_free; + } + if (ca_has_no_pairs(vector)) + { + /** Skip pVector with only a single pair of boundary characters. **/ + ca_free_vector(vector); + continue; + } + + + /** Key value: Type checking. **/ + const int key_datatype = objGetAttrType(entry, source_data->KeyAttr); + if (UNLIKELY(key_datatype == -1)) + { + mssError(0, "Cluster", + "Failed to get type of key on entry #%u.", + vector_xarray.nItems + ); + goto end_free; + } + if (UNLIKELY(key_datatype != DATA_T_STRING)) + { + mssError(1, "Cluster", + "Type of key on entry #%u was %s instead of String:", + vector_xarray.nItems, objTypeToStr(key_datatype) + ); + goto end_free; + } + + /** key value: Get value from database. **/ + char* key; + ret = objGetAttrValue(entry, source_data->KeyAttr, DATA_T_STRING, POD(&key)); + if (UNLIKELY(ret != 0)) + { + mssError(0, "Cluster", + "Failed to value for key on entry #%u (error code: %d).", + vector_xarray.nItems, ret + ); + goto end_free; + } + + /** Store values. **/ + char* key_dup = check_ptr(nmSysStrdup(key)); + if (key_dup == NULL) goto end_free; + char* data_dup = check_ptr(nmSysStrdup(data)); + if (data_dup == NULL) goto end_free; + if (check_neg(xaAddItem(&key_xarray, (void*)key_dup) < 0)) goto end_free; + if (check_neg(xaAddItem(&data_xarray, (void*)data_dup) < 0)) goto end_free; + if (check_neg(xaAddItem(&vector_xarray, (void*)vector) < 0)) goto end_free; + + /** Clean up. **/ + check(objClose(entry)); /* Failure ignored. */ + } + + source_data->nDatas = vector_xarray.nItems; + if (UNLIKELY(source_data->nDatas == 0)) + { + mssError(0, "Cluster", "Data source path did not contain any valid data:\n"); + goto end_free; + } + + /** Trim and store keys. **/ + source_data->Keys = (char**)check_ptr(xaToArray(&key_xarray)); + if (UNLIKELY(source_data->Keys == NULL)) goto end_free; + check(xaDeInit(&key_xarray)); /* Failure ignored. */ + key_xarray.nAlloc = 0; + + /** Trim and store data strings. **/ + source_data->Strings = (char**)check_ptr(xaToArray(&data_xarray)); + if (UNLIKELY(source_data->Strings == NULL)) goto end_free; + check(xaDeInit(&data_xarray)); /* Failure ignored. */ + data_xarray.nAlloc = 0; + + /** Trim and store vectors. **/ + source_data->Vectors = (int**)check_ptr(xaToArray(&vector_xarray)); + if (UNLIKELY(source_data->Vectors == NULL)) goto end_free; + check(xaDeInit(&vector_xarray)); /* Failure ignored. */ + vector_xarray.nAlloc = 0; + + /** Success. **/ + successful = true; + + end_free: + /** Print an error if the function failed. **/ + if (UNLIKELY(!successful)) + { + mssError(0, "Cluster", + "SourceData computation failed:\n" + " > Key Attribute: ['%s' : String]\n" + " > Data Attribute: ['%s' : String]\n" + " > Source Path: \"%s\"\n" + " > Driver Used: %s", + source_data->KeyAttr, + source_data->DataAttr, + source_data->SourcePath, + (obj == NULL || obj->Driver == NULL) ? "Unavailable" : obj->Driver->Name + ); + + /** Free computed data. **/ + if (source_data->Keys != NULL) + { + nmSysFree(source_data->Keys); + source_data->Keys = NULL; + } + if (source_data->Strings != NULL) + { + nmSysFree(source_data->Strings); + source_data->Strings = NULL; + } + if (source_data->Vectors != NULL) + { + nmSysFree(source_data->Vectors); + source_data->Vectors = NULL; + } + } + + /** Clean up xarrays. **/ + /** Unlikely because (assuming no errors) these should already be freed. **/ + if (UNLIKELY(key_xarray.nAlloc != 0)) + { + for (unsigned int i = 0u; i < key_xarray.nItems; i++) + { + char* key = key_xarray.Items[i]; + if (key != NULL) nmSysFree(key); + } + check(xaDeInit(&key_xarray)); /* Failure ignored. */ + } + if (UNLIKELY(data_xarray.nAlloc != 0)) + { + for (unsigned int i = 0u; i < data_xarray.nItems; i++) + { + char* str = data_xarray.Items[i]; + if (str != NULL) nmSysFree(str); + } + check(xaDeInit(&data_xarray)); /* Failure ignored. */ + } + if (UNLIKELY(vector_xarray.nAlloc != 0)) + { + for (unsigned int i = 0u; i < vector_xarray.nItems; i++) + { + pVector vec = vector_xarray.Items[i]; + if (vec != NULL) ca_free_vector(vec); + } + check(xaDeInit(&vector_xarray)); /* Failure ignored. */ + } + + /** Clean up query & object structs. **/ + if (LIKELY(query != NULL)) check(objQueryClose(query)); /* Failure ignored. */ + if (LIKELY(obj != NULL)) check(objClose(obj)); /* Failure ignored. */ + + /** Return the function status code. **/ + return (successful) ? 0 : -1; + } + + +// LINK #functions +/*** Ensures that the computed attributes for `cluster_data` have been + *** computed, running the specified clustering algorithm if necessary. + *** + *** @attention - Promises that mssError() will be invoked on failure. + *** + *** @param cluster_data The pClusterData who's attributes should be computed. + *** @param node_data The current pNodeData, used to get vectors to cluster. + *** @returns 0 if successful, or + *** -1 other value on failure. + ***/ +static int +ci_ComputeClusterData(pClusterData cluster_data, pNodeData node_data) + { + size_t clusters_size = -1; + size_t sims_size = -1; + + /** Guard segfaults. **/ + if (UNLIKELY(check_ptr(cluster_data) == NULL || check_ptr(node_data) == NULL)) goto err_free; + ASSERTMAGIC(cluster_data, MGK_CL_CLUSTER_DATA); + ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); + + /** If the clusters are already computed, we're done. **/ + if (LIKELY(cluster_data->Clusters != NULL)) return 0; + + /** Make source data available. **/ + pSourceData source_data = check_ptr(node_data->SourceData); + if (UNLIKELY(source_data == NULL)) + { + mssError(1, "Cluster", "Failed to get source data for cluster computation."); + goto err_free; + } + ASSERTMAGIC(source_data, MGK_CL_SOURCE_DATA); + + /** We need the SourceData vectors to compute clusters. **/ + pObjSession session = check_ptr(node_data->ParamList->Session); + if (UNLIKELY(session == NULL)) goto err_free; + ASSERTMAGIC(session, MGK_OBJSESSION); + if (UNLIKELY(ci_ComputeSourceData(source_data, session) != 0)) + { + mssError(0, "Cluster", "ClusterData computation failed due to missing SourceData."); + goto err_free; + } + ASSERTMAGIC(source_data, MGK_CL_SOURCE_DATA); + + /** Record the date and time. **/ + if (check(objCurrentDate(&cluster_data->DateComputed)) != 0) goto err_free; + + /** Allocate static memory for finding clusters. **/ + clusters_size = cluster_data->nClusters * sizeof(Cluster); + sims_size = source_data->nDatas * sizeof(double); + cluster_data->Clusters = check_ptr(nmSysMalloc(clusters_size)); + cluster_data->Sims = check_ptr(nmSysMalloc(sims_size)); + if (UNLIKELY(cluster_data->Clusters == NULL)) goto err_free; + if (UNLIKELY(cluster_data->Sims == NULL)) goto err_free; + memset(cluster_data->Clusters, 0, clusters_size); + memset(cluster_data->Sims, 0, sims_size); + + /** Execute clustering. **/ + switch (cluster_data->ClusterAlgorithm) + { + case ALGORITHM_NONE: + { + /** Use a single cluster. **/ + /*** Note: There will only be a single cluster because `cluster_data->nClusters` + *** is set to 1 during parsing when the clustering algorithm is NONE. + ***/ + pCluster only_cluster = &cluster_data->Clusters[0]; + SETMAGIC(only_cluster, MGK_CL_CLUSTER); + + /** Add all data points to that cluster. **/ + only_cluster->Size = source_data->nDatas; + only_cluster->Indexes = check_ptr(nmSysMalloc(only_cluster->Size * sizeof(int))); + if (UNLIKELY(only_cluster->Indexes == NULL)) goto err_free; + for (unsigned int i = 0u; i < only_cluster->Size; i++) + only_cluster->Indexes[i] = i; + + break; + } + + case ALGORITHM_SLIDING_WINDOW: + /** Computed in each search for efficiency. **/ + memset(cluster_data->Clusters, 0, clusters_size); + break; + + case ALGORITHM_KMEANS: + { + /** Check for unimplemented similarity measures. **/ + if (UNLIKELY(cluster_data->SimilarityMeasure != SIMILARITY_COSINE)) + { + mssError(1, "Cluster", + "The similarity measure \"%s\" is not implemented for 'k-means' clusters.", + ci_SimilarityMeasureToString(cluster_data->SimilarityMeasure) + ); + goto err_free; + } + + /** Allocate labels. Note: ca_kmeans() initializes labels for us. **/ + const size_t labels_size = source_data->nDatas * sizeof(unsigned int); + unsigned int* labels = check_ptr(nmSysMalloc(labels_size)); + if (UNLIKELY(labels == NULL)) goto err_free; + + /** Handle seed for ca_kmeans(). **/ + const bool auto_seed = (cluster_data->Seed == CI_NO_SEED); + if (!auto_seed) srand(cluster_data->Seed); + + /** Run ca_kmeans(). **/ + const bool successful = (check(ca_kmeans( + source_data->Vectors, + source_data->nDatas, + cluster_data->nClusters, + cluster_data->MaxIterations, + cluster_data->MinImprovement, + labels, + cluster_data->Sims, + auto_seed + )) == 0); + if (UNLIKELY(!successful)) goto err_free; + + /** Convert the labels into clusters. **/ + + /** Allocate temporary xArrays for tracking the indices stored in each cluster. **/ + XArray indexes_in_cluster[cluster_data->nClusters]; + for (unsigned int i = 0u; i < cluster_data->nClusters; i++) + if (check(xaInit(&indexes_in_cluster[i], 8)) != 0) goto err_free; + + /** Iterate through each label and add the index of the specified cluster to the xArray. **/ + for (unsigned long long i = 0llu; i < source_data->nDatas; i++) + if (check_neg(xaAddItem(&indexes_in_cluster[labels[i]], (void*)i)) < 0) goto err_free; + nmSysFree(labels); /* Free unused data. */ + + /** Store the indices for each cluster and free the temporary xArray. **/ + for (unsigned int i = 0u; i < cluster_data->nClusters; i++) + { + pXArray indexes_in_this_cluster = &indexes_in_cluster[i]; + pCluster cluster = &cluster_data->Clusters[i]; + SETMAGIC(cluster, MGK_CL_CLUSTER); + + /** Store the data in the cluster. **/ + cluster->Size = indexes_in_this_cluster->nItems; + if (cluster->Size == 0) + { + cluster->Indexes = NULL; + continue; + } + cluster->Indexes = check_ptr(nmSysMalloc(cluster->Size * sizeof(unsigned int*))); + if (UNLIKELY(cluster->Indexes == NULL)) goto err_free; + for (unsigned int i = 0u; i < indexes_in_this_cluster->nItems; i++) + { + const unsigned long long index = (unsigned long long)indexes_in_this_cluster->Items[i]; + if (UNLIKELY(index > __UINT32_MAX__)) + { + mssError(1, "Cluster", + "How did you try to cluster more than %u data points and ci_ComputeSearchData() " + "was the first thing to break?! Well... looks like it's time to update %s:%s to " + "handle a larger amount of data.", + __UINT32_MAX__, __FILE__, __LINE__ + ); + goto err_free; + } + cluster->Indexes[i] = (unsigned int)index; + } + check(xaDeInit(indexes_in_this_cluster)); /* Failure ignored. */ + } + + /** k-means done. **/ + break; + } + + default: + mssError(1, "Cluster", + "Clustering algorithm \"%s\" is not implemented.", + ci_ClusteringAlgorithmToString(cluster_data->ClusterAlgorithm) + ); + goto err_free; + } + + /** Success. **/ + return 0; + + err_free: + if (cluster_data->Sims != NULL) + { + nmFree(cluster_data->Sims, sims_size); + cluster_data->Sims = NULL; + } + + if (cluster_data->Clusters != NULL) + { + for (unsigned int i = 0u; i < cluster_data->nClusters; i++) + { + /*** NOTE: The clusters here do not need to each be freed + *** individually because the structs themselves are stored + *** directly in the cluster_data->Clusters array. + *** Thus, this loop only frees each cluster's content. + ***/ + const pCluster cluster = &cluster_data->Clusters[i]; + + /** Skip the cluster if its data hasn't been set. **/ + if (cluster_data->Clusters[i].Magic == 0) continue; + + /** Free the data for the cluster. **/ + ASSERTMAGIC(cluster, MGK_CL_CLUSTER); + if (cluster->Indexes != NULL) + { + nmSysFree(cluster->Indexes); + cluster->Indexes = NULL; + } + } + nmFree(cluster_data->Clusters, clusters_size); + cluster_data->Clusters = NULL; + } + + mssError(0, "Cluster", "ClusterData computation failed for \"%s\".", cluster_data->Name); + + return -1; + } + + +// LINK #functions +/*** Ensures that the computed attributes for `search_data` are computed, + *** running the a search with the specified similarity measure if necessary. + *** + *** @attention - Promises that mssError() will be invoked on failure. + *** + *** @param cluster_data The pSearchData who's attributes should be computed. + *** @param node_data The current pNodeData, used to get vectors to cluster. + *** @returns 0 if successful, or + *** -1 other value on failure. + ***/ +static int +ci_ComputeSearchData(pSearchData search_data, pNodeData node_data) + { + pXArray pairs = NULL; + + /** Guard segfaults. **/ + if (UNLIKELY(check_ptr(search_data) == NULL || check_ptr(node_data) == NULL)) goto err_free; + ASSERTMAGIC(search_data, MGK_CL_SEARCH_DATA); + ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); + + /** If the clusters are already computed, we're done. **/ + if (LIKELY(search_data->Pairs != NULL)) return 0; + + /** We need the cluster data to be computed before we search it. **/ + pClusterData cluster_data = check_ptr(search_data->SourceCluster); + if (UNLIKELY(cluster_data == NULL)) + { + mssError(1, "Cluster", "Failed to get cluster data for search computation."); + goto err_free; + } + ASSERTMAGIC(cluster_data, MGK_CL_CLUSTER_DATA); + if (UNLIKELY(ci_ComputeClusterData(cluster_data, node_data) != 0)) + { + mssError(0, "Cluster", "SearchData computation failed due to missing clusters."); + goto err_free; + } + + /** Extract source data. **/ + pSourceData source_data = cluster_data->SourceData; + ASSERTMAGIC(source_data, MGK_CL_SOURCE_DATA); + + /** Record the date and time. **/ + if (check(objCurrentDate(&search_data->DateComputed)) != 0) goto err_free; + + /** Get the comparison function based on the similarity measure. **/ + const double (*similarity_function)(void *, void *) = check_ptr(ci_SimilarityMeasureToFunction(search_data->SimilarityMeasure)); + if (UNLIKELY(similarity_function == NULL)) goto err_free; + + /** Execute the search using the specified algorithm. **/ + if (cluster_data->ClusterAlgorithm == ALGORITHM_SLIDING_WINDOW) + { + /*** Note: We don't need to examine the clusters because nothing + *** was computed during the clustering phase. + ***/ + + /** Get a pointer to the data that will be used for the search. **/ + void** data = NULL; + switch (search_data->SimilarityMeasure) + { + case SIMILARITY_COSINE: data = (void**)source_data->Vectors; break; + case SIMILARITY_LEVENSHTEIN: data = (void**)source_data->Strings; break; + default: + mssError(1, "Cluster", + "Unknown similarity measure \"%s\".", + ci_SimilarityMeasureToString(search_data->SimilarityMeasure) + ); + goto err_free; + } + + /** Execute sliding search. **/ + pairs = check_ptr(ca_sliding_search( + data, + source_data->nDatas, + cluster_data->WindowSize, + similarity_function, + search_data->Threshold, + NULL + )); + if (UNLIKELY(pairs == NULL)) + { + mssError(1, "Cluster", + "Failed to compute sliding search with %s similarity measure.", + ci_SimilarityMeasureToString(search_data->SimilarityMeasure) + ); + goto err_free; + } + } + else + { + /** Initialize the pairs array with a size of double the amount of data. **/ + const int guess_size = search_data->SourceCluster->SourceData->nDatas * 2; + pairs = check_ptr(xaNew(guess_size)); + if (UNLIKELY(pairs == NULL)) goto err_free; + + /** Iterate over each cluster. **/ + for (unsigned int i = 0u; i < cluster_data->nClusters; i++) + { + /** Extract the struct for the cluster. **/ + pCluster cluster = &cluster_data->Clusters[i]; + ASSERTMAGIC(cluster, MGK_CL_CLUSTER); + + /** Get a pointer to the data of the type needed for the search. **/ + void** data = NULL; + switch (search_data->SimilarityMeasure) + { + case SIMILARITY_COSINE: data = (void**)source_data->Vectors; break; + case SIMILARITY_LEVENSHTEIN: data = (void**)source_data->Strings; break; + default: + mssError(1, "Cluster", + "Unknown similarity measure \"%s\".", + ci_SimilarityMeasureToString(search_data->SimilarityMeasure) + ); + goto err_free; + } + + /** Filter the data to only include values in the current cluster. **/ + void** filtered_data = data; + bool free_filtered_data = false; + if (cluster_data->nClusters > 1) + { + /** Allocate space. **/ + filtered_data = check_ptr(nmSysMalloc(cluster->Size * sizeof(void*))); + if (filtered_data == NULL) goto err_free; + free_filtered_data = true; + + /** Add filtered data. **/ + for (unsigned int i = 0u; i < cluster->Size; i++) + filtered_data[i] = data[cluster->Indexes[i]]; + } + + /** Execute complete search. **/ + const pXArray cluster_pairs = check_ptr(ca_complete_search( + filtered_data, + cluster->Size, + similarity_function, + search_data->Threshold, + NULL + )); + if (free_filtered_data) nmSysFree(filtered_data); + if (UNLIKELY(cluster_pairs == NULL)) + { + mssError(1, "Cluster", + "Failed to compute ca_complete_search() with %s similarity measure.", + ci_SimilarityMeasureToString(search_data->SimilarityMeasure) + ); + goto err_free; + } + + /** Remap the pairs to point to index into the SourceData arrays instead of filtered_data. **/ + for (unsigned int i = 0u; i < cluster_pairs->nItems; i++) + { + const pPair pair = (pPair)cluster_pairs->Items[i]; + pair->i = cluster->Indexes[pair->i]; + pair->j = cluster->Indexes[pair->j]; + if (check_neg(xaAddItem(pairs, pair)) < 0) goto err_free; + } + check(xaFree(cluster_pairs)); /* Failure ignored. */ + } + } + + /** Store pairs. **/ + search_data->nPairs = pairs->nItems; + if (pairs->nItems == 0) + { + search_data->Pairs = check_ptr(nmSysMalloc(0)); + if (search_data->Pairs == NULL) goto err_free; + } + else + { + search_data->Pairs = (pPair*)check_ptr(xaToArray(pairs)); + if (search_data->Pairs == NULL) goto err_free; + check(xaFree(pairs)); /* Failure ignored. */ + pairs = NULL; + } + + /** Success. **/ + return 0; + + err_free: + if (search_data->Pairs != NULL) + { + nmSysFree(search_data->Pairs); + search_data->Pairs = NULL; + } + if (pairs != NULL) + { + for (unsigned int i = 0u; i < pairs->nItems; i++) + { + if (pairs->Items[i] != NULL) nmFree(pairs->Items[i], sizeof(Pair)); + else break; + } + check(xaFree(pairs)); /* Failure ignored. */ + } + + mssError(0, "Cluster", "SearchData computation failed for \"%s\".", search_data->Name); + + return -1; + } + + +/** ================ Parameter Functions ================ **/ +/** ANCHOR[id=params] **/ +// LINK #functions + +/*** Get the type of a parameter. Intended for use in `expSetParamFunctions()`. + *** + *** @param inf_v Node data containing the list of parameters. + *** @param attr_name The name of the requested parameter. + *** @returns The datatype, see datatypes.h for a list of valid datatypes. + *** + *** LINK ../../centrallix-lib/include/datatypes.h:72 + ***/ +static int +ci_GetParamType(void* inf_v, const char* attr_name) + { + pNodeData node_data = check_ptr(inf_v); + if (UNLIKELY(node_data == NULL)) return DATA_T_UNAVAILABLE; + ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); + + /** Find the parameter. **/ + for (unsigned int i = 0; i < node_data->nParams; i++) + { + const pParam param = check_ptr(node_data->Params[i]); + if (UNLIKELY(param == NULL)) continue; + if (strcmp(param->Name, attr_name) != 0) continue; + + /** Parameter found. **/ + return (param->Value == NULL) ? DATA_T_UNAVAILABLE : param->Value->DataType; + } + + /** Parameter not found. **/ + return DATA_T_UNAVAILABLE; + } + + +// LINK #functions +/*** Get the value of a parameter. Intended for use in `expSetParamFunctions()`. + *** + *** @attention - Warning: If the retrieved value is `NULL`, the pObjectData + *** val is not updated, and the function returns 1, indicating `NULL`, + *** similar to other Centrallix functions. + *** + *** @param inf_v Node data containing the list of parameters. + *** @param attr_name The name of the requested parameter. + *** @param datatype The expected datatype of the parameter value. + *** See datatypes.h for a list of valid datatypes. + *** @param val A pointer to a location where a pointer to the requested + *** data should be stored. Typically, the caller creates a local variable + *** to store this pointer, then passes a pointer to that local variable + *** so that they will have a pointer to the data. + *** This buffer will not be modified unless the data is successfully + *** found. If a value other than 0 is returned, the buffer is not updated. + *** @returns 0 if successful, + *** 1 if the variable is null, + *** -1 if an error occurs. + *** + *** LINK ../../centrallix-lib/include/datatypes.h:72 + ***/ +static int +ci_GetParamValue(void* inf_v, char* attr_name, int datatype, pObjData val) + { + pNodeData node_data = check_ptr(inf_v); + if (UNLIKELY(node_data == NULL)) goto err; + ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); + + /** Find the parameter. **/ + for (unsigned int i = 0; i < node_data->nParams; i++) + { + pParam param = (pParam)node_data->Params[i]; + if (UNLIKELY(param == NULL)) continue; + if (strcmp(param->Name, attr_name) != 0) continue; + + /** Parameter found. **/ + if (param->Value == NULL) return 1; + if (param->Value->Flags & DATA_TF_NULL) return 1; + if (UNLIKELY(param->Value->DataType != datatype)) + { + mssError(1, "Cluster", "Type mismatch accessing parameter '%s'.", param->Name); + return -1; + } + + /** Return param value. **/ + if (check(objCopyData(&(param->Value->Data), val, datatype)) != 0) goto err; + return 0; + } + + err: + mssError(1, "Cluster", + "Failed to get parameter ['%s' : %s]", + attr_name, objTypeToStr(datatype) + ); + + return -1; + } + +// LINK #functions +/** Not implemented. **/ +static int +ci_SetParamValue(void* inf_v, char* attr_name, int datatype, pObjData val) + { + mssError(1, "Cluster", "SetParamValue() is not implemented because clusters are immutable."); + + return -1; + } + + +/** ================ Driver functions ================ **/ +/** ANCHOR[id=driver] **/ +// LINK #functions + +/*** Opens a new cluster driver instance by parsing a `.cluster` file found + *** at the path provided in parent. + *** + *** @param parent The parent of the object to be opened, including useful + *** information such as the pathname, session, etc. + *** @param mask Driver permission mask (unused). + *** @param sys_type ? (unused) + *** @param usr_type The object system file type being opened. Should always + *** be "system/cluster" because this driver is only registered for that + *** type of file. + *** @param oxt The transaction tree (for the incomplete transaction system). + *** + *** @returns A pDriverData struct representing a driver instance, or + *** NULL if an error occurs. + ***/ +void* +clusterOpen(pObject parent, int mask, pContentType sys_type, char* usr_type, pObjTrxTree* oxt) + { + pNodeData node_data = NULL; + pDriverData driver_data = NULL; + + /** Update statistics. **/ + ClusterStatistics.OpenCalls++; + + /** Guard segfaults. **/ + if (UNLIKELY(parent == NULL)) + { + mssError(0, "Cluster", "Warning: Call to clusterOpen(NULL, ...);\n"); + goto err_free; + } + ASSERTMAGIC(parent, MGK_OBJECT); + + /** If CREAT and EXCL are specified, exclusively create it, failing if the file already exists. **/ + pSnNode node_struct = NULL; + bool can_create = (parent->Mode & O_CREAT) && (parent->SubPtr == parent->Pathname->nElements); + if (can_create && (parent->Mode & O_EXCL)) + { + node_struct = snNewNode(parent->Prev, usr_type); + if (UNLIKELY(node_struct == NULL)) + { + mssError(0, "Cluster", "Failed to exclusively create new node struct."); + goto err_free; + } + } + + /** Read the node if it exists. **/ + if (node_struct == NULL) + node_struct = snReadNode(parent->Prev); + + /** If we can't read it, create it (if allowed). **/ + if (node_struct == NULL && can_create) + node_struct = snNewNode(parent->Prev, usr_type); + + /** If there still isn't a node, fail early. **/ + if (UNLIKELY(node_struct == NULL)) + { + mssError(0, "Cluster", "Failed to create node struct from provided cluster file."); + goto err_free; + } + + /** Magic. **/ + ASSERTMAGIC(node_struct, MGK_STNODE); + ASSERTMAGIC(node_struct->Data, MGK_STRUCTINF); + + /** Parse node data from the node_struct. **/ + node_data = ci_ParseNodeData(node_struct->Data, parent); + if (UNLIKELY(node_data == NULL)) + { + mssError(0, "Cluster", "Failed to parse structure file \"%s\".", objFileName(parent)); + goto err_free; + } + ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); + + /** Allocate driver instance data. **/ + driver_data = check_ptr(nmMalloc(sizeof(DriverData))); + if (UNLIKELY(driver_data == NULL)) goto err_free; + memset(driver_data, 0, sizeof(DriverData)); + SETMAGIC(driver_data, MGK_CL_DRIVER_DATA); + driver_data->NodeData = node_data; + driver_data->NodeData->OpenCount++; + + /** Detect target from path. **/ + char* target_name = obj_internal_PathPart(parent->Pathname, parent->SubPtr + parent->SubCnt++, 1); + if (target_name == NULL) + { + /** Target found: Root **/ + driver_data->TargetType = TARGET_NODE; + driver_data->TargetData = (void*)driver_data->NodeData->SourceData; + goto success; + } + + /** Search clusters. **/ + for (unsigned int i = 0u; i < node_data->nClusterDatas; i++) + { + pClusterData cluster_data = node_data->ClusterDatas[i]; + ASSERTMAGIC(cluster_data, MGK_CL_CLUSTER_DATA); + + /** Skip clusters with the wrong name. **/ + if (strcmp(cluster_data->Name, target_name) != 0) continue; + + /** Target found: Cluster **/ + driver_data->TargetType = TARGET_CLUSTER; + + /** Sub-clusters are not fully implemented yet, skip the cluster logic below. **/ + parent->SubCnt++; + driver_data->TargetData = (void*)cluster_data; + goto success; /* Done! */ + + /** Check for sub-clusters in the path. **/ + while (true) + { + /** Descend one path part deeper into the path. **/ + const char* path_part = obj_internal_PathPart(parent->Pathname, parent->SubPtr + parent->SubCnt++, 1); + + /** If the path does not go any deeper, we're done. **/ + if (path_part == NULL) + { + driver_data->TargetData = (void*)cluster_data; + break; + } + + /** Need to go deeper: Search for the requested sub-cluster. **/ + for (unsigned int i = 0u; i < cluster_data->nSubClusters; i++) + { + pClusterData sub_cluster = cluster_data->SubClusters[i]; + if (strcmp(sub_cluster->Name, path_part) != 0) continue; + + /** Target found: Sub-cluster_data **/ + cluster_data = sub_cluster; + goto continue_descent; + } + + /** Path names sub-cluster that does not exist. **/ + mssError(1, "Cluster", "Sub-cluster \"%s\" does not exist.", path_part); + goto err_free; + + continue_descent:; + } + goto success; + } + + /** Search searches. **/ + for (unsigned int i = 0u; i < node_data->nSearchDatas; i++) + { + pSearchData search_data = node_data->SearchDatas[i]; + ASSERTMAGIC(search_data, MGK_CL_SEARCH_DATA); + + /** Skip clusters with the wrong name. **/ + if (strcmp(search_data->Name, target_name) != 0) continue; + + /** Target found: Search **/ + driver_data->TargetType = TARGET_SEARCH; + driver_data->TargetData = (void*)search_data; + + /** Check for extra, invalid path parts. **/ + char* extra_data = obj_internal_PathPart(parent->Pathname, parent->SubPtr + parent->SubCnt++, 1); + if (UNLIKELY(extra_data != NULL)) + { + mssError(1, "Cluster", "Unknown path part %s.", extra_data); + goto err_free; + } + return (void*)driver_data; /* Success. */ + } + + /** We were unable to find the requested cluster or search. **/ + mssError(1, "Cluster", "\"%s\" is not the name of a declared cluster or search.", target_name); + + /** Attempt to give a hint. **/ + { + const unsigned int n_targets = node_data->nClusterDatas + node_data->nSearchDatas; + char* target_names[n_targets]; + for (unsigned int i = 0u; i < node_data->nClusterDatas; i++) + target_names[i] = node_data->ClusterDatas[i]->Name; + for (unsigned int i = 0u; i < node_data->nSearchDatas; i++) + target_names[i + node_data->nClusterDatas] = node_data->SearchDatas[i]->Name; + ci_TryHint(target_name, target_names, n_targets); + } + + /** Error cleanup. **/ + err_free: + if (node_data != NULL) ci_FreeNodeData(node_data); + if (driver_data != NULL) nmFree(driver_data, sizeof(DriverData)); + + mssError(0, "Cluster", + "Failed to open cluster file \"%s\" at: %s", + objFileName(parent), objFilePath(parent) + ); + + return NULL; + + success: + return driver_data; + } + + +// LINK #functions +/*** Close a cluster driver instance object, releasing any necessary memory + *** and closing any necessary underlying resources. However, most of that + *** data will be cached and won't be freed unless the cache is dropped. + *** + *** @param inf_v The affected driver instance. + *** @param oxt The transaction tree (for the incomplete transaction system). + *** @returns 0 for success, or -1 if an error occurs. + ***/ +int +clusterClose(void* inf_v, pObjTrxTree* oxt) + { + pDriverData driver_data = check_ptr(inf_v); + if (UNLIKELY(driver_data == NULL)) return -1; + ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); + + /** Update statistics. **/ + ClusterStatistics.CloseCalls++; + + /** Unlink the driver's node data. **/ + pNodeData node_data = check_ptr(driver_data->NodeData); /* Failure ignored. */ + ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); + if (UNLIKELY(node_data != NULL && --node_data->OpenCount == 0)) + ci_FreeNodeData(driver_data->NodeData); + + /** Free driver data. **/ + nmFree(driver_data, sizeof(DriverData)); + + return 0; + } + + +// LINK #functions +/*** Opens a new query pointing to the first row of the data targeted by + *** the driver instance struct. The query has an internal index counter + *** that starts at the first row and increments as data is fetched. + *** + *** @param inf_v The driver instance to be queried. + *** @param query The query to use on this struct. This is assumed to be + *** handled elsewhere, so we don't read it here (unused). + *** @param oxt The transaction tree (for the incomplete transaction system). + *** @returns The cluster query, or + *** NULL if an error occurs. + ***/ +void* +clusterOpenQuery(void* inf_v, pObjQuery query, pObjTrxTree* oxt) + { + pQueryData query_data = NULL; + + /** Get driver data. **/ + pDriverData driver_data = check_ptr(inf_v); + if (driver_data == NULL) goto err_free; + ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); + + if (driver_data->TargetType != TARGET_SEARCH + && driver_data->TargetType != TARGET_CLUSTER + && driver_data->TargetType != TARGET_NODE) + { + /** Queries are not supported for this target type. **/ + goto err; + } + + /** Update statistics. **/ + ClusterStatistics.OpenQueryCalls++; + + /** Allocate memory for the query. **/ + query_data = check_ptr(nmMalloc(sizeof(ClusterQuery))); + if (UNLIKELY(query_data == NULL)) goto err_free; + + /** Initialize the query. **/ + SETMAGIC(query_data, MGK_CL_QUERY_DATA); + query_data->DriverData = (pDriverData)inf_v; + query_data->RowIndex = 0u; + + return query_data; + + err_free: + /** Error cleanup. **/ + if (query_data != NULL) nmFree(query_data, sizeof(ClusterQuery)); + mssError(0, "Cluster", "Failed to open query."); + + err: + return NULL; + } + + +// LINK #functions +/*** Get the next entry of a query as an open driver instance object. + *** + *** @param qy_v A query instance, storing an internal index which is + *** incremented once that data has been fetched. + *** @param obj Unused. + *** @param mode Unused. + *** @param oxt Unused. + *** @returns pDriverData that is either a cluster entry or search entry, + *** pointing to a specific target index into the relevant data, + *** OR NULL if all data has been fetched or an error occurs. + ***/ +void* +clusterQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) + { + pDriverData result_data = NULL; + + /** Unpack data into local variables. **/ + pQueryData query_data = check_ptr(qy_v); + if (UNLIKELY(query_data == NULL)) goto err_free; + ASSERTMAGIC(query_data, MGK_CL_QUERY_DATA); + pDriverData driver_data = check_ptr(query_data->DriverData); + if (UNLIKELY(driver_data == NULL)) goto err_free; + ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); + pNodeData node_data = check_ptr(driver_data->NodeData); + if (UNLIKELY(node_data == NULL)) goto err_free; + ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); + + /** Update statistics. **/ + ClusterStatistics.FetchCalls++; + + /** Allocate result struct. **/ + result_data = check_ptr(nmMalloc(sizeof(DriverData))); + if (UNLIKELY(result_data == NULL)) goto err_free; + + /** Default initialization. **/ + SETMAGIC(result_data, MGK_CL_DRIVER_DATA); + result_data->NodeData = driver_data->NodeData; + result_data->TargetData = driver_data->TargetData; + result_data->TargetType = 0; /* Unset. */ + result_data->TargetIndex = 0; /* Reset. */ + result_data->TargetAttrIndex = 0; /* Reset. */ + result_data->TargetMethodIndex = 0; /* Reset. */ + + /** Ensure that the data being fetched exists and is computed. **/ + const TargetType target_type = driver_data->TargetType; + switch (target_type) + { + case TARGET_NODE: + { + unsigned int index = query_data->RowIndex++; + + /** Iterate over clusters. **/ + const unsigned int n_cluster_datas = node_data->nClusterDatas; + if (index < n_cluster_datas) + { + /** Fetch a cluster. **/ + result_data->TargetType = TARGET_CLUSTER; + result_data->TargetData = node_data->ClusterDatas[index]; + break; + } + else index -= n_cluster_datas; + + /** Iterate over searches. **/ + const unsigned int n_search_datas = node_data->nSearchDatas; + if (index < n_search_datas) + { + /** Fetch a search. **/ + result_data->TargetType = TARGET_SEARCH; + result_data->TargetData = node_data->SearchDatas[index]; + break; + } + else index -= n_search_datas; + + /** Iteration complete. **/ + goto done_free; + } + + case TARGET_CLUSTER: + { + /** Ensure the required data is computed. **/ + pClusterData target = (pClusterData)check_ptr(driver_data->TargetData); + if (UNLIKELY(target == NULL)) goto err_free; + ASSERTMAGIC(target, MGK_CL_CLUSTER_DATA); + if (UNLIKELY(ci_ComputeClusterData(target, node_data) != 0)) + { + mssError(0, "Cluster", "Failed to compute ClusterData for query."); + goto err_free; + } + + /** Stop iteration if the requested data does not exist. **/ + if (UNLIKELY(query_data->RowIndex >= target->nClusters)) goto done_free; + + /** Set the data being fetched. **/ + result_data->TargetType = TARGET_CLUSTER_ENTRY; + result_data->TargetIndex = query_data->RowIndex++; + + break; + } + + case TARGET_SEARCH: + { + /** Ensure the required data is computed. **/ + pSearchData target = (pSearchData)check_ptr(driver_data->TargetData); + if (UNLIKELY(target == NULL)) goto err_free; + ASSERTMAGIC(target, MGK_CL_SEARCH_DATA); + if (UNLIKELY(ci_ComputeSearchData(target, node_data) != 0)) + { + mssError(0, "Cluster", "Failed to compute SearchData for query."); + goto err_free; + } + + /** Stop iteration if the requested data does not exist. **/ + if (UNLIKELY(query_data->RowIndex >= target->nPairs)) goto done_free; + + /** Set the data being fetched. **/ + result_data->TargetType = TARGET_SEARCH_ENTRY; + result_data->TargetIndex = query_data->RowIndex++; + + break; + } + + case TARGET_CLUSTER_ENTRY: + case TARGET_SEARCH_ENTRY: + mssError(1, "Cluster", "Querying a query result is not allowed."); + goto err_free; + + default: + mssError(1, "Cluster", "Unknown target type %u.", target_type); + goto err_free; + } + + /** Add a link to the NodeData so that it isn't freed while we're using it. **/ + node_data->OpenCount++; + + /** Success. **/ + return result_data; + + err_free: + mssError(0, "Cluster", "Failed to fetch query result."); + + done_free: + if (LIKELY(result_data != NULL)) nmFree(result_data, sizeof(DriverData)); + return NULL; + } + + +// LINK #functions +/*** Close a cluster query instance, releasing any necessary memory and + *** closing any necessary underlying resources. This does not close the + *** underlying driver instance, which must be closed with clusterClose(). + *** + *** @param qy_v The affected query instance. + *** @param oxt The transaction tree (for the incomplete transaction system). + *** @returns 0 for success, or -1 if an error occurs. + ***/ +int +clusterQueryClose(void* qy_v, pObjTrxTree* oxt) + { + /** Cast the query data. **/ + pQueryData query_data = qy_v; + if (UNLIKELY(check_ptr(qy_v) == NULL)) return -1; + ASSERTMAGIC(query_data, MGK_CL_QUERY_DATA); + + /** Free the query data. **/ + nmFree(query_data, sizeof(ClusterQuery)); + + return 0; + } + + +// LINK #functions +/*** Get the type of a cluster driver instance attribute. + *** + *** @param inf_v The driver instance. + *** @param attr_name The name of the requested attribute. + *** @param oxt The transaction tree (for the incomplete transaction system). + *** @returns The datatype, see datatypes.h for a list of valid datatypes, or + *** -1 if an error occurs. + *** + *** LINK ../../centrallix-lib/include/datatypes.h:72 + ***/ +int +clusterGetAttrType(void* inf_v, char* attr_name, pObjTrxTree* oxt) + { + /** Extract target type from driver data. **/ + pDriverData driver_data = check_ptr(inf_v); + if (UNLIKELY(driver_data == NULL)) goto err; + ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); + const TargetType target_type = driver_data->TargetType; + + /** Update statistics. **/ + ClusterStatistics.GetTypeCalls++; + + /** Guard possible segfault. **/ + if (UNLIKELY(attr_name == NULL)) + { + fprintf(stderr, "Warning: Call to clusterGetAttrType() with NULL attribute name.\n"); + return DATA_T_UNAVAILABLE; + } + + /** Performance shortcut for frequently requested attributes: key1, key2, and sim. **/ + if (attr_name[0] == 'k' || attr_name[0] == 's') goto handle_targets; + + /** Types for general attributes. **/ + if (LIKELY(strcmp(attr_name, "name") == 0) + || strcmp(attr_name, "annotation") == 0 + || strcmp(attr_name, "content_type") == 0 + || strcmp(attr_name, "inner_type") == 0 + || strcmp(attr_name, "outer_type") == 0 + || strcmp(attr_name, "internal_type") == 0) + return DATA_T_STRING; + if (strcmp(attr_name, "last_modification") == 0) + return DATA_T_DATETIME; + if (strcmp(attr_name, "date_created") == 0 + || strcmp(attr_name, "date_computed") == 0) + { + return (target_type == TARGET_CLUSTER + || target_type == TARGET_CLUSTER_ENTRY + || target_type == TARGET_SEARCH + || target_type == TARGET_SEARCH_ENTRY) + ? DATA_T_DATETIME /* Target has date attr. */ + : DATA_T_UNAVAILABLE; /* Target does not have date attr. */ + } + + /** Types for specific data targets. **/ + handle_targets: + switch (target_type) + { + case TARGET_NODE: + if (strcmp(attr_name, "source") == 0 + || strcmp(attr_name, "key_attr") == 0 + || strcmp(attr_name, "data_attr") == 0) + return DATA_T_STRING; + break; + + case TARGET_CLUSTER: + if (strcmp(attr_name, "algorithm") == 0 + || strcmp(attr_name, "similarity_measure") == 0) + return DATA_T_STRING; + if (strcmp(attr_name, "num_clusters") == 0 + || strcmp(attr_name, "max_iterations") == 0 + || strcmp(attr_name, "seed") == 0) + return DATA_T_INTEGER; + if (strcmp(attr_name, "min_improvement") == 0) + return DATA_T_DOUBLE; + break; + + case TARGET_SEARCH: + if (strcmp(attr_name, "source") == 0 + || strcmp(attr_name, "similarity_measure") == 0) + return DATA_T_STRING; + if (strcmp(attr_name, "threshold") == 0) + return DATA_T_DOUBLE; + break; + + case TARGET_CLUSTER_ENTRY: + if (strcmp(attr_name, "items") == 0) + return DATA_T_STRINGVEC; + break; + + case TARGET_SEARCH_ENTRY: + if (strcmp(attr_name, "key1") == 0 + || strcmp(attr_name, "key2") == 0) + return DATA_T_STRING; + if (strcmp(attr_name, "sim") == 0) + return DATA_T_DOUBLE; + break; + + default: + mssError(1, "Cluster", "Unknown target type %u.", target_type); + return DATA_T_UNAVAILABLE; + } + + return DATA_T_UNAVAILABLE; + + err: + return -1; + } + + +// LINK #functions +/*** Get the value of a cluster driver instance attribute. + *** + *** @param inf_v The driver instance to be read. + *** @param attr_name The name of the requested attribute. + *** @param datatype The expected datatype of the attribute value. + *** See `datatypes.h` for a list of valid datatypes. + *** @param val A pointer to a location where a pointer to the requested + *** data should be stored. Typically, the caller creates a local variable + *** to store this pointer, then passes a pointer to that local variable + *** so that they will have a pointer to the data. + *** This buffer will not be modified unless the data is successfully + *** found. If a value other than 0 is returned, the buffer is not updated. + *** @param oxt The transaction tree (for the incomplete transaction system). + *** @returns 0 if successful, + *** -1 if an error occurs. + *** + *** LINK ../../centrallix-lib/include/datatypes.h:72 + ***/ +int +clusterGetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pObjTrxTree* oxt) + { + TargetType target_type = -1; + + /** Extract target type from driver data. **/ + pDriverData driver_data = check_ptr(inf_v); + if (UNLIKELY(driver_data == NULL)) goto err; + ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); + target_type = driver_data->TargetType; + + /** Update statistics. **/ + ClusterStatistics.GetValCalls++; + + /** Guard possible segfault. **/ + if (UNLIKELY(attr_name == NULL)) + { + fprintf(stderr, "Warning: Call to clusterGetAttrType() with NULL attribute name.\n"); + goto err; + } + + /** Performance shortcut for frequently requested attributes: key1, key2, and sim. **/ + if ((attr_name[0] == 'k' && datatype == DATA_T_STRING) /* key1, key2 : string */ + || (attr_name[0] == 's' && datatype == DATA_T_DOUBLE) /* sim : double */ + ) goto handle_targets; + + /** Type check. **/ + const int expected_datatype = clusterGetAttrType(inf_v, attr_name, oxt); + if (UNLIKELY(expected_datatype == DATA_T_UNAVAILABLE)) goto unknown_attribute; + if (UNLIKELY(datatype != expected_datatype)) + { + mssError(1, "Cluster", + "Type mismatch: Accessing attribute ['%s' : %s] as type %s.", + attr_name, objTypeToStr(expected_datatype), objTypeToStr(datatype) + ); + goto err; + } + + /** Handle name. **/ + if (LIKELY(strcmp(attr_name, "name") == 0)) + { + ClusterStatistics.GetValCalls_name++; + switch (target_type) + { + case TARGET_NODE: + { + pSourceData source_data = check_ptr(driver_data->TargetData); + if (source_data == NULL) goto err; + ASSERTMAGIC(source_data, MGK_CL_SOURCE_DATA); + val->String = source_data->Name; + break; + } + + case TARGET_CLUSTER: + case TARGET_CLUSTER_ENTRY: + { + pClusterData cluster_data = check_ptr(driver_data->TargetData); + if (cluster_data == NULL) goto err; + ASSERTMAGIC(cluster_data, MGK_CL_CLUSTER_DATA); + val->String = cluster_data->Name; + break; + } + + case TARGET_SEARCH: + case TARGET_SEARCH_ENTRY: + { + pSearchData search_data = check_ptr(driver_data->TargetData); + if (search_data == NULL) goto err; + ASSERTMAGIC(search_data, MGK_CL_SEARCH_DATA); + val->String = search_data->Name; + break; + } + + default: + mssError(1, "Cluster", "Unknown target type %u.", target_type); + goto err; + } + + return 0; + } + + /** Handle annotation. **/ + if (strcmp(attr_name, "annotation") == 0) + { + switch (target_type) + { + case TARGET_NODE: val->String = "Clustering driver."; break; + case TARGET_CLUSTER: val->String = "Clustering driver: Cluster."; break; + case TARGET_CLUSTER_ENTRY: val->String = "Clustering driver: Cluster Entry."; break; + case TARGET_SEARCH: val->String = "Clustering driver: Search."; break; + case TARGET_SEARCH_ENTRY: val->String = "Clustering driver: Cluster Entry."; break; + + default: + mssError(1, "Cluster", "Unknown target type %u.", target_type); + goto err; + } + return 0; + } + + /** Handle various types. **/ + if (strcmp(attr_name, "outer_type") == 0) + { + val->String = "system/row"; + return 0; + } + if (strcmp(attr_name, "content_type") == 0 + || strcmp(attr_name, "inner_type") == 0) + { + val->String = "system/void"; + return 0; + } + if (strcmp(attr_name, "internal_type") == 0) + { + switch (target_type) + { + case TARGET_NODE: val->String = "system/cluster"; break; + case TARGET_CLUSTER: val->String = "cluster/cluster"; break; + case TARGET_CLUSTER_ENTRY: val->String = "cluster/entry"; break; + case TARGET_SEARCH: val->String = "cluster/search"; break; + case TARGET_SEARCH_ENTRY: val->String = "search/entry"; break; + default: + mssError(1, "Cluster", "Unknown target type %u.", target_type); + goto err; + } + + return 0; + } + + /** Last modification is not implemented. **/ + if (strcmp(attr_name, "last_modification") == 0) + { + if (target_type == TARGET_CLUSTER + || target_type == TARGET_CLUSTER_ENTRY + || target_type == TARGET_SEARCH + || target_type == TARGET_SEARCH_ENTRY) + goto date_computed; + else return 1; /* null */ + } + + /** Handle date_created. **/ + if (strcmp(attr_name, "date_created") == 0) + { + switch (target_type) + { + case TARGET_NODE: + /** Attribute is not defined for this target type. **/ + goto err; + + case TARGET_CLUSTER: + case TARGET_CLUSTER_ENTRY: + { + pClusterData cluster_data = check_ptr(driver_data->TargetData); + if (cluster_data == NULL) goto err; + ASSERTMAGIC(cluster_data, MGK_CL_CLUSTER_DATA); + if (cluster_data->DateCreated.Value == 0) return 1; /* DateCreated not set: return null - should never occur */ + else val->DateTime = &cluster_data->DateCreated; + return 0; + } + + case TARGET_SEARCH: + case TARGET_SEARCH_ENTRY: + { + pSearchData search_data = check_ptr(driver_data->TargetData); + if (search_data == NULL) goto err; + ASSERTMAGIC(search_data, MGK_CL_SEARCH_DATA); + if (search_data->DateCreated.Value == 0) return 1; /* DateCreated not set: return null - should never occur */ + else val->DateTime = &search_data->DateCreated; + return 0; + } + } + goto err; + } + + /** Handle date_computed. **/ + if (strcmp(attr_name, "date_computed") == 0) + { + date_computed: + switch (target_type) + { + case TARGET_NODE: + /** Attribute is not defined for this target type. **/ + goto err; + + case TARGET_CLUSTER: + case TARGET_CLUSTER_ENTRY: + { + pClusterData target = check_ptr((pClusterData)driver_data->TargetData); + if (target == NULL) goto err; + ASSERTMAGIC(target, MGK_CL_CLUSTER_DATA); + if (target->DateComputed.Value == 0) return 1; /* DateComputed not set: return null */ + else val->DateTime = &target->DateComputed; + return 0; + } + + case TARGET_SEARCH: + case TARGET_SEARCH_ENTRY: + { + pSearchData target = check_ptr((pSearchData)driver_data->TargetData); + if (target == NULL) goto err; + ASSERTMAGIC(target, MGK_CL_SEARCH_DATA); + if (target->DateComputed.Value == 0) return 1; /* DateComputed not set: return null */ + else val->DateTime = &target->DateComputed; + return 0; + } + } + + /** Default: Unknown type. **/ + mssError(1, "Cluster", "Unknown target type %u.", target_type); + goto err; + } + + /** Handle attributes for specific data targets. **/ + handle_targets: + switch (target_type) + { + case TARGET_NODE: + { + pSourceData source_data = check_ptr(driver_data->TargetData); + if (source_data == NULL) goto err; + ASSERTMAGIC(source_data, MGK_CL_SOURCE_DATA); + + if (strcmp(attr_name, "source") == 0) + { + val->String = source_data->SourcePath; + return 0; + } + if (strcmp(attr_name, "key_attr") == 0) + { + val->String = source_data->KeyAttr; + return 0; + } + if (strcmp(attr_name, "data_attr") == 0) + { + val->String = source_data->DataAttr; + return 0; + } + break; + } + + case TARGET_CLUSTER: + { + pClusterData target = check_ptr(driver_data->TargetData); + if (target == NULL) goto err; + ASSERTMAGIC(target, MGK_CL_CLUSTER_DATA); + + if (strcmp(attr_name, "algorithm") == 0) + { + val->String = ci_ClusteringAlgorithmToString(target->ClusterAlgorithm); + return 0; + } + if (strcmp(attr_name, "similarity_measure") == 0) + { + val->String = ci_SimilarityMeasureToString(target->SimilarityMeasure); + return 0; + } + if (strcmp(attr_name, "num_clusters") == 0) + { + if (target->nClusters > INT_MAX) + fprintf(stderr, "Warning: 'num_clusters' value of %u exceeds INT_MAX (%d).\n", target->nClusters, INT_MAX); + val->Integer = (int)target->nClusters; + return 0; + } + if (strcmp(attr_name, "max_iterations") == 0) + { + if (target->MaxIterations > INT_MAX) + fprintf(stderr, "Warning: 'max_iterations' value of %u exceeds INT_MAX (%d).\n", target->MaxIterations, INT_MAX); + val->Integer = (int)target->MaxIterations; + return 0; + } + if (strcmp(attr_name, "min_improvement") == 0) + { + val->Double = target->MinImprovement; + return 0; + } + if (strcmp(attr_name, "seed") == 0) + { + val->Integer = target->Seed; + return 0; + } + break; + } + + case TARGET_SEARCH: + { + pSearchData target = check_ptr(driver_data->TargetData); + if (target == NULL) goto err; + ASSERTMAGIC(target, MGK_CL_SEARCH_DATA); + + if (strcmp(attr_name, "source") == 0) + { + val->String = target->SourceCluster->Name; + return 0; + } + if (strcmp(attr_name, "similarity_measure") == 0) + { + val->String = ci_SimilarityMeasureToString(target->SimilarityMeasure); + return 0; + } + if (strcmp(attr_name, "threshold") == 0) + { + val->Double = target->Threshold; + return 0; + } + } + + case TARGET_CLUSTER_ENTRY: + { + pClusterData target = check_ptr(driver_data->TargetData); + if (target == NULL) goto err; + ASSERTMAGIC(target, MGK_CL_CLUSTER_DATA); + pCluster target_cluster = &target->Clusters[driver_data->TargetIndex]; + ASSERTMAGIC(target_cluster, MGK_CL_CLUSTER); + + if (strcmp(attr_name, "items") == 0) + { + /** Static variable to allow us to free the StringVecs from previous calls. **/ + static pStringVec vec = NULL; + if (vec != NULL) + { + if (vec->Strings != NULL) nmSysFree(vec->Strings); + nmFree(vec, sizeof(StringVec)); + } + + /** Allocate and initialize the requested data. **/ + vec = val->StringVec = check_ptr(nmMalloc(sizeof(StringVec))); + if (vec == NULL) goto err; + memset(vec, 0, sizeof(StringVec)); + vec->nStrings = target_cluster->Size; + vec->Strings = check_ptr(nmSysMalloc(target_cluster->Size * sizeof(char*))); + if (vec->Strings == NULL) goto err; + for (unsigned int i = 0u; i < target_cluster->Size; i++) + vec->Strings[i] = target->SourceData->Strings[target_cluster->Indexes[i]]; + + /** Success. **/ + return 0; + } + break; + } + + case TARGET_SEARCH_ENTRY: + { + pSearchData target = check_ptr(driver_data->TargetData); + if (target == NULL) goto err; + ASSERTMAGIC(target, MGK_CL_SEARCH_DATA); + pPair target_dup = check_ptr(target->Pairs[driver_data->TargetIndex]); + if (target_dup == NULL) goto err; + + if (strcmp(attr_name, "sim") == 0) + { + ClusterStatistics.GetValCalls_sim++; + val->Double = target_dup->similarity; + return 0; + } + if (strcmp(attr_name, "key1") == 0) + { + ClusterStatistics.GetValCalls_key1++; + val->String = target->SourceCluster->SourceData->Keys[target_dup->i]; + return 0; + } + if (strcmp(attr_name, "key2") == 0) + { + ClusterStatistics.GetValCalls_key2++; + val->String = target->SourceCluster->SourceData->Keys[target_dup->j]; + return 0; + } + break; + } + + default: + mssError(1, "Cluster", "Unknown target type %u.", target_type); + goto err; + } + + unknown_attribute: + ci_UnknownAttribute(attr_name, driver_data->TargetType); + + err:; + char* name; + clusterGetAttrValue(inf_v, "name", DATA_T_STRING, POD(&name), NULL); + mssError(1, "Cluster", + "Failed to get attribute for cluster object %s (target type: %u, \"%s\").", + driver_data->NodeData->SourceData->Name, target_type, name + ); + + return -1; + } + + +// LINK #functions +/*** Create a new presentation hints object, describing this attribute on the + *** provided cluster driver instance. + *** + *** Note: Failures from nmSysStrdup() and several others are ignored because + *** the worst case scenario is that the attributes are set to null, which + *** will cause them to be ignored. I consider that to be better than than + *** throwing an error that could unnecessarily disrupt normal usage. + *** + *** @param inf_v The driver instance to be read. + *** @param attr_name The name of the requested attribute. + *** @param oxt The transaction tree (for the incomplete transaction system). + *** @returns A presentation hints object, if successful, + *** NULL if an error occurs. + ***/ +pObjPresentationHints +clusterPresentationHints(void* inf_v, char* attr_name, pObjTrxTree* oxt) + { + pObjPresentationHints hints = NULL; + pParamObjects tmp_list = NULL; + + /** Extract target type from driver data. **/ + pDriverData driver_data = check_ptr(inf_v); + if (UNLIKELY(driver_data == NULL)) goto err_free; + ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); + const TargetType target_type = driver_data->TargetType; + + /** Malloc presentation hints struct. **/ + hints = check_ptr(nmMalloc(sizeof(ObjPresentationHints))); + if (UNLIKELY(hints == NULL)) goto err_free; + memset(hints, 0, sizeof(ObjPresentationHints)); + + /** Hints that are the same for all attributes. **/ + hints->GroupID = -1; + hints->VisualLength2 = 1; + hints->Style |= OBJ_PH_STYLE_READONLY | OBJ_PH_STYLE_CREATEONLY | OBJ_PH_STYLE_NOTNULL; + hints->StyleMask |= OBJ_PH_STYLE_READONLY | OBJ_PH_STYLE_CREATEONLY | OBJ_PH_STYLE_NOTNULL; + + /** Temporary param list for compiling expressions. **/ + tmp_list = check_ptr(expCreateParamList()); + if (UNLIKELY(hints == NULL)) goto err_free; + + /** Search for the requested attribute through attributes common to all instances. **/ + if (strcmp(attr_name, "name") == 0) + { + hints->Length = 32; + hints->VisualLength = 16; + goto end; + } + if (strcmp(attr_name, "annotation") == 0) + { + hints->Length = 36; + hints->VisualLength = 36; + goto end; + } + if (strcmp(attr_name, "inner_type") == 0 + || strcmp(attr_name, "inner_type") == 0 + || strcmp(attr_name, "outer_type") == 0 + || strcmp(attr_name, "content_type") == 0 + || strcmp(attr_name, "last_modification") == 0) + { + hints->VisualLength = 30; + goto end; + } + if (strcmp(attr_name, "internal_type") == 0) + { + check(xaInit(&(hints->EnumList), 5)); /* Failure ignored. */ + check_neg(xaAddItem(&(hints->EnumList), check_ptr(nmSysStrdup("system/cluster")))); /* Failure ignored. */ + check_neg(xaAddItem(&(hints->EnumList), check_ptr(nmSysStrdup("cluster/cluster")))); /* Failure ignored. */ + check_neg(xaAddItem(&(hints->EnumList), check_ptr(nmSysStrdup("cluster/entry")))); /* Failure ignored. */ + check_neg(xaAddItem(&(hints->EnumList), check_ptr(nmSysStrdup("cluster/search")))); /* Failure ignored. */ + check_neg(xaAddItem(&(hints->EnumList), check_ptr(nmSysStrdup("search/entry")))); /* Failure ignored. */ + hints->Length = 16; + hints->VisualLength = 16; + hints->FriendlyName = check_ptr(nmSysStrdup("Internal Type")); /* Failure ignored. */ + hints->Style |= OBJ_PH_STYLE_HIDDEN | OBJ_PH_STYLE_LOWERCASE; + hints->StyleMask |= OBJ_PH_STYLE_HIDDEN | OBJ_PH_STYLE_LOWERCASE; + goto end; + } + + /** Handle date created and date computed. */ + if (strcmp(attr_name, "date_created") == 0 + || strcmp(attr_name, "date_computed") == 0) + { + if (target_type == TARGET_CLUSTER + || target_type == TARGET_CLUSTER_ENTRY + || target_type == TARGET_SEARCH + || target_type == TARGET_SEARCH_ENTRY) + { + hints->Length = 24; + hints->VisualLength = 20; + hints->Format = check_ptr(nmSysStrdup("datetime")); /* Failure ignored. */ + goto end; + } + else goto unknown_attribute; + } + + /** Search by target type. **/ + switch (target_type) + { + case TARGET_NODE: + if (strcmp(attr_name, "source") == 0) + { + hints->Length = _PC_PATH_MAX; + hints->VisualLength = 64; + hints->FriendlyName = check_ptr(nmSysStrdup("Source Path")); /* Failure ignored. */ + goto end; + } + if (strcmp(attr_name, "key_attr") == 0) + { + hints->Length = 255; + hints->VisualLength = 32; + hints->FriendlyName = check_ptr(nmSysStrdup("Key Attribute Name")); /* Failure ignored. */ + goto end; + } + if (strcmp(attr_name, "data_attr") == 0) + { + hints->Length = 255; + hints->VisualLength = 32; + hints->FriendlyName = check_ptr(nmSysStrdup("Data Attribute Name")); /* Failure ignored. */ + goto end; + } + break; + + case TARGET_CLUSTER: + if (strcmp(attr_name, "num_clusters") == 0) + { + /** Min and max values. **/ + hints->MinValue = expCompileExpression("2", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + hints->MaxValue = expCompileExpression("2147483647", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + + /** Other hints. **/ + hints->Length = 8; + hints->VisualLength = 4; + hints->FriendlyName = check_ptr(nmSysStrdup("Number of Clusters")); /* Failure ignored. */ + goto end; + } + if (strcmp(attr_name, "min_improvement") == 0) + { + /** Min and max values. **/ + hints->DefaultExpr = expCompileExpression("0.0001", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + hints->MinValue = expCompileExpression("0.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + hints->MaxValue = expCompileExpression("1.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + + /** Other hints. **/ + hints->Length = 16; + hints->VisualLength = 8; + hints->FriendlyName = check_ptr(nmSysStrdup("Minimum Improvement Threshold")); /* Failure ignored. */ + goto end; + } + if (strcmp(attr_name, "max_iterations") == 0) + { + /** Min and max values. **/ + hints->DefaultExpr = expCompileExpression("64", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + hints->MinValue = expCompileExpression("0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + hints->MaxValue = expCompileExpression("2147483647", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + + /** Other hints. **/ + hints->Length = 8; + hints->VisualLength = 4; + hints->FriendlyName = check_ptr(nmSysStrdup("Maximum Iterations")); /* Failure ignored. */ + goto end; + } + if (strcmp(attr_name, "algorithm") == 0) + { + /** Enum values. **/ + check(xaInit(&(hints->EnumList), N_CLUSTERING_ALGORITHMS)); /* Failure ignored. */ + for (unsigned int i = 0u; i < N_CLUSTERING_ALGORITHMS; i++) + check_neg(xaAddItem(&(hints->EnumList), &ALL_CLUSTERING_ALGORITHMS[i])); /* Failure ignored. */ + + /** Min and max values. **/ + hints->MinValue = expCompileExpression("0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + char buf[8]; + snprintf(buf, sizeof(buf), "%d", N_CLUSTERING_ALGORITHMS); + hints->MaxValue = expCompileExpression(buf, tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + + /** Display flags. **/ + hints->Style |= OBJ_PH_STYLE_BUTTONS; + hints->StyleMask |= OBJ_PH_STYLE_BUTTONS; + + /** Other hints. **/ + hints->Length = 24; + hints->VisualLength = 20; + hints->FriendlyName = check_ptr(nmSysStrdup("Clustering Algorithm")); /* Failure ignored. */ + goto end; + } + /** Fall-through: Start of overlapping region. **/ + + case TARGET_SEARCH: + if (strcmp(attr_name, "similarity_measure") == 0) + { + /** Enum values. **/ + check(xaInit(&(hints->EnumList), N_SIMILARITY_MEASURES)); /* Failure ignored. */ + for (unsigned int i = 0u; i < N_SIMILARITY_MEASURES; i++) + check_neg(xaAddItem(&(hints->EnumList), &ALL_SIMILARITY_MEASURES[i])); /* Failure ignored. */ + + /** Display flags. **/ + hints->Style |= OBJ_PH_STYLE_BUTTONS; + hints->StyleMask |= OBJ_PH_STYLE_BUTTONS; + + /** Min and max values. **/ + hints->MinValue = expCompileExpression("0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + char buf[8]; + snprintf(buf, sizeof(buf), "%d", N_SIMILARITY_MEASURES); + hints->MaxValue = expCompileExpression(buf, tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + + /** Other hints. **/ + hints->Length = 32; + hints->VisualLength = 20; + hints->FriendlyName = check_ptr(nmSysStrdup("Similarity Measure")); /* Failure ignored. */ + goto end; + } + + /** End of overlapping region. **/ + if (target_type == TARGET_CLUSTER) break; + + if (strcmp(attr_name, "source") == 0) + { + hints->Length = 64; + hints->VisualLength = 32; + hints->FriendlyName = check_ptr(nmSysStrdup("Source Cluster Name")); /* Failure ignored. */ + goto end; + } + if (strcmp(attr_name, "threshold") == 0) + { + /** Min and max values. **/ + hints->MinValue = expCompileExpression("0.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + hints->MaxValue = expCompileExpression("1.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + + /** Other hints. **/ + hints->Length = 16; + hints->VisualLength = 8; + hints->FriendlyName = check_ptr(nmSysStrdup("Similarity Threshold")); /* Failure ignored. */ + goto end; + } + break; + + case TARGET_CLUSTER_ENTRY: + { + /** Unused. **/ + // pClusterData target = check_ptr(driver_data->TargetData); + // if (UNLIKELY(target == NULL)) goto err_free; + // ASSERTMAGIC(target, MGK_CL_CLUSTER_DATA); + + if (strcmp(attr_name, "items") == 0) + { + /** Other hints. **/ + hints->Length = 65536; + hints->VisualLength = 256; + hints->FriendlyName = check_ptr(nmSysStrdup("Cluster Data")); /* Failure ignored. */ + goto end; + } + if (strcmp(attr_name, "sim") == 0) + { + /** Min and max values. **/ + hints->MinValue = expCompileExpression("0.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + hints->MaxValue = expCompileExpression("1.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + + /** Other hints. **/ + hints->Length = 16; + hints->VisualLength = 8; + hints->FriendlyName = check_ptr(nmSysStrdup("Similarity")); /* Failure ignored. */ + goto end; + } + break; + } + + case TARGET_SEARCH_ENTRY: + { + /** Unused. **/ + // pSearchData target = check_ptr(driver_data->TargetData); + // if (UNLIKELY(target == NULL)) goto err_free; + // ASSERTMAGIC(target, MGK_CL_SEARCH_DATA); + + if (strcmp(attr_name, "key1") == 0) + { + hints->Length = 255; + hints->VisualLength = 32; + hints->FriendlyName = check_ptr(nmSysStrdup("Key 1")); /* Failure ignored. */ + goto end; + } + if (strcmp(attr_name, "key2") == 0) + { + hints->Length = 255; + hints->VisualLength = 32; + hints->FriendlyName = check_ptr(nmSysStrdup("Key 2")); /* Failure ignored. */ + goto end; + } + if (strcmp(attr_name, "sim") == 0) + { + /** Min and max values. **/ + hints->MinValue = expCompileExpression("0.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + hints->MaxValue = expCompileExpression("1.0", tmp_list, MLX_F_ICASE | MLX_F_FILENAMES, 0); + + /** Other hints. **/ + hints->Length = 16; + hints->VisualLength = 8; + hints->FriendlyName = check_ptr(nmSysStrdup("Similarity")); /* Failure ignored. */ + goto end; + } + break; + } + + default: + mssError(1, "Cluster", "Unknown target type %u.", target_type); + goto err_free; + } + + unknown_attribute: + ci_UnknownAttribute(attr_name, driver_data->TargetType); + + err_free: + /** Error cleanup. **/ + if (hints != NULL) nmFree(hints, sizeof(ObjPresentationHints)); + hints = NULL; + + /** Construct the clearest error message that we can. **/ + char* name = NULL; + char* internal_type = NULL; + check(clusterGetAttrValue(inf_v, "name", DATA_T_STRING, POD(&name), NULL)); /* Failure ignored. */ + check(clusterGetAttrValue(inf_v, "internal_type", DATA_T_STRING, POD(&internal_type), NULL)); /* Failure ignored. */ + mssError(0, "Cluster", + "Failed to get presentation hints for '%s' on object '%s' : \"%s\".", + attr_name, name, internal_type + ); + + end: + if (tmp_list != NULL) check(expFreeParamList(tmp_list)); /* Failure ignored. */ + + return hints; + } + + +// LINK #functions +/*** Returns the name of the first attribute that one can get from + *** this driver instance (using `GetAttrType()` and `GetAttrValue()`). + *** Resets the internal variable (`TargetAttrIndex`) used to maintain + *** iteration state for `clusterGetNextAttr()`. + *** + *** @param inf_v The driver instance to be read. + *** @param oxt The transaction tree (for the incomplete transaction system). + *** @returns The name of the first attribute. + ***/ +char* +clusterGetFirstAttr(void* inf_v, pObjTrxTree* oxt) + { + pDriverData driver_data = check_ptr(inf_v); + if (UNLIKELY(driver_data == NULL)) return NULL; + ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); + + driver_data->TargetAttrIndex = 0u; + + return clusterGetNextAttr(inf_v, oxt); + } + + +// LINK #functions +/*** Returns the name of the next attribute that one can get from + *** this driver instance (using GetAttrType() and GetAttrValue()). + *** Uses an internal variable (TargetAttrIndex) used to maintain + *** the state of this iteration over repeated calls. + *** + *** @param inf_v The driver instance to be read. + *** @param oxt The transaction tree (for the incomplete transaction system). + *** @returns The name of the next attribute. + ***/ +char* +clusterGetNextAttr(void* inf_v, pObjTrxTree* oxt) + { + pDriverData driver_data = check_ptr(inf_v); + if (UNLIKELY(driver_data == NULL)) return NULL; + ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); + + const unsigned int i = driver_data->TargetAttrIndex++; + switch (driver_data->TargetType) + { + case TARGET_NODE: return (i < N_ROOT_ATTRS) ? ROOT_ATTRS[i] : NULL; + case TARGET_CLUSTER: return (i < N_CLUSTER_ATTRS) ? CLUSTER_ATTRS[i] : NULL; + case TARGET_SEARCH: return (i < N_SEARCH_ATTRS) ? SEARCH_ATTRS[i] : NULL; + case TARGET_CLUSTER_ENTRY: return (i < N_CLUSTER_ENTRY_ATTRS) ? CLUSTER_ENTRY_ATTRS[i] : NULL; + case TARGET_SEARCH_ENTRY: return (i < N_SEARCH_ENTRY_ATTRS) ? SEARCH_ENTRY_ATTRS[i] : NULL; + default: + mssError(1, "Cluster", "Unknown target type %u.", driver_data->TargetType); + return NULL; + } + + return NULL; /* Unreachable. */ + } + + +// LINK #functions +/*** Get the capabilities of the driver instance object. + *** + *** @param inf_v The driver instance to be checked. + *** @param info The struct to be populated with driver flags. + *** @returns 0 if successful, + *** -1 if the driver is an unimplemented type (should never happen). + ***/ +int +clusterInfo(void* inf_v, pObjectInfo info) + { + pDriverData driver_data = check_ptr(inf_v); + if (UNLIKELY(driver_data == NULL)) goto err; + ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); + pNodeData node_data = check_ptr(driver_data->NodeData); + if (UNLIKELY(node_data == NULL)) goto err; + ASSERTMAGIC(node_data, MGK_CL_NODE_DATA); + + /** Reset flags buffer. **/ + info->Flags = 0; + + /** Disallow unsupported functionality. **/ + info->Flags |= OBJ_INFO_F_CANT_ADD_ATTR; + info->Flags |= OBJ_INFO_F_CANT_HAVE_CONTENT; + info->Flags |= OBJ_INFO_F_NO_CONTENT; + + switch (driver_data->TargetType) + { + case TARGET_NODE: + info->nSubobjects = node_data->nClusterDatas + node_data->nSearchDatas; + info->Flags |= OBJ_INFO_F_CAN_HAVE_SUBOBJ; + info->Flags |= OBJ_INFO_F_SUBOBJ_CNT_KNOWN; + info->Flags |= (info->nSubobjects > 0) ? OBJ_INFO_F_HAS_SUBOBJ : OBJ_INFO_F_NO_SUBOBJ; + break; + + case TARGET_CLUSTER: + info->Flags |= OBJ_INFO_F_CAN_HAVE_SUBOBJ; + info->Flags |= OBJ_INFO_F_HAS_SUBOBJ; /* Data must not be empty. */ + + /*** Clusters always have one label per vector. + *** If we know how many vectors are in the dataset, + *** we know how many labels this cluster will have, + *** even if it hasn't been computed yet. + ***/ + if (node_data->SourceData->Vectors != NULL) + { + info->Flags |= OBJ_INFO_F_SUBOBJ_CNT_KNOWN; + info->nSubobjects = node_data->SourceData->nDatas; + } + break; + + case TARGET_SEARCH: + { + pSearchData search_data = check_ptr(driver_data->TargetData); + if (UNLIKELY(search_data == NULL)) goto err; + + info->Flags |= OBJ_INFO_F_CAN_HAVE_SUBOBJ; + if (search_data->Pairs != NULL) + { + info->nSubobjects = search_data->nPairs; + info->Flags |= OBJ_INFO_F_SUBOBJ_CNT_KNOWN; + info->Flags |= (info->nSubobjects > 0) ? OBJ_INFO_F_HAS_SUBOBJ : OBJ_INFO_F_NO_SUBOBJ; + } + break; + } + + case TARGET_CLUSTER_ENTRY: + case TARGET_SEARCH_ENTRY: + /** No Subobjects. **/ + info->Flags |= OBJ_INFO_F_CANT_HAVE_SUBOBJ; + info->Flags |= OBJ_INFO_F_NO_SUBOBJ; + info->Flags |= OBJ_INFO_F_SUBOBJ_CNT_KNOWN; + info->nSubobjects = 0; + break; + + default: + mssError(1, "Cluster", "Unknown target type %u.", driver_data->TargetType); + goto err; + } + + return 0; + + err: + mssError(0, "Cluster", "Failed execute get info."); + return -1; + } + + +/** ================ Method Execution Functions ================ **/ +/** ANCHOR[id=method] **/ +// LINK #functions + +/*** Returns the name of the first method that one can execute from + *** this driver instance (using `clusterExecuteMethod()`). Resets the + *** internal variable (`TargetMethodIndex`) used to maintain iteration + *** state for `clusterGetNextMethod()`. + *** + *** @param inf_v The driver instance to be read. + *** @param oxt The transaction tree (for the incomplete transaction system). + *** @returns The name of the first method. + ***/ +char* +clusterGetFirstMethod(void* inf_v, pObjTrxTree* oxt) + { + pDriverData driver_data = check_ptr(inf_v); + if (UNLIKELY(driver_data == NULL)) return NULL; + ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); + + driver_data->TargetMethodIndex = 0u; + + return clusterGetNextMethod(inf_v, oxt); + } + + +// LINK #functions +/*** Returns the name of the next method that one can get from + *** this driver instance (using `GetAttrType()` and `GetAttrValue()`). + *** Uses an internal variable (`TargetMethodIndex`) used to maintain + *** the state of this iteration over repeated calls. + *** + *** @param inf_v The driver instance to be read. + *** @param oxt Unused. + *** @returns The name of the next method. + ***/ +char* +clusterGetNextMethod(void* inf_v, pObjTrxTree* oxt) + { + pDriverData driver_data = check_ptr(inf_v); + if (UNLIKELY(driver_data == NULL)) return NULL; + ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); + + return METHOD_NAMES[driver_data->TargetMethodIndex++]; + } + + +// LINK #functions +/** Intended for use in `xhForEach()`. **/ +static int +ci_PrintEntry(pXHashEntry entry, void* arg) + { + /** Extract entry. **/ + char* key = entry->Key; + void* data = entry->Data; + + /** Extract args. **/ + void** args = (void**)arg; + unsigned int* type_id_ptr = (unsigned int*)args[0]; + unsigned int* total_bytes_ptr = (unsigned int*)args[1]; + unsigned long long* less_ptr = (unsigned long long*)args[2]; + char* path = (char*)args[3]; + + /** If a path is provided, check that it matches the start of the key. **/ + if (path != NULL && strncmp(key, (char*)path, strlen((char*)path)) != 0) return 0; + + /** Handle type. **/ + char* type; + char* name; + size_t bytes; + switch (*type_id_ptr) + { + case 1u: + { + pSourceData source_data = (pSourceData)data; + + /** Compute size. **/ + bytes = ci_SizeOfSourceData(source_data); + + /** If less is specified, skip uncomputed source. **/ + if (*less_ptr > 0llu && source_data->Vectors == NULL) goto no_print; + + /** Compute printing information. **/ + type = "Source"; + name = source_data->Name; + break; + } + case 2u: + { + pClusterData cluster_data = (pClusterData)data; + + /** Compute size. **/ + bytes = ci_SizeOfClusterData(cluster_data, false); + + /** If less is specified, skip uncomputed source. **/ + if (*less_ptr > 0llu && cluster_data->Clusters == NULL) goto no_print; + + /** Compute printing information. **/ + type = "Cluster"; + name = cluster_data->Name; + break; + } + case 3u: + { + pSearchData search_data = (pSearchData)data; + + /** Compute size. **/ + bytes = ci_SizeOfSearchData(search_data); + + /** If less is specified, skip uncomputed source. **/ + if (*less_ptr > 0llu && search_data->Pairs == NULL) goto no_print; + + /** Compute printing information. **/ + type = "Search"; + name = search_data->Name; + break; + } + default: + mssError(0, "Cluster", "Unknown type_id %u.", *type_id_ptr); + return -1; + } + + /** Print the cache entry data. **/ + char buf[12]; + snprint_bytes(buf, sizeof(buf), bytes); + printf("%-8s %-16s %-12s \"%s\"\n", type, name, buf, key); + goto increment_total; + + no_print: + (*less_ptr)++; + + increment_total: + *total_bytes_ptr += bytes; + + return 0; + } + + +// LINK #functions +/** Intended for use in `xhClearKeySafe()`. **/ +static void +ci_CacheFreeSourceData(pXHashEntry entry, void* path) + { + /** Extract hash entry. **/ + char* key = entry->Key; + pSourceData source_data = (pSourceData)entry->Data; + + /** If a path is provided, check that it matches the start of the key. **/ + if (path != NULL && strncmp(key, (char*)path, strlen((char*)path)) != 0) return; + + /** Free data. **/ + ci_FreeSourceData(source_data); + nmSysFree(key); + + return; + } + + +// LINK #functions +/** Intended for use in `xhClearKeySafe()`. **/ +static void +ci_CacheFreeCluster(pXHashEntry entry, void* path) + { + /** Extract hash entry. **/ + char* key = entry->Key; + pClusterData cluster_data = (pClusterData)entry->Data; + + /** If a path is provided, check that it matches the start of the key. **/ + if (path != NULL && strncmp(key, (char*)path, strlen((char*)path)) != 0) return; + + /** Free data. **/ + ci_FreeClusterData(cluster_data, false); + nmSysFree(key); + + return; + } + + +// LINK #functions +/** Intended for use in `xhClearKeySafe()`. **/ +static void +ci_CacheFreeSearch(pXHashEntry entry, void* path) + { + /** Extract hash entry. **/ + char* key = entry->Key; + pSearchData search_data = (pSearchData)entry->Data; + + /** If a path is provided, check that it matches the start of the key. **/ + if (path != NULL && strncmp(key, (char*)path, strlen((char*)path)) != 0) return; + + /** Free data. **/ + ci_FreeSearchData(search_data); + nmSysFree(key); + + return; + } + + +// LINK #functions +/*** Executes a method with the given name. + *** + *** @param inf_v The affected driver instance. + *** @param method_name The name of the method. + *** @param param A possibly optional param passed to the method. + *** @param oxt The transaction tree (for the incomplete transaction system). + ***/ +int +clusterExecuteMethod(void* inf_v, char* method_name, pObjData param, pObjTrxTree* oxt) + { + pDriverData driver_data = check_ptr(inf_v); + if (UNLIKELY(driver_data == NULL)) goto err; + ASSERTMAGIC(driver_data, MGK_CL_DRIVER_DATA); + + /** Cache management method. **/ + if (strcmp(method_name, "cache") == 0) + { + char* path = NULL; + + /** Second parameter is required. **/ + if (UNLIKELY(param->String == NULL)) + { + mssError(1, "Cluster", + "[param : \"show\" | \"show_less\" | \"show_all\" | \"drop_all\"] is required for the cache method." + ); + goto err; + } + + /** 'show' and 'show_all'. **/ + bool show = false; + unsigned long long skip_uncomputed = 0llu; + if (strcmp(param->String, "show_less") == 0) + { + /** Specify show_less to skip uncomputed caches. **/ + skip_uncomputed = 1ull; + } + if (skip_uncomputed == 1ull || strcmp(param->String, "show") == 0) + { + show = true; + path = objFilePath(driver_data->NodeData->Parent); + } + if (strcmp(param->String, "show_all") == 0) show = true; + + if (show) + { + /** Print cache info table. **/ + int ret = 0; + unsigned int i = 1u, source_bytes = 0u, cluster_bytes = 0u, search_bytes = 0u; + bool failed = false; + printf("\nShowing cache for "); + if (path != NULL) printf("\"%s\":\n", path); + else printf("all files:\n"); + printf("%-8s %-16s %-12s %s\n", "Type", "Name", "Size", "Entry CacheKey"); + failed |= (check(xhForEach( + &ClusterDriverCaches.SourceDataCache, + ci_PrintEntry, + (void*[]){&i, &source_bytes, (void*)&skip_uncomputed, path} + )) != 0); + i++; + failed |= (check(xhForEach( + &ClusterDriverCaches.ClusterDataCache, + ci_PrintEntry, + (void*[]){&i, &cluster_bytes, (void*)&skip_uncomputed, path} + )) != 0); + i++; + failed |= (check(xhForEach( + &ClusterDriverCaches.SearchDataCache, + ci_PrintEntry, + (void*[]){&i, &search_bytes, (void*)&skip_uncomputed, path} + )) != 0); + if (failed) + { + mssError(0, "Cluster", "Unexpected error occurred while showing caches."); + ret = -1; + } + + /** Precomputations. **/ + unsigned int total_caches = 0u + + (unsigned int)ClusterDriverCaches.SourceDataCache.nItems + + (unsigned int)ClusterDriverCaches.ClusterDataCache.nItems + + (unsigned int)ClusterDriverCaches.SearchDataCache.nItems; + if (total_caches <= skip_uncomputed) printf("All caches skipped, nothing to show...\n"); + + /** Print stats. **/ + char buf[16]; + printf("\nCache Stats:\n"); + printf("%-8s %-4s %-12s\n", "", "#", "Total Size"); + printf("%-8s %-4d %-12s\n", "Source", ClusterDriverCaches.SourceDataCache.nItems, snprint_bytes(buf, sizeof(buf), source_bytes)); + printf("%-8s %-4d %-12s\n", "Cluster", ClusterDriverCaches.ClusterDataCache.nItems, snprint_bytes(buf, sizeof(buf), cluster_bytes)); + printf("%-8s %-4d %-12s\n", "Search", ClusterDriverCaches.SearchDataCache.nItems, snprint_bytes(buf, sizeof(buf), search_bytes)); + printf("%-8s %-4d %-12s\n\n", "Total", total_caches, snprint_bytes(buf, sizeof(buf), source_bytes + cluster_bytes + search_bytes)); + + /** Print skip stats (if anything was skipped.) **/ + if (skip_uncomputed > 0llu) printf("Skipped %llu uncomputed caches.\n\n", skip_uncomputed - 1llu); + + return ret; + } + + /** 'drop_all'. **/ + if (strcmp(param->String, "drop_all") == 0) + { + ci_ClearCaches(); + printf("Dropped cache for all cluster files.\n"); + return 0; + } + + /** Unknown parameter. **/ + mssError(1, "Cluster", + "Expected [param : \"show\" | \"show_less\" | \"show_all\" | \"drop_all\"] for the cache method, but got: \"%s\"", + param->String + ); + goto err; + } + + if (strcmp(method_name, "stat") == 0) + { + char buf[12]; + printf("Cluster Driver Statistics:\n"); + printf(" Stat Name %12s\n", "Value"); + printf(" OpenCalls %12s\n", snprint_commas_llu(buf, sizeof(buf), ClusterStatistics.OpenCalls)); + printf(" OpenQueryCalls %12s\n", snprint_commas_llu(buf, sizeof(buf), ClusterStatistics.OpenQueryCalls)); + printf(" FetchCalls %12s\n", snprint_commas_llu(buf, sizeof(buf), ClusterStatistics.FetchCalls)); + printf(" CloseCalls %12s\n", snprint_commas_llu(buf, sizeof(buf), ClusterStatistics.CloseCalls)); + printf(" GetTypeCalls %12s\n", snprint_commas_llu(buf, sizeof(buf), ClusterStatistics.GetTypeCalls)); + printf(" GetValCalls %12s\n", snprint_commas_llu(buf, sizeof(buf), ClusterStatistics.GetValCalls)); + printf(" GetValCalls_name %12s\n", snprint_commas_llu(buf, sizeof(buf), ClusterStatistics.GetValCalls_name)); + printf(" GetValCalls_key1 %12s\n", snprint_commas_llu(buf, sizeof(buf), ClusterStatistics.GetValCalls_key1)); + printf(" GetValCalls_key2 %12s\n", snprint_commas_llu(buf, sizeof(buf), ClusterStatistics.GetValCalls_key2)); + printf(" GetValCalls_sim %12s\n", snprint_commas_llu(buf, sizeof(buf), ClusterStatistics.GetValCalls_sim)); + printf("\n"); + + nmStats(); + + return 0; + } + + /** Unknown parameter. **/ + mssError(1, "Cluster", "Unknown command: \"%s\"", method_name); + + /** Attempt to give hint. **/ + unsigned int n_methods = 0; + while (METHOD_NAMES[n_methods] != NULL) n_methods++; + ci_TryHint(method_name, METHOD_NAMES, n_methods); + + err: + mssError(0, "Cluster", "Failed execute command."); + + return -1; + } + + +/** ================ Unimplemented Functions ================ **/ +/** ANCHOR[id=unimplemented] **/ +// LINK #functions + +/** Not implemented. **/ +int +clusterCreate(pObject obj, int mask, pContentType sys_type, char* usr_type, pObjTrxTree* oxt) + { + mssError(1, "Cluster", "clusterCreate() is not implemented."); + + return -ENOSYS; + } + +/** Not implemented. **/ +int +clusterDelete(pObject obj, pObjTrxTree* oxt) + { + mssError(1, "Cluster", "clusterDelete() is not implemented."); + + return -1; + } + +/** Not implemented. **/ +int +clusterDeleteObj(void* inf_v, pObjTrxTree* oxt) + { + mssError(1, "Cluster", "clusterDeleteObj() is not implemented."); + + return -1; + } + +/** Not implemented. **/ +int +clusterRead(void* inf_v, char* buffer, int max_cnt, int offset, int flags, pObjTrxTree* oxt) + { + mssError(1, "Cluster", "clusterRead() not implemented."); + fprintf(stderr, "HINT: Use queries instead, (e.g. clusterOpenQuery()).\n"); + + return -1; + } + +/** Not implemented. **/ +int +clusterWrite(void* inf_v, char* buffer, int cnt, int offset, int flags, pObjTrxTree* oxt) + { + mssError(1, "Cluster", "clusterWrite() not implemented because clusters are immutable."); + + return -1; + } + +/** Not implemented. **/ +int +clusterSetAttrValue(void* inf_v, char* attr_name, int datatype, pObjData val, pObjTrxTree* oxt) + { + mssError(1, "Cluster", "clusterSetAttrValue() not implemented because clusters are immutable."); + + return -1; + } + +/** Not implemented. **/ +int +clusterAddAttr(void* inf_v, char* attr_name, int type, pObjData val, pObjTrxTree* oxt) + { + mssError(1, "Cluster", "clusterAddAttr() not implemented because clusters are immutable."); + + return -1; + } + +/** Not implemented. **/ +void* +clusterOpenAttr(void* inf_v, char* attr_name, int mode, pObjTrxTree* oxt) + { + mssError(1, "Cluster", "clusterOpenAttr() not implemented."); + + return NULL; + } + +/** Not implemented. **/ +int +clusterCommit(void* inf_v, pObjTrxTree* oxt) + { + mssError(1, "Cluster", "clusterCommit() not implemented because clusters are immutable."); + + return 0; + } + + +// LINK #functions +/*** Initialize the driver, including: + *** - Registering the driver with the object system. + *** - Registering structs with newmalloc for debugging. + *** - Initializing global data needed for the driver. + *** + *** @returns 0 if successful, or + *** -1 if an error occurs. + ***/ +int +clusterInitialize(void) + { + /** Allocate the driver. **/ + pObjDriver drv = check_ptr(nmMalloc(sizeof(ObjDriver))); + if (UNLIKELY(drv == NULL)) goto err_free; + memset(drv, 0, sizeof(ObjDriver)); + + /** Initialize caches. **/ + // memset(&ClusterDriverCaches, 0, sizeof(ClusterDriverCaches)); + if (check(xhInit(&ClusterDriverCaches.SourceDataCache, 251, 0)) != 0) goto err_free; + if (check(xhInit(&ClusterDriverCaches.ClusterDataCache, 251, 0)) != 0) goto err_free; + if (check(xhInit(&ClusterDriverCaches.SearchDataCache, 251, 0)) != 0) goto err_free; + + /** Setup the structure. **/ + if (check_ptr(strcpy(drv->Name, "cluster - Clustering Driver")) == NULL) goto err_free; + if (check(xaInit(&drv->RootContentTypes, 1)) != 0) goto err_free; + if (check_neg(xaAddItem(&drv->RootContentTypes, "system/cluster")) < 0) goto err_free; + + drv->Capabilities = 0; /* TODO: Greg - Should I indicate any capabilities? */ + + /** Setup the function references. **/ + drv->Open = clusterOpen; + drv->OpenChild = NULL; + drv->Close = clusterClose; + drv->Create = clusterCreate; + drv->Delete = clusterDelete; + drv->DeleteObj = clusterDeleteObj; + drv->OpenQuery = clusterOpenQuery; + drv->QueryDelete = NULL; + drv->QueryFetch = clusterQueryFetch; + drv->QueryClose = clusterQueryClose; + drv->Read = clusterRead; + drv->Write = clusterWrite; + drv->GetAttrType = clusterGetAttrType; + drv->GetAttrValue = clusterGetAttrValue; + drv->GetFirstAttr = clusterGetFirstAttr; + drv->GetNextAttr = clusterGetNextAttr; + drv->SetAttrValue = clusterSetAttrValue; + drv->AddAttr = clusterAddAttr; + drv->OpenAttr = clusterOpenAttr; + drv->GetFirstMethod = clusterGetFirstMethod; + drv->GetNextMethod = clusterGetNextMethod; + drv->ExecuteMethod = clusterExecuteMethod; + drv->PresentationHints = clusterPresentationHints; + drv->Info = clusterInfo; + drv->Commit = clusterCommit; + drv->GetQueryCoverageMask = NULL; + drv->GetQueryIdentityPath = NULL; + + /** Register the driver. **/ + if (check(objRegisterDriver(drv)) != 0) goto err_free; + + /** Register structs used in this project with the newmalloc memory management system. **/ + nmRegister(sizeof(SourceData), "ClusterSourceData"); + nmRegister(sizeof(Cluster), "Cluster"); + nmRegister(sizeof(ClusterData), "ClusterData"); + nmRegister(sizeof(SearchData), "ClusterSearch"); + nmRegister(sizeof(NodeData), "ClusterNodeData"); + nmRegister(sizeof(DriverData), "ClusterDriverData"); + nmRegister(sizeof(ClusterQuery), "ClusterQuery"); + nmRegister(sizeof(ClusterDriverCaches), "ClusterDriverCaches"); + + /** Success. **/ + return 0; + + err_free: + /** Error cleanup. **/ + if (ClusterDriverCaches.SourceDataCache.nRows != 0) check(xhDeInit(&ClusterDriverCaches.SourceDataCache)); /* Failure ignored. */ + if (ClusterDriverCaches.ClusterDataCache.nRows != 0) check(xhDeInit(&ClusterDriverCaches.ClusterDataCache)); /* Failure ignored. */ + if (ClusterDriverCaches.SearchDataCache.nRows != 0) check(xhDeInit(&ClusterDriverCaches.SearchDataCache)); /* Failure ignored. */ + if (drv != NULL) + { + if (drv->RootContentTypes.nAlloc != 0) check(xaDeInit(&drv->RootContentTypes)); /* Failure ignored. */ + nmFree(drv, sizeof(ObjDriver)); + } + + mssError(1, "Cluster", "Failed to initialize cluster driver.\n"); + + return -1; + } diff --git a/centrallix/osdrivers/objdrv_qytree.c b/centrallix/osdrivers/objdrv_qytree.c index 72994bfef..bec2a511a 100644 --- a/centrallix/osdrivers/objdrv_qytree.c +++ b/centrallix/osdrivers/objdrv_qytree.c @@ -1325,8 +1325,6 @@ qytQueryFetch(void* qy_v, pObject obj, int mode, pObjTrxTree* oxt) pQytQuery qy = ((pQytQuery)(qy_v)); pQytData inf; pObject llobj = NULL; - pStructInf find_inf; - char* ptr; char* objname = NULL; int cur_id = -1; @@ -1883,4 +1881,3 @@ qytInitialize() return 0; } - diff --git a/centrallix/osdrivers/objdrv_shell.c b/centrallix/osdrivers/objdrv_shell.c index 57e2a6ef3..494c5627c 100644 --- a/centrallix/osdrivers/objdrv_shell.c +++ b/centrallix/osdrivers/objdrv_shell.c @@ -4,6 +4,7 @@ #include #include "obj.h" #include "cxlib/mtask.h" +#include "cxlib/mtsession.h" #include "cxlib/xarray.h" #include "cxlib/xhash.h" #include "stparse.h" @@ -1254,4 +1255,3 @@ MODULE_PREFIX("shl"); MODULE_DESC("SHL ObjectSystem Driver"); MODULE_VERSION(0,1,0); MODULE_IFACE(CX_CURRENT_IFACE); - diff --git a/centrallix/test_obj.c b/centrallix/test_obj.c index db6125fdf..a323e6d82 100644 --- a/centrallix/test_obj.c +++ b/centrallix/test_obj.c @@ -33,7 +33,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -811,7 +811,13 @@ testobj_do_cmd(pObjSession s, char* cmd, int batch_mode, pLxSession inp_lx) fdQPrintf(TESTOBJ.Output, "%[,%]%STR", i!=0, ptr); else { - while (strpbrk(ptr, "\r\n")) *(strpbrk(ptr, "\r\n")) = ' '; + char* cur; + while (1) + { + cur = strpbrk(ptr, "\r\n"); + if (cur == NULL) break; + else *cur = ' '; + } fdQPrintf(TESTOBJ.Output, "%[,%]\"%STR&DSYB\"", i!=0, ptr); } @@ -906,6 +912,14 @@ testobj_do_cmd(pObjSession s, char* cmd, int batch_mode, pLxSession inp_lx) case DATA_T_DOUBLE: fdPrintf(TESTOBJ.Output,"%s", objDataToStringTmp(type, &od, 0)); break; + + case DATA_T_STRINGVEC: + fdPrintf(TESTOBJ.Output,"%s", objDataToStringTmp(type, od.StringVec, DATA_F_QUOTED & DATA_F_BRACKETS)); + break; + + case DATA_T_INTVEC: + fdPrintf(TESTOBJ.Output,"%s", objDataToStringTmp(type, od.IntVec, DATA_F_QUOTED & DATA_F_BRACKETS)); + break; case DATA_T_STRING: case DATA_T_DATETIME: @@ -1495,6 +1509,7 @@ testobj_do_cmd(pObjSession s, char* cmd, int batch_mode, pLxSession inp_lx) else { printf("Unknown command '%s'\n",cmdname); + mlxCloseSession(ls); return -1; } diff --git a/centrallix/tests/t_driver.c b/centrallix/tests/t_driver.c index b0b77e3b0..34a132b92 100644 --- a/centrallix/tests/t_driver.c +++ b/centrallix/tests/t_driver.c @@ -50,7 +50,7 @@ print_result(char * result, bool succeeded) #ifdef HAVE_NCURSES if (use_curses) { // Clear blue color from stderr - tputs(tparm(tigetstr("sgr0")), 1, puterr); + tputs(tparm(tigetstr("sgr0")), 1, (int(*)(int))puterr); } #endif @@ -110,7 +110,7 @@ start(void* v) if (use_curses) { // Set any error output to be blue - tputs(tparm(tigetstr("setaf"), COLOR_BLUE), 1, puterr); + tputs(tparm(tigetstr("setaf"), COLOR_BLUE), 1, (int(*)(int))puterr); } #endif @@ -132,4 +132,3 @@ main(int argc, char* argv[]) mtInitialize(0, start); return 0; } - diff --git a/centrallix/tests/test_cos_compare_00.cmp b/centrallix/tests/test_cos_compare_00.cmp index d586365f7..2061443ac 100644 --- a/centrallix/tests/test_cos_compare_00.cmp +++ b/centrallix/tests/test_cos_compare_00.cmp @@ -1,7 +1,11 @@ -Attribute [case1]: integer 1 -Attribute [case2]: integer 1 -Attribute [case3]: integer 1 -Attribute [case4]: integer 1 -Attribute [case5]: integer 1 -Attribute [case6]: integer 1 -Attribute [case7]: integer 1 +Attribute [case1]: string "pass" +Attribute [case2]: string "pass" +Attribute [case3]: string "pass" +Attribute [case4]: string "pass" +Attribute [cynthia]: string "pass" +Attribute [timothy]: string "pass" +Attribute [lance]: string "pass" +Attribute [gregory]: string "pass" +Attribute [nathan]: string "pass" +Attribute [identical]: string "pass" +Attribute [name]: string "pass" diff --git a/centrallix/tests/test_cos_compare_00.to b/centrallix/tests/test_cos_compare_00.to index 5bf950514..f6a635389 100644 --- a/centrallix/tests/test_cos_compare_00.to +++ b/centrallix/tests/test_cos_compare_00.to @@ -1,17 +1,24 @@ ##NAME Text Mining String Similarity with Cosine Compare -# All email addresses and phone numbers are imaginary and were fabricated for the purposes of this test +# Basic tests of cosine similarity. +query select case1 = condition((cos_compare('hello', 'hello') >= 0.999) and (cos_compare('hello', 'hello') <= 1.0), "pass", "fail") +query select case2 = condition((cos_compare('hello', 'zephora') <= 0.001) and (cos_compare('hello', 'zephora') >= 0.0), "pass", "fail") +query select case3 = condition((cos_compare('hello', 'hello world') <= 0.7) and (cos_compare('hello', 'hello world') >= 0.6), "pass", "fail") +query select case4 = condition((cos_compare('hello there', 'hellow there') >= 0.9) and (cos_compare('hello', 'hellow') <= 1.0), "pass", "fail") + -query select case1 = (cos_compare("Cynthia Adams; cynthiaadams@gmail.com; 720-769-1293", "Timothy Adams; thetbear@gmail.com; 720-891-1470") >= 0.49) and (cos_compare("Cynthia Adams; cynthiaadams@gmail.com; 720-769-1293", "Timothy Adams; thetbear@gmail.com; 720-891-1470") <= 0.54) +# Tests on fabricated contact information. +# All email addresses and phone numbers are imaginary and were fabricated for the purposes of this test +query select cynthia = condition((cos_compare("Cynthia Adams; cynthiaadams@gmail.com; 720-769-1293", "Timothy Adams; thetbear@gmail.com; 720-891-1470") >= 0.45) and (cos_compare("Cynthia Adams; cynthiaadams@gmail.com; 720-769-1293", "Timothy Adams; thetbear@gmail.com; 720-891-1470") <= 0.59), "pass", "fail") -query select case2 = (cos_compare("Timothy Adams; thetbear@gmail.com; 720-891-1470", "Lance Freson; lancetheturtle@gmail.com; 720-111-8189") >= 0.425) and (cos_compare("Timothy Adams; thetbear@gmail.com; 720-891-1470", "Lance Freson; lancetheturtle@gmail.com; 720-111-8189") <= 0.475) +query select timothy = condition((cos_compare("Timothy Adams; thetbear@gmail.com; 720-891-1470", "Lance Freson; lancetheturtle@gmail.com; 720-111-8189") >= 0.40) and (cos_compare("Timothy Adams; thetbear@gmail.com; 720-891-1470", "Lance Freson; lancetheturtle@gmail.com; 720-111-8189") <= 0.55), "pass", "fail") -query select case3 = (cos_compare("Lance Freson; lancetheturtle@gmail.com; 720-111-8189", "Gregory Freson; greatgregory@gmail.com; 720-198-5791") >= 0.35) and (cos_compare("Lance Freson; lancetheturtle@gmail.com; 720-111-8189", "Gregory Freson; greatgregory@gmail.com; 720-198-5791") <= 0.40) +query select lance = condition((cos_compare("Lance Freson; lancetheturtle@gmail.com; 720-111-8189", "Gregory Freson; greatgregory@gmail.com; 720-198-5791") >= 0.4) and (cos_compare("Lance Freson; lancetheturtle@gmail.com; 720-111-8189", "Gregory Freson; greatgregory@gmail.com; 720-198-5791") <= 0.5), "pass", "fail") -query select case4 = (cos_compare("Gregory Freson; greatgregory@gmail.com; 720-198-5791", "Gregory Freson; greatgregory@gmail.co; 720-198-5791") >= 0.94) and (cos_compare("Gregory Freson; greatgregory@gmail.com; 720-198-5791", "Gregory Freson; greatgregory@gmail.co; 720-198-5791") <= 0.99) +query select gregory = condition((cos_compare("Gregory Freson; greatgregory@gmail.com; 720-198-5791", "Gregory Freson; greatgregory@gmail.co; 720-198-5791") >= 0.94) and (cos_compare("Gregory Freson; greatgregory@gmail.com; 720-198-5791", "Gregory Freson; greatgregory@gmail.co; 720-198-5791") <= 0.99), "pass", "fail") -query select case5 = (cos_compare("Nathan Mayor; nmmayor@yahoo.com; +1-800-192-9128", "Mindy Mayor; nmmayor@yahoo.com; 720-981-9149") >=0.66) and (cos_compare("Nathan Mayor; nmmayor@yahoo.com; +1-800-192-9128", "Mindy Mayor; nmmayor@yahoo.com; 720-981-9149") <= 0.71) +query select nathan = condition((cos_compare("Nathan Mayor; nmmayor@yahoo.com; +1-800-192-9128", "Mindy Mayor; nmmayor@yahoo.com; 720-981-9149") >= 0.6) and (cos_compare("Nathan Mayor; nmmayor@yahoo.com; +1-800-192-9128", "Mindy Mayor; nmmayor@yahoo.com; 720-981-9149") <= 0.7), "pass", "fail") -query select case6 = (cos_compare("This is an identical case", "This is an identical case") >=0.975) and (cos_compare("This is an identical case", "This is an identical case") <=1.00) +query select identical = condition((cos_compare("This is an identical case", "This is an identical case") >= 0.975) and (cos_compare("This is an identical case", "This is an identical case") <= 1.00), "pass", "fail") -query select case7 = (cos_compare("Samuel", "Alex") >= 0.00) and (cos_compare("Samuel", "Alex") <= 0.025) +query select name = condition((cos_compare("Samuel", "Alex") >= 0.00) and (cos_compare("Samuel", "Alex") <= 0.05), "pass", "fail") diff --git a/centrallix/tests/test_cxss_hexify_00.c b/centrallix/tests/test_cxss_hexify_00.c index beeaa0701..eb8a0d443 100644 --- a/centrallix/tests/test_cxss_hexify_00.c +++ b/centrallix/tests/test_cxss_hexify_00.c @@ -26,7 +26,7 @@ test_hexify(char* str, char* cmp, int dstbuflen, int cmprval) buf1[len+2+2] = 0; memset(buf2, 127, sizeof(buf2)); - rval = cxssHexify(buf1+2, len, buf2+2, dstbuflen); + rval = cxssHexify((unsigned char*)buf1+2, len, buf2+2, dstbuflen); assert(rval == cmprval); assert(buf1[0] == 0); diff --git a/centrallix/tests/test_cxss_hexify_01.c b/centrallix/tests/test_cxss_hexify_01.c index 0307ab558..a0d3bb421 100644 --- a/centrallix/tests/test_cxss_hexify_01.c +++ b/centrallix/tests/test_cxss_hexify_01.c @@ -26,7 +26,7 @@ test_hexify(char* str, char* cmp, int dstlen, int cmprval) buf1[len+2+2] = 0; memset(buf2, 127, sizeof(buf2)); - rval = cxss_i_Hexify(buf1+2, len, buf2+2, dstlen); + rval = cxss_i_Hexify((unsigned char*)buf1+2, len, buf2+2, dstlen); assert(rval == cmprval); assert(buf1[0] == 0); diff --git a/centrallix/tests/test_cxss_hexify_02.c b/centrallix/tests/test_cxss_hexify_02.c index 67fd4f58f..b7bbbf224 100644 --- a/centrallix/tests/test_cxss_hexify_02.c +++ b/centrallix/tests/test_cxss_hexify_02.c @@ -21,7 +21,7 @@ test_hexify(char* str, char* cmp, int dstlen, int cmprval) memset(buf1, 127, sizeof(buf1)); strcpy(buf1+2, str); - rval = cxss_i_Hexify(buf1+2, len, buf1+2, dstlen); + rval = cxss_i_Hexify((unsigned char*)buf1+2, len, buf1+2, dstlen); assert(rval == cmprval); diff --git a/centrallix/tests/test_expfn_log_00.cmp b/centrallix/tests/test_expfn_log_00.cmp new file mode 100644 index 000000000..130056813 --- /dev/null +++ b/centrallix/tests/test_expfn_log_00.cmp @@ -0,0 +1,43 @@ +Attribute [ln(1)]: double 0.0 +Attribute [ln(e)]: double 1.0 +Attribute [ln(0)]: double -inf.0 +Attribute [ln(-1)]: double nan.0 +Attribute [ln(10)]: double 2.30258509 +Attribute [ln(1.5)]: double 0.40546511 +Attribute [ln(1e-10)]: integer 1 +Attribute [ln(1e10)+]: integer 1 +Attribute [ln(1e10)-]: integer 1 +Attribute [log10(1)]: double 0.0 +Attribute [log10(10)]: double 1.0 +Attribute [log10(0)]: double -inf.0 +Attribute [log10(-10)]: double nan.0 +Attribute [log10(100)]: double 2.0 +Attribute [log10(0.01)]: double -2.0 +Attribute [log10(1.234)]: double 0.09131516 +Attribute [log10(1e-10)]: double -10.0 +Attribute [log10(1e10)]: double 10.0 +Attribute [log(8, 2)]: double 3.0 +Attribute [log(1000, 10)]: double 3.0 +Attribute [log(10, 0)]: double -0.0 +Attribute [log(10, 1)]: double inf.0 +Attribute [log(8, -2)]: double nan.0 +Attribute [log(0, 2)]: double -inf.0 +Attribute [log(-8, 2)]: double nan.0 +Attribute [log(1, 2)]: double 0.0 +Attribute [log(1e10, 10)]: double 10.0 +Attribute [log(8, 0.5)]: double -3.0 +Attribute [log(1)]: integer 1 +Attribute [log(e)]: integer 1 +Attribute [log(0)]: integer 1 +Attribute [log(-1)]: integer 1 +Attribute [log(10)]: integer 1 +Attribute [log(1.5)]: integer 1 +Attribute [log(1e-10)]: integer 1 +Attribute [log(1e10)+]: integer 1 +Attribute [log(1e10)-]: integer 1 +Attribute [ln(2.718281828)]: double 1.0 +Attribute [log10(3.14159)]: double 0.49714951 +Attribute [log(10, 1.1)]: double 0.04139269 +Attribute [log(1.1, 10)]: double 24.15885793 +Attribute [log(10, 0.001)]: double -3.0 +Attribute [log(0.1, 1000)]: double -3.0 diff --git a/centrallix/tests/test_expfn_log_00.to b/centrallix/tests/test_expfn_log_00.to new file mode 100644 index 000000000..c73140236 --- /dev/null +++ b/centrallix/tests/test_expfn_log_00.to @@ -0,0 +1,55 @@ +##NAME log() functions + +# Natural Log: ln(x) +query select 'ln(1)' = ln(1) -- Expect 0. +query select 'ln(e)' = ln(2.718281828459045) -- Expect 1. +query select 'ln(0)' = ln(0) -- Expect -inf (log approaches infinity). +query select 'ln(-1)' = ln(-1) -- Expect NaN (log undefined for negative). +query select 'ln(10)' = round(ln(10), 8) -- Expect ~2.30258509. +query select 'ln(1.5)' = round(ln(1.5), 8) -- Expect ~0.40546511. +query select 'ln(1e-10)' = ln(0.0000000001) < 0.0000000001 -- Expect true (value is very small). +query select 'ln(1e10)+' = ln(10000000000.0) > 23.0 -- Expect true (value is ~23.02585). +query select 'ln(1e10)-' = ln(10000000000.0) < 23.1 -- Expect true (value is ~23.02585). + +# Log base 10: log10(x) +query select 'log10(1)' = log10(1) -- Expect 0. +query select 'log10(10)' = log10(10) -- Expect 1. +query select 'log10(0)' = log10(0) -- Expect -inf. +query select 'log10(-10)' = log10(-10) -- Expect NaN. +query select 'log10(100)' = log10(100) -- Expect 2. +query select 'log10(0.01)' = log10(0.01) -- Expect -2. +query select 'log10(1.234)' = round(log10(1.234), 8) -- Expect ~0.091315. +query select 'log10(1e-10)' = log10(0.0000000001) -- Expect ~-10. +query select 'log10(1e10)' = log10(10000000000.0) -- Expect ~10. + +# General base n of x: log(x, n) +# Edge cases: base <= 0 or base == 1 (invalid), x <= 0 (invalid) +query select 'log(8, 2)' = log(8, 2) -- Expect 3. +query select 'log(1000, 10)' = log(1000, 10) -- Expect 3. +query select 'log(10, 0)' = log(10, 0) -- Expect -0.0 (base 0 is undefined). +query select 'log(10, 1)' = log(10, 1) -- Expect inf (base 1 is undefined). +query select 'log(8, -2)' = log(8, -2) -- Expect NaN (negative base). +query select 'log(0, 2)' = log(0, 2) -- Expect -inf (x=0). +query select 'log(-8, 2)' = log(-8, 2) -- Expect NaN or error (x negative). +query select 'log(1, 2)' = log(1, 2) -- Expect 0. +query select 'log(1e10, 10)' = log(10000000000.0, 10) -- Expect 10. +query select 'log(8, 0.5)' = log(8, 0.5) -- Expect negative value. + +# log(x) = ln(x) +query select 'log(1)' = (log(1) == ln(1)) +query select 'log(e)' = (log(2.71828182845) == ln(2.71828182845)) +query select 'log(0)' = (log(0) == ln(0)) +query select 'log(-1)' = (log(-1) == ln(-1)) +query select 'log(10)' = (log(10) == ln(10)) +query select 'log(1.5)' = (log(1.5) == ln(1.5)) +query select 'log(1e-10)' = (log(0.0000000001) == ln(0.0000000001)) +query select 'log(1e10)+' = (log(10000000000.0) == ln(10000000000.0)) +query select 'log(1e10)-' = (log(10000000000.0) == ln(10000000000.0)) + +-- Additional double/int mixed cases +query select 'ln(2.718281828)' = round(ln(2.718281828), 8) -- Expect ~1 (close to e). +query select 'log10(3.14159)' = round(log10(3.14159), 8) -- Expect ~0.49715. +query select 'log(10, 1.1)' = round(log(1.1, 10), 8) -- Expect 0.04139289. +query select 'log(1.1, 10)' = round(log(10, 1.1), 8) -- Expect 24.15885793. +query select 'log(10, 0.001)' = round(log(0.001, 10), 8) -- Expect ~-0.33333333... +query select 'log(0.1, 1000)' = round(log(1000, 0.1), 8) -- Expect ~-0.33333333... diff --git a/centrallix/tests/test_expfn_metaphone_00.cmp b/centrallix/tests/test_expfn_metaphone_00.cmp new file mode 100644 index 000000000..d13cf05ca --- /dev/null +++ b/centrallix/tests/test_expfn_metaphone_00.cmp @@ -0,0 +1,140 @@ +Attribute [result]: string "TST`TST" +Attribute [result]: string "PSK`PSK" +Attribute [result]: string "SNTRLKS`SNTRLKS" +Attribute [result]: string "LRNS`LRNS" +Attribute [result]: string "FLPS`FLPS" +Attribute [result]: string "AKSPTNNS`AKSPTNKNS" +Attribute [result]: string "SPRKLFRJLSTSKSPLTSS`SPRKLFRKLSTSKSPLTXS" +Attribute [result]: string "SKTLPKSSTSLKRFLKRPS`SKTLPKSSTSLKRFLKRPS" +Attribute [result]: string "SM0`XMT" +Attribute [result]: string "XMT`SMT" +Attribute [result]: string "SNTR`XNTR" +Attribute [result]: string "XNTR`SNTR" +Attribute [result]: string "ARN`ARNF" +Attribute [result]: string "ARNF`ARNF" +Attribute [result]: string "AKST`AKST" +Attribute [result]: string "AKSTNT`AKSTNT" +Attribute [result]: string "AKTL`AKTL" +Attribute [result]: string "ARX`ARK" +Attribute [result]: string "ART`ARTS" +Attribute [result]: string "PKS`PKS" +Attribute [result]: string "PX`PX" +Attribute [result]: string "PJTR`PHTR" +Attribute [result]: string "PLX`PLX" +Attribute [result]: string "PRTX`PRTX" +Attribute [result]: string "PJ`PK" +Attribute [result]: string "P`P" +Attribute [result]: string "PR`PR" +Attribute [result]: string "PRTN`PRTN" +Attribute [result]: string "KPRL`KPR" +Attribute [result]: string "SSR`SSR" +Attribute [result]: string "KKN`KKN" +Attribute [result]: string "KMPL`KMPL" +Attribute [result]: string "KRLL`KRLL" +Attribute [result]: string "KRLL`KRLL" +Attribute [result]: string "KMSTR`KMSTR" +Attribute [result]: string "KNT`KNT" +Attribute [result]: string "KRS`KRS" +Attribute [result]: string "KF`KF" +Attribute [result]: string "SRN`XRN" +Attribute [result]: string "TM`TM" +Attribute [result]: string "ATKR`ATKR" +Attribute [result]: string "AJ`AJ" +Attribute [result]: string "FLPTS`FLPFX" +Attribute [result]: string "FKX`FKX" +Attribute [result]: string "KLKS`KKS" +Attribute [result]: string "KRMNK`JRMNK" +Attribute [result]: string "JRTL`JRTL" +Attribute [result]: string "JLN`JLN" +Attribute [result]: string "KSPL`KSPL" +Attribute [result]: string "KF`KF" +Attribute [result]: string "KRK`KRK" +Attribute [result]: string "HKMR`HKMR" +Attribute [result]: string "H`H" +Attribute [result]: string "ALNT`ALNT" +Attribute [result]: string "AL`AL" +Attribute [result]: string "ATLN`ATLN" +Attribute [result]: string "JNKLTS`ANKLFX" +Attribute [result]: string "HS`HS" +Attribute [result]: string "LF`LF" +Attribute [result]: string "MKFR`MKFR" +Attribute [result]: string "MKRKR`MKRKR" +Attribute [result]: string "MNKR`MNJR" +Attribute [result]: string "MK`MK" +Attribute [result]: string "MKLFLN`MKLFLN" +Attribute [result]: string "MKL`MXL" +Attribute [result]: string "MTL`MTL" +Attribute [result]: string "ARKSTR`ARKSTR" +Attribute [result]: string "ARKT`ARKT" +Attribute [result]: string "PNN`PNN" +Attribute [result]: string "RSPR`RSPR" +Attribute [result]: string "RSN`RSNS" +Attribute [result]: string "RJ`RJR" +Attribute [result]: string "RF`RF" +Attribute [result]: string "SLFTR`SLFTR" +Attribute [result]: string "SNHSNT`SNHSNT" +Attribute [result]: string "XNKR`SKNKR" +Attribute [result]: string "XRMRRN`SKRMRRN" +Attribute [result]: string "XLSNKR`SLSNJR" +Attribute [result]: string "SKL`SKL" +Attribute [result]: string "SKNR`SKNR" +Attribute [result]: string "SKST`SKST" +Attribute [result]: string "XKR`SKR" +Attribute [result]: string "XKR`SKR" +Attribute [result]: string "TKLR`TLR" +Attribute [result]: string "TMS`TMS" +Attribute [result]: string "TMS`TMS" +Attribute [result]: string "0M`TM" +Attribute [result]: string "TXNR`TKNR" +Attribute [result]: string "TF`TF" +Attribute [result]: string "FK`FK" +Attribute [result]: string "AKTLR`FKTLR" +Attribute [result]: string "AKSLR`FKSLR" +Attribute [result]: string "ART`FRT" +Attribute [result]: string "SF`SFR" +Attribute [result]: string "ANKLFX`ANKLFK" +Attribute [result]: string "J`J" +Attribute [result]: string "MKLLN`MKLLN" +Attribute [result]: string "MRS`MRS" +Attribute [result]: string "APR`APR" +Attribute [result]: string "KMPRL`KMPR" +Attribute [result]: string "HT`HT" +Attribute [result]: string "K0RN`KTRN" +Attribute [result]: string "K0RN`KTRN" +Attribute [result]: string "RXRT`RKRT" +Attribute [result]: string "PP`PP" +Attribute [result]: string "ARK`ARK" +Attribute [result]: string "JF`KF" +Attribute [result]: string "TF`TF" +Attribute [result]: string "R`R" +Attribute [result]: string "STFN`STFN" +Attribute [result]: string "PRS`PRS" +Attribute [result]: string "RNT`RNT" +Attribute [result]: string "PRN`PRN" +Attribute [result]: string "PRN`PRN" +Attribute [result]: string "AT`AT" +Attribute [result]: string "AT`AT" +Attribute [result]: string "APT`APT" +Attribute [result]: string "PK`PK" +Attribute [result]: string "PKR`PKR" +Attribute [result]: string "XRLS`XRLS" +Attribute [result]: string "KN`KN" +Attribute [result]: string "NM`NM" +Attribute [result]: string "RJ`R" +Attribute [result]: string "KNTN`KNTN" +Attribute [result]: string "A`A" +Attribute [result]: string "XMKR`XMKR" +Attribute [result]: string "SN`XN" +Attribute [result]: string "SKLT`SKLT" +Attribute [result]: string "STXN`STXN" +Attribute [result]: string "MX`MX" +Attribute [result]: string "PS`PTS" +Attribute [result]: string "AKNS`ANS" +Attribute [result]: string "SNS`SNS" +Attribute [result]: string "FNKK`FNKK" +Attribute [result]: string "JSF`HSF" +Attribute [result]: string "APJKT`APJKT" +Attribute [result]: string "SLS`SLS" +Attribute [result]: string "XRF`XRF" +Attribute [result]: string "KS`KS" +Attribute [result]: string "FNKLR`FNKLR" diff --git a/centrallix/tests/test_expfn_metaphone_00.to b/centrallix/tests/test_expfn_metaphone_00.to new file mode 100644 index 000000000..de1897c3e --- /dev/null +++ b/centrallix/tests/test_expfn_metaphone_00.to @@ -0,0 +1,161 @@ +##NAME metaphone() function + +# Special thanks to the following websites for double checking the correct results: +# 1: https://words.github.io/double-metaphone +# 2: https://mainegenealogy.net/metaphone_converter.asp +# 3: https://en.toolpage.org/tool/metaphone + +# These tests were collected from the following sources: +# - Example comments in the source code of exp_double_metaphone.c +# - Maurice Aubrey's Tests* +# - Tests manually written by Israel Fuller +# - Tests written by prompting ChatGPT-5 (preview)** +# +# *Source: https://github.com/gitpan/Text-DoubleMetaphone/blob/master/t/words.txt +# **GPT-5 mini (Preview) was run in GitHub Copilot to suggest the words +# for some tests after analizing a generated coverage report. I (Israel) +# used the suggestions to write some "AI generated" test cases. +# +# For more information, see the manual test suite implementation at the +# end of the exp_double_metaphone.c file. + +query select result = metaphone("Test") +query select result = metaphone("Basic") +query select result = metaphone("Centrallix") +query select result = metaphone("Lawrence") +query select result = metaphone("Philips") +query select result = metaphone("Acceptingness") +query select result = metaphone("Supercalifragilisticexpialidocious") +query select result = metaphone("Suoicodilaipxecitsiligarfilacrepus") +query select result = metaphone("Smith") +query select result = metaphone("Schmidt") +query select result = metaphone("Snider") +query select result = metaphone("Schneider") +query select result = metaphone("Arnow") +query select result = metaphone("Arnoff") +query select result = metaphone("Accede") +query select result = metaphone("Accident") +query select result = metaphone("Actually") +query select result = metaphone("Arch") +query select result = metaphone("Artois") +query select result = metaphone("Bacchus") +query select result = metaphone("Bacci") +query select result = metaphone("Bajador") +query select result = metaphone("Bellocchio") +query select result = metaphone("Bertucci") +query select result = metaphone("Biaggi") +query select result = metaphone("Bough") +query select result = metaphone("Breaux") +query select result = metaphone("Broughton") +query select result = metaphone("Cabrillo") +query select result = metaphone("Caesar") +query select result = metaphone("Cagney") +query select result = metaphone("Campbell") +query select result = metaphone("Carlisle") +query select result = metaphone("Carlysle") +query select result = metaphone("Chemistry") +query select result = metaphone("Chianti") +query select result = metaphone("Chorus") +query select result = metaphone("Cough") +query select result = metaphone("Czerny") +query select result = metaphone("Dumb") +query select result = metaphone("Edgar") +query select result = metaphone("Edge") +query select result = metaphone("Filipowicz") +query select result = metaphone("Focaccia") +query select result = metaphone("Gallegos") +query select result = metaphone("Germanic") +query select result = metaphone("Ghiradelli") +query select result = metaphone("Ghislane") +query select result = metaphone("Gospel") +query select result = metaphone("Gough") +query select result = metaphone("Greek") +query select result = metaphone("Hochmeier") +query select result = metaphone("Hugh") +query select result = metaphone("Island") +query select result = metaphone("Isle") +query select result = metaphone("Italian") +query select result = metaphone("Jankelowicz") +query select result = metaphone("Jose") +query select result = metaphone("Laugh") +query select result = metaphone("Mac Caffrey") +query select result = metaphone("Mac Gregor") +query select result = metaphone("Manager") +query select result = metaphone("McHugh") +query select result = metaphone("McLaughlin") +query select result = metaphone("Michael") +query select result = metaphone("Middle") +query select result = metaphone("Orchestra") +query select result = metaphone("Orchid") +query select result = metaphone("Pinyin") +query select result = metaphone("Raspberry") +query select result = metaphone("Resnais") +query select result = metaphone("Rogier") +query select result = metaphone("Rough") +query select result = metaphone("Salvador") +query select result = metaphone("San jacinto") +query select result = metaphone("Schenker") +query select result = metaphone("Schermerhorn") +query select result = metaphone("Schlesinger") +query select result = metaphone("School") +query select result = metaphone("Schooner") +query select result = metaphone("Succeed") +query select result = metaphone("Sugar") +query select result = metaphone("Sugary") +query select result = metaphone("Tagliaro") +query select result = metaphone("Thames") +query select result = metaphone("Thomas") +query select result = metaphone("Thumb") +query select result = metaphone("Tichner") +query select result = metaphone("Tough") +query select result = metaphone("Vghee") +query select result = metaphone("Wachtler") +query select result = metaphone("Wechsler") +query select result = metaphone("Word") +query select result = metaphone("Xavier") +query select result = metaphone("Yankelovich") +query select result = metaphone("Zhao") +query select result = metaphone("McClellan") +query select result = metaphone("maurice") +query select result = metaphone("aubrey") +query select result = metaphone("cambrillo") +query select result = metaphone("heidi") +query select result = metaphone("katherine") +query select result = metaphone("catherine") +query select result = metaphone("richard") +query select result = metaphone("bob") +query select result = metaphone("eric") +query select result = metaphone("geoff") +query select result = metaphone("dave") +query select result = metaphone("ray") +query select result = metaphone("steven") +query select result = metaphone("bryce") +query select result = metaphone("randy") +query select result = metaphone("bryan") +query select result = metaphone("brian") +query select result = metaphone("otto") +query select result = metaphone("auto") +query select result = metaphone("Abbott") +query select result = metaphone("Back") +query select result = metaphone("Bacher") +query select result = metaphone("Charles") +query select result = metaphone("Ghana") +query select result = metaphone("Gnome") +query select result = metaphone("Raj") +query select result = metaphone("Quentin") +query select result = metaphone("Who") +query select result = metaphone("Shoemaker") +query select result = metaphone("Sian") +query select result = metaphone("Scold") +query select result = metaphone("Station") +query select result = metaphone("Match") +query select result = metaphone("Pizza") +query select result = metaphone("Agnes") +query select result = metaphone("Science") +query select result = metaphone("Van Gogh") +query select result = metaphone("Josef") +query select result = metaphone("Object") +query select result = metaphone("Sholz") +query select result = metaphone("Scharf") +query select result = metaphone("Kasia") +query select result = metaphone("Van Geller") diff --git a/centrallix/tests/test_expfn_trim_00.cmp b/centrallix/tests/test_expfn_trim_00.cmp new file mode 100644 index 000000000..5515d9aa1 --- /dev/null +++ b/centrallix/tests/test_expfn_trim_00.cmp @@ -0,0 +1,10 @@ +Attribute [trim("White space on the left o]: string "No white space on the left or right side." +Attribute [trim(" White space on the l]: string "White space on the left side." +Attribute [trim("White space on the right ]: string "White space on the right side." +Attribute [trim(" White space on the r]: string "White space on the right and the left side." +Attribute [trim("With tab character ")]: string "With tab character " +Attribute [trim("With newline character +")]: string "With newline character +" +Attribute [trim("")]: string "" +Attribute [trim(null)]: string NULL diff --git a/centrallix/tests/test_expfn_trim_00.to b/centrallix/tests/test_expfn_trim_00.to new file mode 100644 index 000000000..746de6ebc --- /dev/null +++ b/centrallix/tests/test_expfn_trim_00.to @@ -0,0 +1,19 @@ +##NAME trim() function + +query select 'trim("White space on the left or right side.")' = trim("No white space on the left or right side.") + +query select 'trim(" White space on the left side.")' = trim(" White space on the left side.") + +query select 'trim("White space on the right side. ")' = trim("White space on the right side. ") + +query select 'trim(" White space on the right and the left side. ")' = trim(" White space on the right and the left side. ") + +query select 'trim("With tab character\t")' = trim("With tab character\t") + +query select 'trim("With newline character\n")' = trim("With newline character\n") + +# query select 'trim("\r With carriage return character.")' = trim("\r With carriage return character") + +query select 'trim("")' = trim("") + +query select 'trim(null)' = trim(null) diff --git a/centrallix/tests/test_fuzzycompare_00.cmp b/centrallix/tests/test_fuzzycompare_00.cmp deleted file mode 100644 index baa6db1e9..000000000 --- a/centrallix/tests/test_fuzzycompare_00.cmp +++ /dev/null @@ -1,13 +0,0 @@ -Attribute [sw1]: integer 1 -Attribute [sw1]: integer 1 -Attribute [sw1]: integer 1 -Attribute [sw1]: integer 1 -Attribute [sw1]: integer 1 -Attribute [sw1]: integer 1 -Attribute [sw1]: integer 1 -Attribute [sw1]: integer 1 -Attribute [sw1]: integer 1 -Attribute [sw1]: integer 1 -Attribute [sw1]: integer 1 -Attribute [sw1]: integer 1 -Attribute [sw1]: integer 1 diff --git a/centrallix/tests/test_fuzzycompare_00.to b/centrallix/tests/test_fuzzycompare_00.to deleted file mode 100644 index 78141a473..000000000 --- a/centrallix/tests/test_fuzzycompare_00.to +++ /dev/null @@ -1,15 +0,0 @@ -##NAME Levenshtein String Comparison - -query select sw1 = 1 where fuzzy_compare('hello', 'hello!', 20) >= 0 and fuzzy_compare("hello","hello!", 20) <= 1 -query select sw1 = 1 where fuzzy_compare('hello', 'asdfkh', 20) >= 0 and fuzzy_compare("hello","asdfkh", 20) <= 1 -query select sw1 = 1 where fuzzy_compare('hello', 'aaaaaaaaaaaaaaaaa', 20) >= 0 and fuzzy_compare("hello","aaaaaaaaaaaaaaaaa", 20) <= 1 -query select sw1 = 1 where fuzzy_compare('hello', 'nope', 20) >= 0 and fuzzy_compare("hello","nope", 20) <= 1 -query select sw1 = 1 where fuzzy_compare('below', 'hello!', 20) >= 0 and fuzzy_compare("below","hello!", 20) <= 1 -query select sw1 = 1 where fuzzy_compare('kitten', 'smitten', 20) >= 0 and fuzzy_compare("kitten","smitten", 20) <= 1 -query select sw1 = 1 where fuzzy_compare('hello', 'bobbobbobbob', 20) >= 0 and fuzzy_compare("hello","bobbobbobbob", 20) <= 1 -query select sw1 = 1 where fuzzy_compare('hello', '', 20) >= 0 and fuzzy_compare("hello","", 20) <= 1 -query select sw1 = 1 where fuzzy_compare('', '', 20) >= 0 and fuzzy_compare("","", 20) <= 1 -query select sw1 = 1 where fuzzy_compare('blooooop', 'blob', 20) >= 0 and fuzzy_compare("blooooop","blob", 20) <= 1 -query select sw1 = 1 where fuzzy_compare('', '!', 20) >= 0 and fuzzy_compare("","!", 20) <= 1 -query select sw1 = 1 where fuzzy_compare('h', 'h', 20) >= 0 and fuzzy_compare("h","h", 20) <= 1 -query select sw1 = 1 where fuzzy_compare('hi', 'hi', 20) >= 0 and fuzzy_compare("hi","hi", 20) <= 1 diff --git a/centrallix/tests/test_lev_compare_00.cmp b/centrallix/tests/test_lev_compare_00.cmp new file mode 100644 index 000000000..1c295a360 --- /dev/null +++ b/centrallix/tests/test_lev_compare_00.cmp @@ -0,0 +1,23 @@ +Attribute [case1]: string "pass" +Attribute [case2]: string "pass" +Attribute [case3]: string "pass" +Attribute [case4]: string "pass" +Attribute [case5]: string "pass" +Attribute [case6]: string "pass" +Attribute [case7]: string "pass" +Attribute [case8]: string "pass" +Attribute [case9]: string "pass" +Attribute [case10]: string "pass" +Attribute [case11]: string "pass" +Attribute [case12]: string "pass" +Attribute [case13]: string "pass" +Attribute [case14]: string "pass" +Attribute [case15]: string "pass" +Attribute [case16]: string "pass" +Attribute [case17]: string "pass" +Attribute [case18]: string "pass" +Attribute [case19]: string "pass" +Attribute [case20]: string "pass" +Attribute [case21]: string "pass" +Attribute [case22]: string "pass" +Attribute [case23]: string "pass" diff --git a/centrallix/tests/test_lev_compare_00.to b/centrallix/tests/test_lev_compare_00.to new file mode 100644 index 000000000..5d9cec0f7 --- /dev/null +++ b/centrallix/tests/test_lev_compare_00.to @@ -0,0 +1,28 @@ +##NAME Levenshtein String Comparison + +# Legacy tests. +query select case1 = condition(lev_compare('hello', 'hello!') >= 0 and lev_compare('hello','hello!') <= 1, 'pass', 'fail') +query select case2 = condition(lev_compare('hello', 'asdfkh') >= 0 and lev_compare('hello','asdfkh') <= 1, 'pass', 'fail') +query select case3 = condition(lev_compare('hello', 'aaaaaaaaaaaaaaaaa') >= 0 and lev_compare('hello','aaaaaaaaaaaaaaaaa') <= 1, 'pass', 'fail') +query select case4 = condition(lev_compare('hello', 'nope') >= 0 and lev_compare('hello', 'nope') <= 1, 'pass', 'fail') +query select case5 = condition(lev_compare('below', 'hello!') >= 0 and lev_compare('below', 'hello!') <= 1, 'pass', 'fail') +query select case6 = condition(lev_compare('kitten', 'smitten') >= 0 and lev_compare('kitten', 'smitten') <= 1, 'pass', 'fail') +query select case7 = condition(lev_compare('hello', 'bobbobbobbob') >= 0 and lev_compare('hello', 'bobbobbobbob') <= 1, 'pass', 'fail') +query select case8 = condition(lev_compare('hello', '') >= 0 and lev_compare('hello', '') <= 1, 'pass', 'fail') +query select case9 = condition(lev_compare('', '') >= 0 and lev_compare('', '') <= 1, 'pass', 'fail') +query select case10 = condition(lev_compare('blooooop', 'blob') >= 0 and lev_compare('blooooop', 'blob') <= 1, 'pass', 'fail') +query select case11 = condition(lev_compare('', '!') >= 0 and lev_compare('','!') <= 1, 'pass', 'fail') +query select case12 = condition(lev_compare('h', 'h') >= 0 and lev_compare('h','h') <= 1, 'pass', 'fail') +query select case13 = condition(lev_compare('hi', 'hi') >= 0 and lev_compare('hi','hi') <= 1, 'pass', 'fail') + +# Kitten tests. +query select case14 = condition(lev_compare('kitten', 'kitten') >= 0.99 and lev_compare('kitten', 'kitten') <= 1.0, 'pass', 'fail') -- 0 edits +query select case15 = condition(lev_compare('kitten', 'skitten') >= 0.8 and lev_compare('kitten', 'skitten') <= 0.9, 'pass', 'fail') -- 1 insert +query select case16 = condition(lev_compare('kitten', 'itten') >= 0.8 and lev_compare('kitten', 'itten') <= 0.9, 'pass', 'fail') -- 1 delete +query select case17 = condition(lev_compare('kitten', 'mitten') >= 0.8 and lev_compare('kitten', 'mitten') <= 0.9, 'pass', 'fail') -- 1 replace +query select case18 = condition(lev_compare('kitten', 'smitten') >= 0.7 and lev_compare('kitten', 'smitten') <= 0.8, 'pass', 'fail') -- 1 insert and one replace +query select case19 = condition(lev_compare('kitten', 'iktten') >= 0.8 and lev_compare('kitten', 'iktten') <= 0.9, 'pass', 'fail') -- 1 transpose +query select case20 = condition(lev_compare('kitten', 'kittens') >= 0.8 and lev_compare('kitten', 'kittens') <= 0.9, 'pass', 'fail') -- 1 insert (end) +query select case21 = condition(lev_compare('kitten', 'kitte') >= 0.8 and lev_compare('kitten', 'kitte') <= 0.9, 'pass', 'fail') -- 1 delete (end) +query select case22 = condition(lev_compare('kitten', 'kittem') >= 0.8 and lev_compare('kitten', 'kittem') <= 0.9, 'pass', 'fail') -- 1 replace (end) +query select case23 = condition(lev_compare('kitten', 'kittne') >= 0.8 and lev_compare('kitten', 'kittne') <= 0.9, 'pass', 'fail') -- 1 transpose (end) diff --git a/centrallix/tests/test_levenshtein_00.cmp b/centrallix/tests/test_levenshtein_00.cmp index 0bc319c9d..b95f2d44a 100644 --- a/centrallix/tests/test_levenshtein_00.cmp +++ b/centrallix/tests/test_levenshtein_00.cmp @@ -1,6 +1,18 @@ -Attribute [sw1]: integer 1 -Attribute [sw1]: integer 1 -Attribute [sw1]: integer 2 -Attribute [sw1]: integer 2 -Attribute [sw1]: integer 1 -Attribute [sw1]: integer 1 +Attribute [case1]: integer 0 +Attribute [case2]: integer 1 +Attribute [case3]: integer 1 +Attribute [case4]: integer 1 +Attribute [case5]: integer 2 +Attribute [case6]: integer 1 +Attribute [case7]: integer 1 +Attribute [case8]: integer 1 +Attribute [case9]: integer 1 +Attribute [case10]: integer 1 +Attribute [case11]: integer 2 +Attribute [case12]: integer 1 +Attribute [case13]: integer 1 +Attribute [case14]: integer 2 +Attribute [case15]: integer 0 +Attribute [case16]: integer 0 +Attribute [case17]: integer 133 +Attribute [case18]: integer 254 diff --git a/centrallix/tests/test_levenshtein_00.to b/centrallix/tests/test_levenshtein_00.to index a666c3a4b..a92bdd743 100644 --- a/centrallix/tests/test_levenshtein_00.to +++ b/centrallix/tests/test_levenshtein_00.to @@ -1,8 +1,25 @@ -##NAME Levenshtein String Comparison +##NAME Levenshtein Basic Comparisons -query select sw1 = levenshtein('hello', 'hello!') -query select sw1 = levenshtein('kitten', 'mitten') -query select sw1 = levenshtein('kitten', 'smitten') -query select sw1 = levenshtein('lawn', 'flown') -query select sw1 = levenshtein('kitten', 'itten') -query select sw1 = levenshtein('kitten', 'skitten') +# Kitten tests. +query select case1 = levenshtein('kitten', 'kitten') -- 0 edits +query select case2 = levenshtein('kitten', 'skitten') -- 1 insert +query select case3 = levenshtein('kitten', 'itten') -- 1 delete +query select case4 = levenshtein('kitten', 'mitten') -- 1 replace +query select case5 = levenshtein('kitten', 'smitten') -- 1 insert and 1 replace +query select case6 = levenshtein('kitten', 'iktten') -- 1 transpose +query select case7 = levenshtein('kitten', 'kittens') -- 1 insert (end) +query select case8 = levenshtein('kitten', 'kitte') -- 1 delete (end) +query select case9 = levenshtein('kitten', 'kittem') -- 1 replace (end) +query select case10 = levenshtein('kitten', 'kittne') -- 1 transpose (end) + +# Alternate words. +query select case11 = levenshtein('lawn', 'flown') -- 1 insert and 1 replace +query select case12 = levenshtein('hello', 'hello!') -- 1 insert (end) +query select case13 = levenshtein('zert', 'zerf') -- 1 replace (end) +query select case14 = levenshtein('llearr', 'lear') -- 2 deletes (start & end) + +# Edge cases. +query select case15 = levenshtein('', '') -- 0 edits +query select case16 = levenshtein('This is a very long string!! I do not expect this function to need to process a string longer than this, because this string is a full 254 characters. That is pretty long. The object system limits strings to this size so we cannot make a longer string...', 'This is a very long string!! I do not expect this function to need to process a string longer than this, because this string is a full 254 characters. That is pretty long. The object system limits strings to this size so we cannot make a longer string...') -- 0 edits. +query select case17 = levenshtein('This is a very long string!! I do not expect this function to need to process a string longer than this, because this string is a full 254 characters. That is pretty long. The object system limits strings to this size so we cannot make a longer string...', 'This is quite a lengthy string. I do not expect the function to compute any longer string since this one is a full 254 characters. That is plenty, even if someone adds many contact details to their record!! Thus, this test should cover most cases we see.') -- 133 edits. +query select case18 = levenshtein('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', 'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB') -- 254 replaces. diff --git a/centrallix/tests/test_objdrv_cluster_00.cmp b/centrallix/tests/test_objdrv_cluster_00.cmp new file mode 100644 index 000000000..171c2a910 --- /dev/null +++ b/centrallix/tests/test_objdrv_cluster_00.cmp @@ -0,0 +1,157 @@ +Attribute [algorithm]: string "k-means" +Attribute [similarity_measure]: string "cosine" +Attribute [num_clusters]: integer 4 +Attribute [min_improvement]: double 0.0001 +Attribute [max_iterations]: integer 16 +Attribute [seed]: integer 1 +Attribute [date_created]: datetime '01 Jan 1970 00:00' +Attribute [date_computed]: datetime '01 Jan 1970 00:00' +Attribute [source]: string "cluster" +Attribute [threshold]: double 0.9 +Attribute [similarity_measure]: string "cosine" +Attribute [date_created]: datetime '01 Jan 1970 00:00' +Attribute [date_computed]: datetime '01 Jan 1970 00:00' +Attribute [items]: stringve "johnny" +Attribute [date_created]: datetime '01 Jan 1970 00:00' +Attribute [date_computed]: datetime '01 Jan 1970 00:00' +Attribute [items]: stringve "MatTheW" +Attribute [date_created]: datetime '01 Jan 1970 00:00' +Attribute [date_computed]: datetime '01 Jan 1970 00:00' +Attribute [items]: stringve "john","johns" +Attribute [date_created]: datetime '01 Jan 1970 00:00' +Attribute [date_computed]: datetime '01 Jan 1970 00:00' +Attribute [items]: stringve "mathew","mathews","math" +Attribute [date_created]: datetime '01 Jan 1970 00:00' +Attribute [date_computed]: datetime '01 Jan 1970 00:00' +Attribute [key1]: string "3: mathew" +Attribute [key2]: string "5: mathews" +Attribute [sim]: double 0.947714 +Attribute [date_created]: datetime '01 Jan 1970 00:00' +Attribute [date_computed]: datetime '01 Jan 1970 00:00' +Attribute [k1_items]: stringve "john","johnny","johns","mathew","MatTheW","mathews","math" +Attribute [k2_items]: stringve "john","johnny","johns" +Attribute [k2_items]: stringve "mathew","MatTheW","mathews","math" +Attribute [k3_items]: stringve "johnny" +Attribute [k3_items]: stringve "mathew","MatTheW","mathews","math" +Attribute [k3_items]: stringve "john","johns" +Attribute [k4_items]: stringve "johnny" +Attribute [k4_items]: stringve "MatTheW" +Attribute [k4_items]: stringve "john","johns" +Attribute [k4_items]: stringve "mathew","mathews","math" +Attribute [k1_items]: stringve "john","johnny","johns","mathew","MatTheW","mathews","math" +Attribute [k2_items]: stringve "john","johnny","johns" +Attribute [k2_items]: stringve "mathew","MatTheW","mathews","math" +Attribute [k3_items]: stringve "johnny" +Attribute [k3_items]: stringve "mathew","MatTheW","mathews","math" +Attribute [k3_items]: stringve "john","johns" +Attribute [k4_items]: stringve "johnny" +Attribute [k4_items]: stringve "MatTheW" +Attribute [k4_items]: stringve "john","johns" +Attribute [k4_items]: stringve "mathew","mathews","math" +Attribute [k1_key1]: string "0: john" +Attribute [k1_key2]: string "2: johns" +Attribute [k1_sim]: double 0.757051 +Attribute [k1_key1]: string "3: mathew" +Attribute [k1_key2]: string "4: MatTheW" +Attribute [k1_sim]: double 0.922627 +Attribute [k1_key1]: string "3: mathew" +Attribute [k1_key2]: string "5: mathews" +Attribute [k1_sim]: double 0.947714 +Attribute [k1_key1]: string "4: MatTheW" +Attribute [k1_key2]: string "5: mathews" +Attribute [k1_sim]: double 0.874386 +Attribute [k2_key1]: string "0: john" +Attribute [k2_key2]: string "2: johns" +Attribute [k2_sim]: double 0.757051 +Attribute [k2_key1]: string "3: mathew" +Attribute [k2_key2]: string "4: MatTheW" +Attribute [k2_sim]: double 0.922627 +Attribute [k2_key1]: string "3: mathew" +Attribute [k2_key2]: string "5: mathews" +Attribute [k2_sim]: double 0.947714 +Attribute [k2_key1]: string "4: MatTheW" +Attribute [k2_key2]: string "5: mathews" +Attribute [k2_sim]: double 0.874386 +Attribute [k3_key1]: string "3: mathew" +Attribute [k3_key2]: string "4: MatTheW" +Attribute [k3_sim]: double 0.922627 +Attribute [k3_key1]: string "3: mathew" +Attribute [k3_key2]: string "5: mathews" +Attribute [k3_sim]: double 0.947714 +Attribute [k3_key1]: string "4: MatTheW" +Attribute [k3_key2]: string "5: mathews" +Attribute [k3_sim]: double 0.874386 +Attribute [k3_key1]: string "0: john" +Attribute [k3_key2]: string "2: johns" +Attribute [k3_sim]: double 0.757051 +Attribute [k4_key1]: string "0: john" +Attribute [k4_key2]: string "2: johns" +Attribute [k4_sim]: double 0.757051 +Attribute [k4_key1]: string "3: mathew" +Attribute [k4_key2]: string "5: mathews" +Attribute [k4_sim]: double 0.947714 +Attribute [slide_key1]: string "0: john" +Attribute [slide_key2]: string "2: johns" +Attribute [slide_sim]: double 0.757051 +Attribute [slide_key1]: string "3: mathew" +Attribute [slide_key2]: string "4: MatTheW" +Attribute [slide_sim]: double 0.922627 +Attribute [slide_key1]: string "3: mathew" +Attribute [slide_key2]: string "5: mathews" +Attribute [slide_sim]: double 0.947714 +Attribute [slide_key1]: string "4: MatTheW" +Attribute [slide_key2]: string "5: mathews" +Attribute [slide_sim]: double 0.874386 +Attribute [k1_key1]: string "0: john" +Attribute [k1_key2]: string "2: johns" +Attribute [k1_sim]: double 0.8 +Attribute [k1_key1]: string "3: mathew" +Attribute [k1_key2]: string "5: mathews" +Attribute [k1_sim]: double 0.857143 +Attribute [k2_key1]: string "0: john" +Attribute [k2_key2]: string "2: johns" +Attribute [k2_sim]: double 0.8 +Attribute [k2_key1]: string "3: mathew" +Attribute [k2_key2]: string "5: mathews" +Attribute [k2_sim]: double 0.857143 +Attribute [k3_key1]: string "3: mathew" +Attribute [k3_key2]: string "5: mathews" +Attribute [k3_sim]: double 0.857143 +Attribute [k3_key1]: string "0: john" +Attribute [k3_key2]: string "2: johns" +Attribute [k3_sim]: double 0.8 +Attribute [k4_key1]: string "0: john" +Attribute [k4_key2]: string "2: johns" +Attribute [k4_sim]: double 0.8 +Attribute [k4_key1]: string "3: mathew" +Attribute [k4_key2]: string "5: mathews" +Attribute [k4_sim]: double 0.857143 +Attribute [slide_key1]: string "0: john" +Attribute [slide_key2]: string "2: johns" +Attribute [slide_sim]: double 0.8 +Attribute [slide_key1]: string "3: mathew" +Attribute [slide_key2]: string "5: mathews" +Attribute [slide_sim]: double 0.857143 +Attribute [items]: stringve "john","johnny","johns","mathew","MatTheW","mathews","math" +Attribute [date_created]: datetime '01 Jan 1970 00:00' +Attribute [date_computed]: datetime '01 Jan 1970 00:00' +Attribute [key1]: string "0: john" +Attribute [key2]: string "2: johns" +Attribute [sim]: double 0.757051 +Attribute [date_created]: datetime '01 Jan 1970 00:00' +Attribute [date_computed]: datetime '01 Jan 1970 00:00' +Attribute [key1]: string "3: mathew" +Attribute [key2]: string "4: MatTheW" +Attribute [sim]: double 0.922627 +Attribute [date_created]: datetime '01 Jan 1970 00:00' +Attribute [date_computed]: datetime '01 Jan 1970 00:00' +Attribute [key1]: string "3: mathew" +Attribute [key2]: string "5: mathews" +Attribute [sim]: double 0.947714 +Attribute [date_created]: datetime '01 Jan 1970 00:00' +Attribute [date_computed]: datetime '01 Jan 1970 00:00' +Attribute [key1]: string "4: MatTheW" +Attribute [key2]: string "5: mathews" +Attribute [sim]: double 0.874386 +Attribute [date_created]: datetime '01 Jan 1970 00:00' +Attribute [date_computed]: datetime '01 Jan 1970 00:00' diff --git a/centrallix/tests/test_objdrv_cluster_00.to b/centrallix/tests/test_objdrv_cluster_00.to new file mode 100644 index 000000000..71a3a5bb5 --- /dev/null +++ b/centrallix/tests/test_objdrv_cluster_00.to @@ -0,0 +1,37 @@ +##NAME Cluster Driver + +# Test parameters, initialization, and default attribute lists. +query select *, date_created=convert(datetime, "1970-01-01 00:00:00"), date_computed=convert(datetime, "1970-01-01 00:00:00") from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=4&threshold=0.9&sim=cosine ; +query select *, date_created=convert(datetime, "1970-01-01 00:00:00"), date_computed=convert(datetime, "1970-01-01 00:00:00") from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=4&threshold=0.9&sim=cosine/cluster ; +query select *, date_created=convert(datetime, "1970-01-01 00:00:00"), date_computed=convert(datetime, "1970-01-01 00:00:00") from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=4&threshold=0.9&sim=cosine/search ; + +# Test cluster. +query select k1_items=:items from /tests/test_objdrv_cluster_00.cluster?algorithm=none&k=1&threshold=0.75&sim=cosine/cluster ; +query select k2_items=:items from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=2&threshold=0.75&sim=cosine/cluster ; +query select k3_items=:items from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=3&threshold=0.75&sim=cosine/cluster ; +query select k4_items=:items from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=4&threshold=0.75&sim=cosine/cluster ; + +# Drop caches and retest clustering. +exec /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=3&threshold=0.75&sim=cosine/cluster "cache" "drop_all" +query select k1_items=:items from /tests/test_objdrv_cluster_00.cluster?algorithm=none&k=1&threshold=0.75&sim=cosine/cluster ; +query select k2_items=:items from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=2&threshold=0.75&sim=cosine/cluster ; +query select k3_items=:items from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=3&threshold=0.75&sim=cosine/cluster ; +query select k4_items=:items from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=4&threshold=0.75&sim=cosine/cluster ; + +# Test searching. +query select k1_key1 = :key1, k1_key2 = :key2, k1_sim = :sim from /tests/test_objdrv_cluster_00.cluster?algorithm=none&k=1&threshold=0.75&sim=cosine/search ; +query select k2_key1 = :key1, k2_key2 = :key2, k2_sim = :sim from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=2&threshold=0.75&sim=cosine/search ; +query select k3_key1 = :key1, k3_key2 = :key2, k3_sim = :sim from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=3&threshold=0.75&sim=cosine/search ; +query select k4_key1 = :key1, k4_key2 = :key2, k4_sim = :sim from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=4&threshold=0.75&sim=cosine/search ; +query select slide_key1 = :key1, slide_key2 = :key2, slide_sim = :sim from /tests/test_objdrv_cluster_00.cluster?algorithm=sliding-window&k=4&threshold=0.75&sim=cosine/search ; + +# Test searching with Levenshtein. +query select k1_key1 = :key1, k1_key2 = :key2, k1_sim = :sim from /tests/test_objdrv_cluster_00.cluster?algorithm=none&k=1&threshold=0.75&sim=levenshtein/search ; +query select k2_key1 = :key1, k2_key2 = :key2, k2_sim = :sim from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=2&threshold=0.75&sim=levenshtein/search ; +query select k3_key1 = :key1, k3_key2 = :key2, k3_sim = :sim from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=3&threshold=0.75&sim=levenshtein/search ; +query select k4_key1 = :key1, k4_key2 = :key2, k4_sim = :sim from /tests/test_objdrv_cluster_00.cluster?algorithm=k-means&k=4&threshold=0.75&sim=levenshtein/search ; +query select slide_key1 = :key1, slide_key2 = :key2, slide_sim = :sim from /tests/test_objdrv_cluster_00.cluster?algorithm=sliding-window&k=4&threshold=0.75&sim=levenshtein/search ; + +# Test the default attribute list for clusters +query select *, date_created=convert(datetime, "1970-01-01 00:00:00"), date_computed=convert(datetime, "1970-01-01 00:00:00") from /tests/test_objdrv_cluster_00.cluster?algorithm=none&k=1&threshold=0.75&sim=cosine/cluster ; +query select *, date_created=convert(datetime, "1970-01-01 00:00:00"), date_computed=convert(datetime, "1970-01-01 00:00:00") from /tests/test_objdrv_cluster_00.cluster?algorithm=none&k=1&threshold=0.75&sim=cosine/search ; diff --git a/centrallix/tests/test_similarity_00.cmp b/centrallix/tests/test_similarity_00.cmp deleted file mode 100644 index a0d292206..000000000 --- a/centrallix/tests/test_similarity_00.cmp +++ /dev/null @@ -1,5 +0,0 @@ -Attribute [sw1]: integer 1 -Attribute [sw1]: integer 1 -Attribute [sw1]: integer 1 -Attribute [sw1]: integer 1 -Attribute [sw1]: integer 1 diff --git a/centrallix/tests/test_similarity_00.to b/centrallix/tests/test_similarity_00.to deleted file mode 100644 index a0942ab76..000000000 --- a/centrallix/tests/test_similarity_00.to +++ /dev/null @@ -1,7 +0,0 @@ -##NAME Text Mining String Similarity - -query select sw1 = (cos_compare('hello', 'hello') >= 0.999) and (cos_compare('hello', 'hello') <= 1) -query select sw1 = (cos_compare('hello', 'nancy') <= 0.001) and (cos_compare('hello', 'nancy') >= 0) -query select sw1 = (cos_compare('hello', 'hello world') <= 0.891) and (cos_compare('hello', 'hello world') >= 0.890) -query select sw1 = (cos_compare('hello', 'hellow') >= 0.935) and (cos_compare('hello', 'hellow') <= 0.936) -query select sw1 = (cos_compare('hello', 'hellow', 1) >= 0.935) and (cos_compare('hello', 'hellow', 1) <= 0.936) diff --git a/centrallix/utility/double_metaphone.c b/centrallix/utility/double_metaphone.c new file mode 100644 index 000000000..b0ed45cbe --- /dev/null +++ b/centrallix/utility/double_metaphone.c @@ -0,0 +1,1291 @@ +/************************************************************************/ +/* Text-DoubleMetaphone */ +/* Centrallix Base Library */ +/* */ +/* Copyright 2000, Maurice Aubrey . */ +/* All rights reserved. */ +/* */ +/* This code is copied for redistribution with modification, from the */ +/* gitpan/Text-DoubleMetaphone implementation on GitHub (1), which is */ +/* under the following license. */ +/* */ +/* This code is based heavily on the C++ implementation by Lawrence */ +/* Philips and incorporates several bug fixes courtesy of Kevin */ +/* Atkinson . */ +/* */ +/* This module is free software; you may redistribute it and/or */ +/* modify it under the same terms as Perl itself. */ +/* */ +/* A summary of the relevant content from https://dev.perl.org/licenses */ +/* has been included below for the convenience of the reader. This */ +/* information was collected and saved on September 5th, 2025 and may */ +/* differ from current information. For the most up to date copy of */ +/* this information, please use the link provided above. */ +/* */ +/* Perl5 is Copyright © 1993 and later, by Larry Wall and others. */ +/* */ +/* It is free software; you can redistribute it and/or modify it */ +/* under the terms of either: */ +/* */ +/* a) the GNU General Public License (2) as published by the Free */ +/* Software Foundation (3); either version 1 (2), or (at your */ +/* option) any later version (4), or */ +/* */ +/* b) the "Artistic License" (5). */ +/* */ +/* Citations: */ +/* 1: https://github.com/gitpan/Text-meta_double_metaphone */ +/* 2: https://dev.perl.org/licenses/gpl1.html */ +/* 3: http://www.fsf.org */ +/* 4: http://www.fsf.org/licenses/licenses.html#GNUGPL */ +/* 5: https://dev.perl.org/licenses/artistic.html */ +/* */ +/* Centrallix is published under the GNU General Public License, */ +/* satisfying the above requirement. A summary of this is included */ +/* below for the convenience of the reader. */ +/* */ +/* This program is free software; you can redistribute it and/or modify */ +/* it under the terms of the GNU General Public License as published by */ +/* the Free Software Foundation; either version 2 of the License, or */ +/* (at your option) any later version. */ +/* */ +/* This program is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU General Public License for more details. */ +/* */ +/* You should have received a copy of the GNU General Public License */ +/* along with this program; if not, write to the Free Software */ +/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA */ +/* 02111-1307 USA */ +/* */ +/* A copy of the GNU General Public License has been included in this */ +/* distribution in the file "COPYING". */ +/* */ +/* Module: double_metaphone.c, double_metaphone.h */ +/* Author: Maurice Aubrey and Israel Fuller */ +/* Description: This module implements a "sounds like" algorithm by */ +/* Lawrence Philips which he published in the June, 2000 */ +/* issue of C/C++ Users Journal. Double Metaphone is an */ +/* improved version of the original Metaphone algorithm */ +/* written by Philips'. This implementation was written by */ +/* Maurice Aubrey for C/C++ with bug fixes provided by */ +/* Kevin Atkinson. It was revised by Israel Fuller to */ +/* better align with the Centrallix coding style and */ +/* standards so that it could be included here. */ +/************************************************************************/ + +/*** Note to future programmers reading this file (by Israel Fuller): + *** + *** This file was copied from a GitHub Repo with proper licensing (in case + *** you didn't read the legal stuff above), so feel free to check it out. + *** + *** As for this code, I've modified it to use styling and memory allocation + *** consistent with the rest of the Centrallix codebase. Also, I have added + *** documentation comments and extensive test cases (at the end of the file), + *** however, these reflect my own (possibly incorrect) understanding, which + *** might not line up with the original author. + *** + *** To be honest, though, trying to make this code as readable as possible + *** was very challenging due to all the messy boolean algebra. If there is + *** ever a professional linguist reading this, please factor out some of the + *** logic into local variables with descriptive names so that the rest of us + *** can read this code without our eyes glazing over. + *** + *** If you have any questions, please feel free to reach out to me or Greg. + *** + *** Original Source: https://github.com/gitpan/Text-DoubleMetaphone + ***/ + +#include +#include +#include +#include +#include +#include + +#include "cxlib/newmalloc.h" +#include "cxlib/strtcpy.h" +#include "cxlib/util.h" +#include "cxlib/expect.h" + +typedef struct + { + char* str; + size_t length; + size_t bufsize; + int free_str_on_destroy; + } + MetaString, *pMetaString; + +/*** Allocates a new MetaString. + *** + *** @param init_str The initial size of the string. + *** @returns The new MetaString, or NULL if an error occurs. + ***/ +pMetaString +meta_new_string(const char* init_str) + { + pMetaString s; + char empty_string[] = ""; + + s = (pMetaString)check_ptr(nmSysMalloc(sizeof(MetaString))); + if (UNLIKELY(s == NULL)) goto err_free; + + if (init_str == NULL) + init_str = empty_string; + + s->length = strlen(init_str); + /** Preallocate a bit more for potential growth. **/ + s->bufsize = s->length + 7u; + + s->str = (char*)check_ptr(nmSysMalloc(s->bufsize * sizeof(char))); + if (UNLIKELY(s->str == NULL)) goto err_free; + + strtcpy(s->str, init_str, s->bufsize); + s->free_str_on_destroy = 1; + + return s; + + err_free: + if (s != NULL) + { + if (s->str != NULL) nmSysFree(s->str); + nmSysFree(s); + } + + return NULL; + } + +/*** Frees a MetaString. + *** + *** @param s The MetaString. + ***/ +void +meta_destroy_string(pMetaString s) + { + if (UNLIKELY(s == NULL)) + return; + + if (s->free_str_on_destroy && s->str != NULL) + nmSysFree(s->str); + + nmSysFree(s); + + return; + } + +/*** Increases a MetaString's buffer size. + *** + *** @param s The pMetaString being modified. + *** @param chars_needed Minimum number of characters to increase buffer size. + *** @returns 0 if successful, or -1 if an error occurs. + ***/ +int +meta_increase_buffer(pMetaString s, const size_t chars_needed) + { + s->bufsize += chars_needed + 8u; + s->str = check_ptr(nmSysRealloc(s->str, s->bufsize * sizeof(char))); + if (UNLIKELY(s->str == NULL)) return -1; + + return 0; + } + +/*** Convert all characters of a MetaString to uppercase. + *** + *** @param s The MetaString being modified. + ***/ +void +meta_make_upper(pMetaString s) + { + for (char* i = s->str; i[0] != '\0'; i++) + *i = (char)toupper(*i); + + return; + } + +/*** @param s The MetaString being checked. + *** @param pos The character location to check within the MetaString. + *** @returns 1 if the location is out of bounds for the MetaString, + *** 0 otherwise. + ***/ +bool +meta_is_out_of_bounds(pMetaString s, unsigned int pos) + { + return (s->length <= pos); + } + +/*** Checks if a character in a MetaString is a vowel. + *** + *** @param s The MetaString being checked. + *** @param pos The character location to check within the MetaString. + ***/ +bool +meta_is_vowel(pMetaString s, unsigned int pos) + { + if (UNLIKELY(meta_is_out_of_bounds(s, pos))) return 0; + + const char c = *(s->str + pos); + + return ((c == 'A') || (c == 'E') || (c == 'I') || + (c == 'O') || (c == 'U') || (c == 'Y')); + } + +/*** Search a MetaString for "W", "K", "CZ", or "WITZ", which indicate that the + *** string is Slavo Germanic. + *** + *** @param s The MetaString to be searched. + *** @returns 1 if the MetaString is Slavo Germanic, or 0 otherwise. + ***/ +bool +meta_is_slavo_germanic(pMetaString s) + { + return (strstr(s->str, "W") != NULL) + || (strstr(s->str, "K") != NULL) + || (strstr(s->str, "CZ") != NULL) + || (strstr(s->str, "WITZ") != NULL); + } + +/*** @param s The MetaString being checked. + *** @param pos The character location to check within the MetaString. + *** @returns The character at the position in the MetaString, or + *** '\0' if the position is not in the MetaString. + ***/ +char +meta_get_char_at(pMetaString s, unsigned int pos) + { + return (UNLIKELY(meta_is_out_of_bounds(s, pos))) ? '\0' : ((char) *(s->str + pos)); + } + +/*** Checks for to see if any of a list of strings appear in a the given + *** MetaString after the given start position. + *** + *** @attention - Note that the START value is 0 based. + *** + *** @param s The MetaString being modified. + *** @param start The zero-based start of at which to begin searching + *** within the MetaString. + *** @param length The length of the character strings being checked. + *** @returns 1 if any of the character sequences appear after the start + *** in the MetaString and 0 otherwise. + ***/ +bool +meta_is_str_at(pMetaString s, unsigned int start, ...) + { + va_list ap; + bool found = false; + + /** Should never happen. **/ + if (UNLIKELY(meta_is_out_of_bounds(s, start))) + return false; + + const char* pos = (s->str + start); + va_start(ap, start); + + char* test; + do + { + test = va_arg(ap, char*); + if (test[0] != '\0' && (strncmp(pos, test, strlen(test)) == 0)) + { + found = true; + break; + } + } + while (test[0] != '\0'); + + va_end(ap); + + return found; + } + +/*** Adds a string to a MetaString, expanding the MetaString if needed. + *** + *** @param s The MetaString being modified. + *** @param new_str The string being added. + *** @returns 0 if successful, or -1 if an error occurs. + ***/ +int +meta_add_str(pMetaString s, const char* new_str) + { + if (UNLIKELY(new_str == NULL)) + return -1; + + /** Increase the buffer to the required size. **/ + const size_t add_length = strlen(new_str); + const size_t new_length = s->length + add_length + 1; + if (UNLIKELY(new_length > s->bufsize) && check(meta_increase_buffer(s, add_length)) != 0) + return -1; + + /** Write the data to the buffer. **/ + strtcat(s->str, new_str, s->bufsize); + s->length += add_length; + + return 0; + } + +/*** Computes double metaphone. + *** + *** Example Usage: + *** ```c + *** char* primary_code; + *** char* secondary_code; + *** meta_double_metaphone(input, &primary_code, &secondary_code); + *** ``` + *** + *** @param str The string to compute. + *** @param primary_code A pointer to a buffer where the pointer to a string + *** containing the produced primary code will be stored. + *** @param secondary_code A pointer to a buffer where the pointer to a string + *** containing the produced secondary code will be stored. + *** @returns 0 if successful, or -1 if an error occurs. + ***/ +int +meta_double_metaphone(const char* str, char** primary_code, char** secondary_code) + { + int ret = -1; + + /** Edge cases. **/ + if (UNLIKELY(str == NULL)) + { + fprintf(stderr, "Error: Missing input string.\n"); + goto end_free; + } + const size_t length = strlen(str); + if (UNLIKELY(length == 0lu)) + { + fprintf(stderr, "Error: Empty input string.\n"); + goto end_free; + } + if (UNLIKELY(primary_code == NULL)) + { + fprintf(stderr, "Error: Missing a pointer to store primary code.\n"); + goto end_free; + } + if (UNLIKELY(secondary_code == NULL)) + { + fprintf(stderr, "Error: Missing a pointer to store secondary code.\n"); + goto end_free; + } + + /** Declare iteration variables. **/ + unsigned int current = 0; + const unsigned int last = (unsigned int)(length - 1); + + /** Pad original so we can index beyond end. **/ + pMetaString original = check_ptr(meta_new_string(str)); + if (UNLIKELY(original == NULL)) goto end_free; + meta_make_upper(original); + if (check(meta_add_str(original, " ")) != 0) goto end_free; + + /** Allocate the primary and secondary output strings. **/ + pMetaString primary = check_ptr(meta_new_string("")); + pMetaString secondary = check_ptr(meta_new_string("")); + if (UNLIKELY(primary == NULL || secondary == NULL)) goto end_free; + + /** Skip these if they are at start of a word. **/ + if (meta_is_str_at(original, 0, "GN", "KN", "PN", "WR", "PS", "")) + current += 1; + + /** Initial 'X' is pronounced 'Z' e.g. 'Xavier' **/ + const char first_char = meta_get_char_at(original, 0); + if (first_char == 'X') + { + if (check(meta_add_str(primary, "S")) != 0) goto end_free; /* 'Z' maps to 'S' */ + if (check(meta_add_str(secondary, "S")) != 0) goto end_free; + current += 1; + } + + /** Precomputing this is useful. **/ + const bool is_slavo_germanic = meta_is_slavo_germanic(original); + + /** Main loop. **/ + while (current < length) + { + const char cur_char = meta_get_char_at(original, current); + const char next_char = meta_get_char_at(original, current + 1); + switch (cur_char) + { + case 'A': + case 'E': + case 'I': + case 'O': + case 'U': + case 'Y': + { + if (current == 0) + { + /** All init vowels now map to 'A'. **/ + if (check(meta_add_str(primary, "A") != 0)) goto end_free; + if (check(meta_add_str(secondary, "A") != 0)) goto end_free; + } + current += 1; + break; + } + + case 'B': + { + /** "-mb", e.g", "dumb", already skipped over... **/ + if (check(meta_add_str(primary, "P") != 0)) goto end_free; + if (check(meta_add_str(secondary, "P") != 0)) goto end_free; + + current += (next_char == 'B') ? 2 : 1; + break; + } + + case 'C': + { + /** Various germanic. **/ + if ( + (current > 1) + && !meta_is_vowel(original, current - 2) + && meta_is_str_at(original, (current - 1), "ACH", "") + && meta_get_char_at(original, current + 2) != 'I' + && ( + meta_get_char_at(original, current + 2) != 'E' + || meta_is_str_at(original, (current - 2), "BACHER", "MACHER", "") + ) + ) + { + if (check(meta_add_str(primary, "K") != 0)) goto end_free; + if (check(meta_add_str(secondary, "K") != 0)) goto end_free; + current += 2; + break; + } + + /** Special case 'caesar' **/ + if (current == 0 && meta_is_str_at(original, current, "CAESAR", "")) + { + if (check(meta_add_str(primary, "S") != 0)) goto end_free; + if (check(meta_add_str(secondary, "S") != 0)) goto end_free; + current += 2; + break; + } + + /** Italian 'chianti' **/ + if (meta_is_str_at(original, current, "CHIA", "")) + { + if (check(meta_add_str(primary, "K") != 0)) goto end_free; + if (check(meta_add_str(secondary, "K") != 0)) goto end_free; + current += 2; + break; + } + + if (meta_is_str_at(original, current, "CH", "")) + { + /** Find 'michael' **/ + if (current > 0 && meta_is_str_at(original, current, "CHAE", "")) + { + if (check(meta_add_str(primary, "K") != 0)) goto end_free; + if (check(meta_add_str(secondary, "X") != 0)) goto end_free; + current += 2; + break; + } + + /** Greek roots e.g. 'chemistry', 'chorus' **/ + if ( + current == 0 + && meta_is_str_at(original, (current + 1), "HOR", "HYM", "HIA", "HEM", "HARAC", "HARIS", "") + && !meta_is_str_at(original, 0, "CHORE", "") + ) + { + if (check(meta_add_str(primary, "K") != 0)) goto end_free; + if (check(meta_add_str(secondary, "K") != 0)) goto end_free; + current += 2; + break; + } + + /** Germanic, greek, or otherwise 'ch' for 'kh' sound. */ + if ( + meta_is_str_at(original, 0, "SCH", "VAN ", "VON ", "") + /** 'architect but not 'arch', 'orchestra', 'orchid' **/ + || meta_is_str_at(original, (current - 2), "ORCHES", "ARCHIT", "ORCHID", "") + || meta_is_str_at(original, (current + 2), "T", "S", "") + || ( + (current == 0 || meta_is_str_at(original, (current - 1), "A", "O", "U", "E", "")) + /** e.g., 'wachtler', 'wechsler', but not 'tichner' **/ + && meta_is_str_at(original, (current + 2), "L", "R", "N", "M", "B", "H", "F", "V", "W", " ", "") + ) + ) + { + if (check(meta_add_str(primary, "K") != 0)) goto end_free; + if (check(meta_add_str(secondary, "K") != 0)) goto end_free; + } + else + { + if (current > 0) + { + if (meta_is_str_at(original, 0, "MC", "")) + { + /* e.g., "McHugh" */ + if (check(meta_add_str(primary, "K") != 0)) goto end_free; + if (check(meta_add_str(secondary, "K") != 0)) goto end_free; + } + else + { + if (check(meta_add_str(primary, "X") != 0)) goto end_free; + if (check(meta_add_str(secondary, "K") != 0)) goto end_free; + } + } + else + { + if (check(meta_add_str(primary, "X") != 0)) goto end_free; + if (check(meta_add_str(secondary, "X") != 0)) goto end_free; + } + } + current += 2; + break; + } + + /** e.g, 'czerny' **/ + if (meta_is_str_at(original, current, "CZ", "") + && !meta_is_str_at(original, (current - 2), "WICZ", "")) + { + if (check(meta_add_str(primary, "S") != 0)) goto end_free; + if (check(meta_add_str(secondary, "X") != 0)) goto end_free; + current += 2; + break; + } + + /** e.g., 'focaccia' **/ + if (meta_is_str_at(original, (current + 1), "CIA", "")) + { + if (check(meta_add_str(primary, "X") != 0)) goto end_free; + if (check(meta_add_str(secondary, "X") != 0)) goto end_free; + current += 3; + break; + } + + /** Double 'C' rule. **/ + if ( + meta_is_str_at(original, current, "CC", "") + && !(current == 1 && first_char == 'M') /* McClellan exception. */ + ) + { + /** 'bellocchio' but not 'bacchus' **/ + if ( + meta_is_str_at(original, (current + 2), "I", "E", "H", "") + && !meta_is_str_at(original, (current + 2), "HU", "") + ) + { + /** 'accident', 'accede' 'succeed' **/ + if ( + (current == 1 && meta_get_char_at(original, current - 1) == 'A') + || meta_is_str_at(original, (current - 1), "UCCEE", "UCCES", "") + ) + { + if (check(meta_add_str(primary, "KS")) != 0) goto end_free; + if (check(meta_add_str(secondary, "KS")) != 0) goto end_free; + /** 'bacci', 'bertucci', other italian **/ + } + else + { + if (check(meta_add_str(primary, "X")) != 0) goto end_free; + if (check(meta_add_str(secondary, "X")) != 0) goto end_free; + } + current += 3; + break; + } + else + { /** Pierce's rule **/ + if (check(meta_add_str(primary, "K")) != 0) goto end_free; + if (check(meta_add_str(secondary, "K")) != 0) goto end_free; + current += 2; + break; + } + } + + if (meta_is_str_at(original, current, "CK", "CG", "CQ", "")) + { + if (check(meta_add_str(primary, "K")) != 0) goto end_free; + if (check(meta_add_str(secondary, "K")) != 0) goto end_free; + current += 2; + break; + } + + if (meta_is_str_at(original, current, "CI", "CE", "CY", "")) + { + /* Italian vs. English */ + if (meta_is_str_at(original, current, "CIO", "CIE", "CIA", "")) + { + if (check(meta_add_str(primary, "S")) != 0) goto end_free; + if (check(meta_add_str(secondary, "X")) != 0) goto end_free; + } + else + { + if (check(meta_add_str(primary, "S")) != 0) goto end_free; + if (check(meta_add_str(secondary, "S")) != 0) goto end_free; + } + current += 2; + break; + } + + /** else **/ + if (check(meta_add_str(primary, "K")) != 0) goto end_free; + if (check(meta_add_str(secondary, "K")) != 0) goto end_free; + + /** Name sent in 'mac caffrey', 'mac gregor **/ + if (meta_is_str_at(original, (current + 1), " C", " Q", " G", "")) + current += 3; + else if (meta_is_str_at(original, (current + 1), "C", "K", "Q", "") + && !meta_is_str_at(original, (current + 1), "CE", "CI", "")) + current += 2; + else + current += 1; + break; + } + + case 'D': + { + if (meta_is_str_at(original, current, "DG", "")) + { + if (meta_is_str_at(original, (current + 2), "I", "E", "Y", "")) + { + /** e.g. 'edge' **/ + if (check(meta_add_str(primary, "J")) != 0) goto end_free; + if (check(meta_add_str(secondary, "J")) != 0) goto end_free; + current += 3; + break; + } + else + { + /** e.g. 'edgar' **/ + if (check(meta_add_str(primary, "TK")) != 0) goto end_free; + if (check(meta_add_str(secondary, "TK")) != 0) goto end_free; + current += 2; + break; + } + } + + if (meta_is_str_at(original, current, "DT", "DD", "")) + { + if (check(meta_add_str(primary, "T")) != 0) goto end_free; + if (check(meta_add_str(secondary, "T")) != 0) goto end_free; + current += 2; + break; + } + + /** else **/ + if (check(meta_add_str(primary, "T")) != 0) goto end_free; + if (check(meta_add_str(secondary, "T")) != 0) goto end_free; + current += 1; + break; + } + + case 'F': + { + current += (next_char == 'F') ? 2 : 1; + if (check(meta_add_str(primary, "F")) != 0) goto end_free; + if (check(meta_add_str(secondary, "F")) != 0) goto end_free; + break; + } + + case 'G': + { + if (next_char == 'H') + { + /** 'Vghee' */ + if (current > 0 && !meta_is_vowel(original, (current - 1))) + { + if (check(meta_add_str(primary, "K")) != 0) goto end_free; + if (check(meta_add_str(secondary, "K")) != 0) goto end_free; + current += 2; + break; + } + + if (current < 3) + { + /** 'ghislane', 'ghiradelli' **/ + if (current == 0) + { + if (meta_get_char_at(original, (current + 2)) == 'I') + { + if (check(meta_add_str(primary, "J")) != 0) goto end_free; + if (check(meta_add_str(secondary, "J")) != 0) goto end_free; + } + else + { + if (check(meta_add_str(primary, "K")) != 0) goto end_free; + if (check(meta_add_str(secondary, "K")) != 0) goto end_free; + } + current += 2; + break; + } + } + + if ( + /** Parker's rule (with some further refinements) - e.g., 'hugh' **/ + (current > 1 && meta_is_str_at(original, (current - 2), "B", "H", "D", "")) + /** e.g., 'bough' **/ + || (current > 2 && meta_is_str_at(original, (current - 3), "B", "H", "D", "")) + /** e.g., 'broughton' **/ + || (current > 3 && meta_is_str_at(original, (current - 4), "B", "H", "")) + ) + { + current += 2; + break; + } + else + { + /** e.g., 'laugh', 'McLaughlin', 'cough', 'gough', 'rough', 'tough' **/ + if ( + current > 2 + && meta_get_char_at(original, (current - 1)) == 'U' + && meta_is_str_at(original, (current - 3), "C", "G", "L", "R", "T", "") + ) + { + if (check(meta_add_str(primary, "F")) != 0) goto end_free; + if (check(meta_add_str(secondary, "F")) != 0) goto end_free; + } + else if (current > 0 && meta_get_char_at(original, (current - 1)) != 'I') + { + if (check(meta_add_str(primary, "K")) != 0) goto end_free; + if (check(meta_add_str(secondary, "K")) != 0) goto end_free; + } + + current += 2; + break; + } + } + + if (next_char == 'N') + { + if (current == 1 && !is_slavo_germanic && meta_is_vowel(original, 0)) + { + if (check(meta_add_str(primary, "KN")) != 0) goto end_free; + if (check(meta_add_str(secondary, "N")) != 0) goto end_free; + } + else + /** not e.g. 'cagney' **/ + if ( + next_char != 'Y' + && !is_slavo_germanic + && !meta_is_str_at(original, (current + 2), "EY", "") + ) + { + if (check(meta_add_str(primary, "N")) != 0) goto end_free; + if (check(meta_add_str(secondary, "KN")) != 0) goto end_free; + } + else + { + if (check(meta_add_str(primary, "KN")) != 0) goto end_free; + if (check(meta_add_str(secondary, "KN")) != 0) goto end_free; + } + current += 2; + break; + } + + /** 'tagliaro' **/ + if ( + !is_slavo_germanic + && meta_is_str_at(original, (current + 1), "LI", "") + ) + { + if (check(meta_add_str(primary, "KL")) != 0) goto end_free; + if (check(meta_add_str(secondary, "L")) != 0) goto end_free; + current += 2; + break; + } + + /** -ges-,-gep-,-gel-, -gie- at beginning **/ + if ( + current == 0 + && ( + next_char == 'Y' + || meta_is_str_at( + original, (current + 1), + "ES", "EP", "EB", "EL", "EY", "IB", + "IL", "IN", "IE", "EI", "ER", "" + ) + ) + ) + { + if (check(meta_add_str(primary, "K")) != 0) goto end_free; + if (check(meta_add_str(secondary, "J")) != 0) goto end_free; + current += 2; + break; + } + + /** -ger-, -gy- **/ + if ( + (next_char == 'Y' || meta_is_str_at(original, (current + 1), "ER", "")) + /** Exceptions. **/ + && !meta_is_str_at(original, 0, "DANGER", "RANGER", "MANGER", "") + && !meta_is_str_at(original, (current - 1), "E", "I", "RGY", "OGY", "") + ) + { + if (check(meta_add_str(primary, "K")) != 0) goto end_free; + if (check(meta_add_str(secondary, "J")) != 0) goto end_free; + current += 2; + break; + } + + /** Italian e.g, 'biaggi' **/ + if ( + meta_is_str_at(original, (current + 1), "E", "I", "Y", "") + || meta_is_str_at(original, (current - 1), "AGGI", "OGGI", "") + ) + { + /** Obvious germanic. **/ + if (meta_is_str_at(original, 0, "SCH", "VAN ", "VON ", "") + || meta_is_str_at(original, (current + 1), "ET", "")) + { + if (check(meta_add_str(primary, "K")) != 0) goto end_free; + if (check(meta_add_str(secondary, "K")) != 0) goto end_free; + } + else + { + /** Always soft, if french ending. **/ + if (meta_is_str_at(original, (current + 1), "IER ", "")) + { + if (check(meta_add_str(primary, "J")) != 0) goto end_free; + if (check(meta_add_str(secondary, "J")) != 0) goto end_free; + } + else + { + if (check(meta_add_str(primary, "J")) != 0) goto end_free; + if (check(meta_add_str(secondary, "K")) != 0) goto end_free; + } + } + current += 2; + break; + } + + current += (next_char == 'G') ? 2 : 1; + if (check(meta_add_str(primary, "K")) != 0) goto end_free; + if (check(meta_add_str(secondary, "K")) != 0) goto end_free; + break; + } + + case 'H': + { + /** Only keep if first & before vowel or between 2 vowels. **/ + if ( + (current == 0 || meta_is_vowel(original, (current - 1))) + && meta_is_vowel(original, current + 1) + ) + { + if (check(meta_add_str(primary, "H")) != 0) goto end_free; + if (check(meta_add_str(secondary, "H")) != 0) goto end_free; + current += 2; + } + else /* also takes care of 'HH' */ + current += 1; + break; + } + + case 'J': + { + /** Obvious spanish, 'jose', 'san jacinto' **/ + const bool has_jose_next = meta_is_str_at(original, current, "JOSE", ""); + const bool starts_with_san = meta_is_str_at(original, 0, "SAN ", ""); + if (has_jose_next || starts_with_san) + { + if ( + starts_with_san + /** I don't know what this condition means. **/ + || (current == 0 && meta_get_char_at(original, current + 4) == ' ') + ) + { + if (check(meta_add_str(primary, "H")) != 0) goto end_free; + if (check(meta_add_str(secondary, "H")) != 0) goto end_free; + } + else + { + if (check(meta_add_str(primary, "J")) != 0) goto end_free; + if (check(meta_add_str(secondary, "H")) != 0) goto end_free; + } + current += 1; + break; + } + + if (current == 0 && !has_jose_next) + { + if (check(meta_add_str(primary, "J")) != 0) goto end_free; /* Yankelovich/Jankelowicz */ + if (check(meta_add_str(secondary, "A")) != 0) goto end_free; + } + else + { + /** spanish pron. of e.g. 'bajador' **/ + if ( + !is_slavo_germanic + && (next_char == 'A' || next_char == 'O') + && meta_is_vowel(original, (current - 1)) + ) + { + if (check(meta_add_str(primary, "J")) != 0) goto end_free; + if (check(meta_add_str(secondary, "H")) != 0) goto end_free; + } + else + { + if (current == last) + { + if (check(meta_add_str(primary, "J")) != 0) goto end_free; + if (check(meta_add_str(secondary, "")) != 0) goto end_free; + } + else + { + if ( + !meta_is_str_at(original, (current + 1), "L", "T", "K", "S", "N", "M", "B", "Z", "") + && !meta_is_str_at(original, (current - 1), "S", "K", "L", "") + ) + { + if (check(meta_add_str(primary, "J")) != 0) goto end_free; + if (check(meta_add_str(secondary, "J")) != 0) goto end_free; + } + } + } + } + + current += (next_char == 'J') ? 2 : 1; + break; + } + + case 'K': + { + current += (next_char == 'K') ? 2 : 1; + if (check(meta_add_str(primary, "K")) != 0) goto end_free; + if (check(meta_add_str(secondary, "K")) != 0) goto end_free; + break; + } + + case 'L': + { + if (next_char == 'L') + { + /** Spanish e.g. 'cabrillo', 'gallegos' **/ + if ( + ( + current == length - 3 + && meta_is_str_at(original, (current - 1), "ILLO", "ILLA", "ALLE", "") + ) + || ( + meta_is_str_at(original, (current - 1), "ALLE", "") + && ( + meta_is_str_at(original, (last - 1), "AS", "OS", "") + || meta_is_str_at(original, last, "A", "O", "") + ) + ) + ) + { + if (check(meta_add_str(primary, "L")) != 0) goto end_free; + if (check(meta_add_str(secondary, "")) != 0) goto end_free; + current += 2; + break; + } + current += 2; + } + else + current += 1; + if (check(meta_add_str(primary, "L")) != 0) goto end_free; + if (check(meta_add_str(secondary, "L")) != 0) goto end_free; + break; + } + + case 'M': + { + current += ( + ( + meta_is_str_at(original, (current - 1), "UMB", "") + && (current + 1 == last || meta_is_str_at(original, (current + 2), "ER", "")) + ) + /** 'dumb','thumb' **/ + || next_char == 'M' + ) ? 2 : 1; + if (check(meta_add_str(primary, "M")) != 0) goto end_free; + if (check(meta_add_str(secondary, "M")) != 0) goto end_free; + break; + } + + case 'N': + { + current += (next_char == 'N') ? 2 : 1; + if (check(meta_add_str(primary, "N")) != 0) goto end_free; + if (check(meta_add_str(secondary, "N")) != 0) goto end_free; + break; + } + + case 'P': + { + if (next_char == 'H') + { + if (check(meta_add_str(primary, "F")) != 0) goto end_free; + if (check(meta_add_str(secondary, "F")) != 0) goto end_free; + current += 2; + break; + } + + /** Also account for "campbell", "raspberry" **/ + current += (meta_is_str_at(original, (current + 1), "P", "B", "")) ? 2 : 1; + if (check(meta_add_str(primary, "P")) != 0) goto end_free; + if (check(meta_add_str(secondary, "P")) != 0) goto end_free; + break; + } + + case 'Q': + { + current += (next_char == 'Q') ? 2 : 1; + if (check(meta_add_str(primary, "K")) != 0) goto end_free; + if (check(meta_add_str(secondary, "K")) != 0) goto end_free; + break; + } + + case 'R': + { + /** French e.g. 'rogier', but exclude 'hochmeier' **/ + const bool no_primary = ( + !is_slavo_germanic + && current == last + && meta_is_str_at(original, (current - 2), "IE", "") + && !meta_is_str_at(original, (current - 4), "ME", "MA", "") + ); + + if (check(meta_add_str(primary, (no_primary) ? "" : "R")) != 0) goto end_free; + if (check(meta_add_str(secondary, "R")) != 0) goto end_free; + current += (next_char == 'R') ? 2 : 1; + break; + } + + case 'S': + { + /** Special cases 'island', 'isle', 'carlisle', 'carlysle' **/ + if (meta_is_str_at(original, (current - 1), "ISL", "YSL", "")) + { + current += 1; + break; + } + + /** Special case 'sugar-' **/ + if (current == 0 && meta_is_str_at(original, current, "SUGAR", "")) + { + if (check(meta_add_str(primary, "X")) != 0) goto end_free; + if (check(meta_add_str(secondary, "S")) != 0) goto end_free; + current += 1; + break; + } + + if (meta_is_str_at(original, current, "SH", "")) + { + const bool germanic = meta_is_str_at(original, (current + 1), "HEIM", "HOEK", "HOLM", "HOLZ", ""); + const char* sound = (germanic) ? "S" : "X"; + if (check(meta_add_str(primary, sound)) != 0) goto end_free; + if (check(meta_add_str(secondary, sound)) != 0) goto end_free; + current += 2; + break; + } + + /** Italian & Armenian. **/ + if (meta_is_str_at(original, current, "SIO", "SIA", "SIAN", "")) + { + if (check(meta_add_str(primary, "S")) != 0) goto end_free; + if (check(meta_add_str(secondary, (is_slavo_germanic) ? "S" : "X")) != 0) goto end_free; + current += 3; + break; + } + + /** german & anglicisations, e.g. 'smith' match 'schmidt', 'snider' match 'schneider' **/ + /** also, -sz- in slavic language although in hungarian it is pronounced 's' **/ + if (current == 0 && meta_is_str_at(original, (current + 1), "M", "N", "L", "W", "")) + { + if (check(meta_add_str(primary, "S")) != 0) goto end_free; + if (check(meta_add_str(secondary, "X")) != 0) goto end_free; + current += 1; + break; + } + if (meta_is_str_at(original, (current + 1), "Z", "")) + { + if (check(meta_add_str(primary, "S")) != 0) goto end_free; + if (check(meta_add_str(secondary, "X")) != 0) goto end_free; + current += 2; + break; + } + + if (meta_is_str_at(original, current, "SC", "")) + { + /** Schlesinger's rule. **/ + if (meta_get_char_at(original, current + 2) == 'H') + { + /** Dutch origin, e.g. 'school', 'schooner' **/ + if (meta_is_str_at(original, (current + 3), "OO", "ER", "EN", "UY", "ED", "EM", "")) + { + /** 'schermerhorn', 'schenker' **/ + const bool x_sound = meta_is_str_at(original, (current + 3), "ER", "EN", ""); + if (check(meta_add_str(primary, (x_sound) ? "X" : "SK")) != 0) goto end_free; + if (check(meta_add_str(secondary, "SK")) != 0) goto end_free; + current += 3; + break; + } + else + { + const bool s_sound = ( + current == 0 + && !meta_is_vowel(original, 3) + && meta_get_char_at(original, 3) != 'W' + ); + if (check(meta_add_str(primary, "X")) != 0) goto end_free; + if (check(meta_add_str(secondary, (s_sound) ? "S" : "X")) != 0) goto end_free; + current += 3; + break; + } + } + + /** Default case. **/ + const char* sound = (meta_is_str_at(original, (current + 2), "E", "I", "Y", "")) ? "S" : "SK"; + if (check(meta_add_str(primary, sound)) != 0) goto end_free; + if (check(meta_add_str(secondary, sound)) != 0) goto end_free; + current += 3; + break; + } + + /** French e.g. 'resnais', 'artois' **/ + const bool no_primary = (current == last && meta_is_str_at(original, (current - 2), "AI", "OI", "")); + if (check(meta_add_str(primary, (no_primary) ? "" : "S")) != 0) goto end_free; + if (check(meta_add_str(secondary, "S")) != 0) goto end_free; + current += (meta_is_str_at(original, (current + 1), "S", "Z", "")) ? 2 : 1; + break; + } + + case 'T': + { + if (meta_is_str_at(original, current, "TIA", "TCH", "TION", "")) + { + if (check(meta_add_str(primary, "X")) != 0) goto end_free; + if (check(meta_add_str(secondary, "X")) != 0) goto end_free; + current += 3; + break; + } + + if (meta_is_str_at(original, current, "TH", "TTH", "")) + { + /** Special case 'thomas', 'thames' or germanic. **/ + char* primary_char = ( + meta_is_str_at(original, (current + 2), "OM", "AM", "") + || meta_is_str_at(original, 0, "SCH", "VAN ", "VON ", "") + ) ? "T" : "0"; /* Zero, not O. */ + + if (check(meta_add_str(primary, primary_char)) != 0) goto end_free; + if (check(meta_add_str(secondary, "T")) != 0) goto end_free; + current += 2; + break; + } + + if (check(meta_add_str(primary, "T")) != 0) goto end_free; + if (check(meta_add_str(secondary, "T")) != 0) goto end_free; + current += (meta_is_str_at(original, (current + 1), "T", "D", "")) ? 2 : 1; + break; + } + + case 'V': + { + if (check(meta_add_str(primary, "F")) != 0) goto end_free; + if (check(meta_add_str(secondary, "F")) != 0) goto end_free; + current += (next_char == 'V') ? 2 : 1; + break; + } + + case 'W': + { + /** Can also be in middle of word. **/ + if (meta_is_str_at(original, current, "WR", "")) + { + if (check(meta_add_str(primary, "R")) != 0) goto end_free; + if (check(meta_add_str(secondary, "R")) != 0) goto end_free; + current += 2; + break; + } + + const bool next_is_vowel = meta_is_vowel(original, current + 1); + if (current == 0 && (next_is_vowel || meta_is_str_at(original, current, "WH", ""))) + { + /** Wasserman should match Vasserman. **/ + if (check(meta_add_str(primary, "A")) != 0) goto end_free; + if (check(meta_add_str(secondary, (next_is_vowel) ? "F" : "A")) != 0) goto end_free; + } + + /** Arnow should match Arnoff. **/ + if ((current == last && meta_is_vowel(original, current - 1)) + || meta_is_str_at(original, (current - 1), "EWSKI", "EWSKY", "OWSKI", "OWSKY", "") + || meta_is_str_at(original, 0, "SCH", "") + ) + { + if (check(meta_add_str(primary, "")) != 0) goto end_free; + if (check(meta_add_str(secondary, "F")) != 0) goto end_free; + current += 1; + break; + } + + /** Polish e.g. 'filipowicz' **/ + if (meta_is_str_at(original, current, "WICZ", "WITZ", "")) + { + if (check(meta_add_str(primary, "TS")) != 0) goto end_free; + if (check(meta_add_str(secondary, "FX")) != 0) goto end_free; + current += 4; + break; + } + + /** Else skip it. **/ + current += 1; + break; + } + + case 'X': + { + /** French e.g. breaux **/ + const bool silent = ( + current == last + && ( + meta_is_str_at(original, (current - 2), "AU", "OU", "") + || meta_is_str_at(original, (current - 3), "IAU", "EAU", "") + ) + ); + if (!silent) + { + if (check(meta_add_str(primary, "KS")) != 0) goto end_free; + if (check(meta_add_str(secondary, "KS")) != 0) goto end_free; + } + + current += (meta_is_str_at(original, (current + 1), "C", "X", "")) ? 2 : 1; + break; + } + + case 'Z': + { + /** Chinese pinyin e.g. 'zhao' **/ + if (next_char == 'H') + { + if (check(meta_add_str(primary, "J")) != 0) goto end_free; + if (check(meta_add_str(secondary, "J")) != 0) goto end_free; + current += 2; + break; + } + + const bool has_t_sound = ( + meta_is_str_at(original, (current + 1), "ZO", "ZI", "ZA", "") + || (is_slavo_germanic && current > 0 && meta_get_char_at(original, (current - 1)) != 'T') + ); + if (check(meta_add_str(primary, "S")) != 0) goto end_free; + if (check(meta_add_str(secondary, (has_t_sound) ? "TS" : "S")) != 0) goto end_free; + current += (next_char == 'Z') ? 2 : 1; + break; + } + + default: + current += 1; + } + } + + /** Write the output strings. **/ + *primary_code = primary->str; + *secondary_code = secondary->str; + primary->free_str_on_destroy = 0; + secondary->free_str_on_destroy = 0; + ret = 0; + + end_free: + if (UNLIKELY(ret != 0)) fprintf(stderr, "Error: meta_double_metaphone() failed (error code %d).\n", ret); + meta_destroy_string(original); + meta_destroy_string(primary); + meta_destroy_string(secondary); + + return ret; + } diff --git a/centrallix/utility/iface_html.c b/centrallix/utility/iface_html.c index f83e46ce6..9d4f1e00f 100644 --- a/centrallix/utility/iface_html.c +++ b/centrallix/utility/iface_html.c @@ -3,7 +3,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1999-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1999-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -336,10 +336,10 @@ ifcHtmlInit(pHtSession s, pWgtrNode tree) /** first add the necessary DHTML, and call to init **/ htrAddScriptInclude(s, "/sys/js/ht_utils_iface.js", 0); - htrAddStylesheetItem(s, " #ifc_layer {position: absolute; visibility: hidden;}\n"); + htrAddStylesheetItem(s, "\t\t#ifc_layer { position: absolute; visibility: hidden; }\n"); htrAddBodyItem(s, "
\n"); - htrAddScriptInit_va(s, " ifcInitialize(\"%STR&JSSTR\");\n", IFC.IfaceDir); - htrAddScriptInit(s, " init_inline_interfaces();\n"); + htrAddScriptInit_va(s, "\tifcInitialize('%STR&JSSTR');\n", IFC.IfaceDir); + htrAddScriptInit(s, "\tinit_inline_interfaces();\n"); /** now create all the interface info we know of in-line **/ xaInit(&AlreadyProcessed, 16); @@ -358,4 +358,3 @@ ifcHtmlInit(pHtSession s, pWgtrNode tree) return 0; } - diff --git a/centrallix/utility/obfuscate.c b/centrallix/utility/obfuscate.c index 838a1827b..3e75513fa 100644 --- a/centrallix/utility/obfuscate.c +++ b/centrallix/utility/obfuscate.c @@ -8,6 +8,7 @@ #include "cxlib/mtask.h" #include "cxlib/strtcpy.h" #include "cxlib/mtlexer.h" +#include "cxlib/mtsession.h" #include "cxlib/xhash.h" #include "obj.h" #include "cxss/cxss.h" @@ -1371,5 +1372,3 @@ obfObfuscateDataSess(pObfSession sess, pObjData srcval, pObjData dstval, int dat return rval; } - - diff --git a/centrallix/utility/param.c b/centrallix/utility/param.c index 2cdd12878..8460305da 100644 --- a/centrallix/utility/param.c +++ b/centrallix/utility/param.c @@ -6,6 +6,7 @@ #include "cxlib/strtcpy.h" #include "cxlib/newmalloc.h" #include "cxlib/datatypes.h" +#include "cxlib/mtsession.h" #include "stparse.h" #include "stparse_ne.h" #include "param.h" @@ -414,4 +415,3 @@ paramGetValue(pParam param, int datatype, pObjData value) return paramGetValueUntyped(param, value); } - diff --git a/centrallix/utility/ptod.c b/centrallix/utility/ptod.c index fb59b730b..25467e236 100644 --- a/centrallix/utility/ptod.c +++ b/centrallix/utility/ptod.c @@ -4,6 +4,7 @@ #include #include "cxlib/datatypes.h" #include "cxlib/newmalloc.h" +#include "cxlib/mtsession.h" #include "ptod.h" #include "obj.h" @@ -303,4 +304,3 @@ ptodTypeOf(pTObjData ptod) { return ptod->DataType; } - diff --git a/centrallix/wgtr/apos.c b/centrallix/wgtr/apos.c index 8c3780205..fcb341491 100644 --- a/centrallix/wgtr/apos.c +++ b/centrallix/wgtr/apos.c @@ -2,7 +2,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1998-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1998-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -26,9 +26,69 @@ /* Author: Nathaniel Colson */ /* Creation: August 9, 2005 */ /* Description: Applies layout logic to the widgets of an application. */ -/* See centrallix-sysdoc/Auto-Positioning.md for more information. */ +/* See centrallix-sysdoc/Auto-Positioning.md for more information. */ /************************************************************************/ +/*** Author: Israel Fuller + *** Date: June, 2025 + *** + *** See also: Auto-Positioning.md + *** + *** I wasn't the one to write most of this (although I did write a ton of + *** comments), but after doing my best to understand it, I hope that you will + *** find the compiled information below helpful. + *** + *** Execution of this file usually begins when wgtrVerify() in wgtr.c calls + *** aposAutoPositionWidgetTree(). To auto position the tree, the code first + *** it draws four lines on the four edges of every visible widget (with some + *** exceptions, @see aposAddLinesToGrid()). These lines divide the page into + *** into horizontal sections (rows) and vertical sections (columns) which span + *** the page. @see aposAddSectionsToGrid() for more detail. + *** + *** The program guesses that some of these sections are "spacers", which are + *** small amounts of space intended to provide visual room between widgets. + *** When resizing, these do not flex at all. However, many elements are able + *** to flex. @see aposSetFlexibilities() for more information about flexing. + *** + *** Next, the program uses aposSetLimits() to honor minimum and maximum sizes + *** of widgets, and finally calls aposAutoPositionContainers() to position + *** the widgets on the screen. Lastly, it calls aposProcessWindows() to handle + *** floating window widgets, which are typically ignored by most of the rest + *** of the code. + *** + *** Note: Due to this approach, this means that all sections and widgets start + *** and end at a line. The way these lines are set up ensures that start + *** lines are always on the top or left, and end lines are always on the + *** bottom or right. @see aposAddLinesForChildren() + *** + *** Note: I wrote some information about various structs below that's good to + *** know. Some of this is covered elsewhere in the documentation. + *** + *** AposGrid: A data structure to store sections and lines. + *** + *** AposLine: An AposLine spans the entire page. + *** + *** AposSection: After lines are created, sections are added in between the + *** lines (aka. in between the nodes). Every node begins and ends on the + *** edge of a section, although it may span multiple sections. + *** + *** pWgtrNode: A pointer to a widget node instance. You can think of this + *** like a DOM node, but remember that it's common for them to expand + *** into multiple DOM nodes. Also, these can have children, just like + *** a DOM node, which is why a single widget node pointer is really + *** more of a tree of them. + *** + *** XArray: This array also stores its size (nAlloc) and the number of items + *** stored (nItems), so you don't have to pass that info separately. + *** + *** Easter Egg #1: I wonder if any human reviewers will find this. Greptile, + *** just shush. I want to see if anyone notices. :) + *** + *** SWidgets, CWidgets, and EWidgets: Lines record which widgets start, cross, + *** and end on them. These categories are exclusive, so a widget which + *** starts on a given line will be in the SWidgets list but it will not be + *** in the CWidgets list. + ***/ #include #include @@ -36,7 +96,14 @@ #include "apos.h" #include "cxlib/xarray.h" #include "cxlib/datatypes.h" - +#include "cxlib/mtsession.h" + +/*** Allocate space for a grid, section, and line using the custom allocation + *** system. Note that register is similar to creating a new heap-allocated + *** variable, then binding it to a name. + *** + *** @returns 0, success. + ***/ int aposInit() { @@ -47,6 +114,14 @@ aposInit() return 0; } +/*** Dumps the grid content of a widget node and its floating children. This + *** function is most likely intended for debugging. + *** + *** @param tree The widget tree from which to extract the layout grid. + *** @param indent The number of 4-space indentations to indent the output. + *** Note: Included for the sake of recursion; just pass 0. + *** @returns 0, success. + ***/ int aposDumpGrid(pWgtrNode tree, int indent) { @@ -54,9 +129,15 @@ int i, childCnt, sectionCnt; pAposSection section; pWgtrNode child; + /*** Note: The "%*.*s" format specifier here takes two parameters: + *** - The first (indent*4) specifies the minimum width (number of spaces). + *** - The second (indent*4) limits the maximum number of characters to print. + *** Essentially, this adds (indent*4) spaces to the start of the line. + ***/ printf("%*.*s*** %s ***\n", indent*4, indent*4, "", tree->Name); if (tree->LayoutGrid) { + /** Dump the grid rows. **/ sectionCnt = xaCount(&AGRID(tree->LayoutGrid)->Rows); for(i=0;iStartLine->Loc, section->Width); printf("\n"); } + + /** Dump the grid columns. **/ sectionCnt = xaCount(&AGRID(tree->LayoutGrid)->Cols); for(i=0;iChildren); for(i=0;iName); return -1; } - /** Set flexibilities on containers **/ + /** Set flexibilities on containers. **/ if (aposSetFlexibilities(tree) < 0) { return -1; @@ -110,7 +202,7 @@ int i=0, count=0; /*aposDumpGrid(tree, 0);*/ - /** Honor minimum/maximum space requirements **/ + /** Detect and honor minimum/maximum space requirements. **/ if (aposSetLimits(tree) < 0) { return -1; @@ -133,7 +225,7 @@ int i=0, count=0; /**makes a final pass through the tree and processes html windows**/ aposProcessWindows(tree, tree); - /**unpatches all of the heights that were specified in aposPrepareTree**/ + /** Unpatches the heights specified by aposPrepareTree(). **/ count=xaCount(&PatchedWidgets); for(i=0; ipre_height = -1; } + /** Free the PatchedWidgets XArray. **/ xaDeInit(&PatchedWidgets); return 0; } - +/*** Recursively sets flexibility values for containers and their children. + *** + *** @param Parent The parent node who's flexibilities are being set. + *** @returns 0 if successful, -1 otherwise. + ***/ int aposSetFlexibilities(pWgtrNode Parent) { @@ -156,14 +253,14 @@ pWgtrNode Child; int i=0, childCount=xaCount(&(Parent->Children)); int sectCount; - /** Check recursion **/ + /** Check recursion. **/ if (thExcessiveRecursion()) { mssError(1,"APOS","Could not layout application: resource exhaustion occurred"); return -1; } - + /** Recursively set the flexibilities of all children. **/ for(i=0; iChildren), i); @@ -174,7 +271,7 @@ int sectCount; } } - /** Reset flexibility values in the grid **/ + /** Reset flexibility values in the grid. **/ if (theGrid && childCount > 0 && !(Parent->Flags & WGTR_F_NONVISUAL)) { sectCount = xaCount(&(theGrid->Rows)); @@ -191,7 +288,7 @@ int sectCount; } } - /**set the flexibility of the given container, if it is visual**/ + /** Set the flexibility of the given container, if it is visual. **/ if(!(Parent->Flags & WGTR_F_NONVISUAL)) if(aposSetContainerFlex(Parent) < 0) { @@ -202,8 +299,13 @@ int sectCount; return 0; } - -/** this function is the recursive function that actually does the work **/ +/*** Adjusts space to accommodate children, somehow? I think? + *** + *** @param Parent The widget node parent who's limits are being calculated. + *** @param delta_w The change in width required to accommodate children. + *** @param delta_h The change in height required to accommodate children. + *** @returns 0 if successful, -1 otherwise. + ***/ int aposSetLimits_r(pWgtrNode Parent, int* delta_w, int* delta_h) { @@ -215,14 +317,14 @@ int sectionCount; pAposSection s; pWgtrNode Child; - /** Check recursion **/ + /** Check recursion. **/ if (thExcessiveRecursion()) { mssError(1,"APOS","Could not layout application: resource exhaustion occurred"); return -1; } - /** Figure what is needed for children **/ + /** Calculate the total required space for children. **/ childCount = xaCount(&(Parent->Children)); total_child_delta_w = total_child_delta_h = 0; for(i=0;iLayoutGrid) { sectionCount = xaCount(&(AGRID(Parent->LayoutGrid)->Rows)); - for(i=0;iLayoutGrid)->Rows), i)); + /*** If it has a desired width, increase the height + *** enough to give it that width. + ***/ if (s->DesiredWidth >= 0) { *delta_h += (s->DesiredWidth - s->Width); @@ -263,6 +372,9 @@ pWgtrNode Child; for(i=0;iLayoutGrid)->Cols), i)); + /*** If it has a desired width, increase the width + *** enough to give it that width. + ***/ if (s->DesiredWidth >= 0) { *delta_w += (s->DesiredWidth - s->Width); @@ -273,7 +385,7 @@ pWgtrNode Child; } } - /** Make space for this widget bigger **/ + /** If there is extra space, expand this widget to fill that space. **/ if (*delta_w) { if (Parent->StartVLine && ALINE(Parent->StartVLine)->SSection) @@ -298,8 +410,11 @@ pWgtrNode Child; return 0; } - -/** This function simply call the recursive version **/ +/*** Adjusts space to accommodate children, somehow? I think? + *** + *** @param Parent The widget node parent who's limits are being calculated. + *** @returns 0, success. + ***/ int aposSetLimits(pWgtrNode Parent) { @@ -311,33 +426,43 @@ int rval; rval = aposSetLimits_r(Parent, &delta_w, &delta_h); - return 0; + return rval; } - +/*** Patch children of the given Parent node with unspecified heights. Searches + *** recursively within containers. Patched children are logged in the given + *** PatchedWidgets array. + *** + *** @param Parent The parent node who's childen should be patched. + *** @param PatchedWidgets The widget children which have been patched. + *** @returns 0 if successful, -1 otherwise. + ***/ int aposPrepareTree(pWgtrNode Parent, pXArray PatchedWidgets) { pWgtrNode Child; int i=0, childCount=xaCount(&(Parent->Children)); - /** Check recursion **/ + /** Check recursion. **/ if (thExcessiveRecursion()) { mssError(1,"APOS","Could not layout application: resource exhaustion occurred"); return -1; } + /** Loop through each child. **/ for(i=0; iChildren), i); - /**if a visual child has an unspecified height, patch it, unless it is a scrollpane**/ + /*** If a visual child has an unspecified height, patch it, unless it is in a scrollpane + *** Remember here that strcmp() returns 0 (false) if the strings are equal. + ***/ if((Child->height < 0) && !(Child->Flags & WGTR_F_NONVISUAL) && - strcmp(Parent->Type, "widget/scrollpane")) + !isScrollpane(Parent)) aposPatchNegativeHeight(Child, PatchedWidgets); - /** If child is a container but not a window, recursively prepare it as well **/ + /** If child is a container, but not a floating window, recursively prepare it as well. **/ if((Child->Flags & WGTR_F_CONTAINER) && !(Child->Flags & WGTR_F_FLOATING)) if (aposPrepareTree(Child, PatchedWidgets) < 0) return -1; @@ -346,12 +471,18 @@ int i=0, childCount=xaCount(&(Parent->Children)); return 0; } +/*** Try to guess the height of a widget with an unspecified height. + *** + *** @param Widget The widget child who's height is unspecified. + *** @param PatchedWidgets The array to add the widget to after patching it. + *** @returns 0 if successful, -1 otherwise. + ***/ int aposPatchNegativeHeight(pWgtrNode Widget, pXArray PatchedWidgets) { ObjData val; - /** set unspecified height of widget to an educated guess**/ + /** Try to guess the height based on the type of widget. **/ if(!strcmp(Widget->Type, "widget/editbox")) { Widget->height = 16; @@ -390,12 +521,21 @@ ObjData val; return 0; } + /** Add the widget to the provided array. **/ xaAddItem(PatchedWidgets, Widget); + + /** Overwrite the "prepositioning" height because it's most likely also invalid. **/ Widget->pre_height = Widget->height; return 0; } +/*** Calculates and sets the flexibility for a container by taking weighted + *** averages in each direction. + *** + *** @param W The container to be set. + *** @returns 0, success. + ***/ int aposSetContainerFlex(pWgtrNode W) { @@ -405,7 +545,10 @@ int i=0, sectCount=0, TotalWidth=0, ProductSum=0; if (!theGrid) return 0; - /**calculate average row flexibility, weighted by height **/ + /*** Calculate average row flexibility, weighted by height. + *** Note: Section height is called width here because rows + *** are one dimensional and the feild is reused. + ***/ sectCount = xaCount(&(theGrid->Rows)); for(i=0; iCols)); for(i=0; iType, "widget/scrollpane")); + /** Set isSP to compensate for scrollpane scrollbars. **/ + if(isSP) *isSP = (isScrollpane(W)); - /**set isWin to compensate windows' titlebars, if any**/ + /** Set isWin to compensate windows' titlebars, if any. **/ if(isWin && !strcmp(W->Type, "widget/childwindow")) { + /*** Set isWin (is window) to compensate for a titlebar. If the + *** node does not specify if it has a titlebar, assume it does. + ***/ if(wgtrGetPropertyValue(W, "titlebar", DATA_T_STRING, &val) < 0) - *isWin = 1; //if property not found, assume it has a titlebar + *isWin = 1; // Property not found, assume it has a titlebar. else *isWin = !strcmp(val.String, "yes"); } - /**isTopTab and isSideTab are used to compensate for tabs**/ - if(isTopTab && !strcmp(W->Type, "widget/tab")) + /** isTopTab and isSideTab are used to compensate for tabs. **/ + if((isTopTab != NULL || isSideTab != NULL) && strcmp(W->Type, "widget/tab") == 0) { - /**set isTopTab and isSideTab**/ + /*** Set isTopTab and isSideTab. If the node does not specify the + *** tab location, assume it has a top tab and leave side-tab unset. + **/ if(wgtrGetPropertyValue(W, "tab_location", DATA_T_STRING, &val) < 0) - *isTopTab = 1; //if property not found, assume top tab**/ + { + if (isTopTab != NULL) *isTopTab = 1; // Property not found, assume it has a top tab only. + } else { *isTopTab = (!strcmp(val.String, "top") || !strcmp(val.String, "bottom")); *isSideTab = (!strcmp(val.String, "left") || (!strcmp(val.String, "right"))); } - - /**set tabWidth**/ - if(wgtrGetPropertyValue(W, "tab_width", DATA_T_INTEGER, &val) < 0) - *tabWidth = 80; - else *tabWidth = val.Integer; } + + /** Set the tab width and height (if needed), defaulting to 24 and 80 if unspecified. **/ + if(tabWidth != NULL) *tabWidth = (wgtrGetPropertyValue(W, "tab_width", DATA_T_INTEGER, &val) == 0) ? val.Integer : 80; + if(tabHeight != NULL) *tabHeight = (wgtrGetPropertyValue(W, "tab_height", DATA_T_INTEGER, &val) == 0) ? val.Integer : 24; return 0; } - +/*** Builds the layout grid for recursively for this container and all of its + *** children, including the lines and sections required for positioning. + *** + *** @param Parent The parent node who's grid is being built. + *** @returns 0 if successful, -1 otherwise. + ***/ int aposBuildGrid(pWgtrNode Parent) { @@ -475,25 +649,24 @@ int childCount, i; pWgtrNode Child; pAposGrid theGrid = NULL; - /** Check recursion **/ + /** Check recursion. **/ if (thExcessiveRecursion()) { mssError(1,"APOS","Could not layout application: resource exhaustion occurred"); return -1; } - /** Allocate a grid **/ + /** Allocate a grid. **/ if (Parent->Flags & WGTR_F_CONTAINER) { if (!(Parent->Flags & WGTR_F_NONVISUAL) || !Parent->Parent) { + /** Allocate and initialize a new pAposGrid. **/ theGrid = Parent->LayoutGrid = (pAposGrid)nmMalloc(sizeof(AposGrid)); if (!Parent->LayoutGrid) goto error; - - /**initiallize the XArrays in the grid**/ aposInitiallizeGrid(theGrid); - /**Add the lines to the grid**/ + /** Add lines for children to the grid. **/ if(aposAddLinesToGrid(Parent, &(theGrid->HLines), &(theGrid->VLines)) < 0) { mssError(0, "APOS", "aposBuildGrid: Couldn't add lines to %s's grid", @@ -501,7 +674,7 @@ pAposGrid theGrid = NULL; return -1; } - /**Add the sections to the grid**/ + /** Add the sections to the grid. **/ if(aposAddSectionsToGrid(theGrid, (Parent->height-Parent->pre_height), (Parent->width-Parent->pre_width)) < 0) @@ -512,7 +685,7 @@ pAposGrid theGrid = NULL; } } - /** Do it for all children of this widget **/ + /** Recursively build this grid for all children of this widget. **/ childCount = xaCount(&(Parent->Children)); for(i=0; iFlags & WGTR_F_VSCROLLABLE)) - aposSnapWidgetsToGrid(&(theGrid->HLines), APOS_ROW); //rows + aposSnapWidgetsToGrid(&(theGrid->HLines), APOS_ROW, Parent->Root->ClientInfo); //rows if (!(Parent->Flags & WGTR_F_HSCROLLABLE)) - aposSnapWidgetsToGrid(&(theGrid->VLines), APOS_COL); //columns + aposSnapWidgetsToGrid(&(theGrid->VLines), APOS_COL, Parent->Root->ClientInfo); //columns /** did not resize? **/ /*if (rows_extra < 0) @@ -591,14 +772,18 @@ int rows_extra=0, cols_extra=0; return 0; } - +/*** Frees memory used by all grids in the widget tree. + *** + *** @param tree The tree containing the grids to be freed. + *** @returns 0, success. + ***/ int aposFreeGrids(pWgtrNode tree) { int childCount, i; pWgtrNode Child; - /**deallocate memory and deinit XArrays in the grid**/ + /** Recursively deallocate memory and deinit XArrays in the grid. **/ childCount = xaCount(&(tree->Children)); for(i=0;ipre_width < Parent->min_width && Parent->min_width != 0) @@ -642,26 +850,30 @@ pXArray FirstCross, LastCross; if (Parent->pre_height < Parent->min_height && Parent->min_height != 0) height_adj = Parent->min_height - Parent->pre_height; - /**Add the 2 horizontal border lines, unless parent is a scrollpane**/ - if(strcmp(Parent->Type, "widget/scrollpane")) + /** Add the 2 horizontal border lines, unless parent is a scrollpane. **/ + if(!isScrollpane(Parent)) { - if(aposCreateLine(NULL, HLines, 0, 0, 1, 0, 0) < 0) + int minHeightLoc = 0, maxHeightLoc = Parent->pre_height - isWin * 24; + if(aposCreateLine(NULL, HLines, minHeightLoc, APOS_NOT_LINKED, APOS_IS_BORDER, 0, APOS_HORIZONTAL) < 0) goto CreateLineError; - if(aposCreateLine(NULL, HLines, (Parent->pre_height-isWin*24), 0, 1, height_adj, 0) < 0) + if(aposCreateLine(NULL, HLines, maxHeightLoc, APOS_NOT_LINKED, APOS_IS_BORDER, height_adj, APOS_HORIZONTAL) < 0) goto CreateLineError; } - /**Add the 2 vertical border lines**/ - if(aposCreateLine(NULL, VLines, 0, 0, 1, 0, 1) < 0) + + /** Add the 2 vertical border lines. **/ + int minWidthLoc = 0, maxWidthLoc = (Parent->pre_width-isSP*18); + if(aposCreateLine(NULL, VLines, minWidthLoc, APOS_NOT_LINKED, APOS_IS_BORDER, 0, APOS_VERTICAL) < 0) goto CreateLineError; - if(aposCreateLine(NULL, VLines, (Parent->pre_width-isSP*18), 0, 1, width_adj, 1) < 0) + if(aposCreateLine(NULL, VLines, maxWidthLoc, APOS_NOT_LINKED, APOS_IS_BORDER, width_adj, APOS_VERTICAL) < 0) goto CreateLineError; + /** Recursively add the nonborder lines for all child nodes. **/ if(aposAddLinesForChildren(Parent, HLines, VLines) < 0) goto CreateLineError; - /**populate horizontal line cross XArrays**/ + /** Record the widgets that cross each horizontal line in its CWidgets XArray. **/ count = xaCount(HLines); - for(i=1; iCWidgets), &(CurrLine->EWidgets), &(CurrLine->CWidgets)); } - /**populate vertical line cross XArrays**/ + /** Record the widgets that cross each vertical line in its CWidgets XArray. **/ count = xaCount(VLines); - for(i=1; iCWidgets), &(CurrLine->EWidgets), &(CurrLine->CWidgets)); } - /**sanity check to make sure no widgets cross the border lines**/ - if(xaCount(HLines)) //don't test borderlines unless they exist + /** Sanity check to make sure no widgets cross the border lines. **/ + if(xaCount(HLines)) // Only check borderlines if they exist. { - FirstCross = &(((pAposLine)xaGetItem(HLines, 0))->CWidgets); - LastCross = &(((pAposLine)xaGetItem(HLines, (xaCount(HLines)-1)))->CWidgets); + // FirstCross = &(((pAposLine)xaGetItem(HLines, 0))->CWidgets); + // LastCross = &(((pAposLine)xaGetItem(HLines, (xaCount(HLines)-1)))->CWidgets); /*if(xaCount(FirstCross)) mssError(1, "APOS", "%d widget(s) crossed the top borderline, including %s '%s'", xaCount(FirstCross), ((pWgtrNode)xaGetItem(FirstCross, 0))->Type, ((pWgtrNode)xaGetItem(FirstCross, 0))->Name); @@ -692,8 +904,8 @@ pXArray FirstCross, LastCross; ((pWgtrNode)xaGetItem(LastCross, 0))->Type, ((pWgtrNode)xaGetItem(LastCross, 0))->Name);*/ } - FirstCross = &(((pAposLine)xaGetItem(VLines, 0))->CWidgets); - LastCross = &(((pAposLine)xaGetItem(VLines, (xaCount(VLines)-1)))->CWidgets); +// FirstCross = &(((pAposLine)xaGetItem(VLines, 0))->CWidgets); +// LastCross = &(((pAposLine)xaGetItem(VLines, (xaCount(VLines)-1)))->CWidgets); /*if(xaCount(FirstCross)) mssError(1, "APOS", "%d widget(s) crossed the left borderline, including %s '%s'", xaCount(FirstCross), ((pWgtrNode)xaGetItem(FirstCross, 0))->Type, ((pWgtrNode)xaGetItem(FirstCross, 0))->Name); @@ -708,26 +920,36 @@ pXArray FirstCross, LastCross; return -1; } +/*** Adds 4 lines for the edges of each visual child. Searches nonvisual + *** containers recursively for qualifying grandchildren. Floating windows + *** are ignored. Scrollpanes receive only 2 vertical lines (skipping their + *** horizontal edges). + *** + *** @param Parent The parent who's children are being given lines. + *** @param HLines The array to which horizontal lines should be added. + *** @param VLines The array to which vertical lines should be added. + *** @returns 0 if successful, -1 otherwise. + ***/ int aposAddLinesForChildren(pWgtrNode Parent, pXArray HLines, pXArray VLines) { int i=0, childCount=xaCount(&(Parent->Children)); -int isTopTab=0, isSideTab=0, tabWidth=0; +int isTopTab=0, isSideTab=0, tabWidth=0, tabHeight=0; int height_adj, width_adj; pWgtrNode C; - /** Check recursion **/ + /** Check recursion. **/ if (thExcessiveRecursion()) { mssError(1,"APOS","Could not layout application: resource exhaustion occurred"); return -1; } - /**loop through the children and create 4 lines for each child's 4 edges**/ + /** Loop through the children and create 4 lines for each child's 4 edges. **/ for(i=0; iChildren), i); - aposSetOffsetBools(C, NULL, NULL, &isTopTab, &isSideTab, &tabWidth); + aposSetOffsetBools(C, NULL, NULL, &isTopTab, &isSideTab, &tabWidth, &tabHeight); /** Does this widget need more room than it was given? **/ height_adj = width_adj = 0; @@ -736,29 +958,44 @@ pWgtrNode C; if (C->pre_height < C->min_height && C->min_height != 0) height_adj = C->min_height - C->pre_height; - /** If C is a nonvisual container, add lines for - *** the grandchildren. Otherwise, if C is visual - *** and not a window, just add 4 lines for it **/ + /** If the child (C) is a nonvisual container, recursively add lines for any grandchildren. **/ if((C->Flags & WGTR_F_NONVISUAL) && (C->Flags & WGTR_F_CONTAINER)) { if (aposAddLinesForChildren(C, HLines, VLines) < 0) goto CreateLineError; } + /** Otherwise, if child (C) is visual and not a floating window, add 4 lines for it. **/ else if(!(C->Flags & WGTR_F_NONVISUAL) && !(C->Flags & WGTR_F_FLOATING)) { - /**add horizontal lines, unless parent is a scrollpane**/ - if(strcmp(Parent->Type, "widget/scrollpane")) + /** Add horizontal lines, unless parent is a scrollpane. **/ + if(!isScrollpane(Parent)) { - if(aposCreateLine(C, HLines, (C->y), APOS_SWIDGETS, 0, 0, 0) < 0) + /*** Note: + *** From this code, we see that the start line is + *** always the minY, and the end of the line is + *** always the maxY. Thus, the top line is the + *** start line and the bottom line is the end line + *** because Y increases as we decend the page. + ***/ + int minY = (C->y), maxY = (C->y + C->height + isTopTab*tabHeight); + if(aposCreateLine(C, HLines, minY, APOS_SWIDGETS, APOS_NOT_BORDER, 0, APOS_HORIZONTAL) < 0) goto CreateLineError; - if(aposCreateLine(C, HLines, (C->y + C->height + isTopTab*24), APOS_EWIDGETS, 0, height_adj, 0) < 0) + if(aposCreateLine(C, HLines, maxY, APOS_EWIDGETS, APOS_NOT_BORDER, height_adj, APOS_HORIZONTAL) < 0) goto CreateLineError; } - - /**add vertical lines**/ - if(aposCreateLine(C, VLines, (C->x), APOS_SWIDGETS, 0, 0, 1) < 0) + + /*** Note: + *** From this code, we see that the start line is always + *** the minX, and the end of the line is always the maxX. + *** Thus, the left line is the start line and the right + *** line is the end line because X increases as we move + *** right along the page. + ***/ + /** Add vertical lines. **/ + int minX = (C->x), maxX = (C->x + C->width + isSideTab*tabWidth); + if(aposCreateLine(C, VLines, minX, APOS_SWIDGETS, APOS_NOT_BORDER, 0, APOS_VERTICAL) < 0) goto CreateLineError; - if(aposCreateLine(C, VLines, (C->x + C->width + isSideTab*tabWidth), APOS_EWIDGETS, 0, width_adj, 1) < 0) + if(aposCreateLine(C, VLines, maxX, APOS_EWIDGETS, APOS_NOT_BORDER, width_adj, APOS_VERTICAL) < 0) goto CreateLineError; } } @@ -770,12 +1007,36 @@ pWgtrNode C; return -1; } +/*** Creates a new line in the grid or updates an existing line if it exists + *** in the same location. Remember that lines record the widgets that start + *** on them (SWidgets), end on them (EWidgets), and cross them (CWidgets). + *** + *** Note: This function all lines in the given array are oriented in the same + *** direction as the new line. At the time of this writing (June 2025), + *** all known calling functions upheld by maintaining an HLines and a + *** VLines array to store horizontal and vertical lines separately. + *** + *** @param Widget The widget which determined the location of this line, + *** which we add to the SWidgets or EWidgets array. + *** @param Lines The array that stores the lines. + *** @param Loc The location of the line. Only a single coordinate in + *** one dimension is needed since lines span the entire grid. + *** @param type The type, indicating whether the associated widget starts + *** or ends on this line. + *** @param isBorder A boolean that is true if this is a grid border line. + *** @param adj An adjustment added to or subtracted from the line to + *** satisfy min or max constraints (respectively). + *** @param is_vert A boolean that is true if this line is vertical. + *** See APOS_VERTICAL and APOS_HORIZONTAL. + *** + *** @returns 0, success. + ***/ int aposCreateLine(pWgtrNode Widget, pXArray Lines, int Loc, int type, int isBorder, int adj, int is_vert) { pAposLine Line = aposExistingLine(Lines, Loc); - /**if there is already a line, just upgrade it**/ + /** If there is already a line, we upgrade it instead of creating a new one. **/ if(Line != NULL) { @@ -789,14 +1050,14 @@ pAposLine Line = aposExistingLine(Lines, Loc); else { - /**otherwise, create and add the new line**/ + /** There's not already a line, so we allocate a new one. **/ if((Line = (pAposLine)nmMalloc(sizeof(AposLine))) < 0) { mssError(1, "APOS", "aposCreateLine: Couldn't allocate memory for new grid line"); return -1; } - /**initiallize new line**/ + /** Initialize the new line. **/ memset(Line, 0, sizeof(AposLine)); xaInit(&(Line->SWidgets),16); xaInit(&(Line->EWidgets),16); @@ -807,11 +1068,11 @@ pAposLine Line = aposExistingLine(Lines, Loc); Line->SSection = NULL; Line->ESection = NULL; - /**add new line, sorted by location**/ + /** Add the new line, to the list of lines, sorted by location. **/ xaAddItemSortedInt32(Lines, Line, 0); } - /** Link the line and the widget together **/ + /** Link the line and the widget together. **/ if (type == APOS_SWIDGETS) { xaAddItem(&(Line->SWidgets), Widget); @@ -834,6 +1095,16 @@ pAposLine Line = aposExistingLine(Lines, Loc); return 0; } +/*** Gets a line from the array at the location, or returns NULL if none exists. + *** + *** Note: This function all lines in the given array are oriented in the same + *** direction. This is not tested, although at the time of this writing + *** (June, 2025), all calling functions upheld this contract. + *** + *** @param Lines The array of lines to search. + *** @param Loc The location to check for a line. + *** @returns A pointer to the line, if it exists, and NULL otherwise. + ***/ pAposLine aposExistingLine(pXArray Lines, int Loc) { @@ -848,36 +1119,64 @@ int i, count = xaCount(Lines); return NULL; } +/*** Detects if a widget in PrevList (usually the widgets that started in or + *** crossed the previous line) ends on this line (aka. appears in EWidgets). + *** If it does not end on this line, we know it crosses this line, so we add + *** the widget to CWidgets. + *** + *** @param PrevList The list of previous widgets being checked. + *** @param EWidgets The list of widgets ending on the line in question. + *** @param CWidgets The list to which widgets that cross should be added. + *** @returns 0, success. + ***/ int aposFillInCWidget(pXArray PrevList, pXArray EWidgets, pXArray CWidgets) { pWgtrNode AddCandidate; int found=0, i=0, j=0, pCount=xaCount(PrevList), eCount=xaCount(EWidgets); - /** loop through the SWidgets or CWidgets array of the previous line**/ + /*** Loop through the array from the previous line. + *** Note: Could be that line's SWidgets OR CWidgets. + **/ for(i=0; iHLines)); for(i=1; iRows), ((pAposLine)xaGetItem(&(theGrid->HLines),(i-1))), @@ -887,7 +1186,7 @@ int count=0, i=0; return -1; } - /**Add columns**/ + /** Add column sections between vertical lines. **/ count = xaCount(&(theGrid->VLines)); for(i=1; iCols), ((pAposLine)xaGetItem(&(theGrid->VLines),(i-1))), @@ -900,15 +1199,29 @@ int count=0, i=0; return 0; } +/*** Calculate and set the flexibility value for a section. Spacers have 0 flex + *** and containers use the flex of their least flexible children. + *** + *** @param sect The section being set. + *** @param type The type of section (either APOS_ROW or APOS_COL). + *** @returns 0 if successful or -1 if a default value should be used instead + ***/ int aposSetSectionFlex(pAposSection sect, int type) { +/*** Note: + *** sCount + cCount includes all widgets intersecting this section because a + *** widget cannot begin inside a section. It always starts or eds at the edge + *** of a section. + ***/ int sCount = xaCount(&(sect->StartLine->SWidgets)); int cCount = xaCount(&(sect->StartLine->CWidgets)); - /** Set flex to 0 if the section is a spacer or contains non-flexible children, - *** otherwise set it to the average of the children. If none of those apply - *** it must be a wide, widgetless gap, assign a default flexibility **/ + /** Set flex to 0 if the section is a spacer or contains non-flexible + *** children, otherwise set it to the minimum of the children. If none + *** of those apply it must be a wide, widgetless gap. In this case, + *** return -1 to prompt the caller to determine a default flexibility. + ***/ if((sect->isSpacer) || (aposNonFlexChildren(sect->StartLine, type))) sect->Flex = 0; else if(sCount || cCount) @@ -919,12 +1232,21 @@ int cCount = xaCount(&(sect->StartLine->CWidgets)); return 0; } +/*** Creates a new row or column section between two lines in the grid. + *** + *** @param Sections The array of sections, to which this section will be added. + *** @param StartL The line which starts this section (typically the top/left line). + *** @param EndL The line which ends this section (typically the bottom/right line). + *** @param Diff I had a hard time figuring out what this means. + *** @param type Whether the section is a row (APOS_ROW) or a column (APAS_COL). + *** @returns 0 if successful, -1 otherwise. + ***/ int aposCreateSection(pXArray Sections, pAposLine StartL, pAposLine EndL, int Diff, int type) { pAposSection NewSect; - /**Allocate and initiallize a new section**/ + /** Allocate and initialize a new section. **/ if((NewSect = (pAposSection)(nmMalloc(sizeof(AposSection)))) < 0) { mssError(1, "APOS", "nmMalloc(): Couldn't allocate memory for new row or column"); @@ -940,11 +1262,11 @@ pAposSection NewSect; StartL->SSection = NewSect; EndL->ESection = NewSect; - /** Need to adjust section width/height? **/ + /** Apply the adjustment from the end line, if needed. **/ if (EndL->Adj) NewSect->DesiredWidth = NewSect->Width + EndL->Adj; - /** Set section flexibility **/ + /** Set section flexibility. **/ if (aposSetSectionFlex(NewSect, type) < 0) { if (Diff < 0) @@ -958,22 +1280,36 @@ pAposSection NewSect; return 0; } +/*** Determines if a section between two lines is a spacer. + *** + *** If a section is a spacer, the assumption is that the designer probably put + *** that space there to provide visual breathing room in their design. Thus, we + *** should avoid resizing it as this may interfere with their design. + *** + *** @param StartL The line starting the section. (I think this is always the left/top.) + *** @param EndL The line starting the section. (I think this is always the right/bottom.) + *** @param type Whether the section is a row (APOS_ROW) or a column (APOS_COL). + *** @param isBorder Whether the section is on the border of the page. + *** @returns 0 if successful, -1 otherwise. + ***/ int aposIsSpacer(pAposLine StartL, pAposLine EndL, int type, int isBorder) { pWgtrNode SW, EW; int i=0, j=0; +/** @brief The number of widgets starting at the end of this section. **/ int sCount=xaCount(&(EndL->SWidgets)); +/** @brief The number of widgets ending at the start of this section. **/ int eCount=xaCount(&(StartL->EWidgets)); - if((EndL->Loc - StartL->Loc) <= APOS_MINSPACE) //if section is sufficiently narrow + if((EndL->Loc - StartL->Loc) <= APOS_MINSPACE) // If section is sufficiently narrow. { - /**gap between border and widget**/ + /** Gaps between the border and any widget(s) are spacers. **/ if(isBorder && (sCount || eCount)) return 1; - /** Checks every widget ending on one side of the section against - *** every widget beginning on the other side to see if any of them - *** are directly across from each other **/ + /** Checks every widget ending on one side to see if a widget + *** starts directly across from it on the other side. + ***/ for(i=0; iSWidgets), i)); @@ -981,9 +1317,10 @@ int eCount=xaCount(&(StartL->EWidgets)); { EW = (pWgtrNode)(xaGetItem(&(StartL->EWidgets), j)); - /** if a corner of a widget on one side of the - *** section falls between the two corners of a widget - *** on the other side, return 1 **/ + /** If a corner of the widget on one side falls + *** between the two corners of a widget on the + *** other side, this is a spacer. + ***/ if((type == APOS_ROW) && (((EW->x >= SW->x) && (EW->x < (SW->x + SW->width))) || (((EW->x + EW->width) > SW->x) && ((EW->x + EW->width) <= (SW->x + SW->width))))) return 1; @@ -998,6 +1335,14 @@ int eCount=xaCount(&(StartL->EWidgets)); return 0; } +/*** Checks for any widgets starting on or crossing a line that are non-flexible + *** in the relevant dimension. + *** + *** @param L The line along which to check. + *** @param type Specifies the relevant dimension using APOS_ROW or APOS_COL. + *** @returns 1 if any child widget is non-flexible in the relevant dimension, + *** 0 otherwise. + ***/ int aposNonFlexChildren(pAposLine L, int type) { @@ -1005,8 +1350,9 @@ int i=0; int sCount = xaCount(&(L->SWidgets)); int cCount = xaCount(&(L->CWidgets)); - /** returns 1 if the widgets starting on or crossing the given - *** line have children that are completely non-flexible **/ + /*** Return 1 if the widgets starting on or crossing the given line have + *** children that are completely non-flexible. + ***/ if(type == APOS_ROW) { for(i=0; iCWidgets)); if(((pWgtrNode)(xaGetItem(&(L->CWidgets), i)))->fl_height == 0) return 1; } - else //type == APOS_COL + else // type == APOS_COL { for(i=0; iSWidgets), i)))->fl_width == 0) @@ -1029,6 +1375,12 @@ int cCount = xaCount(&(L->CWidgets)); return 0; } +/*** Calculates the average flexibility of widgets on a line. + *** + *** @param L The line along which to check. + *** @param type Specifies the relevant dimension using APOS_ROW or APOS_COL. + *** @returns The average flexibility of children on the line. + ***/ int aposAverageChildFlex(pAposLine L, int type) { @@ -1036,7 +1388,7 @@ int TotalFlex=0, i=0; int sCount = xaCount(&(L->SWidgets)); int cCount = xaCount(&(L->CWidgets)); - /** Sum the flexibilities of widgets within the section proceeding the line**/ + /** Sum the flexibilities. **/ if(type == APOS_ROW) { for(i=0; iCWidgets)); TotalFlex += ((pWgtrNode)xaGetItem(&(L->CWidgets), i))->fl_width; } - /**return average flexibility**/ + /** Return the average flexibility with an aditional fudge factor. **/ return (int)(APOS_FUDGEFACTOR + ((float)TotalFlex)/((float)sCount+(float)cCount)); } +/*** Calculates the minimum flexibility of widgets on a line. + *** + *** @param L The line along which to check. + *** @param type Specifies the relevant dimension using APOS_ROW or APOS_COL. + *** @returns The minimum flexibility of children on the line. + ***/ int aposMinimumChildFlex(pAposLine L, int type) { @@ -1063,7 +1421,7 @@ int MinFlex=100, i=0, f; int sCount = xaCount(&(L->SWidgets)); int cCount = xaCount(&(L->CWidgets)); - /** Find the min flex within the section proceeding the line**/ + /** Find the min flexibility. **/ if(type == APOS_ROW) { for(i=0; iCWidgets)); } } - /**return min flexibility**/ + /** Return the minimum flexibility. **/ return MinFlex; } +/*** Distributes extra or missing space among grid lines based on section flexibility. + *** + *** @param Lines The array of lines in the relevant direction on this grid. + *** @param Sections The array of sections in the relevant direction on this grid. + *** @param Diff The space difference from how the elements are currently spaced. + *** @returns The remaining space difference after spacing out elements as much as possible. + ***/ int aposSpaceOutLines(pXArray Lines, pXArray Sections, int Diff) { @@ -1105,7 +1470,7 @@ int FlexibleSections=0; float FlexWeight=0, SizeWeight=0; float TotalSum=0; - /**if there are no sections, don't bother going on**/ + /** If there are no sections, we have nothing to space out. **/ if(!count) return Diff; /**Sum the flexibilities of the sections**/ @@ -1120,48 +1485,64 @@ float TotalSum=0; } } - /** if there is no flexibility for expansion or contraction return 0**/ + /*** If there is no flexibility (no expansion or contraction), we can't + *** space anything out. Return the original difference so this can be + *** spaced out elsewhere. + ***/ if(TotalFlex == 0) return Diff; - /** sets each line's location equal to the previous line's location - *** plus the adjusted width of the preceding section **/ + /** Sum the flex weights of all sections, weighted by their size. **/ count = xaCount(Lines); for(i=1; iFlex) / (float)(TotalFlex); - SizeWeight = 0; - if(FlexWeight > 0) - SizeWeight = (float)(PrevSect->Width) / (float)(TotalFlexibleSpace); + SizeWeight = (FlexWeight > 0) ? (float)(PrevSect->Width) / (float)(TotalFlexibleSpace) : 0; TotalSum += (FlexWeight * SizeWeight); } + /** The initial borders do not adjust. **/ + pAposLine leftBorder = (pAposLine)xaGetItem(Lines, 0); + leftBorder->loc_fl = leftBorder->my_fl = 0.0f; + for(i=1; iFlex) / (float)(TotalFlex); - SizeWeight = 0; - - /** unless there's at least some flexibility, don't factor in size **/ - if(FlexWeight > 0) - SizeWeight = (float)(PrevSect->Width) / (float)(TotalFlexibleSpace); + SizeWeight = (FlexWeight > 0) ? (float)(PrevSect->Width) / (float)(TotalFlexibleSpace) : 0; + + /*** Calculate the adjustment weight, and also save it so we can + *** replicate some of the following logic in the CSS we will + *** eventually send to the client. + ***/ + float fl = (float)(FlexWeight * SizeWeight) / TotalSum; + + /** Store the line adjustment weight for responsive CSS later. **/ + CurrLine->loc_fl = PrevLine->loc_fl + fl; + CurrLine->my_fl = fl; - /**for expanding lines**/ + /** Expand lines. **/ if(Diff > 0) { - /*Adj = APOS_FUDGEFACTOR + (float)(Diff) * ((float)(FlexWeight+SizeWeight)/2.0);*/ - Adj = APOS_FUDGEFACTOR + (float)(Diff) * ((float)(FlexWeight*SizeWeight)/TotalSum); - CurrLine->Loc = PrevLine->Loc + PrevSect->Width + Adj; + /** Calculate adjustment using the adjustment weight. **/ + Adj = (float)(Diff) * fl + APOS_FUDGEFACTOR; + + // printf("Expanding lines by %d*%f=%d\n", Diff, fl, Adj); + + /** Apply the calculated adjustment. **/ PrevSect->Width += Adj; + CurrLine->Loc = PrevLine->Loc + PrevSect->Width; } - /**for contracting lines**/ + /** Contract lines. **/ else if(Diff < 0) { - /*Adj = (float)(Diff) * ((float)(FlexWeight+SizeWeight)/2.0) - APOS_FUDGEFACTOR;*/ - Adj = (float)(Diff) * ((float)(FlexWeight*SizeWeight)/TotalSum) - APOS_FUDGEFACTOR; + /** Calculate adjustment using the adjustment weight. **/ + Adj = (float)(Diff) * fl - APOS_FUDGEFACTOR; + + // printf("Contracting lines by %d*%f=%d\n", Diff, fl, Adj); /** if the section width will be unacceptably *** narrow or negative after the adjustment **/ @@ -1188,8 +1569,8 @@ float TotalSum=0; } else { - CurrLine->Loc = PrevLine->Loc + PrevSect->Width + Adj; PrevSect->Width += Adj; + CurrLine->Loc = PrevLine->Loc + PrevSect->Width; } } } @@ -1201,11 +1582,22 @@ float TotalSum=0; return Extra; } +/*** Adjusts widget positions and sizes to snap them to grid lines. This + *** function should be called after updating grid lines to ensure that + *** widgets properly reflect the changes. + *** + *** @param Lines The lines being updated. + *** @param flag Either APOS_ROW or APOS_COL. + *** @param info Info about the page design, currently used to determine if + *** flexibility should be used. + *** @returns 0, success. + ***/ int -aposSnapWidgetsToGrid(pXArray Lines, int flag) +aposSnapWidgetsToGrid(pXArray Lines, int flag, pWgtrClientInfo info) { +const int is_design = info->IsDesign; int i=0, j=0, count=0, lineCount = xaCount(Lines); -int isTopTab=0, isSideTab=0, tabWidth=0; +int isTopTab=0, isSideTab=0, tabWidth=0, tabHeight=0; int newsize; pAposLine CurrLine; pWgtrNode Widget; @@ -1219,18 +1611,27 @@ pWgtrNode Widget; for(j=0; jSWidgets), j); - if(flag == APOS_ROW) Widget->y = CurrLine->Loc; - else Widget->x = CurrLine->Loc; + if(flag == APOS_ROW) + { + Widget->y = CurrLine->Loc; + Widget->fl_scale_y = (is_design) ? 0.0 : CurrLine->loc_fl; + } + else + { + Widget->x = CurrLine->Loc; + Widget->fl_scale_x = (is_design) ? 0.0 : CurrLine->loc_fl; + } } - /** Adjusts width or height of widgets ending on this line **/ + /** Adjusts width or height of widgets ending on this line. **/ count = xaCount(&(CurrLine->EWidgets)); for(j=0; jEWidgets), j); - aposSetOffsetBools(Widget, NULL, NULL, &isTopTab, &isSideTab, &tabWidth); + aposSetOffsetBools(Widget, NULL, NULL, &isTopTab, &isSideTab, &tabWidth, &tabHeight); if(flag==APOS_ROW && Widget->fl_height) { + /** Calculate the new size, taking APOS_MINWIDTH into account. **/ newsize = CurrLine->Loc - Widget->y - isTopTab*24; if (newsize < APOS_MINWIDTH && Widget->pre_height >= APOS_MINWIDTH) Widget->height = APOS_MINWIDTH; @@ -1239,28 +1640,66 @@ pWgtrNode Widget; else /*Widget->height = APOS_MINWIDTH;*/ Widget->height = Widget->pre_height; + + /*** The widget copies the adjustment weight of the + *** line, ignoring APOS_MINWIDTH. + ***/ + Widget->fl_scale_h += (is_design) ? 0.0 : CurrLine->my_fl; } else if(flag==APOS_COL && Widget->fl_width) { + /** Calculate the new size, taking APOS_MINWIDTH into account. **/ newsize = CurrLine->Loc - Widget->x - isSideTab*tabWidth; + + /** If the new size is now smaller than the minimum, clamp it. **/ if (newsize < APOS_MINWIDTH && Widget->pre_width >= APOS_MINWIDTH) Widget->width = APOS_MINWIDTH; + /** If the size is bigger than the minimum, or growing, that's fine. **/ else if (newsize >= APOS_MINWIDTH || newsize >= Widget->pre_width) Widget->width = newsize; + /** Otherwise, we can't update the size. **/ else /*Widget->width = APOS_MINWIDTH;*/ Widget->width = Widget->pre_width; + + /*** The widget copies the adjustment weight of the + *** line, ignoring APOS_MINWIDTH. + ***/ + Widget->fl_scale_w += (is_design) ? 0.0 : CurrLine->my_fl; } } + + if (!is_design) + { + /** Adjusts width or height of widgets ending on this line. **/ + count = xaCount(&(CurrLine->CWidgets)); + for(j=0; jCWidgets), j); + if(flag==APOS_ROW && Widget->fl_height) + Widget->fl_scale_h += (is_design) ? 0.0 : CurrLine->my_fl; + else if(flag==APOS_COL && Widget->fl_width) + Widget->fl_scale_w += (is_design) ? 0.0 : CurrLine->my_fl; + } + } + } return 0; } +/*** + *** Processes floating windows and recursively positions visual and + *** nonvisual containers. + *** + *** @param VisualRef The last visual container up the inheritance tree. + *** @param Parent The widget being scanned for windows. + *** @returns 0 if successful, -1 otherwise. + ***/ int aposProcessWindows(pWgtrNode VisualRef, pWgtrNode Parent) { -int i=0, changed=0, isWin=0, isSP=0; +int i=0, isWin=0, isSP=0; int childCount=xaCount(&(Parent->Children)); pWgtrNode Child; int rw, rh, rpw, rph; @@ -1274,7 +1713,7 @@ int ival; return -1; } - aposSetOffsetBools(Parent, &isSP, &isWin, NULL, NULL, NULL); + aposSetOffsetBools(Parent, &isSP, &isWin, NULL, NULL, NULL, NULL); /**loop through children and process any windows**/ for(i=0; ix = (rw - Child->width)/2; if (Child->x < 0) Child->x = 0; - changed = 1; } if (abs(Child->pre_y - (rph - (Child->pre_y + Child->pre_height))) < 10) { Child->y = (rh - Child->height)/2; if (Child->y < 0) Child->y = 0; - changed = 1; } /**if it's larger than its container, shrink it and set flag**/ if(Child->width > (rw - isSP*18)) { Child->width = (rw - isSP*18); - changed = 1; } if(Child->height > (rh - isWin*24)) { Child->height = (rh - isWin*24); - changed = 1; } /**if the window changed width or height, process it like a widget tree**/ - //if(changed) aposAutoPositionWidgetTree(Child); - /*Child->width = Child->pre_width; - Child->height = Child->pre_height;*/ /**if it's outside the top left corner pull the whole window in**/ if(Child->x < 0) Child->x = 0; @@ -1359,6 +1791,10 @@ int ival; return 0; } +/*** Frees all memory used by a grid, including its lines and sections. + *** + *** @param theGrid The grid being freed. + ***/ int aposFree(pAposGrid theGrid) { diff --git a/centrallix/wgtr/wgtdrv_autolayout.c b/centrallix/wgtr/wgtdrv_autolayout.c index 3d0f48356..31403d035 100644 --- a/centrallix/wgtr/wgtdrv_autolayout.c +++ b/centrallix/wgtr/wgtdrv_autolayout.c @@ -244,7 +244,7 @@ wgtalVerify(pWgtrVerifySession s) continue; } else - mssError(1, "WGTRAL", "Warning: overflow of end of hbox '%s'",al->Name); + fprintf(stderr, "Warning: overflow of end of hbox '%s'\n", al->Name); } child->x = child->pre_x = xo; child->pre_width = child->width = possible_width; @@ -281,7 +281,7 @@ wgtalVerify(pWgtrVerifySession s) continue; } else - mssError(1, "WGTRAL", "Warning: overflow of end of vbox '%s'",al->Name); + fprintf(stderr, "Warning: overflow of end of vbox '%s'\n", al->Name); } child->y = child->pre_y = yo; child->pre_height = child->height = possible_height; diff --git a/centrallix/wgtr/wgtdrv_sys_osml.c b/centrallix/wgtr/wgtdrv_sys_osml.c deleted file mode 100644 index 56abd5797..000000000 --- a/centrallix/wgtr/wgtdrv_sys_osml.c +++ /dev/null @@ -1,74 +0,0 @@ -#include -#include -#include -#include "obj.h" -#include "cxlib/mtask.h" -#include "cxlib/mtsession.h" -#include "wgtr.h" - -/************************************************************************/ -/* Centrallix Application Server System */ -/* Centrallix Core */ -/* */ -/* Copyright (C) 1998-2001 LightSys Technology Services, Inc. */ -/* */ -/* This program is free software; you can redistribute it and/or modify */ -/* it under the terms of the GNU General Public License as published by */ -/* the Free Software Foundation; either version 2 of the License, or */ -/* (at your option) any later version. */ -/* */ -/* This program is distributed in the hope that it will be useful, */ -/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ -/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ -/* GNU General Public License for more details. */ -/* */ -/* You should have received a copy of the GNU General Public License */ -/* along with this program; if not, write to the Free Software */ -/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA */ -/* 02111-1307 USA */ -/* */ -/* A copy of the GNU General Public License has been included in this */ -/* distribution in the file "COPYING". */ -/* */ -/* Module: wgtr/wgtdrv_sys_osml.c */ -/* Author: Matt McGill (MJM) */ -/* Creation: June 30, 2004 */ -/* Description: */ -/************************************************************************/ - - - -/*** wgtosmlVerify - allows the driver to check elsewhere in the tree - *** to make sure that the conditions it requires for proper functioning - *** are present - checking for other widgets that might be necessary, - *** checking interface versions on widgets to be interacted with, etc. - ***/ -int -wgtosmlVerify(pWgtrVerifySession s) - { - return 0; - } - - -/*** wgtosmlNew - after a node has been filled out with initial values, - *** the driver uses this function to take care of any other initialization - *** that needs to be done on a per-node basis. By far the most important - *** is declaring interfaces. - ***/ -int -wgtosmlNew(pWgtrNode node) - { - return 0; - } - - -int -wgtosmlInitialize() - { - char* name = "System:OSML Driver"; - - wgtrRegisterDriver(name, wgtosmlVerify, wgtosmlNew); - wgtrAddType(name, "sys-osml"); - - return 0; - } diff --git a/centrallix/wgtr/wgtr.c b/centrallix/wgtr/wgtr.c index 0293dfc62..72b484a75 100755 --- a/centrallix/wgtr/wgtr.c +++ b/centrallix/wgtr/wgtr.c @@ -2,7 +2,7 @@ /* Centrallix Application Server System */ /* Centrallix Core */ /* */ -/* Copyright (C) 1999-2001 LightSys Technology Services, Inc. */ +/* Copyright (C) 1999-2026 LightSys Technology Services, Inc. */ /* */ /* This program is free software; you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -46,6 +46,7 @@ #include "cxlib/magic.h" #include "cxlib/xhash.h" #include "cxlib/strtcpy.h" +#include "cxlib/mtsession.h" #include "ht_render.h" #define WGTR_MAX_PARAMS (24) @@ -1306,12 +1307,18 @@ wgtrGetPropertyType(pWgtrNode widget, char* name) pObjProperty prop; ASSERTMAGIC(widget, MGK_WGTR); - if (!strcmp(name, "name")) return DATA_T_STRING; - else if (!strcmp(name, "outer_type")) return DATA_T_STRING; - else if (!strcmp(name, "x") || !strcmp(name, "y") || !strcmp(name, "width") || !strcmp(name, "height") || - !strcmp(name, "r_x") || !strcmp(name, "r_y") || !strcmp(name, "r_width") || !strcmp(name, "r_height") || - !strcmp(name, "fl_x") || !strcmp(name, "fl_y") || !strcmp(name, "fl_width") || !strcmp(name, "fl_height")) + if (strcmp(name, "name") == 0 || strcmp(name, "outer_type") == 0) + return DATA_T_STRING; + else if (strcmp(name, "x") == 0 || strcmp(name, "y") == 0 || strcmp(name, "width") == 0 || strcmp(name, "height") == 0 || + strcmp(name, "r_x") == 0 || strcmp(name, "r_y") == 0 || strcmp(name, "r_width") == 0 || strcmp(name, "r_height") == 0 || + strcmp(name, "fl_x") == 0 || strcmp(name, "fl_y") == 0 || strcmp(name, "fl_width") == 0 || strcmp(name, "fl_height") == 0 || + strcmp(name, "fl_parent_w") == 0 || strcmp(name, "fl_parent_h") == 0) return DATA_T_INTEGER; + else if (strcmp(name, "fl_scale_x") == 0 || strcmp(name, "fl_scale_y") == 0 || + strcmp(name, "fl_scale_w") == 0 || strcmp(name, "fl_scale_h") == 0 || + strcmp(name, "fx") == 0 || strcmp(name, "fy") == 0 || strcmp(name, "fw") == 0 || strcmp(name, "fh") == 0) + return DATA_T_DOUBLE; + count = xaCount(&(widget->Properties)); for (i=0;iInteger = widget->fl_y; return 0; } if (!strcmp(name+3, "width")) { val->Integer = widget->fl_width; return 0; } if (!strcmp(name+3, "height")) { val->Integer = widget->fl_height; return 0; } + if (!strcmp(name+3, "parent_w")) { val->Integer = widget->fl_parent_w; return 0; } + if (!strcmp(name+3, "parent_h")) { val->Integer = widget->fl_parent_h; return 0; } + } + } + else if (datatype == DATA_T_DOUBLE) + { + if (strncmp(name, "fl_scale_", 9) == 0) + { + if (strcmp(name+9, "x") == 0) { val->Double = widget->fl_scale_x; return 0; } + else if (strcmp(name+9, "y") == 0) { val->Double = widget->fl_scale_y; return 0; } + else if (strcmp(name+9, "w") == 0) { val->Double = widget->fl_scale_w; return 0; } + else if (strcmp(name+9, "h") == 0) { val->Double = widget->fl_scale_h; return 0; } + } + else if (strncmp(name, "f", 1) == 0) + { + if (strcmp(name+1, "x") == 0) { val->Double = widget->fx; return 0; } + else if (strcmp(name+1, "y") == 0) { val->Double = widget->fy; return 0; } + else if (strcmp(name+1, "w") == 0) { val->Double = widget->fw; return 0; } + else if (strcmp(name+1, "h") == 0) { val->Double = widget->fh; return 0; } } } else if (datatype == DATA_T_STRING) @@ -1571,10 +1597,15 @@ wgtrNewNode( char* name, char* type, pObjSession s, node->fl_y = fly; node->fl_width = flwidth; node->fl_height = flheight; + node->fl_parent_h = -1; + node->fl_parent_w = -1; + node->fl_scale_x = 0.0; + node->fl_scale_y = 0.0; + node->fl_scale_w = 0.0; + node->fl_scale_h = 0.0; node->ObjSession = s; node->Parent = NULL; - node->min_height = 0; - node->min_width = 0; + node->min_height = node->min_width = 0; node->LayoutGrid = NULL; node->Root = node; /* this will change when it is added as a child */ node->DMPrivate = NULL; @@ -2086,7 +2117,6 @@ wgtrInitialize() wgtsbInitialize(); wgtspaneInitialize(); wgtspnrInitialize(); - wgtosmlInitialize(); wgttabInitialize(); wgttblInitialize(); wgttermInitialize(); @@ -2573,5 +2603,3 @@ wgtrGetNamespace(pWgtrNode widget) { return widget->Namespace; } - -