From 9bb60c7d6064db1e5fc4b5d766338fda3c9a8973 Mon Sep 17 00:00:00 2001 From: Tseng Woody Date: Wed, 5 Sep 2018 09:39:26 +0800 Subject: [PATCH] v2.0 --- addon/doc/zh_CN/readme.md | 54 +- addon/doc/zh_TW/readme.md | 54 +- addon/globalPlugins/MathMlReader/A8M_PM.py | 810 +++-- addon/globalPlugins/MathMlReader/__init__.py | 386 ++- .../globalPlugins/MathMlReader/asciimathml.py | 815 +++++ addon/globalPlugins/MathMlReader/dialogs.py | 640 ++++ .../MathMlReader/latex2mathml/__init__.py | 11 + .../MathMlReader/latex2mathml/aggregator.py | 102 + .../MathMlReader/latex2mathml/commands.py | 43 + .../latex2mathml/symbols_parser.py | 41 + .../MathMlReader/latex2mathml/tokenizer.py | 82 + .../latex2mathml/unimathsymbols.txt | 2861 +++++++++++++++++ .../MathMlReader/locale/en/math.rule | 28 +- .../MathMlReader/locale/zh_CN/math.rule | 28 +- .../MathMlReader/locale/zh_TW/math.rule | 28 +- addon/globalPlugins/MathMlReader/math.example | 45 + addon/globalPlugins/MathMlReader/math.rule | 30 +- .../MathMlReader/xml/dom/expatbuilder.py | 2 +- .../MathMlReader/xml/dom/minicompat.py | 6 +- .../MathMlReader/xml/dom/minidom.py | 3 - .../MathMlReader/xml/etree/ElementInclude.py | 15 +- .../MathMlReader/xml/etree/ElementTree.py | 89 +- .../MathMlReader/xml/sax/expatreader.py | 50 +- .../MathMlReader/xml/sax/saxutils.py | 21 +- addon/locale/zh_CN/LC_MESSAGES/nvda.po | 364 ++- addon/locale/zh_TW/LC_MESSAGES/nvda.po | 373 ++- buildVars.py | 2 +- readme.md | 91 +- 28 files changed, 6478 insertions(+), 596 deletions(-) create mode 100644 addon/globalPlugins/MathMlReader/asciimathml.py create mode 100644 addon/globalPlugins/MathMlReader/dialogs.py create mode 100644 addon/globalPlugins/MathMlReader/latex2mathml/__init__.py create mode 100644 addon/globalPlugins/MathMlReader/latex2mathml/aggregator.py create mode 100644 addon/globalPlugins/MathMlReader/latex2mathml/commands.py create mode 100644 addon/globalPlugins/MathMlReader/latex2mathml/symbols_parser.py create mode 100644 addon/globalPlugins/MathMlReader/latex2mathml/tokenizer.py create mode 100644 addon/globalPlugins/MathMlReader/latex2mathml/unimathsymbols.txt create mode 100644 addon/globalPlugins/MathMlReader/math.example diff --git a/addon/doc/zh_CN/readme.md b/addon/doc/zh_CN/readme.md index f4ac81a..74bff15 100644 --- a/addon/doc/zh_CN/readme.md +++ b/addon/doc/zh_CN/readme.md @@ -4,7 +4,7 @@ 导航浏览对于阅读理解长数学内容相当重要,可协助理解长数学内容的结构。 -功能有: +## 功能 * 可阅读网页浏览器(Mozilla Firefox, Microsoft Internet Explorer and Google Chrome)上以MathML撰写的数学内容 * 可阅读Microsoft Word上以MathType 撰写的数学内容。(需安装MathType) @@ -23,14 +23,40 @@ * esc键退出导航浏览模式 * ctrl+alt+m:可在Access8Math与MathPlayer间切换阅读器(有安装MathPlayer才能切换) -* 菜单: - * 「一般设定」对话框,可设定: - * 语言:Access8Math 朗读数学内容的语言 - * 分析内容的数学意义:将数学内容进行语意分析,符合特定规则时,以该规则的数学意义进行朗读 - * 读出字典有定义模式的意义:当字典文件有定义时,使用字典文件读出提示该项子内容在其上层子内容的意义 - * 读出自动生成的意义:当字典文件无定义或不完整时,使用自动产生功能读出提示该项子内容在其上层子内容的意义或项次 - * 「规则设定」对话框:可选择特定规则是否启用的设定。 -* 简单规则:简单规则是各种规则的简化版,当其内容仅为单一项目时,便可省略前后标记朗读,以达到更好的理解与阅读,而亦不致造成混淆 +## 菜单 + +* 「一般设定」对话框,可设定: + * 语言:Access8Math 朗读数学内容的语言 + * 项目间隔时间:设定项目间停顿时间,数值从1到100,数值愈小表示停顿时间愈短,反之数值愈大表示停顿时间愈长。 + * 分析内容的数学意义:将数学内容进行语意分析,符合特定规则时,以该规则的数学意义进行朗读 + * 读出字典有定义模式的意义:当字典文件有定义时,使用字典文件读出提示该项子内容在其上层子内容的意义 + * 读出自动生成的意义:当字典文件无定义或不完整时,使用自动产生功能读出提示该项子内容在其上层子内容的意义或项次 +* 「规则设定」对话框:可选择特定规则是否启用的设定。 +* 「unicode 字典」可客制设定各项符号文字的报读方式。 +* 「数学规则」可客制设定各数学类型的报读方式。 +* 「加入新语言」可加入原先于内建未提供的语言,加入后于一般设定内会多出刚新增的语系并可再透过「unicode 字典」与「数学规则」定义读法达到多国语言客制化设定 + +## 数学规则 + +Access8Math将常用数学式依据类型与逻辑,建立43项数学规则,程序依据这套规则判别数学式的念法与念读顺序,依据各地习惯不同,可以变更数学念读顺序与念法,方法如下: + +编辑: 进入"数学规则"后,小窗口列有43项数学规则,选则任一规则可选择"编辑按钮"进入编辑条目。 + +规则的"编辑条目"可分为两大区块,分别是序列化顺序与子节点角色。 + * 序列化顺序:将数学规则依据念读顺序划分多个区块,在此区域可变更规则子项目的念读顺序及开始、项目间与结束的分隔文字,以分数规则mfrac为例,此规则分为五个念读顺序,顺序0、2和4分别代表起始提示、项目区隔提示与结束提示,可在各字段中输入变更自己习惯的念法,而顺序1与3则可调整子节点念读的先后,可于下拉式选单中变更顺序。 + * 子节点角色:为该数学规则的下一阶层子项目,以分数规则mfrac为例,此项规则就包含分子与分母两项,而在子节点字段中,可以变更该项子内容在其上层子内容的意义文字,。 + +范例:可先行查阅确认编辑修改后对此类型的数学规则读法。点击后会出现一个预设好符合该对应数学规则的数学内容,供确认对此类型的数学规则读法是否符合预期。 + +还原默认值:将数学规则列表还原到初始默认值。 + +汇入:将数学规则档案汇入,可用于加载数学规则档案。 + +汇出:将数学规则档案储存于指定路径,以利分享或保存数学规则档案。 + +## 其它 + + 简单规则:简单规则是各种规则的简化版,当其内容仅为单一项目时,便可省略前后标记朗读,以达到更好的理解与阅读,而亦不致造成混淆 数学内容解析数学规则意义持续增加中 @@ -111,9 +137,17 @@ 欢迎提出见意与bug回报,谢谢! +# Access8Math v2.0 更新日志 + +* 加入多国语系新增与客制化设定功能,新增三个窗口「unicode 字典」、「数学规则」、「加入新语言」 +* unicode 字典可客制设定各项符号文字的报读方式。 +* 数学规则可客制设定各数学类型的报读方式并可于修改完成前透过范例的按钮先行查阅修改的效果。 +* 加入新语言可加入原先于内建未提供的语言,加入后于一般设定内会多出刚新增的语系并可再透过「unicode 字典」与「数学规则」定义读法达到多国语言客制化设定 +* 优化在互动模式下,可使用数字键7~9以行为单位阅读序列文字 + # Access8Math v1.5 更新日志 -* 在「一般设定」新增项与项间停顿时间设定。 +* 在「一般设定」新增项与项间停顿时间设定。数值从1到100,数值愈小表示停顿时间愈短,反之数值愈大表示停顿时间愈长。 * 更新 unicode.dic # Access8Math v1.4 更新日志 diff --git a/addon/doc/zh_TW/readme.md b/addon/doc/zh_TW/readme.md index d2cce4d..2b9a48c 100644 --- a/addon/doc/zh_TW/readme.md +++ b/addon/doc/zh_TW/readme.md @@ -4,7 +4,7 @@ 導航瀏覽對於閱讀理解長數學內容相當重要,可協助理解長數學內容的結構。 -功能有: +## 功能 * 可閱讀網頁瀏覽器(Mozilla Firefox, Microsoft Internet Explorer and Google Chrome)上以MathML撰寫的數學內容 * 可閱讀Microsoft Word上以MathType 撰寫的數學內容。(需安裝MathType) @@ -23,14 +23,40 @@ * esc鍵退出導航瀏覽模式 * ctrl+alt+m:可在Access8Math與MathPlayer間切換閱讀器(有安裝MathPlayer才能切換) -* 功能表: - * 「一般設定」對話框,可設定: - * 語言:Access8Math 朗讀數學內容的語言 - * 分析內容的數學意義:將數學內容進行語意分析,符合特定規則時,以該規則的數學意義進行朗讀 - * 讀出字典有定義模式的意義:當字典檔有定義時,使用字典檔讀出提示該項子內容在其上層子內容的意義 - * 讀出自動生成的意義:當字典檔無定義或不完整時,使用自動產生功能讀出提示該項子內容在其上層子內容的意義或項次 - * 「規則設定」對話框:可選擇特定規則是否啟用的設定。 -* 簡單規則:簡單規則是各種規則的簡化版,當其內容僅為單一項目時,便可省略前後標記朗讀,以達到更好的理解與閱讀,而亦不致造成混淆 +## 功能表 + +* 「一般設定」對話框,可設定: + * 語言:Access8Math 朗讀數學內容的語言 + * 項目間隔時間:設定項目間停頓時間,數值從1到100,數值愈小表示停頓時間愈短,反之數值愈大表示停頓時間愈長。 + * 分析內容的數學意義:將數學內容進行語意分析,符合特定規則時,以該規則的數學意義進行朗讀 + * 讀出字典有定義模式的意義:當字典檔有定義時,使用字典檔讀出提示該項子內容在其上層子內容的意義 + * 讀出自動生成的意義:當字典檔無定義或不完整時,使用自動產生功能讀出提示該項子內容在其上層子內容的意義或項次 +* 「規則設定」對話框:可選擇特定規則是否啟用的設定。 +* 「unicode 字典」可客製設定各項符號文字的報讀方式。 +* 「數學規則」可客製設定各數學類型的報讀方式。 +* 「加入新語言」可加入原先於內建未提供的語言,加入後於一般設定內會多出剛新增的語系並可再透過「unicode 字典」與「數學規則」定義讀法達到多國語言客製化設定 + +## 數學規則 + +Access8Math將常用數學式依據類型與邏輯,建立43項數學規則,程式依據這套規則判別數學式的唸法與唸讀順序,依據各地習慣不同,可以變更數學唸讀順序與唸法,方法如下: + +編輯: 進入"數學規則"後,小視窗列有43項數學規則,選則任一規則可選擇"編輯按鈕"進入編輯條目。 + +規則的"編輯條目"可分為兩大區塊,分別是序列化順序與子節點角色。 + * 序列化順序:將數學規則依據唸讀順序劃分多個區塊,在此區域可變更規則子項目的唸讀順序及開始、項目間與結束的分隔文字,以分數規則mfrac為例,此規則分為五個唸讀順序,順序0、2和4分別代表起始提示、項目區隔提示與結束提示,可在各欄位中輸入變更自己習慣的唸法,而順序1與3則可調整子節點唸讀的先後,可於下拉式選單中變更順序。 + * 子節點角色:為該數學規則的下一階層子項目,以分數規則mfrac為例,此項規則就包含分子與分母兩項,而在子節點欄位中,可以變更該項子內容在其上層子內容的意義文字,。 + +範例:可先行查閱確認編輯修改後對此類型的數學規則讀法。點擊後會出現一個預設好符合該對應數學規則的數學內容,供確認對此類型的數學規則讀法是否符合預期。 + +還原預設值:將數學規則列表還原到初始預設值。 + +匯入:將數學規則檔案匯入,可用於載入數學規則檔案。 + +匯出:將數學規則檔案儲存於指定路徑,以利分享或保存數學規則檔案。 + +## 其他 + + 簡單規則:簡單規則是各種規則的簡化版,當其內容僅為單一項目時,便可省略前後標記朗讀,以達到更好的理解與閱讀,而亦不致造成混淆 數學內容解析數學規則意義持續增加中 @@ -111,9 +137,17 @@ 歡迎提出見意與bug回報,謝謝! +# Access8Math v2.0 更新日誌 + +* 加入多國語系新增與客製化設定功能,新增三個視窗「unicode 字典」、「數學規則」、「加入新語言」 +* unicode 字典可客製設定各項符號文字的報讀方式。 +* 數學規則可客製設定各數學類型的報讀方式並可於修改完成前透過範例的按鈕先行查閱修改的效果。 +* 加入新語言可加入原先於內建未提供的語言,加入後於一般設定內會多出剛新增的語系並可再透過「unicode 字典」與「數學規則」定義讀法達到多國語言客製化設定 +* 優化在互動模式下,可使用數字鍵7~9以行為單位閱讀序列文字 + # Access8Math v1.5 更新日誌 -* 在「一般設定」新增項與項間停頓時間設定。 +* 在「一般設定」新增項與項間停頓時間設定。數值從1到100,數值愈小表示停頓時間愈短,反之數值愈大表示停頓時間愈長。 * 更新 unicode.dic # Access8Math v1.4 更新日誌 diff --git a/addon/globalPlugins/MathMlReader/A8M_PM.py b/addon/globalPlugins/MathMlReader/A8M_PM.py index 8958007..a781491 100644 --- a/addon/globalPlugins/MathMlReader/A8M_PM.py +++ b/addon/globalPlugins/MathMlReader/A8M_PM.py @@ -1,6 +1,7 @@ # coding: utf-8 # Copyright (C) 2017-2018 Tseng Woody +import collections import copy import io import os @@ -21,7 +22,7 @@ def create_node(et): node_class = nodes[mp_tag.capitalize()] if mp_tag.capitalize() in nodes.keys() else object - if issubclass(node_class, NonTerminalNode) or (issubclass(node_class, BlockNode) and len(et)!=1): + if issubclass(node_class, NonTerminalNode) or issubclass(node_class, BlockNode): child = [] for c in et: node = create_node(c) @@ -29,28 +30,129 @@ def create_node(et): node = node_class(child, et.attrib) elif issubclass(node_class, TerminalNode): node = node_class([], et.attrib, data=et.text) - elif issubclass(node_class, BlockNode) and len(et) == 1: - node = create_node(et[0]) elif mp_tag == 'none': node = Nones() - #elif mp_tag == 'semantics': - #node = create_node(et[0]) else: child = [] for c in et: node = create_node(c) child.append(node) - node = Node(child, et.attrib) + node = Mrow(child, et.attrib) #raise RuntimeError('unknown tag : {}'.format(mp_tag)) - for c in node.child: - c.type = None - c.check_type() - return node +def clean_allnode(node): + for child in node.child: + clean_allnode(child) + + if isinstance(node, BlockNode): + if len(node.child)==1 or (isinstance(node.parent, AlterNode) and len(node.child)>0): + + #remove node + parent_new_child = node.parent.child[0:node.index_in_parent()] +node.child + if node.index_in_parent()+1 < len(node.parent.child): + parent_new_child = parent_new_child +node.parent.child[node.index_in_parent()+1:] + node.parent.child = parent_new_child + for child in node.child: + child.parent = node.parent + + elif isinstance(node.parent, AlterNode) and len(node.child)==0: + index = node.index_in_parent() + node.parent.child[index].child = [] + + + return node + +def set_mathrule_allnode(node, mathrule): + for child in node.child: + set_mathrule_allnode(child, mathrule) + node.set_mathrule(mathrule) + return node + +def clear_type_allnode(node): + for child in node.child: + clear_type_allnode(child) + node.type = None + return node + +def check_type_allnode(node): + for child in node.child: + check_type_allnode(child) + node.check_type() + return node + +class MathContent(object): + def __init__(self, mathrule, et): + self.root = self.pointer = create_node(et) + clean_allnode(self.root) + self.mathrule = {} + self.set_mathrule(mathrule) + + def set_mathrule(self, mathrule): + self.mathrule = mathrule + set_mathrule_allnode(self.root, self.mathrule) + check_type_allnode(self.root) + + def navigate(self, action): + pointer = None + if action == "downArrow": + pointer = self.pointer.down() + elif action == "upArrow": + pointer = self.pointer.up() + elif action == "leftArrow": + pointer = self.pointer.previous_sibling + elif action == "rightArrow": + pointer = self.pointer.next_sibling + elif action == "home": + pointer = self.root + + if pointer is not None: + self.pointer = pointer + return True + else: + return False + + def insert(self, node): + if self.pointer.parent: + self.pointer.parent.insert(self.pointer.index_in_parent(), node) + self.pointer = node + + # node refresh + clean_allnode(self.root) + clear_type_allnode(self.root) + set_mathrule_allnode(self.root, self.mathrule) + check_type_allnode(self.root) + + else: + self.pointer.insert(len(self.pointer.child), node) + self.pointer = node + + # node refresh + clean_allnode(self.root) + clear_type_allnode(self.root) + set_mathrule_allnode(self.root, self.mathrule) + check_type_allnode(self.root) + + def delete(self): + if self.pointer.parent: + parent = self.pointer.parent + index = self.pointer.index_in_parent() + self.pointer.parent.delete(self.pointer.index_in_parent()) + try: + self.pointer = parent.child[index] + except: + self.pointer = parent + + # node refresh + clean_allnode(self.root) + clear_type_allnode(self.root) + set_mathrule_allnode(self.root, self.mathrule) + check_type_allnode(self.root) + class Node(object): def __init__(self, child=[], attrib={}, data=u''): + self._mathcontent = None self._parent = None self.child = list(child) for c in child: @@ -58,9 +160,37 @@ def __init__(self, child=[], attrib={}, data=u''): c.parent = self self.attrib = attrib self.data = unicode(data.strip()) if data else u'' + self.mathrule = {} + self.rule = [] + self.role = [] self.type = None - self.check_type() - self.role = math_role[self.name] if math_role.has_key(self.name) else [symbol_translate('item')] + #self.set_mathrule(mathrule) + #self.check_type() + + def check_type(self): + for nodetype in nodetypes_check: + if nodetype.check(self) and nodetype.name in self.mathrule: + if not self.type: + self.type = nodetype() + elif self.type and issubclass(nodetype, self.type.__class__): + self.type = nodetype() + + if self.type: + self.type.set_mathrule(self.mathrule) + + self.type.set_rule() + self.set_role() + self.set_rule() + + def set_mathrule(self, mathrule): + self.mathrule = mathrule + self.set_role() + self.set_rule() + if self.type: + self.type.set_mathrule(self.mathrule) + + def set_role(self): + self.role = self.mathrule[self.name].role if mathrule.has_key(self.name) else [symbol_translate('item')] d = len(self.child) -len(self.role) if d > 0: append = self.role[-1] @@ -71,58 +201,39 @@ def __init__(self, child=[], attrib={}, data=u''): else: self.role_level = DIC_GENERATE - def check_type(self): - '''for nodetype in SNT_check: - if nodetype.check(self) and nodetype.name in math_rule: - if not self.type: - self.type = nodetype - elif self.type and issubclass(nodetype, self.type): - self.type = nodetype''' - - for nodetype in nodetypes_check: - if nodetype.check(self) and nodetype.name in math_rule: - if not self.type: - self.type = nodetype - elif self.type and issubclass(nodetype, self.type): - self.type = nodetype - - def rule(self): + def set_rule(self): if self.type and self.type.rule: - if issubclass(self.type, NonTerminalNodeType): - rule = self.type.rule() - elif issubclass(self.type, TerminalNodeType): - rule = [unicode(self.type.data.sub(self.type.rule, self.data))] - elif issubclass(self.type, SiblingNodeType): - rule = self.type.rule() + rule = self.type.rule else: - rule = math_rule[self.tag] - if isinstance(rule[1], tuple): + rule = self.mathrule[self.name].serialized_order + if len(rule)>=2 and isinstance(rule[1], tuple): result = [] for i in range(len(self.child)): - if not rule[1][0] == u' ': + if not (rule[1][0].isspace() or rule[1][0]==u''): result.append(u'{0}{1}'.format(rule[1][0], i+1)) result.append(i) rule = rule[0:1] +result +rule[-1:] - return rule + self.rule = rule def serialized(self): serialized = [] if isinstance(self, TerminalNode): - serialized = serialized +['@10@'] - for r in self.rule(): + serialized.append(['@10@']) + for r in self.rule: if isinstance(r, int): - serialized = serialized +self.child[r].serialized() + serialized.append(self.child[r].serialized()) elif r == '*': for c in self.child: - serialized = serialized +c.serialized() if c else serialized - serialized = serialized +['@10@'] + if c: + serialized.append(c.serialized()) + serialized.append(['@10@']) elif isinstance(r, unicode): - serialized = serialized +[r] + serialized.append([r]) else: raise TypeError('rule element type error : expect int or unicode (get {0})'.format(type(r))) if isinstance(self, TerminalNode): - serialized = serialized +['@10@'] + serialized.append(['@10@']) return serialized def get_mathml(self): @@ -147,6 +258,14 @@ def name(self): def tag(self): return self.__class__.__name__.lower() + @property + def mathcontent(self): + return None if self._mathcontent is None else self._mathcontent() + + @mathcontent.setter + def mathcontent(self, mathcontent): + self._mathcontent = weakref.ref(mathcontent) + @property def parent(self): return None if self._parent is None else self._parent() @@ -224,66 +343,116 @@ def up(self): return None class NonTerminalNode(Node): - pass + def set_rule(self): + try: + super(NonTerminalNode, self).set_rule() + except: + self.rule = range(len(self.child)) + + def set_role(self): + try: + super(NonTerminalNode, self).set_role() + except: + self.rule = range(len(self.child)) class TerminalNode(Node): - def rule(self): + def set_rule(self): try: - return super(TerminalNode, self).rule() + super(TerminalNode, self).set_rule() except BaseException as e: - return [unicode(symbol_translate(self.data))] + self.rule = [unicode(symbol_translate(self.data))] def get_mathml(self): mathml = u'' mathml = mathml +self.data if self.data else mathml return u'<{0}>{1}'.format(self.tag, mathml) -class BlockNode(Node): - def rule(self): +class AlterNode(NonTerminalNode): + def insert(self, index, node): + if index > len(self.child): + return None + if index == len(self.child): + self.child.insert(index+1, node) + node.parent = self + elif isinstance(self.child[index], BlockNode) and len(self.child[index].child) == 0: + self.child[index].child.insert(0, node) + node.parent = self.child[index] + else: + self.child.insert(index+1, node) + node.parent = self + return node + + def delete(self, index): + if index >= len(self.child): + return None + node = self.child[index] + del self.child[index] + if len(self.child) <= 0: + mrow_node = Mrow([], {}) + self.child.insert(0, mrow_node) + mrow_node.parent = self + return node + +class FixNode(NonTerminalNode): + def insert(self, index, node): + if index >= len(self.child): + return None + if isinstance(self.child[index], BlockNode): + self.child[index].child.append(node) + node.parent = self.child[index] + else: + mrow_child = [self.child[index], node] + mrow_node = Mrow(mrow_child, {}) + mrow_node.parent = self + for child in mrow_node.child: + child.parent = mrow_node + self.child[index] = mrow_node + + return node + + def delete(self, index): + if index >= len(self.child): + return None + node = self.child[index] + self.child[index] = Mrow([], {}) + self.child[index].parent = self + return node + +class BlockNode(AlterNode): + def set_rule(self): try: - return super(BlockNode, self).rule() + super(BlockNode, self).set_rule() except: - return [u''] +range(len(self.child)) +[u''] - -class Math(NonTerminalNode): - pass + self.rule = range(len(self.child)) class Mrow(BlockNode): pass -class Mstyle(BlockNode): +class Mfrac(FixNode): pass -class Mi(TerminalNode): - def __init__(self, child=[], attrib={}, data=None): - super(Mi, self).__init__(child, attrib, data) - self.identifier = data - -class Mn(TerminalNode): - def __init__(self, child=[], attrib={}, data=None): - super(Mn, self).__init__(child, attrib, data) - self.number = data +class Msqrt(AlterNode): + pass -class Mo(TerminalNode): - def __init__(self, child=[], attrib={}, data=None): - super(Mo, self).__init__(child, attrib, data) - self.operator = data +class Mroot(FixNode): + pass -class Mtext(TerminalNode): +class Mstyle(BlockNode): pass -class Mspace(TerminalNode): +class Merror(AlterNode): pass -class Ms(TerminalNode): +class Mpadded(AlterNode): pass -class Mfrac(NonTerminalNode): +class Mphantom(AlterNode): pass -class Mfenced(NonTerminalNode): - def rule(self): - rule = super(Mfenced, self).rule() +class Mfenced(AlterNode): + def set_rule(self): + super(Mfenced, self).set_rule() + rule = self.rule if not self.type: if self.attrib.has_key('open'): rule = [unicode(self.attrib['open'])] +rule @@ -292,35 +461,33 @@ def rule(self): if (not self.attrib.has_key('open')) and (not self.attrib.has_key('close')): rule = [u'('] +rule[0:-1] +[u')'] +rule[-1:] - return rule - -class Msqrt(NonTerminalNode): - pass + self.rule = rule -class Mroot(NonTerminalNode): +class Menclose(AlterNode): pass -class Msubsup(NonTerminalNode): +class Msub(FixNode): pass -class Msub(NonTerminalNode): +class Msup(FixNode): pass -class Msup(NonTerminalNode): +class Msubsup(FixNode): pass -class Munderover(NonTerminalNode): +class Munder(FixNode): pass -class Munder(NonTerminalNode): +class Mover(FixNode): pass -class Mover(NonTerminalNode): +class Munderover(FixNode): pass -class Mtable(NonTerminalNode): - def rule(self): - rule = super(Mtable, self).rule() +class Mtable(AlterNode): + def set_rule(self): + super(Mtable, self).set_rule() + rule = self.rule row_count = len(self.child) column_count_list = [len(i.child) for i in self.child] @@ -328,19 +495,65 @@ def rule(self): table_head = [rule[0] +u'{0}{1}{2}{3}{4}'.format(symbol_translate('has'), row_count, symbol_translate('row'), column_count, symbol_translate('column'))] cell = rule[1:-1] table_tail = rule[-1:] - return table_head +cell +table_tail + self.rule = table_head +cell +table_tail -class Mtr(NonTerminalNode): - def rule(self): - rule = super(Mtr, self).rule() +class Mlabeledtr(AlterNode): + pass + +class Mtr(AlterNode): + def set_rule(self): + super(Mtr, self).set_rule() + rule = self.rule cell = rule[1:-1] - return rule[:1] +cell +rule[-1:] + self.rule = rule[:1] +cell +rule[-1:] + +class Mtd(AlterNode): + pass + +class Mstack(AlterNode): + pass + +class Mlongdiv(AlterNode): + pass + +class Msgroup(AlterNode): + pass -class Mtd(NonTerminalNode): +class Msrow(AlterNode): pass -class Mmultiscripts(NonTerminalNode): - def __init__(self, *args, **kwargs): +class Mscarries(AlterNode): + pass + +class Mscarry(AlterNode): + pass + +class Maction(AlterNode): + pass + +class Math(AlterNode): + pass + +class Mi(TerminalNode): + pass + +class Mn(TerminalNode): + pass + +class Mo(TerminalNode): + pass + +class Mtext(TerminalNode): + pass + +class Mspace(TerminalNode): + pass + +class Ms(TerminalNode): + pass + +class Mmultiscripts(AlterNode): + '''def __init__(self, *args, **kwargs): super(Mmultiscripts, self).__init__(*args, **kwargs) role = [symbol_translate('main')] @@ -363,10 +576,11 @@ def __init__(self, *args, **kwargs): ] role = role +temp - self.role = role + self.role = role''' - def rule(self): - rule = super(Mmultiscripts, self).rule() + def set_rule(self): + super(Mmultiscripts, self).set_rule() + rule = self.rule index = range(1, self.mprescripts_index_in_child()) index_odd = index[0::2] @@ -395,7 +609,7 @@ def rule(self): rule = rule[:1] +temp +rule[-1:] rule.insert(1,0) - return rule + self.rule = rule def mprescripts_index_in_child(self): for c in self.child: @@ -406,8 +620,7 @@ class Mprescripts(TerminalNode): pass class Nones(Node): - def rule(self): - return [] + pass class NodeType(object): tag = object @@ -415,7 +628,11 @@ class NodeType(object): attrib = {} data = re.compile(r".*") name = 'nodetype' - rule = None + + def __init__(self): + self.mathrule = {} + self.rule = [] + self.role = [] @classmethod def check(cls, obj): @@ -424,8 +641,17 @@ def check(cls, obj): return True -class TerminalNodeType(NodeType): + def set_mathrule(self, mathrule): + self.mathrule = mathrule + self.set_rule() + + def set_rule(self): + try: + self.rule = self.mathrule[self.name].serialized_order + except: + self.rule = None +class TerminalNodeType(NodeType): @classmethod def check(cls, obj): if not issubclass(obj.__class__, cls.tag): @@ -447,18 +673,6 @@ def check(cls, obj): return False return True - @classmethod - def rule(cls, m): - temp = '' - if not math_rule.has_key(cls.name): - return None - for i in math_rule[cls.name]: - if isinstance(i, int): - temp = temp +m.group(int(i)) - else: - temp = temp +i - return temp - class NonTerminalNodeType(NodeType): @classmethod @@ -494,10 +708,6 @@ def check(cls, obj): return True - @classmethod - def rule(cls): - return math_rule[cls.name] - class SiblingNodeType(NodeType): previous_siblings = [] next_siblings = [] @@ -551,10 +761,6 @@ def check(cls, obj): return True - @classmethod - def rule(cls): - return math_rule[cls.name] - class CompoundNodeType(NodeType): compound = [] @@ -729,6 +935,26 @@ class MunderoverFromToType(SingleMunderoverType): child = [FromToOperatorType, NodeType, NodeType] name = 'from_to' +class MsubFromType(SingleMsubType): + tag = Msub + child = [FromToOperatorType, NodeType] + name = 'from' + +class MunderFromType(SingleMunderType): + tag = Munder + child = [FromToOperatorType, NodeType] + name = 'from' + +class MsupToType(SingleMsupType): + tag = Msup + child = [FromToOperatorType, NodeType] + name = 'to' + +class MoverToType(SingleMoverType): + tag = Mover + child = [FromToOperatorType, NodeType] + name = 'to' + class SetType(NonTerminalNodeType): tag = Mfenced attrib = { @@ -806,20 +1032,30 @@ class FirstPositiveSignType(SiblingNodeType): self_ = PlusType name = 'PositiveSignType' -def load_unicode_dic(language): - path = os.path.dirname(os.path.abspath(__file__)) - if not language == 'Windows': - path = path +'/locale/{0}'.format(language) - symbol = {} - - frp = os.path.join(path, 'unicode.dic') - frp_user = os.path.join(path, 'unicode_user.dic') - if not os.path.exists(frp_user): - with io.open(frp, 'r', encoding='utf-8') as fr, io.open(frp_user, 'w', encoding='utf-8') as fr_user: - fr_user.write(fr.read()) +class MathRule(object): + def __init__(self, name, description, category, serialized_order, role, example=''): + self.name = name + self.description = description + self.category= category + self.serialized_order = serialized_order + self.role = role + self.example = example + +def load_unicode_dic(path=None, language=''): + if not path and language: + path = os.path.dirname(os.path.abspath(__file__)) + if not language == 'Windows': + path = path +'/locale/{0}'.format(language) + frp = os.path.join(path, 'unicode.dic') + frp_user = os.path.join(path, 'unicode_user.dic') + if not os.path.exists(frp_user): + with io.open(frp, 'r', encoding='utf-8') as fr, io.open(frp_user, 'w', encoding='utf-8') as fr_user: + fr_user.write(fr.read()) + path = frp_user + symbol = {} try: - with io.open(frp, 'r', encoding='utf-8') as fr: + with io.open(path, 'r', encoding='utf-8') as fr: for line in fr: line = line.split('\t') if len(line) >= 2: @@ -828,22 +1064,28 @@ def load_unicode_dic(language): pass return symbol -def load_math_rule(language): - path = os.path.dirname(os.path.abspath(__file__)) - if not language == 'Windows': - path = path +'/locale/{0}'.format(language) - math_role = {} - math_rule = {} - - frp = os.path.join(path, 'math.rule') - frp_user = os.path.join(path, 'math_user.rule') - if not os.path.exists(frp_user): - with io.open(frp, 'r', encoding='utf-8') as fr, io.open(frp_user, 'w', encoding='utf-8') as fr_user: - fr_user.write(fr.read()) - - try: - with io.open(frp_user, 'r', encoding='utf-8') as fr: - for line in fr: +def load_math_rule(path=None, language=''): + math_example_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'math.example') + if not path and language: + path = os.path.dirname(os.path.abspath(__file__)) + if not language == 'Windows': + path = path +'/locale/{0}'.format(language) + frp = os.path.join(path, 'math.rule') + frp_user = os.path.join(path, 'math_user.rule') + if not os.path.exists(frp_user): + with io.open(frp, 'r', encoding='utf-8') as fr, io.open(frp_user, 'w', encoding='utf-8') as fr_user: + fr_user.write(fr.read()) + path = frp_user + + mathrule = collections.OrderedDict({}) + for category_key in mathrule_category_order: + category = mathrule_order[category_key] + for item in category: + mathrule[item] = None + + with io.open(path, 'r', encoding='utf-8') as fr, io.open(math_example_path, 'r', encoding='utf-8') as math_example: + for line in fr: + try: line = line.split('\t') if len(line) == 3: rule = [] @@ -858,45 +1100,62 @@ def load_math_rule(language): rule.append(int(i)) except: rule.append(i) - math_rule[line[0]] = rule role = [] for i in line[2].split(','): i = i.strip() role.append(i) - math_role[line[0]] = role - except: - pass - - return [math_role, math_rule] -def save_unicode_dic(language, symbol): - path = os.path.dirname(os.path.abspath(__file__)) - if not language == 'Windows': - path = path +'/locale/{0}'.format(language) + mathrule[line[0]] = MathRule(line[0], '', '', rule, role) + except BaseException as e: + pass -def save_math_rule(language, math_role, math_rule): - path = os.path.dirname(os.path.abspath(__file__)) - if not language == 'Windows': - path = path +'/locale/{0}'.format(language) - - math_rule_unicode = {} - for k,v in math_rule.items(): - line = [unicode(i) if not isinstance(i, tuple) else '(' +'.'.join(i) +')' for i in v] - line = ', '.join(line) - math_rule_unicode[k] = line + for line in math_example: + try: + line = line.split('\t') + if len(line) == 2: + mathrule[line[0]].example = line[1].strip() + except BaseException as e: + pass + + return mathrule + +def save_unicode_dic(symbol,path=None, language=''): + if not path and language: + path = os.path.dirname(os.path.abspath(__file__)) + if not language == 'Windows': + path = path +'/locale/{0}'.format(language) + path = os.path.join(path, 'unicode_user.dic') + + with io.open(path, 'w', encoding='utf-8') as f: + f.write(u'symbols:\r\n') + key = symbol.keys() + #key.sort() + for k in key: + line = '\t'.join([k, symbol[k], 'none']) +'\r\n' + f.write(line) - math_role_unicode = {} - for k,v in math_role.items(): - line = ', '.join(v) - math_role_unicode[k] = line + return True - fwp = os.path.join(path, 'math_user.rule') - with io.open(fwp, 'w', encoding='utf-8') as f: - key = math_rule.keys() +def save_math_rule(mathrule, path=None, language=''): + if not path and language: + path = os.path.dirname(os.path.abspath(__file__)) + if not language == 'Windows': + path = path +'/locale/{0}'.format(language) + path = os.path.join(path, 'math_user.rule') + + mathrule_unicode = {} + for k,v in mathrule.items(): + so_line = [unicode(i) if not isinstance(i, tuple) else '(' +'.'.join(i) +')' for i in v.serialized_order] + so_line = ', '.join(so_line) + role_line = ', '.join(v.role) + mathrule_unicode[k] ='\t'.join([so_line, role_line]) + + with io.open(path, 'w', encoding='utf-8') as f: + key = mathrule.keys() key.sort() for k in key: - line = '\t'.join([k, math_rule_unicode[k], math_role_unicode[k]]) +'\r\n' + line = '\t'.join([k, mathrule_unicode[k]]) +'\r\n' f.write(line) return True @@ -906,10 +1165,10 @@ def symbol_translate(u): def config_from_environ(): global language, AMM - global symbol, math_role, math_rule + global symbol, mathrule language = os.environ.get('LANGUAGE', 'Windows') - symbol = load_unicode_dic(language) - math_role, math_rule = load_math_rule(language) + symbol = load_unicode_dic(language=language) + mathrule = load_math_rule(language=language) AMM = True if os.environ.get('AMM', 'True') in [u'True', u'true'] else False global nodetypes_check, SNT_check @@ -957,4 +1216,183 @@ def decorator(cls, obj): all_nodetypes_dict = { k:v for k,v in locals().items() if inspect.isclass(v) and issubclass(v, NodeType) } all_nodetypes_dict.update({'object': object}) +mathrule_info = { + "generics": { + "node": [3, 1, "*",], + "math": [3, 1, "*",], + }, + "fraction": { + "mfrac": [5, 2, ".",], + "single_fraction": [5, 2, ".",], + "AddIntegerFractionType": [5, 2, ".",], + }, + "fenced": { + "mfenced": [3, 1, "*",], + "set": [3, 1, "*",], + "absolute_value": [3, 1, "*",], + "determinant": [3, 1, "*",], + "matrix": [3, 1, "*",], + }, + "root": { + "msqrt": [3, 1, "*",], + "mroot": [5, 2, ".",], + "single_square_root": [3, 1, ".",], + }, + "position": { + "msubsup": [7, 3, ".",], + "msup": [5, 2, ".",], + "msub": [5, 2, ".",], + "munderover": [7, 3, ".",], + "munder": [5, 2, ".",], + "mover": [5, 2, ".",], + "SingleMsubsup": [7, 3, ".",], + "SingleMsub": [5, 2, ".",], + "SingleMsup": [5, 2, ".",], + "SingleMunderover": [7, 3, ".",], + "SingleMunder": [5, 2, ".",], + "SingleMover": [5, 2, ".",], + }, + "power": { + "power": [5, 2, ".",], + "SquarePowerType": [3, 2, ".",], + "CubePowerType": [3, 2, ".",], + }, + "from to": { + "from_to": [7, 3, ".",], + "from": [5, 2, ".",], + "to": [5, 2, ".",], + }, + "table": { + "mtable": [3, 1, "*",], + "mtr": [3, 1, "*",], + "mtd": [3, 1, "*",], + }, + "line": { + "LineType": [3, 2, ".",], + "RayType": [3, 2, ".",], + "LineSegmentType": [3, 2, ".",], + "VectorSingleType": [3, 2, ".",], + "VectorDoubleType": [3, 2, ".",], + "FrownType": [3, 2, ".",], + }, + "other": { + "NegativeSignType": [1, 1, ".",], + "PositiveSignType": [1, 1, ".",], + "mmultiscripts": [0, 0, ".",], + "mprescripts": [0, 0, ".",], + "none": [0, 0, ".",], + }, +} + +mathrule_category_order = [ + "generics", + "fraction", + "fenced", + "root", + "position", + "power", + "from to", + "table", + "line", + "other", +] + +mathrule_order = { + "generics": [ + "node", + "math", + ], + "fraction": [ + "mfrac", + "single_fraction", + "AddIntegerFractionType", + ], + "fenced": [ + "mfenced", + "set", + "absolute_value", + "determinant", + "matrix", + ], + "root": [ + "msqrt", + "mroot", + "single_square_root", + ], + "position": [ + "msubsup", + "msup", + "msub", + "munderover", + "munder", + "mover", + "SingleMsubsup", + "SingleMsub", + "SingleMsup", + "SingleMunderover", + "SingleMunder", + "SingleMover", + ], + "power": [ + "power", + "SquarePowerType", + "CubePowerType", + ], + "from to": [ + "from_to", + "from", + "to", + ], + "table": [ + "mtable", + "mtr", + "mtd", + ], + "line": [ + "LineType", + "RayType", + "LineSegmentType", + "VectorSingleType", + "VectorDoubleType", + "FrownType", + ], + "other": [ + "NegativeSignType", + "PositiveSignType", + "mmultiscripts", + "mprescripts", + "none", + ], +} + config_from_environ() + +def mathrule_validate(mathrule, validator): + if not len(mathrule.serialized_order) == validator[0]: + return False + if not len(mathrule.role) == validator[1]: + return False + if validator[2] == ".": + for index in range(len(mathrule.serialized_order)): + if index%2 == 0: + if not isinstance(mathrule.serialized_order[index], unicode): + return False + else: + if not isinstance(mathrule.serialized_order[index], int): + return False + elif validator[2] == "*": + if not isinstance(mathrule.serialized_order[0], unicode): + return False + elif not isinstance(mathrule.serialized_order[1], tuple): + return False + elif not isinstance(mathrule.serialized_order[2], unicode): + return False + else: + pass + return True + +for category_key in mathrule_category_order: + category = mathrule_order[category_key] + for item in category: + if not mathrule_validate(mathrule[item], mathrule_info[category_key][item]): + pass diff --git a/addon/globalPlugins/MathMlReader/__init__.py b/addon/globalPlugins/MathMlReader/__init__.py index 31837aa..57e12ce 100644 --- a/addon/globalPlugins/MathMlReader/__init__.py +++ b/addon/globalPlugins/MathMlReader/__init__.py @@ -4,7 +4,7 @@ # This file is covered by the GNU General Public License. # See the file COPYING.txt for more details. -from collections import OrderedDict +from collections import Iterable, OrderedDict import os import re import sys @@ -17,10 +17,12 @@ import xml from xml.etree import ElementTree as ET +import A8M_PM +from A8M_PM import MathContent + import addonHandler addonHandler.initTranslation() import api -from brailleInput import BrailleInputGesture import config import eventHandler import globalPlugins @@ -32,6 +34,7 @@ from gui import guiHelper from gui.settingsDialogs import SettingsDialog from keyboardHandler import KeyboardInputGesture +import languageHandler from logHandler import log import mathPres from mathPres.mathPlayer import MathPlayer @@ -45,10 +48,37 @@ from languageHandler_custom import getAvailableLanguages import wxgui +def event_focusEntered(self): + if self.role in (controlTypes.ROLE_MENUBAR,controlTypes.ROLE_POPUPMENU,controlTypes.ROLE_MENUITEM): + speech.cancelSpeech() + return + #if self.isPresentableFocusAncestor: + #speech.speakObject(self,reason=controlTypes.REASON_FOCUSENTERED) + +def mathml2etree(mathMl): + gtlt_pattern = re.compile(ur"([\>])(.*?)([\<])") + mathMl = gtlt_pattern.sub(lambda m: m.group(1) +cgi.escape(HTMLParser.HTMLParser().unescape(m.group(2))) +m.group(3), mathMl) + quote_pattern = re.compile(ur"=([\"\'])(.*?)\1") + mathMl = quote_pattern.sub(lambda m: '=' +m.group(1) +cgi.escape(m.group(2)) +m.group(1), mathMl) + parser = ET.XMLParser() + try: + tree = ET.fromstring(mathMl.encode('utf-8'), parser=parser) + except BaseException as e: + raise SystemError(e) + return tree + +def flatten(l): + for el in l: + if isinstance(el, Iterable) and not isinstance(el, basestring): + for sub in flatten(el): + yield sub + else: + yield el + def translate_SpeechCommand(serializes): pattern = re.compile(r'[@](?P