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