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> ReadDbfFile( string filePath, IEnumerable fieldNames, int maxRecords = int.MaxValue, int startRecord = 0) { var result = new List>(); 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>(estimatedSize); // Filtrar campos memo para evitar errores var safeFields = new List(); 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(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>> ReadDbfFileInBatches( string filePath, IEnumerable 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(fieldNames).ToArray(); int totalRecords = table.recCount; for (int startIndex = 0; startIndex < totalRecords; startIndex += batchSize) { var batch = new List>(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(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 ExploreDbfStructure(string filePath) { var result = new Dictionary(); 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 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); } } } }