El lenguaje de plantillas de calibre

El lenguaje de plantillas de calibre es un lenguaje específico de calibre que se utiliza en todo calibre para tareas como especificar rutas de archivo, dar formato a valores y calcular el valor de las columnas especificadas por el usuario. Ejemplos:

  • Especifar la estructura de carpetas y los nombres de archivo al guardar archivos de la biblioteca de calibre en el disco o en el lector de libros electrónicos.

  • Definir reglas para añadir iconos y colores a la lista de libros de calibre.

  • Definir columnas virtuales que contienen datos de otras columnas.

  • Búsqueda avanzada en la bibliotecas.

  • Búsqueda y sustitución avanzada de metadatos.

El lenguaje está construido en torno al concepto de una «plantilla», que especifica qué metadatos del libro se usan, las operaciones que se realizan sobre los metadatos y qué formato se aplica.

Plantillas básicas

Una plantilla básica consiste en una o varias expresiones de plantilla. Una expresión de plantilla consiste en texto y nombres entre llaves ({}) que se sustituyen por los metadatos correspondientes del libro que está siendo procesado. Por ejemplo, la plantilla predeterminada de calibre para guardar libros en un dispositivo tiene 4 expresiones de plantilla:

{author_sort}/{title}/{title} - {authors}

Para el libro «The Foundation» de «Isaac Asimov» da lugar a:

Asimov, Isaac/The Foundation/The Foundation - Isaac Asimov

Las barras no son expresiones de plantilla porque no están entre {}. Este tipo de texto se mantiene donde aparece. Por ejemplo, si la plantilla es:

{author_sort} Some Important Text {title}/{title} - {authors}

entonces para «The Foundation» la plantilla produce:

Asimov, Isaac Some Important Text The Foundation/The Foundation - Isaac Asimov

Una expresión de plantilla tiene acceso a todos los metadatos disponibles en calibre, incluidas las columnas personalizadas que haya creado, usando su el nombre de búsqueda de una columna. Para obtener el nombre de búsqueda o consulta de una columna (también llamada campo), pase el cursor sobre el encabezado de la columna en la lista de libros de calibre. Los nombres de búsqueda para las columnas personalizadas siempre empiezan por #. Para columnas personalizadas de tipo serie, siempre hay un campo adicional llamado #nombre de búsqueda_index que es el índice de serie para el libro en la serie. Por ejemplo, si tiene una columna personalizada de serie llamada #miserie, también habrá una columna llamada #miserie_index. La columna de índice para la serie normal se llama series_index.

Además de los campos basados en columnas normales, también puede usar:

  • {formats} - Una lista de los formatos disponibles en la biblioteca de calibre para un libro

  • {identifiers:select(isbn)} - El ISBN del libro

Si un campo de metadatos para un libro determinado no está definido, entonces el campo en la plantilla se sustituye por un texto vacío (''). Por ejemplo, considere la siguiente plantilla:

{author_sort}/{series}/{title} {series_index}

Si el libro de Asimov «Second Foundation» está en la serie «Foundation», la plantilla produce:

Asimov, Isaac/Foundation/Second Foundation 3

Si no se ha introducido la serie para el libro, la plantilla produce:

Asimov, Isaac/Second Foundation

El procesador de plantillas elimina automáticamente barras múltiples y espacios iniciales o finales.

Formato avanzado

Además de sustituir metadatos, las plantillas pueden incluir texto adicional de manera condicional y controlar el formato de los datos sustituidos.

Incluir texto de mane condicional

A veces puede querer que aparezca un texto en la salida sólo si un campo no está vacío. Un caso común es series y series_index, para los que puede querer o nada o ambos valores separados por un guión. calibre tiene en cuenta este caso usando una sintaxis de expresión de plantilla especial.

Por ejemplo, y usando el caso anterior de la serie «Foundation», suponga que quiere que la plantilla produzca Foundation - 3 - Second Foundation. Esta plantilla cumple el cometido:

{series} - {series_index} - {title}

Sin embargo, si un libro no tiene serie, la plantilla producirá - - el título, que probablemente no sea lo que deseaba. Normalmente preferiría que el resultado fuese el título sin los guiones. Puede conseguir esto usando la siguiente sintaxis de plantilla:

{campo:|prefijo|sufijo}

Esta expresión de plantilla dice que si campo tiene el valor XXXX, el resultado será prefijoXXXXXsufijo. Si campo está vacío (no tiene ningún valor) el resultado será un texto vacío (nada), porque el prefijo y el sufijo se ignoran. Tanto el prefijo como el sufijo pueden contener espacios.

No use subplantillas ({…}) o funciones (ver más adelante) en el prefijo o el sufijo.

Usando esta sintaxis, podemos resolver el problema anterior de los libros sin serie con esta plantilla:

{series}{series_index:| - | - }{title}

Los guiones se incluirán solamente si el libro tiene índice de serie, que sólo tendrá si tiene una serie. Continuando con el ejemplo de la serie «Foundation», la plantilla producirá Foundation - 1 - Second Foundation.

Notas:

  • Debe incluir los dos puntos tras el nombre de búsqueda si usa un prefijo o sufijo.

  • Debe incluir ambos caracteres | o ninguno. No está permitido usar sólo uno como en {campo:| - }.

  • Es posible asignar un texto vació para el prefijo o sufijo, como en {series:|| - }. La plantilla {title:||} es lo mismo que {title}.

Formato

Supongamos que queremos asegurarnos de que series_index aparezca con dígitos, con ceros a la izquierda. Se consigue de esta manera:

{series_index:0>3s} - Tres dígitos con ceros a la izquierda

Para obtener ceros a la derecha use:

{series_index:0<3s} - Tres dígitos con ceros a la derecha

Si usa índices de serie con decimales (por ejemplo 1.1), puede querer asegurarse de que los decimales queden alineados. Por ejemplo, puede que los índices 1 y 2.5 aparezcan como 01.00 y 02.50 para que se ordenen correctamente en un dispositivo que utilice un orden lexicográfico. Para ello, use:

{series_index:0>5.2f} - Cinco caracteres que incluyen dos dígitos con cero a la izquierda, un punto decimal y dos dígitos tras el punto decimal.

Si quiere sólo las dos primeras letras de los datos, use:

{author_sort:.2} - Sólo las primeras dos letras del orden de autor

Gran parte del formato del lenguaje de plantillas de calibre proviene de Python. Para obtener más detalles sobre la sintaxis de estas operaciones de formato avanzadas, vea la documentación de Python (en inglés).

Usar plantillas para definir columnas personalizadas

Las plantillas pueden usarse para mostrar información que no está en los metadatos de calibre o para mostrar los metadatos de una manera diferente al formato normal de calibre. Por ejemplo, puede querer mostrar el ISBN, un campo que calibre no muestra. Puede conseguirlo creando una columna personalizadad de tipo columna generada a partir de otras columnas (en lo sucesivo llamadas columnas compuestas), e introduciendo una plantilla para generar el texto mostrado. La columna mostrará el resultado de evaluar la plantilla. Por ejemplo, para mostrar el ISBN, cree la columna e introduzca {identifiers:select(isbn)} en el cuadro de plantilla. Para mostrar una columna que contenga los valores de dos campos personalizados de serie separados por una coma, use {#serie1:||,}{#serie2}.

Las columnas compuestas pueden utilizar cualquier opción de plantilla, incluidas las de formato.

Nota: No puede modificar los datos mostrados en una columna compuesta, debe modificar las columnas de origen. Si modifica una columna compuesta, por ejemplo pulsando dos veces sobre ella, calibre abrirá la plantilla para modificarla, no los datos resultantes.

Plantillas y controles de metadatos

Los paneles de conexiones se usan para cambiar los metadatos escritos en los libros durante las operaciones de guardado en disco y de envío a dispositivo. Un panel de conexiones le permite especificar una plantilla para suministrar los datos que se escribirán en los metadatos del libro. Puede usar los paneles de conexiones para modificar los siguientes campos: authors, author_sort, language, publisher, tags, title, title_sort. Esa función es útil para los que quieren usar metadatos diferentes en los libros de los dispositivos, para solucionar problemas de ordenación o de visualización.

Cuando cree un panel de conexiones, especifique el formato y dispositivo para los que se usará. Hay un dispositivo especial save_to_disk, que se usa para guardar formatos (en lugar de enviarlos a un dispositivo). Una vez que ha elegido el formato y dispositivo, elija los campos de metadatos para cambiar, y suministre plantillas para obtener los nuevos valores. Estas plantillas están conectadas con sus campos de destino, de ahí el nombre panel de conexiones. Por supuesto, puede usar columnas compuestas en estas plantillas.

Los paneles de conexiones son muy flexibles y pueden escribirse en modo de función única, en modo programación de plantillas, en modo de programación general o en modo de plantilla de Python.

Cuando un panel de conexiones pueda aplicarse (servidor de contenido, guardado en disco o envío a dispositivo), calibre busca los paneles definidos para elegir el correcto según el formato y dispositivo. Por ejemplo, para encontrar el panel de conexiones apropiado para enviar un libro EPUB a un dispositivo ANDROID, calibre busca en los paneles en el siguiente orden:

  • un panel de conexiones con una coincidencia exacta de formato y dispositivo, por ejemplo: EPUB y ANDROID

  • un panel de conexiones con una coincidencia exacta de formato y el dispositivo especial any device, por ejemplo EPUB y any device

  • un panel de conexiones con el formato especial any format y una coincidencia exacta de dispositivo, por ejemplo: any format y ANDROID

  • un panel de conexiones con any format y any device

Los campos etiquetas y autores tienen un trato especial, debido a que ambos pueden tener más de un elemento. Un libro puede poseer varias etiquetas y varios autores. Cuando indique que desea cambiar uno de estos campos, la plantilla se examina para comprobar si hay más de un elemento. Para las etiquetas, el resultado se divide dondequiera que calibre encuentre una coma. Por ejemplo, si la plantilla produce el valor Intriga, Terror, el resultado serán dos etiquetas: Intriga y Terror. No existe manera de poner una coma dentro de una etiqueta.

Lo mismo ocurre con los autores, pero usando un carácter diferente para el corte, un signo «&» en lugar de una coma. Por ejemplo, si la plantilla produce el valor Blogs, Joe&Posts, Susan, el libro acabará con dos autores, Blogs, Joe y Posts, Susan. Si la plantilla produce el valor Blogs, Joe;Posts, Susan, el libro tendrá un autor con un nombre peculiar.

Los paneles de conexiones afectan a los metadatos escritos en el libro cuando se guarda en disco o se escribe en un dispositivo. Los paneles de conexiones no afectan a los metadatos usados por las funciones Guardar en el disco Enviar al dispositivo para crear los nombres de archivo. En lugar de ello, los nombres de archivo se construyen usando las plantillas introducidas en la ventana de preferencias correspondiente.

Usar funciones en plantillas: modo de función única

Supongamos que desea mostrar el valor de un campo en mayúsculas, aunque normalmente el valor del campo tiene sólo las iniciales en mayúscula. Puede conseguir esto usando las funciones de plantilla. Por ejemplo, para mostrar el título en mayúsculas use la función uppercase, como en {title:uppercase()}. Para mostrarlo con las iniciales en mayúscula use {title:titlecase()}.

Las funciones van en la parte del formato de la plantilla, después de : y antes del primer | o del } de cierre si no se usa ningún prefijo o sufijo. Si tiene tanto un formato como una referencia de función, la función va después de otro :. Las funciones devuelven el valor de la columna especificada en la plantilla, modificado de manera adecuada.

La sintaxis para usar funciones es una de:

{lookup_name:function(arguments)}
{lookup_name:format:function(arguments)}
{lookup_name:function(arguments)|prefix|suffix}
{lookup_name:format:function(arguments)|prefix|suffix}

Los nombres de función deben is siempre seguidos de paréntesis de apertura y cierre. Algunas funciones requieren valores adicionales (argumentos), y éstos van dentro de los paréntesis. Los argumentos se separan por comas. Una coma literal (como texto, no como separador de argumentos) debe ir precedida de una barra invertida (\). El último (o único) argumento no puede contener un paréntesis de cierre textual.

Las funciones se evalúan antes de las especificaciones de formato y del prefijo y sufijo. Véase más abajo un ejemplo de cómo usar un formato y una función.

