---
# Documentation:
#
# The general form for many rules is:
# 1. Say the command if this is first rule to fire (MatchCounter) and depending upon "NavVerbosity"s value
# 2. Say info about moving into/out of 2D structures
# 3. Set some variables and possibly recurse.
#    If recursing, "MatchCounter" should be incremented.
#    If stopping, "NavNode" should be set.
#
# The meaning of NavVerbosity:
# * Verbose -- always echo the command and end points (e.g., "can't move right")
# * Medium -- only echo the command for obscure commands like the placemarker commands; also say end points
# * Terse -- no echo of commands or end points
# 
# For the second item, a common set of rules is used. These rules require the variable "Move2D"
# to be set along with "Child2D", where "Move2D" is either 'in' or 'out'.
#
# In addition, the navigation rules make use of two functions:
#
# int DistanceFromLeaf(node, leftSide, treat2DElementsAsTokens)
#   Returns the distance (number of children) until a leaf is reached by traversing the leftmost/rightmost child
#   If 'treat2DElementsAsTokens' is true, then 2D notations such as fractions are treated like leaves 
#   A leaf has distance == 0
#
# EdgeNode(node, "left"/"right", stopNodeName)
#   Returns the stopNode if at left/right edge of named ancestor node. 'stopNodeName' can also be "2D"
#   If the stopNode isn't found, the original node is returned
#  Note: if stopNodeName=="math", then punctuation is taken into account since it isn't really part of the math
#
# A few other variables are of importance to Navigation
# NavMode -- Enhanced, Simple, Character
# ReadZoomLevel -- -1 for Enhanced, otherwise the distance from leaf the rules should maintain
# PlaceMarkerIndex

# Note: the rules for saying a command and announcing what is said when moving in/out of a 2d exprs are hacks
# They depend upon special variables "SayCommand" and "Move2D" being set and if they are, the rules are activated.
# If/when functions can be defined in a rules file, it is likely these would be much better done via those functions
#   as they would likely be much more efficient and also cleaner.

# Rules for announcing the command
- name: say-command
  tag: "!*"
  match: "$SayCommand != ''"   # value should be '', 'true', or 'false'
  variables: [Prefix: "''"]
  replace:
  - test:
    - if: "$MatchCounter = 0 and $SayCommand = 'true'"
      then_test:
      - if: "self::m:math and starts-with($NavCommand, 'ZoomOut')"
        then: [t: "zoomed out all the way", pause: "medium"]
      - else_if: "IsNode(., 'leaf') and starts-with($NavCommand, 'ZoomIn')"
        then: [t: "zoomed in all the way", pause: "medium"]
        else:
        - test:
          - if: "starts-with($NavCommand, 'Zoom')"
            then: [set_variables: [Prefix: "'zoom'"]]          # phrase('zoom' in to see more details)     
          - else_if: "starts-with($NavCommand, 'Move')"
            then: [set_variables: [Prefix: "'move'"]]          # phrase('move' to next entry in table)     
          - else_if: "starts-with($NavCommand, 'Read')"
            then: [set_variables: [Prefix: "'read'"]]          # phrase('read' to next entry in table)     
          - else_if: "starts-with($NavCommand, 'Describe')"
            then: [set_variables: [Prefix: "'describe'"]]      # phrase('describe' to next entry in table)
        - test:
            if: "$Prefix != ''"
            then:
            - x: "$Prefix"
            - test:
              - if: "substring($NavCommand, string-length($Prefix)+1) = 'In'"
                then: [t: "in"]                                  # phrase(zoom 'in' to see more details)
              - else_if: "substring($NavCommand, string-length($Prefix)+1) = 'InAll'"
                # HACK: '\uF8FE' is used internally for the concatenation char by 'ct' -- this gets "ed" concatenated to "zoom"
                then: [t: "\uF8FEed in all the way"]                     # phrase(zoom 'out all the way' to see more details)
              - else_if: "substring($NavCommand, string-length($Prefix)+1) = 'Out'"
                then: [t: "out"]                                 # phrase(zoom 'out' to see more details)
              - else_if: "substring($NavCommand, string-length($Prefix)+1) = 'OutAll'"
                # HACK: '\uF8FE' is used internally for the concatenation char by 'ct' -- this gets "ed" concatenated to "zoom"
                then: [t: "\uF8FEed out all the way"]                     # phrase(zoom 'out all the way' to see more details)
              - else_if: "substring($NavCommand, string-length($Prefix)+1) = 'Next'"
                then: [t: "right"]                               # phrase(move to the 'right')
              - else_if: "substring($NavCommand, string-length($Prefix)+1) = 'Previous'"
                then: [t: "left"]                                # phrase(move to the 'left')
              - else_if: "substring($NavCommand, string-length($Prefix)+1) = 'Current'"
                then: [t: "current"]                                 # phrase(who is the 'current' president)
              - else_if: "substring($NavCommand, string-length($Prefix)+1) = 'LineStart'"
                then: [t: "to start of line"]                                 # phrase(move 'to start of line')
              - else_if: "substring($NavCommand, string-length($Prefix)+1) = 'LineEnd'"
                then: [t: "to end of line"]                                 # phrase(move 'to end of line')
            - pause: "medium"
  - set_variables: [MatchCounter: "1"]

- name: into-or-out-of-silent
  tag: "*"
  # saying "out of row n" is not very useful, so skip it
  match: "$Move2D != '' and (not(@data-from-mathml) or @data-from-mathml = name(.)) and
          (name(.)='mrow' or name(.) = 'mtr' or name(.) = 'mlabeledtr' or @data-from-mathml = 'mtable')"
  replace: []

- name: into-or-out-of-mtr
  tag: [mtr, mlabeledtr]
  match: "$Move2D = 'in'"
  replace:
  - t: "column"
  - x: "count($Child2D/preceding-sibling::*)+1"
  - pause: "medium"

- name: into-or-out-of-mmultiscripts
  tag: "*"
  match: "$Move2D != '' and (@data-from-mathml='mmultiscripts' or self::m:mmultiscripts)"
  replace:
  - test:
      if: "name($Child2D)!='none'"
      then:
      - with:
          variables:
          - NumPrecedingSiblings: "count($Child2D/preceding-sibling::*)"
          replace:
          - x: "$Move2D"
          - test:
            - if: "$NumPrecedingSiblings=0"
              then: [t: "base"]                 # phrase(the 'base' of the power)
            - else_if: "$Child2D/preceding-sibling::*[self::m:mprescripts]" # are we before mprescripts and hence are postscripts
              then:
              - test:   # in postscripts -- base shifts by one
                  if: "$NumPrecedingSiblings mod 2 = 0" 
                  then: [t: "pre-subscript"]       # phrase(x with 'subscript' 2)
                  else: [t: "pre-superscript"]     # phrase(x with 'superscript' 2)
              else:
              - test:
                  if: "$NumPrecedingSiblings mod 2 = 0"
                  then: [t: "superscript"]    # phrase(x with 'pre-superscript' 2)
                  else: [t: "subscript"]      # phrase(x with 'pre-subscript' 2)
          - pause: "medium"

# Rules for speaking what happens when moving into or out of a notation
- name: into-or-out-of-intent
  tag: "*"
  # saying "out of row n" is not very useful, so skip it
  # match: "$Move2D != '' and @data-from-mathml and @data-from-mathml != name(.) and count(./*)>1 and @data-from-mathml != 'mtable'"
  match: "$Move2D != '' and count(./*)>1 and @data-from-mathml != 'mtable'"
  replace:
  - with:
      variables:
      - PartNumber: "count($Child2D/preceding-sibling::*)"
      - PartName: "GetNavigationPartName(name(.), $PartNumber)"
      replace:
      - x: "$Move2D"
      - test:
          if: "$PartName != ''"
          then: [x: "$PartName"]
          else:
          - t: "part"         # phrase(the 'part' of the expression)
          - x: "count($Child2D/preceding-sibling::*) + 1"
      - pause: "medium"

- name: default-move
  # nothing to do (not 2D) -- need to catch $Move2D though so rules based on NavCommand don't trigger
  tag: "*"
  match: "$Move2D != ''"
  replace: []

