Complemento de IBM Mashup Center para convertir documentos HTML a XML

Una mirada profunda al desarrollo del complemento de IBM Mashup Center

Aprenda a construir un complemento para el IBM® Mashup Center capaz de convertir documentos HTML a XML, lo cual abre la puerta para realizar simples extracciones de datos de páginas HTML mediante el Feed Mashup Editor.

Louis MauIBM

Louis Mau is part of the InfoSphere MashupHub development team. His current focus is to help customers build situational applications using the IBM Mashup Center. Prior to this role, he was the architect for DB2 Everyplace Sync Server, which helps synchronize data from enterprise databases onto a small foot print database running on mobile devices.



11-09-2009

Introducción

IBM Mashup Center viene con generadores de fuentes que pueden acceder y generar fuentes XML directamente a partir de muchas fuentes de datos empresariales distintas. Al mismo tiempo, dada la diversidad de almacenes de datos y software existente, nos encontraremos con fuentes de datos a las que no es posible acceder con estos generadores incorporados. Para permitirle aumentar la capacidad del IBM Mashup Center, la capacidad de generación de fuentes se puede ampliar agregando complementos.

Este artículo es una continuación del artículo denominado "Amplíe el alcance de datos para IBM Mashup Center" y está basado en la versión V1.1 del software. La suposición es que usted ya está familiarizado con los conceptos básicos sobre la programación de un complemento para IBM Mashup Center. En especial, usted deberá saber cómo programar en Java™, JSP, y JavaScript. El artículo le mostrará cómo desarrollar un complemento que sirva para convertir documentos HTML a XML, y se sirve de este ejemplo para ilustrar la programación de un complemento más complejo. El beneficio secundario que se obtiene es que una vez que el documento HTML se encuentra en formato XML, podrá ser introducido en el Feed Mashup Editor, permitiendo así la extracción de datos.


Herramientas utilizadas para convertir HTML a XML

Necesitaremos un paquete Java que convierta HTML a XML. Existe una cantidad de paquetes Java de este tipo. Este proyecto utiliza JTidy, un Puerto Java de HTML Tidy de W3C. HTML Tidy comenzó como un verificador de sintaxis y una herramienta de pretty printing para HTML. Al igual que su primo no basado en Java, JTidy puede utilizarse como una herramienta para limpiar los HTML mal formados y defectuosos. Soporta, además la creación de XML. Usted encontrará un vínculo en la sección Recursos para poder descargar e instalar JTidy.

Utilizaremos el Tidy.jar de la carpeta de construcción. Para hacer que el JAR sea independiente, la misma contiene una versión más antigua de las clases del W3C Document Object Model (DOM). Debido a que actualmente se incluye en el JDK una versión más reciente de las clases del W3C DOM, deberán eliminarse las clases del paquete org.w3c.dom.


Cómo configurar un proyecto Eclipse

Como se describe en la Sección 6.1 de Application Programming Interface Reference (Referencia Interfaz de Programación de Aplicaciones), Versión 1.0, el marco de generación de fuentes busca automáticamente archivos ZIP que contengan complementos de terceros ubicados en la carpeta especial <WebApplication>/WEB-INF/plugins. El archivo ZIP debe tener la estructura de carpetas que se especifica a continuación:

  • /client/plugins/PLUGIN_DIR – Contiene archivos para buscadores, como, como por ejemplo imágenes, archivos JavaScript, etc.
  • /server/plugins/PLUGIN_DIR – Contiene archivos utilizados por el complemento para representarse a sí mismo (archivos HTML, páginas JSP). Pueden incluirse carpetas adicionales para los archivos del complemento.
  • /WEB-INF/classes – Contiene las clases Java del complemento. Puede consistir en una jerarquía de carpetas. Las clases serán copiadas a <WebApplication>/WEB-INF/classes.
  • /WEB-INF/lib – Contiene archivos JAR utilizados por el complemento (de terceros).

A fin de simplificar la construcción y el empaquetado final del complemento, será conveniente que usted cree un proyecto con su IDE de preferencia que tenga la misma estructura de directorio que la requerida por el archivo ZIP final. La Figura 1 muestra el diseño del proyecto en Eclipse creado por el autor.

Figura 1. Proyecto en Eclipse
Proyecto en Eclipse

