Manual:Funciones del analizador sintáctico

This page is a translated version of the page Manual:Parser functions and the translation is 100% complete.
MediaWiki extensions

Las funciones del analizador, añadida en MediaWiki 1.7, es un tipo de extension que se integra estrechamente con el analizador. La expresión «función del analizador» no debería confundirse con $2, que es una colección de funciones simples del analizador. (Véase Ayuda:Extensión:ParserFunctions para esa otra.)

Descripción

Mientras que una extensión de marcado está concebida para tomar texto sin procesar y devolver HTML al navegador, una función del analizador puede «interactuar» con otros elementos wiki de la página. Por ejemplo, la salida de una función de análisis se podría usar como parámetro de una plantilla o en la construcción de un enlace.

La sintaxis típica de una función del analizador es:

{{ #functionname: param1 | param2 | param3 }}

Para más información, consulta la documentación de Parser::setFunctionHook ( $id, $callback, $flags = 0 ). Según esta documentación,

La función de retrollamada (callback) debería tener la forma:
function myParserFunction( $parser, $arg1, $arg2, $arg3 ) { ... }
O con SFH_OBJECT_ARGS:
function myParserFunction( $parser, $frame, $args ) { ... }

La primera variante de la llamada pasa todos los argumentos como texto plano. La segunda pasa todos los argumentos como una matriz de PPNode , excepto el primero ($args[0]), que en la actualidad es texto, aunque esto puede cambiar en el futuro. Estos representan el wikitexto sin expandir. El parámetro $frame se puede utilizar para expandir estos argumentos según sea necesario. Esto se suele usar para el procesamiento condicional de forma que solo se evalúe el caso true con una función del analizador if- o switch-like. El objeto de marco también puede subir por el árbol del documento para obtener información sobre el llamador y dispone de funciones para determinar y gestionar la profundidad de la llamada, el tiempo de vida y si el resultado de la función del analizador es volátil.

Crear una función del analizador es ligeramente más complejo que crear una nueva etiqueta porque el nombre de la función debe ser una palabra mágica, una palabra clave que sea compatible con el uso de alias y localización.

Ejemplo simple

A continuación hay un ejemplo de una extensión que crea una función del analizador.

El registro va a extension.json y el código a includes/ExampleExtension.php, respectivamente:

Standard: Using the HookHandler interface:
extension.json
{
	"name": "ExampleExtension",
	"author": "Me",
	"version": "1.0.0",
	"url": "https://www.mediawiki.org/wiki/Extension:ExampleExtension",
	"descriptionmsg": "exampleextension-desc",
	"license-name": "GPL-2.0-or-later",
	"type": "parserhook",
	"MessagesDirs": {
		"ExampleExtension": [
			"i18n"
		]
	},
	"AutoloadClasses": {
		"ExampleExtensionHooks": "src/ExampleExtensionHooks.php"
	},
	"ExtensionMessagesFiles": {
		"ExampleExtensionMagic": "ExampleExtension.i18n.php"
	},
	"Hooks": {
		"ParserFirstCallInit": "ExampleExtensionHooks::onParserFirstCallInit"
	},
	"manifest_version": 1
}
{
	"name": "ExampleExtension",
	"author": "Me",
	"version": "1.0.0",
	"url": "https://www.mediawiki.org/wiki/Extension:ExampleExtension",
	"descriptionmsg": "exampleextension-desc",
	"license-name": "GPL-2.0-or-later",
	"type": "parserhook",
	"MessagesDirs": {
		"ExampleExtension": [
			"i18n"
		]
	},
	"AutoloadClasses": {
		"ExampleExtensionHooks": "src/ExampleExtensionHooks.php"
	},
	"ExtensionMessagesFiles": {
		"ExampleExtensionMagic": "ExampleExtension.i18n.php"
	},
	"Hooks": {
		"ParserFirstCallInit": "onParserFirstCallInit"
	},
	"HookHandlers": {
		"ExampleExtensionHooks": {
			"class": "MediaWiki\\Extension\\ExampleExtension\\Hooks"
		}
	},
	"manifest_version": 1
}
ExampleExtensionHooks.php
<?php

class ExampleExtensionHooks {

   // Registra cualquier retrollamada de renderización con el analizador
   public static function onParserFirstCallInit( Parser $parser ) {
      // Crea un gancho (''hook'') de función que asocia la palabra mágica <code>example</code> con renderExample()
      $parser->setFunctionHook( 'example', [ self::class, 'renderExample' ] );
   }

   // Muestra la salida de {{#example:}}.
   public static function renderExample( Parser $parser, $param1 = '', $param2 = '', $param3 = '' ) {
      // Los parámetros de entrada son wikitexto con las plantillas expandidas.
      // La salida también debería estar en forma de wikitexto.
      $output = "param1 is $param1 and param2 is $param2 and param3 is $param3";

      return $output;
   }

}
<?php

class ExampleExtensionHooks implements ParserFirstCallInitHook {

   // Register any render callbacks with the parser
   public function onParserFirstCallInit( $parser ) {
      // Create a function hook associating the <code>example</code> magic word with renderExample()
      $parser->setFunctionHook( 'example', [ $this, 'renderExample' ] );
   }

   // Render the output of {{#example:}}.
   public function renderExample( $parser, $param1 = '', $param2 = '', $param3 = '' ) {
      // The input parameters are wikitext with templates expanded.
      // The output should be wikitext too.
      $output = "param1 is $param1 and param2 is $param2 and param3 is $param3";
      return $output;
   }

}

Otro archivo, ExampleExtension.i18n.php, en el directorio de tu extensión (no en el subdirectorio de src/) deberá contener:

<?php
/**
 * @license GPL-2.0-or-later
 * @author Tu Nombre (YourUserName)
 */

$magicWords = [];

/** English
 * @author Tu Nombre (YourUserName)
 */
$magicWords['en'] = [
   'example' => [ 0, 'example' ],
];

Con esta extensión activada,

  • {{#example: hello | hi | hey}}

produce:

  • param1 es hello y param2 es hi y param3 es hey
Esta matriz magicWords no es opcional. Si se omite, la función del analizador sencillamente no funcionará; el {{#example: hello | hi}} se mostrará como si la extensión no estuviera instalada. Si solo se inicializa la matriz específica del idioma y no la propia matriz magicWords, esto puede causar errores de localización, ya que se filtran traducciones de otras extensiones en la(s) tuya(s). Puedes asociar palabras mágicas en PHP en la misma línea en lugar de a través de un archivo de internacionalización. Esto es útil al definir ganchos en LocalSettings.php
MediaWiki\MediaWikiServices::getInstance()->getContentLanguage()->mMagicExtensions['wikicodeToHtml'] = ['MAG_CUSTOM', 'custom'];

Dentro de LocalSettings.php

Las palabras mágicas y sus funciones asociadas del analizador se pueden definir por completo en LocalSettings.php.

$wgHooks['ParserFirstCallInit'][] = function ( Parser $parser ) 
{
	MediaWiki\MediaWikiServices::getInstance()->getContentLanguage()->mMagicExtensions['wikicodeToHtml'] = ['wikicodeToHtml', 'wikicodeToHtml'];

	$parser->setFunctionHook( 'wikicodeToHtml', 'wikicodeToHtml' );
};
 
function wikicodeToHtml( Parser $parser, $code = '' ) 
{
	$title = $parser->getTitle();
	$options = $parser->Options();
	$options->enableLimitReport(false);
	$parser = $parser->getFreshParser();
	return [$parser->parse($code, $title, $options)->getText(), 'isHTML' => true];
}

Funciones más largas

Para funciones más largas, quizá desees separar las funciones de gancho en un archivo _body.php o .hooks.php y convertirlas en funciones estáticas de una clase. Luego puedes cargar la clase con $wgAutoloadClasses y llamar a las funciones estáticas en los ganchos, p. ej.:

Agrega esto en tu archivo extension.json:

"Hooks": {
	"ParserFirstCallInit": "ExampleExtensionHooks::onParserFirstCallInit"
},
"AutoloadClasses": {
	"ExampleExtensionHooks": "src/ExampleExtensionHooks.php"
}

Luego agrega esto en tu archivo src/ExampleExtensionHooks.php:

class ExampleExtensionHooks {
      public static function onParserFirstCallInit( Parser $parser ) {
           $parser->setFunctionHook( 'example', [ self::class, 'renderExample' ] );
      }
}


Interfaz del analizador

Control del análisis sintáctico de la salida

Para que el wikitexto devuelto por tu función del analizador esté completamente analizado (incluyendo las expansiones de plantillas), define la opción noparse como false en la salida:

return [ $output, 'noparse' => false ];

Parece ser que el valor predeterminado de noparse cambió de false a true, al menos en algunas situaciones, en algún momento en torno a la versión 1.12.

En cambio, para que tu función del analizador devuelva HTML no analizado, en lugar de wikitexto, usa lo siguiente:

return [ $output, 'noparse' => true, 'isHTML' => true ];

Nombramiento

Por defecto, MW añade una almohadilla (#) al nombre de cada función del analizador. Para suprimir este añadido (y obtener una función del analizador sin el prefijo #), debes incluir la constante SFH_NO_HASH en el argumento opcional flags a setFunctionHook, como se describe más adelante.

Al elegir un nombre sin almohadilla prefijada, ten en cuenta que ya no es posible transcluir una página con un nombre que empiece por ese nombre de función seguido del carácter dos puntos. En particular, evita nombres de función iguales al nombre de un espacio de nombres. En caso de que la transclusión interwiki [1] esté habilitada, también debes evitar los nombres de función iguales a un prefijo interwiki.

El gancho setFunctionHook

Para más información sobre la interfaz del analizador, consulta la documentación de setFunctionHook en includes/Parser.php. Aquí se muestra una copia (posiblemente obsoleta) de estos comentarios:

función setFunctionHook( $id, $callback, $flags = 0 )

Parámetros:

  • cadena $id - el identificador de la palabra mágica
  • mixto $callback - la función de retrollamada (y objeto) que usar
  • entero $flags - opcional. Valores:
  • SFH_NO_HASH (1) constante si llamas a la función sin #.
  • SFH_OBJECT_ARGS (2) si pasas un objeto PPFrame y una matriz de argumentos en lugar de una serie de argumentos de funciones, para las que vuelve atrás.
  • Su valor por defecto es 0 (sin banderas).

Valor de retorno: La antigua función de retrollamada para este nombre, en caso de existir

Crea una función, por ejemplo, {{#sum:1|2|3}}. La función de retrollamada debería tener la forma:

function myParserFunction( $parser, $arg1, $arg2, $arg3 ) { ... }

La retrollamada podrá devolver el texto resultado de la función o bien una matriz con el texto en el elemento 0 y una serie de banderas en los demás elementos. Los nombres de las banderas se especifican en las claves. Las banderas válidas son:

Nombre Tipo Valor por defecto Descripción
found Booleano true true si el texto devuelto es válido y el procesamiento de la plantilla debe detenerse.
text ? ? El texto devuelto por la función. Si se especifica isChildObj o isLocalObj, esto debería ser un nodo del DOM en su lugar.
noparse Booleano true true si el texto no se debe preprocesar a un árbol del DOM, por ejemplo, las etiquetas HTML inseguras no se deben suprimir, etc.
isHTML Booleano ? true si el texto devuelto es HTML y debe protegerse contra transformaciones a wikitexto. Sin embargo, consulta la discusión
nowiki Booleano generalmente false true si el marcado wiki en el valor (texto) de retorno debe escaparse.
isChildObj Booleano ? true si el texto es un nodo del DOM que necesita expandirse en un marco hijo.
isLocalObj Booleano ? true si el texto es un nodo del DOM que necesita expandirse en el marco actual. El valor por defecto depende de otros valores y resultados.
preprocessFlags ? false Banderas PPFrame opcionales que usar al analizar el texto devuelto. Esto solo se aplica cuando noparse es false.
title ? false El objeto Title del que provino el texto.
forceRawInterwiki Booleano ? true si debe forzarse la transclusión interwiki para que se realice en crudo, sin renderizarse.

Funciones del analizador costosas

Algunas función del analizador representan un uso significativo de los recursos de un wiki y deben marcarse como «costosas». El número de función del analizador costosas en cualquier página dada está limitado por el valor de $wgExpensiveParserFunctionLimit . La propia función es la que decide qué se considera «costoso», pero, por lo general, debe considerarse cualquier cosa susceptible de causar un retraso que se prolongue más allá del simple procesamiento de datos. Esto incluye, por ejemplo, lecturas y escrituras en la base de datos, lanzar un script de la shell de forma síncrona o la manipulación de archivos. Por otra parte, no se tienen por qué marcar así todas estas funciones. Semantic MediaWiki, por ejemplo, solo marca como costosa una parte de sus lecturas de la base de datos. Esto se debe a que, en determinadas páginas con un uso intensivo de datos, podría sobrepasar fácilmente los límites normales de funciones del analizador costosas. En casos así, el potencial de un rendimiento notablemente más lento que no se marca como costoso es un compromiso con tener la funcionalidad que ofrece SMW.

Para marcar tu función del analizador como costoso desde dentro del cuerpo del código de la función, usa $result = $parser->incrementExpensiveFunctionCount();. El valor de retorno será false si se ha alcanzado o excedido el límite de funciones costosas.

Parámetros nombrados

Las funciones del analizador no permiten el uso de parámetros nombrados de la manera en que lo hacen las plantillas y extensiones de marcado, pero a veces puede ser útil imitarlos. Los usuarios a menudo están acostumbrados a usar barras verticales ( | ) para separar argumentos, así que está bien poder hacerlo también en el contexto de las funciones de análisis. He aquí un ejemplo sencillo de cómo conseguirlo:

function ExampleExtensionRenderParserFunction( &$parser ) {
	// Supón que el usuario invocó la función del analizador así:
	// {{#myparserfunction: foo=bar | apple=orange | banana }}

	$options = extractOptions( array_slice( func_get_args(), 1 ) );

	// Ahora dispones de una matriz con este aspecto:
	// [foo] => 'bar'
	// [apple] => 'orange'
	// [banana] => true
	// Sigue escribiendo tu código...
}

/**
 * Convierte una matriz de valores a la forma [0] => "nombre=valor"
 * en una verdadera matriz asociativa de la forma [nombre] => valor
 * Si no se proporciona =, se asume el valor true (verdadero) así: [nombre] => true
 *
 * @param array string $options
 * @return array $results
 */
function extractOptions( array $options ) {
	$results = [];
	foreach ( $options as $option ) {
		$pair = array_map( 'trim', explode( '=', $option, 2 ) );
		if ( count( $pair ) === 2 ) {
			$results[ $pair[0] ] = $pair[1];
		}
		if ( count( $pair ) === 1 ) {
			$results[ $pair[0] ] = true;
		}
	}
	return $results;
}

Véase también

Guías de uso general y guías relacionadas:

Código:

Ejemplos: