SubNtx
Posted: Tue Sep 09, 2008 12:23 pm
Hay algún antiguo usuario de SubNtx y Clipper ?
Tenemos curiosidad en realizar unas pruebas de velocidad, gracias
Tenemos curiosidad en realizar unas pruebas de velocidad, gracias
Code: Select all
REQUEST DBFCDX
function Main()
local nStart, cCondicion := "( 'ESM' $ Art->Art_Nom .or. 'ESM' $ Art->Art_Tam .or. 'ESM' $ Art->Art_Col ) .and. ( 'ROJO' $ Art->Art_Nom .or. 'ROJO' $ Art->Art_Tam .or. 'ROJO' $ Art->Art_Col )"
MsgInfo( File( "art.dbf" ) )
USE Art VIA "DBFCDX" ALIAS "Art"
MsgInfo( Alias() )
nStart = Seconds()
INDEX ON Art->Art_Nom TO "TEMP" FOR &cCondicion MEMORY
MsgInfo( ( Seconds() - nStart ) / 60 )
return nil
Code: Select all
local nStart, result_code, cCondicion := "ESM*ROJO"
CLEAR
USE Art
nStart = Seconds()
result_code = SubNtx( "Art_Nom.NTX", "Temp.NTX", "", "", "*"+cCondicion+"*" )
@1,1 SAY Str( ( Seconds() - nStart ) )
@2,1 SAY Str( result_code ) // muestra el número de registros copiados al subíndice generado por SubNtx()
INKEY( 0 )
SET INDEX TO Temp
DbEdit ( 5, 1, 12, 75, { "Art_Nom", "Art_Tam", "Art_Col" } )
CLOSE ALL
QUIT
Code: Select all
cCondicion := "( ( 'ESM' $ ( Art->Art_Nom + Art->Art_Tam + Art->Art_Col ) ) .and. ( 'ROJO' $ ( Art->Art_Nom + Art->Art_Tam + Art->Art_Col ) ) )"
Code: Select all
NTX file format
---------------
Version of this document: 2.0
by Cesar A. Gil, January 06, 1997
Introduction
The NTX file format is the format of index files used by
Clipper. It is much more efficient than NDX files, with respect to
performance and file size (usually more than twice the speed, and
only 80% of the size of an equivalent NDX file). The extension is also
different to indicate that its format is not the same as NDX.
The NTX file stores a B-tree. A B-tree consists of nodes pointing
to sub-nodes.
The B-tree allows you to traverse the nodes in a depth-first
order, thus allowing you to retrieve the keys in crescent order. It also
allows you to retrieve a given key or its closest-matching key with an
efficient algorithm, since you must only traverse a few nodes to
locate a specific record. However, as far as theory is concerned,
nothing beyond a diagram of a B-tree will be given here.
Updating an NTX file may be very difficult, since after a
change you must keep the B-tree balanced. However updating the B-tree
is a laborious task, the information given here may easily allow you
to read the NTX file.
Diagram of a B-tree
root node
-----------------------
| . | 50 | . | 60 | . |
-----------------------
| | |
+----------------+ | +-----------+
| | |
----------------------- ----------------------- --------------
| . | 10 | . | 20 | . | | . | 52 | . | 56 | . | | . | 62 | . |
----------------------- ----------------------- --------------
| | | | | | |
/// | /// /// /// /// ///
|
-----------------------
| . | 15 | . | 16 | . |
-----------------------
| | |
/// /// ///
In this diagram we have assumed that each node can hold up to 2 keys,
however a B-tree can be specified to have any maximum number of keys
per node (n >= 2). A node doesn't need to be used up to the maximum. For
example, in this diagram we have a node which is not fully used (the node
which only contains the value 62).
We also didn't care here to create a balanced tree, as would happen
in a real situation, since we are concerned about giving the reader
a feeling of how it is to traverse a B-tree. The diagram is also
intended to help visualize the structure of the NTX file.
Each node contains pointers between its key values. For example, look
at the root node. The pointer to the left of the value 50 points to a
sub-tree that contains every key lesser than 50. The pointer bewteen the
values 50 and 60 points to a sub-tree containing every value between 50
and 60. The pointer after the value 60 points to a sub-tree containing
all values greater than 60.
At some given time we will arrive at a point where there will be no more
branching. We indicate that by an empty pointer (represented here by
///). In NTX files, an empty pointer (that does not point to a
sub-tree) contains a 0.
Now it is very easy to see that, if you traverse the tree in a depth-
first search, you'll retrive all the key values in order. It's also
very easy to define an algorithm to locate a given key efficiently
in the tree. The difficult part is updating the B-tree, by inserting,
changing or removing records, because the B-tree must be kept balanced.
The algorithm for doing so is very complex and literature is available
on the subject.
Description of an NTX file
The NTX file consists of pages of 1K.
The first 1K page contains a header.
The other pages contain nodes of the B-tree. (Each 1K page will hold
a node).
Header structure:
Offset
00h unsigned signature - contains 09 if good NTX file
02h unsigned version - version of indexing system that the
file was created under
(0x1 is typical but may change)
04h long root - file offset (within the NTX file)
where the root page is.
08h long free_page - file offset (withing the NTX file)
where the first of a list of free
pages is. A 0 value ends the list.
The Clipper index system is intended
to reuse free pages.
0Ch unsigned item_size - the size of the item.
0Eh unsigned key_size - the size of the index key
10h unsigned key_decim - number of decimal positions for the
key, if it is a numeric key.
12h unsigned max_item - maximum number of items per page.
14h unsigned min_item - minimun number of items per page
(max_item/2 is typical)
16h char key_expr[256] - an array containing the key
expression for the index.
The expression must be terminated by
null (ASCII 0) and its length cannot
be greater than 256 bytes.
272h unsigned unique - contains the value of the UNIQUE
option for the index.
(1=index is UNIQUE; 0=isn't UNIQUE)
Comments:
(1) The size of the index key (key_size) is determined when you issue
the command
INDEX ON key_expr TO file.ntx
The key_expr is evaluated for each record of the data file.
The value returned by evaluating key_expr has its size calculated.
It is assumed that all the evaluations of key_expr will return
values of the same type and length.
If you define a key_expr that returns values of different lengths,
the Clipper indexing system will give unpredictable results and,
eventually, an error will happen.
An example (of what you shouldn't do) is:
INDEX ON TRIM(CLIENT_NAME) TO CLIENTS
Based on the determined key_size, Clipper will calculate how
many items a page can hold and will thus determine max_items
and min_items. Remember, each item holds a key value, whose size
depends on the key_size. That's why the item size is dynamically
determined.
Node structure:
Offset
00h unsigned item_count - how many items this particular
page holds.
02h unsigned item_offset[item_count] - holds the offsets, within
the page, where the items are located.
?? item area
The size of each item is: 8 + key_size.
The last item doesn't contain a key value, only a pointer
to a sub-tree.
Each item has the following structure:
long page_offset - file offset (within NTX file) where
sub-tree is. If there's no subtree,
a 0 is stored.
long recno - record number for this key value
(in the DBF file)
char key_value[key_size]
For example, the node:
-----------------------
| . | 50 | . | 60 | . |
-----------------------
| | |
to page to page to page
at at at
offset x offset y offset z
Will be represented in a page containing:
Offset
00h unsigned item_count = 3
02h unsigned item_offset[0] = 08h
04h unsigned item_offset[1] = 12h
06h unsigned item_offset[2] = 1Ch
08h item[0] :
long page_offset : x
long recno : DBF record number pointed by
key value 50
char key_value[2] : '50'
12h item[1] :
long page_offset : y
long recno : DBF record number pointed by
key value 60
char key_value[2] : '60'
1Ch item[2] :
long page_offset : z
Conclusion:
The NTX file structure is easy to access. The main concern is about
the fact that each NTX file will have items of different sizes and
pages with different number of items.
Source code example - NTXVIEW.C :
Author : Cesar A. Gil
Date : January 06, 1997
Language : C
Use : NTXVIEW file.ntx
Description: lists the contents of the ntx file's header, then
traverses all the tree in depth-first order, listing all
the keys in order.
---------------------------------------------------------------
/*
* ntxdump.h
*/
#define MAX_KEY 256
#define BUF_SIZE 1024
typedef struct {
unsigned type;
unsigned version;
long root;
long next_page;
unsigned item_size;
unsigned key_size;
unsigned key_dec;
unsigned max_item;
unsigned half_page;
char key_expr[MAX_KEY];
char unique;
} NTX_HEADER;
typedef struct
{
long page;
long rec_no;
char key[1];
} NTX_ITEM;
typedef struct
{
unsigned item_count;
unsigned item_offset[1];
} NTX_BUFFER;
---------------------------------------------------------------
/*
* ntxview.c
*/
#include "stdio.h"
#include "stdlib.h"
#include "fcntl.h"
#include "io.h"
#include "ntxview.h"
NTX_HEADER ntx_header;
int ntx_handle;
void view_page(long page_offset);
void main(int argc, char ** argv)
{
if( argc != 2 ) {
printf( "Use: ntxhead arquivo.ntx\n" );
exit(1);
}
ntx_handle = open(argv[1], 0x8000);
if( ntx_handle == -1 ) {
printf( "Open error: %s\n", argv[1] );
exit(1);
}
/* Reads header */
if( read(ntx_handle, &ntx_header, sizeof(NTX_HEADER))
!= sizeof(NTX_HEADER) ) {
printf( "Error reading header of %s\n", argv[1] );
exit(1);
}
/* Prints header information */
printf( "***** NTX header *****'n" );
printf( "Signature %xh\n", ntx_header.type );
printf( "Version %xh\n", ntx_header.version );
printf( "Root %lxh\n", ntx_header.root );
printf( "Next page %xh\n", ntx_header.next_page );
printf( "Item size %xh\n", ntx_header.item_size );
printf( "Key size %xh\n", ntx_header.key_size );
printf( "Key decimals %xh\n", ntx_header.key_size );
printf( "Max item %xh\n", ntx_header.half_page );
printf( "Key expr: '%s'\n", ntx_header.key_expr );
printf( "Unique: %d\n", ntx_header.unique );
view_page(ntx_header.root);
close(ntx_handle);
}
void view_page(long page_offset)
{
char * page;
NTX_BUFFER * buffer;
NTX_ITEM * item;
unsigned i;
/* Allocates page */
page = malloc(BUF_SIZE);
buffer = (NTX_BUFFER *) page;
if( page == NULL ) {
printf( "Error allocating memory.\n" );
exit(1);
}
/* Moves to the given page offset within the file */
lseek(ntx_handle, page_offset, 0);
/* Reads page */
if( read(ntx_handle, buffer, BUF_SIZE) != BUF_SIZE ) {
printf( "Error reading page %lxh\n", page_offset );
exit(1);
}
/* Traverses the vector, item by item */
for( i = 0; i < buffer->item_count; i ++ ) {
item = (NTX_ITEM *) (page + buffer->item_offset[i]);
if( item->page != 0 )
view_page(item->page);
printf("dbf recnum: %5lx Key: %.*s\n", item->rec_no,
ntx_header.key_size, &item->key);
}
/* Handles last pointer */
item = (NTX_ITEM *) (page + buffer->item_offset[buffer->item_count]);
if( item->page )
view_page(item->page);
/* Deallocates page */
free(page);
}
---------------------------------------------------------------
- end of document -
Code: Select all
SET PATH=c:\bcc55\bin
c:\bcc55\bin\bcc32 ntxview.c
Code: Select all
#define MAX_KEY 256
#define BUF_SIZE 1024
typedef struct {
unsigned short int type;
unsigned short int version;
long root;
long next_page;
unsigned short int item_size;
unsigned short int key_size;
unsigned short int key_dec;
unsigned short int max_item;
unsigned short int half_page;
char key_expr[MAX_KEY];
char unique;
} NTX_HEADER;
typedef struct
{
long page;
long rec_no;
char key[1];
} NTX_ITEM;
typedef struct
{
unsigned short item_count;
unsigned short item_offset[1];
} NTX_BUFFER;
#include "stdio.h"
#include "stdlib.h"
#include "fcntl.h"
#include "io.h"
NTX_HEADER ntx_header;
int ntx_handle;
void view_page(long page_offset);
void main(int argc, char ** argv)
{
if( argc != 2 ) {
printf( "Use: ntxhead arquivo.ntx\n" );
exit(1);
}
ntx_handle = open(argv[1], 0x8000);
if( ntx_handle == -1 ) {
printf( "Open error: %s\n", argv[1] );
exit(1);
}
/* Reads header */
if( read(ntx_handle, &ntx_header, sizeof(NTX_HEADER))
!= sizeof(NTX_HEADER) ) {
printf( "Error reading header of %s\n", argv[1] );
exit(1);
}
/* Prints header information */
printf( "***** NTX header *****\n" );
printf( "Signature %xh\n", ntx_header.type );
printf( "Version %xh\n", ntx_header.version );
printf( "Root %lxh\n", ntx_header.root );
printf( "Next page %xh\n", ntx_header.next_page );
printf( "Item size %xh\n", ntx_header.item_size );
printf( "Key size %xh\n", ntx_header.key_size );
printf( "Key decimals %xh\n", ntx_header.key_dec );
printf( "Max item %xh\n", ntx_header.half_page );
printf( "Key expr: '%s'\n", ntx_header.key_expr );
printf( "Unique: %d\n", ntx_header.unique );
view_page(ntx_header.root);
close(ntx_handle);
}
void view_page(long page_offset)
{
char * page;
NTX_BUFFER * buffer;
NTX_ITEM * item;
unsigned char i;
/* Allocates page */
page = malloc(BUF_SIZE);
buffer = (NTX_BUFFER *) page;
printf( "view_page\n" );
if( page == NULL ) {
printf( "Error allocating memory.\n" );
exit(1);
}
/* Moves to the given page offset within the file */
lseek(ntx_handle, page_offset, 0);
/* Reads page */
if( read(ntx_handle, buffer, BUF_SIZE) != BUF_SIZE ) {
printf( "Error reading page %lxh\n", page_offset );
exit(1);
}
printf( "items: %i\n", buffer->item_count );
/* Traverses the vector, item by item */
for( i = 0; i < buffer->item_count; i ++ )
{
item = (NTX_ITEM *) (page + buffer->item_offset[i]);
if( item->page != 0 )
view_page(item->page);
printf( "%7i recno: %3i Key: %s\n", i, item->rec_no, &item->key);
}
/* Handles last pointer */
item = (NTX_ITEM *) (page + buffer->item_offset[buffer->item_count]);
if( item->page )
view_page(item->page);
/* Deallocates page */
free(page);
}
Code: Select all
#define MAX_KEY 256
#define BUF_SIZE 1024
typedef struct {
unsigned short int type;
unsigned short int version;
long root;
long next_page;
unsigned short int item_size;
unsigned short int key_size;
unsigned short int key_dec;
unsigned short int max_item;
unsigned short int half_page;
char key_expr[MAX_KEY];
char unique;
} NTX_HEADER;
typedef struct
{
long page;
long rec_no;
char key[1];
} NTX_ITEM;
typedef struct
{
unsigned short item_count;
unsigned short item_offset[1];
} NTX_BUFFER;
#include "stdio.h"
#include "stdlib.h"
#include "fcntl.h"
#include "io.h"
NTX_HEADER ntx_header;
int ntx_handle;
void view_page(long page_offset);
void main(int argc, char ** argv)
{
if( argc != 2 )
{
printf( "Use: ntxhead arquivo.ntx\n" );
exit(1);
}
ntx_handle = open( argv[1], 0x8000 );
if( ntx_handle == -1 )
{
printf( "Open error: %s\n", argv[1] );
exit(1);
}
/* Reads header */
if( read(ntx_handle, &ntx_header, sizeof(NTX_HEADER) ) != sizeof(NTX_HEADER) )
{
printf( "Error reading header of %s\n", argv[1] );
exit(1);
}
/* Prints header information */
printf( "***** NTX header *****\n" );
printf( "Signature %xh\n", ntx_header.type );
printf( "Version %xh\n", ntx_header.version );
printf( "Root %lxh\n", ntx_header.root );
printf( "Next page %xh\n", ntx_header.next_page );
printf( "Item size %xh\n", ntx_header.item_size );
printf( "Key size %xh\n", ntx_header.key_size );
printf( "Key decimals %xh\n", ntx_header.key_dec );
printf( "Max item %xh\n", ntx_header.half_page );
printf( "Key expr: '%s'\n", ntx_header.key_expr );
printf( "Unique: %d\n", ntx_header.unique );
view_page( ntx_header.root );
close(ntx_handle);
}
void view_page( long page_offset )
{
NTX_ITEM * item;
unsigned char i;
char page[ BUF_SIZE ];
NTX_BUFFER * buffer = (NTX_BUFFER *) page;
printf( "view_page\n" );
/* Moves to the given page offset within the file */
lseek(ntx_handle, page_offset, 0);
/* Reads page */
if( read( ntx_handle, buffer, BUF_SIZE ) != BUF_SIZE )
{
printf( "Error reading page %lxh\n", page_offset );
exit(1);
}
printf( "items: %i\n", buffer->item_count );
/* Traverses the vector, item by item */
for( i = 0; i < buffer->item_count; i ++ )
{
item = (NTX_ITEM *) (page + buffer->item_offset[i]);
if( item->page != 0 )
view_page(item->page);
printf( "%7i recno: %3i Key: %s\n", i, item->rec_no, &item->key);
}
/* Handles last pointer */
item = (NTX_ITEM *) (page + buffer->item_offset[buffer->item_count]);
if( item->page )
view_page(item->page);
}
Code: Select all
// Sample showing how to use SUBNTX with NTX indexes
#include "FiveWin.ch"
static oWnd, cTempo1, cTempo2, cName := "COMERC"
//----------------------------------------------------------------------------//
function Main()
DEFINE WINDOW oWnd FROM 1, 1 TO 20, 60 ;
TITLE "Sistema de Faturamento"
SET MESSAGE OF oWnd TO OemToAnsi( "5volution.com - (r) Jose Carlos da Rocha, 2004 - Brasil" )
ACTIVATE WINDOW oWnd ;
ON INIT ShowDlg()
return nil
//----------------------------------------------------------------------------//
function ShowDlg()
IF ! FILE("CLIENTES.DBF")
msgrun("CLIENTES.DBF nao encontrado!")
QUIT
ENDIF
USE CLIENTES
IF lastrec() < 75
EXPAND_DBF()
ENDIF
IF ! FILE("SUBDEMO.NTX")
//msgrun("WAIT. Creating file SUBDEMO.NTX...")
MsgMeter( { |oMeter,oText,oDlg,lEnd| ;
MakeIndex( oMeter, oText, oDlg, @lEnd, "CLIENTES->NOME+CLIENTES->ESTADO", "SUBDEMO.NTX", "" ) },;
"Creating index SUBDEMO.NTX..." )
ENDIF
msgwait("Primeiro. Mostro todo database...")
browse()
msgwait("Agora. Usando um simples SEEK...")
set index to SUBDEMO
go top
seek "5VOLUTION"
browse()
msgwait("Agora. Usando um simples SET FILTER...")
go top
seek "5vol"
msgwait("Observacao. Quando mover o cursor alem do browse o cursor se perde por muito tempo...")
msgwait("((provavelmente mais de um minuto)")
cTempo1 := time()
set filter to CLIENTES->NOME = "5VOLUTION"
go top
cTempo2 := time()
MsgAlert("Tempo de Operacao: "+tempo( cTempo1,cTempo2 )+" segundo(s)" )
browse()
set filter to
msgwait("E AGORA. Usando SUBNTX...")
nString := 1
do while .t. //cName <> "exit"
if MsgGet( "Digite uma frase:",; // Title
"Nome:",; // Label
@cName ) // A variable by reference
set index to
cTempo1 := time()
//
num = subntx( "SUBDEMO.NTX", "_SUB.NTX", cName )
//
cTempo2 := time()
if ! FILE("_SUB.NTX")
msgwait("Nenhum filtro foi gerador...")
else
set index to _sub
//MsgAlert("Tempo de Operacao: "+tempo( cTempo1,cTempo2 )+" segundo(s)" )
browse()
endif
nString := nString + 1
do case
case nString = 2
cName := "SA"
case nString = 3
cName := "JOS"
case nString = 4
cName := "W"
case nString = 5
cName := "BE"
case nString = 6
cName := "PANI"
otherwise
msgwait("Eu gosto disto. Obrigado...")
quit // exit
endcase
set index to
erase _sub.ntx
else
exit
endif
enddo
close databases
return nil
//----------------------------------------------------------------------------//
function EXPAND_DBF
msgrun("STAND BY while expanding CLIENTES.DBF...")
ERASE SUBDEMO.NTX
COPY TO _SUBTMP
FOR i = 1 to 49
APPEND FROM _SUBTMP
NEXT
ERASE _SUBTMP.DBF
REPLACE ALL REC WITH RECNO()
return nil
//----------------------------------------------------------------------------//
function tempo( inicio, final )
return str(val(strtran(substr(final,4,5),":",""))-val(strtran(substr(inicio,4,5),":","")),6)
//----------------------------------------------------------------------------//
function MakeIndex( oMeter, oText, oDlg, lEnd, cChave, cIndex, cFor )
FIELD Last
oMeter:nTotal = RecCount()
IF EMPTY( cFor )
INDEX ON &(cChave) TO &(cIndex) ;
EVAL ( oMeter:Set( RecNo() ), SysRefresh(), ! lEnd )
ELSE
INDEX ON &(cChave) TO &(cIndex) FOR &(cFor) ;
EVAL ( oMeter:Set( RecNo() ), SysRefresh(), ! lEnd )
ENDIF
return nil
Code: Select all
//-----------------------------------------------
FUNCTION WildSrch ( cNTXFile, cSrchArg, nMaxHits )
//-----------------------------------------------
/*
parameters : cNTXFile : drive:\path\filename.ext
(the NTX-File, to be scanned)
cSrchArg : character-string with the search-argument
nMaxHits : If the result-array is as long as <nMaxHits>
the function returns.
If nMaxHits = NIL, nMaxHits is set to 4,096
(Array is full..)
returns : aResult : an array with the record-numbers who
matched <cSrchArg>
*/
// Search Result
LOCAL aResult := { } // array to hold the record-numbers
// For Low-Level-Services
LOCAL nBlock := 1024 // Size of each index-page
LOCAL cBuffer := Space( nBlock ) // Buffer for FREAD
LOCAL nHandle := - 1 // file-handle
LOCAL nLen := 0 // length of index-file
LOCAL nPages := 0 // index-pages (excl. header)
// Index-Header (offset from 0, substr from 1 -> always (+1) )
LOCAL nPtrItemSize := 13 // h0C -> Pointer to ItemSize (keysize+8)
LOCAL nPtrKeySize := 15 // h0E -> Pointer to KeySize
LOCAL nPtrMaxItem := 19 // h12 -> Pointer to max. items on each page
LOCAL nLenItem := 0 // length of each item
LOCAL nLenKey := 0 // length of each key
LOCAL nItemMax := 0 // max. items on each page
// IndexPage (offset from 0, substr form 1 -> always (+1) )
LOCAL nPtrItemPage := 1 // h00 -> Pointer to Items on this page
LOCAL nItemCnt := 0 // items on this page
LOCAL x := 0, y := 0 // FOR-loop indices
LOCAL nPtrItem := 0 // pointer to current item
LOCAL nItemOffset := 0 // offset of current item
LOCAL cKey := "" // key-value of current item
LOCAL nRecord := 0 // recno of current item (inside the DBF-File)
// Check nMaxHits and Set Break
LOCAL lBreak := .F. // Break-Condition
nMaxHits := IF( nMaxHits == NIL, 4096, nMaxHits )
// Let's do..
// FileOpen
nHandle := FOpen( cNTXFile, FO_READ + FO_SHARED )
IF nHandle == - 1
MsgAlert( 'Error ' + Str( FError(), 2 ) + ' no pudo abrise el fichero ' + cNTXFile )
RETURN { }
ENDIF
// Calculate how many pages excl. header-page
nLen := FSeek( nHandle, 0, FS_END )
nPages := Int( nLen / nBlock ) - 1
FSeek( nHandle, 0 )
// Read Header and get some information
FRead( nHandle, @cBuffer, nBlock )
nLenItem := Bin2W( SubStr( cBuffer, nPtrItemSize, 2 ) )
nLenKey := Bin2W( SubStr( cBuffer, nPtrKeySize, 2 ) )
nItemMax := Bin2W( SubStr( cBuffer, nPtrMaxItem, 2 ) )
//Now Read all Index-Pages and check for Search-Value
FOR x := 1 TO nPages
FRead( nHandle, @cBuffer, nBlock )
// check how many items on this page
nItemCnt := Bin2W( SubStr( cBuffer, nPtrItemPage, 2 ) )
// check all items on this page
FOR y := 1 TO nItemCnt
// Page-Offset to read Item-Offset
nPtrItem := ( y * 2 ) + 1
// Read Item-Offset
nItemOffset := Bin2W( SubStr( cBuffer, nPtrItem, 2 ) ) + 1
// Check current item
cKey := SubStr( cBuffer, nItemOffset + 8, nLenKey )
IF At( cSrchArg, cKey ) != 0 // SearchArgument found
// Read recno from index-page (beginning at <nItemOffset+4>)
nRecord := Bin2L( SubStr( cBuffer, nItemOffset + 4, 4 ) )
// Add recno to result-array
AAdd( aResult, nRecord )
// Check nMaxHits
IF Len( aResult ) >= nMaxHits
lBreak := .T.
EXIT
ENDIF
ENDIF
NEXT y
IF lBreak
EXIT
ENDIF
NEXT x
// close NTX-File
FClose( nHandle )
RETURN( aResult ) // EOF WildSrch