refactor: change project to clean architecture design

This commit is contained in:
Alexandre Nunes 2020-09-27 02:31:35 -03:00
parent 688940bdd1
commit c03e47709f
82 changed files with 759 additions and 689 deletions

@ -1,4 +1,4 @@
import XmlToMathMLAdapter from '../../../src/converters/xml-mathml-to-latex/xml-to-mathml';
import { XmlToMathMLAdapter } from '../../../src/infra/usecases/xmldom-to-mathml-elements';
import {
singleMi,
singleMiNoRoot,

@ -1,18 +0,0 @@
import { MathML } from '../../protocols/MathML';
import MathmlToLatex from './xml-to-mathml/mathml-to-latex';
import XmlToMathml from './xml-to-mathml';
export class XmlMathMLToLaTeX {
constructor(private _mathml: string) {
this._mathml = _mathml;
}
static convert(_mathml: string): string {
return new XmlMathMLToLaTeX(_mathml).convert();
}
convert(): string {
const mathmlInterfaces: MathML[] = new XmlToMathml(this._mathml).convert();
return new MathmlToLatex(mathmlInterfaces).convert();
}
}

@ -1,3 +0,0 @@
import { XmlMathMLToLaTeX } from './XmlMathMLToLaTeX';
export default XmlMathMLToLaTeX;

@ -1,33 +0,0 @@
import { MathML } from '../../../protocols/MathML';
export class ElementsToMathMLAdapter {
convert(els: Element[]): MathML[] {
return els.filter((el: Element) => el.tagName !== undefined).map((el: Element) => this._convertElement(el));
}
private _convertElement(el: Element): MathML {
return {
name: el.tagName,
attributes: el.attributes ? this._convertElementAttributes(el.attributes) : {},
value: this._hasElementChild(el) ? '' : el.textContent || '',
children: this._hasElementChild(el) ? this.convert(Array.from(el.childNodes) as Element[]) : ([] as MathML[]),
};
}
private _convertElementAttributes(attributes: NamedNodeMap): Record<string, string> {
return Array.from(attributes).reduce(
(acc, attr: Attr) =>
Object.assign({ [attr.nodeName]: attr.nodeValue === attr.nodeName ? '' : attr.nodeValue }, acc),
{},
);
}
private _hasElementChild(el: Element): boolean {
const childNodes = el.childNodes;
return !!childNodes && childNodes.length !== 0 && this._isThereAnyNoTextNode(childNodes);
}
private _isThereAnyNoTextNode(children: NodeListOf<ChildNode>): boolean {
return Array.from(children).some((child) => child.nodeName !== '#text');
}
}

@ -1,42 +0,0 @@
import xmldom = require('xmldom');
import { DOMParser } from 'xmldom';
import { ErrorHandler } from './ErrorHandler';
import { MathML } from '../../../protocols/MathML';
import { ElementsToMathMLAdapter } from './ElementsToMathMLAdapter';
export class XmlToMathMLAdapter {
private _xmlDOM: DOMParser;
private _errorHandler: ErrorHandler;
private _elementsConvertor: ElementsToMathMLAdapter;
constructor(private _xml: string) {
this._xml = this._removeLineBreaks(_xml);
this._elementsConvertor = new ElementsToMathMLAdapter();
this._errorHandler = new ErrorHandler();
this._xmlDOM = new xmldom.DOMParser({
locator: this._errorHandler.errorLocator,
errorHandler: this._fixError.bind(this),
});
}
convert(): MathML[] {
return this._elementsConvertor.convert(this._mathMLElements);
}
private _fixError(errorMessage: string): void {
this._xml = this._errorHandler.fixError(this._xml, errorMessage);
}
private _removeLineBreaks(xml: string): string {
const LINE_BREAK = /\n|\r\n|\r/g;
return xml.replace(LINE_BREAK, '');
}
private get _mathMLElements(): Element[] {
const elements = this._xmlDOM.parseFromString(this._xml).getElementsByTagName('math');
return Array.from(elements) as Element[];
}
}

@ -1,2 +0,0 @@
import { XmlToMathMLAdapter } from './XmlToMathMLAdapter';
export default XmlToMathMLAdapter;

@ -1,20 +0,0 @@
import { MathML } from '../../../../protocols/MathML';
import Dispatcher from './mathml-to-latex';
import { MathMLTag } from './mathml-to-latex/mathml-tags';
export class MathMLToLaTeX {
constructor(private _mathMLInterfaces: MathML[]) {
this._mathMLInterfaces = _mathMLInterfaces;
}
convert(): string {
return this._mathMLInterfaces.map((mathml) => this._dispatch(mathml).convert()).join('');
}
private _dispatch(mathml: MathML): MathMLTag {
const { name, value, attributes } = mathml;
const children = mathml.children.map((children) => this._dispatch(children));
return new Dispatcher(name, value, attributes, children).dispatch();
}
}

@ -1,3 +0,0 @@
import { MathMLToLaTeX } from './MathMLToLaTeX';
export default MathMLToLaTeX;

@ -1,68 +0,0 @@
import * as MathMLTags from './mathml-tags';
export class Dispatcher {
private _name: string;
private _value: string;
private _attributes: Record<string, string>;
private _children: MathMLTags.MathMLTag[];
constructor(name: string, value: string, attributes: Record<string, string>, children: MathMLTags.MathMLTag[]) {
this._name = name;
this._value = value;
this._attributes = attributes;
this._children = children;
}
dispatch(): MathMLTags.MathMLTag {
switch (this._name) {
case 'math':
return new MathMLTags.Math(this._value, this._attributes, this._children);
case 'mi':
return new MathMLTags.MI(this._value, this._attributes, this._children);
case 'mo':
return new MathMLTags.MO(this._value, this._attributes, this._children);
case 'mn':
return new MathMLTags.MN(this._value, this._attributes, this._children);
case 'msqrt':
return new MathMLTags.MSqrt(this._value, this._attributes, this._children);
case 'mfenced':
return new MathMLTags.MFenced(this._value, this._attributes, this._children);
case 'mfrac':
return new MathMLTags.MFrac(this._value, this._attributes, this._children);
case 'mroot':
return new MathMLTags.MRoot(this._value, this._attributes, this._children);
case 'maction':
return new MathMLTags.MAction(this._value, this._attributes, this._children);
case 'menclose':
return new MathMLTags.MEnclose(this._value, this._attributes, this._children);
case 'merror':
return new MathMLTags.MError(this._value, this._attributes, this._children);
case 'mphantom':
return new MathMLTags.MPhantom(this._value, this._attributes, this._children);
case 'msup':
return new MathMLTags.MSup(this._value, this._attributes, this._children);
case 'msub':
return new MathMLTags.MSub(this._value, this._attributes, this._children);
case 'msubsup':
return new MathMLTags.MSubsup(this._value, this._attributes, this._children);
case 'mmultiscripts':
return new MathMLTags.MMultiscripts(this._value, this._attributes, this._children);
case 'mtext':
return new MathMLTags.MText(this._value, this._attributes, this._children);
case 'munderover':
return new MathMLTags.MUnderover(this._value, this._attributes, this._children);
case 'mtable':
return new MathMLTags.MTable(this._value, this._attributes, this._children);
case 'mtr':
return new MathMLTags.MTr(this._value, this._attributes, this._children);
case 'mover':
case 'munder':
return new MathMLTags.GenericUnderOverTag(this._name, this._value, this._attributes, this._children);
case 'mrow':
case 'mpadded':
return new MathMLTags.GenericContentWrapperTag(this._name, this._value, this._attributes, this._children);
default:
return new MathMLTags.MathMLTag(this._name, this._value, this._attributes, this._children);
}
}
}

@ -1,3 +0,0 @@
import { Dispatcher } from './Dispatcher';
export default Dispatcher;

@ -1,7 +0,0 @@
import { MathMLTag } from './MathMLTag';
export class GenericContentWrapperTag extends MathMLTag {
convert(): string {
return this._mapChildrenToLaTeX().join(' ');
}
}

@ -1,41 +0,0 @@
import { MathMLTag } from './MathMLTag';
import { InvalidNumberOfChild } from '../../../../../../errors';
import { latexAccents } from '../../../../../../syntax/latexAccents';
export class GenericUnderOverTag extends MathMLTag {
convert(): string {
if (this._children.length !== 2) throw new InvalidNumberOfChild(this.name, 2, this._children.length);
const content = this._children[0].convert();
const accent = this._children[1].convert();
return this._applyCommand(content, accent);
}
private _applyCommand(content: string, accent: string): string {
const type = this.name.match(/under/) ? TagTypes.Under : TagTypes.Over;
return new UnderOverSetter(type).apply(content, accent);
}
}
class UnderOverSetter {
private readonly _type;
constructor(type: TagTypes) {
this._type = type;
}
apply(content: string, accent: string) {
return latexAccents.includes(accent) ? `${accent}{${content}}` : `${this._defaultCommand}{${accent}}{${content}}`;
}
private get _defaultCommand(): string {
if (this._type === TagTypes.Under) return '\\underset';
return '\\overset';
}
}
enum TagTypes {
Under,
Over,
}

@ -1,16 +0,0 @@
import { MathMLTag } from './MathMLTag';
export class MAction extends MathMLTag {
constructor(value: string, attributes: Record<string, string>, children: MathMLTag[]) {
super('maction', value, attributes, children);
}
convert(): string {
if (this._isToggle) return this._mapChildrenToLaTeX().join(' \\Longrightarrow ');
return this._mapChildrenToLaTeX()[0];
}
private get _isToggle(): boolean {
return this._attributes.actiontype === 'toggle' || !this._attributes.actiontype;
}
}

@ -1,11 +0,0 @@
import { MathMLTag } from './MathMLTag';
export class MError extends MathMLTag {
constructor(value: string, attributes: Record<string, string>, children: MathMLTag[]) {
super('merror', value, attributes, children);
}
convert(): string {
return `\\color{red}{${this._mapChildrenToLaTeX().join(' ')}}`;
}
}

@ -1,28 +0,0 @@
import { MathMLTag } from './MathMLTag';
import { InvalidNumberOfChild } from '../../../../../../errors';
import { ParenthesisWrapper } from '../../../../../../utils/wrappers';
export class MFrac extends MathMLTag {
constructor(value: string, attributes: Record<string, string>, children: MathMLTag[]) {
super('mfrac', value, attributes, children);
}
convert(): string {
if (this._children.length !== 2) throw new InvalidNumberOfChild(this.name, 2, this._children.length);
const num = this._children[0].convert();
const den = this._children[1].convert();
if (this._isBevelled) return `${this._wrapIfMoreThanOneChar(num)}/${this._wrapIfMoreThanOneChar(den)}`;
return `\\frac{${num}}{${den}}`;
}
private _wrapIfMoreThanOneChar(str: string): string {
return new ParenthesisWrapper().wrapIfMoreThanOneChar(str);
}
private get _isBevelled(): boolean {
return !!this._attributes.bevelled;
}
}

@ -1,56 +0,0 @@
import { MathMLTag } from './MathMLTag';
import { ParenthesisWrapper } from '../../../../../../utils/wrappers';
import { InvalidNumberOfChild } from '../../../../../../errors';
export class MMultiscripts extends MathMLTag {
constructor(value: string, attributes: Record<string, string>, children: MathMLTag[]) {
super('mmultiscripts', value, attributes, children);
}
convert(): string {
if (this._children.length < 3) throw new InvalidNumberOfChild(this.name, 3, this._children.length, 'at least');
const base = this._children[0];
return this._prescriptLatex() + this._wrapInParenthesisIfThereIsSpace(base.convert()) + this._postscriptLatex();
}
private _prescriptLatex(): string {
let sub;
let sup;
if (this._isPrescripts(this._children[1])) {
sub = this._children[2];
sup = this._children[3];
} else if (this._isPrescripts(this._children[3])) {
sub = this._children[4];
sup = this._children[5];
} else return '';
const subLatex = sub ? sub.convert() : '';
const supLatex = sup ? sup.convert() : '';
return `\\_{${subLatex}}^{${supLatex}}`;
}
private _postscriptLatex(): string {
if (this._isPrescripts(this._children[1])) return '';
const sub = this._children[1];
const sup = this._children[2];
const subLatex = sub ? sub.convert() : '';
const supLatex = sup ? sup.convert() : '';
return `_{${subLatex}}^{${supLatex}}`;
}
private _wrapInParenthesisIfThereIsSpace(str: string): string {
if (!str.match(/\s+/g)) return str;
return new ParenthesisWrapper().wrap(str);
}
private _isPrescripts(child: MathMLTag): boolean {
return child?.name === 'mprescripts';
}
}

@ -1,12 +0,0 @@
import { MathMLTag } from './MathMLTag';
export class MN extends MathMLTag {
constructor(value: string, attributes: Record<string, string>, children: MathMLTag[]) {
super('mn', value, attributes, children);
}
convert(): string {
const normalizedValue = this._normalizeWhiteSpaces(this._value);
return normalizedValue.trim();
}
}

@ -1,11 +0,0 @@
import { MathMLTag } from './MathMLTag';
export class MPhantom extends MathMLTag {
constructor(value: string, attributes: Record<string, string>, children: MathMLTag[]) {
super('mphantom', value, attributes, children);
}
convert(): string {
return '';
}
}

@ -1,17 +0,0 @@
import { MathMLTag } from './MathMLTag';
import { InvalidNumberOfChild } from '../../../../../../errors';
export class MRoot extends MathMLTag {
constructor(value: string, attributes: Record<string, string>, children: MathMLTag[]) {
super('mroot', value, attributes, children);
}
convert(): string {
if (this._children.length !== 2) throw new InvalidNumberOfChild(this.name, 2, this._children.length);
const content = this._children[0].convert();
const rootIndex = this._children[1].convert();
return `\\sqrt[${rootIndex}]{${content}}`;
}
}

@ -1,12 +0,0 @@
import { MathMLTag } from './MathMLTag';
export class MSqrt extends MathMLTag {
constructor(value: string, attributes: Record<string, string>, children: MathMLTag[]) {
super('msqrt', value, attributes, children);
}
convert(): string {
const content = this._mapChildrenToLaTeX().join(' ');
return `\\sqrt{${content}}`;
}
}

@ -1,18 +0,0 @@
import { MathMLTag } from './MathMLTag';
import { BracketWrapper, ParenthesisWrapper } from '../../../../../../utils/wrappers';
import { InvalidNumberOfChild } from '../../../../../../errors';
export class MSub extends MathMLTag {
constructor(value: string, attributes: Record<string, string>, children: MathMLTag[]) {
super('msub', value, attributes, children);
}
convert(): string {
if (this._children.length !== 2) throw new InvalidNumberOfChild(this.name, 2, this._children.length);
const base = this._children[0].convert();
const exponent = this._children[1].convert();
return `${new ParenthesisWrapper().wrapIfMoreThanOneChar(base)}_${new BracketWrapper().wrap(exponent)}`;
}
}

@ -1,27 +0,0 @@
import { MathMLTag } from './MathMLTag';
import { BracketWrapper, ParenthesisWrapper } from '../../../../../../utils/wrappers';
import { InvalidNumberOfChild } from '../../../../../../errors';
export class MSubsup extends MathMLTag {
constructor(value: string, attributes: Record<string, string>, children: MathMLTag[]) {
super('msubsup', value, attributes, children);
}
convert(): string {
if (this._children.length !== 3) throw new InvalidNumberOfChild(this.name, 2, this._children.length);
const base = this._children[0].convert();
const sub = this._children[1].convert();
const sup = this._children[2].convert();
const wrappedSub = new BracketWrapper().wrap(sub);
const wrappedSup = new BracketWrapper().wrap(sup);
return `${this._wrapInParenthesisIfThereIsSpace(base)}_${wrappedSub}^${wrappedSup}`;
}
private _wrapInParenthesisIfThereIsSpace(str: string): string {
if (!str.match(/\s+/g)) return str;
return new ParenthesisWrapper().wrap(str);
}
}

@ -1,18 +0,0 @@
import { MathMLTag } from './MathMLTag';
import { BracketWrapper, ParenthesisWrapper } from '../../../../../../utils/wrappers';
import { InvalidNumberOfChild } from '../../../../../../errors';
export class MSup extends MathMLTag {
constructor(value: string, attributes: Record<string, string>, children: MathMLTag[]) {
super('msup', value, attributes, children);
}
convert(): string {
if (this._children.length !== 2) throw new InvalidNumberOfChild(this.name, 2, this._children.length);
const base = this._children[0].convert();
const exponent = this._children[1].convert();
return `${new ParenthesisWrapper().wrapIfMoreThanOneChar(base)}^${new BracketWrapper().wrap(exponent)}`;
}
}

@ -1,17 +0,0 @@
import { MathMLTag } from './MathMLTag';
export class MTable extends MathMLTag {
constructor(value: string, attributes: Record<string, string>, children: MathMLTag[]) {
super('mtable', value, attributes, children);
this.addFlagRecursiveIfClassName(this.constructor.name, 'innerTable');
}
convert(): string {
const tableContent = this._mapChildrenToLaTeX().join(' \\\\\n');
return this.hasFlag('innerTable') ? this._wrap(tableContent) : tableContent;
}
private _wrap(latex: string): string {
return `\\begin{matrix}${latex}\\end{matrix}`;
}
}

@ -1,11 +0,0 @@
import { MathMLTag } from './MathMLTag';
export class MTr extends MathMLTag {
constructor(value: string, attributes: Record<string, string>, children: MathMLTag[]) {
super('mtr', value, attributes, children);
}
convert(): string {
return this._mapChildrenToLaTeX().join(' & ');
}
}

@ -1,18 +0,0 @@
import { MathMLTag } from './MathMLTag';
import { InvalidNumberOfChild } from '../../../../../../errors';
export class MUnderover extends MathMLTag {
constructor(value: string, attributes: Record<string, string>, children: MathMLTag[]) {
super('munderover', value, attributes, children);
}
convert(): string {
if (this._children.length !== 3) throw new InvalidNumberOfChild(this.name, 3, this._children.length);
const base = this._children[0].convert();
const underContent = this._children[1].convert();
const overContent = this._children[2].convert();
return `${base}_{${underContent}}^{${overContent}}`;
}
}

@ -1,11 +0,0 @@
import { MathMLTag } from './MathMLTag';
export class Math extends MathMLTag {
constructor(value: string, attributes: Record<string, string>, children: MathMLTag[]) {
super('math', value, attributes, children);
}
convert(): string {
return this._normalizeWhiteSpaces(this._mapChildrenToLaTeX().join(''));
}
}

@ -1,48 +0,0 @@
export class MathMLTag {
readonly name: string;
protected _value: string;
protected _attributes: Record<string, string>;
protected _children: MathMLTag[];
protected _flags: string[] = [];
constructor(name: string, value: string, attributes: Record<string, string>, children: MathMLTag[]) {
this.name = name;
this._value = value;
this._attributes = attributes;
this._children = children;
}
convert(): string {
return this._mapChildrenToLaTeX().join('');
}
protected _mapChildrenToLaTeX(): string[] {
return this._children.map((mathMLTag) => mathMLTag.convert());
}
protected _normalizeWhiteSpaces(str: string): string {
return str.replace(/\s+/g, ' ');
}
isThere(className: string): boolean {
const firstChild = this._children[0];
if (!firstChild) return false;
return firstChild.constructor.name === className || firstChild.isThere(className);
}
addFlag(flag: string): void {
if (!this._flags.includes(flag)) this._flags.push(flag);
}
addFlagRecursiveIfClassName(className: string, flag: string): void {
this._children.forEach((child) => {
if (child.constructor.name === className) child.addFlag(flag);
child.addFlagRecursiveIfClassName(className, flag);
});
}
hasFlag(flag: string): boolean {
return this._flags.includes(flag);
}
}

@ -1,23 +0,0 @@
export { Math } from './Math';
export { MathMLTag } from './MathMLTag';
export { MI } from './MI';
export { MO } from './MO';
export { MN } from './MN';
export { MSqrt } from './MSqrt';
export { MFenced } from './MFenced';
export { MFrac } from './MFrac';
export { MRoot } from './MRoot';
export { MAction } from './MAction';
export { MEnclose } from './MEnclose';
export { MError } from './MError';
export { MPhantom } from './MPhantom';
export { MSup } from './MSup';
export { MSub } from './MSub';
export { MSubsup } from './MSubsup';
export { MMultiscripts } from './MMultiscripts';
export { MText } from './MText';
export { MUnderover } from './MUnderover';
export { MTable } from './MTable';
export { MTr } from './MTr';
export { GenericContentWrapperTag } from './GenericContentWrapperTag';
export { GenericUnderOverTag } from './GenericUnderOverTag';

@ -0,0 +1,4 @@
export * from './wrappers';
export * from './join-with-many-separators';
export * from './mathml-element-to-latex-converter';
export * from './normalize-whitespace';

@ -0,0 +1,6 @@
import { MathMLElement } from '@/data/protocols/mathml-element';
import { ToLaTeXConverter } from '@/domain/usecases/to-latex-converter';
import { MathMLElementToLatexConverterAdapter } from '../usecases/mathml-to-latex-convertion/mathml-element-to-latex-converter-adapter';
export const mathMLElementToLaTeXConverter = (mathMLElement: MathMLElement): ToLaTeXConverter =>
new MathMLElementToLatexConverterAdapter(mathMLElement).toLatexConverter();

@ -0,0 +1,3 @@
export const normalizeWhiteSpaces = (str: string): string => {
return str.replace(/\s+/g, ' ');
};

@ -0,0 +1,10 @@
import { Wrapper } from './wrapper';
export class BracketWrapper {
protected _open = '{';
protected _close = '}';
wrap(str: string): string {
return new Wrapper(this._open, this._close).wrap(str);
}
}

@ -1,12 +1,15 @@
import { Wrapper } from './Wrapper';
import { Wrapper } from './wrapper';
export class GenericWrapper extends Wrapper {
export class GenericWrapper {
protected _open: string;
protected _close: string;
constructor(open: string, close: string) {
super();
this._open = '\\left' + open;
this._close = '\\right' + close;
}
wrap(str: string): string {
return new Wrapper(this._open, this._close).wrap(str);
}
}

@ -0,0 +1,3 @@
export { BracketWrapper } from './bracket';
export { ParenthesisWrapper } from './parenthesis';
export { GenericWrapper } from './generic';

@ -0,0 +1,15 @@
import { Wrapper } from './wrapper';
export class ParenthesisWrapper {
protected _open = '\\left(';
protected _close = '\\right)';
wrap(str: string): string {
return new Wrapper(this._open, this._close).wrap(str);
}
wrapIfMoreThanOneChar(str: string): string {
if (str.length <= 1) return str;
return this.wrap(str);
}
}

@ -0,0 +1,13 @@
export class Wrapper {
protected _open: string;
protected _close: string;
constructor(open: string, close: string) {
this._open = open;
this._close = close;
}
wrap(str: string): string {
return this._open + str + this._close;
}
}

@ -0,0 +1,6 @@
export interface MathMLElement {
readonly name: string;
readonly value: string;
readonly children: MathMLElement[];
attributes: Record<string, string>;
}

@ -0,0 +1,14 @@
import { ToLaTeXConverter } from '@/domain/usecases/to-latex-converter';
import { MathMLElement } from '../protocols/mathml-element';
export class MathMLElementToLaTexConverter implements ToLaTeXConverter {
private readonly _mathmlElement: MathMLElement;
constructor(mathElement: MathMLElement) {
this._mathmlElement = mathElement;
}
convert(): string {
return 'a';
}
}

@ -0,0 +1,18 @@
import { ToLaTeXConverter } from '@/domain/usecases/to-latex-converter';
import { MathMLElement } from '../../../protocols/mathml-element';
import { mathMLElementToLaTeXConverter } from '../../../helpers';
export class GenericNoSpacingWrapper implements ToLaTeXConverter {
private readonly _mathmlElement: MathMLElement;
constructor(mathElement: MathMLElement) {
this._mathmlElement = mathElement;
}
convert(): string {
return this._mathmlElement.children
.map((child) => mathMLElementToLaTeXConverter(child))
.map((converter) => converter.convert())
.join('');
}
}

@ -0,0 +1,18 @@
import { ToLaTeXConverter } from '@/domain/usecases/to-latex-converter';
import { MathMLElement } from '../../../protocols/mathml-element';
import { mathMLElementToLaTeXConverter } from '../../../helpers';
export class GenericSpacingWrapper implements ToLaTeXConverter {
private readonly _mathmlElement: MathMLElement;
constructor(mathElement: MathMLElement) {
this._mathmlElement = mathElement;
}
convert(): string {
return this._mathmlElement.children
.map((child) => mathMLElementToLaTeXConverter(child))
.map((converter) => converter.convert())
.join(' ');
}
}

@ -0,0 +1,52 @@
import { ToLaTeXConverter } from '@/domain/usecases/to-latex-converter';
import { MathMLElement } from '../../../protocols/mathml-element';
import { mathMLElementToLaTeXConverter } from '../../../helpers/mathml-element-to-latex-converter';
import { InvalidNumberOfChild } from '../../../errors';
import { latexAccents } from '@/syntax/latexAccents';
export class GenericUnderOver implements ToLaTeXConverter {
private readonly _mathmlElement: MathMLElement;
constructor(mathElement: MathMLElement) {
this._mathmlElement = mathElement;
}
convert(): string {
const { name, children } = this._mathmlElement;
const childrenLength = children.length;
if (childrenLength !== 2) throw new InvalidNumberOfChild(name, 2, childrenLength);
const content = mathMLElementToLaTeXConverter(children[0]).convert();
const accent = mathMLElementToLaTeXConverter(children[1]).convert();
return this._applyCommand(content, accent);
}
private _applyCommand(content: string, accent: string): string {
const type = this._mathmlElement.name.match(/under/) ? TagTypes.Under : TagTypes.Over;
return new UnderOverSetter(type).apply(content, accent);
}
}
class UnderOverSetter {
private readonly _type;
constructor(type: TagTypes) {
this._type = type;
}
apply(content: string, accent: string) {
return latexAccents.includes(accent) ? `${accent}{${content}}` : `${this._defaultCommand}{${accent}}{${content}}`;
}
private get _defaultCommand(): string {
if (this._type === TagTypes.Under) return '\\underset';
return '\\overset';
}
}
enum TagTypes {
Under,
Over,
}

@ -0,0 +1,23 @@
export { Math } from './math';
export { MI } from './mi';
export { MO } from './mo';
export { MN } from './mn';
export { MSqrt } from './msqrt';
export { MFenced } from './mfenced';
export { MFrac } from './mfrac';
export { MRoot } from './mroot';
export { MAction } from './maction';
export { MEnclose } from './menclose';
export { MError } from './merror';
export { MPhantom } from './mphantom';
export { MSup } from './msup';
export { MSub } from './msub';
export { MSubsup } from './msubsup';
export { MMultiscripts } from './mmultiscripts';
export { MText } from './mtext';
export { MUnderover } from './munderover';
export { MTable } from './mtable';
export { MTr } from './mtr';
export { GenericSpacingWrapper } from './generic-spacing-wrapper';
export { GenericNoSpacingWrapper } from './generic-no-spacing-wrapper';
export { GenericUnderOver } from './generic-under-over';

@ -0,0 +1,28 @@
import { ToLaTeXConverter } from '@/domain/usecases/to-latex-converter';
import { MathMLElement } from '../../../protocols/mathml-element';
import { mathMLElementToLaTeXConverter } from '../../../helpers/mathml-element-to-latex-converter';
export class MAction implements ToLaTeXConverter {
private readonly _mathmlElement: MathMLElement;
constructor(mathElement: MathMLElement) {
this._mathmlElement = mathElement;
}
convert(): string {
const { children } = this._mathmlElement;
if (this._isToggle())
return children
.map((child) => mathMLElementToLaTeXConverter(child))
.map((converter) => converter.convert())
.join(' \\Longrightarrow ');
return mathMLElementToLaTeXConverter(children[0]).convert();
}
private _isToggle(): boolean {
const { actiontype } = this._mathmlElement.attributes;
return actiontype === 'toggle' || !actiontype;
}
}

@ -0,0 +1,21 @@
import { ToLaTeXConverter } from '@/domain/usecases/to-latex-converter';
import { MathMLElement } from '../../../protocols/mathml-element';
import { mathMLElementToLaTeXConverter } from '../../../helpers/mathml-element-to-latex-converter';
import { normalizeWhiteSpaces } from '../../../helpers/normalize-whitespace';
export class Math implements ToLaTeXConverter {
private readonly _mathmlElement: MathMLElement;
constructor(mathElement: MathMLElement) {
this._mathmlElement = mathElement;
}
convert(): string {
const unnormalizedLatex = this._mathmlElement.children
.map((child) => mathMLElementToLaTeXConverter(child))
.map((converter) => converter.convert())
.join('');
return normalizeWhiteSpaces(unnormalizedLatex);
}
}

@ -1,12 +1,19 @@
import { MathMLTag } from './MathMLTag';
import { ToLaTeXConverter } from '@/domain/usecases/to-latex-converter';
import { MathMLElement } from '../../../protocols/mathml-element';
import { mathMLElementToLaTeXConverter } from '../../../helpers/mathml-element-to-latex-converter';
export class MEnclose extends MathMLTag {
constructor(value: string, attributes: Record<string, string>, children: MathMLTag[]) {
super('menclose', value, attributes, children);
export class MEnclose implements ToLaTeXConverter {
private readonly _mathmlElement: MathMLElement;
constructor(mathElement: MathMLElement) {
this._mathmlElement = mathElement;
}
convert(): string {
const latexJoinedChildren = this._mapChildrenToLaTeX().join(' ');
const latexJoinedChildren = this._mathmlElement.children
.map((child) => mathMLElementToLaTeXConverter(child))
.map((converter) => converter.convert())
.join(' ');
if (this._notation === 'actuarial') return `\\overline{\\left.${latexJoinedChildren}\\right|}`;
if (this._notation === 'radical') return `\\sqrt{${latexJoinedChildren}}`;
@ -26,8 +33,6 @@ export class MEnclose extends MathMLTag {
}
private get _notation(): string {
if (!this._attributes.notation) return 'longdiv';
return this._attributes.notation;
return this._mathmlElement.attributes.notation || 'longdiv';
}
}

@ -0,0 +1,20 @@
import { ToLaTeXConverter } from '@/domain/usecases/to-latex-converter';
import { MathMLElement } from '../../../protocols/mathml-element';
import { mathMLElementToLaTeXConverter } from '../../../helpers/mathml-element-to-latex-converter';
export class MError implements ToLaTeXConverter {
private readonly _mathmlElement: MathMLElement;
constructor(mathElement: MathMLElement) {
this._mathmlElement = mathElement;
}
convert(): string {
const latexJoinedChildren = this._mathmlElement.children
.map((child) => mathMLElementToLaTeXConverter(child))
.map((converter) => converter.convert())
.join(' ');
return `\\color{red}{${latexJoinedChildren}}`;
}
}

@ -1,24 +1,36 @@
import { MathMLTag } from './MathMLTag';
import { GenericWrapper } from '../../../../../../utils/wrappers';
import { JoinWithManySeparators } from '../../../../../../utils';
import { ToLaTeXConverter } from '@/domain/usecases/to-latex-converter';
import { MathMLElement } from '../../../protocols/mathml-element';
import { mathMLElementToLaTeXConverter } from '../../../helpers/mathml-element-to-latex-converter';
import { GenericWrapper, JoinWithManySeparators } from '../../../helpers';
export class MFenced extends MathMLTag {
export class MFenced implements ToLaTeXConverter {
private readonly _mathmlElement: MathMLElement;
private readonly _open: string;
private readonly _close: string;
private readonly _separators: string[];
constructor(value: string, attributes: Record<string, string>, children: MathMLTag[]) {
super('mfenced', value, attributes, children);
this._open = this._attributes.open || '';
this._close = this._attributes.close || '';
this._separators = Array.from(this._attributes.separators || '');
constructor(mathmlElement: MathMLElement) {
this._mathmlElement = mathmlElement;
this._open = this._mathmlElement.attributes.open || '';
this._close = this._mathmlElement.attributes.close || '';
this._separators = Array.from(this._mathmlElement.attributes.separators || '');
}
convert(): string {
if (this.isThere('MTable')) return new Matrix(this._open, this._close).apply(this._mapChildrenToLaTeX().join());
const latexChildren = this._mathmlElement.children
.map((child) => mathMLElementToLaTeXConverter(child))
.map((converter) => converter.convert());
return new Vector(this._open, this._close, this._separators).apply(this._mapChildrenToLaTeX());
if (this._isThereRelativeOfName(this._mathmlElement.children, 'mtable'))
return new Matrix(this._open, this._close).apply(latexChildren);
return new Vector(this._open, this._close, this._separators).apply(latexChildren);
}
private _isThereRelativeOfName(mathmlElements: MathMLElement[], elementName: string): boolean {
return mathmlElements.some(
(child) => child.name === elementName || this._isThereRelativeOfName(child.children, elementName),
);
}
}
@ -48,9 +60,9 @@ class Matrix {
this._separators = new Separators(open, close);
}
apply(latex: string): string {
apply(latexContents: string[]): string {
const command = this._command;
const matrix = `\\begin{${command}}\n${latex}\n\\end{${command}}`;
const matrix = `\\begin{${command}}\n${latexContents.join('')}\n\\end{${command}}`;
return command === this._genericCommand ? this._separators.wrap(matrix) : matrix;
}

@ -0,0 +1,34 @@
import { ToLaTeXConverter } from '@/domain/usecases/to-latex-converter';
import { MathMLElement } from '../../../protocols/mathml-element';
import { InvalidNumberOfChild } from '../../../errors';
import { ParenthesisWrapper, mathMLElementToLaTeXConverter } from '../../../helpers';
export class MFrac implements ToLaTeXConverter {
private readonly _mathmlElement: MathMLElement;
constructor(mathElement: MathMLElement) {
this._mathmlElement = mathElement;
}
convert(): string {
const { children, name } = this._mathmlElement;
const childrenLength = children.length;
if (childrenLength !== 2) throw new InvalidNumberOfChild(name, 2, childrenLength);
const num = mathMLElementToLaTeXConverter(children[0]).convert();
const den = mathMLElementToLaTeXConverter(children[1]).convert();
if (this._isBevelled()) return `${this._wrapIfMoreThanOneChar(num)}/${this._wrapIfMoreThanOneChar(den)}`;
return `\\frac{${num}}{${den}}`;
}
private _wrapIfMoreThanOneChar(str: string): string {
return new ParenthesisWrapper().wrapIfMoreThanOneChar(str);
}
private _isBevelled(): boolean {
return !!this._mathmlElement.attributes.bevelled;
}
}

@ -1,13 +1,17 @@
import { MathMLTag } from './MathMLTag';
import { allMathSymbolsByChar, allMathSymbolsByGlyph } from '../../../../../../syntax';
import { ToLaTeXConverter } from '@/domain/usecases/to-latex-converter';
import { MathMLElement } from '../../../protocols/mathml-element';
import { normalizeWhiteSpaces } from '../../../helpers';
import { allMathSymbolsByChar, allMathSymbolsByGlyph } from '@/syntax';
export class MI extends MathMLTag {
constructor(value: string, attributes: Record<string, string>, children: MathMLTag[]) {
super('mi', value, attributes, children);
export class MI implements ToLaTeXConverter {
private readonly _mathmlElement: MathMLElement;
constructor(mathElement: MathMLElement) {
this._mathmlElement = mathElement;
}
convert(): string {
const normalizedValue = this._normalizeWhiteSpaces(this._value);
const normalizedValue = normalizeWhiteSpaces(this._mathmlElement.value);
if (normalizedValue === ' ') return Character.apply(normalizedValue);
const trimmedValue = normalizedValue.trim();

@ -0,0 +1,64 @@
import { ToLaTeXConverter } from '@/domain/usecases/to-latex-converter';
import { MathMLElement } from '../../../protocols/mathml-element';
import { mathMLElementToLaTeXConverter, ParenthesisWrapper } from '../../../helpers';
import { InvalidNumberOfChild } from '../../../errors';
export class MMultiscripts implements ToLaTeXConverter {
private readonly _mathmlElement: MathMLElement;
constructor(mathElement: MathMLElement) {
this._mathmlElement = mathElement;
}
convert(): string {
const { name, children } = this._mathmlElement;
const childrenLength = children.length;
if (childrenLength < 3) throw new InvalidNumberOfChild(name, 3, childrenLength, 'at least');
const baseContent = mathMLElementToLaTeXConverter(children[0]).convert();
return this._prescriptLatex() + this._wrapInParenthesisIfThereIsSpace(baseContent) + this._postscriptLatex();
}
private _prescriptLatex(): string {
const { children } = this._mathmlElement;
let sub;
let sup;
if (this._isPrescripts(children[1])) {
sub = children[2];
sup = children[3];
} else if (this._isPrescripts(children[3])) {
sub = children[4];
sup = children[5];
} else return '';
const subLatex = sub ? mathMLElementToLaTeXConverter(sub).convert() : '';
const supLatex = sup ? mathMLElementToLaTeXConverter(sup).convert() : '';
return `\\_{${subLatex}}^{${supLatex}}`;
}
private _postscriptLatex(): string {
const { children } = this._mathmlElement;
if (this._isPrescripts(children[1])) return '';
const sub = children[1];
const sup = children[2];
const subLatex = sub ? mathMLElementToLaTeXConverter(sub).convert() : '';
const supLatex = sup ? mathMLElementToLaTeXConverter(sup).convert() : '';
return `_{${subLatex}}^{${supLatex}}`;
}
private _wrapInParenthesisIfThereIsSpace(str: string): string {
if (!str.match(/\s+/g)) return str;
return new ParenthesisWrapper().wrap(str);
}
private _isPrescripts(child: MathMLElement): boolean {
return child?.name === 'mprescripts';
}
}

@ -0,0 +1,16 @@
import { ToLaTeXConverter } from '@/domain/usecases/to-latex-converter';
import { MathMLElement } from '../../../protocols/mathml-element';
import { normalizeWhiteSpaces } from '../../../helpers';
export class MN implements ToLaTeXConverter {
private readonly _mathmlElement: MathMLElement;
constructor(mathElement: MathMLElement) {
this._mathmlElement = mathElement;
}
convert(): string {
const normalizedValue = normalizeWhiteSpaces(this._mathmlElement.value);
return normalizedValue.trim();
}
}

@ -1,13 +1,17 @@
import { MathMLTag } from './MathMLTag';
import { allMathOperatorsByChar, allMathOperatorsByGlyph } from '../../../../../../syntax';
import { ToLaTeXConverter } from '@/domain/usecases/to-latex-converter';
import { MathMLElement } from '../../../protocols/mathml-element';
import { normalizeWhiteSpaces } from '../../../helpers';
import { allMathOperatorsByChar, allMathOperatorsByGlyph } from '@/syntax';
export class MO extends MathMLTag {
constructor(value: string, attributes: Record<string, string>, children: MathMLTag[]) {
super('mo', value, attributes, children);
export class MO implements ToLaTeXConverter {
private readonly _mathmlElement: MathMLElement;
constructor(mathElement: MathMLElement) {
this._mathmlElement = mathElement;
}
convert(): string {
const normalizedValue = this._normalizeWhiteSpaces(this._value);
const normalizedValue = normalizeWhiteSpaces(this._mathmlElement.value);
const trimmedValue = normalizedValue.trim();
return Operator.operate(trimmedValue);

@ -0,0 +1,14 @@
import { ToLaTeXConverter } from '@/domain/usecases/to-latex-converter';
import { MathMLElement } from '../../../protocols/mathml-element';
export class MPhantom implements ToLaTeXConverter {
private readonly _mathmlElement;
constructor(mathElement: MathMLElement) {
this._mathmlElement = mathElement;
}
convert(): string {
return '';
}
}

@ -0,0 +1,24 @@
import { ToLaTeXConverter } from '@/domain/usecases/to-latex-converter';
import { MathMLElement } from '../../../protocols/mathml-element';
import { mathMLElementToLaTeXConverter } from '../../../helpers';
import { InvalidNumberOfChild } from '../../../errors';
export class MRoot implements ToLaTeXConverter {
private readonly _mathmlElement: MathMLElement;
constructor(mathElement: MathMLElement) {
this._mathmlElement = mathElement;
}
convert(): string {
const { name, children } = this._mathmlElement;
const childrenLength = children.length;
if (childrenLength !== 2) throw new InvalidNumberOfChild(name, 2, childrenLength);
const content = mathMLElementToLaTeXConverter(children[0]).convert();
const rootIndex = mathMLElementToLaTeXConverter(children[1]).convert();
return `\\sqrt[${rootIndex}]{${content}}`;
}
}

@ -0,0 +1,20 @@
import { ToLaTeXConverter } from '@/domain/usecases/to-latex-converter';
import { MathMLElement } from '../../../protocols/mathml-element';
import { mathMLElementToLaTeXConverter } from '../../../helpers';
export class MSqrt implements ToLaTeXConverter {
private readonly _mathmlElement: MathMLElement;
constructor(mathElement: MathMLElement) {
this._mathmlElement = mathElement;
}
convert(): string {
const latexJoinedChildren = this._mathmlElement.children
.map((child) => mathMLElementToLaTeXConverter(child))
.map((converter) => converter.convert())
.join(' ');
return `\\sqrt{${latexJoinedChildren}}`;
}
}

@ -0,0 +1,24 @@
import { ToLaTeXConverter } from '@/domain/usecases/to-latex-converter';
import { MathMLElement } from '../../../protocols/mathml-element';
import { mathMLElementToLaTeXConverter, ParenthesisWrapper, BracketWrapper } from '../../../helpers';
import { InvalidNumberOfChild } from '../../../errors';
export class MSub implements ToLaTeXConverter {
private readonly _mathmlElement: MathMLElement;
constructor(mathElement: MathMLElement) {
this._mathmlElement = mathElement;
}
convert(): string {
const { name, children } = this._mathmlElement;
const childrenLength = children.length;
if (childrenLength !== 2) throw new InvalidNumberOfChild(name, 2, childrenLength);
const base = mathMLElementToLaTeXConverter(children[0]).convert();
const subscript = mathMLElementToLaTeXConverter(children[1]).convert();
return `${new ParenthesisWrapper().wrapIfMoreThanOneChar(base)}_${new BracketWrapper().wrap(subscript)}`;
}
}

@ -0,0 +1,33 @@
import { ToLaTeXConverter } from '@/domain/usecases/to-latex-converter';
import { MathMLElement } from '../../../protocols/mathml-element';
import { mathMLElementToLaTeXConverter, ParenthesisWrapper, BracketWrapper } from '../../../helpers';
import { InvalidNumberOfChild } from '../../../errors';
export class MSubsup implements ToLaTeXConverter {
private readonly _mathmlElement: MathMLElement;
constructor(mathElement: MathMLElement) {
this._mathmlElement = mathElement;
}
convert(): string {
const { name, children } = this._mathmlElement;
const childrenLength = children.length;
if (childrenLength !== 3) throw new InvalidNumberOfChild(name, 3, childrenLength);
const base = mathMLElementToLaTeXConverter(children[0]).convert();
const sub = mathMLElementToLaTeXConverter(children[1]).convert();
const sup = mathMLElementToLaTeXConverter(children[2]).convert();
const wrappedSub = new BracketWrapper().wrap(sub);
const wrappedSup = new BracketWrapper().wrap(sup);
return `${this._wrapInParenthesisIfThereIsSpace(base)}_${wrappedSub}^${wrappedSup}`;
}
private _wrapInParenthesisIfThereIsSpace(str: string): string {
if (!str.match(/\s+/g)) return str;
return new ParenthesisWrapper().wrap(str);
}
}

@ -0,0 +1,24 @@
import { ToLaTeXConverter } from '@/domain/usecases/to-latex-converter';
import { MathMLElement } from '../../../protocols/mathml-element';
import { mathMLElementToLaTeXConverter, ParenthesisWrapper, BracketWrapper } from '../../../helpers';
import { InvalidNumberOfChild } from '../../../errors';
export class MSup implements ToLaTeXConverter {
private readonly _mathmlElement: MathMLElement;
constructor(mathElement: MathMLElement) {
this._mathmlElement = mathElement;
}
convert(): string {
const { name, children } = this._mathmlElement;
const childrenLength = children.length;
if (childrenLength !== 2) throw new InvalidNumberOfChild(name, 2, childrenLength);
const base = mathMLElementToLaTeXConverter(children[0]).convert();
const exponent = mathMLElementToLaTeXConverter(children[1]).convert();
return `${new ParenthesisWrapper().wrapIfMoreThanOneChar(base)}^${new BracketWrapper().wrap(exponent)}`;
}
}

@ -0,0 +1,51 @@
import { ToLaTeXConverter } from '@/domain/usecases/to-latex-converter';
import { MathMLElement } from '../../../protocols/mathml-element';
import { mathMLElementToLaTeXConverter } from '../../../helpers';
export class MTable implements ToLaTeXConverter {
private readonly _mathmlElement: MathMLElement;
constructor(mathElement: MathMLElement) {
this._mathmlElement = mathElement;
this._addFlagRecursiveIfName(this._mathmlElement.children, 'mtable', 'innerTable');
}
convert(): string {
const tableContent = this._mathmlElement.children
.map((child) => mathMLElementToLaTeXConverter(child))
.map((converter) => converter.convert())
.join(' \\\\\n');
return this._hasFlag('innerTable') ? this._wrap(tableContent) : tableContent;
}
private _wrap(latex: string): string {
return `\\begin{matrix}${latex}\\end{matrix}`;
}
private _addFlagRecursiveIfName(mathmlElements: MathMLElement[], name: string, flag: string): void {
mathmlElements.forEach((mathmlElement) => {
if (mathmlElement.name === name) mathmlElement.attributes[flag] = flag;
this._addFlagRecursiveIfName(mathmlElement.children, name, flag);
});
}
private _hasFlag(flag: string): boolean {
return !!this._mathmlElement.attributes[flag];
}
}
export class GenericMathMlElementWrapper implements ToLaTeXConverter {
private readonly _mathmlElement: MathMLElement;
constructor(mathElement: MathMLElement) {
this._mathmlElement = mathElement;
}
convert(): string {
return this._mathmlElement.children
.map((child) => mathMLElementToLaTeXConverter(child))
.map((converter) => converter.convert())
.join(' ');
}
}

@ -1,14 +1,17 @@
import { MathMLTag } from './MathMLTag';
import { ToLaTeXConverter } from '@/domain/usecases/to-latex-converter';
import { MathMLElement } from '../../../protocols/mathml-element';
export class MText extends MathMLTag {
constructor(value: string, attributes: Record<string, string>, children: MathMLTag[]) {
super('mtext', value, attributes, children);
export class MText implements ToLaTeXConverter {
private readonly _mathmlElement: MathMLElement;
constructor(mathElement: MathMLElement) {
this._mathmlElement = mathElement;
}
convert(): string {
const textCommand = new TextCommand(this._attributes.mathvariant);
const { attributes, value } = this._mathmlElement;
return textCommand.apply(this._value);
return new TextCommand(attributes.mathvariant).apply(value);
}
}

@ -0,0 +1,18 @@
import { ToLaTeXConverter } from '@/domain/usecases/to-latex-converter';
import { MathMLElement } from '../../../protocols/mathml-element';
import { mathMLElementToLaTeXConverter } from '../../../helpers';
export class MTr implements ToLaTeXConverter {
private readonly _mathmlElement: MathMLElement;
constructor(mathElement: MathMLElement) {
this._mathmlElement = mathElement;
}
convert(): string {
return this._mathmlElement.children
.map((child) => mathMLElementToLaTeXConverter(child))
.map((converter) => converter.convert())
.join(' & ');
}
}

@ -0,0 +1,25 @@
import { ToLaTeXConverter } from '@/domain/usecases/to-latex-converter';
import { MathMLElement } from '../../../protocols/mathml-element';
import { mathMLElementToLaTeXConverter } from '../../../helpers';
import { InvalidNumberOfChild } from '../../../errors';
export class MUnderover implements ToLaTeXConverter {
private readonly _mathmlElement: MathMLElement;
constructor(mathElement: MathMLElement) {
this._mathmlElement = mathElement;
}
convert(): string {
const { name, children } = this._mathmlElement;
const childrenLength = children.length;
if (childrenLength !== 3) throw new InvalidNumberOfChild(name, 3, childrenLength);
const base = mathMLElementToLaTeXConverter(children[0]).convert();
const underContent = mathMLElementToLaTeXConverter(children[1]).convert();
const overContent = mathMLElementToLaTeXConverter(children[2]).convert();
return `${base}_{${underContent}}^{${overContent}}`;
}
}

@ -0,0 +1,45 @@
import * as ToLatexConverters from './converters';
import { MathMLElement } from '@/data/protocols/mathml-element';
import { ToLaTeXConverter, ToLaTeXConverterClass } from '@/domain/usecases/to-latex-converter';
export class MathMLElementToLatexConverterAdapter {
private readonly _mathMLElement: MathMLElement;
constructor(mathMLElement: MathMLElement) {
this._mathMLElement = mathMLElement;
}
toLatexConverter(): ToLaTeXConverter {
const { name } = this._mathMLElement;
const Converter = fromMathMLElementToLatexConverter[name] || ToLatexConverters.GenericNoSpacingWrapper;
return new Converter(this._mathMLElement);
}
}
const fromMathMLElementToLatexConverter: Record<string, ToLaTeXConverterClass> = {
math: ToLatexConverters.Math,
mi: ToLatexConverters.MI,
mo: ToLatexConverters.MO,
mn: ToLatexConverters.MN,
msqrt: ToLatexConverters.MSqrt,
mfenced: ToLatexConverters.MFenced,
mfrac: ToLatexConverters.MFrac,
mroot: ToLatexConverters.MRoot,
maction: ToLatexConverters.MAction,
menclose: ToLatexConverters.MEnclose,
merror: ToLatexConverters.MError,
mphantom: ToLatexConverters.MPhantom,
msup: ToLatexConverters.MSup,
msub: ToLatexConverters.MSub,
msubsup: ToLatexConverters.MSubsup,
mmultiscripts: ToLatexConverters.MMultiscripts,
mtext: ToLatexConverters.MText,
munderover: ToLatexConverters.MUnderover,
mtable: ToLatexConverters.MTable,
mtr: ToLatexConverters.MTr,
mover: ToLatexConverters.GenericUnderOver,
munder: ToLatexConverters.GenericUnderOver,
mrow: ToLatexConverters.GenericSpacingWrapper,
mpadded: ToLatexConverters.GenericSpacingWrapper,
};

@ -0,0 +1,7 @@
export interface ToLaTeXConverter {
convert(): string;
}
export interface ToLaTeXConverterClass {
new (...args: any): ToLaTeXConverter;
}

@ -1,3 +1,3 @@
import MathMLToLaTeX from './converters/xml-mathml-to-latex';
import { MathMLToLaTeX } from '@/main';
export default MathMLToLaTeX;

@ -0,0 +1 @@
export * from './xmldom-to-mathml-element-adapter';

@ -1,16 +1,18 @@
import { MathML } from '../../protocols/MathML';
import { MathMLElement } from '@/data/protocols/mathml-element';
export class ElementsToMathMLAdapter {
convert(els: Element[]): MathML[] {
convert(els: Element[]): MathMLElement[] {
return els.filter((el: Element) => el.tagName !== undefined).map((el: Element) => this._convertElement(el));
}
private _convertElement(el: Element): MathML {
private _convertElement(el: Element): MathMLElement {
return {
name: el.tagName,
attributes: el.attributes ? this._convertElementAttributes(el.attributes) : {},
value: this._hasElementChild(el) ? '' : el.textContent || '',
children: this._hasElementChild(el) ? this.convert(Array.from(el.childNodes) as Element[]) : ([] as MathML[]),
children: this._hasElementChild(el)
? this.convert(Array.from(el.childNodes) as Element[])
: ([] as MathMLElement[]),
};
}

@ -1,17 +1,18 @@
import xmldom = require('xmldom');
import { DOMParser } from 'xmldom';
import { ErrorHandler } from './ErrorHandler';
import { MathML } from '../../protocols/MathML';
import { ElementsToMathMLAdapter } from './ElementsToMathMLAdapter';
import { ElementsToMathMLAdapter } from './xmldom-elements-to-mathml-elements-adapter';
import { ErrorHandler } from './error-handler';
import { MathMLElement } from '@/data/protocols/mathml-element';
export class XmlToMathMLAdapter {
private _xmlDOM: DOMParser;
private _errorHandler: ErrorHandler;
private _elementsConvertor: ElementsToMathMLAdapter;
private _xml: string;
private readonly _xmlDOM: DOMParser;
private readonly _errorHandler: ErrorHandler;
private readonly _elementsConvertor: ElementsToMathMLAdapter;
constructor(private _xml: string) {
this._xml = this._removeLineBreaks(_xml);
constructor(xml: string) {
this._xml = this._removeLineBreaks(xml);
this._elementsConvertor = new ElementsToMathMLAdapter();
this._errorHandler = new ErrorHandler();
@ -22,7 +23,7 @@ export class XmlToMathMLAdapter {
});
}
convert(): MathML[] {
convert(): MathMLElement[] {
return this._elementsConvertor.convert(this._mathMLElements);
}

1
src/main/index.ts Normal file

@ -0,0 +1 @@
export * from './mathml-to-latex';

@ -0,0 +1,12 @@
import { MathMLElementToLatexConverterAdapter } from '@/data/usecases/mathml-to-latex-convertion/mathml-element-to-latex-converter-adapter';
import { XmlToMathMLAdapter } from '@/infra/usecases/xmldom-to-mathml-elements';
export class MathMLToLaTeX {
static convert(mathml: string): string {
const mathmlElements = new XmlToMathMLAdapter(mathml).convert();
const mathmlElementsToLaTeXConverters = mathmlElements.map((mathMLElement) =>
new MathMLElementToLatexConverterAdapter(mathMLElement).toLatexConverter(),
);
return mathmlElementsToLaTeXConverters.map((toLatexConverters) => toLatexConverters.convert()).join('');
}
}

@ -1,6 +0,0 @@
export interface MathML {
name: string;
attributes: Record<string, string>;
value: string;
children: MathML[];
}

@ -1 +0,0 @@
export { MathML } from './MathML';

@ -1,2 +0,0 @@
export { JoinWithManySeparators } from './Separators';
export * from './wrappers';

@ -1,6 +0,0 @@
import { Wrapper } from './Wrapper';
export class BracketWrapper extends Wrapper {
protected _open = '{';
protected _close = '}';
}

@ -1,6 +0,0 @@
import { Wrapper } from './Wrapper';
export class ParenthesisWrapper extends Wrapper {
protected _open = '\\left(';
protected _close = '\\right)';
}

@ -1,13 +0,0 @@
export abstract class Wrapper {
protected abstract _open: string;
protected abstract _close: string;
wrap(str: string): string {
return this._open + str + this._close;
}
wrapIfMoreThanOneChar(str: string): string {
if (str.length <= 1) return str;
return this.wrap(str);
}
}

@ -1,3 +0,0 @@
export { BracketWrapper } from './BracketWrapper';
export { ParenthesisWrapper } from './ParenthesisWrapper';
export { GenericWrapper } from './GenericWrapper';