The TextConverterBase class is a helper that simplifies building converters for line-oriented text log files. Instead of writing a state machine by hand, you register patterns for each line type and implement two callback methods.
When to use TextConverterBase
Use TextConverterBase when your log file has:
- A recognizable start-of-record marker line (e.g.
=== TEST START ===) - Key-value lines in a header section (e.g.
Serial Number: SN-001) - Step lines you can match with a regex (e.g.
PASS 3.3V Rail 3.31 V) - Optionally: multiple records back-to-back in the same file
For CSV, XML, JSON, or other structured formats, implement IReportConverter_v2 directly — see How to Create a File Converter for WATS.
NuGet package
<ItemGroup>
<PackageReference Include="Virinco.WATS.ClientAPI" Version="7.*">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<!-- TextConverterBase ships in StandardConverters -->
<PackageReference Include="Virinco.WATS.StandardConverters" Version="7.*" />
</ItemGroup>
How it works
- In the constructor, register "search fields" — each one is a pattern (exact prefix or regex) that matches a specific line type.
- The base class reads the file line by line.
- For each line that matches a registered field, it calls
ProcessMatchedLine(). - For lines that don't match, it calls
ProcessNonMatchLine(). - When the file ends,
ProcessMatchedLine()is called one final time withmatch == null. Use this to submit the last UUT.
Minimal example
Suppose your log file looks like this:
=== TEST START ===
Serial Number: SN-001234
Part Number: PCBA-4711
Start Time: 2026-05-15 08:30:00
--- STEPS ---
PASS 3.3V Rail 3.31 V [3.25 .. 3.35]
PASS 5V Rail 5.02 V [4.90 .. 5.10]
FAIL 12V Rail 11.2 V [11.5 .. 12.5]
=== TEST END: FAIL ===
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using Virinco.WATS.Integration.TextConverter;
using Virinco.WATS.Interface;
namespace WatsExamples.TxtConverter
{
public class TxtReportConverter : TextConverterBase
{
// State tracked per UUT
private UUTReport? _uut;
private SequenceCall? _root;
private bool _passed;
private string _opType = "Final Test";
public TxtReportConverter(IDictionary<string, string> args) : base(args)
{
// ── Start marker: transitions state from Unknown → InHeader ────────
searchFields.AddExactField(
"StartTest", ReportReadState.Unknown,
"=== TEST START ===", null, typeof(string),
isStateTransition: true, newState: ReportReadState.InHeader);
// ── Header key-value fields ───────────────────────────────────────
// UUTField.* constants tell the base class to set the UUT property automatically.
searchFields.AddExactField(UUTField.SerialNumber, ReportReadState.InHeader, "Serial Number:", null, typeof(string));
searchFields.AddExactField(UUTField.PartNumber, ReportReadState.InHeader, "Part Number:", null, typeof(string));
searchFields.AddExactField(UUTField.StartDateTime, ReportReadState.InHeader, "Start Time:", "yyyy-MM-dd HH:mm:ss", typeof(DateTime));
// ── Transition to step-reading state ──────────────────────────────
searchFields.AddExactField(
"StepsMarker", ReportReadState.InHeader,
"--- STEPS ---", null, typeof(string),
isStateTransition: true, newState: ReportReadState.InTest);
// ── End marker ────────────────────────────────────────────────────
searchFields.AddExactField("EndTest", ReportReadState.InTest,
"=== TEST END:", null, typeof(string));
// ── Step lines ────────────────────────────────────────────────────
// Regex captures: result, name, value, units, low limit, high limit
const string stepPattern =
@"^(?<Result>PASS|FAIL)\s+" +
@"(?<Name>[^\s].+?)\s{2,}" + // name followed by 2+ spaces
@"(?:(?<Value>-?\d+\.?\d*)\s+(?<Units>[A-Za-z°%]+)" +
@"(?:\s+\[(?<Low>-?\d+\.?\d*)\s*\.\.\s*(?<High>-?\d+\.?\d*)\])?)?$";
var stepField = searchFields.AddRegExpField(
"StepLine", ReportReadState.InTest,
stepPattern, null, typeof(string));
stepField.AddSubField("Result", typeof(string));
stepField.AddSubField("Name", typeof(string));
stepField.AddSubField("Value", typeof(double));
stepField.AddSubField("Units", typeof(string));
stepField.AddSubField("Low", typeof(double));
stepField.AddSubField("High", typeof(double));
}
public override void ProcessMatchedLine(
TDM api, ReportMatchLine? match, ref UUTReport? currentUut)
{
if (match == null)
{
// End of file — submit the last UUT if there is one
SubmitCurrent(api);
return;
}
switch (match.matchField.fieldName)
{
case "StartTest":
SubmitCurrent(api); // submit any previous UUT
_uut = api.CreateUUTReport("", "", "A", "", _opType, "", "");
_root = _uut.GetRootSequenceCall();
_passed = true;
currentUut = _uut;
break;
case "EndTest":
if (_uut != null)
_uut.Status = _passed ? UUTStatusType.Passed : UUTStatusType.Failed;
break;
case "StepLine":
if (_uut == null || _root == null) break;
bool stepPassed = match.GetSubFieldValue("Result")?.ToString() == "PASS";
string name = match.GetSubFieldValue("Name")?.ToString() ?? "";
var status = stepPassed ? StepStatusType.Passed : StepStatusType.Failed;
if (!stepPassed) _passed = false;
// If we have a numeric value, create a NumericLimitStep
if (match.GetSubFieldValue("Value") is double val)
{
var ns = _root.AddNumericLimitStep(name);
string units = match.GetSubFieldValue("Units")?.ToString() ?? "";
if (match.GetSubFieldValue("Low") is double low
&& match.GetSubFieldValue("High") is double high)
ns.AddTest(val, CompOperatorType.GELE, low, high, units, status);
else
ns.AddTest(val, units, status);
}
else
{
var ps = _root.AddPassFailStep(name);
ps.Status = status;
}
break;
default:
// UUTField.* fields are handled automatically by the base class
currentUut = _uut;
break;
}
}
public override void ProcessNonMatchLine(
TDM api, string line, ref UUTReport? currentUut)
{
// Called for every line that matches no registered field.
// Ignore or log as needed.
}
private void SubmitCurrent(TDM api)
{
if (_uut != null)
{
api.Submit(_uut);
_uut = null;
_root = null;
}
}
}
}
UUTField constants
Using UUTField.* constants as the field name tells the base class to set the matching UUT property automatically when the line is matched:
| Constant | UUT property set |
|---|---|
UUTField.SerialNumber | uut.SerialNumber |
UUTField.PartNumber | uut.PartNumber |
UUTField.PartRevisionNumber | uut.PartRevisionNumber |
UUTField.Operator | uut.OperatorName |
UUTField.StartDateTime | uut.StartDateTime |
UUTField.ExecutionTime | uut.ExecutionTime |
Key differences from IReportConverter_v2
- TextConverterBase calls
api.Submit(uut)internally. Do not return a report fromImportReport— the method returnsnull. - The constructor must accept
IDictionary<string, string>and pass it tobase(args). - You implement
ProcessMatchedLineandProcessNonMatchLineinstead ofImportReport.
Working example
See FileConverterExample_TXT in the TheWATSCompany GitHub repository for a complete, annotated implementation with sample data.
Comments
0 comments
Please sign in to leave a comment.