Logo

Lenguaje de scripting

Lenguaje de scripting #

El lenguaje de scripting es un lenguaje creado específicamente para programar validaciones personalizadas en capturas de datos. Para cada tipo de dato definido en el Data Model, es posible generar una verificación personalizada a la hora de capturar el dato desde un cliente. Por ejemplo, se pueden verificar cosas simples (como el largo de un nombre de usuario) o más complejas (como el dígito de validación y formato de una cédula de identidad uruguaya).

Ejemplo - Validación de longitud en un nombre de usuario:

function validateUserName (value) do
  if len(value) < 6 or len(value) > 20 then
    return "La longitud del nombre de usuario debe estar entre 6 y 20 caracteres"
  end
end
addValidations(validateUserName)

En la imagen que aparece a continuación observamos el entorno de trabajo y el editor del lenguaje:

Previsualización del lenguaje de scripting

Entre las particularidades que posee el lenguaje, podemos mencionar que no es necesario utilizar ; para cerrar una linea de código. Esto aplica tanto para la asignación de variables, como con el resto de las sentencias y comandos (directamente se puede utilizar un salto de linea).

Variables #

El lenguaje de scripting admite variables del tipo número, texto y boolean (valores lógicos). No es necesario declararlas previamente, sino que pueden ser asignadas directamente. Los nombres de las variables pueden indicarse utilizando estilos como CamelCase o snake_case si se desea. Ambos se aplican a frases o palabras compuestas de la siguiente manera:

  • CamelCase: se utilizan mayúsculas en la primera letra de las palabras para identificarlas correctamente en la frase, aunque es posible exceptuar esta regla en la primera palabra. Como ejemplos, podemos nombrar MultipleWordVariable o multipleWordVariable.

  • snake_case: las palabras se separan utilizando guiones bajos, como por ejemplo multiple_word_variable.

Número #

Es posible asignar números enteros o decimales a variables, e incluso operaciones numéricas.

Ejemplos:

var1 = 35.28745
var2 = 77 + 48/3

Texto #

Las cadenas de texto que se asignen a variables deben estar encerradas entre comillas dobles "" para su correcto empleo. Las comillas también pueden ser usadas de forma literal dentro de la cadena, utilizando la barra invertida \ para escaparlas. De esta forma, el lenguaje las considera como parte de la cadena en vez de convertirlas en delimitadores del contenido.

Ejemplos:

stringVar = "This is a string"
stringVarWithQuotes = "This is a quoted \"String\""

Texto multilínea #

Se permite asignar texto multilínea a variables como se ilustra a continuación:

Ejemplo:

multiLineText = "Multi Line
                 Text"

Valores lógicos #

Las palabras clave true y false están reservadas como valores booleanos, y pueden ser asignados a variables de la misma manera que los anteriores.

Ejemplos:

varBoolTrue = true
varBoolFalse = false

Funciones #

Las salidas de funciones pueden ser asignadas a variables.

Ejemplo:

functionVar = fncall(127)

Alcance de variables #

El alcance o ámbito de una variable determina en qué partes del programa puede ser utilizada:

  • globales (por fuera de cualquier declaración de función), o
  • internas en una función.

En el primer caso, si se realizan modificaciones dentro de una función únicamente se verán aplicadas dentro de la misma (no globalmente). Por otro lado, las variables internas de las funciones solamente existen dentro de estas.

Comentarios #

Para realizar comentarios se utiliza el caracter #, ya sea al comienzo de una línea vacía o a continuación de una que contenga código.

Ejemplos:

# This is a comment.
balance = 224.22 # This is another comment.

Toda la primera línea del código de arriba constituye un comentario, mientras que en la segunda línea comienza luego de balance = 224.22.

Es importante destacar que la única forma de realizar comentarios multilínea es colocar el carácter # en cada una de las que se desee comentar.

Operadores aritméticos #

El lenguaje admite los operadores aritméticos clásicos a lo largo del código. Los operadores multiplicación y división poseen mayor precedencia frente a suma y resta.

Operadores disponibles:

DescripciónOperador
Suma+
Resta-
Multiplicación*
División/

No es posible utilizar paréntesis para agrupar operaciones.

Concatenación #

