Mostrando entradas con la etiqueta Función. Mostrar todas las entradas
Mostrando entradas con la etiqueta Función. Mostrar todas las entradas

jueves, 2 de mayo de 2024

Procedimientos. Textos.

 Tablas. Campos condicionados

Otro tipo de campo frecuente en las tablas del dictamen y del informe es el que denomino aquí campo condicionado. Estos campos se caracterizan por ser dependientes de otro campo con el que se encuentra conceptualmente relacionado.


Veamos primero un ejemplo de este tipo de campos en el dictamen:


Los campos del apartado 5 del dictamen (también los del 6. Recursos de apoyo especializado) son de tipo condicionado, ya que el campo de la columna Sí/No condiciona su correspondiente de la columna Breve descripción: si contestamos en el primero deberemos incluir información en el segundo; en caso contrario el campo asociado quedará vacío (1)

También se presenta este tipo de campos en el modelo de informe, como comprobamos en la captura que sigue:


En este caso se trata de una tabla en la que se registra la presencia vs. ausencia de Medidas de Atención a la Diversidad (MAD). Su funcionamiento es igual al de la tabla anterior (dictamen): si la respuesta al ítem es , se deberá aportar información en el campo asociado de la columna 4. En caso contrario (No) su campo asociado quedará vacío.

Paso ahora a explicar el código necesario. Como ejemplo trabajaré con la tabla 5 del dictamen desarrollando una de las dos opciones, la menos eficiente, por lo que recomiendo una lectura atenta de todo lo que sigue.

Sub DictamenMod2

Dim oMarcador As Object
Dim sAjustesOrganizativosA As String, sAjustesOrganizativosB As String

sAjustesOrganizativosA = InputBox("¿Son necesarios ajustes organizativos y metodológicos?","DICTAMEN. 5. Adapciones que precisa","Sí-No")

 If sAjustesOrganizativosA= "Sí" Then

sAjustesOrganizativosB = InputBox("Describe las medidas organizativas, metodolóogicas y curriculares de acceso que consideras necesario.","DICTAMEN. 5. Adaptaciones que precisa")

ElseIf  sAjustesOrganizativosA= "Sí" Then

 sAjustesOrganizativosB = ""

Else

MsgBox("No has dado respuesta a lo demandado en el campo Ajustes organizativos del aparado 5 del DICTAMEN o la respuesta no se acepta como válida") 

   End If 

oMarcador = ThisComponent.getBookmarks().getByName("mcr0")
oMarcador.getAnchor.setString(sAjustesOrganizativosA)
oMarcador = ThisComponent.getBookmarks().getByName("mcr1")
oMarcador.getAnchor.setString(sAjustesOrganizativosB)

End Sub

  • Necesitamos crear la misma estructura que usamos en el caso de campos simples, incluyendo la creación de marcadores en el documento. 
  • Además de la variable object (oMarcador) que nos permite acceder a los objetos marcador del documento (mcr0 mcr1) Declaramos dos variables de contenido por aspecto: una para el campo condicionante (sAjustesOrganizativosA ) y otra para el condicionado (sAjustesOrganizativosB):
Dim oMarcador As Object
Dim sAjustesOrganizativosA As String, sAjustesOrganizativosB As String
  • Dar contenido a la variable condicionante se ajusta al modelo de campo básico, por lo que se le puede tratar mediante la misma fórmula...
sAjustesOrganizativosA = InputBox("¿Son necesarios ajustes organizativos y metodológicos?","DICTAMEN. 5. Adapciones que precisa","Sí-No")

... pero esto genera un posible error por omisión (respuesta vacía) o por separarse (en forma o en contenido) de las dos únicas opciones válidas (Sí/No). Para controlar estos posibles errores se pueden usar diferentes opciones y aunque estos errores sean excepcionales, su solución no es simple (2).

Otra posible formulación pasa por emplear la función MsgBox(), lo que nos obliga, a su vez una nueva variable Integer (Dim iR as Integer) a asociar a la función MsgBox(). Esta opción evita posibles errores por parte del usuario, aunque obliga también a modificar la estructura condicional, condicionando un script alternativo:

 Dim oMarcador As Object

Dim sAjustesOrganizativosA As String, sAjustesOrganizativosB As String

Dim iR As Integer

iR = MsgBox ("¿Son necesarios ajustes organizativos y metodológicos?",4,"DICTAMEN. 5. Adaptaciones que precisa")

 If iR = 6 Then

sAjustesOrganizativosA = "Sí"
sAjustesOrganizativosB = InputBox("Describe las medidas organizativas, metodolóogicas y curriculares de acceso que consideras necesario.","DICTAMEN. 5. Adapciones que precisa")

Else

 sAjustesOrganizativosA = "No"
 sAjustesOrganizativosB = ""

 End If 

