feat: add export history auto-fill, fix filename prefixes, persist records for all doc types
- Add database-first lookup for equipment/drawing number auto-fill when reopening previously exported files - Remove prefix prepending for named parts (only use prefix for PT## BOM items) - Create ExportRecord/BomItem/CutTemplate chains for Part and Assembly exports, not just Drawings - Add auto-incrementing item numbers across drawing numbers - Add content hashing (SHA256) for DXF and PDF versioning with stash/archive pattern - Add EF Core initial migration for ExportDxfDb Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
30
ExportDXF/Forms/MainForm.Designer.cs
generated
30
ExportDXF/Forms/MainForm.Designer.cs
generated
@@ -54,10 +54,10 @@ namespace ExportDXF.Forms
|
||||
// runButton
|
||||
//
|
||||
runButton.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right;
|
||||
runButton.Location = new System.Drawing.Point(514, 13);
|
||||
runButton.Location = new System.Drawing.Point(821, 13);
|
||||
runButton.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
|
||||
runButton.Name = "runButton";
|
||||
runButton.Size = new System.Drawing.Size(100, 30);
|
||||
runButton.Size = new System.Drawing.Size(100, 55);
|
||||
runButton.TabIndex = 11;
|
||||
runButton.Text = "Start";
|
||||
runButton.UseVisualStyleBackColor = true;
|
||||
@@ -91,7 +91,7 @@ namespace ExportDXF.Forms
|
||||
mainTabControl.Name = "mainTabControl";
|
||||
mainTabControl.Padding = new System.Drawing.Point(20, 5);
|
||||
mainTabControl.SelectedIndex = 0;
|
||||
mainTabControl.Size = new System.Drawing.Size(599, 330);
|
||||
mainTabControl.Size = new System.Drawing.Size(910, 441);
|
||||
mainTabControl.TabIndex = 12;
|
||||
//
|
||||
// logEventsTab
|
||||
@@ -100,7 +100,7 @@ namespace ExportDXF.Forms
|
||||
logEventsTab.Location = new System.Drawing.Point(4, 30);
|
||||
logEventsTab.Name = "logEventsTab";
|
||||
logEventsTab.Padding = new System.Windows.Forms.Padding(3);
|
||||
logEventsTab.Size = new System.Drawing.Size(591, 296);
|
||||
logEventsTab.Size = new System.Drawing.Size(902, 407);
|
||||
logEventsTab.TabIndex = 0;
|
||||
logEventsTab.Text = "Log Events";
|
||||
logEventsTab.UseVisualStyleBackColor = true;
|
||||
@@ -112,7 +112,7 @@ namespace ExportDXF.Forms
|
||||
logEventsDataGrid.GridColor = System.Drawing.Color.WhiteSmoke;
|
||||
logEventsDataGrid.Location = new System.Drawing.Point(6, 6);
|
||||
logEventsDataGrid.Name = "logEventsDataGrid";
|
||||
logEventsDataGrid.Size = new System.Drawing.Size(579, 282);
|
||||
logEventsDataGrid.Size = new System.Drawing.Size(890, 391);
|
||||
logEventsDataGrid.TabIndex = 0;
|
||||
//
|
||||
// bomTab
|
||||
@@ -121,7 +121,7 @@ namespace ExportDXF.Forms
|
||||
bomTab.Location = new System.Drawing.Point(4, 30);
|
||||
bomTab.Name = "bomTab";
|
||||
bomTab.Padding = new System.Windows.Forms.Padding(3);
|
||||
bomTab.Size = new System.Drawing.Size(982, 549);
|
||||
bomTab.Size = new System.Drawing.Size(902, 407);
|
||||
bomTab.TabIndex = 1;
|
||||
bomTab.Text = "Bill Of Materials";
|
||||
bomTab.UseVisualStyleBackColor = true;
|
||||
@@ -133,30 +133,30 @@ namespace ExportDXF.Forms
|
||||
bomDataGrid.GridColor = System.Drawing.Color.WhiteSmoke;
|
||||
bomDataGrid.Location = new System.Drawing.Point(6, 6);
|
||||
bomDataGrid.Name = "bomDataGrid";
|
||||
bomDataGrid.Size = new System.Drawing.Size(970, 535);
|
||||
bomDataGrid.Size = new System.Drawing.Size(1281, 644);
|
||||
bomDataGrid.TabIndex = 1;
|
||||
//
|
||||
//
|
||||
// cutTemplatesTab
|
||||
//
|
||||
//
|
||||
cutTemplatesTab.Controls.Add(cutTemplatesDataGrid);
|
||||
cutTemplatesTab.Location = new System.Drawing.Point(4, 30);
|
||||
cutTemplatesTab.Name = "cutTemplatesTab";
|
||||
cutTemplatesTab.Padding = new System.Windows.Forms.Padding(3);
|
||||
cutTemplatesTab.Size = new System.Drawing.Size(982, 549);
|
||||
cutTemplatesTab.Size = new System.Drawing.Size(902, 407);
|
||||
cutTemplatesTab.TabIndex = 2;
|
||||
cutTemplatesTab.Text = "Cut Templates";
|
||||
cutTemplatesTab.UseVisualStyleBackColor = true;
|
||||
//
|
||||
//
|
||||
// cutTemplatesDataGrid
|
||||
//
|
||||
//
|
||||
cutTemplatesDataGrid.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
|
||||
cutTemplatesDataGrid.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||
cutTemplatesDataGrid.GridColor = System.Drawing.Color.WhiteSmoke;
|
||||
cutTemplatesDataGrid.Location = new System.Drawing.Point(6, 6);
|
||||
cutTemplatesDataGrid.Name = "cutTemplatesDataGrid";
|
||||
cutTemplatesDataGrid.Size = new System.Drawing.Size(970, 535);
|
||||
cutTemplatesDataGrid.Size = new System.Drawing.Size(1281, 644);
|
||||
cutTemplatesDataGrid.TabIndex = 2;
|
||||
//
|
||||
//
|
||||
// equipmentBox
|
||||
//
|
||||
equipmentBox.FormattingEnabled = true;
|
||||
@@ -194,7 +194,7 @@ namespace ExportDXF.Forms
|
||||
// MainForm
|
||||
//
|
||||
AutoScaleMode = System.Windows.Forms.AutoScaleMode.None;
|
||||
ClientSize = new System.Drawing.Size(626, 416);
|
||||
ClientSize = new System.Drawing.Size(937, 527);
|
||||
Controls.Add(drawingNoBox);
|
||||
Controls.Add(equipmentBox);
|
||||
Controls.Add(mainTabControl);
|
||||
|
||||
@@ -390,6 +390,8 @@ namespace ExportDXF.Forms
|
||||
ActiveDocument = activeDoc,
|
||||
ViewFlipDecider = viewFlipDecider,
|
||||
FilePrefix = filePrefix,
|
||||
Equipment = equipment,
|
||||
DrawingNo = drawingNo,
|
||||
EquipmentId = null,
|
||||
CancellationToken = token,
|
||||
ProgressCallback = (msg, level, file) => LogMessage(msg, level, file),
|
||||
@@ -468,21 +470,58 @@ namespace ExportDXF.Forms
|
||||
var docTitle = activeDoc?.Title ?? "No Document Open";
|
||||
this.Text = $"ExportDXF - {docTitle}";
|
||||
|
||||
// Parse the file name and fill Equipment/Drawing dropdowns
|
||||
if (activeDoc != null)
|
||||
{
|
||||
var drawingInfo = DrawingInfo.Parse(activeDoc.Title);
|
||||
if (drawingInfo != null)
|
||||
{
|
||||
if (!equipmentBox.Items.Contains(drawingInfo.EquipmentNo))
|
||||
equipmentBox.Items.Add(drawingInfo.EquipmentNo);
|
||||
equipmentBox.Text = drawingInfo.EquipmentNo;
|
||||
if (activeDoc == null)
|
||||
return;
|
||||
|
||||
if (!drawingNoBox.Items.Contains(drawingInfo.DrawingNo))
|
||||
drawingNoBox.Items.Add(drawingInfo.DrawingNo);
|
||||
drawingNoBox.Text = drawingInfo.DrawingNo;
|
||||
// Try database first: look up the most recent export for this file path
|
||||
DrawingInfo drawingInfo = null;
|
||||
|
||||
if (!string.IsNullOrEmpty(activeDoc.FilePath))
|
||||
{
|
||||
drawingInfo = LookupDrawingInfoFromHistory(activeDoc.FilePath);
|
||||
}
|
||||
|
||||
// Fall back to parsing the document title
|
||||
if (drawingInfo == null)
|
||||
{
|
||||
drawingInfo = DrawingInfo.Parse(activeDoc.Title);
|
||||
}
|
||||
|
||||
if (drawingInfo != null)
|
||||
{
|
||||
if (!equipmentBox.Items.Contains(drawingInfo.EquipmentNo))
|
||||
equipmentBox.Items.Add(drawingInfo.EquipmentNo);
|
||||
equipmentBox.Text = drawingInfo.EquipmentNo;
|
||||
|
||||
if (!drawingNoBox.Items.Contains(drawingInfo.DrawingNo))
|
||||
drawingNoBox.Items.Add(drawingInfo.DrawingNo);
|
||||
drawingNoBox.Text = drawingInfo.DrawingNo;
|
||||
}
|
||||
}
|
||||
|
||||
private DrawingInfo LookupDrawingInfoFromHistory(string filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var db = _dbContextFactory())
|
||||
{
|
||||
var drawingNumber = db.ExportRecords
|
||||
.Where(r => r.SourceFilePath.ToLower() == filePath.ToLower()
|
||||
&& !string.IsNullOrEmpty(r.DrawingNumber))
|
||||
.OrderByDescending(r => r.Id)
|
||||
.Select(r => r.DrawingNumber)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (drawingNumber != null)
|
||||
return DrawingInfo.Parse(drawingNumber);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Failed to look up drawing info from history: {ex.Message}");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void LogMessage(string message, LogLevel level = LogLevel.Info, string file = null)
|
||||
|
||||
153
ExportDXF/Migrations/20260214044511_InitialCreate.Designer.cs
generated
Normal file
153
ExportDXF/Migrations/20260214044511_InitialCreate.Designer.cs
generated
Normal file
@@ -0,0 +1,153 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using ExportDXF.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ExportDXF.Migrations
|
||||
{
|
||||
[DbContext(typeof(ExportDxfDbContext))]
|
||||
[Migration("20260214044511_InitialCreate")]
|
||||
partial class InitialCreate
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "8.0.11")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("ExportDXF.Models.BomItem", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
|
||||
|
||||
b.Property<string>("ConfigurationName")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("ContentHash")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("CutTemplateName")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<double?>("DefaultBendRadius")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<string>("DxfFilePath")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<int>("ExportRecordId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ItemNo")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<double?>("KFactor")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<string>("Material")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("PartName")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.Property<string>("PartNo")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<int?>("Qty")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("SortOrder")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<double?>("Thickness")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<int?>("TotalQty")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("ExportRecordId");
|
||||
|
||||
b.ToTable("BomItems");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ExportDXF.Models.ExportRecord", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("DrawingNumber")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<DateTime>("ExportedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("ExportedBy")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("OutputFolder")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<string>("PdfContentHash")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("SourceFilePath")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("ExportRecords");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ExportDXF.Models.BomItem", b =>
|
||||
{
|
||||
b.HasOne("ExportDXF.Models.ExportRecord", "ExportRecord")
|
||||
.WithMany("BomItems")
|
||||
.HasForeignKey("ExportRecordId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ExportRecord");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ExportDXF.Models.ExportRecord", b =>
|
||||
{
|
||||
b.Navigation("BomItems");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
82
ExportDXF/Migrations/20260214044511_InitialCreate.cs
Normal file
82
ExportDXF/Migrations/20260214044511_InitialCreate.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ExportDXF.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class InitialCreate : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ExportRecords",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("SqlServer:Identity", "1, 1"),
|
||||
DrawingNumber = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
|
||||
SourceFilePath = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
|
||||
OutputFolder = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
|
||||
ExportedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||
ExportedBy = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
|
||||
PdfContentHash = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ExportRecords", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "BomItems",
|
||||
columns: table => new
|
||||
{
|
||||
ID = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("SqlServer:Identity", "1, 1"),
|
||||
ItemNo = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: true),
|
||||
PartNo = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
|
||||
SortOrder = table.Column<int>(type: "int", nullable: false),
|
||||
Qty = table.Column<int>(type: "int", nullable: true),
|
||||
TotalQty = table.Column<int>(type: "int", nullable: true),
|
||||
Description = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
|
||||
PartName = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true),
|
||||
ConfigurationName = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
|
||||
Material = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
|
||||
CutTemplateName = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
|
||||
DxfFilePath = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
ContentHash = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
|
||||
Thickness = table.Column<double>(type: "float", nullable: true),
|
||||
KFactor = table.Column<double>(type: "float", nullable: true),
|
||||
DefaultBendRadius = table.Column<double>(type: "float", nullable: true),
|
||||
ExportRecordId = table.Column<int>(type: "int", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_BomItems", x => x.ID);
|
||||
table.ForeignKey(
|
||||
name: "FK_BomItems_ExportRecords_ExportRecordId",
|
||||
column: x => x.ExportRecordId,
|
||||
principalTable: "ExportRecords",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_BomItems_ExportRecordId",
|
||||
table: "BomItems",
|
||||
column: "ExportRecordId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "BomItems");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ExportRecords");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,16 @@ namespace ExportDXF.Services
|
||||
/// </summary>
|
||||
public string FilePrefix { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Equipment number from the UI (e.g., "5028").
|
||||
/// </summary>
|
||||
public string Equipment { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Drawing number from the UI (e.g., "A02", "Misc").
|
||||
/// </summary>
|
||||
public string DrawingNo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Selected Equipment ID for API operations (optional).
|
||||
/// </summary>
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace ExportDXF.Models
|
||||
public string OutputFolder { get; set; }
|
||||
public DateTime ExportedAt { get; set; }
|
||||
public string ExportedBy { get; set; }
|
||||
public string PdfContentHash { get; set; }
|
||||
|
||||
public virtual ICollection<BomItem> BomItems { get; set; } = new List<BomItem>();
|
||||
}
|
||||
|
||||
@@ -61,5 +61,15 @@ namespace ExportDXF.Services
|
||||
/// The SolidWorks component reference.
|
||||
/// </summary>
|
||||
public Component2 Component { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// SHA256 content hash of the exported DXF (transient, not persisted).
|
||||
/// </summary>
|
||||
public string ContentHash { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Path to the stashed (backed-up) previous DXF file (transient, not persisted).
|
||||
/// </summary>
|
||||
public string StashedFilePath { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -39,9 +39,9 @@ namespace ExportDXF
|
||||
{
|
||||
var solidWorksService = new SolidWorksService();
|
||||
var bomExtractor = new BomExtractor();
|
||||
var partExporter = new PartExporter();
|
||||
var drawingExporter = new DrawingExporter();
|
||||
var fileExportService = new FileExportService(_outputFolder);
|
||||
var partExporter = new PartExporter(fileExportService);
|
||||
var drawingExporter = new DrawingExporter();
|
||||
|
||||
var exportService = new DxfExportService(
|
||||
solidWorksService,
|
||||
|
||||
@@ -64,7 +64,7 @@ namespace ExportDXF.Services
|
||||
var startTime = DateTime.Now;
|
||||
|
||||
var drawingNumber = ParseDrawingNumber(context);
|
||||
var outputFolder = _fileExportService.GetDrawingOutputFolder(drawingNumber);
|
||||
var outputFolder = _fileExportService.GetDrawingOutputFolder(context.Equipment, context.DrawingNo);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -73,11 +73,11 @@ namespace ExportDXF.Services
|
||||
switch (context.ActiveDocument.DocumentType)
|
||||
{
|
||||
case DocumentType.Part:
|
||||
ExportPart(context, outputFolder);
|
||||
ExportPart(context, outputFolder, drawingNumber);
|
||||
break;
|
||||
|
||||
case DocumentType.Assembly:
|
||||
ExportAssembly(context, outputFolder);
|
||||
ExportAssembly(context, outputFolder, drawingNumber);
|
||||
break;
|
||||
|
||||
case DocumentType.Drawing:
|
||||
@@ -101,7 +101,7 @@ namespace ExportDXF.Services
|
||||
|
||||
#region Export Methods by Document Type
|
||||
|
||||
private void ExportPart(ExportContext context, string outputFolder)
|
||||
private void ExportPart(ExportContext context, string outputFolder, string drawingNumber)
|
||||
{
|
||||
LogProgress(context, "Active document is a Part");
|
||||
|
||||
@@ -112,10 +112,52 @@ namespace ExportDXF.Services
|
||||
return;
|
||||
}
|
||||
|
||||
_partExporter.ExportSinglePart(part, outputFolder, context);
|
||||
var exportRecord = CreateExportRecord(context, drawingNumber, outputFolder);
|
||||
var item = _partExporter.ExportSinglePart(part, outputFolder, context);
|
||||
|
||||
if (item != null)
|
||||
{
|
||||
// Assign auto-incremented item number
|
||||
var nextItemNo = GetNextItemNumber(drawingNumber);
|
||||
item.ItemNo = nextItemNo;
|
||||
|
||||
var bomItem = new BomItem
|
||||
{
|
||||
ExportRecordId = exportRecord?.Id ?? 0,
|
||||
ItemNo = item.ItemNo,
|
||||
PartNo = item.FileName ?? item.PartName ?? "",
|
||||
SortOrder = 0,
|
||||
Qty = item.Quantity,
|
||||
TotalQty = item.Quantity,
|
||||
Description = item.Description ?? "",
|
||||
PartName = item.PartName ?? "",
|
||||
ConfigurationName = item.Configuration ?? "",
|
||||
Material = item.Material ?? ""
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(item.FileName))
|
||||
{
|
||||
var dxfPath = Path.Combine(outputFolder, item.FileName + ".dxf");
|
||||
bomItem.CutTemplate = new CutTemplate
|
||||
{
|
||||
DxfFilePath = dxfPath,
|
||||
ContentHash = item.ContentHash,
|
||||
Thickness = item.Thickness > 0 ? item.Thickness : null,
|
||||
KFactor = item.KFactor > 0 ? item.KFactor : null,
|
||||
DefaultBendRadius = item.BendRadius > 0 ? item.BendRadius : null
|
||||
};
|
||||
|
||||
HandleDxfVersioning(item, dxfPath, context);
|
||||
}
|
||||
|
||||
context.BomItemCallback?.Invoke(bomItem);
|
||||
|
||||
if (exportRecord != null)
|
||||
SaveBomItem(bomItem, context);
|
||||
}
|
||||
}
|
||||
|
||||
private void ExportAssembly(ExportContext context, string outputFolder)
|
||||
private void ExportAssembly(ExportContext context, string outputFolder, string drawingNumber)
|
||||
{
|
||||
LogProgress(context, "Active document is an Assembly");
|
||||
LogProgress(context, "Fetching components...");
|
||||
@@ -137,7 +179,20 @@ namespace ExportDXF.Services
|
||||
|
||||
LogProgress(context, $"Found {items.Count} item(s).");
|
||||
|
||||
ExportItems(items, outputFolder, context);
|
||||
var exportRecord = CreateExportRecord(context, drawingNumber, outputFolder);
|
||||
|
||||
// Auto-assign item numbers for items that don't have one
|
||||
var nextNum = int.Parse(GetNextItemNumber(drawingNumber));
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(item.ItemNo))
|
||||
{
|
||||
item.ItemNo = nextNum.ToString();
|
||||
nextNum++;
|
||||
}
|
||||
}
|
||||
|
||||
ExportItems(items, outputFolder, context, exportRecord?.Id);
|
||||
}
|
||||
|
||||
private void ExportDrawing(ExportContext context, string drawingNumber, string drawingOutputFolder)
|
||||
@@ -190,31 +245,24 @@ namespace ExportDXF.Services
|
||||
}
|
||||
|
||||
// Create export record in database
|
||||
ExportRecord exportRecord = null;
|
||||
try
|
||||
var exportRecord = CreateExportRecord(context, drawingNumber, drawingOutputFolder);
|
||||
|
||||
// Handle PDF versioning and update export record with hash
|
||||
if (exportRecord != null && savedPdfPath != null)
|
||||
{
|
||||
using (var db = _dbContextFactory())
|
||||
try
|
||||
{
|
||||
db.Database.Migrate();
|
||||
exportRecord = new ExportRecord
|
||||
{
|
||||
DrawingNumber = drawingNumber ?? context.ActiveDocument.Title,
|
||||
SourceFilePath = context.ActiveDocument.FilePath,
|
||||
OutputFolder = drawingOutputFolder,
|
||||
ExportedAt = DateTime.Now,
|
||||
ExportedBy = System.Environment.UserName
|
||||
};
|
||||
HandlePdfVersioning(savedPdfPath, exportRecord.DrawingNumber, exportRecord, context);
|
||||
|
||||
// Handle PDF versioning - compute hash and compare with previous
|
||||
if (savedPdfPath != null)
|
||||
// Archive or discard old PDF based on hash comparison
|
||||
if (pdfStashPath != null)
|
||||
{
|
||||
HandlePdfVersioning(savedPdfPath, exportRecord.DrawingNumber, exportRecord, context);
|
||||
|
||||
// Archive or discard old PDF based on hash comparison
|
||||
if (pdfStashPath != null)
|
||||
using (var db = _dbContextFactory())
|
||||
{
|
||||
var previousRecord = db.ExportRecords
|
||||
.Where(r => r.DrawingNumber == exportRecord.DrawingNumber && r.PdfContentHash != null)
|
||||
.Where(r => r.DrawingNumber == exportRecord.DrawingNumber
|
||||
&& r.PdfContentHash != null
|
||||
&& r.Id != exportRecord.Id)
|
||||
.OrderByDescending(r => r.Id)
|
||||
.FirstOrDefault();
|
||||
|
||||
@@ -229,16 +277,24 @@ namespace ExportDXF.Services
|
||||
}
|
||||
}
|
||||
|
||||
db.ExportRecords.Add(exportRecord);
|
||||
db.SaveChanges();
|
||||
LogProgress(context, $"Created export record (ID: {exportRecord.Id})", LogLevel.Info);
|
||||
// Update the record with the PDF hash
|
||||
using (var db = _dbContextFactory())
|
||||
{
|
||||
db.ExportRecords.Attach(exportRecord);
|
||||
db.Entry(exportRecord).Property(r => r.PdfContentHash).IsModified = true;
|
||||
db.SaveChanges();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_fileExportService.DiscardStash(pdfStashPath);
|
||||
LogProgress(context, $"PDF versioning error: {ex.Message}", LogLevel.Error);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
else if (pdfStashPath != null)
|
||||
{
|
||||
// Clean up stash on error
|
||||
// No export record - discard stash
|
||||
_fileExportService.DiscardStash(pdfStashPath);
|
||||
LogProgress(context, $"Database error creating export record: {ex.Message}", LogLevel.Error);
|
||||
}
|
||||
|
||||
// Export parts to DXF and save BOM items
|
||||
@@ -478,6 +534,86 @@ namespace ExportDXF.Services
|
||||
|
||||
#endregion
|
||||
|
||||
#region Database Helpers
|
||||
|
||||
private ExportRecord CreateExportRecord(ExportContext context, string drawingNumber, string outputFolder)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var db = _dbContextFactory())
|
||||
{
|
||||
db.Database.Migrate();
|
||||
var record = new ExportRecord
|
||||
{
|
||||
DrawingNumber = drawingNumber ?? context.ActiveDocument.Title,
|
||||
SourceFilePath = context.ActiveDocument.FilePath,
|
||||
OutputFolder = outputFolder,
|
||||
ExportedAt = DateTime.Now,
|
||||
ExportedBy = System.Environment.UserName
|
||||
};
|
||||
|
||||
db.ExportRecords.Add(record);
|
||||
db.SaveChanges();
|
||||
LogProgress(context, $"Created export record (ID: {record.Id})", LogLevel.Info);
|
||||
return record;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogProgress(context, $"Database error creating export record: {ex.Message}", LogLevel.Error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private string GetNextItemNumber(string drawingNumber)
|
||||
{
|
||||
if (string.IsNullOrEmpty(drawingNumber))
|
||||
return "1";
|
||||
|
||||
try
|
||||
{
|
||||
using (var db = _dbContextFactory())
|
||||
{
|
||||
var existingItems = db.ExportRecords
|
||||
.Where(r => r.DrawingNumber == drawingNumber)
|
||||
.SelectMany(r => r.BomItems)
|
||||
.Select(b => b.ItemNo)
|
||||
.ToList();
|
||||
|
||||
int maxNum = 0;
|
||||
foreach (var itemNo in existingItems)
|
||||
{
|
||||
if (int.TryParse(itemNo, out var num) && num > maxNum)
|
||||
maxNum = num;
|
||||
}
|
||||
|
||||
return (maxNum + 1).ToString();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "1";
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveBomItem(BomItem bomItem, ExportContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var db = _dbContextFactory())
|
||||
{
|
||||
db.BomItems.Add(bomItem);
|
||||
db.SaveChanges();
|
||||
}
|
||||
}
|
||||
catch (Exception dbEx)
|
||||
{
|
||||
LogProgress(context, $"Database error saving BOM item: {dbEx.Message}", LogLevel.Error);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
private string CreateTempWorkDir()
|
||||
@@ -489,7 +625,11 @@ namespace ExportDXF.Services
|
||||
|
||||
private string ParseDrawingNumber(ExportContext context)
|
||||
{
|
||||
// Prefer prefix (e.g., "5007 A02 PT"), fallback to active document title
|
||||
// Use explicit Equipment/DrawingNo from the UI when available
|
||||
if (!string.IsNullOrWhiteSpace(context?.Equipment) && !string.IsNullOrWhiteSpace(context?.DrawingNo))
|
||||
return $"{context.Equipment} {context.DrawingNo}";
|
||||
|
||||
// Fallback: parse from prefix or document title
|
||||
var candidate = context?.FilePrefix;
|
||||
var info = string.IsNullOrWhiteSpace(candidate) ? null : DrawingInfo.Parse(candidate);
|
||||
if (info == null)
|
||||
|
||||
@@ -6,9 +6,13 @@ namespace ExportDXF.Services
|
||||
public interface IFileExportService
|
||||
{
|
||||
string OutputFolder { get; }
|
||||
string GetDrawingOutputFolder(string equipment, string drawingNo);
|
||||
string SaveDxfFile(string sourcePath, string drawingNumber, string itemNo);
|
||||
string SavePdfFile(string sourcePath, string drawingNumber);
|
||||
string SavePdfFile(string sourcePath, string drawingNumber, string outputFolder = null);
|
||||
void EnsureOutputFolderExists();
|
||||
string StashFile(string filePath);
|
||||
void ArchiveFile(string stashPath, string originalPath);
|
||||
void DiscardStash(string stashPath);
|
||||
}
|
||||
|
||||
public class FileExportService : IFileExportService
|
||||
@@ -29,6 +33,18 @@ namespace ExportDXF.Services
|
||||
}
|
||||
}
|
||||
|
||||
public string GetDrawingOutputFolder(string equipment, string drawingNo)
|
||||
{
|
||||
if (string.IsNullOrEmpty(equipment) || string.IsNullOrEmpty(drawingNo))
|
||||
return OutputFolder;
|
||||
|
||||
var folder = Path.Combine(OutputFolder, equipment, drawingNo);
|
||||
if (!Directory.Exists(folder))
|
||||
Directory.CreateDirectory(folder);
|
||||
|
||||
return folder;
|
||||
}
|
||||
|
||||
public string SaveDxfFile(string sourcePath, string drawingNumber, string itemNo)
|
||||
{
|
||||
if (string.IsNullOrEmpty(sourcePath))
|
||||
@@ -49,16 +65,17 @@ namespace ExportDXF.Services
|
||||
return destPath;
|
||||
}
|
||||
|
||||
public string SavePdfFile(string sourcePath, string drawingNumber)
|
||||
public string SavePdfFile(string sourcePath, string drawingNumber, string outputFolder = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(sourcePath))
|
||||
throw new ArgumentNullException(nameof(sourcePath));
|
||||
|
||||
var folder = outputFolder ?? OutputFolder;
|
||||
var fileName = !string.IsNullOrEmpty(drawingNumber)
|
||||
? $"{drawingNumber}.pdf"
|
||||
: Path.GetFileName(sourcePath);
|
||||
|
||||
var destPath = Path.Combine(OutputFolder, fileName);
|
||||
var destPath = Path.Combine(folder, fileName);
|
||||
|
||||
// If source and dest are the same, skip copy
|
||||
if (!string.Equals(sourcePath, destPath, StringComparison.OrdinalIgnoreCase))
|
||||
@@ -68,5 +85,40 @@ namespace ExportDXF.Services
|
||||
|
||||
return destPath;
|
||||
}
|
||||
|
||||
public string StashFile(string filePath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath))
|
||||
return null;
|
||||
|
||||
var stashPath = filePath + ".bak";
|
||||
File.Move(filePath, stashPath, overwrite: true);
|
||||
return stashPath;
|
||||
}
|
||||
|
||||
public void ArchiveFile(string stashPath, string originalPath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(stashPath) || !File.Exists(stashPath))
|
||||
return;
|
||||
|
||||
var fileDir = Path.GetDirectoryName(originalPath) ?? OutputFolder;
|
||||
var archiveDir = Path.Combine(fileDir, "_archive");
|
||||
if (!Directory.Exists(archiveDir))
|
||||
Directory.CreateDirectory(archiveDir);
|
||||
|
||||
var originalName = Path.GetFileNameWithoutExtension(originalPath);
|
||||
var ext = Path.GetExtension(originalPath);
|
||||
var timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
|
||||
var archiveName = $"{originalName} [{timestamp}]{ext}";
|
||||
var archivePath = Path.Combine(archiveDir, archiveName);
|
||||
|
||||
File.Move(stashPath, archivePath, overwrite: true);
|
||||
}
|
||||
|
||||
public void DiscardStash(string stashPath)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(stashPath) && File.Exists(stashPath))
|
||||
File.Delete(stashPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,11 +15,12 @@ namespace ExportDXF.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Exports a single part document to DXF.
|
||||
/// Returns an Item with export metadata (filename, hash, sheet metal properties), or null if export failed.
|
||||
/// </summary>
|
||||
/// <param name="part">The part document to export.</param>
|
||||
/// <param name="saveDirectory">The directory where the DXF file will be saved.</param>
|
||||
/// <param name="context">The export context.</param>
|
||||
void ExportSinglePart(PartDoc part, string saveDirectory, ExportContext context);
|
||||
Item ExportSinglePart(PartDoc part, string saveDirectory, ExportContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Exports an item (component from BOM or assembly) to DXF.
|
||||
@@ -39,7 +40,7 @@ namespace ExportDXF.Services
|
||||
_fileExportService = fileExportService ?? throw new ArgumentNullException(nameof(fileExportService));
|
||||
}
|
||||
|
||||
public void ExportSinglePart(PartDoc part, string saveDirectory, ExportContext context)
|
||||
public Item ExportSinglePart(PartDoc part, string saveDirectory, ExportContext context)
|
||||
{
|
||||
if (part == null)
|
||||
throw new ArgumentNullException(nameof(part));
|
||||
@@ -59,9 +60,57 @@ namespace ExportDXF.Services
|
||||
var fileName = GetSinglePartFileName(model, context.FilePrefix);
|
||||
var savePath = Path.Combine(saveDirectory, fileName + ".dxf");
|
||||
|
||||
// Build result item with metadata
|
||||
var item = new Item
|
||||
{
|
||||
PartName = model.GetTitle()?.Replace(".SLDPRT", "") ?? "",
|
||||
Configuration = originalConfigName ?? "",
|
||||
Quantity = 1
|
||||
};
|
||||
|
||||
// Enrich with sheet metal properties and description
|
||||
var sheetMetalProps = SolidWorksHelper.GetSheetMetalProperties(model);
|
||||
if (sheetMetalProps != null)
|
||||
{
|
||||
item.Thickness = sheetMetalProps.Thickness;
|
||||
item.KFactor = sheetMetalProps.KFactor;
|
||||
item.BendRadius = sheetMetalProps.BendRadius;
|
||||
}
|
||||
|
||||
// Get description from custom properties
|
||||
var configPropMgr = model.Extension.CustomPropertyManager[originalConfigName];
|
||||
item.Description = configPropMgr?.Get("Description");
|
||||
if (string.IsNullOrEmpty(item.Description))
|
||||
{
|
||||
var docPropMgr = model.Extension.CustomPropertyManager[""];
|
||||
item.Description = docPropMgr?.Get("Description");
|
||||
}
|
||||
item.Description = TextHelper.RemoveXmlTags(item.Description);
|
||||
|
||||
// Get material
|
||||
item.Material = part.GetMaterialPropertyName2(originalConfigName, out _);
|
||||
|
||||
// Stash existing file before overwriting
|
||||
item.StashedFilePath = _fileExportService.StashFile(savePath);
|
||||
|
||||
context.GetOrCreateTemplateDrawing();
|
||||
|
||||
ExportPartToDxf(part, originalConfigName, savePath, context);
|
||||
if (ExportPartToDxf(part, originalConfigName, savePath, context))
|
||||
{
|
||||
item.FileName = Path.GetFileNameWithoutExtension(savePath);
|
||||
item.ContentHash = Utilities.ContentHasher.ComputeDxfContentHash(savePath);
|
||||
return item;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Export failed - restore stashed file
|
||||
if (item.StashedFilePath != null && File.Exists(item.StashedFilePath))
|
||||
{
|
||||
File.Move(item.StashedFilePath, savePath, overwrite: true);
|
||||
item.StashedFilePath = null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -290,18 +339,15 @@ namespace ExportDXF.Services
|
||||
var config = model.ConfigurationManager.ActiveConfiguration.Name;
|
||||
var isDefaultConfig = string.Equals(config, "default", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
var name = isDefaultConfig ? title : $"{title} [{config}]";
|
||||
|
||||
return PrependPrefix(name, prefix);
|
||||
return isDefaultConfig ? title : $"{title} [{config}]";
|
||||
}
|
||||
|
||||
private string GetItemFileName(Item item, string prefix)
|
||||
{
|
||||
prefix = prefix?.Replace("\"", "''") ?? string.Empty;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(item.ItemNo))
|
||||
return PrependPrefix(item.PartName, prefix);
|
||||
return item.PartName;
|
||||
|
||||
prefix = prefix?.Replace("\"", "''") ?? string.Empty;
|
||||
var num = item.ItemNo.PadLeft(2, '0');
|
||||
// Expected format: {DrawingNo} PT{ItemNo}
|
||||
return string.IsNullOrWhiteSpace(prefix)
|
||||
@@ -309,17 +355,6 @@ namespace ExportDXF.Services
|
||||
: $"{prefix} PT{num}";
|
||||
}
|
||||
|
||||
private string PrependPrefix(string name, string prefix)
|
||||
{
|
||||
if (string.IsNullOrEmpty(prefix))
|
||||
return name;
|
||||
|
||||
if (name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
|
||||
return name;
|
||||
|
||||
return prefix + name;
|
||||
}
|
||||
|
||||
private void LogExportFailure(Item item, ExportContext context)
|
||||
{
|
||||
var desc = item.Description?.ToLower() ?? string.Empty;
|
||||
|
||||
118
ExportDXF/Utilities/ContentHasher.cs
Normal file
118
ExportDXF/Utilities/ContentHasher.cs
Normal file
@@ -0,0 +1,118 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace ExportDXF.Utilities
|
||||
{
|
||||
public static class ContentHasher
|
||||
{
|
||||
/// <summary>
|
||||
/// Computes a SHA256 hash of DXF file content, skipping the HEADER section
|
||||
/// which contains timestamps that change on every save.
|
||||
/// </summary>
|
||||
public static string ComputeDxfContentHash(string filePath)
|
||||
{
|
||||
var text = File.ReadAllText(filePath);
|
||||
var contentStart = FindEndOfHeader(text);
|
||||
var content = contentStart >= 0 ? text.Substring(contentStart) : text;
|
||||
|
||||
using (var sha = SHA256.Create())
|
||||
{
|
||||
var bytes = sha.ComputeHash(Encoding.UTF8.GetBytes(content));
|
||||
return BitConverter.ToString(bytes).Replace("-", "").ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes a SHA256 hash of the entire file contents (for PDFs and other binary files).
|
||||
/// </summary>
|
||||
public static string ComputeFileHash(string filePath)
|
||||
{
|
||||
using (var sha = SHA256.Create())
|
||||
using (var stream = File.OpenRead(filePath))
|
||||
{
|
||||
var bytes = sha.ComputeHash(stream);
|
||||
return BitConverter.ToString(bytes).Replace("-", "").ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the position immediately after the HEADER section's ENDSEC marker.
|
||||
/// DXF HEADER format:
|
||||
/// 0\nSECTION\n2\nHEADER\n...variables...\n0\nENDSEC\n
|
||||
/// Returns -1 if no HEADER section is found.
|
||||
/// </summary>
|
||||
private static int FindEndOfHeader(string text)
|
||||
{
|
||||
// Find the HEADER section start
|
||||
var headerIndex = FindGroupCode(text, 0, "2", "HEADER");
|
||||
if (headerIndex < 0)
|
||||
return -1;
|
||||
|
||||
// Advance past the HEADER value line so pair scanning stays aligned
|
||||
var headerLineEnd = text.IndexOf('\n', headerIndex);
|
||||
if (headerLineEnd < 0)
|
||||
return -1;
|
||||
|
||||
// Find the ENDSEC that closes the HEADER section
|
||||
var pos = headerLineEnd + 1;
|
||||
while (pos < text.Length)
|
||||
{
|
||||
var endsecIndex = FindGroupCode(text, pos, "0", "ENDSEC");
|
||||
if (endsecIndex < 0)
|
||||
return -1;
|
||||
|
||||
// Move past the ENDSEC line
|
||||
var lineEnd = text.IndexOf('\n', endsecIndex);
|
||||
return lineEnd >= 0 ? lineEnd + 1 : text.Length;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds a DXF group code pair (code line followed by value line) starting from the given position.
|
||||
/// Returns the position of the value line, or -1 if not found.
|
||||
/// </summary>
|
||||
private static int FindGroupCode(string text, int startIndex, string groupCode, string value)
|
||||
{
|
||||
var pos = startIndex;
|
||||
while (pos < text.Length)
|
||||
{
|
||||
// Skip whitespace/newlines to find the group code
|
||||
while (pos < text.Length && (text[pos] == '\r' || text[pos] == '\n' || text[pos] == ' '))
|
||||
pos++;
|
||||
|
||||
if (pos >= text.Length)
|
||||
break;
|
||||
|
||||
// Read the group code line
|
||||
var codeLineEnd = text.IndexOf('\n', pos);
|
||||
if (codeLineEnd < 0)
|
||||
break;
|
||||
|
||||
var codeLine = text.Substring(pos, codeLineEnd - pos).Trim();
|
||||
|
||||
// Move to the value line
|
||||
var valueStart = codeLineEnd + 1;
|
||||
if (valueStart >= text.Length)
|
||||
break;
|
||||
|
||||
var valueLineEnd = text.IndexOf('\n', valueStart);
|
||||
if (valueLineEnd < 0)
|
||||
valueLineEnd = text.Length;
|
||||
|
||||
var valueLine = text.Substring(valueStart, valueLineEnd - valueStart).Trim();
|
||||
|
||||
if (codeLine == groupCode && string.Equals(valueLine, value, StringComparison.OrdinalIgnoreCase))
|
||||
return valueStart;
|
||||
|
||||
// Move to the next pair
|
||||
pos = valueLineEnd + 1;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user