Introduction

Collections allows three main types of variables: local variables, database variables and reserved variables.

Local variables must be declared before using them and they are only available within an ADAPL program: they can be used as counters or to contain and manipulate string (text) values for example. Names of local variables can be from 1 up to 32 characters long. They consist of a series of letters and digits and may include underscores ('_'), but no other punctuation marks. The first character of a variable name must be an underscore or a letter, and may therefore not be numeric. ADAPL is case-sensitive when it comes to names of variables. The variable Counter is therefore not the same as the variable counter. Local variables are defined by the ADAPL programmer using declaration instructions, like so for example:
 
integer occ, i, j
numeric mywidth, myheight
text myobjectname[100]
 
See also: Declaring local variables and Data types.
Database variables refer not to a declared piece of memory but to a field tag in the currently opened or processed record. An adapl is always executed per opened or marked record, at which point it automatically has access to field data from that record only in these database variables. Field tags must not be declared: they exist implicitly. So a simple assignment like the following would put the first occurence of the content of field tag (aka database variable) OB in text variable myobjectname (as declared in the example above):
 
myobjectname = OB[1]
 
If the adapl is executed outside of a Collections application, you'll have to use FACS to access database variables.
Reserved variables are variables that have been predefined by Axiell developers. These variables can be retrieved, and sometimes filled, but their meaning cannot be changed. See below for a list of all available reserved variables. You can use them for a variety of purposes, but a common use is to execute a block of code only after a certain action by the user: a single adapl may have been set up to be executed at different stages during work in Collections, like before input, before opening of a certain screen tab, during copying and before storage of a record. Since you probably don't want the same code to be executed in all of these circumstances, you can enclose each block of code by an appropriate if (&1 = ...) {...} statement, where &1 is the relevant system variable. With if (&1 = 11) {...}, for example, you're actually saying: only execute the code between { and } if this adapl is currently being executed as a before-storage adapl. You could open the collect.ada file from you \adapls folder in a text editor, for example, and search for all appearances of &1 to find out how this system variable is used in practice. (When an adapl is actually executed is set up elsewhere, like on the Procedures and extensions properties tab of a selected .inf file in the Application browser.)

All variables are so-called "global" variables, meaning that they are available anywhere in an ADAPL program (including sub routines and included .inc files) and retain their value in all instruction blocks.

In addition, FACS variables can be declared for the FACS sub system. As far as naming is concerned, these are treated as local variables, but have the characteristics of database variables. They are used to access field data from other records than the currently processed one, even from different database tables.

More on database variables

Collections considers all non-declared variables of 2 characters (beginning with a letter) as database variables. Database variables must have been defined in the data dictionary of a database table (the .inf file).
Note that although you can store multilingual data in temporary tags (via an import job which maps source tags to temporary tags, for example), with ADAPL you cannot access a temporary tag as if it is a multilingual field: if you try to retrieve any value from such a field, even if you specify a data language (see the second paragraph below), you will only obtain the last stored value. To avoid this problem you'll have to specify these temporary tags as proper, plain multilingual fields in the relevant database definition anyway (and if you assign those fields the Temporary data type, they won't ever be stored in records, even if you forget to empty them at the end of the adapl code).

