1/*
2 * This file is part of the syndication library
3 *
4 * Copyright (C) 2006 Frank Osterfeld <osterfeld@kde.org>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, 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 * Library General Public License for more details.
15 *
16 * You should have received a copy of the GNU Library General Public License
17 * along with this library; see the file COPYING.LIB. If not, write to
18 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301, USA.
20 *
21 */
22#include "elementwrapper.h"
23#include "constants.h"
24
25#include <kurl.h>
26
27#include <QtXml/QDomDocument>
28#include <QtXml/QDomElement>
29#include <QtCore/QString>
30#include <QtCore/QStringList>
31#include <QtCore/QTextStream>
32
33namespace Syndication {
34
35class ElementWrapper::ElementWrapperPrivate
36{
37 public:
38
39 QDomElement element;
40 QDomDocument ownerDoc;
41 mutable QString xmlBase;
42 mutable bool xmlBaseParsed;
43 mutable QString xmlLang;
44 mutable bool xmlLangParsed;
45};
46
47ElementWrapper::ElementWrapper() : d(new ElementWrapperPrivate)
48{
49 d->xmlBaseParsed = true;
50 d->xmlLangParsed = true;
51}
52
53ElementWrapper::ElementWrapper(const ElementWrapper& other)
54{
55 *this = other;
56}
57
58ElementWrapper::ElementWrapper(const QDomElement& element) : d(new ElementWrapperPrivate)
59{
60 d->element = element;
61 d->ownerDoc = element.ownerDocument(); //keep a copy of the (shared, thus cheap) document around to ensure the element isn't deleted too early (Bug 190068)
62 d->xmlBaseParsed = false;
63 d->xmlLangParsed = false;
64}
65
66ElementWrapper::~ElementWrapper()
67{
68}
69
70ElementWrapper& ElementWrapper::operator=(const ElementWrapper& other)
71{
72 d = other.d;
73 return *this;
74}
75
76bool ElementWrapper::operator==(const ElementWrapper& other) const
77{
78 return d->element == other.d->element;
79}
80
81bool ElementWrapper::isNull() const
82{
83 return d->element.isNull();
84}
85
86const QDomElement& ElementWrapper::element() const
87{
88 return d->element;
89}
90
91QString ElementWrapper::xmlBase() const
92{
93 if (!d->xmlBaseParsed) // xmlBase not computed yet
94 {
95 QDomElement current = d->element;
96
97 /*
98 An atom feed can contain nested xml:base elements, like this:
99
100 <feed xml:base="http://example.com/foo.atom">
101 <entry xml:base="subdir/">
102 <link href="foo.html"/>
103 </entry>
104 </feed>
105
106 To compute xml:base we explore the tree all the way up to the top.
107 `bases` stores all the xml:base values from the deepest element up to
108 the root element.
109 */
110 QStringList bases;
111 while (!current.isNull())
112 {
113 if (current.hasAttributeNS(xmlNamespace(), QLatin1String("base")))
114 {
115 bases << current.attributeNS(xmlNamespace(), QLatin1String("base"));
116 }
117
118 QDomNode parent = current.parentNode();
119
120 if (!parent.isNull() && parent.isElement())
121 current = parent.toElement();
122 else
123 current = QDomElement();
124 }
125 while (!bases.isEmpty())
126 {
127 KUrl u(d->xmlBase, bases.takeLast());
128 d->xmlBase = u.url();
129 }
130
131 d->xmlBaseParsed = true;
132 }
133
134 return d->xmlBase;
135}
136
137QString ElementWrapper::completeURI(const QString& uri) const
138{
139 KUrl u(xmlBase(), uri);
140
141 if (u.isValid())
142 return u.url();
143
144 return uri;
145}
146
147QString ElementWrapper::xmlLang() const
148{
149 if (!d->xmlLangParsed) // xmlLang not computed yet
150 {
151 QDomElement current = d->element;
152
153 while (!current.isNull())
154 {
155 if (current.hasAttributeNS(xmlNamespace(), QLatin1String("lang")))
156 {
157 d->xmlLang = current.attributeNS(xmlNamespace(), QLatin1String("lang"));
158 return d->xmlLang;
159 }
160
161 QDomNode parent = current.parentNode();
162
163 if (!parent.isNull() && parent.isElement())
164 current = parent.toElement();
165 else
166 current = QDomElement();
167 }
168 d->xmlLangParsed = true;
169 }
170 return d->xmlLang;
171}
172
173QString ElementWrapper::extractElementText(const QString& tagName) const
174{
175 QDomElement el = d->element.namedItem(tagName).toElement();
176 return el.isNull() ? QString() : el.text().trimmed();
177}
178
179QString ElementWrapper::extractElementTextNS(const QString& namespaceURI, const QString& localName) const
180{
181 QDomElement el = firstElementByTagNameNS(namespaceURI, localName);
182 return el.isNull() ? QString() : el.text().trimmed();
183}
184
185QString ElementWrapper::childNodesAsXML(const QDomElement& parent)
186{
187 ElementWrapper wrapper(parent);
188
189 if (parent.isNull())
190 return QString();
191
192 QDomNodeList list = parent.childNodes();
193
194 QString str;
195 QTextStream ts( &str, QIODevice::WriteOnly );
196
197 // if there is a xml:base in our scope, first set it for
198 // each child element so the xml:base shows up in the
199 // serialization
200 QString base = wrapper.xmlBase();
201
202
203 for (int i = 0; i < list.count(); ++i)
204 {
205 QDomNode it = list.item(i);
206 if (!base.isEmpty() && it.isElement()
207 && !it.toElement().hasAttributeNS(xmlNamespace(), QLatin1String("base")))
208 {
209 it.toElement().setAttributeNS(xmlNamespace(), QLatin1String("base"), base);
210 }
211
212 ts << it;
213 }
214 return str.trimmed();
215}
216
217QString ElementWrapper::childNodesAsXML() const
218{
219 return childNodesAsXML(d->element);
220}
221
222QList<QDomElement> ElementWrapper::elementsByTagName(const QString& tagName) const
223{
224 QList<QDomElement> elements;
225 for (QDomNode n = d->element.firstChild(); !n.isNull(); n = n.nextSibling())
226 {
227 if (n.isElement())
228 {
229 QDomElement e = n.toElement();
230 if (e.tagName() == tagName)
231 elements.append(e);
232 }
233 }
234 return elements;
235}
236
237QDomElement ElementWrapper::firstElementByTagNameNS(const QString& nsURI, const QString& localName) const
238{
239 if (isNull())
240 return QDomElement();
241
242 for (QDomNode n = d->element.firstChild(); !n.isNull(); n = n.nextSibling())
243 {
244 if (n.isElement())
245 {
246 QDomElement e = n.toElement();
247 if (e.localName() == localName && e.namespaceURI() == nsURI)
248 return e;
249 }
250 }
251
252 return QDomElement();
253}
254
255
256QList<QDomElement> ElementWrapper::elementsByTagNameNS(const QString& nsURI, const QString& localName) const
257{
258 if (isNull())
259 return QList<QDomElement>();
260
261 QList<QDomElement> elements;
262 for (QDomNode n = d->element.firstChild(); !n.isNull(); n = n.nextSibling())
263 {
264 if (n.isElement())
265 {
266 QDomElement e = n.toElement();
267 if (e.localName() == localName && e.namespaceURI() == nsURI)
268 elements.append(e);
269 }
270 }
271 return elements;
272}
273
274QString ElementWrapper::text() const
275{
276 return d->element.text();
277}
278
279QString ElementWrapper::attribute(const QString& name, const QString& defValue) const
280{
281 return d->element.attribute(name, defValue);
282}
283
284QString ElementWrapper::attributeNS(const QString& nsURI, const QString& localName, const QString& defValue) const
285{
286 return d->element.attributeNS(nsURI, localName, defValue);
287}
288
289bool ElementWrapper::hasAttribute(const QString& name) const
290{
291 return d->element.hasAttribute(name);
292}
293
294bool ElementWrapper::hasAttributeNS(const QString& nsURI, const QString& localName) const
295{
296 return d->element.hasAttributeNS(nsURI, localName);
297}
298
299} // namespace Syndication
300