fhirsmith 0.9.6 → 0.9.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -326,6 +326,20 @@ class SnomedServices {
326
326
  return 0;
327
327
  }
328
328
 
329
+ // Like getConceptRefSet, but distinguishes "this concept is not a reference
330
+ // set" (returns null) from "it is a reference set that happens to have no
331
+ // members" (returns a numeric membersByRef, which may be 0 or the
332
+ // MAGIC_NO_CHILDREN sentinel). Used by memberOf to reject non-refset operands.
333
+ _findRefSetMembersRef(conceptIndex) {
334
+ for (let i = 0; i < this.refSetIndex.count(); i++) {
335
+ const refSet = this.refSetIndex.getReferenceSet(i);
336
+ if (refSet.definition === conceptIndex) {
337
+ return refSet.membersByRef;
338
+ }
339
+ }
340
+ return null;
341
+ }
342
+
329
343
  // Filter support methods
330
344
  filterEquals(id) {
331
345
  const result = new SnomedFilterContext();
@@ -374,7 +388,7 @@ class SnomedServices {
374
388
  }
375
389
 
376
390
 
377
- filterGeneralizes(id = true) {
391
+ filterGeneralizes(id = true, opContext = null) {
378
392
  const result = new SnomedFilterContext();
379
393
  const conceptResult = this.concepts.findConcept(id);
380
394
 
@@ -386,6 +400,7 @@ class SnomedServices {
386
400
  let parents = this.getConceptParents(conceptResult.index);
387
401
  let isNew = true;
388
402
  while (isNew) {
403
+ if (opContext) opContext.deadCheck('ecl:filterGeneralizes');
389
404
  isNew = false;
390
405
  let np = [];
391
406
  for (let parent of parents) {
@@ -485,7 +500,7 @@ class SnomedServices {
485
500
  }
486
501
  let result;
487
502
  try {
488
- result = this._evalECLNode(ast);
503
+ result = this._evalECLNode(ast, opContext);
489
504
  } catch (err) {
490
505
  debugLog(err);
491
506
  throw new Issue('error', 'invalid', null, 'UNSUPPORTED_ECL', opContext.i18n.translate('UNSUPPORTED_ECL', opContext.langs, [eclExpression, err.message]), 'vs-invalid').handleAsOO(400);
@@ -495,7 +510,7 @@ class SnomedServices {
495
510
  // we actually need the full concept list, otherwise filterSize returns 0
496
511
  // and the iteration yields nothing. Materialise active concepts now.
497
512
  if (forIteration && result.eclWildcard && (!result.descendants || result.descendants.length === 0)) {
498
- result.descendants = this._eclEnumerateActiveConcepts();
513
+ result.descendants = this._eclEnumerateActiveConcepts(opContext);
499
514
  delete result.eclWildcard;
500
515
  }
501
516
  return result;
@@ -506,10 +521,11 @@ class SnomedServices {
506
521
  * when the filter needs to be iterated over (e.g. $expand).
507
522
  * @returns {number[]}
508
523
  */
509
- _eclEnumerateActiveConcepts = function () {
524
+ _eclEnumerateActiveConcepts = function (opContext) {
510
525
  const all = [];
511
526
  const n = this.concepts.count();
512
527
  for (let i = 0; i < n; i++) {
528
+ if (opContext) opContext.deadCheck('ecl:enumerateActiveConcepts');
513
529
  const concept = this.concepts.getConceptByCount(i);
514
530
  if ((concept.flags & 0x0F) === 0) { // active
515
531
  all.push(concept.index);
@@ -523,36 +539,37 @@ class SnomedServices {
523
539
  * @param {object} node
524
540
  * @returns {SnomedFilterContext}
525
541
  */
526
- _evalECLNode = function (node) {
542
+ _evalECLNode = function (node, opContext) {
527
543
  if (!node) {
528
544
  throw new Error('ECL evaluation error: null AST node');
529
545
  }
546
+ if (opContext) opContext.deadCheck('ecl:evalNode');
530
547
 
531
548
  switch (node.type) {
532
549
 
533
550
  case ECLNodeType.SUB_EXPRESSION_CONSTRAINT:
534
- return this._evalSubExpression(node);
551
+ return this._evalSubExpression(node, opContext);
535
552
 
536
553
  case ECLNodeType.COMPOUND_EXPRESSION_CONSTRAINT: {
537
- const left = this._evalECLNode(node.left);
538
- const right = this._evalECLNode(node.right);
554
+ const left = this._evalECLNode(node.left, opContext);
555
+ const right = this._evalECLNode(node.right, opContext);
539
556
  switch (node.operator) {
540
557
  case ECLNodeType.CONJUNCTION:
541
- return this._eclIntersect(left, right);
558
+ return this._eclIntersect(left, right, opContext);
542
559
  case ECLNodeType.DISJUNCTION:
543
- return this._eclUnion(left, right);
560
+ return this._eclUnion(left, right, opContext);
544
561
  case ECLNodeType.EXCLUSION:
545
- return this._eclMinus(left, right);
562
+ return this._eclMinus(left, right, opContext);
546
563
  default:
547
564
  throw new Error(`Unsupported ECL compound operator: ${node.operator}`);
548
565
  }
549
566
  }
550
567
 
551
568
  case ECLNodeType.REFINED_EXPRESSION_CONSTRAINT:
552
- return this._evalRefined(node);
569
+ return this._evalRefined(node, opContext);
553
570
 
554
571
  case ECLNodeType.DOTTED_EXPRESSION_CONSTRAINT:
555
- return this._evalDotted(node);
572
+ return this._evalDotted(node, opContext);
556
573
 
557
574
  default:
558
575
  // Could be a bare concept reference or wildcard passed in directly
@@ -561,7 +578,7 @@ class SnomedServices {
561
578
  node.type === ECLNodeType.WILDCARD ||
562
579
  node.type === ECLNodeType.MEMBER_OF) {
563
580
  // Wrap it as if it came from a no-operator SubExpressionConstraint
564
- return this._evalSubExpression({type: ECLNodeType.SUB_EXPRESSION_CONSTRAINT, operator: null, focus: node});
581
+ return this._evalSubExpression({type: ECLNodeType.SUB_EXPRESSION_CONSTRAINT, operator: null, focus: node}, opContext);
565
582
  }
566
583
  throw new Error(`Unsupported ECL node type: ${node.type}`);
567
584
  }
@@ -573,7 +590,7 @@ class SnomedServices {
573
590
  * @param {object} node
574
591
  * @returns {SnomedFilterContext}
575
592
  */
576
- _evalSubExpression = function (node) {
593
+ _evalSubExpression = function (node, opContext) {
577
594
  const operator = node.operator; // an ECLTokenType string, or null
578
595
  const focus = node.focus;
579
596
 
@@ -590,16 +607,16 @@ class SnomedServices {
590
607
  if (operator) {
591
608
  throw new Error('ECL hierarchy operators combined with ^ (member-of) are not yet supported');
592
609
  }
593
- return this._evalMemberOf(focus);
610
+ return this._evalMemberOf(focus, opContext);
594
611
  }
595
612
 
596
613
  // Plain concept reference
597
614
  if (focus.type === ECLNodeType.CONCEPT_REFERENCE) {
598
- return this._evalConceptWithOperator(focus.conceptId, operator);
615
+ return this._evalConceptWithOperator(focus.conceptId, operator, opContext);
599
616
  }
600
617
 
601
618
  // Parenthesised sub-expression: focus is itself a full constraint node
602
- return this._evalECLNode(focus);
619
+ return this._evalECLNode(focus, opContext);
603
620
  };
604
621
 
605
622
  /**
@@ -608,7 +625,7 @@ class SnomedServices {
608
625
  * @param {string|null} operator ECLTokenType constant
609
626
  * @returns {SnomedFilterContext}
610
627
  */
611
- _evalConceptWithOperator = function (conceptId, operator) {
628
+ _evalConceptWithOperator = function (conceptId, operator, opContext) {
612
629
  switch (operator) {
613
630
  case null:
614
631
  case undefined:
@@ -640,7 +657,7 @@ class SnomedServices {
640
657
 
641
658
  // ── Ancestors ──────────────────────────────────────────────────────────
642
659
  case ECLTokenType.ANCESTOR_OR_SELF_OF: { // >> self + all transitive ancestors
643
- const result = this.filterGeneralizes(conceptId);
660
+ const result = this.filterGeneralizes(conceptId, opContext);
644
661
  const self = this.concepts.findConcept(conceptId);
645
662
  if (self.found && !result.descendants.includes(self.index)) {
646
663
  result.descendants.push(self.index);
@@ -649,7 +666,7 @@ class SnomedServices {
649
666
  }
650
667
 
651
668
  case ECLTokenType.ANCESTOR_OF: { // > all transitive ancestors, no self
652
- return this.filterGeneralizes(conceptId);
669
+ return this.filterGeneralizes(conceptId, opContext);
653
670
  }
654
671
 
655
672
  case ECLTokenType.PARENT_OR_SELF_OF: { // >>! self + direct parents only
@@ -679,18 +696,54 @@ class SnomedServices {
679
696
  };
680
697
 
681
698
  /**
682
- * Evaluate a MEMBER_OF node. Only plain concept-reference refsets are
683
- * supported; complex expressions inside ^ are not yet supported.
699
+ * Evaluate a MEMBER_OF (^) node. The operand may be any ECL expression: it is
700
+ * resolved to a set of candidate reference-set concepts, and the result is the
701
+ * union of their active concept members' referenced components.
702
+ *
703
+ * When the operand is a bare, explicitly-named concept that is not a reference
704
+ * set, this throws (the caller asked for an error in that case). When the
705
+ * operand is a computed expression (e.g. ^(<<900000000000455006)), concepts in
706
+ * the resolved set that are not reference sets are simply skipped — the closure
707
+ * of "Reference set" necessarily includes the non-refset parent itself.
708
+ *
684
709
  * @param {object} memberOfNode
710
+ * @param {OperationContext} [opContext]
685
711
  * @returns {SnomedFilterContext}
686
712
  */
687
- _evalMemberOf = function (memberOfNode) {
688
- const refSet = memberOfNode.refSet;
689
- if (refSet.type !== ECLNodeType.CONCEPT_REFERENCE) {
690
- throw new Error('ECL ^ (member-of) with a non-concept-reference refset is not yet supported');
713
+ _evalMemberOf = function (memberOfNode, opContext) {
714
+ const operandIsBareRef = memberOfNode.refSet.type === ECLNodeType.CONCEPT_REFERENCE;
715
+ const refsetConcepts = this._eclResolveSet(this._evalECLNode(memberOfNode.refSet, opContext), opContext);
716
+
717
+ const members = new Set();
718
+ for (const refsetIdx of refsetConcepts) {
719
+ if (opContext) opContext.deadCheck('ecl:memberOf');
720
+ const membersRef = this._findRefSetMembersRef(refsetIdx);
721
+ if (membersRef === null) {
722
+ if (operandIsBareRef) {
723
+ const code = this.concepts.getConceptId(refsetIdx).toString();
724
+ throw new Error(`The SNOMED CT Concept ${code} is not a reference set`);
725
+ }
726
+ continue; // computed operand: ignore concepts that aren't reference sets
727
+ }
728
+ if (membersRef === 0 || membersRef === 0xFFFFFFFF) {
729
+ continue; // a reference set with no members
730
+ }
731
+ const memberList = this.refSetMembers.getMembers(membersRef);
732
+ for (const m of memberList || []) {
733
+ // Concept referenced components only (kind 0). Description/other members
734
+ // are excluded, which also prevents the no-component sentinel (0xFFFFFFFF)
735
+ // from leaking in. The referenced concept may itself be INACTIVE and is
736
+ // still returned — "active" in the spec qualifies the membership row (and
737
+ // inactive rows are already dropped at import), not the referenced
738
+ // concept. The expansion marks/handles inactivity (e.g. activeOnly).
739
+ if (m.kind === 0) {
740
+ members.add(m.ref);
741
+ }
742
+ }
691
743
  }
692
- // filterIn accepts a comma-separated string; a single ID works fine
693
- return this.filterIn(refSet.conceptId);
744
+ const result = new SnomedFilterContext();
745
+ result.descendants = [...members];
746
+ return result;
694
747
  };
695
748
 
696
749
  /**
@@ -714,8 +767,8 @@ class SnomedServices {
714
767
  * @param {object} node
715
768
  * @returns {SnomedFilterContext}
716
769
  */
717
- _evalDotted = function (node) {
718
- let current = this._eclResolveSet(this._evalECLNode(node.base));
770
+ _evalDotted = function (node, opContext) {
771
+ let current = this._eclResolveSet(this._evalECLNode(node.base, opContext), opContext);
719
772
 
720
773
  for (const attr of node.attributes || []) {
721
774
  if (attr.type !== ECLNodeType.CONCEPT_REFERENCE) {
@@ -729,6 +782,7 @@ class SnomedServices {
729
782
 
730
783
  const next = new Set();
731
784
  for (const conceptIdx of current) {
785
+ if (opContext) opContext.deadCheck('ecl:dotted');
732
786
  const relIdxs = this.getConceptRelationships(conceptIdx);
733
787
  for (const relIdx of relIdxs) {
734
788
  const rel = this.relationships.getRelationship(relIdx);
@@ -758,11 +812,12 @@ class SnomedServices {
758
812
  * @param {object} node
759
813
  * @returns {SnomedFilterContext}
760
814
  */
761
- _evalRefined = function (node) {
762
- const baseSet = this._eclResolveSet(this._evalECLNode(node.base));
815
+ _evalRefined = function (node, opContext) {
816
+ const baseSet = this._eclResolveSet(this._evalECLNode(node.base, opContext), opContext);
763
817
  const matching = [];
764
818
  for (const conceptIdx of baseSet) {
765
- if (this._refinementMatches(conceptIdx, node.refinement)) {
819
+ if (opContext) opContext.deadCheck('ecl:refined');
820
+ if (this._refinementMatches(conceptIdx, node.refinement, opContext)) {
766
821
  matching.push(conceptIdx);
767
822
  }
768
823
  }
@@ -778,17 +833,17 @@ class SnomedServices {
778
833
  * @param {object} refinement
779
834
  * @returns {boolean}
780
835
  */
781
- _refinementMatches = function (conceptIdx, refinement) {
836
+ _refinementMatches = function (conceptIdx, refinement, opContext) {
782
837
  switch (refinement.type) {
783
838
  case ECLNodeType.ATTRIBUTE:
784
- return this._attributeMatches(conceptIdx, refinement, null);
839
+ return this._attributeMatches(conceptIdx, refinement, null, opContext);
785
840
  case ECLNodeType.ATTRIBUTE_SET:
786
841
  for (const a of refinement.attributes) {
787
- if (!this._refinementMatches(conceptIdx, a)) return false;
842
+ if (!this._refinementMatches(conceptIdx, a, opContext)) return false;
788
843
  }
789
844
  return true;
790
845
  case ECLNodeType.ATTRIBUTE_GROUP:
791
- return this._attributeGroupMatches(conceptIdx, refinement);
846
+ return this._attributeGroupMatches(conceptIdx, refinement, opContext);
792
847
  default:
793
848
  throw new Error(`Unsupported refinement node type: ${refinement.type}`);
794
849
  }
@@ -804,7 +859,7 @@ class SnomedServices {
804
859
  * @param {number|null} groupFilter
805
860
  * @returns {boolean}
806
861
  */
807
- _attributeMatches = function (conceptIdx, attr, groupFilter) {
862
+ _attributeMatches = function (conceptIdx, attr, groupFilter, opContext) {
808
863
  if (attr.reverse) {
809
864
  throw new Error('ECL reverse attributes (R) are not yet supported');
810
865
  }
@@ -821,7 +876,7 @@ class SnomedServices {
821
876
  throw new Error('ECL refinements only support plain concept-reference attribute names');
822
877
  }
823
878
 
824
- const count = this._countAttributeMatches(conceptIdx, attr, groupFilter);
879
+ const count = this._countAttributeMatches(conceptIdx, attr, groupFilter, opContext);
825
880
 
826
881
  if (attr.cardinality) {
827
882
  return this._cardinalityAccepts(attr.cardinality, count);
@@ -838,25 +893,44 @@ class SnomedServices {
838
893
  * @param {number|null} groupFilter
839
894
  * @returns {number}
840
895
  */
841
- _countAttributeMatches = function (conceptIdx, attr, groupFilter) {
842
- const attrResult = this.concepts.findConcept(attr.name.conceptId);
843
- if (!attrResult.found) {
844
- throw new Error(`The SNOMED CT Concept ${attr.name.conceptId} is not known`);
896
+ _countAttributeMatches = function (conceptIdx, attr, groupFilter, opContext) {
897
+ // The attribute type and the value set depend only on the (static) AST node,
898
+ // not on the concept being tested, so resolve them once per refinement
899
+ // attribute and reuse across the whole base set. Without this memo the value
900
+ // expression is re-evaluated for every concept — and for a wildcard value
901
+ // (`= *`) the entire active-concept set is re-enumerated per concept —
902
+ // making refinement evaluation O(baseSet × valueExpr). The AST is parsed
903
+ // fresh per request, so memoising on the node is safe within a request.
904
+ let resolved = attr._eclResolved;
905
+ if (!resolved) {
906
+ const attrResult = this.concepts.findConcept(attr.name.conceptId);
907
+ if (!attrResult.found) {
908
+ throw new Error(`The SNOMED CT Concept ${attr.name.conceptId} is not known`);
909
+ }
910
+ resolved = {
911
+ attrTypeIdx: attrResult.index,
912
+ valueSet: new Set(this._eclResolveSet(this._evalECLNode(attr.comparison.value, opContext), opContext))
913
+ };
914
+ attr._eclResolved = resolved;
845
915
  }
846
- const attrTypeIdx = attrResult.index;
847
-
848
- const valueSet = new Set(this._eclResolveSet(this._evalECLNode(attr.comparison.value)));
916
+ const attrTypeIdx = resolved.attrTypeIdx;
917
+ const valueSet = resolved.valueSet;
849
918
 
850
919
  const relIdxs = this.getConceptRelationships(conceptIdx);
851
- let count = 0;
920
+ // ECL cardinality counts non-redundant matching attributes — i.e. distinct
921
+ // matching values — not raw relationship rows. The same (type, value) can
922
+ // appear in multiple role groups; counting rows over-counts and makes e.g.
923
+ // [1..1] fail and [2..*] succeed for a concept with a single morphology.
924
+ const matchedTargets = new Set();
852
925
  for (const relIdx of relIdxs) {
926
+ if (opContext) opContext.deadCheck('ecl:countAttributeMatches');
853
927
  const rel = this.relationships.getRelationship(relIdx);
854
928
  if (!rel.active) continue;
855
929
  if (rel.relType !== attrTypeIdx) continue;
856
930
  if (groupFilter !== null && rel.group !== groupFilter) continue;
857
- if (valueSet.has(rel.target)) count++;
931
+ if (valueSet.has(rel.target)) matchedTargets.add(rel.target);
858
932
  }
859
- return count;
933
+ return matchedTargets.size;
860
934
  };
861
935
 
862
936
  /**
@@ -884,7 +958,7 @@ class SnomedServices {
884
958
  * @param {object} group
885
959
  * @returns {boolean}
886
960
  */
887
- _attributeGroupMatches = function (conceptIdx, group) {
961
+ _attributeGroupMatches = function (conceptIdx, group, opContext) {
888
962
  const relIdxs = this.getConceptRelationships(conceptIdx);
889
963
  const groupNumbers = new Set();
890
964
  for (const relIdx of relIdxs) {
@@ -896,9 +970,10 @@ class SnomedServices {
896
970
 
897
971
  let matchingGroupCount = 0;
898
972
  for (const g of groupNumbers) {
973
+ if (opContext) opContext.deadCheck('ecl:attributeGroup');
899
974
  let allMatch = true;
900
975
  for (const attr of group.attributes) {
901
- if (!this._attributeMatches(conceptIdx, attr, g)) {
976
+ if (!this._attributeMatches(conceptIdx, attr, g, opContext)) {
902
977
  allMatch = false;
903
978
  break;
904
979
  }
@@ -939,9 +1014,9 @@ class SnomedServices {
939
1014
  * @param {SnomedFilterContext} ctx
940
1015
  * @returns {number[]}
941
1016
  */
942
- _eclResolveSet = function (ctx) {
1017
+ _eclResolveSet = function (ctx, opContext) {
943
1018
  if (ctx.eclWildcard && (!ctx.descendants || ctx.descendants.length === 0)) {
944
- return this._eclEnumerateActiveConcepts();
1019
+ return this._eclEnumerateActiveConcepts(opContext);
945
1020
  }
946
1021
  return this._eclToIndexArray(ctx);
947
1022
  };
@@ -949,20 +1024,24 @@ class SnomedServices {
949
1024
  /**
950
1025
  * AND: concepts present in both sets.
951
1026
  */
952
- _eclIntersect = function (left, right) {
1027
+ _eclIntersect = function (left, right, opContext) {
953
1028
  if (left.eclWildcard) return right;
954
1029
  if (right.eclWildcard) return left;
955
1030
  const leftSet = new Set(this._eclToIndexArray(left));
956
1031
  const result = new SnomedFilterContext();
957
- result.descendants = this._eclToIndexArray(right).filter(idx => leftSet.has(idx));
1032
+ result.descendants = this._eclToIndexArray(right).filter(idx => {
1033
+ if (opContext) opContext.deadCheck('ecl:intersect');
1034
+ return leftSet.has(idx);
1035
+ });
958
1036
  return result;
959
1037
  };
960
1038
 
961
1039
  /**
962
1040
  * OR: concepts present in either set.
963
1041
  */
964
- _eclUnion = function (left, right) {
1042
+ _eclUnion = function (left, right, opContext) {
965
1043
  if (left.eclWildcard || right.eclWildcard) return this._eclWildcard();
1044
+ if (opContext) opContext.deadCheck('ecl:union');
966
1045
  const combined = new Set([
967
1046
  ...this._eclToIndexArray(left),
968
1047
  ...this._eclToIndexArray(right)
@@ -975,7 +1054,7 @@ class SnomedServices {
975
1054
  /**
976
1055
  * MINUS: concepts in left that are not in right.
977
1056
  */
978
- _eclMinus = function (left, right) {
1057
+ _eclMinus = function (left, right, opContext) {
979
1058
  const result = new SnomedFilterContext();
980
1059
 
981
1060
  if (right.eclWildcard) {
@@ -989,6 +1068,7 @@ class SnomedServices {
989
1068
  // Enumerate all active concepts minus the right set
990
1069
  const all = [];
991
1070
  for (let i = 0; i < this.concepts.count(); i++) {
1071
+ if (opContext) opContext.deadCheck('ecl:minus');
992
1072
  const concept = this.concepts.getConceptByCount(i);
993
1073
  if (this.isActive(concept.index) && !rightSet.has(concept.index)) {
994
1074
  all.push(concept.index);
@@ -998,7 +1078,10 @@ class SnomedServices {
998
1078
  return result;
999
1079
  }
1000
1080
 
1001
- result.descendants = this._eclToIndexArray(left).filter(idx => !rightSet.has(idx));
1081
+ result.descendants = this._eclToIndexArray(left).filter(idx => {
1082
+ if (opContext) opContext.deadCheck('ecl:minus');
1083
+ return !rightSet.has(idx);
1084
+ });
1002
1085
  return result;
1003
1086
  };
1004
1087
 
Binary file
@@ -129,8 +129,10 @@ class CanonicalResource {
129
129
  return this.dateIsMoreRecent(this.version, other.version);
130
130
  case 'integer':
131
131
  return parseInt(this.version, 10) > parseInt(other.version, 10);
132
- case 'alpha': return this.version.localeCompare(other.version) > 0;
133
- default: return this.version.localeCompare(other.version);
132
+ case 'alpha':
133
+ default:
134
+ // Return a boolean: true only when this.version sorts after other.version.
135
+ return this.version.localeCompare(other.version) > 0;
134
136
  }
135
137
  }
136
138
  if (this.date && other.date && this.date != other.date) {
@@ -46,7 +46,7 @@ class SearchFilterText {
46
46
  validateParameter(value, 'value', String);
47
47
  validateOptionalParameter(returnRating, 'returnRating', Boolean);
48
48
 
49
- if (this.null) {
49
+ if (this.isNull) {
50
50
  return returnRating ? {passes: true, rating: 0} : true;
51
51
  }
52
52
 
@@ -610,35 +610,42 @@ class Designations {
610
610
 
611
611
  const matchTypes = [LangMatchType.FULL, LangMatchType.LANG_REGION, LangMatchType.LANG];
612
612
 
613
- for (const matchType of matchTypes) {
614
- for (const cd of this.designations) {
615
- if (this._langMatches(lang, cd.language, matchType) && this.isDisplay(cd)) {
616
- if (supplements && cd.source) {
617
- supplements.add(cd.source);
613
+ for (const activeOnly of [true, false]) {
614
+ for (const matchType of matchTypes) {
615
+ for (const cd of this.designations) {
616
+ if (this._langMatches(lang, cd.language, matchType) && this.isDisplay(cd) && (!activeOnly || cd.isActive())) {
617
+ if (supplements && cd.source) {
618
+ supplements.add(cd.source);
619
+ }
620
+ return cd;
618
621
  }
619
- return cd;
620
622
  }
621
- }
622
- for (const cd of this.designations) {
623
- if (this._langMatches(lang, cd.language, matchType) && this._isPreferred(cd)) {
624
- if (supplements && cd.source) {
625
- supplements.add(cd.source);
623
+ for (const cd of this.designations) {
624
+ if (this._langMatches(lang, cd.language, matchType) && this._isPreferred(cd)&& (!activeOnly || cd.isActive())) {
625
+ if (supplements && cd.source) {
626
+ supplements.add(cd.source);
627
+ }
628
+ return cd;
626
629
  }
627
- return cd;
628
630
  }
629
- }
630
- for (const cd of this.designations) {
631
- if (this._langMatches(lang, cd.language, matchType)) {
632
- if (supplements && cd.source) {
633
- supplements.add(cd.source);
631
+ for (const cd of this.designations) {
632
+ if (this._langMatches(lang, cd.language, matchType) && (!activeOnly || cd.isActive())) {
633
+ if (supplements && cd.source) {
634
+ supplements.add(cd.source);
635
+ }
636
+ return cd;
634
637
  }
635
- return cd;
636
638
  }
637
639
  }
638
640
  }
639
641
  }
640
642
  for (const cd of this.designations) {
641
- if (!cd.language && this.isDisplay(cd)) {
643
+ if (!cd.language && this.isDisplay(cd) && cd.isActive()) {
644
+ if (supplements && cd.source) {
645
+ supplements.add(cd.source);
646
+ }
647
+ return cd;
648
+ } if (!cd.language && this.isDisplay(cd)) {
642
649
  if (supplements && cd.source) {
643
650
  supplements.add(cd.source);
644
651
  }