miércoles, 27 de mayo de 2015

Leer XML de la web y grabar en base de datos

Para algunos trabajar con archivos XML puede ser confuso y engorroso, sin embargo Xojo nos facilita mucho el trabajo con su clase XMLDocument.

He preparado un pequeño paso a paso con un ejemplo que viene con Xojo, la versión que trabajo es la 2015r21 pero funciona con versiones anteriores.
  1. En la ventana "Project Chooser" clic en Examples, Communications/Internet/HTTP Example.xojo_binary_project.
  2. Cambiar la propiedad Super de EdidFieldPlus a TextArea.
  3. Crear un Botón con el caption toDB y en evento action adicionar el código.
  4. Correr en proyecto.
  5. Cambiar la URL a "http://www.w3schools.com/xml/cd_catalog.xml" oprimir "Go".
  6. Oprmir "toDB" y revisar la baseDatos "FromXML.sqlite" del escritorio.

Código para toDB.Action:

  Dim f As FolderItem= SpecialFolder.Desktop.Child("FromXML.sqlite")
  
  Dim db As New SQLiteDatabase
  db.DatabaseFile= f
  
  If f= Nil Or Not f.Exists Then
    If db.CreateDatabaseFile Then
      Dim sql As String= "CREATE TABLE cd_catalog (title TEXT, artist TEXT, year INT);"
      db.SQLExecute(sql)
      If db.Error Then
        MsgBox "DB Error: " + db.ErrorMessage
        Return
      End If
    Else
      MsgBox "The database couldn't be created. Error: " + db.ErrorMessage
      Return
    End If
  Else
    If Not db.Connect Then
      MsgBox "The database couldn't be opened. Error: " + db.ErrorMessage
      Return
    End If
  End If
  
  Dim xml As New XmlDocument(BodyField.Text)
  
  Dim ps As SQLitePreparedStatement= db.Prepare("INSERT INTO cd_catalog (title, artist, year) VALUES (?, ?, ?)")
  ps.BindType(0, SQLitePreparedStatement.SQLITE_TEXT)
  ps.BindType(1, SQLitePreparedStatement.SQLITE_TEXT)
  ps.BindType(2, SQLitePreparedStatement.SQLITE_INTEGER)
  
  db.SQLExecute("BEGIN TRANSACTION")
  
  For i As Integer= 0 To xml.DocumentElement.ChildCount- 1
    Dim sTitle, sArtist As String
    Dim iYear As Integer
    For j As Integer= 0 To xml.DocumentElement.Child(i).ChildCount- 1
      Dim node As XmlNode= xml.DocumentElement.Child(i).Child(j)
      Select Case node.Name.Uppercase
      Case "TITLE"
        sTitle= node.Child(0).Value
      Case "ARTIST"
        sArtist= node.Child(0).Value
      Case "YEAR"
        iYear= Val(node.Child(0).Value)
      End Select
    Next
    ps.SQLExecute(sTitle, sArtist, iYear)
  Next
  
  db.Commit


Puede ver video aquí.

Espero les sirva, chao, chao.

Imágenes en BaseDatos y Xojo

Cuando se intenta obtener imágenes en bases de datos como PostgreSQL, MySQL o MSSQL desde Xojo tenemos problemas, y es que no funciona la propiedad PictureValue debido a que son bases de datos diseñadas (generalmente) para acceder sobre red, es decir, los datos se tiene que "codificar" (encode) a texto y después enviarlo a la base de datos; por el contrario una base de datos como SQLite los datos son binarios.

Si ud. almacena las imágenes desde xojo, el siguiente código funciona en PostgreSQL, MySQL, MSSQL y ODBC:

Para almacenar:

  ' Image.png, db, la tabla tbl_images y el registro con id=1 deben existir.
  ' el campo image debe ser tipo bytea (postgres) o text

  Dim p As Picture= SpecialFolder.Desktop.Child("image.png")
  Dim mb As MemoryBlock= p.GetData(Picture.FormatPNG)
  Dim s As String= EncodeBase64(mb.StringValue(0, mb.Size))

  Dim sql As String= "UPDATE tbl_images SET image= '"+ s+ "' WHERE id=1"
  db.SQLExecute(sql)

  If db.Error Then
    System.Log(System.LogLevelError, "PostgreSQL(DB:"+ db.DatabaseName+ _
    ",U:"+ db.UserName+ "):"+ EndOfLine+ EndOfLine+ sql)
    Return
  End If

Para obtener:

  Dim rs As RecordSet= db.SQLSelect("SELECT * FROM tbl_images WHERE id=1")

  mb= DecodeBase64(DecodeHex(rs.Field("image").StringValue))
  p= Picture.FromData(mb)

  'Canvas1 debe existir
  If p<> Nil Then Canvas1.Backdrop= p


Si ud NO almacena las imágenes desde xojo, bueno, las cosas son un poco más difícil, para obtener imágenes desde bases de datos se tiene que hacer las operaciones inversas de las que cuales fueron almacenadas, por ejemplo en PosrgreSQL para almacenar imágenes en campos bytea puede usarse:

En PHP usando escape_byea:

<?php
  // Connectar a la base de datos
  $dbconn = pg_connect('dbname=foo');

  // Leer en un fichero binario
  $data = file_get_contents('image1.jpg');

  // Escapar el dato binario
  $escaped = pg_escape_bytea($data);

  // Insertarlo en la base de datos
  pg_query("INSERT INTO gallery (name, data) VALUES ('Pine trees', '{$escaped}')");?>
En PHP usando bin2hex:

<?php
  // Connect to the database
  $dbconn = pg_connect( 'dbname=foo' );
  
  // Read in a binary file
  $data = file_get_contents( 'image1.jpg' );
  
  // Escape the binary data
  $escaped = bin2hex( $data );
  
  // Insert it into the database
  pg_query( "INSERT INTO gallery (name, data) VALUES ('Pine trees', decode('{$escaped}' , 'hex'))" );

  // Get the bytea data
  $res = pg_query("SELECT encode(data, 'base64') AS data FROM gallery WHERE name='Pine trees'");  
  $raw = pg_fetch_result($res, 'data');
  
  // Convert to binary and send to the browser
  header('Content-type: image/jpeg');
  echo base64_decode($raw);
?>
En PHP usando lo_import:
<?php
 $uploaddir = '/home/postgres/';
 $uploadfile = $uploaddir . basename($_FILES['userfile']['name']);
 $name = $_POST['name'];

 if (move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile))
 {   // echo "File is valid, and was successfully uploaded.\n";
 }
 else   {   echo "File size greater than 300kb!\n\n";   }

 echo "'$name'\n";

 $conn = pg_pconnect("dbname=test user=postgres password=postgres");
 $query = "insert into image values ('$name', lo_import('$uploadfile'), 'now')";
 $result = pg_query($query);

 if($result)
 {
     echo "File is valid, and was successfully uploaded.\n";
     unlink($uploadfile);
 }
 else
 {
     echo "Filename already exists. Use another filename. Enter all the values.";
     unlink($uploadfile);
 }
 pg_close($conn);
 ?>

Y en otras bases de datos sucede algo similar, no hay un estandar.Para obtener se debe utilizar DecodeBase64 o DecodeHex (o los dos) para llevar el valor del campo a una variable MemoryBlock y utilizar Picture.FromData para convertirlo en imagen.