tbf/TBF/Rig/Output/DataStorage/UniDataStorageWriter/UI/XmlPayloadViewerDlg.cs
Marek Frniak 054075a341 <Feat>: Add XML result generation with file and MSSQL output support, increase revision to 3.9.3145.100
Cause:

- ResultsWriter required a generic way to generate customer-specific XML result data from TBF measurement results.
- The customer reference XML contains example runtime values and repeated result structures, so it cannot be used directly as the generated output.
- TBF result variables must be explicitly mapped to destinations in the customer XML structure.
- The generated XML result data must support two output targets:
  - direct creation of an XML file,
  - delivery of the XML payload to a Microsoft SQL stored procedure.
- Preview generation must allow the XML structure and configured mappings to be verified without executing the production database write.
- Increase revision to 3.9.3145.100.

Solution:

1. Added XML reference analysis
   - Creates a clean base XML structure.
   - Extracts the repeating result prototype.
   - Prevents sample runtime values from the customer reference XML from leaking into generated results.
2. Added configurable TBF-to-XML result mapping
   - Allows explicit mapping of TBF result variables to customer XML destinations.
   - Supports one-time and repeating XML destinations.
   - Keeps the mapping independent of the semantic meaning of customer XML attribute names.
3. Added XML destination viewer and configurator
   - Shows the current mapping.
   - Highlights repeating destinations.
   - Identifies already used one-time destinations.
4. Added runtime XML result generation
   - Uses the configured TBF-to-XML mappings.
   - Uses measurement procedure result data.
   - Builds the output from the clean base XML and repeating XML prototype.
   - Generates repeated result records according to the executed measurement procedure.
5. Added simulation-based Preview request
   - Generates a complete XML payload using simulation values.
   - Allows XML structure and mapping verification before production execution.
   - Does not execute the production stored procedure.
6. Added direct XML file output support to UniDataStorageWriter
   - Supports File / .xml as a physical output target.
   - Creates the generated XML result file in the configured output directory.
7. Added Microsoft SQL stored-procedure XML output
   - Supports Microsoft SQL / StoredProcedure as a physical output target.
   - Passes the generated XML payload through the configured stored procedure parameter.
8. Added optional XML payload archiving
   - Allows generated XML payloads to be stored in the configured Payload archive.
   - Can be used together with the Microsoft SQL stored-procedure output.
9. Kept ResultsWriter independent of the physical output target
   - ResultsWriter generates the result payload.
   - UniDataStorageWriter decides how and where the payload is physically written.
   - The same ResultsWriter XML generation mechanism is therefore used for both XML file and MSSQL outputs.
10. Increased revision
   - Updated revision to 3.9.3145.100.
2026-09-02 12:05:10 +02:00

307 lines
8.0 KiB
C#