# ********* Go back to last position ***************
# This is first since start/end position shouldn't matter
- name: move-last-location


  tag: "*"
  match: "$NavCommand = 'MoveLastLocation'"
  replace:
  - test:
      if: "$NavVerbosity != 'Terse'"
      then:
      - test:
        - if: "$PreviousNavCommand = 'ZoomIn'"
          then: [t: "undo zoom in"]                       # phrase('undo zoom in')
        - else_if: "$PreviousNavCommand = 'ZoomOut'"
          then: [t: "undo zoom out"]                      # phrase('undo zoom out')
        - else_if: "$PreviousNavCommand = 'ZoomInAll'"
          then: [t: "undo zooming in all the way"]        # phrase('undo zooming in all the way')
        - else_if: "$PreviousNavCommand = 'ZoomOutAll'"
          then: [t: "undo zooming out all the way"]       # phrase('undo zooming out all the way')
        - else_if: "$PreviousNavCommand = 'MovePrevious' or $PreviousNavCommand = 'MovePreviousZoom'"
          then: [t: "undo move left"]                     # phrase('undo move left')
        - else_if: "$PreviousNavCommand = 'MoveNext' or $PreviousNavCommand = 'MoveNextZoom'"
          then: [t: "undo move right"]                    # phrase('undo move right')
        - else_if: "$PreviousNavCommand = 'None'"
          then: [t: "no previous command"]                # phrase('no previous command')
      - pause: "medium"
  - set_variables: [NavNode: "@id"]

# many times, for typographic reasons, people include punctuation at the end of a math expr
# these rules detect that and skip speaking it (should be similar regular rule)
- name: skip-punct-at-end-zoom-in
  tag: mrow
  match:
  - "($NavCommand = 'ZoomIn' or $NavCommand = 'ZoomInAll' or $NavCommand = 'MoveNextZoom' or $NavCommand = 'MovePreviousZoom') and"
  - " parent::m:math and count(*)=2 and"
  - " *[2][translate(.,'.,;:?', '')='']"
  replace:
  - x: "*[1]"

# ********* ZoomIn  ***************
- name: zoom-in-leaf

  tag: "*"
  match: "($NavCommand = 'ZoomIn' or $NavCommand = 'ZoomInAll') and IsNode(., 'leaf')"
  replace:
  - test:
      if: "$MatchCounter = 0 and $NavVerbosity != 'Terse'"
      then: [t: "zoomed in all of the way", pause: "long"]    # phrase('zoomed in all of the way')
  - test:
      if: "$ReadZoomLevel!=-1"
      then:
      - set_variables: [ReadZoomLevel: "0"]
  - set_variables: [NavNode: "@id"]

# special case of zooming into a table -- move to the first row (if only one row, first column)
- name: zoom-in-table
  tag: "*"
  match: "$NavCommand = 'ZoomIn' and (name(.) = 'mtable' or (count(*)=1 and *[1][@data-from-mathml='mtable']))"
  replace:
  - with:
      variables: [SayCommand: "string($NavVerbosity = 'Verbose')"]
      replace: [x: "."]
  - test:
      if: "count(*)=1"
      then: 
      - set_variables: [NavNode: "*[1]/*[1]/@id"]
      else:
      - set_variables: [NavNode: "*[1]/@id"]

- name: zoom-in-mrow-in-math
  # zooming in only once is meaningless because 'math' has only a single child and it was spoken at the math level -- dig inside and do it again 
  tag: math
  match: "$NavCommand = 'ZoomIn' or $NavCommand = 'MoveNextZoom' or $NavCommand = 'MovePreviousZoom'"
  replace:
  - test:
      if: "$NavCommand = 'MovePreviousZoom'"
      then: [x: "*[last()]"]
      else: [x: "*[1]"]

- # For msqrt and menclose, if the single child isn't an mrow, don't zoom in
  name: zoom-in-again
  # If there is only one child, this isn't an interesting move -- zoom in again
  tag: "*"
  match:
  - "($NavCommand = 'ZoomIn' or "
  - "  ($NavCommand = 'MoveNextZoom' or $NavCommand = 'MovePreviousZoom') and $NavMode='Enhanced') and "
  - "count(*)=1 and
     (*[1][self::m:mrow or @data-from-mathml='mrow'] and
      not(@data-from-mathml='msqrt' or self::m:msqrt or @data-from-mathml='menclose' or self::m:menclose))"
  replace:
  - with:
      variables: [SayCommand: "string($NavVerbosity = 'Verbose')"]
      replace: [x: "."]
  - test:
      if: "name(*[1]) != 'mrow'"
      then:
      - with:
          variables: [Move2D: "'in'", Child2D: "IfThenElse(count(*)=0, $Move2D, $Move2D)"]   # phrase('in' the denominator)
          replace: [x: "IfThenElse($NavCommand = 'MovePreviousZoom', 1, $Child2D)"]
  - test:
      if: "$NavCommand = 'MovePreviousZoom'"
      then: [x: "*[last()]"]
      else: [x: "*[1]"]

- name: zoom-in-enhanced
  tag: "*"
  match: "$NavCommand = 'ZoomIn' and $NavMode='Enhanced'"
  replace:
  - with:
      variables: [SayCommand: "string($NavVerbosity = 'Verbose')"]
      replace: [x: "."]
  - test:
    - if: "self::m:mtr or self::m:mlabeledtr"
      then:
      - with:
          variables: [Move2D: "'in'", Child2D: "*[1]/*[1]"]   # phrase('in' the denominator)
          replace: [x: "."]
      - set_variables: [NavNode: "*[1]/*[1]/@id"] # skip mtd
    - else_if: "*[1][self::m:mrow and (IsBracketed(., '(', ')', false) or IsBracketed(., '[', ']', false))]" # auto zoom
      then:
      - with:
          variables: [Move2D: "'in'", Child2D: "*[1]"]   # phrase('in' the denominator)
          replace: [x: "."]
      - set_variables: [NavNode: "*[1]/*[2]/@id"]        # skip parens/brackets
      else:
      - with:
          variables: [Move2D: "'in'", Child2D: "*[1]"]        # phrase('in' the denominator)
          replace: [x: "."]
      - set_variables: [NavNode: "*[1]/@id"]

- name: zoom-in-2D-not-enhanced
  tag: "*"
  match: "$NavCommand = 'ZoomIn' and $NavMode!='Enhanced' and (IsNode(., '2D') or not(IsNode(., 'mathml')))"
  replace:
  - with:
      variables: [SayCommand: "string($NavVerbosity = 'Verbose')"]
      replace: [x: "."]
  - with:
      variables: [Move2D: "'in'", Child2D: "*[1]"]          # phrase('in' the denominator)
      replace: [x: "."]
  - with:
      variables: [NavCommand: "'MoveNextZoom'"]
      replace: [x: "*[1]"]

- name: zoom-in-default
  tag: "*"
  match: "$NavCommand = 'ZoomIn'"
  replace:
  - with:
      variables: [SayCommand: "string($NavVerbosity = 'Verbose')"]
      replace: [x: "."]
  - with:
      variables: [Move2D: "'in'", Child2D: "*[1]"]        # phrase('in' the denominator)
      replace: [x: "."]
  - test:
      if: "$NavMode='Character'"
      then: [x: "*[1]"]
      else_test:
        if: "self::m:mtd"
        then: [x: "*[1]"]
        else:
        - test:
            if: "$ReadZoomLevel!=-1"
            then:
            - set_variables: [ReadZoomLevel: "DistanceFromLeaf(*[1], true, $NavMode!='Character')"]
        - set_variables: [NavNode: "*[1]/@id"]

- name: zoom-in-all-default
  tag: "*"
  match: "$NavCommand = 'ZoomInAll'"
  replace:
  - with:
      variables: [SayCommand: "string($NavVerbosity = 'Verbose')"]
      replace: [x: "."]
  - with:
      variables: [Move2D: "'in'", Child2D: "*[1]"]              # phrase('in' the denominator)
      replace: [x: "."]
  - x: "*[1]"

- name: zoom-out
  tag: math
  match: "$NavCommand = 'ZoomOut' or $NavCommand = 'ZoomOutAll'"
  replace:
  - with:
      variables: [SayCommand: "string($NavVerbosity = 'Verbose')"]
      replace: [x: "."]
  - set_variables: [NavNode: "*[1]/@id"] # no-op for $NavCommand = 'ZoomOut'

- name: skip-punct-at-end-zoom-out
  tag: mrow
  match:
  - "($NavCommand = 'ZoomOut' or $NavCommand = 'ZoomOutAll') and"
  - " parent::m:math and count(*)=2 and"
  - " *[2][translate(.,'.,;:?', '')='']"
  replace:
  - x: ".."

- name: zoom-out-top
  tag: "*"
  match:
  - "($NavCommand = 'ZoomOut' or $NavCommand = 'ZoomOutAll') and"
  - "parent::m:math "
  replace:
  - x: ".." # let math rule deal with it

- name: zoom-out-all-default
  tag: "*"
  match: "$NavCommand = 'ZoomOutAll'"
  replace:
  - with:
      variables: [SayCommand: "string($NavVerbosity = 'Verbose')"]
      replace: [x: "."]
  - with:
      variables: [Move2D: "'out of'", Child2D: "."]
      replace: [x: ".."]
  - x: ".."

# deal with internal zooming: MoveNextZoom and MovePreviousZoom

# start with Enhanced mode
- name: move-zoom-enhanced
  tag: "*"
  match:
  - "($NavCommand = 'MoveNextZoom' or $NavCommand = 'MovePreviousZoom') and "
  - "$NavMode = 'Enhanced'"
  replace:
  - with:
      variables: [Move2D: "'in'", Child2D: "*[1]"]            # phrase('in' the denominator)
      replace: [x: "."]
  - test:
    - if: "count(*)> 1 or IsNode(., 'leaf') or
           @data-from-mathml='msqrt' or self::m:msqrt or @data-from-mathml='menclose' or self::m:menclose"
      then: [set_variables: [NavNode: "@id"]]
      else: [x: "*[1]"]

- name: move-next-zoom-not-enhanced
  # $ReadZoomLevel must be >= 0
  tag: "*"
  match: "$NavCommand = 'MoveNextZoom'"
  replace:
  #don't bother with MatchCounter since we only get here if > 1
  - test:
      if: "IsNode(., 'leaf') or $ReadZoomLevel >= DistanceFromLeaf(., false, $NavMode!='Character')"
      then:
      # - with:
      #     variables: [Move2D: "'in'", Child2D: "following-sibling::*[1]"]    # phrase('in' the denominator)
      #     replace: [x: ".."]
      - set_variables: [NavNode: "@id"]
      else:
      - with:
          variables: [Move2D: "'in'", Child2D: "*[1]"]                        # phrase('in' the denominator)
          replace: [x: "."]
      - x: "*[1]"

- name: move-previous-zoom-not-enhanced
  # $ReadZoomLevel must be >= 0
  tag: "*"
  match: "$NavCommand = 'MovePreviousZoom'"
  replace:
  #don't bother with MatchCounter since we only get here if > 1
  - test:
      if: "$ReadZoomLevel >= DistanceFromLeaf(., true, $NavMode!='Character')"
      then:
      # - with:
      #     variables: [Move2D: "'in'", Child2D: "preceding-sibling::*[1]"]     # phrase('in' the denominator)
      #     replace: [x: ".."]
      - set_variables: [NavNode: "@id"]
      else:
      - with:
          variables: [Move2D: "'in'", Child2D: "*[last()]"]                     # phrase('in' the denominator)
          replace: [x: "."]
      - x: "*[last()]"

# ********* ZoomOut  ***************
- name: zoom-out-default


  tag: mtd
  match: "$Move2D = '' and ($NavCommand = 'ZoomOut')"
  replace:
  - with:
      variables: [SayCommand: "string($NavVerbosity = 'Verbose')"]
      replace: [x: "."]
  # we need to speak it here
  # - t: "row"                                                  # phrase(the first 'row' of the matrix)
  #   # if we let the speech rules speak the row, it is given just the MathML for the row, so the row # will always be '1'
  # - x: "count(../preceding-sibling::*)+1"
  # - pause: medium
  - set_variables: [NavNode: "../@id"]

- name: zoom-out
  # a row around a single element -- these might duplicate the position/offset, so we jump an extra level here  
  tag: "*"
  match: "$NavCommand = 'ZoomOut'"
  replace:
  - with:
      variables: [SayCommand: "string($NavVerbosity = 'Verbose')"]
      replace: [x: "."]
  - test:
      if: "$NavMode='Enhanced' and parent::*[self::m:mrow and (IsBracketed(., '(', ')', false) or IsBracketed(., '[', ']', false))]"
      then: [x: ".."] # auto-zoom: move out a level and retry
      else:
      - with:
          variables: [Move2D: "'out of'", Child2D: "."]
          replace: [x: ".."]
      - test:
          if: "parent::m:mtd"
          then: [x: ".."]
          else:
          - test:
              if: "$ReadZoomLevel!=-1"
              then: [set_variables: [ReadZoomLevel: "DistanceFromLeaf(.., true, $NavMode!='Character')"]]
          - set_variables: [NavNode: "../@id"]

# ********* MoveStart/End  ***************
- name: math-move-to-start-or-end
  tag: math
  match: "$NavCommand = 'MoveStart' or $NavCommand = 'MoveLineStart' or $NavCommand = 'MoveEnd' or $NavCommand = 'MoveLineEnd'"
  replace:
  - with:
      variables: [MatchCounter: "$MatchCounter + 1"]
      replace:
      - test:
          if: "$NavVerbosity = 'Verbose'"
          then:
          - test:
            - if: "$NavCommand = 'MoveStart'"
              then: [t: "move to start of math"]                  # phrase('move to start of math')
            - else_if: "$NavCommand = 'MoveLineStart'"
              then: [t: "move to start of line"]                  # phrase('move to start of line')
            - else_if: "$NavCommand = 'MoveEnd'"
              then: [t: "move to end of math"]                    # phrase('move to end of math')
              else: [t: "move to end of line"] # "$NavCommand = 'MoveLineEnd'"  # phrase('move to end of line')
          - pause: "medium"
      - test:
          if: "$NavCommand = 'MoveStart' or $NavCommand = 'MoveLineStart'"
          then:
          # move inside of the mrow inside of 'math' or inside the fraction, etc  (hence two levels down)
          # Note: an apparent bug in the xpath code doesn't let me use IfThenElse for the 2 if: then: below
          - with:
              variables: [NavCommand: "'MoveNextZoom'"]
              replace:
              - test:
                  if: "*[1]/*[1]"    # could be a <math><mi>x</mi></math>, so no grandchild
                  then: [x: "*[1]/*[1]"]
                  else: [x: "*[1]"]
          else:
          - with:
              variables: [NavCommand: "'MovePreviousZoom'"]
              replace:
              - test:
                  if: "*[last()]/*[last()]"  # could be a <math><mi>x</mi></math>, so no grandchild
                  then: [x: "*[last()]/*[last()]"]
                  else: [x: "*[last()]"]

# We stop when the parent is 2d (e.g., frac), but not if in leaf base of msub/msup/msubsup/mmultiscripts because that's really on the same line
- name: move-to-start-or-end-2d
  tag: "*"
  match:
  - "($NavCommand = 'MoveLineStart' or $NavCommand = 'MoveLineEnd') and IsNode(.., '2D') and"
  - "not( IsNode(., 'leaf') and"
  - "     parent::*[1][self::m:msub or self::m:msup or self::m:msubsup or self::m:mmultiscripts or"
  - "                 @data-from-mathml and"
  - "                  (@data-from-mathml='msub' or @data-from-mathml='msup' or"
  - "                   @data-from-mathml='msubsup' or @data-from-mathml='mmultiscripts')"
  - "                 ] )"
  replace:
  - test:
      if: "$NavVerbosity = 'Verbose'"
      then:
      - test:
          if: "$NavCommand = 'MoveLineStart'"
          then: [t: "move to start of line"]                                # phrase('move to start of line')
          else: [t: "move to end of line"] # "$NavCommand = 'MoveLineEnd'"  # phrase('move to end of line')
      - pause: "medium"
  - test:
      if: "self::m:mrow or @data-from-mathml = 'mrow'"
      then_test:
        if: "$NavCommand = 'MoveLineStart'"
        then:
        - with:
            variables: [NavCommand: "'MoveNextZoom'"]
            replace: [x: "*[1]"]
        else:
        - with:
            variables: [NavCommand: "'MovePreviousZoom'"]
            replace: [x: "*[last()]"]
      else: [set_variables: [NavNode: "@id"]]

- name: move-to-start-or-end-default
  tag: "*"
  match: "$NavCommand = 'MoveStart' or $NavCommand = 'MoveLineStart' or $NavCommand = 'MoveEnd' or $NavCommand = 'MoveLineEnd'"
  replace:
  - with:
      variables: [MatchCounter: "$MatchCounter + 1"]
      replace: [x: ".."]

# Table-related movement
# Typically, we need to zoom out to the mtd level, then we move the appropriate direction
- name: not-in-table


  tag: math
  match:
  - "$NavCommand='MoveCellPrevious' or $NavCommand='MoveCellNext' or"
  - "$NavCommand='MoveCellUp' or $NavCommand='MoveCellDown' or"
  - "$NavCommand='MoveColumnStart' or $NavCommand='MoveColumnEnd' or"
  - "$NavCommand='ReadCellCurrent'"
  replace:
  - t: "not in table"                               # phrase('not in table')
  - pause: long
  - set_variables: [SpeakExpression: "'false'"]

- name: move-cell-previous
  tag: mtd
  match: "$NavCommand='MoveCellPrevious'"
  replace:
  - test:
      if: "preceding-sibling::*"
      then:
      - test:
          if: "$NavVerbosity = 'Verbose'"
          then:
          - t: "move left"                        # phrase('move left')
          - pause: short
      - test:
          if: "$NavVerbosity != 'Terse'"
          then:
          - t: "column"                           # phrase(the first 'column' of the table)
          - x: "count(preceding-sibling::*)"
          - pause: medium
      - test:
          if: "$NavMode='Character'"
          then:
          - with:
              variables: [NavCommand: "'MovePreviousZoom'"]
              replace: [x: "preceding-sibling::*[1]"]
          else:
          - set_variables: [NavNode: "preceding-sibling::*[1]/*[1]/@id"]
      else:
      - t: "no previous column"                 # phrase('no previous column' in the table)
      - set_variables: [SpeakExpression: "'false'"]

