From 15b5b6d3c13632c56f2d1d436e8956b6bfe7ece3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D1=83=D1=81=D0=BB=D0=B0=D0=BD=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B7=D0=BA=D0=BE=D0=B2?= Date: Sat, 16 Sep 2023 22:06:03 +0300 Subject: [PATCH] fix: best-effort whitespace restoration --- src/lib.rs | 473 ++++++++++++++++++++++++++++++++++- tests/non_standard_genres.rs | 12 +- tests/parse_complex.rs | 2 +- tests/parse_many_bodies.rs | 18 +- tests/parse_minimal.rs | 12 +- 5 files changed, 493 insertions(+), 24 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 8e6daf0..268d656 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2199,6 +2199,45 @@ impl From for Style { } } +trait NoneIfUseless { + fn none_if_useless(self) -> Option + where + Self: Sized; +} + +impl NoneIfUseless for Vec { + fn none_if_useless(self) -> Option { + Some( + self.into_iter() + .filter_map(T::none_if_useless) + .collect::>(), + ) + .filter(|result| !result.is_empty()) + } +} + +impl NoneIfUseless for String { + fn none_if_useless(self) -> Option { + // we don't trim, this is already done by quick-xml + // String::trim() may also remove extra characters like NBSP \u{a0} + if self.is_empty() { + None + } else { + Some(self) + } + } +} + +impl NoneIfUseless for Style { + fn none_if_useless(self) -> Option { + let elements = self.elements.none_if_useless()?; + Some(Style { + lang: self.lang, + elements, + }) + } +} + /// Markup #[derive(Debug, PartialEq)] pub enum StyleElement { @@ -2214,6 +2253,212 @@ pub enum StyleElement { Text(String), } +impl StyleElement { + fn prepend_whitespace(&mut self) { + use StyleElement::*; + match self { + Strong(s) => { + if let Some(e) = s.elements.first_mut() { + e.prepend_whitespace(); + } + } + Emphasis(e) => { + if let Some(e) = e.elements.first_mut() { + e.prepend_whitespace(); + } + } + Style(s) => { + if let Some(e) = s.elements.first_mut() { + e.prepend_whitespace(); + } + } + Link(l) => { + if let Some(e) = l.elements.first_mut() { + e.prepend_whitespace(); + } + } + Strikethrough(s) => { + if let Some(e) = s.elements.first_mut() { + e.prepend_whitespace(); + } + } + Subscript(s) => { + if let Some(e) = s.elements.first_mut() { + e.prepend_whitespace(); + } + } + Superscript(s) => { + if let Some(e) = s.elements.first_mut() { + e.prepend_whitespace(); + } + } + Code(c) => { + if let Some(e) = c.elements.first_mut() { + e.prepend_whitespace(); + } + } + Image(_) => {} + Text(t) => { + if t.trim_start() == t { + t.insert(0, ' '); + } + } + } + } + + fn append_whitespace(&mut self) { + use StyleElement::*; + match self { + Strong(s) => { + if let Some(e) = s.elements.last_mut() { + e.append_whitespace(); + } + } + Emphasis(e) => { + if let Some(e) = e.elements.last_mut() { + e.append_whitespace(); + } + } + Style(s) => { + if let Some(e) = s.elements.last_mut() { + e.append_whitespace(); + } + } + Link(l) => { + if let Some(e) = l.elements.last_mut() { + e.append_whitespace(); + } + } + Strikethrough(s) => { + if let Some(e) = s.elements.last_mut() { + e.append_whitespace(); + } + } + Subscript(s) => { + if let Some(e) = s.elements.last_mut() { + e.append_whitespace(); + } + } + Superscript(s) => { + if let Some(e) = s.elements.last_mut() { + e.append_whitespace(); + } + } + Code(c) => { + if let Some(e) = c.elements.last_mut() { + e.append_whitespace(); + } + } + Image(_) => {} + Text(t) => { + if t.trim_end() == t { + t.push(' '); + } + } + } + } +} + +impl NoneIfUseless for StyleElement { + fn none_if_useless(self) -> Option { + use StyleElement::*; + match self { + Strong(s) => s.none_if_useless().map(Strong), + Emphasis(e) => e.none_if_useless().map(Emphasis), + Style(NamedStyle { + name, + lang, + elements, + }) => elements.none_if_useless().map(|elements| { + Style(NamedStyle { + name, + lang, + elements, + }) + }), + Link(l) => Some(Link(l)), + Strikethrough(s) => s.none_if_useless().map(Strikethrough), + Subscript(s) => s.none_if_useless().map(Subscript), + Superscript(s) => s.none_if_useless().map(Superscript), + Code(c) => c.none_if_useless().map(Code), + Image(i) => Some(Image(i)), + Text(t) => t.none_if_useless().map(Text), + } + } +} + +fn fix_whitespaces(mut elements: Vec) -> Vec { + use StyleElement::*; + elements = elements + .into_iter() + .map(|e| match e { + Strong(mut s) => { + s.elements = fix_whitespaces(s.elements); + Strong(s) + } + Emphasis(mut s) => { + s.elements = fix_whitespaces(s.elements); + Emphasis(s) + } + Style(mut s) => { + s.elements = fix_whitespaces(s.elements); + Style(s) + } + Link(l) => Link(l), + Strikethrough(mut s) => { + s.elements = fix_whitespaces(s.elements); + Strikethrough(s) + } + Subscript(mut s) => { + s.elements = fix_whitespaces(s.elements); + Subscript(s) + } + Superscript(mut s) => { + s.elements = fix_whitespaces(s.elements); + Superscript(s) + } + Code(mut c) => { + c.elements = fix_whitespaces(c.elements); + Code(c) + } + Image(i) => Image(i), + Text(t) => Text(t), + }) + .collect(); + + let mut decisions = vec![]; + for w in elements.windows(2) { + decisions.push(match (&w[0], &w[1]) { + (_, Subscript(_)) | (_, Superscript(_)) | (Image(_), _) | (_, Image(_)) => 0, + (Subscript(_), _) | (Superscript(_), _) => 2, + (_, Link(_)) => 1, + (Link(_), _) => 2, + (_, Strikethrough(_)) => 1, + (Strikethrough(_), _) => 2, + (_, Code(_)) => 1, + (Code(_), _) => 2, + (_, Strong(_)) => 1, + (Strong(_), _) => 2, + (_, Emphasis(_)) => 1, + (Emphasis(_), _) => 2, + (_, Style(_)) => 1, + (Style(_), _) => 2, + (Text(_), Text(_)) => 1, + }); + } + + for (i, decision) in decisions.into_iter().enumerate() { + match decision { + 0 => {} + 1 => elements[i].append_whitespace(), + 2 => elements[i + 1].prepend_whitespace(), + _ => {} + } + } + + elements +} + impl Serialize for StyleElement { fn serialize(&self, serializer: S) -> Result where @@ -2423,21 +2668,120 @@ fn parse_style_elements_permissively(choices: Vec) -> Vec elements.push(StyleElement::Text(t)), } } - elements + let elements = elements.none_if_useless().unwrap_or_default(); + fix_whitespaces(elements) } /// Generic hyperlinks. Cannot be nested. Footnotes should be implemented by links referring to additional bodies /// in the same document #[derive(Debug, PartialEq, Deserialize, Serialize)] +#[serde(from = "LinkInternal")] pub struct Link { #[serde(rename = "@href")] pub href: Option, #[serde(rename = "@type", skip_serializing_if = "Option::is_none")] pub kind: Option, - #[serde(default, rename = "$value")] + #[serde(rename = "$value")] pub elements: Vec, } +impl From for Link { + fn from( + LinkInternal { + href, + kind, + mut elements, + }: LinkInternal, + ) -> Self { + elements = elements.none_if_useless().unwrap_or_default(); + elements = fix_whitespaces_link(elements); + Link { + href, + kind, + elements, + } + } +} + +#[derive(Debug, PartialEq, Deserialize)] +struct LinkInternal { + #[serde(rename = "@href")] + href: Option, + #[serde(rename = "@type")] + kind: Option, + #[serde(default, rename = "$value")] + elements: Vec, +} + +fn fix_whitespaces_link(mut elements: Vec) -> Vec { + use StyleLinkElement::*; + elements = elements + .into_iter() + .map(|e| match e { + Strong { mut elements } => { + elements = fix_whitespaces_link(elements); + Strong { elements } + } + Emphasis { mut elements } => { + elements = fix_whitespaces_link(elements); + Emphasis { elements } + } + Style { mut elements } => { + elements = fix_whitespaces_link(elements); + Style { elements } + } + Strikethrough { mut elements } => { + elements = fix_whitespaces_link(elements); + Strikethrough { elements } + } + Subscript { mut elements } => { + elements = fix_whitespaces_link(elements); + Subscript { elements } + } + Superscript { mut elements } => { + elements = fix_whitespaces_link(elements); + Superscript { elements } + } + Code { mut elements } => { + elements = fix_whitespaces_link(elements); + Code { elements } + } + Image(i) => Image(i), + Text(t) => Text(t), + }) + .collect(); + + let mut decisions = vec![]; + for w in elements.windows(2) { + decisions.push(match (&w[0], &w[1]) { + (_, Subscript { .. }) | (_, Superscript { .. }) | (Image(_), _) | (_, Image(_)) => 0, + (Subscript { .. }, _) | (Superscript { .. }, _) => 2, + (_, Strikethrough { .. }) => 1, + (Strikethrough { .. }, _) => 2, + (_, Code { .. }) => 1, + (Code { .. }, _) => 2, + (_, Strong { .. }) => 1, + (Strong { .. }, _) => 2, + (_, Emphasis { .. }) => 1, + (Emphasis { .. }, _) => 2, + (_, Style { .. }) => 1, + (Style { .. }, _) => 2, + (Text(_), Text(_)) => 1, + }); + } + + for (i, decision) in decisions.into_iter().enumerate() { + match decision { + 0 => {} + 1 => elements[i].append_whitespace(), + 2 => elements[i + 1].prepend_whitespace(), + _ => {} + } + } + + elements +} + /// Markup #[derive(Debug, PartialEq, Deserialize)] pub enum StyleLinkElement { @@ -2482,6 +2826,131 @@ pub enum StyleLinkElement { Text(String), } +impl NoneIfUseless for StyleLinkElement { + fn none_if_useless(self) -> Option { + use StyleLinkElement::*; + match self { + Strong { elements } => elements + .none_if_useless() + .map(|elements| Strong { elements }), + Emphasis { elements } => elements + .none_if_useless() + .map(|elements| Emphasis { elements }), + Style { elements } => elements + .none_if_useless() + .map(|elements| Style { elements }), + Strikethrough { elements } => elements + .none_if_useless() + .map(|elements| Strikethrough { elements }), + Subscript { elements } => elements + .none_if_useless() + .map(|elements| Subscript { elements }), + Superscript { elements } => elements + .none_if_useless() + .map(|elements| Superscript { elements }), + Code { elements } => elements.none_if_useless().map(|elements| Code { elements }), + Image(i) => Some(Image(i)), + Text(t) => t.none_if_useless().map(Text), + } + } +} + +impl StyleLinkElement { + fn prepend_whitespace(&mut self) { + use StyleLinkElement::*; + match self { + Strong { elements } => { + if let Some(e) = elements.first_mut() { + e.prepend_whitespace(); + } + } + Emphasis { elements } => { + if let Some(e) = elements.first_mut() { + e.prepend_whitespace(); + } + } + Style { elements } => { + if let Some(e) = elements.first_mut() { + e.prepend_whitespace(); + } + } + Strikethrough { elements } => { + if let Some(e) = elements.first_mut() { + e.prepend_whitespace(); + } + } + Subscript { elements } => { + if let Some(e) = elements.first_mut() { + e.prepend_whitespace(); + } + } + Superscript { elements } => { + if let Some(e) = elements.first_mut() { + e.prepend_whitespace(); + } + } + Code { elements } => { + if let Some(e) = elements.first_mut() { + e.prepend_whitespace(); + } + } + Image(_) => {} + Text(t) => { + if t.trim_start() == t { + t.insert(0, ' '); + } + } + } + } + + fn append_whitespace(&mut self) { + use StyleLinkElement::*; + match self { + Strong { elements } => { + if let Some(e) = elements.last_mut() { + e.append_whitespace(); + } + } + Emphasis { elements } => { + if let Some(e) = elements.last_mut() { + e.append_whitespace(); + } + } + Style { elements } => { + if let Some(e) = elements.last_mut() { + e.append_whitespace(); + } + } + Strikethrough { elements } => { + if let Some(e) = elements.last_mut() { + e.append_whitespace(); + } + } + Subscript { elements } => { + if let Some(e) = elements.last_mut() { + e.append_whitespace(); + } + } + Superscript { elements } => { + if let Some(e) = elements.last_mut() { + e.append_whitespace(); + } + } + Code { elements } => { + if let Some(e) = elements.last_mut() { + e.append_whitespace(); + } + } + Image(_) => {} + Text(t) => { + if t.trim_end() == t { + t.push(' '); + } + } + } + } +} + impl Serialize for StyleLinkElement { fn serialize(&self, serializer: S) -> Result where diff --git a/tests/non_standard_genres.rs b/tests/non_standard_genres.rs index 0efa037..0640c84 100644 --- a/tests/non_standard_genres.rs +++ b/tests/non_standard_genres.rs @@ -60,7 +60,7 @@ fn parse_prose_contemporary_genre() { style: None, elements: vec![ StyleElement::Text( - "Шеститомный труд У.\u{a0}Черчилля – героическая эпопея народов, выступивших против планетарной опасности, написанная выдающимся политиком, скрупулезным историком и талантливым литератором. Это летопись повседневного руководства страной государственного деятеля, чей вклад в общее дело победы антигитлеровской коалиции ни у кого не вызывает сомнений. Это размышления над прошлым, призванные послужить назиданием потомкам. В первой книге публикуются в сокращенном переводе с английского I и II тома мемуаров и описаны события с 1919 года по декабрь 1940 года, которые привели к ненужной, по словам автора, войне, которой можно было избежать. Во второй книге публикуются третий и четвертый тома мемуаров и описаны события в период с января 1941 по июнь 1943\u{a0}г.: вторжение фашистской Германии в Советский Союз, нападение милитаристской Японии на США, создание антигитлеровской коалиции, переход союзников от обороны к наступлению. В третьей книге публикуются пятый и шестой тома мемуаров и описаны события в период с июня 1943\u{a0}г. по июль 1945\u{a0}г.\u{a0}– капитуляция союзников Германии, Тегеранская, Ялтинская и Потсдамская конференции, высадка американских, английских и канадских войск в Нормандии, разгром гитлеровских войск в результате исторических побед Советской армии и союзников, капитуляция Германии.".into(), + "Шеститомный труд У.\u{a0}Черчилля – героическая эпопея народов, выступивших против планетарной опасности, написанная выдающимся политиком, скрупулезным историком и талантливым литератором. Это летопись повседневного руководства страной государственного деятеля, чей вклад в общее дело победы антигитлеровской коалиции ни у кого не вызывает сомнений. Это размышления над прошлым, призванные послужить назиданием потомкам. В первой книге публикуются в сокращенном переводе с английского I и II тома мемуаров и описаны события с 1919 года по декабрь 1940 года, которые привели к ненужной, по словам автора, войне, которой можно было избежать. Во второй книге публикуются третий и четвертый тома мемуаров и описаны события в период с января 1941 по июнь 1943\u{a0}г.: вторжение фашистской Германии в Советский Союз, нападение милитаристской Японии на США, создание антигитлеровской коалиции, переход союзников от обороны к наступлению. В третьей книге публикуются пятый и шестой тома мемуаров и описаны события в период с июня 1943\u{a0}г. по июль 1945\u{a0}г.\u{a0}– капитуляция союзников Германии, Тегеранская, Ялтинская и Потсдамская конференции, высадка американских, английских и канадских войск в Нормандии, разгром гитлеровских войск в результате исторических побед Советской армии и союзников, капитуляция Германии. ".into(), ), StyleElement::Emphasis( Style { @@ -297,7 +297,7 @@ fn parse_prose_contemporary_genre() { style: None, elements: vec![ StyleElement::Text( - "Руководитель проекта".into(), + "Руководитель проекта ".into(), ), StyleElement::Emphasis( Style { @@ -319,7 +319,7 @@ fn parse_prose_contemporary_genre() { style: None, elements: vec![ StyleElement::Text( - "Технический редактор".into(), + "Технический редактор ".into(), ), StyleElement::Emphasis( Style { @@ -341,7 +341,7 @@ fn parse_prose_contemporary_genre() { style: None, elements: vec![ StyleElement::Text( - "Корректор".into(), + "Корректор ".into(), ), StyleElement::Emphasis( Style { @@ -363,7 +363,7 @@ fn parse_prose_contemporary_genre() { style: None, elements: vec![ StyleElement::Text( - "Компьютерная верстка".into(), + "Компьютерная верстка ".into(), ), StyleElement::Emphasis( Style { @@ -385,7 +385,7 @@ fn parse_prose_contemporary_genre() { style: None, elements: vec![ StyleElement::Text( - "Художник обложки".into(), + "Художник обложки ".into(), ), StyleElement::Emphasis( Style { diff --git a/tests/parse_complex.rs b/tests/parse_complex.rs index 87ba80b..262f7e4 100644 --- a/tests/parse_complex.rs +++ b/tests/parse_complex.rs @@ -114,7 +114,7 @@ fn parse_complex() { ), elements: vec![ StyleElement::Text( - "Шеститомный труд У.\u{a0}Черчилля – героическая эпопея народов, выступивших против\n планетарной опасности, написанная выдающимся политиком, скрупулезным историком и талантливым литератором. Это летопись\n повседневного руководства страной государственного деятеля, чей вклад в общее дело победы антигитлеровской коалиции ни у\n кого не вызывает сомнений. Это размышления над прошлым, призванные послужить назиданием потомкам. В первой книге\n публикуются в сокращенном переводе с английского I и II тома мемуаров и описаны события с 1919 года по декабрь 1940\n года, которые привели к ненужной, по словам автора, войне, которой можно было избежать. Во второй книге публикуются\n третий и четвертый тома мемуаров и описаны события в период с января 1941 по июнь 1943\u{a0}г.: вторжение фашистской Германии\n в Советский Союз, нападение милитаристской Японии на США, создание антигитлеровской коалиции, переход союзников от\n обороны к наступлению. В третьей книге публикуются пятый и шестой тома мемуаров и описаны события в период с июня\n 1943\u{a0}г. по июль 1945\u{a0}г.\u{a0}– капитуляция союзников Германии, Тегеранская, Ялтинская и Потсдамская конференции, высадка\n американских, английских и канадских войск в Нормандии, разгром гитлеровских войск в результате исторических побед\n Советской армии и союзников, капитуляция Германии.".into(), + "Шеститомный труд У.\u{a0}Черчилля – героическая эпопея народов, выступивших против\n планетарной опасности, написанная выдающимся политиком, скрупулезным историком и талантливым литератором. Это летопись\n повседневного руководства страной государственного деятеля, чей вклад в общее дело победы антигитлеровской коалиции ни у\n кого не вызывает сомнений. Это размышления над прошлым, призванные послужить назиданием потомкам. В первой книге\n публикуются в сокращенном переводе с английского I и II тома мемуаров и описаны события с 1919 года по декабрь 1940\n года, которые привели к ненужной, по словам автора, войне, которой можно было избежать. Во второй книге публикуются\n третий и четвертый тома мемуаров и описаны события в период с января 1941 по июнь 1943\u{a0}г.: вторжение фашистской Германии\n в Советский Союз, нападение милитаристской Японии на США, создание антигитлеровской коалиции, переход союзников от\n обороны к наступлению. В третьей книге публикуются пятый и шестой тома мемуаров и описаны события в период с июня\n 1943\u{a0}г. по июль 1945\u{a0}г.\u{a0}– капитуляция союзников Германии, Тегеранская, Ялтинская и Потсдамская конференции, высадка\n американских, английских и канадских войск в Нормандии, разгром гитлеровских войск в результате исторических побед\n Советской армии и союзников, капитуляция Германии. ".into(), ), StyleElement::Emphasis( Style { diff --git a/tests/parse_many_bodies.rs b/tests/parse_many_bodies.rs index 5cb0ef6..6a09e85 100644 --- a/tests/parse_many_bodies.rs +++ b/tests/parse_many_bodies.rs @@ -68,7 +68,7 @@ fn parse_many_bodies() { style: None, elements: vec![ StyleElement::Text( - "Шеститомный труд У.\u{a0}Черчилля – героическая эпопея народов, выступивших против планетарной опасности, написанная выдающимся политиком, скрупулезным историком и талантливым литератором. Это летопись повседневного руководства страной государственного деятеля, чей вклад в общее дело победы антигитлеровской коалиции ни у кого не вызывает сомнений. Это размышления над прошлым, призванные послужить назиданием потомкам. В первой книге публикуются в сокращенном переводе с английского I и II тома мемуаров и описаны события с 1919 года по декабрь 1940 года, которые привели к ненужной, по словам автора, войне, которой можно было избежать. Во второй книге публикуются третий и четвертый тома мемуаров и описаны события в период с января 1941 по июнь 1943\u{a0}г.: вторжение фашистской Германии в Советский Союз, нападение милитаристской Японии на США, создание антигитлеровской коалиции, переход союзников от обороны к наступлению. В третьей книге публикуются пятый и шестой тома мемуаров и описаны события в период с июня 1943\u{a0}г. по июль 1945\u{a0}г.\u{a0}– капитуляция союзников Германии, Тегеранская, Ялтинская и Потсдамская конференции, высадка американских, английских и канадских войск в Нормандии, разгром гитлеровских войск в результате исторических побед Советской армии и союзников, капитуляция Германии.".into(), + "Шеститомный труд У.\u{a0}Черчилля – героическая эпопея народов, выступивших против планетарной опасности, написанная выдающимся политиком, скрупулезным историком и талантливым литератором. Это летопись повседневного руководства страной государственного деятеля, чей вклад в общее дело победы антигитлеровской коалиции ни у кого не вызывает сомнений. Это размышления над прошлым, призванные послужить назиданием потомкам. В первой книге публикуются в сокращенном переводе с английского I и II тома мемуаров и описаны события с 1919 года по декабрь 1940 года, которые привели к ненужной, по словам автора, войне, которой можно было избежать. Во второй книге публикуются третий и четвертый тома мемуаров и описаны события в период с января 1941 по июнь 1943\u{a0}г.: вторжение фашистской Германии в Советский Союз, нападение милитаристской Японии на США, создание антигитлеровской коалиции, переход союзников от обороны к наступлению. В третьей книге публикуются пятый и шестой тома мемуаров и описаны события в период с июня 1943\u{a0}г. по июль 1945\u{a0}г.\u{a0}– капитуляция союзников Германии, Тегеранская, Ялтинская и Потсдамская конференции, высадка американских, английских и канадских войск в Нормандии, разгром гитлеровских войск в результате исторических побед Советской армии и союзников, капитуляция Германии. ".into(), ), StyleElement::Emphasis( Style { @@ -305,7 +305,7 @@ fn parse_many_bodies() { style: None, elements: vec![ StyleElement::Text( - "Руководитель проекта".into(), + "Руководитель проекта ".into(), ), StyleElement::Emphasis( Style { @@ -327,7 +327,7 @@ fn parse_many_bodies() { style: None, elements: vec![ StyleElement::Text( - "Технический редактор".into(), + "Технический редактор ".into(), ), StyleElement::Emphasis( Style { @@ -349,7 +349,7 @@ fn parse_many_bodies() { style: None, elements: vec![ StyleElement::Text( - "Корректор".into(), + "Корректор ".into(), ), StyleElement::Emphasis( Style { @@ -371,7 +371,7 @@ fn parse_many_bodies() { style: None, elements: vec![ StyleElement::Text( - "Компьютерная верстка".into(), + "Компьютерная верстка ".into(), ), StyleElement::Emphasis( Style { @@ -393,7 +393,7 @@ fn parse_many_bodies() { style: None, elements: vec![ StyleElement::Text( - "Художник обложки".into(), + "Художник обложки ".into(), ), StyleElement::Emphasis( Style { @@ -665,7 +665,7 @@ fn parse_many_bodies() { StyleElement::Text("Достоевский Ф. М.".into()) ], }), - StyleElement::Text("Полн. собр. соч.: В. 30 т. Л., 1980. Т. 21. С. 133.".into()), + StyleElement::Text(" Полн. собр. соч.: В. 30 т. Л., 1980. Т. 21. С. 133.".into()), ], }), ], @@ -719,14 +719,14 @@ fn parse_many_bodies() { lang: None, style: None, elements: vec![ - StyleElement::Text("С. 31.".into()), + StyleElement::Text("С. 31. ".into()), StyleElement::Emphasis(Style { lang: None, elements: vec![ StyleElement::Text("Ох уж эти мне сказочники!..".into()) ], }), - StyleElement::Text(" — Эпиграф взят из рассказа В. Ф. Одоевского «Живой мертвец» (1839; ср.:".into()), + StyleElement::Text(" — Эпиграф взят из рассказа В. Ф. Одоевского «Живой мертвец» (1839; ср.: ".into()), StyleElement::Emphasis(Style { lang: None, elements: vec![ diff --git a/tests/parse_minimal.rs b/tests/parse_minimal.rs index 1f445e3..f858fc9 100644 --- a/tests/parse_minimal.rs +++ b/tests/parse_minimal.rs @@ -68,7 +68,7 @@ fn parse_minimal() { style: None, elements: vec![ StyleElement::Text( - "Шеститомный труд У.\u{a0}Черчилля – героическая эпопея народов, выступивших против планетарной опасности, написанная выдающимся политиком, скрупулезным историком и талантливым литератором. Это летопись повседневного руководства страной государственного деятеля, чей вклад в общее дело победы антигитлеровской коалиции ни у кого не вызывает сомнений. Это размышления над прошлым, призванные послужить назиданием потомкам. В первой книге публикуются в сокращенном переводе с английского I и II тома мемуаров и описаны события с 1919 года по декабрь 1940 года, которые привели к ненужной, по словам автора, войне, которой можно было избежать. Во второй книге публикуются третий и четвертый тома мемуаров и описаны события в период с января 1941 по июнь 1943\u{a0}г.: вторжение фашистской Германии в Советский Союз, нападение милитаристской Японии на США, создание антигитлеровской коалиции, переход союзников от обороны к наступлению. В третьей книге публикуются пятый и шестой тома мемуаров и описаны события в период с июня 1943\u{a0}г. по июль 1945\u{a0}г.\u{a0}– капитуляция союзников Германии, Тегеранская, Ялтинская и Потсдамская конференции, высадка американских, английских и канадских войск в Нормандии, разгром гитлеровских войск в результате исторических побед Советской армии и союзников, капитуляция Германии.".into(), + "Шеститомный труд У.\u{a0}Черчилля – героическая эпопея народов, выступивших против планетарной опасности, написанная выдающимся политиком, скрупулезным историком и талантливым литератором. Это летопись повседневного руководства страной государственного деятеля, чей вклад в общее дело победы антигитлеровской коалиции ни у кого не вызывает сомнений. Это размышления над прошлым, призванные послужить назиданием потомкам. В первой книге публикуются в сокращенном переводе с английского I и II тома мемуаров и описаны события с 1919 года по декабрь 1940 года, которые привели к ненужной, по словам автора, войне, которой можно было избежать. Во второй книге публикуются третий и четвертый тома мемуаров и описаны события в период с января 1941 по июнь 1943\u{a0}г.: вторжение фашистской Германии в Советский Союз, нападение милитаристской Японии на США, создание антигитлеровской коалиции, переход союзников от обороны к наступлению. В третьей книге публикуются пятый и шестой тома мемуаров и описаны события в период с июня 1943\u{a0}г. по июль 1945\u{a0}г.\u{a0}– капитуляция союзников Германии, Тегеранская, Ялтинская и Потсдамская конференции, высадка американских, английских и канадских войск в Нормандии, разгром гитлеровских войск в результате исторических побед Советской армии и союзников, капитуляция Германии. ".into(), ), StyleElement::Emphasis( Style { @@ -305,7 +305,7 @@ fn parse_minimal() { style: None, elements: vec![ StyleElement::Text( - "Руководитель проекта".into(), + "Руководитель проекта ".into(), ), StyleElement::Emphasis( Style { @@ -327,7 +327,7 @@ fn parse_minimal() { style: None, elements: vec![ StyleElement::Text( - "Технический редактор".into(), + "Технический редактор ".into(), ), StyleElement::Emphasis( Style { @@ -349,7 +349,7 @@ fn parse_minimal() { style: None, elements: vec![ StyleElement::Text( - "Корректор".into(), + "Корректор ".into(), ), StyleElement::Emphasis( Style { @@ -371,7 +371,7 @@ fn parse_minimal() { style: None, elements: vec![ StyleElement::Text( - "Компьютерная верстка".into(), + "Компьютерная верстка ".into(), ), StyleElement::Emphasis( Style { @@ -393,7 +393,7 @@ fn parse_minimal() { style: None, elements: vec![ StyleElement::Text( - "Художник обложки".into(), + "Художник обложки ".into(), ), StyleElement::Emphasis( Style {