Analizar XML con Java

Aunque el formato de datos XML alardea de características como: extensibilidad, legibilidad, jerarquización, estandarización y capacidad de adaptación a multitud de formatos. A la hora de analizarlo y extraer información de él es, computacionalmente, un formato que presenta algunas dificultades con respecto a otros. Si bien es verdad que existen librerías/APIs dedicadas a superar este pequeño handicap. A continuación veremos ejemplos de dos tipos de librerías para analizar un documente XML desde java. Uno de ellos es el DOM (Document Object Model) y otro es el SAX (Simple API from XML).

Las diferencias principales entre DOM y SAX es que DOM genera un arból de objetos con sus dependencias en memoria. Esto permite acceder a cualquier elemento en cualquier posición una y otra vez, atrás y adelante sin problemas. El problema es que consume mucha memoria y no sería una opción recomendable para XMLs muy grandes. SAX por otro lado no almacena información en memoria y lee el fichero secuencialmente hasta encontrar el elemento y la información que necesitas en un proceso en donde se registran métodos callback cada vez que se detecta determinados patrones en el documento (apertura de etiquetas XML por ejemplo). SAX podría manejar con mayor facilidad grandes ficheros XML pero sin la libertad del DOM.

Veamos como podemos analizar este fragmento de XML


<?xml version="1.0" encoding="UTF-8"?>
<personas>
 <persona sexo="masculino">
  <nombre>Pepe</nombre>
  <apellidos>Gracia Perez</apellidos>
 </persona>
 <persona sexo="femenino">
  <nombre>María</nombre>
  <apellidos>Suarez Martinez</apellidos>
 </persona>
</personas>

Primero hay que crear un objeto Document (org.w3c.dom.Document) empleando otros dos objetos DocumentBuilder (javax.xml.parsers.DocumentBuilder) y DocumentBuilderFactory (javax.xml.parsers.DocumentBuilderFactory). Si por ejemplo el fichero que contiene nuestro xml se llama test.xml

Podríamos poner para cargar el dom el siguiente fragmento de código

(Dejando los namespaces completos)


org.w3c.dom.Document dom;
javax.xml.parsers.DocumentBuilderFactory dbf;
javax.xml.parsers.DocumentBuilder db;

dbf = javax.xml.parsers.DocumentBuilderFactory.newInstance();

try
{
  db = dbf.newDocumentBuilder();
  dom = db.parse("test.xml");
}
catch(Exception ex) {}

Ahora dom contiene toda la información y estructura de nuestro documento en memoria. Veamos como extraer la información que necesitamos

Para obtener el nodo raiz, en este caso personas, llamamos a:


org.w3c.dom.Element rootElement = dom.getDocumentElement();

rootElement es el objeto que ahora debemos interrogar. Por ejemplo si llamamos al método getElementsByTagName(String etiqueta) obtendremos un objeto de tipo NodeList (org.w3c.dom.NodeList) que contiene a su vez todos los posibles elementos incluidos dentro de la etiqueta. Para obtener por ejemplo el contenido de las etiquetas persona podríamos hacer


org.w3c.dom.NodeList nodeList = rootElement.getElementsByTagName("persona");

Para iterar sobre nodeList podriamos hacer


