Kate
insanehtmlplugin_le.cpp
Go to the documentation of this file.
00001 /* 00002 Copyright (C) 2010 Joseph Wenninger <jowenn@kde.org> 00003 00004 Redistribution and use in source and binary forms, with or without 00005 modification, are permitted provided that the following conditions 00006 are met: 00007 00008 1. Redistributions of source code must retain the above copyright 00009 notice, this list of conditions and the following disclaimer. 00010 2. Redistributions in binary form must reproduce the above copyright 00011 notice, this list of conditions and the following disclaimer in the 00012 documentation and/or other materials provided with the distribution. 00013 00014 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 00015 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 00016 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 00017 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 00018 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 00019 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 00020 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 00021 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 00022 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 00023 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 00024 */ 00025 00026 #include "insanehtmlplugin_le.h" 00027 #include "insanehtmlplugin_le.moc" 00028 #include <kpluginfactory.h> 00029 #include <kaction.h> 00030 #include <kactioncollection.h> 00031 #include <kpassivepopup.h> 00032 #include <ktexteditor/document.h> 00033 #include <kstandarddirs.h> 00034 #include <kconfiggroup.h> 00035 00036 #undef IHP_DEBUG 00037 00038 K_PLUGIN_FACTORY_DECLARATION(InsaneHTMLPluginLEFactory) 00039 K_PLUGIN_FACTORY_DEFINITION(InsaneHTMLPluginLEFactory, 00040 registerPlugin<InsaneHTMLPluginLE>(); 00041 ) 00042 K_EXPORT_PLUGIN(InsaneHTMLPluginLEFactory("ktexteditor_insanehtml_le", "ktexteditor_plugins")) 00043 00044 InsaneHTMLPluginLE::InsaneHTMLPluginLE(QObject *parent, const QList<QVariant> data): 00045 KTextEditor::Plugin(parent) { 00046 Q_UNUSED(data); 00047 00048 } 00049 00050 00051 void InsaneHTMLPluginLE::addView (KTextEditor::View *view) { 00052 m_map.insert(view,new InsaneHTMLPluginLEView(this,view)); 00053 } 00054 00055 void InsaneHTMLPluginLE::removeView (KTextEditor::View *view) { 00056 delete m_map.take(view); 00057 } 00058 00059 00060 InsaneHTMLPluginLEView::InsaneHTMLPluginLEView(QObject* parent,KTextEditor::View* view): 00061 QObject(parent),KXMLGUIClient(),m_view(view) { 00062 00063 setComponentData(InsaneHTMLPluginLEFactory::componentData()); 00064 00065 KAction *a=actionCollection()->addAction( "tools_insanehtml_le", this,SLOT(expand()) ); 00066 a->setText(i18n("Insane HTML (LE) Expansion")); 00067 a->setShortcut( Qt::CTRL + Qt::Key_Period ); 00068 00069 setXMLFile( "insanehtml_le_ui.rc" ); 00070 00071 m_view->insertChildClient(this); 00072 00073 m_emptyTags<<"br"<<"hr"<<"img"<<"input"<<"meta"<<"link"; 00074 QStringList cfgFiles=KGlobal::dirs()->findAllResources("data", "ktexteditor_insanehtml_le/xhtml.cfg",KStandardDirs::NoDuplicates); 00075 if (cfgFiles.count()>0) { 00076 KConfig attribConfig(cfgFiles[0],KConfig::SimpleConfig); 00077 KConfigGroup group(&attribConfig,"Default Attributes"); 00078 foreach (const QString& tag, group.keyList()) { 00079 QStringList attribs=group.readEntry(tag,QStringList()); 00080 foreach (const QString& attrib,attribs) { 00081 m_defaultAttributes.insert(tag,attrib); 00082 } 00083 } 00084 } 00085 } 00086 00087 InsaneHTMLPluginLEView::~InsaneHTMLPluginLEView() { 00088 m_view->removeChildClient(this); 00089 } 00090 00091 00092 /* 00093 We expect the cursor to be in them middle or at the end of a tag and don't allow attribute definitions on the right hand side, only quantifiers 00094 */ 00095 00096 int InsaneHTMLPluginLEView::find_region_end(int cursor_x, const QString& line, int *filtercount) { 00097 int end_x=cursor_x; 00098 const int len=line.length(); 00099 while (end_x<len) { 00100 QChar c=line.at(end_x); 00101 if (c.isLetter() || c.isDigit() || (c==QChar('*')) || (c==QChar('_')) || (c==QChar('-')) || (c==QChar(':')) || (c==QChar('.')) || (c==QChar('#')) || (c==QChar(')')) ) 00102 end_x++; 00103 else if (c==QChar('|')) { 00104 end_x++; 00105 (*filtercount)++; 00106 } else 00107 break; 00108 } 00109 int tmp=end_x-1; 00110 if ((tmp>=0) && (tmp<len)) 00111 if (line.at(tmp)==QChar('>')) return -1; 00112 return end_x; 00113 } 00114 00115 00116 /* everything is allowed in the front*/ 00117 int InsaneHTMLPluginLEView::find_region_start(int cursor_x, const QString& line, int *filtercount) { 00118 int len=line.length(); 00119 int start_x=cursor_x; 00120 bool in_attrib=false; 00121 bool in_string=false; 00122 while (start_x>0) { 00123 int tmp_x=start_x-1; 00124 if (tmp_x==-1) break; 00125 QChar c=line.at(tmp_x); 00126 if (c==QChar('"')) { 00127 if (!in_attrib) break; 00128 in_string=!in_string; 00129 start_x=tmp_x; 00130 continue; 00131 } 00132 if (in_string) { 00133 start_x=tmp_x; 00134 continue; 00135 } 00136 00137 if (c==QChar(']')) { 00138 in_attrib=true; 00139 start_x=tmp_x; 00140 continue; 00141 } 00142 00143 if (c==QChar('[')) { 00144 if (in_attrib) { 00145 in_attrib=false; 00146 start_x=tmp_x; 00147 continue; 00148 } else { 00149 break; 00150 } 00151 } 00152 00153 if (in_attrib) { 00154 start_x=tmp_x; 00155 continue; 00156 } 00157 00158 if ( (c.isSpace() || c==QChar('=')) && (!in_attrib)) 00159 break; 00160 00161 00162 if (c.isLetter() || c.isDigit() || (c==QChar('*')) || (c==QChar('_')) || 00163 (c==QChar('-')) || (c==QChar(':')) || (c==QChar('.')) || (c==QChar('#')) || 00164 (c==QChar('>')) || (c==QChar('$')) || (c==QChar('+')) || (c==QChar('(')) || 00165 (c==QChar(')'))) { 00166 start_x=tmp_x; 00167 continue; 00168 } 00169 00170 if (c==QChar('|')) { 00171 (*filtercount)++; 00172 start_x=tmp_x; 00173 continue; 00174 } 00175 00176 break; 00177 } 00178 00179 if (in_attrib || in_string) return -1; 00180 if (start_x>=len) return -1; 00181 if (start_x>=0) { 00182 if (!( (line.at(start_x).isLetter()) || (line.at(start_x)==QChar('(')) ) ) return -1; 00183 } 00184 return start_x; 00185 } 00186 00187 00188 QString InsaneHTMLPluginLEView::parseIdentifier(const QString& input, int *offset,bool firstDigit) { 00189 int offset_tmp=*offset; 00190 int len=input.length(); 00191 QString identifier; 00192 if (!firstDigit) { 00193 if (offset_tmp<input.length()) { 00194 if (input.at(offset_tmp).isDigit()) return QString(); 00195 } 00196 } 00197 while (offset_tmp<len) { 00198 QChar c=input.at(offset_tmp); 00199 if (! ( 00200 c.isDigit() || c.isLetter() || (c==QChar(':')) || (c==QChar('_')) || (c==QChar('-')) 00201 )) break; 00202 identifier+=c; 00203 offset_tmp++; 00204 } 00205 *offset=offset_tmp; 00206 return identifier; 00207 00208 } 00209 00210 int InsaneHTMLPluginLEView::parseNumber(const QString& input, int *offset) { 00211 int offset_tmp=*offset; 00212 int len=input.length(); 00213 QString number; 00214 if (offset_tmp<input.length()) { 00215 if (!input.at(offset_tmp).isDigit()) return 1; 00216 } 00217 while (offset_tmp<len) { 00218 QChar c=input.at(offset_tmp); 00219 if (! ( 00220 c.isDigit() 00221 )) break; 00222 number+=c; 00223 offset_tmp++; 00224 } 00225 *offset=offset_tmp; 00226 return number.toInt(); 00227 00228 } 00229 00230 QStringList InsaneHTMLPluginLEView::parse(const QString& input, int offset,int *newOffset) { 00231 QString tag; 00232 QStringList classes; 00233 QStringList sub; 00234 QStringList relatives; 00235 QString id; 00236 bool compound=false; 00237 QStringList compoundSub; 00238 QMap<QString,QString> attributes; 00239 QString attributesString; 00240 int multiply=1; 00241 bool error=false; 00242 tag=parseIdentifier(input,&offset); 00243 QStringList defAttribs=m_defaultAttributes.values(tag); 00244 foreach (const QString& defAttr,defAttribs) 00245 attributes.insert(defAttr,""); 00246 while (offset<input.length()) { 00247 QChar c=input.at(offset); 00248 if (c==QChar(')')) { 00249 offset++; 00250 break; 00251 } else if (c==QChar('(')) { 00252 offset++; 00253 #ifdef IHP_DEBUG 00254 KPassivePopup::message(i18n("offset1 %1",offset),m_view); 00255 #endif 00256 compoundSub=parse(input,offset,&offset); 00257 compound=true; 00258 #ifdef IHP_DEBUG 00259 KPassivePopup::message(i18n("offset2 %1",offset),m_view); 00260 #endif 00261 00262 } else if (c==QChar('.')) { 00263 offset++; 00264 classes << parseIdentifier(input,&offset); 00265 } else if (c==QChar('>')) { 00266 offset++; 00267 sub=parse(input,offset,&offset); 00268 break; 00269 } else if (c==QChar('+')) { 00270 offset++; 00271 relatives=parse(input,offset,&offset); 00272 break; 00273 } else if (c==QChar('*')) { 00274 offset++; 00275 multiply=parseNumber(input,&offset); 00276 } else if (c==QChar('#')) { 00277 offset++; 00278 id=parseIdentifier(input,&offset); 00279 } else if (c==QChar('[')) { 00280 offset++; 00281 while (offset<input.length()) { 00282 c=input.at(offset); 00283 if (! ( (c==QChar(' ')) || (c==QChar('\t')) || (c==QChar(',')) ) ) { 00284 break; 00285 } 00286 offset++; 00287 } 00288 if (offset>=input.length()) { 00289 error=true; 00290 break; 00291 } 00292 while (offset<input.length()) { 00293 if (input.at(offset)==QChar(']')) { 00294 offset++; 00295 break; 00296 } 00297 QString attr=parseIdentifier(input,&offset); 00298 QString value; 00299 if (attr.isEmpty() || offset>=input.length()) { 00300 error=true; 00301 break; 00302 } 00303 c=input.at(offset); 00304 if (c==QChar('=')) { 00305 offset++; 00306 //PARSE PARAMETER 00307 if (offset>=input.length()) { 00308 error=true; 00309 break; 00310 } 00311 if (input.at(offset)==QChar('"')) { 00312 // parse quoted string 00313 offset++; 00314 int stringStart=offset; 00315 while (offset<input.length()) { 00316 if (input.at(offset)==QChar('"')) { 00317 attributes.insert(attr,input.mid(stringStart,offset-stringStart)); 00318 offset++; 00319 break; 00320 } 00321 offset++; 00322 } 00323 if (offset>=input.length()) { 00324 error=true; 00325 break; 00326 } 00327 //skip whitespace and , 00328 while (offset<input.length()) { 00329 c=input.at(offset); 00330 if (! ( (c==QChar(' ')) || (c==QChar('\t')) || (c==QChar(',')) ) ) { 00331 break; 00332 } 00333 offset++; 00334 } 00335 } else { 00336 //no " 00337 value=parseIdentifier(input,&offset,true); 00338 attributes.insert(attr,value); 00339 } 00340 00341 00342 if (offset>=input.length()) { 00343 error=true; 00344 break; 00345 } 00346 //skip whitespace and , 00347 while (offset<input.length()) { 00348 c=input.at(offset); 00349 if (! ( (c==QChar(' ')) || (c==QChar('\t')) || (c==QChar(',')) ) ) { 00350 break; 00351 } 00352 offset++; 00353 } 00354 } else { //no parameter for attribute specified 00355 if (offset>=input.length()) { 00356 error=true; 00357 break; 00358 } 00359 //skip whitespace and , 00360 while (offset<input.length()) { 00361 c=input.at(offset); 00362 if (! ( (c==QChar(' ')) || (c==QChar('\t')) || (c==QChar(',')) ) ) { 00363 break; 00364 } 00365 offset++; 00366 } 00367 attributes.insert(attr,QString()); 00368 } 00369 //offset++; 00370 } 00371 if (error) break; 00372 } else { 00373 #ifdef IHP_DEBUG 00374 KPassivePopup::message(i18n("error %1",c),m_view); 00375 #endif 00376 00377 error=true; 00378 break; 00379 } 00380 } 00381 00382 if (newOffset) *newOffset=offset; 00383 00384 if (!error) { 00385 00386 00387 for(QMap<QString,QString>::const_iterator it=attributes.constBegin();it!=attributes.constEnd();++it) { 00388 attributesString+=" "+it.key(); 00389 if (!it.value().isNull()) attributesString+="=\""+it.value()+"\""; 00390 } 00391 QStringList result; 00392 if (!compound) { 00393 QString idAttrib; 00394 if (!id.isEmpty()) idAttrib=QString(" id=\"%1\"").arg(id); 00395 QString classAttrib=classes.join(" "); 00396 QString classComment; 00397 if (!classAttrib.isEmpty()) classComment="."+classes.join(" ."); 00398 if (!classAttrib.isEmpty()) classAttrib=QString(" class=\"%1\"").arg(classAttrib); 00399 00400 if (!sub.isEmpty()) 00401 sub=sub.replaceInStrings(QRegExp("^")," "); 00402 00403 for (int i=1;i<=multiply;i++) { 00404 bool done=false; 00405 if (!idAttrib.isEmpty()) result<<QString("|c-#%1-c|").arg(id); 00406 if (!classComment.isEmpty()) result<<QString("|c-%1-c|").arg(classComment); 00407 if (sub.isEmpty()) { 00408 if (m_emptyTags.contains(tag)) { 00409 result<<QString("<%1%2%3%4/>").arg(tag).arg(idAttrib).arg(classAttrib).arg(attributesString); 00410 done=true; 00411 } 00412 } 00413 if (!done){ 00414 if (!sub.isEmpty()) { 00415 result<<QString("<%1%2%3%4>").arg(tag).arg(idAttrib).arg(classAttrib).arg(attributesString); 00416 result<<sub; 00417 result<<QString("</%1>").arg(tag); 00418 } else 00419 result<<QString("<%1%2%3%4></%1>").arg(tag).arg(idAttrib).arg(classAttrib).arg(attributesString); 00420 } 00421 00422 if (!idAttrib.isEmpty()) result<<QString("|c-/#%1-c|").arg(id); 00423 if (!classComment.isEmpty()) result<<QString("|c-/%1-c|").arg(classComment); 00424 } 00425 } else { 00426 for (int i=1;i<=multiply;i++) { 00427 QStringList tmp=compoundSub; 00428 result<<tmp; 00429 } 00430 } 00431 if (!relatives.isEmpty()) 00432 result<<relatives; 00433 return result; 00434 } 00435 return QStringList(); 00436 } 00437 00438 void InsaneHTMLPluginLEView::apply_filter_e(QStringList *lines) { 00439 lines->replaceInStrings("&","&"); 00440 lines->replaceInStrings("<","<"); 00441 lines->replaceInStrings(">",">"); 00442 } 00443 00444 void InsaneHTMLPluginLEView::apply_filter_c(QStringList *lines) { 00445 lines->replaceInStrings("|c-","<!-- "); 00446 lines->replaceInStrings("-c|"," -->"); 00447 } 00448 00449 void InsaneHTMLPluginLEView::expand() { 00450 KTextEditor::Cursor c=m_view->cursorPosition(); 00451 QString line=m_view->document()->line(c.line()); 00452 int filtercount=0; 00453 int start_x=find_region_start(c.column(),line,&filtercount); 00454 int end_x=find_region_end(c.column(),line,&filtercount); 00455 if ( (start_x<0) || (end_x<0) || (start_x==end_x) ) { 00456 KPassivePopup::message(i18n("No valid Insane HTML markup detected at current cursor position"),m_view); 00457 return; 00458 } 00459 #ifdef IHP_DEBUG 00460 KPassivePopup::message(i18n("This looks like valid Insane HTML markup: %1",line.mid(start_x,end_x-start_x)),m_view); 00461 #endif 00462 QString region_text=line.mid(start_x,end_x-start_x); 00463 QStringList filters; 00464 while(filtercount>0) { 00465 int li=region_text.lastIndexOf("|"); 00466 filters<<region_text.mid(li+1); 00467 region_text=region_text.left(li); 00468 filtercount--; 00469 } 00470 QStringList result_list=parse(region_text,0); 00471 while(!filters.isEmpty()) { 00472 //built_in_filters 00473 QString filter=filters.takeLast(); 00474 if (filter=="e") 00475 apply_filter_e(&result_list); 00476 else if (filter=="c") apply_filter_c(&result_list); 00477 } 00478 //remove unwanted comment marks 00479 QRegExp rcm("\\|c\\-.*\\-c\\|"); 00480 for (int i=result_list.count()-1;i>=0;i--) { 00481 QString tmp=result_list[i]; 00482 tmp.remove(rcm); 00483 if (tmp.trimmed().isEmpty()) 00484 result_list.takeAt(i); 00485 else 00486 result_list[i]=tmp; 00487 } 00488 //prefix with indentation 00489 QString line_prefix=line.left(start_x); 00490 line_prefix.replace(QRegExp("\\S")," "); 00491 for (int i=1;i<result_list.count();i++) 00492 result_list[i]=line_prefix+result_list[i]; 00493 QString result=result_list.join("\n"); 00494 KTextEditor::Document *doc=m_view->document(); 00495 KTextEditor::Range r(c.line(),start_x,c.line(),end_x); 00496 doc->replaceText(r,result); 00497 } 00498 00499 // kate: space-indent on; indent-width 2; replace-tabs on;
KDE 4.6 API Reference