- name: move-cell-next
  tag: mtd
  match: "$NavCommand='MoveCellNext'"
  replace:
  - test:
      if: "following-sibling::*"
      then:
      - test:
          if: "$NavVerbosity = 'Verbose'"
          then:
          - t: "move right"                     # phrase('move right')
          - pause: short
      - test:
          if: "$NavVerbosity != 'Terse'"
          then:
          - t: "column"                         # phrase(the first 'column' in the table)
          - x: "count(preceding-sibling::*)+2"
          - pause: medium
      - test:
          if: "$NavMode='Character'"
          then:
          - with:
              variables: [NavCommand: "'MoveNextZoom'"]
              replace: [x: "following-sibling::*[1]"]
          else:
          - set_variables: [NavNode: "following-sibling::*[1]/*[1]/@id"]
      else:
      - t: "no next column"                  # phrase('no next column' in the table)
      - set_variables: [SpeakExpression: "'false'"]

- name: move-cell-up
  tag: mtd
  match: "$NavCommand='MoveCellUp'"
  replace:
  - test:
      if: "../preceding-sibling::*"
      then:
      - with:
          variables: [Column: "count(preceding-sibling::*)+1"] # store this because otherwise the value is used in the wrong context below
          replace:
          - test:
              if: "$NavVerbosity = 'Verbose'"
              then:
              - t: "move up"                             # phrase('move up' to previous row in the table)
              - pause: short
          - test:
              if: "$NavVerbosity != 'Terse'"
              then:
              - t: "row"                                 # phrase(the previous 'row' in the table)
              - x: "count(../preceding-sibling::*)"
              - pause: short
              - t: "column"                              # phrase(the previous 'column' in the table)
              - x: "count(preceding-sibling::*)+1"
              - pause: medium
          - test:
              if: "$NavMode='Character'"
              then:
              - with:
                  variables: [NavCommand: "'MoveNextZoom'"]
                  replace: [x: "../preceding-sibling::*[1]/*[$Column]"]
              else:
              - set_variables: [NavNode: "../preceding-sibling::*[1]/*[$Column]/*[1]/@id"]
      else:
      - t: "no previous row"                            # phrase('no previous row' in the table)
      - set_variables: [SpeakExpression: "'false'"]

- name: move-cell-down
  tag: mtd
  match: "$NavCommand='MoveCellDown'"
  replace:
  - test:
      if: "../following-sibling::*"
      then:
      - with:
          variables: [Column: "count(preceding-sibling::*)+1"] # store this because otherwise the value is used in the wrong context below
          replace:
          - test:
              if: "$NavVerbosity = 'Verbose'"
              then:
              - t: "move down"                          # phrase('move down to the next row in the table)
              - pause: short
          - test:
              if: "$NavVerbosity != 'Terse'"
              then:
              - t: "row"                                # phrase(the next 'row' in the table)
              - x: "count(../preceding-sibling::*)+2"
              - pause: short
              - t: "column"                             # phrase(the next 'column' in the table)
              - x: "count(preceding-sibling::*)+1"
              - pause: medium
          - test:
              if: "$NavMode='Character'"
              then:
              - with:
                  variables: [NavCommand: "'MoveNextZoom'"]
                  replace: [x: "../following-sibling::*[1]/*[$Column]"]
              else:
              - set_variables: [NavNode: "../following-sibling::*[1]/*[$Column]/*[1]/@id"]
      else:
      - t: "no next row"                                # phrase('no next row' in the table)
      - set_variables: [SpeakExpression: "'false'"]

- name: move-cell-up
  tag: [mtr, mlabeledtr]
  match: "$NavCommand='MoveCellUp'"
  replace:
  - test:
      if: "$NavVerbosity = 'Verbose'"
      then:
      - t: "move to previous row"                              # phrase('move to previous row' to the previous row in the table)
      - pause: medium
  - test:
      if: "preceding-sibling::*"
      then:
      - test:
          if: "$NavMode='Character'"
          then:
          - with:
              variables: [NavCommand: "'MoveNextZoom'"]
              replace: [x: "preceding-sibling::*[1]"]
          else:
          - set_variables: [NavNode: "preceding-sibling::*[1]/@id"]
      else:
      - t: "no previous row"                          # phrase('no previous row' in the table)
      - set_variables: [SpeakExpression: "'false'"]

- name: move-cell-down
  tag: [mtr, mlabeledtr]
  match: "$NavCommand='MoveCellDown'"
  replace:
  - test:
      if: "$NavVerbosity = 'Verbose'"
      then:
      - t: "move to next row"                          # phrase('move to next row' to the next row in the table)
      - pause: medium
  - test:
      if: "following-sibling::*"
      then:
      - test:
          if: "$NavMode='Character'"
          then:
          - with:
              variables: [NavCommand: "'MoveNextZoom'"]
              replace: [x: "following-sibling::*[1]"]
          else:
          - set_variables: [NavNode: "following-sibling::*[1]/@id"]
      else:
      - t: "no next row"                          # phrase('no next row' in the table)
      - set_variables: [SpeakExpression: "'false'"]

- name: move-cell-previous
  # if a row is selected, there is no previous/next column, so this is trivial
  tag: [mtr, mlabeledtr]
  match: "$NavCommand='MoveCellPrevious'"
  replace:
  - test:
      if: "$NavVerbosity = 'Verbose'"
      then:
      - t: "move to previous column"                              # phrase('move to previous column' to the previous row in the table)
      - pause: medium
  - t: "no previous column"                          # phrase('no previous column' in the table)
  - set_variables: [SpeakExpression: "'false'"]

- name: move-cell-next
  # if a row is selected, there is no previous/next column, so this is trivial
  tag: [mtr, mlabeledtr]
  match: "$NavCommand='MoveCellNext'"
  replace:
  - test:
      if: "$NavVerbosity = 'Verbose'"
      then:
      - t: "move to next column"                              # phrase('move up' to the previous row in the table)
      - pause: medium
  - t: "no next column"                          # phrase('no next row' in the table)
  - set_variables: [SpeakExpression: "'false'"]

- name: default-read-cell
  tag: "*"
  match: "$NavCommand='ReadCellCurrent'"
  replace:
  - with:
      variables: [MTD: "ancestor::m:mtd"]
      replace:
      - test:
          if: "$MTD"
          then:
          - test:
              if: "$NavVerbosity = 'Verbose'"
              then:
              - t: "read current entry"                         # phrase('read current entry' in the table)
              - pause: medium
          - test:
              if: "$NavVerbosity != 'Terse'"
              then:
              - t: "row"                                        # phrase(the previous 'row' in the table)
              - x: "count($MTD[1]/../preceding-sibling::*)+1"
              - pause: short
              - t: "column"                                     # phrase(the previous 'column' in the table)
              - x: "count($MTD[1]/preceding-sibling::*)+1"
              - pause: short
          - set_variables: [NavNode: "$MTD[1]/*[1]/@id"]
          else:
          - t: "not in table"                                   # phrase('not in table' or matrix)
          - pause: long
          - set_variables: [SpeakExpression: "'false'"]

# mtd ? ( $NavCommand='MoveColumnStart' )
# => MoveColStart {
#       ruleRef = name(^^match);
#       column = index(match);
#       ::StartPosition = ^^match[0][index(match)][0].dfs;
#       ::EndPosition = ^^match[0][index(match)][0].offset;
#   };

# mtd ? ( $NavCommand='MoveColumnEnd' )
# => MoveColEnd {
#       ruleRef = name(^^match);
#       column = index(match);
#       ::StartPosition = ^^match[count(^^match)-1][index(match)][0].dfs;
#       ::EndPosition = ^^match[count(^^match)-1][index(match)][0].offset;
#   };



# # Rules for columnar math (mstack and mlongdiv) -- each row is an msrow or mscarries except for the start of mlongdiv
# # FIX:  not dealing with different number of digits on different lines
# # FIX:  not dealing with + (etc) on same line if they are on the right side (Dutch, others)
# # FIX:  not dealing with intervening msline (say it and move on??)
# # FIX:  not dealing with carries well
# # FIX:  not dealing with navigation of first three children of mlongdiv
# char ? ( has_parent(match, 2) && name(^match)=="mn" && name(^^match)=="msrow" &&
#         ($NavCommand="MovePrevious" || $NavCommand='MoveCellPrevious') && has_previous(match) )
# => MoveCell {
#       ruleRef = name(^^match);
#       wordRef = "previous";
#       ::StartPosition = previous(match).dfs;
#       ::EndPosition = previous(match).offset;
#   };

