Capítulo 3. Plantillas

Tabla de contenidos

3.1. Introducción
3.2. Un ejemplo mínimo
3.3. Arboles de plantillas
3.4. Controlando el formato
3.5. Características avanzadas

3.1. Introducción

Una plantilla es un documento de texto plano, donde pueden escribirse algunos marcadores especiales. Estos marcadores pueden luego ser reemplazados por valores durante la ejecución de un programa. No hay un formato predefinido para los marcadores; cada programador puede escoger el esquema que más le guste o que más le convenga.

La librería para manipular plantillas es ExpertCoder.Templates.dll.

3.2. Un ejemplo mínimo

Para ilustrar el uso de las plantillas, escribiremos una para generar una página web con un mensaje personalizado. En un directorio vacío, creamos el fichero helloworld.txt, cuyo contenido es el siguiente:

<HTML>
	<HEAD>
		<TITLE>$Message!</TITLE>
	</HEAD>
	<BODY><P>$Message!</P><BODY>
</HTML>

Está claro que se trata de un simple fichero de texto plano, pero llama la atención el texto $Message!. Esta cadena es lo que llamamos un "marcador" (o place holder). No hay nada de por sí que determine que esta cadena es efectivamente un marcador; lo es porque lo estamos estableciendo por convención.

Ahora necesitamos una clase para manipular programáticamente esta plantilla. Escribamos el siguiente contenido en un fichero Test.cs:

using ExpertCoder.Templates;
namespace Testing
{
	public class Page : Template
	{
		public Page() : base(
			new System.IO.FileInfo("helloworld.txt"),
			new string[] {"$Message!"})
		{ }
		
		public string Message;
		
		protected override void SetPlaceHoldersValues()
		{
			base["$Message!"] = Message;
		}
	}

	public class Test
	{
		public static void Main(string[] args)
		{
			Page page = new Page();
			page.Message = "Hola Mundo!";
			System.Console.WriteLine(page.ToString());
		}
	}
}

Como generalmente es el caso, queremos ver lo que este programa hace antes de estudiar el código. Necesitamos primero compilar el fuente, lo cual se logra con la siguiente línea:

mcs -r:ExpertCoder.Templates.dll -out:test.exe Test.cs

obteniendo el archivo test.exe, que ejecutamos a continuación haciendo:

mono test.exe

La salida que obtenemos es la que ya nos imaginábamos:

<HTML>
        <HEAD>
                <TITLE>Hola Mundo!</TITLE>
        </HEAD>
        <BODY><P>Hola Mundo!</P><BODY>
</HTML>

Detengámonos ahora a estudiar el código. Se trata de dos clases, Test y Page. Test contiene un método Main, el punto de entrada del programa, que se encarga de crear una instancia de Page, asignar la propiedad Message e imprimir el resultado de la llamada a ToString.

Page es la clase plantilla. Hacia el exterior, sólo expone un campo Message de tipo string; este campo permite asignar el valor del marcador $Message!. La clase hereda de Template, quien provee toda la funcionalidad. En el constructor, se pasan dos parámetros (hay otras maneras de inicializar instancias de Template): el primer parámetro indica la ubicación del fichero que contiene la plantilla de texto plano, y un array con las cadenas que deben interpretarse como marcadores. De esta forma, le indicamos a la librería el texto de la plantilla y cuales son los marcadores; todavía falta indicar el valor de estos últimos.

Cuando se llama al método ToString, la plantilla necesita saber el valor de los marcadores. Estos valores se obtienen mediante a una llamada al método SetPlaceHoldersValues, que debe ser sobreescrito por el implementador de la clase derivada para proveer un valor a cada marcador. El mecanismo para asignar los valores a los marcadores funciona mediante el uso de la sintaxis de índice (los corchetes). Entonces, la línea

	base["$Message!"] = Message;

asigna el valor del campo Message al marcador $Message!.

3.3. Arboles de plantillas

Un marcador puede reemplazarse por una cadena de caracteres cualquiera, sin importar de donde se obtuvo dicha cadena. Por lo tanto, el valor de un marcador podría obtenerse a partir de otra plantilla. Aprovechando esto es posible crear árboles de plantillas, o sea, estructuras jerárquicas donde una plantilla contiene a otras plantillas.

Continuando con el ejemplo anterior, podríamos modificarlo para obtener un elemento BODY con varios párrafos. En este caso tendríamos dos plantillas - una para la página y otra para cada párrafo. La plantilla de la página (helloworld.txt) quedaría así:

<HTML>
	<HEAD>
		<TITLE>$Message!</TITLE>
	</HEAD>
	<BODY>
	$Paragraphs*
	<BODY>
</HTML>

He introducido un nuevo marcador $Paragraphs*. Estoy siguiendo una convención que a mi me gusta, y consiste en comenzar cada marcador con un signo $, llamarlo con notación Pascal, y terminar con ! si el marcador puede tener a lo sumo un valor, o * si puede tener varios. No debe perderse de vista que esta es solo una convención, no hay nada en la librería imponga este formato.

Para manipular programáticamente esta plantilla, modificamos el ejemplo anterior de la siguiente manera:

using ExpertCoder.Templates;
namespace Testing
{
	public class Page : Template
	{
		public Page() : base(
			new System.IO.FileInfo("helloworld.txt"),
			new string[] {"$Paragraphs*", "$Message!"})
		{
			Paragraphs = new TemplateCollection("\n");
		}
		
		public readonly TemplateCollection Paragraphs;
		public string Message;
		
		protected override void SetPlaceHoldersValues()
		{
			base["$Paragraphs*"] = Paragraphs.ToString();
			base["$Message!"] = Message;
		}
	}

	/* ... */
}

Lo que hicimos fue declarar el marcador $Paragraphs* en el constructor, asignarle su valor en SetPlaceHoldersValues y declarar un campo para que contenga su valor. Como este marcador puede tener varios valores, utilizamos la clase TemplateCollection, que es una colección de plantillas.

Ahora crearemos una clase que representará a los párrafos. Podemos agregarla al mismo fichero fuente que ya teníamos:

	public class Para : Template
	{
		public Para() : base(
			"<p>$Content!</p>",
			new string[] {"$Content!"})
		{ }

		public Para(string content) : this()
		{
			Content = content;
		}

		public string Content;

		protected override void SetPlaceHoldersValues()
		{
			base["$Content!"] = Content;
		}
	}

Esta clase Para es muy similar a la primera versión de Page, salvo que en el constructor, en lugar de pasar la ubicación de un fichero que contiene a la plantilla, pasamos directamente el texto de la plantilla. Esto es más práctico cuando el texto de la plantilla es breve. Además de eso, escribimos otro constructor que recibe el contenido del párrafo y lo asigna. Esto simplifica el código de pruebas, que se muestra a continuación:

	public class Test
	{
		public static void Main(string[] args)
		{
			Page page = new Page();
			page.Message = "Hola Mundo!";
			page.Paragraphs.Add(new Para("1 2 3"));
			page.Paragraphs.Add(new Para("probando"));
			System.Console.WriteLine(page.ToString());
		}
	}

Está claro que estamos anidando plantillas, ya que dentro de la plantilla Page estamos agregando instancias de Para, utilizando el método Add del campo Paragraphs. Este método Add recibe objetos de tipo Template.

La salida que obtenemos en este caso es la siguiente:

<HTML>
        <HEAD>
                <TITLE>Hola Mundo!</TITLE>
        </HEAD>
        <BODY>
        <p>1 2 3</p>
<p>probando</p>
        <BODY>
</HTML>

3.4. Controlando el formato

En el último ejemplo notamos que la salida tiene un pequeño defecto de formato. Si bien el código es correcto en el caso de HTML, por alguna razón podríamos querer que todos los párrafos comiencen en la misma columna.

Esto se logra asignando propiedades de formato a las instancias de TemplateCollection. En este caso, bastará con indicarle la cantidad de tabuladores que queremos antes de cada línea:

		protected override void SetPlaceHoldersValues()
		{
			Paragraphs.Tabs = 1;
			Paragraphs.FirstLineTabs = 0;
			base["$Paragraphs*"] = Paragraphs.ToString();
			base["$Message!"] = Message;
		}

Con Tabs controlamos la cantidad de tabuladores que se agregan antes de cada línea de cada plantilla de la colección, y con FirstLineTabs controlamos la cantidad de tabuladores de la primer línea. En este caso asignamos 0, porque en la plantilla el marcador está ubicado en una posición correctamente tabulada.

La salida que obtenemos con esta versión final es:

<HTML>
        <HEAD>
                <TITLE>Hola Mundo!</TITLE>
        </HEAD>
        <BODY>
        <p>1 2 3</p>
        <p>probando</p>
        <BODY>
</HTML>

3.5. Características avanzadas

La librería de plantillas tiene otras características, por ejemplo la posibilidad de indicar el caracter que se utiliza a modo de tabulador o un prefijo a añadir a cada línea de una plantilla. Es posible empotrar el fichero de una plantilla en un ensamblado. También existe una clase derivada de Template llamada TraceableTemplate, que provee una propiedad para indicar el objeto en base al cual se creó la instancia de la plantilla - esto es útil en ciertos casos cuando se escriben generadores de código.

Se recomienda consultar la referencia de la API para descubrir las características más específicas que provee ExpertCoder.Templates.