Importante: Si tiene experiencia en programación, tenga en cuenta que la sintaxis del modo de función única no es la que podría esperarse. Los textos van sin comillas y los espacios son importantes. Todos los argumentos se interpretan como constantes; no hay expresiones.

No use subplantillas ({…}) como argumentos de función. En su lugar, use el modo de programación de plantilla o el modo de programación general.

Notas sobre la ejecución de funciones en modo de función única:

  • Cuando se usan las funciones en modo de función única, el primer parámetro, valor, se sustituye automáticamente por el contenido del campo especificado en la plantilla. Por ejemplo, cuando se procesa la plantilla {title:capitalize()}, el contenido del campo title se pasa como el parámetro valor a la función capitalize.

  • En la documentación de funciones, la notación [algo]* significa que algo puede repetirse cero o más veces. La notación [algo]+ significa que algo se repite una o más veces (debe existir al menos una vez).

  • Algunas funciones usan expresiones regulares. En el lenguaje de plantillas, las expresiones regulares no distinguen entre mayúsculas y minúsculas.

Las funciones están documentadas en Referencia de las funciones de plantilla. La documentación indica qué argumentos requieren las funciones y qué hacen. Por ejemplo, aquí está la documentación de la función ifempty.

  • ifempty(valor, texto_si_vacío) – si valor no está vacío, devuelve dicho valor, en caso contrario devuelve texto_si_vacío.

La función requiere dos argumentos, valor y texto_si_vacío. Sin embargo, debido a que estamos usando el modo de función única, omitimos el argumento valor, pasando sólo texto_si_vacío. Por ejemplo, esta plantilla:

{tags:ifempty(No tags on this book)}

muestra las etiquetas de un libro, si tiene alguna. Si no tiene etiquetas, muestra No tags on this book.

Las siguientes funciones se pueden usar en el modo de función única porque su primer parámetro es valor.

  • capitalize(valor) – devuelve valor con la primera letra mayúscula y el resto en minúsculas.

  • ceiling(valor) – devuelve el menor entero que es mayor o igual a valor.

  • cmp(valor, y, mn, ig, my) – compara valor e y después de convertirlas en números.

  • contains(valor, patrón, texto_si_coincide, texto_si_no_coincide) – comprueba si valor coincide con la expresión regular patrón.

  • date_arithmetic(valor, cálculo, fmt) – calcula una nueva fecha a partir de valor usando cálculo.

  • floor(valor) – devuelve el mayor entero que es menor o igual a valor.

  • format_date(valor, texto_formato) – da formato a valor, que debe ser un campo de fecha, según texto_formato y devuelve un texto.

  • format_number(valor, plantilla) – interpreta valor como un número y le da formato usando una plantilla de formato de Python como {0:5.2f} o {0:,d} o ${0:5,.2f}.

  • fractional_part(valor) – devuelve la parte del valor tras el punto decimal.

  • human_readable(valor) – espera que valor sea un número y devuelve un texto que representa ese número en KB, MB, GB, etc.

  • ifempty(valor, texto_si_vacío) – si valor no está vacío, devuelve dicho valor, en caso contrario devuelve texto_si_vacío.

  • language_strings(valor, localizar) – devuelve los nombres de los idiomas identificados por los códigos de idioma (ver aquí los nombres y códigos) pasados como valor.

  • list_contains(valor, separador, [ patrón, encontrado, ]* no_encontrado) – interpreta valor como una lista de elementos separados por separador y comprueba patrón para cada elemento de la lista.

  • list_count(valor, separador) – interpreta el valor como una lista de elementos separados por separador y devuelve el número de elementos de la lista.

  • list_count_matching(valor, patrón, separador) – interpreta valor como una lista de elementos separados por separador y devuelve el número de elementos de la lista que coinciden con la expresión regular patrón.

  • list_item(valor, índice, separador) – interpreta valor como una lista de elementos separados po separador y devuelve el elemento número índice.

  • list_sort(valor, dirección, separador) – devuelve la lista valor ordenada lexicográficamente sin distinción de mayúsculas y minúsculas.

  • lookup(valor, [ patrón, clave, ]* clave_alternativa) – Cada patrón se compara con valor en orden.

  • lowercase(valor) – devuelve valor en minúsculas.

  • mod(valor, y) – devuelve floor del resto de valor / y.

  • rating_to_stars(valor, usar_media_estrella) – Devuelve valor como una serie de caracteres de estrella ().

  • re(valor, patrón, sustitución) – devuelve valor después de aplicar la expresión regular.

  • re_group(valor, patrón [, plantilla_para_grupo]*) – devuelve un texto formado por aplicación de la expresión regular patrón a valor, sustituyendo cada coincidencia

  • round(valor) – devuelve el entero más cercano a valor.

  • select(valor, clave) – interpreta valor como una lista de elementos separados por comas, con cada elemento de la forma id:valor_id (el formato de la columna identifier de calibre).

  • shorten(valor, car_izq, texto_medio, car_der) – devuelve una versión abreviada valor

  • str_in_list(valor, separador, [ texto, encontrado, ]+ no_encontrado) – interpreta valor como una lista de elementos separados por separador, y compara texto con cada valor de la lista.

  • subitems(valor, índice_inicio, índice_fin) – Esta función separa listas de elementos jerárquicos de tipo etiqueta, tales como los géneros.

  • sublist(valor, índice_inicio, índice_fin, separador) – interpreta valor como una lista de elementos separados por separador y devuelve una nueva lista con los elementos comprendidos entre la posición índice_inicio e índice_fin.

  • substr(valor, inicio, fin) – devuelve los caracteres entre la posición inicio y fin de valor.

  • swap_around_articles(valor, separador) – devuelve valor con los artículos puestos al final.

  • swap_around_comma(valor) – dado un valor de la forma B, A, devuelve A B.

  • switch(valor, [patrónN, valorN,]+ otro_valor) – para cada pareja patrónN, valorN, comprueba si valor coincide con la expresión regular patrónN

  • test(valor, texto_si_no_vacío, texto_si_vacío) – devuelve texto_si_no_vacío si valor no está vacío, devuelve texto_si_vacío en caso contrario.

  • titlecase(valor) – devuelve valor con las iniciales en mayúscula.

  • transliterate(valor) – Devuelve un texto en el alfabeto latino formado por aproximación del sonido de las palabras en valor.

  • uppercase(valor) – devuelve valor en mayúsculas.