if(nodeList != null && nodeList.getLength()>0
{
  for(int i=0;i<nodeList.getLength();i++)
  {
    org.w3c.dom.Element element = (Element)nodeList.item(i);
    if(element.hasAttribute("sexo")) System.out.println(element.getAttribute("sexo"));
    org.w3c.dom.NodeList nodeList2 = element.getElementsByTagName("nombre");
    if(nodeList2 != null && nodeList2.getLength()>0
    {
      org.w3c.dom.Element el = (Element)nodeList2.item(0);
      System.out.println("Nombre: " + el.getFirstChild().getNodeValue());
    }
  }
}

Importante saber los métodos de nodeList:

– getLength() devolviendo el tamaño de la lista de nodos
– item(i) devuelve el nodo (objeto Node) de índice i

Y los métodos de Element

– getElementsByTagName(String nombreTag) devolviendo un NodeList
– getFirstChild() primer elemento inferior del nodo
– getAttribute(String atributo) valor del atributo
– hasAttribute(String atributo) si el nodo tiene o no este atributo

Convertir String a int en Java y viceversa

A continuación unas pequeñas recetas de java para pasar de cadena a entero o de entero a cadena.

Para convertir un String (cadena) a int (entero) hay que emplear el método estático de la clase Integer, parseInt

Ejemplo:


String enteroString = "5";
int entero = Integer.parseInt(enteroString);

Para convertir int(entero) a String sólo debemos hacer una llamada al método estático de la clase Integer, toString

Ejemplo:


int entero = 1;
String enteroString = Integer.toString(entero);

Análogamente para pasar de cadena a double tenemos el método Double.parseDouble

Ejemplo:


double aDouble = Double.parseDouble(aString);

o al revés (double a string) llamamos al método Double.toString

Ejemplo:


double d = 8342342;
System.out.println(Double.toString(d));

Tanto la clase Integer con la clase Double se encuentran en el package java.lang. Atención int y double son tipos primitivos del lenguaje java. Integer y Double son clases que representan estos tipos primitivos y le añaden más funcionalidades. Como por ejemplo las que acabamos de ver.

JQuery y Ajax

Cualquier librería actual que se precie debe tener un soporte completo de ajax, de la facilidad con la que maneje este importante y vital tarea vendrá buena parte de su éxito. Vamos a estudiar el soporte que tiene JQuery de ajax y como se desenvuelve en esta tarea.

Veamos como podemos lanzar una petición ajax directamente empleando sólo javascript

Para iniciar nuestro objeto ajax debemos emplear este código


var ajax;
if(window.XMLHttpRequest)
{
ajax = new XMLHttpRequest();
}
else if (window.ActiveXObject)
{
ajax = new ActiveXObject("Msxml2.XMLHTTP");
}
else
{
throw new Error("Este navegador no soporta ajax");
}

Es más complejo de lo que debería debido al soporte no estandar que le dio Microsoft es un navegador. A fin de cuentas Microsoft fue quien primero desarrollo esta tecnología.

Después de que la instancia ajax haya sido creada esta tiene una serie de métodos y propiedades que la dotan de funcionalidad. Todos los navegadores que soportan este objeto ajax tiene todos estos métodos/propiedades:

Métodos:

abort()
getAllResponseHeaders()
getResponseHeader(name)
open(method, url, async, username, password)
send(content)
setRequestHeader(name,value)

Propiedades:

onreadystatechange
readyState
reponseText
responseXML
status
statusText

Para lanzar una petición ajax debemos indicar el método a utilizar get o post, la URL a la que lanzar la petición, el cuerpo del mensaje si se trata de una petición POST (en el POST la información a enviar no viaja en la cabecera HTTP como con el método GET), si deseamos que la petición sea sincrona o no, autentificación si la URL lo precisa y una función callback para informar del progreso de la petición ajax.

el método open nos permite informar de la mayor parte de estos parámetros

ajax.open(‘GET’,’/url’);

Este método no lanza la petición, sólo la prepara

open(metodo,url,asincrono,usuario,password);

metodo – HTTP empleado, GET o POST, indica el tipo de formato empleado en la petición.
url – La url a solicitar.
asincrono – La petición debe ser asincrona, valor por defecto true, (no detiene la ejecución, básico en ajax) o sincrona (detiene ejecución hasta que se completa la tarea) valor a false.
usuario – Proporciona el usuario si es necesaria la autentificación.
password – Propociona un password si es necesaria la autentificación.

Ahora necesitamos informar cual será la función que se debe llamar cuando nuestra petición cambie de estado a través de la propiedad onreadystatechange. Debemos proporcionar una función para esta propiedad que será llamada varias veces después a medida que la petición ajax se vaya resolviendo para bien o para mal.

Podriamos definir esta función de esta manera

ajax.onreadystatechange = function ()
{
if(ajax.readyState == 4) //Estado terminado
{
if(ajax.status >= 200 && ajax.status < 300) //Sólo peticiones exitosas { // Código OK aquí } else //Peticiones que fallan { // Código de error aquí } } } Una cosa interesante, y muy desagradable, a tener en cuenta a la vista de este código es que la variable ajax es la del alcance global pero no es la variable pasada al controlador. Esto puede ser un problema si tenemos varias llamadas ajax activas simultáneamente. Tras hacer esto el último paso es lanzar la petición. Para ello llamamos al método send(content), cuyo parámetro es el contenido de la petición. Si es una petición GET no es preciso enviar nada
ajax.send(null);

En el caso de petición tipo POST debemos enviarle el contenido de la información que queremos enviar al servidor de manera debidamente formateada y URI-encoded, por ejemplo:

ajax.send(‘a=1&b=2&c=3’);

Como obtener la respuesta de la llamada

Trucos para acelerar php

Vamos ha realizar un análisis crítico de este vídeo de Eric Higgins, webmaster de Google. En el vídeo nos cuenta alguno de sus trucos para acelerar sus scripts en php:

El primero de los trucos es emplear comillas simples en lugar de comillas dobles. Las comillas dobles hace que la cadena interprete un determinado número de secuencias de escape:

\n linefeed (LF or 0x0A (10) en ASCII)
\r retorno de carro (CR or 0x0D (13) en ASCII)
\t tabulación horizontal (HT or 0x09 (9) in ASCII)
\v tabulación vertical tab (VT or 0x0B (11) in ASCII) (since PHP 5.2.5)
\f form feed (FF or 0x0C (12) in ASCII) (since PHP 5.2.5)
\\ barra invertida
\$ signo de dolar
\” comillas dobles
\[0-7]{1,3} carácter en notación octal
\x[0-9A-Fa-f]{1,2} carácter en notación hexadecimal

Además de, por supuesto, expandir las variables que aparezcan en las cadenas.

En el caso de las comillas simples las únicas secuencias de escape reconocidas son estas dos:

\’ comillas simples
\\ barra invertida

Además de no expandir las variables en la cadena. Por todo ello es lógico pensar que una cadena con comillas simples será más rápida de procesar que una con comillas dobles, veámoslo con un código de ejemplo que después probaremos sin emplear ningún acelerador de php tipo APC o XCache:


echo '==Tests comillas dobles==',"\n";
for($j=0;$j<10;$j++)
{
$t1 = microtime(true);
for($i=0;$i<100000;$i++)
{
$a = "hola mundo";
}
$t2 = microtime(true);
$test1[] = $t2-$t1;
echo $t2-$t1;
echo "\n";
}

echo 'Valor medio: ',array_sum($test1)/($j-1),"\n\n\n";

echo '==Tests comillas simples==',"\n";
for($j=0;$j<10;$j++)
{
$t1 = microtime(true);
for($i=0;$i<100000;$i++)
{
$a = 'hola mundo';
}
$t2 = microtime(true);
$test2[] = $t2-$t1;
echo $t2-$t1;
echo "\n";
}
echo 'Valor medio: ',array_sum($test2)/($j-1),"\n";

Los resultados en mi vieja máquina son los siguientes:

==Tests comillas dobles==
1.2272200584412
1.2168197631836
1.2232639789581
1.2248270511627
1.2338960170746
1.2382409572601
1.5701949596405
1.4171171188354
1.251455783844
1.2365839481354
Valor medio: 1.4266244040595

==Tests comillas simples==
1.2352590560913
1.1931908130646
1.2339069843292
1.2618341445923
1.2174820899963
1.2468011379242
1.2535820007324
1.2457919120789
1.3603079319
1.3120169639587
Valor medio: 1.3955747816298

Parece pues que estamos en lo cierto las comillas simples son más rápidas que las dobles como cabría esperar.

Pero vamos a invertir el orden de las pruebas, empezando ahora por las comillas simples, este es el resultado:

==Tests comillas simples==
1.2222979068756
1.2254979610443
1.2982668876648
1.1845409870148
1.2094640731812
1.1939878463745
1.153324842453
1.2170951366425
1.1506760120392
1.1662788391113
Valor medio: 1.3357144991557

==Tests comillas dobles==
1.2285170555115
1.1904451847076
1.1701879501343
1.184662103653
1.1987600326538
1.1889038085938
1.190358877182
1.1833288669586
1.1585729122162
1.2015302181244
Valor medio: 1.321696334415

¿Como? ¡Las comillas simples con más lentas ahora! en contra de toda nuestra lógica. Después de repetir los tests un buen número de veces estos resultados siguen repitiéndose. Parece que la primera prueba es más lenta que la segunda sin importar que sean comillas dobles o simples. Incluso después de probar repetidas veces el orden de las pruebas no se ha observado ninguna variación.

Vamos ahora con la prueba de la concatenación con print y el echo sin concatenación.

Este es el código para poder ejecutar las pruebas (he empleado output buffering para no volcar por pantalla todos esas decenas de miles de cadenas):


Resultado:

==Print concatenado==
3.2009110450745
3.1793689727783
3.2047038078308
3.1655762195587
3.1424939632416
3.0726661682129
3.2378449440002
3.4216639995575
3.5003128051758
3.1557071208954
Valor medio: 3.5868054495917

==Echo sin concatenación==
3.1254539489746
3.0349171161652
2.9997961521149
2.9809520244598
2.9241809844971
3.1526489257812
2.9299869537354
2.9526100158691
2.9198181629181
2.9321920871735
Valor medio: 3.3280618190765

Invirtiendo las pruebas para comprobar que el orden no afecta al resultado

==Echo sin concatenación==
3.0520401000977
3.184662103653
2.8883378505707
2.8706419467926
3.0163989067078
2.8317699432373
2.856645822525
2.871414899826
2.8769338130951
2.7966349124908
Valor medio: 3.2494978109996

==Print concatenado==
2.8693239688873
2.9165189266205
2.8810060024261
3.0247449874878
2.8426420688629
2.8691411018372
2.8807561397552
2.8754470348358
2.8367080688477
2.8549909591675
Valor medio: 3.2056976954142

La conclusión es que las pruebas no son relevantes y que en estos casos tan sencillos la mejoras en rendimiento no son significativas.

Repitamos las pruebas para cadenas más complejas. Por ejemplo, para la prueba de concatenación con print contra echo y varios argumentos substituimos la línea de echo por:

echo 'h','o','l','a',' ','m','u','n','d','o';

y la de print por:

print 'h'.'o'.'l'.'a'.' '.'m'.'u'.'n'.'d'.'o';

En este caso la mayor velocidad es para el echo no concatenado sin importar el orden. Uno de los tests por ejemplo arrojó estos datos:

Valor medio: 3.6208234363132 (echo)
Valor medio: 3.7140626907349 (print)

Repitiendo varias veces la pruebas y variando el orden el resultado se confirma. Eric Higgins estaba en lo correcto. Aunque con algunos peros, en los casos más simples no se observa mejoras de rendimiento.

Para el caso de las comillas dobles contra las simples debemos modificar las cadenas pero es complejo crear dos cadenas equivalentes que no desvirtúen la prueba. Lo ideal sería emplear sustitución de variables y/o secuencias de escape pero al sólo poder emplearla en una de las dos pruebas no podemos usar formas equivalentes. Lo mejor en este caso es seguir la lógica y emplear las comillas simples en donde podamos. Pero esto da lugar a modificaciones en las cadenas y a operaciones de concatenación extra. ¿Cuál es la mejor solución?. Bien provémoslo:

Siendo
$hola = 'hola';
$mundo = 'mundo';

Intentaremos probar que es mas rápido:

$a = "$hola $mundo";

ó

$a = $hola.' '.$mundo;

Esta prueba si que es más realista y aparece en multitud de ocasiones, en diversas variaciones, en muchos scripts reales.

Los resultados si que son relevantes en este caso, modificando el orden de las pruebas la segunda opción con las comillas simple es siempre más rápida en todas nuestras pruebas sin importar el orden.

Por lo tanto nos ratificamos: la comillas simples son mejores (por lo menos con este nivel de concatenación/sustitución de variables) aunque haya que emplear concatenaciones adicionales a la opción con dobles comillas. Le volvemos a dar la razón a Eric Higgins pero con más peros.

Pruebas realizadas con la siguiente configuración

Versión de php: 5.2.5

Arquitectura/CPU:
processor : 0
vendor_id : AuthenticAMD
cpu family : 15
model : 44
model name : AMD Sempron(tm) Processor 2600+
stepping : 2
cpu MHz : 1596.502
cache size : 128 KB
fdiv_bug : no
hlt_bug : no
f00f_bug : no
coma_bug : no
fpu : yes
fpu_exception : yes
cpuid level : 1
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 syscall nx mmxext fxsr_opt lm 3dnowext 3dnow up pni lahf_lm ts ttp tm stc
bogomips : 3194.83
clflush size : 64

kernel: Linux 2.6.24.3-custom
Sistema operativo: Ubuntu 9.04 - Jaunty Jackalope (Abril 2009)

jQuery, la libreria de javascript

Existen diversas librerías javascript disponibles para utilizar en tus proyectos web sin necesidad de empezar desde cero. Por nombrar algunas de las más extendidas: jquery, MooTools, prototype, script.aculo.us, YUI tools o Dojo. Existen muchas más; unas cuantas búsquedas por google te traerán listados más amplios que este. Sin embargo estas son quizás las más recomendables o las que quizás necesites conocer para hacerte una buena idea de las opciones que tienes disponibles.

En este artículo comentaré la filosofía y las características principales de jQuery. No entraré a hacer una comparativa entre las distintas librerías del tipo: jQuery vs prototype vs YUI etc, etc, etc. Sólo indicar que aunque muchas tengan el pomposo nombre de framework, yo no se lo voy a atribuir. Para mi un framework es un concepto más amplio que involucra desde el desarrollo del software en si pasando por la gestión del versionado de su código, los procesos asociados a su desarrollo (gestión de incidencias, subidas a producción) y llegando hasta el control de calidad (test unitarios y funcionales), incluyendo las herramientas utilizadas para llevar a cabo todo esto, editores de código, gestores de workflow, etc. Un framework debería tener, incluir o al menos tener en mente todas o varias de estas características.

Bueno volviendo a jQuery empezaré comenzando por una pequeña característica que permite emplear esta librería con cualquier otra. O sea que si tienes dudas entre jQuery y YUI, por ejmplo, ten en cuenta que puedes emplear las dos pues jQuery ya ha pensado en esto. Lo explico: jQuery emplea su propio namespace para evitar colisionar con otras librerías, el nombre de este namespace es obviamente jQuery (incluso los plugins basados en jQuery emplean casi todos este namespace). Sin embargo, hay un pequeño asunto a solucionar y es que jQuery emplea como alias el identificado $. Por ejemplo:

jQuery(“div”).hide(); //Oculta todos los div del documento

es igual a:

$(“div”).hide();

Y como muchos sabréis hay otras librerías javascript que también lo emplean. Para solucionarlo basta con llamar a la siguiente función:

jQuery.noConflict();

Teniendo en cuenta que esta llamada ha de hacerse después de cargar las dos librerías que colisionan:

<html>
<head>
<script type="text/javascript" src="prototype.js"></script>
<script type="text/javascript" src="jquery.js"></script>
<script>
jQuery.noConflict();

// Use jQuery via jQuery(...)
jQuery(document).ready(function(){
jQuery("div").hide();
});

// Use Prototype with $(...), etc.
$('someid').hide();
</script>
</head>
<body></body>
</html>

Bien, con este tema aclarado pasemos a la filosofía de jQuery. jQuery emplea selectores CSS para referirse a uno o a un grupo de elementos sobre los que actúa. Emplea incluso selectores que no están completamente implementados en los más modernos navegadores pero si estandarizados (selectores CSS3) y añade incluso algunos más para añadir más versatilidad a la librería. jQuery trabaja principalmente con el DOM de los documentos XHTML. La sintaxis básica es:

$(selector)

por ejemplo

$(“a”)

Hace referencia a todos los enlaces del documento actual

$(“p a”)

Hace referencia a todos los enlaces dentro de párrafos (etiqueta p).

Lo que devuelven estas funciones es un objeto Javascript especial que contiene un array de elementos DOM que verifican el selector. Este objeto tiene un elevado número de funciones predefinidas que, de ser llamadas, actuan sobre todo el grupo de elementos. Por ejemplo si quieres que añadir una clase especial llamada “enlace” a todos los enlaces de tu documento bastaría con poner:

$(“a”).addClass(“enlace”);

Otra propiedad es que estas funciones al terminar de actuar devuelven a su vez el mismo array de elementos DOM. Así que puedes agregar un método tras otro, por ejemplo:

$(“a”).addClass(“enlace”).append(“<strong>[ ENLACE ]</strong>”);

Que añade la clase enlace a todos los enlaces y además les añade el fragmento de código html “<strong>[ ENLACE ]</strong>” después de su contenido normal.

Estas cadenas de funciones pueden continuar indefinidamente. Además de este módo de trabajo jQuery también tiene otras funciones, digamos, de propósito general que no actúan sobre elementos DOM. Son funciones tradicionales por ejemplo

$.trim(” Elimina espacios sobrantes del principio y final de la cadena “);

Un poco raro ¿no? Pero recordemos que $ es un alisas de jQuery

jQuery.trim(” Elimina espacios sobrantes del principio y final de la cadena “);

Ahora parece más normal esta llamada.

Estas funciones aumentan las funcionalidades disponibles (que no tiene javascript) sin tener que crearlas tu mismo.

Ahora que sabemos cual es la filosofía de trabajo de jQuery basado en emplear selectores del DOM cobra especial importancia esta pregunta. Cuando se carga una página ¿Cuando tenemos disponible el DOM del documento?. La respuesta podría ser cuando el navegador lance el evento onload. Pero no, esto no es correcto, pues el DOM se completa antes de lanzarse este evento. El evento onload se lanza cuando la página se ha cargado completamente, esto involucra no sólo la carga del documento html y su procesamiento para formar el árbol DOM sino también la carga de las imágenes y otros recursos necesarios para mostrar la página. Por otro lado javascript comienza a ejecutarse antes de que se haya completado el DOM. ¿Como podemos ejecutar una sentencia de jQuery asegurándonos que los elementos del DOM estén completos?

Pues jQuery tiene la solución a través de su función ready que se ejecuta cuando el árbol DOM esté completamente formado. Pongámoslo en un ejemplo:

$(document).ready(function() {$(“table”).slideDown(“slow”)})

Esto hace que cuando se cargue el DOM de la página todas las tablas ocultas se muestren poco a poco de arriba a abajo (ajustando su altura). Si a la función $() le pasamos una función esta se ejecutará también cuando el DOM este listo. Es pues un alias de la función ready

$(function() {$(“table”).slideDown(“slow”)})

Una pregunta. Porqué empleamos estas funciones anónimas en lugar de escribir directamente:

$(document).ready($(“table”).slideDown(“slow”))

La respuesta procede de la forma de trabajar de javascript y es que si utilizamos esta última forma, Javascript va a evaluar el argumento de la llamada a la función al ejecutar esta línea de código, es decir, antes de que esté listo el DOM. Empleando la función anónima sólo se evaluará una vez que tengamos el DOM listo

Bueno pues jQuery tiene muchas otras características. Por ejemplo, puedes extenderlo fácilmente. Mira este código:

<script type="text/javascript">
$.fn.idle = function(time)
{
var o = $(this);
o.queue(function()
{
setTimeout(function()
{
o.dequeue();
}, time);
});
return this;
}
</script>

Añade la función idle que puedes encadenar en las llamadas a jQuery para poner una pausa entre los efectos que añadas a los elementos. Ejemplo:

$(function() {$(“a”).hide(1000).idle(2000).show(1000);});

Hace que se oculten los enlaces lentamente (en un segundo) y tras dos segundos vuelvan a aparecer lentamente (en otro segundo).

Comentar por último que la funcion jQuery o $ también puede ser empleada para crear nuevos elementos DOM a través de html, por ejemplo:

$(‘<div>Hola mundo</div>’);

Con sólo parsarle como argumento a la función un fragmento de html podemos crear nuevos elementos DOM. Si queremos agregar este elemento DOM al documento actual podríamos por ejemplo escribir:

$(‘<div>Hola mundo</div>’).insertAfter(‘#identificador’);

Que hará que se agrege el fragmento después de la capa con id=”identificador”

Espero que haya sido de utilidad esta pequeña introducción a jQuery.

Internacionalización y localizacion con gettext

Trataré de explicar algo de terminología antes de entrar en los detalles técnicos de este artículo:

Gettext es un conjunto de herramientas que sirven para que un programa pueda producir mensajes en múltiples idiomas.
Los ficheros po son ficheros de traducciones empleados por el conjunto de herramientas gettext de GNU. Un fichero po contiene una relación entre una cadena no traducida a otra cadena traducida en un idioma en particular.
A la operación por el cual un programa ha sido preparado y es capaz de soportar múltiples idiomas se le llama internacionalización, abreviado por I18n (en inglés internazionalization tiene 18 caracteres entre la i y la n, igual que en castellano)
A la operación por la cual un programa, ya internacionalizado, se le pasa toda la información necesaria para que pueda adaptar su entrada (stdin) y su salida (stdout) de manera que sea correcta para diferentes idiomas y hábitos culturales de un idioma o país en concreto se le llama localización, y suele ser abreviado por L10n (localization, 10 caracteres entre la ele y la ene)
El soporte de lenguaje nativo (Native Language Support o NLS) engloba ambos procesos; internacionalización y localización.
Un locale es un conjunto de componentes de los datos culturales de idiomas y/o países. El soporte de mensajes es uno de los locales más importantes, además hay como locales: los conjuntos de caracteres a utilizar (charsets), la representación de números (numbers), la representación de fechas (dates) y la representación de monedas (currency).

El po de la extensión de los ficheros de traducción significa Portable Object, para distinguirlo de los ficheros mo que significa Machine Object. Los ficheros po son legibles y editables por humanos. Cada fichero po está dedicado a sólo un idioma. Si un programa necesita traducciones en más idiomas se precisa crear un fichero po por idioma. Gettext tiene utilidades para detectar cadenas marcadas para traducir en un programa (xgettext) que crea un fichero tipo pot Portable Object Template que sirve de planilla para iniciar el trabajo de traducción, y utilidades para comentar cadenas que ya no se utilizan en futuras versiones del programa (msgmerge). También lógicamente para convertir los po’s en mo’s mediante fmtstr.

Los fichero mo son ficheros compilados para la lectura por parte del programa y son de tipo binario. El formato de los ficheros mo es a menudo diferente de sistema a sistema y cada sistema debe tener sus propias utilidades para convertir po’s a mo’s.

El formato del fichero po

Esta es la estructura general de una entrada en un fichero po

white-space
# translator-comments
#. extracted-comments
#: reference…
#, flag…
#| msgid previous-untranslated-string
msgid untranslated-string
msgstr translated-string

El fichero po esta formada por múltiples entrada de este estilo un ejemplo podría ser:

#: hello.c:17
msgid “hello world!”
msgstr “¡hola mundo!”

Las entradas comienzan por white-space opcional (generalmente un linea en blanco). Luego vienen los comentarios, todos ellos comienzan por el caracter #. Un espacio en blanco después del # indican un comentario creado y mantenido exclusivamente por el traductor. Si al caracter le sigue un punto este indica que el comentario esta sacado del código del programa, es decir, es un comentario del programador. Si el caracter tras el # es el de dos puntos : indica una referencia al punto en el programa donde esta la cadena por traducir, si hay más de una se separan por un espacio. Si la línea comienza por ‘#|’ indica la última cadena no traducida por el traductor. Y finalmente ‘#,’ contiene flags que veremos más adelante. Todos estos comentarios son también opcionales.

Todas las entradas tienen 2 cadenas una muestra la cadena no traducida (msgid) en la misma manera que aparece en el programa fuente y la otra contiene la cadena traducida (msgstr). Las cadenas traducidas y no traducidas usan comilla doble ‘ ” ‘ como delimitador y barra invertida ‘ \ ‘ como escapador (inicio de secuencia de escape). Pero el traductor no necesita tener esto en cuenta a la hora de editar los po’s.

El comentario ‘#,’ es una lista separada por comas de una o varias opciones. Estas son

fuzzy – indica que la traducción ya no tiene por que ser correcta
xxx-format o no-xxx-format – Donde xxx indica el lenguaje que se emplea en la programación (C, Lisp, PHP, C++, Python etc) o entornos específicos de desarrollo (KDE, GCC, Qt, …)

Contextualizar con gettext

También es posible tener entradas con un contexto específico. Por ejemplo:

white-space
# translator-comments
#. extracted-comments
#: reference…
#, flag…
#| msgctxt previous-context
#| msgid previous-untranslated-string
msgctxt context
msgid untranslated-string
msgstr translated-string

El contexto se emplea para desambiguar mensajes con la misma cadena no traducida ya que esto es posible en un fichero po haciendo que cada una tenga un contexto específico. Observa que es diferente tener un contexto vacío que no tenerlo en absoluto.

Es posible, sobre todo en cadenas cortas, que existan varias traducciones para una misma palabra o grupo de palabras. Para tener con el mismo identificador de mensajes msgid varios mensajes debemos emplear msgctx seguido de una cadena para desambiguar los diferentes mensajes. En el programa para indicar el contexto adecuado de un mensaje que necesita ser contextualizado basta con utilizar la función C (o la equivalente en otros lenguajes soportados):

const char *pgettext (const char *msgctxt, const char *msgid);

En la llamada a esta macro, msgctxt y msgid deben ser cadenas literales. La macro devuelve la traducción del msgid pero restringida al contexto dado por msgctxt.

La ‘p’ en ‘pgettext’ significa “particular: pgettext devuelve una traducción en particular de msgid

También existen estas funciones

const char *dpgettext (const char *domain_name,
const char *msgctxt, const char *msgid);
const char *dcpgettext (const char *domain_name,
const char *msgctxt, const char *msgid,
int category);

Que son generalizaciones de pgettext. Estas funciones permiten especificar el nombre de dominio para la traducción y la categoría permite usar otra categoría locale que la proporcionada por la variable de entorno LC_MESSAGES

Para tener una información completa sobre internacionalización y localización, junto con gettext visita el Manual sobre gettext que está disponible en múltiples formatos.

Formas en plural/singular con gettext

Uno de los problemas que no se han tenido en cuanta hasta ahora es el que tiene que ver con las modificaciones en las cadenas debido al número. Existen muchas diferencias en la cadena e incluso en las diferentes formas que adopta una cadena dependiendo del número. Por ejemplo en español se emplea lo que llamamos la forma plural para el 0 y todos los números mayores que uno y para el 1 se emplea la forma singular. Esto puede no ser así para otros idiomas o tener incluso más casos diferenciados para distintas cantidades (por ejemplo, entre otros muchos, el Polaco, el turco o el Japonés).

En la cabecera del fichero po se puede introducir una cabecera para indicar las reglas de las formas plurales en el idioma específico. Este es un ejemplo:

Plural-Forms: nplurals=2; plural=n == 1 ? 0 : 1;

nplurals es un entero que indica cuantas formas de plural hay en un idioma en concreto, la cadena que sigue a ‘plural=’ es una expresión en C que cuya una variable permitida es la n y que no debe contener números negativos.

Para el español podríamos utilizar:

Plural-Forms: nplurals=2; plural=n != 1;

Atención ten en cuenta que la expresión emplea la característica de C que devuelve 0 para false y 1 para true.

La sintaxis para el tipo de entradas que sirven para resolver formas en plural/singular tiene la siguiente formato:

white-space
# translator-comments
#. extracted-comments
#: reference…
#, flag…
#| msgid previous-untranslated-string-singular
#| msgid_plural previous-untranslated-string-plural
msgid untranslated-string-singular
msgid_plural untranslated-string-plural
msgstr[0] translated-string-case-0

msgstr[N] translated-string-case-n

Las diferentes versiones pluralizadas aparecen con indices del array msgstr y el indice se escoge de la expresión en C anterior. Observa que ahora tenemos dos identificadores de cadena no traducida una para el singular y otra para el plural.

La función que permite traducir estos formatos en el programa es ngettext:

char * ngettext (const char *msgid1, const char *msgid2, unsigned long int n)

Un ejemplo de su uso sería este:

printf (ngettext (“%d objeto encontrado”, “%d objetos encontrados”, n), n);

Fijate que hay que pasarle también el valor n a printf, además de a ngettext. Es posible además no tener cardinales en la función aprovechando la característica de que printf descarta los parámetros por defecto. Ejemplo

printf(ngettext(“Un objeto encontrado”,”Varios objetos encontrados”,n),n);

Subversion y sus mejores recetas (3ª parte)

En la tercera parte de este recetario sobre subversion hablaremos de tags y ramas. Haciendo incapié en su gestión y en como se emplean las ramas en situaciones reales de desarrollo de software.

Puedes encontrar la otras partes de este artículo a continuación:

Subversion y sus mejores recetas (1ª parte)

Subversion y sus mejores recetas (2ª parte)

*Creación de un nuevo tag (etiqueta) del head de trunk*

svn copy file:///var/svn/newrepos/myproject/trunk file:///var/svn/newrepos/myproject/tags/miprimertag -m “Creación de mi primer tag”

*Crear un nuevo tag de tu copia de trabajo*

(siendo micopiadetrabajo el directorio donde está tu copia de trabajo)
svn copy micopiadetrabajo file:///var/svn/newrepos/myproject/tags/misegundotag -m “Tag de mi copia de trabajo”

*Borrar un tag (o una rama)*

svn delete file:///var/svn/newrepos/myproject/tags/misegundotag -m “Eliminando tag obsoleto que ya no se usan”
(Recuerda que siempre puedes recuperar este tag o rama borrado)

*Recuperando un tag (o una rama) borrada*

(suponiendo que el borrado del tag fue en la revisión 215)
svn copy file:///var/svn/newrepos/myproject/tags/[email protected] file:///var/svn/newrepos/myproject/tags/misegundotag -m “Restaurando tag”

Lo habitual al desarrollar software es desarrollar sobre una rama principal (trunk) y cada cierto tiempo (dependiendo de la política del proyecto) se saque una versión nueva del proyecto generando una rama llamada estable, esta rama estable no agregará ninguna nueva característica y sólo se trabajará sobre ella para arreglar bugs. Las nuevas características y mejoras (y también las correcciones de bugs) se aplicarán sobre la rama principal sin que se molesten los dos desarrollos. La rama estable suele tener una vida que depende de las características del proyecto y puede ser de tan sólo unos pocos días a varios años, durante este tiempo se da soporte a esa versión del proyecto.

Subversion y sus mejores recetas (2ª parte)

¿Quien ha dicho que segundas partes nunca fueron buenas?

Continuando con la serie sobre subversión (subversión 1ª parte) seguimos con recetas rápidas para el uso de este sistema de control de versiones. En esta parte explicaremos algunas recetas para poder utilizar ramas de desarrollo paralelas.

*Eliminar bloqueos (locks) que hayan quedado pendientes*

$ svn cleanup
(a veces quedan locks sobre ficheros en tu copia de trabajo. Se retiran con este comando. Si alguna vez subversion se queja de un lock en tu copia de trabajo prueba este comando. Por ejemplo, a veces, svn status puede devolver una L (lock) sobre algún fichero, entonces hay que emplear este comando)

Concepto importante: Revisiones (revisions)

Una operación de commit (svn commit) publica cambios en cualquier cantidad de ficheros y/o directorios en una transacción atómica. Es decir las modificaciones que realices implicadas en un commit se realizan de manera indivisible, no existe posibilidad de que algún otro desarrollador vea o descargue en su copia de trabajo una modificación parcial de tu commit, lo ve todo o nada (si esta viendo o tiene una revisión no actualizada anterior a la tuya). Cada uno de estos commits crean una revisión que tiene asignado un número entero que comienza en cero (repositorio vacío) y que se va incrementando de 1 en 1 por cada commit exitoso. Cada revisión genera un nuevo “sistema de ficheros” que contiene el estado del repositorio después de los cambios efectuados por todos los commits anteriores a esta revisión incluyéndola.

*Obtener la copia de trabajo de una fecha especifica en lugar de un número de revisión*

$ svn checkout -r {“2006-02-17 15:30”}
(Obtiene como copia de trabajo la release más cercana anterior a la fecha 2006-02-17 15:30)

*Crear una nueva rama de desarrollo (branching)*

$ svn copy file:///var/svn/newrepos/myproject/trunk file:///var/svn/newrepos/myproject/branches/myfirstbranch -m “Creada una nueva rama llamada myfistbranch”
(Crea un nueva rama de desarrollo incluyendo un commit para que esta rama ya esté disponible en el repositorio)

*Obtener una copia de trabajo de una rama de desarrollo*

$ svn checkout file:///var/svn/newrepos/myproject/branches/myfirstbranch

*Mantener tu rama de desarrollo en sincronía con la rama principal (trunk)*

(Desde la copia de trabajo de tu rama de desarrollo, asegúrate de que tu copia de trabajo esta limpia svn status limpio)
$ svn commit -m “Llevar los cambios locales a la rama de desarrollo”
(Si creamos el branch en la revisión 9)
$ svn merge -r 9:HEAD ^/trunk
(mezclar los cambios procedentes de la rama principal (trunk) a tu copia de trabajo. Es posible que surjan conflictos que haya que resolver, el acento circunflejo es un alias de la url del repositorio)
$ svn commit -m “Actualizando rama con trunk”
(Se actualiza esta rama de desarrollo)

*Mezclando los cambios de tu rama con la rama principal (trunk)*

$ svn commit -m “Llevar los cambios locales a la rama de desarrollo”
$ svn merge -r 9:HEAD file:///var/svn/newrpos/myproject/trunk
(Volvemos a poner en sincronía la nueva rama)
$ svn commit -m “Actualizando rama con trunk”
$ svn merge –reintegrate ^/branches/myfirstbranch
(la rama es terminada aquí y no se debe trabajar con ella)
$ svn commit -m “Mezclar cambios en nueva rama a la rama principal”

*Borrar rama*

$ svn delete ^/branches/myfirstbranch -m “Eliminando rama myfirstbranch”
(Se borra pero el historial se puede revisar/reutilizar)

*Llevar tu working copy a otra rama de desarrollo*

$ svn switch ^/branches/myfirstbranch

*Información sobre la copia de trabajo actual*

$ svn info

*Obtener información de mezclado de cambios que ya han sido llevados de la rama principal (trunk) a la nueva rama de desarrollo*

(copia de trabajo es la nueva rama de desarrollo, /branches/myfirstbranch)
$ svn mergeinfo file:///var/svn/newrepos/myproject/trunk

*Obtener cambios que aún deben ser mezclados desde la rama principal (trunk) a la nueva rama de desarrollo*

(copia de trabajo es la nueva rama de desarrollo, /branches/myfirstbranch)
$ svn mergeinfo file:///var/svn/newrepos/myproject/trunk –show-revs eligible

*Obtener informe de resultados de una mezcla sin hacerla realmente*

(copia de trabajo es la nueva rama de desarrollo, /branches/myfirstbranch)
$ svn merge file:///var/svn/newrepos/myproject/trunk –dry-run

*Echar atrás cambios ya realizados (comiteados)*

(suponiendo que queremos echar atrás la revisión 101 que contiene graves errores)
$ svn merge -r 101:100 file:///var/svn/newrepos/myproject/trunk

ó

$ svn merge -c -101 file:///var/svn/newrepos/myproject/trunk
(actualizas tu copia de trabajo con lo que esta en la revisión 100)
$ svn diff
(compruebas los cambios)
$ svn commit -m “Deshaciendo los cambios de la revisión 101”

*Recuperar un fichero borrado en una revisión anterior*

(Tenemos un fichero llamado para_borrar.txt que se borró en la revisión 32 y queremos recuperarlo
$ svn copy http://svn.example.com/repos/calc/trunk/[email protected] ./para_borrar.txt
$ svn status
(Verificamos que se va a añadir)
$ svn commit -m “Recuperamos para_borrar.txt borrado en la revisión 32”

Así lo recuperaríamos con historial si por cualquier motivo no quisiéramos el historial de este archivo recuperado tenemos otra opción

*Recuperar un fichero borrado en una revisión anterior eliminando su historial*

$ svn cat http://svn.example.com/repos/calc/trunk/[email protected] > ./para_borrar.txt
$ svn add para_borrar.txt
$ svn commit -m “Re-creado el fichero para_borrar.txt de la revisión 31”

*Recupera un fichero borrado en una revisión anterior sin tener una copia de trabajo*

$ svn copy http://svn.example.com/repos/calc/trunk/[email protected] http://svn.example.com/repos/calc/trunk/para_borrar.txt -m “Recuperamos para_borrar.txt borrado en la revisión 32”

Espero que os haya sido de utilidad.

Como crear un extensión para Firefox

Tutorial paso a paso de como crear una extensión, plugin o addon para firefox. Solamente es necesario tener conocimientos de xul (un lenguaje de marcas xml), javascript y algunas herramientas básicas de edición/compresión de ficheros. Ahora ya no parece tan complicado, ¿verdad?. Con muy poco esfuerzo podrás crear un pequeño plugin para firefox. Adaptarlo para crear el tuyo propio es sencillo pues explico para que sirve cada una de las cosas que haremos durante el tutorial. Espero que te sirva de ayuda pero sólo podrás utilizarlo a condición de que dejes algún pequeño feedback como comentario al final para poder adaptar y mejorar el texto (¡no te olvides de esto!).

Dicho esto comencemos como siempre por el principio:

Para comenzar comentaros que al crear la extensión necesitaremos cambiar de una manera bastante peligrosa nuestro navegador (de hecho, está prácticamente garantizado que echaremos a perder nuestra instalación de firefox). Es por eso que una de las maneras más seguras de actuar es creando un perfil especial para nuestras pruebas. Para generar este perfil debemos primero ejecutar nuestro firefox con la opción –profilemanager

$ firefox –profilemanager

A la hora de arrancar veras que te aparece una ventana donde se muestran los perfiles actuales (default) y te permite crear uno nuevo, borrar perfiles renombrar y utilizar un perfil en concreto. Puedes hacer, además que se lanze esta ventana cada vez que ejecutar firefox o trabajar offline si lo deseas. En este punto vamos a crear un nuevo perfil que llamaremos test además forzaremos que se nos muestre este menu en los próximos arranques de la máquina (eliminando el chekbox de “Don’t ask at startup”). También es posible arrancar firefox con un perfil determinado con la opción -P así si guardamos el perfil test la próxima vez podríamos arrancar utilizando

$ firefox -P test

ó

$ firefox -P default

Empleando directamente el perfil adecuado. Podemos incluso crear accesos directos a cada uno de estos perfiles desde accesos directos en nuestro escritorio

En principio sólo puedes tener en ejecución un tipo de perfil en firefox aunque tengas diversas ventanas independientes. Pero puedes eliminar esta limitación añadiendo el parámetro –no-remote al arrancar.

$ firefox -P test –no-remote

y

$ firefox -P default -no-remote

¡Tendras dos firefox con extensiones, bookmarks, cookies etcereta completamente diferentes!

En este perfil de test es donde desarrollaremos nuestra propia extensión. Es importante acceder a la página about:config (sólo tienes que poner about:config en al barra de direcciones) y modificar algunos parámetros que nos ayudarán en nuestro desarrollo. Estos parámetros suponen una penalización en el rendimiento de firefox para este perfil pero son realmente necesarios para nuestro desarrollo. Estos son:

*javascript.options.showInConsole = true* ok

Muestra los errores de los ficheros del chrome en la consola de errores (luego explico que es el chrome)

*javascript.options.strict = true* ok

Habilita el modo strict para javascript para avisos en la consola.

*extensions.logging.enabled = true* ok

Envía información más detallada a la consola de errores sobre problemas de instalación y actualización.

Para crear un extensión hay que crear en un archivo comprimido mediante zip con la extensión .xpi con un conjunto de ficheros que contienen datos sobre la extensión, código de la extensión, xul y recursos propios a la extensión. Si vas a servir tu extensión desde tu página web asegúrate que tu servidor envia las cabeceras adecuadas al descargar al extensión .xpi. Para ello basta configurar tu apache con

AddType application/x-xpinstall .xpi

Vemos ahora que debe contener este fichero .xpi

En su raiz un fichero llamado install.rdf con la información básica sobre la extensión

Subversion y sus mejores recetas

En este primer artículo haré un repaso a los comandos más comunes de subversion y a su utilización en un escenario real. Empezando desde la creación del repositorio hasta la generación de una nueva release para su salida a producción. En otros artículos extenderé la temática al empleo de ramas paralelas de desarrollo y la configuración de servidores locales y remotos de subversion. Empecemos por el principio

*Crear un nuevo repositorio*

$ sudo mkdir /var/svn
(creamos el directorio para albergar nuestros repositorios)
$ sudo chown $USER.$USER /var/svn
(asignamos este directorio al usuario actual para evitar problemas de permisos)

$ svnadmin create /var/svn/newrepos
(creamos un nuevo repositorio en /var/svn/newrepos
$ svn list file:///var/svn/newrepos
(listamos los ficheros incluidos en este repositorio, vacío de momento)

*Importar ficheros al repositorio*

$ mkdir -p tmp/branches tmp/tags tmp/trunk
(creamos los primeros directorios para introducir en el repositorio)
$ svn import tmp file:///var/svn/newrepos/myproject -m “Importación inicial”
(importamos el contenido del directorio tmp al repositorio dentro de la carpeta myproject con el commentario Importación inicial. myproject no es un directorio real dentro del sistemas de archivos es gestionado internamente por subversion)

*Preparar nuestra copia de trabajo*

$ mkdir myproject
(creamos el directorio donde situaremos nuestra copia de trabajo)
$ svn checkout file:///var/svn/newrepos/myproject/trunk .
(obtenemos los ficheros del directorio trunk del repositorio, en nuestro caso aún está vacío)

*Añadir nuevos ficheros a nuestro proyecto*

$ vi README
(creamos un nuevo fichero dentro de la copia de trabajo)
$ svn add REAME
(añadimos el fichero a los programados para ser añadidos al repositorio)
$ svn commit -m “Añadimos primer fichero README”
(enviamos los cambios al repositorio añadiendo el fichero README al mismo, el texto entrecomillado después del -m es un comentario sobre este envío)

*Sincronizando con el repositorio*

$ svn update
(Sincronizando con el contenido del repositorio por si hay cambios en los ficheros)

*Copiar ficheros*

$ svn copy README README2
$ svn commit -m “README copiado a README2”

*Mover ficheros*

$ svn move README2 README3
$ svn commit -m “Moviendo README2 a README3”

*Borrar un fichero*

$ svn del README3
$ svn commit -m “Borrando README3”

*Creando un nuevo directorio*

$ svn mkdir src
$ svn commit -m “Creando directorio src”

Comandos de apoyo

$ svn log
(registro de cada cambio hasta el último svn update ejecutado)
$ svn list
(Lista de ficheros hasta el último svn update ejecutado)
$ svn status
(Lista de tareas programadas para el siguiente commit basados en los cambios efectuados en la copia actual. Leyenda: A – fichero a añadir, D – fichero a borrar, C – conflictos con respecto al repositorio, M – Modificaciones locales, ? fichero no controlado por el sistema de versionado)

*Modificar ficheros ya existentes y analizar cambios*

$ vi README
(editamos README modificando su contenido)
$ svn diff
(vemos los cambios en ficheros)
$ svn commit -m “Modificaciones en README”

*Deshacer modificaciones en tu copia de trabajo*

$ vi README
(editamos README e introducimos algún error)
$ svn diff
(observamos de nuevo estos cambios en README)
$ svn revert README
(deshacemos las modificaciones sobre README, revert es capaz de deshacer cualquier modificación programada para el siguiente commit)

Tratamiento de conflictos

*Anticipando posibles problemas antes de actualizar tu copia de trabajo*

$ svn update -u

*Actualizando con el repositorios (este retorna coflictos)*

$ svn update
U INSTALL
G README
Conflict discovered in ‘bar.c’.
Select: (p) postpone, (df) diff-full, (e) edit,
(h) help for more options:

Desde la versión 1.5 de subversion (interactive conflict resolution)

U (UPDATED) – fichero actualizado del repositorio (cambios sólo en el repositorio)
G (MERGED) – fichero actualizado del repositorio y con cambios locales combinados automáticamente
Conflict discovered in ‘xxxx’ – fichero actualizado en el repositorio y con cambios locales que se solapan. Hay que proceder a resolver el problema. Atención mientras no se resuelva el conflicto no se puede hacer commit de tus cambios al repositorio.

Las opciones son:

postpone – Deja en la copia local 3 archivos para ayudarte a resolver el conflicto. Suponiendo que el fichero en conflicto se llama filename estos son los 3 ficheros filename.mine (la versión en tu copia de trabajo), filename.r (la versión base de tu copia de trabajo, es decir, la del último update exitoso o en su defecto tu checkout), filename.r (la versión head del repositorio). Además filename incluye ahora el diff entre las dos versiones. La manera de resolver es:

*Si quieres aceptar los cambios procedentes del repositorio y eliminar tus cambios*

$ svn revert

ó

$ svn resolve –accept theirs-full filename

*Si quieres subir tu versión y eliminar los cambios que hayan llegado con el update*

$ svn resolve –accept mine-full filename

Recuerda que svn resolve eliminará las diferentes versiones del fichero conflictivo en tu copia de trabajo

*Si se editan los cambios*

$ vi filename
(se corrigen manualmente los conflictos)
$ svn resolve –accept working filename
(Se resuelve el conflicto)

$ svn commit -m “Conflicto resuelto en filename”
(Tras la resolución se envían los cambios al repositorio)

diff-full – Te devuelve las diferencias
edit – Edita directamente el archivo con las diferencias para resolver el conflicto desde este menu interactivo
resolved – Después de editarlo se declara el conflicto como resuelto
mine-full – Acepta tus cambios y elimina los procedentes del update
theis-full – Elimina tus cambios y acepta los procedentes del update
launch – Ejecuta un programa externo para resolver el conflicto (necesita preparación previa, introducir la variable de entorno SVN_MERGE o definir merge-tool-cmd en los ficheros de configuración de subversion. Al programa se le pasaran 4 ficheros (base, update, working y diff)
help – Muestra la lista de comandos disponibles

*Enviando tus modificaciones*

$ svn commit -m “Modificaciones”
(Envia los cambios de tu copia de trabajo al repositorio, si hay problemas y no se pueden enviar los cambios debes ejecutar un svn update para resolver conflictos tal y como comentaba previamente)

*Ver el contenido de un fichero en una versión en particular*

svn cat -r 3 README
(svn cat -r

*Registro de cambios de una revision*

$ svn log -r 4
(svn log -r )

*Registro de cambios entre revisiones*

$ svn log -r 3:5
(svn log -r :)

*Comparando tu copia de trabajo con el repositorio*

$ svn diff -r 3 README
(svn diff -r )

*Comparando distintas versiones del repositorio*

$ svn diff -r 3:4 README
(svn diff -r :

*Devolviendo fichero del repositorio*

$ svn cat -r 3 README
(svn cat -r

*Crear una copia de trabajo de una versión antigua de tu repositorio*

$ svn checkout -r 3
(svn checkout -r )
Atención, no se pueden enviar cambios desde esta copia de trabajo

*Actualizar tu copia de trabajo a una versión antigua de tu repositorio*

$ svn update -r 3 file:///var/svn/mirepo .
(svn update -r )
Atención, no se pueden enviar cambios desde esta copia de trabajo

*Generar release*

$ svn export file:///var/svn/newrepos/myproject/trunk

*Geneare release de una versión en particular*

$ svn export file:///var/svn/newrepos/myproject/trunk -r 3

Si os ha resultado útil esta artículo aquí tenéis otro con la 2ª parte sobre subversión.