--- #default rules shared among several speech rules - name: default tag: math match: "." replace: - with: variables: - ClearSpeak_Fractions: "IfThenElse($Verbosity='Verbose' and $ClearSpeak_Fractions='Auto', 'EndFrac', $ClearSpeak_Fractions)" - ClearSpeak_AbsoluteValue: "IfThenElse($Verbosity='Verbose' and $ClearSpeak_AbsoluteValue='Auto', 'AbsEnd', $ClearSpeak_AbsoluteValue)" - ClearSpeak_Roots: "IfThenElse($Verbosity='Verbose' and $ClearSpeak_Roots='Auto', 'RootEnd', $ClearSpeak_Roots)" - ClearSpeak_Matrix: "IfThenElse($Verbosity='Verbose' and $ClearSpeak_Matrix='Auto', 'EndMatrix', $ClearSpeak_Matrix)" replace: - test: if: "$MathRate = 100" then: [x: "*"] else: - rate: value: "$MathRate" replace: [x: "*"] - name: empty-mrow tag: mrow match: "not(*)" replace: - T: "" # say nothing -- placeholder - name: default tag: mrow match: "." replace: - insert: nodes: "*" replace: [pause: auto] - name: default tag: mn match: "." replace: - bookmark: "@id" - x: "translate(., $BlockSeparators, '')" # remove digit block separators - name: default tag: [mo, mtext] match: "." replace: - bookmark: "@id" - x: "text()" - name: default tag: mi match: "." replace: - bookmark: "@id" - test: if: "string-length(.) = 1 and text() != '_'" # need unicode.tdl to kick in for single letter tokens then: [x: "text()"] else: [x: "translate(., '-_', ' ')" ] # from intent literals - name: default tag: ms match: "." replace: - T: "字串" # phrase('the string' is long) - pause: short - x: "text()" - name: default tag: mstyle match: "." replace: [x: "*"] - name: literal-simple # don't include nested fractions. E.g, fraction a plus b over c + 1 end fraction" is ambiguous # by simplistic SimpleSpeak's rules "b over c" is a fraction, but if we say nested fractions # are never simple, then any 'over' applies only to enclosing "fraction...end fraction" pair. tag: mfrac match: - "(IsNode(*[1],'leaf') and IsNode(*[2],'leaf')) and" - "not(ancestor::*[name() != 'mrow'][1]/self::m:fraction)" # FIX: can't test for mrow -- what should be used??? replace: - x: "*[2]" - T: "分之" # phrase("the fraction x 'over' y") - x: "*[1]" - pause: short - name: literal-default tag: mfrac match: "." replace: - T: "分數" # phrase("'start' fraction x over y end of fraction") - pause: short - x: "*[2]" - test: if: "not(IsNode(*[1],'leaf'))" then: [{pause: short}] - T: "分之" # phrase("the fraction x 'over' y") - test: if: "not(IsNode(*[2],'leaf'))" then: [{pause: short}] - x: "*[1]" - pause: short - T: "結束分數" # phrase("start of fraction x over y 'end over'") - pause: medium # not sure what really should be said for these since we should not assume they are square roots - name: literal-default tag: msqrt match: "." replace: - test: if: "$Verbosity!='Terse'" then: [T: ""] # phrase("'the' root of x") - T: "根號" - test: if: "$Verbosity!='Terse'" then: [T: ""] # phrase("the root 'of' x") - x: "*[1]" - pause: short - test: if: "not(IsNode(*[1],'leaf'))" then: [T: "結束根號"] # phrase("root of x 'end root symbol'") # not sure what really should be said for these since we should not assume they are square roots - name: literal-default tag: mroot match: "." replace: - test: if: "$Verbosity!='Terse'" then: [T: ""] # phrase("'the' root of x") - T: "根號" - T: "開方次數" # phrase("the root of x 'with index' 5") - x: "*[1]" - pause: short - test: if: "$Verbosity!='Terse'" then: [T: ""] # phrase("the root 'of' x") - x: "*[2]" - pause: short - test: if: "not(IsNode(*[2],'leaf'))" then: [T: "結束根號"] # phrase("root of x 'end root symbol'") - name: simple-sub tag: particular-value-of # invisible comma -- want "x sub 1 1" without "end sub" match: "count(*)=2 and (IsNode(*[2], 'leaf') or *[2][self::m:mrow][*[2][text()='⁣']])" replace: - x: "*[1]" - test: if: "$Verbosity!='Terse' or not(*[2][self::m:mn])" # just say "x 1" for terse vs "x sub 1" then: [T: "下標"] # phrase(x 'sub' 2) - x: "*[2]" - name: default tag: particular-value-of match: "count(*)=2" replace: - x: "*[1]" - T: "下標" # phrase(x 'sub' 2) - x: "*[2]" - T: "結束下標" # phrase(x sub 2 'end of sub') - pause: short - name: literal tag: msub match: "." replace: - x: "*[1]" - T: "下標" # phrase(x 'sub' 2) - x: "*[2]" - name: literal tag: [msup, msubsup] match: "." replace: - x: "*[1]" - test: if: "name(.)='msubsup'" then: - T: "下標" # phrase(x 'sub' 2) - x: "*[2]" - test: if: "*[last()][translate(., '′″‴⁗†‡°*', '')='']" then: [x: "*[last()]"] else_test: if: "ancestor-or-self::*[contains(@data-intent-property, ':literal:')]" # FIX: is this test necessary? then: - T: "上標" # phrase(x 'super' 2) - x: "*[last()]" - test: if: "not(IsNode(*[last()], 'simple'))" then: [T: "結束上標"] # phrase(x super 2 'end of super') else: - T: "的" # phrase(5 'raised to the' second power equals 25) - x: "*[last()]" - T: "次方" # phrase(5 raised to the second 'power' equals 25) - name: default tag: munder match: "." replace: - test: if: "not(IsNode(*[1], 'leaf'))" then: [T: ""] # phrase(phrase(x 'modified' with y above it) - x: "*[1]" - T: "下層" # phrase(x 'with' z below it) - x: "*[2]" - T: "" # phrase(x with z 'below' it) - name: diacriticals tag: mover match: "*[1][self::m:mi] and *[2][translate(., '\u0306\u030c.\u00A8\u02D9\u20DB\u20DC`^~→¯_', '')='']" replace: - x: "*[1]" - x: "*[2]" - name: default tag: mover match: "." replace: - test: if: "not(IsNode(*[1], 'leaf'))" then: [T: ""] # phrase(phrase(x 'modified' with y above it) - x: "*[1]" - test: if: "$Verbosity='Verbose'" then: [T: "上層"] - x: "*[2]" - T: "" # phrase(x modified 'with' y above it) - name: default tag: munderover match: "." replace: - test: if: "not(IsNode(*[1], 'leaf'))" then: [T: ""] # phrase(the equation has been 'modified') - x: "*[1]" - test: if: "$Verbosity='Verbose'" then: [T: "下層"] - x: "*[2]" - T: "上層" # phrase(x modified with y 'below and' y above it) - x: "*[3]" - T: "" # phrase(x modified with y 'above' it) - name: default # Here we support up to 2 prescripts and up to 4 postscripts -- that should cover all reasonable cases # If there are more, we just dump them out without regard to sup/super :-( # FIX: this could use more special cases # There is (currently) no way in MathCAT to deal with n-ary arguments other than "all" ('*') or an individual entry ('*[1]'). tag: mmultiscripts match: "." variables: # computing the number of postscripts is messy because of being optionally present -- we use "mod" to get the count right - Prescripts: "m:mprescripts/following-sibling::*" - NumChildren: "count(*)" # need to stash this since the count is wrong inside '*[...]' below - Postscripts: "*[position()>1 and position() < (last() + ($NumChildren mod 2) -count($Prescripts))]" replace: - x: "*[1]" - test: if: "$Prescripts" # more common case then: - with: variables: - PreSubscript: "IfThenElse($Verbosity='Verbose', '前下標', '前下標')" - PreSuperscript: "IfThenElse($Verbosity='Verbose', '前上標', '前上標')" replace: - test: # only bother announcing if there is more than one prescript if: "count($Prescripts) > 2" then: - T: "有" # phrase(substitute x 'with' y) - x: "count($Prescripts) div 2" - T: "前標" # phrase(in this equation certain 'prescripts' apply) - pause: short - test: if: "not($Prescripts[1][self::m:none])" then: - x: "$PreSubscript" - x: "$Prescripts[1]" - test: if: "not($Prescripts[1][self::m:none] or $Prescripts[2][self::m:none])" then: [T: "且"] # phrase(10 is greater than 8 'and' less than 15) - test: if: "not($Prescripts[2][self::m:none])" then: - x: "$PreSuperscript" - x: "$Prescripts[2]" - pause: short - test: if: "count($Prescripts) > 2" # more common case then: - test: if: "not($Prescripts[3][self::m:none])" then: - x: "$PreSubscript" - x: "$Prescripts[3]" - test: if: "not($Prescripts[3][self::m:none] or $Prescripts[4][self::m:none])" then: [T: "且"] # phrase(10 is grater than 8 'and' less than 15) - test: if: "not($Prescripts[4][self::m:none])" then: - x: "$PreSuperscript" - x: "$Prescripts[4]" - test: if: "count($Prescripts) > 4" # give up and just dump them out so at least the content is there then: - T: "與交替前標" # phrase(in this case there are values 'and alternating prescripts') - x: "$Prescripts[position() > 4]" - T: "結束前標" # phrase(This is where 'end prescripts' occurs) - test: if: "$Postscripts" then: - with: variables: - PostSubscript: "IfThenElse($Verbosity='Verbose', '下標', '下標')" - PostSuperscript: "IfThenElse($Verbosity='Verbose', '上標', '上標')" replace: - test: # only bother announcing if there is more than one postscript if: "count($Postscripts) > 2" then: - test: if: "$Prescripts" then: [T: "且"] # phrase(10 is greater than 8 'and' less than 15) - T: "有" # phrase(substitute x 'with' y) - x: "count($Postscripts) div 2" - T: "後標" # phrase(this material includes several 'postscripts') - pause: short - test: if: "not($Postscripts[1][self::m:none])" then: - x: "$PostSubscript" - x: "$Postscripts[1]" - test: if: "not($Postscripts[1][self::m:none] or $Postscripts[2][self::m:none])" then: [T: "且"] # phrase(10 is greater than 8 'and' less than 15) - test: if: "not($Postscripts[2][self::m:none])" then: - x: "$PostSuperscript" - x: "$Postscripts[2]" - test: if: "count($Postscripts) > 2" then: - test: if: "not($Postscripts[3][self::m:none])" then: - x: "$PostSubscript" - x: "$Postscripts[3]" - test: if: "not($Postscripts[3][self::m:none] or $Postscripts[4][self::m:none])" then: [T: "且"] # phrase(10 is greater than 8 'and' less than 15) - test: if: "not($Postscripts[4][self::m:none])" then: - x: "$PostSuperscript" - x: "$Postscripts[4]" - test: if: "count($Postscripts) > 4" then: - test: if: "not($Postscripts[5][self::m:none])" then: - x: "$PostSubscript" - x: "$Postscripts[5]" - test: if: "not($Postscripts[5][self::m:none] or $Postscripts[6][self::m:none])" then: [T: "且"] # phrase(10 is greater than 8 'and' less than 15) - test: if: "not($Postscripts[6][self::m:none])" then: - x: "$PostSuperscript" - x: "$Postscripts[6]" - test: if: "count($Postscripts) > 6" then: - test: if: "not($Postscripts[7][self::m:none])" then: - x: "$PostSubscript" - x: "$Postscripts[7]" - test: if: "not($Postscripts[7][self::m:none] or $Postscripts[8][self::m:none])" then: [T: "且"] # phrase(10 is less than 15 'and' greater than 5) - test: if: "not($Postscripts[8][self::m:none])" then: - x: "$PostSuperscript" - x: "$Postscripts[8]" - test: if: "count($Postscripts) > 8" # give up and just dump them out so at least the content is there then: - T: "與交替後標" # phrase(this situation involves complexities 'and alternating scripts') - x: "$Postscripts[position() > 8]" - T: "結束後標" # phrase(At this point 'end scripts' occurs) - name: default tag: mtable variables: [IsColumnSilent: false()] match: "." replace: - T: "表" # phrase(the 'table with' 3 rows) - x: count(*) - test: if: count(*)=1 then: [T: "列"] # phrase(the table with 1 'row') else: [T: "列"] # phrase(the table with 3 'rows') - T: "與" # phrase(the table with 3 rows 'and' 4 columns) - x: "count(*[1]/*)" - test: if: "count(*[1]/*)=1" then: [T: "行"] # phrase(the table with 3 rows and 1 'column') else: [T: "行"] # phrase(the table with 3 rows and 4 'columns') - pause: long - x: "*" - name: default # callers/context should do that. # this may get called from navigation -- in that case, there is no context to speak the row #, so don't do it tag: [mtr, mlabeledtr] match: "." replace: - T: "列" # phrase(the first 'row' of a matrix) - x: "count(preceding-sibling::*)+1" - test: if: .[self::m:mlabeledtr] then: - T: "帶有標籤" # phrase(the line 'with label' first equation) - x: "*[1]/*" - pause: short - pause: medium - test: if: .[self::m:mlabeledtr] then: [{x: "*[position()>1]"}] else: {x: "*"} - name: default tag: mtd match: "." replace: - test: # ClearSpeak normally speaks "column 1" even though it says the row number, which is a waste... # The following is commented out but the count(...)!=0 probably belongs in other rule sets # if: not($IsColumnSilent) and ($ClearSpeak_Matrix = 'SpeakColNum' or count(preceding-sibling::*) != 0) if: "not($IsColumnSilent)" then: - T: "行" # phrase(the first 'column' of the matrix) - x: "count(preceding-sibling::*)+1" - pause: medium - x: "*" - test: # short pause after each element; medium pause if last element in a row; long pause for last element in matrix - if: count(following-sibling::*) > 0 then: {pause: short} - else_if: count(../following-sibling::*) > 0 then: {pause: medium} else: {pause: long} - name: empty-box # The ordering below is the order in which words come out when there is more than one value # Note: @notation can contain more than one value tag: menclose match: "@notation='box' and *[self::m:mtext and text()=' ']" replace: - T: "空盒子" # phrase(the 'empty box' contains no values) - name: default # The ordering below is the order in which words come out when there is more than one value # Note: @notation can contain more than one value tag: menclose match: "." replace: - test: if: ".[contains(concat(' ', normalize-space(@notation), ' '), ' box ')]" then: [T: "盒子", pause: short] # phrase(the 'box' around the expression) - test: if: ".[contains(@notation,'roundedbox')]" then: [T: "圓盒", pause: short] # phrase(the 'round box' around the expression) - test: if: ".[contains(@notation,'circle')]" then: [T: "圈圈", pause: short] # phrase(the 'circle' around the expression) - test: if: ".[ contains(concat(' ', normalize-space(@notation), ' '), ' left ') or contains(concat(' ', normalize-space(@notation), ' '), ' right ') or contains(@notation,'top') or contains(@notation,'bottom') ]" then: - T: "線" # phrase(draw a straight 'line' on the page) - test: if: ".[contains(concat(' ', normalize-space(@notation), ' '), ' left ')]" then: [T: "左邊", pause: short] # phrase(line on 'left' of the expression) - test: if: ".[contains(concat(' ', normalize-space(@notation), ' '), ' right ')]" then: [T: "右邊", pause: short] # phrase(line on 'right' of the expression) - test: if: ".[contains(@notation,'top')]" then: [T: "頂部", pause: short] # phrase(line on 'top' of the expression) - test: if: ".[contains(@notation,'bottom')]" then: [T: "底部", pause: short] # phrase(line on the 'bottom' of the expression) - test: if: ".[ contains(@notation,'updiagonalstrike') or contains(@notation,'downdiagonalstrike') or contains(@notation,'verticalstrike') or contains(@notation,'horizontalstrike') ]" then: - test: if: ".[contains(@notation,'updiagonalstrike') and contains(@notation,'downdiagonalstrike')]" then: [SPELL: "'叉叉'", pause: short] # seems better to say 'x cross out' than 'up diagonal, down diagonal cross out' else: - test: if: ".[contains(@notation,'updiagonalstrike')]" then: [T: "右上對角線", pause: short] # phrase(the line runs 'up diagonal') - test: if: ".[contains(@notation,'downdiagonalstrike')]" then: [T: "右下對角線", pause: short] # phrase(the line runs 'down diagonal') - test: if: ".[contains(@notation,'verticalstrike')]" then: [T: "垂直", pause: short] # phrase(the line is 'vertical') - test: if: ".[contains(@notation,'horizontalstrike')]" then: [T: "水平", pause: short] # phrase(the line is 'horizontal') - T: "劃掉" # phrase(please 'cross out' the incorrect answer) - pause: short - test: if: ".[contains(@notation,'uparrow')]" then: [T: "向上箭頭", pause: short] # phrase(direction is shown by the 'up arrow') - test: if: ".[contains(concat(' ', normalize-space(@notation), ' '), ' downarrow ')]" then: [T: "向下箭頭", pause: short] # phrase(the trend is shown by the 'down arrow') - test: if: ".[contains(@notation,'leftarrow')]" then: [T: "左箭頭", pause: short] # phrase(the 'left arrow' indicates going back) - test: if: ".[contains(concat(' ', normalize-space(@notation), ' '), ' rightarrow ')]" then: [T: "右箭頭", pause: short] # phrase(the 'right arrow' indicates moving forward) - test: if: ".[contains(@notation,'northeastarrow')]" then: [T: "右上箭頭", pause: short] # phrase(direction is indicated by the 'northeast arrow') - test: if: ".[contains(concat(' ', normalize-space(@notation), ' '), ' southeastarrow ')]" then: [T: "右下箭頭", pause: short] # phrase(direction is shown by the 'southeast arrow') - test: if: ".[contains(concat(' ', normalize-space(@notation), ' '), ' southwestarrow ')]" then: [T: "左下箭頭", pause: short] # phrase(direction is shown by the 'southwest arrow') - test: if: ".[contains(@notation,'northwestarrow')]" then: [T: "左上箭頭", pause: short] # phrase(direction is shown by the 'northwest arrow') - test: if: ".[contains(@notation,'updownarrow')]" then: [T: "垂直上下箭頭", pause: short] # phrase(upward movement is indicated by the 'double ended vertical arrow') - test: if: ".[contains(@notation,'leftrightarrow')]" then: [T: "水平左右箭頭", pause: short] # phrase(progress is indicated by the 'double ended horizontal arrow') - test: if: ".[contains(@notation,'northeastsouthwestarrow')]" then: [T: "左上兩端箭頭", pause: short] # phrase(trend is indicated by the 'double ended up diagonal arrow') - test: if: ".[contains(@notation,'northwestsoutheastarrow')]" then: [T: "右下兩端箭頭", pause: short] # phrase(trend is indicated by the 'double ended down diagonal arrow') - test: if: ".[contains(@notation,'actuarial')]" then: [T: "精算符號", pause: short] # phrase(the 'actuarial symbol' represents a specific quantity) - test: if: ".[contains(@notation,'madrub')]" then: [T: "阿拉伯階乘符號", pause: short] # phrase(the 'arabic factorial symbol' represents a factorial operation) - test: if: ".[contains(@notation,'phasorangle')]" then: [T: "相角", pause: short] # phrase(the 'phasor angle' is used to measure electrical current) - test: if: ".[contains(@notation,'longdiv') or not(@notation) or normalize-space(@notation) ='']" # default then: [T: "長除符號", pause: short] # phrase(the 'long division symbol' indicates a long division calculation) - test: if: ".[contains(@notation,'radical')]" then: [T: "平方根", pause: short] # phrase(5 is the 'square root' of 25) - T: "圍住" # phrase(parentheses are 'enclosing' part of the equation) - test: if: "*[self::m:mtext and text()=' ']" then: [T: "空白"] # otherwise there is complete silence # phrase(there is a 'space' between the words) else: [x: "*"] - test: if: "$Impairment = 'Blindness' and ( $SpeechStyle != 'SimpleSpeak' or not(IsNode(*[1], 'leaf')) )" then: [T: "結束圍住"] # phrase(reached the 'end enclosure' point) - pause: short - name: semantics tag: "semantics" match: "*[@encoding='MathML-Presentation']" replace: - x: "*[@encoding='MathML-Presentation']/*[1]" - name: semantics-default tag: "semantics" match: . replace: - x: "*[1]" - name: apply-function tag: "apply-function" match: . replace: - x: "*[1]" - T: "作用到" # phrase(the function sine 'applied to' x plus y) - x: "*[2]" # Here are the intent hints that need to be handled: 'prefix' | 'infix' | 'postfix' | 'function' | 'silent' - name: silent-intent # uncaught intent -- speak as arg1 arg2 .... tag: "*" match: "contains(@data-intent-property, ':silent:') and count(*)>0" replace: - x: "*" - name: prefix-intent # uncaught intent -- speak as arg1 arg2 .... tag: "*" match: "contains(@data-intent-property, ':prefix:') and count(*)>0" replace: - x: "translate(name(.), '-_', ' ')" - x: "*" - pause: short - name: postfix-intent # uncaught intent -- speak as arg1 arg2 .... tag: "*" match: "contains(@data-intent-property, ':postfix:') and count(*)>0" replace: - pause: short - x: "*" - x: "translate(name(.), '-_', ' ')" - name: infix-intent # uncaught intent -- speak as foo of arg1 comma arg2 .... tag: "*" match: "contains(@data-intent-property, ':infix:') and count(*)>0" replace: - pause: short - insert: nodes: "*" replace: [x: "translate(name(.), '-_', ' ')", pause: auto] - pause: short - name: function-intent # uncaught intent -- speak as foo of arg1 comma arg2 .... tag: "*" match: count(*)>0 replace: - x: "translate(name(.), '-_', ' ')" - T: "" # phrase(sine 'of' 5) - pause: short - insert: nodes: "*" replace: [T: "逗號", pause: auto] # phrase(f of x 'comma' y) - name: default-text # unknown leaf -- just speak the text -- could be a literal intent tag: "*" match: "." replace: - x: "translate(name(), '-_', ' ')"