}
impl TextRenderer {
+ fn start_object<W>(&mut self, writer: &mut W) -> FmtResult
+ where
+ W: FmtWrite,
+ {
+ if self.n_objects > 0 {
+ writeln!(writer)?;
+ }
+ self.n_objects += 1;
+ Ok(())
+ }
+
fn render<W>(&mut self, item: &Item, writer: &mut W) -> FmtResult
where
W: FmtWrite,
{
- for (index, item) in ItemRefIterator::without_hidden(item)
- .filter(|item| !item.details.is_heading())
- .enumerate()
+ for item in ItemRefIterator::without_hidden(item).filter(|item| !item.details.is_heading())
{
- if index > 0 {
- writeln!(writer)?;
- }
match &item.details {
- Details::Chart => writeln!(writer, "Omitting chart from text output")?,
- Details::Image(_) => writeln!(writer, "Omitting image from text output")?,
+ Details::Chart => {
+ self.start_object(writer)?;
+ writeln!(writer, "Omitting chart from text output")?
+ }
+ Details::Image(_) => {
+ self.start_object(writer)?;
+ writeln!(writer, "Omitting image from text output")?
+ }
Details::Heading(_) => unreachable!(),
Details::Message(_diagnostic) => todo!(),
Details::PageBreak => (),
where
W: FmtWrite,
{
- for (index, layer_indexes) in table.layers(true).enumerate() {
- if index > 0 {
- writeln!(writer)?;
- }
+ for layer_indexes in table.layers(true) {
+ self.start_object(writer)?;
let mut pager = Pager::new(self, table, Some(layer_indexes.as_slice()));
while pager.has_next(self).is_some() {
}
// SPSS often starts paragraphs with an initial `<BR>` that it
// ignores, but it does honor `<br>`. So weird.
- Node::Element(br) if br.name == "br" => {
+ Node::Element(br)
+ if br.name.eq_ignore_ascii_case("br") && (br.name == "br" || i != 0) =>
+ {
add_markup(&mut retval, Markup::Text('\n'.into()));
}
Node::Element(element) => {
);
}
- /*
- #[test]
- fn value() {
- let value = parse_value(
- r#"<b>bold</b><br><i>italic</i><BR><b><i>bold italic</i></b><br><font color="red" face="Serif">red serif</font><br><font size="7">big</font><br>"#,
- );
- assert_eq!(
- value,
- Value::new_markup(
- r##"<b>bold</b>
-<i>italic</i>
-<b><i>bold italic</i></b>
-<span face="Serif" color="#ff0000">red serif</span>
-<span size="20480">big</span>
-"##
- )
- .with_font_style(FontStyle::default().with_size(10))
- );
- }*/
-
/// From the corpus (also included in the documentation).
#[test]
fn header1() {
);
}
+ /// From the corpus, anonymized.
+ ///
+ /// This tests the unusual treatment of `<BR>` at the start of text (`<BR>`
+ /// is ignored at the start, but `<br>` is not).
+ #[test]
+ fn breaks() {
+ let text = r##"<xml><head><style type="text/css">p{color:0;font-family:Monospaced;font-size:13pt;font-style:normal;font-weight:normal;text-decoration:none}</style></head><BR>USE ALL.<BR>COMPUTE filter_$=(group = 1).<BR>VARIABLE LABEL filter_$ 'group = 1 (FILTER)'.<BR>VALUE LABELS filter_$ 0 'Not Selected' 1 'Selected'.<BR>FORMAT filter_$ (f1.0).<BR>FILTER BY filter_$.<BR>EXECUTE.<BR>NPAR TEST<BR> /WILCOXON=x WITH y<BR> z w (PAIRED)<BR> /MISSING ANALYSIS.</xml>"##;
+ let content = quick_xml::de::from_str::<String>(text).unwrap();
+ let html = Document::from_html(&content);
+ let s = html.into_value().display(()).to_string();
+ assert_eq!(
+ s,
+ r##"USE ALL.
+COMPUTE filter_$=(group = 1).
+VARIABLE LABEL filter_$ 'group = 1 (FILTER)'.
+VALUE LABELS filter_$ 0 'Not Selected' 1 'Selected'.
+FORMAT filter_$ (f1.0).
+FILTER BY filter_$.
+EXECUTE.
+NPAR TEST
+ /WILCOXON=x WITH y
+ z w (PAIRED)
+ /MISSING ANALYSIS."##
+ );
+ }
+
/// Checks that the `escape-html` feature is enabled in [quick_xml], since
/// we need that to resolve ` ` and other HTML entities.
#[test]
use std::{
cell::{Cell, RefCell},
collections::{BTreeMap, HashMap},
+ fmt::Debug,
marker::PhantomData,
mem::take,
num::NonZeroUsize,
let cell = series.get("cell").unwrap()/*XXX*/;
let mut coords = Vec::with_capacity(dims.len());
let (cell_formats, format_map) = graph.interval.labeling.decode_format_map(&series);
- let cell_footnotes =
- graph
- .interval
- .labeling
- .children
- .iter()
- .find_map(|child| match child {
- LabelingChild::Footnotes(footnotes) => series.get(footnotes.variable.as_str()),
- _ => None,
- });
+ let cell_footnotes = graph
+ .interval
+ .footnotes()
+ .and_then(|footnotes| series.get(footnotes.variable.as_str()));
let mut data = HashMap::new();
for (i, cell) in cell.values.iter().enumerate() {
coords.clear();
for part in s.split(',') {
if let Ok(index) = part.parse::<usize>()
&& let Some(index) = index.checked_sub(1)
- && let Some(footnote) = footnotes.get(index)
+ && let Some(footnote) = dbg!(footnotes.get(index))
{
- value = value.with_footnote(footnote);
+ value.add_footnote(footnote);
}
}
}
.collect::<Vec<_>>();
let mut pivot_table = PivotTable::new(dimensions)
.with_look(Arc::new(look))
- .with_footnotes(footnotes)
+ .with_footnotes(dbg!(footnotes))
.with_data(data)
.with_layer(¤t_layer);
let decimal = Decimal::for_lang(&self.lang);
dimension_index: Cell<Option<usize>>,
}
+impl Debug for Series {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("Series")
+ .field("name", &self.name)
+ .finish_non_exhaustive()
+ }
+}
+
impl Series {
fn new(name: String, values: Vec<DataValue>, map: Map) -> Self {
Self {
base_style: &AreaStyle,
) {
if let Some(sf) = sf {
- if sf.reset == Some(true) {
- value.styling_mut().footnotes.clear();
- }
-
let format = match &sf.child {
Some(SetFormatChild::Format(format)) => Some(format.decode()),
Some(SetFormatChild::NumberFormat(format)) => {
footnotes: Option<Footnotes>,
}
+impl Interval {
+ fn footnotes(&self) -> Option<&Footnotes> {
+ if let Some(footnotes) = &self.footnotes {
+ Some(footnotes)
+ } else {
+ self.labeling
+ .children
+ .iter()
+ .find_map(|child| child.as_footnotes())
+ }
+ }
+}
+
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct Labeling {
Footnotes(Footnotes),
}
+impl LabelingChild {
+ fn as_footnotes(&self) -> Option<&Footnotes> {
+ match self {
+ Self::Footnotes(footnotes) => Some(footnotes),
+ _ => None,
+ }
+ }
+}
+
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct Formatting {
test_raw_spvfile("legacy6");
}
+/// Regression test for `<setFormat reset="true">`.
+#[test]
+fn legacy7() {
+ test_raw_spvfile("legacy7");
+}
+
fn test_raw_spvfile(name: &str) {
let input_filename = Path::new("src/spv/testdata")
.join(name)
--- /dev/null
+ Ranks
+╭───────────────────────────────────────────────────────┬─────┬─────────┬────────────╮
+│ │ N │Mean Rank│Sum of Ranks│
+├───────────────────────────────────────────────────────┼─────┼─────────┼────────────┤
+│xxxxxxxxxx - yyyyyyyyyyyyy Negative Ranks│25[a]│ 13,00│ 325,00│
+│ Positive Ranks│ 0[b]│ ,00│ ,00│
+│ Ties │ 0[c]│ │ │
+│ Total │ 25│ │ │
+├───────────────────────────────────────────────────────┼─────┼─────────┼────────────┤
+│xxxxxxxxxxxxx - yyyyyyyyyyyyyy Negative Ranks│25[d]│ 13,00│ 325,00│
+│ Positive Ranks│ 0[e]│ ,00│ ,00│
+│ Ties │ 0[f]│ │ │
+│ Total │ 25│ │ │
+├───────────────────────────────────────────────────────┼─────┼─────────┼────────────┤
+│xxxxxxxxxxxxxx - yyyyyyyyyyyyyyy Negative Ranks│25[g]│ 13,00│ 325,00│
+│ Positive Ranks│ 0[h]│ ,00│ ,00│
+│ Ties │ 0[i]│ │ │
+│ Total │ 25│ │ │
+├───────────────────────────────────────────────────────┼─────┼─────────┼────────────┤
+│xxxxxxxxxxxxxxxx - yyyyyyyyyyyyyyyyy Negative Ranks│ 5[j]│ 3,00│ 15,00│
+│ Positive Ranks│ 0[k]│ ,00│ ,00│
+│ Ties │20[l]│ │ │
+│ Total │ 25│ │ │
+├───────────────────────────────────────────────────────┼─────┼─────────┼────────────┤
+│xxxxxxxxxxxxxxxxxxx - yyyyyyyyyyyyyyyyyy Negative Ranks│ 0[m]│ ,00│ ,00│
+│ Positive Ranks│ 5[n]│ 3,00│ 15,00│
+│ Ties │20[o]│ │ │
+│ Total │ 25│ │ │
+╰───────────────────────────────────────────────────────┴─────┴─────────┴────────────╯
+a. Footnote A
+b. Footnote B
+c. Footnote C
+d. Footnote D
+e. Footnote E
+f. Footnote F
+g. Footnote G
+h. Footnote H
+i. Footnote I
+j. Footnote J
+k. Footnote K
+l. Footnote L
+m. Footnote M
+n. Footnote N
+o. Footnote O