toTreeReference method

TreeReference toTreeReference()

This path as a TreeReference.

Only the subset JavaRosa supports is allowed: child::name, *, @name, . and leading .. steps (with predicates), starting at the root, the context node, instance('id') or current(). Anything else throws XPathUnsupportedException.

Port of XPathPathExpr.getReference.

Implementation

TreeReference toTreeReference() {
  TreeReference ref;
  bool parentsAllowed;
  switch (start) {
    case PathStart.root:
      ref = const TreeReference.root();
      parentsAllowed = false;
    case PathStart.relative:
      ref = const TreeReference.relative();
      parentsAllowed = true;
    case PathStart.expression:
      final x = filterExpr!.x;
      if (x is! XPathFuncExpr) {
        // Also reached when a boolean operator is missing.
        throw XPathUnsupportedException('filter expression: $this');
      }
      switch (x.id.toString()) {
        case 'instance':
          parentsAllowed = false;
          if (x.args.length != 1) {
            throw XPathUnsupportedException(
              'instance() function used with ${x.args.length} arguments. '
              'Expecting 1 argument',
            );
          }
          final arg = x.args.first;
          if (arg is! XPathStringLiteral) {
            throw XPathUnsupportedException(
              'instance() function expecting 1 string literal argument',
            );
          }
          ref = const TreeReference.root()
              .withContextType(ReferenceContext.instance)
              .withInstanceName(arg.value);
        case 'current':
          // current() in a calculate is the node itself; in a choice
          // filter it is the select question, not the itemset node.
          parentsAllowed = true;
          ref = const TreeReference.relative().withContextType(
            ReferenceContext.original,
          );
        default:
          throw XPathUnsupportedException('filter expression');
      }
  }

  for (var i = 0; i < steps.length; i++) {
    final step = steps[i];
    const unsupported = "step other than 'child::name', '.', '..'";
    switch (step.axis) {
      case XPathAxis.self:
        if (step.test != StepTest.node) {
          throw XPathUnsupportedException(unsupported);
        }
      case XPathAxis.parent:
        if (!parentsAllowed || step.test != StepTest.node) {
          throw XPathUnsupportedException(unsupported);
        }
        ref = ref.withIncrementedRefLevel();
      case XPathAxis.attribute:
        if (step.test != StepTest.name) {
          throw XPathUnsupportedException(
            "attribute step other than 'attribute::name",
          );
        }
        ref = ref.extend(step.name.toString(), TreeReference.indexAttribute);
        parentsAllowed = false;
      case XPathAxis.child:
        if (step.test == StepTest.name) {
          ref = ref.extend(step.name.toString(), TreeReference.indexUnbound);
        } else if (step.test == StepTest.nameWildcard) {
          ref = ref.extend(
            TreeReference.nameWildcard,
            TreeReference.indexUnbound,
          );
        } else {
          throw XPathUnsupportedException(unsupported);
        }
        parentsAllowed = true;
      default:
        throw XPathUnsupportedException(unsupported);
    }
    if (step.predicates.isNotEmpty) {
      // ".." steps add no level, so shift the index back by refLevel.
      final level = ref.refLevel > 0 ? i - ref.refLevel : i;
      ref = ref.withPredicates(level, step.predicates);
    }
  }
  return ref;
}