dsntk_recognizer/
builder.rs

1//! # Decision table builder
2
3use crate::errors::*;
4use crate::recognizer::Recognizer;
5use crate::utils::get_allowed_and_default_output_values;
6use crate::{AnnotationClause, AnnotationEntry, DecisionRule, DecisionTable, HitPolicy, InputClause, InputEntry, OutputClause, OutputEntry};
7use dsntk_common::Result;
8
9/// Combines sizes of individual components of the decision table.
10struct Size {
11  input_clauses_count: usize,
12  input_values_count: usize,
13  output_clauses_count: usize,
14  output_components_count: usize,
15  output_values_count: usize,
16  annotation_clauses_count: usize,
17  rule_count: usize,
18}
19
20/// Validates the size of the individual components of the decision table in relation to each other.
21fn validate_size(recognizer: &Recognizer) -> Result<Size> {
22  // decision table may have zero or more input clauses
23  let input_clauses_count = recognizer.input_clause_count;
24  // number of input expressions must be equal to the number of input clauses
25  let input_expression_count = recognizer.input_expressions.len();
26  if input_expression_count != input_clauses_count {
27    return Err(err_invalid_size(&format!(
28      "number of input expressions ({input_expression_count}) must be equal to the number of input clauses ({input_clauses_count})"
29    )));
30  }
31  // when input values are present, then the number of input values must be equal to the number input expressions
32  let input_values_count = recognizer.allowed_input_values.len();
33  if input_values_count > 0 && input_values_count != input_clauses_count {
34    return Err(err_invalid_size(&format!(
35      "number of input values ({input_values_count}) must be equal to the number of input clauses ({input_clauses_count})"
36    )));
37  }
38  // decision table must have minimum one output clause
39  let output_clauses_count = recognizer.output_clause_count;
40  if output_clauses_count == 0 {
41    return Err(err_invalid_size("decision table must have minimum one output clause"));
42  }
43  // the number of the output components must be equal to the number of output clauses when more than one output clause is present
44  let output_components_count = recognizer.output_components.len();
45  if output_clauses_count > 1 {
46    if output_components_count != output_clauses_count {
47      return Err(err_invalid_size(&format!(
48        "number of output components ({output_components_count}) must be equal to the number of output clauses ({output_clauses_count})"
49      )));
50    }
51  } else if output_components_count != 0 {
52    return Err(err_invalid_size("number of output components must be zero"));
53  }
54  // when output values are present, then the number of output values must be equal to the number of output clauses
55  let output_values_count = recognizer.allowed_output_values.len();
56  if output_values_count > 0 && output_values_count != output_clauses_count {
57    return Err(err_invalid_size(&format!(
58      "number of output values ({output_values_count}) must be equal to the number of output clauses ({output_clauses_count})"
59    )));
60  }
61  // decision table must contain minimum one rule
62  let rule_count = recognizer.rule_count;
63  if rule_count == 0 {
64    return Err(err_invalid_size("decision table must contain minimum one rule"));
65  }
66  // number of rows of input entries must be equal to the number of rules
67  let input_entries_row_count = recognizer.input_entries.len();
68  if input_entries_row_count != rule_count {
69    return Err(err_invalid_size(&format!(
70      "number of input entries ({input_entries_row_count}) must be equal to the number of rules ({rule_count})",
71    )));
72  }
73  // number of input entries in each row must be equal to the number of input clauses
74  for (row_index, row) in recognizer.input_entries.iter().enumerate() {
75    if row.len() != input_clauses_count {
76      return Err(err_invalid_size(&format!(
77        "number of input entries ({}) must be equal to the number of input clauses ({input_clauses_count}) in row {row_index}",
78        row.len(),
79      )));
80    }
81  }
82  // number of rows of output entries must be equal to the number of rules
83  let output_entries_row_count = recognizer.output_entries.len();
84  if output_entries_row_count != rule_count {
85    return Err(err_invalid_size(&format!(
86      "number of output entries ({output_entries_row_count}) must be equal to the number of rules ({rule_count})"
87    )));
88  }
89  // number of output entries in each row must be equal to the number of output clauses
90  for (row_index, row) in recognizer.output_entries.iter().enumerate() {
91    if row.len() != output_clauses_count {
92      return Err(err_invalid_size(&format!(
93        "number of output entries ({}) must be equal to the number of output clauses ({output_clauses_count}) in row {row_index}",
94        row.len()
95      )));
96    }
97  }
98  // decision table may contain some annotations
99  let annotation_clauses_count = recognizer.annotation_clause_count;
100  if annotation_clauses_count > 0 {
101    // number of rows of annotation entries must be equal to the number of rules
102    let annotation_entries_row_count = recognizer.annotation_entries.len();
103    if annotation_entries_row_count != rule_count {
104      return Err(err_invalid_size(&format!(
105        "number of annotation entries ({annotation_entries_row_count}) must be equal to the number of rules ({rule_count})"
106      )));
107    }
108    // number of annotation entries in each row must be equal to the number of annotation clauses
109    for (row_index, row) in recognizer.annotation_entries.iter().enumerate() {
110      if row.len() != annotation_clauses_count {
111        return Err(err_invalid_size(&format!(
112          "number of annotation entries ({}) must be equal to the number of annotation clauses ({}) in row {}",
113          row.len(),
114          annotation_clauses_count,
115          row_index
116        )));
117      }
118    }
119  }
120  Ok(Size {
121    input_clauses_count,
122    input_values_count,
123    output_clauses_count,
124    output_components_count,
125    output_values_count,
126    annotation_clauses_count,
127    rule_count,
128  })
129}
130
131/// Recognizes a decision table defined as Unicode text.
132pub fn from_unicode(text: &str, trace: bool) -> Result<DecisionTable> {
133  // Recognize the decision table.
134  let recognizer = Recognizer::recognize(text, trace)?;
135
136  // Validate the size of the individual parts of the decision table.
137  let size = validate_size(&recognizer)?;
138
139  // Prepare the information item name.
140  let information_item_name = recognizer.information_item_name.clone();
141
142  // Prepare the hit policy.
143  let hit_policy = recognizer.hit_policy;
144
145  // Prepare the aggregation.
146  let aggregation = if let HitPolicy::Collect(built_in_aggregator) = hit_policy {
147    Some(built_in_aggregator)
148  } else {
149    None
150  };
151
152  // Prepare the preferred orientation.
153  let preferred_orientation = recognizer.orientation;
154
155  // Prepare the output label
156  let output_label = recognizer.output_label.clone();
157
158  // Prepare the input clauses.
159  let mut input_clauses = vec![];
160  for i in 0..size.input_clauses_count {
161    input_clauses.push(InputClause {
162      input_expression: recognizer.input_expressions[i].clone(),
163      allowed_input_values: if size.input_values_count > 0 {
164        recognizer.allowed_input_values[i].clone()
165      } else {
166        None
167      },
168    });
169  }
170
171  // Prepare output clauses.
172  let mut output_clauses = vec![];
173  for i in 0..size.output_clauses_count {
174    let output_component_name = if size.output_components_count > 0 {
175      recognizer.output_components[i].clone()
176    } else {
177      output_label.clone()
178    };
179    let (allowed_output_values, default_output_value) = get_allowed_and_default_output_values(&if size.output_values_count > 0 {
180      recognizer.allowed_output_values[i].clone()
181    } else {
182      None
183    });
184    output_clauses.push(OutputClause {
185      output_component_name,
186      allowed_output_values,
187      default_output_value,
188    });
189  }
190
191  // Prepare annotations.
192  let mut annotations = vec![];
193  for i in 0..recognizer.annotation_clause_count {
194    annotations.push(AnnotationClause {
195      name: recognizer.annotations[i].clone(),
196    });
197  }
198
199  // Prepare rules.
200  let mut rules = vec![];
201  for rule_index in 0..size.rule_count {
202    let mut input_entries = vec![];
203    for column_index in 0..size.input_clauses_count {
204      let input_entry = InputEntry {
205        text: recognizer.input_entries[rule_index][column_index].clone(),
206      };
207      input_entries.push(input_entry);
208    }
209    let mut output_entries = vec![];
210    for column_index in 0..size.output_clauses_count {
211      let output_entry = OutputEntry {
212        text: recognizer.output_entries[rule_index][column_index].clone(),
213      };
214      output_entries.push(output_entry);
215    }
216    let mut annotation_entries = vec![];
217    for column_index in 0..size.annotation_clauses_count {
218      let annotation_entry = AnnotationEntry {
219        text: recognizer.annotation_entries[rule_index][column_index].clone(),
220      };
221      annotation_entries.push(annotation_entry);
222    }
223    rules.push(DecisionRule {
224      input_entries,
225      output_entries,
226      annotation_entries,
227    });
228  }
229
230  // Return the recognized decision table.
231  Ok(DecisionTable::new(
232    information_item_name,
233    input_clauses,
234    output_clauses,
235    annotations,
236    rules,
237    hit_policy,
238    aggregation,
239    preferred_orientation,
240    output_label,
241  ))
242}