1/*
2 * This file is part of the KDE Help Center
3 *
4 * Copyright (C) 2002 Frerich Raabe (raabe@kde.org)
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program 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
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 */
20
21#include "toc.h"
22
23#include "docentry.h"
24
25#include <KIconLoader>
26#include <KProcess>
27#include <KStandardDirs>
28#include <KDebug>
29#include <KXmlGuiWindow>
30#include <KApplication>
31#include <KStatusBar>
32#include <KLocale>
33
34#include <QDir>
35#include <QFileInfo>
36#include <QTextStream>
37#include <QPixmap>
38
39#include <sys/stat.h>
40
41using namespace KHC;
42
43class TOCItem : public NavigatorItem
44{
45 public:
46 TOCItem( TOC *parent, QTreeWidgetItem *parentItem, QTreeWidgetItem *after, const QString &text );
47
48 const TOC *toc() const { return m_toc; }
49
50 private:
51 TOC *m_toc;
52};
53
54class TOCChapterItem : public TOCItem
55{
56 public:
57 TOCChapterItem( TOC *toc, NavigatorItem *parent, QTreeWidgetItem *after, const QString &title,
58 const QString &name );
59
60 virtual QString url();
61
62 private:
63 QString m_name;
64};
65
66class TOCSectionItem : public TOCItem
67{
68 public:
69 TOCSectionItem( TOC *toc, TOCChapterItem *parent, QTreeWidgetItem *after, const QString &title,
70 const QString &name );
71
72 virtual QString url();
73
74 private:
75 QString m_name;
76};
77
78bool TOC::m_alreadyWarned = false;
79
80TOC::TOC( NavigatorItem *parentItem )
81{
82 m_parentItem = parentItem;
83}
84
85void TOC::build( const QString &file )
86{
87 QFileInfo fileInfo( file );
88 QString fileName = fileInfo.absoluteFilePath();
89 const QStringList resourceDirs = KGlobal::dirs()->resourceDirs( "html" );
90 QStringList::ConstIterator it = resourceDirs.begin();
91 QStringList::ConstIterator end = resourceDirs.end();
92 for ( ; it != end; ++it ) {
93 if ( fileName.startsWith( *it ) ) {
94 fileName.remove( 0, ( *it ).length() );
95 break;
96 }
97 }
98
99 QString cacheFile = fileName.replace( '/', "__" );
100#ifdef Q_WS_WIN
101 cacheFile = cacheFile.replace( ':', "_" );
102#endif
103 m_cacheFile = KStandardDirs::locateLocal( "cache", "help/" + cacheFile );
104 m_sourceFile = file;
105
106 if ( cacheStatus() == NeedRebuild )
107 buildCache();
108 else
109 fillTree();
110}
111
112TOC::CacheStatus TOC::cacheStatus() const
113{
114 if ( !QFile::exists( m_cacheFile ) ||
115 sourceFileCTime() != cachedCTime() )
116 return NeedRebuild;
117
118 return CacheOk;
119}
120
121int TOC::sourceFileCTime() const
122{
123 struct stat stat_buf;
124 stat( QFile::encodeName( m_sourceFile ).data(), &stat_buf );
125
126 return stat_buf.st_ctime;
127}
128
129int TOC::cachedCTime() const
130{
131 QFile f( m_cacheFile );
132 if ( !f.open( QIODevice::ReadOnly ) )
133 return 0;
134
135 QDomDocument doc;
136 if ( !doc.setContent( &f ) )
137 return 0;
138
139 QDomComment timestamp = doc.documentElement().lastChild().toComment();
140
141 return timestamp.data().trimmed().toInt();
142}
143
144void TOC::buildCache()
145{
146 KXmlGuiWindow *mainWindow = dynamic_cast<KXmlGuiWindow *>( kapp->activeWindow() );
147
148 KProcess *meinproc = new KProcess;
149 connect( meinproc, SIGNAL( finished( int, QProcess::ExitStatus) ),
150 this, SLOT( meinprocExited( int, QProcess::ExitStatus) ) );
151
152 *meinproc << KStandardDirs::locate("exe", "meinproc4");
153 *meinproc << "--stylesheet" << KStandardDirs::locate( "data", "khelpcenter/table-of-contents.xslt" );
154 *meinproc << "--output" << m_cacheFile;
155 *meinproc << m_sourceFile;
156
157 meinproc->setOutputChannelMode(KProcess::OnlyStderrChannel);
158 meinproc->start();
159 if (!meinproc->waitForStarted()) {
160 kError() << "could not start process" << meinproc->program();
161 if (mainWindow && !m_alreadyWarned) {
162 ; // add warning message box with don't display again option
163 // http://api.kde.org/4.0-api/kdelibs-apidocs/kdeui/html/classKDialog.html
164 m_alreadyWarned = true;
165 }
166 delete meinproc;
167 }
168}
169
170void TOC::meinprocExited( int exitCode, QProcess::ExitStatus exitStatus)
171{
172 KProcess *meinproc = static_cast<KProcess *>(sender());
173 KXmlGuiWindow *mainWindow = dynamic_cast<KXmlGuiWindow *>( kapp->activeWindow() );
174
175 if ( exitStatus == QProcess::CrashExit || exitCode != 0 ) {
176 kError() << "running" << meinproc->program() << "failed with exitCode" << exitCode;
177 kError() << "stderr output:" << meinproc->readAllStandardError();
178 if (mainWindow && !m_alreadyWarned) {
179 ; // add warning message box with don't display again option
180 // http://api.kde.org/4.0-api/kdelibs-apidocs/kdeui/html/classKDialog.html
181 m_alreadyWarned = true;
182 }
183 delete meinproc;
184 return;
185 }
186
187 delete meinproc;
188
189 // add a timestamp to the meinproc4 created xml file
190 QFile f( m_cacheFile );
191 if ( !f.open( QIODevice::ReadWrite ) )
192 return;
193
194 QDomDocument doc;
195 if ( !doc.setContent( &f ) )
196 return;
197
198 QDomComment timestamp = doc.createComment( QString::number( sourceFileCTime() ) );
199 doc.documentElement().appendChild( timestamp );
200
201 // write back updated xml content
202 f.seek( 0 );
203 QTextStream stream( &f );
204 stream.setCodec( "UTF-8" );
205#ifdef Q_WS_WIN
206 /*
207 the problem that on german systems umlauts are displayed as '?' for unknown (Qt'r related ?) reasons
208 is caused by wrong encoding type conversations and has been fixed in kdelibs/kdoctools
209 To have propper encoding tags in the xml file, QXmlDocument::save() is used.
210 */
211 doc.save(stream, 1, QDomNode::EncodingFromTextStream);
212
213#else
214 stream << doc.toString();
215#endif
216 f.close();
217 fillTree();
218}
219
220void TOC::fillTree()
221{
222 QFile f( m_cacheFile );
223 if ( !f.open( QIODevice::ReadOnly ) )
224 return;
225
226 QDomDocument doc;
227 if ( !doc.setContent( &f ) )
228 return;
229
230 TOCChapterItem *chapItem = 0;
231 QDomNodeList chapters = doc.documentElement().elementsByTagName( "chapter" );
232 for ( int chapterCount = 0; chapterCount < chapters.count(); chapterCount++ ) {
233 QDomElement chapElem = chapters.item( chapterCount ).toElement();
234 QDomElement chapTitleElem = childElement( chapElem, QLatin1String( "title" ) );
235 QString chapTitle = chapTitleElem.text().simplified();
236 QDomElement chapRefElem = childElement( chapElem, QLatin1String( "anchor" ) );
237 QString chapRef = chapRefElem.text().trimmed();
238
239 chapItem = new TOCChapterItem( this, m_parentItem, chapItem, chapTitle, chapRef );
240
241 TOCSectionItem *sectItem = 0;
242 QDomNodeList sections = chapElem.elementsByTagName( "section" );
243 for ( int sectCount = 0; sectCount < sections.count(); sectCount++ ) {
244 QDomElement sectElem = sections.item( sectCount ).toElement();
245 QDomElement sectTitleElem = childElement( sectElem, QLatin1String( "title" ) );
246 QString sectTitle = sectTitleElem.text().simplified();
247 QDomElement sectRefElem = childElement( sectElem, QLatin1String( "anchor" ) );
248 QString sectRef = sectRefElem.text().trimmed();
249
250 sectItem = new TOCSectionItem( this, chapItem, sectItem, sectTitle, sectRef );
251 }
252 }
253}
254
255QDomElement TOC::childElement( const QDomElement &element, const QString &name )
256{
257 QDomElement e;
258 for ( e = element.firstChild().toElement(); !e.isNull(); e = e.nextSibling().toElement() )
259 if ( e.tagName() == name )
260 break;
261 return e;
262}
263
264void TOC::slotItemSelected( QTreeWidgetItem *item )
265{
266 TOCItem *tocItem;
267 if ( ( tocItem = dynamic_cast<TOCItem *>( item ) ) )
268 emit itemSelected( tocItem->entry()->url() );
269
270 item->setExpanded( !item->isExpanded() );
271}
272
273TOCItem::TOCItem( TOC *toc, QTreeWidgetItem *parentItem, QTreeWidgetItem *after, const QString &text )
274 : NavigatorItem( new DocEntry( text ), parentItem, after )
275{
276 setAutoDeleteDocEntry( true );
277 m_toc = toc;
278}
279
280TOCChapterItem::TOCChapterItem( TOC *toc, NavigatorItem *parent, QTreeWidgetItem *after,
281 const QString &title, const QString &name )
282 : TOCItem( toc, parent, after, title ),
283 m_name( name )
284{
285 setExpanded( false );
286 entry()->setUrl(url());
287}
288
289QString TOCChapterItem::url()
290{
291 return QLatin1String("help:") + toc()->application() + QLatin1Char('/') + m_name
292 + QLatin1String(".html");
293}
294
295TOCSectionItem::TOCSectionItem( TOC *toc, TOCChapterItem *parent, QTreeWidgetItem *after,
296 const QString &title, const QString &name )
297 : TOCItem( toc, parent, after, title ),
298 m_name( name )
299{
300 setIcon( 0, SmallIcon( "text-plain" ) );
301 entry()->setUrl(url());
302}
303
304QString TOCSectionItem::url()
305{
306 if ( static_cast<TOCSectionItem *>( parent()->child(0) ) == this )
307 return static_cast<TOCChapterItem *>( parent() )->url() + '#' + m_name;
308
309 return "help:" + toc()->application() + '/' + m_name + ".html";
310}
311
312#include "toc.moc"
313// vim:ts=2:sw=2:et
314