• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

MeindertN / RoboClerk / 19481929392

18 Nov 2025 10:05PM UTC coverage: 72.65% (+0.3%) from 72.366%
19481929392

push

github

MeindertN
Fixed failing test

2217 of 3233 branches covered (68.57%)

Branch coverage included in aggregate %.

4 of 4 new or added lines in 1 file covered. (100.0%)

526 existing lines in 15 files now uncovered.

6894 of 9308 relevant lines covered (74.07%)

96.09 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.5
/RoboClerk.Core/ItemTemplateSupport/ScriptingBridge.cs
1
using RoboClerk.Core;
2
using RoboClerk.Core.Configuration;
3
using System;
4
using System.Collections.Generic;
5
using System.Linq;
6
using System.Text;
7
using System.Text.RegularExpressions;
8

9
namespace RoboClerk
10
{
11
    public class ScriptingBridge<T> where T : Item
12
    {
13
        private IDataSources data = null!;
102✔
14
        private ITraceabilityAnalysis analysis = null!;
102✔
15
        private IConfiguration configuration = null!;
102✔
16
        private List<string> traces = new List<string>();
102✔
17
        private List<T> items = new List<T>();
102✔
18
        private IRoboClerkTag currentTag = null;
102✔
19
        private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
1✔
20

21
        public ScriptingBridge(IDataSources data, ITraceabilityAnalysis trace, TraceEntity sourceTraceEntity, IConfiguration config)
102✔
22
        {
102✔
23
            this.data = data;
102✔
24
            analysis = trace;
102✔
25
            configuration = config;
102✔
26
            SourceTraceEntity = sourceTraceEntity;
102✔
27
        }
102✔
28

29
        /// <summary>
30
        /// The item that needs to be rendered in the documentation. 
31
        /// </summary>
32
        public T Item { get; set; } = default!;
145✔
33

34
        /// <summary>
35
        /// The items that need to be rendered in the documentation (empty if there is only a single item). 
36
        /// </summary>
37
        public IEnumerable<T> Items
38
        {
39
            get { return items; }
81✔
40
            set { items = value.ToList(); }
81✔
41
        }
42

43
        /// <summary>
44
        /// The RoboClerk tag that triggered the template rendering. Provides access to tag parameters.
45
        /// </summary>
46
        public IRoboClerkTag Tag
47
        {
48
            get { return currentTag; }
9✔
49
            set { currentTag = value; }
201✔
50
        }
51

52
        /// <summary>
53
        /// This function adds a trace link to the item who's ID is provided in the id parameter.
54
        /// </summary>
55
        /// <param name="id"></param>
56
        public void AddTrace(string id)
57
        {
76✔
58
            traces.Add(id);
76✔
59
        }
76✔
60

61
        /// <summary>
62
        /// Returns all the traces in the bridge.
63
        /// </summary>
64
        public IEnumerable<string> Traces
65
        {
66
            get { return traces; }
108✔
67
        }
68

69
        /// <summary>
70
        /// Returns the source trace entity, indicates what kind of trace item is being rendered.
71
        /// </summary>
72
        public TraceEntity SourceTraceEntity { get; }
48✔
73

74
        /// <summary>
75
        /// Provides access to the RoboClerk datasources object. This can be used to search in all 
76
        /// the information RoboClerk has.
77
        /// </summary>
UNCOV
78
        public IDataSources Sources { get { return data; } }
×
79

80
        /// <summary>
81
        /// Provides access to the traceability analysis object. The object stores all traceability
82
        /// information in RoboClerk.
83
        /// </summary>
84
        public ITraceabilityAnalysis TraceabilityAnalysis { get { return analysis; } }
69✔
85

86
        /// <summary>
87
        /// Gets a parameter value from the current tag, returning the default value if the parameter doesn't exist.
88
        /// </summary>
89
        /// <param name="parameterName">The name of the parameter to retrieve</param>
90
        /// <param name="defaultValue">The default value to return if parameter doesn't exist</param>
91
        /// <returns>The parameter value or default value</returns>
92
        public string GetTagParameter(string parameterName, string defaultValue = "")
93
        {
19✔
94
            if (currentTag == null)
19✔
95
            {
2✔
96
                logger.Error($"Attempted to get tag parameter '{parameterName}' but no tag is set in ScriptingBridge");
2✔
97
                throw new InvalidOperationException("No tag is set in ScriptingBridge.");
2✔
98
            }
99
            return currentTag.GetParameterOrDefault(parameterName, defaultValue);
17✔
100
        }
17✔
101

102
        /// <summary>
103
        /// Checks if the current tag has a specific parameter.
104
        /// </summary>
105
        /// <param name="parameterName">The name of the parameter to check for</param>
106
        /// <returns>True if the parameter exists, false otherwise</returns>
107
        public bool HasTagParameter(string parameterName)
108
        {
6✔
109
            if (currentTag == null)
6✔
110
            {
1✔
111
                logger.Error($"Attempted to check for tag parameter '{parameterName}' but no tag is set in ScriptingBridge");
1✔
112
                throw new InvalidOperationException("No tag is set in ScriptingBridge.");
1✔
113
            }
114
            return currentTag.HasParameter(parameterName);
5✔
115
        }
5✔
116

117
        /// <summary>
118
        /// Gets all parameter names from the current tag.
119
        /// </summary>
120
        /// <returns>Collection of all parameter names, or empty collection if no tag is set</returns>
121
        public IEnumerable<string> GetAllTagParameterNames()
122
        {
2✔
123
            if (currentTag == null)
2✔
124
            {
1✔
125
                logger.Error("Attempted to get all tag parameter names but no tag is set in ScriptingBridge");
1✔
126
                throw new InvalidOperationException("No tag is set in ScriptingBridge.");
1✔
127
            }
128
            return currentTag.Parameters;
1✔
129
        }
1✔
130

131
        /// <summary>
132
        /// Returns all linked items attached to li with a link type of linkType
133
        /// </summary>
134
        /// <param name="li"></param>
135
        /// <param name="linkType"></param>
136
        /// <returns></returns>
137
        public IEnumerable<LinkedItem> GetLinkedItems(LinkedItem li, ItemLinkType linkType)
138
        {
43✔
139
            List<LinkedItem> results = new List<LinkedItem>();
43✔
140
            var linkedItems = li.LinkedItems.Where(x => x.LinkType == linkType);
51✔
141
            foreach (var item in linkedItems)
135✔
142
            {
3✔
143
                var linkedItem = (LinkedItem)data.GetItem(item.TargetID);
3✔
144
                if (linkedItem != null)
3✔
145
                {
3✔
146
                    results.Add(linkedItem);
3✔
147
                }
3✔
148
            }
3✔
149
            return results;
43✔
150
        }
43✔
151

152
        /// <summary>
153
        /// This function retrieves all items linked to li with a link of type linkType and returns an
154
        /// asciidoc string that has links to all these items. Usually used to provide trace information.
155
        /// You can use includeTitle to control if the title of the linked item is included as well.
156
        /// </summary>
157
        /// <param name="li"></param>
158
        /// <param name="linkType"></param>
159
        /// <param name="includeTitle"</param>
160
        /// <returns>AsciiDoc string with http link to item.</returns>
161
        /// <exception cref="System.Exception"></exception>
162
        public string GetLinkedField(LinkedItem li, ItemLinkType linkType, bool includeTitle = true)
163
        {
20✔
164
            StringBuilder field = new StringBuilder();
20✔
165
            var linkedItems = li.LinkedItems.Where(x => x.LinkType == linkType);
44✔
166
            if (linkedItems.Count() > 0)
20✔
167
            {
11✔
168
                foreach (var item in linkedItems)
55✔
169
                {
12✔
170
                    if (field.Length > 0)
12✔
171
                    {
1✔
172
                        field.Append(" / ");
1✔
173
                    }
1✔
174
                    var linkedItem = data.GetItem(item.TargetID);
12✔
175
                    if (linkedItem != null)
12✔
176
                    {
10✔
177
                        AddTrace(linkedItem.ItemID);
10✔
178
                        field.Append(linkedItem.HasLink ? GetItemLinkString(linkedItem) : linkedItem.ItemID);
10✔
179
                        if (includeTitle && linkedItem.ItemTitle != string.Empty)
10✔
180
                        {
8✔
181
                            field.Append($": \"{linkedItem.ItemTitle}\"");
8✔
182
                        }
8✔
183
                    }
10✔
184
                    else
185
                    {
2✔
186
                        logger.Error($"Item with ID {li.ItemID} has a trace link to item with ID {item.TargetID} but that item does not exist.");
2✔
187
                        throw new System.Exception("Item with invalid trace link encountered.");
2✔
188
                    }
189
                }
10✔
190
                return field.ToString();
9✔
191
            }
192
            return "N/A";
9✔
193
        }
18✔
194

195
        /// <summary>
196
        /// Returns a hyperlink string for the provided item, formatted according to the configured output format.
197
        /// </summary>
198
        /// <param name="item">The item for which to generate a hyperlink.</param>
199
        /// <returns>
200
        /// A formatted hyperlink string. If the output format is HTML, returns an &lt;a&gt; tag.
201
        /// If the format is ASCIIDOC, returns an AsciiDoc-style link. 
202
        /// If the item has no link, returns just the item ID.
203
        /// </returns>
204
        /// <exception cref="NotSupportedException">Thrown if the configured output format is not supported.</exception>
205
        public string GetItemLinkString(Item item)
206
        {
97✔
207
            string format = configuration.OutputFormat.ToUpper();
97✔
208
            string result = item.ItemID;
97✔
209
            if (item.HasLink)
97✔
210
            {
21✔
211
                if (format == "HTML" || format == "DOCX")
21!
UNCOV
212
                {
×
UNCOV
213
                    result = $"<a href=\"{item.Link}\">{item.ItemID}</a>";
×
UNCOV
214
                }
×
215
                else if (format == "ASCIIDOC")
21!
216
                {
21✔
217
                    result = $"{item.Link}[{item.ItemID}]";
21✔
218
                }
21✔
219
                else
UNCOV
220
                {
×
UNCOV
221
                    logger.Warn($"Unknown output format \"{format}\" specified. Links may be missing from output.");
×
UNCOV
222
                }
×
223
            }
21✔
224
            return result;
97✔
225
        }
97✔
226

227
        /// <summary>
228
        /// Convenience function, checks if value is an empty string, if so, the
229
        /// defaultValue is returned.
230
        /// </summary>
231
        /// <param name="value"></param>
232
        /// <param name="defaultValue"></param>
233
        /// <returns></returns>
234
        public string GetValOrDef(string value, string defaultValue)
235
        {
33✔
236
            if (value == string.Empty)
33✔
237
            {
16✔
238
                return defaultValue;
16✔
239
            }
240
            return value;
17✔
241
        }
33✔
242

243
        /// <summary>
244
        /// Convenience function, calls ToString on input and returns resulting string.
245
        /// </summary>
246
        /// <param name="input"></param>
247
        /// <returns>string representation</returns>
248
        public string Insert(object? input)
249
        {
2✔
250
            return input?.ToString() ?? string.Empty;
2!
251
        }
2✔
252

253
        /// <summary>
254
        /// Convenience function, combines other convenience functions and ensures that
255
        /// ASCIDOC meant to be inside a table cell will render in an appropriate way by
256
        /// embedding tables and removing embedded headings because ASCIIDOC does not 
257
        /// support scoped headings and most likely the user does not want to have
258
        /// embedded headings numbered with the document headers.
259
        /// </summary>
260
        /// <param name="input">The AsciiDoc content that may contain headings and 
261
        /// or tables</param>
262
        /// <returns>Modified AsciiDoc that can be embedded in a table cell</returns>
263
        public string ProcessAsciidocForTableCell(string input)
264
        {
6✔
265
            string temp = ConvertHeadingsForASCIIDOCTableCell(input);
6✔
266
            return EmbedAsciidocTables(temp);
6✔
267
        }
6✔
268

269
        /// <summary>
270
        /// Convenience function, takes any asciidoc tables in the input and makes them
271
        /// embedded tables. 
272
        /// </summary>
273
        /// <param name="input"></param>
274
        /// <returns></returns>
275
        public string EmbedAsciidocTables(string input)
276
        {
12✔
277
            // Split the input into lines (preserving newlines)
278
            var lines = input.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None);
12✔
279
            var outputLines = new List<string>();
12✔
280
            bool inTable = false;
12✔
281

282
            foreach (var line in lines)
102✔
283
            {
33✔
284
                string trimmed = line.Trim();
33✔
285

286
                // Detect the table delimiter
287
                if (trimmed == "|===")
33✔
288
                {
12✔
289
                    inTable = !inTable;
12✔
290
                    // Replace outer table delimiter with nested table delimiter
291
                    outputLines.Add(line.Replace("|===", "!==="));
12✔
292
                }
12✔
293
                else if (inTable)
21✔
294
                {
7✔
295
                    // Preserve leading whitespace
296
                    int leadingSpaces = line.Length - line.TrimStart().Length;
7✔
297
                    string converted = line;
7✔
298

299
                    // If the cell begins with a pipe, replace it with an exclamation mark,
300
                    // but only if the pipe is not escaped.
301
                    if (line.TrimStart().StartsWith("|"))
7✔
302
                    {
7✔
303
                        converted = new string(' ', leadingSpaces) + "!" + line.TrimStart().Substring(1);
7✔
304
                    }
7✔
305

306
                    // Replace any unescaped cell separator pipe.
307
                    // The regex (?<!\\)\| matches any pipe that is not preceded by a backslash.
308
                    converted = Regex.Replace(converted, @"(?<!\\)\|", "!");
7✔
309
                    outputLines.Add(converted);
7✔
310
                }
7✔
311
                else
312
                {
14✔
313
                    // Outside a table block, leave the line unchanged.
314
                    outputLines.Add(line);
14✔
315
                }
14✔
316
            }
33✔
317

318
            // Rejoin all lines into a single string.
319
            return string.Join("\n", outputLines);
12✔
320
        }
12✔
321

322
        /// <summary>
323
        /// Converts AsciiDoc heading syntax to alternative markup suitable for embedding in table cells.
324
        /// This prevents heading content from being numbered along with main document headings.
325
        /// </summary>
326
        /// <param name="input">The AsciiDoc content that may contain headings</param>
327
        /// <returns>Modified AsciiDoc with headings converted to alternative markup</returns>
328
        public string ConvertHeadingsForASCIIDOCTableCell(string input)
329
        {
17✔
330
            if (string.IsNullOrEmpty(input))
17✔
331
                return input;
2✔
332

333
            // Process each line to detect and transform headings
334
            string[] lines = input.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None);
15✔
335
            for (int i = 0; i < lines.Length; i++)
96✔
336
            {
33✔
337
                string line = lines[i];
33✔
338

339
                // Match heading patterns (e.g., "== Heading", "=== Subheading", etc.)
340
                var match = System.Text.RegularExpressions.Regex.Match(line, @"^(=+)\s+(.+)$");
33✔
341
                if (match.Success)
33✔
342
                {
10✔
343
                    int level = match.Groups[1].Length;
10✔
344
                    string headingText = match.Groups[2].Value.Trim();
10✔
345

346
                    // Convert heading based on its level
347
                    switch (level)
10!
348
                    {
349
                        case 1: // Document title level
350
                        case 2: // Top section level - convert to bold
351
                            lines[i] = $"*{headingText}*";
4✔
352
                            break;
4✔
353
                        case 3: // Subsection level - convert to italic
354
                            lines[i] = $"_{headingText}_";
2✔
355
                            break;
2✔
356
                        case 4: // Subsubsection level - convert to italic with indentation
357
                            lines[i] = $"&#160;&#160; _{headingText}_";
2✔
358
                            break;
2✔
359
                        case 5: // Even deeper levels - use monospace with indentation
360
                        case 6:
361
                            lines[i] = $"&#160;&#160;&#160;&#160; `{headingText}`";
2✔
362
                            break;
2✔
363
                        default: // For extremely deep levels or unexpected cases
UNCOV
364
                            lines[i] = headingText;
×
UNCOV
365
                            break;
×
366
                    }
367

368
                    // Add a blank line after the heading for better readability
369
                    // only if there isn't already one and we're not at the end of the text
370
                    if (i < lines.Length - 1 && !string.IsNullOrWhiteSpace(lines[i + 1]))
10!
371
                    {
9✔
372
                        lines[i] += "\n";
9✔
373
                    }
9✔
374
                }
10✔
375
            }
33✔
376

377
            return string.Join("\n", lines);
15✔
378
        }
17✔
379
    }
380

381
    
382

383
    public class ScriptingBridge : ScriptingBridge<LinkedItem>
384
    {
385
        public ScriptingBridge(IDataSources data, ITraceabilityAnalysis trace, TraceEntity sourceTraceEntity, IConfiguration config)
386
            : base(data, trace, sourceTraceEntity, config)
101✔
387
        {
101✔
388
        }
101✔
389
    }
390
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc