use jsonpath_rust::query::queryable::Queryable as _;
use serde_json::Value;
use crate::{indexer::split_path, prelude::*, rules::IndexRules};
pub fn prune_document(doc: &mut Value, patterns: &[String]) -> (usize, usize) {
let mut deleted = 0;
let mut failed = 0;
for pattern in patterns {
match doc.delete_by_path(pattern) {
Ok(n) => deleted += n,
Err(_) => failed += 1,
}
}
(deleted, failed)
}
pub fn prune_docs(rules: &IndexRules, docs: &mut [(Box<str>, Value)], tn_id: TnId, file_id: &str) {
if !rules.parts.iter().any(|p| !p.prune.is_empty()) {
return;
}
let mut deleted = 0;
let mut failed = 0;
let mut culprit: Option<&str> = None;
for (path, doc) in docs.iter_mut() {
let Some((kind, _)) = split_path(path) else { continue };
for rule in rules.parts.iter().filter(|p| p.kind == kind) {
let (d, f) = prune_document(doc, &rule.prune);
deleted += d;
if f > 0 {
failed += f;
culprit = rule.prune.first().map(String::as_str);
}
}
}
if failed > 0 {
warn!(tn_id = %tn_id, file_id, failed, pattern = culprit.unwrap_or(""),
"Search prune pattern failed");
}
debug!(tn_id = %tn_id, file_id, deleted, "Search prune");
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::*;
fn notillo_patterns() -> Vec<String> {
vec!["$..c[0:][1:]".to_owned(), "$..cells[0:][0:][1:]".to_owned()]
}
fn pruned(mut doc: Value) -> Value {
let (_, failed) = prune_document(&mut doc, ¬illo_patterns());
assert_eq!(failed, 0, "the shipped patterns must evaluate");
doc
}
#[test]
fn a_table_block_keeps_every_row_and_column_width() {
let out = pruned(json!({
"c": { "type": "tableContent", "cw": [100, 200, 150], "hr": 1,
"rows": [
{ "cells": [["Alma"], ["Körte"]] },
{ "cells": [["Szilva"], ["Barack"]] }
] }
}));
assert_eq!(out["c"]["rows"].as_array().map(Vec::len), Some(2), "got {out}");
assert_eq!(out["c"]["cw"].as_array().map(Vec::len), Some(3), "got {out}");
let text = out.to_string();
for word in ["Alma", "Körte", "Szilva", "Barack"] {
assert!(text.contains(word), "missing {word} in {text}");
}
}
#[test]
fn a_style_flag_slot_is_deleted_and_its_text_kept() {
let out = pruned(json!({
"c": ["Sima ", ["félkövér", "b"], ["dőlt és aláhúzott", "iu"]]
}));
assert_eq!(out["c"], json!(["Sima ", ["félkövér"], ["dőlt és aláhúzott"]]), "got {out}");
}
#[test]
fn a_colour_run_loses_its_empty_flag_slot_and_its_colour_object() {
let out = pruned(json!({ "c": [["piros", "", { "tc": "#f00" }]] }));
assert_eq!(out["c"], json!([["piros"]]), "got {out}");
}
#[test]
fn links_wiki_links_and_tags_survive_the_prune() {
let out = pruned(json!({
"c": [
{ "l": "https://pelda.hu", "c": ["hivatkozás", ["kiemelt", "b"]] },
{ "wl": "p1", "wt": "Oldalcím" },
{ "tg": "projekt" }
]
}));
assert_eq!(
out["c"],
json!([
{ "l": "https://pelda.hu", "c": ["hivatkozás", ["kiemelt"]] },
{ "wl": "p1", "wt": "Oldalcím" },
{ "tg": "projekt" }
]),
"got {out}"
);
}
#[test]
fn object_form_table_cells_keep_their_props() {
let out = pruned(json!({
"c": { "type": "tableContent", "rows": [{ "cells": [
{ "pr": { "backgroundColor": "#ff0000" }, "c": ["Alma", ["Szia", "b"]] },
{ "c": ["Körte"] }
] }] }
}));
assert_eq!(
out["c"]["rows"][0]["cells"],
json!([
{ "pr": { "backgroundColor": "#ff0000" }, "c": ["Alma", ["Szia"]] },
{ "c": ["Körte"] }
]),
"got {out}"
);
}
#[test]
fn array_form_table_cells_lose_only_their_style_slots() {
let out = pruned(json!({
"c": { "type": "tableContent", "rows": [{ "cells": [
["Alma", ["Szia", "b"]],
["Körte"]
] }] }
}));
assert_eq!(
out["c"]["rows"][0]["cells"],
json!([["Alma", ["Szia"]], ["Körte"]]),
"got {out}"
);
}
#[test]
fn a_document_with_nothing_to_prune_is_returned_unchanged() {
let page = json!({ "ti": "Bevezetés", "tg": ["munka"], "pp": "gyoker" });
let block = json!({ "p": "page1", "o": 2, "c": ["sima szöveg", { "tg": "projekt" }] });
for doc in [page, block] {
let before = doc.clone();
assert_eq!(pruned(doc), before);
}
}
#[test]
fn an_empty_prune_list_is_a_no_op() {
let mut doc = json!({ "c": [["félkövér", "b"]] });
let before = doc.clone();
assert_eq!(prune_document(&mut doc, &[]), (0, 0));
assert_eq!(doc, before);
}
fn rules(json: &Value) -> IndexRules {
IndexRules::parse(json).expect("rules")
}
#[test]
fn only_the_kinds_that_declare_a_prune_list_are_touched() {
let rules = rules(&json!({
"parts": [
{ "kind": "p", "title": ["ti"] },
{ "kind": "b", "attachTo": { "kind": "p", "field": "p" },
"prune": ["$..c[0:][1:]"], "body": ["c"] }
]
}));
let mut docs: Vec<(Box<str>, Value)> = vec![
("p/page1".into(), json!({ "ti": "Cím", "c": [["Cím", "b"]] })),
("b/b1".into(), json!({ "p": "page1", "c": [["szöveg", "b"]] })),
];
prune_docs(&rules, &mut docs, TnId(1), "f1~doc");
assert_eq!(docs[0].1["c"], json!([["Cím", "b"]]), "the page rule declares no prune");
assert_eq!(docs[1].1["c"], json!([["szöveg"]]));
}
#[test]
fn a_kind_with_both_an_emitting_and_an_attaching_rule_gets_both_prune_lists() {
let rules = rules(&json!({
"parts": [
{ "kind": "p", "title": ["ti"] },
{ "kind": "b", "title": ["ti"], "prune": ["$..c[0:][1:]"] },
{ "kind": "b", "attachTo": { "kind": "p", "field": "p" },
"prune": ["$..zaj"], "body": ["c"] }
]
}));
let mut docs: Vec<(Box<str>, Value)> = vec![
("p/page1".into(), json!({ "ti": "Cím" })),
("b/b1".into(), json!({ "p": "page1", "c": [["szöveg", "b"]], "zaj": "törlendő" })),
];
prune_docs(&rules, &mut docs, TnId(1), "f1~doc");
assert_eq!(docs[1].1["c"], json!([["szöveg"]]));
assert!(docs[1].1.get("zaj").is_none(), "got {}", docs[1].1);
}
}