1use std::collections::BTreeMap;
12
13use roxmltree::{Document, Node};
14
15use crate::error::{CslParseError, CslParseErrorKind};
16use crate::style::{
17 Bibliography, BibliographyOptions, Branch, Choose, Citation, CitationOptions, Common,
18 Conditions, DateElement, DatePart, Element, EtAl, Group, Info, InfoCategory, InfoContributor,
19 InfoLink, InheritableNameOptions, Label, Layout, LocaleBlock, Match, NameElement, NamePart,
20 Names, Number, SortKey, SortKeyOptions, SortTarget, Style, StyleClass, StyleOptions, Text,
21 TextSource,
22};
23
24const CSL_NAMESPACE: &str = "http://purl.org/net/xbiblio/csl";
26
27pub fn parse_style(input: &str) -> Result<Style, CslParseError> {
54 let document = Document::parse(input).map_err(|error| {
55 let offset = text_pos_to_byte_offset(input, error.pos()).unwrap_or(0);
56 CslParseError::new(CslParseErrorKind::MalformedXml(error.to_string()), offset)
57 })?;
58 let root = document.root_element();
59 if root.tag_name().name() != "style" {
60 let name = root.tag_name().name().to_owned();
61 return Err(err_at(root, CslParseErrorKind::UnexpectedRoot(name)));
62 }
63 if let Some(namespace) = root.tag_name().namespace()
67 && namespace != CSL_NAMESPACE
68 {
69 return Err(err_at(
70 root,
71 CslParseErrorKind::ForeignNamespace(namespace.to_owned()),
72 ));
73 }
74
75 let version = root
76 .attribute("version")
77 .ok_or_else(|| err_at(root, CslParseErrorKind::MissingVersion))?;
78 if version != "1.0" && !version.starts_with("1.0.") {
82 return Err(err_at(
83 root,
84 CslParseErrorKind::UnsupportedVersion(version.to_owned()),
85 ));
86 }
87 let version = version.to_owned();
88 let class = match root.attribute("class") {
89 Some("in-text") => StyleClass::InText,
90 Some("note") => StyleClass::Note,
91 Some(other) => {
92 return Err(err_at(
93 root,
94 CslParseErrorKind::UnknownClass(other.to_owned()),
95 ));
96 }
97 None => return Err(err_at(root, CslParseErrorKind::MissingClass)),
98 };
99 let default_locale = attr(root, "default-locale");
100
101 let mut info = Info::default();
102 let mut citation = None;
103 let mut bibliography = None;
104 let mut macros = BTreeMap::new();
105 let mut locales = Vec::new();
106
107 for child in child_elements(root) {
108 match child.tag_name().name() {
109 "info" => info = parse_info(child),
110 "citation" => citation = Some(parse_citation(child)?),
111 "bibliography" => bibliography = Some(parse_bibliography(child)?),
112 "macro" => {
113 let name = child
114 .attribute("name")
115 .ok_or_else(|| err_at(child, CslParseErrorKind::MissingMacroName))?;
116 macros.insert(name.to_owned(), parse_elements(child)?);
117 }
118 "locale" => locales.push(parse_locale(child, input)),
119 other => {
120 return Err(err_at(
121 child,
122 CslParseErrorKind::UnsupportedElement(other.to_owned()),
123 ));
124 }
125 }
126 }
127
128 Ok(Style {
129 class,
130 version,
131 default_locale,
132 options: parse_style_options(root),
133 info,
134 citation,
135 bibliography,
136 macros,
137 locales,
138 })
139}
140
141fn parse_info(node: Node<'_, '_>) -> Info {
142 let mut info = Info::default();
143 for child in child_elements(node) {
144 match child.tag_name().name() {
145 "id" => info.id = child.text().map(str::to_owned),
146 "title" => info.title = child.text().map(str::to_owned),
147 "link" => info.links.push(parse_info_link(child)),
148 "category" => info.categories.push(parse_info_category(child)),
149 "author" => info.authors.push(parse_info_contributor(child)),
150 "contributor" => info.contributors.push(parse_info_contributor(child)),
151 "updated" => info.updated = child.text().map(str::to_owned),
152 "issn" => {
153 if let Some(text) = child.text() {
154 info.issn.push(text.to_owned());
155 }
156 }
157 _ => {}
159 }
160 }
161 info
162}
163
164fn parse_info_link(node: Node<'_, '_>) -> InfoLink {
165 InfoLink {
166 rel: attr(node, "rel"),
167 href: attr(node, "href"),
168 media_type: attr(node, "type"),
169 }
170}
171
172fn parse_info_category(node: Node<'_, '_>) -> InfoCategory {
173 InfoCategory {
174 citation_format: attr(node, "citation-format"),
175 field: attr(node, "field"),
176 }
177}
178
179fn parse_info_contributor(node: Node<'_, '_>) -> InfoContributor {
180 let mut contributor = InfoContributor::default();
181 for child in child_elements(node) {
182 match child.tag_name().name() {
183 "name" => contributor.name = child.text().map(str::to_owned),
184 "uri" => contributor.uri = child.text().map(str::to_owned),
185 "email" => contributor.email = child.text().map(str::to_owned),
186 _ => {}
187 }
188 }
189 contributor
190}
191
192fn parse_locale(node: Node<'_, '_>, input: &str) -> LocaleBlock {
193 let xml = input
194 .get(node.range())
195 .map_or_else(String::new, str::to_owned);
196 LocaleBlock { xml }
197}
198
199fn parse_citation(node: Node<'_, '_>) -> Result<Citation, CslParseError> {
200 let (layout, sort) = parse_layout_and_sort(node)?;
201 Ok(Citation {
202 layout,
203 sort,
204 options: parse_citation_options(node),
205 })
206}
207
208fn parse_bibliography(node: Node<'_, '_>) -> Result<Bibliography, CslParseError> {
209 let (layout, sort) = parse_layout_and_sort(node)?;
210 Ok(Bibliography {
211 layout,
212 sort,
213 options: parse_bibliography_options(node),
214 })
215}
216
217fn parse_layout_and_sort(node: Node<'_, '_>) -> Result<(Layout, Vec<SortKey>), CslParseError> {
218 let mut layout = None;
219 let mut sort = Vec::new();
220 for child in child_elements(node) {
221 match child.tag_name().name() {
222 "layout" => layout = Some(parse_layout(child)?),
223 "sort" => sort = parse_sort(child),
224 other => {
225 return Err(err_at(
226 child,
227 CslParseErrorKind::UnsupportedElement(other.to_owned()),
228 ));
229 }
230 }
231 }
232 let layout = layout.ok_or_else(|| err_at(node, CslParseErrorKind::MissingLayout))?;
233 Ok((layout, sort))
234}
235
236fn parse_layout(node: Node<'_, '_>) -> Result<Layout, CslParseError> {
237 Ok(Layout {
238 elements: parse_elements(node)?,
239 common: parse_common(node),
240 })
241}
242
243fn parse_sort(node: Node<'_, '_>) -> Vec<SortKey> {
244 let mut keys = Vec::new();
245 for child in child_elements(node) {
246 if child.tag_name().name() == "key" {
247 let target = child.attribute("macro").map_or_else(
248 || SortTarget::Variable(attr(child, "variable").unwrap_or_default()),
249 |name| SortTarget::Macro(name.to_owned()),
250 );
251 keys.push(SortKey {
252 target,
253 descending: child.attribute("sort") == Some("descending"),
254 options: parse_sort_key_options(child),
255 });
256 }
257 }
258 keys
259}
260
261fn parse_elements(node: Node<'_, '_>) -> Result<Vec<Element>, CslParseError> {
262 let mut elements = Vec::new();
263 for child in child_elements(node) {
264 elements.push(parse_element(child)?);
265 }
266 Ok(elements)
267}
268
269fn parse_element(node: Node<'_, '_>) -> Result<Element, CslParseError> {
270 let element = match node.tag_name().name() {
271 "text" => Element::Text(parse_text(node)?),
272 "number" => Element::Number(parse_number(node)),
273 "date" => Element::Date(parse_date(node)),
274 "names" => Element::Names(Box::new(parse_names(node)?)),
275 "label" => Element::Label(parse_label(node)),
276 "group" => Element::Group(parse_group(node)?),
277 "choose" => Element::Choose(parse_choose(node)?),
278 other => {
279 return Err(err_at(
280 node,
281 CslParseErrorKind::UnsupportedElement(other.to_owned()),
282 ));
283 }
284 };
285 Ok(element)
286}
287
288fn parse_text(node: Node<'_, '_>) -> Result<Text, CslParseError> {
289 let source_count = [
290 node.attribute("variable"),
291 node.attribute("macro"),
292 node.attribute("term"),
293 node.attribute("value"),
294 ]
295 .into_iter()
296 .flatten()
297 .count();
298 if source_count > 1 {
299 return Err(err_at(node, CslParseErrorKind::TextWithMultipleSources));
300 }
301
302 let source = if let Some(variable) = node.attribute("variable") {
303 TextSource::Variable {
304 name: variable.to_owned(),
305 form: attr(node, "form"),
306 }
307 } else if let Some(name) = node.attribute("macro") {
308 TextSource::Macro(name.to_owned())
309 } else if let Some(term) = node.attribute("term") {
310 TextSource::Term {
311 name: term.to_owned(),
312 form: attr(node, "form"),
313 plural: bool_attr(node, "plural"),
314 }
315 } else if let Some(value) = node.attribute("value") {
316 TextSource::Value(value.to_owned())
317 } else {
318 return Err(err_at(node, CslParseErrorKind::TextWithoutSource));
319 };
320 Ok(Text {
321 source,
322 quotes: bool_attr(node, "quotes"),
323 strip_periods: bool_attr(node, "strip-periods"),
324 common: parse_common(node),
325 })
326}
327
328fn parse_number(node: Node<'_, '_>) -> Number {
329 Number {
330 variable: attr(node, "variable").unwrap_or_default(),
331 form: attr(node, "form"),
332 common: parse_common(node),
333 }
334}
335
336fn parse_date(node: Node<'_, '_>) -> DateElement {
337 let mut parts = Vec::new();
338 for child in child_elements(node) {
339 if child.tag_name().name() == "date-part" {
340 parts.push(DatePart {
341 name: attr(child, "name").unwrap_or_default(),
342 form: attr(child, "form"),
343 range_delimiter: attr(child, "range-delimiter"),
344 strip_periods: attr(child, "strip-periods"),
345 common: parse_common(child),
346 });
347 }
348 }
349 DateElement {
350 variable: attr(node, "variable").unwrap_or_default(),
351 form: attr(node, "form"),
352 date_parts: attr(node, "date-parts"),
353 parts,
354 common: parse_common(node),
355 }
356}
357
358fn parse_names(node: Node<'_, '_>) -> Result<Names, CslParseError> {
359 let variables = attr(node, "variable")
360 .unwrap_or_default()
361 .split_whitespace()
362 .map(str::to_owned)
363 .collect();
364 let mut name = None;
365 let mut et_al = None;
366 let mut label = None;
367 let mut substitute = Vec::new();
368 for child in child_elements(node) {
369 match child.tag_name().name() {
370 "name" => {
371 name = Some(parse_name_element(child));
372 }
373 "et-al" => {
374 et_al = Some(EtAl {
375 term: attr(child, "term"),
376 common: parse_common(child),
377 });
378 }
379 "label" => label = Some(parse_label(child)),
380 "substitute" => substitute = parse_elements(child)?,
381 other => {
382 return Err(err_at(
383 child,
384 CslParseErrorKind::UnsupportedElement(other.to_owned()),
385 ));
386 }
387 }
388 }
389 Ok(Names {
390 variables,
391 name,
392 et_al,
393 label,
394 substitute,
395 common: parse_common(node),
396 })
397}
398
399fn parse_name_element(node: Node<'_, '_>) -> NameElement {
400 let mut parts = Vec::new();
401 for child in child_elements(node) {
402 if child.tag_name().name() == "name-part" {
403 parts.push(NamePart {
404 name: attr(child, "name"),
405 common: parse_common(child),
406 });
407 }
408 }
409
410 NameElement {
411 form: attr(node, "form"),
412 options: parse_inheritable_name_options(node),
413 parts,
414 common: parse_common(node),
415 }
416}
417
418fn parse_label(node: Node<'_, '_>) -> Label {
419 Label {
420 variable: attr(node, "variable"),
421 form: attr(node, "form"),
422 plural: attr(node, "plural"),
423 strip_periods: attr(node, "strip-periods"),
424 common: parse_common(node),
425 }
426}
427
428fn parse_group(node: Node<'_, '_>) -> Result<Group, CslParseError> {
429 Ok(Group {
430 children: parse_elements(node)?,
431 common: parse_common(node),
432 })
433}
434
435fn parse_choose(node: Node<'_, '_>) -> Result<Choose, CslParseError> {
436 let mut branches = Vec::new();
437 let mut otherwise = Vec::new();
438 let mut seen_if = false;
439 let mut seen_else = false;
440 for child in child_elements(node) {
441 match child.tag_name().name() {
442 "if" => {
443 if seen_if || seen_else {
444 return Err(err_at(child, CslParseErrorKind::InvalidChooseOrder));
445 }
446 seen_if = true;
447 branches.push(Branch {
448 conditions: parse_conditions(child),
449 children: parse_elements(child)?,
450 });
451 }
452 "else-if" => {
453 if !seen_if || seen_else {
454 return Err(err_at(child, CslParseErrorKind::InvalidChooseOrder));
455 }
456 branches.push(Branch {
457 conditions: parse_conditions(child),
458 children: parse_elements(child)?,
459 });
460 }
461 "else" => {
462 if !seen_if || seen_else {
463 return Err(err_at(child, CslParseErrorKind::InvalidChooseOrder));
464 }
465 seen_else = true;
466 otherwise = parse_elements(child)?;
467 }
468 other => {
469 return Err(err_at(
470 child,
471 CslParseErrorKind::UnsupportedElement(other.to_owned()),
472 ));
473 }
474 }
475 }
476 if !seen_if {
477 return Err(err_at(node, CslParseErrorKind::InvalidChooseOrder));
478 }
479 Ok(Choose {
480 branches,
481 otherwise,
482 })
483}
484
485fn parse_conditions(node: Node<'_, '_>) -> Conditions {
486 let match_mode = match node.attribute("match") {
487 Some("any") => Match::Any,
488 Some("none") => Match::None,
489 _ => Match::All,
490 };
491 Conditions {
492 match_mode,
493 kind: tokens(node, "type"),
494 variable: tokens(node, "variable"),
495 is_numeric: tokens(node, "is-numeric"),
496 is_uncertain_date: tokens(node, "is-uncertain-date"),
497 locator: tokens(node, "locator"),
498 position: tokens(node, "position"),
499 disambiguate: bool_attr(node, "disambiguate"),
500 }
501}
502
503fn parse_common(node: Node<'_, '_>) -> Common {
504 Common {
505 prefix: attr(node, "prefix"),
506 suffix: attr(node, "suffix"),
507 delimiter: attr(node, "delimiter"),
508 font_style: attr(node, "font-style"),
509 font_variant: attr(node, "font-variant"),
510 font_weight: attr(node, "font-weight"),
511 text_decoration: attr(node, "text-decoration"),
512 vertical_align: attr(node, "vertical-align"),
513 text_case: attr(node, "text-case"),
514 display: attr(node, "display"),
515 }
516}
517
518fn parse_style_options(node: Node<'_, '_>) -> StyleOptions {
519 StyleOptions {
520 page_range_format: attr(node, "page-range-format"),
521 demote_non_dropping_particle: attr(node, "demote-non-dropping-particle"),
522 initialize_with_hyphen: attr(node, "initialize-with-hyphen"),
523 names: parse_inheritable_name_options(node),
524 }
525}
526
527fn parse_citation_options(node: Node<'_, '_>) -> CitationOptions {
528 CitationOptions {
529 collapse: attr(node, "collapse"),
530 cite_group_delimiter: attr(node, "cite-group-delimiter"),
531 year_suffix_delimiter: attr(node, "year-suffix-delimiter"),
532 after_collapse_delimiter: attr(node, "after-collapse-delimiter"),
533 disambiguate_add_names: attr(node, "disambiguate-add-names"),
534 disambiguate_add_givenname: attr(node, "disambiguate-add-givenname"),
535 disambiguate_add_year_suffix: attr(node, "disambiguate-add-year-suffix"),
536 givenname_disambiguation_rule: attr(node, "givenname-disambiguation-rule"),
537 near_note_distance: attr(node, "near-note-distance"),
538 names: parse_inheritable_name_options(node),
539 }
540}
541
542fn parse_bibliography_options(node: Node<'_, '_>) -> BibliographyOptions {
543 BibliographyOptions {
544 hanging_indent: attr(node, "hanging-indent"),
545 second_field_align: attr(node, "second-field-align"),
546 line_spacing: attr(node, "line-spacing"),
547 entry_spacing: attr(node, "entry-spacing"),
548 subsequent_author_substitute: attr(node, "subsequent-author-substitute"),
549 subsequent_author_substitute_rule: attr(node, "subsequent-author-substitute-rule"),
550 names: parse_inheritable_name_options(node),
551 }
552}
553
554fn parse_sort_key_options(node: Node<'_, '_>) -> SortKeyOptions {
555 SortKeyOptions {
556 min: attr(node, "names-min"),
557 use_first: attr(node, "names-use-first"),
558 use_last: attr(node, "names-use-last"),
559 }
560}
561
562fn parse_inheritable_name_options(node: Node<'_, '_>) -> InheritableNameOptions {
563 InheritableNameOptions {
564 et_al_min: attr(node, "et-al-min"),
565 et_al_use_first: attr(node, "et-al-use-first"),
566 et_al_subsequent_min: attr(node, "et-al-subsequent-min"),
567 et_al_subsequent_use_first: attr(node, "et-al-subsequent-use-first"),
568 et_al_use_last: attr(node, "et-al-use-last"),
569 and: attr(node, "and"),
570 delimiter_precedes_et_al: attr(node, "delimiter-precedes-et-al"),
571 delimiter_precedes_last: attr(node, "delimiter-precedes-last"),
572 initialize: attr(node, "initialize"),
573 initialize_with: attr(node, "initialize-with"),
574 name_as_sort_order: attr(node, "name-as-sort-order"),
575 sort_separator: attr(node, "sort-separator"),
576 }
577}
578
579fn child_elements<'a, 'input>(node: Node<'a, 'input>) -> impl Iterator<Item = Node<'a, 'input>> {
581 node.children().filter(Node::is_element)
582}
583
584fn attr(node: Node<'_, '_>, name: &str) -> Option<String> {
586 node.attribute(name).map(str::to_owned)
587}
588
589fn bool_attr(node: Node<'_, '_>, name: &str) -> bool {
591 node.attribute(name) == Some("true")
592}
593
594fn tokens(node: Node<'_, '_>, name: &str) -> Vec<String> {
596 node.attribute(name)
597 .map(|value| value.split_whitespace().map(str::to_owned).collect())
598 .unwrap_or_default()
599}
600
601fn text_pos_to_byte_offset(input: &str, position: roxmltree::TextPos) -> Option<usize> {
602 let row = usize::try_from(position.row).ok()?;
603 let col = usize::try_from(position.col).ok()?;
604 if row == 0 || col == 0 {
605 return None;
606 }
607
608 let (line_start, line) = line_at(input, row)?;
609 let col_offset = column_to_byte_offset(line, col)?;
610 Some(line_start + col_offset)
611}
612
613fn line_at(input: &str, row: usize) -> Option<(usize, &str)> {
614 let mut line_start = 0;
615 for (line_index, line) in input.split_inclusive('\n').enumerate() {
616 if line_index + 1 == row {
617 let line_without_newline = line.strip_suffix('\n').map_or(line, |stripped| stripped);
618 return Some((line_start, line_without_newline));
619 }
620 line_start += line.len();
621 }
622
623 if row == 1 && input.is_empty() {
624 return Some((0, ""));
625 }
626 None
627}
628
629fn column_to_byte_offset(line: &str, col: usize) -> Option<usize> {
630 let target_chars = col.checked_sub(1)?;
631 let mut chars_seen = 0;
632 for (byte_offset, _) in line.char_indices() {
633 if chars_seen == target_chars {
634 return Some(byte_offset);
635 }
636 chars_seen += 1;
637 }
638
639 if chars_seen == target_chars {
640 Some(line.len())
641 } else {
642 None
643 }
644}
645
646fn err_at(node: Node<'_, '_>, kind: CslParseErrorKind) -> CslParseError {
648 CslParseError::new(kind, node.range().start)
649}