Syndication

atom/parser.cpp
1 /*
2  This file is part of the syndication library
3  SPDX-FileCopyrightText: 2006 Frank Osterfeld <[email protected]>
4 
5  SPDX-License-Identifier: LGPL-2.0-or-later
6 */
7 
8 #include "parser.h"
9 #include "constants.h"
10 #include "content.h"
11 #include "document.h"
12 
13 #include <documentsource.h>
14 
15 #include <QDomAttr>
16 #include <QDomDocument>
17 #include <QDomElement>
18 #include <QDomNamedNodeMap>
19 #include <QDomNode>
20 #include <QDomNodeList>
21 
22 #include <QHash>
23 #include <QString>
24 
25 namespace Syndication
26 {
27 namespace Atom
28 {
29 class SYNDICATION_NO_EXPORT Parser::ParserPrivate
30 {
31 public:
32  static QDomDocument convertAtom0_3(const QDomDocument &document);
33  static QDomNode convertNode(QDomDocument &doc, const QDomNode &node, const QHash<QString, QString> &nameMapper);
34 };
35 
36 bool Parser::accept(const Syndication::DocumentSource &source) const
37 {
38  QDomElement root = source.asDomDocument().documentElement();
39  return !root.isNull() && (root.namespaceURI() == atom1Namespace() || root.namespaceURI() == atom0_3Namespace());
40 }
41 
42 Syndication::SpecificDocumentPtr Parser::parse(const Syndication::DocumentSource &source) const
43 {
44  QDomDocument doc = source.asDomDocument();
45 
46  if (doc.isNull()) {
47  // if this is not atom, return an invalid feed document
48  return FeedDocumentPtr(new FeedDocument());
49  }
50 
51  QDomElement feed = doc.namedItem(QStringLiteral("feed")).toElement();
52 
53  bool feedValid = !feed.isNull();
54 
55  if (feedValid //
56  && feed.attribute(QStringLiteral("version")) == QLatin1String("0.3")) {
57  doc = ParserPrivate::convertAtom0_3(doc);
58  feed = doc.namedItem(QStringLiteral("feed")).toElement();
59  }
60 
61  feedValid = !feed.isNull() && feed.namespaceURI() == atom1Namespace();
62 
63  if (feedValid) {
64  return FeedDocumentPtr(new FeedDocument(feed));
65  }
66 
67  QDomElement entry = doc.namedItem(QStringLiteral("entry")).toElement();
68  bool entryValid = !entry.isNull() && entry.namespaceURI() == atom1Namespace();
69 
70  if (entryValid) {
71  return EntryDocumentPtr(new EntryDocument(feed));
72  }
73 
74  // if this is not atom, return an invalid feed document
75  return FeedDocumentPtr(new FeedDocument());
76 }
77 
78 QString Parser::format() const
79 {
80  return QStringLiteral("atom");
81 }
82 
83 QDomNode Parser::ParserPrivate::convertNode(QDomDocument &doc, const QDomNode &node, const QHash<QString, QString> &nameMapper)
84 {
85  if (!node.isElement()) {
86  return node.cloneNode(true);
87  }
88 
89  bool isAtom03Element = node.namespaceURI() == atom0_3Namespace();
90  QDomElement oldEl = node.toElement();
91 
92  // use new namespace
93  QString newNS = isAtom03Element ? atom1Namespace() : node.namespaceURI();
94 
95  QString newName = node.localName();
96 
97  // rename tags that are listed in the nameMapper
98  if (isAtom03Element && nameMapper.contains(node.localName())) {
99  newName = nameMapper[node.localName()];
100  }
101 
102  QDomElement newEl = doc.createElementNS(newNS, newName);
103 
104  QDomNamedNodeMap attributes = oldEl.attributes();
105 
106  // copy over attributes
107  const int numberOfAttributes(attributes.count());
108  for (int i = 0; i < numberOfAttributes; ++i) {
109  const QDomAttr attr = attributes.item(i).toAttr();
110  if (attr.namespaceURI().isEmpty()) {
111  newEl.setAttribute(attr.name(), attr.value());
112  } else {
113  newEl.setAttributeNS(attr.namespaceURI(), attr.name(), attr.value());
114  }
115  }
116 
117  /* clang-format off */
118  bool isTextConstruct = newNS == atom1Namespace()
119  && (newName == QLatin1String("title")
120  || newName == QLatin1String("rights")
121  || newName == QLatin1String("subtitle")
122  || newName == QLatin1String("summary"));
123  /* clang-format on */
124 
125  // for atom text constructs, map to new type schema (which only allows text, type, xhtml)
126 
127  if (isTextConstruct) {
128  QString oldType = newEl.attribute(QStringLiteral("type"), QStringLiteral("text/plain"));
129  QString newType;
130 
131  Content::Format format = Content::mapTypeToFormat(oldType);
132  switch (format) {
133  case Content::XML:
134  newType = QStringLiteral("xhtml");
135  break;
136  case Content::EscapedHTML:
137  newType = QStringLiteral("html");
138  break;
139  case Content::PlainText:
140  case Content::Binary:
141  default:
142  newType = QStringLiteral("text");
143  }
144 
145  newEl.setAttribute(QStringLiteral("type"), newType);
146  } else {
147  // for generator, rename the "url" attribute to "uri"
148 
149  bool isGenerator = newNS == atom1Namespace() && newName == QLatin1String("generator");
150  if (isGenerator && newEl.hasAttribute(QStringLiteral("url"))) {
151  newEl.setAttribute(QStringLiteral("uri"), newEl.attribute(QStringLiteral("url")));
152  }
153  }
154 
155  // process child nodes recursively and append them
156  QDomNodeList children = node.childNodes();
157  for (int i = 0; i < children.count(); ++i) {
158  newEl.appendChild(convertNode(doc, children.item(i), nameMapper));
159  }
160 
161  return newEl;
162 }
163 
164 QDomDocument Parser::ParserPrivate::convertAtom0_3(const QDomDocument &doc03)
165 {
166  QDomDocument doc = doc03.cloneNode(false).toDocument();
167 
168  // these are the tags that were renamed in 1.0
169  QHash<QString, QString> nameMapper;
170  nameMapper.insert(QStringLiteral("issued"), QStringLiteral("published"));
171  nameMapper.insert(QStringLiteral("modified"), QStringLiteral("updated"));
172  nameMapper.insert(QStringLiteral("url"), QStringLiteral("uri"));
173  nameMapper.insert(QStringLiteral("copyright"), QStringLiteral("rights"));
174  nameMapper.insert(QStringLiteral("tagline"), QStringLiteral("subtitle"));
175 
176  const QDomNodeList children = doc03.childNodes();
177 
178  for (int i = 0; i < children.count(); ++i) {
179  doc.appendChild(convertNode(doc, children.item(i), nameMapper));
180  }
181 
182  return doc;
183 }
184 
185 Parser::Parser()
186  : d(nullptr)
187 {
188  Q_UNUSED(d) // silence -Wunused-private-field
189 }
190 
191 Parser::~Parser()
192 {
193 }
194 Parser::Parser(const Parser &other)
195  : AbstractParser(other)
196  , d(nullptr)
197 {
198 }
199 Parser &Parser::operator=(const Parser & /*other*/)
200 {
201  return *this;
202 }
203 
204 } // namespace Atom
205 } // namespace Syndication
int count() const const
QDomElement toElement() const const
QDomElement createElementNS(const QString &nsURI, const QString &qName)
bool isNull() const const
QString namespaceURI() const const
QHash::iterator insert(const Key &key, const T &value)
QDomAttr toAttr() const const
void setAttribute(const QString &name, const QString &value)
QDomNode cloneNode(bool deep) const const
QDomNode item(int index) const const
QString localName() const const
bool isEmpty() const const
QDomDocument toDocument() const const
QDomNamedNodeMap attributes() const const
QDomNodeList childNodes() const const
QString atom1Namespace()
namespace used by Atom 1.0 elements
QDomNode item(int index) const const
QString value() const const
int count() const const
QString atom0_3Namespace()
namespace used by Atom 0.3 elements
void setAttributeNS(const QString nsURI, const QString &qName, const QString &value)
QString name() const const
bool hasAttribute(const QString &name) const const
QDomNode appendChild(const QDomNode &newChild)
QString attribute(const QString &name, const QString &defValue) const const
bool contains(const Key &key) const const
bool isElement() const const
QDomNode namedItem(const QString &name) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Mon May 8 2023 03:57:11 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.