El operador suma permite concatenar dos o más cadenas de texto entre sí, o textos con números.

Ejemplos:

concatText = "My name is " + "Ricardo"
outstandingNumber = 2487224
textWithNumber = "I'm fascinated by the number " + outstandingNumber

En estos ejemplos, concatText y textWithNumber contienen los valores My name is Ricardo y I'm fascinated by the number 2487224, respectivamente.

Comparadores #

Mediante el uso de los siguientes operadores se pueden realizar comparaciones para generar variables booleanas. También pueden emplearse en estructuras de control de flujo condicionales (if-then-else) o bucles (while).

DescripciónComparador
Igual a==
Distinto a!=
Menor a<
Menor o igual a<=
Mayor a>
Mayor o igual a>=

Ejemplos:

var1 = 8
var2 = 15
comp1 = var1 < var2
var3 = 10
var4 = 15
comp2 = var3 == var4

Como 8 es menor que 15, comp1 es true. Por el contrario, comp2 toma el valor false debido a que 10 no es igual a 15.

Declaración de funciones #

La primera alternativa para declarar funciones toma la siguiente forma. El símbolo * indica que el argumento en cuestión es opcional:

function <ident>(<arg1>*, <arg2>*) do
     <body*>
    return*
end

donde:

  • <ident> es el identificador o nombre de la función.
  • (<arg1>, <arg2>) son los argumentos de entrada a la función. Se pueden utilizar tantos argumentos como se desee separándolos con comas.
  • <body> representa las sentencias que conforman la función.
  • return define la salida de la función, la cual puede devolver tanto variables (que tengan asociadas un tipo de dato o función) como números, booleanos o textos. También es válido no utilizar esta sentence y directamente no devolver nada.

Ejemplo:

function fnWithArgs(value1, value2, value3) do
  aux = value1 + value2*value3
  return "Function returns: " + aux
end

Otra opción para declarar una función toma el siguiente formato:

ident = function(<arg>*) do
 <body*>
 return*
end

En este caso, se declara la función y se asigna su valor de retorno a la variable nombrada ident en un mismo paso.

Funciones con parametros que aceptan funciones #

Una función puede aceptar otra función como posible argumento de entrada.

Ejemplo:

function mean(value1, value2) do
  aux = value1 + value2
  return aux/2
end
function main(value1, value2, func1) do
  result = func1(value1, value2)
  return result
end
main(15, 10, mean)  # function call with another function as an argument

En el código de arriba, la función main espera tres argumentos. Los dos primeros (value1 y value2) son valores dados y el restante una función. Cuando se invoca esta función en la última línea, los dos valores son procesados en realidad por mean.

Funciones sin parámetros #

Es posible declarar una función sin argumentos simplemente colocando () luego del identificador de la función.

Ejemplo:

function fnWithoutArgs() do
  return "Global Error"
end

Funciones que no devuelven nada #

En el caso de que la función se vaya a emplear para ejecutar una serie de sentencias y no exista la necesidad de devolver un valor, se debe omitir el comando return del final.

Ejemplo:

function noReturn (val1) do
  if val1 < 48 then
    loginfo(val1)
  end
end

Estructuras de control #

If-Then-Else #

Es posible utilizar una estructura condicional del tipo if-then-else para el control de flujo en el código. Su forma general es la siguiente:

if <exp> <comp> <expr> then
     <body>*
else
     <body>*
end

donde se puede obviar la utilización de else y en tal caso, la estructura anterior se reduce a

if <exp> <comp> <expr> then
     <body>*
end

Se permite la utilización de multiples sentencias ubicadas una luego de la otra, tanto dentro de then como de else.

<exp> <comp> <expr> es una condición que implica la comparación entre dos expresiones. <comp> puede ser cualquiera de los comparadores que se presentaron anteriormente en la sección Comparadores. Si la condición se cumple, se ejecuta la sentencia dentro de then. De lo contrario, si la condición NO se cumple, se hace lo propio con la sentencia dentro de else (siempre y cuando esta exista).

Ejemplo: If-Then-Else con Else

function conditional (value1, value2) do
  if value1 >= value2 then
    return "value1 is greater or equal than value2"
  else
    return "value1 is less than value2"
  end
