/* This file is part of the KDE project Copyright (C) 2001 Andrea Rizzi Ulrich Kuettler This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include //#include #include "kformulamathmlread.h" #include "symboltable.h" KFORMULA_NAMESPACE_BEGIN using namespace std; class MathML2KFormulaPrivate { friend class MathML2KFormula; public: MathML2KFormulaPrivate( MathML2KFormula* mml_filter, const ContextStyle& contextStyle, const TQDomDocument& formuladoc ); ~MathML2KFormulaPrivate(); void math( TQDomElement element ); // Token Elements void mi( TQDomElement element, TQDomNode docnode ); void mn( TQDomElement element, TQDomNode docnode ); void mo( TQDomElement element, TQDomNode docnode ); void mtext( TQDomElement element, TQDomNode docnode ); void mspace( TQDomElement element, TQDomNode docnode ); void ms( TQDomElement element, TQDomNode docnode ); // mglyph not supported // General Layout Schemata void mrow( TQDomElement element, TQDomNode docnode ); void mfrac( TQDomElement element, TQDomNode docnode ); void msqrt( TQDomElement element, TQDomNode docnode ); void mroot( TQDomElement element, TQDomNode docnode ); void mstyle( TQDomElement element, TQDomNode docnode ); // merror not supported // mpadded not supported // mphantom not supported void mfenced( TQDomElement element, TQDomNode docnode ); // menclose not supported // Script and Limit Schemata void msub_msup( TQDomElement element, TQDomNode docnode ); void msubsup( TQDomElement element, TQDomNode docnode ); void munder( TQDomElement element, TQDomNode docnode, bool oasisFormat ); void mover( TQDomElement element, TQDomNode docnode, bool oasisFormat ); void munderover( TQDomElement element, TQDomNode docnode, bool oasisFormat ); // mmultiscripts not supported // Tables and Matrices void mtable( TQDomElement element, TQDomNode docnode ); // not much supported // Enlivening Expressions // maction not supported protected: void createTextElements( TQString text, TQDomNode docnode ); void createNameSequence( TQString text, TQDomNode docnode ); double convertToPoint( TQString value, bool* ok ); bool isEmbellishedOperator( TQDomNode node, TQDomElement* mo, bool oasisFormat ); bool isSpaceLike( TQDomNode node, bool oasisFormat ); enum MathVariant { normal, bold, italic, bold_italic, double_struck, bold_fraktur, script, bold_script, fraktur, sans_serif, bold_sans_serif, sans_serif_italic, sans_serif_bold_italic, monospace }; struct MathStyle { MathStyle() : scriptsizemultiplier( 0.71 ), scriptminsize( 8 ), veryverythinmathspace( 1.0/18.0 ), verythinmathspace( 2.0/18.0 ), thinmathspace( 3.0/18.0 ), mediummathspace( 4.0/18.0 ), thickmathspace( 5.0/18.0 ), verythickmathspace( 6.0/18.0 ), veryverythickmathspace( 7.0/18.0 ), useVariant( false ) { } void styleChange() { kdDebug( DEBUGID ) << "Style Change:" << "\n scriptlevel = " << scriptlevel << "\n displaystyle = " << displaystyle << "\n scriptsizemultiplier = " << scriptsizemultiplier << "\n scriptminsize = " << scriptminsize << endl; } void setStyles( TQDomElement element ) { if ( !useVariant ) return; switch ( mathvariant ) { case normal: element.setAttribute( "STYLE", "normal" ); break; case bold: element.setAttribute( "STYLE", "bold" ); break; case bold_italic: element.setAttribute( "STYLE", "bolditalic" ); break; case italic: element.setAttribute( "STYLE", "italic" ); break; case double_struck: element.setAttribute( "FAMILY", "doublestruck" ); break; case bold_fraktur: element.setAttribute( "STYLE", "bold" ); case fraktur: element.setAttribute( "FAMILY", "fraktur" ); break; case bold_script: element.setAttribute( "STYLE", "bold" ); case script: element.setAttribute( "FAMILY", "script" ); break; case bold_sans_serif: element.setAttribute( "STYLE", "bold" ); case sans_serif: element.setAttribute( "FAMILY", "normal" ); break; case sans_serif_bold_italic: element.setAttribute( "STYLE", "bolditalic" ); element.setAttribute( "FAMILY", "normal" ); break; case sans_serif_italic: element.setAttribute( "STYLE", "italic" ); element.setAttribute( "FAMILY", "normal" ); break; //case monospace: default: break; } } void readStyles( TQDomElement mmlElement ) { if ( mmlElement.hasAttribute( "mathvariant" ) ) { useVariant = true; if ( mmlElement.attribute( "mathvariant" ) == "normal" ) mathvariant = normal; else if ( mmlElement.attribute( "mathvariant" ) == "bold" ) mathvariant = bold; else if ( mmlElement.attribute( "mathvariant" ) == "italic" ) mathvariant = italic; else if ( mmlElement.attribute( "mathvariant" ) == "bold-italic" ) mathvariant = bold_italic; else if ( mmlElement.attribute( "mathvariant" ) == "double-struck" ) mathvariant = double_struck; else if ( mmlElement.attribute( "mathvariant" ) == "bold-fraktur" ) mathvariant = bold_fraktur; else if ( mmlElement.attribute( "mathvariant" ) == "script" ) mathvariant = script; else if ( mmlElement.attribute( "mathvariant" ) == "bold-script" ) mathvariant = bold_script; else if ( mmlElement.attribute( "mathvariant" ) == "fraktur" ) mathvariant = fraktur; else if ( mmlElement.attribute( "mathvariant" ) == "sans-serif" ) mathvariant = sans_serif; else if ( mmlElement.attribute( "mathvariant" ) == "bold-sans-serif" ) mathvariant = bold_sans_serif; else if ( mmlElement.attribute( "mathvariant" ) == "sans-serif-italic" ) mathvariant = sans_serif_italic; else if ( mmlElement.attribute( "mathvariant" ) == "sans-serif-bold-italic" ) mathvariant = sans_serif_bold_italic; else if ( mmlElement.attribute( "mathvariant" ) == "monospace" ) mathvariant = monospace; } } // Styles, set by // default int scriptlevel; // inherited bool displaystyle; // inherited double scriptsizemultiplier; // 0.71 double scriptminsize; // 8pt // color // background double veryverythinmathspace; // 1/18em = 0.0555556em double verythinmathspace; // 2/18em = 0.111111em double thinmathspace; // 3/18em = 0.166667em double mediummathspace; // 4/18em = 0.222222em double thickmathspace; // 5/18em = 0.277778em double verythickmathspace; // 6/18em = 0.333333em double veryverythickmathspace; // 7/18em = 0.388889em // 'Local' styles MathVariant mathvariant; bool useVariant; //int mathsize; }; MathStyle style; TQDomDocument doc; private: const ContextStyle& context; MathML2KFormula* filter; }; MathML2KFormulaPrivate::MathML2KFormulaPrivate( MathML2KFormula* mml_filter, const ContextStyle& cs, const TQDomDocument& formuladoc ) : doc( formuladoc ), context( cs ), filter( mml_filter ) { } MathML2KFormulaPrivate::~MathML2KFormulaPrivate() { } void MathML2KFormulaPrivate::math( TQDomElement element ) { TQDomElement formula = doc.createElement( "FORMULA" ); TQDomNode n = element.firstChild(); TQString display = element.attribute( "display" ); if ( display == "block" ) { style.displaystyle = true; } else { // if display == "inline" (default) or illegal (then use default) style.displaystyle = false; } style.scriptlevel = 0; /*kdDebug( DEBUGID ) << " element:\n displaystyle = " << style.displaystyle << "\n scriptlevel = " << style.scriptlevel << endl;*/ while ( !n.isNull() ) { filter->processElement( n, doc, formula ); n = n.nextSibling(); } doc.appendChild( formula ); } void MathML2KFormulaPrivate::mi( TQDomElement element, TQDomNode docnode ) { MathStyle previousStyle( style ); TQString text = element.text().stripWhiteSpace(); if ( text.length() == 1 ) { // Default italic, only when content is one char style.mathvariant = italic; style.useVariant = true; style.readStyles( element ); createTextElements( text, docnode ); } else { // If length is 0 or >1, it should be a text sequence style.readStyles( element ); createNameSequence( text, docnode ); } style = previousStyle; } void MathML2KFormulaPrivate::mo( TQDomElement element, TQDomNode docnode ) { MathStyle previousStyle( style ); style.readStyles( element ); TQString text = element.text().stripWhiteSpace(); createTextElements( text, docnode ); style = previousStyle; } void MathML2KFormulaPrivate::mn( TQDomElement element, TQDomNode docnode ) { MathStyle previousStyle( style ); style.readStyles( element ); TQString text = element.text().stripWhiteSpace(); createTextElements( text, docnode ); style = previousStyle; } void MathML2KFormulaPrivate::mtext( TQDomElement element, TQDomNode docnode ) { MathStyle previousStyle( style ); style.readStyles( element ); TQDomNode n = element.firstChild(); while ( !n.isNull() ) { if ( n.isText() ) { TQString text = n.toText().data().stripWhiteSpace(); createTextElements( text, docnode ); } else if ( n.isElement() ) { filter->processElement( n, doc, docnode ); } else { kdDebug( DEBUGID ) << " child: " << n.nodeName() << endl; } n = n.nextSibling(); } style = previousStyle; } void MathML2KFormulaPrivate::ms( TQDomElement element, TQDomNode docnode ) { TQString lquote = element.attribute( "lquote", "\"" ); TQString rquote = element.attribute( "rquote", "\"" ); TQString text; text = lquote; text += element.text().stripWhiteSpace(); text += rquote; createTextElements( text, docnode ); } void MathML2KFormulaPrivate::mspace( TQDomElement element, TQDomNode docnode ) { // we support only horizontal space TQString width = element.attribute( "width" ); TQDomElement spaceelement = doc.createElement( "SPACE" ); // check for namedspace. We don't support much... if ( width == "veryverythinmathspace" ) { spaceelement.setAttribute( "WIDTH", "thin" ); } else if ( width == "verythinmathspace" ) { spaceelement.setAttribute( "WIDTH", "thin" ); } else if ( width == "thinmathspace" ) { spaceelement.setAttribute( "WIDTH", "thin" ); } else if ( width == "mediummathspace" ) { spaceelement.setAttribute( "WIDTH", "medium" ); } else if ( width == "thickmathspace" ) { spaceelement.setAttribute( "WIDTH", "thick" ); } else if ( width == "verythickmathspace" ) { spaceelement.setAttribute( "WIDTH", "thick" ); } else if ( width == "veryverythickmathspace" ) { spaceelement.setAttribute( "WIDTH", "quad" ); } else { // units double w = 0; bool ok; if ( width.endsWith( "em" ) ) { // See MathML specification, Appendix H w = context.getDefaultFont().pointSize(); if ( w == -1 ) { TQFontMetrics fm( context.getDefaultFont() ); w = fm.width( 'm' ); } w = w * width.remove( width.length() - 2, 2 ).toDouble( &ok ); // w in points? } else if ( width.endsWith( "px" ) ) { w = width.remove( width.length() - 2, 2 ).toDouble( &ok ); // w in pixels } else if ( width.endsWith( "in" ) ) { w = width.remove( width.length() - 2, 2 ).toDouble( &ok ); w *= 72; // w in points } else if ( width.endsWith( "cm" ) ) { w = width.remove( width.length() - 2, 2 ).toDouble( &ok ); w *= 1/2.54 * 72; // w in points } else if ( width.endsWith( "mm" ) ) { w = width.remove( width.length() - 2, 2 ).toDouble( &ok ); w *= 1/25.4 * 72; // w in points } else if ( width.endsWith( "pt" ) ) { w = width.remove( width.length() - 2, 2 ).toDouble( &ok ); // w in points } else if ( width.endsWith( "pc" ) ) { w = width.remove( width.length() - 2, 2 ).toDouble( &ok ); w /= 12; // w in points } else { w = width.toDouble( &ok ); } if ( !ok ) return; if ( w < 20 ) spaceelement.setAttribute( "WIDTH", "thin" ); else if ( w < 40 ) spaceelement.setAttribute( "WIDTH", "medium" ); else if ( w < 80 ) spaceelement.setAttribute( "WIDTH", "thick" ); else spaceelement.setAttribute( "WIDTH", "quad" ); } docnode.appendChild( spaceelement ); } void MathML2KFormulaPrivate::mrow( TQDomElement element, TQDomNode docnode ) { TQDomNode n = element.firstChild(); while ( !n.isNull() ) { if ( n.isElement () ) { TQDomElement e = n.toElement(); // We do not allow sequence inside sequence filter->processElement( e, doc, docnode ); } else { kdDebug( DEBUGID ) << " child: " << n.nodeName() << endl; } n = n.nextSibling(); } } void MathML2KFormulaPrivate::mfrac( TQDomElement element, TQDomNode docnode ) { TQDomNode n = element.firstChild(); TQDomElement fraction = doc.createElement( "FRACTION" ); MathStyle previousStyle( style ); style.displaystyle ? style.displaystyle = false : style.scriptlevel += 1; style.styleChange(); int i = 0; while ( !n.isNull() && i < 2 ) { if ( n.isElement() ) { ++i; if ( i == 1 ) { //first is numerator TQDomElement numerator = doc.createElement( "NUMERATOR" ); TQDomElement sequence = doc.createElement( "SEQUENCE" ); numerator.appendChild( sequence ); TQDomElement e = n.toElement(); filter->processElement( e, doc, sequence ); fraction.appendChild( numerator ); } else { TQDomElement denominator = doc.createElement( "DENOMINATOR" ); TQDomElement sequence = doc.createElement( "SEQUENCE" ); denominator.appendChild( sequence ); TQDomElement e = n.toElement(); filter->processElement( e, doc, sequence ); fraction.appendChild( denominator ); } } else { kdDebug( DEBUGID ) << " child: " << n.nodeName() << endl; } n = n.nextSibling(); } style = previousStyle; docnode.appendChild( fraction ); } void MathML2KFormulaPrivate::mroot( TQDomElement element, TQDomNode docnode ) { TQDomNode n = element.firstChild(); int i = 0; TQDomElement root = doc.createElement( "ROOT" ); while ( !n.isNull() && i < 2 ) { if ( n.isElement() ) { ++i; if ( i == 1 ) { //first is content (base) TQDomElement content = doc.createElement( "CONTENT" ); TQDomElement sequence = doc.createElement( "SEQUENCE" ); content.appendChild( sequence ); TQDomElement e = n.toElement(); filter->processElement( e, doc, sequence ); root.appendChild(content); } else { // index MathStyle previousStyle( style ); style.scriptlevel += 2; style.displaystyle = false; style.styleChange(); TQDomElement index = doc.createElement( "INDEX" ); TQDomElement sequence = doc.createElement( "SEQUENCE" ); index.appendChild( sequence ); TQDomElement e = n.toElement(); filter->processElement( e, doc, sequence ); root.appendChild( index ); style = previousStyle; } } else { kdDebug( DEBUGID ) << " child: " << n.nodeName() << endl; } n = n.nextSibling(); } docnode.appendChild( root ); } void MathML2KFormulaPrivate::msqrt( TQDomElement element, TQDomNode docnode ) { TQDomNode n = element.firstChild(); TQDomElement root = doc.createElement( "ROOT" ); TQDomElement content = doc.createElement( "CONTENT" ); TQDomElement sequence = doc.createElement( "SEQUENCE" ); content.appendChild( sequence ); root.appendChild( content ); while ( !n.isNull() ) { if ( n.isElement() ) { filter->processElement( n.toElement(), doc, sequence ); } else { kdDebug( DEBUGID ) << " child: " << n.nodeName() << endl; } n = n.nextSibling(); } docnode.appendChild( root ); } void MathML2KFormulaPrivate::mstyle( TQDomElement element, TQDomNode docnode ) { bool ok; MathStyle previousStyle( style ); style.readStyles( element ); if ( element.hasAttribute( "scriptlevel" ) ) { TQString scriptlevel = element.attribute( "scriptlevel" ); if ( scriptlevel.startsWith( "+" ) || scriptlevel.startsWith( "-" ) ) style.scriptlevel += scriptlevel.toInt( &ok ); else style.scriptlevel = scriptlevel.toInt( &ok ); if ( !ok ) style.scriptlevel = previousStyle.scriptlevel; } if ( element.hasAttribute( "displaystyle" ) ) { TQString displaystyle = element.attribute( "displaystyle" ); if ( displaystyle == "true" ) style.displaystyle = true; else if ( displaystyle == "false" ) style.displaystyle = false; } if ( element.hasAttribute( "scriptsizemultiplier" ) ) { style.scriptsizemultiplier = element.attribute( "scriptsizemultiplier" ).toDouble( &ok ); if ( !ok ) style.scriptsizemultiplier = previousStyle.scriptsizemultiplier; } if ( element.hasAttribute( "scriptminsize" ) ) { TQString scriptminsize = element.attribute( "scriptminsize" ); style.scriptminsize = convertToPoint( scriptminsize, &ok ); if ( !ok ) style.scriptminsize = previousStyle.scriptminsize; } if ( element.hasAttribute( "veryverythinmathspace" ) ) { TQString vvthinmspace = element.attribute( "veryverythinmathspace" ); style.veryverythinmathspace = convertToPoint( vvthinmspace, &ok ); if ( !ok ) style.veryverythinmathspace = previousStyle.veryverythinmathspace; } if ( element.hasAttribute( "verythinmathspace" ) ) { TQString vthinmspace = element.attribute( "verythinmathspace" ); style.verythinmathspace = convertToPoint( vthinmspace, &ok ); if ( !ok ) style.verythinmathspace = previousStyle.verythinmathspace; } if ( element.hasAttribute( "thinmathspace" ) ) { TQString thinmathspace = element.attribute( "thinmathspace" ); style.thinmathspace = convertToPoint( thinmathspace, &ok ); if ( !ok ) style.thinmathspace = previousStyle.thinmathspace; } if ( element.hasAttribute( "mediummathspace" ) ) { TQString mediummathspace = element.attribute( "mediummathspace" ); style.mediummathspace = convertToPoint( mediummathspace, &ok ); if ( !ok ) style.mediummathspace = previousStyle.mediummathspace; } if ( element.hasAttribute( "thickmathspace" ) ) { TQString thickmathspace = element.attribute( "thickmathspace" ); style.thickmathspace = convertToPoint( thickmathspace, &ok ); if ( !ok ) style.thickmathspace = previousStyle.thickmathspace; } if ( element.hasAttribute( "verythickmathspace" ) ) { TQString vthickmspace = element.attribute( "verythickmathspace" ); style.verythickmathspace = convertToPoint( vthickmspace, &ok ); if ( !ok ) style.verythickmathspace = previousStyle.verythickmathspace; } if ( element.hasAttribute( "veryverythickmathspace" ) ) { TQString vvthickmspace = element.attribute( "veryverythickmathspace" ); style.veryverythickmathspace = convertToPoint( vvthickmspace, &ok ); if ( !ok ) style.veryverythickmathspace = previousStyle.veryverythickmathspace; } style.styleChange(); TQDomNode n = element.firstChild(); while ( !n.isNull() ) { filter->processElement( n, doc, docnode ); n = n.nextSibling(); } style = previousStyle; } void MathML2KFormulaPrivate::mfenced( TQDomElement element, TQDomNode docnode ) { TQDomElement bracket = doc.createElement( "BRACKET" ); TQString value = element.attribute( "open", "(" ); bracket.setAttribute( "LEFT", TQString::number( value.at( 0 ).latin1() ) ); value = element.attribute( "close", ")" ); bracket.setAttribute( "RIGHT", TQString::number( value.at( 0 ).latin1() ) ); TQDomElement content = doc.createElement( "CONTENT" ); TQDomElement sequence = doc.createElement( "SEQUENCE" ); content.appendChild( sequence ); TQString separators = element.attribute( "separators", "," ); TQDomNode n = element.firstChild(); uint i = 0; while ( !n.isNull() ) { if ( n.isElement() ) { if ( i != 0 && !separators.isEmpty() ) { TQDomElement textelement = doc.createElement( "TEXT" ); if ( i > separators.length() ) i = separators.length(); textelement.setAttribute( "CHAR", TQString( separators.at( i - 1 ) ) ); //style.setStyles( textelement ); sequence.appendChild( textelement ); } ++i; TQDomElement e = n.toElement(); filter->processElement( e, doc, sequence ); } else { kdDebug( DEBUGID ) << " child: " << n.nodeName() << endl; } n = n.nextSibling(); } bracket.appendChild( content ); docnode.appendChild( bracket ); } void MathML2KFormulaPrivate::mtable( TQDomElement element, TQDomNode docnode ) { MathStyle previousStyle( style ); TQString displaystyle = element.attribute( "displaystyle", "false" ); if ( displaystyle == "true" ) { style.displaystyle = true; } else { // false is default and also used for illegal values style.displaystyle = false; } style.styleChange(); TQString subtag; int rows = 0; int cols = 0; TQDomNode n = element.firstChild(); while ( !n.isNull() ) { if ( n.isElement() ) { TQDomElement e = n.toElement(); subtag = e.tagName(); if (subtag == "mtr") { ++rows; /* Determins the number of columns */ TQDomNode cellnode = e.firstChild(); int cc = 0; while ( !cellnode.isNull() ) { if ( cellnode.isElement() ) cc++; cellnode = cellnode.nextSibling(); } if ( cc > cols ) cols = cc; } } else { kdDebug( DEBUGID ) << " child: " << n.nodeName() << endl; } n = n.nextSibling(); } /* Again createing elements, I need to know the number of rows and cols to leave empty spaces */ n = element.firstChild(); TQDomElement matrix = doc.createElement( "MATRIX" ); matrix.setAttribute( "COLUMNS", cols ); matrix.setAttribute( "ROWS", rows ); while ( !n.isNull() ) { if ( n.isElement() ) { TQDomElement e = n.toElement(); subtag = e.tagName(); if ( subtag == "mtr" ) { TQDomNode cellnode = e.firstChild(); int cc = 0; while ( !cellnode.isNull() ) { if ( cellnode.isElement() ) { ++cc; TQDomElement cell = doc.createElement( "SEQUENCE" ); TQDomElement cellelement = cellnode.toElement(); filter->processElement( cellelement, doc, cell ); matrix.appendChild( cell ); } cellnode = cellnode.nextSibling(); } /* Add empty elements */ for(; cc < cols; cc++ ) { TQDomElement cell = doc.createElement( "SEQUENCE" ); matrix.appendChild( cell ); } } } n = n.nextSibling(); } style = previousStyle; docnode.appendChild(matrix); } void MathML2KFormulaPrivate::msub_msup( TQDomElement element, TQDomNode docnode ) { TQDomNode n = element.firstChild(); int i = 0; TQDomElement root = doc.createElement( "INDEX" ); while ( !n.isNull() && i < 2 ) { if ( n.isElement() ) { ++i; if ( i == 1 ) { // first is content (base) TQDomElement content = doc.createElement( "CONTENT" ); TQDomElement sequence = doc.createElement( "SEQUENCE" ); content.appendChild( sequence ); TQDomElement e = n.toElement(); filter->processElement( e, doc, sequence ); root.appendChild( content ); } else { TQDomElement index; if ( element.tagName() == "msup" ) index = doc.createElement( "UPPERRIGHT" ); else index = doc.createElement( "LOWERRIGHT" ); MathStyle previousStyle( style ); style.scriptlevel += 1; style.displaystyle = false; style.styleChange(); TQDomElement sequence = doc.createElement( "SEQUENCE" ); index.appendChild( sequence ); TQDomElement e = n.toElement(); filter->processElement( e, doc, sequence ); root.appendChild( index ); style = previousStyle; } } else { kdDebug( DEBUGID ) << "<" << element.tagName() << "> child: " << n.nodeName() << endl; } n = n.nextSibling(); } docnode.appendChild( root ); } void MathML2KFormulaPrivate::munder( TQDomElement element, TQDomNode docnode, bool oasisFormat ) { bool accentunder; TQString au = element.attribute( "accentunder" ); if ( au == "true" ) accentunder = true; else if ( au == "false" ) accentunder = false; else { // use default TQDomElement mo; // is underscript an embellished operator? if ( isEmbellishedOperator( element.childNodes().item( 1 ), &mo, oasisFormat ) ) { if ( mo.attribute( "accent" ) == "true" ) accentunder = true; else accentunder = false; } else accentunder = false; } TQDomNode n = element.firstChild(); int i = 0; TQDomElement root = doc.createElement( "INDEX" ); while ( !n.isNull() && i < 2 ) { if ( n.isElement() ) { ++i; if ( i == 1 ) { // first is content (base) TQDomElement content = doc.createElement( "CONTENT" ); TQDomElement sequence = doc.createElement( "SEQUENCE" ); content.appendChild( sequence ); TQDomElement e = n.toElement(); filter->processElement( e, doc, sequence ); root.appendChild( content ); } else { // underscript MathStyle previousStyle( style ); style.displaystyle = false; if ( !accentunder ) { style.scriptlevel += 1; style.styleChange(); } TQDomElement mo; TQDomElement index; if ( isEmbellishedOperator( n.previousSibling(), &mo, oasisFormat ) && !previousStyle.displaystyle && mo.attribute( "movablelimits" ) == "true" ) { index = doc.createElement( "LOWERRIGHT" ); } else { index = doc.createElement( "LOWERMIDDLE" ); } TQDomElement sequence = doc.createElement( "SEQUENCE" ); index.appendChild( sequence ); TQDomElement e = n.toElement(); filter->processElement( e, doc, sequence ); root.appendChild( index ); style = previousStyle; } } else { kdDebug( DEBUGID ) << "<" << element.tagName() << "> child: " << n.nodeName() << endl; } n = n.nextSibling(); } docnode.appendChild( root ); } void MathML2KFormulaPrivate::mover( TQDomElement element, TQDomNode docnode, bool oasisFormat ) { bool accent; TQString ac = element.attribute( "accent" ); if ( ac == "true" ) accent = true; else if ( ac == "false" ) accent = false; else { // use default TQDomElement mo; // is overscript an embellished operator? if ( isEmbellishedOperator( element.childNodes().item( 1 ), &mo, oasisFormat ) ) { if ( mo.attribute( "accent" ) == "true" ) accent = true; else accent = false; } else accent = false; } TQDomNode n = element.firstChild(); int i = 0; TQDomElement root = doc.createElement( "INDEX" ); while ( !n.isNull() && i < 2 ) { if ( n.isElement() ) { ++i; if ( i == 1 ) { // first is content (base) TQDomElement content = doc.createElement( "CONTENT" ); TQDomElement sequence = doc.createElement( "SEQUENCE" ); content.appendChild( sequence ); TQDomElement e = n.toElement(); filter->processElement( e, doc, sequence ); root.appendChild( content ); } else { // overscript MathStyle previousStyle( style ); style.displaystyle = false; if ( !accent ) { style.scriptlevel += 1; style.styleChange(); } TQDomElement mo; TQDomElement index; if ( isEmbellishedOperator( n.previousSibling(), &mo, oasisFormat ) && !previousStyle.displaystyle && mo.attribute( "movablelimits" ) == "true" ) { index = doc.createElement( "UPPERRIGHT" ); } else { index = doc.createElement( "UPPERMIDDLE" ); } TQDomElement sequence = doc.createElement( "SEQUENCE" ); index.appendChild( sequence ); TQDomElement e = n.toElement(); filter->processElement( e, doc, sequence ); root.appendChild( index ); style = previousStyle; } } else { kdDebug( DEBUGID ) << "<" << element.tagName() << "> child: " << n.nodeName() << endl; } n = n.nextSibling(); } docnode.appendChild( root ); } void MathML2KFormulaPrivate::munderover( TQDomElement element, TQDomNode docnode, bool oasisFormat ) { bool accent; bool accentunder; TQString value = element.attribute( "accentunder" ); if ( value == "true" ) accentunder = true; else if ( value == "false" ) accentunder = false; else { // use default TQDomElement mo; // is underscript an embellished operator? if ( isEmbellishedOperator( element.childNodes().item( 1 ), &mo, oasisFormat ) ) { if ( mo.attribute( "accent" ) == "true" ) accentunder = true; else accentunder = false; } else accentunder = false; } value = element.attribute( "accent" ); if ( value == "true" ) accent = true; else if ( value == "false" ) accent = false; else { // use default TQDomElement mo; // is overscript an embellished operator? if ( isEmbellishedOperator( element.childNodes().item( 2 ), &mo,oasisFormat ) ) { kdDebug( DEBUGID ) << "embellished operator" << endl; if ( mo.attribute( "accent" ) == "true" ) accent = true; else accent = false; } else accent = false; } kdDebug( DEBUGID ) << "munderover:\n accentunder = " << accentunder << "\n accent = " << accent << endl; TQDomNode n = element.firstChild(); int i = 0; TQDomElement root = doc.createElement( "INDEX" ); while ( !n.isNull() && i < 3 ) { if ( n.isElement() ) { ++i; if ( i == 1 ) { // base TQDomElement content = doc.createElement( "CONTENT" ); TQDomElement sequence = doc.createElement( "SEQUENCE" ); content.appendChild( sequence ); TQDomElement e = n.toElement(); filter->processElement( e, doc, sequence ); root.appendChild( content ); } else if ( i == 2 ) { // underscript MathStyle previousStyle( style ); style.displaystyle = false; if ( !accentunder ) { style.scriptlevel += 1; style.styleChange(); } TQDomElement mo; TQDomElement index; // is the base an embellished operator? if ( isEmbellishedOperator( element.firstChild(), &mo, oasisFormat ) && !previousStyle.displaystyle && mo.attribute( "movablelimits" ) == "true" ) { index = doc.createElement( "LOWERRIGHT" ); } else { index = doc.createElement( "LOWERMIDDLE" ); } TQDomElement sequence = doc.createElement( "SEQUENCE" ); index.appendChild( sequence ); TQDomElement e = n.toElement(); filter->processElement( e, doc, sequence ); root.appendChild( index ); style = previousStyle; } else { // overscript MathStyle previousStyle( style ); style.displaystyle = false; if ( !accent ) { style.scriptlevel += 1; style.styleChange(); } TQDomElement mo; TQDomElement index; if ( isEmbellishedOperator( element.firstChild(), &mo, oasisFormat ) && !previousStyle.displaystyle && mo.attribute( "movablelimits" ) == "true" ) { index = doc.createElement( "UPPERRIGHT" ); } else { index = doc.createElement( "UPPERMIDDLE" ); } TQDomElement sequence = doc.createElement( "SEQUENCE" ); index.appendChild( sequence ); TQDomElement e = n.toElement(); filter->processElement( e, doc, sequence ); root.appendChild( index ); style = previousStyle; } } else { kdDebug( DEBUGID ) << "<" << element.tagName() << "> child: " << n.nodeName() << endl; } n = n.nextSibling(); } docnode.appendChild( root ); } void MathML2KFormulaPrivate::msubsup( TQDomElement element, TQDomNode docnode ) { TQDomNode n = element.firstChild(); int i = 0; TQDomElement root = doc.createElement("INDEX"); MathStyle previousStyle( style ); while ( !n.isNull() && i < 2 ) { if ( n.isElement() ) { ++i; if ( i == 1 ) { // base TQDomElement content = doc.createElement( "CONTENT" ); TQDomElement sequence = doc.createElement( "SEQUENCE" ); content.appendChild( sequence ); TQDomElement e = n.toElement(); filter->processElement( e, doc, sequence ); root.appendChild( content ); } else if ( i == 2 ) { // subscript style.scriptlevel += 1; style.displaystyle = false; style.styleChange(); TQDomElement index; index = doc.createElement( "LOWERRIGHT" ); TQDomElement sequence = doc.createElement( "SEQUENCE" ); index.appendChild( sequence ); TQDomElement e = n.toElement(); filter->processElement( e, doc, sequence ); root.appendChild( index ); } else { // superscript TQDomElement index; index = doc.createElement( "UPPERRIGHT" ); TQDomElement sequence = doc.createElement( "SEQUENCE" ); index.appendChild( sequence ); TQDomElement e = n.toElement(); filter->processElement( e, doc, sequence ); root.appendChild( index ); style = previousStyle; } } else { kdDebug( DEBUGID ) << " child: " << n.nodeName() << endl; } n = n.nextSibling(); } docnode.appendChild( root ); } void MathML2KFormulaPrivate::createTextElements( TQString text, TQDomNode docnode ) { for ( uint i = 0; i < text.length(); ++i ) { TQDomElement textelement = doc.createElement( "TEXT" ); textelement.setAttribute( "CHAR", TQString( text.at( i ) ) ); style.setStyles( textelement ); if ( context.symbolTable().inTable( text.at( i ) ) ) { // The element is a symbol. textelement.setAttribute( "SYMBOL", "3" ); } docnode.appendChild( textelement ); } } void MathML2KFormulaPrivate::createNameSequence( TQString text, TQDomNode docnode ) { TQDomElement namesequence = doc.createElement( "NAMESEQUENCE" ); for ( uint i = 0; i < text.length(); ++i ) { TQDomElement textelement = doc.createElement( "TEXT" ); textelement.setAttribute( "CHAR", TQString( text.at( i ) ) ); style.setStyles( textelement ); if ( context.symbolTable().inTable( text.at( i ) ) ) { // The element is a symbol. textelement.setAttribute( "SYMBOL", "3" ); } namesequence.appendChild( textelement ); } docnode.appendChild( namesequence ); } double MathML2KFormulaPrivate::convertToPoint( TQString value, bool* ok ) { double pt = 0; if ( value.endsWith( "em" ) ) { // See MathML specification, Appendix H pt = context.getDefaultFont().pointSize(); if ( pt == -1 ) { TQFontMetrics fm( context.getDefaultFont() ); pt = fm.width( 'M' ); // PIXELS! } pt = pt * value.remove( value.length() - 2, 2 ).toDouble( ok ); } else if ( value.endsWith( "ex" ) ) { TQFontMetrics fm( context.getDefaultFont() ); pt = fm.height(); // PIXELS, and totally wrong! } else if ( value.endsWith( "px" ) ) { pt = value.remove( value.length() - 2, 2 ).toDouble( ok ); // PIXELS! } else if ( value.endsWith( "in" ) ) { pt = value.remove( value.length() - 2, 2 ).toDouble( ok ); pt *= 72; } else if ( value.endsWith( "cm" ) ) { pt = value.remove( value.length() - 2, 2 ).toDouble( ok ); pt *= 1/2.54 * 72; } else if ( value.endsWith( "mm" ) ) { pt = value.remove( value.length() - 2, 2 ).toDouble( ok ); pt *= 1/25.4 * 72; } else if ( value.endsWith( "pt" ) ) { pt = value.remove( value.length() - 2, 2 ).toDouble( ok ); } else if ( value.endsWith( "pc" ) ) { pt = value.remove( value.length() - 2, 2 ).toDouble( ok ); pt /= 12; } else { pt = value.toDouble( ok ); } return pt; } bool MathML2KFormulaPrivate::isEmbellishedOperator( TQDomNode node, TQDomElement* mo, bool oasisFormat ) { // See MathML 2.0 specification: 3.2.5.7 if ( !node.isElement() ) return false; TQDomElement element = node.toElement(); TQString tag = element.tagName(); if ( tag == "mo" ) { *mo = element; return true; } if ( tag == "msub" || tag == "msup" || tag == "msubsup" || tag == "munder" || tag == "mover" || tag == "munderover" || tag == "mmultiscripts" || tag == "mfrac" || tag == "semantics" ) { return isEmbellishedOperator( element.firstChild(), mo,oasisFormat ); } if ( tag == "maction" ) { return false; // not supported } if ( tag == "mrow" || tag == "mstyle" || tag == "mphantom" || tag == "mpadded" ) { TQDomNode n = element.firstChild(); int i = 0; while ( !n.isNull() ) { if ( isEmbellishedOperator( n, mo,oasisFormat ) ) { if ( ++i > 1 ) // one (only one) embellished operator return false; } else if ( !isSpaceLike( n, oasisFormat ) ) { // zero or more space-like elements return false; } n = n.nextSibling(); } return ( i == 1 ); } return false; } bool MathML2KFormulaPrivate::isSpaceLike( TQDomNode node, bool oasisFormat ) { // See MathML 2.0 specification: 3.2.7.3 if ( !node.isElement() ) return false; TQDomElement element = node.toElement(); TQString tag = element.tagName(); if ( tag == "mtext" || tag == "mspace" || tag == "maligngroup" || tag == "malignmark" ) { return true; } if ( tag == "mstyle" || tag == "mphantom" || tag == "mpadded" || tag == "mrow" ) { TQDomNode n = element.firstChild(); while ( !n.isNull() ) { if ( isSpaceLike( n,oasisFormat ) ) n = n.nextSibling(); else return false; } return true; } if ( tag == "maction" ) { return false; // not supported } return false; } MathML2KFormula::MathML2KFormula( const TQDomDocument& mmldoc, const ContextStyle &contextStyle, bool _oasisFormat ) : m_error( false ), oasisFormat( _oasisFormat ), context( contextStyle ) { orig_element = mmldoc.documentElement(); done = false; } MathML2KFormula::MathML2KFormula( const TQDomElement& mmlelm, const ContextStyle &contextStyle, bool _oasisFormat ) : m_error( false ), orig_element( mmlelm ), oasisFormat( _oasisFormat ), context( contextStyle ) { done = false; } TQDomDocument MathML2KFormula::getKFormulaDom() { return formuladoc; } void MathML2KFormula::startConversion() { //TODO:let it be async //kdDebug() << origdoc.toString() << endl; done = false; formuladoc = TQDomDocument( "KFORMULA" ); impl = new MathML2KFormulaPrivate( this, context, formuladoc ); if ( orig_element.tagName() == "math" ) { impl->math( orig_element ); m_error = false; } else { kdError() << "Not a MathML document!" << endl; KMessageBox::error( 0, i18n( "The document does not seem to be MathML." ), i18n( "MathML Import Error" ) ); m_error = true; } done = true; } bool MathML2KFormula::processElement( TQDomNode node, TQDomDocument& doc, TQDomNode docnode ) { //TQDomElement *element; Type type = UNKNOWN; if ( node.isElement() ) { TQDomElement element = node.toElement(); TQString tag = element.tagName(); if ( tag == "mi" ) { type = TOKEN; impl->mi( element, docnode ); } else if ( tag == "mo" ) { type = TOKEN; impl->mo( element, docnode ); } else if ( tag == "mn" ) { type = TOKEN; impl->mn( element, docnode ); } else if ( tag == "mtext" ) { type = TOKEN; impl->mtext( element, docnode ); } else if ( tag == "ms" ) { type = TOKEN; impl->ms( element, docnode ); } else if ( tag == "mspace" ) { type = TOKEN; impl->mspace( element, docnode ); } else if ( tag == "mrow" ) { type = LAYOUT; impl->mrow( element, docnode ); } else if ( tag == "mfrac" ) { type = LAYOUT; impl->mfrac( element, docnode ); } else if ( tag == "mroot" ) { type = LAYOUT; impl->mroot( element, docnode ); } else if ( tag == "msqrt" ) { type = LAYOUT; impl->msqrt( element, docnode ); } else if ( tag == "mstyle" ) { type = LAYOUT; impl->mstyle( element, docnode ); } else if ( tag == "mfenced" ) { type = LAYOUT; impl->mfenced( element, docnode ); } else if ( tag == "mtable" ) { type = TABLE; impl->mtable( element, docnode ); } else if ( tag == "msub" || tag == "msup" ) { type = SCRIPT; impl->msub_msup( element, docnode ); } else if ( tag == "munder" ) { type = SCRIPT; impl->munder( element, docnode,oasisFormat ); } else if ( tag == "mover" ) { type = SCRIPT; impl->mover( element, docnode,oasisFormat ); } else if ( tag == "munderover" ) { type = SCRIPT; impl->munderover( element, docnode, oasisFormat ); } else if ( tag == "msubsup" ) { type = SCRIPT; impl->msubsup( element, docnode ); } // content markup (not yet complete) else if ( tag == "apply" ) { type = CONTENT; TQDomNode n = element.firstChild(); TQDomElement op = n.toElement(); uint count = element.childNodes().count(); //adding explicit brackets to replace "apply"s implicit ones TQDomElement brackets = doc.createElement("BRACKET"); brackets.setAttribute("RIGHT", "41"); brackets.setAttribute("LEFT", "40"); TQDomElement content = doc.createElement("CONTENT"); brackets.appendChild(content); TQDomElement base = doc.createElement("SEQUENCE"); content.appendChild(base); docnode.appendChild(brackets); //Arithmetic, Algebra and Logic operators status // quotient X // factorial O // divide O // max, min X // minus O // plus O // power O // rem X // times O // root X // gcd X // and O // or O // xor O // not O // implies O // forall X // exists X // abs O // conjugate X // arg X // real X // imaginary X // lcm X // floor X // ceiling X // n-ary if ( op.tagName() == "plus" || op.tagName() == "times" || op.tagName() == "and" || op.tagName() == "or" || op.tagName() == "xor" ) { n = n.nextSibling(); bool first = true; while ( !n.isNull() ) { if ( n.isElement() ) { if ( !first ) { TQDomElement text = doc.createElement( "TEXT" ); TQString value; if ( op.tagName() == "plus" ) value = "+"; else if ( op.tagName() == "times" ) value = "*"; else if ( op.tagName() == "and" ) value = "&"; else if ( op.tagName() == "or" ) value = "|"; else if ( op.tagName() == "xor" ) value = "^"; // ??? text.setAttribute( "CHAR", value ); //switch to createTextElements? base.appendChild( text ); } first = false; TQDomElement e = n.toElement(); processElement( e, doc, base ); } n = n.nextSibling(); } } else if ( op.tagName() == "factorial" ) { TQDomElement e = n.nextSibling().toElement(); processElement( e, doc, docnode ); impl->createTextElements( "!", base ); } else if ( op.tagName() == "minus" ) { n = n.nextSibling(); if ( count == 2 ) { // unary impl->createTextElements( "-", base ); TQDomElement e = n.toElement(); processElement( e, doc, base ); } else if ( count == 3 ) { // binary TQDomElement e = n.toElement(); processElement( e, doc, base ); impl->createTextElements( "-", base ); n = n.nextSibling(); e = n.toElement(); processElement( e, doc, base ); } } else if ( op.tagName() == "divide" && count == 3 ) { n = n.nextSibling(); TQDomElement e = n.toElement(); processElement( e, doc, base ); impl->createTextElements("/", base); n = n.nextSibling(); e = n.toElement(); processElement( e, doc, base ); } else if ( op.tagName() == "power" && count == 3 ) { //code duplication of msub_sup(), but I can't find a way to cleanly call it n = n.nextSibling(); TQDomElement e = n.toElement(); TQDomElement index = doc.createElement("INDEX"); base.appendChild(index); TQDomElement content = doc.createElement("CONTENT"); index.appendChild(content); TQDomElement sequence = doc.createElement("SEQUENCE"); content.appendChild(sequence); processElement(e, doc, sequence); TQDomElement upper = doc.createElement("UPPERRIGHT"); index.appendChild(upper); sequence = doc.createElement("SEQUENCE"); upper.appendChild(sequence); n = n.nextSibling(); e = n.toElement(); processElement(e, doc, sequence); } else if ( op.tagName() == "abs" && count == 2) { n = n.nextSibling(); TQDomElement e = n.toElement(); TQDomElement bracket = doc.createElement("BRACKET"); bracket.setAttribute("RIGHT", "257"); bracket.setAttribute("LEFT", "256"); base.appendChild(bracket); TQDomElement content = doc.createElement("CONTENT"); bracket.appendChild(content); TQDomElement sequence = doc.createElement("SEQUENCE"); content.appendChild(sequence); processElement(e, doc, sequence); } else if ( op.tagName() == "not" && count == 2) { n = n.nextSibling(); TQDomElement e = n.toElement(); impl->createTextElements(TQString(TQChar(0xAC)), base); processElement(e, doc, base); } else if ( op.tagName() == "implies" && count == 3 ) { n = n.nextSibling(); TQDomElement e = n.toElement(); processElement( e, doc, base ); impl->createTextElements(TQString(TQChar(0x21D2)), base); n = n.nextSibling(); e = n.toElement(); processElement( e, doc, base ); } // many, many more... } else if ( tag == "cn" ) { type = CONTENT; TQString type = element.attribute( "type", "real" ); if ( type == "real" || type == "constant" ) { impl->createTextElements( element.text().stripWhiteSpace(), docnode ); } else if ( type == "integer" ) { TQString base = element.attribute( "base" ); if ( !base ) { impl->createTextElements( element.text().stripWhiteSpace(), docnode ); } else { TQDomElement index = doc.createElement( "INDEX" ); TQDomElement content = doc.createElement( "CONTENT" ); TQDomElement sequence = doc.createElement( "SEQUENCE" ); impl->createTextElements( element.text().stripWhiteSpace(), sequence ); content.appendChild( sequence ); index.appendChild( content ); TQDomElement lowerright = doc.createElement( "LOWERRIGHT" ); sequence = doc.createElement( "SEQUENCE" ); impl->createTextElements( base, sequence ); lowerright.appendChild( sequence ); index.appendChild( lowerright ); docnode.appendChild( index ); } } else if ( type == "rational" ) { TQDomNode n = element.firstChild(); impl->createTextElements( n.toText().data().stripWhiteSpace(), docnode ); n = n.nextSibling(); // impl->createTextElements( "/", docnode ); n = n.nextSibling(); impl->createTextElements( n.toText().data().stripWhiteSpace(), docnode ); } else if ( type == "complex-cartesian" ) { TQDomNode n = element.firstChild(); impl->createTextElements( n.toText().data().stripWhiteSpace(), docnode ); n = n.nextSibling(); // impl->createTextElements( "+", docnode ); n = n.nextSibling(); impl->createTextElements( n.toText().data().stripWhiteSpace(), docnode ); impl->createTextElements( "i", docnode ); } else if ( type == "complex-polar" ) { TQDomNode n = element.firstChild(); impl->createTextElements( n.toText().data().stripWhiteSpace(), docnode ); n = n.nextSibling(); // TQDomElement index = doc.createElement( "INDEX" ); TQDomElement content = doc.createElement( "CONTENT" ); TQDomElement sequence = doc.createElement( "SEQUENCE" ); TQDomElement textelement = doc.createElement( "TEXT" ); textelement.setAttribute( "CHAR", "e" ); sequence.appendChild( textelement ); content.appendChild( sequence ); index.appendChild( content ); TQDomElement upperright = doc.createElement( "UPPERRIGHT" ); sequence = doc.createElement( "SEQUENCE" ); textelement = doc.createElement( "TEXT" ); textelement.setAttribute( "CHAR", "i" ); sequence.appendChild( textelement ); n = n.nextSibling(); impl->createTextElements( n.toText().data().stripWhiteSpace(), sequence ); upperright.appendChild( sequence ); index.appendChild( upperright ); docnode.appendChild( index ); } } else if ( tag == "ci" ) { type = CONTENT; TQDomNode n = element.firstChild(); if ( n.isText() ) { impl->createTextElements( n.toText().data().stripWhiteSpace(), docnode ); } else if ( n.isElement() ) { TQDomElement e = n.toElement(); processElement( e, doc, docnode ); } else if ( n.isEntityReference() ) { kdDebug( DEBUGID ) << "isEntityReference: " << n.toEntityReference().nodeName().latin1() << endl; } else kdDebug( DEBUGID ) << "ci: " << n.nodeName().latin1() << endl; } else if ( tag == "list" ) { type = CONTENT; TQDomNode n = element.firstChild(); TQDomElement bracket = doc.createElement( "BRACKET" ); bracket.setAttribute( "LEFT", 91 ); // [ bracket.setAttribute( "RIGHT", 93 ); // ] TQDomElement content = doc.createElement( "CONTENT" ); TQDomElement sequence = doc.createElement( "SEQUENCE" ); bool first = true; while ( !n.isNull() ) { if ( n.isElement() ) { if ( !first ) { TQDomElement textelement = doc.createElement( "TEXT" ); textelement.setAttribute( "CHAR", "," ); sequence.appendChild( textelement ); } first = false; TQDomElement e = n.toElement(); processElement( e, doc, sequence ); } n = n.nextSibling(); } content.appendChild( sequence ); bracket.appendChild( content ); docnode.appendChild( bracket ); } } if ( type == UNKNOWN && node.nodeType() != TQDomNode::AttributeNode ) { kdDebug() << "Not an element: " << node.nodeName() << endl; TQDomNode n = node.firstChild(); while ( !n.isNull() ) { processElement( n, doc, docnode ); n = n.nextSibling(); } } return true; } KFORMULA_NAMESPACE_END using namespace KFormula; #include "kformulamathmlread.moc"