PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
worklist_result_formatter.h
Go to the documentation of this file.
1
11#ifndef WORKLIST_SCU_WORKLIST_RESULT_FORMATTER_HPP
12#define WORKLIST_SCU_WORKLIST_RESULT_FORMATTER_HPP
13
16
17#include <algorithm>
18#include <iomanip>
19#include <sstream>
20#include <string>
21#include <vector>
22
23namespace worklist_cli {
24
28enum class output_format {
29 table,
30 json,
31 csv,
32 xml
33};
34
40[[nodiscard]] inline output_format parse_output_format(std::string_view format_str) {
41 if (format_str == "json") return output_format::json;
42 if (format_str == "csv") return output_format::csv;
43 if (format_str == "xml") return output_format::xml;
44 if (format_str == "text") return output_format::table;
46}
47
56public:
62 : format_(format) {}
63
69 [[nodiscard]] std::string format(
70 const std::vector<kcenon::pacs::core::dicom_dataset>& results) const {
71 switch (format_) {
73 return format_json(results);
75 return format_csv(results);
77 return format_xml(results);
79 default:
80 return format_table(results);
81 }
82 }
83
84private:
89 // Patient info
90 std::string patient_name;
91 std::string patient_id;
92 std::string patient_birth_date;
93 std::string patient_sex;
94
95 // Scheduled Procedure Step info
96 std::string scheduled_date;
97 std::string scheduled_time;
98 std::string modality;
99 std::string station_ae;
100 std::string step_id;
101 std::string step_description;
102
103 // Study/Request info
104 std::string accession_number;
105 std::string study_uid;
107 };
108
117 const kcenon::pacs::core::dicom_dataset& ds) const {
118 using namespace kcenon::pacs::core;
119
120 worklist_item item;
121
122 // Patient demographics
123 item.patient_name = ds.get_string(tags::patient_name);
124 item.patient_id = ds.get_string(tags::patient_id);
125 item.patient_birth_date = ds.get_string(tags::patient_birth_date);
126 item.patient_sex = ds.get_string(tags::patient_sex);
127
128 // Study-level attributes
129 item.accession_number = ds.get_string(tags::accession_number);
130 item.study_uid = ds.get_string(tags::study_instance_uid);
131 item.requested_procedure_id = ds.get_string(tags::requested_procedure_id);
132
133 // Scheduled Procedure Step attributes (flat structure)
134 item.scheduled_date = ds.get_string(tags::scheduled_procedure_step_start_date);
135 item.scheduled_time = ds.get_string(tags::scheduled_procedure_step_start_time);
136 item.modality = ds.get_string(tags::modality);
137 item.station_ae = ds.get_string(tags::scheduled_station_ae_title);
138 item.step_id = ds.get_string(tags::scheduled_procedure_step_id);
139 item.step_description = ds.get_string(tags::scheduled_procedure_step_description);
140
141 return item;
142 }
143
147 [[nodiscard]] std::string format_table(
148 const std::vector<kcenon::pacs::core::dicom_dataset>& results) const {
149 std::ostringstream oss;
150
151 if (results.empty()) {
152 oss << "No worklist items found.\n";
153 return oss.str();
154 }
155
156 // Extract all items
157 std::vector<worklist_item> items;
158 items.reserve(results.size());
159 for (const auto& r : results) {
160 items.push_back(extract_item(r));
161 }
162
163 // Define column widths
164 size_t w_name = 20, w_id = 12, w_date = 10, w_time = 8;
165 size_t w_mod = 6, w_station = 16, w_accession = 12, w_step = 12;
166
167 // Update widths based on data
168 for (const auto& item : items) {
169 w_name = std::min(size_t(30), std::max(w_name, item.patient_name.length()));
170 w_id = std::min(size_t(20), std::max(w_id, item.patient_id.length()));
171 w_station = std::min(size_t(20), std::max(w_station, item.station_ae.length()));
172 w_accession = std::min(size_t(20), std::max(w_accession, item.accession_number.length()));
173 w_step = std::min(size_t(20), std::max(w_step, item.step_id.length()));
174 }
175
176 // Print header
177 oss << "\n=== Worklist Results (" << results.size() << " scheduled procedure(s)) ===\n\n";
178
179 // Print column headers
180 oss << std::left
181 << std::setw(static_cast<int>(w_name + 2)) << "Patient Name"
182 << std::setw(static_cast<int>(w_id + 2)) << "Patient ID"
183 << std::setw(static_cast<int>(w_date + 2)) << "Sched Date"
184 << std::setw(static_cast<int>(w_time + 2)) << "Time"
185 << std::setw(static_cast<int>(w_mod + 2)) << "Mod"
186 << std::setw(static_cast<int>(w_station + 2)) << "Station AE"
187 << std::setw(static_cast<int>(w_accession + 2)) << "Accession#"
188 << std::setw(static_cast<int>(w_step + 2)) << "Step ID"
189 << "\n";
190
191 // Print separator
192 oss << std::string(w_name, '-') << " "
193 << std::string(w_id, '-') << " "
194 << std::string(w_date, '-') << " "
195 << std::string(w_time, '-') << " "
196 << std::string(w_mod, '-') << " "
197 << std::string(w_station, '-') << " "
198 << std::string(w_accession, '-') << " "
199 << std::string(w_step, '-') << " "
200 << "\n";
201
202 // Print data rows
203 for (const auto& item : items) {
204 oss << std::left
205 << std::setw(static_cast<int>(w_name + 2)) << truncate(item.patient_name, w_name)
206 << std::setw(static_cast<int>(w_id + 2)) << truncate(item.patient_id, w_id)
207 << std::setw(static_cast<int>(w_date + 2)) << format_date(item.scheduled_date)
208 << std::setw(static_cast<int>(w_time + 2)) << format_time(item.scheduled_time)
209 << std::setw(static_cast<int>(w_mod + 2)) << item.modality
210 << std::setw(static_cast<int>(w_station + 2)) << truncate(item.station_ae, w_station)
211 << std::setw(static_cast<int>(w_accession + 2)) << truncate(item.accession_number, w_accession)
212 << std::setw(static_cast<int>(w_step + 2)) << truncate(item.step_id, w_step)
213 << "\n";
214 }
215
216 return oss.str();
217 }
218
222 [[nodiscard]] std::string format_json(
223 const std::vector<kcenon::pacs::core::dicom_dataset>& results) const {
224 std::ostringstream oss;
225
226 oss << "{\n";
227 oss << " \"resultCount\": " << results.size() << ",\n";
228 oss << " \"worklistItems\": [\n";
229
230 for (size_t i = 0; i < results.size(); ++i) {
231 auto item = extract_item(results[i]);
232
233 oss << " {\n";
234 oss << " \"patient\": {\n";
235 oss << " \"name\": \"" << escape_json(item.patient_name) << "\",\n";
236 oss << " \"id\": \"" << escape_json(item.patient_id) << "\",\n";
237 oss << " \"birthDate\": \"" << item.patient_birth_date << "\",\n";
238 oss << " \"sex\": \"" << item.patient_sex << "\"\n";
239 oss << " },\n";
240 oss << " \"scheduledProcedureStep\": {\n";
241 oss << " \"startDate\": \"" << item.scheduled_date << "\",\n";
242 oss << " \"startTime\": \"" << item.scheduled_time << "\",\n";
243 oss << " \"modality\": \"" << item.modality << "\",\n";
244 oss << " \"stationAETitle\": \"" << escape_json(item.station_ae) << "\",\n";
245 oss << " \"stepId\": \"" << escape_json(item.step_id) << "\",\n";
246 oss << " \"description\": \"" << escape_json(item.step_description) << "\"\n";
247 oss << " },\n";
248 oss << " \"accessionNumber\": \"" << escape_json(item.accession_number) << "\",\n";
249 oss << " \"studyInstanceUid\": \"" << item.study_uid << "\",\n";
250 oss << " \"requestedProcedureId\": \"" << escape_json(item.requested_procedure_id) << "\"\n";
251 oss << " }";
252
253 if (i < results.size() - 1) {
254 oss << ",";
255 }
256 oss << "\n";
257 }
258
259 oss << " ]\n";
260 oss << "}\n";
261
262 return oss.str();
263 }
264
268 [[nodiscard]] std::string format_csv(
269 const std::vector<kcenon::pacs::core::dicom_dataset>& results) const {
270 std::ostringstream oss;
271
272 // Header row
273 oss << "PatientName,PatientID,BirthDate,Sex,"
274 << "ScheduledDate,ScheduledTime,Modality,StationAE,"
275 << "StepID,StepDescription,AccessionNumber,StudyUID,RequestedProcedureID\n";
276
277 // Data rows
278 for (const auto& r : results) {
279 auto item = extract_item(r);
280
281 oss << escape_csv(item.patient_name) << ","
282 << escape_csv(item.patient_id) << ","
283 << item.patient_birth_date << ","
284 << item.patient_sex << ","
285 << item.scheduled_date << ","
286 << item.scheduled_time << ","
287 << item.modality << ","
288 << escape_csv(item.station_ae) << ","
289 << escape_csv(item.step_id) << ","
290 << escape_csv(item.step_description) << ","
291 << escape_csv(item.accession_number) << ","
292 << item.study_uid << ","
293 << escape_csv(item.requested_procedure_id) << "\n";
294 }
295
296 return oss.str();
297 }
298
302 [[nodiscard]] std::string format_xml(
303 const std::vector<kcenon::pacs::core::dicom_dataset>& results) const {
304 std::ostringstream oss;
305
306 oss << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
307 oss << "<WorklistQueryResult>\n";
308 oss << " <ResultCount>" << results.size() << "</ResultCount>\n";
309 oss << " <WorklistItems>\n";
310
311 for (size_t i = 0; i < results.size(); ++i) {
312 auto item = extract_item(results[i]);
313
314 oss << " <WorklistItem index=\"" << (i + 1) << "\">\n";
315
316 // Patient information
317 oss << " <Patient>\n";
318 oss << " <Name>" << escape_xml(item.patient_name) << "</Name>\n";
319 oss << " <ID>" << escape_xml(item.patient_id) << "</ID>\n";
320 oss << " <BirthDate>" << item.patient_birth_date << "</BirthDate>\n";
321 oss << " <Sex>" << item.patient_sex << "</Sex>\n";
322 oss << " </Patient>\n";
323
324 // Scheduled Procedure Step
325 oss << " <ScheduledProcedureStep>\n";
326 oss << " <StartDate>" << item.scheduled_date << "</StartDate>\n";
327 oss << " <StartTime>" << item.scheduled_time << "</StartTime>\n";
328 oss << " <Modality>" << item.modality << "</Modality>\n";
329 oss << " <StationAETitle>" << escape_xml(item.station_ae) << "</StationAETitle>\n";
330 oss << " <StepID>" << escape_xml(item.step_id) << "</StepID>\n";
331 oss << " <Description>" << escape_xml(item.step_description) << "</Description>\n";
332 oss << " </ScheduledProcedureStep>\n";
333
334 // Study information
335 oss << " <AccessionNumber>" << escape_xml(item.accession_number) << "</AccessionNumber>\n";
336 oss << " <StudyInstanceUID>" << item.study_uid << "</StudyInstanceUID>\n";
337 oss << " <RequestedProcedureID>" << escape_xml(item.requested_procedure_id) << "</RequestedProcedureID>\n";
338
339 oss << " </WorklistItem>\n";
340 }
341
342 oss << " </WorklistItems>\n";
343 oss << "</WorklistQueryResult>\n";
344
345 return oss.str();
346 }
347
351 [[nodiscard]] static std::string truncate(const std::string& s, size_t max_len) {
352 if (s.length() <= max_len) {
353 return s;
354 }
355 return s.substr(0, max_len - 3) + "...";
356 }
357
361 [[nodiscard]] static std::string format_date(const std::string& date) {
362 if (date.length() == 8) {
363 return date.substr(0, 4) + "-" + date.substr(4, 2) + "-" + date.substr(6, 2);
364 }
365 return date;
366 }
367
371 [[nodiscard]] static std::string format_time(const std::string& time) {
372 if (time.length() >= 4) {
373 return time.substr(0, 2) + ":" + time.substr(2, 2);
374 }
375 return time;
376 }
377
381 [[nodiscard]] static std::string escape_json(const std::string& s) {
382 std::string result;
383 result.reserve(s.length());
384 for (char c : s) {
385 switch (c) {
386 case '"': result += "\\\""; break;
387 case '\\': result += "\\\\"; break;
388 case '\b': result += "\\b"; break;
389 case '\f': result += "\\f"; break;
390 case '\n': result += "\\n"; break;
391 case '\r': result += "\\r"; break;
392 case '\t': result += "\\t"; break;
393 default:
394 if (static_cast<unsigned char>(c) < 0x20) {
395 result += "\\u";
396 char buf[5];
397 std::snprintf(buf, sizeof(buf), "%04X",
398 static_cast<unsigned char>(c));
399 result += buf;
400 } else {
401 result += c;
402 }
403 }
404 }
405 return result;
406 }
407
411 [[nodiscard]] static std::string escape_csv(const std::string& s) {
412 if (s.find_first_of(",\"\n\r") == std::string::npos) {
413 return s;
414 }
415
416 std::string result = "\"";
417 for (char c : s) {
418 if (c == '"') {
419 result += "\"\"";
420 } else {
421 result += c;
422 }
423 }
424 result += "\"";
425 return result;
426 }
427
431 [[nodiscard]] static std::string escape_xml(const std::string& s) {
432 std::string result;
433 result.reserve(s.length());
434 for (char c : s) {
435 switch (c) {
436 case '&': result += "&amp;"; break;
437 case '<': result += "&lt;"; break;
438 case '>': result += "&gt;"; break;
439 case '"': result += "&quot;"; break;
440 case '\'': result += "&apos;"; break;
441 default: result += c;
442 }
443 }
444 return result;
445 }
446
448};
449
450} // namespace worklist_cli
451
452#endif // WORKLIST_SCU_WORKLIST_RESULT_FORMATTER_HPP
auto get_string(dicom_tag tag, std::string_view default_value="") const -> std::string
Get the string value of an element.
Result formatter for worklist query results.
static std::string escape_json(const std::string &s)
Escape string for JSON output.
static std::string truncate(const std::string &s, size_t max_len)
Truncate string to max length.
static std::string format_time(const std::string &time)
Format DICOM time (HHMMSS) for display.
static std::string format_date(const std::string &date)
Format DICOM date (YYYYMMDD) for display.
std::string format_xml(const std::vector< kcenon::pacs::core::dicom_dataset > &results) const
Format results as XML.
static std::string escape_csv(const std::string &s)
Escape string for CSV output.
std::string format_table(const std::vector< kcenon::pacs::core::dicom_dataset > &results) const
Format results as a human-readable table.
std::string format_json(const std::vector< kcenon::pacs::core::dicom_dataset > &results) const
Format results as JSON.
std::string format_csv(const std::vector< kcenon::pacs::core::dicom_dataset > &results) const
Format results as CSV.
worklist_item extract_item(const kcenon::pacs::core::dicom_dataset &ds) const
Extract worklist item data from dataset.
worklist_result_formatter(output_format format)
Construct formatter with output format.
std::string format(const std::vector< kcenon::pacs::core::dicom_dataset > &results) const
Format worklist results.
static std::string escape_xml(const std::string &s)
Escape string for XML output.
DICOM Dataset - ordered collection of Data Elements.
Compile-time constants for commonly used DICOM tags.
output_format
Output format enumeration.
@ xml
XML format for integration.
@ json
JSON format for integration.
@ csv
CSV format for export.
@ table
Human-readable table format (alias: text)
output_format parse_output_format(std::string_view format_str)
Parse output format from string.