Tải bản đầy đủ (.pdf) (45 trang)

The book of qt 4 the art of building qt applications - phần 9 docx

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (558.13 KB, 45 trang )

13.1 TheSAX2API
astatusand twocomparisons.Ifthisisnot thecaseweset an errormessage and
return from themethod withfalse.
If, on theother hand,wecomeacross<rss>,wefirstcheck theversion number.
We on lysupportRSS version 2.0(orasubset of this). We thereforerejectother
versionsasapreventivemeasure.Nowit is also time to setrssTagParsedtotrueso
that weare notwronglyrejected bythefirstcheck on elements that weparse later.
If wecomeacross<item>, then wechangethe status of inItem to true to distin-
guishthe contextofthe tags,asdescribedabove. We also add anewlin etothe
model, which wewillthenfill withvalues.
If anewtagstarts, weshouldalsoemptycurrentText, sincethisvariable should
onlycontainthe textbetween apairofstart andend tags.
Next, thecharactersmethod is used to read in thedatathatliesbetween apair
of startand endtags.Ifthe parserinterprets this textasseveral consecutivetexts,
for example, anormaltextand aCDATA sectioninwhich datamaybe enclosed in
diamondoperators, without it beinginterpreted as XML, wecombineall thetexts
into one. Since no errorcan ariseherefromour perspective, wereturn true in all
circumstances:
// rssreader/rsshandler.cpp (continued)
bool RssHandler::characters( const QString &str )
{
currentText +=str;
returntrue;
}
We insert thetextcollected in this wayin endElement()intothe ready-to-useline
of themodel. Againweare notinterested in namespaces, butmerelyin thecurrent
tag, which is waiting in theqName variable:
// rssreader/rsshandler.cpp (continued)
bool RssHandler::endElement( const QString &/
*
namespaceURI


*
/,
const QString &/
*
localName
*
/,
const QString &qName )
{
if (qName == "item"){
inItem =false;
} elseif(qName == "title"){
if (inItem) {
QModelIndexidx=itemModel->index(0,0);
itemModel->setData(idx,currentText);
}
} elseif(qName == "pubDate"){
if (inItem) {
QModelIndexidx=itemModel->index(0,1);
359
13 Handling XMLwithQtXml
itemModel->setData(idx,currentText);
}
} elseif(qName == "description"){
if (inItem) {
QModelIndexidx=itemModel->index(0,0);
QString preview;
if (preview.length() >= 300 )
preview=currentText.left(300)+" ";
else

preivew=currentText;
itemModel->setData(idx,preview,Qt::ToolTipRole);
itemModel->setData(idx,currentText,Qt::UserRole);
}
}
returntrue;
}
If wecomeacrossan<item>tag, weleavethe contextofanitem, andwetherefore
set<inItem>back to false.Ifweare currentlylookingatthe contents of thetags
<title>, <pubDate>, or <description>, wemustbesureineach casethatweare
locatedwithinanitem, which is whywealsoneed to checkinItem in thesecases.
Since weinsertthe dataintolinezero—after all, weinserted thenewelementinto
this lineaswell—wewillspecifythemodelindexin column zeroasthe title. There
weset currentText, that is thetextreadin, as thecontentbetween thetags.The
same is donewithpubDate, exceptthatweselectthe first column here.
We proceed in twowayswiththe descriptionfrom<description></description>.
On onehand, wearbitrarilycutoff thefirst300 characters to provideatextpreview
in thetooltip. To indicate that thetextcontinues, weattach an ellipsis( ) to it.
4
In addition,weplace datafor thefirsttimeinthe UserRole, in this casethe com-
plete contents of <description>. We willuse this later to displaythecontents of
thecurrent entryin aQTextBrowser.
In thefinalpartofthe RssHandlerimplementation, wetake alook at errorhandling.
On page 357 it was brieflymentionedthaterrorsthattriggerthe implementation
of ourclass is retrievable for theparservia errorString(). This is whythis method
simplyreturnsthe last errordescription,written to thevariable errorString:
// rssreader/rsshandler.cpp (continued)
QString RssHandler::errorString() const
{
returnerrString;

}
4
Since weare in themiddleofanHTMLtag,there is no guaranteethatthe user will actuallysee
thethree dots. Aproperfeed reader would havetouse abetteralgorithm to cutthe text.
360
13.1 TheSAX2API
This error, as wellasfatal errors that originatefromthe parseritself, andwhich
preventthe continuedprocessing of thedocument,sets off acalltothe fatalError()
method, butonlyon thefirstparsererror,unlesswereturn true.Events arenot
processedfurther after an errorhas occurred:
// rssreader/rsshandler.cpp (continued)
bool RssHandler::fatalError( const QXmlParseException &exception )
{
QMessageBox::information(0,QObject::tr( "RSS-Reader" ),
QObject::tr( "Parseerrorin line %1, columne %2: \ n%3" )
.arg(exception.lineNumber() )
.arg(exception.columnNumber() )
.arg(exception.message() ));
returnfalse;
}
We passthe errorontothe user bymeansofQMessageBox.The parameter excep-
tion provides details on theerror that hasoccurred.
13.1.3Digression:Equipping theRSS ReaderwithaGUI and
Network Capability
Nowourparsercan be built into afeed readerthatusesanHTTP address to down-
load an RSSfeed,parse it,and displayit.Figure13.1showshowtheapplicationis
constructed:The lineeditwaits for theaddressofthe fe ed,the contents of which
aredisplayed byaQTextViewon theleft-hand page.Onthe rightwesee thearticle
selected from thelistinaQTextBrowser.
Figure 13.1:

TheSAX-based RSS
readerdisplays the
blogs of KDE
developers.
To download thefile from awebserver,weuse theQHttp class, which enables
asynchronouscommunication withwebservers. This is oneofthe networkclasses
introducedinChapter 11, butwehavenot yet discusseditinmoredetail. We also
361
13 Handling XMLwithQtXml
come acrossthe QBufferclass again, where wetemporarilystorethe contents of
theRSS file. Later on weneed theinteger jobIdinconnectionwithQHttp.Our
windowis basedonQMainWindow,among otherthings, because wewilluse its
status bar:
// rssreader/mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
class QLineEdit;
class QTextBrowser;
class QTreeView;
class QHttp;
class QBuffer;
class QModelIndex;
class MainWindow:public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget
*
parent=0);

protected slots:
void readResponse(intid, bool error);
void retrieveRss();
void showArticle(const QModelIndex&index);
void showRss();
private:
QHttp
*
http;
QLineEdit
*
lineEdit;
QTextBrowser
*
textBrowser;
QTreeView
*
treeView;
QBuffer
*
rssBuffer;
intjobId;
} ;
#endif // MAINWINDOW_H
In theconstructor wegivethe windowanameand arrangethe subwindowin a
table layout. Here wedragthe lineeditover onelineand twocolumns,which
can be seen in thefourthand fifth parametersofthe first addWidget()details.We
insert thetreeviewandthe textbrowserinthe second line, in thefirstand second
columns respectively:
// rssreader/mainwindow.cpp

#include <QtGui>
362
13.1 TheSAX2API
#include <QtXml>
#include <QtNetwork>
#include "mainwindow.h"
#include "rsshandler.h"
MainWindow::MainWindow(QWidget
*
parent)
:QMainWindow(parent),jobId(0)
{
setWindowTitle(tr("RSS Reader"));
QWidget
*
cw=newQWidget;
QGridLayout
*
lay=newQGridLayout(cw);
lineEdit=newQLineEdit;
lay->addWidget(lineEdit,0,0,1,2);
treeView=newQTreeView;
treeView->setRootIsDecorated(false);
lay->addWidget(treeView,1,0);
textBrowser=newQTextBrowser;
lay->addWidget(textBrowser,1,1);
setCentralWidget(cw);
rssBuffer=newQBuffer(this);
rssBuffer->open(QIODevice::ReadWrite);
http =newQHttp(this);

connect(lineEdit,SIGNAL(returnPressed()),SLOT(retrieveRss()));
connect(treeView,SIGNAL(activated(const QModelIndex&)),
SLOT(showArticle(const QModelIndex&)));
connect(treeView,SIGNAL(doubleClicked(const QModelIndex&)),
SLOT(showArticle(const QModelIndex&)));
connect(http,SIGNAL(requestFinished(int,bool)),
SLOT(readResponse(int,bool)));
statusBar()->showMessage(tr("Welcome toRSS Reader!"));
}
Theentirelayoutliesonasimple QWidgetbythenameofcw,which weinsertinto
themainwindowas thecentral widget. Finally,wegenerateabufferand openit
for read andwrite access.
In addition wecreatethe QHttp object.Thisclass works in apurelyasynchronous
manner, so thereisnot even thetheoretical possibilityof placingthe object on
thestack, which would block processing until an eventispresent.Instead, allcalls
immediatelyreturn.Whenthe QHttp object hastreated therequest,itsends outa
signal.
Forthisreason, wecreatesignal/slot connections at theend,the last oneofwhich
is connected withthe QHttp instance.Assoon as theusersets off theinputsinthe
lineeditwith




Enter ,retrieveRss()beginsdownloading thefile.The second andthird
connect() calls connect akeyactionoradouble-clicktoanentryin thelistview
withthe showArticle()method, which displaysthe corresponding article. Finally,
363
13 Handling XMLwithQtXml
weconnect therequestFinished() signal,which triggers QHttp after an operation

