use super::model::*;
use super::skip;
pub fn simplify(results: &mut [FileResults]) -> Vec<(String, Vec<skip::SkipEntry>)> {
let mut all_skips = Vec::new();
for fr in results.iter_mut() {
let mut collector = skip::SkipCollector::new();
for shape in &mut fr.shapes {
let class_names: Vec<String> = shape.targets.iter()
.filter(|t| t.kind == "targetClass" || t.kind == "targetNode")
.map(|t| local_name(&t.value))
.filter(|n| !n.is_empty())
.collect();
simplify_shape(shape, &class_names, &mut collector);
}
all_skips.push((fr.file_name.clone(), collector.into_entries()));
}
all_skips
}
fn local_name(iri: &str) -> String {
iri.find(':').map(|i| iri[i + 1..].to_string()).unwrap_or_else(|| iri.to_string())
}
fn simplify_shape(
shape: &mut ShapeInfo,
class_names: &[String],
collector: &mut skip::SkipCollector,
) {
if shape.deactivated {
for prop in &shape.properties {
let path = prop.path.first().map(|s| s.as_str()).unwrap_or("");
for c in &prop.constraints {
push_for_classes(collector, class_names, path, &c.component, &c.name,
"sh:deactivated — switched off by the schema");
}
}
shape.properties.clear();
shape.constraints.clear();
shape.closed = None;
return;
}
shape.properties.retain(|prop| {
if !prop.deactivated {
return true;
}
let path = prop.path.first().map(|s| s.as_str()).unwrap_or("");
for c in &prop.constraints {
push_for_classes(collector, class_names, path, &c.component, &c.name,
"sh:deactivated — switched off by the schema");
}
false
});
for prop in &mut shape.properties {
let path = prop.path.first().map(|s| s.as_str()).unwrap_or("");
prop.constraints = simplify_constraints(
std::mem::take(&mut prop.constraints),
class_names,
path,
collector,
);
}
}
fn simplify_constraints(
constraints: Vec<ConstraintInfo>,
class_names: &[String],
path: &str,
collector: &mut skip::SkipCollector,
) -> Vec<ConstraintInfo> {
let mut out: Vec<ConstraintInfo> = Vec::with_capacity(constraints.len());
for c in constraints {
match c.component.as_str() {
"sh:MinCountConstraintComponent"
| "sh:MaxCountConstraintComponent"
| "sh:ExactCountConstraintComponent"
| "sh:RequiredConstraintComponent" => {
let min = c.payload.get("minCount").and_then(|v| v.as_int()).unwrap_or(0);
let max = c.payload.get("maxCount").and_then(|v| v.as_int());
if min == 0 && max.is_none() && c.component == "sh:MinCountConstraintComponent" {
push_for_classes(collector, class_names, path, &c.component, &c.name,
"MinCount=0 vacuously true");
continue;
}
if min == 0 && max == Some(1) && c.component == "sh:MinCountConstraintComponent" {
push_for_classes(collector, class_names, path, &c.component, &c.name,
"MinCount=0 vacuously true (paired with MaxCount=1)");
continue;
}
out.push(c);
}
"sh:InConstraintComponent" => {
let values = c.payload.get("in").and_then(|v| v.as_list());
if let Some(vals) = values
&& vals.len() == 1 {
let single = vals[0].clone();
let mut payload = std::collections::HashMap::new();
payload.insert("hasValue".to_string(), ShaclValue::Str(single));
if let Some(lits) = c.payload.get(LITERALS) {
payload.insert(LITERALS.to_string(), lits.clone());
}
out.push(ConstraintInfo {
component: "sh:HasValueConstraintComponent".to_string(),
payload,
..c
});
continue;
}
out.push(c);
}
_ => out.push(c),
}
}
out
}
fn push_for_classes(
collector: &mut skip::SkipCollector,
class_names: &[String],
prop: &str,
component: &str,
name: &str,
reason: &str,
) {
for class in class_names {
collector.push(class, prop, component, name, reason);
}
}