¿Creación de archivos PDF? ¡Hay una API para eso!

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!

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *