Skip to main content

dicom_object/
mem.rs

1//! This module contains the implementation for an in-memory DICOM object.
2//!
3//! Use [`InMemDicomObject`] for your DICOM data set construction needs.
4//! Values of this type support infallible insertion, removal, and retrieval
5//! of elements by DICOM tag,
6//! or name (keyword) with a data element dictionary look-up.
7//!
8//! If you wish to build a complete DICOM file,
9//! you can start from an `InMemDicomObject`
10//! and complement it with a [file meta group table](crate::meta)
11//! (see [`with_meta`](InMemDicomObject::with_meta)
12//! and [`with_exact_meta`](InMemDicomObject::with_exact_meta)).
13//!
14//! # Example
15//!
16//! A new DICOM data set can be built by providing a sequence of data elements.
17//! Insertion and removal methods are also available.
18//!
19//! ```
20//! # use dicom_core::{DataElement, VR, dicom_value};
21//! # use dicom_dictionary_std::tags;
22//! # use dicom_dictionary_std::uids;
23//! # use dicom_object::InMemDicomObject;
24//! let mut obj = InMemDicomObject::from_element_iter([
25//!     DataElement::new(tags::SOP_CLASS_UID, VR::UI, uids::COMPUTED_RADIOGRAPHY_IMAGE_STORAGE),
26//!     DataElement::new(tags::SOP_INSTANCE_UID, VR::UI, "2.25.60156688944589400766024286894543900794"),
27//!     // ...
28//! ]);
29//!
30//! // continue adding elements
31//! obj.put(DataElement::new(tags::MODALITY, VR::CS, "CR"));
32//! ```
33//!
34//! In-memory DICOM objects may have a byte length recorded,
35//! if it was part of a data set sequence with explicit length.
36//! If necessary, this number can be obtained via the [`HasLength`] trait.
37//! However, any modifications made to the object will reset this length
38//! to [_undefined_](dicom_core::Length::UNDEFINED).
39use dicom_core::ops::{
40    ApplyOp, AttributeAction, AttributeOp, AttributeSelector, AttributeSelectorStep,
41};
42use dicom_encoding::Codec;
43use dicom_parser::dataset::read::{DataSetReaderOptions, OddLengthStrategy};
44use dicom_parser::dataset::write::DataSetWriterOptions;
45use dicom_parser::stateful::decode::CharacterSetOverride;
46use itertools::Itertools;
47use smallvec::SmallVec;
48use snafu::{OptionExt, ResultExt, ensure};
49use std::borrow::Cow;
50use std::fs::File;
51use std::io::{BufRead, BufReader, Read};
52use std::path::Path;
53use std::{collections::BTreeMap, io::Write};
54
55use crate::file::ReadPreamble;
56use crate::ops::{
57    ApplyError, ApplyResult, IncompatibleTypesSnafu, ModifySnafu, UnsupportedActionSnafu,
58};
59use crate::{
60    AccessByNameError, AccessError, AtAccessError, BuildMetaTableSnafu, CreateParserSnafu,
61    CreatePrinterSnafu, DicomObject, ElementNotFoundSnafu, FileDicomObject, InvalidGroupSnafu,
62    MissingElementValueSnafu, MissingLeafElementSnafu, NoSpaceSnafu, NoSuchAttributeNameSnafu,
63    NoSuchDataElementAliasSnafu, NoSuchDataElementTagSnafu, NotASequenceSnafu, OpenFileSnafu,
64    ParseMetaDataSetSnafu, ParseSopAttributeSnafu, PrematureEndSnafu, PrepareMetaTableSnafu,
65    PrintDataSetSnafu, PrivateCreatorNotFoundSnafu, PrivateElementError, ReadError, ReadFileSnafu,
66    ReadPreambleBytesSnafu, ReadTokenSnafu, ReadUnrecognizedTransferSyntaxSnafu,
67    ReadUnsupportedTransferSyntaxSnafu, ReadUnsupportedTransferSyntaxWithSuggestionSnafu,
68    UnexpectedTokenSnafu, WithMetaError, WriteError,
69};
70use crate::{FileMetaTableBuilder, meta::FileMetaTable};
71use dicom_core::dictionary::{DataDictionary, DataDictionaryEntry};
72use dicom_core::header::{GroupNumber, HasLength, Header};
73use dicom_core::value::{C, DataSetSequence, PixelFragmentSequence, Value, ValueType};
74use dicom_core::{DataElement, Length, PrimitiveValue, Tag, VR};
75use dicom_dictionary_std::{StandardDataDictionary, tags, uids};
76use dicom_encoding::transfer_syntax::TransferSyntaxIndex;
77use dicom_encoding::{TransferSyntax, encode::EncodeTo, text::SpecificCharacterSet};
78use dicom_parser::dataset::{DataSetReader, DataToken, IntoTokensOptions};
79use dicom_parser::{
80    StatefulDecode,
81    dataset::{DataSetWriter, IntoTokens, read::Error as ParserError},
82};
83use dicom_transfer_syntax_registry::TransferSyntaxRegistry;
84
85/// A full in-memory DICOM data element.
86pub type InMemElement<D = StandardDataDictionary> = DataElement<InMemDicomObject<D>, InMemFragment>;
87
88/// The type of a pixel data fragment.
89pub type InMemFragment = dicom_core::value::InMemFragment;
90
91type Result<T, E = AccessError> = std::result::Result<T, E>;
92
93type ParserResult<T> = std::result::Result<T, ParserError>;
94
95/// A DICOM object that is fully contained in memory.
96///
97/// See the [module-level documentation](self)
98/// for more details.
99#[derive(Debug, Clone)]
100pub struct InMemDicomObject<D = StandardDataDictionary> {
101    /// the element map
102    entries: BTreeMap<Tag, InMemElement<D>>,
103    /// the data dictionary
104    dict: D,
105    /// The length of the DICOM object in bytes.
106    /// It is usually undefined, unless it is part of an item
107    /// in a sequence with a specified length in its item header.
108    len: Length,
109    /// In case the SpecificCharSet changes we need to mark the object as dirty,
110    /// because changing the character set may change the length in bytes of
111    /// stored text. It has to be public for now because we need
112    pub(crate) charset_changed: bool,
113}
114
115impl<D> PartialEq for InMemDicomObject<D> {
116    // This implementation ignores the data dictionary.
117    fn eq(&self, other: &Self) -> bool {
118        self.entries == other.entries
119    }
120}
121
122impl<D> HasLength for InMemDicomObject<D> {
123    fn length(&self) -> Length {
124        self.len
125    }
126}
127
128impl<D> HasLength for &InMemDicomObject<D> {
129    fn length(&self) -> Length {
130        self.len
131    }
132}
133
134impl<D> DicomObject for InMemDicomObject<D>
135where
136    D: DataDictionary,
137    D: Clone,
138{
139    type Attribute<'a>
140        = &'a Value<InMemDicomObject<D>, InMemFragment>
141    where
142        Self: 'a;
143
144    type LeafAttribute<'a>
145        = &'a Value<InMemDicomObject<D>, InMemFragment>
146    where
147        Self: 'a;
148
149    #[inline]
150    fn attr_opt(&self, tag: Tag) -> Result<Option<Self::Attribute<'_>>> {
151        let elem = InMemDicomObject::element_opt(self, tag)?;
152        Ok(elem.map(|e| e.value()))
153    }
154
155    #[inline]
156    fn attr_by_name_opt(
157        &self,
158        name: &str,
159    ) -> Result<Option<Self::Attribute<'_>>, AccessByNameError> {
160        let elem = InMemDicomObject::element_by_name_opt(self, name)?;
161        Ok(elem.map(|e| e.value()))
162    }
163
164    #[inline]
165    fn attr(&self, tag: Tag) -> Result<Self::Attribute<'_>> {
166        let elem = InMemDicomObject::element(self, tag)?;
167        Ok(elem.value())
168    }
169
170    #[inline]
171    fn attr_by_name(&self, name: &str) -> Result<Self::Attribute<'_>, AccessByNameError> {
172        let elem = InMemDicomObject::element_by_name(self, name)?;
173        Ok(elem.value())
174    }
175
176    #[inline]
177    fn at(
178        &self,
179        selector: impl Into<AttributeSelector>,
180    ) -> Result<Self::LeafAttribute<'_>, AtAccessError> {
181        self.value_at(selector)
182    }
183}
184
185impl<'s, D: 's> DicomObject for &'s InMemDicomObject<D>
186where
187    D: DataDictionary,
188    D: Clone,
189{
190    type Attribute<'a>
191        = &'a Value<InMemDicomObject<D>, InMemFragment>
192    where
193        Self: 'a,
194        's: 'a;
195
196    type LeafAttribute<'a>
197        = &'a Value<InMemDicomObject<D>, InMemFragment>
198    where
199        Self: 'a,
200        's: 'a;
201
202    #[inline]
203    fn attr_opt(&self, tag: Tag) -> Result<Option<Self::Attribute<'_>>> {
204        let elem = InMemDicomObject::element_opt(*self, tag)?;
205        Ok(elem.map(|e| e.value()))
206    }
207
208    #[inline]
209    fn attr_by_name_opt(
210        &self,
211        name: &str,
212    ) -> Result<Option<Self::Attribute<'_>>, AccessByNameError> {
213        let elem = InMemDicomObject::element_by_name_opt(*self, name)?;
214        Ok(elem.map(|e| e.value()))
215    }
216
217    #[inline]
218    fn attr(&self, tag: Tag) -> Result<Self::Attribute<'_>> {
219        let elem = InMemDicomObject::element(*self, tag)?;
220        Ok(elem.value())
221    }
222
223    #[inline]
224    fn attr_by_name(&self, name: &str) -> Result<Self::Attribute<'_>, AccessByNameError> {
225        let elem = InMemDicomObject::element_by_name(*self, name)?;
226        Ok(elem.value())
227    }
228
229    #[inline]
230    fn at(
231        &self,
232        selector: impl Into<AttributeSelector>,
233    ) -> Result<Self::LeafAttribute<'_>, AtAccessError> {
234        self.value_at(selector)
235    }
236}
237
238impl FileDicomObject<InMemDicomObject<StandardDataDictionary>> {
239    /// Create a DICOM object by reading from a file.
240    ///
241    /// This function assumes the standard file encoding structure:
242    /// first it automatically detects whether the 128-byte preamble is present,
243    /// skipping it if found.
244    /// Then it reads the file meta group,
245    /// followed by the rest of the data set.
246    pub fn open_file<P: AsRef<Path>>(path: P) -> Result<Self, ReadError> {
247        Self::open_file_with_dict(path, StandardDataDictionary)
248    }
249
250    /// Create a DICOM object by reading from a byte source.
251    ///
252    /// This function assumes the standard file encoding structure:
253    /// first it automatically detects whether the 128-byte preamble is present,
254    /// skipping it if found.
255    /// Then it reads the file meta group,
256    /// followed by the rest of the data set.
257    pub fn from_reader<S>(src: S) -> Result<Self, ReadError>
258    where
259        S: Read,
260    {
261        Self::from_reader_with_dict(src, StandardDataDictionary)
262    }
263}
264
265impl InMemDicomObject<StandardDataDictionary> {
266    /// Create a new empty DICOM object.
267    pub fn new_empty() -> Self {
268        InMemDicomObject {
269            entries: BTreeMap::new(),
270            dict: StandardDataDictionary,
271            len: Length::UNDEFINED,
272            charset_changed: false,
273        }
274    }
275
276    /// Construct a DICOM object from a fallible source of structured elements.
277    #[inline]
278    pub fn from_element_source<I>(iter: I) -> Result<Self>
279    where
280        I: IntoIterator<Item = Result<InMemElement<StandardDataDictionary>>>,
281    {
282        Self::from_element_source_with_dict(iter, StandardDataDictionary)
283    }
284
285    /// Construct a DICOM object from a non-fallible source of structured elements.
286    #[inline]
287    pub fn from_element_iter<I>(iter: I) -> Self
288    where
289        I: IntoIterator<Item = InMemElement<StandardDataDictionary>>,
290    {
291        Self::from_iter_with_dict(iter, StandardDataDictionary)
292    }
293
294    /// Construct a DICOM object representing a command set,
295    /// from a non-fallible iterator of structured elements.
296    ///
297    /// This method will automatically insert
298    /// a _Command Group Length_ (0000,0000) element
299    /// based on the command elements found in the sequence.
300    #[inline]
301    pub fn command_from_element_iter<I>(iter: I) -> Self
302    where
303        I: IntoIterator<Item = InMemElement<StandardDataDictionary>>,
304    {
305        Self::command_from_iter_with_dict(iter, StandardDataDictionary)
306    }
307
308    /// Read an object from a source using the given decoder.
309    ///
310    /// Note: [`read_dataset_with_ts`] and [`read_dataset_with_ts_cs`]
311    /// may be easier to use.
312    ///
313    /// [`read_dataset_with_ts`]: InMemDicomObject::read_dataset_with_ts
314    /// [`read_dataset_with_ts_cs`]: InMemDicomObject::read_dataset_with_ts_cs
315    #[inline]
316    pub fn read_dataset<S>(decoder: S) -> Result<Self, ReadError>
317    where
318        S: StatefulDecode,
319    {
320        Self::read_dataset_with_dict(decoder, StandardDataDictionary)
321    }
322
323    /// Read an object from a source,
324    /// using the given transfer syntax and default character set.
325    ///
326    /// If the attribute _Specific Character Set_ is found in the encoded data,
327    /// this will override the given character set.
328    #[inline]
329    pub fn read_dataset_with_ts_cs<S>(
330        from: S,
331        ts: &TransferSyntax,
332        cs: SpecificCharacterSet,
333    ) -> Result<Self, ReadError>
334    where
335        S: Read,
336    {
337        Self::read_dataset_with_dict_ts_cs(from, StandardDataDictionary, ts, cs)
338    }
339
340    /// Read an object from a source,
341    /// using the given transfer syntax.
342    ///
343    /// The default character set is assumed
344    /// until _Specific Character Set_ is found in the encoded data,
345    /// after which the text decoder will be overridden accordingly.
346    #[inline]
347    pub fn read_dataset_with_ts<S>(from: S, ts: &TransferSyntax) -> Result<Self, ReadError>
348    where
349        S: Read,
350    {
351        Self::read_dataset_with_dict_ts_cs(
352            from,
353            StandardDataDictionary,
354            ts,
355            SpecificCharacterSet::default(),
356        )
357    }
358}
359
360impl<D> FileDicomObject<InMemDicomObject<D>>
361where
362    D: DataDictionary,
363    D: Clone,
364{
365    /// Create a new empty object, using the given dictionary and
366    /// file meta table.
367    pub fn new_empty_with_dict_and_meta(dict: D, meta: FileMetaTable) -> Self {
368        FileDicomObject {
369            meta,
370            obj: InMemDicomObject {
371                entries: BTreeMap::new(),
372                dict,
373                len: Length::UNDEFINED,
374                charset_changed: false,
375            },
376        }
377    }
378
379    /// Create a DICOM object by reading from a file.
380    ///
381    /// This function assumes the standard file encoding structure:
382    /// first it automatically detects whether the 128-byte preamble is present,
383    /// skipping it when found.
384    /// Then it reads the file meta group,
385    /// followed by the rest of the data set.
386    pub fn open_file_with_dict<P: AsRef<Path>>(path: P, dict: D) -> Result<Self, ReadError> {
387        Self::open_file_with(path, dict, TransferSyntaxRegistry)
388    }
389
390    /// Create a DICOM object by reading from a file.
391    ///
392    /// This function assumes the standard file encoding structure:
393    /// first it automatically detects whether the 128-byte preamble is present,
394    /// skipping it when found.
395    /// Then it reads the file meta group,
396    /// followed by the rest of the data set.
397    ///
398    /// This function allows you to choose a different transfer syntax index,
399    /// but its use is only advised when the built-in transfer syntax registry
400    /// is insufficient. Otherwise, please use [`open_file_with_dict`] instead.
401    ///
402    /// [`open_file_with_dict`]: #method.open_file_with_dict
403    pub fn open_file_with<P, R>(path: P, dict: D, ts_index: R) -> Result<Self, ReadError>
404    where
405        P: AsRef<Path>,
406        R: TransferSyntaxIndex,
407    {
408        Self::open_file_with_all_options(
409            path,
410            dict,
411            ts_index,
412            None,
413            None,
414            ReadPreamble::Auto,
415            Default::default(),
416            Default::default(),
417        )
418    }
419
420    #[allow(clippy::too_many_arguments)]
421    pub(crate) fn open_file_with_all_options<P, R>(
422        path: P,
423        dict: D,
424        ts_index: R,
425        read_until: Option<Tag>,
426        read_to: Option<Tag>,
427        mut read_preamble: ReadPreamble,
428        odd_length: OddLengthStrategy,
429        charset_override: CharacterSetOverride,
430    ) -> Result<Self, ReadError>
431    where
432        P: AsRef<Path>,
433        R: TransferSyntaxIndex,
434    {
435        let path = path.as_ref();
436        let mut file =
437            BufReader::new(File::open(path).with_context(|_| OpenFileSnafu { filename: path })?);
438
439        if read_preamble == ReadPreamble::Auto {
440            read_preamble = Self::detect_preamble(&mut file)
441                .with_context(|_| ReadFileSnafu { filename: path })?;
442        }
443
444        if read_preamble == ReadPreamble::Auto || read_preamble == ReadPreamble::Always {
445            let mut buf = [0u8; 128];
446            // skip the preamble
447            file.read_exact(&mut buf)
448                .with_context(|_| ReadFileSnafu { filename: path })?;
449        }
450
451        Self::read_parts_with_all_options_impl(
452            file,
453            dict,
454            ts_index,
455            read_until,
456            read_to,
457            odd_length,
458            charset_override,
459        )
460    }
461
462    /// Create a DICOM object by reading from a byte source.
463    ///
464    /// This function assumes the standard file encoding structure:
465    /// first it automatically detects whether the 128-byte preamble is present,
466    /// skipping it when found.
467    /// Then it reads the file meta group,
468    /// followed by the rest of the data set.
469    pub fn from_reader_with_dict<S>(src: S, dict: D) -> Result<Self, ReadError>
470    where
471        S: Read,
472    {
473        Self::from_reader_with(src, dict, TransferSyntaxRegistry)
474    }
475
476    /// Create a DICOM object by reading from a byte source.
477    ///
478    /// This function assumes the standard file encoding structure:
479    /// first it automatically detects whether the preamble is present,
480    /// skipping it when found.
481    /// Then it reads the file meta group,
482    /// followed by the rest of the data set.
483    ///
484    /// This function allows you to choose a different transfer syntax index,
485    /// but its use is only advised when the built-in transfer syntax registry
486    /// is insufficient. Otherwise, please use [`from_reader_with_dict`] instead.
487    ///
488    /// [`from_reader_with_dict`]: #method.from_reader_with_dict
489    pub fn from_reader_with<S, R>(src: S, dict: D, ts_index: R) -> Result<Self, ReadError>
490    where
491        S: Read,
492        R: TransferSyntaxIndex,
493    {
494        Self::from_reader_with_all_options(
495            src,
496            dict,
497            ts_index,
498            None,
499            None,
500            ReadPreamble::Auto,
501            Default::default(),
502            Default::default(),
503        )
504    }
505
506    #[allow(clippy::too_many_arguments)]
507    pub(crate) fn from_reader_with_all_options<S, R>(
508        src: S,
509        dict: D,
510        ts_index: R,
511        read_until: Option<Tag>,
512        read_to: Option<Tag>,
513        mut read_preamble: ReadPreamble,
514        odd_length: OddLengthStrategy,
515        charset_override: CharacterSetOverride,
516    ) -> Result<Self, ReadError>
517    where
518        S: Read,
519        R: TransferSyntaxIndex,
520    {
521        let mut file = BufReader::new(src);
522
523        if read_preamble == ReadPreamble::Auto {
524            read_preamble = Self::detect_preamble(&mut file).context(ReadPreambleBytesSnafu)?;
525        }
526
527        if read_preamble == ReadPreamble::Always {
528            // skip preamble
529            let mut buf = [0u8; 128];
530            // skip the preamble
531            file.read_exact(&mut buf).context(ReadPreambleBytesSnafu)?;
532        }
533
534        Self::read_parts_with_all_options_impl(
535            file,
536            dict,
537            ts_index,
538            read_until,
539            read_to,
540            odd_length,
541            charset_override,
542        )
543    }
544
545    // detect the presence of a preamble
546    // and provide a better `ReadPreamble` option accordingly
547    fn detect_preamble<S>(reader: &mut BufReader<S>) -> std::io::Result<ReadPreamble>
548    where
549        S: Read,
550    {
551        let buf = reader.fill_buf()?;
552        let buflen = buf.len();
553
554        if buflen < 4 {
555            return Err(std::io::ErrorKind::UnexpectedEof.into());
556        }
557
558        if buflen >= 132 && &buf[128..132] == b"DICM" {
559            return Ok(ReadPreamble::Always);
560        }
561
562        if &buf[0..4] == b"DICM" {
563            return Ok(ReadPreamble::Never);
564        }
565
566        // could not detect
567        Ok(ReadPreamble::Auto)
568    }
569
570    /// Common implementation for reading the file meta group
571    /// and the main data set (expects no preamble and no magic code),
572    /// according to the file's transfer syntax and the given options.
573    ///
574    /// If Media Storage SOP Class UID or Media Storage SOP Instance UID
575    /// are missing in the file meta group,
576    /// this function will attempt to populate them from the main data set.
577    fn read_parts_with_all_options_impl<S, R>(
578        mut src: BufReader<S>,
579        dict: D,
580        ts_index: R,
581        read_until: Option<Tag>,
582        read_to: Option<Tag>,
583        odd_length: OddLengthStrategy,
584        charset_override: CharacterSetOverride,
585    ) -> Result<Self, ReadError>
586    where
587        S: Read,
588        R: TransferSyntaxIndex,
589    {
590        // read metadata header
591        let mut meta = FileMetaTable::from_reader(&mut src).context(ParseMetaDataSetSnafu)?;
592
593        let ts_uid = meta.transfer_syntax();
594        // read rest of data according to metadata, feed it to object
595        if let Some(ts) = ts_index.get(ts_uid) {
596            let mut options = DataSetReaderOptions::default();
597            options.odd_length = odd_length;
598            options.charset_override = charset_override;
599
600            let obj = match ts.codec() {
601                Codec::Dataset(Some(adapter)) => {
602                    let adapter = adapter.adapt_reader(Box::new(src));
603                    let mut dataset = DataSetReader::new_with_ts_options(adapter, ts, options)
604                        .context(CreateParserSnafu)?;
605                    InMemDicomObject::build_object(
606                        &mut dataset,
607                        dict,
608                        false,
609                        Length::UNDEFINED,
610                        read_until,
611                        read_to,
612                    )?
613                }
614                Codec::Dataset(None) => {
615                    if ts_uid == uids::DEFLATED_EXPLICIT_VR_LITTLE_ENDIAN
616                        || ts_uid == uids::JPIP_REFERENCED_DEFLATE
617                        || ts_uid == uids::JPIPHTJ2K_REFERENCED_DEFLATE
618                    {
619                        return ReadUnsupportedTransferSyntaxWithSuggestionSnafu {
620                            uid: ts.uid(),
621                            name: ts.name(),
622                            feature_name: "dicom-transfer-syntax-registry/deflate",
623                        }
624                        .fail();
625                    }
626
627                    return ReadUnsupportedTransferSyntaxSnafu {
628                        uid: ts.uid(),
629                        name: ts.name(),
630                    }
631                    .fail();
632                }
633                Codec::None | Codec::EncapsulatedPixelData(..) => {
634                    let mut dataset = DataSetReader::new_with_ts_options(src, ts, options)
635                        .context(CreateParserSnafu)?;
636                    InMemDicomObject::build_object(
637                        &mut dataset,
638                        dict,
639                        false,
640                        Length::UNDEFINED,
641                        read_until,
642                        read_to,
643                    )?
644                }
645            };
646
647            // if Media Storage SOP Class UID is empty attempt to infer from SOP Class UID
648            if meta.media_storage_sop_class_uid().is_empty() {
649                if let Some(elem) = obj.get(tags::SOP_CLASS_UID) {
650                    meta.media_storage_sop_class_uid = elem
651                        .value()
652                        .to_str()
653                        .context(ParseSopAttributeSnafu)?
654                        .to_string();
655                }
656            }
657
658            // if Media Storage SOP Instance UID is empty attempt to infer from SOP Instance UID
659            if meta.media_storage_sop_instance_uid().is_empty() {
660                if let Some(elem) = obj.get(tags::SOP_INSTANCE_UID) {
661                    meta.media_storage_sop_instance_uid = elem
662                        .value()
663                        .to_str()
664                        .context(ParseSopAttributeSnafu)?
665                        .to_string();
666                }
667            }
668
669            Ok(FileDicomObject { meta, obj })
670        } else {
671            ReadUnrecognizedTransferSyntaxSnafu {
672                uid: ts_uid.to_string(),
673            }
674            .fail()
675        }
676    }
677}
678
679impl FileDicomObject<InMemDicomObject<StandardDataDictionary>> {
680    /// Create a new empty object, using the given file meta table.
681    pub fn new_empty_with_meta(meta: FileMetaTable) -> Self {
682        FileDicomObject {
683            meta,
684            obj: InMemDicomObject {
685                entries: BTreeMap::new(),
686                dict: StandardDataDictionary,
687                len: Length::UNDEFINED,
688                charset_changed: false,
689            },
690        }
691    }
692}
693
694impl<D> InMemDicomObject<D>
695where
696    D: DataDictionary,
697    D: Clone,
698{
699    /// Create a new empty object, using the given dictionary for name lookup.
700    pub fn new_empty_with_dict(dict: D) -> Self {
701        InMemDicomObject {
702            entries: BTreeMap::new(),
703            dict,
704            len: Length::UNDEFINED,
705            charset_changed: false,
706        }
707    }
708
709    /// Construct a DICOM object from an iterator of structured elements.
710    pub fn from_element_source_with_dict<I>(iter: I, dict: D) -> Result<Self>
711    where
712        I: IntoIterator<Item = Result<InMemElement<D>>>,
713    {
714        let entries: Result<_> = iter.into_iter().map_ok(|e| (e.tag(), e)).collect();
715        Ok(InMemDicomObject {
716            entries: entries?,
717            dict,
718            len: Length::UNDEFINED,
719            charset_changed: false,
720        })
721    }
722
723    /// Construct a DICOM object from a non-fallible iterator of structured elements.
724    pub fn from_iter_with_dict<I>(iter: I, dict: D) -> Self
725    where
726        I: IntoIterator<Item = InMemElement<D>>,
727    {
728        let entries = iter.into_iter().map(|e| (e.tag(), e)).collect();
729        InMemDicomObject {
730            entries,
731            dict,
732            len: Length::UNDEFINED,
733            charset_changed: false,
734        }
735    }
736
737    /// Construct a DICOM object representing a command set,
738    /// from a non-fallible iterator of structured elements.
739    ///
740    /// This method will automatically insert
741    /// a _Command Group Length_ (0000,0000) element
742    /// based on the command elements found in the sequence.
743    pub fn command_from_iter_with_dict<I>(iter: I, dict: D) -> Self
744    where
745        I: IntoIterator<Item = InMemElement<D>>,
746    {
747        let mut calculated_length: u32 = 0;
748        let mut entries: BTreeMap<_, _> = iter
749            .into_iter()
750            .map(|e| {
751                // count the length of command set elements
752                if e.tag().0 == 0x0000 && e.tag().1 != 0x0000 {
753                    let l = e.value().length();
754                    calculated_length += if l.is_defined() { even_len(l.0) } else { 0 } + 8;
755                }
756
757                (e.tag(), e)
758            })
759            .collect();
760
761        entries.insert(
762            Tag(0, 0),
763            InMemElement::new(Tag(0, 0), VR::UL, PrimitiveValue::from(calculated_length)),
764        );
765
766        InMemDicomObject {
767            entries,
768            dict,
769            len: Length::UNDEFINED,
770            charset_changed: false,
771        }
772    }
773
774    /// Read an object from a source,
775    /// using the given decoder
776    /// and the given dictionary for name lookup.
777    pub fn read_dataset_with_dict<S>(decoder: S, dict: D) -> Result<Self, ReadError>
778    where
779        S: StatefulDecode,
780        D: DataDictionary,
781    {
782        let mut dataset = DataSetReader::new(decoder, Default::default());
783        InMemDicomObject::build_object(&mut dataset, dict, false, Length::UNDEFINED, None, None)
784    }
785
786    /// Read an object from a source,
787    /// using the given data dictionary and transfer syntax.
788    #[inline]
789    pub fn read_dataset_with_dict_ts<S>(
790        from: S,
791        dict: D,
792        ts: &TransferSyntax,
793    ) -> Result<Self, ReadError>
794    where
795        S: Read,
796        D: DataDictionary,
797    {
798        Self::read_dataset_with_dict_ts_cs(from, dict, ts, SpecificCharacterSet::default())
799    }
800
801    /// Read an object from a source,
802    /// using the given data dictionary,
803    /// transfer syntax,
804    /// and the given character set to assume by default.
805    ///
806    /// If the attribute _Specific Character Set_ is found in the encoded data,
807    /// this will override the given character set.
808    pub fn read_dataset_with_dict_ts_cs<S>(
809        from: S,
810        dict: D,
811        ts: &TransferSyntax,
812        cs: SpecificCharacterSet,
813    ) -> Result<Self, ReadError>
814    where
815        S: Read,
816        D: DataDictionary,
817    {
818        let from = BufReader::new(from);
819
820        match ts.codec() {
821            Codec::Dataset(Some(adapter)) => {
822                let adapter = adapter.adapt_reader(Box::new(from));
823                let mut dataset =
824                    DataSetReader::new_with_ts_cs(adapter, ts, cs).context(CreateParserSnafu)?;
825                InMemDicomObject::build_object(
826                    &mut dataset,
827                    dict,
828                    false,
829                    Length::UNDEFINED,
830                    None,
831                    None,
832                )
833            }
834            Codec::Dataset(None) => {
835                let uid = ts.uid();
836                if uid == uids::DEFLATED_EXPLICIT_VR_LITTLE_ENDIAN
837                    || uid == uids::JPIP_REFERENCED_DEFLATE
838                    || uid == uids::JPIPHTJ2K_REFERENCED_DEFLATE
839                {
840                    return ReadUnsupportedTransferSyntaxWithSuggestionSnafu {
841                        uid,
842                        name: ts.name(),
843                        feature_name: "dicom-transfer-syntax-registry/deflate",
844                    }
845                    .fail();
846                }
847
848                ReadUnsupportedTransferSyntaxSnafu {
849                    uid,
850                    name: ts.name(),
851                }
852                .fail()
853            }
854            Codec::None | Codec::EncapsulatedPixelData(..) => {
855                let mut dataset =
856                    DataSetReader::new_with_ts_cs(from, ts, cs).context(CreateParserSnafu)?;
857                InMemDicomObject::build_object(
858                    &mut dataset,
859                    dict,
860                    false,
861                    Length::UNDEFINED,
862                    None,
863                    None,
864                )
865            }
866        }
867    }
868
869    // Standard methods follow. They are not placed as a trait implementation
870    // because they may require outputs to reference the lifetime of self,
871    // which is not possible without GATs.
872
873    /// Retrieve a particular DICOM element by its tag.
874    ///
875    /// An error is returned if the element does not exist.
876    /// For an alternative to this behavior,
877    /// see [`element_opt`](InMemDicomObject::element_opt).
878    pub fn element(&self, tag: Tag) -> Result<&InMemElement<D>> {
879        self.entries
880            .get(&tag)
881            .context(NoSuchDataElementTagSnafu { tag })
882    }
883
884    /// Retrieve a particular DICOM element by its name.
885    ///
886    /// This method translates the given attribute name into its tag
887    /// before retrieving the element.
888    /// If the attribute is known in advance,
889    /// using [`element`](InMemDicomObject::element)
890    /// with a tag constant is preferred.
891    ///
892    /// An error is returned if the element does not exist.
893    /// For an alternative to this behavior,
894    /// see [`element_by_name_opt`](InMemDicomObject::element_by_name_opt).
895    pub fn element_by_name(&self, name: &str) -> Result<&InMemElement<D>, AccessByNameError> {
896        let tag = self.lookup_name(name)?;
897        self.entries
898            .get(&tag)
899            .with_context(|| NoSuchDataElementAliasSnafu {
900                tag,
901                alias: name.to_string(),
902            })
903    }
904
905    /// Retrieve a particular DICOM element that might not exist by its tag.
906    ///
907    /// If the element does not exist,
908    /// `None` is returned.
909    pub fn element_opt(&self, tag: Tag) -> Result<Option<&InMemElement<D>>, AccessError> {
910        match self.element(tag) {
911            Ok(e) => Ok(Some(e)),
912            Err(super::AccessError::NoSuchDataElementTag { .. }) => Ok(None),
913        }
914    }
915
916    /// Get a particular DICOM attribute from this object by tag.
917    ///
918    /// If the element does not exist,
919    /// `None` is returned.
920    pub fn get(&self, tag: Tag) -> Option<&InMemElement<D>> {
921        self.entries.get(&tag)
922    }
923
924    // Get a mutable reference to a particular DICOM attribute from this object by tag.
925    //
926    // Should be private as it would allow a user to change the tag of an
927    // element and diverge from the dictionary
928    fn get_mut(&mut self, tag: Tag) -> Option<&mut InMemElement<D>> {
929        self.entries.get_mut(&tag)
930    }
931
932    /// Retrieve a particular DICOM element that might not exist by its name.
933    ///
934    /// If the element does not exist,
935    /// `None` is returned.
936    ///
937    /// This method translates the given attribute name into its tag
938    /// before retrieving the element.
939    /// If the attribute is known in advance,
940    /// using [`element_opt`](InMemDicomObject::element_opt)
941    /// with a tag constant is preferred.
942    pub fn element_by_name_opt(
943        &self,
944        name: &str,
945    ) -> Result<Option<&InMemElement<D>>, AccessByNameError> {
946        match self.element_by_name(name) {
947            Ok(e) => Ok(Some(e)),
948            Err(AccessByNameError::NoSuchDataElementAlias { .. }) => Ok(None),
949            Err(e) => Err(e),
950        }
951    }
952
953    fn find_private_creator(&self, group: GroupNumber, creator: &str) -> Option<&Tag> {
954        let range = Tag(group, 0)..Tag(group, 0xFF);
955        for (tag, elem) in self.entries.range(range) {
956            // Private Creators are always LO
957            // https://dicom.nema.org/medical/dicom/2024a/output/chtml/part05/sect_7.8.html
958            if elem.header().vr() == VR::LO && elem.to_str().unwrap_or_default() == creator {
959                return Some(tag);
960            }
961        }
962        None
963    }
964
965    /// Get a private element from the dataset using the group number, creator and element number.
966    ///
967    /// An error is raised when the group number is not odd,
968    /// the private creator is not found in the group,
969    /// or the private element is not found.
970    ///
971    /// For more info, see the [DICOM standard section on private elements][1].
972    ///
973    /// [1]: https://dicom.nema.org/medical/dicom/2024a/output/chtml/part05/sect_7.8.html
974    ///
975    /// ## Example
976    ///
977    /// ```
978    /// # use dicom_core::{VR, PrimitiveValue, Tag, DataElement};
979    /// # use dicom_object::{InMemDicomObject, PrivateElementError};
980    /// # use std::error::Error;
981    /// let mut ds = InMemDicomObject::from_element_iter([
982    ///     DataElement::new(
983    ///         Tag(0x0009, 0x0010),
984    ///         VR::LO,
985    ///         PrimitiveValue::from("CREATOR 1"),
986    ///     ),
987    ///     DataElement::new(Tag(0x0009, 0x1001), VR::DS, "1.0"),
988    /// ]);
989    /// assert_eq!(
990    ///     ds.private_element(0x0009, "CREATOR 1", 0x01)?
991    ///         .value()
992    ///         .to_str()?,
993    ///     "1.0"
994    /// );
995    /// # Ok::<(), Box<dyn Error>>(())
996    /// ```
997    pub fn private_element(
998        &self,
999        group: GroupNumber,
1000        creator: &str,
1001        element: u8,
1002    ) -> Result<&InMemElement<D>, PrivateElementError> {
1003        let tag = self.find_private_creator(group, creator).ok_or_else(|| {
1004            PrivateCreatorNotFoundSnafu {
1005                group,
1006                creator: creator.to_string(),
1007            }
1008            .build()
1009        })?;
1010
1011        let element_num = (tag.element() << 8) | (element as u16);
1012        self.get(Tag(group, element_num)).ok_or_else(|| {
1013            ElementNotFoundSnafu {
1014                group,
1015                creator: creator.to_string(),
1016                elem: element,
1017            }
1018            .build()
1019        })
1020    }
1021
1022    /// Insert a data element to the object, replacing (and returning) any
1023    /// previous element of the same attribute.
1024    /// This might invalidate all sequence and item lengths if the charset of the
1025    /// element changes.
1026    pub fn put(&mut self, elt: InMemElement<D>) -> Option<InMemElement<D>> {
1027        self.put_element(elt)
1028    }
1029
1030    /// Insert a data element to the object, replacing (and returning) any
1031    /// previous element of the same attribute.
1032    /// This might invalidate all sequence and item lengths if the charset of the
1033    /// element changes.
1034    pub fn put_element(&mut self, elt: InMemElement<D>) -> Option<InMemElement<D>> {
1035        self.len = Length::UNDEFINED;
1036        self.invalidate_if_charset_changed(elt.tag());
1037        self.entries.insert(elt.tag(), elt)
1038    }
1039
1040    /// Insert a private element into the dataset, replacing (and returning) any
1041    /// previous element of the same attribute.
1042    ///
1043    /// This function will find the next available private element block in the given
1044    /// group. If the creator already exists, the element will be added to the block
1045    /// already reserved for that creator. If it does not exist, then a new block
1046    /// will be reserved for the creator in the specified group.
1047    /// An error is returned if there is no space left in the group.
1048    ///
1049    /// For more info, see the [DICOM standard section on private elements][1].
1050    ///
1051    /// [1]: https://dicom.nema.org/medical/dicom/2024a/output/chtml/part05/sect_7.8.html
1052    ///
1053    /// ## Example
1054    /// ```
1055    /// # use dicom_core::{VR, PrimitiveValue, Tag, DataElement, header::Header};
1056    /// # use dicom_object::InMemDicomObject;
1057    /// # use std::error::Error;
1058    /// let mut ds = InMemDicomObject::new_empty();
1059    /// ds.put_private_element(
1060    ///     0x0009,
1061    ///     "CREATOR 1",
1062    ///     0x02,
1063    ///     VR::DS,
1064    ///     PrimitiveValue::from("1.0"),
1065    /// )?;
1066    /// assert_eq!(
1067    ///     ds.private_element(0x0009, "CREATOR 1", 0x02)?
1068    ///         .value()
1069    ///         .to_str()?,
1070    ///     "1.0"
1071    /// );
1072    /// assert_eq!(
1073    ///     ds.private_element(0x0009, "CREATOR 1", 0x02)?
1074    ///         .header()
1075    ///         .tag(),
1076    ///     Tag(0x0009, 0x0102)
1077    /// );
1078    /// # Ok::<(), Box<dyn Error>>(())
1079    /// ```
1080    pub fn put_private_element(
1081        &mut self,
1082        group: GroupNumber,
1083        creator: &str,
1084        element: u8,
1085        vr: VR,
1086        value: PrimitiveValue,
1087    ) -> Result<Option<InMemElement<D>>, PrivateElementError> {
1088        ensure!(group % 2 == 1, InvalidGroupSnafu { group });
1089        let private_creator = self.find_private_creator(group, creator);
1090        if let Some(tag) = private_creator {
1091            // Private creator already exists
1092            let tag = Tag(group, (tag.element() << 8) | element as u16);
1093            Ok(self.put_element(DataElement::new(tag, vr, value)))
1094        } else {
1095            // Find last reserved block of tags.
1096            let range = Tag(group, 0)..Tag(group, 0xFF);
1097            let last_entry = self.entries.range(range).next_back();
1098            let next_available = match last_entry {
1099                Some((tag, _)) => tag.element() + 1,
1100                None => 0x01,
1101            };
1102            if next_available < 0xFF {
1103                // Put private creator
1104                let tag = Tag(group, next_available);
1105                self.put_str(tag, VR::LO, creator);
1106
1107                // Put private element
1108                let tag = Tag(group, (next_available << 8) | element as u16);
1109                Ok(self.put_element(DataElement::new(tag, vr, value)))
1110            } else {
1111                NoSpaceSnafu { group }.fail()
1112            }
1113        }
1114    }
1115
1116    /// Insert a new element with a string value to the object,
1117    /// replacing (and returning) any previous element of the same attribute.
1118    pub fn put_str(
1119        &mut self,
1120        tag: Tag,
1121        vr: VR,
1122        string: impl Into<String>,
1123    ) -> Option<InMemElement<D>> {
1124        self.put_element(DataElement::new(tag, vr, string.into()))
1125    }
1126
1127    /// Remove a DICOM element by its tag,
1128    /// reporting whether it was present.
1129    pub fn remove_element(&mut self, tag: Tag) -> bool {
1130        if self.entries.remove(&tag).is_some() {
1131            self.len = Length::UNDEFINED;
1132            true
1133        } else {
1134            false
1135        }
1136    }
1137
1138    /// Remove a DICOM element by its keyword,
1139    /// reporting whether it was present.
1140    pub fn remove_element_by_name(&mut self, name: &str) -> Result<bool, AccessByNameError> {
1141        let tag = self.lookup_name(name)?;
1142        Ok(self.entries.remove(&tag).is_some()).inspect(|removed| {
1143            if *removed {
1144                self.len = Length::UNDEFINED;
1145            }
1146        })
1147    }
1148
1149    /// Remove and return a particular DICOM element by its tag.
1150    pub fn take_element(&mut self, tag: Tag) -> Result<InMemElement<D>> {
1151        self.entries
1152            .remove(&tag)
1153            .inspect(|_e| {
1154                self.len = Length::UNDEFINED;
1155            })
1156            .context(NoSuchDataElementTagSnafu { tag })
1157    }
1158
1159    /// Remove and return a particular DICOM element by its tag,
1160    /// if it is present,
1161    /// returns `None` otherwise.
1162    pub fn take(&mut self, tag: Tag) -> Option<InMemElement<D>> {
1163        self.entries.remove(&tag).inspect(|_e| {
1164            self.len = Length::UNDEFINED;
1165        })
1166    }
1167
1168    /// Remove and return a particular DICOM element by its name.
1169    pub fn take_element_by_name(
1170        &mut self,
1171        name: &str,
1172    ) -> Result<InMemElement<D>, AccessByNameError> {
1173        let tag = self.lookup_name(name)?;
1174        self.entries
1175            .remove(&tag)
1176            .inspect(|_e| {
1177                self.len = Length::UNDEFINED;
1178            })
1179            .with_context(|| NoSuchDataElementAliasSnafu {
1180                tag,
1181                alias: name.to_string(),
1182            })
1183    }
1184
1185    /// Modify the object by
1186    /// retaining only the DICOM data elements specified by the predicate.
1187    ///
1188    /// The elements are visited in ascending tag order,
1189    /// and those for which `f(&element)` returns `false` are removed.
1190    pub fn retain(&mut self, mut f: impl FnMut(&InMemElement<D>) -> bool) {
1191        self.entries.retain(|_, elem| f(elem));
1192        self.len = Length::UNDEFINED;
1193    }
1194
1195    /// Obtain a temporary mutable reference to a DICOM value by tag,
1196    /// so that mutations can be applied within.
1197    ///
1198    /// If found, this method resets all related lengths recorded
1199    /// and returns `true`.
1200    /// Returns `false` otherwise.
1201    ///
1202    /// # Example
1203    ///
1204    /// ```
1205    /// # use dicom_core::{DataElement, VR, dicom_value};
1206    /// # use dicom_dictionary_std::tags;
1207    /// # use dicom_object::InMemDicomObject;
1208    /// let mut obj = InMemDicomObject::from_element_iter([
1209    ///     DataElement::new(tags::LOSSY_IMAGE_COMPRESSION_RATIO, VR::DS, dicom_value!(Strs, ["25"])),
1210    /// ]);
1211    ///
1212    /// // update lossy image compression ratio
1213    /// obj.update_value(tags::LOSSY_IMAGE_COMPRESSION_RATIO, |e| {
1214    ///     e.primitive_mut().unwrap().extend_str(["2.56"]);
1215    /// });
1216    ///
1217    /// assert_eq!(
1218    ///     obj.get(tags::LOSSY_IMAGE_COMPRESSION_RATIO).unwrap().value().to_str().unwrap(),
1219    ///     "25\\2.56"
1220    /// );
1221    /// ```
1222    pub fn update_value(
1223        &mut self,
1224        tag: Tag,
1225        f: impl FnMut(&mut Value<InMemDicomObject<D>, InMemFragment>),
1226    ) -> bool {
1227        self.invalidate_if_charset_changed(tag);
1228        if let Some(e) = self.entries.get_mut(&tag) {
1229            e.update_value(f);
1230            self.len = Length::UNDEFINED;
1231            true
1232        } else {
1233            false
1234        }
1235    }
1236
1237    /// Obtain a temporary mutable reference to a DICOM value by AttributeSelector,
1238    /// so that mutations can be applied within.
1239    ///
1240    /// If found, this method resets all related lengths recorded
1241    /// and returns `true`.
1242    /// Returns `false` otherwise.
1243    ///
1244    /// See the documentation of [`AttributeSelector`] for more information
1245    /// on how to write attribute selectors.
1246    ///
1247    /// Note: Consider using [`apply`](ApplyOp::apply) when possible.
1248    ///
1249    /// # Example
1250    ///
1251    /// ```
1252    /// # use dicom_core::{DataElement, VR, dicom_value, value::DataSetSequence};
1253    /// # use dicom_dictionary_std::tags;
1254    /// # use dicom_object::InMemDicomObject;
1255    /// # use dicom_core::ops::{AttributeAction, AttributeOp, ApplyOp};
1256    /// let mut dcm = InMemDicomObject::from_element_iter([
1257    ///     DataElement::new(
1258    ///         tags::OTHER_PATIENT_I_DS_SEQUENCE,
1259    ///         VR::SQ,
1260    ///         DataSetSequence::from(vec![InMemDicomObject::from_element_iter([
1261    ///             DataElement::new(
1262    ///                 tags::PATIENT_ID,
1263    ///                 VR::LO,
1264    ///                 dicom_value!(Str, "1234")
1265    ///             )])
1266    ///         ])
1267    ///     ),
1268    /// ]);
1269    /// let selector = (
1270    ///     tags::OTHER_PATIENT_I_DS_SEQUENCE,
1271    ///     0,
1272    ///     tags::PATIENT_ID
1273    /// );
1274    ///
1275    /// // update referenced SOP instance UID for deidentification potentially
1276    /// dcm.update_value_at(*&selector, |e| {
1277    ///     let mut v = e.primitive_mut().unwrap();
1278    ///     *v = dicom_value!(Str, "abcd");
1279    /// });
1280    ///
1281    /// assert_eq!(
1282    ///     dcm.entry_at(*&selector).unwrap().value().to_str().unwrap(),
1283    ///     "abcd"
1284    /// );
1285    /// ```
1286    pub fn update_value_at(
1287        &mut self,
1288        selector: impl Into<AttributeSelector>,
1289        f: impl FnMut(&mut Value<InMemDicomObject<D>, InMemFragment>),
1290    ) -> Result<(), AtAccessError> {
1291        self.entry_at_mut(selector)
1292            .map(|e| e.update_value(f))
1293            .map(|_| {
1294                self.len = Length::UNDEFINED;
1295            })
1296    }
1297
1298    /// Obtain the DICOM value by finding the element
1299    /// that matches the given selector.
1300    ///
1301    /// Returns an error if the respective element or any of its parents
1302    /// cannot be found.
1303    ///
1304    /// See the documentation of [`AttributeSelector`] for more information
1305    /// on how to write attribute selectors.
1306    ///
1307    /// # Example
1308    ///
1309    /// ```no_run
1310    /// # use dicom_core::prelude::*;
1311    /// # use dicom_core::ops::AttributeSelector;
1312    /// # use dicom_dictionary_std::tags;
1313    /// # use dicom_object::InMemDicomObject;
1314    /// # let obj: InMemDicomObject = unimplemented!();
1315    /// let referenced_sop_instance_iod = obj.value_at(
1316    ///     (
1317    ///         tags::SHARED_FUNCTIONAL_GROUPS_SEQUENCE,
1318    ///         tags::REFERENCED_IMAGE_SEQUENCE,
1319    ///         tags::REFERENCED_SOP_INSTANCE_UID,
1320    ///     ))?
1321    ///     .to_str()?;
1322    /// # Ok::<_, Box<dyn std::error::Error>>(())
1323    /// ```
1324    pub fn value_at(
1325        &self,
1326        selector: impl Into<AttributeSelector>,
1327    ) -> Result<&Value<InMemDicomObject<D>, InMemFragment>, AtAccessError> {
1328        let selector: AttributeSelector = selector.into();
1329
1330        let mut obj = self;
1331        for (i, step) in selector.iter().enumerate() {
1332            match step {
1333                // reached the leaf
1334                AttributeSelectorStep::Tag(tag) => {
1335                    return obj.get(*tag).map(|e| e.value()).with_context(|| {
1336                        MissingLeafElementSnafu {
1337                            selector: selector.clone(),
1338                        }
1339                    });
1340                }
1341                // navigate further down
1342                AttributeSelectorStep::Nested { tag, item } => {
1343                    let e = obj
1344                        .entries
1345                        .get(tag)
1346                        .with_context(|| crate::MissingSequenceSnafu {
1347                            selector: selector.clone(),
1348                            step_index: i as u32,
1349                        })?;
1350
1351                    // get items
1352                    let items = e.items().with_context(|| NotASequenceSnafu {
1353                        selector: selector.clone(),
1354                        step_index: i as u32,
1355                    })?;
1356
1357                    // if item.length == i and action is a constructive action, append new item
1358                    obj =
1359                        items
1360                            .get(*item as usize)
1361                            .with_context(|| crate::MissingSequenceSnafu {
1362                                selector: selector.clone(),
1363                                step_index: i as u32,
1364                            })?;
1365                }
1366            }
1367        }
1368
1369        unreachable!()
1370    }
1371
1372    /// Change the 'specific_character_set' tag to ISO_IR 192, marking the dataset as UTF-8
1373    pub fn convert_to_utf8(&mut self) {
1374        self.put(DataElement::new(
1375            tags::SPECIFIC_CHARACTER_SET,
1376            VR::CS,
1377            "ISO_IR 192",
1378        ));
1379    }
1380
1381    /// Get a DataElement by AttributeSelector
1382    ///
1383    /// If the element or other intermediate elements do not exist, the method will return an error.
1384    ///
1385    /// See the documentation of [`AttributeSelector`] for more information
1386    /// on how to write attribute selectors.
1387    ///
1388    /// If you only need the value, use [`value_at`](Self::value_at).
1389    pub fn entry_at(
1390        &self,
1391        selector: impl Into<AttributeSelector>,
1392    ) -> Result<&InMemElement<D>, AtAccessError> {
1393        let selector: AttributeSelector = selector.into();
1394
1395        let mut obj = self;
1396        for (i, step) in selector.iter().enumerate() {
1397            match step {
1398                // reached the leaf
1399                AttributeSelectorStep::Tag(tag) => {
1400                    return obj.get(*tag).with_context(|| MissingLeafElementSnafu {
1401                        selector: selector.clone(),
1402                    });
1403                }
1404                // navigate further down
1405                AttributeSelectorStep::Nested { tag, item } => {
1406                    let e = obj
1407                        .entries
1408                        .get(tag)
1409                        .with_context(|| crate::MissingSequenceSnafu {
1410                            selector: selector.clone(),
1411                            step_index: i as u32,
1412                        })?;
1413
1414                    // get items
1415                    let items = e.items().with_context(|| NotASequenceSnafu {
1416                        selector: selector.clone(),
1417                        step_index: i as u32,
1418                    })?;
1419
1420                    // if item.length == i and action is a constructive action, append new item
1421                    obj =
1422                        items
1423                            .get(*item as usize)
1424                            .with_context(|| crate::MissingSequenceSnafu {
1425                                selector: selector.clone(),
1426                                step_index: i as u32,
1427                            })?;
1428                }
1429            }
1430        }
1431
1432        unreachable!()
1433    }
1434
1435    // Get a mutable reference to a particular entry by AttributeSelector
1436    //
1437    // Should be private for the same reason as `self.get_mut`
1438    fn entry_at_mut(
1439        &mut self,
1440        selector: impl Into<AttributeSelector>,
1441    ) -> Result<&mut InMemElement<D>, AtAccessError> {
1442        let selector: AttributeSelector = selector.into();
1443
1444        let mut obj = self;
1445        for (i, step) in selector.iter().enumerate() {
1446            match step {
1447                // reached the leaf
1448                AttributeSelectorStep::Tag(tag) => {
1449                    return obj.get_mut(*tag).with_context(|| MissingLeafElementSnafu {
1450                        selector: selector.clone(),
1451                    });
1452                }
1453                // navigate further down
1454                AttributeSelectorStep::Nested { tag, item } => {
1455                    let e =
1456                        obj.entries
1457                            .get_mut(tag)
1458                            .with_context(|| crate::MissingSequenceSnafu {
1459                                selector: selector.clone(),
1460                                step_index: i as u32,
1461                            })?;
1462
1463                    // get items
1464                    let items = e.items_mut().with_context(|| NotASequenceSnafu {
1465                        selector: selector.clone(),
1466                        step_index: i as u32,
1467                    })?;
1468
1469                    // if item.length == i and action is a constructive action, append new item
1470                    obj = items.get_mut(*item as usize).with_context(|| {
1471                        crate::MissingSequenceSnafu {
1472                            selector: selector.clone(),
1473                            step_index: i as u32,
1474                        }
1475                    })?;
1476                }
1477            }
1478        }
1479
1480        unreachable!()
1481    }
1482
1483    /// Apply the given attribute operation on this object.
1484    ///
1485    /// For more complex updates, see [`update_value_at`].
1486    ///
1487    /// See the [`dicom_core::ops`] module
1488    /// for more information.
1489    ///
1490    /// # Examples
1491    ///
1492    /// ```rust
1493    /// # use dicom_core::header::{DataElement, VR};
1494    /// # use dicom_core::value::PrimitiveValue;
1495    /// # use dicom_dictionary_std::tags;
1496    /// # use dicom_object::mem::*;
1497    /// # use dicom_object::ops::ApplyResult;
1498    /// use dicom_core::ops::{ApplyOp, AttributeAction, AttributeOp};
1499    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1500    /// // given an in-memory DICOM object
1501    /// let mut obj = InMemDicomObject::from_element_iter([
1502    ///     DataElement::new(
1503    ///         tags::PATIENT_NAME,
1504    ///         VR::PN,
1505    ///         PrimitiveValue::from("Rosling^Hans")
1506    ///     ),
1507    /// ]);
1508    ///
1509    /// // apply patient name change
1510    /// obj.apply(AttributeOp::new(
1511    ///   tags::PATIENT_NAME,
1512    ///   AttributeAction::SetStr("Patient^Anonymous".into()),
1513    /// ))?;
1514    ///
1515    /// assert_eq!(
1516    ///     obj.element(tags::PATIENT_NAME)?.to_str()?,
1517    ///     "Patient^Anonymous",
1518    /// );
1519    /// # Ok(())
1520    /// # }
1521    /// ```
1522    fn apply(&mut self, op: AttributeOp) -> ApplyResult {
1523        let AttributeOp { selector, action } = op;
1524        let dict = self.dict.clone();
1525
1526        let mut obj = self;
1527        for (i, step) in selector.iter().enumerate() {
1528            match step {
1529                // reached the leaf
1530                AttributeSelectorStep::Tag(tag) => return obj.apply_leaf(*tag, action),
1531                // navigate further down
1532                AttributeSelectorStep::Nested { tag, item } => {
1533                    if !obj.entries.contains_key(tag) {
1534                        // missing sequence, create it if action is constructive
1535                        if action.is_constructive() {
1536                            let vr = dict
1537                                .by_tag(*tag)
1538                                .and_then(|entry| entry.vr().exact())
1539                                .unwrap_or(VR::UN);
1540
1541                            if vr != VR::SQ && vr != VR::UN {
1542                                return Err(ApplyError::NotASequence {
1543                                    selector: selector.clone(),
1544                                    step_index: i as u32,
1545                                });
1546                            }
1547
1548                            obj.put(DataElement::new(*tag, vr, DataSetSequence::empty()));
1549                        } else {
1550                            return Err(ApplyError::MissingSequence {
1551                                selector: selector.clone(),
1552                                step_index: i as u32,
1553                            });
1554                        }
1555                    };
1556
1557                    // get items
1558                    let items = obj
1559                        .entries
1560                        .get_mut(tag)
1561                        .expect("sequence element should exist at this point")
1562                        .items_mut()
1563                        .ok_or_else(|| ApplyError::NotASequence {
1564                            selector: selector.clone(),
1565                            step_index: i as u32,
1566                        })?;
1567
1568                    // if item.length == i and action is a constructive action, append new item
1569                    obj = if items.len() == *item as usize && action.is_constructive() {
1570                        items.push(InMemDicomObject::new_empty_with_dict(dict.clone()));
1571                        items.last_mut().unwrap()
1572                    } else {
1573                        items.get_mut(*item as usize).ok_or_else(|| {
1574                            ApplyError::MissingSequence {
1575                                selector: selector.clone(),
1576                                step_index: i as u32,
1577                            }
1578                        })?
1579                    };
1580                }
1581            }
1582        }
1583        unreachable!()
1584    }
1585
1586    fn apply_leaf(&mut self, tag: Tag, action: AttributeAction) -> ApplyResult {
1587        self.invalidate_if_charset_changed(tag);
1588        match action {
1589            AttributeAction::Remove => {
1590                self.remove_element(tag);
1591                Ok(())
1592            }
1593            AttributeAction::Empty => {
1594                if let Some(e) = self.entries.get_mut(&tag) {
1595                    let vr = e.vr();
1596                    // replace element
1597                    *e = DataElement::empty(tag, vr);
1598                    self.len = Length::UNDEFINED;
1599                }
1600                Ok(())
1601            }
1602            AttributeAction::SetVr(new_vr) => {
1603                if let Some(e) = self.entries.remove(&tag) {
1604                    let (header, value) = e.into_parts();
1605                    let e = DataElement::new(header.tag, new_vr, value);
1606                    self.put(e);
1607                } else {
1608                    self.put(DataElement::empty(tag, new_vr));
1609                }
1610                Ok(())
1611            }
1612            AttributeAction::Set(new_value) => {
1613                self.apply_change_value_impl(tag, new_value);
1614                Ok(())
1615            }
1616            AttributeAction::SetStr(string) => {
1617                let new_value = PrimitiveValue::from(&*string);
1618                self.apply_change_value_impl(tag, new_value);
1619                Ok(())
1620            }
1621            AttributeAction::SetIfMissing(new_value) => {
1622                if self.get(tag).is_none() {
1623                    self.apply_change_value_impl(tag, new_value);
1624                }
1625                Ok(())
1626            }
1627            AttributeAction::SetStrIfMissing(string) => {
1628                if self.get(tag).is_none() {
1629                    let new_value = PrimitiveValue::from(&*string);
1630                    self.apply_change_value_impl(tag, new_value);
1631                }
1632                Ok(())
1633            }
1634            AttributeAction::Replace(new_value) => {
1635                if self.get(tag).is_some() {
1636                    self.apply_change_value_impl(tag, new_value);
1637                }
1638                Ok(())
1639            }
1640            AttributeAction::ReplaceStr(string) => {
1641                if self.get(tag).is_some() {
1642                    let new_value = PrimitiveValue::from(&*string);
1643                    self.apply_change_value_impl(tag, new_value);
1644                }
1645                Ok(())
1646            }
1647            AttributeAction::PushStr(string) => self.apply_push_str_impl(tag, string),
1648            AttributeAction::PushI32(integer) => self.apply_push_i32_impl(tag, integer),
1649            AttributeAction::PushU32(integer) => self.apply_push_u32_impl(tag, integer),
1650            AttributeAction::PushI16(integer) => self.apply_push_i16_impl(tag, integer),
1651            AttributeAction::PushU16(integer) => self.apply_push_u16_impl(tag, integer),
1652            AttributeAction::PushF32(number) => self.apply_push_f32_impl(tag, number),
1653            AttributeAction::PushF64(number) => self.apply_push_f64_impl(tag, number),
1654            AttributeAction::Truncate(limit) => {
1655                self.update_value(tag, |value| value.truncate(limit));
1656                Ok(())
1657            }
1658            _ => UnsupportedActionSnafu.fail(),
1659        }
1660    }
1661
1662    fn apply_change_value_impl(&mut self, tag: Tag, new_value: PrimitiveValue) {
1663        self.invalidate_if_charset_changed(tag);
1664
1665        if let Some(e) = self.entries.get_mut(&tag) {
1666            let vr = e.vr();
1667            // handle edge case: if VR is SQ and suggested value is empty,
1668            // then create an empty data set sequence
1669            let new_value = if vr == VR::SQ && new_value.is_empty() {
1670                DataSetSequence::empty().into()
1671            } else {
1672                Value::from(new_value)
1673            };
1674            *e = DataElement::new(tag, vr, new_value);
1675            self.len = Length::UNDEFINED;
1676        } else {
1677            // infer VR from tag
1678            let vr = dicom_dictionary_std::StandardDataDictionary
1679                .by_tag(tag)
1680                .and_then(|entry| entry.vr().exact())
1681                .unwrap_or(VR::UN);
1682            // insert element
1683
1684            // handle edge case: if VR is SQ and suggested value is empty,
1685            // then create an empty data set sequence
1686            let new_value = if vr == VR::SQ && new_value.is_empty() {
1687                DataSetSequence::empty().into()
1688            } else {
1689                Value::from(new_value)
1690            };
1691
1692            self.put(DataElement::new(tag, vr, new_value));
1693        }
1694    }
1695
1696    fn invalidate_if_charset_changed(&mut self, tag: Tag) {
1697        if tag == tags::SPECIFIC_CHARACTER_SET {
1698            self.charset_changed = true;
1699        }
1700    }
1701
1702    fn apply_push_str_impl(&mut self, tag: Tag, string: Cow<'static, str>) -> ApplyResult {
1703        if let Some(e) = self.entries.remove(&tag) {
1704            let (header, value) = e.into_parts();
1705            match value {
1706                Value::Primitive(mut v) => {
1707                    self.invalidate_if_charset_changed(tag);
1708                    // extend value
1709                    v.extend_str([string]).context(ModifySnafu)?;
1710                    // reinsert element
1711                    self.put(DataElement::new(tag, header.vr, v));
1712                    Ok(())
1713                }
1714
1715                Value::PixelSequence(..) => IncompatibleTypesSnafu {
1716                    kind: ValueType::PixelSequence,
1717                }
1718                .fail(),
1719                Value::Sequence(..) => IncompatibleTypesSnafu {
1720                    kind: ValueType::DataSetSequence,
1721                }
1722                .fail(),
1723            }
1724        } else {
1725            // infer VR from tag
1726            let vr = dicom_dictionary_std::StandardDataDictionary
1727                .by_tag(tag)
1728                .and_then(|entry| entry.vr().exact())
1729                .unwrap_or(VR::UN);
1730            // insert element
1731            self.put(DataElement::new(tag, vr, PrimitiveValue::from(&*string)));
1732            Ok(())
1733        }
1734    }
1735
1736    fn apply_push_i32_impl(&mut self, tag: Tag, integer: i32) -> ApplyResult {
1737        if let Some(e) = self.entries.remove(&tag) {
1738            let (header, value) = e.into_parts();
1739            match value {
1740                Value::Primitive(mut v) => {
1741                    // extend value
1742                    v.extend_i32([integer]).context(ModifySnafu)?;
1743                    // reinsert element
1744                    self.put(DataElement::new(tag, header.vr, v));
1745                    Ok(())
1746                }
1747
1748                Value::PixelSequence(..) => IncompatibleTypesSnafu {
1749                    kind: ValueType::PixelSequence,
1750                }
1751                .fail(),
1752                Value::Sequence(..) => IncompatibleTypesSnafu {
1753                    kind: ValueType::DataSetSequence,
1754                }
1755                .fail(),
1756            }
1757        } else {
1758            // infer VR from tag
1759            let vr = dicom_dictionary_std::StandardDataDictionary
1760                .by_tag(tag)
1761                .and_then(|entry| entry.vr().exact())
1762                .unwrap_or(VR::SL);
1763            // insert element
1764            self.put(DataElement::new(tag, vr, PrimitiveValue::from(integer)));
1765            Ok(())
1766        }
1767    }
1768
1769    fn apply_push_u32_impl(&mut self, tag: Tag, integer: u32) -> ApplyResult {
1770        if let Some(e) = self.entries.remove(&tag) {
1771            let (header, value) = e.into_parts();
1772            match value {
1773                Value::Primitive(mut v) => {
1774                    // extend value
1775                    v.extend_u32([integer]).context(ModifySnafu)?;
1776                    // reinsert element
1777                    self.put(DataElement::new(tag, header.vr, v));
1778                    Ok(())
1779                }
1780
1781                Value::PixelSequence(..) => IncompatibleTypesSnafu {
1782                    kind: ValueType::PixelSequence,
1783                }
1784                .fail(),
1785                Value::Sequence(..) => IncompatibleTypesSnafu {
1786                    kind: ValueType::DataSetSequence,
1787                }
1788                .fail(),
1789            }
1790        } else {
1791            // infer VR from tag
1792            let vr = dicom_dictionary_std::StandardDataDictionary
1793                .by_tag(tag)
1794                .and_then(|entry| entry.vr().exact())
1795                .unwrap_or(VR::UL);
1796            // insert element
1797            self.put(DataElement::new(tag, vr, PrimitiveValue::from(integer)));
1798            Ok(())
1799        }
1800    }
1801
1802    fn apply_push_i16_impl(&mut self, tag: Tag, integer: i16) -> ApplyResult {
1803        if let Some(e) = self.entries.remove(&tag) {
1804            let (header, value) = e.into_parts();
1805            match value {
1806                Value::Primitive(mut v) => {
1807                    // extend value
1808                    v.extend_i16([integer]).context(ModifySnafu)?;
1809                    // reinsert element
1810                    self.put(DataElement::new(tag, header.vr, v));
1811                    Ok(())
1812                }
1813
1814                Value::PixelSequence(..) => IncompatibleTypesSnafu {
1815                    kind: ValueType::PixelSequence,
1816                }
1817                .fail(),
1818                Value::Sequence(..) => IncompatibleTypesSnafu {
1819                    kind: ValueType::DataSetSequence,
1820                }
1821                .fail(),
1822            }
1823        } else {
1824            // infer VR from tag
1825            let vr = dicom_dictionary_std::StandardDataDictionary
1826                .by_tag(tag)
1827                .and_then(|entry| entry.vr().exact())
1828                .unwrap_or(VR::SS);
1829            // insert element
1830            self.put(DataElement::new(tag, vr, PrimitiveValue::from(integer)));
1831            Ok(())
1832        }
1833    }
1834
1835    fn apply_push_u16_impl(&mut self, tag: Tag, integer: u16) -> ApplyResult {
1836        if let Some(e) = self.entries.remove(&tag) {
1837            let (header, value) = e.into_parts();
1838            match value {
1839                Value::Primitive(mut v) => {
1840                    // extend value
1841                    v.extend_u16([integer]).context(ModifySnafu)?;
1842                    // reinsert element
1843                    self.put(DataElement::new(tag, header.vr, v));
1844                    Ok(())
1845                }
1846
1847                Value::PixelSequence(..) => IncompatibleTypesSnafu {
1848                    kind: ValueType::PixelSequence,
1849                }
1850                .fail(),
1851                Value::Sequence(..) => IncompatibleTypesSnafu {
1852                    kind: ValueType::DataSetSequence,
1853                }
1854                .fail(),
1855            }
1856        } else {
1857            // infer VR from tag
1858            let vr = dicom_dictionary_std::StandardDataDictionary
1859                .by_tag(tag)
1860                .and_then(|entry| entry.vr().exact())
1861                .unwrap_or(VR::US);
1862            // insert element
1863            self.put(DataElement::new(tag, vr, PrimitiveValue::from(integer)));
1864            Ok(())
1865        }
1866    }
1867
1868    fn apply_push_f32_impl(&mut self, tag: Tag, number: f32) -> ApplyResult {
1869        if let Some(e) = self.entries.remove(&tag) {
1870            let (header, value) = e.into_parts();
1871            match value {
1872                Value::Primitive(mut v) => {
1873                    // extend value
1874                    v.extend_f32([number]).context(ModifySnafu)?;
1875                    // reinsert element
1876                    self.put(DataElement::new(tag, header.vr, v));
1877                    Ok(())
1878                }
1879
1880                Value::PixelSequence(..) => IncompatibleTypesSnafu {
1881                    kind: ValueType::PixelSequence,
1882                }
1883                .fail(),
1884                Value::Sequence(..) => IncompatibleTypesSnafu {
1885                    kind: ValueType::DataSetSequence,
1886                }
1887                .fail(),
1888            }
1889        } else {
1890            // infer VR from tag
1891            let vr = dicom_dictionary_std::StandardDataDictionary
1892                .by_tag(tag)
1893                .and_then(|entry| entry.vr().exact())
1894                .unwrap_or(VR::FL);
1895            // insert element
1896            self.put(DataElement::new(tag, vr, PrimitiveValue::from(number)));
1897            Ok(())
1898        }
1899    }
1900
1901    fn apply_push_f64_impl(&mut self, tag: Tag, number: f64) -> ApplyResult {
1902        if let Some(e) = self.entries.remove(&tag) {
1903            let (header, value) = e.into_parts();
1904            match value {
1905                Value::Primitive(mut v) => {
1906                    // extend value
1907                    v.extend_f64([number]).context(ModifySnafu)?;
1908                    // reinsert element
1909                    self.put(DataElement::new(tag, header.vr, v));
1910                    Ok(())
1911                }
1912
1913                Value::PixelSequence(..) => IncompatibleTypesSnafu {
1914                    kind: ValueType::PixelSequence,
1915                }
1916                .fail(),
1917                Value::Sequence(..) => IncompatibleTypesSnafu {
1918                    kind: ValueType::DataSetSequence,
1919                }
1920                .fail(),
1921            }
1922        } else {
1923            // infer VR from tag
1924            let vr = dicom_dictionary_std::StandardDataDictionary
1925                .by_tag(tag)
1926                .and_then(|entry| entry.vr().exact())
1927                .unwrap_or(VR::FD);
1928            // insert element
1929            self.put(DataElement::new(tag, vr, PrimitiveValue::from(number)));
1930            Ok(())
1931        }
1932    }
1933
1934    /// Write this object's data set into the given writer,
1935    /// with the given encoder specifications,
1936    /// without preamble, magic code, nor file meta group.
1937    ///
1938    /// The text encoding to use will be the default character set
1939    /// until _Specific Character Set_ is found in the data set,
1940    /// in which then that character set will be used.
1941    ///
1942    /// Uses the default [DataSetWriterOptions] for the writer.
1943    ///
1944    /// Note: [`write_dataset_with_ts`] and [`write_dataset_with_ts_cs`]
1945    /// may be easier to use and _will_ apply a dataset adapter (such as
1946    /// DeflatedExplicitVRLittleEndian (1.2.840.10008.1.2.99)) whereas this
1947    /// method will _not_
1948    ///
1949    /// [`write_dataset_with_ts`]: #method.write_dataset_with_ts
1950    /// [`write_dataset_with_ts_cs`]: #method.write_dataset_with_ts_cs
1951    pub fn write_dataset<W, E>(&self, to: W, encoder: E) -> Result<(), WriteError>
1952    where
1953        W: Write,
1954        E: EncodeTo<W>,
1955    {
1956        // prepare data set writer
1957        let mut dset_writer = DataSetWriter::new(to, encoder);
1958        let required_options = IntoTokensOptions::new(self.charset_changed);
1959        // write object
1960        dset_writer
1961            .write_sequence(self.into_tokens_with_options(required_options))
1962            .context(PrintDataSetSnafu)?;
1963
1964        Ok(())
1965    }
1966
1967    /// Write this object's data set into the given printer,
1968    /// with the specified transfer syntax and character set,
1969    /// without preamble, magic code, nor file meta group.
1970    ///
1971    /// The default [DataSetWriterOptions] is used for the writer. To change
1972    /// that, use [`write_dataset_with_ts_cs_options`](Self::write_dataset_with_ts_cs_options).
1973    ///
1974    /// If the attribute _Specific Character Set_ is found in the data set,
1975    /// the last parameter is overridden accordingly.
1976    /// See also [`write_dataset_with_ts`](Self::write_dataset_with_ts).
1977    pub fn write_dataset_with_ts_cs<W>(
1978        &self,
1979        to: W,
1980        ts: &TransferSyntax,
1981        cs: SpecificCharacterSet,
1982    ) -> Result<(), WriteError>
1983    where
1984        W: Write,
1985    {
1986        if let Codec::Dataset(Some(adapter)) = ts.codec() {
1987            let adapter = adapter.adapt_writer(Box::new(to));
1988            // prepare data set writer
1989            let mut dset_writer =
1990                DataSetWriter::with_ts(adapter, ts).context(CreatePrinterSnafu)?;
1991
1992            // write object
1993            dset_writer
1994                .write_sequence(self.into_tokens())
1995                .context(PrintDataSetSnafu)?;
1996
1997            Ok(())
1998        } else {
1999            // prepare data set writer
2000            let mut dset_writer =
2001                DataSetWriter::with_ts_cs(to, ts, cs).context(CreatePrinterSnafu)?;
2002
2003            // write object
2004            dset_writer
2005                .write_sequence(self.into_tokens())
2006                .context(PrintDataSetSnafu)?;
2007
2008            Ok(())
2009        }
2010    }
2011
2012    /// Write this object's data set into the given printer,
2013    /// with the specified transfer syntax and character set,
2014    /// without preamble, magic code, nor file meta group.
2015    ///
2016    /// If the attribute _Specific Character Set_ is found in the data set,
2017    /// the last parameter is overridden accordingly.
2018    /// See also [`write_dataset_with_ts`](Self::write_dataset_with_ts).
2019    pub fn write_dataset_with_ts_cs_options<W>(
2020        &self,
2021        to: W,
2022        ts: &TransferSyntax,
2023        cs: SpecificCharacterSet,
2024        options: DataSetWriterOptions,
2025    ) -> Result<(), WriteError>
2026    where
2027        W: Write,
2028    {
2029        // prepare data set writer
2030        let mut dset_writer =
2031            DataSetWriter::with_ts_cs_options(to, ts, cs, options).context(CreatePrinterSnafu)?;
2032        let required_options = IntoTokensOptions::new(self.charset_changed);
2033
2034        // write object
2035        dset_writer
2036            .write_sequence(self.into_tokens_with_options(required_options))
2037            .context(PrintDataSetSnafu)?;
2038
2039        Ok(())
2040    }
2041
2042    /// Write this object's data set into the given writer,
2043    /// with the specified transfer syntax,
2044    /// without preamble, magic code, nor file meta group.
2045    ///
2046    /// The default [DataSetWriterOptions] is used for the writer. To change
2047    /// that, use [`write_dataset_with_ts_options`](Self::write_dataset_with_ts_options).
2048    ///
2049    /// The default character set is assumed
2050    /// until the _Specific Character Set_ is found in the data set,
2051    /// after which the text encoder is overridden accordingly.
2052    pub fn write_dataset_with_ts<W>(&self, to: W, ts: &TransferSyntax) -> Result<(), WriteError>
2053    where
2054        W: Write,
2055    {
2056        self.write_dataset_with_ts_cs(to, ts, SpecificCharacterSet::default())
2057    }
2058
2059    /// Write this object's data set into the given writer,
2060    /// with the specified transfer syntax,
2061    /// without preamble, magic code, nor file meta group.
2062    ///
2063    /// The default character set is assumed
2064    /// until the _Specific Character Set_ is found in the data set,
2065    /// after which the text encoder is overridden accordingly.
2066    pub fn write_dataset_with_ts_options<W>(
2067        &self,
2068        to: W,
2069        ts: &TransferSyntax,
2070        options: DataSetWriterOptions,
2071    ) -> Result<(), WriteError>
2072    where
2073        W: Write,
2074    {
2075        self.write_dataset_with_ts_cs_options(to, ts, SpecificCharacterSet::default(), options)
2076    }
2077
2078    /// Encapsulate this object to contain a file meta group
2079    /// as described exactly by the given table.
2080    ///
2081    /// **Note:** this method will not adjust the file meta group
2082    /// to be semantically valid for the object.
2083    /// Namely, the _Media Storage SOP Instance UID_
2084    /// and _Media Storage SOP Class UID_
2085    /// are not updated based on the receiving data set.
2086    pub fn with_exact_meta(self, meta: FileMetaTable) -> FileDicomObject<Self> {
2087        FileDicomObject { meta, obj: self }
2088    }
2089
2090    /// Encapsulate this object to contain a file meta group,
2091    /// created through the given file meta table builder.
2092    ///
2093    /// A complete file meta group should provide
2094    /// the _Transfer Syntax UID_,
2095    /// the _Media Storage SOP Instance UID_,
2096    /// and the _Media Storage SOP Class UID_.
2097    /// The last two will be filled with the values of
2098    /// _SOP Instance UID_ and _SOP Class UID_
2099    /// if they are present in this object.
2100    ///
2101    /// # Example
2102    ///
2103    /// ```no_run
2104    /// # use dicom_core::{DataElement, VR};
2105    /// # use dicom_dictionary_std::tags;
2106    /// # use dicom_dictionary_std::uids;
2107    /// use dicom_object::{InMemDicomObject, meta::FileMetaTableBuilder};
2108    ///
2109    /// let obj = InMemDicomObject::from_element_iter([
2110    ///     DataElement::new(tags::SOP_CLASS_UID, VR::UI, uids::COMPUTED_RADIOGRAPHY_IMAGE_STORAGE),
2111    ///     DataElement::new(tags::SOP_INSTANCE_UID, VR::UI, "2.25.60156688944589400766024286894543900794"),
2112    ///     // ...
2113    /// ]);
2114    ///
2115    /// let obj = obj.with_meta(FileMetaTableBuilder::new()
2116    ///     .transfer_syntax(uids::EXPLICIT_VR_LITTLE_ENDIAN))?;
2117    ///
2118    /// // can now save everything to a file
2119    /// let meta = obj.write_to_file("out.dcm")?;
2120    /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
2121    /// ```
2122    pub fn with_meta(
2123        self,
2124        mut meta: FileMetaTableBuilder,
2125    ) -> Result<FileDicomObject<Self>, WithMetaError> {
2126        if let Some(elem) = self.get(tags::SOP_INSTANCE_UID) {
2127            meta = meta.media_storage_sop_instance_uid(
2128                elem.value().to_str().context(PrepareMetaTableSnafu)?,
2129            );
2130        }
2131        if let Some(elem) = self.get(tags::SOP_CLASS_UID) {
2132            meta = meta
2133                .media_storage_sop_class_uid(elem.value().to_str().context(PrepareMetaTableSnafu)?);
2134        }
2135        Ok(FileDicomObject {
2136            meta: meta.build().context(BuildMetaTableSnafu)?,
2137            obj: self,
2138        })
2139    }
2140
2141    /// Obtain an iterator over the elements of this object.
2142    pub fn iter(&self) -> impl Iterator<Item = &InMemElement<D>> + '_ {
2143        self.into_iter()
2144    }
2145
2146    /// Obtain an iterator over the tags of the object's elements.
2147    pub fn tags(&self) -> impl Iterator<Item = Tag> + '_ {
2148        self.entries.keys().copied()
2149    }
2150
2151    // private methods
2152
2153    /// Build an object by consuming a data set parser.
2154    fn build_object<I>(
2155        dataset: &mut I,
2156        dict: D,
2157        in_item: bool,
2158        len: Length,
2159        read_until: Option<Tag>,
2160        read_to: Option<Tag>,
2161    ) -> Result<Self, ReadError>
2162    where
2163        I: ?Sized + Iterator<Item = ParserResult<DataToken>>,
2164    {
2165        let mut entries: BTreeMap<Tag, InMemElement<D>> = BTreeMap::new();
2166        // perform a structured parsing of incoming tokens
2167        while let Some(token) = dataset.next() {
2168            let elem = match token.context(ReadTokenSnafu)? {
2169                DataToken::PixelSequenceStart => {
2170                    let sq_start_tag = Tag(0x7fe0, 0x0010);
2171                    // stop reading if reached `read_until` tag (exclusive)
2172                    if read_until.map(|t| t <= sq_start_tag).unwrap_or(false) {
2173                        break;
2174                    }
2175                    // stop reading if exceeded `read_to` tag (inclusive)
2176                    if read_to.map(|t| t < sq_start_tag).unwrap_or(false) {
2177                        break;
2178                    }
2179                    let value = InMemDicomObject::build_encapsulated_data(&mut *dataset)?;
2180                    DataElement::new(sq_start_tag, VR::OB, value)
2181                }
2182                DataToken::ElementHeader(header) => {
2183                    // stop reading if reached `read_until` tag (exclusive)
2184                    if read_until.map(|t| t <= header.tag).unwrap_or(false) {
2185                        break;
2186                    }
2187                    // stop reading if exceeded `read_to` tag (inclusive)
2188                    if read_to.map(|t| t < header.tag).unwrap_or(false) {
2189                        break;
2190                    }
2191
2192                    // fetch respective value, place it in the entries
2193                    let next_token = dataset.next().context(MissingElementValueSnafu)?;
2194                    match next_token.context(ReadTokenSnafu)? {
2195                        DataToken::PrimitiveValue(v) => InMemElement::new_with_len(
2196                            header.tag,
2197                            header.vr,
2198                            header.len,
2199                            Value::Primitive(v),
2200                        ),
2201                        token => {
2202                            return UnexpectedTokenSnafu { token }.fail();
2203                        }
2204                    }
2205                }
2206                DataToken::SequenceStart { tag, len } => {
2207                    // stop reading if reached `read_until` tag (exclusive)
2208                    if read_until.map(|t| t <= tag).unwrap_or(false) {
2209                        break;
2210                    }
2211                    // stop reading if exceeded `read_to` tag (inclusive)
2212                    if read_to.map(|t| t < tag).unwrap_or(false) {
2213                        break;
2214                    }
2215
2216                    // delegate sequence building to another function
2217                    let items = Self::build_sequence(tag, len, &mut *dataset, &dict)?;
2218                    DataElement::new_with_len(
2219                        tag,
2220                        VR::SQ,
2221                        len,
2222                        Value::Sequence(DataSetSequence::new(items, len)),
2223                    )
2224                }
2225                DataToken::ItemEnd if in_item => {
2226                    // end of item, leave now
2227                    return Ok(InMemDicomObject {
2228                        entries,
2229                        dict,
2230                        len,
2231                        charset_changed: false,
2232                    });
2233                }
2234                token => return UnexpectedTokenSnafu { token }.fail(),
2235            };
2236            entries.insert(elem.tag(), elem);
2237        }
2238
2239        Ok(InMemDicomObject {
2240            entries,
2241            dict,
2242            len,
2243            charset_changed: false,
2244        })
2245    }
2246
2247    /// Build an encapsulated pixel data by collecting all fragments into an
2248    /// in-memory DICOM value.
2249    fn build_encapsulated_data<I>(
2250        dataset: I,
2251    ) -> Result<Value<InMemDicomObject<D>, InMemFragment>, ReadError>
2252    where
2253        I: Iterator<Item = ParserResult<DataToken>>,
2254    {
2255        // continue fetching tokens to retrieve:
2256        // - the offset table
2257        // - the various compressed fragments
2258
2259        let mut offset_table = None;
2260
2261        let mut fragments = C::new();
2262
2263        for token in dataset {
2264            match token.context(ReadTokenSnafu)? {
2265                DataToken::OffsetTable(table) => {
2266                    offset_table = Some(table);
2267                }
2268                DataToken::ItemValue(data) => {
2269                    fragments.push(data);
2270                }
2271                DataToken::ItemEnd => {
2272                    // at the end of the first item ensure the presence of
2273                    // an empty offset_table here, so that the next items
2274                    // are seen as compressed fragments
2275                    if offset_table.is_none() {
2276                        offset_table = Some(Vec::new())
2277                    }
2278                }
2279                DataToken::ItemStart { len: _ } => { /* no-op */ }
2280                DataToken::SequenceEnd => {
2281                    // end of pixel data
2282                    break;
2283                }
2284                // the following variants are unexpected
2285                token @ DataToken::ElementHeader(_)
2286                | token @ DataToken::PixelSequenceStart
2287                | token @ DataToken::SequenceStart { .. }
2288                | token @ DataToken::PrimitiveValue(_) => {
2289                    return UnexpectedTokenSnafu { token }.fail();
2290                }
2291            }
2292        }
2293
2294        Ok(Value::PixelSequence(PixelFragmentSequence::new(
2295            offset_table.unwrap_or_default(),
2296            fragments,
2297        )))
2298    }
2299
2300    /// Build a DICOM sequence by consuming a data set parser.
2301    fn build_sequence<I>(
2302        _tag: Tag,
2303        _len: Length,
2304        dataset: &mut I,
2305        dict: &D,
2306    ) -> Result<C<InMemDicomObject<D>>, ReadError>
2307    where
2308        I: ?Sized + Iterator<Item = ParserResult<DataToken>>,
2309    {
2310        let mut items: C<_> = SmallVec::new();
2311        while let Some(token) = dataset.next() {
2312            match token.context(ReadTokenSnafu)? {
2313                DataToken::ItemStart { len } => {
2314                    items.push(Self::build_object(
2315                        &mut *dataset,
2316                        dict.clone(),
2317                        true,
2318                        len,
2319                        None,
2320                        None,
2321                    )?);
2322                }
2323                DataToken::SequenceEnd => {
2324                    return Ok(items);
2325                }
2326                token => return UnexpectedTokenSnafu { token }.fail(),
2327            };
2328        }
2329
2330        // iterator fully consumed without a sequence delimiter
2331        PrematureEndSnafu.fail()
2332    }
2333
2334    fn lookup_name(&self, name: &str) -> Result<Tag, AccessByNameError> {
2335        self.dict
2336            .by_name(name)
2337            .context(NoSuchAttributeNameSnafu { name })
2338            .map(|e| e.tag())
2339    }
2340}
2341
2342impl<D> ApplyOp for InMemDicomObject<D>
2343where
2344    D: DataDictionary,
2345    D: Clone,
2346{
2347    type Err = ApplyError;
2348
2349    #[inline]
2350    fn apply(&mut self, op: AttributeOp) -> ApplyResult {
2351        self.apply(op)
2352    }
2353}
2354
2355impl<'a, D> IntoIterator for &'a InMemDicomObject<D> {
2356    type Item = &'a InMemElement<D>;
2357    type IntoIter = ::std::collections::btree_map::Values<'a, Tag, InMemElement<D>>;
2358
2359    fn into_iter(self) -> Self::IntoIter {
2360        self.entries.values()
2361    }
2362}
2363
2364impl<D> IntoIterator for InMemDicomObject<D> {
2365    type Item = InMemElement<D>;
2366    type IntoIter = Iter<D>;
2367
2368    fn into_iter(self) -> Self::IntoIter {
2369        Iter {
2370            inner: self.entries.into_iter(),
2371        }
2372    }
2373}
2374
2375/// Base iterator type for an in-memory DICOM object.
2376#[derive(Debug)]
2377pub struct Iter<D> {
2378    inner: ::std::collections::btree_map::IntoIter<Tag, InMemElement<D>>,
2379}
2380
2381impl<D> Iterator for Iter<D> {
2382    type Item = InMemElement<D>;
2383
2384    fn next(&mut self) -> Option<Self::Item> {
2385        self.inner.next().map(|x| x.1)
2386    }
2387
2388    fn size_hint(&self) -> (usize, Option<usize>) {
2389        self.inner.size_hint()
2390    }
2391
2392    fn count(self) -> usize {
2393        self.inner.count()
2394    }
2395}
2396
2397impl<D> Extend<InMemElement<D>> for InMemDicomObject<D> {
2398    fn extend<I>(&mut self, iter: I)
2399    where
2400        I: IntoIterator<Item = InMemElement<D>>,
2401    {
2402        self.len = Length::UNDEFINED;
2403        self.entries.extend(iter.into_iter().map(|e| (e.tag(), e)))
2404    }
2405}
2406
2407fn even_len(l: u32) -> u32 {
2408    (l + 1) & !1
2409}
2410
2411#[cfg(test)]
2412mod tests {
2413    use super::*;
2414    use crate::{DicomAttribute as _, open_file};
2415    use byteordered::Endianness;
2416    use dicom_core::chrono::FixedOffset;
2417    use dicom_core::value::{DicomDate, DicomDateTime, DicomTime};
2418    use dicom_core::{dicom_value, header::DataElementHeader};
2419    use dicom_encoding::{
2420        decode::{basic::BasicDecoder, implicit_le::ImplicitVRLittleEndianDecoder},
2421        encode::{EncoderFor, implicit_le::ImplicitVRLittleEndianEncoder},
2422    };
2423    use dicom_parser::StatefulDecoder;
2424
2425    fn assert_obj_eq<D>(obj1: &InMemDicomObject<D>, obj2: &InMemDicomObject<D>)
2426    where
2427        D: std::fmt::Debug,
2428    {
2429        // debug representation because it makes a stricter comparison and
2430        // assumes that Undefined lengths are equal.
2431        assert_eq!(format!("{obj1:?}"), format!("{:?}", obj2))
2432    }
2433
2434    #[test]
2435    fn inmem_object_compare() {
2436        let mut obj1 = InMemDicomObject::new_empty();
2437        let mut obj2 = InMemDicomObject::new_empty();
2438        assert_eq!(obj1, obj2);
2439        let empty_patient_name = DataElement::empty(Tag(0x0010, 0x0010), VR::PN);
2440        obj1.put(empty_patient_name.clone());
2441        assert_ne!(obj1, obj2);
2442        obj2.put(empty_patient_name.clone());
2443        assert_obj_eq(&obj1, &obj2);
2444    }
2445
2446    #[test]
2447    fn inmem_object_read_dataset() {
2448        let data_in = [
2449            0x10, 0x00, 0x10, 0x00, // Tag(0x0010, 0x0010)
2450            0x08, 0x00, 0x00, 0x00, // Length: 8
2451            b'D', b'o', b'e', b'^', b'J', b'o', b'h', b'n',
2452        ];
2453
2454        let decoder = ImplicitVRLittleEndianDecoder::default();
2455        let text = SpecificCharacterSet::default();
2456        let mut cursor = &data_in[..];
2457        let parser = StatefulDecoder::new(
2458            &mut cursor,
2459            decoder,
2460            BasicDecoder::new(Endianness::Little),
2461            text,
2462        );
2463
2464        let obj = InMemDicomObject::read_dataset(parser).unwrap();
2465
2466        let mut gt = InMemDicomObject::new_empty();
2467
2468        let patient_name = DataElement::new(
2469            Tag(0x0010, 0x0010),
2470            VR::PN,
2471            dicom_value!(Strs, ["Doe^John"]),
2472        );
2473        gt.put(patient_name);
2474
2475        assert_eq!(obj, gt);
2476    }
2477
2478    #[test]
2479    fn inmem_object_read_dataset_with_ts_cs() {
2480        let data_in = [
2481            0x10, 0x00, 0x10, 0x00, // Tag(0x0010, 0x0010)
2482            0x08, 0x00, 0x00, 0x00, // Length: 8
2483            b'D', b'o', b'e', b'^', b'J', b'o', b'h', b'n',
2484        ];
2485
2486        let ts = TransferSyntaxRegistry.get("1.2.840.10008.1.2").unwrap();
2487        let cs = SpecificCharacterSet::default();
2488        let mut cursor = &data_in[..];
2489
2490        let obj = InMemDicomObject::read_dataset_with_dict_ts_cs(
2491            &mut cursor,
2492            StandardDataDictionary,
2493            ts,
2494            cs,
2495        )
2496        .unwrap();
2497
2498        let mut gt = InMemDicomObject::new_empty();
2499
2500        let patient_name = DataElement::new(
2501            Tag(0x0010, 0x0010),
2502            VR::PN,
2503            dicom_value!(Strs, ["Doe^John"]),
2504        );
2505        gt.put(patient_name);
2506
2507        assert_eq!(obj, gt);
2508    }
2509
2510    /// Reading a data set
2511    /// saves the original length of a text element.
2512    #[test]
2513    fn inmem_object_read_dataset_saves_len() {
2514        let data_in = [
2515            // SpecificCharacterSet (0008,0005)
2516            0x08, 0x00, 0x05, 0x00, //
2517            // Length: 10
2518            0x0a, 0x00, 0x00, 0x00, //
2519            b'I', b'S', b'O', b'_', b'I', b'R', b' ', b'1', b'0', b'0',
2520            // ReferringPhysicianName (0008,0090)
2521            0x08, 0x00, 0x90, 0x00, //
2522            // Length: 12
2523            0x0c, 0x00, 0x00, 0x00, b'S', b'i', b'm', 0xF5, b'e', b's', b'^', b'J', b'o', 0xE3,
2524            b'o', b' ',
2525        ];
2526
2527        let ts = TransferSyntaxRegistry.get("1.2.840.10008.1.2").unwrap();
2528        let mut cursor = &data_in[..];
2529
2530        let obj =
2531            InMemDicomObject::read_dataset_with_dict_ts(&mut cursor, StandardDataDictionary, ts)
2532                .unwrap();
2533
2534        let physician_name = obj.element(Tag(0x0008, 0x0090)).unwrap();
2535        assert_eq!(physician_name.header().len, Length(12));
2536        assert_eq!(physician_name.value().to_str().unwrap(), "Simões^João");
2537    }
2538
2539    #[test]
2540    fn inmem_object_write_dataset() {
2541        let mut obj = InMemDicomObject::new_empty();
2542
2543        let patient_name =
2544            DataElement::new(Tag(0x0010, 0x0010), VR::PN, dicom_value!(Str, "Doe^John"));
2545        obj.put(patient_name);
2546
2547        let mut out = Vec::new();
2548
2549        let printer = EncoderFor::new(ImplicitVRLittleEndianEncoder::default());
2550
2551        obj.write_dataset(&mut out, printer).unwrap();
2552
2553        assert_eq!(
2554            out,
2555            &[
2556                0x10, 0x00, 0x10, 0x00, // Tag(0x0010, 0x0010)
2557                0x08, 0x00, 0x00, 0x00, // Length: 8
2558                b'D', b'o', b'e', b'^', b'J', b'o', b'h', b'n',
2559            ][..],
2560        );
2561    }
2562
2563    #[test]
2564    fn inmem_object_write_dataset_with_ts() {
2565        let mut obj = InMemDicomObject::new_empty();
2566
2567        let patient_name =
2568            DataElement::new(Tag(0x0010, 0x0010), VR::PN, dicom_value!(Str, "Doe^John"));
2569        obj.put(patient_name);
2570
2571        let mut out = Vec::new();
2572
2573        let ts = TransferSyntaxRegistry.get("1.2.840.10008.1.2.1").unwrap();
2574
2575        obj.write_dataset_with_ts(&mut out, ts).unwrap();
2576
2577        assert_eq!(
2578            out,
2579            &[
2580                0x10, 0x00, 0x10, 0x00, // Tag(0x0010, 0x0010)
2581                b'P', b'N', // VR: PN
2582                0x08, 0x00, // Length: 8
2583                b'D', b'o', b'e', b'^', b'J', b'o', b'h', b'n',
2584            ][..],
2585        );
2586    }
2587
2588    #[test]
2589    fn inmem_object_write_dataset_encapsulated_pixel_data() {
2590        let mut obj = InMemDicomObject::new_empty();
2591
2592        let sop_instance_uid = DataElement::new(
2593            tags::SOP_INSTANCE_UID,
2594            VR::UI,
2595            "2.25.44399302050596340528032699331187776010",
2596        );
2597        obj.put(sop_instance_uid);
2598
2599        obj.put(DataElement::new(
2600            tags::PIXEL_DATA,
2601            VR::OB,
2602            PixelFragmentSequence::new_fragments([
2603                vec![0x01, 0x02, 0x03, 0x04],
2604                vec![0x05, 0x06, 0x07, 0x08],
2605            ]),
2606        ));
2607
2608        let mut out = Vec::new();
2609
2610        let ts = TransferSyntaxRegistry
2611            .get(uids::ENCAPSULATED_UNCOMPRESSED_EXPLICIT_VR_LITTLE_ENDIAN)
2612            .unwrap();
2613
2614        obj.write_dataset_with_ts(&mut out, ts).unwrap();
2615
2616        assert_eq!(
2617            out,
2618            &[
2619                0x08, 0x00, 0x18, 0x00, // Tag(0x0008, 0x0018)
2620                b'U', b'I', // VR: UI
2621                0x2c, 0x00, // Length: 44
2622                // 2.25.44399302050596340528032699331187776010
2623                b'2', b'.', b'2', b'5', b'.', b'4', b'4', b'3', b'9', b'9', b'3', b'0', b'2', b'0',
2624                b'5', b'0', b'5', b'9', b'6', b'3', b'4', b'0', b'5', b'2', b'8', b'0', b'3', b'2',
2625                b'6', b'9', b'9', b'3', b'3', b'1', b'1', b'8', b'7', b'7', b'7', b'6', b'0', b'1',
2626                b'0', b'\0', // pixel data
2627                0xe0, 0x7f, 0x10, 0x00, // Tag(0x7fe0, 0x0010)
2628                b'O', b'B', // VR: OB
2629                0x00, 0x00, // reserved
2630                0xff, 0xff, 0xff, 0xff, // Length: undefined
2631                // first fragment (offset table)
2632                0xfe, 0xff, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x00, // Length: 0
2633                // second fragment
2634                0xfe, 0xff, 0x00, 0xe0, 0x04, 0x00, 0x00, 0x00, // Length: 4
2635                0x01, 0x02, 0x03, 0x04, // third fragment
2636                0xfe, 0xff, 0x00, 0xe0, 0x04, 0x00, 0x00, 0x00, // Length: 4
2637                0x05, 0x06, 0x07, 0x08, // sequence delimitation item
2638                0xfe, 0xff, 0xdd, 0xe0, 0x00, 0x00, 0x00, 0x00, // Length: 0
2639            ][..],
2640        );
2641    }
2642
2643    #[test]
2644    fn inmem_object_write_dataset_with_ts_cs() {
2645        let mut obj = InMemDicomObject::new_empty();
2646
2647        let patient_name =
2648            DataElement::new(Tag(0x0010, 0x0010), VR::PN, dicom_value!(Str, "Doe^John"));
2649        obj.put(patient_name);
2650
2651        let mut out = Vec::new();
2652
2653        let ts = TransferSyntaxRegistry.get("1.2.840.10008.1.2").unwrap();
2654        let cs = SpecificCharacterSet::default();
2655
2656        obj.write_dataset_with_ts_cs(&mut out, ts, cs).unwrap();
2657
2658        assert_eq!(
2659            out,
2660            &[
2661                0x10, 0x00, 0x10, 0x00, // Tag(0x0010, 0x0010)
2662                0x08, 0x00, 0x00, 0x00, // Length: 8
2663                b'D', b'o', b'e', b'^', b'J', b'o', b'h', b'n',
2664            ][..],
2665        );
2666    }
2667
2668    /// writing a DICOM date time into an object
2669    /// should include value padding
2670    #[test]
2671    fn inmem_object_write_datetime_odd() {
2672        let mut obj = InMemDicomObject::new_empty();
2673
2674        // add a number that will be encoded in text
2675        let instance_number =
2676            DataElement::new(Tag(0x0020, 0x0013), VR::IS, PrimitiveValue::from(1_i32));
2677        obj.put(instance_number);
2678
2679        // add a date time
2680        let dt = DicomDateTime::from_date_and_time_with_time_zone(
2681            DicomDate::from_ymd(2022, 11, 22).unwrap(),
2682            DicomTime::from_hms(18, 9, 35).unwrap(),
2683            FixedOffset::east_opt(3600).unwrap(),
2684        )
2685        .unwrap();
2686        let instance_coercion_date_time =
2687            DataElement::new(Tag(0x0008, 0x0015), VR::DT, dicom_value!(DateTime, dt));
2688        obj.put(instance_coercion_date_time);
2689
2690        // explicit VR Little Endian
2691        let ts = TransferSyntaxRegistry.get("1.2.840.10008.1.2.1").unwrap();
2692
2693        let mut out = Vec::new();
2694        obj.write_dataset_with_ts(&mut out, ts)
2695            .expect("should write DICOM data without errors");
2696
2697        assert_eq!(
2698            out,
2699            &[
2700                // instance coercion date time
2701                0x08, 0x00, 0x15, 0x00, // Tag(0x0008, 0x0015)
2702                b'D', b'T', // VR: DT
2703                0x14, 0x00, // Length: 20 bytes
2704                b'2', b'0', b'2', b'2', b'1', b'1', b'2', b'2', // date
2705                b'1', b'8', b'0', b'9', b'3', b'5', // time
2706                b'+', b'0', b'1', b'0', b'0', // offset
2707                b' ', // padding to even length
2708                // instance number
2709                0x20, 0x00, 0x13, 0x00, // Tag(0x0020, 0x0013)
2710                b'I', b'S', // VR: IS
2711                0x02, 0x00, // Length: 2 bytes
2712                b'1', b' ' // 1, with padding
2713            ][..],
2714        );
2715    }
2716
2717    /// Writes a file from scratch
2718    /// and opens it to check that the data is equivalent.
2719    #[test]
2720    fn inmem_write_to_file_with_meta() {
2721        let sop_uid = "1.4.645.212121";
2722        let mut obj = InMemDicomObject::new_empty();
2723
2724        obj.put(DataElement::new(
2725            Tag(0x0010, 0x0010),
2726            VR::PN,
2727            dicom_value!(Strs, ["Doe^John"]),
2728        ));
2729        obj.put(DataElement::new(
2730            Tag(0x0008, 0x0060),
2731            VR::CS,
2732            dicom_value!(Strs, ["CR"]),
2733        ));
2734        obj.put(DataElement::new(
2735            Tag(0x0008, 0x0018),
2736            VR::UI,
2737            dicom_value!(Strs, [sop_uid]),
2738        ));
2739
2740        let file_object = obj
2741            .with_meta(
2742                FileMetaTableBuilder::default()
2743                    // Explicit VR Little Endian
2744                    .transfer_syntax("1.2.840.10008.1.2.1")
2745                    // Computed Radiography image storage
2746                    .media_storage_sop_class_uid("1.2.840.10008.5.1.4.1.1.1")
2747                    .media_storage_sop_instance_uid(sop_uid),
2748            )
2749            .unwrap();
2750
2751        // create temporary file path and write object to that file
2752        let dir = tempfile::tempdir().unwrap();
2753        let mut file_path = dir.keep();
2754        file_path.push(format!("{sop_uid}.dcm"));
2755
2756        file_object.write_to_file(&file_path).unwrap();
2757
2758        // read the file back to validate the outcome
2759        let saved_object = open_file(file_path).unwrap();
2760        assert_eq!(file_object, saved_object);
2761    }
2762
2763    /// Creating a file DICOM object from an in-mem DICOM object
2764    /// infers the SOP instance UID.
2765    #[test]
2766    fn inmem_with_meta_infers_sop_instance_uid() {
2767        let sop_uid = "1.4.645.252521";
2768        let mut obj = InMemDicomObject::new_empty();
2769
2770        obj.put(DataElement::new(
2771            tags::SOP_INSTANCE_UID,
2772            VR::UI,
2773            PrimitiveValue::from(sop_uid),
2774        ));
2775
2776        let file_object = obj
2777            .with_meta(
2778                // Media Storage SOP Instance UID deliberately not set
2779                FileMetaTableBuilder::default()
2780                    // Explicit VR Little Endian
2781                    .transfer_syntax("1.2.840.10008.1.2.1")
2782                    // Computed Radiography image storage
2783                    .media_storage_sop_class_uid("1.2.840.10008.5.1.4.1.1.1"),
2784            )
2785            .unwrap();
2786
2787        let meta = file_object.meta();
2788
2789        assert_eq!(
2790            meta.media_storage_sop_instance_uid.trim_end_matches('\0'),
2791            sop_uid.trim_end_matches('\0'),
2792        );
2793    }
2794
2795    /// Write a file from scratch, with exact file meta table.
2796    #[test]
2797    fn inmem_write_to_file_with_exact_meta() {
2798        let sop_uid = "1.4.645.212121";
2799        let mut obj = InMemDicomObject::new_empty();
2800
2801        obj.put(DataElement::new(
2802            Tag(0x0010, 0x0010),
2803            VR::PN,
2804            dicom_value!(Strs, ["Doe^John"]),
2805        ));
2806        obj.put(DataElement::new(
2807            Tag(0x0008, 0x0060),
2808            VR::CS,
2809            dicom_value!(Strs, ["CR"]),
2810        ));
2811        obj.put(DataElement::new(
2812            Tag(0x0008, 0x0018),
2813            VR::UI,
2814            dicom_value!(Strs, [sop_uid]),
2815        ));
2816
2817        let file_object = obj.with_exact_meta(
2818            FileMetaTableBuilder::default()
2819                // Explicit VR Little Endian
2820                .transfer_syntax("1.2.840.10008.1.2.1")
2821                // Computed Radiography image storage
2822                .media_storage_sop_class_uid("1.2.840.10008.5.1.4.1.1.1")
2823                .media_storage_sop_instance_uid(sop_uid)
2824                .build()
2825                .unwrap(),
2826        );
2827
2828        // create temporary file path and write object to that file
2829        let dir = tempfile::tempdir().unwrap();
2830        let mut file_path = dir.keep();
2831        file_path.push(format!("{sop_uid}.dcm"));
2832
2833        file_object.write_to_file(&file_path).unwrap();
2834
2835        // read the file back to validate the outcome
2836        let saved_object = open_file(file_path).unwrap();
2837        assert_eq!(file_object, saved_object);
2838    }
2839
2840    #[test]
2841    fn inmem_object_get() {
2842        let another_patient_name = DataElement::new(
2843            Tag(0x0010, 0x0010),
2844            VR::PN,
2845            PrimitiveValue::Str("Doe^John".to_string()),
2846        );
2847        let mut obj = InMemDicomObject::new_empty();
2848        obj.put(another_patient_name.clone());
2849        let elem1 = obj.element(Tag(0x0010, 0x0010)).unwrap();
2850        assert_eq!(elem1, &another_patient_name);
2851    }
2852
2853    #[test]
2854    fn infer_media_sop_from_dataset_sop_elements() {
2855        let sop_instance_uid = "1.4.645.313131";
2856        let sop_class_uid = "1.2.840.10008.5.1.4.1.1.2";
2857        let mut obj = InMemDicomObject::new_empty();
2858
2859        obj.put(DataElement::new(
2860            Tag(0x0008, 0x0018),
2861            VR::UI,
2862            dicom_value!(Strs, [sop_instance_uid]),
2863        ));
2864        obj.put(DataElement::new(
2865            Tag(0x0008, 0x0016),
2866            VR::UI,
2867            dicom_value!(Strs, [sop_class_uid]),
2868        ));
2869
2870        let file_object = obj.with_exact_meta(
2871            FileMetaTableBuilder::default()
2872                .transfer_syntax("1.2.840.10008.1.2.1")
2873                // Media Storage SOP Class and Instance UIDs are missing and set to an empty string
2874                .media_storage_sop_class_uid("")
2875                .media_storage_sop_instance_uid("")
2876                .build()
2877                .unwrap(),
2878        );
2879
2880        // create temporary file path and write object to that file
2881        let dir = tempfile::tempdir().unwrap();
2882        let mut file_path = dir.keep();
2883        file_path.push(format!("{sop_instance_uid}.dcm"));
2884
2885        file_object.write_to_file(&file_path).unwrap();
2886
2887        // read the file back to validate the outcome
2888        let saved_object = open_file(file_path).unwrap();
2889
2890        // verify that the empty string media storage sop instance and class UIDs have been inferred from the sop instance and class UID
2891        assert_eq!(
2892            saved_object.meta().media_storage_sop_instance_uid(),
2893            sop_instance_uid
2894        );
2895        assert_eq!(
2896            saved_object.meta().media_storage_sop_class_uid(),
2897            sop_class_uid
2898        );
2899    }
2900
2901    #[test]
2902    fn inmem_object_get_opt() {
2903        let another_patient_name = DataElement::new(
2904            Tag(0x0010, 0x0010),
2905            VR::PN,
2906            PrimitiveValue::Str("Doe^John".to_string()),
2907        );
2908        let mut obj = InMemDicomObject::new_empty();
2909        obj.put(another_patient_name.clone());
2910        let elem1 = obj.element_opt(Tag(0x0010, 0x0010)).unwrap();
2911        assert_eq!(elem1, Some(&another_patient_name));
2912
2913        // try a missing element, should return None
2914        assert_eq!(obj.element_opt(Tag(0x0010, 0x0020)).unwrap(), None);
2915    }
2916
2917    #[test]
2918    fn inmem_object_get_by_name() {
2919        let another_patient_name = DataElement::new(
2920            Tag(0x0010, 0x0010),
2921            VR::PN,
2922            PrimitiveValue::Str("Doe^John".to_string()),
2923        );
2924        let mut obj = InMemDicomObject::new_empty();
2925        obj.put(another_patient_name.clone());
2926        let elem1 = obj.element_by_name("PatientName").unwrap();
2927        assert_eq!(elem1, &another_patient_name);
2928    }
2929
2930    #[test]
2931    fn inmem_object_get_by_name_opt() {
2932        let another_patient_name = DataElement::new(
2933            Tag(0x0010, 0x0010),
2934            VR::PN,
2935            PrimitiveValue::Str("Doe^John".to_string()),
2936        );
2937        let mut obj = InMemDicomObject::new_empty();
2938        obj.put(another_patient_name.clone());
2939        let elem1 = obj.element_by_name_opt("PatientName").unwrap();
2940        assert_eq!(elem1, Some(&another_patient_name));
2941
2942        // try a missing element, should return None
2943        assert_eq!(obj.element_by_name_opt("PatientID").unwrap(), None);
2944    }
2945
2946    #[test]
2947    fn inmem_object_take_element() {
2948        let another_patient_name = DataElement::new(
2949            Tag(0x0010, 0x0010),
2950            VR::PN,
2951            PrimitiveValue::Str("Doe^John".to_string()),
2952        );
2953        let mut obj = InMemDicomObject::new_empty();
2954        obj.put(another_patient_name.clone());
2955        let elem1 = obj.take_element(Tag(0x0010, 0x0010)).unwrap();
2956        assert_eq!(elem1, another_patient_name);
2957        assert!(matches!(
2958            obj.take_element(Tag(0x0010, 0x0010)),
2959            Err(AccessError::NoSuchDataElementTag {
2960                tag: Tag(0x0010, 0x0010),
2961                ..
2962            })
2963        ));
2964    }
2965
2966    #[test]
2967    fn inmem_object_take_element_by_name() {
2968        let another_patient_name = DataElement::new(
2969            Tag(0x0010, 0x0010),
2970            VR::PN,
2971            PrimitiveValue::Str("Doe^John".to_string()),
2972        );
2973        let mut obj = InMemDicomObject::new_empty();
2974        obj.put(another_patient_name.clone());
2975        let elem1 = obj.take_element_by_name("PatientName").unwrap();
2976        assert_eq!(elem1, another_patient_name);
2977        assert!(matches!(
2978            obj.take_element_by_name("PatientName"),
2979            Err(AccessByNameError::NoSuchDataElementAlias {
2980                tag: Tag(0x0010, 0x0010),
2981                alias,
2982                ..
2983            }) if alias == "PatientName"));
2984    }
2985
2986    #[test]
2987    fn inmem_object_remove_element() {
2988        let another_patient_name = DataElement::new(
2989            Tag(0x0010, 0x0010),
2990            VR::PN,
2991            PrimitiveValue::Str("Doe^John".to_string()),
2992        );
2993        let mut obj = InMemDicomObject::new_empty();
2994        obj.put(another_patient_name.clone());
2995        assert!(obj.remove_element(Tag(0x0010, 0x0010)));
2996        assert!(!obj.remove_element(Tag(0x0010, 0x0010)));
2997    }
2998
2999    #[test]
3000    fn inmem_object_remove_element_by_name() {
3001        let another_patient_name = DataElement::new(
3002            Tag(0x0010, 0x0010),
3003            VR::PN,
3004            PrimitiveValue::Str("Doe^John".to_string()),
3005        );
3006        let mut obj = InMemDicomObject::new_empty();
3007        obj.put(another_patient_name.clone());
3008        assert!(obj.remove_element_by_name("PatientName").unwrap());
3009        assert!(!obj.remove_element_by_name("PatientName").unwrap());
3010    }
3011
3012    /// Elements are traversed in tag order.
3013    #[test]
3014    fn inmem_traverse_elements() {
3015        let sop_uid = "1.4.645.212121";
3016        let mut obj = InMemDicomObject::new_empty();
3017
3018        obj.put(DataElement::new(
3019            Tag(0x0010, 0x0010),
3020            VR::PN,
3021            dicom_value!(Strs, ["Doe^John"]),
3022        ));
3023        obj.put(DataElement::new(
3024            Tag(0x0008, 0x0060),
3025            VR::CS,
3026            dicom_value!(Strs, ["CR"]),
3027        ));
3028        obj.put(DataElement::new(
3029            Tag(0x0008, 0x0018),
3030            VR::UI,
3031            dicom_value!(Strs, [sop_uid]),
3032        ));
3033
3034        {
3035            let mut iter = obj.iter();
3036            assert_eq!(
3037                *iter.next().unwrap().header(),
3038                DataElementHeader::new(Tag(0x0008, 0x0018), VR::UI, Length(sop_uid.len() as u32)),
3039            );
3040            assert_eq!(
3041                *iter.next().unwrap().header(),
3042                DataElementHeader::new(Tag(0x0008, 0x0060), VR::CS, Length(2)),
3043            );
3044            assert_eq!(
3045                *iter.next().unwrap().header(),
3046                DataElementHeader::new(Tag(0x0010, 0x0010), VR::PN, Length(8)),
3047            );
3048        }
3049
3050        // .tags()
3051        let tags: Vec<_> = obj.tags().collect();
3052        assert_eq!(
3053            tags,
3054            vec![
3055                Tag(0x0008, 0x0018),
3056                Tag(0x0008, 0x0060),
3057                Tag(0x0010, 0x0010),
3058            ]
3059        );
3060
3061        // .into_iter()
3062        let mut iter = obj.into_iter();
3063        assert_eq!(
3064            iter.next(),
3065            Some(DataElement::new(
3066                Tag(0x0008, 0x0018),
3067                VR::UI,
3068                dicom_value!(Strs, [sop_uid]),
3069            )),
3070        );
3071        assert_eq!(
3072            iter.next(),
3073            Some(DataElement::new(
3074                Tag(0x0008, 0x0060),
3075                VR::CS,
3076                dicom_value!(Strs, ["CR"]),
3077            )),
3078        );
3079        assert_eq!(
3080            iter.next(),
3081            Some(DataElement::new(
3082                Tag(0x0010, 0x0010),
3083                VR::PN,
3084                PrimitiveValue::from("Doe^John"),
3085            )),
3086        );
3087    }
3088
3089    #[test]
3090    fn inmem_empty_object_into_tokens() {
3091        let obj = InMemDicomObject::new_empty();
3092        let tokens = obj.into_tokens();
3093        assert_eq!(tokens.count(), 0);
3094    }
3095
3096    #[test]
3097    fn inmem_shallow_object_from_tokens() {
3098        let tokens = vec![
3099            DataToken::ElementHeader(DataElementHeader {
3100                tag: Tag(0x0008, 0x0060),
3101                vr: VR::CS,
3102                len: Length(2),
3103            }),
3104            DataToken::PrimitiveValue(PrimitiveValue::Str("MG".to_owned())),
3105            DataToken::ElementHeader(DataElementHeader {
3106                tag: Tag(0x0010, 0x0010),
3107                vr: VR::PN,
3108                len: Length(8),
3109            }),
3110            DataToken::PrimitiveValue(PrimitiveValue::Str("Doe^John".to_owned())),
3111        ];
3112
3113        let gt_obj = InMemDicomObject::from_element_iter(vec![
3114            DataElement::new(
3115                Tag(0x0010, 0x0010),
3116                VR::PN,
3117                PrimitiveValue::Str("Doe^John".to_string()),
3118            ),
3119            DataElement::new(
3120                Tag(0x0008, 0x0060),
3121                VR::CS,
3122                PrimitiveValue::Str("MG".to_string()),
3123            ),
3124        ]);
3125
3126        let obj = InMemDicomObject::build_object(
3127            &mut tokens.into_iter().map(Result::Ok),
3128            StandardDataDictionary,
3129            false,
3130            Length::UNDEFINED,
3131            None,
3132            None,
3133        )
3134        .unwrap();
3135
3136        assert_obj_eq(&obj, &gt_obj);
3137    }
3138
3139    #[test]
3140    fn inmem_shallow_object_into_tokens() {
3141        let patient_name = DataElement::new(
3142            Tag(0x0010, 0x0010),
3143            VR::PN,
3144            PrimitiveValue::Str("Doe^John".to_string()),
3145        );
3146        let modality = DataElement::new(
3147            Tag(0x0008, 0x0060),
3148            VR::CS,
3149            PrimitiveValue::Str("MG".to_string()),
3150        );
3151        let mut obj = InMemDicomObject::new_empty();
3152        obj.put(patient_name);
3153        obj.put(modality);
3154
3155        let tokens: Vec<_> = obj.into_tokens().collect();
3156
3157        assert_eq!(
3158            tokens,
3159            vec![
3160                DataToken::ElementHeader(DataElementHeader {
3161                    tag: Tag(0x0008, 0x0060),
3162                    vr: VR::CS,
3163                    len: Length(2),
3164                }),
3165                DataToken::PrimitiveValue(PrimitiveValue::Str("MG".to_owned())),
3166                DataToken::ElementHeader(DataElementHeader {
3167                    tag: Tag(0x0010, 0x0010),
3168                    vr: VR::PN,
3169                    len: Length(8),
3170                }),
3171                DataToken::PrimitiveValue(PrimitiveValue::Str("Doe^John".to_owned())),
3172            ]
3173        );
3174    }
3175
3176    #[test]
3177    fn inmem_deep_object_from_tokens() {
3178        use smallvec::smallvec;
3179
3180        let obj_1 = InMemDicomObject::from_element_iter(vec![
3181            DataElement::new(Tag(0x0018, 0x6012), VR::US, Value::Primitive(1_u16.into())),
3182            DataElement::new(Tag(0x0018, 0x6014), VR::US, Value::Primitive(2_u16.into())),
3183        ]);
3184
3185        let obj_2 = InMemDicomObject::from_element_iter(vec![DataElement::new(
3186            Tag(0x0018, 0x6012),
3187            VR::US,
3188            Value::Primitive(4_u16.into()),
3189        )]);
3190
3191        let gt_obj = InMemDicomObject::from_element_iter(vec![
3192            DataElement::new(
3193                Tag(0x0018, 0x6011),
3194                VR::SQ,
3195                Value::from(DataSetSequence::new(
3196                    smallvec![obj_1, obj_2],
3197                    Length::UNDEFINED,
3198                )),
3199            ),
3200            DataElement::new(Tag(0x0020, 0x4000), VR::LT, Value::Primitive("TEST".into())),
3201        ]);
3202
3203        let tokens: Vec<_> = vec![
3204            DataToken::SequenceStart {
3205                tag: Tag(0x0018, 0x6011),
3206                len: Length::UNDEFINED,
3207            },
3208            DataToken::ItemStart {
3209                len: Length::UNDEFINED,
3210            },
3211            DataToken::ElementHeader(DataElementHeader {
3212                tag: Tag(0x0018, 0x6012),
3213                vr: VR::US,
3214                len: Length(2),
3215            }),
3216            DataToken::PrimitiveValue(PrimitiveValue::U16([1].as_ref().into())),
3217            DataToken::ElementHeader(DataElementHeader {
3218                tag: Tag(0x0018, 0x6014),
3219                vr: VR::US,
3220                len: Length(2),
3221            }),
3222            DataToken::PrimitiveValue(PrimitiveValue::U16([2].as_ref().into())),
3223            DataToken::ItemEnd,
3224            DataToken::ItemStart {
3225                len: Length::UNDEFINED,
3226            },
3227            DataToken::ElementHeader(DataElementHeader {
3228                tag: Tag(0x0018, 0x6012),
3229                vr: VR::US,
3230                len: Length(2),
3231            }),
3232            DataToken::PrimitiveValue(PrimitiveValue::U16([4].as_ref().into())),
3233            DataToken::ItemEnd,
3234            DataToken::SequenceEnd,
3235            DataToken::ElementHeader(DataElementHeader {
3236                tag: Tag(0x0020, 0x4000),
3237                vr: VR::LT,
3238                len: Length(4),
3239            }),
3240            DataToken::PrimitiveValue(PrimitiveValue::Str("TEST".into())),
3241        ];
3242
3243        let obj = InMemDicomObject::build_object(
3244            &mut tokens.into_iter().map(Result::Ok),
3245            StandardDataDictionary,
3246            false,
3247            Length::UNDEFINED,
3248            None,
3249            None,
3250        )
3251        .unwrap();
3252
3253        assert_obj_eq(&obj, &gt_obj);
3254    }
3255
3256    #[test]
3257    fn inmem_deep_object_into_tokens() {
3258        use smallvec::smallvec;
3259
3260        let obj_1 = InMemDicomObject::from_element_iter(vec![
3261            DataElement::new(Tag(0x0018, 0x6012), VR::US, Value::Primitive(1_u16.into())),
3262            DataElement::new(Tag(0x0018, 0x6014), VR::US, Value::Primitive(2_u16.into())),
3263        ]);
3264
3265        let obj_2 = InMemDicomObject::from_element_iter(vec![DataElement::new(
3266            Tag(0x0018, 0x6012),
3267            VR::US,
3268            Value::Primitive(4_u16.into()),
3269        )]);
3270
3271        let main_obj = InMemDicomObject::from_element_iter(vec![
3272            DataElement::new(
3273                Tag(0x0018, 0x6011),
3274                VR::SQ,
3275                Value::from(DataSetSequence::new(
3276                    smallvec![obj_1, obj_2],
3277                    Length::UNDEFINED,
3278                )),
3279            ),
3280            DataElement::new(Tag(0x0020, 0x4000), VR::LT, Value::Primitive("TEST".into())),
3281        ]);
3282
3283        let tokens: Vec<_> = main_obj.into_tokens().collect();
3284
3285        assert_eq!(
3286            tokens,
3287            vec![
3288                DataToken::SequenceStart {
3289                    tag: Tag(0x0018, 0x6011),
3290                    len: Length::UNDEFINED,
3291                },
3292                DataToken::ItemStart {
3293                    len: Length::UNDEFINED,
3294                },
3295                DataToken::ElementHeader(DataElementHeader {
3296                    tag: Tag(0x0018, 0x6012),
3297                    vr: VR::US,
3298                    len: Length(2),
3299                }),
3300                DataToken::PrimitiveValue(PrimitiveValue::U16([1].as_ref().into())),
3301                DataToken::ElementHeader(DataElementHeader {
3302                    tag: Tag(0x0018, 0x6014),
3303                    vr: VR::US,
3304                    len: Length(2),
3305                }),
3306                DataToken::PrimitiveValue(PrimitiveValue::U16([2].as_ref().into())),
3307                DataToken::ItemEnd,
3308                DataToken::ItemStart {
3309                    len: Length::UNDEFINED,
3310                },
3311                DataToken::ElementHeader(DataElementHeader {
3312                    tag: Tag(0x0018, 0x6012),
3313                    vr: VR::US,
3314                    len: Length(2),
3315                }),
3316                DataToken::PrimitiveValue(PrimitiveValue::U16([4].as_ref().into())),
3317                DataToken::ItemEnd,
3318                DataToken::SequenceEnd,
3319                DataToken::ElementHeader(DataElementHeader {
3320                    tag: Tag(0x0020, 0x4000),
3321                    vr: VR::LT,
3322                    len: Length(4),
3323                }),
3324                DataToken::PrimitiveValue(PrimitiveValue::Str("TEST".into())),
3325            ]
3326        );
3327    }
3328
3329    #[test]
3330    fn inmem_encapsulated_pixel_data_from_tokens() {
3331        use smallvec::smallvec;
3332
3333        let gt_obj = InMemDicomObject::from_element_iter(vec![DataElement::new(
3334            Tag(0x7fe0, 0x0010),
3335            VR::OB,
3336            Value::from(PixelFragmentSequence::new_fragments(smallvec![vec![
3337                0x33;
3338                32
3339            ]])),
3340        )]);
3341
3342        let tokens: Vec<_> = vec![
3343            DataToken::PixelSequenceStart,
3344            DataToken::ItemStart { len: Length(0) },
3345            DataToken::ItemEnd,
3346            DataToken::ItemStart { len: Length(32) },
3347            DataToken::ItemValue(vec![0x33; 32]),
3348            DataToken::ItemEnd,
3349            DataToken::SequenceEnd,
3350        ];
3351
3352        let obj = InMemDicomObject::build_object(
3353            &mut tokens.into_iter().map(Result::Ok),
3354            StandardDataDictionary,
3355            false,
3356            Length::UNDEFINED,
3357            None,
3358            None,
3359        )
3360        .unwrap();
3361
3362        assert_obj_eq(&obj, &gt_obj);
3363    }
3364
3365    #[test]
3366    fn inmem_encapsulated_pixel_data_into_tokens() {
3367        use smallvec::smallvec;
3368
3369        let main_obj = InMemDicomObject::from_element_iter(vec![DataElement::new(
3370            Tag(0x7fe0, 0x0010),
3371            VR::OB,
3372            Value::from(PixelFragmentSequence::new_fragments(smallvec![vec![
3373                0x33;
3374                32
3375            ]])),
3376        )]);
3377
3378        let tokens: Vec<_> = main_obj.into_tokens().collect();
3379
3380        assert_eq!(
3381            tokens,
3382            vec![
3383                DataToken::PixelSequenceStart,
3384                DataToken::ItemStart { len: Length(0) },
3385                DataToken::ItemEnd,
3386                DataToken::ItemStart { len: Length(32) },
3387                DataToken::ItemValue(vec![0x33; 32]),
3388                DataToken::ItemEnd,
3389                DataToken::SequenceEnd,
3390            ]
3391        );
3392    }
3393
3394    /// Test that a DICOM object can be reliably used
3395    /// behind the `DicomObject` trait.
3396    #[test]
3397    fn can_use_behind_trait() {
3398        fn dicom_dataset() -> impl DicomObject {
3399            InMemDicomObject::from_element_iter([DataElement::new(
3400                tags::PATIENT_NAME,
3401                VR::PN,
3402                PrimitiveValue::Str("Doe^John".to_string()),
3403            )])
3404        }
3405
3406        let obj = dicom_dataset();
3407        let elem1 = obj
3408            .attr_by_name_opt("PatientName")
3409            .unwrap()
3410            .expect("PatientName should be present");
3411        assert_eq!(
3412            &elem1
3413                .to_str()
3414                .expect("should be able to retrieve patient name as string"),
3415            "Doe^John"
3416        );
3417
3418        // try a missing element, should return None
3419        assert!(obj.attr_opt(tags::PATIENT_ID).unwrap().is_none());
3420    }
3421
3422    /// Test attribute operations on in-memory DICOM objects.
3423    #[test]
3424    fn inmem_ops() {
3425        // create a base DICOM object
3426        let base_obj = InMemDicomObject::from_element_iter([
3427            DataElement::new(
3428                tags::SERIES_INSTANCE_UID,
3429                VR::UI,
3430                PrimitiveValue::from("2.25.137041794342168732369025909031346220736.1"),
3431            ),
3432            DataElement::new(
3433                tags::SERIES_INSTANCE_UID,
3434                VR::UI,
3435                PrimitiveValue::from("2.25.137041794342168732369025909031346220736.1"),
3436            ),
3437            DataElement::new(
3438                tags::SOP_INSTANCE_UID,
3439                VR::UI,
3440                PrimitiveValue::from("2.25.137041794342168732369025909031346220736.1.1"),
3441            ),
3442            DataElement::new(
3443                tags::STUDY_DESCRIPTION,
3444                VR::LO,
3445                PrimitiveValue::from("Test study"),
3446            ),
3447            DataElement::new(
3448                tags::INSTITUTION_NAME,
3449                VR::LO,
3450                PrimitiveValue::from("Test Hospital"),
3451            ),
3452            DataElement::new(tags::ROWS, VR::US, PrimitiveValue::from(768_u16)),
3453            DataElement::new(tags::COLUMNS, VR::US, PrimitiveValue::from(1024_u16)),
3454            DataElement::new(
3455                tags::LOSSY_IMAGE_COMPRESSION,
3456                VR::CS,
3457                PrimitiveValue::from("01"),
3458            ),
3459            DataElement::new(
3460                tags::LOSSY_IMAGE_COMPRESSION_RATIO,
3461                VR::DS,
3462                PrimitiveValue::from("5"),
3463            ),
3464            DataElement::new(
3465                tags::LOSSY_IMAGE_COMPRESSION_METHOD,
3466                VR::DS,
3467                PrimitiveValue::from("ISO_10918_1"),
3468            ),
3469        ]);
3470
3471        {
3472            // remove
3473            let mut obj = base_obj.clone();
3474            let op = AttributeOp {
3475                selector: AttributeSelector::from(tags::STUDY_DESCRIPTION),
3476                action: AttributeAction::Remove,
3477            };
3478
3479            obj.apply(op).unwrap();
3480
3481            assert_eq!(obj.get(tags::STUDY_DESCRIPTION), None);
3482        }
3483        {
3484            let mut obj = base_obj.clone();
3485
3486            // set if missing does nothing
3487            // on an existing string
3488            let op = AttributeOp {
3489                selector: tags::INSTITUTION_NAME.into(),
3490                action: AttributeAction::SetIfMissing("Nope Hospital".into()),
3491            };
3492
3493            obj.apply(op).unwrap();
3494
3495            assert_eq!(
3496                obj.get(tags::INSTITUTION_NAME),
3497                Some(&DataElement::new(
3498                    tags::INSTITUTION_NAME,
3499                    VR::LO,
3500                    PrimitiveValue::from("Test Hospital"),
3501                ))
3502            );
3503
3504            // replace string
3505            let op = AttributeOp::new(
3506                tags::INSTITUTION_NAME,
3507                AttributeAction::ReplaceStr("REMOVED".into()),
3508            );
3509
3510            obj.apply(op).unwrap();
3511
3512            assert_eq!(
3513                obj.get(tags::INSTITUTION_NAME),
3514                Some(&DataElement::new(
3515                    tags::INSTITUTION_NAME,
3516                    VR::LO,
3517                    PrimitiveValue::from("REMOVED"),
3518                ))
3519            );
3520
3521            // replacing a non-existing attribute
3522            // does nothing
3523            let op = AttributeOp::new(
3524                tags::REQUESTING_PHYSICIAN,
3525                AttributeAction::ReplaceStr("Doctor^Anonymous".into()),
3526            );
3527
3528            obj.apply(op).unwrap();
3529
3530            assert_eq!(obj.get(tags::REQUESTING_PHYSICIAN), None);
3531
3532            // but DetIfMissing works
3533            let op = AttributeOp::new(
3534                tags::REQUESTING_PHYSICIAN,
3535                AttributeAction::SetStrIfMissing("Doctor^Anonymous".into()),
3536            );
3537
3538            obj.apply(op).unwrap();
3539
3540            assert_eq!(
3541                obj.get(tags::REQUESTING_PHYSICIAN),
3542                Some(&DataElement::new(
3543                    tags::REQUESTING_PHYSICIAN,
3544                    VR::PN,
3545                    PrimitiveValue::from("Doctor^Anonymous"),
3546                ))
3547            );
3548        }
3549        {
3550            // reset string
3551            let mut obj = base_obj.clone();
3552            let op = AttributeOp::new(
3553                tags::REQUESTING_PHYSICIAN,
3554                AttributeAction::SetStr("Doctor^Anonymous".into()),
3555            );
3556
3557            obj.apply(op).unwrap();
3558
3559            assert_eq!(
3560                obj.get(tags::REQUESTING_PHYSICIAN),
3561                Some(&DataElement::new(
3562                    tags::REQUESTING_PHYSICIAN,
3563                    VR::PN,
3564                    PrimitiveValue::from("Doctor^Anonymous"),
3565                ))
3566            );
3567        }
3568
3569        {
3570            // extend with number
3571            let mut obj = base_obj.clone();
3572            let op = AttributeOp::new(
3573                tags::LOSSY_IMAGE_COMPRESSION_RATIO,
3574                AttributeAction::PushF64(1.25),
3575            );
3576
3577            obj.apply(op).unwrap();
3578
3579            assert_eq!(
3580                obj.get(tags::LOSSY_IMAGE_COMPRESSION_RATIO),
3581                Some(&DataElement::new(
3582                    tags::LOSSY_IMAGE_COMPRESSION_RATIO,
3583                    VR::DS,
3584                    dicom_value!(Strs, ["5", "1.25"]),
3585                ))
3586            );
3587        }
3588    }
3589
3590    /// Test attribute operations on nested data sets.
3591    #[test]
3592    fn nested_inmem_ops() {
3593        let obj_1 = InMemDicomObject::from_element_iter([
3594            DataElement::new(Tag(0x0018, 0x6012), VR::US, PrimitiveValue::from(1_u16)),
3595            DataElement::new(Tag(0x0018, 0x6014), VR::US, PrimitiveValue::from(2_u16)),
3596        ]);
3597
3598        let obj_2 = InMemDicomObject::from_element_iter([DataElement::new(
3599            Tag(0x0018, 0x6012),
3600            VR::US,
3601            PrimitiveValue::from(4_u16),
3602        )]);
3603
3604        let mut main_obj = InMemDicomObject::from_element_iter(vec![
3605            DataElement::new(
3606                tags::SEQUENCE_OF_ULTRASOUND_REGIONS,
3607                VR::SQ,
3608                DataSetSequence::from(vec![obj_1, obj_2]),
3609            ),
3610            DataElement::new(Tag(0x0020, 0x4000), VR::LT, Value::Primitive("TEST".into())),
3611        ]);
3612
3613        let selector: AttributeSelector =
3614            (tags::SEQUENCE_OF_ULTRASOUND_REGIONS, 0, Tag(0x0018, 0x6014)).into();
3615
3616        main_obj
3617            .apply(AttributeOp::new(selector, AttributeAction::Set(3.into())))
3618            .unwrap();
3619
3620        assert_eq!(
3621            main_obj
3622                .get(tags::SEQUENCE_OF_ULTRASOUND_REGIONS)
3623                .unwrap()
3624                .items()
3625                .unwrap()[0]
3626                .get(Tag(0x0018, 0x6014))
3627                .unwrap()
3628                .value(),
3629            &PrimitiveValue::from(3).into(),
3630        );
3631
3632        let selector: AttributeSelector =
3633            (tags::SEQUENCE_OF_ULTRASOUND_REGIONS, 1, Tag(0x0018, 0x6012)).into();
3634
3635        main_obj
3636            .apply(AttributeOp::new(selector, AttributeAction::Remove))
3637            .unwrap();
3638
3639        // item should be empty
3640        assert_eq!(
3641            main_obj
3642                .get(tags::SEQUENCE_OF_ULTRASOUND_REGIONS)
3643                .unwrap()
3644                .items()
3645                .unwrap()[1]
3646                .tags()
3647                .collect::<Vec<_>>(),
3648            Vec::<Tag>::new(),
3649        );
3650
3651        // trying to access the removed element returns an error
3652        assert!(matches!(
3653            main_obj.value_at((tags::SEQUENCE_OF_ULTRASOUND_REGIONS, 1, Tag(0x0018, 0x6012),)),
3654            Err(AtAccessError::MissingLeafElement { .. })
3655        ))
3656    }
3657
3658    /// Test that constructive operations create items if necessary.
3659    #[test]
3660    fn constructive_op() {
3661        let mut obj = InMemDicomObject::from_element_iter([DataElement::new(
3662            tags::SEQUENCE_OF_ULTRASOUND_REGIONS,
3663            VR::SQ,
3664            DataSetSequence::empty(),
3665        )]);
3666
3667        let op = AttributeOp::new(
3668            (
3669                tags::SEQUENCE_OF_ULTRASOUND_REGIONS,
3670                0,
3671                tags::REGION_SPATIAL_FORMAT,
3672            ),
3673            AttributeAction::Set(5_u16.into()),
3674        );
3675
3676        obj.apply(op).unwrap();
3677
3678        // should have an item
3679        assert_eq!(
3680            obj.get(tags::SEQUENCE_OF_ULTRASOUND_REGIONS)
3681                .unwrap()
3682                .items()
3683                .unwrap()
3684                .len(),
3685            1,
3686        );
3687
3688        // item should have 1 element
3689        assert_eq!(
3690            &obj.get(tags::SEQUENCE_OF_ULTRASOUND_REGIONS)
3691                .unwrap()
3692                .items()
3693                .unwrap()[0],
3694            &InMemDicomObject::from_element_iter([DataElement::new(
3695                tags::REGION_SPATIAL_FORMAT,
3696                VR::US,
3697                PrimitiveValue::from(5_u16)
3698            )]),
3699        );
3700
3701        // new value can be accessed using value_at
3702        assert_eq!(
3703            obj.value_at((
3704                tags::SEQUENCE_OF_ULTRASOUND_REGIONS,
3705                0,
3706                tags::REGION_SPATIAL_FORMAT
3707            ))
3708            .unwrap(),
3709            &Value::from(PrimitiveValue::from(5_u16)),
3710        )
3711    }
3712
3713    /// Test that operations on in-memory DICOM objects
3714    /// can create sequences from scratch.
3715    #[test]
3716    fn inmem_ops_can_create_seq() {
3717        let mut obj = InMemDicomObject::new_empty();
3718
3719        obj.apply(AttributeOp::new(
3720            tags::SEQUENCE_OF_ULTRASOUND_REGIONS,
3721            AttributeAction::SetIfMissing(PrimitiveValue::Empty),
3722        ))
3723        .unwrap();
3724
3725        {
3726            // should create an empty sequence
3727            let sequence_ultrasound = obj
3728                .get(tags::SEQUENCE_OF_ULTRASOUND_REGIONS)
3729                .expect("should have sequence element");
3730
3731            assert_eq!(sequence_ultrasound.vr(), VR::SQ);
3732
3733            assert_eq!(sequence_ultrasound.items(), Some(&[][..]),);
3734        }
3735
3736        obj.apply(AttributeOp::new(
3737            (
3738                tags::SEQUENCE_OF_ULTRASOUND_REGIONS,
3739                tags::REGION_SPATIAL_FORMAT,
3740            ),
3741            AttributeAction::Set(1_u16.into()),
3742        ))
3743        .unwrap();
3744
3745        {
3746            // sequence should now have an item
3747            assert_eq!(
3748                obj.get(tags::SEQUENCE_OF_ULTRASOUND_REGIONS)
3749                    .unwrap()
3750                    .items()
3751                    .map(|items| items.len()),
3752                Some(1),
3753            );
3754        }
3755    }
3756
3757    /// Test that operations on in-memory DICOM objects
3758    /// can create deeply nested attributes from scratch.
3759    #[test]
3760    fn inmem_ops_can_create_nested_attribute() {
3761        let mut obj = InMemDicomObject::new_empty();
3762
3763        obj.apply(AttributeOp::new(
3764            (
3765                tags::SEQUENCE_OF_ULTRASOUND_REGIONS,
3766                tags::REGION_SPATIAL_FORMAT,
3767            ),
3768            AttributeAction::Set(1_u16.into()),
3769        ))
3770        .unwrap();
3771
3772        {
3773            // should create a sequence with a single item
3774            assert_eq!(
3775                obj.get(tags::SEQUENCE_OF_ULTRASOUND_REGIONS)
3776                    .unwrap()
3777                    .items()
3778                    .map(|items| items.len()),
3779                Some(1),
3780            );
3781
3782            // item should have Region Spatial Format
3783            assert_eq!(
3784                obj.value_at((
3785                    tags::SEQUENCE_OF_ULTRASOUND_REGIONS,
3786                    tags::REGION_SPATIAL_FORMAT
3787                ))
3788                .unwrap(),
3789                &PrimitiveValue::from(1_u16).into(),
3790            );
3791
3792            // same result when using `DicomObject::at`
3793            assert_eq!(
3794                DicomObject::at(
3795                    &obj,
3796                    (
3797                        tags::SEQUENCE_OF_ULTRASOUND_REGIONS,
3798                        tags::REGION_SPATIAL_FORMAT
3799                    )
3800                )
3801                .unwrap(),
3802                &PrimitiveValue::from(1_u16).into(),
3803            );
3804        }
3805    }
3806
3807    /// Test that operations on in-memory DICOM objects
3808    /// can truncate sequences.
3809    #[test]
3810    fn inmem_ops_can_truncate_seq() {
3811        let mut obj = InMemDicomObject::from_element_iter([
3812            DataElement::new(
3813                tags::SEQUENCE_OF_ULTRASOUND_REGIONS,
3814                VR::SQ,
3815                DataSetSequence::from(vec![InMemDicomObject::new_empty()]),
3816            ),
3817            DataElement::new_with_len(
3818                tags::PIXEL_DATA,
3819                VR::OB,
3820                Length::UNDEFINED,
3821                PixelFragmentSequence::new(vec![], vec![vec![0xcc; 8192], vec![0x55; 1024]]),
3822            ),
3823        ]);
3824
3825        // removes the single item in the sequences
3826        obj.apply(AttributeOp::new(
3827            tags::SEQUENCE_OF_ULTRASOUND_REGIONS,
3828            AttributeAction::Truncate(0),
3829        ))
3830        .unwrap();
3831
3832        {
3833            let sequence_ultrasound = obj
3834                .get(tags::SEQUENCE_OF_ULTRASOUND_REGIONS)
3835                .expect("should have sequence element");
3836            assert_eq!(sequence_ultrasound.items(), Some(&[][..]),);
3837        }
3838
3839        // remove one of the fragments
3840        obj.apply(AttributeOp::new(
3841            tags::PIXEL_DATA,
3842            AttributeAction::Truncate(1),
3843        ))
3844        .unwrap();
3845
3846        {
3847            // pixel data should now have a single fragment
3848            assert_eq!(
3849                obj.get(tags::PIXEL_DATA)
3850                    .unwrap()
3851                    .fragments()
3852                    .map(|fragments| fragments.len()),
3853                Some(1),
3854            );
3855        }
3856    }
3857
3858    #[test]
3859    fn inmem_obj_reset_defined_length() {
3860        let mut entries: BTreeMap<Tag, InMemElement<StandardDataDictionary>> = BTreeMap::new();
3861
3862        let patient_name =
3863            DataElement::new(tags::PATIENT_NAME, VR::CS, PrimitiveValue::from("Doe^John"));
3864
3865        let study_description = DataElement::new(
3866            tags::STUDY_DESCRIPTION,
3867            VR::LO,
3868            PrimitiveValue::from("Test study"),
3869        );
3870
3871        entries.insert(tags::PATIENT_NAME, patient_name.clone());
3872
3873        // create object and force an arbitrary defined Length value
3874        let obj = InMemDicomObject::<StandardDataDictionary> {
3875            entries,
3876            dict: StandardDataDictionary,
3877            len: Length(1),
3878            charset_changed: false,
3879        };
3880
3881        assert!(obj.length().is_defined());
3882
3883        let mut o = obj.clone();
3884        o.put_element(study_description);
3885        assert!(o.length().is_undefined());
3886
3887        let mut o = obj.clone();
3888        o.remove_element(tags::PATIENT_NAME);
3889        assert!(o.length().is_undefined());
3890
3891        let mut o = obj.clone();
3892        o.remove_element_by_name("PatientName").unwrap();
3893        assert!(o.length().is_undefined());
3894
3895        let mut o = obj.clone();
3896        o.take_element(tags::PATIENT_NAME).unwrap();
3897        assert!(o.length().is_undefined());
3898
3899        let mut o = obj.clone();
3900        o.take_element_by_name("PatientName").unwrap();
3901        assert!(o.length().is_undefined());
3902
3903        // resets Length even when retain does not make any changes
3904        let mut o = obj.clone();
3905        o.retain(|e| e.tag() == tags::PATIENT_NAME);
3906        assert!(o.length().is_undefined());
3907
3908        let mut o = obj.clone();
3909        o.apply(AttributeOp::new(
3910            tags::PATIENT_NAME,
3911            AttributeAction::Remove,
3912        ))
3913        .unwrap();
3914        assert!(o.length().is_undefined());
3915
3916        let mut o = obj.clone();
3917        o.apply(AttributeOp::new(tags::PATIENT_NAME, AttributeAction::Empty))
3918            .unwrap();
3919        assert!(o.length().is_undefined());
3920
3921        let mut o = obj.clone();
3922        o.apply(AttributeOp::new(
3923            tags::PATIENT_NAME,
3924            AttributeAction::SetVr(VR::IS),
3925        ))
3926        .unwrap();
3927        assert!(o.length().is_undefined());
3928
3929        let mut o = obj.clone();
3930        o.apply(AttributeOp::new(
3931            tags::PATIENT_NAME,
3932            AttributeAction::Set(dicom_value!(Str, "Unknown")),
3933        ))
3934        .unwrap();
3935        assert!(o.length().is_undefined());
3936
3937        let mut o = obj.clone();
3938        o.apply(AttributeOp::new(
3939            tags::PATIENT_NAME,
3940            AttributeAction::SetStr("Patient^Anonymous".into()),
3941        ))
3942        .unwrap();
3943        assert!(o.length().is_undefined());
3944
3945        let mut o = obj.clone();
3946        o.apply(AttributeOp::new(
3947            tags::PATIENT_AGE,
3948            AttributeAction::SetIfMissing(dicom_value!(75)),
3949        ))
3950        .unwrap();
3951        assert!(o.length().is_undefined());
3952
3953        let mut o = obj.clone();
3954        o.apply(AttributeOp::new(
3955            tags::PATIENT_ADDRESS,
3956            AttributeAction::SetStrIfMissing("Chicago".into()),
3957        ))
3958        .unwrap();
3959        assert!(o.length().is_undefined());
3960
3961        let mut o = obj.clone();
3962        o.apply(AttributeOp::new(
3963            tags::PATIENT_NAME,
3964            AttributeAction::Replace(dicom_value!(Str, "Unknown")),
3965        ))
3966        .unwrap();
3967        assert!(o.length().is_undefined());
3968
3969        let mut o = obj.clone();
3970        o.apply(AttributeOp::new(
3971            tags::PATIENT_NAME,
3972            AttributeAction::ReplaceStr("Unknown".into()),
3973        ))
3974        .unwrap();
3975        assert!(o.length().is_undefined());
3976
3977        let mut o = obj.clone();
3978        o.apply(AttributeOp::new(
3979            tags::PATIENT_NAME,
3980            AttributeAction::PushStr("^Prof".into()),
3981        ))
3982        .unwrap();
3983        assert!(o.length().is_undefined());
3984
3985        let mut o = obj.clone();
3986        o.apply(AttributeOp::new(
3987            tags::PATIENT_NAME,
3988            AttributeAction::PushI32(-16),
3989        ))
3990        .unwrap();
3991        assert!(o.length().is_undefined());
3992
3993        let mut o = obj.clone();
3994        o.apply(AttributeOp::new(
3995            tags::PATIENT_NAME,
3996            AttributeAction::PushU32(16),
3997        ))
3998        .unwrap();
3999        assert!(o.length().is_undefined());
4000
4001        let mut o = obj.clone();
4002        o.apply(AttributeOp::new(
4003            tags::PATIENT_NAME,
4004            AttributeAction::PushI16(-16),
4005        ))
4006        .unwrap();
4007        assert!(o.length().is_undefined());
4008
4009        let mut o = obj.clone();
4010        o.apply(AttributeOp::new(
4011            tags::PATIENT_NAME,
4012            AttributeAction::PushU16(16),
4013        ))
4014        .unwrap();
4015        assert!(o.length().is_undefined());
4016
4017        let mut o = obj.clone();
4018        o.apply(AttributeOp::new(
4019            tags::PATIENT_NAME,
4020            AttributeAction::PushF32(16.16),
4021        ))
4022        .unwrap();
4023        assert!(o.length().is_undefined());
4024
4025        let mut o = obj.clone();
4026        o.apply(AttributeOp::new(
4027            tags::PATIENT_NAME,
4028            AttributeAction::PushF64(16.1616),
4029        ))
4030        .unwrap();
4031        assert!(o.length().is_undefined());
4032    }
4033
4034    #[test]
4035    fn create_commands() {
4036        // empty
4037        let obj = InMemDicomObject::command_from_element_iter([]);
4038        assert_eq!(
4039            obj.get(tags::COMMAND_GROUP_LENGTH)
4040                .map(|e| e.value().to_int::<u32>().unwrap()),
4041            Some(0)
4042        );
4043
4044        // C-FIND-RQ
4045        let obj = InMemDicomObject::command_from_element_iter([
4046            // affected SOP class UID: 8 + 28 = 36
4047            DataElement::new(
4048                tags::AFFECTED_SOP_CLASS_UID,
4049                VR::UI,
4050                PrimitiveValue::from("1.2.840.10008.5.1.4.1.2.1.1"),
4051            ),
4052            // command field: 36 + 8 + 2 = 46
4053            DataElement::new(
4054                tags::COMMAND_FIELD,
4055                VR::US,
4056                // 0020H: C-FIND-RQ message
4057                dicom_value!(U16, [0x0020]),
4058            ),
4059            // message ID: 46 + 8 + 2 = 56
4060            DataElement::new(tags::MESSAGE_ID, VR::US, dicom_value!(U16, [0])),
4061            //priority: 56 + 8 + 2 = 66
4062            DataElement::new(
4063                tags::PRIORITY,
4064                VR::US,
4065                // medium
4066                dicom_value!(U16, [0x0000]),
4067            ),
4068            // data set type: 66 + 8 + 2 = 76
4069            DataElement::new(
4070                tags::COMMAND_DATA_SET_TYPE,
4071                VR::US,
4072                dicom_value!(U16, [0x0001]),
4073            ),
4074        ]);
4075        assert_eq!(
4076            obj.get(tags::COMMAND_GROUP_LENGTH)
4077                .map(|e| e.value().to_int::<u32>().unwrap()),
4078            Some(76)
4079        );
4080
4081        let storage_sop_class_uid = "1.2.840.10008.5.1.4.1.1.4";
4082        let storage_sop_instance_uid = "2.25.221314879990624101283043547144116927116";
4083
4084        // C-STORE-RQ
4085        let obj = InMemDicomObject::command_from_element_iter([
4086            // group length (should be ignored in calculations and overridden)
4087            DataElement::new(
4088                tags::COMMAND_GROUP_LENGTH,
4089                VR::UL,
4090                PrimitiveValue::from(9999_u32),
4091            ),
4092            // SOP Class UID: 8 + 26 = 34
4093            DataElement::new(
4094                tags::AFFECTED_SOP_CLASS_UID,
4095                VR::UI,
4096                dicom_value!(Str, storage_sop_class_uid),
4097            ),
4098            // command field: 34 + 8 + 2 = 44
4099            DataElement::new(tags::COMMAND_FIELD, VR::US, dicom_value!(U16, [0x0001])),
4100            // message ID: 44 + 8 + 2 = 54
4101            DataElement::new(tags::MESSAGE_ID, VR::US, dicom_value!(U16, [1])),
4102            //priority: 54 + 8 + 2 = 64
4103            DataElement::new(tags::PRIORITY, VR::US, dicom_value!(U16, [0x0000])),
4104            // data set type: 64 + 8 + 2 = 74
4105            DataElement::new(
4106                tags::COMMAND_DATA_SET_TYPE,
4107                VR::US,
4108                dicom_value!(U16, [0x0000]),
4109            ),
4110            // affected SOP Instance UID: 74 + 8 + 44 = 126
4111            DataElement::new(
4112                tags::AFFECTED_SOP_INSTANCE_UID,
4113                VR::UI,
4114                dicom_value!(Str, storage_sop_instance_uid),
4115            ),
4116        ]);
4117
4118        assert_eq!(
4119            obj.get(tags::COMMAND_GROUP_LENGTH)
4120                .map(|e| e.value().to_int::<u32>().unwrap()),
4121            Some(126)
4122        );
4123    }
4124
4125    #[test]
4126    fn test_even_len() {
4127        assert_eq!(even_len(0), 0);
4128        assert_eq!(even_len(1), 2);
4129        assert_eq!(even_len(2), 2);
4130        assert_eq!(even_len(3), 4);
4131        assert_eq!(even_len(4), 4);
4132        assert_eq!(even_len(5), 6);
4133    }
4134
4135    #[test]
4136    fn can_update_value() {
4137        let mut obj = InMemDicomObject::from_element_iter([DataElement::new(
4138            tags::ANATOMIC_REGION_SEQUENCE,
4139            VR::SQ,
4140            DataSetSequence::empty(),
4141        )]);
4142        assert_eq!(
4143            obj.get(tags::ANATOMIC_REGION_SEQUENCE).map(|e| e.length()),
4144            Some(Length(0)),
4145        );
4146
4147        assert!(!obj.update_value(tags::BURNED_IN_ANNOTATION, |_value| {
4148            panic!("should not be called")
4149        }),);
4150
4151        let o = obj.update_value(tags::ANATOMIC_REGION_SEQUENCE, |value| {
4152            // add an item
4153            let items = value.items_mut().unwrap();
4154            items.push(InMemDicomObject::from_element_iter([DataElement::new(
4155                tags::INSTANCE_NUMBER,
4156                VR::IS,
4157                PrimitiveValue::from(1),
4158            )]));
4159        });
4160        assert!(o);
4161
4162        assert!(
4163            obj.get(tags::ANATOMIC_REGION_SEQUENCE)
4164                .unwrap()
4165                .length()
4166                .is_undefined()
4167        );
4168    }
4169
4170    #[test]
4171    fn deep_sequence_change_encoding_writes_undefined_sequence_length() {
4172        use smallvec::smallvec;
4173
4174        let obj_1 = InMemDicomObject::from_element_iter(vec![
4175            //The length of this string is 20 bytes in ISO_IR 100 but should be 22 bytes in ISO_IR 192 (UTF-8)
4176            DataElement::new(
4177                tags::STUDY_DESCRIPTION,
4178                VR::SL,
4179                Value::Primitive("MORFOLOGÍA Y FUNCIÓN".into()),
4180            ),
4181            //ISO_IR 100 and ISO_IR 192 length are the same
4182            DataElement::new(
4183                tags::SERIES_DESCRIPTION,
4184                VR::SL,
4185                Value::Primitive("0123456789".into()),
4186            ),
4187        ]);
4188
4189        let some_tag = Tag(0x0018, 0x6011);
4190
4191        let inner_sequence = InMemDicomObject::from_element_iter(vec![DataElement::new(
4192            some_tag,
4193            VR::SQ,
4194            Value::from(DataSetSequence::new(
4195                smallvec![obj_1],
4196                Length(30), //20 bytes from study, 10 from series
4197            )),
4198        )]);
4199        let outer_sequence = DataElement::new(
4200            some_tag,
4201            VR::SQ,
4202            Value::from(DataSetSequence::new(
4203                smallvec![inner_sequence.clone(), inner_sequence],
4204                Length(60), //20 bytes from study, 10 from series
4205            )),
4206        );
4207
4208        let original_object = InMemDicomObject::from_element_iter(vec![
4209            DataElement::new(tags::SPECIFIC_CHARACTER_SET, VR::CS, "ISO_IR 100"),
4210            outer_sequence,
4211        ]);
4212
4213        assert_eq!(
4214            original_object
4215                .get(some_tag)
4216                .expect("object should be present")
4217                .length(),
4218            Length(60)
4219        );
4220
4221        let mut changed_charset = original_object.clone();
4222        changed_charset.convert_to_utf8();
4223        assert!(changed_charset.charset_changed);
4224
4225        use dicom_parser::dataset::DataToken as token;
4226        let options = IntoTokensOptions::new(true);
4227        let converted_tokens: Vec<_> = changed_charset.into_tokens_with_options(options).collect();
4228
4229        assert_eq!(
4230            vec![
4231                token::ElementHeader(DataElementHeader {
4232                    tag: Tag(0x0008, 0x0005),
4233                    vr: VR::CS,
4234                    len: Length(10),
4235                }),
4236                token::PrimitiveValue("ISO_IR 192".into()),
4237                token::SequenceStart {
4238                    tag: Tag(0x0018, 0x6011),
4239                    len: Length::UNDEFINED,
4240                },
4241                token::ItemStart {
4242                    len: Length::UNDEFINED
4243                },
4244                token::SequenceStart {
4245                    tag: Tag(0x0018, 0x6011),
4246                    len: Length::UNDEFINED,
4247                },
4248                token::ItemStart {
4249                    len: Length::UNDEFINED
4250                },
4251                token::ElementHeader(DataElementHeader {
4252                    tag: Tag(0x0008, 0x1030),
4253                    vr: VR::SL,
4254                    len: Length(22),
4255                }),
4256                token::PrimitiveValue("MORFOLOGÍA Y FUNCIÓN".into()),
4257                token::ElementHeader(DataElementHeader {
4258                    tag: Tag(0x0008, 0x103E),
4259                    vr: VR::SL,
4260                    len: Length(10),
4261                }),
4262                token::PrimitiveValue("0123456789".into()),
4263                token::ItemEnd,
4264                token::SequenceEnd,
4265                token::ItemEnd,
4266                token::ItemStart {
4267                    len: Length::UNDEFINED
4268                },
4269                token::SequenceStart {
4270                    tag: Tag(0x0018, 0x6011),
4271                    len: Length::UNDEFINED,
4272                },
4273                token::ItemStart {
4274                    len: Length::UNDEFINED
4275                },
4276                token::ElementHeader(DataElementHeader {
4277                    tag: Tag(0x0008, 0x1030),
4278                    vr: VR::SL,
4279                    len: Length(22),
4280                }),
4281                token::PrimitiveValue("MORFOLOGÍA Y FUNCIÓN".into()),
4282                token::ElementHeader(DataElementHeader {
4283                    tag: Tag(0x0008, 0x103E),
4284                    vr: VR::SL,
4285                    len: Length(10),
4286                }),
4287                token::PrimitiveValue("0123456789".into()),
4288                token::ItemEnd,
4289                token::SequenceEnd,
4290                token::ItemEnd,
4291                token::SequenceEnd,
4292            ],
4293            converted_tokens
4294        );
4295    }
4296
4297    #[test]
4298    fn private_elements() {
4299        let mut ds = InMemDicomObject::from_element_iter(vec![
4300            DataElement::new(
4301                Tag(0x0009, 0x0010),
4302                VR::LO,
4303                PrimitiveValue::from("CREATOR 1"),
4304            ),
4305            DataElement::new(
4306                Tag(0x0009, 0x0011),
4307                VR::LO,
4308                PrimitiveValue::from("CREATOR 2"),
4309            ),
4310            DataElement::new(
4311                Tag(0x0011, 0x0010),
4312                VR::LO,
4313                PrimitiveValue::from("CREATOR 3"),
4314            ),
4315        ]);
4316        ds.put_private_element(
4317            0x0009,
4318            "CREATOR 1",
4319            0x01,
4320            VR::DS,
4321            PrimitiveValue::Str("1.0".to_string()),
4322        )
4323        .unwrap();
4324        ds.put_private_element(
4325            0x0009,
4326            "CREATOR 4",
4327            0x02,
4328            VR::DS,
4329            PrimitiveValue::Str("1.0".to_string()),
4330        )
4331        .unwrap();
4332
4333        let res = ds.put_private_element(
4334            0x0012,
4335            "CREATOR 4",
4336            0x02,
4337            VR::DS,
4338            PrimitiveValue::Str("1.0".to_string()),
4339        );
4340        assert_eq!(
4341            &res.err().unwrap().to_string(),
4342            "Group number must be odd, found 0x0012"
4343        );
4344
4345        assert_eq!(
4346            ds.private_element(0x0009, "CREATOR 1", 0x01)
4347                .unwrap()
4348                .value()
4349                .to_str()
4350                .unwrap(),
4351            "1.0"
4352        );
4353        assert_eq!(
4354            ds.private_element(0x0009, "CREATOR 4", 0x02)
4355                .unwrap()
4356                .value()
4357                .to_str()
4358                .unwrap(),
4359            "1.0"
4360        );
4361        assert_eq!(
4362            ds.private_element(0x0009, "CREATOR 4", 0x02)
4363                .unwrap()
4364                .header()
4365                .tag(),
4366            Tag(0x0009, 0x1202)
4367        );
4368    }
4369
4370    #[test]
4371    fn private_element_group_full() {
4372        let mut ds = InMemDicomObject::from_element_iter(
4373            (0..=0x00FFu16)
4374                .map(|i| {
4375                    DataElement::new(Tag(0x0009, i), VR::LO, PrimitiveValue::from("CREATOR 1"))
4376                })
4377                .collect::<Vec<DataElement<_>>>(),
4378        );
4379        let res = ds.put_private_element(0x0009, "TEST", 0x01, VR::DS, PrimitiveValue::from("1.0"));
4380        assert_eq!(
4381            res.err().unwrap().to_string(),
4382            "No space available in group 0x0009"
4383        );
4384    }
4385
4386    /// Helper to build the token stream used in read_to/read_until sequence tests:
4387    /// - (0018,6011) SQ  (sequence with two items)
4388    /// - (0020,4000) LT  "TEST"
4389    fn tokens_with_sequence() -> Vec<DataToken> {
4390        vec![
4391            DataToken::SequenceStart {
4392                tag: Tag(0x0018, 0x6011),
4393                len: Length::UNDEFINED,
4394            },
4395            DataToken::ItemStart {
4396                len: Length::UNDEFINED,
4397            },
4398            DataToken::ElementHeader(DataElementHeader {
4399                tag: Tag(0x0018, 0x6012),
4400                vr: VR::US,
4401                len: Length(2),
4402            }),
4403            DataToken::PrimitiveValue(PrimitiveValue::U16([1].as_ref().into())),
4404            DataToken::ItemEnd,
4405            DataToken::SequenceEnd,
4406            DataToken::ElementHeader(DataElementHeader {
4407                tag: Tag(0x0020, 0x4000),
4408                vr: VR::LT,
4409                len: Length(4),
4410            }),
4411            DataToken::PrimitiveValue(PrimitiveValue::Str("TEST".into())),
4412        ]
4413    }
4414
4415    #[test]
4416    fn build_object_read_to_sequence_tag() {
4417        // read_to(0x0018,0x6011) must include the sequence and stop before (0020,4000)
4418        let tokens = tokens_with_sequence();
4419        let obj = InMemDicomObject::build_object(
4420            &mut tokens.into_iter().map(Result::Ok),
4421            StandardDataDictionary,
4422            false,
4423            Length::UNDEFINED,
4424            None,
4425            Some(Tag(0x0018, 0x6011)),
4426        )
4427        .unwrap();
4428
4429        assert!(
4430            obj.element(Tag(0x0018, 0x6011)).is_ok(),
4431            "sequence should be present"
4432        );
4433        assert!(
4434            obj.element(Tag(0x0020, 0x4000)).is_err(),
4435            "element after sequence should be absent"
4436        );
4437    }
4438
4439    #[test]
4440    fn build_object_read_to_element_after_sequence() {
4441        // read_to(0x0020,0x4000) must include both the sequence and (0020,4000)
4442        let tokens = tokens_with_sequence();
4443        let obj = InMemDicomObject::build_object(
4444            &mut tokens.into_iter().map(Result::Ok),
4445            StandardDataDictionary,
4446            false,
4447            Length::UNDEFINED,
4448            None,
4449            Some(Tag(0x0020, 0x4000)),
4450        )
4451        .unwrap();
4452
4453        assert!(
4454            obj.element(Tag(0x0018, 0x6011)).is_ok(),
4455            "sequence should be present"
4456        );
4457        assert!(
4458            obj.element(Tag(0x0020, 0x4000)).is_ok(),
4459            "element at read_to tag should be present"
4460        );
4461    }
4462
4463    #[test]
4464    fn build_object_read_to_and_read_until_same_sequence_tag() {
4465        // when both are the same tag, read_until wins and the tag is excluded
4466        let tokens = tokens_with_sequence();
4467        let obj = InMemDicomObject::build_object(
4468            &mut tokens.into_iter().map(Result::Ok),
4469            StandardDataDictionary,
4470            false,
4471            Length::UNDEFINED,
4472            Some(Tag(0x0018, 0x6011)),
4473            Some(Tag(0x0018, 0x6011)),
4474        )
4475        .unwrap();
4476
4477        assert!(
4478            obj.element(Tag(0x0018, 0x6011)).is_err(),
4479            "read_until takes priority: sequence should be excluded"
4480        );
4481    }
4482
4483    #[test]
4484    fn build_object_read_to_sequence_equals_read_until_next() {
4485        // read_to(seq) should give the same result as read_until(next element)
4486        let tokens_a = tokens_with_sequence();
4487        let tokens_b = tokens_with_sequence();
4488
4489        let obj_read_to = InMemDicomObject::build_object(
4490            &mut tokens_a.into_iter().map(Result::Ok),
4491            StandardDataDictionary,
4492            false,
4493            Length::UNDEFINED,
4494            None,
4495            Some(Tag(0x0018, 0x6011)),
4496        )
4497        .unwrap();
4498
4499        let obj_read_until = InMemDicomObject::build_object(
4500            &mut tokens_b.into_iter().map(Result::Ok),
4501            StandardDataDictionary,
4502            false,
4503            Length::UNDEFINED,
4504            Some(Tag(0x0020, 0x4000)),
4505            None,
4506        )
4507        .unwrap();
4508
4509        assert_obj_eq(&obj_read_to, &obj_read_until);
4510    }
4511}