1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
//! Reading, writing, and validation of RDML (Real-time PCR Data Markup
//! Language) files — the [RDML consortium]'s XML interchange format for
//! qPCR and digital PCR data.
//!
//! This crate stores and transports results computed elsewhere. It
//! performs no analysis: fields like `cq`, `ampEff`, and `bgFluor` are
//! carried verbatim from whatever instrument or software produced them.
//!
//! # Document structure
//!
//! An RDML document is normalised. The root ([`Rdml`]) holds flat lists
//! of master elements — [`Experimenter`], [`Documentation`], [`Dye`],
//! [`Sample`], [`Target`], [`ThermalCyclingConditions`], and
//! [`Experiment`] — each identified by a unique [`Id`]. Other elements
//! refer to them by id rather than nesting. References are typed
//! ([`SampleRef`], [`TargetRef`], [`DyeRef`], …) so a reference cannot be
//! confused with a definition or with a reference of another kind.
//!
//! Measurements sit in one containment chain:
//!
//! ```text
//! rdml ─ experiment ─ run ─ react ─ data ─┬─ adp (amplification points, per cycle)
//! └─ mdp (melting points, per temperature)
//! ```
//!
//! A [`React`] is one physical reaction (well, capillary, or
//! through-hole), identified by an integer position (row-first /
//! column-second on plates) and referencing the [`Sample`] it contains.
//! Each [`Data`] entry holds one target's results; multiplex reactions
//! have several. Digital PCR stores partition counts ([`Partitions`]) in
//! the document and optional per-partition values in TSV tables
//! ([`PartitionTable`]) inside the archive.
//!
//! Ids are short human-readable names chosen by the user; qPCR software
//! displays them, and the format notes tell producers not to use
//! generated strings. This crate provides no id generator.
//!
//! # Reading
//!
//! [`RdmlFile::open`] reads all format versions (1.0–1.4) and both
//! physical forms — the `.rdml`/`.rdm` zip container and bare
//! uncompressed XML — detected by content:
//!
//! ```no_run
//! let file = rdml_qpcr::RdmlFile::open("run42.rdml")?;
//! for ctx in file.document.data_entries() {
//! let well = ctx.run.pcr_format.well_name(ctx.react.id);
//! match ctx.data.cq {
//! Some(cq) => println!("{well} {}: Cq {cq:.2}", ctx.data.tar),
//! None => println!("{well} {}: no Cq", ctx.data.tar),
//! }
//! }
//! # Ok::<(), rdml_qpcr::Error>(())
//! ```
//!
//! Old versions are migrated to the current model on read. Mappings that
//! are not plain renames are reported in [`RdmlFile::read_notes`];
//! nothing is dropped silently. The migration rules are documented in
//! [`version`].
//!
//! # Writing
//!
//! ```
//! use rdml_qpcr::*;
//! use std::num::NonZeroU32;
//!
//! let mut doc = Rdml::new();
//! doc.dyes.push(Dye::new(Id::new("SYBR")?));
//! doc.samples.push(Sample::new(Id::new("liver 1")?));
//! doc.targets.push(Target::new(
//! Id::new("GAPDH")?,
//! TargetType::Reference,
//! DyeRef::new("SYBR")?,
//! ));
//!
//! let mut run = Run::new(Id::new("plate 1")?, PcrFormat::plate96());
//! let mut react = React::new(NonZeroU32::new(1).unwrap(), SampleRef::new("liver 1")?);
//! let mut data = Data::new(TargetRef::new("GAPDH")?);
//! data.cq = Some(21.4);
//! data.adps = (1..=40)
//! .map(|cyc| AmpPoint::new(cyc as f64, 0.02 * cyc as f64))
//! .collect();
//! react.data.push(data);
//! run.reacts.push(react);
//! let mut experiment = Experiment::new(Id::new("pilot")?);
//! experiment.runs.push(run);
//! doc.experiments.push(experiment);
//!
//! // Zip container, RDML 1.3 (the latest recommendation):
//! let file = RdmlFile::new(doc);
//! let (bytes, report) = file.to_bytes(RdmlVersion::V1_3)?;
//! assert!(report.is_lossless());
//!
//! // Or bare XML of any writable version:
//! let (xml, _) = file.document.to_xml(RdmlVersion::V1_4)?;
//! assert!(xml.contains("<cq>21.4</cq>"));
//! # Ok::<(), rdml_qpcr::Error>(())
//! ```
//!
//! Writing validates first and fails on broken references or duplicate
//! ids; the `*_unchecked` variants skip that check. Every write returns
//! a [`WriteReport`] listing anything the target version could not
//! express. Archive members this crate does not understand (vendor
//! sidecar files) are preserved byte-for-byte across read–modify–write;
//! [`RdmlFile::insert_member`] adds new ones.
//!
//! # Format rules outside the schema
//!
//! Several normative RDML rules appear in the consortium's format notes
//! rather than the XSD. They are encoded in the types and documented on
//! the affected items:
//!
//! - `-1.0` means "not available" on [`Data::cq`], [`Data::n0`],
//! [`Data::corr_p`], [`Data::corr_cq`], and [`Data::n_copy`], and only
//! there. The fields are `Option<f64>`; the sentinel reads as `None`
//! and is never written.
//! - `excl` and `note` signal by presence. They are
//! `Option<`[`Reasons`]`>`: `None` writes nothing, so
//! `<excl>false</excl>` cannot be produced. [`Reasons`] handles the
//! `;`-joined multi-reason convention.
//! - Amplification fluorescence must not be baseline-corrected; see
//! [`AmpPoint::fluor`].
//! - `pcrFormat` display conventions (`rows = -1`, `columns = 1`); see
//! [`PcrFormat`].
//! - Files are UTF-8 with `\n` newlines and `.` decimal separators. The
//! reader accepts any namespace prefix and child order and skips
//! unknown elements with a note; the writer emits schema order.
//!
//! # Versions
//!
//! RDML 1.0–1.3 are consortium recommendations; 1.4 is a candidate
//! recommendation and may still change. This crate reads all five
//! versions into one model (shaped like 1.4) and writes 1.1–1.4, 1.3 by
//! default. 1.0 is read-only. See [`version`] for the migration rules
//! and loss reporting.
//!
//! # Validation
//!
//! XSD validators do not check RDML's `xs:key`/`xs:keyref`/`xs:unique`
//! constraints. [`Rdml::validate`] does: reference resolution, id
//! uniqueness, and the per-element uniqueness rules (`run`/`react` ids,
//! `step` numbers, `adp` cycles, `mdp` temperatures). It returns all
//! findings with document paths, not just the first. See [`validate`].
//!
//! # Serde
//!
//! The model derives [`serde::Serialize`] and [`serde::Deserialize`]
//! with schema-vocabulary field names and no XML artifacts, so documents
//! can be converted to JSON, CBOR, or any other serde format.
//!
//! # Cargo features
//!
//! - `time` (off by default): conversions between [`DateTime`] and the
//! [`time`](https://docs.rs/time) crate's `OffsetDateTime` and
//! `PrimitiveDateTime`.
//!
//! Serde support is not feature-gated.
//!
//! [RDML consortium]: https://rdml.org/
// Compile the README's examples as doctests.
;
pub use ;
pub use ;
pub use ;
pub use *;
pub use ;
pub use ;
pub use ;
pub use ;