# # no previous child -- in first column -- don't move
# char ? ( has_parent(match, 2) && name(^match)=="mn" && name(^^match)=="msrow" &&
#         ($NavCommand="MovePrevious" || $NavCommand='MoveCellPrevious' ) )
# => MoveCell {
#       ruleRef = name(^^match);
#       wordRef = "previous";
#       childIndex = -1;                   # key to know what to say for each notation
#       ::SpeakAfterMove = false;
#   };

# char ? ( has_parent(match, 2) && name(^match)=="mn" && name(^^match)=="msrow" &&
#         ($NavCommand="MoveNext" || $NavCommand='MoveCellNext') && has_next(match) )
# => MoveCell {
#       ruleRef = name(^^match);
#       wordRef = "next";
#       ::StartPosition = next(match).dfs;
#       ::EndPosition = next(match).offset;
#   };

# # no next child -- in first column -- don't move
# char ? ( has_parent(match, 2) && name(^match)=="mn" && name(^^match)=="msrow" &&
#         ($NavCommand="MoveNext" || $NavCommand='MoveCellNext' ) )
# => MoveCell {
#       ruleRef = name(^^match);
#       wordRef = "next";
#       childIndex = -1;                   # key to know what to say for each notation
#       ::SpeakAfterMove = false;
#   };

# char ? ( has_parent(match, 2) && name(^match)=="mn" && name(^^match)=="msrow" &&
#         $NavCommand='MoveCellUp' && has_previous(^^match) )
# => MoveCell {
#       ruleRef = name(^^match);
#       wordRef = "up";
#       ::StartPosition = ^^^match[index(^^match)-1][-1][index(match)-count(^match)].dfs;
#       ::EndPosition = ^^^match[index(^^match)-1][-1][index(match)-count(^match)].offset;
#   };

# # no previous child -- in first column -- don't move
# char ? ( has_parent(match, 2) && name(^match)=="mn" && name(^^match)=="msrow" &&
#         $NavCommand='MoveCellUp' )
# => MoveCell {
#       ruleRef = name(^^match);
#       wordRef = "up";
#       childIndex = -1;                   # key to know what to say for each notation
#       ::SpeakAfterMove = false;
#   };

# char ? ( has_parent(match, 2) && name(^match)=="mn" && name(^^match)=="msrow" &&
#         $NavCommand='MoveCellDown' && has_next(^^match) )
# => MoveCell {
#       ruleRef = name(^^match);
#       wordRef = "down";
#       ::StartPosition = ^^^match[index(^^match)+1][-1][index(match)-count(^match)].dfs;
#       ::EndPosition = ^^^match[index(^^match)+1][-1][index(match)-count(^match)].offset;
#   };

# # no previous child -- in first column -- don't move
# char ? ( has_parent(match, 2) && name(^match)=="mn" && name(^^match)=="msrow" &&
#         $NavCommand='MoveCellDown' )
# => MoveCell {
#       ruleRef = name(^^match);
#       wordRef = "down";
#       childIndex = -1;                   # key to know what to say for each notation
#       ::SpeakAfterMove = false;
#   };

# char ? ( has_parent(match, 2) && name(^match)=="mn" && name(^^match)=="msrow" &&
#           $NavCommand='MoveColumnStart' )
# => MoveColStart {
#       ruleRef = name(^^match);
#       column = index(match);
#       ::StartPosition = ^^^match[0][-1][index(match)-count(^match)].dfs;
#       ::EndPosition = ^^^match[0][-1][index(match)-count(^match)].offset;
#   };

# char ? ( has_parent(match, 2) && name(^match)=="mn" && name(^^match)=="msrow" &&
#           $NavCommand='MoveColumnEnd' )
# => MoveColEnd {
#       ruleRef = name(^^match);
#       column = index(match);
#       ::StartPosition = ^^^match[count(^^^match)-1][-1][index(match)-count(^match)].dfs;
#       ::EndPosition = ^^^match[count(^^^match)-1][-1][index(match)-count(^match)].offset;
#   };

# any ? ( (name(match)=="mn" || name(match)=="none") &&
#         has_parent(match) && name(^match)=="mscarries" &&
#         ($NavCommand="MovePrevious" || $NavCommand='MoveCellPrevious') && has_previous(match) )
# => MoveCell {
#       ruleRef = name(^match);
#       wordRef = "previous";
#       ::StartPosition = previous(match).dfs;
#       ::EndPosition = previous(match).offset;
#   };

# # no previous child -- in first column -- don't move
# any ? ( (name(match)=="mn" || name(match)=="none") &&
#         has_parent(match) && name(^match)=="mscarries" &&
#         ($NavCommand="MovePrevious" || $NavCommand='MoveCellPrevious') )
# => MoveCell {
#       ruleRef = name(^match);
#       wordRef = "previous";
#       childIndex = -1;                   # key to know what to say for each notation
#       ::SpeakAfterMove = false;
#   };

# any ? ( (name(match)=="mn" || name(match)=="none") &&
#         has_parent(match) && name(^match)=="mscarries" &&
#         ($NavCommand="MoveNext" || $NavCommand='MoveCellNext') && has_next(match) )
# => MoveCell {
#       ruleRef = name(^match);
#       wordRef = "next";
#       ::StartPosition = next(match).dfs;
#       ::EndPosition = next(match).offset;
#   };

# # no next child -- in last column -- don't move
# any ? ( (name(match)=="mn" || name(match)=="none") &&
#         has_parent(match) && name(^match)=="mscarries" &&
#         ($NavCommand="MoveNext" || $NavCommand='MoveCellNext') )
# => MoveCell {
#       ruleRef = name(^match);
#       wordRef = "next";
#       childIndex = -1;                   # key to know what to say for each notation
#       ::SpeakAfterMove = false;
#   };

# any ? ( (name(match)=="mn" || name(match)=="none") &&
#         has_parent(match) && name(^match)=="mscarries" &&
#         $NavCommand='MoveCellUp' && has_previous(^match) )
# => MoveCell {
#       ruleRef = name(^match);
#       wordRef = "up";
#       ::StartPosition = ^^match[index(^match)-1][index(match)-count(^match)].dfs;
#       ::EndPosition = ^^match[index(^match)-1][index(match)-count(^match)].offset;
#   };

# # no previous child -- in first row -- don't move
# any ? ( (name(match)=="mn" || name(match)=="none") &&
#         has_parent(match) && name(^match)=="mscarries" &&
#         $NavCommand='MoveCellUp' )
# => MoveCell {
#       ruleRef = name(^match);
#       wordRef = "up";
#       childIndex = -1;                   # key to know what to say for each notation
#       ::SpeakAfterMove = false;
#   };

# any ? ( (name(match)=="mn" || name(match)=="none") &&
#         has_parent(match) && name(^match)=="mscarries" &&
#         $NavCommand='MoveCellDown' && has_next(^match) )
# => MoveCell {
#       ruleRef = name(^match);
#       wordRef = "down";
#       ::StartPosition = ^^match[index(^match)+1][-1][index(match)-count(^match)].dfs;
#       ::EndPosition = ^^match[index(^match)+1][-1][index(match)-count(^match)].offset;
#   };

# # no next child -- in last row -- don't move
# any ? ( (name(match)=="mn" || name(match)=="none") &&
#         has_parent(match) && name(^match)=="mscarries" &&
#         $NavCommand='MoveCellDown' )
# => MoveCell {
#       ruleRef = name(^match);
#       wordRef = "down";
#       childIndex = -1;                   # key to know what to say for each notation
#       ::SpeakAfterMove = false;
#   };

# mn ? ( has_parent(match, 2) && name(^match)=="mn" && name(^^match)=="msrow" &&
#           $NavCommand='MoveColumnStart' )
# => MoveColStart {
#       ruleRef = name(^^match);
#       column = index(match);
#       ::StartPosition = ^^^match[0][-1][index(match)-count(^match)].dfs;
#       ::EndPosition = ^^^match[0][-1][index(match)-count(^match)].offset;
#   };

# mn ? ( has_parent(match, 2) && name(^match)=="mn" && name(^^match)=="msrow" &&
#           $NavCommand='MoveColumnEnd' )
# => MoveColEnd {
#       ruleRef = name(^^match);
#       column = index(match);
#       ::StartPosition = ^^^match[count(^^^match)-1][-1][index(match)-count(^match)].dfs;
#       ::EndPosition = ^^^match[count(^^^match)-1][-1][index(match)-count(^match)].offset;
#   };



- name: default-cell-move

  tag: "*"
  match:
  - "$NavCommand='MoveCellPrevious' or $NavCommand='MoveCellNext' or"
  - "$NavCommand='MoveCellUp' or $NavCommand='MoveCellDown' or"
  - "$NavCommand='MoveColumnStart' or $NavCommand='MoveColumnEnd' or"
  - "$NavCommand='ReadCellCurrent'"
  replace:
  - test:
      if: "ancestor::m:mtd"
      then:
      - x: "ancestor::m:mtd[1]" # try again on an mtd node
      else:
      - t: "not in table"                                               # phrase('not in table' or matrix)
      - pause: long
      - set_variables: [SpeakExpression: "'false'"]

# ========  Move/Read/Describe Next rules =================

# skip 'none'
- name: move-next-none
  tag: [none, mprescripts]
  match: 
  - "($NavCommand = 'MoveNext' or $NavCommand = 'ReadNext' or $NavCommand = 'DescribeNext' or $NavCommand = 'DescribePrevious' or $NavCommand = 'MoveNextZoom') and"
  - "parent::*[1][name(.)='mmultiscripts'] and following-sibling::*"
  replace:
  - with:
      variables: [Following: "following-sibling::*[1]"]
      replace:
      # two 'none's in a row -- move over and try again; one 'none', zoom in on next 
      - test:
          if: "$Following[name(.)='none']"
          then: [x: "$Following"]
          else:
          - with:
              variables: [Move2D: "'in'", Child2D: "$Following"]        # phrase('in' the denominator)
              replace: [x: ".."]
          - with:
              variables: [NavCommand: "'MoveNextZoom'"]
              replace: [x: "$Following"]

# skip 'none'
- name: move-previous-none
  tag: [none, mprescripts]
  match: 
  - "($NavCommand = 'MovePrevious' or $NavCommand = 'ReadPrevious' or $NavCommand = 'DescribePrevious' or $NavCommand = 'MovePreviousZoom') and"
  - "parent::*[1][name(.)='mmultiscripts'] and preceding-sibling::*"
  replace:
  - with:
      variables: [Preceding: "preceding-sibling::*[1]"]
      replace:
      # two 'none's in a row -- move over and try again; one 'none', zoom in on preceding 
      - test:
          if: "$Preceding[name(.)='none']"
          then: [x: "$Preceding"]
          else:
          - with:
              variables: [Move2D: "'in'", Child2D: "$Preceding"]          # phrase('in' the denominator)
              replace: [x: ".."]
          - with:
              variables: [NavCommand: "'MovePreviousZoom'"]
              replace: [x: "$Preceding"]



# skip invisible chars except for Enhanced mode when "times" should be read
- name: move-next-invisible
  tag: "*"
  match:
  - "($NavCommand = 'MoveNext' or $NavCommand = 'ReadNext' or $NavCommand = 'DescribeNext') and"
  - "following-sibling::*[1][name(.)='mo' and translate(., '\u2061\u2062\u2063\u2064', '')='']"
  replace:
  - with:
      variables: [SayCommand: "string($NavVerbosity = 'Verbose')"]
      replace: [x: "."]
  - test:
      if: "following-sibling::*[1][.='\u2062' or .='\u2064'] and
           ($NavMode='Enhanced' or ($NavMode='Simple' and following-sibling::*[2][not(IsNode(., 'mathml'))]))"   # invisible times and plus
      then: [set_variables: [NavNode: "following-sibling::*[1]/@id"]]
      else: [x: "following-sibling::*[1]"]

- name: move-next-no-auto-zoom-at-edge
  # at edge of 2D and in a mode where moving right isn't an option
  tag: "*"
  variables: [EdgeNode: "EdgeNode(., 'right', '2D')"]
  match: "$NavCommand = 'MoveNext' and $NavMode!='Character' and not($AutoZoomOut) and $EdgeNode/@id!=@id"
  replace:
  - test:
      if: "$MatchCounter = 0 and $NavVerbosity = 'Verbose'"
      then:
      - t: "cannot move right"                              # phrase('cannot move right')
      - pause: medium
  - with:
      variables: [Move2D: "'end of'", Child2D: "$EdgeNode/*[last()]"]
      replace: [x: "$EdgeNode"]
  - pause: long
  - set_variables: [SpeakExpression: "'false'"]

- name: move-next-no-auto-zoom-at-edge-math
  # at edge of math -- no where to go (must be after we rule out being at the edge of 2D) because we want to speak that
  tag: "*"
  match:
  - "($NavCommand = 'MoveNext' or $NavCommand = 'ReadNext' or $NavCommand = 'DescribeNext') and"
  - "(self::m:math or name(EdgeNode(., 'right', 'math'))='math')" # at edge of math
  replace:
  - test:
      if: "$MatchCounter = 0 and $NavVerbosity = 'Verbose'"
      then:
      - t: "cannot"                                          # phrase('cannot' move right in expression)
      - test:
        - if: "$NavCommand = 'MoveNext'"
          then: [t: "move"]                                  # phrase('move' to next entry in table)     
        - else_if: "$NavCommand = 'ReadNext'"
          then: [t: "read"]                                  # phrase('read' next entry in table)     
          else: [t: "describe"]                              # phrase('describe' next entry in table)     
      - t: "right"                              # phrase(move 'right')
      - pause: short
  - t: "end of math"                              # phrase(move 'end of math')
  - pause: long
  - set_variables: [SpeakExpression: "'false'"]

- name: move-next-auto-zoom-up-one-level
  # Last child or in auto-zoom'd in-- move up a level and try again 
  # Note: we've already checked the for the case where we are at an edge and should not AutoZoomOut
  tag: "*"
  match:
  - "($NavCommand = 'MoveNext' or $NavCommand = 'ReadNext' or $NavCommand = 'DescribeNext') and"
  - "( not(following-sibling::*) or"
  - "  ( $NavMode='Enhanced' and "
  - "    count(following-sibling::*)=1 and (IsBracketed(.., '(', ')') or IsBracketed(.., '[', ']'))"
  - "  )"
  - ")"
  replace:
  - with:
      variables: [SayCommand: "string($NavVerbosity = 'Verbose')"]
      replace: [x: "."]
  - test:
      if: "following-sibling::*"
      then:
      - with:
          variables: [Move2D: "'in'", Child2D: "."]             # phrase('in' the denominator)
          replace: [x: ".."]
      else:
      - with:
          variables: [Move2D: "'out of'", Child2D: "."]
          replace: [x: ".."]
  - x: ".."

# At this point, if XXXNext, then we know there is must be a right sibling
- name: move-next-default
  tag: mtd
  match: "$Move2D = '' and ($NavCommand = 'MoveNext' or $NavCommand = 'ReadNext' or $NavCommand = 'DescribeNext')"
  replace:
  - with:
      variables: [SayCommand: "string($NavVerbosity = 'Verbose')"]
      replace: [x: "."]
  - test:
      if: "following-sibling::*"
      then:
      - test:
          if: "$NavVerbosity = 'Verbose'"
          then:
          - t: "column"                                       # phrase(the previous 'column' in the table)
          - x: "count(preceding-sibling::*)+2"
          - pause: short
      - test:
          if: "$NavMode = 'Character'"
          then:
          - with:
              variables: [NavCommand: "'MoveNextZoom'"]
              replace: [x: "following-sibling::*[1]"]
          else: [set_variables: [NavNode: "following-sibling::*[1]/*[1]/@id"]]
      else:
      - x: ".." # try again at the row level

- name: move-next-default
  tag: [mtr, mlabeledtr]
  match: "$Move2D = '' and
          ($NavCommand = 'MoveNext' or $NavCommand = 'ReadNext' or $NavCommand = 'DescribeNext') and
          following-sibling::*"
  replace:
  - with:
      variables: [SayCommand: "string($NavVerbosity = 'Verbose')"]
      replace: [x: "."]
  - test:
      if: "$NavMode = 'Character'"
      then:
      - with:
          variables: [NavCommand: "'MoveNextZoom'"]
          replace: [x: "following-sibling::*[1]"]
      else:
      - set_variables: [NavNode: "following-sibling::*[1]/@id"]

- name: move-next-auto-zoom-parens
  # auto-zoom into next child if next child is parenthesized expr
  tag: "*"
  match:
  - "($NavCommand = 'MoveNext' or $NavCommand = 'ReadNext' or $NavCommand = 'DescribeNext') and"
  - "$NavMode='Enhanced' and"
  - "parent::m:mrow and following-sibling::* and"
  - "following-sibling::*[1][self::m:mrow and count(*)=3 and " #exclude empty parens
  - "                        (IsBracketed(., '(', ')') or IsBracketed(., '[', ']'))"
  - "                       ]"
  replace:
  - with:
      variables: [SayCommand: "string($NavVerbosity = 'Verbose')"]
      replace: [x: "."]
  - set_variables: [NavNode: "following-sibling::*[1]/*[2]/@id"]