Obsérvese que el PLUGIN_DIR debe ser único y tener el mismo nombre que el paquete del complemento. Para este ejemplo, utilicé el nombre de paquete sample.mashupcenter.tidyhtml. Observe además, que hemos ubicado a Tidy.jar en la carpeta WEB-INF/lib y creamos una carpeta denominada lib_noship que contiene dos JAR adicionales que son provistos por el servidor del Mashup Center en tiempo de ejecución pero que no se encuentran disponibles en el entorno de desarrollo. Estos JAR no deberán estar incluidos en el archivo ZIP de la implementación final. (De hecho, la carga del archivo ZIP del complemento fallaría si se los incluyera por error.)


Archivo plugin.xml

Cada uno de los complementos del Mashup Center cuenta con dos operaciones principales: un editor que recoge los parámetros de creación, y un generador para crear la fuente. (La fuente se refiere normalmente a un documento xml que se adapta a RSS o a la especificación ATOM. Observe que en este caso, el XML generado se originará a partir de HTML y se adaptará a RSS o a la especificación ATOM.) El marco del complemento encuentra cuáles son las clases Java que implementan las dos operaciones clave al leer el archivo package.xml, que debe estar ubicado en la carpeta server/plugins/PLUGIN_DIR. El Listado 1 muestra el archivo package.xml para este complemento.

Listado 1. Archivo XML del paquete
<plugin>
  <name>Tidy Html</name>
  <author>L. Mau</author>
  <version>1.0</version>
  <category>departmental</category>
  <editor>Html2XmlEditorPlugin</editor>
  <generator>Html2XmlGeneratorPlugin</generator>
  <description>convert any Html page into xhtml</description>
  <icon16path>/plugins/sample.mashupcenter.tidyhtml/icons/btn16_hello.gif</
	icon16path>
  <icon32path>/plugins/sample.mashupcenter.tidyhtml/icons/btn32_hello.gif</
	icon32path>
  <icon64path>/plugins/sample.mashupcenter.tidyhtml/icons/btn64_hello.gif</
	icon64path>
  <objectType>feed</objectType>
</plugin>

Cabe mencionar que los elementos name (nombre), description (descripción) y version (versión) se incluyen para beneficio del creador y no son utilizados por el marco del complemento del IBM Mashup Center. El marco del complemento utiliza las propiedades de plugin.uiname dentro del archivo ui.properties como nombre del complemento cuando presenta la lista de opciones después de que los usuarios seleccionan New Feed (Nueva fuente).

El archivo ui.properties se encuentra en la carpeta /server/plugins/PLUGIN_DIR/nls y se carga mediante la convención de carga estándar para conjuntos de recursos Java. Para cada lenguaje soportado, coloquela cadena traducida para plugin.name en un archivo de propiedades con el locale agregado a "ui." Por ejemplo, la versión japonesa del archivo deberá denominarse ui_ja.properties.


Cómo implantar el editor

El Html2XmlEditorPlugin amplía el BaseEditorPlugin, una clase base que nos obliga a implementar el método renderEditor.

Listado 2. Clase Html2XmlEditorPlugin
package sample.mashupcenter.tidyhtml;

import : : : : :   // omitted from listing

/**
 * This plugin uses JTidy to convert Html to Xml.
 */
public class Html2XmlEditorPlugin extends BaseEditorPlugin {

    private static final Log log = LogFactory.getLog(Html2XmlEditorPlugin.class);