hasbeen completed,withthe slot wehavewritten ourselves,readResponse().
In retrieveRss()wetransferthe textfromthe lineeditintoaQUrl object.Ittries
automaticallyto parse aURL from thetextpassedtoit:
// rssreader/mainwindow.cpp (continued)
void MainWindow::retrieveRss()
{
QUrlurl(lineEdit->text());
if(!url.isValid || url.schema() != "http") {
statusBar()->showMessage(tr("Invalid URL:’%1’")
.arg(lineEdit->text());
return;
}
http->setHost(url.host());
jobId=http->get(url.path(),rssBuffer);
statusBar()->showMessage(tr("Getting RSS Feed ’%1’ ")
.arg(url.toString()));
}
If thetextdoesnot yield avalid URL(i.e., isValid() returnsfalse)orifthe scheme
(i.e., th eprotocol) is not http://,wedonot need to continue,and return without
leavingbehindanerror message.Wenowsetthe name of theserver from which
wewanttoobtainthe RSSfeed,using setHost(). Thematchinghostnameisalready
stored for us byurl.host().
Because of theasynchronousnatureofQHttp,all method calls that workonthe
server arearrangedintothe queueand performedone after theother.Each method
callreturnsajobID. As soon as ajob hasbeen processed, QHttp emitsthe re-
questFinished()signal, thefirstargument of which is thejob ID.
Forthisreasonwemake anoteofthe jobID(in themembervariable jobId) for the
Getrequest to theserver.Asarguments, theget()meth od demandsthe pathtothe
fileand apointer to aQIODevicewhere it can storethe retrieved file. Finally,we
informthe user that weare downloading theRSS feed.

In thereadResponse() slot wefetch onlytheresultofthe Getjob.The second
parameter specifies whether an erroroccurr ed during thefile download, perhaps
because theserver was notavailable or thepathwas incorrect. If this is notthe
case, weprocess thedatavia showRss()and issueathree-second success message
in thestatusbar.Otherwise, an errormessage willappear for thesamelen gthof
time:
// rssreader/mainwindow.cpp (continued)
void MainWindow::readResponse(intid, bool error)
{
364
13.1 TheSAX2API
if (id == jobId) {
if (!error) {
showRss();
statusBar()->showMessage(
tr("RSS-Feed loaded successfully"),3000);
}
else
statusBar()->showMessage(
tr("Fehlerwhile fetching RSS feed!"),3000);
}
}
showRss()doesthe actualwork. Here wecreateastandardmodelwithtwocolumns
that welater passontoRssHandler:
// rssreader/mainwindow.cpp (continued)
void MainWindow::showRss()
{
QStandardItemModel
*
model =newQStandardItemModel(0,2);

RssHandlerhandler(model);
QXmlSimpleReaderreader;
reader.setContentHandler(&handler);
reader.setErrorHandler(&handler);
rssBuffer->reset();
QXmlInputSource source(rssBuffer);
if (!reader.parse(source))
return;
deletetreeView->model();
treeView->setModel(model);
}
QXmlSimpleReaderisresponsible for parsing thefile usingthe RssHandlers.Since
RssHandlerinherits from QXmlDefaultHandler, andthus from allhandlers, butwe
haveimplemented onlythefunctionalityof QXmlContentHandler andQXmlEr-
rorHandler, wemustregister theRssHandlerasbothacontentand errorhandler
withthe readerobject.
As the document source for QXmlSimpleReader, theQXmlInputSourceclass is used,
which obtainsits datafromaQIODevice. Butbeforeweinstantiate such an input
source,passing on thebufferasanargument at thesametime, wemustset the
read position in thebuffertothe beginning of theinternalQByteArraywithreset(),
so that thecontentjustwritten can be read out. reader.parse() nowstarts the
actualparsing process.
If this runs successfully,wefirstdelete anyalreadyexisting modellinkedtothe tree
view,and then passour model, equippedwithfresh content, to theview.
In thefinalstep wenowneed to implementthe showArticle()slottodisplaythe
entryselected in thetreeviewin thetextbrowser. To do this weaccess thedata()
365
13 Handling XMLwithQtXml
method of theact ivemodel. We obtain theindexof thecurrent entryfrom the
argument of theslot. As theroleweselectUserRole, where wepreviouslystored the

complete contents of the<description>tag. We nowconvertthis, usingtoString(),
from aQVariantback to aQSt ring andpassthistothe textbrowserasHTML:
// rssreader/mainwindow.cpp (continued)
void MainWindow::showArticle(const QModelIndex&index)
{
QVarianttmp=treeView->model()->data(index,Qt::UserRole);
QString content=tmp.toString();
textBrowser->setHtml(content);
}
NowourrudimentaryRSSreaderisfinished.The obligatorymain() method instan-
tiates QApplication, andthe MainWindowobject displaysthe windowandsets it
to an initialsizeof 640 × 480 pixels. Theapplicationthenentersthe eventloop:
// rssreader/main.cpp
#include <QtGui>
#include "mainwindow.h"
intmain(intargc, char
*
argv[])
{
QApplication app(argc, argv);
MainWindowmw;
mw.show();
mw.resize(640,480);
returnapp.exec();
}
This simple examplealreadydemonstrates that SAX2 allowsyou to parse docu-
mentswithcomparativelysmalloutlay.But themoreexact thechecks become,the
more complexthecode.Ifyou wanttoavoidthiscomplexity,and you onlyprocess
smalldocumentsanyway,you should take alook at theDocument Object Model,
which followsacompletelydifferent approach.

13.2The DOM API
QDom,the DOMAPI of Qt, is averyconvenient wayof accessing XMLfiles. Th e
QDomDocument classhererepresentsacomplete XMLfile.Its setContent()method
is capable of generating DOMtrees outofXML files andconverselywriting the
contents of aDOM tree to an XMLdocument.
TheDOM tree itself consists of DOM elements (QDomElement). Theirstart tags may
containattributes.Between thestart andend tag, DOM elements maycontaintext
366
13.2 TheDOM API
or child elements.InthiswaytheDOM tree is built up from theXML structure, and
itselementsare without exceptionDOM nodes(QDomNodes).
QDomNodesknowtheprinciple of parenthood: If theyareinserted in anotherpart
of thetree, theyarenot copied,but change theirlocationinthe tree.The node
into which aQDomNode is inserted nowfunctionsasits newparentnode.Not
everynode mayposesschild nodes, however.Ifyou try,for exam ple, to givea
child to an attribute node,the object willinsertthe newnode as asiblingnode of
theattribute node.Thisdeviates from theDOM specification,which at this point
divergentlydemandsthatanexceptionshouldbethrown.
Here ageneral distinctionfromthe DOMspecificationcan alreadybe seen:Qtdoes
notuse exceptions to announceerrors, buteitherusesreturn valuesorchooses an
alternativebehavior. This is whyit is recommended that you excludecases of error
in advance of acallbymaking as manychecks as possible in themethod’scode,
andalsocheck return valuesofmethods after calls.
13.2.1Reading in andProcessing XMLFiles
ThefollowingHTMLfile is written in XHTML:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
" /><html xmlns=" /><head>
<title>Title</title>
</head>
<body>

<p>
<ahref="">Example.com</a>
<ahref="">Example.net</a>
<ahref="">Example.org</a>
</p>
</body>
</html>
We wanttoload this into atestapplicationinaQDomDocument andworkwithit
in ordertoobservedifferent aspectsofQDom. Forthispurposeweopenthe file
withQFile andreadout itscontents.Thenweinstantiate aQDomDocument and
passthe byte arrayread outofthe filetothe QDomDocument withsetcontent().
We useusing namespace std; to simplywrite cout (insteadofstd::cout) to display
dataonthe standardoutput.
// xhtmldomparser/main.cpp
#include <QtCore>
#include <QtXml>
367
13 Handling XMLwithQtXml
#include <iostream>
using namespace std;
intmain(intargc, char
*
argv[])
{
QCoreApplication app(argc, argv);
QFile file("index.xml");
if (!file.open(QIODevice::ReadOnly))
return1;
QByteArraycontent=file.readAll();
QDomDocumentdoc;

QString errorMessage;
intline, col;
if (!doc.setContent(content,&errorMessage, &line, &col))
{
cout << "Errorin Line "<< line << ",column "<< col
<< ":"<< qPrintable(errorMessage)<< endl;
return1;
}
setContent()parsesthe inputand returnsafalse if thereisaparsererror.If—like
us—you wanttolearn more aboutthe error, you passtothisfunction,apart from
thebytearray,apointer to aQString andtwointegers. If thereisanerror,the
function fills theselastthree valueswiththe problemdescription,aswellasthe
relevantlineand column in thedocument.
Figure 13.2:
Everyobjectinthe
DOMtreegenerated
from theXML is a
QDomNode as well.
368
13.2 TheDOM API
TheDOM tree parsedinthiswayis showninFigure13.2. We willnowworkwith
this.First wereadout thenameofthe document type andthe tagnameofthe
root elementinthe document.docType() provides us withthe document type in a
QDomDocumentType object.Its name—thatis, thepartwhich stands directlyafter
DOCTYPE—is calledhtmlinthiscase. This is revealedtousthrough thename()
method:
// xhtmldomparser/main.cpp (continued)
QDomDocumentTypetype=doc.doctype();
cout << "Documenttype: "<< qPrintable(type.name()) << endl;;
QDomElementroot=doc.documentElement();

if (root.hasAttribute("xmlns")) {
QDomAttr attr =root.attributeNode("xmlns");
cout << "xmlns:"<< qPrintable(attr.value()) << endl;
}
if (root.hasAttribute("lang")) {
QDomAttr attr =root.attributeNode("lang");
cout << "lang: "<< qPrintable(attr.value()) << endl;
}
QDomNode node =root.firstChild();
while(!node.isNull())
{
if(node.isElement()) {
QDomElementelem =node.toElement();
cout << "Child of rootnode: "<< qPrintable(elem.tagName()) <<
endl;
cout << "Its text:"<< qPrintable(elem.text()) << endl;
}
node =node.nextSibling();
}
We obtain theroot elementand saveitasaQDomElement via theQDomDoc-
ument method documentElement(). Theattributes of elements areprovided by
attributeNode(). In theexampleweextract theattributes xmlns andlang. If th e
filedoesnot containthem, attributeNode() would return an emptyQDomAttr ob-
ject.Thisiswhywecheck whether theelement hasacorresponding attribute at
all, usinghasAttribute(). Finally,using value(), weobtainthe value of theattribute.
Then wereadout allthe child nodesofthe root element. We usefirstChild() to
giveusthe first DOM node (thatis, head),and allthe others (here,justbody)
weobtainwithnextSibling(). If this is just an element, weconvertthe node to a
QDomElement.Ifwecomeacrossanattribute or comment node,thiswillnot work,
of course.Toobtainthe name of aQDomElements, weuse thetagName()method.

Thetext( )method collectsthe textnodesofanelement andits ch ild elements in
aQString.Herewereceivethe textofthe headers from theheadele ment,and on
369
13 Handling XMLwithQtXml
thenextloop passthe texts of allthree referenceelements(<a>) beneaththe body
element.
We nowfillthe node variable withthe nextsis ter nodesand repeat theprocedure.
If no more sister nodesare available,thennextSibling()returnsanullnode,which
no longer fu lfils theloop condition. Theoutputofthe aboveexampletherefore
appears as follows:
Documenttype: html
Roottag: html
Documenttype: html
xmlns: />lang: en
Child of rootnode: head
Its text:Title
Child of rootnode: body
Its text:Example.comExample.netExample.org
Theconversion of theQDomNodesreturned byfirstChild() andnextSibling()into
QDomElementscan be left out, bytheway,ifyou usefirstChildElement()and
nextSiblingElement()instead. Theprocedure used here makesparticularsense if
you want, for example, to filter outadditional comments(represented byQDom-
Comment)ortext(represented byQDomTextand QDomCDATASection).
13.2.2Searching forSpecific Elements
Nowthat wehaveseen howwenavigateinaDOM tree, wewilllook at themethods
wecan usetosearchspecificallyfor certainelements. DOM provides theelements-
ByTagName()function for this purpose. It expectsthe name of an elementtype.If
you callitasamethod of aQDomDocument instance,thenitwilllook throughall
theelementsinthe entire document,whereas themethod of thesamenameinthe
QDomElement classlooksthrough allthe elements beneaththe elementreceiving

themethod call.
Bothfunctionsreturn aQDomList. This is not atype definitionfor QList<QDomNo-
de>but is adiffe rent kind of datastructure,which is specified in theDOM spec-
ifications. We can thereforenot processthislistwithforeach(). Insteadweuse a
for() loop to iteratethrough thelist, as shownhere:
// xhtmldomparser/main.cpp(continued)
QDomNodeList anchors =doc.elementsByTagName("a");
for(uinti=0; i<anchors.length(); i++) {
QDomElementanchor=anchors.at(i).toElement();
QString href =anchor.attribute("href");
cout << qPrintable(href)<< endl;
}
370
13.2 TheDOM API
Thenumber of elements is determinedbythelength() method. Beforewecan
read outthe attributes from thecurrent DOMnode,wemustconvertitback to
an element. TheDOM APIalwaysprovides onlyoneQDomNodeList for lists.The
attribute("href")callisashortformfor attributeNode("href").value() andreturns
thevalue directlyas aQString.The output for ourexampleaccordinglyappears as
follows:



TheQDomNode::childNodes()method returnsall subnodes, also in aQDomNodeList.
13.2.3Manipulatingthe DOMTree
Of course,wecan also insert newelements into thetreeoreliminateexisting ones.
To delete aDOM node from thetree, you callthe removeChild() method of thepar-
entnode,which can be determinedbyparentNode(), andpassthe node in question
to this method as aQDomElement:
// xhtmldomparser/main.cpp (continued)

QDomElementexamplecom =anchors.at(0).toElement();
examplecom.parentNode().removeChild(examplecom);
We nowwanttoconvertthe remaininglinks into anonclickable text, butthe text
should still be emphasized,for which weuse a<b> tag. Ideally,wewould th ere-
forechangethe tagnameofthe elementand removethe href attribute withthe
followingcode:
// xhtmldomparser/main.cpp (continued)
for(uinti=0; i<anchors.length(); i++) {
QDomElementanchor=anchors.at(i).toElement();
anchor.setTagName("b");
anchor.removeAttribute("href");
}
Alternativelywecould havecreated anewelement, copied thetext, looked for the
parentnode withparentNode(), andfromthere replaced thehreftag usingthe
replaceChild() method. As arguments, this expectsthe node to be replaced and
then thenewnode.
Nextwecreateanewpartialtreeand insert it into theDOM tree.Asabasisfor
partialtrees,the classQDomDocumentFragmentcan be used to savetrees th at
371
13 Handling XMLwithQtXml
do nothavetocontain well-formedXML.Thismeans that such apartial tree may
containseveral direct child elements,whereas in aQDomDocument,one element
at themost, theroot element, mayexist.
QDomDocumentFragments playaspecial role in methods such as appendChild(),
insertBefore(), or insertAfter(): If thesecontain afragmentasaparameter,then
theyinsert allits sub-nodes.
To create anode (thatis, onebased on aQDomNode subclass),wemustuse oneof
thefactorymethods from QDomDocument,which allstart withcreate. Theonly
exceptionisthe QDomDocument itself,which wecan instantiatedirectly.Ifyou
just instantiateanode without initializingitusing theappropriate factorymethod,

then it is considered to be undefined. This behaviorrepresentsasource of errors
that is noteasyto detect.
In th efollowingcode wewillgenerateafragmentand insert in it an italic element
(i). Into this elementweplace atextnode byfirst creating aQDomTextand then
appending it to theitalic elementwithappendChild(). After this wecreatean
XMLcomment (QDomComment)and insert both thenewitalic elementand the
comment into thedocument fragment:
// xhtmldomparser/main.cpp(continued)
QDomDocumentFragmentfragment=doc.createDocumentFragment();
QDomElementitalic =doc.createElement("i");
QDomText text =doc.createTextNode("some linksforyou:");
italic.appendChild(text);
QDomCommentcomment=doc.createComment("converted links:");
fragment.appendChild(italic);
fragment.appendChild(comment);
QDomNode para=doc.elementsByTagName("p").at(0);
para.insertBefore(fragment,para.firstChild());
To enter both elements in ourdocument abovethe links,welocatethe first p
elementand insert thefragment’scomponents as children, via insertBefore(), in
frontofwhatwas thefirstchild until now.
13.2.4The DOMTreeasXML Output
Thetreeobtaineduptothispoint can againbedisplayed byQDocument as an
XMLstructure.The methods toString() andtoByteArray() can be used for this.The
latter is of particular interestifyou wanttowrite theXML fileback to aQIODevice.
Theparameter specifies thenumber of emptyspacesthatshouldbeusedwhen
indentingthe XMLstructure.Ifthisismissing,Qtsets theindentdepth to one
emptyspace perlevel.
In thefollowingexamplewewillwrite thecurrent status of theDOM tree into the
fileopenedfor writing,out.xml.Thenweclose this andsendittothe standard
372

13.2 TheDOM API
output withtoString(). In both cases weuse twoemptyspacesper level when
indentingthe elements in theoutput:
// xhtmldomparser/main.cpp (continued)
QFile outfile("out.xml");
if (!outfile.open(QIODevice::WriteOnly)) {
cout << "Could notwritefile: "
<< qPrintable(outfile.fileName()) << endl;
return1;
}
QByteArraydata=doc.toByteArray(2);
outfile.write(data);
outfile.close();
// unicode string representation
QString string =doc.toString(2);
cout << qPrintable(string)<< endl;
return0;
}
373

14
Chapter
Internationalization
Manyprogramstodayareintendedtoreach usersinmanydifferent countri es.For
this reason it is veryimportant that an applicationcan be modifiedeasilyandflex-
iblyto theparticularities of anotherlanguage.One aspect of this is thetranslation
of allvisibletexts into thetargetlanguage.The directionoftextflow,onwhich the
arrangementofwidgets is based, is also of centralimportance.
In th is chapter wewillfirsttranslate theapplicationCuteEdit, which wecreated in
Chapter 4using thetoolsofQt. In addition wewillget to knowafewuseful classes

which,whenusedduringthe developmentprocess,willhelptoavoidproblems later
on whentranslating thesoftwaretoanother language.
14.1TranslatingAppl ications into OtherLanguages
Qt includes several mechanisms to prepareapplicationprogramsfor translation
into otherlanguageslater on (see page 50andalsopage 123).
375
14 Internationalization
We nowrepeat once againthe twomostimportant points: Alltranslatable strings
in theprogram code mustalwaysbeenclosedbytheQObject method tr(). In ad-
dition,variablesinstrings maynever be directlyconcatenated,since it would be
impossible to produce thecorrect wordorder in thetargetlanguage,asthe follow-
ingEnglish-to-German sentence conversion illustrates:
QString filename ="file.txt";
QString message =tr("Could notsave") +filename;
In theGermantranslation,the wordorder should be “Couldfile.txtnot save,”but
theexpression used to constructthe message assumesEnglishwordorder an dso
cannotproduce thedesired phrase.Todothe translationcorrectly,you need to use
placeholders, as shownbelow.
QString filename ="file.txt";
QString message =tr("Could notsave%1.").arg(filename);
Nowtr() is able to translatethissentence withthe correctinverted (relativeto
English) wordorder,Could %1 notsave. We willnowexplainhowthis works.
14.1.1Preparing theApplication
TheCuteEditversion used here is different from theone in Chapter 4, in that it uses
Englishstrings in thecode.Thisisnot necessarybutitdoesmake sense, as Englishis
normallyused as thelinguafranca, allowingexternalprogrammers whose mother
tongue is notGermantoworkonthe program.
Forthe translationofQt-basedapplications,Qtprovides theprogramslupdate,
linguist, andlrelease. Thetranslation processisnot aseparatetask, completely
isolated from thecode development, butisintegrated into theQtproject manage-

ment.Ifour projectfile up until nowlookslikethis:
#cuteediti18n/cuteediti18n.pro
TEMPLATE =app
SOURCES =main.cpp mainwindow.cpp
HEADERS =mainwindow.h
FORMS =mainwindow.ui
RESOURCES =pics.qrc
then allthatismissing is theentryfor TRANSLATIONS,which expectsone or more
translationfilesasarguments. Adding translationsupportfor German,French, and
Italianwilllook likethis:
376
14.1 Translating ApplicationsintoOther Languages
#cuteediti18n/cuteediti18n.pro(continued)
TRANSLATIONS =cuteedit_de.ts \
cuteedit_fr.ts \
cuteedit_it.ts
Using thelupdatetool,weextract thesefilesfromthe projectsources,the files
registered under SOURCES, HEADERS,and FORMS. Thefollowingcommand is suf-
ficient to do this:
lupdatecuteediti18n.pro
This extractsall thestrings in thesources that need to be translated.These trans-
lation sources arenowavailable in an XML-based format.
If newstringsare added during furtherprogram development, lupdatecuteed-
iti18n.pro updates thetranslation sources, andtranslators can workonthe new
strings.
14.1.2Processing TranslationSources with Linguist
Themostconvenient wayto openand edit translationsources is withthe program
Qt Linguist.Thisworkcan be donebypeopleworking independentlyof theQt
softwaredevelopers, such as freelance translators.
Figure 14.1showsthe main windowof theLinguistafter it hasloadedthe filecute-

edit_de.ts. Thecontextdockwindowon theleft-hand page gives an overviewof
thetranslation context, anditusuallydisplaysthe name of theclass in which a
string appears.
If you select acontext, th estrings for translationinthiscontextwill appear. The
field in thecenter provides space foranindividualtranslation.
Since thereare standardtranslationsfor manycommonplace phrasesand menu
items, you can findsuggestions from so-called phrasebooks .Qtprovides such
collections of suggestions for manycommonlanguagesunder Phrases → Open
Phrasebook If theseare loaded, suggestions willappear in thelower-right win-
dowif Linguist finds similarities to theword(s) beingtranslated.
Untranslated stringsare given abluequestionmark, andtranslated ones an orange
question mark.IfLinguistdiscoversaninconsistencyin thetranslation,suchas
missing “ ” in menu items, it places ared exclamation mark in frontand displays
theprobleminthe status bar. If you aresatisfiedwithatranslation, you confirm it
with




Ctrl +




Enter .Itisthengiven agreen ch eckm ark.
377
14 Internationalization
Figure 14.1:
Usingphrasebooks,
theQtLinguist helps

youtofind matching
translationsand
checks th esefor
consistency.
When allstrings aretranslated,the resultsjustneed to be saved:The .tsfile now
contains thetranslated strings. Thecommand
lreleasecuteediti18n.pro
creates files from (complete or partial) translationsou rces in aspecial binaryformat
that theQtprogram can use. In ourcasethese arecuteedit_de.qm, cuteedit_fr.q m,
andcuteedit_it.qm.
14.1.3Using Translations in theProgram
Loading thecorrect translationwhenthe program starts is thetaskofthe QTrans-
latorclass.Itwillsearchfor thetranslation files in theworking directoryof the
applicationifitisnot given apathasthe second argument whenitiscalled.
To determine thenameofthe translationfile for therespectivesystem envir on-
ment,weuse QLocale::system(). Thestaticmethod outputsaQLocaleobject with
information on thecurrent system locale. Thenam e( )function returnsthe localeto
us as astring, consisting of alanguage code andacountrycode in capitals, which
378
14.1 Translating ApplicationsintoOther Languages
for Germanywould be de _DE.Therefore, ourfilename is cuteedit_de_DE,and this
is turned into cuteedit_de_de as aprecaution,using toLower():
// cuteediti18n/main.cpp
#include <QApplication>
#include "mainwindow.h"
intmain(intargc, char
*
argv[])
{
QApplication a(argc, argv);

QTranslatorcuteeditTranslator;
filename =QString("cuteedit_%1").arg(QLocale::system().name());
filename =filename.toLower();
cuteeditTranslator.load(filename);
app.installTranslator(&cuteeditTranslator);
QTranslatornowlooksfor thefilename according to afixed pattern: Firstitadds
.qmtothe file, then it trieswithout this extension.Ifithas still notfound anything,
it removes allthe numbersfromthe endofthe name up to thefirstunderscoreor
dot,and triesagain. In ourcasethe search sequence would look likethis:
cuteedit_de_de.qm
cuteedit_de_de
cuteedit_de.qm
cuteedit_de
cuteedit.qm
cuteedit
Thealgorithm alreadyfound somethinginthe thirdstep.Through thecountry
code,localizationisalsopossible between countries that usethe same language
butwithdiffe rences in vocabularyor usage.Thus en usuallymatchesAmerican
English, whereas theapplicationwould make adjustmentsaimed towardthe lan-
guage customsofGreat Britain if thelocalewereset to en_UK.
If weinclude th etranslation in ourQApplicationinstancewithinstallT ranslator(),
theapplicationshowsatranslated user interfaceafter show() hasbeen called.
In addition weinstall aQTranslator,which contains allthe stringsofthe Qt library.
// cuteediti18n/main.cpp (continued)
QTranslatorqtTranslator;
QString filename("qt_%1").arg(QLocale::system().name());
filename =filename.toLower();
qtTranslator.load("qt_" +QLocale::system().name());
app.installTranslator(&qtTranslator);
379

14 Internationalization
QCoreApplication::setOrganizationName("OpenSourcePress");
QCoreApplication::setOrganizationDomain("OpenSourcePress.de");
QCoreApplication::setApplicationName("CuteEdit");
MainWindowmainWindow;
mainWindow.show();
returna.exec();
}
Thedirectory$QTDIR/translationscontainsthe rawtranslationsources in thefile
qt_untranslated.ts, together withaseries of translations for different languages,
such as qt_de.tsand qt_de.qmfor Germany.All you need to do is copythecor-
responding files to thecurrent directoryso that theQTranslator object willfind
them.
Since theorganizationand applicationname, as wellasthe domain, areusedin
thepathfor configurationsfiles, thesestrings should notbetranslated.
14.1.4Adding Notes forthe Translation
If astring’smeaning is notunique, for examplebecause it just consists of oneword,
this can lead to problems in translations.For instance,the translator who just sees
thewordasasingle word, andnot in itsentireusage context, hasnoclues as to
whether theword stop means“stop thecurrent operation”or“busstop.”For th is
reason thetr()method allowsatranslationcomment to be placed as thesecond
argument.The code
QString busstop=tr("Stop","bus stop");
QString stopaction =tr("Stop","stopaction");
generates twodifferent stringswithcorrespon ding comments, after lupdatehas
been runonthe translationsource.
14.1.5 Specifying theTranslation Context
Forstrings occurring in global functionsthatdonot belong to anyclass, thereis
no classtouse as adefault translationcontext. It is neverthelesspossible to assi gn
acontexttosuchastring bycallingthe actualstaticmethod tr() of aparticular

class:
1
1
TheQApplicationmethodtranslate() alwaysdemands de tails of thetranslation contextanyway
(see page 50).
380
14.1 Translating ApplicationsintoOther Languages
void global_function(MyWidget
*
w)
{
QLabel
*
label =newQLabel(MyWidget::tr("foo"),w);
}
lupdatethenacceptsthe label“foo” in thecorrect correcttranslation context(in
this case, MyWidget).
14.1.6InternationalizingStrings OutsideQtClasses
Forreasons of space,certain dataisoften stored in astaticarray.Inorder that
lupdatecan also record entriesfromsuchchararrays, theymustbeenclosedby
themacroQT_TR_NOOP. tr() then searches for itstranslationsasbeforefromthe
catalog.
This is illustrated in thefollowingexample, in which westore several citynames
in astatic, null-terminated array.After wehaveinstantiated theQCoreApplication
object andinstalledthe translator there, theprogram displaysthe localized city
names, via thetr()instruction,assoon as atranslation fileisavailable:
// trnoop/main.cpp
#include <QtCore>
#include <QDebug>
intmain(intargc, char

*
argv[])
{
static const char
*
cities[] = {
QT_TR_NOOP("Cologne"),
QT_TR_NOOP("Munich"),
QT_TR_NOOP("Rome"),
0
} ;
QCoreApplication app(argc, argv);
QTranslatortranslator;
filename =QString("trnoop_%1").arg(QLocale::system().name());
filename =filename.toLower();
translator.load(filename);
app.installTranslator(&translator);
inti=0;
while (cities[i])
qDebug<<QObject::tr(cities[i++]);
return0;
}
381

Appendixes

×