Como podemos comprobar, esta segunda opción simplifica el script y lo hace más resisten a errores de uso (en realidad los reduce a cero), por lo que es preferible al primer script (3).

  • Finalmente, la escritura en el texto se mantiene en la formulación ya explicada en la entrada anterior sobre campos simples, aunque ahora son necesarias dos fases de escritura, que aquí se resuelven meramente por repetición del mismo procedimiento (4):

oMarcador = ThisComponent.getBookmarks().getByName("mcr0")
oMarcador.getAnchor.setString(sAjustesOrganizativosA)
oMarcador = ThisComponent.getBookmarks().getByName("mcr1")
oMarcador.getAnchor.setString(sAjustesOrganizativosB)

NOTAS

(1) Este segundo campo también sería condicionado si el contenido a introducir fuera uno u otro según la respuesta dada al campo condicionante. El ejemplo que presento aquí es la formulación más simple de este tipo de campos, pero también el único que se presenta en el informe y en el dictamen.

(2) En este caso se incluye en InputBox() las dos opciones de respuesta como tercer parámetro de la función; también se incluye la opción Else en el condicional If con un mensaje de advertencia. En el modelo propuesto podría añadirse a continuación de Else volver a interrogar sobre la variable sAjustesOrganizativosA y repetir el análisis que realiza el condicional, dando así una segunda opción. Como se puede ver, la solución no es sencilla, aunque en realidad es posible que estas circunstancias adversas se produzcan excepcionalmente.

(3) En principio, cuando nos encontremos con un campo de dos opciones siempre es preferible utilizar la función MsgBox() en lugar de InputBox(); no ahorraremos posibles errores y obtendremos un código más limpio y funcional. MsgBox() funciona en realidad como Boolean, pero su configuración de respuesta en OOo Basic nos obliga a asociarla a una variable numérica, ya que sus comandos (aquí el parámetro 4, que muestra las opciones - No) devuelven respectivamente los valores 6 y 7.

(4) Cuando el número de variables y marcadores a usar se multiplique será necesario trabajar con una matriz para agrupar las variables y un bucle para el proceso de escritura. Para los objetivos de esta entrada ambos resultan innecesarios. 

miércoles, 27 de diciembre de 2023

Funciones Calc.

 Calc. Funciones propias (3). Números



Calc dispone de dos funciones para controlar si un dato es numérico o no lo es.  Estas funciones son ESNUMERO() y N(). La primera función devuelve VERDADERO si un dato es numérico y FALSO si no lo es.