    public  static final String I18N_RESFILE = Html2XmlConstants.PLUGIN_NAME
                                         + ".nls.tidyhtml";
    public  static final String HTTP_BASEURL = "plugins/"
                                         + Html2XmlConstants.PLUGIN_NAME +  "/";
    public  static final String RES_BASEURL  = "/" + HTTP_BASEURL;
    public  static final String HELPPATH     = HTTP_BASEURL + "help/tidyhtml.htm";

El primer enunciado de la clase crea una instancia de registro estático a partir del paquete de registro común Apache. Esta es la misma infraestructura de registro que se utiliza en el marco de generación de fuentes. Los mensajes de registro quedarán intercalados con los del marceo de generación de fuentes, y se escribirán en el siguiente archivo <WebApplication>/META-INF/logs/javamashuphub.log. En forma predeterminada, sólo se escribirán en el archivo de registro los mensajes provenientes de WARN y superiores (por ejemplo: ERROR).

El siguiente enunciado es la constante de cadena para un archivo de recursos. Si bien no hay necesidad de traducir todas las constantes de cadena a distintos lenguajes, resulta una buena práctica de programación mantener todas las cadenas de texto visualizadas por la interfaz del usuario en un archivo de recursos independiente. Observe que el archivo tidyhtml.properties se ubica en el mismo directorio que el archivo ui.properties anteriormente descripto.

Las pocas constantes de cadena restantes definen rutas a los diversos recursos que el complemento necesita. Observe que la ubicación en tiempo de ejecución de estos recursos imita la estructura del archivo ZIP del complemento.


Método renderEditor

El método renderEditor toma dos parámetros: RequestData y Entry. Este método es llamado por el marco cuando los usuarios eligen crear una nueva fuente con este complemento, o cuando los usuarios editan una fuente existente que fuera creada anteriormente por este complemento. Como veremos, el método toma dos parámetros de los tipos RequestData y Entry. Ambos parámetros son en realidad comunes para todos los métodos invocados por el marco en respuesta a las acciones de los usuarios. RequestData contiene información enviada desde el buscador, y Entry contiene toda la información guardada por el marco para esta instancia de la fuente.

Listado 3. Cuerpo del método renderEditor
public ViewBean renderEditor(RequestData rdata, Entry entry)
{
    ResourceBundle i18n = ResourceBundle.getBundle(I18N_RESFILE,rdata.getLocale());
    String pluginId = this.getId();

    Html2XmlUrlViewBean htViewBean = new Html2XmlUrlViewBean();
    htViewBean.setEntry(entry);
    htViewBean.setHtmlUrl( entry.getAttribute(Html2XmlUrlViewBean.PARAM_HTMLURL ) );
    htViewBean.setSnapshot( entry.getAttribute(Html2XmlUrlViewBean.PARAM_SNAPSHOT ) );

    FormViewBean form = new FormViewBean();
    form.setSuffix( htViewBean.getSuffix() );
    form.addComponent( htViewBean );
    form.setOnsubmit( PluginHelper.getClientMe(pluginId, entry.getObjectId()) +
            ".invokeServer('displayHtmlPage',"  +
            PluginHelper.getClientId(pluginId, entry.getObjectId()) +
            "_" + form.getSuffix() + ");");
    form.setEntry(entry);

    FrameViewBean frame = new FrameViewBean();
    frame.addComponent(form);
    frame.setLabel(entry.getTitle());
    frame.setTitle(i18n.getString("frame.urltitle"));
    frame.setEntry(entry);
    frame.setHelpPath( HELPPATH );
    return frame;
}

El método devuelve una instancia del tipo ViewBean. ViewBean es similar a Java Bean con getters y setters para las propiedades de visualización. Su propósito principal es especificar la JSP utilizada por el marco de generación de fuentes para crear HTML para el editor específico del complemento. Debido a que el método renderEditor no podría ser llamado para editar una instancia existente, recupera todos los datos guardados anteriormente para esta instancia llamando al método Entry::getAttribute. Más tarde veremos cuándo y cómo se guardan estos datos. Los valores recuperados pasan luego a Html2XmlUrlViewBean para que la JSP asociada pueda mostrar el valor provisto anteriormente por el usuario. Observe que el Html2XmlViewBean específico del complemento no es devuelto de manera directa, sino que en cambio queda ajustado dentro de una instancia FormViewBean mediante el método addComponent. FormViewBean brinda una lógica de JavaScript a medida para enviar la información ingresada por el usuario al complemento cuando se oprime el botón Next (siguiente) de la interfaz del editor, similar a un asistente. Por último, FormViewBean está a su vez ajustado dentro de un FrameViewBean. El que se devuelve es este último.

Una última observación antes de seguir. Llamamos al método setOnsubmit para que nos brinde un fragmento de código JavaScript que se pueda ejecutar cuando se oprime el botón Next. El código JavaScript llama a la función hub.managers.InvokePlugin's invokeServer descripta en la Sección 6.3.2 de la Application Programming Interface Reference. El primer parámetro especifica el método displayHtmlPage de esta clase que se utilizará para dar servicio a la siguiente página de edición.


Html2XmlUrlViewBean y su archivo JSP asociado

En la anterior sección, ya hemos descripto brevemente el Html2XmlUrlViewBean. El Listado 4 muestra parte de la definición de la clase:

Listado 4. Html2XmlUrlViewBean
public class Html2XmlUrlViewBean extends ViewBean
{
  public static final String PARAM_HTMLURL     = "htmlurl";
  public static final String PARAM_SNAPSHOT    = "snapshot";
	
  private String  htmlUrl;
  private String  snapshot;

  public Html2XmlUrlViewBean()
 {
    this.setI18NProperties( Html2XmlConstants.PLUGIN_NAME + ".nls.tidyhtml");
 }

  /* (non-Javadoc)
   * @see com.ibm.mashuphub.model.ViewBean#getJSPPath()
   */
  @Override
  public String getJSPPath() {
    return "/server/plugins/" + Html2XmlConstants.PLUGIN_NAME + "/tidyhtmlUrl.jsp";
 }

