<commit_before>//! Module containing data structures and readers of DICOM file meta information tables.
use byteordered::byteorder::{ByteOrder, LittleEndian};
use dicom_core::dicom_value;
use dicom_core::header::{DataElement, EmptyObject, HasLength, Header};
use dicom_core::value::{PrimitiveValue, Value};
use dicom_core::{Length, Tag, VR};
use dicom_encoding::decode::{self, DecodeFrom};
use dicom_encoding::encode::explicit_le::ExplicitVRLittleEndianEncoder;
use dicom_encoding::encode::EncoderFor;
use dicom_encoding::text::{self, TextCodec};
use dicom_parser::dataset::{DataSetWriter, IntoTokens};
use snafu::{ensure, Backtrace, OptionExt, ResultExt, Snafu};
use std::io::{Read, Write};

use crate::{IMPLEMENTATION_CLASS_UID, IMPLEMENTATION_VERSION_NAME};

const DICM_MAGIC_CODE: [u8; 4] = [b'D', b'I', b'C', b'M'];

#[derive(Debug, Snafu)]
#[non_exhaustive]
pub enum Error {
    /// The file meta group parser could not read
    /// the magic code `DICM` from its source.
    #[snafu(display("Could not start reading DICOM data"))]
    ReadMagicCode {
        backtrace: Backtrace,
        source: std::io::Error,
    },

    /// The file meta group parser could not fetch
    /// the value of a data element from its source.
    #[snafu(display("Could not read data value"))]
    ReadValueData {
        backtrace: Backtrace,
        source: std::io::Error,
    },

    /// The file meta group parser could not decode
    /// the text in one of its data elements.
    #[snafu(display("Could not decode text in {}", name))]
    DecodeText {
        name: &'static str,
        #[snafu(backtrace)]
        source: dicom_encoding::text::DecodeTextError,
    },

    /// Invalid DICOM data, detected by checking the `DICM` code.
    #[snafu(display("Invalid DICOM file (magic code check failed)"))]
    NotDicom { backtrace: Backtrace },

    /// An issue occurred while decoding the next data element
    /// in the file meta data set.
    #[snafu(display("Could not decode data element"))]
    DecodeElement {
        #[snafu(backtrace)]
        source: dicom_encoding::decode::Error,
    },

    /// A data element with an unexpected tag was retrieved:
    /// the parser was expecting another tag first,
    /// or at least one that is part of the the file meta group.
    #[snafu(display("Unexpected data element tagged {}", tag))]
    UnexpectedTag { tag: Tag, backtrace: Backtrace },

    /// A required file meta data element is missing.
    #[snafu(display("Missing data element `{}`", alias))]
    MissingElement {
        alias: &'static str,
        backtrace: Backtrace,
    },

    /// The value length of a data elements in the file meta group
    /// was unexpected.
    #[snafu(display("Unexpected length {} for data element tagged {}", length, tag))]
    UnexpectedDataValueLength {
        tag: Tag,
        length: Length,
        backtrace: Backtrace,
    },

    /// The value length of a data element is undefined,
    /// but knowing the length is required in its context.
    #[snafu(display("Undefined value length for data element tagged {}", tag))]
    UndefinedValueLength { tag: Tag, backtrace: Backtrace },

    /// The file meta group data set could not be written.
    #[snafu(display("Could not write file meta group data set"))]
    WriteSet {
        #[snafu(backtrace)]
        source: dicom_parser::dataset::write::Error,
    },
}

type Result<T> = std::result::Result<T, Error>;

/// DICOM File Meta Information Table.
///
/// This data type contains the relevant parts of the file meta information table, as
/// specified in [part 6, chapter 7][1] of the standard.
///
/// Creating a new file meta table from scratch
/// is more easily done using a [`FileMetaTableBuilder`].
///
/// [1]: http://dicom.nema.org/medical/dicom/current/output/chtml/part06/chapter_7.html
/// [`FileMetaTableBuilder`]: crate::meta::FileMetaTableBuilder
#[derive(Debug, Clone, PartialEq)]
pub struct FileMetaTable {
    /// File Meta Information Group Length
    pub information_group_length: u32,
    /// File Meta Information Version
    pub information_version: [u8; 2],
    /// Media Storage SOP Class UID
    pub media_storage_sop_class_uid: String,
    /// Media Storage SOP Instance UID
    pub media_storage_sop_instance_uid: String,
    /// Transfer Syntax UID
    pub transfer_syntax: String,
    /// Implementation Class UID
    pub implementation_class_uid: String,

