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