Always use QStringBuilder.
[grantlee:grantlee.git] / templates / lib / engine.cpp
1 /*
2   This file is part of the Grantlee template system.
3
4   Copyright (c) 2009,2010 Stephen Kelly <steveire@gmail.com>
5
6   This library is free software; you can redistribute it and/or
7   modify it under the terms of the GNU Lesser General Public
8   License as published by the Free Software Foundation; either version
9   2.1 of the Licence, or (at your option) any later version.
10
11   This library is distributed in the hope that it will be useful,
12   but WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14   Lesser General Public License for more details.
15
16   You should have received a copy of the GNU Lesser General Public
17   License along with this library.  If not, see <http://www.gnu.org/licenses/>.
18
19 */
20
21 #include "engine.h"
22 #include "engine_p.h"
23
24 #include "exception.h"
25 #include "grantlee_config_p.h"
26 #include "grantlee_version.h"
27 #include "scriptabletags.h"
28 #include "template_p.h"
29 #include "templateloader.h"
30
31 #include <QtCore/QCoreApplication>
32 #include <QtCore/QDir>
33 #include <QtCore/QPluginLoader>
34 #include <QtCore/QRegExp>
35 #include <QtCore/QTextStream>
36
37 using namespace Grantlee;
38
39 static const char __scriptableLibName[] = "grantlee_scriptabletags";
40
41 Engine::Engine( QObject *parent )
42     : QObject( parent ), d_ptr( new EnginePrivate( this ) )
43 {
44   d_ptr->m_defaultLibraries << QLatin1String( "grantlee_defaulttags" )
45                             << QLatin1String( "grantlee_loadertags" )
46                             << QLatin1String( "grantlee_defaultfilters" );
47
48   d_ptr->m_pluginDirs = QCoreApplication::instance()->libraryPaths();
49   d_ptr->m_pluginDirs << QString::fromLocal8Bit( GRANTLEE_PLUGIN_PATH );
50 }
51
52 Engine::~Engine()
53 {
54   qDeleteAll( d_ptr->m_scriptableLibraries );
55   d_ptr->m_libraries.clear();
56   delete d_ptr;
57 }
58
59 QList<AbstractTemplateLoader::Ptr> Engine::templateLoaders()
60 {
61   Q_D( Engine );
62   return d->m_loaders;
63 }
64
65 void Engine::addTemplateLoader( AbstractTemplateLoader::Ptr loader )
66 {
67   Q_D( Engine );
68   d->m_loaders << loader;
69 }
70
71 QPair<QString, QString> Engine::mediaUri( const QString &fileName ) const
72 {
73   Q_D( const Engine );
74   QListIterator<AbstractTemplateLoader::Ptr> it( d->m_loaders );
75
76   QPair<QString, QString> uri;
77   while ( it.hasNext() ) {
78     const AbstractTemplateLoader::Ptr loader = it.next();
79     uri = loader->getMediaUri( fileName );
80     if ( !uri.second.isEmpty() )
81       break;
82   }
83   return uri;
84 }
85
86 void Engine::setPluginPaths( const QStringList &dirs )
87 {
88   Q_D( Engine );
89   d->m_pluginDirs = dirs;
90 }
91
92 void Engine::addPluginPath( const QString &dir )
93 {
94   Q_D( Engine );
95   QStringList temp;
96   temp << dir;
97   temp << d->m_pluginDirs;
98   d->m_pluginDirs = temp;
99 }
100
101 void Engine::removePluginPath( const QString &dir )
102 {
103   Q_D( Engine );
104   d->m_pluginDirs.removeAll( dir );
105 }
106
107 QStringList Engine::pluginPaths() const
108 {
109   Q_D( const Engine );
110   return d->m_pluginDirs;
111 }
112
113 QStringList Engine::defaultLibraries() const
114 {
115   Q_D( const Engine );
116   return d->m_defaultLibraries;
117 }
118
119 void Engine::addDefaultLibrary( const QString &libName )
120 {
121   Q_D( Engine );
122   d->m_defaultLibraries << libName;
123 }
124
125 void Engine::removeDefaultLibrary( const QString &libName )
126 {
127   Q_D( Engine );
128   d->m_defaultLibraries.removeAll( libName );
129 }
130
131 template<uint v>
132 bool acceptableVersion(uint minorVersion)
133 {
134   return minorVersion >= v;
135 }
136
137 template<>
138 bool acceptableVersion<0>(uint)
139 {
140   return true;
141 }
142
143 void Engine::loadDefaultLibraries()
144 {
145   Q_D( Engine );
146   // Make sure we can load default scriptable libraries if we're supposed to.
147   if ( d->m_defaultLibraries.contains( QLatin1String( __scriptableLibName ) ) && !d->m_scriptableTagLibrary ) {
148     d->m_scriptableTagLibrary = new ScriptableTagLibrary( this );
149
150     // It would be better to load this as a plugin, but that is not currently possible with webkit/javascriptcore
151     // so we new the library directly.
152     // https://bugs.webkit.org/show_bug.cgi?id=38193
153 #if 0
154     d->loadCppLibrary( __scriptableLibName, GRANTLEE_VERSION_MINOR );
155     PluginPointer<TagLibraryInterface> library = d->loadCppLibrary( __scriptableLibName, GRANTLEE_VERSION_MINOR );
156     if ( !library )
157       throw Grantlee::Exception( TagSyntaxError, QLatin1String( "Could not load scriptable tags library" ) );
158 #endif
159   }
160
161   Q_FOREACH( const QString &libName, d->m_defaultLibraries ) {
162     if ( libName == QLatin1String( __scriptableLibName ) )
163       continue;
164
165     // already loaded by the engine.
166     if ( d->m_libraries.contains( libName ) )
167       continue;
168
169     // Warning. We load C++ plugins in this method, but not plugins written in QtScript.
170     // This should be a better situation in Grantlee 0.2 when the TagLibraryInterface
171     // can have shared pointers instead of raw pointers in its API. The whole scriptable
172     // thing likely needs to be redesigned too.
173     // The reason for explicitly not loading scripted plugins here is that they create new
174     // NodeFactory and Filter instances with each call to loadLibrary.
175     // NodeFactories are memory-managed by the Parser, as are Filters to an extent, because it
176     // creates shared pointers for Filters.
177     // Because this method loads the libraries but doesn't actually use them or send them
178     // back to the Parser the scriptable NodeFactories and Filters but be deleted immediately.
179     // The C++ plugins are different because although they are loaded, their NodeFactories
180     // and Filters are accessed only by the Parser, which manages them after that.
181     uint minorVersion = GRANTLEE_VERSION_MINOR;
182     while ( acceptableVersion<GRANTLEE_MIN_PLUGIN_VERSION>(minorVersion) ) {
183       // Although we don't use scripted libaries here, we need to recognize them being first
184       // in the search path and not load a c++ plugin of the same name in that case.
185       ScriptableLibraryContainer* scriptableLibrary = d->loadScriptableLibrary( libName, minorVersion );
186       if ( scriptableLibrary ) {
187         scriptableLibrary->clear();
188         break;
189       }
190
191       PluginPointer<TagLibraryInterface> library = d->loadCppLibrary( libName, minorVersion );
192       if ( minorVersion == 0)
193         break;
194       minorVersion--;
195       if ( library )
196         break;
197     }
198   }
199 }
200
201 TagLibraryInterface* Engine::loadLibrary( const QString &name )
202 {
203   Q_D( Engine );
204
205   if ( name == QLatin1String( __scriptableLibName ) )
206     return 0;
207
208   // already loaded by the engine.
209   if ( d->m_libraries.contains( name ) )
210     return d->m_libraries.value( name ).data();
211
212   uint minorVersion = GRANTLEE_VERSION_MINOR;
213   while ( acceptableVersion<GRANTLEE_MIN_PLUGIN_VERSION>(minorVersion) ) {
214     TagLibraryInterface* library = d->loadLibrary( name, minorVersion );
215     if ( library )
216       return library;
217     if (minorVersion == 0)
218       break;
219     minorVersion--;
220   }
221   throw Grantlee::Exception( TagSyntaxError, QString::fromLatin1( "Plugin library '%1' not found." ).arg( name ) );
222   return 0;
223 }
224
225 TagLibraryInterface* EnginePrivate::loadLibrary( const QString &name, uint minorVersion )
226 {
227   TagLibraryInterface* scriptableLibrary = loadScriptableLibrary( name, minorVersion );
228   if ( scriptableLibrary )
229     return scriptableLibrary;
230
231   // else this is not a scriptable library.
232
233   return loadCppLibrary( name, minorVersion ).data();
234 }
235
236 EnginePrivate::EnginePrivate( Engine *engine )
237   : q_ptr( engine ), m_scriptableTagLibrary( 0 ), m_smartTrimEnabled( false )
238 {
239 }
240
241 QString EnginePrivate::getScriptLibraryName( const QString &name, uint minorVersion ) const
242 {
243   int pluginIndex = 0;
244   const QString prefix = QLatin1Literal( "/grantlee/" )
245                        + QString::number( GRANTLEE_VERSION_MAJOR )
246                        + QLatin1Char( '.' )
247                        + QString::number( minorVersion )
248                        + QLatin1Char( '/' );
249   while ( m_pluginDirs.size() > pluginIndex ) {
250     const QString nextDir = m_pluginDirs.at( pluginIndex++ );
251     const QString libFileName = nextDir
252                               + prefix
253                               + name
254                               + QLatin1Literal( ".qs" );
255
256     const QFile file( libFileName );
257     if ( !file.exists() )
258       continue;
259     return libFileName;
260   }
261   QList<AbstractTemplateLoader::Ptr>::const_iterator it = m_loaders.constBegin();
262   const QList<AbstractTemplateLoader::Ptr>::const_iterator end = m_loaders.constEnd();
263   for ( ; it != end; ++it ) {
264     const QPair<QString, QString> pair = ( *it )->getMediaUri( prefix
265                                                             + name
266                                                             + QLatin1Literal( ".qs" ) );
267
268     if ( !pair.first.isEmpty() && !pair.second.isEmpty() ) {
269       return pair.first + pair.second;
270     }
271   }
272   return QString();
273 }
274
275 ScriptableLibraryContainer* EnginePrivate::loadScriptableLibrary( const QString &name, uint minorVersion )
276 {
277   if ( !m_scriptableTagLibrary )
278     return 0;
279
280 #if 0
281   if ( !m_libraries.contains( __scriptableLibName ) )
282     return 0;
283 #endif
284
285   const QString libFileName = getScriptLibraryName( name, minorVersion );
286
287   if ( libFileName.isEmpty() )
288     return 0;
289
290   if ( m_scriptableLibraries.contains( libFileName ) ) {
291     ScriptableLibraryContainer *library = m_scriptableLibraries.value( libFileName );
292     library->setNodeFactories( m_scriptableTagLibrary->nodeFactories( libFileName ) );
293     library->setFilters( m_scriptableTagLibrary->filters( libFileName ) );
294     return library;
295   }
296 #if 0
297   PluginPointer<TagLibraryInterface> scriptableTagLibrary = m_libraries.value( __scriptableLibName );
298 #endif
299
300   const QHash<QString, AbstractNodeFactory*> factories = m_scriptableTagLibrary->nodeFactories( libFileName );
301   const QHash<QString, Filter*> filters = m_scriptableTagLibrary->filters( libFileName );
302
303   ScriptableLibraryContainer *library = new ScriptableLibraryContainer( factories, filters );
304   m_scriptableLibraries.insert( libFileName, library );
305   return library;
306 }
307
308 PluginPointer<TagLibraryInterface> EnginePrivate::loadCppLibrary( const QString &name, uint minorVersion )
309 {
310   int pluginIndex = 0;
311   QString libFileName;
312
313   while ( m_pluginDirs.size() > pluginIndex ) {
314     const QString nextDir = m_pluginDirs.at( pluginIndex++ );
315     const QString pluginDirString = nextDir
316                                   + QLatin1Literal( "/grantlee/" )
317                                   + QString::number( GRANTLEE_VERSION_MAJOR )
318                                   + QLatin1Char( '.' )
319                                   + QString::number( minorVersion )
320                                   + QLatin1Char( '/' );
321
322     const QDir pluginDir( pluginDirString );
323
324     if ( !pluginDir.exists() )
325       continue;
326
327     const QStringList list = pluginDir.entryList( QStringList( name + QLatin1Char( '*' ) ) );
328
329     if ( list.isEmpty() )
330       continue;
331
332     QString pluginPath=pluginDir.absoluteFilePath( list.first() );
333     PluginPointer<TagLibraryInterface> plugin = PluginPointer<TagLibraryInterface>( pluginPath );
334
335     if ( plugin ) {
336 #ifdef __COVERAGESCANNER__
337       __coveragescanner_register_library(pluginPath.toLatin1().data());
338 #endif
339       m_libraries.insert( name, plugin );
340       return plugin;
341     }
342   }
343   return 0;
344 }
345
346 Template Engine::loadByName( const QString &name ) const
347 {
348   Q_D( const Engine );
349
350   QListIterator<AbstractTemplateLoader::Ptr> it( d->m_loaders );
351   while ( it.hasNext() ) {
352     const AbstractTemplateLoader::Ptr loader = it.next();
353
354     if ( !loader->canLoadTemplate( name ) )
355       continue;
356
357     const Template t = loader->loadByName( name, this );
358
359     if ( t ) {
360       return t;
361     }
362   }
363   Template t = Template( new TemplateImpl( this ) );
364   t->setObjectName( name );
365   t->d_ptr->m_error = TagSyntaxError;
366   t->d_ptr->m_errorString = QString::fromLatin1( "Template not found, %1" ).arg( name );
367   return t;
368 }
369
370 Template Engine::newTemplate( const QString &content, const QString &name ) const
371 {
372   Q_D( const Engine );
373   Template t = Template( new TemplateImpl( this, d->m_smartTrimEnabled ) );
374   t->setObjectName( name );
375   t->setContent( content );
376   return t;
377 }
378
379 void Engine::setSmartTrimEnabled( bool enabled )
380 {
381   Q_D( Engine );
382   d->m_smartTrimEnabled = enabled;
383 }
384
385 bool Engine::smartTrimEnabled() const
386 {
387   Q_D( const Engine );
388   return d->m_smartTrimEnabled;
389 }
390
391 #include "engine.moc"