def _export_munder(node, exporter, context, variables, **kwargs): base, under = _child_nodes(node) result = (_export(base, exporter, context, variables, **kwargs) + _Braille('⠩') + _export(under, exporter, context, variables, **kwargs)) if not variables.get('no-under-boundaries'): result = _modifier(variables) + result + _Braille('⠻') return result
def _export_mover(node, exporter, context, variables, **kwargs): base, over = _child_nodes(node) exported_base = _export(base, exporter, context, variables, **kwargs) if ((over.tag == 'mo' and over.text.strip() == '¯' and base.tag in ('mi', 'mn',) and len(base.text.strip()) == 1)): return exported_base + _Braille('⠱') return (_modifier(variables) + exported_base + _Braille('⠣') + _export(over, exporter, context, variables, **kwargs) + _Braille('⠻'))
def _mroot(base, index, exporter, context, variables, **kwargs): level = variables.get('root-level', 0) repeater = _Braille('⠨' * level) prefix = _Braille('⠜') if index is not None: prefix = _Braille('⠣') + _export(index, exporter, context, variables, **kwargs) + prefix prefix = repeater + prefix suffix = repeater + _Braille('⠻') with variables.let('root-level', level + 1): if index is None: exported_base = _child_export(base, exporter, context, variables, **kwargs) else: exported_base = _export(base, exporter, context, variables, **kwargs) return prefix + exported_base + suffix
def _export_msubsup(node, exporter, context, variables, **kwargs): base, sub, sup = _child_nodes(node) prime = None sub_only = False if sup.tag == 'mo' and sup.text.strip() in _primes: prime = _nemeth_operators[sup.text.strip()] sub_only = True elif sup.tag == 'mrow': sup_children = _child_nodes(sup) if ((sup_children and sup_children[0].tag == 'mo' and sup_children[0].text.strip() in _primes)): prime = _nemeth_operators[sup_children[0].text.strip()] if len(sup_children) > 1: sup.remove(sup_children[0]) else: sub_only = True if prime is not None: base.braille = _export(base, exporter, context, variables, **kwargs) + _Braille(prime) from xml.etree import ElementTree node.clear() if sub_only: node.tag = 'msub' node.append(base) node.append(sub) return _export_msub(node, exporter, context, variables, **kwargs) else: node.tag = 'msup' ElementTree.SubElement(node, 'msub') node.append(sup) c = node.getchildren()[0] c.append(base) c.append(sub) return _export_msup(node, exporter, context, variables, **kwargs)
def _export_mn(node, exporter, context, variables, **kwargs): text = _node_value(node).strip() prefix = '' style_applied = False with _style(node, variables): style = variables.get('style', '') if style.find('bold') >= 0: prefix += '⠸' style_applied = True if style.find('italic') >= 0: prefix += '⠨' style_applied = True if text and text[0] == '-': prefix += '⠤' text = text[1:] if style_applied: prefix += '⠼' if variables.get('enclosed-list') != 'yes': prefix += _CONDITIONAL_NUM_PREFIX if context.lang() == 'cs': text = text.replace(',', '.') translated = prefix + string.join([_nemeth_numbers[c] for c in text], '') variables.set('enclosed-list', 'no') hyphenation = string.join([exporter.HYPH_NEMETH_WS if c == '⠀' else exporter.HYPH_NEMETH_NUMBER for c in translated], '') return _Braille(translated, hyphenation)
def _export_mtable(node, exporter, context, variables, **kwargs): matrix = _Braille(_MATRIX_START) start = variables.get('matrix-start', _Braille('')) end = variables.get('matrix-end', _Braille('')) rows = _child_nodes(node) n_columns = 0 for r in rows: n_columns = max(n_columns, len(_child_nodes(r))) with variables.let('matrix-n-columns', n_columns): for r in rows: matrix += start matrix += _export(r, exporter, context, variables, **kwargs) matrix += end matrix.append('\n') matrix.append(_MATRIX_END) return matrix
def _export_mtr(node, exporter, context, variables, **kwargs): n_columns = variables.get('matrix-n-columns') cells = _child_nodes(node) from xml.etree import ElementTree while len(cells) < n_columns: ElementTree.SubElement(node, 'mtd') cells = _child_nodes(node) row = _Braille('') for c in cells: row += _export(c, exporter, context, variables, **kwargs) row.append(_MATRIX_SEPARATOR) if row.text()[0] == _CONDITIONAL_NUM_PREFIX: row = _Braille('⠼' + row.text()[1:]) elif row.text()[:2] == '⠤' + _CONDITIONAL_NUM_PREFIX: row = _Braille('⠤⠼' + row.text()[2:]) return row
def _text_export(text, exporter, context, variables, node=None, plain=False): with _style(node, variables): # liblouis doesn't handle typeforms and letter prefixes (correctly) in # Nemeth so we can't relay that to it prefix = suffix = '' style = variables.get('style', '') if style.find('bold') >= 0: prefix += '⠸' if style.find('italic') >= 0: prefix += '⠨' if ((style and style != 'normal' and not plain and text and all(c in string.ascii_letters for c in text))): prefix += '⠰' elif (not style and text and all(c in string.ascii_letters for c in text) and variables.get('enclosed-list') != 'yes' and variables.get('direct-delimiters') != 'yes' and variables.get('no-letter-prefix') != 'yes' and (len(text) == 1 or text in ('cd',))): # short-form combinations prefix = _SINGLE_LETTER_START + prefix suffix += _SINGLE_LETTER_END else: if text in _signs_of_shape or text in _math_comparison_operators: suffix += _SINGLE_LETTER_KILLER_SUFFIX if text in _signs_of_shape_and_omission or text in _math_comparison_operators: prefix = _SINGLE_LETTER_KILLER_PREFIX + prefix lang = 'en' if plain else 'nemeth' braille = _nemeth_texts.get(text) if braille is None: braille = exporter.text(context, text, lang=lang).text().strip(_braille_whitespace) if prefix: braille = prefix + braille if suffix: braille += suffix return _Braille(braille)
def _export_msup(node, exporter, context, variables, **kwargs): base, index = _child_nodes(node) if index.tag == 'mo' and index.text.strip() in _primes: return (_export(base, exporter, context, variables, **kwargs) + _Braille(_nemeth_operators[index.text.strip()])) if index.tag == 'mrow': index_children = _child_nodes(index) if ((index_children and index_children[0].tag == 'mo' and index_children[0].text.strip() in _primes)): base.braille = (_export(base, exporter, context, variables, **kwargs) + _Braille(_nemeth_operators[index_children[0].text.strip()])) if len(index_children) > 1: index.remove(index_children[0]) else: return base.braille return __export_subsup('⠘', node, exporter, context, variables, **kwargs)
def _export_msline(node, exporter, context, variables, **kwargs): variables.set('msline-present', True) width = variables.get('ms-max-width') if width is None: text = '' else: text = '⠒' * (width) return _Braille(text)
def _export_mfrac(node, exporter, context, variables, **kwargs): numerator, denominator = _child_nodes(node) exported_numerator = _export(numerator, exporter, context, variables, **kwargs) exported_denominator = _export(denominator, exporter, context, variables, **kwargs) if _attribute(node, 'linethickness') == '0': # something like binomical coefficient return exported_numerator + _Braille('⠩') + exported_denominator left_node = variables.get('left-node') if left_node is not None and left_node.tag == 'mn': opening = _Braille('⠸⠹') closing = _Braille('⠸⠼') line = _Braille('⠌') else: def fraction_level(node): level = 0 for c in _child_nodes(node): if c.tag not in ('msub', 'msup'): level = max(level, fraction_level(c)) if node.tag == 'mfrac': level += 1 return level level = fraction_level(node) - 1 if level > 2: raise Exception("Overcomplex fraction") opening = _Braille('⠠' * level + '⠹') closing = _Braille('⠠' * level + '⠼') line = _Braille('⠠' * level + '⠌') return opening + exported_numerator + line + exported_denominator + closing
def _export_mspace(node, exporter, context, variables, **kwargs): # Just basic support width = _attribute(node, 'width', default='0') try: n = int(width) except ValueError: if width.endswith('em') or width.endswith('ex'): n = int(width[:-2]) else: raise return _Braille('⠀' * n, '4' * n)
def _child_export(node, exporter, context, variables, separators=None, **kwargs): braille = _Braille('', '') children = _child_nodes(node) # Check for Enclosed List (simplified) enclosed_list = None if len(children) >= 5: first = children[0] last = children[-1] if ((first.tag == 'mo' and first.text.strip() in ('(', '[', '{',) and last.tag == 'mo' and last.text.strip() in (')', ']', '}',) and all([c.text.strip() not in ('', ';', '.') for c in children if c.tag == 'mo']) and all([c.tag != 'mspace' for c in children]) and node.find('mtext') is None and all([c.text.strip() not in _math_comparison_operators for c in node.findall('.//mo')]))): enclosed_list = 'yes' # Check for direct contact with opening and closing group signs direct_delimiters = None if len(children) == 3: first = children[0] last = children[-1] content = children[1] while content.tag == 'mrow' and len(_child_nodes(content)) == 1: content = _child_nodes(content)[0] if ((first.tag == 'mo' and first.text.strip() in ('(', '[', '{',) and last.tag == 'mo' and last.text.strip() in (')', ']', '}',) and content.tag == 'mi')): direct_delimiters = 'yes' # Export left_node = None for i in range(len(children)): n = children[i] with variables.xlet(('enclosed-list', enclosed_list), ('direct-delimiters', direct_delimiters), ('left-node', left_node)): braille = braille + _export(n, exporter, context, variables) left_node = n return braille
def _op_export(operator, exporter, context, variables, node=None): op_braille = _nemeth_operators.get(operator) hyphenation = None if op_braille is None: op_braille = _text_export(operator, exporter, context, variables, node=node).text() # If liblouis translation returns something like character code # on unknown characters, we try to identify and handle such a # situation here. if op_braille is None or '⠈⠀⠭' in op_braille or '⡳' in op_braille: op_braille = string.join([c for c in op_braille if c != '⡳'], '') op_braille = exporter.braille_unknown_char(op_braille, operator) hyphenation = exporter.HYPH_NO * len(op_braille) elif operator in _math_comparison_operators: _comparison(operator, op_braille) op_braille = _nemeth_operators[operator] hyphenation = None if hyphenation is None: hyphenation = exporter.HYPH_NO * len(op_braille) if op_braille[0] == '⠀' and hyphenation[0] == exporter.HYPH_NO: hyphenation = exporter.HYPH_NEMETH_WS + hyphenation[1:] if op_braille[-1] == '⠀' and hyphenation[-1] == exporter.HYPH_NO: hyphenation = hyphenation[:-1] + exporter.HYPH_NEMETH_WS return _Braille(op_braille, hyphenation)
def __export_subsup(indicator, node, exporter, context, variables, **kwargs): base, index = _child_nodes(node) base_node = _child_nodes(base)[0] if base.tag == 'msup' else base base_tag = base_node.tag base_text = (base_node.text or '').strip() subsup = variables.get('subsup', _Braille('')) new_subsup = subsup indicate = True if indicator == '⠰' and not subsup.text() and index.tag == 'mn': index_text = index.text.strip() if index_text and index_text[0] != '-': if ((base_node.tag == 'mi' and (len(base_text) == 1 or base_text in _function_names + ('Na',)))): indicate = False elif base_node.tag == 'mo' and base_text in '∑∏': indicate = False if indicate: new_subsup += _Braille(indicator) for n in _child_nodes(index): if n.tag == 'mo' and n.text.strip() == ',': n.braille = _Braille('⠪') if (((not new_subsup) or (base_tag == 'mo' and base_text in _signs_of_shape) or (base_tag == 'mi' and base_text in _function_names))): terminator = _Braille('') elif subsup: terminator = subsup else: terminator = _Braille(_END_SUBSUP) with variables.let('no-letter-prefix', 'yes'): # probably not *completely* correct exported_base = _export(base, exporter, context, variables, **kwargs) if exported_base.text().endswith(_END_SUBSUP): exported_base = _Braille(exported_base.text()[:-1], exported_base.hyphenation()[:-1]) with variables.let('subsup', new_subsup): exported_index = _export(index, exporter, context, variables, **kwargs) if not indicate: exported_index = _Braille(_IMPLICIT_SUBSCRIPT) + exported_index return exported_base + new_subsup + exported_index + terminator
def _export_mstack(node, exporter, context, variables, **kwargs): rows = [] for n in _child_nodes(node): if n.tag == 'msgroup': group_rows = _child_nodes(n) shift = int(_attribute(n, 'shift', default='0')) if shift > 0: rows.append((0, shift,)) elif shift < 0: rows.append((((len(group_rows) - 1) * shift), shift,)) rows += group_rows if shift: rows.append('end-shift') else: rows.append(n) widths_1 = [0] widths_2 = None pattern = '' extra_width_1 = [0] extra_width_2 = [0] widths = widths_1 extra_width = extra_width_1 addition_or_subtraction = False shift = None shift_inc = 0 with variables.let('msline-present', False): for r in rows: if isinstance(r, tuple): shift, shift_inc = r continue if r == 'end-shift': shift = None continue exported = _export(r, exporter, context, variables, **kwargs).text() if exported and exported[0] in '⠬⠤': exported = exported[1:] extra_width[0] = 1 addition_or_subtraction = True if exported and exported[0] == _CONDITIONAL_NUM_PREFIX: exported = exported[1:] for op in ('⠈⠡', '⠨'): if exported.startswith(op + _CONDITIONAL_NUM_PREFIX): n = len(op) exported = exported[:n] + exported[n + 1:] # Just very simplified row handling: We ignore all the MathML # attributes and we assume that: 1. no row has got any special # separator pattern suffix not present in other rows; 2. no two # rows have distinct non-empty separator pattern prefixes. parts = [] p = '' i = j = 0 for op in ('⠈⠡',): if exported.startswith(op): i = len(op) if shift: exported += '⠀' * shift shift += shift_inc l = len(exported) while True: while i < l and exported[i] in '⠴⠂⠆⠒⠲⠢⠖⠶⠦⠔': i += 1 parts.append(exported[j:i]) if i == l: break p += exported[i] i += 1 j = i if pattern is not None and not pattern.endswith(p): if p.endswith(pattern): widths[0:0] = [0] * (len(p) - len(pattern)) pattern = p else: pattern = None for i in range(-1, -len(p) - 2, -1): widths[i] = max(widths[i], len(parts[i])) if widths_2 is None and variables.get('msline-present'): widths = widths_2 = copy.copy(widths_1) extra_width = extra_width_2 if pattern is None: if addition_or_subtraction: raise Exception("Non-matching mstack rows") pattern = '' max_width = (max(sum(widths_1) + extra_width_1[0], sum(widths_2) + extra_width_2[0]) + len(pattern)) msline_present = variables.get('msline-present') if msline_present: max_width += 2 widths_1 = [0] * (len(widths_2) - len(widths_1)) + widths_1 widths = widths_1 shift = None result = '' with variables.xlet(('ms-widths', widths), ('ms-pattern', pattern), ('ms-max-width', max_width), ('msline-present', False),): for r in rows: if isinstance(r, tuple): shift, shift_inc = r continue if r == 'end-shift': shift = None continue exported = _export(r, exporter, context, variables, **kwargs).text() if exported and exported[0] == _CONDITIONAL_NUM_PREFIX: exported = exported[1:] if shift is not None: exported += '⠀' * shift shift += shift_inc if len(exported) < max_width: if exported and exported[0] in '⠬⠤': prefix = exported[0] exported = exported[1:] if exported and exported[0] == _CONDITIONAL_NUM_PREFIX: exported = exported[1:] else: prefix = '' if msline_present: prefix = '⠀' + prefix formatted = '' for i in range(len(pattern) - 1, -1, -1): pos = exported.rfind(pattern[i]) part = exported[pos + 1:] exported = exported[:max(pos, 0)] part = '⠀' * (widths[i + 1] - len(part)) + part formatted = pattern[i] + part + formatted formatted = (prefix + '⠀' * (widths[0] - len(exported)) + exported + formatted) if msline_present: formatted = '⠀' * max(max_width - 1 - len(formatted), 0) + formatted else: formatted = exported result = result + formatted.rstrip('⠀') + '\n' if variables.get('msline-present'): widths = widths_2 result = _Braille(result) return result
def _export_mphantom(node, exporter, context, variables, **kwargs): braille = _child_export(node, exporter, context, variables) n = len(braille) return _Braille('⠀' * n)
def _modifier(variables): modifier = _Braille('⠐') subsup = variables.get('subsup') if subsup: modifier = _Braille(_INNER_SUBSUP) + subsup + modifier return modifier
def mathml_nemeth(exporter, context, element): class EntityHandler(element.EntityHandler): def __init__(self, *args, **kwargs): super(EntityHandler, self).__init__(*args, **kwargs) self._data = mathml.entities def __getitem__(self, key): return self._data.get(key, '?') entity_handler = EntityHandler() top_node = element.tree_content(entity_handler, transform=True) post = element.next_element() variables = _Variables() braille = _child_export(top_node, exporter, context, variables) text = braille.text() hyphenation = braille.hyphenation().replace(exporter.HYPH_WS, exporter.HYPH_NEMETH_WS) # Separate vertical bars while True: pos = text.find('%s%s' % (_AFTER_BAR, _BEFORE_BAR,)) if pos == -1: break text = text[:pos] + '⠐' + text[pos + 2:] hyphenation = hyphenation[:pos] + '0' + hyphenation[pos + 2:] # Remove repeated subsup's while True: match = _braille_repeated_subsup_regexp.search(text) if match is None: break start, end = match.span(1) text = text[:start] + text[end:] hyphenation = hyphenation[:start] + hyphenation[end:] # Handle numeric prefixes while True: match = _num_prefix_regexp.search(text) if match is None: break start, end = match.start(3), match.end(3) text = text[:start] + '⠼' + text[end:] hyphenation = hyphenation[:start] + '0' + hyphenation[end:] # Punctuation indicator -- part 1 single_letter = text and text[-1] == _SINGLE_LETTER_END # Handle letter prefixes space_or_punctuation = '⠀⠨⠠⠰' while True: pos = text.find(_SINGLE_LETTER_START) if pos == -1: break pos_end = text.find(_SINGLE_LETTER_END) assert pos_end > pos, text # I don't understand Nemeth definition of "single letters" very well. # The definitions seem to contradict the examples, especially as for # parentheses. As we use the examples in tests, we try to be # consistent with them. The most important "clarification" rules we # add here are: # - Single letters may occur at the beginning or at the end of the # whole math construct. # - Single letters may be preceeded or succeeded by parentheses, but # not from both the sides -- this is explicitly prohibited by Nemeth, # see §25.a.v-vi, while being explicitly applied in the examples, # see §26.b.(4)-(5). pre_punctuation = '' if pos == 0 else text[pos - 1] post_punctuation = '' if pos_end >= len(text) - 1 else text[pos_end + 1] if ((pre_punctuation == '' or pre_punctuation in space_or_punctuation or pre_punctuation == '⠷') and (post_punctuation == '' or post_punctuation in space_or_punctuation or post_punctuation == '⠾')): prefix = '⠰' prefix_hyph = '0' else: prefix = prefix_hyph = '' text = text[:pos] + prefix + text[pos + 1:pos_end] + text[pos_end + 1:] hyphenation = (hyphenation[:pos] + prefix_hyph + hyphenation[pos + 1:pos_end] + hyphenation[pos_end + 1:]) # Multipurpose indicator to distinguish base line numbers from subscripts while True: pos = text.find(_CONDITIONAL_NUM_PREFIX) if pos == -1: break if _braille_separate_subscript_regexp.search(text[:pos]): text = text[:pos] + '⠐' + text[pos + 1:] hyphenation = hyphenation[:pos] + '0' + hyphenation[pos + 1:] else: text = text[:pos] + text[pos + 1:] hyphenation = hyphenation[:pos] + hyphenation[pos + 1:] # Cleanup text_len = len(text) i = 0 while i < text_len: if text[i] in (_CONDITIONAL_NUM_PREFIX, _NUM_PREFIX_REQUIRED, _IMPLICIT_SUBSCRIPT, _SINGLE_LETTER_KILLER_PREFIX, _SINGLE_LETTER_KILLER_SUFFIX, _INNER_SUBSUP, _AFTER_BAR, _BEFORE_BAR,): text = text[:i] + text[i + 1:] hyphenation = hyphenation[:i] + hyphenation[i + 1:] text_len -= 1 else: i += 1 # Punctuation indicator -- part 2 if isinstance(post, lcg.TextContent): post_text = post.text() punctuated = False if post_text: if post_text[0] in _prefixed_punctuation: punctuated = True else: m = _punctuation_regexp.match(post_text) if m is not None: pos = m.end(1) context.set_alternate_text(post, post_text[:pos] + u'_' + post_text[pos:]) if punctuated: indicate = single_letter for indicator in _braille_right_indicators: if text.endswith(indicator): indicate = True break if not indicate and _braille_number_regexp.search(text): indicate = True if not indicate: last_element = top_node children = last_children = last_element.getchildren() while last_element.tag not in ('mi', 'mo', 'mn',) and children: last_children = children last_element = children[-1] children = last_element.getchildren() if last_element.tag == 'mo' and (last_element.text or '') in ',-–—': if len(last_children) > 1: last_element = last_children[-2] if ((last_element.tag == 'mo' or (last_element.tag == 'mi' and last_element.text == '…'))): op = last_element.text.strip() if op in ('–—…' + _signs_of_shape + _math_comparison_operators): indicate = True if ((not indicate and last_element.tag == 'mi' and last_element.text.strip() in _function_names)): indicate = True if indicate: text += '⠸' hyphenation += '0' # Subscript/superscript base level while True: pos = text.find(_END_SUBSUP) if pos == -1: break indicate = True if pos >= len(text) - 1: indicate = False elif text[pos + 1] in '⠸⠠%s' % (_END_SUBSUP,): indicate = False else: p = pos + 1 text_len = len(text) while p < text_len and text[p] in '\n⠀': p += 1 if p > pos + 1: next_text = text[p:] for o in _braille_comparison: if next_text.startswith(o.strip('⠀')): indicate = False break pos0 = pos while pos0 > 0 and text[pos0 - 1] in '⠰⠘': pos0 -= 1 if indicate: text = text[:pos0] + '⠐' + text[pos + 1:] hyphenation = hyphenation[:pos0] + '0' + hyphenation[pos + 1:] else: text = text[:pos0] + text[pos + 1:] hyphenation = hyphenation[:pos0] + hyphenation[pos + 1:] # Whitespace text_len = len(text) i = 0 while i < text_len: space = True if text[i] == _LEFT_WHITESPACE_42: t = text[:i] if not post and _braille_empty_regexp.match(t): space = False elif not _braille_number_regexp.search(t): # numbers may look like punctuation for b in (_braille_punctuation + _braille_right_indicators + _braille_left_grouping + _braille_right_grouping + _braille_symbols_42 + ('⠀',)): if b != '⠤' and t.endswith(b): space = False break elif text[i] == _RIGHT_WHITESPACE_42: t = text[i + 1:] if not post and _braille_empty_regexp.match(t): space = False else: for b in (_braille_punctuation + _braille_left_indicators + _braille_left_grouping + _braille_right_grouping + _braille_symbols_42 + ('⠀',)): if b != '⠤' and b != '⠼' and t.startswith(b): # We do insert space before numeric indicator. It # seems to contradict §42-43 but it respects the # example in §9 (we prefer the examples in case of # conflicts). space = False break else: i += 1 continue if space: text = text[:i] + '⠀' + text[i + 1:] hyphenation = hyphenation[:i] + '4' + hyphenation[i + 1:] i += 1 else: text = text[:i] + text[i + 1:] hyphenation = hyphenation[:i] + hyphenation[i + 1:] text_len -= 1 # Adjust matrices while True: start = text.find(_MATRIX_START) if start == -1: break end = text.find(_MATRIX_END) assert end > start + 1 rows = text[start + 1:end - 1].split('\n') n_columns = len(rows[0].split(_MATRIX_SEPARATOR)) column_widths = [0] * n_columns for r in rows: column_widths = [max(w, len(c)) for w, c in zip(column_widths, r.split(_MATRIX_SEPARATOR))] matrix = '' for r in rows: cells = r.split(_MATRIX_SEPARATOR) for i in range(n_columns): c = cells[i] matrix += c + '⠀' * (column_widths[i] - len(c) + (1 if i < n_columns - 2 else 0)) matrix += '\n' text = text[:start] + matrix + text[end + 1:] hyphenation = hyphenation[:start] + '0' * len(matrix) + hyphenation[end + 1:] # Done return _Braille(text, hyphenation)