Usar funciones y formato en la misma plantilla

Supongamos que tiene una columna personalizada con números enteros #myint, que quiere mostrar con ceros a la izquierda, como en 003. Una posibilidad es usa como formato 0>3s. Sin embargo, de manera predeterminada, si un número (entero o decimal) es igual a cero, el valor se muestra como un texto vacío, así que el valor cero producirá un texto vacío, no 000. Si quiere ver los valores como 000, debe usar tanto el texto de formato como la función ifempty para cambiar el valor vacío a cero de nuevo, la plantilla sería:

{#myint:0>3s:ifempty(0)}

Tenga en cuenta que puede usar también prefijo y sufijo. Si desea que el número aparezca como [003] o [000], use la plantilla:

{#myint:0>3s:ifempty(0)|[|]}

Modo de programación general

El Modo de programación general (MPG) sustituye las expresiones de plantilla por un programa escrito en el lenguaje de plantillas. La sintaxis del lenguaje está definida por la siguiente gramática:

program         ::= 'program:' expression_list
expression_list ::= top_expression [ ';' top_expression ]*
top_expression  ::= or_expression
or_expression   ::= and_expression [ '||' and_expression ]*
and_expression  ::= not_expression [ '&&' not_expression ]*
not_expression  ::= [ '!' not_expression ]* | concatenate_expr
concatenate_expr::= compare_expr [ '&' compare_expr ]*
compare_expr    ::= add_sub_expr [ compare_op add_sub_expr ]
compare_op      ::= '==' | '!=' | '>=' | '>' | '<=' | '<' |
                    'in' | 'inlist' | 'inlist_field' |
                    '==#' | '!=#' | '>=#' | '>#' | '<=#' | '<#'
add_sub_expr    ::= times_div_expr [ add_sub_op times_div_expr ]*
add_sub_op      ::= '+' | '-'
times_div_expr  ::= unary_op_expr [ times_div_op unary_op_expr ]*
times_div_op    ::= '*' | '/'
unary_op_expr   ::= [ add_sub_op unary_op_expr ]* | expression
expression      ::= identifier | constant | function | assignment | field_reference |
                    if_expr | for_expr | break_expr | continue_expr |
                    '(' expression_list ')' | function_def
field_reference ::= '$' [ '$' ] [ '#' ] identifier
identifier      ::= id_start [ id_rest ]*
id_start        ::= letter | underscore
id_rest         ::= id_start | digit
constant        ::= " string " | ' string ' | number
function        ::= identifier '(' expression_list [ ',' expression_list ]* ')'
function_def    ::= 'def' identifier '(' top_expression [ ',' top_expression ]* ')' ':'
                    expression_list 'fed'
assignment      ::= identifier '=' top_expression
if_expr         ::= 'if' condition 'then' expression_list
                    [ elif_expr ] [ 'else' expression_list ] 'fi'
condition       ::= top_expression
elif_expr       ::= 'elif' condition 'then' expression_list elif_expr | ''
for_expr        ::= for_list | for_range
for_list        ::= 'for' identifier 'in' list_expr
                    [ 'separator' separator_expr ] ':' expression_list 'rof'
for_range       ::= 'for' identifier 'in' range_expr ':' expression_list 'rof'
range_expr      ::= 'range' '(' [ start_expr ',' ] stop_expr
                    [ ',' step_expr [ ',' limit_expr ] ] ')'
list_expr       ::= top_expression
break_expr      ::= 'break'
continue_expr   ::= 'continue'
separator_expr  ::= top_expression
start_expr      ::= top_expression
stop_expr       ::= top_expression
step_expr       ::= top_expression
limit_expr      ::= top_expression

Notas:

  • una top_expression siempre tiene un valor. El valor de una expression_list es el valor de la última top_expression de la lista. Por ejemplo, el valor de la expression_list 1;2;'blabla';3 es 3.

  • En un contexto lógico, cualquier valor que no sea vacío es True (verdadero)

  • En un contexto lógico, el valor vacío es False (falso)

  • Textos y números pueden usarse indistintamente. Por ejemplo, 10 y '10' son lo mismo.

  • Los comentarios son líneas que empiezan por un carácter #. No se permiten comentarios que empiecen a mitad de línea.

Prioridad de operadores

La prioridad de los operadores (orden de evaluación) de mayor (se evalúa primero) a menor (se evalúa el último) es:

  • Llamadas a función, constantes, expresiones entre paréntesis, expresiones de declaración, expresiones de asignación, referencias de campo.

  • Más (+) y menos (-) unarios. Estos operadores se evalúan de derecha a izquierda.

    Estas y todas las otras operaciones aritméticas devuelven enteros si la expresión resulta en una parte decimal igual a cero. Por ejemplo, si la expresión devuelve 3.0 se cambia a 3.

  • Multiplicación (*) y división (/). Estas operaciones son asociativas y se evalúan de izquierda a derecha. Use paréntesis si quiere cambiar el orden de evaluación.

  • Suma (+) y resta (-). Estas operaciones son asociativas y se evalúan de izquierda a derecha.

  • Comparaciones numéricas y de textos. Estas operaciones devuelven '1' si la comparación tiene éxito, en caso contrario un texto vacío (''). Las comparaciones no son asociativas: a < b < c es un error de sintaxis.

  • Concatenación de textos (&). El operador & devuelve un texto formado por concatenación de las expresiones a su izquierda y derecha. Ejemplo: 'aaa' & 'bbb' devuelve 'aaabbb'. El operador es asociativo y se evalúa de izquierda a derecha.

  • Negación lógica unaria (!). Esta operación devuelve '1' si la expresión es False (da como resultado un texto vacío), en caso contrario ''.

  • Y lógico (&&). Esta operación devuelve '1' si ambas expresiones a derecha e izquierda son True, o un texto vacío ('') si alguna es False. Es asociativa, se evalúa de izquierda a derecha y con cortocircuito https://chortle.ccsu.edu/java5/Notes/chap40/ch40_2.html.

  • O lógico (||). Esta operación devuelve '1' si alguna de las expresiones a derecha e izquierda es True, o un '' si ambas son False. Es asociativa, se evalúa de izquierda a derecha y con cortocircuito https://chortle.ccsu.edu/java5/Notes/chap40/ch40_8.html.

Referencias de campo

Un elemento de tipo field_reference (referencia de campo) se evalúa como el valor del campo de metadatos nombrado por el nombre de búsqueda que sigue al $ o $$. Usar $ es equivalente a usar la función field. Usar $$ es equivalente a usar la función raw_field. Ejemplos:

* $authors ==> field('authors')
* $#genre ==> field('#genre')
* $$pubdate ==> raw_field('pubdate')
* $$#my_int ==> raw_field('#my_int')

Expresiones condicionales

Las expresiones condicionales (if_expression) evalúan primero la condition. Si la condition es True (un valor no vacío) entonces se evalúa expression_list en la sentencia then. Si es False, entonces se evalúa expression_list en la sentencia elif o else, si existen. Las partes elif y else son opcionales. Las palabras if, then, elif, else y fi están reservadas, no pueden usarse como nombres de identificador. Se pueden añadir espacios y saltos de línea donde sea conveniente. La condition es una top_expression, no usa expression_list, no se permite punto y coma. Los elementos de tipo expression_list son secuencias de top_expression separadas por punto y coma. Una expresión condicional devuelve el resultado de la última top_expression en la expression_list evaluada, o un texto vacío si no se evaluó ninguna expression_list.

Ejemplos:

* program: if field('series') then 'yes' else 'no' fi
* program:
      if field('series') then
          a = 'yes';
          b = 'no'
      else
          a = 'no';
          b = 'yes'
      fi;
      strcat(a, '-', b)

Ejemplo de if anidado:

program:
  if field('series') then
    if check_yes_no(field('#mybool'), '', '', '1') then
      'yes'
    else
      'no'
    fi
  else
    'no series'
  fi

Como se ha mencionado, un if produce un valor. Esto significa que todas las siguientes expresiones son equivalentes:

* program: if field('series') then 'foo' else 'bar' fi
* program: if field('series') then a = 'foo' else a = 'bar' fi; a
* program: a = if field('series') then 'foo' else 'bar' fi; a

Por ejemplo, este programa devuelve el valor de la columna series si el libro tiene una serie, de lo contrario, el valor de la columna title:

program: field(if field('series') then 'series' else 'title' fi)

Expresiones de bucle

Una expresión de bucle (for_expression) itera sobre una lista de valores, procesando uno cada vez. La list_expression debe resultar en el nombre de búsqueda de un campo de metadatos, como tags o #genero, o una lista de valores. La función range genera una lista de números. Si el resultado es un nombre de búsqueda válido, se obtiene el valor del campo y se usa el separador especificado para ese tipo de campo. Si el resultado no es un nombre de búsqueda válido, se supone que es una lista de valores. La lista se supone separada por comas, a no ser que use la palabra clave opcional separator, en ese caso los valores de la lista deben estar separados por el resultado de evaluar separator_expr. No se puede usar un separador si la lista es generada por range(). Cada valor de la lista se asigna a la variable especificada y luego se evalúa expression_list. Puede usar break para salir del bucle y continue para comenzar con la siguiente iteración.

Ejemplo: Esta plantilla elimina el primer nombre jerárquico para cada valor de Género (#genre`), construyendo una lista con los nuevos nombres.

program:
  new_tags = '';
  for i in '#genre':
    j = re(i, '^.*?\.(.*)$', '\1');
    new_tags = list_union(new_tags, j, ',')
  rof;
  new_tags

Si el Género original es Historia.Militar, Ciencia ficción.Historia alternativa, Léeme, la plantilla devolverá Militar, Historia alternativa, Léeme. Puede usar esta plantilla en Modificar metadatos en masa > Buscar y sustituir con Buscar establecido en plantilla para eliminar el primer nivel de la jerarquía y asignar el valor resultante a Género.

Nota: la última línea en la plantilla, new_tags, no es estrictamente necesaria en este caso, porque for devuelve el valor de la última top_expression en expression_list. El valor de una asignación (assignment) es el valor de su expresión, así que el valor del for es lo que se asignó a new_tags.

Definición de funciones

Si tiene código repetido en una plantilla, puede poner ese código en una función local. La palabra clave def comienza la definición. Le sigue el nombre de la función, la lista de argumentos y el código en la función. La definición de función termina con la palabra clave fed.

Los argumentos son posicionales. Cuando se invoca una función, los argumentos proporcionados se hacen corresponder de izquierda a derecha con los parámetros definidos, con el valor del argumento asignado al parámetro. Es un error proporcionar más argumentos que parámetros definidos. Los parámetros pueden tener valores predeterminados, como a = 25. Si no se proporciona un argumento para un parámetro, se usará el valor predeterminado, en caso contrario el parámetro será un texto vacío.

La instrucción return se puede usar en una función local.

Una función debe definirse antes de poder usarse.

Ejemplo: Esta plantilla calcula una duración aproximada en años, meses y días a partir de un número de días. La función to_plural() da formato a los valores calculados. Tenga en cuenta que el ejemplo también usa el operador &:

program:
      days = 2112;
      years = floor(days/360);
      months = floor(mod(days, 360)/30);
      days = days - ((years*360) + (months * 30));

      def to_plural(v, str):
              if v == 0 then return '' fi;
              return v & ' ' & (if v == 1 then str else str & 's' fi) & ' '
      fed;

      to_plural(years, 'year') & to_plural(months, 'month') & to_plural(days,'day')

Operadores de relación

Los operadores de relación devuelven '1' si la comparación es cierta, en caso contrario devuelven un texto vacío ('').

Hay dos formas de operadores de relación: comparaciones de texto y comparaciones numéricas.

Las comparaciones de texto usan el orden lexicográfico y no distinguen mayúsculas y minúsculas. Las comparaciones de texto permitidas son ==, !=, <, <=, >, >=, in, inlist e inlist_field. Para el operador in, el resultado de la expresión de la izquierda se interpreta como un patrón de expresión regular. El operador in es True si el valor de la expresión regular de la izquierda coincide con el valor de la expresión de la derecha.

El operador inlist es True si la expresión regular de la izquierda coincide con alguno de los elementos de la lista de la derecha, en la que los elementos están separados por comas. El operador inlist_field es True si la expresión regular de la izquierda coincide con alguno de los elementos del campo (columna) nombrado por la expresión de la derecha, usando el separador definido para el campo. Nota: el operador inlist_field requiere que la expresión de la derecha resulte en un nombre de campo, mientras que el operador inlist requiere que la expresión de la derecha resulte en un texto que contenga una lista separada por comas. Debido a esta diferencia, inlist_field es bastante más rápido que inlist porque no implica conversiones de texto o construcciones de listas. Las expresiones regulares no distinguen mayúsculas y minúsculas.

Los operadores de comparación numérica son ==#, !=#, <#, <=#, ># y >=#. Las expresiones de la izquierda y la derecha deben resultar en valores numéricos con dos excepciones: Tanto el valor textual None (campo no definido) como el texto vacío equivalen al valor cero.

Ejemplos:

  • program: field('series') == 'lala' devuelve '1' si la serie del libro es lala, en caso contrario devuelve ''.

  • program: 'l.a' in field('series') devuelve '1' si la serie del libro coincide con la expresión regular l..a (por ejemplo, lala, El asesino de reyes, etc.), en caso contrario devuelve ''.

  • program: 'ciencia' inlist $#genero` devuelve ``'1' si alguno de los valores obtenidos de los géneros del libro coincide con la expresión regular ciencia (por ejemplo Ciencia, Historia de la ciencia, Ciencia ficción, etc.), en caso contrario devuelve ''.

  • program: '^ciencia$' inlist $#genero devuelve '1' si alguno de los géneros del libro coincide con la expresión regular ^ciencia$ (por ejemplo Ciencia). Los géneros Historia de la ciencia y Ciencia ficción no coinciden.

  • program: 'asimov' inlist $authors devuelve '1' si algún autor coincide con la expresión regular asimov (por ejemplo Asimov, Isaac o Isaac Asimov), en caso contrario devuelve ''.

  • program: 'asimov' inlist_field 'authors' devuelve '1' si algún autor coincide con la expresión regular asimov (por ejemplo Asimov, Isaac o Isaac Asimov), en caso contrario devuelve ''.

  • program: 'asimov$' inlist_field 'authors' devuelve '1' si algún autor coincide con la expresión regular asimov$ (por ejemplo Isaac Asimov), en caso contrario devuelve ''. No coincide con Asimov, Isaac debido al punto de anclaje $ en la expresión regular.

  • program: if field('series') != 'lala' then 'nana' else 'blabla' fi devuelve 'nana' si la serie del libro no es lala, en caso contrario devuelve 'blabla'.

  • program: if field('series') == 'lala' || field('series') == '1632' then 'sí' else 'no' fi devuelve 'sí' si la serie es lala o 1632, en caso contrario devuelve 'no'.

  • program: if '^(lala|1632)$' in field('series') then 'sí' else 'no' fi devuelve 'sí' si la serie es lala o 1632, en caso contrario devuelve 'no'.

  • program: if 11 > 2 then 'sí' else 'no' fi devuelve 'no' porque el operador > realiza una comparación lexicográfica.

  • program: if 11 ># 2 then 'sí' else 'no' fi devuelve 'sí' porque el operador ># realiza una comparación numérica.

Funciones en modo de programación general

Consulte Referencia de las funciones de plantilla para ver la lista de funciones predefinidas en el lenguaje de plantillas.

Notas:

  • A diferencia del modo de función única, en el modo de programación general debe especificar el primer parámetro valor.

  • Todos los parámetros son elementos de tipo expression_list (ver la gramática más arriba).

Programas más complejos en expresiones de plantilla: modo de programación de plantillas

El modo de programación de plantillas (MPP) es una mezcla entre el modo de programación general y el modo de función única. El MPP se diferencia del modo de función única en que permite escribir expresiones de plantilla que hacen referencia a otros campos de metadatos, usar funciones anidadas, modificar variables y hacer operaciones aritméticas. Se diferencia del modo de programación general en que la plantilla está entre caracteres { y } y no empieza por la palabra program. La parte del programa de la plantilla es una expression_list del modo de programación general.

Un ejemplo: supongamos que quiere una plantilla que muestre la serie de un libro si la tiene, y en caso contrario muestre el valor del campo personalizado #genre. Esto no se puede hacer en el modo de función única, porque no se puede hacer referencia a otro campo en la expresión de la plantilla. En el MPP sí se puede. La siguiente expresión lo demuestra:

{series_index:0>7.1f:'ifempty($, -5)'}

El ejemplo muestra varias cosas:

  • El MPP se usa si la expresión empieza por :' y termina en '}. Cualquier otra cosa se supone que corresponde al modo de función única.

    Si la plantilla contiene un prefijo y un sufijo, la expresión termina con '| donde | es el delimitador para el prefijo. Ejemplo:

    {series_index:0>7.1f:'ifempty($, -5)'|prefix | suffix}
    
  • Las funciones deben llevar todos sus argumentos. Por ejemplo, las funciones estándar predefinidas deben tener el parámetro inicial valor.

  • La variable $ se puede usar como el argumento valor y representa el valor del campo nombrado en la plantilla, series_index en este caso.

  • los espacios en blanco se ignoran y se pueden utilizar en cualquier lugar dentro de la expresión.

  • los textos constantes se encierran en comillas del mismo tipo, ya sea ' o ".

En el MPP, usar los caracteres { y } en textos literales puede conducir a errores o resultados inesperados porque confunden al procesador de plantillas. Éste intenta interpretarlos como límites de expresiones de plantilla y no como caracteres. En algunos casos, pero no todos, puede sustituir un { por [[ y un } por ]]. En general, si un program contiene caracteres { y }, debería usar el modo de programación general.

Modo de plantilla de Python

El modo de plantilla de Python (MPPy) le permite escribir plantillas usando el lenguaje Python nativo y la interfaz de programación de calibre. La interfaz de programación de base de datos será la más útil, pero en este manual no se discutirán más detalles. Las plantillas MPPy son más rápidas y pueden realizar operaciones más complicadas, pero debe saber escribir código Python usando la interfaz de programación de calibre.

Una plantilla MPPy empieza por:

python:
def evaluate(book, context):
    # book is a calibre metadata object
    # context is an instance of calibre.utils.formatter.PythonTemplateContext,
    # which currently contains the following attributes:
    # db: a calibre legacy database object.
    # globals: the template global variable dictionary.
    # arguments: is a list of arguments if the template is called by a GPM template, otherwise None.
    # funcs: used to call Built-in/User functions and Stored GPM/Python templates.
    # Example: context.funcs.list_re_group()

    # your Python code goes here
    return 'a string'

Puede añadir el texto anterior a la plantilla usando el menú contextual, al que normalmente se accede pulsando el botón derecho. Los comentarios no son importantes y pueden eliminarse. Debe usar la sangría de Python.

El objeto de contexto admite str(context), que devuelve un texto con el contenido del contexto y context.attributes, que devuelve una lista con los nombres de los atributos del contexto.

El atributo context.funcs le permite invocar funciones incorporadas y de usuario, así como plantillas guardadas MPG o Python, de manera que puede ejecutarlas directamente en el código. Las funciones se identifican por sus nombres. Si el nombre entra en conflicto con una palabra clave de Python, añada un guión bajo al final. Ejemplos:

context.funcs.list_re_group()
context.funcs.assert_()

He aquí un ejemplo de una plantilla MPPy que genera una lista de todos los autores de una serie. La lista se guarda en una Columna generada a partir de otras columnas, funciona como etiquetas. Se muestra en los detalles del libro y tiene la opción en líneas separadas activada (en Preferencias > Apariencia > Detalles del libro). Esta opción requiere que la lista esté separada por comas. Para satisfacer este requisito la plantilla convierte las comas en los nombres de autor a punto y coma y luego construye una lista de autores separados por comas. Los autores se ordenan, razón por la cual la plantilla usa author_sort.

python:
def evaluate(book, context):
    if book.series is None:
        return ''
    db = context.db.new_api
    ans = set()
    # Get the list of books in the series
    ids = db.search(f'series:"={book.series}"', '')
    if ids:
        # Get all the author_sort values for the books in the series
        author_sorts = (v for v in db.all_field_for('author_sort', ids).values())
        # Add the names to the result set, removing duplicates
        for aus in author_sorts:
            ans.update(v.strip() for v in aus.split('&'))
    # Make a sorted comma-separated string from the result set
    return ', '.join(v.replace(',', ';') for v in sorted(ans))

La salida en los detalles del libro tiene este aspecto:

Diálogo de conversión de libros electrónicos

Plantillas guardadas

El modo de programación general y el modo de plantilla de Python permiten guardar plantillas e invocarlas desde otra plantilla, casi como invocar funciones guardadas. Puede guardar plantillas en Preferencias > Avanzado > Funciones de plantilla. Hay más información en el diálogo correspondiente. Puede invocar una plantilla de la misma manera que una función, pasando argumentos por posición si lo desea. Un argumento puede ser una expresión. Ejemplos para invocar una plantilla, suponiendo que la plantilla guardada se llame foo:

  • lala() – invoca la plantilla sin pasar argumentos.

  • lala(a, b) – invoca la plantilla pasando los valores de las variables a y b.

  • lala(if field('series') then field('series_index') else 0 fi) – si el libro tiene series, pasa el valor de series_index, en caso contrario pasa el valor 0.

En el MPG, puede obtener el valor de los argumentos pasados en la invocación de la plantilla guardada usando la función arguments. Esta función declara e inicializa las variables locales, parámetros en la práctica. Las variables son posicionales: obtienen el valor del argumento usado en la misma posición en la invocación. Si el argumento correspondiente no aparece en la invocación, arguments asigna a esa variable el valor predeterminado suministrado. Si no hay valor predeterminado, será un texto vacío. Por ejemplo, la siguiente función arguments declara dos variables: key y ``alternate`:

arguments(key, alternate='series')

Ejemplos, suponiendo de nuevo que la plantilla guardada se llame lala:

  • lala('#miserie') – a la variable key se le asigna el valor '#miserie' y a la variable alternate se le asigna el valor predeterminado 'serie'.

  • lala('series', '#genero') a la variable key se le asigna el valor 'series' y a la variable alternate se le asigna el valor '#genero'.

  • lala() – a la variable key se le asigna un texto vacío y a la variable alternate se le asigna el valor predeterminado 'series'.

En el MPPy los argumentos se pasan en el parámetro arguments, que es una lista de textos. No existe la posibilidad de especificar valores predeterminados. Debe comprobar la longitud de la lista arguments para asegurarse de que el número de argumentos es el esperado.

Una buena manera de probar las plantillas guardadas es usar el diálogo Prueba de plantillas. Para facilitar su acceso, asígnele un atajo de teclado en Preferencias > Avanzado > Atajos > Prueba de plantillas. Si le asigna un atajo de teclado podrá alternar rápidamente entre la prueba de plantillas y la modificación del código de programa de la plantilla.

Pasar información adicional a las plantillas

Un desarrollador puede decidir pasar información adicional al procesador de plantillas, como metadatos del libro específicos para una aplicación o información sobre lo que se quiere que haga el procesador. Una plantilla puede acceder a esta información y usarla durante la evaluación.

Desarrollador: cómo pasar información adicional

La información adicional es un diccionario Python que contiene pares nombre_de_variable: valor de variable donde los valores deberían ser textos. La plantilla puede acceder al diccionario, creando variables locales con nombre nombre_de_variable y valor valor de variable. El usuario no puede cambiar el nombre, así que lo mejor es usar nombres que no entren en conflicto con otras variables locales, por ejemplo añadiendo un guión bajo al principio del nombre.

El diccionario se pasa al procesador de plantillas (el formatter) usando el argumento global_vars=diccionario. La forma completa del método es:

def safe_format(self, fmt, kwargs, error_value, book,
                column_name=None, template_cache=None,
                strip_results=True, template_functions=None,
                global_vars={})

Creador de plantillas: cómo acceder a la información adicional

Puede acceder a la información adicional (el diccionario globals) en una plantilla usando la función de plantilla:

globals(id[=expression] [, id[=expression]]*)

donde id es cualquier nombre de variable permitido. Esta función comprueba si la información adicional prevista por el desarrollador contiene el nombre. Si es así, la función asigna el valor a una variable local con ese nombre. Si el nombre no está presente en la información adicional y se suministra una expresión, se evalua dicha expresión y el resultado se asigna a la variable local. Si no existe ni un valor ni una expresión, la función asigna el texto vacío ('') a la variable local.

Una plantilla puede establecer un valor en el diccionario globals usando la función de plantilla:

set_globals(id[=expression] [, id[=expression]]*)

Esta función asigna la pareja id:valor del diccionario globals, donde valor es el valor de la variable local de la plantilla id. Si la variable no existe, valor se toma como el resultado de evaluar expresión.

Notas sobre las diferencias entre modos

Los tres modos de programación, el modo de función única (MFU), el modo de programación de plantillas (MPP) y el modo de programación general (MPG) funcionan de distinta manera. MFU pretende ser «sencillo» y oculta muchos elementos del lenguaje de programación.

Diferencias:

  • En el MFU el valor de la columna se pasa siempre como un primer argumento «invisible» a la función que se incluye en la plantilla.

  • El MFU no hace distinciones entre variables y textos; todos los valores son textos.

  • La siguiente plantilla MFU devuelve el nombre de la serie o el texto 'no series':

    {series:ifempty(no series)}
    

    La plantilla equivalente en MPP es:

    {series:'ifempty($, 'no series')'}
    

    La plantilla equivalente en MPG es:

    program: ifempty(field('series'), 'no series')
    

    El primer argumento de ifempty es el valor del campo series. El segundo argumento es el texto no series. En MFU el primer argumento, el valor, se pasa automáticamente (es el argumento invisible).

  • Varias funciones de plantilla, como booksize() y current_library_name(), no admiten argumentos. Debido al «argumento invisible», no puede usar estas funciones en MFU.

  • Las funciones anidadas, en las que una función invoca a otra función para evaluar un argumento, no pueden usarse en MFU. Por ejemplo, esta plantilla, que pretende devolver los 5 primeros caracteres del valor de la serie convertidos en mayúsculas, no funcionará en MFU:

    {series:uppercase(substr(0,5))}
    
  • MPP y MPG permiten usar funciones anidadas. La plantilla anterior en MPP sería:

    {series:'uppercase(substr($, 0,5))'}
    

    En MPG sería:

    program: uppercase(substr(field('series'), 0,5))
    
  • Como se mencionó ateriormente en la sección modo de programación de plantillas, en el MPP, usar los caracteres { y } en textos literales puede conducir a errores o resultados inesperados porque confunden al procesador de plantillas. Éste intenta interpretarlos como límites de plantilla y no como caracteres. En algunos casos, pero no todos, puede sustituir un { por [[ y un } por ]]. En general, si un program contiene caracteres { y }, debería usar el modo de programación general.

Funciones de plantilla Python definidas por el usuario

Puede añadir funciones propias al procesador de plantillas. Dichas funciones pueden usarse en cualquiera de los tres modos de programación de plantillas. Las funciones se añaden en Preferencias > Avanzado > Funciones de plantilla. Las instrucciones se muestran en el correspondiente cuadro de diálogo. Tenga en cuenta que puede usar plantillas de Python para lo mismo. Dado que es más rápido ejecutar funciones definidas por el usuario que una plantilla de Python, las funciones definidas por el usuario pueden ser más eficientes, dependiendo de la complejidad de lo que haga la función o plantilla.

Notas especiales para plantillas de guardado o envío

Cuando una plantilla se usa como plantilla de «guardado a disco» o de «envío a dispositivo», ocurre un procesado especial. Los valores de los campos se sanean, eliminando caracteres especiales para los sistemas operativos por guiones bajos, incluyendo barras. Esto significa que el texto de los campos no puede usarse para crear carpetas. Sin embargo, las barras no se modifican en los textos de prefijo o sufijo, por lo que las barras en estos textos harán que se creen carpetas. Gracias a esto, es posible crear estructuras de carpetas de profundidad variable.

Por ejemplo, supongamos que quiere una estructura de carpetas serie/índice de serie - título, con la salvedad de que si la serie no existe el título debe estar en la carpeta superior. La plantilla para conseguir esto es:

{series:||/}{series_index:|| - }{title}

La barra y el guión sólo aparecen si la serie no está vacía.

La función lookup() nos permite realizar un procesado aún más complejo. Por ejemplo, supongamos que si un libro tiene una serie, entonces queremos una estructura de carpetas serie/índice de serie - título. Si el libro no tiene una serie, entonces queremos la estructura género/orden de autor/título. Si el libro no tiene género, queremos que use «Desconocido». Queremos seguir dos caminos completamente distintos según el valor de la serie.

Para lograr esto:

  1. Creamos un campo compuesto (démosle el nombre de consulta #aa) que contiene {series}/{series_index} - {title}. Si la serie no está vacía, esta plantilla produce serie/número_de_serie - título.

  2. Creamos un campo compuesto (démosle el nombre de consulta #bb) que contenga {#genre:ifempty(Desconocido)}/{author_sort}/{title}. Esta plantilla produce género/orden de autor/título, donde un género vacío se sustituye por Desconocido.

  3. Establecemos la plantilla de guardado en {series:lookup(.,#aa,#bb)}. Esta plantilla elige el campo compuesto #aa si la serie no está vacía y el campo compuesto #bb si la serie está vacía. Obtenemos por lo tanto dos rutas de guardado completamente diferentes según el campo series esté o no vacío.

Consejos

  • Use la prueba de plantillas para probar plantillas. Añada esta función al menú contextual para libros en lea biblioteca o asígnele un atajo de teclado.

  • Las plantillas pueden usar otras plantillas haciendo referencia a columnas compuestas construidas con la plantilla deseada. También puede usar plantillas guardadas.

  • En un panel de conexiones, puede establecer un campo como vacío (o lo que sea equivalente a vacío) utilizando la plantilla especial {}. Esta plantilla siempre producirá un texto vacío.

  • La técnica descrita anteriormente para mostrar los números incluso si son cero funciona con el campo estándar series_index.

Referencia de las funciones de plantilla