filters

amiproparser.cpp

00001 /* This file is part of the KDE project
00002    Copyright (C) 2002 Ariya Hidayat <ariyahidayat@yahoo.de>
00003 
00004    This library is free software; you can redistribute it and/or
00005    modify it under the terms of the GNU Library General Public
00006    License as published by the Free Software Foundation; either
00007    version 2 of the License, or (at your option) any later version.
00008 
00009    This library is distributed in the hope that it will be useful,
00010    but WITHOUT ANY WARRANTY; without even the implied warranty of
00011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012    Library General Public License for more details.
00013 
00014    You should have received a copy of the GNU Library General Public License
00015    along with this library; see the file COPYING.LIB.  If not, write to
00016    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00017  * Boston, MA 02110-1301, USA.
00018 */
00019 
00020 #include "amiproparser.h"
00021 
00022 #include <qfile.h>
00023 #include <qstring.h>
00024 #include <qstringlist.h>
00025 #include <qtextstream.h>
00026 
00027 const float AmiPro::LS_Single = -1;
00028 const float AmiPro::LS_OneAndHalf = -1.5;
00029 const float AmiPro::LS_Double = -2;
00030 
00031 // helper function to "unescape" AmiPro string
00032 static QString AmiProUnescape( const QString& str )
00033 {
00034   QString result;
00035 
00036   for( unsigned i=0; i< str.length(); i++ )
00037   {
00038     QChar c = str[i];
00039     result.append( c );
00040 
00041     // check for "@@", decoded as '@'
00042     if( c == '@' )
00043       if( str[i+1] == '@' )
00044          i++ ; // eat !
00045 
00046     // a few possible escape sequence
00047     if( c == '<'  )
00048     {
00049 
00050       // check for "<<", decoded as '<'
00051       if( str[i+1] == '<' )
00052       {
00053         result.truncate( result.length() - 1 ); // remove the '<'
00054         result.append( '<' );
00055         i++;
00056       }
00057 
00058       // check for "<;>", decoded as '>'
00059       if( str[i+1] == ';' )
00060       {
00061         result.truncate( result.length() - 1 ); // remove the '<'
00062         result.append( '>' );
00063         i+=2;
00064       }
00065 
00066       // check for "<[>", decoded as '['
00067       if( str[i+1] == '[' )
00068       {
00069         result.truncate( result.length() - 1 ); // remove the '<'
00070         result.append( '[' );
00071         i+=2;
00072       }
00073 
00074       // some special characters
00075       if( str[i+1] == '/' )
00076       {
00077         if( str[i+2] == 'R' )
00078         {
00079           result.truncate( result.length() - 1 ); // remove the '<'
00080           result.append( '\'' ); // </R> decoded as '
00081           i += 3;
00082         }
00083         else
00084         {
00085           result.truncate( result.length() - 1 ); // remove the '<'
00086           result.append( QChar(str[i+2].unicode() + 0x40 ) );
00087           i += 3;
00088         }
00089       }
00090 
00091       // yet another special characters
00092       if( str[i+1] == '\\' )
00093       {
00094         result.truncate( result.length() - 1 ); // remove the '<'
00095         result.append( QChar(str[i+2].unicode() | 0x80 ) );
00096         i += 3;
00097       }
00098 
00099     }
00100 
00101   }
00102 
00103   return result;
00104 }
00105 
00106 AmiProParser::AmiProParser()
00107 {
00108   m_result = OK;
00109   m_listener = NULL;
00110 }
00111 
00112 AmiProParser::~AmiProParser()
00113 {
00114 }
00115 
00116 bool AmiProParser::setResult( int result )
00117 {
00118   m_result = result;
00119   return m_result == OK;
00120 }
00121 
00122 void AmiProParser::setListener( AmiProListener *listener )
00123 {
00124   m_listener = listener;
00125 }
00126 
00127 bool AmiProParser::process( const QString& filename )
00128 {
00129   QString line;
00130 
00131   // open input file
00132   QFile in( filename );
00133   if( !in.open( IO_ReadOnly))
00134     return setResult( FileError );
00135 
00136   QTextStream stream;
00137   stream.setDevice( &in );
00138 
00139   // the first should be "[ver]"
00140   line = stream.readLine();
00141   if( line != "[ver]" ) 
00142     return setResult( InvalidFormat ); 
00143  
00144   // get format version, typically 4 
00145   line = stream.readLine();
00146   int format_version = line.toInt();
00147 
00148   // FIXME is this necessary ?
00149   // accept only format version 4
00150   if( format_version != 4 )
00151     return setResult( InvalidFormat );
00152 
00153   // initialize
00154   m_currentFormat = AmiProFormat();
00155   m_formatList.clear();
00156   m_styleList.clear();
00157   m_currentSection = "";
00158   QStringList lines;
00159 
00160   // parse line-by-line
00161   for( ;; )
00162   {
00163 
00164     line = stream.readLine();
00165     if( line.isNull() ) break;
00166 
00167     QString old_section = m_currentSection;
00168     bool enter_new_section = false;
00169 
00170     // new main section ?
00171     if( !line.isEmpty() ) if( line[0] == '[' )
00172     {
00173       enter_new_section = true;
00174       m_currentSection = "";
00175       for( unsigned i=1; i<line.length(); i++ )
00176         if( line[i] == ']' ) break;
00177         else m_currentSection += line[i];
00178     }
00179 
00180     // leave [tag]
00181     if( enter_new_section && ( old_section == "tag" ) )
00182     {
00183       parseStyle( lines );
00184       lines.clear();
00185     }
00186 
00187     // leave [edoc]
00188     if( enter_new_section && ( old_section == "edoc" ) )
00189     {
00190       parseParagraph( lines.join(" ") );
00191       lines.clear();
00192     }
00193 
00194     // still in [tag]
00195     if( !enter_new_section && ( old_section == "tag" ) )
00196     {
00197       lines.append( line );
00198     } 
00199 
00200     // still in [edoc]
00201     if( !enter_new_section && ( old_section == "edoc" ) )
00202     {
00203       if( line.isEmpty() ) 
00204       {
00205          parseParagraph( lines );
00206          lines.clear(); 
00207       }
00208         lines.append( line );
00209     }
00210 
00211     // enter [tag]
00212     if( enter_new_section && ( m_currentSection == "tag" ) )
00213     {
00214       lines.clear();
00215     }
00216 
00217     // enter [edoc]
00218     if( enter_new_section && ( m_currentSection == "edoc" ) )
00219     {
00220       processOpenDocument();
00221       lines.clear();
00222     }
00223 
00224   }
00225 
00226   // in case left-over
00227   if( lines.count() > 0 ) parseParagraph( lines.join( " " ) );
00228 
00229   processCloseDocument();
00230 
00231   return true;
00232 }
00233 
00234 bool AmiProParser::processOpenDocument()
00235 {
00236   if( m_listener ) return m_listener->doOpenDocument();
00237   return true;
00238 }
00239 
00240 bool AmiProParser::processCloseDocument()
00241 {
00242   if( m_listener ) 
00243     return m_listener->doCloseDocument();
00244   return true;
00245 }
00246 
00247 bool AmiProParser::parseParagraph( const QStringList& lines )
00248 {
00249   m_text = "";
00250   m_formatList.clear();
00251   m_layout = AmiProLayout();
00252 
00253   // join the lines, up until first char in a line is '>'
00254   QString partext = "";
00255   for( unsigned i=0; i<lines.count(); i++ )
00256     if( lines[i][0] == '>' ) break;
00257       else partext.append( lines[i] + "\n" );
00258 
00259   QChar ch = partext[partext.length()-1];
00260   while( ( ch == '\n' ) || ( ch == '\r' ) )
00261   {
00262     partext.remove( partext.length()-1, 1 );
00263     ch = partext[partext.length()-1];
00264   }
00265 
00266   // "unescape", process special chars and such
00267   QString text = AmiProUnescape( partext );
00268 
00269   // apply default style first
00270   m_layout.applyStyle( findStyle( "Body Text" ) );
00271 
00272   for( unsigned i=0; i<text.length(); i++ )
00273   {
00274     QChar ch = text[i];
00275 
00276     // handle a tag
00277     if( ch == '<' )
00278     {
00279         QString tag = "";
00280         for( i++; (i < text.length()) && 
00281            (text[i] != '>'); i++) tag.append( text[i] );
00282         handleTag( tag );
00283     }
00284 
00285     else
00286 
00287     // handle style change
00288     if( ch == '@' )
00289     {
00290         QString styleName;
00291         for( i++; (i < partext.length()) && (partext[i] != '@'); i++)
00292           styleName += partext[i];
00293         m_layout.name = styleName;
00294         AmiProStyle style = findStyle( styleName );
00295         m_currentFormat.applyStyle( style );
00296         m_formatList.append( m_currentFormat ); 
00297         m_layout.applyStyle( style );
00298     }
00299 
00300      else 
00301        // normal character
00302        m_text.append( ch ); 
00303   }
00304 
00305   // calc length of each format tag
00306   for( unsigned j=0; j<m_formatList.count(); j++ )
00307   {
00308     int nextpos;
00309     AmiProFormat& format = m_formatList[j];
00310     if( j < m_formatList.count()-1 )
00311     {
00312       AmiProFormat& nextformat = m_formatList[j+1];
00313       nextpos = nextformat.pos;
00314     }
00315     else  nextpos = m_text.length();
00316     format.len = nextpos - format.pos;
00317   }
00318 
00319   if( m_listener ) 
00320     return m_listener->doParagraph( m_text, m_formatList, m_layout );
00321 
00322   return true;
00323 }
00324 
00325 bool AmiProParser::parseStyle( const QStringList& lines )
00326 {
00327   AmiProStyle style;
00328 
00329   style.name = AmiProUnescape( lines[0].stripWhiteSpace() );
00330   if( style.name.isEmpty() ) return true;
00331 
00332   // font
00333   if( lines[2].stripWhiteSpace() != "[fnt]" ) return true;
00334   style.fontFamily = lines[3].stripWhiteSpace();
00335   style.fontSize = lines[4].stripWhiteSpace().toFloat() / 20.0;
00336 
00337   unsigned color = lines[5].stripWhiteSpace().toUInt();
00338   style.fontColor.setRgb( color&255, (color>>8)&255, (color>>16)&255);
00339 
00340   unsigned flag = lines[6].stripWhiteSpace().toUInt();
00341   style.bold = flag & 1;
00342   style.italic = flag & 2;
00343   style.underline = flag & 4;
00344   style.word_underline = flag & 8;
00345   style.double_underline = flag & 64;
00346 
00347   // alignment
00348   if( lines[7].stripWhiteSpace() != "[algn]" ) return true;
00349   unsigned align_flag = lines[8].stripWhiteSpace().toUInt();
00350   if( align_flag & 1 ) style.align = Qt::AlignLeft;
00351   if( align_flag & 2 ) style.align = Qt::AlignRight;
00352   if( align_flag & 4 ) style.align = Qt::AlignCenter;
00353   if( align_flag & 8 ) style.align = Qt::AlignJustify;
00354 
00355   // linespace
00356   if( lines[13].stripWhiteSpace() != "[spc]" ) return true;
00357   unsigned ls_flag = lines[14].stripWhiteSpace().toUInt();
00358   if( ls_flag & 1 ) style.linespace = AmiPro::LS_Single;
00359   if( ls_flag & 2 ) style.linespace = AmiPro::LS_OneAndHalf;
00360   if( ls_flag & 4 ) style.linespace = AmiPro::LS_Double;
00361   if( ls_flag & 8 ) 
00362     style.linespace = lines[15].stripWhiteSpace().toFloat() / 20.0;
00363   style.spaceBefore = lines[17].stripWhiteSpace().toFloat() / 20.0;
00364   style.spaceAfter = lines[18].stripWhiteSpace().toFloat() / 20.0;
00365 
00366   m_styleList.append( style );
00367 
00368   // "Style #0", "Style #1" and such are special styles
00369   // do not import these styles
00370   if( style.name.left( 7 ) != "Style #" )
00371   if( m_listener )
00372     return m_listener->doDefineStyle( style );
00373   return true;
00374 }
00375 
00376 AmiProStyle AmiProParser::findStyle( const QString& name )
00377 {
00378   AmiProStyleList::iterator it;
00379   for( it=m_styleList.begin(); it!=m_styleList.end(); ++it )
00380   {
00381     AmiProStyle& style = *it;
00382     if( style.name == name )
00383       return style;
00384   }
00385   return AmiProStyle();
00386 }
00387 
00388 bool AmiProParser::handleTag( const QString& tag )
00389 {
00390   // > (actually encoded as <;>)
00391   if( tag == ";" )
00392     m_text.append( ">" );
00393 
00394   // [ (actually encoded as <[>)
00395   if( tag == "[" )
00396     m_text.append( "[" );
00397 
00398   // bold on
00399   if( tag == "+!" )
00400   {
00401     m_currentFormat.bold = true;
00402     m_currentFormat.pos = m_text.length();
00403     m_formatList.append( m_currentFormat );
00404   }
00405 
00406   // bold off
00407   if( tag == "-!" )
00408   {
00409     m_currentFormat.bold = false;
00410     m_currentFormat.pos = m_text.length();
00411     m_formatList.append( m_currentFormat );
00412   }
00413 
00414   // italic on
00415   if( tag == "+\"" )
00416   {
00417     m_currentFormat.italic = true;
00418     m_currentFormat.pos = m_text.length();
00419     m_formatList.append( m_currentFormat );
00420   }
00421 
00422   // italic off
00423   if( tag == "-\"" )
00424   {
00425     m_currentFormat.italic = false;
00426     m_currentFormat.pos = m_text.length();
00427     m_formatList.append( m_currentFormat );
00428   }
00429 
00430   // underline on
00431   if( tag == "+#" )
00432   {
00433     m_currentFormat.underline = true;
00434     m_currentFormat.pos = m_text.length();
00435     m_formatList.append( m_currentFormat );
00436   }
00437 
00438   // underline off
00439   if( tag == "-#" )
00440   {
00441     m_currentFormat.underline = false;
00442     m_currentFormat.pos = m_text.length();
00443     m_formatList.append( m_currentFormat );
00444   }
00445 
00446   // double underline on
00447   if( tag == "+)" )
00448   {
00449     m_currentFormat.double_underline = true; 
00450     m_currentFormat.pos = m_text.length();
00451     m_formatList.append( m_currentFormat );
00452   }
00453 
00454   // double underline off
00455   if( tag == "-)" )
00456   {
00457     m_currentFormat.double_underline = false;
00458     m_currentFormat.pos = m_text.length();
00459     m_formatList.append( m_currentFormat );
00460   }
00461 
00462  // word underline on
00463   if( tag == "+$" )
00464   {
00465     m_currentFormat.word_underline = true; 
00466     m_currentFormat.pos = m_text.length();
00467     m_formatList.append( m_currentFormat );
00468   }
00469 
00470   // word underline off
00471   if( tag == "-$" )
00472   {
00473     m_currentFormat.word_underline = false;
00474     m_currentFormat.pos = m_text.length();
00475     m_formatList.append( m_currentFormat );
00476   }
00477 
00478   // superscript on
00479   if( tag == "+&" )
00480   {
00481     m_currentFormat.superscript = true; 
00482     m_currentFormat.pos = m_text.length();
00483     m_formatList.append( m_currentFormat );
00484   }
00485 
00486   // superscript off
00487   if( tag == "-&" )
00488   {
00489     m_currentFormat.superscript = false;
00490     m_currentFormat.pos = m_text.length();
00491     m_formatList.append( m_currentFormat );
00492   }
00493 
00494   // subscript on
00495   if( tag == "+'" )
00496   {
00497     m_currentFormat.subscript = true;
00498     m_currentFormat.pos = m_text.length();
00499     m_formatList.append( m_currentFormat );
00500   }
00501 
00502   // subscript off
00503   if( tag == "-'" )
00504   {
00505     m_currentFormat.subscript = false;
00506     m_currentFormat.pos = m_text.length();
00507     m_formatList.append( m_currentFormat );
00508   }
00509 
00510   // strikethrough on
00511   if( tag == "+%" )
00512   {
00513     m_currentFormat.strikethrough = true;
00514     m_currentFormat.pos = m_text.length();
00515     m_formatList.append( m_currentFormat );
00516   }
00517 
00518   // strikethrough off
00519   if( tag == "-%" )
00520   {
00521     m_currentFormat.strikethrough = false;
00522     m_currentFormat.pos = m_text.length();
00523     m_formatList.append( m_currentFormat );
00524   }
00525 
00526   // paragraph left-align
00527   if( tag == "+@" )
00528     m_layout.align = Qt::AlignLeft;
00529 
00530   // paragraph right-align
00531   if( tag == "+A" )
00532     m_layout.align = Qt::AlignRight;
00533 
00534   // paragraph center
00535   if( tag == "+B" )
00536     m_layout.align = Qt::AlignCenter;
00537 
00538   // paragraph justify
00539   if( tag == "+C" )
00540     m_layout.align = Qt::AlignJustify;
00541 
00542   // linespace
00543   if( tag.left( 3 ) == ":S+" )
00544   {
00545     float ls = tag.right( tag.length() - 3 ).toFloat();
00546     m_layout.linespace = (ls == -1) ? AmiPro::LS_Single :
00547      (ls == -2) ? AmiPro::LS_OneAndHalf :
00548      (ls == -3) ? AmiPro::LS_Double : ls / 20.0;
00549   }
00550 
00551   // font
00552   if( tag.left( 2 ) == ":f" )
00553   {
00554     QString fontdesc = tag.right( tag.length()-2 );
00555     QStringList desc = QStringList::split( ",", fontdesc );
00556     if( desc.count() > 0 ) m_currentFormat.fontSize = desc[0].toFloat() / 20.0;
00557     if( desc.count() > 1 )
00558     {
00559       QString fontFamily = desc[1];
00560       if( fontFamily[0].isDigit() ) fontFamily.remove( 0, 1 );
00561       m_currentFormat.fontFamily = fontFamily;
00562     }
00563     if( desc.count() > 4 )
00564     {
00565       unsigned red = desc[2].toUInt();
00566       unsigned green = desc[3].toUInt();
00567       unsigned blue = desc[4].toUInt();
00568       m_currentFormat.fontColor.setRgb( red, green, blue );
00569     }
00570     m_formatList.append( m_currentFormat );
00571   }
00572 
00573   return true;
00574 }
00575 
00576 // text formatting
00577 AmiProFormat::AmiProFormat()
00578 {
00579   pos = len = 0;
00580   bold = italic = underline = 
00581   word_underline = double_underline = 
00582   subscript = superscript = strikethrough = FALSE;
00583   fontFamily = "";
00584   fontSize = 12;
00585   fontColor = Qt::black;
00586 }
00587 
00588 void AmiProFormat::assign( const AmiProFormat& f )
00589 {
00590   pos = f.pos;
00591   len = f.len;
00592   bold = f.bold;
00593   italic = f.italic;
00594   underline = f.underline;
00595   word_underline = f.word_underline;
00596   double_underline = f.double_underline;
00597   subscript = f.subscript;
00598   superscript = f.superscript;
00599   strikethrough = f.strikethrough;
00600   fontFamily = f.fontFamily;
00601   fontSize = f.fontSize;
00602   fontColor = f.fontColor;
00603 }
00604 
00605 AmiProFormat::AmiProFormat( const AmiProFormat& f )
00606 {
00607   assign( f );
00608 }
00609 
00610 AmiProFormat& AmiProFormat::operator=(  const AmiProFormat& f )
00611 {
00612   assign( f );
00613   return *this;
00614 }
00615 
00616 void AmiProFormat::applyStyle( const AmiProStyle& style )
00617 {
00618   fontFamily = style.fontFamily;
00619   fontSize = style.fontSize;
00620   fontColor = style.fontColor;
00621   bold = style.bold;
00622   italic = style.italic;
00623   underline = style.underline;
00624   word_underline = style.word_underline;
00625   double_underline = style.double_underline;
00626   subscript = style.subscript;
00627   superscript = style.superscript;
00628   strikethrough = style.strikethrough;
00629 }
00630 
00631 // paragraph layout
00632 AmiProLayout::AmiProLayout()
00633 {
00634   name = "";
00635   fontFamily = "";
00636   fontSize = 12;
00637   fontColor = Qt::black;
00638   bold = italic = underline = 
00639   word_underline = double_underline = 
00640   subscript = superscript = strikethrough = FALSE;
00641   align = Qt::AlignLeft;
00642   linespace = AmiPro::LS_Single;
00643   spaceBefore = spaceAfter = 0;
00644 }
00645 
00646 void AmiProLayout::assign( const AmiProLayout &l )
00647 {
00648   name = l.name;
00649   fontFamily = l.fontFamily;
00650   fontSize = l.fontSize;
00651   fontColor = l.fontColor;
00652   bold = l.bold;
00653   italic = l.italic;
00654   underline = l.underline;
00655   word_underline = l.word_underline;
00656   double_underline = l.double_underline;
00657   subscript = l.subscript;
00658   superscript = l.superscript;
00659   strikethrough = l.strikethrough;
00660   align = l.align;
00661   linespace = l.linespace;
00662   spaceBefore = l.spaceBefore;
00663   spaceAfter = l.spaceAfter;
00664 }
00665 
00666 AmiProLayout::AmiProLayout( const AmiProLayout& l )
00667 {
00668   assign( l );
00669 }
00670 
00671 AmiProLayout& AmiProLayout::operator=( const AmiProLayout& l )
00672 {
00673   assign( l );
00674   return *this;
00675 }
00676 
00677 void AmiProLayout::applyStyle( const AmiProStyle& style )
00678 {
00679   fontFamily = style.fontFamily;
00680   fontSize = style.fontSize;
00681   fontColor = style.fontColor;
00682   bold = style.bold;
00683   italic = style.italic;
00684   underline = style.underline;
00685   word_underline = style.word_underline;
00686   double_underline = style.double_underline;
00687   subscript = style.subscript;
00688   superscript = style.superscript;
00689   strikethrough = style.strikethrough;
00690   align = style.align;
00691   linespace = style.linespace;
00692   spaceBefore = style.spaceBefore;
00693   spaceAfter = style.spaceAfter;
00694 }
00695 
00696 // style definition
00697 AmiProStyle::AmiProStyle()
00698 {
00699   name = "Unnamed";
00700   fontFamily = "";
00701   fontSize = 12;
00702   fontColor = Qt::black;
00703   bold = italic = underline = 
00704   word_underline = double_underline = 
00705   subscript = superscript = strikethrough = FALSE;
00706   linespace = AmiPro::LS_Single;
00707   spaceBefore = spaceAfter = 0;
00708 }
00709 
00710 void AmiProStyle::assign( const AmiProStyle& s )
00711 {
00712   name = s.name;
00713   fontFamily = s.fontFamily;
00714   fontSize = s.fontSize;
00715   fontColor = s.fontColor;
00716   bold = s.bold;
00717   italic = s.italic;
00718   underline = s.underline;
00719   word_underline = s.word_underline;
00720   double_underline = s.double_underline;
00721   subscript = s.subscript;
00722   superscript = s.superscript;
00723   strikethrough = s.strikethrough;
00724   align = s.align;
00725   linespace = s.linespace;
00726   spaceBefore = s.spaceBefore;
00727   spaceAfter = s.spaceAfter;
00728 }
00729 
00730 AmiProStyle::AmiProStyle( const AmiProStyle& s )
00731 {
00732   assign( s );
00733 }
00734 
00735 AmiProStyle& AmiProStyle::operator=( const AmiProStyle& s )
00736 {
00737   assign( s );
00738   return *this;
00739 }
00740 
00741 // base listener for the parser
00742 AmiProListener::AmiProListener()
00743 {
00744 }
00745 
00746 AmiProListener::~AmiProListener()
00747 {
00748 }
00749 
00750 #define DO_TRUE_DEFINITION(string) \
00751     bool AmiProListener::string \
00752     {\
00753         return true;\
00754     }
00755 
00756 DO_TRUE_DEFINITION(doOpenDocument())
00757 DO_TRUE_DEFINITION(doCloseDocument())
00758 DO_TRUE_DEFINITION(doDefineStyle(const AmiProStyle& style))
00759 DO_TRUE_DEFINITION(doParagraph(const QString& text, AmiProFormatList formatList,
00760   AmiProLayout& ))
KDE Home | KDE Accessibility Home | Description of Access Keys