1 /*
2  * Copyright (C) 2010-2015, Roberto Guido <rguido@src.gnome.org>
3  *                          Michele Tameni <michele@amdplanet.it>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 3 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  * Boston, MA  02110-1301, USA.
19  */
20 
21 /*
22  * Original code is from Liferea:
23  *
24  * opml_source.c  OPML Planet/Blogroll feed list source
25  *
26  * Copyright (C) 2006-2010 Lars Lindner <lars.lindner@gmail.com>
27  */
28 
29 #include "feeds-group-handler.h"
30 #include "feeds-opml-group-handler.h"
31 #include "utils.h"
32 #include "feed-channel.h"
33 
34 #define FEEDS_OPML_GROUP_HANDLER_GET_PRIVATE(o)	(G_TYPE_INSTANCE_GET_PRIVATE ((o), FEEDS_OPML_GROUP_HANDLER_TYPE, FeedsOpmlGroupHandlerPrivate))
35 
36 struct FeedsOpmlGroupHandlerPrivate {
37 	int	rfu;
38 };
39 
40 static void grss_feeds_group_handler_interface_init (GrssFeedsGroupHandlerInterface *iface);
41 G_DEFINE_TYPE_WITH_CODE (FeedsOpmlGroupHandler, feeds_opml_group_handler, G_TYPE_OBJECT,
42                          G_IMPLEMENT_INTERFACE (FEEDS_GROUP_HANDLER_TYPE,
43                                                 grss_feeds_group_handler_interface_init));
44 
45 static void
feeds_opml_group_handler_finalize(GObject * object)46 feeds_opml_group_handler_finalize (GObject *object)
47 {
48 	G_OBJECT_CLASS (feeds_opml_group_handler_parent_class)->finalize (object);
49 }
50 
51 static const gchar*
feeds_opml_group_handler_get_name(GrssFeedsGroupHandler * self)52 feeds_opml_group_handler_get_name (GrssFeedsGroupHandler *self)
53 {
54 	return "OPML";
55 }
56 
57 static gboolean
feeds_opml_group_handler_check_format(GrssFeedsGroupHandler * self,xmlDocPtr doc,xmlNodePtr cur)58 feeds_opml_group_handler_check_format (GrssFeedsGroupHandler *self, xmlDocPtr doc, xmlNodePtr cur)
59 {
60 	if (!xmlStrcmp (cur->name, BAD_CAST"opml"))
61 		return TRUE;
62 	else
63 		return FALSE;
64 }
65 
66 static xmlChar*
get_source_url(xmlNodePtr cur)67 get_source_url (xmlNodePtr cur)
68 {
69 	xmlChar *tmp;
70 
71 	tmp = xmlGetProp (cur, BAD_CAST "xmlUrl");
72 	if (!tmp)
73 		tmp = xmlGetProp (cur, BAD_CAST "xmlurl");	/* e.g. for AmphetaDesk */
74 	if (!tmp)
75 		tmp = xmlGetProp (cur, BAD_CAST"xmlURL");	/* e.g. for LiveJournal */
76 
77 	return tmp;
78 }
79 
80 static GrssFeedChannel*
import_parse_outline(xmlNodePtr cur)81 import_parse_outline (xmlNodePtr cur)
82 {
83 	xmlChar *tmp;
84 	GrssFeedChannel *channel;
85 
86 	channel = grss_feed_channel_new ();
87 
88 	tmp = xmlGetProp (cur, BAD_CAST"title");
89 	if (!tmp || !xmlStrcmp (tmp, BAD_CAST"")) {
90 		if (tmp)
91 			xmlFree (tmp);
92 		tmp = xmlGetProp (cur, BAD_CAST"text");
93 	}
94 
95 	if (tmp) {
96 		grss_feed_channel_set_title (channel, (gchar*) tmp);
97 		xmlFree (tmp);
98 	}
99 
100 	tmp = get_source_url (cur);
101 
102 	if (tmp) {
103 		grss_feed_channel_set_source (channel, (gchar*) tmp);
104 		xmlFree (tmp);
105 
106 		tmp = xmlGetProp (cur, BAD_CAST"htmlUrl");
107 		if (tmp && xmlStrcmp (tmp, BAD_CAST""))
108 			grss_feed_channel_set_homepage (channel, (gchar*) tmp);
109 		xmlFree (tmp);
110 	}
111 
112 	return channel;
113 }
114 
115 static GList*
import_parse_body(xmlNodePtr n)116 import_parse_body (xmlNodePtr n)
117 {
118 	xmlChar *type;
119 	xmlChar *tmp;
120 	GList *items;
121 	GList *subitems;
122 	GrssFeedChannel *outline;
123 	xmlNodePtr cur;
124 
125 	cur = n->xmlChildrenNode;
126 	items = NULL;
127 
128 	while (cur) {
129 		if (!xmlStrcmp (cur->name, BAD_CAST"outline")) {
130 			outline = NULL;
131 			subitems = NULL;
132 			type = xmlGetProp (cur, BAD_CAST"type");
133 
134 			if (type) {
135 				if (xmlStrcasecmp (type, BAD_CAST"rss") == 0 || xmlStrcasecmp (type, BAD_CAST"atom") == 0)
136 					outline = import_parse_outline (cur);
137 				else if (xmlStrcasecmp (type, BAD_CAST"folder") == 0)
138 					subitems = import_parse_body (cur);
139 
140 				xmlFree (type);
141 			}
142 			else {
143 				/* if we didn't find a type attribute we use heuristics */
144 
145 				tmp = get_source_url (cur);
146 
147 				if (tmp) {
148 					outline = import_parse_outline (cur);
149 					xmlFree (tmp);
150 				}
151 				else {
152 					subitems = import_parse_body (cur);
153 				}
154 			}
155 
156 			if (outline != NULL)
157 				items = g_list_prepend (items, outline);
158 			else if (subitems != NULL)
159 				items = g_list_concat (items, subitems);
160 		}
161 
162 		cur = cur->next;
163 	}
164 
165 	return items;
166 }
167 
168 static GList*
import_parse_OPML(xmlNodePtr n)169 import_parse_OPML (xmlNodePtr n)
170 {
171 	GList *items;
172 	xmlNodePtr cur;
173 
174 	cur = n->xmlChildrenNode;
175 	items = NULL;
176 
177 	while (cur) {
178 		if (!xmlStrcmp (cur->name, BAD_CAST"body")) {
179 			items = import_parse_body (cur);
180 			break;
181 		}
182 
183 		cur = cur->next;
184 	}
185 
186 	return items;
187 }
188 
189 static GList*
feeds_opml_group_handler_parse(GrssFeedsGroupHandler * self,xmlDocPtr doc,GError ** error)190 feeds_opml_group_handler_parse (GrssFeedsGroupHandler *self, xmlDocPtr doc, GError **error)
191 {
192 	xmlNodePtr cur;
193 	GList *items;
194 
195 	items = NULL;
196 	cur = xmlDocGetRootElement (doc);
197 
198 	while (cur) {
199 		if (!xmlIsBlankNode (cur))
200 			if (!xmlStrcmp (cur->name, BAD_CAST"opml")) {
201 				items = import_parse_OPML (cur);
202 				break;
203 			}
204 
205 		cur = cur->next;
206 	}
207 
208 	if (items != NULL)
209 		items = g_list_reverse (items);
210 	return items;
211 }
212 
213 static gchar*
feeds_opml_group_handler_dump(GrssFeedsGroupHandler * self,GList * channels,GError ** error)214 feeds_opml_group_handler_dump (GrssFeedsGroupHandler *self, GList *channels, GError **error)
215 {
216 	int size;
217 	xmlChar *ret;
218 	xmlDocPtr doc;
219 	xmlNodePtr cur;
220 	xmlNodePtr opmlNode;
221 	xmlNodePtr childNode;
222 	GList *iter;
223 	GrssFeedChannel *channel;
224 
225 	doc = xmlNewDoc (BAD_CAST"1.0");
226 
227 	opmlNode = xmlNewDocNode (doc, NULL, BAD_CAST"opml", NULL);
228 	xmlNewProp (opmlNode, BAD_CAST"version", BAD_CAST"1.0");
229 	xmlNewChild (opmlNode, NULL, BAD_CAST"head", NULL);
230 
231 	cur = xmlNewChild (opmlNode, NULL, BAD_CAST"body", NULL);
232 	if (cur) {
233 		for (iter = channels; iter; iter = g_list_next (iter)) {
234 			channel = (GrssFeedChannel*) iter->data;
235 			childNode = xmlNewChild (cur, NULL, BAD_CAST"outline", NULL);
236 			xmlNewProp (childNode, BAD_CAST"text", BAD_CAST grss_feed_channel_get_title (channel));
237 			xmlNewProp (childNode, BAD_CAST"type", BAD_CAST"rss");
238 			xmlNewProp (childNode, BAD_CAST"xmlUrl", BAD_CAST grss_feed_channel_get_source (channel));
239 		}
240 	}
241 
242 	xmlDocSetRootElement (doc, opmlNode);
243 	xmlDocDumpFormatMemoryEnc (doc, &ret, &size, "utf-8", 1);
244 	xmlFreeDoc (doc);
245 
246 	return (gchar*) ret;
247 }
248 
249 static void
grss_feeds_group_handler_interface_init(GrssFeedsGroupHandlerInterface * iface)250 grss_feeds_group_handler_interface_init (GrssFeedsGroupHandlerInterface *iface)
251 {
252 	iface->get_name = feeds_opml_group_handler_get_name;
253 	iface->check_format = feeds_opml_group_handler_check_format;
254 	iface->parse = feeds_opml_group_handler_parse;
255 	iface->dump = feeds_opml_group_handler_dump;
256 }
257 
258 static void
feeds_opml_group_handler_class_init(FeedsOpmlGroupHandlerClass * klass)259 feeds_opml_group_handler_class_init (FeedsOpmlGroupHandlerClass *klass)
260 {
261 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
262 
263 	g_type_class_add_private (object_class, sizeof (FeedsOpmlGroupHandlerPrivate));
264 	object_class->finalize = feeds_opml_group_handler_finalize;
265 }
266 
267 static void
feeds_opml_group_handler_init(FeedsOpmlGroupHandler * object)268 feeds_opml_group_handler_init (FeedsOpmlGroupHandler *object)
269 {
270 	object->priv = FEEDS_OPML_GROUP_HANDLER_GET_PRIVATE (object);
271 }
272 
273 FeedsOpmlGroupHandler*
feeds_opml_group_handler_new()274 feeds_opml_group_handler_new ()
275 {
276 	FeedsOpmlGroupHandler *parser;
277 
278 	parser = g_object_new (FEEDS_OPML_GROUP_HANDLER_TYPE, NULL);
279 	return parser;
280 }
281