#include "hbclass.ch" PROCEDURE Main() LOCAL cBase := hb_DirBase() + "xlsx_test_" LOCAL cOne := cBase + "one.xlsx" LOCAL cAll := cBase + "all.xlsx" LOCAL cLarge := cBase + "large.xlsx" LOCAL cTitle := cBase + "title.xlsx" LOCAL cCanceled := cBase + "canceled.xlsx" LOCAL hIR := { => } LOCAL hLargeIR := { => } LOCAL aRows := {} LOCAL aLargeRows := Array( 20000 ) LOCAL oXLSX LOCAL n hb_HCaseMatch( hIR, .T. ) AAdd( aRows, { "000123", "001-A", "12345678901234567890", 12, 12.34, -8.5, SToD( "20260804" ), hb_CToT( "20260804123456" ), .T., NIL, "", "Línea 1" + Chr( 10 ) + "Línea 2", "áéíóú ñ º ª €" } ) AAdd( aRows, { "000124", "002-B", "99999999999999999999", 0, 0.25, -1, SToD( "" ), hb_CToT( "20260805010203" ), .F., NIL, "", "Texto", "Unicode ✓" } ) hIR[ "aDataTables" ] := { ; MakeTable( "detail", "Detalle:/?*[] nombre de hoja extraordinariamente largo", aRows ), ; MakeTable( "detail2", "Detalle:/?*[] nombre de hoja extraordinariamente largo", { { "000999", "009-Z", "111", 1, 2.5, -2, SToD( "20260805" ), hb_CToT( "20260805112233" ), .T., NIL, "", "Otra", "Segunda" } } ) } oXLSX := TExportXLSX():New():Create() oXLSX:lOverwrite := .T. Assert( oXLSX:ExportTable( hIR, "detail", cOne ), "exporta una tabla: " + oXLSX:GetLastError() ) Assert( File( cOne ) .AND. IsZip( cOne ), "una tabla genera un paquete ZIP/XLSX real" ) Assert( oXLSX:ExportAllTables( hIR, cAll ), "exporta varias tablas: " + oXLSX:GetLastError() ) Assert( File( cAll ) .AND. IsZip( cAll ), "varias tablas generan un paquete ZIP/XLSX real" ) oXLSX:lIncludeTitle := .T. oXLSX:cTitle := "Informe de prueba" Assert( oXLSX:ExportTable( hIR, "detail", cTitle ), "exporta título general" ) oXLSX:lIncludeTitle := .F. FOR n := 1 TO Len( aLargeRows ) aLargeRows[ n ] := { StrZero( n, 8 ), "R" + LTrim( Str( n ) ), "90000000000000000000", n, n / 3, -n, SToD( "20260804" ), hb_CToT( "20260804123456" ), ( n % 2 ) == 0, NIL, "", "Registro", "Prueba" } NEXT hLargeIR[ "aDataTables" ] := { MakeTable( "large", "20.000 registros", aLargeRows ) } Assert( oXLSX:ExportAllTables( hLargeIR, cLarge ), "exporta 20.000 registros: " + oXLSX:GetLastError() ) Assert( File( cLarge ) .AND. IsZip( cLarge ), "20.000 registros generan XLSX" ) oXLSX:SetProgressCallback( {| nRecord | nRecord < 25 } ) Assert( ! oXLSX:ExportAllTables( hLargeIR, cCanceled ), "permite cancelar" ) Assert( oXLSX:WasCanceled(), "informa cancelacion" ) Assert( ! File( cCanceled ), "elimina el archivo incompleto" ) oXLSX:SetProgressCallback( NIL ) Assert( ! oXLSX:ExportAllTables( { "aDataTables" => {} }, cBase + "empty.xlsx" ), "rechaza informes sin tablas" ) oXLSX:End() oXLSX := NIL ? "XLSX_TEST_ONE=" + cOne ? "XLSX_TEST_ALL=" + cAll ? "XLSX_TEST_LARGE=" + cLarge ? "OK: TExportXLSX" RETURN FUNCTION MakeTable( cId, cName, aRows ) LOCAL hTable := { => } hTable[ "cId" ] := cId hTable[ "cName" ] := cName hTable[ "cEncoding" ] := "UTF-8" hTable[ "nRecordCount" ] := Len( aRows ) hTable[ "aColumns" ] := { ; MakeColumn( "codigo", "Código", "C" ), ; MakeColumn( "codigo2", "Código mixto", "C" ), ; MakeColumn( "identificador", "Identificador largo", "C" ), ; MakeColumn( "entero", "Entero", "N" ), ; MakeColumn( "decimal", "Decimal", "N" ), ; MakeColumn( "negativo", "Negativo", "N" ), ; MakeColumn( "fecha", "Fecha", "D" ), ; MakeColumn( "momento", "Fecha y hora", "T" ), ; MakeColumn( "logico", "Lógico", "L" ), ; MakeColumn( "nulo", "Nulo", "U" ), ; MakeColumn( "vacio", "Cadena vacía", "C" ), ; MakeColumn( "multilinea", "Texto con salto", "C" ), ; MakeColumn( "unicode", "Descripción artículo nº (€)", "C" ) } hTable[ "aRows" ] := aRows RETURN hTable FUNCTION MakeColumn( cField, cTitle, cType ) RETURN { "cId" => cField, "cField" => cField, "cTitle" => cTitle, "cType" => cType } FUNCTION IsZip( cFileName ) LOCAL hFile := FOpen( cFileName ) LOCAL cMagic := Space( 4 ) LOCAL lOk := hFile >= 0 .AND. FRead( hFile, @cMagic, 4 ) == 4 .AND. Left( cMagic, 2 ) == "PK" IF hFile >= 0; FClose( hFile ); ENDIF RETURN lOk PROCEDURE Assert( lCondition, cMessage ) IF ! lCondition ? "ERROR: " + cMessage ErrorLevel( 1 ) QUIT ENDIF RETURN