# normal cases for MoveNext
- name: move-next-locked-zoom-level
  # locked zoom level
  tag: "*"
  match:
  - "($NavCommand = 'MoveNext' or $NavCommand = 'ReadNext' or $NavCommand = 'DescribeNext') and"
  - "$ReadZoomLevel>=0"
  replace:
  - with:
      variables: [SayCommand: "string($NavVerbosity = 'Verbose')"]
      replace: [x: "."]
  - test:
      # if in base (nothing before), we must be moving to a script, so "in" will be said
      if: "preceding-sibling::* and following-sibling::*[1][name(.)='none']"
      then:
      - with:
          variables: [Move2D: "'out of'", Child2D: "."]
          replace: [x: ".."]
      - with:
          variables: [MatchCounter: "$MatchCounter + 1"]
          replace: [x: "following-sibling::*[1]"]          # skip over 'none'
      else:
      - with:
          variables: [Move2D: "'in'", Child2D: "following-sibling::*[1]"]       # phrase('in' the denominator)
          replace: [x: ".."]
      - with:
          variables: [MatchCounter: "$MatchCounter + 1", NavCommand: "'MoveNextZoom'"]
          replace: [x: "following-sibling::*[1]"]

- name: move-next-default
  tag: "*"
  match: "$NavCommand = 'MoveNext' or $NavCommand = 'ReadNext' or $NavCommand = 'DescribeNext'"
  replace:
  - with:
      variables: [SayCommand: "string($NavVerbosity = 'Verbose')"]
      replace: [x: "."]
  - test:
      if: "following-sibling::*[1][@data-from-mathml='none' or @data-from-mathml='mprescripts']"
      then: [x: "following-sibling::*[1]"]
      else:
      - test:
          if: "IsNode(.., '2D') or not(IsNode(.., 'mathml'))"
          then:
          - with:
              variables: [Move2D: "'in'", Child2D: "following-sibling::*[1]"]           # phrase('in' the denominator)
              replace: [x: ".."]
      - set_variables: [NavNode: "following-sibling::*[1]/@id"]

# ========  Move/Read/Describe Previous rules =================

# skip invisible chars except for Enhanced mode when "times" should be read
- name: move-previous-invisible
  tag: "*"
  match:
  - "($NavCommand = 'MovePrevious' or $NavCommand = 'ReadPrevious' or $NavCommand = 'DescribePrevious') and"
  - "preceding-sibling::*[1][name(.)='mo' and translate(., '\u2061\u2062\u2063\u2064', '')='']"
  replace:
  - with:
      variables: [SayCommand: "string($NavVerbosity = 'Verbose')"]
      replace: [x: "."]
  - with:
      variables: [MatchCounter: "$MatchCounter + 1"]
      replace:
      - test:
          if: "preceding-sibling::*[1][.='\u2062' or .='\u2064'] and $NavMode='Enhanced'"   # invisible times and plus
          then: [set_variables: [NavNode: "preceding-sibling::*[1]/@id"]]
          else: [x: "preceding-sibling::*[1]"]

# two rules for when can't move left
- name: move-previous-no-auto-zoom-at-edge
  # at edge of 2D and in a mode where moving left isn't an option
  tag: "*"
  variables: [EdgeNode: "EdgeNode(., 'left', '2D')"]
  match: "$NavCommand = 'MovePrevious' and $NavMode!='Character' and not($AutoZoomOut) and $EdgeNode/@id!=@id"
  replace:
  - test:
      if: "$MatchCounter = 0 and $NavVerbosity = 'Verbose' and $NavCommand = 'MovePrevious'"
      then:
      - t: "cannot move left"                                   # phrase('cannot move left' in expression)
      - pause: medium
  - with:
      variables: [Move2D: "'end of'", Child2D: "$EdgeNode/*[1]"]
      replace: [x: "$EdgeNode"]
  - pause: long

- name: move-previous-no-auto-zoom-at-edge-of-math
  # at edge of math -- no where to go (must be after we rule out being at the edge of 2D) because we want to speak that
  tag: "*"
  match:
  - "($NavCommand = 'MovePrevious' or $NavCommand = 'ReadPrevious' or $NavCommand = 'DescribePrevious') and"
  - "(self::m:math or name(EdgeNode(., 'left', 'math'))='math')"
  replace:
  - t: "start of math"                                              # phrase('start of math')
  - pause: long
  - set_variables: [SpeakExpression: "'false'"]

- name: move-previous-at-end
  tag: "*"
  match:
  - "($NavCommand = 'MovePrevious' or $NavCommand = 'ReadPrevious' or $NavCommand = 'DescribePrevious') and"
  - "name(EdgeNode(., 'left', 'math'))='math'" # at edge of math
  replace:
  - test:
      if: "$MatchCounter = 0 and $NavVerbosity = 'Verbose'"
      then:
      - t: "cannot move left"                      # phrase('cannot move left')
      - pause: short
  - with:
      variables: [Move2D: "'start of'", Child2D: "."]
      replace: [x: "."]
  - pause: long
  - set_variables: [SpeakExpression: "'false'"]

- name: move-previous-auto-zoom-up-one-level
  # Last child or in auto-zoom'd in-- move up a level and try again 
  # Note: we've already checked the for the case where we are at an edge and should not AutoZoomOut
  tag: "*"
  match:
  - "($NavCommand = 'MovePrevious' or $NavCommand = 'ReadPrevious' or $NavCommand = 'DescribePrevious') and"
  - "( not(preceding-sibling::*) or"
  - "  ( $NavMode='Enhanced' and "
  - "    count(preceding-sibling::*)=1 and (IsBracketed(.., '(', ')') or IsBracketed(.., '[', ']'))"
  - "  )"
  - ")"
  replace:
  - with:
      variables: [SayCommand: "string($NavVerbosity = 'Verbose')"]
      replace: [x: "."]
  - test:
      if: "preceding-sibling::*"
      then:
      - with:
          variables: [Move2D: "'in'", Child2D: "."]             # phrase('in' the denominator)
          replace: [x: ".."]
      else:
      - with:
          variables: [Move2D: "'out of'", Child2D: "."]
          replace: [x: ".."]
  - x: ".."

- name: move-previous-auto-zoom-parens
  # auto-zoom into previous child if previous child is parenthesized expr
  # Note: there is an asymmetry here from MoveNext because the base of a scripted might have parens for grouping, but not true for the script
  tag: "*"
  match:
  - "($NavCommand = 'MovePrevious' or $NavCommand = 'ReadPrevious' or $NavCommand = 'DescribePrevious') and"
  - "$NavMode='Enhanced' and"
  - "preceding-sibling::* and"
  - "(parent::m:mrow or parent::m:msub or parent::m:msup or"
  - " (count(preceding-sibling::*)=1 and (parent::m:msubsup or parent::m:mmultiscripts))"  # make sure moving into base
  - ") and"
  - "preceding-sibling::*[1][self::m:mrow and count(*)=3 and " #exclude empty parens
  - "       (IsBracketed(., '(', ')') or IsBracketed(., '[', ']'))"
  - "   ]"
  replace:
  - with:
      variables: [SayCommand: "string($NavVerbosity = 'Verbose')"]
      replace: [x: "."]
  - test:
      if: "not(parent::m:mrow)"
      then:
      - with:
          variables: [Move2D: "'in'", Child2D: "preceding-sibling::*[1]"]         # phrase('in' the denominator)
          replace: [x: ".."]
  - set_variables: [NavNode: "preceding-sibling::*[1]/*[2]/@id"]

# normal cases for MovePrevious

- name: move-previous-default
  tag: mtd
  match: "$Move2D = '' and
          ($NavCommand = 'MovePrevious' or $NavCommand = 'ReadPrevious' or $NavCommand = 'DescribePrevious') and
          preceding-sibling::*"
  replace:
  - with:
      variables: [SayCommand: "string($NavVerbosity = 'Verbose')"]
      replace: [x: "."]
  - test:
      if: "$NavVerbosity = 'Verbose'"
      then:
      - t: "column"                               # phrase(the first 'column' in the table)
      - x: "count(preceding-sibling::*)"
      - pause: short
  - test:
      if: "$NavMode = 'Character'"
      then:
      - with:
          variables: [NavCommand: "'MovePreviousZoom'"]
          replace: [x: "preceding-sibling::*[1]"]
      else: [set_variables: [NavNode: "preceding-sibling::*[1]/*[last()]/@id"]]

- name: move-previous-default
  tag: [mtr, mlabeledtr]
  match: "$Move2D = '' and ($NavCommand = 'MovePrevious' or $NavCommand = 'ReadPrevious' or $NavCommand = 'DescribePrevious')"
  replace:
  - with:
      variables: [SayCommand: "string($NavVerbosity = 'Verbose')"]
      replace: [x: "."]
  - test:
      if: "preceding-sibling::*"
      then:
      - test:
          if: "$NavMode = 'Character'"
          then:
          - with:
              variables: [NavCommand: "'MovePreviousZoom'"]
              replace: [x: "preceding-sibling::*[1]"]
          else:
          - set_variables: [NavNode: "preceding-sibling::*[1]/@id"]
      else: [x: ".."] # try again for after

