404 lines
16 KiB
C#
404 lines
16 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Text;
|
|
using System.Linq;
|
|
using NDbfReaderEx; // Cambiar referencia de librería
|
|
|
|
namespace S7Explorer.Parsers
|
|
{
|
|
public class DbfParser
|
|
{
|
|
// Cache de codificación para reducir instanciación repetida
|
|
private static readonly Encoding Windows1252 = Encoding.GetEncoding(1252);
|
|
private static readonly Encoding Utf8 = Encoding.UTF8;
|
|
|
|
// Lee campos específicos de un archivo DBF, puede leer por lotes si se especifica
|
|
public static List<Dictionary<string, string>> ReadDbfFile(
|
|
string filePath,
|
|
IEnumerable<string> fieldNames,
|
|
int maxRecords = int.MaxValue,
|
|
int startRecord = 0)
|
|
{
|
|
var result = new List<Dictionary<string, string>>();
|
|
|
|
if (!File.Exists(filePath))
|
|
throw new FileNotFoundException($"No se encontró el archivo DBF: {filePath}");
|
|
|
|
try
|
|
{
|
|
// Usar nuestra función mejorada para abrir el archivo
|
|
using (var table = OpenDbfTableSafe(filePath))
|
|
{
|
|
int estimatedSize = Math.Min(1000, maxRecords);
|
|
result = new List<Dictionary<string, string>>(estimatedSize);
|
|
|
|
// Filtrar campos memo para evitar errores
|
|
var safeFields = new List<string>();
|
|
foreach (var fieldName in fieldNames)
|
|
{
|
|
// Verificar si el campo es memo
|
|
var column = table.columns.FirstOrDefault(c => c.name.Equals(fieldName, StringComparison.OrdinalIgnoreCase));
|
|
if (column != null && column.dbfType != NDbfReaderEx.NativeColumnType.Memo)
|
|
{
|
|
safeFields.Add(fieldName);
|
|
}
|
|
else if (column == null)
|
|
{
|
|
// Si el campo no existe, lo incluimos para que sea manejado por el código posterior
|
|
safeFields.Add(fieldName);
|
|
}
|
|
// Campos memo se omiten intencionalmente para evitar errores
|
|
}
|
|
|
|
for (int i = startRecord; i < startRecord + maxRecords && i < table.recCount; i++)
|
|
{
|
|
var row = table.GetRow(i);
|
|
var record = new Dictionary<string, string>(safeFields.Count, StringComparer.OrdinalIgnoreCase);
|
|
|
|
foreach (var fieldName in safeFields)
|
|
{
|
|
try
|
|
{
|
|
if (row.IsNull(fieldName))
|
|
{
|
|
record[fieldName] = string.Empty;
|
|
continue;
|
|
}
|
|
|
|
object value = row.GetValue(fieldName);
|
|
|
|
if (value is byte[] byteValue)
|
|
{
|
|
record[fieldName] = Windows1252.GetString(byteValue);
|
|
}
|
|
else if (value is DateTime dateValue)
|
|
{
|
|
record[fieldName] = dateValue.ToString("yyyy-MM-dd HH:mm:ss");
|
|
}
|
|
else
|
|
{
|
|
record[fieldName] = value?.ToString() ?? string.Empty;
|
|
}
|
|
}
|
|
catch (Exception)
|
|
{
|
|
// Si hay un error al leer este campo, simplemente ponemos un valor vacío
|
|
record[fieldName] = string.Empty;
|
|
}
|
|
}
|
|
|
|
result.Add(record);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
throw new Exception($"Error al leer el archivo DBF {filePath}: {ex.Message}", ex);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// Nuevo método para lectura por lotes usando las capacidades mejoradas de NDbfReaderEx
|
|
public static IEnumerable<List<Dictionary<string, string>>> ReadDbfFileInBatches(
|
|
string filePath,
|
|
IEnumerable<string> fieldNames,
|
|
int batchSize = 1000)
|
|
{
|
|
if (!File.Exists(filePath))
|
|
throw new FileNotFoundException($"No se encontró el archivo DBF: {filePath}");
|
|
|
|
using (var table = OpenDbfTableSafe(filePath))
|
|
{
|
|
var fieldsArray = new List<string>(fieldNames).ToArray();
|
|
int totalRecords = table.recCount;
|
|
|
|
for (int startIndex = 0; startIndex < totalRecords; startIndex += batchSize)
|
|
{
|
|
var batch = new List<Dictionary<string, string>>(Math.Min(batchSize, totalRecords - startIndex));
|
|
|
|
int endIndex = Math.Min(startIndex + batchSize, totalRecords);
|
|
|
|
for (int i = startIndex; i < endIndex; i++)
|
|
{
|
|
var row = table.GetRow(i);
|
|
var record = new Dictionary<string, string>(fieldsArray.Length, StringComparer.OrdinalIgnoreCase);
|
|
|
|
foreach (var fieldName in fieldsArray)
|
|
{
|
|
if (row.IsNull(fieldName))
|
|
{
|
|
record[fieldName] = string.Empty;
|
|
continue;
|
|
}
|
|
|
|
object value = row.GetValue(fieldName);
|
|
|
|
if (value is byte[] byteValue)
|
|
{
|
|
record[fieldName] = Windows1252.GetString(byteValue);
|
|
}
|
|
else if (value is DateTime dateValue)
|
|
{
|
|
record[fieldName] = dateValue.ToString("yyyy-MM-dd HH:mm:ss");
|
|
}
|
|
else
|
|
{
|
|
record[fieldName] = value?.ToString() ?? string.Empty;
|
|
}
|
|
}
|
|
|
|
batch.Add(record);
|
|
}
|
|
|
|
yield return batch;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Convierte un string que representa un número a un entero opcional
|
|
public static int? StringToInt(string value)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(value))
|
|
return null;
|
|
|
|
// Usar TryParse directamente si es posible
|
|
if (int.TryParse(value, out int directResult))
|
|
return directResult;
|
|
|
|
// Si no, intentar extraer la parte numérica
|
|
string numericPart = string.Empty;
|
|
bool foundDigit = false;
|
|
|
|
foreach (char c in value)
|
|
{
|
|
if (char.IsDigit(c))
|
|
{
|
|
numericPart += c;
|
|
foundDigit = true;
|
|
}
|
|
else if (foundDigit)
|
|
{
|
|
// Si ya encontramos dígitos y ahora encontramos otro carácter, terminamos
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (int.TryParse(numericPart, out int result))
|
|
return result;
|
|
|
|
return null;
|
|
}
|
|
|
|
// Optimizado convertidor de codificación
|
|
public static string ConvertCP1252ToUtf8(string input)
|
|
{
|
|
if (string.IsNullOrEmpty(input))
|
|
return string.Empty;
|
|
|
|
try
|
|
{
|
|
// Optimización: Si solo contiene caracteres ASCII, no necesitamos conversión
|
|
bool needsConversion = false;
|
|
for (int i = 0; i < input.Length; i++)
|
|
{
|
|
if (input[i] > 127)
|
|
{
|
|
needsConversion = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!needsConversion)
|
|
return input;
|
|
|
|
// Solo realizar conversión para texto con caracteres no-ASCII
|
|
byte[] bytes = Windows1252.GetBytes(input);
|
|
return Utf8.GetString(bytes);
|
|
}
|
|
catch
|
|
{
|
|
return input;
|
|
}
|
|
}
|
|
|
|
// Los demás métodos se mantienen igual
|
|
// Busca un archivo DBF en varias ubicaciones posibles basadas en el patrón de archivos STEP7
|
|
public static string FindDbfFile(string basePath, string relativePath)
|
|
{
|
|
// Ruta directa
|
|
string path = Path.Combine(basePath, relativePath);
|
|
if (File.Exists(path))
|
|
return path;
|
|
|
|
// Comprobar si se puede encontrar en un directorio padre
|
|
string parentPath = Directory.GetParent(basePath)?.FullName;
|
|
if (!string.IsNullOrEmpty(parentPath))
|
|
{
|
|
path = Path.Combine(parentPath, relativePath);
|
|
if (File.Exists(path))
|
|
return path;
|
|
}
|
|
|
|
// Obtener solo el nombre del archivo sin ruta
|
|
string fileName = Path.GetFileName(relativePath);
|
|
|
|
// Búsqueda en profundidad pero con límite para evitar que sea demasiado lenta
|
|
return FindFileWithDepthLimit(basePath, fileName, 3);
|
|
}
|
|
|
|
// Método de búsqueda con límite de profundidad
|
|
private static string FindFileWithDepthLimit(string directory, string fileName, int maxDepth)
|
|
{
|
|
if (maxDepth <= 0)
|
|
return null;
|
|
|
|
try
|
|
{
|
|
// Buscar en este directorio
|
|
string filePath = Path.Combine(directory, fileName);
|
|
if (File.Exists(filePath))
|
|
return filePath;
|
|
|
|
// Buscar en subdirectorios con profundidad reducida
|
|
foreach (var subdir in Directory.GetDirectories(directory))
|
|
{
|
|
string result = FindFileWithDepthLimit(subdir, fileName, maxDepth - 1);
|
|
if (result != null)
|
|
return result;
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Ignorar errores de acceso a directorios
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// Agregar al archivo DbfParser.cs
|
|
public static Dictionary<string, string> ExploreDbfStructure(string filePath)
|
|
{
|
|
var result = new Dictionary<string, string>();
|
|
|
|
try
|
|
{
|
|
using (var table = OpenDbfTableSafe(filePath))
|
|
{
|
|
foreach (var column in table.columns)
|
|
{
|
|
result[column.name.ToUpperInvariant()] = column.name;
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"Error explorando estructura: {ex.Message}");
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public static void LogColumnInfo(string filePath, Action<string> logMethod, int maxRows = 3)
|
|
{
|
|
try
|
|
{
|
|
using (var table = OpenDbfTableSafe(filePath))
|
|
{
|
|
logMethod($"Archivo: {filePath}");
|
|
logMethod($"Número de columnas: {table.columns.Count}");
|
|
|
|
// Mostrar información de columnas usando las propiedades correctas
|
|
logMethod("Columnas:");
|
|
foreach (var column in table.columns)
|
|
{
|
|
logMethod($" Nombre: {column.name}, Tipo DBF: {column.dbfType}, Tipo .NET: {column.type}");
|
|
logMethod($" Tamaño: {column.size}, Decimales: {column.dec}, Ancho: {column.displayWidth}");
|
|
logMethod($" Alineación: {(column.leftSideDisplay ? "Izquierda" : "Derecha")}");
|
|
logMethod(" -----");
|
|
}
|
|
|
|
// Mostrar algunos valores de muestra
|
|
logMethod("\nValores de muestra:");
|
|
for (int i = 0; i < Math.Min(maxRows, table.recCount); i++)
|
|
{
|
|
var row = table.GetRow(i);
|
|
logMethod($"Registro {i}:");
|
|
|
|
foreach (var column in table.columns)
|
|
{
|
|
try
|
|
{
|
|
object value = row.GetValue(column.name);
|
|
string valueStr = value != null ? value.ToString() : "null";
|
|
|
|
// Limitar longitud para no saturar el log
|
|
if (valueStr.Length > 100)
|
|
valueStr = valueStr.Substring(0, 97) + "...";
|
|
|
|
logMethod($" {column.name}: {valueStr}");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logMethod($" {column.name}: ERROR - {ex.Message}");
|
|
}
|
|
}
|
|
logMethod(string.Empty);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logMethod($"Error explorando columnas: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
public static DbfTable OpenDbfTableSafe(string filePath)
|
|
{
|
|
try
|
|
{
|
|
// Primera opción: abrir normalmente sin campos memo
|
|
return DbfTable.Open(filePath, null, false);
|
|
}
|
|
catch (Exception ex) when (ex.Message.Contains("encoding"))
|
|
{
|
|
// Problemas de codificación, probar con codificaciones específicas
|
|
try { return DbfTable.Open(filePath, Encoding.GetEncoding(1252), false); }
|
|
catch
|
|
{
|
|
try { return DbfTable.Open(filePath, Encoding.GetEncoding(850), false); }
|
|
catch
|
|
{
|
|
try { return DbfTable.Open(filePath, Encoding.GetEncoding(437), false); }
|
|
catch { return DbfTable.Open(filePath, Encoding.Default, false); }
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex) when (ex.Message.Contains("ReadMemoBytes") || ex.Message.Contains("block number"))
|
|
{
|
|
// Problemas específicos con campos memo - intentar con diferentes tipos de DBF
|
|
try { return DbfTable.Open(filePath, Encoding.GetEncoding(1252), false, StrictHeader.none, DbfTableType.DBF_Ver3); }
|
|
catch
|
|
{
|
|
try { return DbfTable.Open(filePath, Encoding.GetEncoding(1252), false, StrictHeader.none, DbfTableType.DBF_Ver3_dBase); }
|
|
catch
|
|
{
|
|
try { return DbfTable.Open(filePath, Encoding.GetEncoding(1252), false, StrictHeader.none, DbfTableType.DBF_Ver3_Clipper); }
|
|
catch
|
|
{
|
|
try { return DbfTable.Open(filePath, Encoding.GetEncoding(1252), false, StrictHeader.none, DbfTableType.DBF_Ver4); }
|
|
catch { return DbfTable.Open(filePath, Encoding.Default, false, StrictHeader.none, DbfTableType.DBF_Ver3); }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Exception)
|
|
{
|
|
// Último recurso: intentar abrir con configuraciones extremadamente permisivas
|
|
return DbfTable.Open(filePath, Encoding.Default, false, StrictHeader.none);
|
|
}
|
|
}
|
|
|
|
}
|
|
} |