Lo he oído muchas veces: ¿cómo puedo exportar a PDF desde Xojo? Fijo, hay multiples respuestas apuntando a una buena cantidad de recursos, incluyendo excelentes plug-ins de terceros. ¿Sabes qué? ¿Y si pudieses hacer prácticamente lo mismo con sólo usar una API ya existente? Sí, ¡hay una API remota para eso! El único requisito para su uso es que la app necesitará mantener una conexión a Internet… y, por supuesto, algo de programación; pero esa es la parte divertida de usar Xojo, ¿no es así?
Así que tengo una propuesta para ti, puedes descargar la clase ya creada y lista para usar… o también puedes continuar leyendo ¡y descubrir cuan fácil es usar Xojo para crear tus documentos PDF!
El servicio que vamos a utilizar es el software OpenSource Docverter, capaz de convertir cualquier archivo de texto plano, HTML, Markdown o LaTeX a formatos PDF, Docx, RTF o ePub utilizando para ello una sencilla API HTTP. En mis pruebas funcionó a la perfección a partir del HTML + CSS obtenido mediante mi clase Markdown Parser para Xojo.
De hecho, es posible utilizar este servicio directamente desde una instancia de la clase Shell, pero he encontrado más portable y multiplataforma crear una Clase en torno a esta API, de modo que simplemente puedas soltarla en tu proyecto, pasar el archivo que quieras e indicar el formato de conversión que quieres obtener como resultado. ¡Tan simple como eso!
Para este wrapper de la API vamos a crear una clase derivada de HTTPSocket. Nómbrala HTTPDocConverter
, y crea sobre ella las siguientes Constantes de tipo String, y que nos facilitarán el uso de la clase:
kDOCX As String = "docx"; scope: public
.kePUB As String = "ePub"; scope: public
.kMOBI As String = "mobi"; scope: public
.kRTF As String = "rtf"; scope: public
.kRemoteAPI As String = "http://c.docverter.com/convert"; scope: private
.
Creo que es un buen hábito acostumbrarse a definir constantes para todas las cadenas y otros valores congelados, de modo que se minimicen los bugs menores a lo largo del código, además de que facilitarán el cambio de su valor a lo largo y ancho del proyecto, en caso de que sea necesario.
Ahora es el momento de implementar el Constructor de la clase y que recibirá tres parámetros que simplificarán su uso:
convertDataTo As String
. Este es el texto que identifica el formato al cual queremos convertir nuestro documento, y para lo cual hemos definido las Constantes en el anterior paso.inputFile As folderItem
. Un FolderItem válido apuntanto al archivo que queremos usar como fuente para el proceso de conversión.notification as callback
. Este es el nuevo Tipo de Delegado que crearemos en el siguiente paso, y que se utilizará en la clase, bien, para notificar sobre la finalización del proceso. Así, nuestro código y UI no se bloquearán.
Por tanto, nuestra clase Constructor tendrá la siguiente signatura:
Constructor( convertDataTo As String, inputFile As FolderItem, notification as callback )
Una vez que hemos añadido el método Constructor, el IDE de Xojo incorporará la oportuna inicialización de clase en el Editor de Código, y nosotros lo completaremos con las siguientes instrucciones:
Super.Constructor registeredCallback = notification encodeRequest( inputFile, convertDataTo )
Es el momento de añadir la clase al nuevo Tipo Delegado (Insert > Delegate), utilizando la siguiente signatura en el Panel Inspector asociado:
- Delegate Name: callback
- Parameters: content As String, documentType As String
- Scope: Private.
Esto es: nuestra clase aceptará como Delegado de callback
cualquier método que acepte dos String como argumentos. De hecho nuestra clase devolverá los datos ya convertidos en el argumento content
y también el tipo de documento utilizado para lo conversión en el argumento documentType
.
Con nuestro Delegado ya listo, es momento de añadir a la clase la Propiedad registeredCallback
, y esta será la responsable de apuntar al método Delegado pasado a la instancia de clase mediante el Constructor:
- Name: registeredCallback
- Type: callback
- Scope: Private
De hecho, aprovecharmeos para añadir otro par más de propiedades:
- Name: convertedToType. Esta contendrá el formato de conversión.
- Type: String
- Scope: Private
- Name: receivedData. Esta propiedad contendrá los datos convertidos, recibidos desde el servicio remoto.
- Type: String
- Scope: Private
Codificar la petición Post… de forma correcta
Con todas nuestras propiedades definidas, es momento de añadir el Método encodeRequest
. Aquí es donde nos encargaremos de construir la cabecera de nuestra petición Post, incluyendo los datos para el archivo fuente que hemos de enviar al URL remoto, junto con el resto de información requerida. Crea el método con la siguiente signatura:
- Method Name: encodeRequest
- Parameters: inputFile As Folderitem, convertdataTo as String
Y escribe el siguiente fragmento de código en el Editor de Código resultante:
Dim formData As New Dictionary formData.Value("input_files[]") = inputFile formData.value("from") = "html" formData.value("to") = convertDataTo convertedToType = convertDataTo Dim boundary As String = "" Boundary = "--" + Right(EncodeHex(MD5(Str(Microseconds))), 24) + "-reQLimIT" Static CRLF As String = EndOfLine.Windows Dim data As New MemoryBlock(0) Dim out As New BinaryStream(data) For Each key As String In FormData.Keys out.Write("--" + Boundary + CRLF) If VarType(FormData.Value(Key)) = Variant.TypeString Then out.Write("Content-Disposition: form-data; name=""" + key + """" + CRLF + CRLF) out.Write(FormData.Value(key) + CRLF) Elseif FormData.Value(Key) IsA FolderItem Then Dim file As FolderItem = FormData.Value(key) out.Write("Content-Disposition: form-data; name=""" + key + """" + "; filename="""+inputFile.Name+""""+ CRLF) out.Write("Content-Type: text/html" + CRLF + CRLF) Dim bs As BinaryStream = BinaryStream.Open(File) out.Write(bs.Read(bs.Length) + CRLF) bs.Close End If Next out.Write("--" + Boundary + "--" + CRLF) out.Close Super.SetRequestContent(data, "multipart/form-data; boundary=" + Boundary)
Llamar a la API remota
Ahora que hemos fianlizado con la parte complicada, añadamos el Método getConvertedFile
, responsable de invocar la API remota:
- Method Name: getConvertedFile
- Scope: Public
Y escribe esta simple línea de código en el Editor de Código asociado:
Super.post( kRemoteAPI )
Esta simple línea se encarga de poner en marcha toda la magia entre bastidores, y para obtener la respuesta necesitamos añadir ahora a nuestra clase el Evento PageReceived
, escribiendo el siguiente código en el Editor de Código resultante:
If registeredCallback <> Nil Then registeredCallback.Invoke content, convertedToType End If
Si lo deseas, también puedes añadir el Evento Error
, de modo que puedas elevar una excepción o informar de cualquier error que se haya producido durante el proceso.
¡Probando nuestra clase!
Para probar la clase, y convertir algunos documentos en el proceso, añade un nuevo Método a la ventana del proyecto (asumamos que se trata de un Proyecto Desktop de Xojo). Este es el que actuará como Delegado para el callback una vez que hayamos recibido los datos ya convertidos:
- Method Name: conversionCompleted
- Parameters: convertedData As String, documentType as String
- Scope: Public
Y escribe el siguiente código en el Editor de Código resultante para el método:
Dim f As FolderItem = GetSaveFolderItem("","ConvertedFile." + documentType) If f <> Nil Then Dim tof As TextOutputStream = TextOutputStream.Create(f) If tof <> Nil Then tof.Write convertedData tof.Flush tof.Close End End
Añade ahora el Evento Open a la misma ventana y escribe el siguiente código. Este es el encargado de crear nuestra instancia de clase y de disparar el proceso de conversión. En este ejemplo, convirtiendo el archivo fuente a un archivo PDF (sólo tendrás que cambiar la constante de archivo de formato para obtener otro tipo de documentos como resultado):
Dim f As FolderItem = GetOpenFolderItem("") If f <> Nil Then Dim post As New HTTPDocConverter(HTTPDocConverter.kPDF,f, AddressOf conversioncompleted) post.getConvertedFile End If
Conclusiones
Como has podido ver, con un par de métodos y Eventos hemos podido crear una Clase multiplataforma que funciona con ejecutables de 32 y 64 bits, tanto para Desktop como para Consola, Web y Raspberry Pi. En mis pruebas he podido crear archivos PDF, DOCX, RFT y también archivos MOBI ¡al instante!