File indexing completed on 2024-12-08 09:46:40
0001 /* 0002 This file is part of the syndication library 0003 SPDX-FileCopyrightText: 2006 Frank Osterfeld <osterfeld@kde.org> 0004 0005 SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "elementwrapper.h" 0009 #include "constants.h" 0010 0011 #include <QUrl> 0012 0013 #include <QDomDocument> 0014 #include <QDomElement> 0015 #include <QIODevice> 0016 #include <QStringList> 0017 #include <QTextStream> 0018 0019 namespace Syndication 0020 { 0021 class SYNDICATION_NO_EXPORT ElementWrapper::ElementWrapperPrivate 0022 { 0023 public: 0024 QDomElement element; 0025 QDomDocument ownerDoc; 0026 mutable QString xmlBase; 0027 mutable bool xmlBaseParsed; 0028 mutable QString xmlLang; 0029 mutable bool xmlLangParsed; 0030 }; 0031 0032 ElementWrapper::ElementWrapper() 0033 : d(new ElementWrapperPrivate) 0034 { 0035 d->xmlBaseParsed = true; 0036 d->xmlLangParsed = true; 0037 } 0038 0039 ElementWrapper::ElementWrapper(const ElementWrapper &other) 0040 { 0041 *this = other; 0042 } 0043 0044 ElementWrapper::ElementWrapper(const QDomElement &element) 0045 : d(new ElementWrapperPrivate) 0046 { 0047 d->element = element; 0048 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) 0049 d->xmlBaseParsed = false; 0050 d->xmlLangParsed = false; 0051 } 0052 0053 ElementWrapper::~ElementWrapper() 0054 { 0055 } 0056 0057 ElementWrapper &ElementWrapper::operator=(const ElementWrapper &other) 0058 { 0059 d = other.d; 0060 return *this; 0061 } 0062 0063 bool ElementWrapper::operator==(const ElementWrapper &other) const 0064 { 0065 return d->element == other.d->element; 0066 } 0067 0068 bool ElementWrapper::isNull() const 0069 { 0070 return d->element.isNull(); 0071 } 0072 0073 const QDomElement &ElementWrapper::element() const 0074 { 0075 return d->element; 0076 } 0077 0078 QString ElementWrapper::xmlBase() const 0079 { 0080 if (!d->xmlBaseParsed) { // xmlBase not computed yet 0081 QDomElement current = d->element; 0082 0083 /* 0084 An atom feed can contain nested xml:base elements, like this: 0085 0086 <feed xml:base="http://example.com/foo.atom"> 0087 <entry xml:base="subdir/"> 0088 <link href="foo.html"/> 0089 </entry> 0090 </feed> 0091 0092 To compute xml:base we explore the tree all the way up to the top. 0093 `bases` stores all the xml:base values from the deepest element up to 0094 the root element. 0095 */ 0096 QStringList bases; 0097 while (!current.isNull()) { 0098 if (current.hasAttributeNS(xmlNamespace(), QStringLiteral("base"))) { 0099 bases << current.attributeNS(xmlNamespace(), QStringLiteral("base")); 0100 } 0101 0102 QDomNode parent = current.parentNode(); 0103 0104 if (!parent.isNull() && parent.isElement()) { 0105 current = parent.toElement(); 0106 } else { 0107 current = QDomElement(); 0108 } 0109 } 0110 while (!bases.isEmpty()) { 0111 QUrl u = QUrl(d->xmlBase).resolved(QUrl(bases.takeLast())); 0112 d->xmlBase = u.url(); 0113 } 0114 0115 d->xmlBaseParsed = true; 0116 } 0117 0118 return d->xmlBase; 0119 } 0120 0121 QString ElementWrapper::completeURI(const QString &uri) const 0122 { 0123 QUrl u = QUrl(xmlBase()).resolved(QUrl(uri)); 0124 0125 if (u.isValid()) { 0126 return u.url(); 0127 } 0128 0129 return uri; 0130 } 0131 0132 QString ElementWrapper::xmlLang() const 0133 { 0134 if (!d->xmlLangParsed) { // xmlLang not computed yet 0135 QDomElement current = d->element; 0136 0137 while (!current.isNull()) { 0138 if (current.hasAttributeNS(xmlNamespace(), QStringLiteral("lang"))) { 0139 d->xmlLang = current.attributeNS(xmlNamespace(), QStringLiteral("lang")); 0140 return d->xmlLang; 0141 } 0142 0143 QDomNode parent = current.parentNode(); 0144 0145 if (!parent.isNull() && parent.isElement()) { 0146 current = parent.toElement(); 0147 } else { 0148 current = QDomElement(); 0149 } 0150 } 0151 d->xmlLangParsed = true; 0152 } 0153 return d->xmlLang; 0154 } 0155 0156 QString ElementWrapper::extractElementText(const QString &tagName) const 0157 { 0158 const QDomElement el = d->element.namedItem(tagName).toElement(); 0159 return el.isNull() ? QString() : el.text().trimmed(); 0160 } 0161 0162 QString ElementWrapper::extractElementTextNS(const QString &namespaceURI, const QString &localName) const 0163 { 0164 const QDomElement el = firstElementByTagNameNS(namespaceURI, localName); 0165 return el.isNull() ? QString() : el.text().trimmed(); 0166 } 0167 0168 QString ElementWrapper::childNodesAsXML(const QDomElement &parent) 0169 { 0170 ElementWrapper wrapper(parent); 0171 0172 if (parent.isNull()) { 0173 return QString(); 0174 } 0175 0176 QDomNodeList list = parent.childNodes(); 0177 0178 QString str; 0179 QTextStream ts(&str, QIODevice::WriteOnly); 0180 0181 // if there is a xml:base in our scope, first set it for 0182 // each child element so the xml:base shows up in the 0183 // serialization 0184 QString base = wrapper.xmlBase(); 0185 0186 for (int i = 0; i < list.count(); ++i) { 0187 QDomNode it = list.item(i); 0188 if (!base.isEmpty() // 0189 && it.isElement() // 0190 && !it.toElement().hasAttributeNS(xmlNamespace(), QStringLiteral("base"))) { 0191 it.toElement().setAttributeNS(xmlNamespace(), QStringLiteral("base"), base); 0192 } 0193 0194 ts << it; 0195 } 0196 return str.trimmed(); 0197 } 0198 0199 QString ElementWrapper::childNodesAsXML() const 0200 { 0201 return childNodesAsXML(d->element); 0202 } 0203 0204 QList<QDomElement> ElementWrapper::elementsByTagName(const QString &tagName) const 0205 { 0206 QList<QDomElement> elements; 0207 for (QDomNode n = d->element.firstChild(); !n.isNull(); n = n.nextSibling()) { 0208 if (n.isElement()) { 0209 QDomElement e = n.toElement(); 0210 if (e.tagName() == tagName) { 0211 elements.append(e); 0212 } 0213 } 0214 } 0215 return elements; 0216 } 0217 0218 QDomElement ElementWrapper::firstElementByTagNameNS(const QString &nsURI, const QString &localName) const 0219 { 0220 if (isNull()) { 0221 return QDomElement(); 0222 } 0223 0224 for (QDomNode n = d->element.firstChild(); !n.isNull(); n = n.nextSibling()) { 0225 if (n.isElement()) { 0226 QDomElement e = n.toElement(); 0227 if (e.localName() == localName && e.namespaceURI() == nsURI) { 0228 return e; 0229 } 0230 } 0231 } 0232 0233 return QDomElement(); 0234 } 0235 0236 QList<QDomElement> ElementWrapper::elementsByTagNameNS(const QString &nsURI, const QString &localName) const 0237 { 0238 if (isNull()) { 0239 return QList<QDomElement>(); 0240 } 0241 0242 QList<QDomElement> elements; 0243 for (QDomNode n = d->element.firstChild(); !n.isNull(); n = n.nextSibling()) { 0244 if (n.isElement()) { 0245 QDomElement e = n.toElement(); 0246 if (e.localName() == localName && e.namespaceURI() == nsURI) { 0247 elements.append(e); 0248 } 0249 } 0250 } 0251 return elements; 0252 } 0253 0254 QString ElementWrapper::text() const 0255 { 0256 return d->element.text(); 0257 } 0258 0259 QString ElementWrapper::attribute(const QString &name, const QString &defValue) const 0260 { 0261 return d->element.attribute(name, defValue); 0262 } 0263 0264 QString ElementWrapper::attributeNS(const QString &nsURI, const QString &localName, const QString &defValue) const 0265 { 0266 return d->element.attributeNS(nsURI, localName, defValue); 0267 } 0268 0269 bool ElementWrapper::hasAttribute(const QString &name) const 0270 { 0271 return d->element.hasAttribute(name); 0272 } 0273 0274 bool ElementWrapper::hasAttributeNS(const QString &nsURI, const QString &localName) const 0275 { 0276 return d->element.hasAttributeNS(nsURI, localName); 0277 } 0278 0279 } // namespace Syndication