    /// Implementation Version Name
    pub implementation_version_name: Option<String>,
    /// Source Application Entity Title
    pub source_application_entity_title: Option<String>,
    /// Sending Application Entity Title
    pub sending_application_entity_title: Option<String>,
    /// Receiving Application Entity Title
    pub receiving_application_entity_title: Option<String>,
    /// Private Information Creator UID
    pub private_information_creator_uid: Option<String>,
    /// Private Information
    pub private_information: Option<Vec<u8>>,
    /*
    Missing attributes:

    (0002,0026) Source Presentation Address Source​Presentation​Address UR 1
    (0002,0027) Sending Presentation Address Sending​Presentation​Address UR 1
    (0002,0028) Receiving Presentation Address Receiving​Presentation​Address UR 1
    (0002,0031) RTV Meta Information Version RTV​Meta​Information​Version OB 1
    (0002,0032) RTV Communication SOP Class UID RTV​Communication​SOP​Class​UID UI 1
    (0002,0033) RTV Communication SOP Instance UID RTV​Communication​SOP​Instance​UID UI 1
    (0002,0035) RTV Source Identifier RTV​Source​Identifier OB 1
    (0002,0036) RTV Flow Identifier RTV​Flow​Identifier OB 1
    (0002,0037) RTV Flow RTP Sampling Rate RTV​Flow​RTP​Sampling​Rate UL 1
    (0002,0038) RTV Flow Actual Frame Duration RTV​Flow​Actual​Frame​Duration FD 1
    */
}