 public String getSuffix() {
    return "tidyhtmlUrl";
 }

FormViewBean llamará al método getJSPPath cuando trate de generar el formulario HTML para reunir los parámetros específicos para este complemento. El método getSuffix deberá devolver una cadena única entre los diversos ViewBeans de este complemento. Antes de analizar el archivo JSP asociado, resulta útil observar el formulario HTML representado:

Figura 2. Primera página de edición de InfoSphere MashupHug
Primera página de edición de InfoSphere MashupHug

Observe que el formulario posee dos elementos de entrada de datos:

  • Un campo de texto que sirve para recoger la URL correspondiente al HTML que el usuario desea convertir a XML, y
  • Una casilla de verificación que indica que guardaremos el XML generado en la primera invocación y que simplemente se devolverá el XML en los siguientes pedidos de generación de fuentes. Esto resulta adecuado cuando la página HTML es estática y cambia en muy pocas ocasiones.

Ahora que hemos visto qué es lo que se va a generar, resultará mucho más sencillo comprender el archivo JSP.

Listado 5. tidyhtmlUrl.jsp
<%@page import="sample.mashupcenter.tidyhtml.Html2XmlUrlViewBean"%>
<%
    Html2XmlUrlViewBean htViewBean = new Html2XmlUrlViewBean();
    htViewBean = (Html2XmlUrlViewBean) htViewBean.getViewBeanFromRequest(request);
ResourceBundle i18n = ResourceBundle.getBundle(htViewBean.getI18NProperties(),
                                               request.getLocale());

    String objectId = htViewBean.getEntry().getObjectId();
String id = com.ibm.mashuphub.helper.PluginHelper.getClientId(
                                            htViewBean.getPluginId(), objectId);
%>

<br/>

<label for='htmlurl'><%=i18n.getString("form.htmlurl.label") %></label>
<div  class="rightCol">
   <input type='text'
          id='<%=id%>_htmlurl'
          name='<%= Html2XmlUrlViewBean.PARAM_HTMLURL %>'
          value='<%= htViewBean.getHtmlUrl() %>'
          maxlength='256' style='width=600px;' />
</div>

<div   class="rightCol">
   <input type='checkbox'
          id='<%=id%>_snapshot'
          name='<%= Html2XmlUrlViewBean.PARAM_SNAPSHOT %>'
          value='y'
          <%= "y".equals(htViewBean.getSnapshot()) ? "checked" : "" %>  />
   <%= i18n.getString("form.snapshot.label") %>
</div>

Ignorando el enunciado de importación, el propósito de estos primeros dos enunciados es recuperar el ViewBean asociado al JSP. Difiere levemente del modo en que los JSPs normalmente recuperan su Java bean asociado, es decir, a partir del objeto de pedido. Hay dos elementos de entrada de datos para HTML (correspondientes a los dos elementos de entrada de datos del formulario) que son del tipo texto y casilla de verificación, respectivamente. Observe que utilizamos las constantes PARAM_HTMLURL y PARAM_SNAPSHOT de la clase Html2XmlUrlViewBean para denominar a ambos elementos de ingreso de datos. Estas denominaciones aparecerán como nombres en la cadena de consultas URL que se envíe cuando se oprima el botón Next. El uso de constantes de cadena es la mejor manera de asegurar que exista una correspondencia exacta con lo que espera el servidor. Por último, inicializamos estos elementos de entrada de datos utilizando el valor potencial previo recuperado por el método renderEditor.


Método DisplayHtmlPage

Mencioné en una sección anterior que el método displayHtmlPage de la clase Html2XMLEditorPlugin será el utilizado para dar servicio a la próxima página de edición. El método displayHtmlPage no se hereda de la clase base BaseEditorPlugin y toma dos parámetros del tipo RequestData y Entry. Un EditorPlugin puede ingresar cualquier cantidad de métodos públicos con la misma firma. Todos estos métodos podrán ser invocados por el cliente que está en el buscador mediante una llamada AJAX.

Listado 6. Método displayHtmlPage
public  ViewBean  displayHtmlPage(RequestData rdata, Entry entry)
{
    ResourceBundle i18n = ResourceBundle.getBundle(I18N_RESFILE,rdata.getLocale());
    String pluginId = this.getId();

    // do not use "url" since the latter got intercepted in RequestData.init();
    String  sHtmlUrl  = rdata.getParameter( Html2XmlUrlViewBean.PARAM_HTMLURL );
    String  snapshot  = rdata.getParameter( Html2XmlUrlViewBean.PARAM_SNAPSHOT );
    log.debug("snapshot,sHtml=" + snapshot + "," + sHtmlUrl );

    Html2XmlContentViewBean htViewBean = new Html2XmlContentViewBean();
    htViewBean.setEntry(entry);
    htViewBean.setHtmlUrl( sHtmlUrl );
    htViewBean.setSnapshot( snapshot );

    FormViewBean form = new FormViewBean();
    form.setSuffix( htViewBean.getSuffix() );
    form.addComponent( htViewBean );
form.setOnsubmit(PluginHelper.getClientMe(pluginId,
                                          entry.getObjectId())+".submit();");
    form.setEntry(entry); // must be set, used to init JS plugin object

    FrameViewBean frame = new FrameViewBean();
    frame.addComponent(form);
    frame.setLabel(entry.getTitle());
    frame.setTitle(i18n.getString("frame.tabtitle"));
    frame.setEntry(entry);
    frame.setHelpPath( HELPPATH );

    JSONAJAXResponseViewBean ajaxViewBean = new JSONAJAXResponseViewBean();
    ajaxViewBean.setMethod(JSONAJAXResponseViewBean.METHOD_SHOW_EDITOR);
    ajaxViewBean.setCode( JSONAJAXResponseViewBean.PAGE_CONTENT );
    ajaxViewBean.addComponent(frame);
    return ajaxViewBean;
}

El propósito de este método es presentar una segunda página de edición para que los usuarios verifiquen el contenido HTML recuperado. Como consecuencia, el tipo de la devolución deberá ser ViewBean. La lógica dentro del método displayHtmlPage es similar a la del método renderEditor que discutiéramos anteriormente, a excepción de tres diferencias notables:

  • En lugar de recuperar los valores de configuración anteriormente ingresados de la instancia Entry, recuperamos lo que el usuario ingresó durante la sesión de edición al llamar al método getParameter de RequestData. Estos parámetros corresponden a los elementos de entrada de datos del formulario JSP enviado mediante una llamada AJAX al servidor.
  • Cada una de las páginas requiere un ViewBean diferente. Este método produce una instancia de Html2XmlContentViewBean. Al igual que antes, debe quedar ajustada dentro de una cadena FormViewBean, FrameViewBean. Además, todavía debemos ajustar el FrameViewBean dentro de una instancia JSONAJAXResponseViewBean. Esta última se produjo automáticamente en el método renderEditor pero debe realizarse explícitamente en este lugar.
  • Debido a que proporcionaremos nuestro propio JavaScript, mostramos una pequeña variación en el JavaScript que pasara al método setOnsubmit. En lugar de llamar directamente a invokeServer, llamaremos al método enviar del JavaScript asociado.

Un detalle adicional que vale la pena mencionar es la llamada a la instancia de registros estática para registrar los parámetros especificados por el usuario con el fin de colaborar en la determinación de problemas.


Html2XmlContentViewBean y el JSP asociado

El Html2XmlContentViewBean es bastante simple y básicamente sólo devuelve una ruta JSP y un sufijo diferentes del Html2XmlUrlViewBean que vimos anteriormente. Se lo puede examinar descargando el paquete adjunto por lo que no haremos demasiado hincapié en él. La página de edición que se genera también es simple, y consiste en un área para visualizar el HTML recuperado. La siguiente imagen de pantalla muestra un ángulo del área de visualización:

Figura 3. Vista previa de la página de contenidos HTML
Vista previa de la página de contenidos HTML

A continuación examinaremos el archivo JSP asociado tidyhtmlContent.jsp. Para generar un área de visualización, usted podrá observar que el JSP asociado incluye simplemente un único elemento div en la parte inferior del archivo JSP. Debido a que más tarde utilizaremos el atributo id, éste es un buen momento para ocuparnos de su construcción.

El atributo id debe ser único entre todos los elementos del HTML que se encuentran dentro de una ventana del buscador. Usando el id, el API proporcionado por el buscador puede recuperar los elementos HTML como objetos DOM en JavaScript, lo cual permite la manipulación dinámica. Debido a la posibilidad de que un usuario cuente con múltiples instancias de edición en un complemento dado abiertas al mismo tiempo, los elementos HTML de la plantilla JSP se producirán en múltiples ocasiones. Para asegurar que las id de estos elementos sean únicas, llamamos al método PluginHelper's getClientId para que recupere la id de instancia única de fuente y la agregue a la id.

Listado 7. tidyhtmlContent.jsp
<%@page import="sample.mashupcenter.tidyhtml.Html2XmlContentViewBean"%>
<%
    Html2XmlContentViewBean htViewBean = new Html2XmlContentViewBean();
    htViewBean = (Html2XmlContentViewBean) htViewBean.getViewBeanFromRequest(request);
ResourceBundle i18n = ResourceBundle.getBundle(htViewBean.getI18NProperties(),
                                               request.getLocale());

    String objectId = htViewBean.getEntry().getObjectId();
String id = com.ibm.mashuphub.helper.PluginHelper.getClientId(
                                             htViewBean.getPluginId(), objectId);
String me = com.ibm.mashuphub.helper.PluginHelper.getClientMe(
                                             htViewBean.getPluginId(), objectId);

    String   snapshot    = "\"" + htViewBean.getSnapshot() + "\"";
    String   htmlUrl     = htViewBean.getHtmlUrl();
    htmlUrl     = ( htmlUrl == null ?  "\"\""   :  "\"" + htmlUrl + "\"" ); 
%>
<script type="text/javascript">

    dojo.registerModulePath("plugins.tidyhtml" ,
                 "../../../../client/plugins/sample.mashupcenter.tidyhtml/script");
    dojo.require("plugins.tidyhtml.PreviewHtml");

    new plugins.tidyhtml.PreviewHtml(
               <%= me %>.plugin_id,
               <%= me %>.entry_id,
               <%= me %>.workflow);

    <%=me%>.init( <%= htmlUrl %> , <%= snapshot %> );
    <%=me%>.onLoadEditor();

</script>

<div id='<%=id%>_htmlContent' style='width:100%;
     overflow:auto; border: 2px  solid #000000;'>
</div>

Un nuevo aspecto de este JSP es la inclusión de JavaScript a medida para que sea ejecutado del lado del cliente. El marco de generación de fuentes del IBM Mashup Center utiliza el paquete Dojo AJAX. Ver la sección Recursos, donde se encuentra el vínculo a la documentación sobre Dojo. Utilizaremos el paquete Dojo AJAX en nuestro JavaScript a medida. La mayor parte del JavaScript a medida se encuentra en una clase Dojo llamada "plugins.tidyhtml.PreviewHtml".

Para poder utilizarla, debemos importarla mediante una llamada a la función dojo.require. La función registerModulePath de Dojo se utiliza para decirle a Dojo cómo ubicar las clases desde el plugins.tidyhtml del “módulo”. Observe que la ruta especificada se relaciona con el lugar donde se ubica el paquete Dojo y por lo tanto necesita de la referencia hacia atrás "../../../..". La lógica de inicialización anterior se genera en línea y queda encerrada en una etiqueta del script. Además, el JavaScript en línea crea una instancia de la clase PreviewHtml y llama a sus métodos init y onLoadEditor. La sección siguiente examina en más detalle la clase PreviewHtml.


Clase PreviewHtml Dojo

La clase PreviewHtml Dojo se hereda de la clase hub.managers.InvokePlugin que es parte del marco de generación de fuentes del lado del cliente. La clase InvokePlugin se describe en más detalle en la Sección 6.3.2 de la Application Programming Interface Reference, Versión 1.0. Los métodos de importancia de la clase PreviewHtml Dojo son onLoadEditor y populateContent.

Listado 8. Clase PreviewHtml Dojo
onLoadEditor: function()
{
    this.id = this.getEditorId();
    this.htmlContentNode = dojo.byId( this.id + '_htmlContent' );

    this.populateContent();
},

populateContent: function( )
{
    console.log( "populateContent called" );

    var baseUrl = hub.urls.getAjaxUrl( this.plugin_id,this.entry_id, 'getHtmlContent');
    var htmlurl = baseUrl + "?htmlurl="   + escape( this.htmlUrl   );
    if ( this.htmlContentInternalNode )
        this.htmlContentNode.removeChild( this.htmlContentInternalNode );
    this.htmlContentInternalNode = document.createElement( 'iframe' );
    this.htmlContentInternalNode.setAttribute( "src", htmlurl ); 
    this.htmlContentInternalNode.setAttribute( "width", "100%" );
    this.htmlContentInternalNode.setAttribute( "height", "400px" );  
    this.htmlContentNode.appendChild( this.htmlContentInternalNode );
},

La función populateContent es llamada por onLoadEditor durante el tiempo de carga de la página. Crea un de manera dinámica con el fin de visualizar el HTNL recuperado que ubica el efecto de toda hoja de estilo o script incluidos, evitando que afecten la apariencia de otras páginas. El iframe creado dinámicamente queda agregado al div estático creado por el JSP. Para recuperar el nodo DOM correspondiente al área de visualización, utilizamos la id única del elemento div generado mediante el agregado de la id de la instancia única de fuente a un sufijo común.

Del lado del servidor, utilizamos un método sobre la clase PluginHelper para obtener la id de la instancia única de fuente. Del lado del buscador, llamamos a la función getEditorId del elemento primario de la clase PreviewHtml Dojo, es decir, hub.managers.InvokePlugin. Para recuperar los contenidos HTML, aprovecharemos el atributo “src” del Iframe. El iframe automáticamente recuperará y mostrará los contenidos señalados por el atributo src durante la inicialización. Configuraremos el atributo src para que invoque al método getHtmlContent del complemento de edición. Observe el modo en que creamos la URL llamando a la función getAjaxUrl y agregando el resultado a la cadena "getHtmlContent".


Método AJAX para recuperar HTML

Mencioné en una sección anterior que se puede invocar a cualquier método público con RequestData y Entry como parámetros utilizando una llamada a AJAX. En especial, se puede llamar al método getHtmlContent con la clase PreviewHtml Dojo para que devuelva HTML a partir de la suministrada por el usuario. Debido a que la verdadera recuperación de HTML es común a la generación de fuentes y será tratada más adelante, en este momento no presentaré ningún fragmento de código. Lo único que quiero señalar es el tipo de devolución del método. En el ejemplo anterior, el método displayHtmlPage de AJAX devuelve un ViewBean. Los métodos de AJAX en general pueden devolver cualquier objeto, devolviendo también su valor toString. Ver Sección 6.3.2 de la Application Programming Interface Reference, Versión 1.0.


Nuestro último método de edición: saveFeedEntry

saveFeedEntry es otro método público de Html2XmlEditorPlugin invocado a través de AJAX para manejar el paso final del proceso de edición, guardando lo que ha ingresado el usuario. Es similar a los métodos de guardado de otros complementos. Lo novedoso es el manejo de "recursos". Los recursos difieren de los atributos en su tamaño y su tipo. Los recursos pueden ser binarios y pueden tener un tamaño de hasta un gigabyte. Por el contrario, los atributos se limitan a cadenas de 10MB de tamaño. El límite de tamaño de los atributos debería ser suficiente para sus contenidos, pero a fines ilustrativos, guardaremos el contenido HTML como un recurso. Cuando se active la opción snapshot, el generador sólo recuperará una vez el contenido HTML de la url especificada. El contenido HTML se convierte luego a XML y se guarda. Todos los pedidos posteriores de generación de fuentes serán cumplidas a partir del XML guardado. Para manejar el caso en que el usuario desee otra snapshot debido a un posible cambio en el sitio, será conveniente eliminar la copia guardad en el momento en que se edite la fuente. El fragmento de código muestra cómo hacerlo en un proceso de dos pasos: recuperar el recurso por su nombre, y luego llamar al método deleteResource en el objeto devuelto.

Listado 9. Método saveFeedEntry
try {
    entry.generateURL(rdata.getBaseUrl(), this.getId() );
    entry.addAttribute(Html2XmlUrlViewBean.PARAM_HTMLURL, sHtmlUrl , this.getId() );
    entry.addAttribute(Html2XmlUrlViewBean.PARAM_SNAPSHOT , snapshot , this.getId() );
    // after every edit, cleanup any previously cached snapshot
    Resource  oldRes = entry.getResource( Html2XmlConstants.CACHED_XHTML );
    if ( oldRes != null )
        oldRes.deleteResource();
    } catch (HubException ex) {
        log.error("Error adding entry attribute.",ex);
}

Finalmente hemos terminado con el Editor, y pasaremos al Generador.


GeneratorPlugin

La clase Html2XmlGeneratorPlugin amplía el BaseGeneratorPlugin y debe implementar el método abstracto generateFeed. No debe sorprender que los parámetros de entrada de los tipos RequestData y Entry sean idénticos a lo que pasa a los métodos EditorPlugin llamados por el marco de generación de fuentes. Para generar la fuente, se deberán en primer lugar recuperar los atributos que contengan la información de configuración guardada durante el proceso de edición. Esto se hace llamando al método getAttribute de Entry.

Listado 10. Método generateFeed
public FeedContent generateFeed(RequestData rdata, Entry entry) {
    String  sHtmlUrl  = entry.getAttribute(Html2XmlUrlViewBean.PARAM_HTMLURL );
    String  snapshot  = entry.getAttribute(Html2XmlUrlViewBean.PARAM_SNAPSHOT );

Debido a que este complemento no cuenta con soporte de parametrización, no debemos recuperar los parámetros provistos en tiempo de ejecución. Podemos devolver el contenido XML guardado o recuperar el contenido HTML y convertirlo a XML mediante JTidy. La lógica ilustra cómo se crean los recursos y resulta relativamente directa.

Listado 11. Cuerpo de generateFeed
String result = "Html might have changed.  Table not found.";

Resource  oldRes = entry.getResource( Html2XmlConstants.CACHED_XHTML );
if ( "y".equals( snapshot ) &&  oldRes != null  ) {
   log.warn( "returning cached, snapshot=" + snapshot );
   return new FeedContent(oldRes.loadResource(), entry.getLifeTime());
}
            
String sHtml = getXhtml( sHtmlUrl );
if ( sHtml.length() > 0 ) {
    result = sHtml;
    if ( "y".equals( snapshot ) ) {
        try {
            Resource prepared = new Resource();
            prepared.setObjectid( entry.getObjectId() );
            prepared.setMimetype( "text/xml; charset=utf-8" );
            prepared.setFilename( Html2XmlConstants.CACHED_XHTML );
            prepared.uploadResource( sHtml.getBytes( "utf-8" ) );
       } catch (HubException e) {
            log.error(e);
       }
   }
}
return new FeedContent( result.getBytes( "utf-8" ), entry.getLifeTime());

Los archivos de fuentes java incluyen más detalles sobre cómo los datos de entrada HTML se convierten en XML. Sólo mencionaré dos puntos clave. Para que el XML de salida pueda ser utilizado por el editor del mashup de fuentes, quitamos toda declaración DOCTYPE. Además, la lógica de generación realiza la suposición simplificadora de que el elemento HTML de entrada es en UTF-8 ay necesita ser ampliado para soportar otros lenguajes.


Implementación

La totalidad del proyecto Eclipse con todos sus archivos fuente se encuentra disponible en un archivo ZIP en la zona de descargas. Además, para facilitar la prueba del complemento, también se proporciona el archivo ZIP del complemento (sample.mashupcenter.tidyhtml.zip). Para instalar el complemento, siga los pasos que figuran a continuación:

  1. Descargue Tidy.jar desde el vínculo que aparece en la sección recursos.
  2. Luego que quitar los archivos de clases del paquete org.w3c.dom, agregue Tidy.jar al archivo zip del complemento que se encuentra en el directorio WEB-INF/lib.
  3. Coloque el archivo zip del complemento en el directorio <WebApplication>/WEB-INF/plugins.
  4. Apague y reinicie el servidor.

Conclusión

Hemos atravesado la construcción de un complemento más complicado que involucra múltiples páginas de edición, JavaScript a medida y el guardado de recursos. Ahora, usted cuenta con los conceptos básicos para comenzar a ampliar las capacidades de generación de fuentes del IBM Mashup Center. En un artículo posterior, discutiremos temas más avanzados tales como seguridad y parametrización.


Descargar

DescripciónNombretamaño
Samples for this articleDownload.zip325KB

Recursos

Aprender

Obtener los productos y tecnologías

Comentar

Comentarios

developerWorks: Ingrese

Los campos obligatorios están marcados con un asterisco (*).


¿Necesita un IBM ID?
¿Olvidó su IBM ID?


¿Olvidó su Password?
Cambie su Password

Al hacer clic en Enviar, usted está de acuerdo con los términos y condiciones de developerWorks.

 


La primera vez que inicie sesión en developerWorks, se creará un perfil para usted. La información en su propio perfil (nombre, país/región y nombre de la empresa) se muestra al público y acompañará a cualquier contenido que publique, a menos que opte por la opción de ocultar el nombre de su empresa. Puede actualizar su cuenta de IBM en cualquier momento.

Toda la información enviada es segura.

Elija su nombre para mostrar



La primera vez que inicia sesión en developerWorks se crea un perfil para usted, teniendo que elegir un nombre para mostrar en el mismo. Este nombre acompañará el contenido que usted publique en developerWorks.

Por favor elija un nombre de 3 - 31 caracteres. Su nombre de usuario debe ser único en la comunidad developerWorks y debe ser distinto a su dirección de email por motivos de privacidad.

Los campos obligatorios están marcados con un asterisco (*).

(Por favor elija un nombre de 3 - 31 caracteres.)

Al hacer clic en Enviar, usted está de acuerdo con los términos y condiciones de developerWorks.

 


Toda la información enviada es segura.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=90
Zone=Information mgmt, Lotus
ArticleID=427806
ArticleTitle=Complemento de IBM Mashup Center para convertir documentos HTML a XML
publish-date=09112009