1use std::collections::HashMap;
8
9use crate::error::Result;
10use crate::records::sch::{PinElectricalType, SchRecord};
11use crate::tree::{RecordId, RecordTree};
12
13use super::common::{FilterValue as CommonFilterValue, compare_filter};
14use super::pattern::Pattern;
15use super::selector::{
16 Combinator, FilterOperator, FilterValue, NetConnectedTarget, PropertyFilter, PseudoSelector,
17 RecordMatcher, RecordType, Selector, SelectorChain, SelectorSegment,
18};
19
20#[derive(Debug, Clone)]
22pub struct QueryMatch {
23 pub id: RecordId,
25 pub depth: usize,
27}
28
29impl QueryMatch {
30 pub fn new(id: RecordId, depth: usize) -> Self {
32 Self { id, depth }
33 }
34}
35
36pub struct SelectorEngine<'a> {
41 tree: &'a RecordTree<SchRecord>,
42 designator_cache: HashMap<RecordId, String>,
44 value_cache: HashMap<RecordId, String>,
46 part_number_cache: HashMap<RecordId, String>,
48 sheet_name_cache: HashMap<RecordId, String>,
50}
51
52impl<'a> SelectorEngine<'a> {
53 pub fn new(tree: &'a RecordTree<SchRecord>) -> Self {
55 let mut engine = Self {
56 tree,
57 designator_cache: HashMap::new(),
58 value_cache: HashMap::new(),
59 part_number_cache: HashMap::new(),
60 sheet_name_cache: HashMap::new(),
61 };
62 engine.build_caches();
63 engine
64 }
65
66 fn build_caches(&mut self) {
68 for (id, record) in self.tree.iter() {
69 match record {
70 SchRecord::Component(comp) => {
71 if !comp.lib_reference.is_empty() {
73 self.part_number_cache
74 .insert(id, comp.lib_reference.clone());
75 }
76 }
77 SchRecord::Designator(des) => {
78 if let Some(parent_id) = self.tree.parent_id(id) {
80 self.designator_cache
81 .insert(parent_id, des.text().to_string());
82 }
83 }
84 SchRecord::Parameter(param) => {
85 if param.name.eq_ignore_ascii_case("value") {
87 if let Some(parent_id) = self.tree.parent_id(id) {
88 self.value_cache
89 .insert(parent_id, param.value().to_string());
90 }
91 }
92 }
93 _ => {}
94 }
95 }
96 }
97
98 pub fn set_sheet_name(&mut self, sheet_id: RecordId, name: String) {
102 self.sheet_name_cache.insert(sheet_id, name);
103 }
104
105 pub fn set_document_name(&mut self, name: String) {
109 for (id, record) in self.tree.iter() {
110 if matches!(record, SchRecord::SheetHeader(_)) {
111 self.sheet_name_cache.insert(id, name.clone());
112 }
113 }
114 }
115
116 pub fn query(&self, selector: &Selector) -> Vec<QueryMatch> {
118 if selector.is_empty() {
119 return vec![];
120 }
121
122 let mut results = Vec::new();
123
124 for chain in &selector.alternatives {
126 let chain_results = self.execute_chain(chain);
127 for result in chain_results {
128 if !results.iter().any(|r: &QueryMatch| r.id == result.id) {
130 results.push(result);
131 }
132 }
133 }
134
135 results
136 }
137
138 fn execute_chain(&self, chain: &SelectorChain) -> Vec<QueryMatch> {
140 if chain.segments.is_empty() {
141 return vec![];
142 }
143
144 let mut current_matches: Vec<RecordId> = self.find_matching_records(&chain.segments[0]);
146
147 for segment in chain.segments.iter().skip(1) {
149 let combinator = chain.segments[chain
150 .segments
151 .iter()
152 .position(|s| std::ptr::eq(s, segment))
153 .map(|i| i.saturating_sub(1))
154 .unwrap_or(0)]
155 .combinator
156 .unwrap_or(Combinator::Descendant);
157
158 current_matches = self.apply_combinator(current_matches, segment, combinator);
159
160 if current_matches.is_empty() {
161 break;
162 }
163 }
164
165 current_matches
166 .into_iter()
167 .map(|id| QueryMatch::new(id, self.tree.depth(id)))
168 .collect()
169 }
170
171 fn find_matching_records(&self, segment: &SelectorSegment) -> Vec<RecordId> {
173 self.tree
174 .iter()
175 .filter(|(id, record)| self.matches_segment(*id, record, segment))
176 .map(|(id, _)| id)
177 .collect()
178 }
179
180 fn apply_combinator(
182 &self,
183 from_ids: Vec<RecordId>,
184 segment: &SelectorSegment,
185 combinator: Combinator,
186 ) -> Vec<RecordId> {
187 let mut results = Vec::new();
188
189 for from_id in from_ids {
190 match combinator {
191 Combinator::DirectChild => {
192 for (child_id, child_record) in self.tree.children(from_id) {
194 if self.matches_segment(child_id, child_record, segment) {
195 results.push(child_id);
196 }
197 }
198 }
199 Combinator::Descendant => {
200 for (desc_id, desc_record) in self.tree.descendants(from_id) {
202 if self.matches_segment(desc_id, desc_record, segment) {
203 results.push(desc_id);
204 }
205 }
206 }
207 }
208 }
209
210 results
211 }
212
213 fn matches_segment(&self, id: RecordId, record: &SchRecord, segment: &SelectorSegment) -> bool {
215 if !self.matches_record_matcher(id, record, &segment.matcher) {
217 return false;
218 }
219
220 for filter in &segment.filters {
222 if !self.matches_property_filter(id, record, filter) {
223 return false;
224 }
225 }
226
227 for pseudo in &segment.pseudo {
229 if !self.matches_pseudo_selector(id, record, pseudo) {
230 return false;
231 }
232 }
233
234 true
235 }
236
237 fn matches_record_matcher(
239 &self,
240 id: RecordId,
241 record: &SchRecord,
242 matcher: &RecordMatcher,
243 ) -> bool {
244 match matcher {
245 RecordMatcher::Any => true,
246
247 RecordMatcher::Type(record_type) => self.record_has_type(record, *record_type),
248
249 RecordMatcher::Designator(pattern) => {
250 if let SchRecord::Component(_) = record {
252 if let Some(designator) = self.designator_cache.get(&id) {
253 return pattern.matches(designator);
254 }
255 }
256 false
257 }
258
259 RecordMatcher::PartNumber(pattern) => {
260 if let SchRecord::Component(comp) = record {
262 return pattern.matches(&comp.lib_reference);
263 }
264 false
265 }
266
267 RecordMatcher::Net(pattern) => {
268 match record {
270 SchRecord::NetLabel(nl) => pattern.matches(&nl.label.text),
271 SchRecord::PowerObject(po) => pattern.matches(&po.text),
272 _ => false,
273 }
274 }
275
276 RecordMatcher::Value(pattern) => {
277 if let SchRecord::Component(_) = record {
279 if let Some(value) = self.value_cache.get(&id) {
280 return pattern.matches(value);
281 }
282 }
283 false
284 }
285
286 RecordMatcher::Sheet(pattern) => {
287 if let SchRecord::SheetHeader(_header) = record {
289 if let Some(title) = self.sheet_name_cache.get(&id) {
291 return pattern.matches(title);
292 }
293 return pattern.matches("");
295 }
296 false
297 }
298
299 RecordMatcher::Pin { component, pin } => {
300 if let SchRecord::Pin(pin_record) = record {
302 if let Some(parent_id) = self.tree.parent_id(id) {
304 if let Some(SchRecord::Component(_)) = self.tree.get(parent_id) {
306 let comp_matches =
308 if let Some(designator) = self.designator_cache.get(&parent_id) {
309 component.matches(designator)
310 } else {
311 component.as_str() == "*"
313 };
314
315 if comp_matches {
316 return pin.matches(&pin_record.designator)
318 || pin.matches(&pin_record.name);
319 }
320 }
321 }
322 }
323 false
324 }
325
326 RecordMatcher::NetConnected { net, target } => {
327 self.matches_net_connected(id, record, net, *target)
330 }
331
332 RecordMatcher::DesignatorWithValue { designator, value } => {
333 if let SchRecord::Component(_) = record {
335 let des_matches = self
336 .designator_cache
337 .get(&id)
338 .map(|d| designator.matches(d))
339 .unwrap_or(false);
340
341 let val_matches = self
342 .value_cache
343 .get(&id)
344 .map(|v| value.matches(v))
345 .unwrap_or(false);
346
347 return des_matches && val_matches;
348 }
349 false
350 }
351 }
352 }
353
354 fn record_has_type(&self, record: &SchRecord, record_type: RecordType) -> bool {
356 matches!(
357 (record, record_type),
358 (SchRecord::Component(_), RecordType::Component)
359 | (SchRecord::Pin(_), RecordType::Pin)
360 | (SchRecord::Wire(_), RecordType::Wire)
361 | (SchRecord::NetLabel(_), RecordType::NetLabel)
362 | (SchRecord::Port(_), RecordType::Port)
363 | (SchRecord::PowerObject(_), RecordType::PowerObject)
364 | (SchRecord::Junction(_), RecordType::Junction)
365 | (SchRecord::Label(_), RecordType::Label)
366 | (SchRecord::Rectangle(_), RecordType::Rectangle)
367 | (SchRecord::Line(_), RecordType::Line)
368 | (SchRecord::Arc(_), RecordType::Arc)
369 | (SchRecord::Ellipse(_), RecordType::Ellipse)
370 | (SchRecord::Polygon(_), RecordType::Polygon)
371 | (SchRecord::Polyline(_), RecordType::Polyline)
372 | (SchRecord::Bezier(_), RecordType::Bezier)
373 | (SchRecord::Image(_), RecordType::Image)
374 | (SchRecord::Parameter(_), RecordType::Parameter)
375 | (SchRecord::SheetHeader(_), RecordType::Sheet)
376 | (SchRecord::Symbol(_), RecordType::Symbol)
377 | (SchRecord::Designator(_), RecordType::Designator)
378 | (SchRecord::TextFrame(_), RecordType::TextFrame)
379 | (SchRecord::TextFrameVariant(_), RecordType::TextFrame)
380 )
381 }
382
383 fn matches_net_connected(
385 &self,
386 id: RecordId,
387 record: &SchRecord,
388 net_pattern: &Pattern,
389 target: NetConnectedTarget,
390 ) -> bool {
391 let net_names: Vec<String> = self
393 .tree
394 .iter()
395 .filter_map(|(_, r)| match r {
396 SchRecord::NetLabel(nl) if net_pattern.matches(&nl.label.text) => {
397 Some(nl.label.text.clone())
398 }
399 SchRecord::PowerObject(po) if net_pattern.matches(&po.text) => {
400 Some(po.text.clone())
401 }
402 _ => None,
403 })
404 .collect();
405
406 if net_names.is_empty() {
407 return false;
408 }
409
410 match (target, record) {
414 (NetConnectedTarget::Pins, SchRecord::Pin(pin)) => {
415 net_names
417 .iter()
418 .any(|n| n.eq_ignore_ascii_case(&pin.hidden_net_name))
419 }
420 (NetConnectedTarget::Components, SchRecord::Component(_)) => {
421 for (_child_id, child) in self.tree.children(id) {
423 if let SchRecord::Pin(pin) = child {
424 if net_names
425 .iter()
426 .any(|n| n.eq_ignore_ascii_case(&pin.hidden_net_name))
427 {
428 return true;
429 }
430 }
431 }
432 false
433 }
434 _ => false,
435 }
436 }
437
438 fn matches_property_filter(
440 &self,
441 id: RecordId,
442 record: &SchRecord,
443 filter: &PropertyFilter,
444 ) -> bool {
445 let prop_value = self.get_property(id, record, &filter.property);
447
448 match prop_value {
449 Some(value) => self.compare_filter_value(&value, &filter.operator, &filter.value),
450 None => false,
451 }
452 }
453
454 fn get_property(&self, id: RecordId, record: &SchRecord, property: &str) -> Option<String> {
456 let prop_lower = property.to_lowercase();
457
458 match prop_lower.as_str() {
460 "x" | "location.x" => return self.get_location_x(record),
461 "y" | "location.y" => return self.get_location_y(record),
462 "owner_index" | "ownerindex" => return Some(self.get_owner_index(record).to_string()),
463 "hidden" => return Some(self.is_hidden(record).to_string()),
464 _ => {}
465 }
466
467 match record {
469 SchRecord::Component(comp) => match prop_lower.as_str() {
470 "lib_reference" | "libreference" => Some(comp.lib_reference.clone()),
471 "description" | "componentdescription" => Some(comp.component_description.clone()),
472 "unique_id" | "uniqueid" => Some(comp.unique_id.clone()),
473 "part_count" | "partcount" => Some(comp.part_count.to_string()),
474 "designator" => self.designator_cache.get(&id).cloned(),
475 "value" => self.value_cache.get(&id).cloned(),
476 _ => None,
477 },
478 SchRecord::Pin(pin) => match prop_lower.as_str() {
479 "designator" => Some(pin.designator.clone()),
480 "name" => Some(pin.name.clone()),
481 "electrical" => Some(format!("{:?}", pin.electrical)),
482 "length" | "pin_length" | "pinlength" => Some(pin.pin_length.to_string()),
483 "description" => Some(pin.description.clone()),
484 _ => None,
485 },
486 SchRecord::NetLabel(nl) => match prop_lower.as_str() {
487 "text" | "net" | "name" => Some(nl.label.text.clone()),
488 _ => None,
489 },
490 SchRecord::PowerObject(po) => match prop_lower.as_str() {
491 "text" | "net" | "name" => Some(po.text.clone()),
492 "style" => Some(format!("{:?}", po.style)),
493 _ => None,
494 },
495 SchRecord::Parameter(param) => match prop_lower.as_str() {
496 "name" => Some(param.name.clone()),
497 "value" | "text" => Some(param.value().to_string()),
498 _ => None,
499 },
500 SchRecord::Label(label) => match prop_lower.as_str() {
501 "text" => Some(label.text.clone()),
502 _ => None,
503 },
504 SchRecord::Port(port) => match prop_lower.as_str() {
505 "name" => Some(port.name.clone()),
506 "io_type" | "iotype" => Some(format!("{:?}", port.io_type)),
507 "style" => Some(format!("{:?}", port.style)),
508 _ => None,
509 },
510 SchRecord::SheetHeader(_header) => match prop_lower.as_str() {
511 "name" | "title" => self.sheet_name_cache.get(&id).cloned(),
512 _ => None,
513 },
514 _ => None,
515 }
516 }
517
518 fn get_location_x(&self, record: &SchRecord) -> Option<String> {
520 match record {
521 SchRecord::Component(c) => Some(c.graphical.location_x.to_string()),
522 SchRecord::Pin(p) => Some(p.graphical.location_x.to_string()),
523 SchRecord::Symbol(s) => Some(s.graphical.location_x.to_string()),
524 SchRecord::Label(l) => Some(l.graphical.location_x.to_string()),
525 SchRecord::NetLabel(nl) => Some(nl.label.graphical.location_x.to_string()),
526 SchRecord::PowerObject(po) => Some(po.graphical.location_x.to_string()),
527 SchRecord::Port(p) => Some(p.graphical.location_x.to_string()),
528 SchRecord::Junction(j) => Some(j.graphical.location_x.to_string()),
529 SchRecord::Rectangle(r) => Some(r.graphical.location_x.to_string()),
530 SchRecord::Line(l) => Some(l.graphical.location_x.to_string()),
531 SchRecord::Arc(a) => Some(a.graphical.location_x.to_string()),
532 SchRecord::Ellipse(e) => Some(e.graphical.location_x.to_string()),
533 _ => None,
534 }
535 }
536
537 fn get_location_y(&self, record: &SchRecord) -> Option<String> {
539 match record {
540 SchRecord::Component(c) => Some(c.graphical.location_y.to_string()),
541 SchRecord::Pin(p) => Some(p.graphical.location_y.to_string()),
542 SchRecord::Symbol(s) => Some(s.graphical.location_y.to_string()),
543 SchRecord::Label(l) => Some(l.graphical.location_y.to_string()),
544 SchRecord::NetLabel(nl) => Some(nl.label.graphical.location_y.to_string()),
545 SchRecord::PowerObject(po) => Some(po.graphical.location_y.to_string()),
546 SchRecord::Port(p) => Some(p.graphical.location_y.to_string()),
547 SchRecord::Junction(j) => Some(j.graphical.location_y.to_string()),
548 SchRecord::Rectangle(r) => Some(r.graphical.location_y.to_string()),
549 SchRecord::Line(l) => Some(l.graphical.location_y.to_string()),
550 SchRecord::Arc(a) => Some(a.graphical.location_y.to_string()),
551 SchRecord::Ellipse(e) => Some(e.graphical.location_y.to_string()),
552 _ => None,
553 }
554 }
555
556 fn get_owner_index(&self, record: &SchRecord) -> i32 {
558 match record {
559 SchRecord::Component(c) => c.graphical.base.owner_index,
560 SchRecord::Pin(p) => p.graphical.base.owner_index,
561 SchRecord::Symbol(s) => s.graphical.base.owner_index,
562 SchRecord::Label(l) => l.graphical.base.owner_index,
563 SchRecord::NetLabel(nl) => nl.label.graphical.base.owner_index,
564 SchRecord::PowerObject(po) => po.graphical.base.owner_index,
565 SchRecord::Port(p) => p.graphical.base.owner_index,
566 SchRecord::Junction(j) => j.graphical.base.owner_index,
567 _ => -1,
568 }
569 }
570
571 fn is_hidden(&self, record: &SchRecord) -> bool {
573 match record {
574 SchRecord::Pin(p) => p.is_hidden(),
575 SchRecord::Label(l) => l.is_hidden,
576 _ => false,
577 }
578 }
579
580 fn compare_filter_value(
584 &self,
585 actual: &str,
586 operator: &FilterOperator,
587 expected: &FilterValue,
588 ) -> bool {
589 let filter_op = operator.to_filter_op();
591 let common_value = match expected {
592 FilterValue::String(s) => CommonFilterValue::String(s.clone()),
593 FilterValue::Number(n) => CommonFilterValue::Number(*n),
594 FilterValue::Bool(b) => CommonFilterValue::Bool(*b),
595 FilterValue::Pattern(p) => CommonFilterValue::Pattern(p.clone()),
596 };
597
598 if matches!(operator, FilterOperator::Wildcard) {
600 if let FilterValue::Pattern(p) = expected {
601 return p.matches(actual);
602 }
603 if let FilterValue::String(s) = expected {
604 return Pattern::new(s).map(|p| p.matches(actual)).unwrap_or(false);
605 }
606 }
607
608 compare_filter(Some(actual), filter_op, &common_value, true)
609 }
610
611 fn matches_pseudo_selector(
613 &self,
614 id: RecordId,
615 record: &SchRecord,
616 pseudo: &PseudoSelector,
617 ) -> bool {
618 match pseudo {
619 PseudoSelector::Root => self.tree.is_root(id),
620 PseudoSelector::Empty => !self.tree.has_children(id),
621 PseudoSelector::FirstChild => self.is_first_child(id),
622 PseudoSelector::LastChild => self.is_last_child(id),
623 PseudoSelector::NthChild(n) => self.is_nth_child(id, *n),
624 PseudoSelector::OnlyChild => self.is_only_child(id),
625
626 PseudoSelector::Connected => self.is_pin_connected(record),
628 PseudoSelector::Unconnected => !self.is_pin_connected(record),
629 PseudoSelector::Input => self.pin_has_electrical(record, PinElectricalType::Input),
630 PseudoSelector::Output => self.pin_has_electrical(record, PinElectricalType::Output),
631 PseudoSelector::Bidirectional => {
632 self.pin_has_electrical(record, PinElectricalType::InputOutput)
633 }
634 PseudoSelector::Power => self.pin_has_electrical(record, PinElectricalType::Power),
635 PseudoSelector::Passive => self.pin_has_electrical(record, PinElectricalType::Passive),
636 PseudoSelector::OpenCollector => {
637 self.pin_has_electrical(record, PinElectricalType::OpenCollector)
638 }
639 PseudoSelector::OpenEmitter => {
640 self.pin_has_electrical(record, PinElectricalType::OpenEmitter)
641 }
642 PseudoSelector::HiZ => self.pin_has_electrical(record, PinElectricalType::HiZ),
643
644 PseudoSelector::Visible => !self.is_hidden(record),
646 PseudoSelector::Hidden => self.is_hidden(record),
647 PseudoSelector::Selected => false, PseudoSelector::Not(selector) => {
651 let matches = self.query(selector);
652 !matches.iter().any(|m| m.id == id)
653 }
654 PseudoSelector::Has(selector) => {
655 for (desc_id, _) in self.tree.descendants(id) {
657 let desc_matches = self.query(selector);
658 if desc_matches.iter().any(|m| m.id == desc_id) {
659 return true;
660 }
661 }
662 false
663 }
664 PseudoSelector::Is(selector) => {
665 let matches = self.query(selector);
666 matches.iter().any(|m| m.id == id)
667 }
668 }
669 }
670
671 fn is_first_child(&self, id: RecordId) -> bool {
673 if let Some(parent_id) = self.tree.parent_id(id) {
674 if let Some((first_id, _)) = self.tree.children(parent_id).next() {
675 return first_id == id;
676 }
677 }
678 false
679 }
680
681 fn is_last_child(&self, id: RecordId) -> bool {
683 if let Some(parent_id) = self.tree.parent_id(id) {
684 if let Some((last_id, _)) = self.tree.children(parent_id).last() {
685 return last_id == id;
686 }
687 }
688 false
689 }
690
691 fn is_nth_child(&self, id: RecordId, n: usize) -> bool {
693 if let Some(parent_id) = self.tree.parent_id(id) {
694 if let Some((nth_id, _)) = self.tree.children(parent_id).nth(n.saturating_sub(1)) {
695 return nth_id == id;
696 }
697 }
698 false
699 }
700
701 fn is_only_child(&self, id: RecordId) -> bool {
703 if let Some(parent_id) = self.tree.parent_id(id) {
704 return self.tree.child_count(parent_id) == 1;
705 }
706 false
707 }
708
709 fn is_pin_connected(&self, record: &SchRecord) -> bool {
711 if let SchRecord::Pin(pin) = record {
712 return !pin.hidden_net_name.is_empty();
714 }
715 false
716 }
717
718 fn pin_has_electrical(&self, record: &SchRecord, electrical: PinElectricalType) -> bool {
720 if let SchRecord::Pin(pin) = record {
721 return pin.electrical == electrical;
722 }
723 false
724 }
725
726 pub fn get_component_designator(&self, id: RecordId) -> Option<&str> {
728 self.designator_cache.get(&id).map(|s| s.as_str())
729 }
730
731 pub fn get_component_value(&self, id: RecordId) -> Option<&str> {
733 self.value_cache.get(&id).map(|s| s.as_str())
734 }
735
736 pub fn get_component_part_number(&self, id: RecordId) -> Option<&str> {
738 self.part_number_cache.get(&id).map(|s| s.as_str())
739 }
740}
741
742pub fn query_records(tree: &RecordTree<SchRecord>, selector_str: &str) -> Result<Vec<QueryMatch>> {
744 query_records_with_doc_name(tree, selector_str, None)
745}
746
747pub fn query_records_with_doc_name(
750 tree: &RecordTree<SchRecord>,
751 selector_str: &str,
752 document_name: Option<&str>,
753) -> Result<Vec<QueryMatch>> {
754 use super::parser::SelectorParser;
755
756 let parser = SelectorParser::new(selector_str);
757 let selector = parser.parse()?;
758 let mut engine = SelectorEngine::new(tree);
759
760 if let Some(name) = document_name {
762 engine.set_document_name(name.to_string());
763 }
764
765 Ok(engine.query(&selector))
766}
767
768#[cfg(test)]
769mod tests {
770 use super::*;
771 use crate::records::sch::{SchComponent, SchDesignator, SchNetLabel, SchParameter, SchPin};
772
773 fn create_test_tree() -> RecordTree<SchRecord> {
775 let mut records = Vec::new();
776
777 let mut comp = SchComponent::default();
779 comp.lib_reference = "LM358".to_string();
780 comp.graphical.base.owner_index = -1;
781 records.push(SchRecord::Component(comp));
782
783 let mut des = SchDesignator::default();
785 des.param.label.text = "U1".to_string();
786 des.param.label.graphical.base.owner_index = 0;
787 records.push(SchRecord::Designator(des));
788
789 let mut param = SchParameter::default();
791 param.name = "Value".to_string();
792 param.label.text = "OpAmp".to_string();
793 param.label.graphical.base.owner_index = 0;
794 records.push(SchRecord::Parameter(param));
795
796 let mut pin1 = SchPin::default();
798 pin1.designator = "1".to_string();
799 pin1.name = "IN+".to_string();
800 pin1.electrical = PinElectricalType::Input;
801 pin1.graphical.base.owner_index = 0;
802 records.push(SchRecord::Pin(pin1));
803
804 let mut pin2 = SchPin::default();
806 pin2.designator = "2".to_string();
807 pin2.name = "OUT".to_string();
808 pin2.electrical = PinElectricalType::Output;
809 pin2.graphical.base.owner_index = 0;
810 records.push(SchRecord::Pin(pin2));
811
812 let mut comp2 = SchComponent::default();
814 comp2.lib_reference = "Resistor".to_string();
815 comp2.graphical.base.owner_index = -1;
816 records.push(SchRecord::Component(comp2));
817
818 let mut des2 = SchDesignator::default();
820 des2.param.label.text = "R1".to_string();
821 des2.param.label.graphical.base.owner_index = 5;
822 records.push(SchRecord::Designator(des2));
823
824 let mut param2 = SchParameter::default();
826 param2.name = "Value".to_string();
827 param2.label.text = "10K".to_string();
828 param2.label.graphical.base.owner_index = 5;
829 records.push(SchRecord::Parameter(param2));
830
831 let mut netlabel = SchNetLabel::default();
833 netlabel.label.text = "VCC".to_string();
834 netlabel.label.graphical.base.owner_index = -1;
835 records.push(SchRecord::NetLabel(netlabel));
836
837 RecordTree::from_records(records)
838 }
839
840 #[test]
841 fn test_query_by_designator() {
842 let tree = create_test_tree();
843 let results = query_records(&tree, "U1").unwrap();
844
845 assert_eq!(results.len(), 1);
846 if let Some(SchRecord::Component(comp)) = tree.get(results[0].id) {
847 assert_eq!(comp.lib_reference, "LM358");
848 } else {
849 panic!("Expected component");
850 }
851 }
852
853 #[test]
854 fn test_query_by_designator_pattern() {
855 let tree = create_test_tree();
856 let results = query_records(&tree, "R*").unwrap();
857
858 assert_eq!(results.len(), 1);
859 if let Some(SchRecord::Component(comp)) = tree.get(results[0].id) {
860 assert_eq!(comp.lib_reference, "Resistor");
861 } else {
862 panic!("Expected component");
863 }
864 }
865
866 #[test]
867 fn test_query_by_part_number() {
868 let tree = create_test_tree();
869 let results = query_records(&tree, "$LM358").unwrap();
870
871 assert_eq!(results.len(), 1);
872 }
873
874 #[test]
875 fn test_query_by_value() {
876 let tree = create_test_tree();
877 let results = query_records(&tree, "@10K").unwrap();
878
879 assert_eq!(results.len(), 1);
880 }
881
882 #[test]
883 fn test_query_by_net() {
884 let tree = create_test_tree();
885 let results = query_records(&tree, "~VCC").unwrap();
886
887 assert_eq!(results.len(), 1);
888 if let Some(SchRecord::NetLabel(nl)) = tree.get(results[0].id) {
889 assert_eq!(nl.label.text, "VCC");
890 } else {
891 panic!("Expected net label");
892 }
893 }
894
895 #[test]
896 fn test_query_pin() {
897 let tree = create_test_tree();
898 let results = query_records(&tree, "U1:1").unwrap();
899
900 assert_eq!(results.len(), 1);
901 if let Some(SchRecord::Pin(pin)) = tree.get(results[0].id) {
902 assert_eq!(pin.designator, "1");
903 } else {
904 panic!("Expected pin");
905 }
906 }
907
908 #[test]
909 fn test_query_pin_by_name() {
910 let tree = create_test_tree();
911 let results = query_records(&tree, "U1:OUT").unwrap();
912
913 assert_eq!(results.len(), 1);
914 if let Some(SchRecord::Pin(pin)) = tree.get(results[0].id) {
915 assert_eq!(pin.name, "OUT");
916 } else {
917 panic!("Expected pin");
918 }
919 }
920
921 #[test]
922 fn test_query_by_type() {
923 let tree = create_test_tree();
924 let results = query_records(&tree, "pin").unwrap();
925
926 assert_eq!(results.len(), 2); }
928
929 #[test]
930 fn test_query_alternatives() {
931 let tree = create_test_tree();
932 let results = query_records(&tree, "U1, R1").unwrap();
933
934 assert_eq!(results.len(), 2); }
936
937 #[test]
938 fn test_query_with_pseudo_selector() {
939 let tree = create_test_tree();
940 let results = query_records(&tree, "pin:input").unwrap();
941
942 assert_eq!(results.len(), 1);
943 if let Some(SchRecord::Pin(pin)) = tree.get(results[0].id) {
944 assert_eq!(pin.electrical, PinElectricalType::Input);
945 } else {
946 panic!("Expected pin");
947 }
948 }
949
950 #[test]
951 fn test_query_sheet_by_name() {
952 use crate::records::sch::SchSheetHeader;
953
954 let mut records = Vec::new();
955
956 let mut sheet = SchSheetHeader::default();
958 sheet.base.owner_index = -1;
959 records.push(SchRecord::SheetHeader(sheet));
960
961 let tree = RecordTree::from_records(records);
962
963 let results =
965 query_records_with_doc_name(&tree, "sheet[name='PowerSupply']", Some("PowerSupply"))
966 .unwrap();
967 assert_eq!(results.len(), 1);
968
969 let results =
971 query_records_with_doc_name(&tree, "sheet[name~='Power*']", Some("PowerSupply"))
972 .unwrap();
973 assert_eq!(results.len(), 1);
974
975 let results =
977 query_records_with_doc_name(&tree, "sheet[name='Other']", Some("PowerSupply")).unwrap();
978 assert_eq!(results.len(), 0);
979
980 let results = query_records(&tree, "sheet").unwrap();
982 assert_eq!(results.len(), 1);
983
984 let results =
986 query_records_with_doc_name(&tree, "#PowerSupply", Some("PowerSupply")).unwrap();
987 assert_eq!(results.len(), 1);
988
989 let results = query_records_with_doc_name(&tree, "#Power*", Some("PowerSupply")).unwrap();
991 assert_eq!(results.len(), 1);
992
993 let results = query_records_with_doc_name(&tree, "#Other", Some("PowerSupply")).unwrap();
995 assert_eq!(results.len(), 0);
996
997 let results = query_records(&tree, "sheet[name='PowerSupply']").unwrap();
999 assert_eq!(
1000 results.len(),
1001 0,
1002 "Should not match when document name not set"
1003 );
1004
1005 let results = query_records(&tree, "#*").unwrap();
1007 assert_eq!(
1008 results.len(),
1009 1,
1010 "Wildcard should match sheets without name"
1011 );
1012 }
1013}