Los datos numéricos que lo parecen, pero que no lo son, son los que resultan de escribir números como palabras, como, ñor ejemplo, cuando escribimos un numero dentro de una cadena ('"tengo 24 años"), pero también cuando utilizamos controles de formulario como Cuadro de texto para introducir datos numéricos y los asociamos a celdas.

Cuando usamos estos controles de formulario como forma de entrada de datos y asociamos la entrada a una celda, si en el cuadro de texto escribimos un texto no se produce ningún problema, pero si escribimos números, el resultado esperado puede no corresponderse con lo que obtenemos, presentando un comportamiento irregular.

  • Si aplicamos la función ESNUMERO() a esa celda, nos devolverá FALSO, 
  • pero si en otra celda recogemos el resultado de aplicar un operador aritmético al valor de la celda asociada al control, la función ESNUMERO() devuelve VERDADERO.
  • Ahora bien, si ese dato está incluido en un conjunto de datos, unos numéricos y otros string, y aplicamos funciones que suponen trabajar con el conjunto, como SUMA()PROMEDIO() y otras, ESNUMERO() devuelve VERDADERO, pero el cálculo excluye los valores no numéricos.

Lo que sucede en estos casos es que las citadas funciones (SUMA() y PROMEDIO()) realizan los cálculos respectivos sobre el conjunto de datos que sí son número sin tomar en cuenta los valores que se presentan como tales pero que no los son, como es el caso de los que proceden de los controles del formulario.

Si utilizamos controles de formulario como recurso de entrada de datos en sustitución de la entrada directa en celdas, y mediante funciones específicas trasladamos sistemáticamente estos valores a una tabla sobre la que realizamos cálculos, no es difícil imaginar el efecto que el comportamiento observado anteriormente va a tener en los resultados que obtengamos de tales cálculos. 

En estos caso, para evitar estos errores sugiero recurrir a multiplicar el valor original por 1 (D2*1), lo que convierte el dato en un número reconocido como tal por ESNUMERO() y por las funciones de cálculo del conjunto, como SUMA() y PROMEDIO().

Calc dispone de la función N(), que transforma un carácter en número, pero que puede ser que devuelva 0, convertido eso sí, en un valor numérico, tal y como revela ESNUMERO(), por lo que altera obviamente, nuestro cálculo.

jueves, 14 de diciembre de 2023

Procedimientos. Datos.

Calc. Cumplimentar documento.

Como dije en la [entrada anterior], es posible crear soluciones (que no (aun) docap) basadas en el uso de funciones Calc. Comentaba en ella la conveniencia de ejemplificar la afirmación anterior explicando la construcción de un soporte de esas características y recientemente [publiqué una entrada en OrientAsLO] que cumple este objetivo, quedando pendiente (en ese momento) complementar dicha entrada con la que ahora desarrollo aquí.



En esa entrada de OrientAsLO expliqué el test de referencia (Test de Weepman) y la estructura del soporte, por lo que no me voy a detener aquí en esos detalles. Me centraré, eso sí, en explicar con cierto detalle el uso de las funciones SI() y CONCATENAR() empleadas en la construcción del sencillo informe con el que finaliza el soporte (pág. Info). 

A este mismo efecto podría haber servido el test Palabras mágicas, ya que también en él se utilizan los mismos recursos con la misma finalidad, pero al exponer el test Weepman matamos dos pájaros de un mismo tiro: aumentamos los recursos publicados en el blog, complementando desde una doble perspectiva la evaluación de la discriminación auditiva (1), y exponemos un procedimiento de trabajo cuyo conocimiento puede considerarse "rentable" en términos de frecuencia de uso.

De hecho se trata de explicitar precisamente lo que podemos considerar la "prehistoria" del desarrollo del nivel competencial que facilita en estos momentos la creación de docap (2): profundizar en el uso de los recursos disponibles en los servicios e incrementar el campo de ámbitos de uso de uno (la hoja de cálculo Calc)  que es sabido no forma parte de lo que normalmente se suele emplear por parte de los SEO.

Con todo, lo más importante ahora es que con esta entrada complemento lo expuesto [en la precedente] referida a la función CONCATENAR() y sus similares (CONCAT() UNIRCADENAS()), a la vez que también complementa lo tratado en [otras entradas anteriores] referidas al uso de la función SI() (3), por lo que ahora abordamos su uso conjunto, el cual se revela así con toda su capacidad de automatizar la creación de textos (informes) básicos sin recurrir (aun) al uso de script OOo Basic.

La función condicional SI() ya se emplea con profusión en la puntuación de los ítem del Test (4)...

  • =SI(D7=E7;1;0) (base para el análisis cuantitativo)

... y el registro de los pares de fonemas en los que se presentan errores...

  • =SI(F7=0;B7;" ") (como base para el análisis cualitativo)
No obstante, es en la creación del informe (hoja Info) donde me voy a centrar ahora, ya que es en ella donde se utilizan las funciones SI() y CONCATENAR() de forma combinada, pero también donde se aprecia el "primitivismo" y las limitaciones de estas primeras formulaciones de la automatización basada en funciones nativas de Calc: incluso en el marco de esta delimitación  se aprecia que la solución se desarrolla en los inicios de la profundización en el conocimiento de las posibilidades de la hoja de cálculo (5).

El mencionado informe está dividido en cinco bloques, correspondiendo el primero a la descripción del test, sin que se emplee en él ningún procedimiento de personalización, limitándose a la escritura directa de un breve texto genérico e introductorio.

El resto de los párrafos sí incluyen el uso combinado de las funciones (básicamente la condicional SI() junto con CONCATENAR() y alguna otra de carácter complementario (y secundario para lo que nos interesa ahora) (6). La estructura básica es la que sigue:

SI(Condición a valorar... (se refiere a la valoración global del rendimiento del sujeto)

... Entonces -> CONCATENAR() -> datos + cadenas + datos

... En caso contrario ("Else") -> CONCATENAR() -> datos + cadenas + datos )

... (donde datos se concreta como referencia a celdas que contienen los datos) y que se concreta como sigue:

=SI($Id.C14="presenta dudas";CONCATENAR($Id.B6;" obtiene un porcentaje de acierto del ";TRUNCAR($Id.C12;2);"%, por lo que puede considerarse que su rendimiento en tareas de discriminación auditiva es  ";$Id.C14);CONCATENAR($Id.B6;" obtiene un porcentaje de acierto del ";TRUNCAR($Id.C12;2);"%, por lo que puede considerarse que su rendimiento en tareas de discriminación auditiva es";$Id.C14))

El párrafo siguiente se crea únicamente mediante concatenación de string y datos y sirve de entrada para exponer los pares de fonemas afectados por error (párrafos 4º y 5º), que también se crean mediante la función concatenar.

Son estos dos párrafos donde mejor se aprecia el arcaísmo y las limitaciones de uso de las funciones Calc: en ellos habría sido mucho más pertinente utilizar la función CONCAT() o UNIRCADENAS(), que se suponen una ejemplificación de la concatenación de un conjunto de celdas (G7:G46), así como del tratamiento de las celdas vacías y/o de la separación entre ellas. 

Si utilizo estas dos funciones como alternativa a la más primitiva (CONCATENAR()) obtengo un resultado más satisfactorio y de forma más simple (7), pero no del todo satisfactoria. De hecho la función que mejor funciona es UNIRCADENAS(), pero tampoco de forma totalmente satisfactoria en cuanto al manejo de los espacios que deja la no presencia de contenido en determinadas celdas del conjunto (G7:G46) y/o del carácter de separación (el que mejor funciona es " ").

Es entonces cuando el uso de otras dos funciones de tipo texto ayuda a mejorar los resultados. Me refiero a la función ESPACIOS(), que elimina los espacios sobrantes, y la función SUSTITUIR(), que empleo para sustituir los espacios en blanco que utilizo originariamente como separador y que ESPACIOS() respeta por considerarlos espacio propio entre palabras (que son los únicos que no elimina). De este modo consigo un resultado que se ajusta perfectamente al objetivo: generar un listado de los pares de fonemas afectados por error, ajustado a la longitud de la lista resultante y diferenciándolos claramente unos de otros. Como ejemplo...

G-D | S/C | C-G | C-T | E-O | C-P | BR-GR | T/R | P/N | C-T

... que resulta de la combinación de las tres funciones (8): 

=SUSTITUIR(ESPACIOS(UNIRCADENAS(" ";1;$It.G7:G46));" ";" | ")

NOTAS

(1) Con lo que proporcionamos dos recursos que se pueden considerar complementarios en cuanto a la forma de evaluar la discriminación auditiva, lo que permite desarrollar estrategias de evaluación que combinen ambos, si así se considera.

(2) O lo que es lo mismo, la (semi)automatización de soportes documentales en base a OOo Basic.

(3) Se trata de tres entradas aunque ahora sólo se enlace a la publicada en primer lugar. El resto están disponibles desde la página [DATOS] . Pdt. Añadir que en estas entradas no me limito a explicar el uso de las funciones condicionales de Calc. 

(4) Hoja It(em), columnas F para puntuación y G para recopilación de errores.

(5) El documento original fue creado hace más de 10 años sobre MSO-Excel y transformado recientemente a formato Calc sin revisión ni modificaciones, de ahí que se limite al uso de la función CONCATENAR() sin tener en cuenta otras opciones.

(6) También incluyen la referencia a celdas de diferentes hojas, aunque este es un procedimiento básico y muy frecuente en el uso de Calc o de Excel.

(7) De hecho no necesito crear dos párrafos, siendo ahora suficiente con uno.

(8) Te sugiero aplicar esta modificación al documento que puedes [descargar desde este enlace]

martes, 12 de diciembre de 2023

Funciones. Texto.

Concatenar cadenas.

Aunque no sólo, cuando creamos un texto cuyo contenido puede variar en función de características y/o resultados del procesamiento de datos, además de utilizar condicionales para crear recorridos posibles (mediante la función SI() y la estructura If), también necesitamos unir en un único texto cadenas de texto y variables. Para eso contamos en Calc con funciones como CONCATENAR(), pero también con procedimientos específicos en OOo Basic.


Aunque quizá no sea el servicio donde más utilidad se va a obtener de estos procedimientos, resulta que es en Calc donde tenemos disponibles tres funciones relacionadas con algo que en principio parece de mayor utilidad en Writer. Claro que hablo desde la perspectiva de la automatización de documentos y aun así esta aparente limitación no lo es tanto si pensamos en la creación de docap complejos que combinan Calc+Writer, como algunos que ya hemos creado.

En todo caso, ahora nos centraremos en la identificación y el análisis de estas funciones: CONCAT()CONCATENAR() y UNIRCADENAS() (1). Todas ellas se ubican dentro de la categoría Texto de funciones Calc, comparten el mismo objetivo y, en el caso de las dos primeras, una formulación o sintaxis muy similar y sencilla, consistente en una serie de entradas (directas y/o referencias a celdas) separadas por ; como, por ejemplo:

=CONCAT(B4;B5;B6;B$9:B$13)
=CONCATENAR("La casa de la Pradera ";"está en mitad del campo")

No obstante, en  lo relativo a la referencia a celdas CONCAT() resulta ser una evolución de desarrollo de CONCATENAR() (2), ya que...

  • Mientras  que CONCAT() admite referencias a celdas individuales, pero también a colecciones o intervalos de celdas
  • CONCATENAR() sólo admite referencias a celdas individuales (no a intervalos)

La función UNIRCADENAS() tiene una sintaxis ligeramente diferente, permitiendo trabajar mejor el formateo de la cadena resultante. Consta de:

  • Un delimitador, que es de uso obligatorio y que permite establecer una cadena de texto (incluyendo un  intervalo) que se usará como separadores de las cadenas entre sí.
  • Una variable booleana que permite omitir (1/True) o no (0/False) las cadenas vacías
  • Y las cadenas a unir, que pueden ser string en entrada directa (los números se admiten pero se convierten a string), cadenas vacías, referencias a celdas (con o sin contenido) e intervalos del celdas. A modo de ejemplo...

=UNIRCADENAS("--";1;1234; ;"adbd";B10;B4:B6)

... la función UNIRCADENA() especifica el string -- como separador, omite las cadenas vacías (valor 1 en el segundo parámetro) y contiene entradas directas desde teclado (número y string), pero también referencias a celdas individuales (B10) y a intervalos de celdas (B4:B6). El resultado es el que sigue:

1234--adbd--EEEE--PPPP--LLLL--MMMM (3)

El resultado de usar cualquiera de las tres funciones (4) es una única entrada de texto (los valores numéricos son tratados como texto) que puede ser todo lo larga que deseemos, por lo que es perfectamente válida para generar procesos de automatización de la composición de documentos, utilizándolas, claro está en combinación con otras funciones, como hemos hecho en algunos soportes (no docap) de trabajo sobre pruebas de evaluación (5)

Aunque realmente el uso de estas funciones puede ser más que suficiente para la automatización de un documento, sin que sea necesario recurrir a un script OOo Basic, lo cierto es que sí parece conveniente trabajar con esta segunda opción, no tanto para la concatenación en sentido estricto como para la generación de textos (string) a concatenar. Con frecuencia para ello es necesario utilizar estructuras condicionales anidadas complejas, con lo que, por la que ya vimos en una entrada anterior, un script OOo Basic resulta ser más legible, sostenible y funcional que su equivalente en funciones condicionales Calc (SI() principalmente). En ese contexto, una vez que se utiliza el IDE para una parte del proceso, no es infrecuente que se termine utilizando también para crear salidas de texto basadas en la concatenación mediante procedimientos OOo Basic.

Además la simplicidad con la que se concatenan cadenas de texto con variables, bien tipo String, bien numéricas, hace que sea sumamente sencillo realizar estos procesos desde OOo Basic, así que ya de puestos...

Ciertamente crear un contenido resultante de la concatenación de literales (datos introducidos directamente) y/o variables de diferentes tipo es sumamente sencillo. Básicamente se trata de enunciar esos contenidos enlazados bien con +, bien con &. Ambos caracteres cumplen, en la práctica, la misma función por lo que, salvo contadas ocasiones, se pueden usar indiscriminadamente y todas la veces que resulte necesario.

De hecho + sirve tanto para concatenar como para sumar, por lo que sólo cuando los literales (o las variables) son numéricos es conveniente diferenciar cuando nos interesa que se ejecute una suma y cuando una concatenación de dos números (6). Veamos ejemplos en un script cuya única función es de carácter didáctico:

dim txt As String
dim num1 As Integer, num2 As Integer
txt = "Hola Mundo"
num1 = 123
num2 = 893
MsgBox("Cadena1" + "cadena2")         (1)
MsgBox("Cadena1" & "Cadena2")         (2)
MsgBox("Cadena1 " + txt)                   (3)
MsgBox("Cadena1 " & txt)                   (4)
MsgBox("Cadena1 " + num1)               (5)
MsgBox("Cadena1 " & num1)               (6)
MsgBox(num1 + num2)                       (7)
MsgBox(num1 & num2)                       (8)
MsgBox(CStr(num1) & CStr(num2))     (9)

  • Se expresa la concatenación como salida por pantalla (MsgBox()), pero el procedimiento es exactamente igual si lo utilizamos para asignar como contenido de una variable.
  • (1) y (2) ilustran básicamente la concatenación de literales textuales y la igualdad del procedimiento + vs. &.
  • (3) y (4) combina literal con variable String. El comportamiento es el mismo que en el par anterior.
  • (5) y (6) son equivalentes al par precedente, sustituyendo la variable string por una variable Integer.
  • (7), (8) y (9) ilustran la diferencia operación (suma-> +) vs. concatenación (&) cuando empleamos dos variables numéricas. (8) y (9) dan el mismo resultados (concatenan dos expresiones numéricas) En realidad ambas operan sobre variables numéricas convertidas a String, (8) de forma implícita, (9) explicitando la conversión mediante el uso de la función CStr(). Aunque no resulte necesario, se recomienda utilizar este procedimiento cuando se desee tratar a un variable numérica como variable String. En buena lógica, lo mismo debería aplicarse en (5) y en (6)
Pensando en la claridad de la codificación, quizá lo más apropiado sea utilizar + únicamente como operador aritmético y emplear & para concatenar contenidos. De este modo no hay confusión posible. Este modo de proceder...
  • Eliminaría del listado las alternativas (1), (3), (5) y (8)
  • Exigiría utilizar CStr() en (6)
  • Eliminaría (7) o lo conservaría (7) como ejemplo de operador aritmético (no de concatenación)
  • Y mantendría (9) como alternativa a (8).

En la concatenación, o mejor sería decir  en el formateo de las cadenas resultantes de la concatenación también cabe utilizar algunos recursos que facilitan la composición de textos, como puede ser el uso del salto de línea (Chr(13)). Su repetición nos permite aumentar el número de saltos de línea (7).

NOTAS

(1) Ya sabes que lo que sigue después de este análisis es estudiar las alternativas OOo Basic y, en otro momento y entrada, el estudio del modo en que se resuelven problemas similares en Python. Pero aun queda para hacer todo este recorrido.
(2) CONCAT() es de creación más reciente que CONCATENAR()está disponibles desde la versión 5.2 de LibreOffice
(3) Este caprichoso resultado es el que deriva de la prueba realizada. Las dos primeras cadenas fueron introducidas directamente, la tercera es una cadena vacía que queda omitida por el uso de 1 en el segundo parámetro y el resto corresponden al contenido de la celda B10 y del intervalo B4:B6 respectivamente. 
(4) Hasta donde yo entiendo, sólo la necesidad de concatenar intervalos de celdas condiciona la elección de CONCAT() sobre CONCATENAR(). El uso de UNIRCADENA() vendrá dado por el interés que tenga especificar separadores de cadenas específicos y/o omitir cadenas vacías. No encuentro otras razones que justifiquen su uso. 
(5) En otro momento comentaré alguno de estos soportes que he empleado como recurso para el registro de datos en relación al uso de determinados test. Estos soportes contaban con una hoja que servía directamente de informe o indirectamente, tras su conversión a .pdf. 
(6) En realidad, cuando concatenamos dos números con &, de forma implícita OOo Basic convierte ambos números en String. Por claridad en la codificación, aunque no sea necesario, es conveniente realizar la conversión de número a String mediante la función CStr()
(7) Chr(10) parece cumplir la misma función que Chr(13): generar un salto de línea. De todas formas, aunque estos procedimientos son muy simples, en cuanto a formateo del texto Grabar macro permite enriquecer estas opciones de forma significativa, pero no entra dentro del contenido de esta entrada.

miércoles, 6 de diciembre de 2023

Procedimientos. Datos.

Calc. Función. Script como función

Hemos visto en entradas que preceden a la actual, diferentes aspectos de la configuración de la condicionalidad/opcionalidad/iteración y hemos opuesto el uso de funciones Calc (SI(), por ejemplo) al uso de script OOo Basic. Pero bajo determinadas condiciones, Calc asume como funciones propias los script creados con OOo Basic.


En esta entrada vamos a profundizar en esta posibilidad, en sus condicionantes y también en sus limitaciones.

Ya sabemos que accedemos a las funciones Calc bien desde la línea de entrada (de funciones), bien desde el icono que nos da acceso al Asistente para funciones. También sabemos que para acceder a un script debemos hacerlo desde el menú Herramientas/Macros/Ejecutar macro, o bien desde un botón de comando creado en la hoja como elemento de formulario y asociado previamente a la macro en cuestión.

Estos conocimientos nos van a seguir siendo útiles y lo que añado en esta entrada es una forma alternativa y de utilidad ciertamente limitada, pero de interés: acceder a ciertos script desde la línea de entrada de funciones Calc.

Estos script pueden presentarse identificados como subrutinas (Sub...End Sub) y van a funcionar (inicialmente al menos) de forma correcta, pero no recomiendo formularlos de ese modo, ya que pueden presentar un comportamiento un tanto errático. Estas subrutinas pueden serlo en efecto, pero también pueden presentar una formulación mixta entre el esperado en una subrutina y el propio de una función(1)

También, sean identificados como subrutinas o como funciones, pueden contener variables internas personalizables (esto es: que necesiten una entrada de datos desde, por ejemplo, InputBox()), pero personalmente tampoco recomiendo este tipo de construcción mientras LibreOffice-Calc no mejore su comportamiento al usarlas: se pueden generar insistentes demandas de entrada de datos (¿bucles?) que no vienen a cuento.

Ahora bien, existe un tipo de estructura que funciona perfectamente y sin que se observen inconvenientes, pudiendo ser empleadas desde rutinas que contienen su llamada (2) como desde la línea de entrada de funciones de Calc, y del mismo modo que se llama a una función propia de Calc (como la función condicional SI(), por ejemplo). Me estoy refiriendo a las funciones creadas con OOo Basic.

Recordemos que para escribir correctamente una función debemos seguir los siguientes pasos:
  1. Nombrarla siguiendo esta sintaxis: Function NombreFuncion(Var1 As Tipo[...]) As Tipo
  2. Declarar, si existen, variables propias o privadas de la función y darles los valores que corresponda.
  3. Formular la operación que incluye la(s) variable(s) indicadas entre el paréntesis de la función (parámetros). Aquí entra también el uso de las estructuras condicionales o cíclicas, simples o complejas, que sean necesarias.
  4. Asignar al nombre de la función la salida o resultado de la(s) operación(es) anteriores. Debe corresponder en tipología a lo indicado en la denominación de la función: NombreFuncion=Operación/VariableQueContieneElResultado 
  5. Y cierre de la función: End Function 
Un ejemplo muy sencillo:

Function multiplicados(Num As Integer) As Integer
Dim Multi As Integer, Resulta As Integer
Multi = 5
Resulta = Num * Multi
multiplicados = Resulta
End Function

La forma de llamar a estas funciones es la misma que a cualquier otra función Calc (desde Línea de entrada) o a cómo las llamamos desde un script:

 NombreFuncion(Valores/VariablesDeParametros) -> multiplicados(12)

Si lo hacemos (y de eso se trata ahora) desde Línea de entrada (de funciones) en Calc, obtendremos el resultado en la celda desde la que la llamamos...



... pero al contrario de lo que pasa con las funciones propias de Calc, nuestras funciones no se encuentran en los listados del Asistente para funciones, sino en la pestaña Estructura...

... desde donde también podremos llamar a la función (ventana Fórmula, tras el signo =), aunque posiblemente con algún mensaje de error que no se corresponde con tal, por lo que se puede eludir sin más complicación, pero que hace que esta forma de llamar a la función no sea mi preferida (3)

Estas nuestras funciones son accesibles desde Calc estando ubicadas en la misma hoja de cálculo (obviamente en una Biblioteca/Librería y en un Módulo) o en Mis macros y diálogos. En el primer caso estará disponible para la hoja que la contiene y en el segundo para cualquier hoja de cálculo en que deseemos usarla. Obviamente, salvo circunstancias especiales, es la segunda opción la más rendimiento nos va a proporcionar. 

NOTAS

(1) Recuerda que en OOo Basic se diferencia entre subrutina y función, incluso en  la forma de identificar cada una de ellas: Sub Nombre().... End Sub (subrutina) frente a Function Nombre()... End Function (función)
(2) Y por tanto activadas desde botones de comando o directamente desde Ejecutar macro.
(3) Llamar a la función desde Línea de entrada resulta ser más directo y más seguro, por lo que recomiendo utilizar esta opción. 

sábado, 29 de julio de 2023

Funciones.

Macro, subrutina y función

Para entender en qué consisten las funciones hay que tener en cuenta dos cuestiones: las instrucciones específicas que incorpora todo lenguaje para realizar determinadas tareas y el modo en que programamos los script.



La primera de estas cuestiones nos lleva a pensar en las funciones que incorpora todo lenguaje y que sirven para realizar "funciones" concretas y específicas. Un ejemplo son las funciones de conversión de tipos de variables, como CInt() que sirve para convertir una variable de cualquier tipo (string, por ejemplo) a numérica tipo Integer.

La segunda cuestión nos lleva a plantearnos los modos de programar, cuestión ésta de mayor complejidad conceptual y práctica que la anterior, pero ineludible a partir de cierto nivel de extensión y complejidad del código.

La forma de programar básica puede describirse como lineal: el conjunto de tareas que se definen en el algoritmo se convierten en líneas de código que se van sucediendo una tras otra hasta la conclusión del proceso.

Un ejemplo de ello es la forma de programar en el lenguaje Basic al inicio de la popularización  de los ordenadores. Otra, sin ir muy lejos, la que supone grabar una macro mediante Grabar macro.

Otra forma de plantearse la creación de programas se basa en reutilizar segmentos de código para simplificar el programa y hacerlo más legible y funcional. La forma más sencilla en que se manifiesta esta opción consiste en llamar a un script desde otro script, algo que también podemos hacer con macros. 

Sub EscribirMsgA
Dim sMsg As String
sMsg = "Hola Mundo 1"
MsgBox sMsg,64, "MENSAJE"
End Sub

 

Sub Receptor1
MsgBox "Mensaje desde script",64,"RECEPTOR 1"
Call  EscribirMsgA
End Sub

En este caso el script EscribirMsgA es funcional en si mismo,  pero también podemos reutilizarlo desde un segundo script (Receptor1llamándolo mediante la expresión Call, de la que podemos prescindir ya que se da por supuesta. Y lo dicho: también podemos hacerlo con una macro en sustitución del script EscribirMsgAaunque en este caso para escribir un texto directamente en el documento Writer.

A pesar de las ventajas de esta opción, también  tiene sus limitaciones; y la más importante es quizá, que es necesario modificar (en este ejemplo) el texto del mensaje en el script (o en la macro) subordinado, lo que en programas largos supone mucho trabajo e incrementa la probabilidad de cometer errores. 

Frente a estas opciones, funcionales pero limitadas, tenemos las subrutinas y las funciones. Empezaremos por aclarar en qué se diferencian ambas.

En OOo Basic las funciones se diferencian de las subrutinas claramente: mientras que las funciones devuelven  un valor (un  dato) y las subrutinas hacen algo, así que lo que "devuelven" es eso que hacen.

Veamos cómo crear una subrutina a partir del script anterior:

Sub EscribirMsgB (sMsg As String)
MsgBox sMsg,64, "MENSAJE"
End Sub

 

Sub Receptor2
MsgBox "Mensaje desde script",64,"RECEPTOR 2"
        EscribirMsgB ("Hola Mundo")
End Sub

Vamos a analizar ahora las diferencias entre EscribirMsgA (script) y EscribirMsgB (sMsg As String) (subrutina), aunque en cosa de denominación podrás comprobar que existe cierta confusión: en la documentación se suele emplear indistintamente el término macro, pero yo prefiero diferenciar entre macro, script (o rutina) y subrutina (1).

  • El script auxiliar (el primero) funciona con independencia del script principal (el segundo); la subrutina no.
  • La subrutina incorpora un identificador de variable en el paréntesis que lleva tras el nombre. En el script no existe dicho paréntesis y, en caso de identificarse una variable asimilable a la que declara la subrutina (es el caso de EscribirMsgA) se declara dentro del script y asigna contenido también dentro del script.
  • Mientras que en la llamada al script desde el principal (Receptor1) simplemente se llama al script auxiliar (Call  EscribirMsgA), al ser llamada la subrutina es necesario añadir un paréntesis tras el nombre y un contenido, en este caso un texto  (EscribirMsgB ("Hola Mundo")(2)

También podemos convertir las macros simples (algunas de ellas) en subrutinas, pero no en funciones, ya que las macros siempre hacen algo. Para ello, y en primer lugar, tenemos que diferenciar las que llamamos variables de sistema (que son de tipo objeto) de las que utilicemos para dar contenido concreto a la macro. A estas segundas son a las que daremos el tratamiento de subrutina, ya que las primeras, es posible darlas ese tratamiento, pero también otros, siendo preferible optar por uno más propio del trabajo con macros simples. Lo veremos a continuación en el ejemplo que vamos a desarrollar en base a la macro de escritura de texto.

Sub EscribirMsgC  (sTexto As String)
Dim document   as object
Dim dispatcher as object
document   = ThisComponent.CurrentController.Frame
dispatcher = createUnoService("com.sun.star.frame.DispatchHelper")
Dim args2(0) as new com.sun.star.beans.PropertyValue
args2(0).Name = "Text"
args2(0).Value = sTexto
dispatcher.executeDispatch(document, ".uno:InsertText", "", 0, args2())
End Sub

 

Sub Receptor3
MsgBox "Mensaje desde macro",64,"RECEPTOR 3"
EscribirMsgC ("Hola Mundo")
End Sub
  • Puedes observar que he formulado la macro del mismo modo que la subrutina. Gracias a ello, de hecho, he convertido la macro en una subrutina, aunque sería más correcto decir que esta formulación está a medio camino entre macro y subrutina, ya que mantiene componentes y formas propias de las macros, incorporando otras propias de la subrutinas. También sus "limitaciones": no funciona autónomamente, debiendo ser llamada desde un script "principal". 
  • El script "receptor" trata la macro como subrutina, tal y como podemos observar en la forma en que la llama (EscribirMsgC ("Hola Mundo"))

Frente a las subrutinas, en OOo Basic las funciones se diferencian claramente ya en su formulación: al denominarlas (apertura y cierre) se debe usar el identificador Function:

Function AreaCuadrado( iLado As Integer) As Double
    Dim dArea as Double
    AreaCuadrado = iLado * iLado
End Function

Esto es:
    • La función se declara con la expresión Function y finaliza con  End Function.
    • La función no funciona independientemente, necesita ser "llamada" desde un script principal.
    • Además del paréntesis donde se declaran las variables, también finaliza con una tipificación (As Double) que permite entender qué tipo de valor devuelve (numérico Double, en este caso) así como que la propia función es una variable, como de hecho lo es (o como tal se comporta), ya que devuelve un valor (el que contiene como variable)
    • El resultado del procesamiento interno de la función se asigna a la misma función a modo de variable (volvemos al punto anterior): AreaCuadrado = iLado * iLado. Otra formulación no provoca que la "función" dé error, pero tampoco devuelve nada.
Veamos cómo se formula el que llamamos script principal:

Sub Cuadrado
Dim sResultado As String
Dim iLong As Integer
Dim dArea
iLong = InputBox ("Dime el lado del cuadrado")
dArea = AreaCuadrado(iLong)
sResultado = "El cuadrado de lado " & iLong & " tiene un área de " & dArea
MsgBox (sResultado,64,"AREA DEL CUADRADO")
End sub

Puedes observar que la "llamada" a la función se diferencia de la llamada a la subrutina en que en la primera utilizamos la asignación a una variable, lo cual no hacemos en la llamada a subrutina puesto que la función es un valor (se comporta como una variable que tiene asignado un dato o valor), por lo que debe ser recogido por (y asignado a) una variable. Después podrá ser tratado como dato, tal y como sucede en este ejemplo en su concatenación en la variable string sResultado (3)

NOTAS

(1) Las funciones se diferencian claramente, por lo que no existen estos problemas terminológicos.
(2) Podríamos sustituir el texto por una variable, pero en este ejemplo he optado por la formulación más sencilla. En ese caso, dicha variable es diferente de la que se declara en la subrutina, aunque termina cumpliendo la misma función que aquella. Esta equivalencia viene facilitada por la funcionalidad compartida de ambas: complementan la forma en que se escribe la subrutina.
(3) En la que contradigo lo que he considerado conveniente en otra entrada: no convierto las variables numéricas en string, como sería recomendable, por lo que una buena formulación de esta instrucción sería la siguiente: sResultado = "El cuadrado de lado " & CStr(iLong) & " tiene un área de " & CStr(dArea)