1use crate::field::document_format::DocumentFormat;
12use crate::field::observe::{Representation, Selector};
13
14#[derive(Debug, Clone, Copy)]
16pub struct SelectorCapability {
17 pub selector: &'static str,
19 pub representations: &'static [&'static str],
21}
22
23#[derive(Debug, Clone)]
25pub struct Capabilities {
26 pub format: DocumentFormat,
28 pub compiled: bool,
30 pub selectors: Vec<SelectorCapability>,
32 pub profiles: Vec<String>,
34 pub native_selectors: Vec<&'static str>,
36}
37
38impl Capabilities {
39 pub fn to_json(&self) -> String {
41 let selectors = self
42 .selectors
43 .iter()
44 .map(|s| {
45 let reps = s
46 .representations
47 .iter()
48 .map(|r| format!("\"{r}\""))
49 .collect::<Vec<_>>()
50 .join(",");
51 format!(
52 "{{\"selector\":\"{}\",\"representations\":[{reps}]}}",
53 s.selector
54 )
55 })
56 .collect::<Vec<_>>()
57 .join(",");
58 let profiles = self
59 .profiles
60 .iter()
61 .map(|p| format!("\"{}\"", crate::field::provenance::json_escape(p)))
62 .collect::<Vec<_>>()
63 .join(",");
64 let native = self
65 .native_selectors
66 .iter()
67 .map(|s| format!("\"{s}\""))
68 .collect::<Vec<_>>()
69 .join(",");
70 format!(
71 concat!(
72 "{{",
73 "\"root\":\"document\",",
74 "\"format\":\"{}\",",
75 "\"adapter\":\"{}\",",
76 "\"compiled\":{},",
77 "\"selectors\":[{}],",
78 "\"profiles\":[{}],",
79 "\"native_selectors\":[{}]",
80 "}}"
81 ),
82 self.format.name(),
83 self.format.adapter(),
84 self.compiled,
85 selectors,
86 profiles,
87 native
88 )
89 }
90}
91
92const DOCX_NATIVE: &[&str] = &[
93 "docx-story",
94 "docx-paragraph",
95 "docx-table",
96 "docx-cell",
97 "docx-find",
98 "package-part",
99 "relationship",
100 "member",
101];
102const EPUB_NATIVE: &[&str] = &[
103 "epub-package",
104 "epub-manifest-item",
105 "epub-spine-item",
106 "epub-nav",
107 "epub-nav-node",
108 "epub-resource",
109 "epub-block",
110 "epub-cell",
111 "epub-link",
112 "epub-find",
113 "member",
114];
115const ODT_NATIVE: &[&str] = &[
116 "odt-part",
117 "odt-paragraph",
118 "odt-heading",
119 "odt-table",
120 "odt-cell",
121 "odt-list",
122 "odt-find",
123 "package-part",
124 "member",
125];
126const PDF_NATIVE: &[&str] = &[
127 "document",
128 "object",
129 "stream",
130 "revision",
131 "revisions",
132 "page",
133 "byte-range",
134 "text-match",
135];
136const ODS_NATIVE: &[&str] = &[
137 "ods-sheet",
138 "ods-cell",
139 "ods-find",
140 "ods-styles",
141 "ods-named-expressions",
142 "ods-comments",
143 "package-part",
144 "member",
145];
146const XLSX_NATIVE: &[&str] = &[
147 "xlsx-sheet",
148 "xlsx-cell",
149 "xlsx-find",
150 "xlsx-styles",
151 "xlsx-defined-names",
152 "xlsx-external-rels",
153 "xlsx-comments",
154 "xlsx-hyperlinks",
155 "xlsx-tables",
156 "xlsx-drawing",
157 "package-part",
158 "relationship",
159 "member",
160];
161const PPTX_NATIVE: &[&str] = &[
162 "pptx-slide",
163 "pptx-shape",
164 "pptx-notes",
165 "pptx-layouts",
166 "pptx-masters",
167 "pptx-theme",
168 "pptx-media",
169 "pptx-tables",
170 "pptx-find",
171 "package-part",
172 "relationship",
173 "member",
174];
175const ODP_NATIVE: &[&str] = &[
176 "odp-slide",
177 "odp-shape",
178 "odp-notes",
179 "odp-masters",
180 "odp-media",
181 "odp-tables",
182 "odp-find",
183 "package-part",
184 "member",
185];
186const JSON_NATIVE: &[&str] = &["json-pointer", "json-node", "json-find"];
187const JSON5_NATIVE: &[&str] = &[
188 "json5-pointer",
189 "json5-node",
190 "json5-find",
191 "json5-comments",
192];
193const YAML_NATIVE: &[&str] = &[
194 "yaml-path",
195 "yaml-node",
196 "yaml-documents",
197 "yaml-anchor",
198 "yaml-find",
199];
200const CSV_NATIVE: &[&str] = &["csv-row", "csv-cell", "csv-header", "csv-range", "csv-find"];
201const MARKDOWN_NATIVE: &[&str] = &["md-heading", "md-block", "md-code", "md-link", "md-find"];
202const XML_NATIVE: &[&str] = &[
203 "xml-path",
204 "xml-element",
205 "xml-attr",
206 "xml-namespaces",
207 "xml-find",
208];
209const HTML_NATIVE: &[&str] = &[
210 "html-path",
211 "html-element",
212 "html-attr",
213 "html-scripts",
214 "html-find",
215];
216const TOML_NATIVE: &[&str] = &["toml-path", "toml-table", "toml-find"];
217const JSONL_NATIVE: &[&str] = &["jsonl-line", "jsonl-pointer", "jsonl-find"];
218const EML_NATIVE: &[&str] = &[
219 "eml-header",
220 "eml-part",
221 "eml-attachments",
222 "eml-body",
223 "eml-find",
224];
225const PARQUET_NATIVE: &[&str] = &[
226 "parquet-schema",
227 "parquet-column",
228 "parquet-row-group",
229 "parquet-cell",
230];
231const ARROW_NATIVE: &[&str] = &["arrow-schema", "arrow-column", "arrow-batch", "arrow-cell"];
232const CBOR_NATIVE: &[&str] = &["cbor-pointer", "cbor-node", "cbor-find"];
233const MSGPACK_NATIVE: &[&str] = &["msgpack-pointer", "msgpack-node", "msgpack-find"];
234const CONFIG_NATIVE: &[&str] = &[
235 "config-line",
236 "config-entry",
237 "config-section",
238 "config-find",
239];
240const FEED_NATIVE: &[&str] = &[
241 "feed-channel",
242 "feed-field",
243 "feed-entry",
244 "feed-entry-field",
245 "feed-find",
246];
247const GEOJSON_NATIVE: &[&str] = &[
248 "geojson-type",
249 "geojson-feature",
250 "geojson-geometry",
251 "geojson-coordinates",
252 "geojson-property",
253 "geojson-find",
254];
255const GIS_NATIVE: &[&str] = &[
256 "gis-root",
257 "gis-field",
258 "gis-record",
259 "gis-record-field",
260 "gis-point",
261 "gis-find",
262];
263const NOTEBOOK_NATIVE: &[&str] = &[
264 "notebook-nbformat",
265 "notebook-cell",
266 "notebook-cell-type",
267 "notebook-cell-source",
268 "notebook-cell-output",
269 "notebook-find",
270];
271const FIXEDWIDTH_NATIVE: &[&str] = &[
272 "fixedwidth-row",
273 "fixedwidth-cell",
274 "fixedwidth-header",
275 "fixedwidth-columns",
276 "fixedwidth-range",
277 "fixedwidth-find",
278];
279const RST_NATIVE: &[&str] = &[
280 "rst-heading",
281 "rst-block",
282 "rst-directive",
283 "rst-inline",
284 "rst-find",
285];
286const ASCIIDOC_NATIVE: &[&str] = &[
287 "adoc-heading",
288 "adoc-block",
289 "adoc-attribute",
290 "adoc-inline",
291 "adoc-find",
292];
293const MDX_NATIVE: &[&str] = &[
294 "mdx-heading",
295 "mdx-block",
296 "mdx-esm",
297 "mdx-jsx",
298 "mdx-expression",
299 "mdx-find",
300];
301
302const MHTML_NATIVE: &[&str] = &[
303 "mhtml-root",
304 "mhtml-resource",
305 "mhtml-location",
306 "mhtml-find",
307];
308
309const LOGSTREAM_NATIVE: &[&str] = &["logstream-line", "logstream-field", "logstream-find"];
310
311const PKGMETA_NATIVE: &[&str] = &[
312 "pkgmeta-section",
313 "pkgmeta-entry",
314 "pkgmeta-key",
315 "pkgmeta-find",
316];
317
318const APISPEC_NATIVE: &[&str] = &[
319 "apispec-dialect",
320 "apispec-version",
321 "apispec-object",
322 "apispec-ref",
323 "apispec-find",
324];
325
326const BIB_NATIVE: &[&str] = &[
327 "bib-dialect",
328 "bib-entry",
329 "bib-field",
330 "bib-key",
331 "bib-find",
332];
333
334const RTF_NATIVE: &[&str] = &[
335 "rtf-header",
336 "rtf-group",
337 "rtf-control",
338 "rtf-hex",
339 "rtf-unicode",
340 "rtf-text",
341 "rtf-dest",
342 "rtf-find",
343];
344
345const CONTAINER_NATIVE: &[&str] = &["container-dialect", "container-entry", "container-find"];
346
347const HDF_NATIVE: &[&str] = &[
348 "hdf-dialect",
349 "hdf-object",
350 "hdf-dimension",
351 "hdf-attribute",
352 "hdf-find",
353];
354
355const CFBF_NATIVE: &[&str] = &["cfbf-dialect", "cfbf-entry", "cfbf-stream", "cfbf-find"];
356
357const DOC_NATIVE: &[&str] = &["doc-fib", "doc-piece", "doc-text", "doc-find"];
358
359const XLS_NATIVE: &[&str] = &[
360 "xls-biff",
361 "xls-sheet",
362 "xls-cell",
363 "xls-record",
364 "xls-find",
365];
366
367const PPT_NATIVE: &[&str] = &[
368 "ppt-document",
369 "ppt-slide",
370 "ppt-run",
371 "ppt-record",
372 "ppt-find",
373];
374
375const MSG_NATIVE: &[&str] = &[
376 "msg-summary",
377 "msg-property",
378 "msg-recipient",
379 "msg-attachment",
380 "msg-find",
381];
382
383const COMMON_METADATA: &[&str] = &["metadata"];
384const COMMON_TEXT: &[&str] = &["text"];
385const COMMON_TEXT_META: &[&str] = &["text", "metadata"];
386const COMMON_RESOURCE: &[&str] = &["metadata"];
387const COMMON_RESOURCE_BYTES: &[&str] = &["metadata", "exact", "decoded"];
388const SEARCH: &[&str] = &["text"];
389
390fn caps(selector: &'static str, representations: &'static [&'static str]) -> SelectorCapability {
391 SelectorCapability {
392 selector,
393 representations,
394 }
395}
396
397pub fn capabilities_for_format(format: DocumentFormat) -> Capabilities {
399 let compiled = format.compiled();
400 if !compiled {
401 return Capabilities {
402 format,
403 compiled,
404 selectors: Vec::new(),
405 profiles: Vec::new(),
406 native_selectors: Vec::new(),
407 };
408 }
409 let (selectors, profiles, native_selectors): (Vec<SelectorCapability>, Vec<String>, Vec<&str>) =
410 match format {
411 DocumentFormat::Pdf => (
412 vec![
413 caps("metadata", COMMON_METADATA),
414 caps("text", COMMON_TEXT),
415 caps("search-match", SEARCH),
416 ],
417 Vec::new(),
418 PDF_NATIVE.to_vec(),
419 ),
420 DocumentFormat::Docx => (
421 vec![
422 caps("metadata", COMMON_METADATA),
423 caps("text", COMMON_TEXT),
424 caps("heading", COMMON_TEXT_META),
425 caps("block", COMMON_TEXT_META),
426 caps("table", COMMON_TEXT_META),
427 caps("cell", COMMON_TEXT_META),
428 caps("resource", COMMON_RESOURCE),
429 caps("link", COMMON_METADATA),
430 caps("search-match", SEARCH),
431 ],
432 docx_profiles(),
433 DOCX_NATIVE.to_vec(),
434 ),
435 DocumentFormat::Epub => (
436 vec![
437 caps("metadata", COMMON_METADATA),
438 caps("text", COMMON_TEXT),
439 caps("heading", COMMON_TEXT_META),
440 caps("block", COMMON_TEXT_META),
441 caps("table", COMMON_TEXT_META),
442 caps("cell", COMMON_TEXT_META),
443 caps("resource", COMMON_RESOURCE_BYTES),
444 caps("link", COMMON_METADATA),
445 caps("search-match", SEARCH),
446 ],
447 epub_profiles(),
448 EPUB_NATIVE.to_vec(),
449 ),
450 DocumentFormat::Odt => (
451 vec![
452 caps("metadata", COMMON_METADATA),
453 caps("text", COMMON_TEXT),
454 caps("heading", COMMON_TEXT_META),
455 caps("block", COMMON_TEXT_META),
456 caps("table", COMMON_TEXT_META),
457 caps("cell", COMMON_TEXT_META),
458 caps("resource", COMMON_RESOURCE),
459 caps("link", COMMON_METADATA),
460 caps("search-match", SEARCH),
461 ],
462 odt_profiles(),
463 ODT_NATIVE.to_vec(),
464 ),
465 DocumentFormat::Ods => (
466 vec![
467 caps("metadata", COMMON_METADATA),
468 caps("text", COMMON_TEXT),
469 caps("table", COMMON_TEXT_META),
470 caps("cell", COMMON_TEXT_META),
471 caps("search-match", SEARCH),
472 ],
473 ods_profiles(),
474 ODS_NATIVE.to_vec(),
475 ),
476 DocumentFormat::Xlsx => (
477 vec![
478 caps("metadata", COMMON_METADATA),
479 caps("text", COMMON_TEXT),
480 caps("table", COMMON_TEXT_META),
481 caps("cell", COMMON_TEXT_META),
482 caps("search-match", SEARCH),
483 ],
484 xlsx_profiles(),
485 XLSX_NATIVE.to_vec(),
486 ),
487 DocumentFormat::Pptx => (
488 vec![
489 caps("metadata", COMMON_METADATA),
490 caps("text", COMMON_TEXT),
491 caps("table", COMMON_TEXT_META),
492 caps("cell", COMMON_TEXT_META),
493 caps("search-match", SEARCH),
494 ],
495 pptx_profiles(),
496 PPTX_NATIVE.to_vec(),
497 ),
498 DocumentFormat::Odp => (
499 vec![
500 caps("metadata", COMMON_METADATA),
501 caps("text", COMMON_TEXT),
502 caps("table", COMMON_TEXT_META),
503 caps("cell", COMMON_TEXT_META),
504 caps("search-match", SEARCH),
505 ],
506 odp_profiles(),
507 ODP_NATIVE.to_vec(),
508 ),
509 DocumentFormat::Json => (
510 vec![
511 caps("metadata", COMMON_METADATA),
512 caps("text", COMMON_TEXT),
513 caps("search-match", SEARCH),
514 ],
515 Vec::new(),
516 JSON_NATIVE.to_vec(),
517 ),
518 DocumentFormat::Json5 => (
519 vec![
520 caps("metadata", COMMON_METADATA),
521 caps("text", COMMON_TEXT),
522 caps("search-match", SEARCH),
523 ],
524 Vec::new(),
525 JSON5_NATIVE.to_vec(),
526 ),
527 DocumentFormat::Yaml => (
528 vec![
529 caps("metadata", COMMON_METADATA),
530 caps("text", COMMON_TEXT),
531 caps("search-match", SEARCH),
532 ],
533 Vec::new(),
534 YAML_NATIVE.to_vec(),
535 ),
536 DocumentFormat::Csv => (
537 vec![
538 caps("metadata", COMMON_METADATA),
539 caps("text", COMMON_TEXT),
540 caps("table", COMMON_TEXT_META),
541 caps("cell", COMMON_TEXT_META),
542 caps("search-match", SEARCH),
543 ],
544 Vec::new(),
545 CSV_NATIVE.to_vec(),
546 ),
547 DocumentFormat::Markdown => (
548 vec![
549 caps("metadata", COMMON_METADATA),
550 caps("text", COMMON_TEXT),
551 caps("heading", COMMON_TEXT_META),
552 caps("block", COMMON_TEXT_META),
553 caps("search-match", SEARCH),
554 ],
555 Vec::new(),
556 MARKDOWN_NATIVE.to_vec(),
557 ),
558 DocumentFormat::Xml => (
559 vec![
560 caps("metadata", COMMON_METADATA),
561 caps("text", COMMON_TEXT),
562 caps("search-match", SEARCH),
563 ],
564 Vec::new(),
565 XML_NATIVE.to_vec(),
566 ),
567 DocumentFormat::Html => (
568 vec![
569 caps("metadata", COMMON_METADATA),
570 caps("text", COMMON_TEXT),
571 caps("heading", COMMON_TEXT_META),
572 caps("link", COMMON_METADATA),
573 caps("search-match", SEARCH),
574 ],
575 Vec::new(),
576 HTML_NATIVE.to_vec(),
577 ),
578 DocumentFormat::Toml => (
579 vec![
580 caps("metadata", COMMON_METADATA),
581 caps("text", COMMON_TEXT),
582 caps("search-match", SEARCH),
583 ],
584 Vec::new(),
585 TOML_NATIVE.to_vec(),
586 ),
587 DocumentFormat::Jsonl => (
588 vec![
589 caps("metadata", COMMON_METADATA),
590 caps("text", COMMON_TEXT),
591 caps("search-match", SEARCH),
592 ],
593 Vec::new(),
594 JSONL_NATIVE.to_vec(),
595 ),
596 DocumentFormat::Eml => (
597 vec![
598 caps("metadata", COMMON_METADATA),
599 caps("text", COMMON_TEXT),
600 caps("resource", COMMON_RESOURCE_BYTES),
601 caps("search-match", SEARCH),
602 ],
603 Vec::new(),
604 EML_NATIVE.to_vec(),
605 ),
606 DocumentFormat::Parquet => (
607 vec![
608 caps("metadata", COMMON_METADATA),
609 caps("text", COMMON_TEXT),
610 caps("table", COMMON_TEXT_META),
611 caps("cell", COMMON_TEXT_META),
612 caps("search-match", SEARCH),
613 ],
614 Vec::new(),
615 PARQUET_NATIVE.to_vec(),
616 ),
617 DocumentFormat::ArrowIpc => (
618 vec![
619 caps("metadata", COMMON_METADATA),
620 caps("text", COMMON_TEXT),
621 caps("table", COMMON_TEXT_META),
622 caps("cell", COMMON_TEXT_META),
623 caps("search-match", SEARCH),
624 ],
625 Vec::new(),
626 ARROW_NATIVE.to_vec(),
627 ),
628 DocumentFormat::Cbor => (
629 vec![
630 caps("metadata", COMMON_METADATA),
631 caps("text", COMMON_TEXT),
632 caps("search-match", SEARCH),
633 ],
634 Vec::new(),
635 CBOR_NATIVE.to_vec(),
636 ),
637 DocumentFormat::Msgpack => (
638 vec![
639 caps("metadata", COMMON_METADATA),
640 caps("text", COMMON_TEXT),
641 caps("search-match", SEARCH),
642 ],
643 Vec::new(),
644 MSGPACK_NATIVE.to_vec(),
645 ),
646 DocumentFormat::Config => (
647 vec![
648 caps("metadata", COMMON_METADATA),
649 caps("text", COMMON_TEXT),
650 caps("search-match", SEARCH),
651 ],
652 Vec::new(),
653 CONFIG_NATIVE.to_vec(),
654 ),
655 DocumentFormat::Feed => (
656 vec![
657 caps("metadata", COMMON_METADATA),
658 caps("text", COMMON_TEXT),
659 caps("search-match", SEARCH),
660 ],
661 Vec::new(),
662 FEED_NATIVE.to_vec(),
663 ),
664 DocumentFormat::Geojson => (
665 vec![
666 caps("metadata", COMMON_METADATA),
667 caps("text", COMMON_TEXT),
668 caps("search-match", SEARCH),
669 ],
670 Vec::new(),
671 GEOJSON_NATIVE.to_vec(),
672 ),
673 DocumentFormat::Gis => (
674 vec![
675 caps("metadata", COMMON_METADATA),
676 caps("text", COMMON_TEXT),
677 caps("search-match", SEARCH),
678 ],
679 Vec::new(),
680 GIS_NATIVE.to_vec(),
681 ),
682 DocumentFormat::Notebook => (
683 vec![
684 caps("metadata", COMMON_METADATA),
685 caps("text", COMMON_TEXT),
686 caps("search-match", SEARCH),
687 ],
688 Vec::new(),
689 NOTEBOOK_NATIVE.to_vec(),
690 ),
691 DocumentFormat::FixedWidth => (
692 vec![
693 caps("metadata", COMMON_METADATA),
694 caps("text", COMMON_TEXT),
695 caps("table", COMMON_TEXT_META),
696 caps("cell", COMMON_TEXT_META),
697 caps("search-match", SEARCH),
698 ],
699 Vec::new(),
700 FIXEDWIDTH_NATIVE.to_vec(),
701 ),
702 DocumentFormat::Rst => (
703 vec![
704 caps("metadata", COMMON_METADATA),
705 caps("text", COMMON_TEXT),
706 caps("heading", COMMON_TEXT_META),
707 caps("block", COMMON_TEXT_META),
708 caps("search-match", SEARCH),
709 ],
710 Vec::new(),
711 RST_NATIVE.to_vec(),
712 ),
713 DocumentFormat::Asciidoc => (
714 vec![
715 caps("metadata", COMMON_METADATA),
716 caps("text", COMMON_TEXT),
717 caps("heading", COMMON_TEXT_META),
718 caps("block", COMMON_TEXT_META),
719 caps("search-match", SEARCH),
720 ],
721 Vec::new(),
722 ASCIIDOC_NATIVE.to_vec(),
723 ),
724 DocumentFormat::Mdx => (
725 vec![
726 caps("metadata", COMMON_METADATA),
727 caps("text", COMMON_TEXT),
728 caps("heading", COMMON_TEXT_META),
729 caps("block", COMMON_TEXT_META),
730 caps("search-match", SEARCH),
731 ],
732 Vec::new(),
733 MDX_NATIVE.to_vec(),
734 ),
735 DocumentFormat::Mhtml => (
736 vec![
737 caps("metadata", COMMON_METADATA),
738 caps("text", COMMON_TEXT),
739 caps("resource", COMMON_RESOURCE_BYTES),
740 caps("search-match", SEARCH),
741 ],
742 Vec::new(),
743 MHTML_NATIVE.to_vec(),
744 ),
745 DocumentFormat::Logstream => (
746 vec![
747 caps("metadata", COMMON_METADATA),
748 caps("text", COMMON_TEXT),
749 caps("search-match", SEARCH),
750 ],
751 Vec::new(),
752 LOGSTREAM_NATIVE.to_vec(),
753 ),
754 DocumentFormat::Pkgmeta => (
755 vec![
756 caps("metadata", COMMON_METADATA),
757 caps("text", COMMON_TEXT),
758 caps("search-match", SEARCH),
759 ],
760 Vec::new(),
761 PKGMETA_NATIVE.to_vec(),
762 ),
763 DocumentFormat::Apispec => (
764 vec![
765 caps("metadata", COMMON_METADATA),
766 caps("text", COMMON_TEXT),
767 caps("search-match", SEARCH),
768 ],
769 Vec::new(),
770 APISPEC_NATIVE.to_vec(),
771 ),
772 DocumentFormat::Bib => (
773 vec![
774 caps("metadata", COMMON_METADATA),
775 caps("text", COMMON_TEXT),
776 caps("search-match", SEARCH),
777 ],
778 Vec::new(),
779 BIB_NATIVE.to_vec(),
780 ),
781 DocumentFormat::Rtf => (
782 vec![
783 caps("metadata", COMMON_METADATA),
784 caps("text", COMMON_TEXT),
785 caps("search-match", SEARCH),
786 ],
787 Vec::new(),
788 RTF_NATIVE.to_vec(),
789 ),
790 DocumentFormat::Container => (
791 vec![
792 caps("metadata", COMMON_METADATA),
793 caps("text", COMMON_TEXT),
794 caps("search-match", SEARCH),
795 ],
796 Vec::new(),
797 CONTAINER_NATIVE.to_vec(),
798 ),
799 DocumentFormat::Hdf => (
800 vec![
801 caps("metadata", COMMON_METADATA),
802 caps("text", COMMON_TEXT),
803 caps("search-match", SEARCH),
804 ],
805 Vec::new(),
806 HDF_NATIVE.to_vec(),
807 ),
808 DocumentFormat::Cfbf => (
809 vec![
810 caps("metadata", COMMON_METADATA),
811 caps("text", COMMON_TEXT),
812 caps("search-match", SEARCH),
813 ],
814 Vec::new(),
815 CFBF_NATIVE.to_vec(),
816 ),
817 DocumentFormat::Doc => (
818 vec![
819 caps("metadata", COMMON_METADATA),
820 caps("text", COMMON_TEXT),
821 caps("search-match", SEARCH),
822 ],
823 Vec::new(),
824 DOC_NATIVE.to_vec(),
825 ),
826 DocumentFormat::Xls => (
827 vec![
828 caps("metadata", COMMON_METADATA),
829 caps("text", COMMON_TEXT),
830 caps("search-match", SEARCH),
831 ],
832 Vec::new(),
833 XLS_NATIVE.to_vec(),
834 ),
835 DocumentFormat::Ppt => (
836 vec![
837 caps("metadata", COMMON_METADATA),
838 caps("text", COMMON_TEXT),
839 caps("search-match", SEARCH),
840 ],
841 Vec::new(),
842 PPT_NATIVE.to_vec(),
843 ),
844 DocumentFormat::Msg => (
845 vec![
846 caps("metadata", COMMON_METADATA),
847 caps("text", COMMON_TEXT),
848 caps("search-match", SEARCH),
849 ],
850 Vec::new(),
851 MSG_NATIVE.to_vec(),
852 ),
853 DocumentFormat::Opaque => (Vec::new(), Vec::new(), Vec::new()),
854 };
855 Capabilities {
856 format,
857 compiled,
858 selectors,
859 profiles,
860 native_selectors,
861 }
862}
863
864#[cfg(feature = "docx")]
865fn docx_profiles() -> Vec<String> {
866 vec![crate::adapter::docx::DocxExtractProfile::DEFAULT.fingerprint()]
867}
868#[cfg(not(feature = "docx"))]
869fn docx_profiles() -> Vec<String> {
870 Vec::new()
871}
872
873#[cfg(feature = "epub")]
874fn epub_profiles() -> Vec<String> {
875 vec![crate::adapter::epub::EpubExtractProfile::DEFAULT.fingerprint()]
876}
877#[cfg(not(feature = "epub"))]
878fn epub_profiles() -> Vec<String> {
879 Vec::new()
880}
881
882#[cfg(feature = "odt")]
883fn odt_profiles() -> Vec<String> {
884 vec![crate::adapter::odt::OdtExtractProfile::DEFAULT.fingerprint()]
885}
886#[cfg(not(feature = "odt"))]
887fn odt_profiles() -> Vec<String> {
888 Vec::new()
889}
890
891#[cfg(feature = "ods")]
892fn ods_profiles() -> Vec<String> {
893 vec![crate::adapter::ods::OdsExtractProfile::DEFAULT.fingerprint()]
894}
895#[cfg(not(feature = "ods"))]
896fn ods_profiles() -> Vec<String> {
897 Vec::new()
898}
899
900#[cfg(feature = "xlsx")]
901fn xlsx_profiles() -> Vec<String> {
902 vec![crate::adapter::xlsx::XlsxExtractProfile::DEFAULT.fingerprint()]
903}
904#[cfg(not(feature = "xlsx"))]
905fn xlsx_profiles() -> Vec<String> {
906 Vec::new()
907}
908
909#[cfg(feature = "pptx")]
910fn pptx_profiles() -> Vec<String> {
911 vec![crate::adapter::pptx::PptxExtractProfile::DEFAULT.fingerprint()]
912}
913#[cfg(not(feature = "pptx"))]
914fn pptx_profiles() -> Vec<String> {
915 Vec::new()
916}
917
918#[cfg(feature = "odp")]
919fn odp_profiles() -> Vec<String> {
920 vec![crate::adapter::odp::OdpExtractProfile::DEFAULT.fingerprint()]
921}
922#[cfg(not(feature = "odp"))]
923fn odp_profiles() -> Vec<String> {
924 Vec::new()
925}
926
927pub fn common_selector_name(selector: &Selector) -> Option<&'static str> {
929 match selector {
930 Selector::Metadata => Some("metadata"),
931 Selector::Text => Some("text"),
932 Selector::Heading(_) => Some("heading"),
933 Selector::Block(_) => Some("block"),
934 Selector::Table(_) => Some("table"),
935 Selector::Cell { .. } => Some("cell"),
936 Selector::Resource(_) => Some("resource"),
937 Selector::Link(_) => Some("link"),
938 Selector::SearchMatch(_) => Some("search-match"),
939 _ => None,
940 }
941}
942
943pub fn common_representations(
946 format: DocumentFormat,
947 selector: &Selector,
948) -> Option<&'static [&'static str]> {
949 if !format.compiled() {
950 return None;
951 }
952 let name = common_selector_name(selector)?;
953 let caps = capabilities_for_format(format);
954 caps.selectors
955 .iter()
956 .find(|c| c.selector == name)
957 .map(|c| c.representations)
958}
959
960pub fn common_supported(
962 format: DocumentFormat,
963 selector: &Selector,
964 representation: Representation,
965) -> bool {
966 common_representations(format, selector)
967 .is_some_and(|reps| reps.contains(&representation.name()))
968}
969
970#[cfg(test)]
971mod tests {
972 use super::*;
973
974 #[test]
975 fn pdf_supports_text_and_search_but_not_tables() {
976 assert!(common_supported(
977 DocumentFormat::Pdf,
978 &Selector::Text,
979 Representation::Text
980 ));
981 assert!(common_supported(
982 DocumentFormat::Pdf,
983 &Selector::SearchMatch("x".into()),
984 Representation::Text
985 ));
986 assert!(!common_supported(
987 DocumentFormat::Pdf,
988 &Selector::Table(0),
989 Representation::Text
990 ));
991 assert!(!common_supported(
992 DocumentFormat::Pdf,
993 &Selector::Text,
994 Representation::Metadata
995 ));
996 }
997
998 #[test]
999 fn opaque_and_unknown_selectors_are_unsupported() {
1000 assert!(!common_supported(
1001 DocumentFormat::Opaque,
1002 &Selector::Text,
1003 Representation::Text
1004 ));
1005 assert!(!common_supported(
1006 DocumentFormat::Pdf,
1007 &Selector::Document,
1008 Representation::Metadata
1009 ));
1010 }
1011}