Reports have always been the part of a legacy migration that the tooling leaves behind. Forms, modules and data-access code get transformed automatically, while reports are handled separately, often by hand. When we ran a batch of legacy Crystal Reports through a standard IDE import, about 90% came out wrong and fixing each one by hand took several hours. With the pipeline below, final review dropped to about ten minutes per report. This article explains why reports got left behind and then lays out the pipeline. It covers Crystal Reports stored in VB6 designer files to DevExpress Reporting on Blazor, and it extends to Delphi.
The root cause is the source format. In VB6, reports often live in .dsr files, which are ActiveX designer files. A .dsr can contain a Crystal report, a Data Report, or some other designer. The extension says very little about what is inside, so a rule-based transformer has no stable grammar to target. The practical decision was to migrate the application code and deal with reports per project, once a real inventory showed up.
Generative AI changes this. Decisions that used to require fixed heuristics, such as how to map an element with no direct equivalent or how to correct a layout that came through wrong, can now be made case by case. That is what lets the pipeline below go from one report technology to another, not just from an old version of Crystal to a newer one.
Before choosing a target, identify the structural model of the source reports. Crystal Reports uses a banded model. A report is built from bands (report header, page header, group sections, footers) and any number of detail sections, plus subreports for nested data.
Targets that share this model make conversion much easier:
The differences that remain between banded engines are mostly at the edges. These include how the report connects to its data source, how functions and expressions are written, and how subreports are wired in. The mapper has to handle these explicitly.
Validate the target with a small sample. Don't decide based on documentation alone. In one experiment, we converted five representative Crystal reports to both candidate targets. DevExpress handled four of the five cleanly, while Telerik handled two, and the manual cleanup on the Telerik output was much larger. A five-report test is cheap and gives you evidence about which target's model is closest to your source.
For legacy Crystal (old .rpt files tied to the ActiveX runtime), the supported upgrade path is to install Crystal Reports for .NET developer tooling, open the old report in Visual Studio, and save it. In practice, format inconsistencies and errors are common. In one case with about twenty legacy reports, none converted cleanly, and all of them were rebuilt by hand.
The same pattern shows up when importing .rpt files into DevExpress through Visual Studio. In a batch we ran manually, about 90% of the reports came out wrong. Typical defects included:
Treat a built-in importer as one step in a pipeline, not as the migration.
The first half of the pipeline is fully deterministic: scriptable, repeatable, and with no model in the loop at runtime. AI can help you write this code faster, but nothing here needs probabilistic decisions.
In our reference project, the inventory was about 200 Crystal-based reports, 177 of which made up the processed set. They did not arrive as .rpt files; they arrived as VB6 designer files (.dsr and .dsx). The chain looked like this:
These steps ended up as one tool with multiple stages, not separate utilities. That keeps the whole chain re-runnable from source to target whenever a mapping rule changes.
Without AI, this is roughly where a migration would have stopped. You get every report in the target format, but with the same defects as a manual import, now arriving in bulk. When a deterministic import gets something wrong, the only fallback is to open the file and fix it by hand. At several hours per report, that doesn't scale to hundreds of reports.
The second half of the pipeline handles what deterministic rules can't. Its core idea is that a human observation about one report becomes a correction applied to the whole corpus.
The review application loads every converted report and renders it next to the original. Reports are filled with generated test data from a database, so layout problems show up even without production data.
Each report has a status:
The reviewer goes through the queue and attaches a short note to a specific report, for example:
Each note is written against one report, but it describes a defect class, not a single fix. That is what makes it reusable.
When around ten notes have built up, they go to the AI model as a batch, with an instruction like: verify these notes and fix the issues across all reports. The model does two things:
The rule-lines case shows this well. The deterministic import had dropped rule lines from almost every report. One note, plus an instruction to restore the lines so they match the originals, was enough. A single run across the corpus put them back everywhere, and reports that had been broken in the same way came back correct without anyone looking at them individually.
The loop repeats: review, write notes, run a batch correction, re-render. Each pass moves more reports from Review to Approved. Approval stays a human decision based on a side-by-side comparison with the original. Toward the end, most remaining issues are small positional fixes (an address field a few units off, for example), and the final review takes about ten minutes per report.
A few principles are what make this approach work, and they apply beyond Crystal and DevExpress.
Deterministic where the transformation is known, probabilistic where it isn't. Format normalization, batch import and project assembly are deterministic and re-runnable. The model is used only for the residual defects that rules can't anticipate.
The model generalizes defects; it doesn't translate reports. You never ask the model to convert a report from scratch. You ask it to take a specific, human-confirmed observation and apply it across hundreds of artifacts. This keeps its output bounded and checkable.
Keep humans in the verification step. Notes come from people looking at rendered output, and approval comes from a side-by-side check. The model speeds up correction but doesn't decide what "correct" means.
Iterate from the first day. A working first conversion should exist on day one. Everything after that is refinement of mapping rules and correction rounds. In practice, building the tool takes about a week and running and iterating it takes about another week, even for inventories of 130 to 180 reports. In a separate project, about 130 Crystal reports were moved to DevExpress Reporting with a very little manual cleanup.
Delphi reports are a different case on the source side. They aren't separate files; they are embedded in the Delphi form definitions and produced by Delphi's built-in reporting system. There are no .rpt files and no Crystal involved.
Structurally, though, they follow the same banded model: bands, detail sections, columns. In effect, they are a simplified version of the same components found in Crystal. So the work is limited to the front of the pipeline:
DevExpress is the natural target here because the mapping knowledge already exists from the Crystal work. Telerik is a workable alternative, with a somewhat more manual cleanup expected.
More broadly, once the target side and the correction loop exist, a new legacy report format is a parser problem. And as long as the source and target share a structural model, the choice of target matters less and less.
If you have VB6 and want to know what it takes to move it to something modern like .NET 10 or .NET 11 - give us a shoutout