end

Ejemplo: If-Then-Else sin Else (If-Then)

function conditional_without_else (value) do
    if value1 == 8 then
      result = value*15
    end
    return result
end

Ejemplo: If-Then-Else con condición directamente booleana

Es válido reemplazar la condición <exp> <comp> <expr> directamente por una variable booleana <bool>. Como es de esperarse, se ejecutarán la sentencias dentro de then si <bool> es true, o aquellas dentro de else (si este existiera) en caso contrario.

El formato general es el siguiente:

if <bool> then
  <stm*>
else
  <stm*>
end

A continuación, una aplicación concreta:

function condBool(value) do
  bool1 = value == 8
  if bool1 then
    return "Input equals 8"
  else
    return "Input does not equal 8"
  end
end

Ejemplo: If-Then-Else con operadores lógicos entre condiciones

Los operadores lógicos and u or pueden estar presentes entre dos o más condiciones. Por otro lado, el operador lógico not() permite negar una condición.

La utilización del operador and implica que todas las condiciones individuales unidas por este operador deben ser true para cumplir la condición completa del if. En cambio, al emplear el operador or basta que al menos una de las condiciones individuales sea true para hacerlo.

Ejemplo:

function condLogic(value1, value2, value3, value4) do
  if value1 != value2 and value3 < value4 then
    return "Yeah!"
  end
  if not(value1 != value2) then
    return "Value1 is equal to Value2"
  end
end

While #

El bucle while se utiliza para iterar sobre un grupo de sentencias mientras se cumple una determinada condición. Su sintaxis es la siguiente:

while <expr> do
  <body>
end

donde <expr> es una expresión lógica que tiene el mismo formato que en las estructuras if-then-else:

function iterate1 (value) do
  while value < 150 do
    value = value*2
  end
end

Dentro de iterate1, el bucle continuará multiplicando a value (inicialmente un argumento a la función) por 2 mientras el resultado sea menor que 150.

Ejemplo: While con operadores lógicos entre condiciones

Se permite la utilización de operadores lógicos and, or y not() de la misma forma que con if-then-else:

function iterate2 (value1, value2) do
  while value1 < 150 and value2 < 200 do
    value1 = value1+10
    value2 = value2+40
  end
end

En el caso de arriba, value1 y value2 se incrementan en 10 y 40 unidades en cada iteración. Este proceso continúa hasta que el primero y el segundo sean mayores o iguales a 150 o 200, respectivamente.

Estructuras de Datos #

Arreglos #

Un arreglo es un tipo de datos estructurado que está formado por una colección finita y ordenada de datos. El lenguaje de scripting incluye una serie de funciones que permiten crear y trabajar con arreglos unidimensionales.

Cabe aclarar que los datos almacenados pueden ser de cualquier tipo (número, texto, booleanos) dentro de un mismo arreglo.

array() #

Esta función hace posible la creación de un arreglo. Su uso general toma la forma arr = array(), donde arr queda definido como un arreglo.

aset(arr, ind, data) #

Permite introducir el dato data dentro del arreglo arr en la posición definida por el índice ind.

aget(arr, ind) #

Devuelve el dato asociado con el índice ind en el arreglo arr.

alen(arr) #

Devuelve la longitud del arreglo arr.

Ejemplos de trabajo con arreglos #

En los siguientes bloques, se crea un arreglo y luego se agregan datos. Como próximo paso, se calcula su longitud y por último se extraen los elementos del arreglo:

Ejemplo 1:

arreg = array()
aset(arreg,0,25)
aset(arreg,1,454)
aset(arreg,2,422.25)
arrayLength = alen(arreg)
value = aget(arreg,2)

La variable value toma el valor 422.25 que previamente se había almacenado en la posición 2 dentro del arreglo. Por otro lado, arrayLength es igual a 3 ya que esa es la longitud del arreglo.

Ejemplo 2:

arrText = array()
aset(arrText, 0, "Braulio")
aset(arrText, 1, "Hector")
aset(arrText, 2, "Hugo")
arrayLength = alen(arrText)
value = aget(arrText, 0)
# variable `value` is Braulio , variable `arrayLength` is equal to 3.

En este caso, value y arrayLength contienen los valores Braulio y 3, respectivamente.

