diff --git a/Rules/Braille/Russian/Russian_Rules.yaml b/Rules/Braille/Russian/Russian_Rules.yaml index a691101e8..0f91068ec 100644 --- a/Rules/Braille/Russian/Russian_Rules.yaml +++ b/Rules/Braille/Russian/Russian_Rules.yaml @@ -1,6 +1,45 @@ --- # Russian mathematical braille rules. +- + name: flat-vector-arrow-postfix-operator + tag: mo + match: + - "not(following-sibling::*) and" + - "translate(., '→⟶⇀⃗', '')='' and" + - "count(preceding-sibling::*[not(self::m:mo[.='⁡' or .='⁢' or .='⁣' or .='⁤'])]) > 0" + replace: + - test: + if: "count(preceding-sibling::*[not(self::m:mo[.='⁡' or .='⁢' or .='⁣' or .='⁤'])]) > 1 or string-length(preceding-sibling::*[not(self::m:mo[.='⁡' or .='⁢' or .='⁣' or .='⁤'])][1]) > 1" + then: [t: "⠨⠒⠂"] + else: [t: "⠒⠂"] + +- + name: flat-vector-bar-postfix-operator + tag: mo + match: + - "not(following-sibling::*) and" + - "translate(., '¯‾_ ̄', '')='' and" + - "count(preceding-sibling::*[not(self::m:mo[.='⁡' or .='⁢' or .='⁣' or .='⁤'])]) > 0" + replace: + - test: + if: "count(preceding-sibling::*[not(self::m:mo[.='⁡' or .='⁢' or .='⁣' or .='⁤'])]) > 1 or string-length(preceding-sibling::*[not(self::m:mo[.='⁡' or .='⁢' or .='⁣' or .='⁤'])][1]) > 1" + then: [t: "⠨⠒"] + else: [t: "⠒"] + +- + name: flat-geometry-line-double-arrow-postfix-operator + tag: mo + match: + - "not(following-sibling::*) and" + - "translate(., '↔⟷', '')='' and" + - "count(preceding-sibling::*[not(self::m:mo[.='⁡' or .='⁢' or .='⁣' or .='⁤'])]) > 0" + replace: + - test: + if: "count(preceding-sibling::*[not(self::m:mo[.='⁡' or .='⁢' or .='⁣' or .='⁤'])]) > 1 or string-length(preceding-sibling::*[not(self::m:mo[.='⁡' or .='⁢' or .='⁣' or .='⁤'])][1]) > 1" + then: [t: "⠨⠒⠂⠂"] + else: [t: "⠒⠂⠂"] + - name: whitespace tag: "!*" @@ -27,6 +66,88 @@ match: "contains(@intent, ':blank')" replace: [t: "⠀"] +- + name: omitted-digit-in-number + tag: mrow + match: + - "count(*)=3 and" + - "*[1][self::m:mn and translate(., '0123456789', '')=''] and" + - "*[2][contains(@intent, ':blank')] and" + - "*[3][self::m:mn and translate(., '0123456789', '')='']" + replace: + - x: "*[1]" + - t: "⠬" + - x: "BrailleChars(translate(*[3], '0123456789', '\ue020\ue021\ue022\ue023\ue024\ue025\ue026\ue027\ue028\ue029'), 'Russian')" + +- + name: flat-vector-arrow-postfix-math + tag: math + match: + - "count(*) >= 2 and" + - "*[last()][self::m:mo and translate(., '→⟶⇀⃗', '')=''] and" + - "not(*[position() < last()][not(self::m:mi or self::m:msub or self::m:mo[.='⁡' or .='⁢' or .='⁣' or .='⁤'])])" + variables: + - RowStart: "''" + - RowEnd: "''" + - InSystemLeftBrace: "false()" + - InSystemRightBrace: "false()" + - InChemElementGroup: "false()" + - InColumnArithmetic: "false()" + - InLongDivision: "false()" + - MatchingWhitespace: "false()" + replace: + - x: "*[position() < last() and not(self::m:mo[.='⁡' or .='⁢' or .='⁣' or .='⁤'])]" + - test: + if: "count(*[position() < last() and not(self::m:mo[.='⁡' or .='⁢' or .='⁣' or .='⁤'])]) > 1" + then: [t: "⠨⠒⠂"] + else: [t: "⠒⠂"] + +- + name: flat-vector-bar-postfix-math + tag: math + match: + - "count(*) >= 2 and" + - "*[last()][self::m:mo and translate(., '¯‾_ ̄', '')=''] and" + - "not(*[position() < last()][not(self::m:mi or self::m:msub or self::m:mo[.='⁡' or .='⁢' or .='⁣' or .='⁤'])])" + variables: + - RowStart: "''" + - RowEnd: "''" + - InSystemLeftBrace: "false()" + - InSystemRightBrace: "false()" + - InChemElementGroup: "false()" + - InColumnArithmetic: "false()" + - InLongDivision: "false()" + - MatchingWhitespace: "false()" + replace: + - x: "*[position() < last() and not(self::m:mo[.='⁡' or .='⁢' or .='⁣' or .='⁤'])]" + - test: + if: "count(*[position() < last() and not(self::m:mo[.='⁡' or .='⁢' or .='⁣' or .='⁤'])]) > 1" + then: [t: "⠨⠒"] + else: [t: "⠒"] + +- + name: flat-geometry-line-double-arrow-postfix-math + tag: math + match: + - "count(*) >= 2 and" + - "*[last()][self::m:mo and translate(., '↔⟷', '')=''] and" + - "not(*[position() < last()][not(self::m:mi or self::m:msub or self::m:mo[.='⁡' or .='⁢' or .='⁣' or .='⁤'])])" + variables: + - RowStart: "''" + - RowEnd: "''" + - InSystemLeftBrace: "false()" + - InSystemRightBrace: "false()" + - InChemElementGroup: "false()" + - InColumnArithmetic: "false()" + - InLongDivision: "false()" + - MatchingWhitespace: "false()" + replace: + - x: "*[position() < last() and not(self::m:mo[.='⁡' or .='⁢' or .='⁣' or .='⁤'])]" + - test: + if: "count(*[position() < last() and not(self::m:mo[.='⁡' or .='⁢' or .='⁣' or .='⁤'])]) > 1" + then: [t: "⠨⠒⠂⠂"] + else: [t: "⠒⠂⠂"] + - name: unicode-override tag: "*" @@ -41,6 +162,10 @@ - RowStart: "''" - RowEnd: "''" - InSystemLeftBrace: "false()" + - InSystemRightBrace: "false()" + - InChemElementGroup: "false()" + - InColumnArithmetic: "false()" + - InLongDivision: "false()" - MatchingWhitespace: "false()" replace: [x: "*"] @@ -50,12 +175,188 @@ match: "not(preceding-sibling::*) and following-sibling::*[1][self::m:mtable] and count(../*)=2" replace: [t: ""] +- + name: system-right-brace-closer + tag: mo + match: "not(following-sibling::*) and preceding-sibling::*[1][self::m:mtable] and count(../*)=2" + replace: [t: ""] + +- + name: chemistry-single-letter-element-group + tag: mrow + match: + - "(@data-chem-formula or ancestor::*[@data-chem-equation or contains(@intent, ':chemical-equation')]) and" + - "count(*[not(self::m:mo[.='⁡' or .='⁢' or .='⁣' or .='⁤'])]) > 1 and" + - "count(*[not(self::m:mo[.='⁡' or .='⁢' or .='⁣' or .='⁤'])]) = count(*[self::m:mi[@data-chem-element='1']])" + replace: + - t: "⠸" + - with: + variables: [InChemElementGroup: "true()"] + replace: [x: "*"] + +- + name: canonical-prefix-geometry-op + tag: mrow + match: + - "count(*)=3 and" + - "*[1][(self::m:mo or self::m:mi) and (.='∠' or .='△' or .='∪')] and" + - "*[2][self::m:mo and .='⁡']" + replace: + - test: + - if: "*[1][.='∪']" + then: [t: "⠸⠜"] + - else: + - x: "*[1]/text()" + - t: "#" + - x: "*[3]" + +- + name: geometry-prefix-after-canonical-multiplier + tag: mrow + match: + - "count(*)=3 and" + - "*[2][self::m:mo and (.='⁢' or .='⁤')] and" + - "*[3][self::m:mrow and *[1][(self::m:mo or self::m:mi) and (.='∠' or .='△' or .='∪')]]" + replace: + - x: "*[1]" + - t: "⠄" + - x: "*[3]" + +- + name: flat-vector-arrow-postfix + tag: mrow + match: + - "count(*) >= 2 and" + - "*[last()][self::m:mo and translate(., '→⟶⇀⃗', '')=''] and" + - "not(*[position() < last()][not(self::m:mi or self::m:msub or self::m:mo[.='⁡' or .='⁢' or .='⁣' or .='⁤'])])" + replace: + - x: "*[position() < last() and not(self::m:mo[.='⁡' or .='⁢' or .='⁣' or .='⁤'])]" + - test: + if: "count(*[position() < last() and not(self::m:mo[.='⁡' or .='⁢' or .='⁣' or .='⁤'])]) > 1" + then: [t: "⠨⠒⠂"] + else: [t: "⠒⠂"] + +- + name: flat-vector-bar-postfix + tag: mrow + match: + - "count(*) >= 2 and" + - "*[last()][self::m:mo and translate(., '¯‾_ ̄', '')=''] and" + - "not(*[position() < last()][not(self::m:mi or self::m:msub or self::m:mo[.='⁡' or .='⁢' or .='⁣' or .='⁤'])])" + replace: + - x: "*[position() < last() and not(self::m:mo[.='⁡' or .='⁢' or .='⁣' or .='⁤'])]" + - test: + if: "count(*[position() < last() and not(self::m:mo[.='⁡' or .='⁢' or .='⁣' or .='⁤'])]) > 1" + then: [t: "⠨⠒"] + else: [t: "⠒"] + +- + name: flat-geometry-line-double-arrow-postfix + tag: mrow + match: + - "count(*) >= 2 and" + - "*[last()][self::m:mo and translate(., '↔⟷', '')=''] and" + - "not(*[position() < last()][not(self::m:mi or self::m:msub or self::m:mo[.='⁡' or .='⁢' or .='⁣' or .='⁤'])])" + replace: + - x: "*[position() < last() and not(self::m:mo[.='⁡' or .='⁢' or .='⁣' or .='⁤'])]" + - test: + if: "count(*[position() < last() and not(self::m:mo[.='⁡' or .='⁢' or .='⁣' or .='⁤'])]) > 1" + then: [t: "⠨⠒⠂⠂"] + else: [t: "⠒⠂⠂"] + +- + name: periodic-decimal-row + tag: mrow + match: + - "count(*)=4 and" + - "*[1][self::m:mn and contains(., ',') and translate(., '0123456789,', '')=''] and" + - "*[2][self::m:mo and .='('] and" + - "*[3][self::m:mn and translate(., '0123456789', '')=''] and" + - "*[4][self::m:mo and .=')']" + replace: + - x: "*[1]" + - t: "⠣" + - x: "BrailleChars(translate(string(*[3]), '0123456789', '\ue020\ue021\ue022\ue023\ue024\ue025\ue026\ue027\ue028\ue029'), 'Russian')" + - t: "⠜" + +- + name: mixed-number-simple-fraction-with-invisible-times + tag: mrow + match: + - "count(*)=3 and" + - "*[1][self::m:mn and translate(., '0123456789', '')=''] and" + - "*[2][self::m:mo and (.='⁢' or .='⁤')] and" + - "*[3][self::m:mfrac] and" + - "*[3]/*[1][self::m:mn and translate(., '0123456789', '')=''] and" + - "*[3]/*[2][self::m:mn and translate(., '0123456789', '')='']" + replace: + - x: "*[1]" + - t: "#" + - x: "*[3]" + +- + name: mixed-number-simple-fraction + tag: mrow + match: + - "count(*)=2 and" + - "*[1][self::m:mn and translate(., '0123456789', '')=''] and" + - "*[2][self::m:mfrac] and" + - "*[2]/*[1][self::m:mn and translate(., '0123456789', '')=''] and" + - "*[2]/*[2][self::m:mn and translate(., '0123456789', '')='']" + replace: + - x: "*[1]" + - t: "#" + - x: "*[2]" + +- + name: mixed-number-with-arithmetic-fraction-with-invisible-times + tag: mrow + match: + - "count(*)=3 and" + - "*[1][self::m:mn and translate(., '0123456789', '')=''] and" + - "*[2][self::m:mo and (.='⁢' or .='⁤')] and" + - "*[3][self::m:mfrac] and" + - "not(*[3]/*[1][self::m:mn and translate(., '0123456789', '')=''] and *[3]/*[2][self::m:mn and translate(., '0123456789', '')=''])" + replace: + - x: "*[1]" + - t: "⠐" + - x: "*[3]" + +- + name: mixed-number-with-arithmetic-fraction + tag: mrow + match: + - "count(*)=2 and" + - "*[1][self::m:mn and translate(., '0123456789', '')=''] and" + - "*[2][self::m:mfrac] and" + - "not(*[2]/*[1][self::m:mn and translate(., '0123456789', '')=''] and *[2]/*[2][self::m:mn and translate(., '0123456789', '')=''])" + replace: + - x: "*[1]" + - t: "⠐" + - x: "*[2]" + - name: default tag: [mrow, mstyle, semantics] match: "." replace: [x: "*[1] | *[position()>1]"] +- + name: mspace + tag: mspace + match: "@linebreak='newline' or @linebreak='indentingnewline'" + replace: + - test: + - if: "string(@width) != '' or @data-previous-space-width >= 0.25 or @data-following-space-width >= 0.25" + then: [t: "⠠"] + - else: [t: "⠐"] + +- + name: mspace + tag: mspace + match: "." + replace: [t: "W"] + - name: no-content tag: [math, mrow] @@ -114,17 +415,39 @@ - x: "*[1]" - x: "BrailleChars(translate(*[2], '0123456789', '\ue000\ue001\ue002\ue003\ue004\ue005\ue006\ue007\ue008\ue009'), 'Russian')" +- + name: simple-algebraic-negative-integer-denominator + tag: mfrac + match: + - "(*[1][self::m:mi] or *[1][self::m:mn and translate(., '0123456789,', '')='']) and" + - "*[2][self::m:mrow and count(*)=2 and *[1][self::m:mo and (.='-' or .='−')] and *[2][self::m:mn and translate(., '0123456789', '')='']]" + replace: + - x: "*[1]" + - t: "⠳⠤" + - x: "BrailleChars(translate(*[2]/*[2], '0123456789', '\ue000\ue001\ue002\ue003\ue004\ue005\ue006\ue007\ue008\ue009'), 'Russian')" + +- + name: simple-algebraic-negative-decimal-denominator + tag: mfrac + match: + - "(*[1][self::m:mi] or *[1][self::m:mn and translate(., '0123456789,', '')='']) and" + - "*[2][self::m:mrow and count(*)=2 and *[1][self::m:mo and (.='-' or .='−')] and *[2][self::m:mn and contains(., ',') and translate(., '0123456789,', '')='']]" + replace: + - x: "*[1]" + - t: "⠳⠤" + - x: "*[2]/*[2]" + - name: simple-algebraic tag: mfrac match: - - "(*[1][self::m:mi] or *[1][self::m:mn and translate(., '0123456789', '')='']) and" - - "(*[2][self::m:mi] or *[2][self::m:mn and translate(., '0123456789', '')=''])" + - "(*[1][self::m:mi] or *[1][self::m:mn and translate(., '0123456789,', '')='']) and" + - "(*[2][self::m:mi] or *[2][self::m:mn and translate(., '0123456789,', '')=''])" replace: - x: "*[1]" - t: "⠳" - test: - if: "*[2][self::m:mn]" + if: "*[2][self::m:mn and translate(., '0123456789', '')='']" then: - x: "BrailleChars(translate(*[2], '0123456789', '\ue000\ue001\ue002\ue003\ue004\ue005\ue006\ue007\ue008\ue009'), 'Russian')" else: @@ -165,6 +488,36 @@ - x: "*[2]/*[1]/*[1]/*[1]" - x: "*[3]" +- + name: long-division-table + tag: mtable + variables: + - InLongDivision: "true()" + - RowStart: "''" + - RowEnd: "''" + match: "contains(@intent, ':long-division') or contains(@intent, ':angle-division')" + replace: [x: "*"] + +- + name: column-arithmetic-table + tag: mtable + variables: + - InColumnArithmetic: "true()" + - RowStart: "''" + - RowEnd: "''" + match: "contains(@intent, ':column-arithmetic')" + replace: [x: "*"] + +- + name: system-right-brace-table + tag: mtable + variables: + - InSystemRightBrace: "true()" + - RowStart: "''" + - RowEnd: "''" + match: "not(preceding-sibling::*) and count(../*)=2" + replace: [x: "*"] + - name: system-left-brace-table tag: mtable @@ -209,10 +562,23 @@ then: [t: "⠇⠀"] - else: - x: $RowStart + - test: + if: .[self::m:mlabeledtr] + then: + - t: "⠍⠑⠞⠅⠁⠀⠎⠞⠗⠕⠅⠊⠀⠲" + - x: "*[1]/*" + - t: "W" - test: if: .[self::m:mlabeledtr] then: [x: "*[position()>1]"] else: [x: "*"] + - test: + - if: "$InSystemRightBrace and not(preceding-sibling::*)" + then: [t: "⠀⠹"] + - else_if: "$InSystemRightBrace and not(following-sibling::*)" + then: [t: "⠀⠼"] + - else_if: "$InSystemRightBrace" + then: [t: "⠀⠸"] - x: $RowEnd - @@ -243,6 +609,45 @@ replace: - t: "⠀⠒⠕⠀⠦⠶" +- + name: chemistry-reaction-arrow-above + tag: mover + match: + - "(ancestor::*[@data-chem-equation or contains(@intent, ':chemical-equation')] or @data-chem-equation-op) and" + - "*[1][self::m:mo and translate(., '→⟶←⟵⇌⥂⥄↑↓', '')='']" + replace: + - x: "*[1]" + - t: "⠨⠌" + - x: "*[2]" + - t: "⠱" + +- + name: chemistry-reaction-arrow-below + tag: munder + match: + - "(ancestor::*[@data-chem-equation or contains(@intent, ':chemical-equation')] or @data-chem-equation-op) and" + - "*[1][self::m:mo and translate(., '→⟶←⟵⇌⥂⥄↑↓', '')='']" + replace: + - x: "*[1]" + - t: "⠨⠡" + - x: "*[2]" + - t: "⠱" + +- + name: chemistry-reaction-arrow-above-and-below + tag: munderover + match: + - "(ancestor::*[@data-chem-equation or contains(@intent, ':chemical-equation')] or @data-chem-equation-op) and" + - "*[1][self::m:mo and translate(., '→⟶←⟵⇌⥂⥄↑↓', '')='']" + replace: + - x: "*[1]" + - t: "⠨⠡" + - x: "*[2]" + - t: "⠱" + - t: "⠨⠌" + - x: "*[3]" + - t: "⠱" + - name: vector-arrow tag: mover @@ -256,6 +661,19 @@ then: [t: "⠨⠒⠂"] else: [t: "⠒⠂"] +- + name: geometry-line-double-arrow + tag: mover + match: + - "*[2][translate(., '↔⟷', '')=''] and" + - "string-length(translate(normalize-space(*[1]), ' ', '')) > 0" + replace: + - x: "*[1]" + - test: + if: "string-length(translate(normalize-space(*[1]), ' ', '')) > 1" + then: [t: "⠨⠒⠂⠂"] + else: [t: "⠒⠂⠂"] + - name: vector-bar tag: mover @@ -269,6 +687,48 @@ then: [t: "⠨⠒"] else: [t: "⠒"] +- + name: lower-vector-arrow + tag: munder + match: + - "*[2][translate(., '→⟶⇀⃗', '')=''] and" + - "string-length(translate(normalize-space(*[1]), ' ', '')) > 0" + replace: + - x: "*[1]" + - t: "⠰" + - test: + if: "string-length(translate(normalize-space(*[1]), ' ', '')) > 1" + then: [t: "⠨⠒⠂"] + else: [t: "⠒⠂"] + +- + name: lower-geometry-line-double-arrow + tag: munder + match: + - "*[2][translate(., '↔⟷', '')=''] and" + - "string-length(translate(normalize-space(*[1]), ' ', '')) > 0" + replace: + - x: "*[1]" + - t: "⠰" + - test: + if: "string-length(translate(normalize-space(*[1]), ' ', '')) > 1" + then: [t: "⠨⠒⠂⠂"] + else: [t: "⠒⠂⠂"] + +- + name: lower-vector-bar + tag: munder + match: + - "*[2][translate(., '¯‾_ ̄', '')=''] and" + - "string-length(translate(normalize-space(*[1]), ' ', '')) > 0" + replace: + - x: "*[1]" + - t: "⠰" + - test: + if: "string-length(translate(normalize-space(*[1]), ' ', '')) > 1" + then: [t: "⠨⠒"] + else: [t: "⠒"] + - name: geometry-arc tag: mover @@ -278,6 +738,211 @@ - t: "#" - x: "*[1]" +- + name: exact-over-label-no-indicator + tag: mover + match: + - "DefinitionValue(*[2], 'Braille', 'RussianExactOverLabelMarksWithoutIndicator') != '' and" + - "string-length(*[2]) = 1" + replace: + - test: + if: "*[1][self::m:mrow or self::m:mfrac or self::m:msqrt or self::m:mroot or self::m:mtable]" + then: + - t: "⠯" + - x: "*[1]" + - t: "⠽" + else: + - x: "*[1]" + - x: "DefinitionValue(*[2], 'Braille', 'RussianExactOverLabelMarksWithoutIndicator')" + +- + name: exact-over-label-repeated + tag: mover + match: + - "string-length(*[2]) <= 3 and" + - "translate(*[2], substring(*[2], 1, 1), '') = '' and" + - "DefinitionValue(substring(*[2], 1, 1), 'Braille', 'RussianLabelMarks') != ''" + replace: + - test: + if: "*[1][self::m:mrow or self::m:mfrac or self::m:msqrt or self::m:mroot or self::m:mtable]" + then: + - t: "⠯" + - x: "*[1]" + - t: "⠽" + else: + - x: "*[1]" + - t: "⠘" + - x: "DefinitionValue(substring(*[2], 1, 1), 'Braille', 'RussianLabelMarks')" + - test: + if: "string-length(*[2]) > 1" + then: + - x: "DefinitionValue(substring(*[2], 1, 1), 'Braille', 'RussianLabelMarks')" + - test: + if: "string-length(*[2]) > 2" + then: + - x: "DefinitionValue(substring(*[2], 1, 1), 'Braille', 'RussianLabelMarks')" + +- + name: exact-over-label + tag: mover + match: "DefinitionValue(*[2], 'Braille', 'RussianLabelMarks') != ''" + replace: + - test: + if: "*[1][self::m:mrow or self::m:mfrac or self::m:msqrt or self::m:mroot or self::m:mtable]" + then: + - t: "⠯" + - x: "*[1]" + - t: "⠽" + else: + - x: "*[1]" + - t: "⠘" + - x: "DefinitionValue(*[2], 'Braille', 'RussianLabelMarks')" + +- + name: exact-under-label-repeated + tag: munder + match: + - "string-length(*[2]) <= 3 and" + - "translate(*[2], substring(*[2], 1, 1), '') = '' and" + - "DefinitionValue(substring(*[2], 1, 1), 'Braille', 'RussianLabelMarks') != ''" + replace: + - test: + if: "*[1][self::m:mrow or self::m:mfrac or self::m:msqrt or self::m:mroot or self::m:mtable]" + then: + - t: "⠯" + - x: "*[1]" + - t: "⠽" + else: + - x: "*[1]" + - t: "⠰" + - x: "DefinitionValue(substring(*[2], 1, 1), 'Braille', 'RussianLabelMarks')" + - test: + if: "string-length(*[2]) > 1" + then: + - x: "DefinitionValue(substring(*[2], 1, 1), 'Braille', 'RussianLabelMarks')" + - test: + if: "string-length(*[2]) > 2" + then: + - x: "DefinitionValue(substring(*[2], 1, 1), 'Braille', 'RussianLabelMarks')" + +- + name: exact-under-label + tag: munder + match: "DefinitionValue(*[2], 'Braille', 'RussianLabelMarks') != ''" + replace: + - test: + if: "*[1][self::m:mrow or self::m:mfrac or self::m:msqrt or self::m:mroot or self::m:mtable]" + then: + - t: "⠯" + - x: "*[1]" + - t: "⠽" + else: + - x: "*[1]" + - t: "⠰" + - x: "DefinitionValue(*[2], 'Braille', 'RussianLabelMarks')" + +- + name: upper-right-label-no-indicator-repeated + tag: msup + match: + - "not(ancestor::*[@data-chem-equation or contains(@intent, ':chemical-equation')]) and" + - "not(*[2][contains(., '+') or contains(., '-') or contains(., '−')] and" + - " *[1][self::m:mi or self::m:mtext or self::m:mrow or self::m:msub or self::m:mmultiscripts]) and" + - "string-length(*[2]) <= 3 and" + - "translate(*[2], substring(*[2], 1, 1), '') = '' and" + - "DefinitionValue(substring(*[2], 1, 1), 'Braille', 'RussianUpperRightLabelMarksWithoutIndicator') != ''" + replace: + - x: "*[1]" + - x: "DefinitionValue(substring(*[2], 1, 1), 'Braille', 'RussianUpperRightLabelMarksWithoutIndicator')" + - test: + if: "string-length(*[2]) > 1" + then: + - x: "DefinitionValue(substring(*[2], 1, 1), 'Braille', 'RussianUpperRightLabelMarksWithoutIndicator')" + - test: + if: "string-length(*[2]) > 2" + then: + - x: "DefinitionValue(substring(*[2], 1, 1), 'Braille', 'RussianUpperRightLabelMarksWithoutIndicator')" + +- + name: upper-right-label-no-indicator + tag: msup + match: + - "not(ancestor::*[@data-chem-equation or contains(@intent, ':chemical-equation')]) and" + - "not(*[2][contains(., '+') or contains(., '-') or contains(., '−')] and" + - " *[1][self::m:mi or self::m:mtext or self::m:mrow or self::m:msub or self::m:mmultiscripts]) and" + - "DefinitionValue(*[2], 'Braille', 'RussianUpperRightLabelMarksWithoutIndicator') != ''" + replace: + - x: "*[1]" + - x: "DefinitionValue(*[2], 'Braille', 'RussianUpperRightLabelMarksWithoutIndicator')" + +- + name: upper-right-label-repeated + tag: msup + match: + - "not(ancestor::*[@data-chem-equation or contains(@intent, ':chemical-equation')]) and" + - "not(*[2][contains(., '+') or contains(., '-') or contains(., '−')] and" + - " *[1][self::m:mi or self::m:mtext or self::m:mrow or self::m:msub or self::m:mmultiscripts]) and" + - "string-length(*[2]) <= 3 and" + - "translate(*[2], substring(*[2], 1, 1), '') = '' and" + - "DefinitionValue(substring(*[2], 1, 1), 'Braille', 'RussianLabelMarks') != ''" + replace: + - x: "*[1]" + - t: "⠨" + - x: "DefinitionValue(substring(*[2], 1, 1), 'Braille', 'RussianLabelMarks')" + - test: + if: "string-length(*[2]) > 1" + then: + - x: "DefinitionValue(substring(*[2], 1, 1), 'Braille', 'RussianLabelMarks')" + - test: + if: "string-length(*[2]) > 2" + then: + - x: "DefinitionValue(substring(*[2], 1, 1), 'Braille', 'RussianLabelMarks')" + +- + name: upper-right-label + tag: msup + match: + - "not(ancestor::*[@data-chem-equation or contains(@intent, ':chemical-equation')]) and" + - "not(*[2][contains(., '+') or contains(., '-') or contains(., '−')] and" + - " *[1][self::m:mi or self::m:mtext or self::m:mrow or self::m:msub or self::m:mmultiscripts]) and" + - "DefinitionValue(*[2], 'Braille', 'RussianLabelMarks') != ''" + replace: + - x: "*[1]" + - t: "⠨" + - x: "DefinitionValue(*[2], 'Braille', 'RussianLabelMarks')" + +- + name: lower-right-label-repeated + tag: msub + match: + - "not(ancestor::*[@data-chem-equation or contains(@intent, ':chemical-equation')]) and" + - "string-length(*[2]) <= 3 and" + - "translate(*[2], substring(*[2], 1, 1), '') = '' and" + - "DefinitionValue(substring(*[2], 1, 1), 'Braille', 'RussianLabelMarks') != ''" + replace: + - x: "*[1]" + - t: "⠸" + - x: "DefinitionValue(substring(*[2], 1, 1), 'Braille', 'RussianLabelMarks')" + - test: + if: "string-length(*[2]) > 1" + then: + - x: "DefinitionValue(substring(*[2], 1, 1), 'Braille', 'RussianLabelMarks')" + - test: + if: "string-length(*[2]) > 2" + then: + - x: "DefinitionValue(substring(*[2], 1, 1), 'Braille', 'RussianLabelMarks')" + +- + name: lower-right-label + tag: msub + match: + - "not(ancestor::*[@data-chem-equation or contains(@intent, ':chemical-equation')]) and" + - "DefinitionValue(*[2], 'Braille', 'RussianLabelMarks') != ''" + replace: + - x: "*[1]" + - t: "⠸" + - x: "DefinitionValue(*[2], 'Braille', 'RussianLabelMarks')" + - name: signed-integer-number tag: [msub, munder] @@ -325,6 +990,32 @@ else: [t: "⠡"] - x: "BrailleChars(translate(*[2], '0123456789', '\ue000\ue001\ue002\ue003\ue004\ue005\ue006\ue007\ue008\ue009'), 'Russian')" +- + name: integer-sequence + tag: [msub, munder] + match: + - "*[2][not(self::m:mn) and translate(normalize-space(.), '0123456789⁣', '')='' and" + - " not(.//m:mo[.=',' or .='،'])]" + replace: + - x: "*[1]" + - test: + if: "self::m:munder" + then: [t: "⠨⠡"] + else: [t: "⠡"] + - x: "BrailleChars(translate(normalize-space(*[2]), '0123456789⁣', '\ue000\ue001\ue002\ue003\ue004\ue005\ue006\ue007\ue008\ue009'), 'Russian')" + +- + name: infinity-index + tag: [msub, munder] + match: "*[2][.='∞']" + replace: + - x: "*[1]" + - test: + if: "self::m:munder" + then: [t: "⠨⠡"] + else: [t: "⠡"] + - x: "*[2]" + - name: default tag: [msub, munder] @@ -336,7 +1027,7 @@ then: [t: "⠨⠡"] else: [t: "⠡"] - test: - if: "*[2][self::m:mrow[m:mo[.='+']] or self::m:mfrac]" + if: "*[2][self::m:mrow[m:mo[.='+'] or m:mfrac] or self::m:mfrac]" then: [t: "⠐"] - x: "*[2]" - t: "⠱" @@ -429,6 +1120,21 @@ - else: - x: "*[2]" +- + name: trig-function-integer-power-before-complex-fraction + tag: msup + match: + - "DefinitionValue(*[1], 'Braille', 'RussianFunctionBraille') != '' and" + - "*[2][self::m:mn and translate(., '0123456789', '')=''] and" + - "(following-sibling::*[1][self::m:mfrac and not((*[1][self::m:mi or self::m:mn]) and (*[2][self::m:mi or self::m:mn]))] or" + - " following-sibling::*[1][self::m:mo and (.='⁡' or .='⁢' or .='⁣' or .='⁤')]" + - " [following-sibling::*[1][self::m:mfrac and not((*[1][self::m:mi or self::m:mn]) and (*[2][self::m:mi or self::m:mn]))]])" + replace: + - x: "*[1]" + - t: "⠌" + - x: "BrailleChars(translate(*[2], '0123456789', '\ue000\ue001\ue002\ue003\ue004\ue005\ue006\ue007\ue008\ue009'), 'Russian')" + - t: "⠐" + - name: integer tag: [msup, mover] @@ -451,6 +1157,9 @@ if: "self::m:mover" then: [t: "⠨⠌"] else: [t: "⠌"] + - test: + if: "*[2][self::m:mfrac[not((*[1][self::m:mi or self::m:mn]) and (*[2][self::m:mi or self::m:mn]))]]" + then: [t: "⠐"] - x: "*[2]" - t: "⠱" @@ -531,6 +1240,24 @@ - else: - x: "*[3]" +- + name: mixed-subscript-before-superscript + tag: msubsup + match: + - "not(ancestor::*[@data-chem-equation or contains(@intent, ':chemical-equation')]) and" + - "*[2][self::m:mrow[m:mn and (m:mi or m:mtext) and not(m:mo[not(.='⁡' or .='⁢' or .='⁣' or .='⁤')])]]" + replace: + - x: "*[1]" + - t: "⠡" + - test: + if: "*[2][m:mo[.='+'] or m:mfrac]" + then: [t: "⠐"] + - x: "*[2]" + - t: "⠱" + - t: "⠌" + - x: "*[3]" + - t: "⠱" + - name: default tag: [msubsup, munderover] @@ -542,7 +1269,7 @@ then: [t: "⠨⠡"] else: [t: "⠡"] - test: - if: "*[2][self::m:mrow[m:mo[.='+']] or self::m:mfrac]" + if: "*[2][self::m:mrow[m:mo[.='+'] or m:mfrac] or self::m:mfrac]" then: [t: "⠐"] - x: "*[2]" - test: @@ -552,11 +1279,128 @@ - x: "*[3]" - t: "⠱" +- + name: left-labels + tag: mmultiscripts + match: + - "not(ancestor::*[@data-chem-equation or contains(@intent, ':chemical-equation')]) and" + - "count(m:mprescripts/preceding-sibling::*) >= 1 and" + - "(m:mprescripts/following-sibling::*[1][not(self::m:none) and DefinitionValue(., 'Braille', 'RussianLabelMarks') != ''] or" + - " m:mprescripts/following-sibling::*[2][not(self::m:none) and DefinitionValue(., 'Braille', 'RussianLabelMarks') != ''])" + variables: + - Prescripts: "m:mprescripts/following-sibling::*" + - NumChildren: "count(*)" + - Postscripts: "*[position()>1 and position() < (last() + ($NumChildren mod 2) - count($Prescripts))]" + replace: + - test: + if: "m:mprescripts/following-sibling::*[1][not(self::m:none) and DefinitionValue(., 'Braille', 'RussianLabelMarks') != '']" + then: + - t: "⠸" + - x: "DefinitionValue(m:mprescripts/following-sibling::*[1], 'Braille', 'RussianLabelMarks')" + - test: + if: "m:mprescripts/following-sibling::*[2][not(self::m:none) and DefinitionValue(., 'Braille', 'RussianLabelMarks') != '']" + then: + - t: "⠨" + - x: "DefinitionValue(m:mprescripts/following-sibling::*[2], 'Braille', 'RussianLabelMarks')" + - x: "*[1]" + - test: + if: "$Postscripts" + then: + - test: + if: "not($Postscripts[1][self::m:none])" + then: + - t: "⠡" + - test: + - if: "$Postscripts[1][self::m:mn and translate(., '0123456789', '')='']" + then: + - x: "BrailleChars(translate($Postscripts[1], '0123456789', '\ue000\ue001\ue002\ue003\ue004\ue005\ue006\ue007\ue008\ue009'), 'Russian')" + - else: + - x: "$Postscripts[1]" + - test: + if: "not($Postscripts[1][self::m:mn and translate(., '0123456789', '')='']) and not($Postscripts[1][.='∞'])" + then: [t: "⠱"] + - test: + if: "not($Postscripts[2][self::m:none])" + then: + - t: "⠌" + - test: + - if: "$Postscripts[2][self::m:mn and translate(., '0123456789', '')='']" + then: + - x: "BrailleChars(translate($Postscripts[2], '0123456789', '\ue000\ue001\ue002\ue003\ue004\ue005\ue006\ue007\ue008\ue009'), 'Russian')" + - else: + - x: "$Postscripts[2]" + - test: + if: "not($Postscripts[2][self::m:mn and translate(., '0123456789', '')='']) and not($Postscripts[2][.='∞'])" + then: [t: "⠱"] + - name: default tag: mmultiscripts match: "." - replace: [x: "*"] + variables: + - Prescripts: "m:mprescripts/following-sibling::*" + - NumChildren: "count(*)" + - Postscripts: "*[position()>1 and position() < (last() + ($NumChildren mod 2) - count($Prescripts))]" + replace: + - test: + if: "$Prescripts" + then: + - test: + if: "not($Prescripts[1][self::m:none])" + then: + - t: "⠡" + - test: + - if: "$Prescripts[1][self::m:mn and translate(., '0123456789', '')='']" + then: + - x: "BrailleChars(translate($Prescripts[1], '0123456789', '\ue000\ue001\ue002\ue003\ue004\ue005\ue006\ue007\ue008\ue009'), 'Russian')" + - else: + - x: "$Prescripts[1]" + - test: + if: "not($Prescripts[1][self::m:mn and translate(., '0123456789', '')='']) and not($Prescripts[1][.='∞'])" + then: [t: "⠱"] + - test: + if: "not($Prescripts[2][self::m:none])" + then: + - t: "⠌" + - test: + - if: "$Prescripts[2][self::m:mn and translate(., '0123456789', '')='']" + then: + - x: "BrailleChars(translate($Prescripts[2], '0123456789', '\ue000\ue001\ue002\ue003\ue004\ue005\ue006\ue007\ue008\ue009'), 'Russian')" + - else: + - x: "$Prescripts[2]" + - test: + if: "not($Prescripts[2][self::m:mn and translate(., '0123456789', '')='']) and not($Prescripts[2][.='∞'])" + then: [t: "⠱"] + - x: "*[1]" + - test: + if: "$Postscripts" + then: + - test: + if: "not($Postscripts[1][self::m:none])" + then: + - t: "⠡" + - test: + - if: "$Postscripts[1][self::m:mn and translate(., '0123456789', '')='']" + then: + - x: "BrailleChars(translate($Postscripts[1], '0123456789', '\ue000\ue001\ue002\ue003\ue004\ue005\ue006\ue007\ue008\ue009'), 'Russian')" + - else: + - x: "$Postscripts[1]" + - test: + if: "not($Postscripts[1][self::m:mn and translate(., '0123456789', '')='']) and not($Postscripts[1][.='∞'])" + then: [t: "⠱"] + - test: + if: "not($Postscripts[2][self::m:none])" + then: + - t: "⠌" + - test: + - if: "$Postscripts[2][self::m:mn and translate(., '0123456789', '')='']" + then: + - x: "BrailleChars(translate($Postscripts[2], '0123456789', '\ue000\ue001\ue002\ue003\ue004\ue005\ue006\ue007\ue008\ue009'), 'Russian')" + - else: + - x: "$Postscripts[2]" + - test: + if: "not($Postscripts[2][self::m:mn and translate(., '0123456789', '')='']) and not($Postscripts[2][.='∞'])" + then: [t: "⠱"] - name: default @@ -612,16 +1456,71 @@ - else: - x: "text()" +- + name: geometry-prefix-after-multiplier + tag: mo + match: + - "(.='∠' or .='△' or .='∪') and" + - "preceding-sibling::*[1][self::m:mn] and" + - "following-sibling::*[1][self::m:mi or self::m:msub or self::m:mrow]" + replace: + - t: "⠄" + - test: + - if: ".='∪'" + then: [t: "⠸⠜"] + - else: + - x: "text()" + - t: "#" + - name: prefix-geometry-ops tag: mo match: - - "parent::*[count(*) = 2 and name()='mrow' and not(preceding-sibling::*)] and" + - "not(preceding-sibling::*) and" + - "following-sibling::*[1][self::m:mi or self::m:msub or self::m:mrow] and" - "( .='∠' or .='△' or .='∪' )" replace: - - x: "text()" + - test: + - if: ".='∪'" + then: [t: "⠸⠜"] + - else: + - x: "text()" - t: "#" +- + name: long-division-angle-operator + tag: mo + match: + - "$InLongDivision and" + - "(.='÷' or .='⟌')" + replace: + - t: "⠴" + +- + name: column-arithmetic-operator + tag: mo + match: + - "$InColumnArithmetic and" + - "ancestor::m:mtr[preceding-sibling::*] and" + - "(.='+' or .='-' or .='−' or .='×' or .='⋅' or .='·')" + replace: + - test: + - if: ".='+'" + then: [t: "⠖"] + - else_if: ".='-' or .='−'" + then: [t: "⠤"] + - else: [t: "⠦"] + +- + name: column-arithmetic-result-line + tag: mo + match: + - "$InColumnArithmetic and" + - "ancestor::m:mtr[preceding-sibling::*] and" + - ".='='" + replace: + - t: "⠶" + - name: operator-functions tag: mo @@ -643,6 +1542,49 @@ replace: - x: "text()" +- + name: long-division-continuation-number + tag: mn + match: + - "$InLongDivision and" + - "ancestor::m:mtr[preceding-sibling::*] and" + - "translate(., '0123456789', '')=''" + replace: + - x: "BrailleChars(translate(., '0123456789', '\ue020\ue021\ue022\ue023\ue024\ue025\ue026\ue027\ue028\ue029'), 'Russian')" + +- + name: column-arithmetic-continuation-number + tag: mn + match: + - "$InColumnArithmetic and" + - "ancestor::m:mtr[preceding-sibling::*] and" + - "translate(., '0123456789', '')=''" + replace: + - x: "BrailleChars(translate(., '0123456789', '\ue020\ue021\ue022\ue023\ue024\ue025\ue026\ue027\ue028\ue029'), 'Russian')" + +- + name: periodic-decimal-period-digits + tag: mn + match: + - "translate(., '0123456789', '')='' and" + - "preceding-sibling::*[1][self::m:mo and .='('] and" + - "following-sibling::*[1][self::m:mo and .=')'] and" + - "preceding-sibling::*[2][self::m:mn and contains(., ',')]" + replace: + - x: "BrailleChars(translate(., '0123456789', '\ue020\ue021\ue022\ue023\ue024\ue025\ue026\ue027\ue028\ue029'), 'Russian')" + +- + name: periodic-decimal + tag: mn + match: "contains(., ',') and substring-after(., '(') != '' and substring-before(substring-after(., '('), ')') != ''" + replace: + - x: "BrailleChars(substring-before(., ','), 'Russian')" + - t: "⠂" + - x: "BrailleChars(translate(substring-before(substring-after(., ','), '('), '0123456789', '\ue020\ue021\ue022\ue023\ue024\ue025\ue026\ue027\ue028\ue029'), 'Russian')" + - t: "⠣" + - x: "BrailleChars(translate(substring-before(substring-after(., '('), ')'), '0123456789', '\ue020\ue021\ue022\ue023\ue024\ue025\ue026\ue027\ue028\ue029'), 'Russian')" + - t: "⠜" + - name: decimal-comma tag: mn @@ -666,6 +1608,13 @@ replace: - x: "DefinitionValue(., 'Braille', 'RussianFunctionBraille')" +- + name: chemistry-single-letter-element-in-group + tag: mi + match: "$InChemElementGroup and @data-chem-element='1'" + replace: + - x: "translate(BrailleChars(translate(., 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), 'Russian'), 'l', '')" + - name: div-function tag: [mi, mo, mtext] diff --git a/Rules/Braille/Russian/definitions.yaml b/Rules/Braille/Russian/definitions.yaml index de0492043..9aa56c6c7 100644 --- a/Rules/Braille/Russian/definitions.yaml +++ b/Rules/Braille/Russian/definitions.yaml @@ -16,3 +16,30 @@ "grad": "\uE11B", "gradient": "\uE11B", "rot": "\uE11C", "div": "\uE11D", "gcd": "\uE11E", "lcm": "\uE11F" } + +- RussianLabelMarks: { + "′": "\uE130", "″": "\uE130\uE130", "‴": "\uE130\uE130\uE130", "'": "\uE130", + "*": "\uE131", "∗": "\uE131", "⋆": "\uE131", + "×": "\uE132", "✕": "\uE132", "✖": "\uE132", + "∘": "\uE133", "○": "\uE133", "◦": "\uE133", "°": "\uE133", + "□": "\uE134", "◻": "\uE134", + "~": "\uE135", "∼": "\uE135", "˜": "\uE135", "̃": "\uE135", + "^": "\uE136", "∧": "\uE136", "ˆ": "\uE136", "̂": "\uE136", + "∨": "\uE137", "˅": "\uE137", "ˇ": "\uE137", "̌": "\uE137", + "+": "\uE138", "-": "\uE139", "−": "\uE139", + "?": "\uE13A", "⍰": "\uE13A", + "←": "\uE13B", "⟵": "\uE13B", "⃖": "\uE13B" + } + +- RussianExactOverLabelMarksWithoutIndicator: { + "~": "\uE135", "∼": "\uE135", "˜": "\uE135", "̃": "\uE135", + "^": "\uE136", "∧": "\uE136", "ˆ": "\uE136", "̂": "\uE136", + "∨": "\uE137", "˅": "\uE137", "ˇ": "\uE137", "̌": "\uE137" + } + +- RussianUpperRightLabelMarksWithoutIndicator: { + "′": "\uE130", "″": "\uE130\uE130", "‴": "\uE130\uE130\uE130", "'": "\uE130", + "*": "\uE131", "∗": "\uE131", "⋆": "\uE131", + "×": "\uE132", "✕": "\uE132", "✖": "\uE132", + "+": "\uE138", "-": "\uE139", "−": "\uE139" + } diff --git a/Rules/Braille/Russian/unicode-full.yaml b/Rules/Braille/Russian/unicode-full.yaml index f78feac35..813669ecc 100644 --- a/Rules/Braille/Russian/unicode-full.yaml +++ b/Rules/Braille/Russian/unicode-full.yaml @@ -107,7 +107,7 @@ # bold script chars in math alphabetic block - "𝓐-𝓩": # 0x1d4d0 - 0x1d4e9 - - tc: "BTs" + - tc: "BTsC" - spell: "translate('.', '𝓐𝓑𝓒𝓓𝓔𝓕𝓖𝓗𝓘𝓙𝓚𝓛𝓜𝓝𝓞𝓟𝓠𝓡𝓢𝓣𝓤𝓥𝓦𝓧𝓨𝓩', 'abcdefghijklmnopqrstuvwxyz')" - "𝓪-𝔃": # 0x1d4ea - 0x1d503 @@ -115,7 +115,7 @@ - spell: "translate('.', '𝓪𝓫𝓬𝓭𝓮𝓯𝓰𝓱𝓲𝓳𝓴𝓵𝓶𝓷𝓸𝓹𝓺𝓻𝓼𝓽𝓾𝓿𝔀𝔁𝔂𝔃', 'abcdefghijklmnopqrstuvwxyz')" - "𝐀-𝐙": # 0x1d400 - 0x1d419 - - tc: "B" + - tc: "BC" - spell: "translate('.', '𝐀𝐁𝐂𝐃𝐄𝐅𝐆𝐇𝐈𝐉𝐊𝐋𝐌𝐍𝐎𝐏𝐐𝐑𝐒𝐓𝐔𝐕𝐖𝐗𝐘𝐙', 'abcdefghijklmnopqrstuvwxyz')" - "𝐚-𝐳": # 0x1d41a - 0x1d433 @@ -123,12 +123,11 @@ - spell: "translate('.', '𝐚𝐛𝐜𝐝𝐞𝐟𝐠𝐡𝐢𝐣𝐤𝐥𝐦𝐧𝐨𝐩𝐪𝐫𝐬𝐭𝐮𝐯𝐰𝐱𝐲𝐳', 'abcdefghijklmnopqrstuvwxyz')" - "𝐴-𝑍": # 0x1d434 - 0x1d44d - # don't include italics - - tc: "C" + - tc: "IC" - spell: "translate('.', '𝐴𝐵𝐶𝐷𝐸𝐹𝐺𝐻𝐼𝐽𝐾𝐿𝑀𝑁𝑂𝑃𝑄𝑅𝑆𝑇𝑈𝑉𝑊𝑋𝑌𝑍', 'abcdefghijklmnopqrstuvwxyz')" - "𝑎-𝑧": # 0x1d44e - 0x1d467 - # don't include italics + - tc: "I" - spell: "translate('.', '𝑎𝑏𝑐𝑑𝑒𝑓𝑔𝑕𝑖𝑗𝑘𝑙𝑚𝑛𝑜𝑝𝑞𝑟𝑠𝑡𝑢𝑣𝑤𝑥𝑦𝑧', 'abcdefghijklmnopqrstuvwxyz')" - "𝑨-𝒁": # 0x1d468 - 0x1d481 @@ -136,6 +135,7 @@ - spell: "translate('.', '𝑨𝑩𝑪𝑫𝑬𝑭𝑮𝑯𝑰𝑱𝑲𝑳𝑴𝑵𝑶𝑷𝑸𝑹𝑺𝑻𝑼𝑽𝑾𝑿𝒀𝒁', 'abcdefghijklmnopqrstuvwxyz')" - "𝒂-𝒛": # 0x1d482 - 0x1d49b + - tc: "BI" - spell: "translate('.', '𝒂𝒃𝒄𝒅𝒆𝒇𝒈𝒉𝒊𝒋𝒌𝒍𝒎𝒏𝒐𝒑𝒒𝒓𝒔𝒕𝒖𝒗𝒘𝒙𝒚𝒛', 'abcdefghijklmnopqrstuvwxyz')" - "𝖠-𝖹": # 0x1d5a0 - 0x1d5b9 @@ -157,11 +157,11 @@ - spell: "translate('.', '𝗮𝗯𝗰𝗱𝗲𝗳𝗴𝗵𝗶𝗷𝗸𝗹𝗺𝗻𝗼𝗽𝗾𝗿𝘀𝘁𝘂𝘃𝘄𝘅𝘆𝘇', 'abcdefghijklmnopqrstuvwxyz')" - "𝘈-𝘡": # 0x1d608 - 0x1d621 - # - tc: "italic" + - tc: "IC" - spell: "translate('.', '𝘈𝘉𝘊𝘋𝘌𝘍𝘎𝘏𝘐𝘑𝘒𝘓𝘔𝘕𝘖𝘗𝘘𝘙𝘚𝘛𝘜𝘝𝘞𝘟𝘠𝘡', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')" - "𝘢-𝘻": # 0x1d622 - 0x1d63b - # - tc: "italic" + - tc: "I" - spell: "translate('.', '𝘢𝘣𝘤𝘥𝘦𝘧𝘨𝘩𝘪𝘫𝘬𝘭𝘮𝘯𝘰𝘱𝘲𝘳𝘴𝘵𝘶𝘷𝘸𝘹𝘺𝘻', 'abcdefghijklmnopqrstuvwxyz')" - "𝘼-𝙕": # 0x1d63c - 0x1d655 diff --git a/Rules/Braille/Russian/unicode.yaml b/Rules/Braille/Russian/unicode.yaml index 53fb06938..cf8617352 100644 --- a/Rules/Braille/Russian/unicode.yaml +++ b/Rules/Braille/Russian/unicode.yaml @@ -6,6 +6,12 @@ - "?": [t: "⠠⠢"] - ":": [t: "⠀⠲"] - ";": [t: "⠠⠆"] +- "№": [t: "⠝"] +- "§": [t: "⠬"] +- "…": [t: "⠠⠄"] +- "↵": [t: "⠐"] +- "⏎": [t: "⠠"] +- "⍰": [t: "⠐"] - "[": [t: "⠷"] - "]": [t: "⠾"] - "{": [t: "⠪"] @@ -20,7 +26,7 @@ - ")": [t: "⠜"] - ">": [t: "⠀⠕⠀"] - "<": [t: "⠀⠪⠀"] -- "%": [t: "⠼⠴"] +- "%": [t: "⠼⠍⠴"] - "±": [t: "⠀⠖⠤"] - ";": [t: "⠠⠢"] - "Ϳ": [t: "⠰⠊"] @@ -148,6 +154,18 @@ - "\uE11D": [t: "⠫⠙⠊⠧#"] - "\uE11E": [t: "⠫⠛⠉⠙#"] - "\uE11F": [t: "⠫⠇⠉⠍#"] +- "\uE130": [t: "⠔"] +- "\uE131": [t: "⠆"] +- "\uE132": [t: "⠦"] +- "\uE133": [t: "⠴"] +- "\uE134": [t: "⠶"] +- "\uE135": [t: "⠢"] +- "\uE136": [t: "⠲"] +- "\uE137": [t: "⠰⠔"] +- "\uE138": [t: "⠖"] +- "\uE139": [t: "⠤"] +- "\uE13A": [t: "⠠⠢"] +- "\uE13B": [t: "⠦⠶"] - "\ue020": [t: "⠚"] - "\ue021": [t: "⠁"] - "\ue022": [t: "⠃"] @@ -170,11 +188,7 @@ - "∧": [t: "⠀⠰⠢"] - "∨": [t: "⠀⠰⠔"] - "∩": [t: "⠀⠰⠲"] -- "∪": - - test: - if: "following-sibling::*[1][self::m:mi]" - then: [t: "⠸⠜"] - else: [t: "⠀⠰⠴"] +- "∪": [t: "⠀⠰⠴"] - "∫": [t: "⠮"] - "∬": [t: "⠮⠮"] - "∭": [t: "⠮⠮⠮"] diff --git a/Rules/Languages/ru/SharedRules/calculus.yaml b/Rules/Languages/ru/SharedRules/calculus.yaml index 76fce80f2..cda63fbab 100644 --- a/Rules/Languages/ru/SharedRules/calculus.yaml +++ b/Rules/Languages/ru/SharedRules/calculus.yaml @@ -4,13 +4,13 @@ tag: laplacian match: "count(*) <= 1" # can be on ∇^2 or on enclosing mrow replace: - - t: "лапласиан" # phrase('laplacian' of x) -- "LahPlahsian" sounds better with speech engines tested + - T: "лапласиан" # phrase('laplacian' of x) -- "LahPlahsian" sounds better with speech engines tested - test: if: "count(*) = 1" then: - test: if: "$Verbosity!='Terse'" - then: [t: "от"] # phrase(function 'of' one variable) -- note OneCore voices spell out "div" + then: [T: "от"] # phrase(function 'of' one variable) -- note OneCore voices spell out "div" - test: if: "not(IsNode(*[1], 'leaf'))" then: [pause: short] @@ -22,11 +22,11 @@ replace: - test: if: "$Verbosity='Terse'" - then: [t: "див"] # phrase('div' is short for divergence) - else: [t: "дивергенция"] # phrase('divergence of' this function from the mean) + then: [T: "див"] # phrase('div' is short for divergence) + else: [T: "дивергенция"] # phrase('divergence of' this function from the mean) - test: if: "$Verbosity!='Terse' and *[1][self::m:mi[contains('ABCDEFGHIJKLMNOPQRSTUVWXYZ', text())]]" - then: [t: "заглавной"] + then: [T: "заглавной"] - test: if: "not(IsNode(*[1], 'leaf'))" then: [pause: short] @@ -39,10 +39,10 @@ tag: curl match: "count(*) = 1" replace: - - t: "ротор" # phrase(the 'curl of' a field) + - T: "ротор" # phrase(the 'curl of' a field) - test: if: "$Verbosity!='Terse' and *[1][self::m:mi[contains('ABCDEFGHIJKLMNOPQRSTUVWXYZ', text())]]" - then: [t: "заглавной"] + then: [T: "заглавной"] - test: if: "not(IsNode(*[1], 'leaf'))" then: [pause: short] @@ -57,11 +57,11 @@ replace: - test: if: "$Verbosity!='Terse'" - then: [t: "градиент"] # phrase(the hill has a 'gradient of' five percent) - else: [t: "набла"] # phrase(the delete key is labeled 'del') + then: [T: "градиент"] # phrase(the hill has a 'gradient of' five percent) + else: [T: "набла"] # phrase(the delete key is labeled 'del') - test: if: "$Verbosity!='Terse' and *[1][self::m:mi[contains('ABCDEFGHIJKLMNOPQRSTUVWXYZ', text())]]" - then: [t: "заглавной"] + then: [T: "заглавной"] - test: if: "not(IsNode(*[1], 'leaf'))" then: [pause: short] diff --git a/Rules/Languages/ru/SharedRules/default.yaml b/Rules/Languages/ru/SharedRules/default.yaml index e23171027..36a1a1de3 100644 --- a/Rules/Languages/ru/SharedRules/default.yaml +++ b/Rules/Languages/ru/SharedRules/default.yaml @@ -680,6 +680,15 @@ - x: "*[2]" - pause: short +- name: power-indexed-by + tag: power-indexed-by + match: "." + replace: + - x: "*[1]" + - T: "нижний индекс" + - x: "*[2]" + - pause: short + # otherwise let definitions/default infix handle it - name: literal diff --git a/Rules/Languages/ru/SharedRules/general.yaml b/Rules/Languages/ru/SharedRules/general.yaml index 2b10f1012..13db9ffba 100644 --- a/Rules/Languages/ru/SharedRules/general.yaml +++ b/Rules/Languages/ru/SharedRules/general.yaml @@ -343,6 +343,15 @@ - pause: short +# in terse mode, we just say "m" or "s", etc., not meters or seconds +- name: unit-terse + tag: unit + # audit-ignore: spell text() is an operation, not untranslated speech text. + match: "$Verbosity = 'Terse' and string-length(.)=1" + replace: + - bookmark: "@id" + - spell: "text()" + # the order of matching is # 1. does it match the base of an SI unit # 2. does it match an English unit (if in an English language) @@ -421,6 +430,26 @@ then: [x: "concat('\uF8FE', DefinitionValue($Word, 'Speech', 'PluralForms'))"] else: [x: "concat('\uF8FE', $Word)", ct: "с"] +# need to reverse the order of speech: $ 3 -> 3 dollars +- name: currency + tag: mrow + # audit-ignore: parity placeholder; ru definitions.yaml does not define + # CurrencySymbols yet, so enabling this would change runtime behavior. + match: "false()" + variables: + # If the amount is singular, we don't add the plural ending. + - IsSingular: "*[3][self::m:mn and .=1] and *[2]='\u2062'" + - CurrencyWord: "DefinitionValue(*[1], 'Speech', 'CurrencySymbols')" + replace: + - bookmark: "*[3]/@id" + - x: "*[3]" + - test: + - if: "$IsSingular" + then: [x: "$CurrencyWord"] + - else_if: "DefinitionValue($CurrencyWord, 'Speech', 'PluralForms') != ''" + then: [x: "DefinitionValue($CurrencyWord, 'Speech', 'PluralForms')"] + else: [x: "DefinitionValue(*[1], 'Speech', 'CurrencySymbols')"] + - name: sin tag: mi match: ".='sin'" diff --git a/Rules/Languages/ru/SharedRules/geometry.yaml b/Rules/Languages/ru/SharedRules/geometry.yaml index 000c4e654..b8aa4f2df 100644 --- a/Rules/Languages/ru/SharedRules/geometry.yaml +++ b/Rules/Languages/ru/SharedRules/geometry.yaml @@ -7,16 +7,16 @@ - test: if: "$Verbosity='Verbose'" then: - - t: "линейный сегмент от" # phrase('the line segment from' A to B) + - T: "линейный сегмент от" # phrase('the line segment from' A to B) - x: "*[1]" - - t: "до" # phrase(the line segment from A 'to' B) + - T: "до" # phrase(the line segment from A 'to' B) - x: "*[2]" else: - - t: "отрезок" # phrase(the 'line segment' A B) + - T: "отрезок" # phrase(the 'line segment' A B) - x: "translate(*[1], 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')" - - t: "большое" + - T: "большое" - x: "translate(*[2], 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')" - - t: "большое" + - T: "большое" - name: geometry-ray tag: ray @@ -25,16 +25,16 @@ - test: if: "$Verbosity='Verbose'" then: - - t: "луч от " # phrase('the ray from' A to B) + - T: "луч от " # phrase('the ray from' A to B) - x: "*[1]" - - t: "до" # phrase(the ray from A 'to' B) + - T: "до" # phrase(the ray from A 'to' B) - x: "*[2]" else: - - t: "луч" # phrase(the 'ray'A B) + - T: "луч" # phrase(the 'ray'A B) - x: "translate(*[1], 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')" - - t: "большое" + - T: "большое" - x: "translate(*[2], 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')" - - t: "большое" + - T: "большое" - name: geometry-arc tag: arc @@ -42,12 +42,12 @@ replace: - test: if: "$Verbosity='Verbose'" - then: [t: "а"] # phrase('the' arc A B C) - - t: "дуга" # phrase(the 'arc' A B C) + then: [T: "а"] # phrase('the' arc A B C) + - T: "дуга" # phrase(the 'arc' A B C) - x: "translate(*[1], 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')" - - t: "большое" + - T: "большое" - x: "translate(*[2], 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')" - - t: "большое" + - T: "большое" - name: measure-of-angle tag: measure-of-angle @@ -56,9 +56,9 @@ - test: if: "$Verbosity='Verbose'" then: - - t: "мера угла" # phrase('the measure of the angle' ABC) + - T: "мера угла" # phrase('the measure of the angle' ABC) else: - - t: "мера угла" # phrase('measure of angle' ABC) + - T: "мера угла" # phrase('measure of angle' ABC) - x: "*[1]" - x: "*[2]" - x: "*[3]" @@ -70,16 +70,16 @@ replace: - test: if: "$Verbosity='Verbose'" - then: [t: "а"] # phrase('the' point at 1, 2) - - t: "точка" # phrase(the 'point' at 1, 2) + then: [T: "а"] # phrase('the' point at 1, 2) + - T: "точка" # phrase(the 'point' at 1, 2) - test: if: "$Verbosity='Verbose'" - then: [t: "в"] # phrase('the' point at 1, 2) + then: [T: "в"] # phrase('the' point at 1, 2) - pause: short - insert: nodes: "*" - replace: [t: "запятая", pause: auto] # phrase(f of x 'comma' y) + replace: [T: "запятая", pause: auto] # phrase(f of x 'comma' y) - pause: short - test: if: "($SpeechStyle='ClearSpeak' and $Verbosity='Verbose') or not(IsNode(*[last()],'leaf'))" - then: [t: "конечная точка"] # phrase(start point, 'end point') + then: [T: "конечная точка"] # phrase(start point, 'end point') diff --git a/Rules/Languages/ru/SharedRules/linear-algebra.yaml b/Rules/Languages/ru/SharedRules/linear-algebra.yaml index 974007bba..5117f3292 100644 --- a/Rules/Languages/ru/SharedRules/linear-algebra.yaml +++ b/Rules/Languages/ru/SharedRules/linear-algebra.yaml @@ -7,16 +7,16 @@ - test: if: "$Verbosity='Verbose'" then: - - t: "а" # phrase('the' square root of 25 equals 5) - - t: "определяющий" # phrase(the 'determinant' of a matrix) + - T: "а" # phrase('the' square root of 25 equals 5) + - T: "определяющий" # phrase(the 'determinant' of a matrix) - test: if: "$Verbosity!='Terse'" then: - - t: "из" # phrase(systems 'of' linear equations) + - T: "из" # phrase(systems 'of' linear equations) - x: "*[1]" - test: if: "not(IsNode(*[1], 'simple')) and $Impairment = 'Blindness'" - then: [t: "конец определителя"] # phrase('end determinant' of a matrix) + then: [T: "конец определителя"] # phrase('end determinant' of a matrix) - name: subscripted-norm @@ -26,11 +26,11 @@ - test: if: "$Verbosity='Verbose'" then: - - t: "а" # phrase('the' square root of 25 equals 5) + - T: "а" # phrase('the' square root of 25 equals 5) - x: "*[2]" - - t: "норма" # phrase(the 'norm' can be a measure of distance) + - T: "норма" # phrase(the 'norm' can be a measure of distance) - test: if: "$Verbosity!='Terse'" then: - - t: "из" # phrase(systems 'of' linear equations) + - T: "из" # phrase(systems 'of' linear equations) - x: "*[1]" diff --git a/Rules/Languages/ru/navigate.yaml b/Rules/Languages/ru/navigate.yaml index 745c13857..527dcce60 100644 --- a/Rules/Languages/ru/navigate.yaml +++ b/Rules/Languages/ru/navigate.yaml @@ -346,6 +346,39 @@ match: "$Move2D != ''" replace: [] +- name: into-or-out-of-silent + tag: "*" + # audit-ignore: parity placeholder; Russian navigation keeps existing rules. + # Present for parity with the English navigation rules. Kept after default-move + # so the existing Russian navigation phrasing keeps its historical priority. + match: "false()" + replace: [] + +- name: into-or-out-of-default + tag: "*" + # audit-ignore: parity placeholder; Russian navigation keeps existing rules. + # Present for parity with the English navigation rules. Kept after default-move + # so the existing Russian navigation phrasing keeps its historical priority. + match: "false()" + replace: + - with: + variables: + - PartNumber: "count($Child2D/preceding-sibling::*)" + - PartName: "GetNavigationPartName(name(.), $PartNumber)" + replace: + - x: "$Move2D" + - test: + - if: "$PartName != ''" + then: [x: "$PartName"] + - else_if: "count(*) = 1" + then_test: + if: "$NavVerbosity = 'Verbose'" + then: [x: "translate(name(.), '-_', ' ')"] # e.g., "in absolute value" + else: + - t: "часть" # phrase(the 'part' of the expression) + - x: "count($Child2D/preceding-sibling::*) + 1" + - pause: "medium" + # ********* Go back to last position *************** # This is first since start/end position shouldn't matter - name: move-last-location @@ -402,6 +435,17 @@ - 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: "*" + # audit-ignore: parity placeholder; the existing mtable-specific rule below + # preserves current Russian navigation behavior. + match: "false()" + replace: + - with: + variables: [SayCommand: "string($NavVerbosity = 'Verbose')"] + replace: [x: "."] + - set_variables: [NavNode: "IfThenElse(count(*)=1, (*[1]/*[1]/@id), (*[1]/@id))"] + - name: zoom-in-table tag: mtable match: "$NavCommand = 'ZoomIn'" @@ -486,6 +530,46 @@ variables: [NavCommand: "'MoveNextZoom'"] replace: [x: "*[1]"] +- name: zoom-in-simple + tag: "*" + # audit-ignore: parity placeholder; covered by zoom-in-2D-not-enhanced/default. + match: "false()" + replace: + - with: + variables: [SayCommand: "string($NavVerbosity = 'Verbose')"] + replace: [x: "."] + - test: + if: "$MatchCounter > 1 and IsNode(., '2D')" + then: + - set_variables: # time to stop, not going "in" to next thing, so before "Move2D" + - NavNode: "@id" + - NavNodeOffset: "IfThenElse(@data-id-offset, @data-id-offset, '0')" + else: + - with: + variables: [Move2D: "'in'", Child2D: "*[1]"] # phrase('in' the denominator) + replace: [x: "."] + - x: "*[1]" + +- name: zoom-in-2D-character + tag: "*" + # audit-ignore: parity placeholder; covered by zoom-in-2D-not-enhanced/default. + match: "false()" + replace: + - with: + variables: [SayCommand: "string($NavVerbosity = 'Verbose')"] + replace: [x: "."] + - with: + variables: [Move2D: "'in'", Child2D: "*[1]"] # phrase('in' the denominator) + replace: [x: "."] + - test: + if: "$NavMode = 'Simple'" + then: + - set_variables: [NavNode: "*[1]/@id"] + else: + - with: + variables: [NavCommand: "'MoveNextZoom'"] + replace: [x: "*[1]"] + - name: zoom-in-default tag: "*" match: "$NavCommand = 'ZoomIn'" @@ -1231,9 +1315,29 @@ # ======== Move/Read/Describe Next rules ================= +- name: move-next-character + tag: [mn, mi, mtext] + # audit-ignore: parity placeholder; current Russian navigation uses whole-token + # movement for these nodes. + match: "false()" + replace: + - test: + if: "$NavNodeOffset < string-length(.)" + then: + - with: + variables: [SayCommand: "string($NavVerbosity = 'Verbose')"] + replace: [x: "."] + - set_variables: [NavNodeOffset: "$NavNodeOffset + 1"] + else: + - with: + variables: [SayCommand: "string($NavVerbosity = 'Verbose')"] + replace: [x: "."] + - set_variables: [NavNodeOffset: "0"] + - x: "." + # skip 'none' - name: move-next-none - tag: "none" + tag: none match: - "($NavCommand = 'MoveNext' or $NavCommand = 'ReadNext' or $NavCommand = 'DescribeNext' or $NavCommand = 'DescribePrevious' or $NavCommand = 'MoveNextZoom') and" - "parent::*[1][name(.)='mmultiscripts'] and following-sibling::*" @@ -1253,9 +1357,16 @@ variables: [NavCommand: "'MoveNextZoom'"] replace: [x: "$Following"] +# audit-ignore: parity placeholder for English tag list without changing the +# existing Russian none-specific rule above. +- name: move-next-none + tag: [none, mprescripts] + match: "false()" + replace: [] + # skip 'none' - name: move-previous-none - tag: "none" + tag: none match: - "($NavCommand = 'MovePrevious' or $NavCommand = 'ReadPrevious' or $NavCommand = 'DescribePrevious' or $NavCommand = 'MovePreviousZoom') and" - "parent::*[1][name(.)='mmultiscripts'] and preceding-sibling::*" @@ -1275,6 +1386,13 @@ variables: [NavCommand: "'MovePreviousZoom'"] replace: [x: "$Preceding"] +# audit-ignore: parity placeholder for English tag list without changing the +# existing Russian none-specific rule above. +- name: move-previous-none + tag: [none, mprescripts] + match: "false()" + replace: [] + # skip invisible chars except for Enhanced mode when "times" should be read @@ -1534,6 +1652,26 @@ - pause: long - set_variables: [SpeakExpression: "'false'"] +- name: move-prev-character + tag: [mn, mi, mtext] + # audit-ignore: parity placeholder; current Russian navigation uses whole-token + # movement for these nodes. + match: "false()" + replace: + - test: + if: "$NavNodeOffset > 1" + then: + - with: + variables: [SayCommand: "string($NavVerbosity = 'Verbose')"] + replace: [x: "."] + - set_variables: [NavNodeOffset: "$NavNodeOffset - 1"] + else: + - with: + variables: [SayCommand: "string($NavVerbosity = 'Verbose')"] + replace: [x: "."] + - set_variables: [NavNodeOffset: "0"] + - x: "." + - 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 diff --git a/Rules/Languages/ru/overview.yaml b/Rules/Languages/ru/overview.yaml index 131c9b701..2341d4042 100644 --- a/Rules/Languages/ru/overview.yaml +++ b/Rules/Languages/ru/overview.yaml @@ -21,22 +21,22 @@ if: "IsNode(*[1], 'simple') and IsNode(*[2], 'simple')" then: - x: "*[1]" - - t: "разделить на" # (en: 'over') + - T: "разделить на" # (en: 'over') - x: "*[2]" else: - - t: "дробь, числитель:" # (en: 'fraction') + - T: "дробь, числитель:" # (en: 'fraction') - x: "*[1]" - - t: "знаменатель:" + - T: "знаменатель:" - x: "*[2]" - name: overview-default tag: [msqrt, "square-root"] match: "." replace: - - t: "квадратный корень" + - T: "квадратный корень" - test: if: "$Verbosity!='Terse'" - then: [t: "из"] + then: [T: "из"] - x: "*[1]" - name: overview-default @@ -47,11 +47,11 @@ if: "*[2][self::m:mn]" then_test: - if: "*[2][.='2']" - then: [t: "квадратный корень"] + then: [T: "квадратный корень"] - else_if: "*[2][.='3']" - then: [t: "кубический корень"] # (en: 'cube root') + then: [T: "кубический корень"] # (en: 'cube root') - else_if: "*[2][not(contains(., '.'))]" - then: [x: "ToOrdinal(*[2])", t: "корень"] + then: [x: "ToOrdinal(*[2])", T: "корень"] else: - test: if: "*[2][self::m:mi][string-length(.)=1]" @@ -59,10 +59,10 @@ - x: "*[2]" - pronounce: [{text: "-й"}, {ipa: "j"}, {sapi5: "th"}, {eloquence: "T"}] else: {x: "*[2]"} - - t: "корень" + - T: "корень" - test: if: "$Verbosity!='Terse'" - then: [t: "из"] + then: [T: "из"] - x: "*[1]" - name: matrix-override @@ -71,24 +71,24 @@ - "*[2][self::m:mtable] and" - "(IsBracketed(., '(', ')') or IsBracketed(., '[', ']') or IsBracketed(., '|', '|'))" replace: - - t: "на" # (en: 'a') + - T: "на" # (en: 'a') - x: count(*[2]/*) - - t: "на" # (en: 'by') + - T: "на" # (en: 'by') - x: count(*[2]/*[self::m:mtr][1]/*) - test: if: "*[1][.='|']" # just need to check the first bracket since we know it must be (, [, or | - then: [t: "определитель"] # (en: 'determinant') - else: [t: "матрица"] # (en: 'matrix') + then: [T: "определитель"] # (en: 'determinant') + else: [T: "матрица"] # (en: 'matrix') - name: overview-default tag: mtable match: "." replace: - - t: "на" # (en: 'a') + - T: "на" # (en: 'a') - x: count(*) - - t: "на" # (en: 'by') + - T: "на" # (en: 'by') - x: count(*[self::m:mtr][1]/*) - - t: "таблица" # (en: 'table') + - T: "таблица" # (en: 'table') - name: short-mrow tag: mrow @@ -112,6 +112,6 @@ - pause: auto - x: "*[5]" - pause: auto - - t: "и так далее" # (en: 'and so on') + - T: "и так далее" # (en: 'and so on') - include: "SimpleSpeak_Rules.yaml" diff --git a/src/braille.rs b/src/braille.rs index 2bcba4edc..eb9704739 100644 --- a/src/braille.rs +++ b/src/braille.rs @@ -2169,8 +2169,11 @@ fn swedish_cleanup(pref_manager: Ref, raw_braille: String) -> } fn russian_cleanup(_pref_manager: Ref, raw_braille: String) -> String { - static REPLACE_INDICATORS: LazyLock = LazyLock::new(|| Regex::new(r"([BCILNW#])").unwrap()); + static REPLACE_INDICATORS: LazyLock = LazyLock::new(|| Regex::new(r"([LNW#])").unwrap()); static COLLAPSE_SPACES: LazyLock = LazyLock::new(|| Regex::new(r"⠀+").unwrap()); + static PERIODIC_DECIMAL_NUM_INDICATOR: LazyLock = LazyLock::new(|| { + Regex::new(r"(N[⠚⠁⠃⠉⠙⠑⠋⠛⠓⠊]+⠂[⠚⠁⠃⠉⠙⠑⠋⠛⠓⠊]*⠣)N([⠚⠁⠃⠉⠙⠑⠋⠛⠓⠊]+⠜)").unwrap() + }); let mut raw_braille_without_repeated_number_indicators = String::with_capacity(raw_braille.len()); let mut previous_char_was_digit = false; @@ -2183,12 +2186,13 @@ fn russian_cleanup(_pref_manager: Ref, raw_braille: String) - previous_char_was_digit = matches!(ch, '⠚' | '⠁' | '⠃' | '⠉' | '⠙' | '⠑' | '⠋' | '⠛' | '⠓' | '⠊'); } - let result = add_russian_alphabet_indicators(&raw_braille_without_repeated_number_indicators); + let raw_braille_without_periodic_number_indicator = PERIODIC_DECIMAL_NUM_INDICATOR + .replace_all(&raw_braille_without_repeated_number_indicators, "$1$2"); + let result = add_russian_typeform_indicators(&raw_braille_without_periodic_number_indicator); + let result = add_russian_alphabet_indicators(&result); let result = REPLACE_INDICATORS.replace_all(&result, |cap: &Captures| { match &cap[0] { - "B" => "⠸", - "C" => "⠠", - "I" => "⠨", + "C" => "⠨", "L" => "", "N" => "⠼", "W" => "⠀", @@ -2201,12 +2205,111 @@ fn russian_cleanup(_pref_manager: Ref, raw_braille: String) - .trim_matches('⠀') .to_string(); + fn add_russian_typeform_indicators(raw_braille: &str) -> String { + let mut result = String::with_capacity(raw_braille.len()); + let mut active_typeforms: Vec = Vec::with_capacity(2); + let mut pending_typeforms: Vec = Vec::with_capacity(2); + let mut previous_was_capital_marker = false; + + for ch in raw_braille.chars() { + if is_russian_typeform_marker(ch) { + if !pending_typeforms.contains(&ch) { + pending_typeforms.push(ch); + } + previous_was_capital_marker = false; + continue; + } + if matches!(ch, 's' | 'w' | 'e') && (!pending_typeforms.is_empty() || !active_typeforms.is_empty()) { + continue; + } + + if is_russian_token_start(ch) { + if !previous_was_capital_marker { + close_inactive_typeforms(&mut result, &mut active_typeforms, &pending_typeforms); + } + for &typeform in &pending_typeforms { + if !active_typeforms.contains(&typeform) { + result.push_str(russian_typeform_indicator(typeform)); + active_typeforms.push(typeform); + } + } + pending_typeforms.clear(); + } else if !pending_typeforms.is_empty() { + close_inactive_typeforms(&mut result, &mut active_typeforms, &pending_typeforms); + for &typeform in &pending_typeforms { + if !active_typeforms.contains(&typeform) { + result.push_str(russian_typeform_indicator(typeform)); + result.push_str(russian_typeform_indicator(typeform)); + } + } + pending_typeforms.clear(); + } else if !active_typeforms.is_empty() && !is_russian_token_body(ch) { + close_all_typeforms(&mut result, &mut active_typeforms); + } + + result.push(ch); + previous_was_capital_marker = ch == 'C'; + } + + close_all_typeforms(&mut result, &mut active_typeforms); + return result; + } + + fn is_russian_typeform_marker(ch: char) -> bool { + matches!(ch, 'B' | 'I' | 'T' | 'D' | 'S' | '𝔹') + } + + fn is_russian_token_start(ch: char) -> bool { + matches!(ch, 'l' | 'u' | 'g' | 'v' | 'L' | 'N' | 'C') + } + + fn is_russian_token_body(ch: char) -> bool { + ('\u{2801}'..='\u{28ff}').contains(&ch) + } + + fn close_inactive_typeforms(result: &mut String, active_typeforms: &mut Vec, pending_typeforms: &[char]) { + let mut i = active_typeforms.len(); + while i > 0 { + i -= 1; + if !pending_typeforms.contains(&active_typeforms[i]) { + let typeform = active_typeforms.remove(i); + result.push_str(russian_typeform_indicator(typeform)); + } + } + } + + fn close_all_typeforms(result: &mut String, active_typeforms: &mut Vec) { + while let Some(typeform) = active_typeforms.pop() { + result.push_str(russian_typeform_indicator(typeform)); + } + } + + fn russian_typeform_indicator(typeform: char) -> &'static str { + match typeform { + 'B' => "⠻", // ГОСТ Р 58511: жирный шрифт, точки 12456. + 'I' | 'T' | 'D' | 'S' | '𝔹' => "⠸", // ГОСТ Р 58511: курсив/шрифтовое выделение, точки 456. + _ => "", + } + } + fn add_russian_alphabet_indicators(raw_braille: &str) -> String { let mut result = String::with_capacity(raw_braille.len()); let mut alphabet_mode = None; + let mut capital_marker_pending = false; for ch in raw_braille.chars() { match ch { + 'C' => { + if alphabet_mode != Some('u') { + result.push('⠨'); + alphabet_mode = Some('u'); + } + capital_marker_pending = true; + }, + 'l' if capital_marker_pending => { + capital_marker_pending = false; + }, 'l' | 'u' | 'g' | 'v' => { + capital_marker_pending = false; if alphabet_mode != Some(ch) { result.push_str(match ch { 'l' => "⠠", // Latin lowercase: dots 6 @@ -2218,11 +2321,15 @@ fn russian_cleanup(_pref_manager: Ref, raw_braille: String) - alphabet_mode = Some(ch); } }, - 'C' | 'N' | '#' => { + 'N' | '#' => { + capital_marker_pending = false; alphabet_mode = None; result.push(ch); }, - _ => result.push(ch), + _ => { + capital_marker_pending = false; + result.push(ch); + }, } } return result; @@ -2392,7 +2499,7 @@ impl BrailleChars { "Vietnam" => BrailleChars:: get_braille_vietnam_chars(node, text_range), "Swedish" => BrailleChars:: get_braille_ueb_chars(node, text_range), // FIX: need to figure out what to implement "Finnish" => BrailleChars:: get_braille_ueb_chars(node, text_range), // FIX: need to figure out what to implement - "Russian" => BrailleChars:: get_braille_ueb_chars(node, text_range), + "Russian" => BrailleChars:: get_braille_russian_chars(node, text_range), _ => return Err(sxd_xpath::function::Error::Other(format!("get_braille_chars: unknown braille code '{code}'"))) }; return match result { @@ -2529,6 +2636,41 @@ impl BrailleChars { return Ok(result.to_string()) } + fn get_braille_russian_chars(node: Element, text_range: Option>) -> Result { + let text = BrailleChars::substring(as_text(node), &text_range); + let braille_chars = braille_replace_chars(&text, node)?; + let Some(math_variant) = node.attribute_value("mathvariant") else { + return Ok(braille_chars); + }; + + let mut prefix = String::new(); + let mut suffix = String::new(); + if math_variant.contains("bold") && !braille_chars.contains('B') { + prefix.push('⠻'); + suffix.insert(0, '⠻'); + } + if math_variant.contains("italic") && !braille_chars.contains('I') { + prefix.push('⠸'); + suffix.insert(0, '⠸'); + } + let has_typeface_marker = braille_chars.contains('T') + || braille_chars.contains('D') + || braille_chars.contains('S') + || braille_chars.contains('𝔹'); + if !has_typeface_marker { + let typeface = match math_variant { + "double-struck" | "script" | "fraktur" | "sans-serif" => Some("⠸"), + _ => None, + }; + if let Some(indicator) = typeface { + prefix.push_str(indicator); + suffix.insert_str(0, indicator); + } + } + + return Ok(prefix + &braille_chars + &suffix); + } + fn get_braille_cmu_chars(node: Element, text_range: Option>) -> Result { // In CMU, we need to replace spaces used for number blocks with "." // For other numbers, we need to add "." to create digit blocks diff --git a/tests/braille/Russian/russian.rs b/tests/braille/Russian/russian.rs index 4d2f9c838..ae712c41c 100644 --- a/tests/braille/Russian/russian.rs +++ b/tests/braille/Russian/russian.rs @@ -13,12 +13,15 @@ fn script_grouping_regressions() -> Result<()> { ("sup_sub_x2", r#"ax2"#), ("sup_nested", r#"ax2"#), ("sup_frac", r#"a12"#), + ("sup_complex_frac", r#"ax+1y"#), ("sup_sqrt", r#"ax"#), ("sup_follow_letter", r#"x2y"#), ("sup_follow_number", r#"x23"#), ("sub_x", r#"ax"#), ("sub_zero", r#"x0"#), ("sub_ten", r#"x10"#), + ("sub_digits_sequence", r#"D13"#), + ("sub_infinity", r#"t"#), ("sub_minus_x", r#"a-x"#), ("sub_minus_2", r#"a-2"#), ("sub_x_plus_1", r#"ax+1"#), @@ -44,6 +47,9 @@ fn script_grouping_regressions() -> Result<()> { ("sup_after_fraction", r#"x+1y-12"#), ("sub_after_fraction", r#"x+1y-1i"#), ("tensor_like", r#"Tijxjk"#), + ("mmultiscripts_left_sub", r#"Fk"#), + ("mmultiscripts_left_sup", r#"W4"#), + ("mmultiscripts_both_sides", r#"Tijrs"#), ("pre_negative_power", r#"2x-1"#), ("power_of_power_follow", r#"x23y"#), ("subscripted_power_follow", r#"x23y"#), @@ -60,12 +66,15 @@ fn script_grouping_regressions() -> Result<()> { ("sup_sub_x2", "⠠⠁⠌⠭⠡⠆⠱"), ("sup_nested", "⠠⠁⠌⠭⠌⠆⠱"), ("sup_frac", "⠠⠁⠌⠼⠁⠆⠱"), + ("sup_complex_frac", "⠠⠁⠌⠐⠆⠭⠀⠖⠼⠁⠀⠳⠠⠽⠰⠱"), ("sup_sqrt", "⠠⠁⠌⠩⠱⠭⠹⠱"), ("sup_follow_letter", "⠠⠭⠌⠆⠽"), ("sup_follow_number", "⠠⠭⠌⠆⠼⠉"), ("sub_x", "⠠⠁⠡⠭⠱"), ("sub_zero", "⠠⠭⠡⠴"), ("sub_ten", "⠠⠭⠡⠂⠴"), + ("sub_digits_sequence", "⠨⠙⠡⠂⠒"), + ("sub_infinity", "⠠⠞⠡⠻"), ("sub_minus_x", "⠠⠁⠡⠀⠤⠭⠱"), ("sub_minus_2", "⠠⠁⠡⠤⠆"), ("sub_x_plus_1", "⠠⠁⠡⠐⠭⠀⠖⠼⠁⠱"), @@ -91,6 +100,9 @@ fn script_grouping_regressions() -> Result<()> { ("sup_after_fraction", "⠆⠠⠭⠀⠖⠼⠁⠀⠳⠠⠽⠀⠤⠼⠁⠰⠌⠆"), ("sub_after_fraction", "⠆⠠⠭⠀⠖⠼⠁⠀⠳⠠⠽⠀⠤⠼⠁⠰⠡⠠⠊⠱"), ("tensor_like", "⠨⠞⠡⠠⠊⠌⠚⠱⠭⠡⠚⠌⠅⠱"), + ("mmultiscripts_left_sub", "⠡⠠⠅⠱⠨⠋"), + ("mmultiscripts_left_sup", "⠌⠲⠨⠺"), + ("mmultiscripts_both_sides", "⠡⠠⠗⠱⠌⠎⠱⠨⠞⠡⠠⠊⠱⠌⠚⠱"), ("pre_negative_power", "⠼⠃⠠⠭⠌⠤⠂"), ("power_of_power_follow", "⠠⠭⠌⠆⠌⠒⠽"), ("subscripted_power_follow", "⠠⠭⠡⠆⠌⠒⠽"), @@ -110,6 +122,42 @@ fn numbers_and_operators() -> Result<()> { return Ok(()); } +#[test] +fn source_general_math_text_rules() -> Result<()> { + let expr = r#"5,§2"#; + test_braille("Russian", expr, "⠝⠼⠑⠠⠂⠬⠼⠃")?; + + let expr = r#"1+23++10=55."#; + test_braille("Russian", expr, "⠼⠁⠀⠖⠼⠃⠐⠼⠉⠀⠖⠠⠄⠀⠖⠼⠁⠚⠠⠀⠶⠼⠑⠑⠠⠲")?; + + let expr = r#"xy2"#; + test_braille("Russian", expr, "⠠⠭⠐⠽⠳⠆")?; + + let expr = r#"1+23+4"#; + test_braille("Russian", expr, "⠼⠁⠀⠖⠼⠃⠐⠼⠉⠀⠖⠼⠙")?; + + let expr = r#"1+23+4"#; + test_braille("Russian", expr, "⠼⠁⠀⠖⠼⠃⠠⠼⠉⠀⠖⠼⠙")?; + return Ok(()); +} + +#[test] +fn percent_and_special_marks() -> Result<()> { + let expr = r#"25%"#; + test_braille("Russian", expr, "⠼⠃⠑⠼⠍⠴")?; + + let expr = r#"0,56"#; + test_braille("Russian", expr, "⠼⠚⠂⠑⠋")?; + return Ok(()); +} + +#[test] +fn labeled_table_rows() -> Result<()> { + let expr = r#"(1)x=0"#; + test_braille("Russian", expr, "⠍⠑⠞⠅⠁⠀⠎⠞⠗⠕⠅⠊⠀⠲⠣⠼⠁⠜⠀⠠⠭⠀⠶⠀⠼⠚")?; + return Ok(()); +} + #[test] fn fraction() -> Result<()> { let expr = r#"x2"#; @@ -148,6 +196,18 @@ fn source_arithmetic_examples() -> Result<()> { let expr = r#"12×35=420"#; test_braille("Russian", expr, "⠼⠁⠃⠀⠦⠼⠉⠑⠀⠶⠼⠙⠃⠚")?; + + let expr = r#"7456+5623=13079"#; + test_braille("Russian", expr, "⠼⠛⠙⠑⠋⠨⠳⠖⠑⠋⠃⠉⠨⠳⠶⠁⠉⠚⠛⠊")?; + + let expr = r#"78650-1952=76698"#; + test_braille("Russian", expr, "⠼⠛⠓⠋⠑⠚⠨⠳⠤⠁⠊⠑⠃⠨⠳⠶⠛⠋⠋⠊⠓")?; + + let expr = r#"327×54826161308=179196"#; + test_braille("Russian", expr, "⠼⠉⠃⠛⠨⠳⠦⠑⠙⠓⠨⠳⠃⠋⠁⠋⠨⠳⠁⠉⠚⠓⠨⠳⠶⠁⠛⠊⠁⠊⠋")?; + + let expr = r#"2982÷14=21328181442420"#; + test_braille("Russian", expr, "⠼⠃⠊⠓⠃⠀⠴⠀⠼⠁⠙⠀⠶⠀⠼⠃⠁⠉⠨⠳⠃⠓⠨⠳⠁⠓⠨⠳⠁⠙⠨⠳⠙⠃⠨⠳⠙⠃⠨⠳⠚")?; return Ok(()); } @@ -174,6 +234,44 @@ fn source_simple_fractions_scripts_roots() -> Result<()> { return Ok(()); } +#[test] +fn source_thin_fraction_rules() -> Result<()> { + let expr = r#"a3,2"#; + test_braille("Russian", expr, "⠠⠁⠳⠼⠉⠂⠃")?; + + let expr = r#"a-3"#; + test_braille("Russian", expr, "⠠⠁⠳⠤⠒")?; + + let expr = r#"3x+yz"#; + test_braille("Russian", expr, "⠼⠉⠄⠆⠠⠭⠀⠖⠽⠀⠳⠵⠰")?; + + let expr = r#"xyz"#; + test_braille("Russian", expr, "⠠⠭⠳⠽⠄⠵")?; + + let expr = r#"xa+ba-b"#; + test_braille("Russian", expr, "⠠⠭⠌⠐⠆⠁⠀⠖⠃⠀⠳⠁⠀⠤⠃⠰⠱")?; + return Ok(()); +} + +#[test] +fn source_index_rules() -> Result<()> { + let expr = r#"D1,3"#; + test_braille("Russian", expr, "⠨⠙⠡⠼⠁⠠⠂⠼⠉⠱")?; + + let expr = r#"Ps,1"#; + test_braille("Russian", expr, "⠨⠏⠡⠠⠎⠠⠂⠼⠁⠱")?; + + let expr = r#"a2kn+1"#; + test_braille("Russian", expr, "⠠⠁⠡⠼⠃⠠⠅⠱⠌⠝⠀⠖⠼⠁⠱")?; + + let expr = r#"Z+"#; + test_braille("Russian", expr, "⠨⠵⠡⠐⠀⠖⠻⠱")?; + + let expr = r#"a-n+12"#; + test_braille("Russian", expr, "⠠⠁⠡⠐⠀⠤⠆⠝⠀⠖⠼⠁⠀⠳⠼⠃⠰⠱")?; + return Ok(()); +} + #[test] fn source_gost_numbers_fractions_and_sets() -> Result<()> { let expr = r#"0,56"#; @@ -182,18 +280,97 @@ fn source_gost_numbers_fractions_and_sets() -> Result<()> { let expr = r#"2/3"#; test_braille("Russian", expr, "⠼⠃⠠⠌⠼⠉")?; + let expr = r#"a/b"#; + test_braille("Russian", expr, "⠠⠁⠠⠌⠃")?; + + let expr = r#"км/ч"#; + test_braille("Russian", expr, "⠅⠍⠠⠌⠟")?; + + let expr = r#"25кг"#; + test_braille("Russian", expr, "⠼⠃⠑⠅⠛")?; + + let expr = r#"5м30см"#; + test_braille("Russian", expr, "⠼⠑⠍⠀⠼⠉⠚⠎⠍")?; + + let expr = r#"12м2"#; + test_braille("Russian", expr, "⠼⠁⠃⠍⠌⠆")?; + + let expr = r#"9,8м/с2"#; + test_braille("Russian", expr, "⠼⠊⠂⠓⠍⠠⠌⠎⠌⠆")?; + + let expr = r#"15Нм"#; + test_braille("Russian", expr, "⠼⠁⠑⠨⠝⠄⠍")?; + + let expr = r#"12?4"#; + test_braille("Russian", expr, "⠼⠁⠃⠬⠙")?; + let expr = r#"[1,4]\{4}=[1,4)"#; test_braille("Russian", expr, "⠷⠼⠁⠠⠂⠼⠙⠾⠀⠰⠤⠪⠼⠙⠕⠀⠶⠷⠼⠁⠠⠂⠼⠙⠜")?; + let expr = r#"5|x"#; + test_braille("Russian", expr, "⠼⠑⠸⠠⠭")?; + + let expr = r#"xA"#; + test_braille("Russian", expr, "⠠⠭⠀⠐⠪⠀⠨⠁")?; + + let expr = r#"xA"#; + test_braille("Russian", expr, "⠠⠭⠘⠪⠨⠁")?; + + let expr = r#"AB"#; + test_braille("Russian", expr, "⠨⠁⠘⠪⠃")?; + + let expr = r#"AB"#; + test_braille("Russian", expr, "⠨⠁⠀⠯⠀⠃")?; + + let expr = r#"DE"#; + test_braille("Russian", expr, "⠨⠙⠈⠯⠑")?; + + let expr = r#"AB"#; + test_braille("Russian", expr, "⠨⠁⠈⠯⠃")?; + + let expr = r#"AB"#; + test_braille("Russian", expr, "⠨⠁⠀⠰⠲⠃")?; + + let expr = r#"AB"#; + test_braille("Russian", expr, "⠨⠁⠀⠰⠴⠃")?; + + let expr = r#"AB\CD"#; + test_braille("Russian", expr, "⠨⠁⠀⠰⠴⠃⠀⠰⠤⠉⠀⠰⠲⠙")?; + + let expr = r#"(3,5][7,)="#; + test_braille("Russian", expr, "⠣⠼⠉⠠⠂⠼⠑⠾⠀⠰⠲⠷⠼⠛⠠⠂⠻⠜⠀⠶⠈⠴")?; + let expr = r#"36,6"#; test_braille("Russian", expr, "⠼⠉⠋⠂⠋⠨⠴⠨⠉")?; return Ok(()); } +#[test] +fn source_mixed_numbers_and_periodic_decimals() -> Result<()> { + let expr = r#"0,4(71)"#; + test_braille("Russian", expr, "⠼⠚⠂⠙⠣⠛⠁⠜")?; + + let expr = r#"1,(523)"#; + test_braille("Russian", expr, "⠼⠁⠂⠣⠑⠃⠉⠜")?; + + let expr = r#"0,4(71)"#; + test_braille("Russian", expr, "⠼⠚⠂⠙⠣⠛⠁⠜")?; + + let expr = r#"538"#; + test_braille("Russian", expr, "⠼⠑⠼⠉⠦")?; + + let expr = r#"224+4-1524"#; + test_braille("Russian", expr, "⠼⠃⠐⠆⠼⠃⠙⠀⠖⠼⠙⠀⠤⠼⠁⠑⠀⠳⠼⠃⠙⠰")?; + return Ok(()); +} + #[test] fn latin_alphabet_indicators() -> Result<()> { let expr = r#"x+A+y+B=x+y+A+B"#; test_braille("Russian", expr, "⠠⠭⠀⠖⠨⠁⠀⠖⠠⠽⠀⠖⠨⠃⠀⠶⠠⠭⠀⠖⠽⠀⠖⠨⠁⠀⠖⠃")?; + + let expr = r#"MCDLXIV"#; + test_braille("Russian", expr, "⠨⠍⠉⠙⠇⠭⠊⠧")?; return Ok(()); } @@ -210,6 +387,56 @@ fn alphabet_indicators_after_numbers_and_greek() -> Result<()> { return Ok(()); } +#[test] +fn source_typeform_and_mathvariant_indicators() -> Result<()> { + let expr = r#"x"#; + test_braille("Russian", expr, "⠻⠠⠭⠻")?; + + let expr = r#"y"#; + test_braille("Russian", expr, "⠸⠠⠽⠸")?; + + let expr = r#"z"#; + test_braille("Russian", expr, "⠻⠸⠠⠵⠸⠻")?; + + let expr = r#"AB"#; + test_braille("Russian", expr, "⠻⠨⠁⠃⠻")?; + + let expr = r#"x+y"#; + test_braille("Russian", expr, "⠻⠠⠭⠻⠀⠖⠽")?; + + let expr = r#"𝐱"#; + test_braille("Russian", expr, "⠻⠠⠭⠻")?; + + let expr = r#"𝑥"#; + test_braille("Russian", expr, "⠸⠠⠭⠸")?; + return Ok(()); +} + +#[test] +fn source_gost_58511_typeform_layout_indicators() -> Result<()> { + let expr = r#"xy"#; + test_braille("Russian", expr, "⠻⠠⠭⠽⠻")?; + + let expr = r#"ab"#; + test_braille("Russian", expr, "⠸⠠⠁⠸⠸⠃⠸")?; + + let expr = r#"xy"#; + test_braille("Russian", expr, "⠻⠸⠠⠭⠽⠸⠻")?; + + let expr = r#"x"#; + test_braille("Russian", expr, "⠸⠠⠭⠸")?; + + let expr = r#"x"#; + test_braille("Russian", expr, "⠠⠭")?; + + let expr = r#"AB"#; + test_braille("Russian", expr, "⠨⠁⠀⠃")?; + + let expr = r#"xприy"#; + test_braille("Russian", expr, "⠠⠭⠏⠗⠊⠽")?; + return Ok(()); +} + #[test] fn wikipedia_times_divide() -> Result<()> { let expr = r#"6×7:14=3"#; @@ -228,6 +455,9 @@ fn wikipedia_linear_parens() -> Result<()> { fn wikipedia_sqrt() -> Result<()> { let expr = r#"10000<101"#; test_braille("Russian", expr, "⠩⠱⠼⠁⠚⠚⠚⠚⠹⠀⠪⠀⠼⠁⠚⠁")?; + + let expr = r#"ab"#; + test_braille("Russian", expr, "⠩⠱⠠⠁⠹⠄⠃")?; return Ok(()); } @@ -239,6 +469,24 @@ fn source_functions_logs_derivatives() -> Result<()> { let expr = r#"tgxctgx=1"#; test_braille("Russian", expr, "⠫⠞⠠⠭⠄⠫⠉⠞⠠⠭⠀⠶⠼⠁")?; + let expr = r#"sin2x"#; + test_braille("Russian", expr, "⠫⠎⠌⠆⠠⠭")?; + + let expr = r#"tg3α+β2"#; + test_braille("Russian", expr, "⠫⠞⠌⠒⠐⠆⠰⠁⠀⠖⠃⠀⠳⠼⠃⠰")?; + + let expr = r#"(ctgβ)2m-n+3"#; + test_braille("Russian", expr, "⠣⠫⠉⠞⠰⠃⠜⠌⠼⠃⠠⠍⠀⠤⠝⠀⠖⠼⠉⠱")?; + + let expr = r#"arcsin2x"#; + test_braille("Russian", expr, "⠫⠁⠎⠌⠆⠠⠭")?; + + let expr = r#"arccosn+1x"#; + test_braille("Russian", expr, "⠫⠁⠉⠌⠠⠝⠀⠖⠼⠁⠱⠠⠭")?; + + let expr = r#"arctg3x+y2"#; + test_braille("Russian", expr, "⠫⠁⠞⠌⠒⠐⠆⠠⠭⠀⠖⠽⠀⠳⠼⠃⠰")?; + let expr = r#"tanx+cotx"#; test_braille("Russian", expr, "⠫⠞⠠⠭⠀⠖⠫⠉⠞⠠⠭")?; @@ -268,6 +516,12 @@ fn source_functions_logs_derivatives() -> Result<()> { let expr = r#"y(x)=f(x)"#; test_braille("Russian", expr, "⠠⠽⠔⠣⠭⠜⠀⠶⠋⠣⠭⠜")?; + + let expr = r#"y(x)=limxx0y(x)-y(x0)x-x0"#; + test_braille("Russian", expr, "⠠⠽⠔⠣⠭⠜⠀⠶⠫⠇⠍⠨⠡⠠⠭⠀⠒⠕⠭⠡⠴⠱⠆⠽⠣⠭⠜⠀⠤⠽⠣⠭⠡⠴⠜⠀⠳⠭⠀⠤⠭⠡⠴⠰")?; + + let expr = r#"xnaприn+"#; + test_braille("Russian", expr, "⠠⠭⠡⠝⠱⠀⠒⠕⠁⠀⠏⠗⠊⠀⠝⠀⠒⠕⠀⠖⠻")?; return Ok(()); } @@ -289,9 +543,33 @@ fn source_geometry_matrix_chemistry() -> Result<()> { let expr = r#"ABC=15°3012"#; test_braille("Russian", expr, "⠸⠪⠨⠁⠃⠉⠀⠶⠼⠁⠑⠨⠴⠼⠉⠚⠨⠔⠼⠁⠃⠨⠔⠔")?; + let expr = r#"ABC"#; + test_braille("Russian", expr, "⠸⠪⠨⠁⠔⠃⠔⠉⠔")?; + + let expr = r#"A1B1C1"#; + test_braille("Russian", expr, "⠸⠙⠨⠁⠡⠂⠃⠡⠂⠉⠡⠂")?; + + let expr = r#"2ABC"#; + test_braille("Russian", expr, "⠼⠃⠄⠸⠪⠨⠁⠃⠉")?; + + let expr = r#"3ABC"#; + test_braille("Russian", expr, "⠼⠉⠄⠸⠙⠨⠁⠃⠉")?; + + let expr = r#"4EF"#; + test_braille("Russian", expr, "⠼⠙⠄⠸⠜⠨⠑⠋")?; + + let expr = r#"KLMPQR"#; + test_braille("Russian", expr, "⠸⠙⠨⠅⠇⠍⠀⠢⠸⠙⠨⠏⠟⠗")?; + let expr = r#"ABCD"#; test_braille("Russian", expr, "⠨⠁⠃⠸⠸⠨⠉⠙")?; + let expr = r#"ab"#; + test_braille("Russian", expr, "⠠⠁⠼⠄⠃")?; + + let expr = r#"ABCD"#; + test_braille("Russian", expr, "⠨⠁⠃⠼⠄⠉⠙")?; + let expr = r#"a=2"#; test_braille("Russian", expr, "⠠⠁⠒⠂⠀⠶⠼⠃")?; @@ -301,15 +579,39 @@ fn source_geometry_matrix_chemistry() -> Result<()> { let expr = r#"KL¯=4PQ¯"#; test_braille("Russian", expr, "⠨⠅⠇⠨⠒⠀⠶⠼⠙⠄⠨⠏⠟⠨⠒")?; + let expr = r#"ABCD"#; + test_braille("Russian", expr, "⠨⠁⠃⠨⠒⠂⠸⠸⠨⠉⠙⠨⠒⠂")?; + let expr = r#"EF=KL"#; test_braille("Russian", expr, "⠸⠜⠨⠑⠋⠀⠶⠸⠜⠨⠅⠇")?; + let expr = r#"ABCD"#; + test_braille("Russian", expr, "⠨⠁⠃⠨⠒⠂⠂⠸⠸⠨⠉⠙⠰⠨⠒⠂⠂")?; + + let expr = r#"AB"#; + test_braille("Russian", expr, "⠨⠁⠃⠨⠒⠂")?; + + let expr = r#"AB¯"#; + test_braille("Russian", expr, "⠨⠁⠃⠨⠒")?; + + let expr = r#"AB"#; + test_braille("Russian", expr, "⠨⠁⠃⠨⠒⠂⠂")?; + + let expr = r#"AB=KL¯"#; + test_braille("Russian", expr, "⠨⠁⠃⠰⠨⠒⠂⠀⠶⠅⠇⠰⠨⠒")?; + let expr = r#"(abcd)"#; test_braille("Russian", expr, "⠣⠠⠁⠀⠃⠨⠳⠉⠀⠙⠜")?; let expr = r#"{x+y+z=1x+2y+3z=2x+3y+2z=5"#; test_braille("Russian", expr, "⠏⠀⠠⠭⠀⠖⠽⠀⠖⠵⠀⠶⠼⠁⠨⠳⠇⠀⠠⠭⠀⠖⠼⠃⠠⠽⠀⠖⠼⠉⠠⠵⠀⠶⠼⠃⠨⠳⠧⠀⠠⠭⠀⠖⠼⠉⠠⠽⠀⠖⠼⠃⠠⠵⠀⠶⠼⠑")?; + let expr = r#"1ваза-5цветов2ваза-3цветка3ваза-?цветов}"#; + test_braille("Russian", expr, "⠼⠁⠺⠁⠵⠁⠀⠤⠼⠑⠉⠺⠑⠞⠕⠺⠀⠹⠨⠳⠼⠃⠺⠁⠵⠁⠀⠤⠼⠉⠉⠺⠑⠞⠅⠁⠀⠸⠨⠳⠼⠉⠺⠁⠵⠁⠀⠤⠠⠢⠉⠺⠑⠞⠕⠺⠀⠼")?; + + let expr = r#"x+y=72x+3y=17"#; + test_braille("Russian", expr, "⠠⠭⠀⠖⠽⠀⠶⠼⠛⠨⠳⠼⠃⠠⠭⠀⠖⠼⠉⠠⠽⠀⠶⠼⠁⠛")?; + let expr = r#"f(x)={0x<0xx0"#; test_braille("Russian", expr, "⠠⠋⠣⠭⠜⠀⠶⠏⠀⠼⠚⠀⠠⠭⠀⠪⠀⠼⠚⠨⠳⠧⠀⠠⠭⠀⠭⠀⠕⠶⠼⠚")?; @@ -324,6 +626,82 @@ fn source_geometry_matrix_chemistry() -> Result<()> { return Ok(()); } +#[test] +fn source_label_marks() -> Result<()> { + let expr = r#"x~"#; + test_braille("Russian", expr, "⠠⠭⠢")?; + + let expr = r#"7~"#; + test_braille("Russian", expr, "⠼⠛⠢")?; + + let expr = r#"x^"#; + test_braille("Russian", expr, "⠠⠭⠲")?; + + let expr = r#"xˇ"#; + test_braille("Russian", expr, "⠠⠭⠰⠔")?; + + let expr = r#"x"#; + test_braille("Russian", expr, "⠠⠭⠘⠆")?; + + let expr = r#"x?"#; + test_braille("Russian", expr, "⠠⠭⠘⠠⠢")?; + + let expr = r#"x"#; + test_braille("Russian", expr, "⠠⠭⠘⠦⠶")?; + + let expr = r#"x"#; + test_braille("Russian", expr, "⠠⠭⠔")?; + + let expr = r#"x"#; + test_braille("Russian", expr, "⠠⠭⠨⠦⠶")?; + + let expr = r#"x~"#; + test_braille("Russian", expr, "⠠⠭⠰⠢")?; + + let expr = r#"x"#; + test_braille("Russian", expr, "⠠⠭⠆")?; + + let expr = r#"x"#; + test_braille("Russian", expr, "⠠⠭⠨⠴")?; + + let expr = r#"x"#; + test_braille("Russian", expr, "⠠⠭⠸⠶")?; + + let expr = r#"x×"#; + test_braille("Russian", expr, "⠠⠭⠸⠦")?; + + let expr = r#"x"#; + test_braille("Russian", expr, "⠠⠭⠆⠆")?; + + let expr = r#"x+y~"#; + test_braille("Russian", expr, "⠯⠠⠭⠀⠖⠽⠽⠢")?; + + let expr = r#"x"#; + test_braille("Russian", expr, "⠸⠶⠠⠭")?; + + let expr = r#"x"#; + test_braille("Russian", expr, "⠨⠆⠠⠭")?; + + let expr = r#"2x"#; + test_braille("Russian", expr, "⠼⠃⠐⠸⠆⠠⠭")?; + + let expr = r#"ax"#; + test_braille("Russian", expr, "⠠⠁⠐⠨⠶⠭")?; + + let expr = r#"x1"#; + test_braille("Russian", expr, "⠸⠆⠠⠭⠡⠂")?; + + let expr = r#"xn2"#; + test_braille("Russian", expr, "⠨⠦⠶⠠⠭⠡⠝⠱⠌⠆")?; + + let expr = r#"7i3?"#; + test_braille("Russian", expr, "⠸⠠⠢⠼⠛⠡⠠⠊⠱⠌⠒")?; + + let expr = r#"x~12"#; + test_braille("Russian", expr, "⠠⠭⠢⠡⠼⠁⠌⠼⠃⠱")?; + return Ok(()); +} + #[test] fn source_chemical_reactions_and_charges() -> Result<()> { let expr = r#"2HCl+2Na2NaCl+H2"#; @@ -340,6 +718,24 @@ fn source_chemical_reactions_and_charges() -> Result<()> { let expr = r#"HPO4--"#; test_braille("Russian", expr, "⠨⠓⠏⠕⠡⠲⠌⠆⠤")?; + + let expr = r#"CNO"#; + test_braille("Russian", expr, "⠸⠉⠝⠕")?; + + let expr = r#"N300N"#; + test_braille("Russian", expr, "⠨⠝⠀⠒⠕⠨⠌⠼⠉⠚⠚⠱⠨⠝")?; + + let expr = r#"N300N"#; + test_braille("Russian", expr, "⠨⠝⠀⠒⠕⠨⠡⠼⠉⠚⠚⠱⠨⠝")?; + + let expr = r#"N300PtN"#; + test_braille("Russian", expr, "⠨⠝⠀⠒⠕⠨⠡⠼⠉⠚⠚⠱⠨⠌⠨⠏⠠⠞⠱⠨⠝")?; + + let expr = r#"NaCl(aq)+H(l)+C(s)"#; + test_braille("Russian", expr, "⠨⠝⠠⠁⠨⠉⠠⠇⠣⠁⠟⠜⠀⠖⠨⠓⠣⠠⠇⠜⠀⠖⠨⠉⠣⠠⠎⠜")?; + + let expr = r#"e++e--"#; + test_braille("Russian", expr, "⠠⠑⠌⠖⠀⠖⠑⠌⠆⠤")?; return Ok(()); } @@ -353,5 +749,48 @@ fn source_gost_logic_arrows() -> Result<()> { let expr = r#"CD"#; test_braille("Russian", expr, "⠨⠉⠀⠦⠶⠜⠙")?; + + let expr = r#"x:P(x)"#; + test_braille("Russian", expr, "⠫⠄⠠⠭⠀⠲⠨⠏⠣⠠⠭⠜")?; + + let expr = r#"x:P(x)"#; + test_braille("Russian", expr, "⠫⠢⠠⠭⠀⠲⠨⠏⠣⠠⠭⠜")?; + return Ok(()); +} + +#[test] +fn source_less_common_math_symbols() -> Result<()> { + let expr = r#"3±2"#; + test_braille("Russian", expr, "⠼⠉⠀⠖⠤⠼⠃")?; + + let expr = r#"xy"#; + test_braille("Russian", expr, "⠠⠭⠀⠢⠢⠽")?; + + let expr = r#"ab"#; + test_braille("Russian", expr, "⠠⠁⠀⠾⠃")?; + + let expr = r#"x1y"#; + test_braille("Russian", expr, "⠠⠭⠀⠪⠶⠼⠁⠀⠪⠶⠠⠽")?; + + let expr = r#"y0"#; + test_braille("Russian", expr, "⠠⠽⠀⠕⠶⠼⠚")?; + + let expr = r#"AB"#; + test_braille("Russian", expr, "⠨⠁⠀⠠⠡⠀⠃")?; + + let expr = r#"BA"#; + test_braille("Russian", expr, "⠨⠃⠀⠈⠌⠀⠁")?; + + let expr = r#"f/x"#; + test_braille("Russian", expr, "⠹⠠⠋⠠⠌⠹⠭")?; + + let expr = r#"f"#; + test_braille("Russian", expr, "⠫⠴⠠⠋")?; + + let expr = r#"ab"#; + test_braille("Russian", expr, "⠠⠁⠸⠃")?; + + let expr = r#"ab"#; + test_braille("Russian", expr, "⠠⠁⠀⠼⠀⠃")?; return Ok(()); }