/// Utility function for reading the body of the DICOM element as a UID.
fn read_str_body<'s, S: 's, T>(source: &'s mut S, text: &T, len: u32) -> Result<String>
where
    S: Read,
    T: TextCodec,
{
    let mut v = Vec::new();
    source
        .take(len as u64)
        .read_to_end(&mut v)
        .context(ReadValueDataSnafu)?;
    text.decode(&v)
        .context(DecodeTextSnafu { name: text.name() })
}

impl FileMetaTable {
    pub fn from_reader<R: Read>(file: R) -> Result<Self> {
        FileMetaTable::read_from(file)
    }

    fn read_from<S: Read>(mut file: S) -> Result<Self> {
        let mut buff: [u8; 4] = [0; 4];
        {
            // check magic code
            file.read_exact(&mut buff).context(ReadMagicCodeSnafu)?;

            ensure!(buff == DICM_MAGIC_CODE, NotDicomSnafu);
        }

        let decoder = decode::file_header_decoder();
        let text = text::DefaultCharacterSetCodec;

        let builder = FileMetaTableBuilder::new();

        let group_length: u32 = {
            let (elem, _bytes_read) = decoder
                .decode_header(&mut file)
                .context(DecodeElementSnafu)?;
            if elem.tag() != (0x0002, 0x0000) {
                return UnexpectedTagSnafu { tag: elem.tag() }.fail();
            }
            if elem.length() != Length(4) {
                return UnexpectedDataValueLengthSnafu {
                    tag: elem.tag(),
                    length: elem.length(),
                }
                .fail();
            }
            let mut buff: [u8; 4] = [0; 4];
            file.read_exact(&mut buff).context(ReadValueDataSnafu)?;
            LittleEndian::read_u32(&buff)
        };

        let mut total_bytes_read = 0;
        let mut builder = builder.group_length(group_length);

        // Fetch optional data elements
        while total_bytes_read < group_length {
            let (elem, header_bytes_read) = decoder
                .decode_header(&mut file)
                .context(DecodeElementSnafu)?;
            let elem_len = match elem.length().get() {
                None => {
                    return UndefinedValueLengthSnafu { tag: elem.tag() }.fail();
                }
                Some(len) => len,
            };
            builder = match elem.tag() {
                Tag(0x0002, 0x0001) => {
                    // Implementation Version
                    if elem.length() != Length(2) {
                        return UnexpectedDataValueLengthSnafu {
                            tag: elem.tag(),
                            length: elem.length(),
                        }
                        .fail();
                    }
                    let mut hbuf = [0u8; 2];
                    file.read_exact(&mut hbuf[..]).context(ReadValueDataSnafu)?;

                    builder.information_version(hbuf)
                }
                // Media Storage SOP Class UID
                Tag(0x0002, 0x0002) => builder.media_storage_sop_class_uid(read_str_body(&mut file, &text, elem_len)?),
                // Media Storage SOP Instance UID
                Tag(0x0002, 0x0003) => builder
                        .media_storage_sop_instance_uid(read_str_body(&mut file, &text, elem_len)?),
                // Transfer Syntax
                Tag(0x0002, 0x0010) => builder.transfer_syntax(read_str_body(&mut file, &text, elem_len)?),
                // Implementation Class UID
                Tag(0x0002, 0x0012) => builder.implementation_class_uid(read_str_body(&mut file, &text, elem_len)?),
                Tag(0x0002, 0x0013) => {
                    // Implementation Version Name
                    let mut v = Vec::new();
                    (&mut file)
                        .take(elem_len as u64)
                        .read_to_end(&mut v)
                        .context(ReadValueDataSnafu)?;
                        builder.implementation_version_name(
                            text.decode(&v)
                                .context(DecodeTextSnafu { name: text.name() })?,
                        )
                }
                Tag(0x0002, 0x0016) => {
                    // Source Application Entity Title
                    let mut v = Vec::new();
                    (&mut file)
                        .take(elem_len as u64)
                        .read_to_end(&mut v)
                        .context(ReadValueDataSnafu)?;
                        builder.source_application_entity_title(
                            text.decode(&v)
                                .context(DecodeTextSnafu { name: text.name() })?,
                        )
                }
                Tag(0x0002, 0x0017) => {
                    // Sending Application Entity Title
                    let mut v = Vec::new();
                    (&mut file)
                        .take(elem_len as u64)
                        .read_to_end(&mut v)
                        .context(ReadValueDataSnafu)?;
                        builder.sending_application_entity_title(
                            text.decode(&v)
                                .context(DecodeTextSnafu { name: text.name() })?,
                        )
                }
                Tag(0x0002, 0x0018) => {
                    // Receiving Application Entity Title
                    let mut v = Vec::new();
                    (&mut file)
                        .take(elem_len as u64)
                        .read_to_end(&mut v)
                        .context(ReadValueDataSnafu)?;
                        builder.receiving_application_entity_title(
                            text.decode(&v)
                                .context(DecodeTextSnafu { name: text.name() })?,
                        )
                }
                Tag(0x0002, 0x0100) => {
                    // Private Information Creator UID
                    let mut v = Vec::new();
                    (&mut file)
                        .take(elem_len as u64)
                        .read_to_end(&mut v)
                        .context(ReadValueDataSnafu)?;
                        builder.private_information_creator_uid(
                            text.decode(&v)
                                .context(DecodeTextSnafu { name: text.name() })?,
                        )
                }
                Tag(0x0002, 0x0102) => {
                    // Private Information
                    let mut v = Vec::new();
                    (&mut file)
                        .take(elem_len as u64)
                        .read_to_end(&mut v)
                        .context(ReadValueDataSnafu)?;
                    builder.private_information(v)
                }
                tag @ Tag(0x0002, _) => {
                    // unknown tag, do nothing
                    // could be an unsupported or non-standard attribute
                    tracing::info!("Unknown tag {}", tag);
                    // consume value without saving it
                    std::io::copy(&mut (&mut file).take(elem_len as u64), &mut std::io::sink())
                        .context(ReadValueDataSnafu)?;
                    builder
                }
                tag => {
                    // unexpected tag from another group! do nothing for now,
                    // but this could pose an issue up ahead (see #50)
                    tracing::warn!("Unexpected off-group tag {}", tag);
                    // consume value without saving it
                    std::io::copy(&mut (&mut file).take(elem_len as u64), &mut std::io::sink())
                        .context(ReadValueDataSnafu)?;
                    builder
                }
            };
            total_bytes_read += header_bytes_read as u32 + elem_len;
        }

        builder.build()
    }

    pub fn into_element_iter(self) -> impl Iterator<Item = DataElement<EmptyObject, [u8; 0]>> {
        let mut elems = vec![
            // file information group length
            DataElement::new(
                Tag(0x0002, 0x0000),
                VR::UL,
                Value::Primitive(self.information_group_length.into()),
            ),
            DataElement::new(
                Tag(0x0002, 0x0001),
                VR::OB,
                Value::Primitive(dicom_value!(
                    U8,
                    [self.information_version[0], self.information_version[1]]
                )),
            ),
            DataElement::new(
                Tag(0x0002, 0x0002),
                VR::UI,
                Value::Primitive(self.media_storage_sop_class_uid.into()),
            ),
            DataElement::new(
                Tag(0x0002, 0x0003),
                VR::UI,
                Value::Primitive(self.media_storage_sop_instance_uid.into()),
            ),
            DataElement::new(
                Tag(0x0002, 0x0010),
                VR::UI,
                Value::Primitive(self.transfer_syntax.into()),
            ),
            DataElement::new(
                Tag(0x0002, 0x0012),
                VR::UI,
                Value::Primitive(self.implementation_class_uid.into()),
            ),
        ];
        if let Some(v) = self.implementation_version_name {
            elems.push(DataElement::new(
                Tag(0x0002, 0x0013),
                VR::SH,
                Value::Primitive(v.into()),
            ));
        }
        if let Some(v) = self.source_application_entity_title {
            elems.push(DataElement::new(
                Tag(0x0002, 0x0016),
                VR::AE,
                Value::Primitive(v.into()),
            ));
        }
        if let Some(v) = self.sending_application_entity_title {
            elems.push(DataElement::new(
                Tag(0x0002, 0x0017),
                VR::AE,
                Value::Primitive(v.into()),
            ));
        }
        if let Some(v) = self.receiving_application_entity_title {
            elems.push(DataElement::new(
                Tag(0x0002, 0x0018),
                VR::AE,
                Value::Primitive(v.into()),
            ));
        }
        if let Some(v) = self.private_information_creator_uid {
            elems.push(DataElement::new(
                Tag(0x0002, 0x0100),
                VR::UI,
                Value::Primitive(v.into()),
            ));
        }
        if let Some(v) = self.private_information {
            elems.push(DataElement::new(
                Tag(0x0002, 0x0102),
                VR::OB,
                Value::Primitive(PrimitiveValue::U8(v.into())),
            ));
        }

        elems.into_iter()
    }

    pub fn write<W: Write>(&self, writer: W) -> Result<()> {
        let mut dset = DataSetWriter::new(
            writer,
            EncoderFor::new(ExplicitVRLittleEndianEncoder::default()),
        );
        dset.write_sequence(
            self.clone()
                .into_element_iter()
                .flat_map(IntoTokens::into_tokens),
        )
        .context(WriteSetSnafu)
    }
}

/// A builder for DICOM meta information tables.
#[derive(Debug, Clone)]
pub struct FileMetaTableBuilder {
    /// File Meta Information Group Length (UL)
    information_group_length: Option<u32>,
    /// File Meta Information Version (OB)
    information_version: Option<[u8; 2]>,
    /// Media Storage SOP Class UID (UI)
    media_storage_sop_class_uid: Option<String>,
    /// Media Storage SOP Instance UID (UI)
    media_storage_sop_instance_uid: Option<String>,
    /// Transfer Syntax UID (UI)
    transfer_syntax: Option<String>,
    /// Implementation Class UID (UI)
    implementation_class_uid: Option<String>,

    /// Implementation Version Name (SH)
    implementation_version_name: Option<String>,
    /// Source Application Entity Title (AE)
    source_application_entity_title: Option<String>,
    /// Sending Application Entity Title (AE)
    sending_application_entity_title: Option<String>,
    /// Receiving Application Entity Title (AE)
    receiving_application_entity_title: Option<String>,
    /// Private Information Creator UID (UI)
    private_information_creator_uid: Option<String>,
    /// Private Information (OB)
    private_information: Option<Vec<u8>>,
}

impl Default for FileMetaTableBuilder {
    fn default() -> FileMetaTableBuilder {
        FileMetaTableBuilder {
            information_group_length: None,
            information_version: None,
            media_storage_sop_class_uid: None,
            media_storage_sop_instance_uid: None,
            transfer_syntax: None,
            implementation_class_uid: None,
            implementation_version_name: None,
            source_application_entity_title: None,
            sending_application_entity_title: None,
            receiving_application_entity_title: None,
            private_information_creator_uid: None,
            private_information: None,
        }
    }
}

/// Ensure that the string is even lengthed, by adding a trailing character
/// if not.
#[inline]
fn padded<T>(s: T, pad: char) -> String
where
    T: Into<String>,
{
    let mut s = s.into();
    if s.len() % 2 == 1 {
        s.push(pad);
    }
    s
}
<commit_msg>[object] Derive Default for FileMetaTableBuilder<commit_after>//! Module containing data structures and readers of DICOM file meta information tables.
use byteordered::byteorder::{ByteOrder, LittleEndian};
use dicom_core::dicom_value;
use dicom_core::header::{DataElement, EmptyObject, HasLength, Header};
use dicom_core::value::{PrimitiveValue, Value};
use dicom_core::{Length, Tag, VR};
use dicom_encoding::decode::{self, DecodeFrom};
use dicom_encoding::encode::explicit_le::ExplicitVRLittleEndianEncoder;
use dicom_encoding::encode::EncoderFor;
use dicom_encoding::text::{self, TextCodec};
use dicom_parser::dataset::{DataSetWriter, IntoTokens};
use snafu::{ensure, Backtrace, OptionExt, ResultExt, Snafu};
use std::io::{Read, Write};

use crate::{IMPLEMENTATION_CLASS_UID, IMPLEMENTATION_VERSION_NAME};

const DICM_MAGIC_CODE: [u8; 4] = [b'D', b'I', b'C', b'M'];

#[derive(Debug, Snafu)]
#[non_exhaustive]
pub enum Error {
    /// The file meta group parser could not read
    /// the magic code `DICM` from its source.
    #[snafu(display("Could not start reading DICOM data"))]
    ReadMagicCode {
        backtrace: Backtrace,
        source: std::io::Error,
    },

    /// The file meta group parser could not fetch
    /// the value of a data element from its source.
    #[snafu(display("Could not read data value"))]
    ReadValueData {
        backtrace: Backtrace,
        source: std::io::Error,
    },

    /// The file meta group parser could not decode
    /// the text in one of its data elements.
    #[snafu(display("Could not decode text in {}", name))]
    DecodeText {
        name: &'static str,
        #[snafu(backtrace)]
        source: dicom_encoding::text::DecodeTextError,
    },

    /// Invalid DICOM data, detected by checking the `DICM` code.
    #[snafu(display("Invalid DICOM file (magic code check failed)"))]
    NotDicom { backtrace: Backtrace },

    /// An issue occurred while decoding the next data element
    /// in the file meta data set.
    #[snafu(display("Could not decode data element"))]
    DecodeElement {
        #[snafu(backtrace)]
        source: dicom_encoding::decode::Error,
    },

    /// A data element with an unexpected tag was retrieved:
    /// the parser was expecting another tag first,
    /// or at least one that is part of the the file meta group.
    #[snafu(display("Unexpected data element tagged {}", tag))]
    UnexpectedTag { tag: Tag, backtrace: Backtrace },

    /// A required file meta data element is missing.
    #[snafu(display("Missing data element `{}`", alias))]
    MissingElement {
        alias: &'static str,
        backtrace: Backtrace,
    },

    /// The value length of a data elements in the file meta group
    /// was unexpected.
    #[snafu(display("Unexpected length {} for data element tagged {}", length, tag))]
    UnexpectedDataValueLength {
        tag: Tag,
        length: Length,
        backtrace: Backtrace,
    },

    /// The value length of a data element is undefined,
    /// but knowing the length is required in its context.
    #[snafu(display("Undefined value length for data element tagged {}", tag))]
    UndefinedValueLength { tag: Tag, backtrace: Backtrace },

    /// The file meta group data set could not be written.
    #[snafu(display("Could not write file meta group data set"))]
    WriteSet {
        #[snafu(backtrace)]
        source: dicom_parser::dataset::write::Error,
    },
}

type Result<T> = std::result::Result<T, Error>;

/// DICOM File Meta Information Table.
///
/// This data type contains the relevant parts of the file meta information table, as
/// specified in [part 6, chapter 7][1] of the standard.
///
/// Creating a new file meta table from scratch
/// is more easily done using a [`FileMetaTableBuilder`].
///
/// [1]: http://dicom.nema.org/medical/dicom/current/output/chtml/part06/chapter_7.html
/// [`FileMetaTableBuilder`]: crate::meta::FileMetaTableBuilder
#[derive(Debug, Clone, PartialEq)]
pub struct FileMetaTable {
    /// File Meta Information Group Length
    pub information_group_length: u32,
    /// File Meta Information Version
    pub information_version: [u8; 2],
    /// Media Storage SOP Class UID
    pub media_storage_sop_class_uid: String,
    /// Media Storage SOP Instance UID
    pub media_storage_sop_instance_uid: String,
    /// Transfer Syntax UID
    pub transfer_syntax: String,
    /// Implementation Class UID
    pub implementation_class_uid: String,

    /// Implementation Version Name
    pub implementation_version_name: Option<String>,
    /// Source Application Entity Title
    pub source_application_entity_title: Option<String>,
    /// Sending Application Entity Title
    pub sending_application_entity_title: Option<String>,
    /// Receiving Application Entity Title
    pub receiving_application_entity_title: Option<String>,
    /// Private Information Creator UID
    pub private_information_creator_uid: Option<String>,
    /// Private Information
    pub private_information: Option<Vec<u8>>,
    /*
    Missing attributes:

    (0002,0026) Source Presentation Address Source​Presentation​Address UR 1
    (0002,0027) Sending Presentation Address Sending​Presentation​Address UR 1
    (0002,0028) Receiving Presentation Address Receiving​Presentation​Address UR 1
    (0002,0031) RTV Meta Information Version RTV​Meta​Information​Version OB 1
    (0002,0032) RTV Communication SOP Class UID RTV​Communication​SOP​Class​UID UI 1
    (0002,0033) RTV Communication SOP Instance UID RTV​Communication​SOP​Instance​UID UI 1
    (0002,0035) RTV Source Identifier RTV​Source​Identifier OB 1
    (0002,0036) RTV Flow Identifier RTV​Flow​Identifier OB 1
    (0002,0037) RTV Flow RTP Sampling Rate RTV​Flow​RTP​Sampling​Rate UL 1
    (0002,0038) RTV Flow Actual Frame Duration RTV​Flow​Actual​Frame​Duration FD 1
    */
}

/// Utility function for reading the body of the DICOM element as a UID.
fn read_str_body<'s, S: 's, T>(source: &'s mut S, text: &T, len: u32) -> Result<String>
where
    S: Read,
    T: TextCodec,
{
    let mut v = Vec::new();
    source
        .take(len as u64)
        .read_to_end(&mut v)
        .context(ReadValueDataSnafu)?;
    text.decode(&v)
        .context(DecodeTextSnafu { name: text.name() })
}

impl FileMetaTable {
    pub fn from_reader<R: Read>(file: R) -> Result<Self> {
        FileMetaTable::read_from(file)
    }

    fn read_from<S: Read>(mut file: S) -> Result<Self> {
        let mut buff: [u8; 4] = [0; 4];
        {
            // check magic code
            file.read_exact(&mut buff).context(ReadMagicCodeSnafu)?;

            ensure!(buff == DICM_MAGIC_CODE, NotDicomSnafu);
        }

        let decoder = decode::file_header_decoder();
        let text = text::DefaultCharacterSetCodec;

        let builder = FileMetaTableBuilder::new();

        let group_length: u32 = {
            let (elem, _bytes_read) = decoder
                .decode_header(&mut file)
                .context(DecodeElementSnafu)?;
            if elem.tag() != (0x0002, 0x0000) {
                return UnexpectedTagSnafu { tag: elem.tag() }.fail();
            }
            if elem.length() != Length(4) {
                return UnexpectedDataValueLengthSnafu {
                    tag: elem.tag(),
                    length: elem.length(),
                }
                .fail();
            }
            let mut buff: [u8; 4] = [0; 4];
            file.read_exact(&mut buff).context(ReadValueDataSnafu)?;
            LittleEndian::read_u32(&buff)
        };

        let mut total_bytes_read = 0;
        let mut builder = builder.group_length(group_length);

        // Fetch optional data elements
        while total_bytes_read < group_length {
            let (elem, header_bytes_read) = decoder
                .decode_header(&mut file)
                .context(DecodeElementSnafu)?;
            let elem_len = match elem.length().get() {
                None => {
                    return UndefinedValueLengthSnafu { tag: elem.tag() }.fail();
                }
                Some(len) => len,
            };
            builder = match elem.tag() {
                Tag(0x0002, 0x0001) => {
                    // Implementation Version
                    if elem.length() != Length(2) {
                        return UnexpectedDataValueLengthSnafu {
                            tag: elem.tag(),
                            length: elem.length(),
                        }
                        .fail();
                    }
                    let mut hbuf = [0u8; 2];
                    file.read_exact(&mut hbuf[..]).context(ReadValueDataSnafu)?;

                    builder.information_version(hbuf)
                }
                // Media Storage SOP Class UID
                Tag(0x0002, 0x0002) => builder.media_storage_sop_class_uid(read_str_body(&mut file, &text, elem_len)?),
                // Media Storage SOP Instance UID
                Tag(0x0002, 0x0003) => builder
                        .media_storage_sop_instance_uid(read_str_body(&mut file, &text, elem_len)?),
                // Transfer Syntax
                Tag(0x0002, 0x0010) => builder.transfer_syntax(read_str_body(&mut file, &text, elem_len)?),
                // Implementation Class UID
                Tag(0x0002, 0x0012) => builder.implementation_class_uid(read_str_body(&mut file, &text, elem_len)?),
                Tag(0x0002, 0x0013) => {
                    // Implementation Version Name
                    let mut v = Vec::new();
                    (&mut file)
                        .take(elem_len as u64)
                        .read_to_end(&mut v)
                        .context(ReadValueDataSnafu)?;
                        builder.implementation_version_name(
                            text.decode(&v)
                                .context(DecodeTextSnafu { name: text.name() })?,
                        )
                }
                Tag(0x0002, 0x0016) => {
                    // Source Application Entity Title
                    let mut v = Vec::new();
                    (&mut file)
                        .take(elem_len as u64)
                        .read_to_end(&mut v)
                        .context(ReadValueDataSnafu)?;
                        builder.source_application_entity_title(
                            text.decode(&v)
                                .context(DecodeTextSnafu { name: text.name() })?,
                        )
                }
                Tag(0x0002, 0x0017) => {
                    // Sending Application Entity Title
                    let mut v = Vec::new();
                    (&mut file)
                        .take(elem_len as u64)
                        .read_to_end(&mut v)
                        .context(ReadValueDataSnafu)?;
                        builder.sending_application_entity_title(
                            text.decode(&v)
                                .context(DecodeTextSnafu { name: text.name() })?,
                        )
                }
                Tag(0x0002, 0x0018) => {
                    // Receiving Application Entity Title
                    let mut v = Vec::new();
                    (&mut file)
                        .take(elem_len as u64)
                        .read_to_end(&mut v)
                        .context(ReadValueDataSnafu)?;
                        builder.receiving_application_entity_title(
                            text.decode(&v)
                                .context(DecodeTextSnafu { name: text.name() })?,
                        )
                }
                Tag(0x0002, 0x0100) => {
                    // Private Information Creator UID
                    let mut v = Vec::new();
                    (&mut file)
                        .take(elem_len as u64)
                        .read_to_end(&mut v)
                        .context(ReadValueDataSnafu)?;
                        builder.private_information_creator_uid(
                            text.decode(&v)
                                .context(DecodeTextSnafu { name: text.name() })?,
                        )
                }
                Tag(0x0002, 0x0102) => {
                    // Private Information
                    let mut v = Vec::new();
                    (&mut file)
                        .take(elem_len as u64)
                        .read_to_end(&mut v)
                        .context(ReadValueDataSnafu)?;
                    builder.private_information(v)
                }
                tag @ Tag(0x0002, _) => {
                    // unknown tag, do nothing
                    // could be an unsupported or non-standard attribute
                    tracing::info!("Unknown tag {}", tag);
                    // consume value without saving it
                    std::io::copy(&mut (&mut file).take(elem_len as u64), &mut std::io::sink())
                        .context(ReadValueDataSnafu)?;
                    builder
                }
                tag => {
                    // unexpected tag from another group! do nothing for now,
                    // but this could pose an issue up ahead (see #50)
                    tracing::warn!("Unexpected off-group tag {}", tag);
                    // consume value without saving it
                    std::io::copy(&mut (&mut file).take(elem_len as u64), &mut std::io::sink())
                        .context(ReadValueDataSnafu)?;
                    builder
                }
            };
            total_bytes_read += header_bytes_read as u32 + elem_len;
        }

        builder.build()
    }

    pub fn into_element_iter(self) -> impl Iterator<Item = DataElement<EmptyObject, [u8; 0]>> {
        let mut elems = vec![
            // file information group length
            DataElement::new(
                Tag(0x0002, 0x0000),
                VR::UL,
                Value::Primitive(self.information_group_length.into()),
            ),
            DataElement::new(
                Tag(0x0002, 0x0001),
                VR::OB,
                Value::Primitive(dicom_value!(
                    U8,
                    [self.information_version[0], self.information_version[1]]
                )),
            ),
            DataElement::new(
                Tag(0x0002, 0x0002),
                VR::UI,
                Value::Primitive(self.media_storage_sop_class_uid.into()),
            ),
            DataElement::new(
                Tag(0x0002, 0x0003),
                VR::UI,
                Value::Primitive(self.media_storage_sop_instance_uid.into()),
            ),
            DataElement::new(
                Tag(0x0002, 0x0010),
                VR::UI,
                Value::Primitive(self.transfer_syntax.into()),
            ),
            DataElement::new(
                Tag(0x0002, 0x0012),
                VR::UI,
                Value::Primitive(self.implementation_class_uid.into()),
            ),
        ];
        if let Some(v) = self.implementation_version_name {
            elems.push(DataElement::new(
                Tag(0x0002, 0x0013),
                VR::SH,
                Value::Primitive(v.into()),
            ));
        }
        if let Some(v) = self.source_application_entity_title {
            elems.push(DataElement::new(
                Tag(0x0002, 0x0016),
                VR::AE,
                Value::Primitive(v.into()),
            ));
        }
        if let Some(v) = self.sending_application_entity_title {
            elems.push(DataElement::new(
                Tag(0x0002, 0x0017),
                VR::AE,
                Value::Primitive(v.into()),
            ));
        }
        if let Some(v) = self.receiving_application_entity_title {
            elems.push(DataElement::new(
                Tag(0x0002, 0x0018),
                VR::AE,
                Value::Primitive(v.into()),
            ));
        }
        if let Some(v) = self.private_information_creator_uid {
            elems.push(DataElement::new(
                Tag(0x0002, 0x0100),
                VR::UI,
                Value::Primitive(v.into()),
            ));
        }
        if let Some(v) = self.private_information {
            elems.push(DataElement::new(
                Tag(0x0002, 0x0102),
                VR::OB,
                Value::Primitive(PrimitiveValue::U8(v.into())),
            ));
        }

        elems.into_iter()
    }

    pub fn write<W: Write>(&self, writer: W) -> Result<()> {
        let mut dset = DataSetWriter::new(
            writer,
            EncoderFor::new(ExplicitVRLittleEndianEncoder::default()),
        );
        dset.write_sequence(
            self.clone()
                .into_element_iter()
                .flat_map(IntoTokens::into_tokens),
        )
        .context(WriteSetSnafu)
    }
}

/// A builder for DICOM meta information tables.
#[derive(Debug, Default, Clone)]
pub struct FileMetaTableBuilder {
    /// File Meta Information Group Length (UL)
    information_group_length: Option<u32>,
    /// File Meta Information Version (OB)
    information_version: Option<[u8; 2]>,
    /// Media Storage SOP Class UID (UI)
    media_storage_sop_class_uid: Option<String>,
    /// Media Storage SOP Instance UID (UI)
    media_storage_sop_instance_uid: Option<String>,
    /// Transfer Syntax UID (UI)
    transfer_syntax: Option<String>,
    /// Implementation Class UID (UI)
    implementation_class_uid: Option<String>,

    /// Implementation Version Name (SH)
    implementation_version_name: Option<String>,
    /// Source Application Entity Title (AE)
    source_application_entity_title: Option<String>,
    /// Sending Application Entity Title (AE)
    sending_application_entity_title: Option<String>,
    /// Receiving Application Entity Title (AE)
    receiving_application_entity_title: Option<String>,
    /// Private Information Creator UID (UI)
    private_information_creator_uid: Option<String>,
    /// Private Information (OB)
    private_information: Option<Vec<u8>>,
}

/// Ensure that the string is even lengthed, by adding a trailing character
/// if not.
#[inline]
fn padded<T>(s: T, pad: char) -> String
where
    T: Into<String>,
{
    let mut s = s.into();
    if s.len() % 2 == 1 {
        s.push(pad);
    }
    s
}

{'fraction_non_alphanumeric': 0.10875181656709187, 'fraction_numerical': 0.021744980892405404, 'mean_word_length': 1.7580345876939063, 'pattern_counts': {'":': 0, '<': 73, '<?xml version=': 0, '>': 114, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3992609', 'n_tokens_mistral': 10963, 'n_tokens_neox': 10082, 'n_words': 2503}