Diccionarios #

El lenguaje de scripting facilita una serie de funciones que permiten manipular diccionarios. Estas estructuras son pares clave/valor que requieren que las claves sean únicas.

dict() #

Esta función permite la creación de un diccionario. Su sintaxis general toma la forma dictionary = dict() mediante la cual se define dictionary como un diccionario.

set(dictionary, key, data) #

Permite introducir el dato data, asociado a la llave (clave) key, dentro del diccionario dictionary.

get(dictionary, key) #

Devuelve el dato asociado a la llave key dentro del diccionario dictionary.

Ejemplos de trabajo con diccionarios #

En el siguiente bloque se ilustra cómo crear un diccionario, introducirle datos, y después recuperarlos.

infoPerson = dict()
set(infoPerson, "name", "Bob")
set(infoPerson, "ID", 8504234)
set(infoPerson, "job", "Sales manager")
set(infoPerson, "car", "BMW")
car1 = get(infoPerson,"car")

Luego de ejecutar las líneas que aparecen arriba, la variable car1 toma el valor BMW.

Funciones Matemáticas #

El lenguaje de scripting proporciona varias funciones matemáticas predefinidas.

abs(value) #

Devuelve el valor absoluto de value.

round(value) #

Regresa el redondeo de value.

ceil(value) #

Devuelve el número entero mayor o igual más próximo a value.

floor(value) #

Retorna el máximo entero menor o igual a value.

max(value1, value2) #

Acepta uno o más argumentos y regresa el máximo de ellos.

min(value1, value2) #

Recibe uno o más argumentos y devuelve el mínimo entre tales valores.

mod(value1, value2) #

Devuelve el resto de la división entera entre value1 y value2 (value1/value2).

random() #

Devuelve un valor aleatorio entre 0 y 1.

Funciones para texto #

regexp(patrón) #

Crea un objeto del tipo expresión regular para buscar coincidencias dentro de un texto a partir de un patrón dado.

Ejemplo:

patternLettersOrNumbers = regexp("[A-Za-z0-9ñÑ]")

En este caso, patternLettersOrNumbers devolverá coincidencias siempre que encuentre una o más letras minúsculas o mayúsculas (incluyendo la ñ) o números; en síntesis, cualquier caracter alfanumérico cumplirá esta expresión regular.

val(string) #

Convierte la cadena de texto string a su equivalente valor numérico, siempre y cuando string este conformado solamente por números.

substring(string, arg1, arg2) #

Extrae caracteres del texto dado por string comenzando por la posición arg1 (sin incluirlo) hasta arg2 (inclusive). Como el argumento arg2 es opcional, la extracción puede realizarse simplemente desde arg1 hasta el final de la cadena.

Al manipular caracteres dentro de una cadena con esta función, las posiciones dadas por arg1 y arg2 son basadas en cero.

Ejemplo:

var1 = substring("Complete string",2)

Al ejecutar la línea anterior, var1 tomará el valor mplete string.

Ejemplo:

var1 = substring("Complete string",2,11)
# var1 is 'mplete st'

En este segundo caso, var1 será igual a mplete string.

trim(string) #

Elimina los espacios en blanco de ambos extremos de la cadena de texto representada por string.

Ejemplo:

text = "      text with lots of blank spaces             "
text2 = trim(text)

Como resultado, el contenido de text2 será text with lots of blank spaces.

replace(string, arg1, arg2) #

Reemplaza arg1 por arg2 en la cadena string, donde arg1 puede ser texto o una expresión regular. Si arg1 es texto, el cambio se realiza solamente en la primera ocurrencia. Caso contrario, la modificación se efectúa en toda coincidencia del patrón dentro de la cadena de texto.

Ejemplo (uso como texto):

text = "it is remarkable to remember that the last meeting was remarkable"
out = replace(string, "remarkable", "important")

Luego de ejecutar las sentencias de arriba, el valor de out será it is important to remember that the last meeting was remarkable.

Ejemplo (uso como expresión regular):

expReg = regexp("she\w{2}")
string = "She sells seashells on the seashore. The shells she sells are surely seashells. So, if she sells shells on the seashore, I'm sure she sells seashore shells."
out = replace(string, expReg, "star")

En esta instancia, out será igual a She sells seastars on the seashore. The stars she sells are surely seastars. So, if she sells stars on the seashore, I'm sure she sells seashore stars..

match(string, arg) #

Regresa un valor booleano (true o false) en base a la presencia o inexistencia de la cadena arg dentro de string.

len(string) #

Devuelve la cantidad de caracteres de la cadena de texto representada por string.

Otras funciones #

loginfo(arg) #

Imprime el contenido de arg en la consola del navegador.

rest(method, host, headers, params) #

Permite realizar peticiones HTTP a un servicio web. Los argumentos de entrada son los siguientes:

ArgumentoTipo de datoDescripción
methodTextoMétodo de la solicitud (puede ser GET, POST, o PUT)
hostTextoURL del host al que se va a realizar la petición
headersDiccionarioEncabezado que se enviará al servicio web en la petición
paramsDiccionarioOtras estructuras que se enviarán al servicio web

La función devuelve un diccionario con las siguientes claves:

LlaveTipo de datoDescripción
bodyTextoCuerpo que el servicio web devuelve como resultado de la solicitud
statusTextoCódigo de respuesta HTTP (resultado de la petición)

Ejemplo:

headers = dict()
params = dict()
host = "https://google.com"
set(headers, "content-type", "application/soap+xml")
set(params, "body", "")
response = rest("GET", host, headers, params)
status = get(response, "status")
bodyReturn = get(response, "body")

Si la solicitud es exitosa, status tomará el valor 200 mientras que bodyReturn será igual al cuerpo (body) de response.

Flujo de ejecución del código #

El primer paso en el diseño del código debe consistir en declarar las funciones que se invocarán luego. Es importante recordar que llamar a una función que no ha sido definida conducirá a errores en la ejecución. También es recomendable declarar las variables globales que se necesiten en este punto.

Las funciones que se llamen en el cuerpo del código serán ejecutadas inmediatamente al ingresar al paso del funnel en el que se encuentre la captura de datos que se necesite validar. Para que esto suceda, es imprescindible invocar la función predefinida addValidations() y pasarle como argumento la función que realizará la validación sobre el campo en cuestión.

Función addValidations(f) #

Esta función vincula el campo de captura de datos que se desea validar con el código diseñado. La función f que se use como argumento de addValidations será el punto de inicio de la validación. Debido a que se puede invocar addValidations() varias veces en el mismo código, es posible realizar varias validaciones independientes para un mismo campo.

Como es de esperar, addValidations(f) debe ser llamada luego de la declaración de f y de aquellas que sean llamadas por esta última.

Algunos puntos a tener en cuenta:

  • El argumento de entrada de f es el valor que se introducirá en el campo que se está validando.

  • El valor de retorno de la función f será el mensaje que se mostrará ante un error en la validación.

  • Si no se devuelve nada, se entiende que la verificación fue correcta, y no es necesario devolver ningún tipo de mensaje. Por ese motivo, se puede omitir el comando return en el flujo de código.

Para ilustrar, consideremos el siguiente ejemplo de validación:

function validateRange (value) do
  if value > 8 then
    return "Valor de entrada debe ser menor o igual a 8"
  end
end
addValidations(validateRange)

donde value es el valor que se ingresa en el campo de captura. Si este valor es mayor a 8, se mostrará un mensaje de error (Valor de entrada debe ser menor o igual a 8):

Mensaje de error ante validación

Al no considerar el caso en que value sea menor o igual a 8, la captura será correcta y se avanzará en el funnel al presionar el botón Submit cuando se cumpla esa condición.

Múltiples validaciones para un mismo campo #

Como se explicó anteriormente, pueden utilizarse múltiples llamadas a la función addValidations() para realizar el mismo número de validaciones.

Ejemplo:

function validateRange (value) do
  if value > 8 then
    return "Valor de entrada debe ser menor o igual a 8"
  end
end

function validateNumber (value) do
  if match(value,regexp("[^0-9]")) then
    return "Valor de entrada solamente puede contener números"
  end
end

addValidations(validateRange)
addValidations(validateNumber)

