diff --git a/Rules/Braille/Russian/Russian_Rules.yaml b/Rules/Braille/Russian/Russian_Rules.yaml index a691101e..c62168c5 100644 --- a/Rules/Braille/Russian/Russian_Rules.yaml +++ b/Rules/Braille/Russian/Russian_Rules.yaml @@ -27,6 +27,19 @@ 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: unicode-override tag: "*" @@ -41,6 +54,8 @@ - RowStart: "''" - RowEnd: "''" - InSystemLeftBrace: "false()" + - InChemElementGroup: "false()" + - InColumnArithmetic: "false()" - MatchingWhitespace: "false()" replace: [x: "*"] @@ -50,12 +65,140 @@ match: "not(preceding-sibling::*) and following-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: 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 +257,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 +330,16 @@ - x: "*[2]/*[1]/*[1]/*[1]" - x: "*[3]" +- + name: column-arithmetic-table + tag: mtable + variables: + - InColumnArithmetic: "true()" + - RowStart: "''" + - RowEnd: "''" + match: "contains(@intent, ':column-arithmetic')" + replace: [x: "*"] + - name: system-left-brace-table tag: mtable @@ -209,6 +384,12 @@ 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]"] @@ -243,6 +424,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 @@ -278,6 +498,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 +750,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 +787,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 +880,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 +917,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 +1000,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 +1029,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 +1039,95 @@ - 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') != ''])" + 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]" + - 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 +1183,62 @@ - 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: 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 +1260,39 @@ replace: - x: "text()" +- + 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 +1316,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 de049204..513b1d1d 100644 --- a/Rules/Braille/Russian/definitions.yaml +++ b/Rules/Braille/Russian/definitions.yaml @@ -16,3 +16,28 @@ "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" + } + +- 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 f78feac3..813669ec 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 53fb0693..f87979bf 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,16 @@ - "\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: "⠤"] - "\ue020": [t: "⠚"] - "\ue021": [t: "⠁"] - "\ue022": [t: "⠃"] @@ -170,11 +186,7 @@ - "∧": [t: "⠀⠰⠢"] - "∨": [t: "⠀⠰⠔"] - "∩": [t: "⠀⠰⠲"] -- "∪": - - test: - if: "following-sibling::*[1][self::m:mi]" - then: [t: "⠸⠜"] - else: [t: "⠀⠰⠴"] +- "∪": [t: "⠀⠰⠴"] - "∫": [t: "⠮"] - "∬": [t: "⠮⠮"] - "∭": [t: "⠮⠮⠮"] diff --git a/src/braille.rs b/src/braille.rs index 2bcba4ed..82dcdd88 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_str("⠨"); + 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_str("⠻"); + suffix.insert_str(0, "⠻"); + } + if math_variant.contains("italic") && !braille_chars.contains('I') { + prefix.push_str("⠸"); + suffix.insert_str(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 4d2f9c83..659d0ecd 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,15 @@ 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, "⠼⠉⠃⠛⠨⠳⠦⠑⠙⠓⠨⠳⠃⠋⠁⠋⠨⠳⠁⠉⠚⠓⠨⠳⠶⠁⠛⠊⠁⠊⠋")?; return Ok(()); } @@ -174,6 +231,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 +277,85 @@ 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#"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 +372,31 @@ 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 wikipedia_times_divide() -> Result<()> { let expr = r#"6×7:14=3"#; @@ -228,6 +415,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 +429,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 +476,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 +503,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,6 +539,9 @@ 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, "⠸⠜⠨⠑⠋⠀⠶⠸⠜⠨⠅⠇")?; @@ -310,6 +551,9 @@ fn source_geometry_matrix_chemistry() -> Result<()> { let expr = r#"{x+y+z=1x+2y+3z=2x+3y+2z=5"#; 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 +568,64 @@ 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+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#"x~12"#; + test_braille("Russian", expr, "⠠⠭⠢⠡⠼⠁⠌⠼⠃⠱")?; + return Ok(()); +} + #[test] fn source_chemical_reactions_and_charges() -> Result<()> { let expr = r#"2HCl+2Na2NaCl+H2"#; @@ -340,6 +642,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 +673,11 @@ 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(()); }