1 // vil_nitf2: Written by Harry Voorhees (hlv@) and Rob Radtke (rob@) of
2 // Stellar Science Ltd. Co. (stellarscience.com) for
3 // Air Force Research Laboratory, 2005.
4
5 #include "vil_nitf2_tagged_record.h"
6 #include <iomanip>
7 #include <iostream>
8 #include <sstream>
9 #include <string>
10 #include <utility>
11
12 #ifdef _MSC_VER
13 # include "vcl_msvc_warnings.h"
14 #endif
15
16 #include "vil/vil_stream_core.h"
17 #include "vil/vil_stream_section.h"
18
19 #include "vil_nitf2_tagged_record_definition.h"
20 #include "vil_nitf2_field_definition.h"
21 #include "vil_nitf2_field_sequence.h"
22 #include "vil_nitf2_index_vector.h"
23 #include "vil_nitf2_typed_field_formatter.h"
24 #include "vil_nitf2_scalar_field.h"
25
26 vil_nitf2_field_definition &
s_length_definition()27 vil_nitf2_tagged_record::s_length_definition()
28 {
29 static vil_nitf2_field_definition length_definition("CEL", "Extension Length", new vil_nitf2_integer_formatter(5));
30 return length_definition;
31 }
32
33 vil_nitf2_field_definition &
s_tag_definition()34 vil_nitf2_tagged_record::s_tag_definition()
35 {
36 static vil_nitf2_field_definition tag_definition("CETAG", "Extension Tag", new vil_nitf2_string_formatter(6));
37 return tag_definition;
38 }
39
40 vil_nitf2_integer_formatter &
s_length_formatter()41 vil_nitf2_tagged_record::s_length_formatter()
42 {
43 static vil_nitf2_integer_formatter length_formatter(5);
44 return length_formatter;
45 }
46
47 vil_nitf2_string_formatter &
s_tag_formatter()48 vil_nitf2_tagged_record::s_tag_formatter()
49 {
50 static vil_nitf2_string_formatter tag_formatter(6);
51 return tag_formatter;
52 }
53
54 std::string
name() const55 vil_nitf2_tagged_record::name() const
56 {
57 std::string cetag;
58 if (m_tag_field->value(cetag))
59 return cetag;
60 else
61 return "<Unknown>";
62 }
63
64 std::string
pretty_name() const65 vil_nitf2_tagged_record::pretty_name() const
66 {
67 if (m_definition)
68 return m_definition->m_pretty_name;
69 else
70 return "<unknown>";
71 }
72
73 vil_nitf2_tagged_record *
create(vil_nitf2_istream & input)74 vil_nitf2_tagged_record::create(vil_nitf2_istream & input)
75 {
76 auto * record = new vil_nitf2_tagged_record();
77 if (record->read(input))
78 {
79 return record;
80 }
81 else
82 {
83 delete record;
84 return nullptr;
85 }
86 }
87
88 bool
read(vil_nitf2_istream & input)89 vil_nitf2_tagged_record::read(vil_nitf2_istream & input)
90 {
91 // Read TRE tag
92 m_tag_field = vil_nitf2_scalar_field::read(input, &s_tag_definition());
93 if (!m_tag_field)
94 {
95 std::cerr << "Error reading extension tag at offset " << input.tell() << ".\n";
96 // Can't continue reading file
97 return false;
98 }
99 std::string cetag;
100 m_tag_field->value(cetag);
101
102 // Read TRE data length
103 m_length_field = vil_nitf2_scalar_field::read(input, &s_length_definition());
104 if (!m_length_field)
105 {
106 std::cerr << "Error reading extension length for tag " << cetag << ".\n";
107 // Can't continue reading file
108 return false;
109 }
110 m_length_field->value(m_length);
111
112 // See if this record is defined ...
113 vil_nitf2_tagged_record_definition * record_definition = vil_nitf2_tagged_record_definition::find(cetag);
114
115 // ... if not, skip ahead to next record ...
116 if (record_definition == nullptr)
117 {
118 VIL_NITF2_LOG(log_info) << "Skipping unknown record " << cetag << ".\n";
119 // Return whether I've found end of record
120 // input.seekg(ceLength, std::ios::cur);
121 input.seek(input.tell() + m_length);
122 return input.ok();
123 }
124 // ... otherwise, populate this record's fields.
125 // First save the position to check later that we read entire record
126 vil_streampos record_data_start_pos = input.tell();
127 m_definition = record_definition;
128 m_field_sequence = new vil_nitf2_field_sequence(record_definition->field_definitions());
129 m_field_sequence->read(input);
130
131 // Check that the expected amount of data was read
132 vil_streampos expected_pos = record_data_start_pos;
133 expected_pos += m_length;
134 if (input.tell() != expected_pos)
135 {
136 std::cerr << "vil_nitf2_tagged_record::read(): Read " << input.tell() - record_data_start_pos
137 << " bytes instead of " << m_length << " as expected in " << cetag << ".\n";
138 // Attempt to reposition input stream to end of record; return whether
139 // successful
140 input.seek(expected_pos);
141 return input.ok();
142 }
143 // At end of record, as expected
144 return true;
145 }
146
147 bool
get_value(std::string tag,int & out_value) const148 vil_nitf2_tagged_record::get_value(std::string tag, int & out_value) const
149 {
150 return m_field_sequence->get_value(std::move(tag), out_value);
151 }
152
153 bool
get_value(std::string tag,double & out_value) const154 vil_nitf2_tagged_record::get_value(std::string tag, double & out_value) const
155 {
156 return m_field_sequence->get_value(std::move(tag), out_value);
157 }
158
159 bool
get_value(std::string tag,char & out_value) const160 vil_nitf2_tagged_record::get_value(std::string tag, char & out_value) const
161 {
162 return m_field_sequence->get_value(std::move(tag), out_value);
163 }
164 bool
get_value(std::string tag,void * & out_value) const165 vil_nitf2_tagged_record::get_value(std::string tag, void *& out_value) const
166 {
167 return m_field_sequence->get_value(std::move(tag), out_value);
168 }
169
170 bool
get_value(std::string tag,std::string & out_value) const171 vil_nitf2_tagged_record::get_value(std::string tag, std::string & out_value) const
172 {
173 return m_field_sequence->get_value(std::move(tag), out_value);
174 }
175
176 bool
get_value(std::string tag,vil_nitf2_location * & out_value) const177 vil_nitf2_tagged_record::get_value(std::string tag, vil_nitf2_location *& out_value) const
178 {
179 return m_field_sequence->get_value(std::move(tag), out_value);
180 }
181
182 bool
get_value(std::string tag,vil_nitf2_date_time & out_value) const183 vil_nitf2_tagged_record::get_value(std::string tag, vil_nitf2_date_time & out_value) const
184 {
185 return m_field_sequence->get_value(std::move(tag), out_value);
186 }
187
188 #if VXL_HAS_INT_64
189 // if not VXL_HAS_INT_64 isn't defined the vil_nitf2_long is the same as just plain 'int'
190 // and this function will be a duplicate of that get_value
191 bool
get_value(std::string tag,vil_nitf2_long & out_value) const192 vil_nitf2_tagged_record::get_value(std::string tag, vil_nitf2_long & out_value) const
193 {
194 return m_field_sequence->get_value(std::move(tag), out_value);
195 }
196 #endif
197
198 bool
get_value(std::string tag,const vil_nitf2_index_vector & indexes,int & out_value) const199 vil_nitf2_tagged_record::get_value(std::string tag, const vil_nitf2_index_vector & indexes, int & out_value) const
200 {
201 return m_field_sequence->get_value(std::move(tag), indexes, out_value);
202 }
203
204 bool
get_value(std::string tag,const vil_nitf2_index_vector & indexes,double & out_value) const205 vil_nitf2_tagged_record::get_value(std::string tag, const vil_nitf2_index_vector & indexes, double & out_value) const
206 {
207 return m_field_sequence->get_value(std::move(tag), indexes, out_value);
208 }
209
210 bool
get_value(std::string tag,const vil_nitf2_index_vector & indexes,char & out_value) const211 vil_nitf2_tagged_record::get_value(std::string tag, const vil_nitf2_index_vector & indexes, char & out_value) const
212 {
213 return m_field_sequence->get_value(std::move(tag), indexes, out_value);
214 }
215
216 bool
get_value(std::string tag,const vil_nitf2_index_vector & indexes,void * & out_value) const217 vil_nitf2_tagged_record::get_value(std::string tag, const vil_nitf2_index_vector & indexes, void *& out_value) const
218 {
219 return m_field_sequence->get_value(std::move(tag), indexes, out_value);
220 }
221
222 bool
get_value(std::string tag,const vil_nitf2_index_vector & indexes,std::string & out_value) const223 vil_nitf2_tagged_record::get_value(std::string tag,
224 const vil_nitf2_index_vector & indexes,
225 std::string & out_value) const
226 {
227 return m_field_sequence->get_value(std::move(tag), indexes, out_value);
228 }
229
230 bool
get_value(std::string tag,const vil_nitf2_index_vector & indexes,vil_nitf2_location * & out_value) const231 vil_nitf2_tagged_record::get_value(std::string tag,
232 const vil_nitf2_index_vector & indexes,
233 vil_nitf2_location *& out_value) const
234 {
235 return m_field_sequence->get_value(std::move(tag), indexes, out_value);
236 }
237
238 bool
get_value(std::string tag,const vil_nitf2_index_vector & indexes,vil_nitf2_date_time & out_value) const239 vil_nitf2_tagged_record::get_value(std::string tag,
240 const vil_nitf2_index_vector & indexes,
241 vil_nitf2_date_time & out_value) const
242 {
243 return m_field_sequence->get_value(std::move(tag), indexes, out_value);
244 }
245
246 #if VXL_HAS_INT_64
247 // if not VXL_HAS_INT_64 isn't defined the vil_nitf2_long is the same as just plain 'int'
248 // and this function will be a duplicate of that get_value
249 bool
get_value(std::string tag,const vil_nitf2_index_vector & indexes,vil_nitf2_long & out_value) const250 vil_nitf2_tagged_record::get_value(std::string tag,
251 const vil_nitf2_index_vector & indexes,
252 vil_nitf2_long & out_value) const
253 {
254 return m_field_sequence->get_value(std::move(tag), indexes, out_value);
255 }
256 #endif
257
258 // Macro to define both overloads of get_values()
259 #define VIL_NITF2_TAGGED_RECORD_GET_VALUES(T) \
260 bool vil_nitf2_tagged_record::get_values( \
261 std::string tag, const vil_nitf2_index_vector & indexes, std::vector<T> & out_values, bool clear_out_values) const \
262 { \
263 return m_field_sequence->get_values(tag, indexes, out_values, clear_out_values); \
264 } \
265 bool vil_nitf2_tagged_record::get_values(std::string tag, std::vector<T> & out_values) const \
266 { \
267 return m_field_sequence->get_values(tag, out_values); \
268 }
269
270 VIL_NITF2_TAGGED_RECORD_GET_VALUES(int);
271 VIL_NITF2_TAGGED_RECORD_GET_VALUES(double);
272 VIL_NITF2_TAGGED_RECORD_GET_VALUES(char);
273 VIL_NITF2_TAGGED_RECORD_GET_VALUES(void *);
274 VIL_NITF2_TAGGED_RECORD_GET_VALUES(std::string);
275 VIL_NITF2_TAGGED_RECORD_GET_VALUES(vil_nitf2_location *);
276 VIL_NITF2_TAGGED_RECORD_GET_VALUES(vil_nitf2_date_time);
277 #if VXL_HAS_INT_64
278 VIL_NITF2_TAGGED_RECORD_GET_VALUES(vil_nitf2_long);
279 #endif
280
281 vil_nitf2_tagged_record::vil_nitf2_tagged_record()
282
283 = default;
284
285 // TO DO: rewrite this method a sequence of unit tests!
286 //
287 bool
test()288 vil_nitf2_tagged_record::test()
289 {
290 bool error = false;
291 const char * test_tre_tag = "@TEST@";
292 // Example Tagged Record Extension definition
293 vil_nitf2_tagged_record_definition::define(test_tre_tag, "Test Definition")
294 .field("MTI_DP", "Destination Point", NITF_INT(2))
295 .field("MTI_PACKET_ID", "MTI Packed ID Number", NITF_INT(3))
296 .field("DATIME", "Scan Date & Time", NITF_DAT(14), true)
297 .field("ACFT_LOC", "Aircraft Position", NITF_LOC(21))
298 .field("ACFT_LOC2", "Aircraft Position 2", NITF_LOC(21))
299 .field("SQUINT_ANGLE", "Squint Angle", NITF_DBL(6, 2, true), true)
300 .field("NO_VALID_TARGETS", "Number of Targets", NITF_INT(3))
301 .field("TGT_CAT",
302 "Target Classification Category",
303 NITF_ENUM(1,
304 vil_nitf2_enum_values()
305 .value("H", "Helicopter")
306 .value("T", "Tracked")
307 .value("U", "Unknown")
308 .value("W", "Wheeled")
309 .value("TOO LONG", "Too long value test")
310 .value("T", "Duplicate value test")))
311 .repeat(new vil_nitf2_field_value<int>("NO_VALID_TARGETS"),
312 vil_nitf2_field_definitions()
313 .field("TGT_n_SPEED", "Target Estimated Ground Speed", NITF_INT(4), true)
314 .field("TGT_n_CAT",
315 "Target Classification Category",
316 NITF_ENUM(1,
317 vil_nitf2_enum_values()
318 .value("H", "Helicopter")
319 .value("T", "Tracked")
320 .value("U", "Unknown")
321 .value("W", "Wheeled")),
322 true))
323 .field("TEST_NEG_COND",
324 "Test False Condition",
325 NITF_STR_BCSA(14),
326 false,
327 nullptr,
328 new vil_nitf2_field_value_greater_than<int>("MTI_DP", 5))
329 .field("TEST_POS_COND",
330 "Test True Condition",
331 NITF_STR_BCSA(14),
332 false,
333 nullptr,
334 new vil_nitf2_field_value_greater_than<int>("MTI_DP", 1))
335 .field("CLASS",
336 "Security Classification",
337 NITF_ENUM(1,
338 vil_nitf2_enum_values()
339 .value("T", "Top Secret")
340 .value("S", "Secret")
341 .value("C", "Confindential")
342 .value("R", "Restricted")
343 .value("U", "Unclassified")),
344 true,
345 nullptr,
346 nullptr)
347 .field("CODEW",
348 "Code Words",
349 NITF_STR_BCSA(15),
350 false,
351 nullptr,
352 new vil_nitf2_field_value_one_of<std::string>("CLASS", "T"))
353 .field("CWTEST",
354 "Another Code Word Test",
355 NITF_STR_BCSA(15),
356 false,
357 nullptr,
358 new vil_nitf2_field_value_one_of<std::string>("CLASS", "U"))
359 .field("NBANDS", "Number of bands", NITF_INT(1), false, nullptr, nullptr)
360 .field("XBANDS",
361 "Large number of bands",
362 NITF_INT(2),
363 false,
364 nullptr,
365 new vil_nitf2_field_value_one_of<int>("NBANDS", 0))
366 .repeat(new vil_nitf2_choose_field_value<int>(
367 "NBANDS", "XBANDS", new vil_nitf2_field_value_greater_than<int>("NBANDS", 0)),
368 vil_nitf2_field_definitions().field("BAND_LTR", "Band Description", NITF_CHAR(), true, nullptr))
369 .field("EXP_TEST", "Exponential format test", NITF_EXP(6, 1))
370 // test nested repeats and functor references to tags within and
371 // outside repeat loops
372 .field("N", "Test repeat N", NITF_INT(1))
373 .repeat(new vil_nitf2_field_value<int>("N"),
374 vil_nitf2_field_definitions()
375 .field("A", "Test repeat A", NITF_INT(1))
376 .repeat(new vil_nitf2_field_value<int>("N"),
377 vil_nitf2_field_definitions().field("S", "Test repeat S", NITF_STR(3)))
378 .repeat(new vil_nitf2_field_value<int>("A"),
379 vil_nitf2_field_definitions()
380 .field("B", "Test repeat B", NITF_STR_BCSA(3))
381 .repeat(new vil_nitf2_field_value<int>("A"),
382 vil_nitf2_field_definitions().field("C", "Test repeat C", NITF_STR_BCSA(4)))))
383 // test fixed repeat count
384 .repeat(4, vil_nitf2_field_definitions().field("D", "Test fixed repeat", NITF_INT(1)))
385 .end();
386 // Create a test input std::string
387 std::string testFieldsStr = "02" // MTI_DP
388 "003" // MTI_PACKET_ID
389 //"19990908070605" // DATIME
390 " " // DATIME
391 "+89.111111-159.222222" // ACFT_LOC
392 "890102.33N0091122.00W" // ACFT_LOC2
393 "-60.00" // SQUINT_ANGLE
394 "003" // NO_VALID_TARGETS
395 " " // TGT_CAT
396 "2222"
397 "H" // TGT_1_SPEED2, TGT_1_CAT2
398 " "
399 " " // TGT_2_SPEED2, TGT_2_CAT2
400 "4444"
401 "T" // TGT_3_SPEED2, TGT_3_CAT2
402 "" // TEST_NEG_COND not present
403 "True Condition" // TEST_POS_COND
404 "T" // CLASS
405 "RandomCodeWords" // CODEW (only present if CLASS=T)
406 "" // CWTEST (not present because CLASS!=U
407 "0" // NBANDS (0, so use XBANDS instead)
408 "12" // XBANDS (present because NBANDS=0)
409 "abcdefghijkl" // 12 BAND_LTRs (XBAND=12)
410 "+1.234567E-8" // Exponential format test
411 // test nested repeats
412 "2" // N
413 // for i=0...N-1: i=0
414 "1" // A[0]
415 "S00" // S[0,0]
416 "S01" // S[0,1]
417 // for j=0...A[i]-1: j=0
418 "B00" // B[0,0]
419 // for k=0..A[i]-1: k=0
420 "C000" // C[0,0,0]
421 // i=1:
422 "2" // A[1]
423 "S10" // S[1,0]
424 "S11" // S[1,1]
425 // for j=0..A[i]: j=0
426 "B10" // B[1,0]
427 // for k=0..A[i]
428 "C100" // C[1,0,0]
429 "C101" // C[1,0,1]
430 "B11" // B[1,1]
431 "C110" // C[1,1,0]
432 "C111" // C[1,1,1]
433 // test fixed repeat
434 "7890";
435 std::stringstream test_stream;
436 test_stream << test_tre_tag; // CETAG
437 test_stream << std::setw(5) << std::setfill('0') << testFieldsStr.length(); // CELENGTH
438 test_stream << testFieldsStr; // rest of fields
439 std::string read_string = test_stream.str();
440 // Write the test input std::string to a vil_stream
441 auto * vs = new vil_stream_core();
442 vs->write(read_string.c_str(), read_string.length());
443 vs->seek(0);
444 auto * vss = new vil_stream_section(vs, 0, int(read_string.length()));
445 // Record from the vil_stream
446 vil_nitf2_tagged_record * record = vil_nitf2_tagged_record::create(*vss);
447 if (record)
448 {
449 std::cerr << *record << '\n';
450 // Now write the record, and compare the output to the test input
451 std::cerr << "\nOriginal string:\n" << read_string << "\nWrite() output:\n";
452 auto * vs2 = new vil_stream_core();
453 record->write(*(vil_stream *)vs2);
454 vil_streampos bufsize = vs2->file_size();
455 char * buf = new char[(unsigned int)bufsize + 1];
456 vs2->seek(0);
457 vs2->read(buf, bufsize);
458 buf[bufsize] = '\0';
459 std::string write_string = std::string(buf);
460 std::cerr << write_string << '\n';
461 if (read_string != write_string)
462 {
463 std::cerr << "\nWrite failed!\n";
464 error = true;
465 }
466 delete[] buf;
467 std::cerr << "Testing get_value:\n";
468 int mti_dp;
469 if (!record->get_value("MTI_DP", mti_dp) || mti_dp != 2)
470 {
471 std::cerr << "Get value failed!\n";
472 error = true;
473 }
474 else
475 {
476 std::cerr << "MTI_DP = " << mti_dp << '\n';
477 }
478 int tgt_speed[4];
479 if (!record->get_value("TGT_n_SPEED", vil_nitf2_index_vector(0), tgt_speed[0]) || tgt_speed[0] != 2222 ||
480 record->get_value("TGT_n_SPEED", vil_nitf2_index_vector(1), tgt_speed[1]) /*should be null*/ ||
481 !record->get_value("TGT_n_SPEED", vil_nitf2_index_vector(2), tgt_speed[2]) || tgt_speed[2] != 4444)
482 {
483 std::cerr << "Get vector value test failed!\n";
484 error = true;
485 }
486 else
487 {
488 std::cerr << "TGT_2_SPEED = " << tgt_speed[2] << '\n';
489 }
490 int d2;
491 if (!record->get_value("D", vil_nitf2_index_vector(2), d2) || d2 != 9)
492 {
493 std::cerr << "Get fixed repeat count test failed!\n";
494 error = true;
495 }
496 // fetch C[*]
497 std::cerr << "Testing get_values (all values)...\n";
498 std::vector<std::string> c_values;
499 if (!record->get_values("C", c_values) || c_values.size() != 5 || c_values[0] != "C000" || c_values[1] != "C100" ||
500 c_values[2] != "C101" || c_values[3] != "C110" || c_values[4] != "C111")
501 {
502 std::cerr << "failed!\n\n";
503 error = true;
504 }
505 // Fetch A[1,*]
506 std::cerr << "Get values (partial index)...\n";
507 vil_nitf2_index_vector indexes;
508 std::vector<int> a_values;
509 indexes.push_back(1);
510 if (!record->get_values("A", indexes, a_values) || a_values.size() != 1 || a_values[0] != 2)
511 {
512 std::cerr << "failed!\n\n";
513 error = true;
514 }
515 // Fetch C[1,*]
516 if (!record->get_values("C", indexes, c_values) || c_values.size() != 4 || c_values[0] != "C100" ||
517 c_values[1] != "C101" || c_values[2] != "C110" || c_values[3] != "C111")
518 {
519 std::cerr << "failed!\n\n";
520 }
521 }
522 else
523 {
524 std::cerr << "Didn't create record!\n";
525 error = true;
526 }
527 // Try output of vector field
528 std::cerr << "Output of vector field C:\n" << *(record->get_field("C"));
529 // Clean up test definition and test cleanup
530 if (!vil_nitf2_tagged_record_definition::undefine(test_tre_tag))
531 {
532 std::cerr << "Error undefining TRE.\n";
533 error = true;
534 }
535 return !error;
536 }
537
538 std::ostream &
output(std::ostream & os) const539 vil_nitf2_tagged_record::output(std::ostream & os) const
540 {
541 os << "CETAG: " << name() << '\n' << "CELEN: " << length() << std::endl;
542 for (auto & m_field_definition : *m_definition->m_field_definitions)
543 {
544 vil_nitf2_field_definition * field_def = m_field_definition->field_definition();
545 // to do: handle other nodes
546 if (!field_def)
547 break;
548 vil_nitf2_field * field = get_field(field_def->tag);
549 os << field_def->tag << ": ";
550 if (field)
551 {
552 os << *field << std::endl;
553 }
554 else
555 {
556 os << "(undefined)" << std::endl;
557 }
558 }
559 return os;
560 }
561
562 bool
write(vil_nitf2_ostream & output)563 vil_nitf2_tagged_record::write(vil_nitf2_ostream & output)
564 {
565 // To track of how much is written
566 vil_streampos start = output.tell();
567 // Write tag and length fields
568 if (m_tag_field && m_length_field)
569 {
570 m_tag_field->write(output);
571 m_length_field->write(output);
572 }
573 else
574 return false;
575 // Write data fields
576 m_field_sequence->write(output);
577 // Check whether the std::right amount was written
578 vil_streampos end = output.tell();
579 vil_streampos length_written = end - start;
580 int expected_length = s_tag_formatter().field_width + s_length_formatter().field_width + length();
581 return length_written == expected_length;
582 }
583
~vil_nitf2_tagged_record()584 vil_nitf2_tagged_record::~vil_nitf2_tagged_record()
585 {
586 delete m_field_sequence;
587 }
588
589 vil_nitf2_field_definition *
find_field_definition(const std::string & tag)590 vil_nitf2_field_sequence::find_field_definition(const std::string & tag)
591 {
592 for (auto m_field_definition : *m_field_definitions)
593 {
594 vil_nitf2_field_definition * field_def = m_field_definition->field_definition();
595 // to do: search other nodes
596 if (!field_def)
597 break;
598
599 if (field_def->tag == tag)
600 {
601 return field_def;
602 }
603 }
604 // tag definition not found
605 return nullptr;
606 }
607
608 vil_nitf2_field::field_tree *
get_tree() const609 vil_nitf2_tagged_record::get_tree() const
610 {
611 // create our tree
612 // we add the field definitions if the TRE was recognized, or we note that we
613 // skipped it otherwise
614 vil_nitf2_field::field_tree * tr;
615 if (m_field_sequence)
616 {
617 tr = m_field_sequence->get_tree();
618 }
619 else
620 {
621 tr = new vil_nitf2_field::field_tree;
622 auto * skipped_node = new vil_nitf2_field::field_tree;
623 skipped_node->columns.emplace_back("CEDATA");
624 skipped_node->columns.emplace_back("<skipped unknown TRE>");
625 tr->children.push_back(skipped_node);
626 }
627
628 // add the columns describing the name of the TRE
629 tr->columns.push_back(name());
630 tr->columns.push_back(pretty_name());
631 // add the CEL (length) field to the front
632 auto * first_child = new vil_nitf2_field::field_tree;
633 first_child->columns.emplace_back("CEL");
634 first_child->columns.emplace_back("Extension Length");
635 std::stringstream len_stream;
636 len_stream << length();
637 first_child->columns.push_back(len_stream.str());
638 tr->children.insert(tr->children.begin(), first_child);
639 return tr;
640 }
641
642 std::ostream &
operator <<(std::ostream & os,const vil_nitf2_tagged_record & record)643 operator<<(std::ostream & os, const vil_nitf2_tagged_record & record)
644 {
645 return record.output(os);
646 }
647
648 // vil_nitf2_tagged_record_sequence
649
650 std::ostream &
operator <<(std::ostream & os,const vil_nitf2_tagged_record_sequence & seq)651 operator<<(std::ostream & os, const vil_nitf2_tagged_record_sequence & seq)
652 {
653 os << seq.size() << " TRE's:" << std::endl;
654 vil_nitf2_tagged_record_sequence::const_iterator it;
655 for (it = seq.begin(); it != seq.end(); ++it)
656 {
657 os << *it << std::endl;
658 }
659 return os;
660 }
661