| 88 | } |
| 89 | |
| 90 | void XMLCALL OpdsParser::startElement(void* userData, const XML_Char* name, const XML_Char** atts) { |
| 91 | auto* self = static_cast<OpdsParser*>(userData); |
| 92 | |
| 93 | if (strcmp(name, "link") == 0 || strstr(name, ":link") != nullptr) { |
| 94 | const char* href = findAttribute(atts, "href"); |
| 95 | if (href) { |
| 96 | const char* rel = findAttribute(atts, "rel"); |
| 97 | const char* type = findAttribute(atts, "type"); |
| 98 | |
| 99 | if (rel && strcmp(rel, "search") == 0) { |
| 100 | std::string sHref(href); |
| 101 | if (sHref.find("{searchTerms}") != std::string::npos) { |
| 102 | self->searchTemplate = sHref; |
| 103 | } |
| 104 | } else if (rel && strcmp(rel, "next") == 0 && !self->inEntry) { |
| 105 | self->nextPageUrl = href; |
| 106 | } else if (rel && strcmp(rel, "previous") == 0 && !self->inEntry) { |
| 107 | self->prevPageUrl = href; |
| 108 | } |
| 109 | |
| 110 | if (self->inEntry) { |
| 111 | if (rel && type && strstr(rel, "opds-spec.org/acquisition") != nullptr && |
| 112 | strcmp(type, "application/epub+zip") == 0) { |
| 113 | // Prefer plain EPUB links over derived formats when multiple |
| 114 | // acquisition links are present for one entry. |
| 115 | const bool isPlainEpub = strstr(href, ".epub") != nullptr || strstr(href, "/epub/") != nullptr; |
| 116 | const bool alreadyHasPlainEpub = self->currentEntry.type == OpdsEntryType::BOOK && |
| 117 | (self->currentEntry.href.find(".epub") != std::string::npos || |
| 118 | self->currentEntry.href.find("/epub/") != std::string::npos); |
| 119 | if (self->currentEntry.type != OpdsEntryType::BOOK || (isPlainEpub && !alreadyHasPlainEpub)) { |
| 120 | self->currentEntry.type = OpdsEntryType::BOOK; |
| 121 | self->currentEntry.href = href; |
| 122 | } |
| 123 | } else if (type && strstr(type, "application/atom+xml") != nullptr) { |
| 124 | if (self->currentEntry.type != OpdsEntryType::BOOK) { |
| 125 | self->currentEntry.type = OpdsEntryType::NAVIGATION; |
| 126 | self->currentEntry.href = href; |
| 127 | } |
| 128 | } |
| 129 | } |
| 130 | } |
| 131 | } |
| 132 | |
| 133 | if (strcmp(name, "entry") == 0 || strstr(name, ":entry") != nullptr) { |
| 134 | self->inEntry = true; |
| 135 | self->currentEntry = OpdsEntry{}; |
| 136 | return; |
| 137 | } |
| 138 | |
| 139 | if (!self->inEntry) return; |
| 140 | |
| 141 | if (strcmp(name, "title") == 0 || strstr(name, ":title") != nullptr) { |
| 142 | self->inTitle = true; |
| 143 | self->currentText.clear(); |
| 144 | } else if (strcmp(name, "author") == 0 || strstr(name, ":author") != nullptr) { |
| 145 | self->inAuthor = true; |
| 146 | } else if (self->inAuthor && (strcmp(name, "name") == 0 || strstr(name, ":name") != nullptr)) { |
| 147 | self->inAuthorName = true; |