- name: move-previous-locked-zoom-level
  # locked zoom level
  tag: "*"
  match:
  - "($NavCommand = 'MovePrevious' or $NavCommand = 'ReadPrevious' or $NavCommand = 'DescribePrevious') and"
  - "$ReadZoomLevel>=0"
  replace:
  - with:
      variables: [SayCommand: "string($NavVerbosity = 'Verbose')"]
      replace: [x: "."]
  - test:
      # if moving into base (nothing before), we must be moving to the base, so "in" will be said
      if: "count(preceding-sibling::*) > 2 and preceding-sibling::*[1][name(.)='none']"
      then:
      - with:
          variables: [Move2D: "'out of'", Child2D: "."]
          replace: [x: ".."]
      - x: "preceding-sibling::*[1]"          # skip over 'none'
      else:
      - with:
          variables: [Move2D: "'in'", Child2D: "preceding-sibling::*[1]"]               # phrase('in' the denominator)
          replace: [x: ".."]
      - with:
          variables: [NavCommand: "'MovePreviousZoom'"]
          replace: [x: "preceding-sibling::*[1]"]

- name: move-previous-default
  tag: "*"
  match: "$NavCommand = 'MovePrevious' or $NavCommand = 'ReadPrevious' or $NavCommand = 'DescribePrevious'"
  replace:
  - with:
      variables: [SayCommand: "string($NavVerbosity = 'Verbose')"]
      replace: [x: "."]
  - test:
      if: "preceding-sibling::*[1][@data-from-mathml='none' or @data-from-mathml='mprescripts']"
      then: [x: "preceding-sibling::*[1]"]
      else:
      - test:
          if: "IsNode(.., '2D') or not(IsNode(.., 'mathml'))"
          then:
          - with:
              variables: [Move2D: "'in'", Child2D: "preceding-sibling::*[1]"]           # phrase('in' the denominator)
              replace: [x: ".."]
      - set_variables: [NavNode: "preceding-sibling::*[1]/@id"]

# ********* ReadZoomLevel toggle ***************
# These set ::NavMode

- name: toggle-mode-up
  tag: "*"
  match: "$NavCommand = 'ToggleZoomLockUp'"
  replace:
  - test:
    - if: "$NavMode = 'Enhanced'"
      then:
      - t: "character"                                                # phrase(a mathematical 'character')
      - set_variables: [NavMode: "'Character'", ReadZoomLevel: "1"]
    - else_if: "$NavMode = 'Character'"
      then:
      - t: "simple"                                                   # phrase(a 'simple' way to do something)
      - set_variables: [NavMode: "'Simple'", ReadZoomLevel: "1"]
    - else:
      - t: "enhanced"                                                 # phrase(an 'enhanced' way to do something)
      - set_variables: [NavMode: "'Enhanced'", ReadZoomLevel: "-1"]
  - t: "mode"                                                         # phrase(a simple 'mode' of use)
  - pause: long
  - test:
    - if: "$NavMode != 'Enhanced'" # potentially need to zoom to the sibling
      then:
      - with:
          variables: [MatchCounter: "$MatchCounter + 1", NavCommand: "'MoveNextZoom'"]
          replace: [x: "."]

- name: toggle-mode-down
  tag: "*"
  match: "$NavCommand = 'ToggleZoomLockDown'"
  replace:
  - test:
    - if: "$NavMode = 'Enhanced'"
      then:
      - t: "simple"                                                 # phrase(an 'simple' way to do something)
      - set_variables: [NavMode: "'Simple'", ReadZoomLevel: "1"]
    - else_if: "$NavMode = 'Character'"
      then:
      - t: "enhanced"                                               # phrase(an 'enhanced' way to do something)
      - set_variables: [NavMode: "'Enhanced'", ReadZoomLevel: "-1"]
    - else:
      - t: "character"                                              # phrase(a mathematical 'character')
      - set_variables: [NavMode: "'Character'", ReadZoomLevel: "1"]
  - t: "mode"                                                       # phrase(a simple 'mode' of use)
  - pause: long
  - test:
    - if: "$NavMode != 'Enhanced'" # potentially need to zoom to the sibling
      then:
      - with:
          variables: [MatchCounter: "$MatchCounter + 1", NavCommand: "'MoveNextZoom'"]
          replace: [x: "."]

- name: toggle-speech-describe
  tag: "*"
  match: "$NavCommand = 'ToggleSpeakMode'"
  replace:
  - test:
      if: "$Overview = 'true'"
      then:
      - t: "speak expression after move"                # phrase('speak expression after move')
      - pause: long
      - set_variables: [Overview: "'false'"]
      else:
      - t: "overview of expression after move"          # phrase('overview of expression after move')
      - pause: long
      - set_variables: [Overview: "'true'"]

- name: current
  tag: "*"
  match: "$NavCommand = 'ReadCurrent' or $NavCommand = 'DescribeCurrent'"
  replace:
  - test:
      if: "$NavVerbosity = 'Verbose'"
      then:
      - test:
        - if: "$NavCommand = 'ReadCurrent'"
          then: [t: "read"]                                  # phrase('read' next entry in table) 
          else: [t: "describe"]                              # phrase('describe' next entry in table)     
      - t: "current"                                         # phrase('current' entry in table)
      - pause: long
  - set_variables: [NavNode: "@id"]

# this needs to be near the end because we only test for 'Describe', "Read", etc., and we don't want to get 'DescribeNext', etc.
- name: placemarker

  tag: "*"
  match:
  - "starts-with($NavCommand, 'Read') or "
  - "starts-with($NavCommand, 'Describe') or "
  - "starts-with($NavCommand, 'MoveTo')"
  replace:
  - test:
      if: "$NavVerbosity != 'Terse'"
      then:
      - test:
        - if: "starts-with($NavCommand, 'Read')"
          then: [t: "read"]                                  # phrase('read' next entry in table)     
        - else_if: "starts-with($NavCommand, 'Describe')"
          then: [t: "describe"]                              # phrase('describe' next entry in table)     
        - else_if: "starts-with($NavCommand, 'MoveTo')"
          then: [t: "move to "]                              # phrase('move to' the next entry in table) 
          else: [t: "set"]                                   # phrase('set' the value of the next entry in table) 
      - t: "placeholder"                                     # phrase('placeholder' for the value)
      - x: "$PlaceMarkerIndex"
      - pause: long
  - set_variables: [NavNode: "$PlaceMarker"]

- name: set-placemarker
  tag: "*"
  match: "starts-with($NavCommand, 'SetPlacemarker')"
  replace:
  - test:
      if: "$NavVerbosity != 'Terse'"
      then:
      - t: "set placeholder"                                 # phrase('set placeholder' to the value) 
      - x: "$PlaceMarkerIndex"
      - pause: long
  - set_variables: [NavNode: "@id"]

# ********* WhereAmI  ***************

# FIX: WhereAmI needs support from the Rust code to loop around and do speech at each iteration.
#      Alternatively, it could insert a special token that Rust code does a "replace" on with the speech (e.g. SPEECH_AT{id})
#      or a new command "speak" which takes a node id
- name: where-am-i-start
  tag: "*"
  match: "($NavCommand = 'WhereAmI' or $NavCommand = 'WhereAmIAll') and $MatchCounter = 0"
  replace:
  - translate: "@id"
  - pause: long
  - with:
      variables: [MatchCounter: "$MatchCounter + 1"]
      replace: [x: ".."]

- name: where-am-i-stop
  tag: "*"
  match:
  - "($NavCommand = 'WhereAmI' or $NavCommand = 'WhereAmIAll') and $MatchCounter > 0 and"
  # stopping conditions
  - "(self::m:math or "
  - " parent::*[self::m:math and (count(*)=1 or (count(*)=2 and *[2][.=',' or .='.' or .=';' or .='?']) ) ] "
  - ")"
  replace:
  - test:
      if: "$NavCommand = 'WhereAmI'"
      then:
      - t: "inside of nothing more"                         # phrase('inside of nothing more') 
      - pause: long
      - set_variables: [SpeakExpression: "'false'"]
      else:
      - t: "inside"                                         # phrase('inside' a big expression) 
      - pause: medium
  - set_variables: [NavNode: "@id"]

- name: where-am-i-middle
  tag: "*"
  match: "$NavCommand = 'WhereAmI' or $NavCommand = 'WhereAmIAll'"
  replace:
  - t: "inside"                                           # phrase('inside' a big expression)
  - pause: medium
  - test:
    - if: "$NavMode='Enhanced' and parent::*[self::m:mrow and (IsBracketed(., '(', ')', false) or IsBracketed(., '[', ']', false))]"
      then: [x: ".."] # auto-zoom up
    - else_if: "$NavCommand = 'WhereAmI'"
      then: [set_variables: [NavNode: "@id"]]
      else:
      - translate: "@id"
      - pause: long
      - x: '..'
