Line data Source code
1 : #include "SitemapParser.h"
2 :
3 : #include <QXmlStreamReader>
4 :
5 : #include "FeedDiscoveryLogging.h"
6 :
7 12 : SitemapParser::SitemapParser()
8 12 : : _hasNewsEntries(false)
9 : {
10 12 : }
11 :
12 12 : SitemapParser::SitemapType SitemapParser::parse(const QString& xml)
13 : {
14 12 : _entries.clear();
15 12 : _subSitemaps.clear();
16 12 : _hasNewsEntries = false;
17 :
18 12 : QXmlStreamReader reader(xml);
19 24 : return parseImpl(reader);
20 12 : }
21 :
22 0 : SitemapParser::SitemapType SitemapParser::parse(const QByteArray& data)
23 : {
24 0 : _entries.clear();
25 0 : _subSitemaps.clear();
26 0 : _hasNewsEntries = false;
27 :
28 0 : QXmlStreamReader reader(data);
29 0 : return parseImpl(reader);
30 0 : }
31 :
32 12 : SitemapParser::SitemapType SitemapParser::parseImpl(QXmlStreamReader& reader)
33 : {
34 : // Find the root element.
35 25 : while (!reader.atEnd()) {
36 23 : reader.readNext();
37 23 : if (reader.isStartElement()) {
38 10 : QString root = reader.name().toString().toLower();
39 10 : if (root == "urlset") {
40 7 : parseUrlSet(reader);
41 7 : return UrlSet;
42 3 : } else if (root == "sitemapindex") {
43 1 : parseSitemapIndex(reader);
44 1 : return SitemapIndex;
45 : } else {
46 4 : qCDebug(logFeedDiscovery) << "SitemapParser: unexpected root element:" << root;
47 2 : return Invalid;
48 : }
49 10 : }
50 : }
51 :
52 4 : qCDebug(logFeedDiscovery) << "SitemapParser: no root element found";
53 2 : return Invalid;
54 : }
55 :
56 7 : void SitemapParser::parseUrlSet(QXmlStreamReader& xml)
57 : {
58 : // We're inside <urlset>. Look for <url> children.
59 69 : while (!xml.atEnd()) {
60 55 : xml.readNext();
61 :
62 55 : if (xml.isStartElement() && xml.name().toString().toLower() == "url") {
63 17 : SitemapEntry entry;
64 :
65 : // Parse children of <url>. This includes both standard sitemap
66 : // elements (<loc>, <lastmod>) and Google News extension elements
67 : // nested inside <news:news>.
68 17 : int depth = 1; // Track nesting depth within <url>
69 339 : while (!xml.atEnd() && depth > 0) {
70 322 : xml.readNext();
71 :
72 322 : if (xml.isEndElement()) {
73 50 : if (xml.name().toString().toLower() == "url") {
74 17 : depth = 0;
75 : }
76 : }
77 :
78 322 : if (xml.isStartElement()) {
79 : // The namespace-qualified local names come through as just
80 : // the local part (e.g. "title" for news:title), so we use
81 : // the qualified name to distinguish.
82 111 : QString localName = xml.name().toString().toLower();
83 111 : QString qualifiedName = xml.qualifiedName().toString().toLower();
84 :
85 111 : if (localName == "loc" && !qualifiedName.contains(':')) {
86 17 : entry.url = QUrl(xml.readElementText().trimmed());
87 94 : } else if (localName == "lastmod" && !qualifiedName.contains(':')) {
88 24 : entry.lastmod = QDateTime::fromString(
89 36 : xml.readElementText().trimmed(), Qt::ISODate);
90 0 : } else if (qualifiedName == "news:title"
91 82 : || (localName == "title"
92 82 : && xml.namespaceUri().toString().contains("sitemap-news"))) {
93 12 : entry.newsTitle = xml.readElementText().trimmed();
94 0 : } else if (qualifiedName == "news:publication_date"
95 70 : || (localName == "publication_date"
96 70 : && xml.namespaceUri().toString().contains("sitemap-news"))) {
97 24 : entry.publicationDate = QDateTime::fromString(
98 36 : xml.readElementText().trimmed(), Qt::ISODate);
99 0 : } else if (qualifiedName == "news:language"
100 58 : || (localName == "language"
101 58 : && xml.namespaceUri().toString().contains("sitemap-news"))) {
102 12 : entry.language = xml.readElementText().trimmed().toLower();
103 0 : } else if (qualifiedName == "news:name"
104 46 : || (localName == "name"
105 46 : && xml.namespaceUri().toString().contains("sitemap-news"))) {
106 12 : entry.publicationName = xml.readElementText().trimmed();
107 0 : } else if (qualifiedName == "image:loc"
108 34 : || (localName == "loc"
109 34 : && xml.namespaceUri().toString().contains("sitemap-image"))) {
110 1 : entry.imageUrl = QUrl(xml.readElementText().trimmed());
111 : }
112 111 : }
113 : }
114 :
115 17 : if (entry.url.isValid()) {
116 17 : if (!entry.newsTitle.isEmpty()) {
117 12 : _hasNewsEntries = true;
118 : }
119 17 : _entries.append(entry);
120 : }
121 17 : }
122 : }
123 :
124 14 : qCDebug(logFeedDiscovery) << "SitemapParser: parsed" << _entries.size() << "URL entries"
125 7 : << (_hasNewsEntries ? "(with news extensions)" : "(no news extensions)");
126 7 : }
127 :
128 1 : void SitemapParser::parseSitemapIndex(QXmlStreamReader& xml)
129 : {
130 : // We're inside <sitemapindex>. Look for <sitemap> children.
131 11 : while (!xml.atEnd()) {
132 9 : xml.readNext();
133 :
134 9 : if (xml.isStartElement() && xml.name().toString().toLower() == "sitemap") {
135 3 : SubSitemap sub;
136 :
137 : // Parse children of <sitemap>.
138 15 : while (!xml.atEnd()) {
139 12 : xml.readNext();
140 :
141 12 : if (xml.isEndElement() && xml.name().toString().toLower() == "sitemap") {
142 3 : break;
143 : }
144 :
145 9 : if (xml.isStartElement()) {
146 3 : QString tag = xml.name().toString().toLower();
147 3 : if (tag == "loc") {
148 3 : sub.url = QUrl(xml.readElementText().trimmed());
149 0 : } else if (tag == "lastmod") {
150 0 : sub.lastmod = QDateTime::fromString(
151 0 : xml.readElementText().trimmed(), Qt::ISODate);
152 : }
153 3 : }
154 : }
155 :
156 3 : if (sub.url.isValid()) {
157 3 : _subSitemaps.append(sub);
158 : }
159 3 : }
160 : }
161 :
162 2 : qCDebug(logFeedDiscovery) << "SitemapParser: parsed" << _subSitemaps.size() << "sub-sitemaps";
163 1 : }
|