Any value obtained from a database variable is considered a string. This is relevant for value comparisons, assigning tag values to local variables, numeric functions and arithmetic expressions. Therefore, values obtained from database tags which you want to treat as numbers, must always first be converted from strings to numerics (with VAL) and then possibly to integers (using INT) before they can be compared as numbers or be used in calculations. For example, if numvar1 is an integer variable and x1 and x2 are two tags containing integer values (which you'll extract as strings) you can add them like so: numvar1 = (int(val(x1)) + int(val(x2))).

A database tag may have up to 32.767 occurrences. A field occurrence is a repetition of the field, containing a different value. So, if for instance the Author field is a repeatable field, then the user can enter more than one author in this field, each author in its own field occurrence. In ADAPL, you can address specific field occurrences using the syntax: <tag>[<occ>], so to address the second occurrence of the Author field and obtain its data, we would write: au[2]. Occurrence numbers are 1-based, meaning that the first occurrence has the number 1.

Multilingual fields: in SQL databases, fields can be set up to be multilingual. This means that every field occurrence may contain values in multiple translations. (The Collections user normally only sees one translation at a time, depending on the currently chosen data language.) Such multilingual field occurrences can be addressed (for reading and writing of data) in ADAPL. The syntax for addressing one particular language value in a field occurrence is: <tag>[<occ>, 'language code'], for example: te[1, 'en-US'] to address the English-US translation of the first occurrence of a multilingual Term field. (The language code string may be a text variable as well.) The occurrence number is mandatory. An example of a value assignment would be: te[1, 'en-US'] = te[1, 'en-GB']. If you also want to make the target language the invariant language, you can do so by inserting a hash character in front of the target language code, for example: BA[1, '#fr-FR'] = BA[1, 'fr-FR'] to set the French language value as the invariant one. If, when reading, the desired occurrence is empty whilst the same occurrence does have a value in a different language that happens to be the invariant value, then that value is retrieved instead. If you'd like to know if a multilingual field is empty (has no values in any language), just write your code like the field is not multilingual at all, for example: if (te[1] = '') {...}. And to count the total number of occurrences of multilingual fields up to and including the last occurrence that has a value in at least one data language, simply use something like: myvariable = repcnt(YB): any empty occurrences in between will be counted as well. Note that temporary tags (which have not been defined in the data dictionary of the database) containing multilingual data cannot be addressed as multilingual fields in adapl until you define those tags as proper, plain multilingual fields in the database definition still.
Also note that a simple tag assignment (without specifying any language) from one multilingual field to another won't work: you'll have to use the copydata function to copy all language values from one tag to another.
After you've changed a previously uniligual field into a multilingual field and filled it with multilingual data, you may still not have to change any adapls or output formats. Although existing adapls won't use any language codes, Collections will provide the value from the currently set data language when no language is specified and when you assign a value to a multilingual field without specifying the language, the value will get the current data language automatically and it will leave the values in any other languages intact. The only minor concern could be that an existing READ <FACS name> using <FACS variable> = <value> searches the value in all data languages, not just the current data language, but most FACS READ statements in adapls will search on a unique (unilingual) identifier anyway, so the impact is probably small.

Available reserved variables

Reserved variables are reserved tags that start with ‘&’. They are not declared.

The information in reserved tags is passed on to ADAPL by Axiell Collections. The table below contains a list of reserved tags and the information they provide.

Tag

Type

Information

&0

numeric

Number of the current record. If you use this variable in a derival procedure (such an adapl is executed before and after derival), then before derival &0 will contain the record number of the source record, while after derival it will contain the record number of the target record (it this is an existing record). If you started to derive a source record into a new record (not an existing one) then after derival &0 will contain the number 0; the new record has not been stored yet so no actual record number is available.

&0[2]

numeric

Number of the current source record in case this variable is used in a derival procedure. If you use this variable in a derival procedure (such an adapl is executed before and after derival), then before and after derival &0[2] will contain the record number of the source record.

&0[3]

numeric

Number of the current target record in case this variable is used in a derival procedure. If you use this variable in a derival procedure (such an adapl is executed before and after derival), then before and after derival &0[3] will contain the record number of the target record (it this is an existing record). If you started to derive a source record into a new record (not an existing one) then before and after derival &0[3] will contain the number 0; the new record has not been stored yet so no actual record number is available.

&0[4]

numeric

If the notifications functionality has been implemented and a task is started from the Action button in a notification on the home screen, then &0[4] will contain the record number of the notifications record responsible for that notification. It allows the task adapl to identify the notifications record, to find it and edit it.

&1

numeric

Execution code (See next table: ‘Execution codes &1’)

&1[2]

numeric

Execution sub codes (See the 'Execution sub codes &1[2]' table below). This reserved variable provides more information about the circumstances under which certain execution codes (&1) were generated.
For Axiell Collections, only a selection of &1[2] codes has been implemented, namely 4, 5, 9 and 10. .

&1[3]

numeric

When the manual saving of an edited record causes other records to be automatically edited and saved as well, for instance because a storage adapl uses FACS to edit and store some other records or when you've edited some write-back fields for a linked field (which must be written to the relevant linked record), then in ADAPL you'd sometimes like to know which storage iteration you're dealing with, especially when you're writing data to an internally linked record via a storage adapl: then you have the problem that the internal link functionality itself wants to write data back and forth while your storage adapl is executed for both records too. &1[3] then comes in handy because it contains the iteration of the currently processed save. For the manually stored record this value is 1, for a second automatically stored record (still before storage of the first is completely finished) the value is 2, for a third 3, etc. And when the storage procedure climbs up again from saving record 3 or 2 to finally really store the first record, the &1[3] value returns to 1. For an internal link (a link to a record in the same database), for example, any storage adapl set for this database will be executed four times: 1. before storage of the main record (&1[3] = 1); 2. before storage of the internally linked record (&1[3] = 2); 3. after storage of the internally linked record (&1[3] = 2); 4. after storage of the main record (&1[3] = 1). (You can use &1 to distinguish between before and after storage.) Now the &1[3] value allows you to specify exactly when some piece of ADAPL code must be executed.

&1[4]

numeric

Identifies the process that started a before-storage adapl.
In a before-storage adapl it is sometimes handy to know which process caused the execution of the adapl, because you'd like certain code to be executed for one process but not for another. Although the adapl is associated with an .inf (database table specification), there might be different reasons for its execution: the WebAPI may have written a record in the database or the Move app did, or an import process has inserted new records, for example.
Therefore the &1[4] reserved variable has been introduced. In a before-storage adapl this will contain a number ranging from 0 to 6 to indicate which process caused the adapl execution:

0 - This is the default value. It's the generic value for Axiell and Adlib products.

1 - Axiell Collections (from version 1.13)

2 - Axiell WebAPI (from 3.1.1.1298)

3 - Axiell Move server (from 3.0.2287.1)

4 - Axiell Migration (from 3.0.2287.1)

5 - Axiell SDK (from version 1.10.1.1329)

6 - Import jobs started from Axiell Collections (from version 1.13)

&1[5]

numeric

This variable (available from Collections 2.0) is 1 if the adapl is being run during a Collections import job Test run. Once the virtually imported records are actually saved (if the Save after successful test checkbox has been marked) and the storage adapl for the .inf is run for real - the test run execution didn't make any real changes to the record and didn't do any FACS write actions - the variable will become 0, which is also the default value.
You can use the variable to skip or replace FACS write actions in a neat way or to write error handling which is only executed during one of both stages in a Save after successful test import run.

&4[0]

text

The current screen label

&4[1]

text[8]

Name of current screen file

&4[2]

text[2]

Current tag

&4[3]

text[10]

Number of current occurrence as a string. This also works for an empty last occurrence.

&5

numeric

Adloan.exe current transaction code. Adloan.exe is deprecated software.

&6[1]

text[80]

Name of database table in which the main record is located that the user has opened.
If, from within some main record, the user opens a zoom screen to a record in another database table, then this zoomed record becomes the new main record that the user has opened.

&6[2]

text[80]

Name of dataset in which the main record is located that the user has opened.
If, from within some main record, the user opens a zoom screen to a record in another database table, then this zoomed record becomes the new main record that the user has opened.
Names are case-sensitive.

&6[3]

text[80]

Path name of the dataset in the database table in which the main record is located that the user has opened. For example: collect>museum
If, from within some main record, the user opens a zoom screen to a record in another database table, then this zoomed record becomes the new main record that the user has opened.

 

 

A problem with the above described three variables is that when during editing or saving of the main record another record is implicitly created or updated in a linked database table (through a linked field in the main record or adapl), these variables still contain values related to the main record.
So if you would like to call up properties of the database table in which the system is working when some record not explicitly opened by the user is being updated or created, to use, for instance, the name of the linked database table to fill an automatic field or as a condition for executing certain adapl code, you need the three variables described below.

&6[4]

text[80]

Name of database table that the system has opened.

&6[5]

text[80]

Name of dataset that the system has opened. Names are case-sensitive.

&6[6]

text[80]

Path name of the dataset in the database table that the system has opened. For example: collect>museum

&6[7]

text[80]

The English name of the data source the user is currently working in. The name and its exact spelling will be obtained from the application definition file (.pbk) of the currently run application. That means &6[7] cannot be used in stand-alone adapls because then no .pbk file will be available.
Note that all database tables and datasets listed in the Select data source window in Collections are called data sources. Data source names have been set in the .pbk while database table names and dataset names have been set in their own database table definition files (.inf).

&B[1]

numeric

Number of records in selection.

&B[2]

numeric

Total number of hits.

&B[3]

numeric

This variable can only be used with (the now obsolete) wwwopac.exe. See the wwwopac reference guide.

&B[4]

numeric

This variable can only be used with (the now obsolete) wwwopac.exe. See the wwwopac reference guide.

&B[5]

numeric

This variable can only be used with (the now obsolete) wwwopac.exe. See the wwwopac reference guide.

&C[1]

text[64]

In Axiell Collections: the full executed search statement (regardless of the search method)

&C[2]

text[80]

No function in Collections.

&C[3]

text[80]

No function in Collections.

&C[4]

text[80]

No function in Collections.

&C[5]

text[80]

No function in Collections.

&D

numeric

No function in Collections.

&E

numeric

FACS result code; Exit code

&F

text[2]

No function in Collections.

&G

numeric

No function in Collections.

&H[n]

text[n]

Contains an unlimited number of command-line arguments. &H makes it possible to invoke the program with one or more arguments. (runadapl.exe adapl_name argument) &H[1]is filled with the name of the adapl, &H[2]with the first argument, &H[3] with the second argument, etc.

&I

numeric

In a print adapl, a task adapl or advanced search adapl* (not in import adapls), the serial number (not the record number) of the currently printed record in the selection of marked records.

* In a Select (advanced search) adapl (from Adlib 7.2.15027.1), the serial number (not the record number) of the currently found record in the growing search result of the executed search statement. Advanced search adapls are not supported in Axiell Collections.

&L

numeric

In a print adapl, the line number of the next line to be printed in the printout.

&P

numeric

Number of active interface language (0 is default, 1 is language1, etc.)

&S

numeric

Page count in print adapl

&T[1]

text

Date on which record is edited

&T[2]

text

Time at which record is edited

&T[3]

text

Date on which record is created

&T[4]

text

Time at which record is created

&V

text

During the execution of an update import in Collections (the Update tag option in the import job must have been filled), this variable will contain the record number of any currently updated record. On the other hand, if from the exchange file a record is being processed which is currently being added to the database as a new record (because there's no existing record to update), then the variable will be empty. You can use the variable in an adapl associated with the import job, to distinguish between updated records and new records.

&W

text

In ADAPL you'd sometimes like to know how many hits a certain FACS READ operation has generated. That number is  available from Collections 1.12 in the &W reserved variable. The variable is not influenced by a READ NEXT and it doesn't need to be called directly after the READ, but some time later is fine too. The number in &W always pertains to the last executed READ WHERE, READ USING or READ POINTER. The number is stored in the variable as a string, not as an integer, so if you want to do a calculation with the number, you must convert it to an integer first, using int(val(&W)).

ADAPL execution codes that can occur in the reserved variable &1:

Code

Meaning

0

(Reserved)

1

Before screen adapl called before a record is displayed, either in Display, Read-only or Edit mode.

2

After screen adapl (not supported in Collections).

3

After screen adapl (not supported in Collections).

4

Before screen adapl or Edit record procedure called before editing a record. See &1[2] to determine which one was called.

5

(Reserved)

6

(Reserved)

7

Adapl executed as stand-alone adapl with the now obsolete ADEVAL. Not supported by RunAdapl.exe.

8

Adapl (output format adapl) called from print function.

9

Before screen adapl or Input record procedure called before input of a new record. See &1[2] to determine which one was called.

10

(Reserved)

11

Storage procedure called before storage of a record.

12

Storage procedure called before deletion of a record.

13

Not supported in Collections.

14

Storage procedure called after storage of a record.

15

Storage procedure called after deletion of a record.

16

Before screen adapl or Copy record procedure called after copying of a record. See &1[2] to determine which one was called.

17

Not supported in Collections.

18

Not supported in Collections.

19

Not supported in Collections.

20

Before screen adapl or Field based procedure called before display of a field: when entering a field in edit mode of record. See &1[2] to determine which one was called.

21

Before screen adapl or Field based procedure called after input of a field: when leaving a field in edit mode, irrespective of whether the contents of the field have been changed. See &1[2] to determine which one was called.
An after-field adapl will run two or three times instead of just a single time. The first time the adapl is executed &1=21, the second and possibly third time &1=28. This is relevant for linked fields: the first time the adapl is executed, Collections has not attempted to resolve the link yet.

22

Not supported in Collections.

23

After retrieval procedure called after a record has been retrieved. This may happen in a multitude of circumstances, not only for the detailed display of a record for instance.

24

After derival procedure (ignore the After part) called after deriving a record. To avoid this procedure from being executed twice (before and after derival), check the execution sub code before executing ADAPL program code.

26

After derival procedure (ignore the After part) called before deriving a record. To avoid this procedure from being executed twice (before and after derival), check the execution sub code before executing ADAPL program code.

27

After read adapl called during an import job after reading in a record from the exchange file and having completed the field mapping, but before any update process has been set in motion (if the Update tag option has been set) and thus also before writing the record to the target database table.

28

Field based procedure called after input of a field: when leaving a field in edit mode, irrespective of whether the contents of the field have been changed. For the screen field, the Run after field adapl option must have been set.
An after-field adapl will run two or three times instead of just a single time. The first time the adapl is executed &1=21, the second and possibly third time &1=28. This is relevant for linked fields: the first time the adapl is executed, Collections has not attempted to resolve the link yet, so you can't be sure a preferred term has been entered nor is a record number of the record to be linked available in the link reference field yet. So you have no validated data to work with yet. The second and third run will be executed after the one or two attempts at link resolving. You can be sure your linked data is valid if &1=28 and &1[2]=1 (a preferred term was entered) or if &1=28 and &1[2]=0 and &1[3]=0 (the field was left empty). Only if &1[3]>0 (for &1=28) at some point, you know the adapl is executed for the second time, after link resolving of a directly entered term failed (and the Find data for the field window opens automatically). &1[3]=0 (for &1=28) means either that the field was left empty, that a correct preferred term was entered or that a preferred term was selected from the Find data for the field window.
For a more elaborate explanation see the New ADAPL reserved variable &1 = 28 value chapter in the Release notes 7.3 topic.

29
30

From Collections 1.11, storage and edit procedures set up for a reversely linked database are now executed when either:

the current main record is saved, after a new link is established between this record and a record in the linked database (before edit procedure, followed by storage procedure before storage)
the current main record is saved, after the value in a write-back field for a new or existing link to a record in the linked database has been changed (only the storage procedure before storage)
the current main record is saved, after an existing link between the current main record and a record in the linked database has been removed (before edit procedure, followed by storage procedure before storage)
the current main record is saved, after the current main record with an existing link to a record in the linked database is deleted (before edit procedure, followed by storage procedure before storage)

When the edit procedure for a reversely linked database is executed because of one of the reasons above, &1 will be 30.
When the storage procedure for a reversely linked database is executed because of one of the reasons above, &1 will be 29.
These execution codes allow you to build code into an edit or storage adapl (use the latest ADAPL compiler that is issued along with Designer 7.7.3 or up) which is only executed when the record is updated through a reverse link. If you don't need that but you just want the existing code (which might currently still only be executed if &1 = 11) to be executed as well when the record is updated through a reverse link, then you'll have to change conditions like if (&1 = 11) to if (&1 = 11 or &1 = 29).

31

Execution code value for WebAPI XML payload processing adapls. If not all code in your custom adapl is meant for payload processing, you can enclose the payload section by an if (&1 = 31) { ... } condition and the other code by other &1 conditions.

32

Execution code for CTS (Change Tracking Service) adapl, available from Collections 1.10.1.1225.

33

From Collections 1.14, when a task is being run, &1 is filled with value 33. So with if (&1 = 33) { ... } in an adapl you can execute the code between { and } only when the adapl is executed as a task adapl.

125

Execution code generated by the Axiell Move app, before a movement is processed. This code can be used by an <ActionAdapl> set up in the Move server configuration file.

126

Execution code generated by the Axiell Move app, after a movement is processed. This code can be used by an <ActionAdapl> set up in the Move server configuration file.

Some execution codes from the list above are accompanied by an execution sub code in &1[2] to further specify the circumstances under which the currently executed adapl was called. For Axiell Collections only a selection of all legacy codes has been implemented thus far, namely the codes 4, 5, 9 and 10. :

&1[2] code

&1 codes

Meaning

0

8

 

4

4, 9

This code is produced if &1=4 and the currently executed adapl has been called as an Edit record procedure in the current database (also when copying a record), or if &1=9 and the currently executed adapl has been called as an Input record procedure in the current database.

5

4, 9

This code is produced if &1=4 and the currently executed adapl has been called as a Before screen adapl while entering Edit mode, or if &1=9 and the currently executed adapl has been called as a Before screen adapl.

9

16, 20, 21

This code is produced if &1=16 or &1=20 or &1=21 and the currently executed adapl has been called as a Before screen adapl.

10

16, 20, 21

This code is produced if &1=20 or &1=21 and the currently executed adapl has been called as a Field based procedure in the current database, or if &1=16 and the currently executed adapl has been called as a Copy record procedure.

12

8

An output format which (also) uses an adapl, actually runs the adapl twice for each selected record, the first time is for preprocessing, before actually printing. This preprocessing run is executed with &1[2] = 12. You can use this preprocessing to remove certain records from the record selection to be printed or to add extra records to it. The first option can be used to allow users to make simpler record selections and have the adapl remove certain records from that selection based on more complex conditions, using SELECT NO. The second option can be used in a similar way, but to expand the record selection with records that cannot easily be collected in a single search result, one can think of certain hierarchically linked records for example.

(The second run is executed with &1[2] = 0.)
Note that for output formats (using adapls) which will likely be run for large records selections, while preprocessing is not required, it will help performance substantially (and may fix the issue where large record selections aren't printed at all) if preprocessing is skipped by including the following at the top of the adapl:

if (&1[2] = 12) { end }

Initialization

Before you can use a variable, Adlib must initialize it. That means Adlib will reserve memory for it and assign it a starting value. This takes place automatically. Local variables are initialized when the adapl program is loaded, and retain their value until program termination or until a different value is assigned. Database variables are available automatically if the adapl is running within Collections. As soon as a new record is loaded, the database variables assume the values of the fields in the new record.

If an ADAPL program is executed for a number of marked records consecutively, the declared variables will lose their values when program execution for the currently processed record ends. Database variables will also be reinitialized and will take their values from each record in turn as it is processed.
A work-around to create custom variables which do retain their values during consecutive execution of multiple records, is to use setvar and getvar.

FACS variables are initialized when the corresponding external database is opened. When a new FACS record is read, the variables assume the values of the fields in the new record.

Arrays

Normally speaking, it is only possible to store one piece of data in a local variable. However, you can specify that multiple pieces of data must fit in one variable by referring to the various data with sequential numbers, placed after the variable name in square brackets. A plural variable like this is known as an array. An array can have several dimensions. A declaration of a text array of 5 times 2 elements with a maximum length of 20 characters will look like this:

text textarray[5, 2, 20]

The subscripts (the numbers between square brackets) in the declaration are integer constants, which define the number and size of the dimensions in plural variables. An array can have a maximum of 4 dimensions and may take up a maximum of 64 KB of memory. A normal, local text variable is actually handled as an array of characters with a single dimension (namely its length), so the last dimension of the subscripts in a text array indicates the length of the string. In text expressions (when you're retrieving data from an array or assigning data to it), the last dimension subscript may be used to address an individual character within the string, while if you leave out the last dimension, the whole string will be retrieved or replaced. Thus, with textarray as declared above and character declared as a text character[1], the following function call returns the third character of the second column in the first row of a text array and puts it in character:

character = textarray[1, 2, 3]

while the following function call returns the entire string stored in the second column of the first row of textarray (with mytext first having been declared as text mytext[20], for example):

mytext = textarray[1, 2]

 

Example of a stand-alone adapl in which an array is used:

text textarray[5, 2, 20]
  /* text array of 5*2 elements, length 20
integer i /* array pointer
* fill the array
i = 1
while (i <= 5) {
  textarray[i, 1] = 'cell ' + i + ',1'
  textarray[i, 2] = 'cell ' + i + ',2'
  i = i + 1
}

* read the contents of the array and
* display the contents in the command-line window

i = 1
while (i <= 5) {
  errorm textarray[i, 1] + ' | ' + textarray[i, 2]
  i = i + 1
}
end

The result when running this adapl with runadapl.exe:

cell 1,1 | cell 1,2
cell 2,1 | cell 2,2
cell 3,1 | cell 3,2
cell 4,1 | cell 4,2
cell 5,1 | cell 5,2
 

This example shows that a two-dimensional array can be seen as a table. The first subscript represents the row, and the second the column. Together, they constitute the unique identification of each cell in the table, in which each cell may contain 20 characters.