Las dos validaciones que se hacen en este caso son las siguientes:

  • Que el contenido del campo sea igual o menor a 8
  • Que el caracter que se introduzca sea numérico

Si alguna de estas condiciones no se cumple, se mostrará un mensaje particular para indicar el error.

Ejemplos de códigos para casos comunes de validación #

Ejemplos básicos #

Validación de cantidad de caracteres en campos de texto #

Mediante el siguiente bloque de código, se usa la función checkDataLength para validar que la cantidad de caracteres presentes en un campo sea mayor que 6 pero menor a 20:

function checkDataLength(value) do
    value = trim(value)
    if len(value) < 6 or len(value) > 20 then
        return "Field length must be between 6 and 20 characters"
    end
end
addValidations(checkDataLength)

Como se explicó previamente, la llamada a la función se realiza al pasarla como argumento a addValidations.

Validación de tipo de caracteres en campo de texto #

En este caso, checkChars verificará que el campo en cuestión solamente contenga letras y/o números.

function checkChars(value) do
    value = trim(value)
    if match(value,regexp("[^A-Za-z0-9ñÑ]")) then
        return "Only letters and numbers are allowed on this field"
    end
end
addValidations(checkChars)

Validación de fechas #

El lenguaje de scripting también permite validar que una fecha sea posterior a otra dada. Para lograr esto, se espera que la entrada de fecha en el campo de captura tenga el formato mm/dd/yyyy o mm-dd-yyyy.

function checkDate(value) do
  value = trim(value)
  month = val(substring(value,0,2))
  day = val(substring(value,3,5))
  year = val(substring(value,6,10))
  if year == 2002 and month == 6 and day >= 23 then
    return "Only dates before 6/23/2002 are accepted"
  end
  if year == 2002 and month > 6 then
    return "Only dates before 6/23/2002 are accepted"
  end
  if year > 2002 then
    return "Only dates before 6/23/2002 are accepted"
  end
end
addValidations(checkDate)

En el ejemplo de arriba, una fecha se tomará como válida solamente si es posterior al 23 de junio de 2002.

Ejemplos avanzados #

Validación de cédula de identidad uruguaya #

function normalize_ci(value) do
    value = trim(value)
    loginfo("trim " + value)
    value = replace(value,regexp("[\.-]"),"")
    while len(value) < 8 do
        value = "0" + value
    end
    return value
end
function validate_uy_ci (value) do
    i = 0
    a = 0
    digit = 0
    value = normalize_ci(value)
    v = substring(value,0,7)
    dv = substring(value,7)
    while i < 7 do
        vl = val(substring("2987634",i,i+1)) * val(substring(v,i,i+1))
        a = a + vl
        i=i+1
    end
    if mod(a,10) == 0 then
        digit = 0
    else
        digit = 10 - mod(a,10)
    end
    return digit == dv
end
function validations (value) do
    if not(validate_uy_ci(value)) then
        return "No es una cédula de identidad válida"
    end
end
addValidations(validations)

Validación de nombre de usuario #

El siguiente ejemplo verifica si nombre de usuario tiene la longitud correcta (entre 6 y 20 caracteres), si solamente incluye letras y/o números, y si ya existe o no en la base de datos. Esta última validación se realiza a través de una petición a un servicio web.

function checkUsernameLength(value) do
    value = trim(value)
    if len(value) < 6 or len(value) > 20 then
        return "El nombre de usuario debe tener entre 6 y 20 caracteres de largo"
    end
end
function checkUsernameChars(value) do
    value = trim(value)
    if match(value,regexp("[^A-Za-z0-9ñÑ]")) then
        return "El nombre de usuario solamente puede tener letras y números"
    end
end
function checkUsernameExists(value) do
    headers = dict()
    set(headers,"content-type","application/soap+xml")
    params = dict()
    host = "https://knrioscript.free.beeceptor.com"
    body = ""
    set(params,"body",body)
    response = rest("GET",host,headers,params)
    useduser = get(response,"body")
    statusresponse = get(response,"status")
    loginfo(statusresponse)
    if match(useduser,value) then
        return "El nombre de usuario no está disponible. Por favor, elija uno diferente."
    end
end
addValidations(checkUsernameLength)
addValidations(checkUsernameChars)
addValidations(checkUsernameExists)