///
/// Copyright (c) 2026 Sensus Slovensko a.s.
///
using System;
using System.Linq;
using System.Windows.Forms;
using System.Xml.Linq;
namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.UI
{
/// <summary>
/// Displays one XML stored-procedure payload in a read-only tree and raw XML view.
/// </summary>
/// <remarks>
/// This dialog is intended for diagnostic inspection of the exact XML value
/// that will be supplied to a stored procedure parameter.
/// </remarks>
public partial class XmlPayloadViewerDlg : Form
{
/// <summary>
/// Gets or sets the stored-procedure parameter name.
/// </summary>
public string ParameterName
{
get;
set;
}
/// <summary>
/// Gets or sets the XML payload displayed by the dialog.
/// </summary>
public string XmlPayload
{
get;
set;
}
/// <summary>
/// Initializes a new XML payload viewer.
/// </summary>
public XmlPayloadViewerDlg()
{
InitializeComponent();
}
private void XmlPayloadViewerDlg_Load(
object sender,
EventArgs e)
{
Text =
string.IsNullOrWhiteSpace(
ParameterName)
? "XML payload viewer"
: "XML payload viewer - " +
ParameterName;
parameterNameTextBox.Text =
ParameterName ??
string.Empty;
LoadPayload();
}
/// <summary>
/// Parses and displays the configured XML payload.
/// </summary>
private void LoadPayload()
{
structureTreeView.Nodes.Clear();
rawXmlTextBox.Clear();
if (string.IsNullOrWhiteSpace(
XmlPayload))
{
statusLabel.Text =
"XML payload is empty.";
return;
}
try
{
XDocument document =
XDocument.Parse(
XmlPayload,
LoadOptions.PreserveWhitespace);
rawXmlTextBox.Text =
document.ToString();
if (document.Root != null)
{
TreeNode rootNode =
CreateElementNode(
document.Root);
structureTreeView.Nodes.Add(
rootNode);
rootNode.Expand();
ExpandInitialLevels(
rootNode,
2);
}
statusLabel.Text =
"Valid XML payload.";
}
catch (Exception ex)
{
//
// Preserve the exact supplied text even when it is invalid XML.
//
rawXmlTextBox.Text =
XmlPayload;
statusLabel.Text =
"Invalid XML: " +
ex.Message;
tabControl.SelectedTab =
rawXmlTabPage;
}
}
/// <summary>
/// Creates a tree node for one XML element.
/// </summary>
private TreeNode CreateElementNode(
XElement element)
{
TreeNode elementNode =
new TreeNode(
element.Name.LocalName);
foreach (XAttribute attribute
in element.Attributes())
{
string attributeName =
attribute.IsNamespaceDeclaration
? "xmlns" +
(attribute.Name.LocalName == "xmlns"
? string.Empty
: ":" + attribute.Name.LocalName)
: "@" +
attribute.Name.LocalName;
TreeNode attributeNode =
new TreeNode(
string.Format(
"{0} = {1}",
attributeName,
FormatValue(
attribute.Value)));
attributeNode.ToolTipText =
attribute.Value ??
string.Empty;
elementNode.Nodes.Add(
attributeNode);
}
foreach (XElement child
in element.Elements())
{
elementNode.Nodes.Add(
CreateElementNode(
child));
}
if (!element.Elements().Any())
{
string textValue =
string.Concat(
element.Nodes()
.OfType<XText>()
.Select(
node =>
node.Value));
if (!string.IsNullOrWhiteSpace(
textValue))
{
TreeNode textNode =
new TreeNode(
"#text = " +
FormatValue(
textValue));
textNode.ToolTipText =
textValue;
elementNode.Nodes.Add(
textNode);
}
}
return elementNode;
}
/// <summary>
/// Formats a potentially long XML value for the tree.
/// </summary>
private string FormatValue(
string value)
{
if (value == null)
return string.Empty;
const int maxLength =
160;
string singleLine =
value
.Replace(
"\r",
" ")
.Replace(
"\n",
" ");
if (singleLine.Length <=
maxLength)
{
return singleLine;
}
return singleLine.Substring(
0,
maxLength) +
"...";
}
/// <summary>
/// Expands the first levels of the XML tree without expanding a large
/// payload completely.
/// </summary>
private void ExpandInitialLevels(
TreeNode node,
int remainingLevels)
{
if (node == null ||
remainingLevels < 0)
{
return;
}
node.Expand();
if (remainingLevels == 0)
return;
foreach (TreeNode child
in node.Nodes)
{
if (child.Nodes.Count > 0)
{
ExpandInitialLevels(
child,
remainingLevels - 1);
}
}
}
private void expandAllButton_Click(
object sender,
EventArgs e)
{
structureTreeView.ExpandAll();
}
private void collapseAllButton_Click(
object sender,
EventArgs e)
{
structureTreeView.CollapseAll();
if (structureTreeView.Nodes.Count > 0)
{
structureTreeView.Nodes[0]
.Expand();
}
}
private void copyXmlButton_Click(
object sender,
EventArgs e)
{
if (!string.IsNullOrEmpty(
rawXmlTextBox.Text))
{
Clipboard.SetText(
rawXmlTextBox.Text);
}
}
private void closeButton_Click(
object sender,
EventArgs e)
{
Close();
}
}
}